From c79bc82227ed846e2da346fd87c28624ce9928c2 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 01/99] Remove old files --- .gitignore | 11 - Cargo.toml | 43 +- benches/README.md | 10 - benches/common.rs | 53 - benches/hypernova.rs | 84 - benches/nova.rs | 75 - benches/protogalaxy.rs | 78 - cli/Cargo.toml | 17 - cli/README.md | 48 - cli/src/main.rs | 40 - cli/src/settings.rs | 113 -- examples/circom_full_flow.rs | 169 -- examples/external_inputs.rs | 209 --- examples/full_flow.rs | 154 -- examples/multi_inputs.rs | 153 -- examples/noir_full_flow.rs | 141 -- examples/noname_full_flow.rs | 162 -- examples/sha256.rs | 139 -- experimental-frontends/Cargo.toml | 34 - experimental-frontends/README.md | 17 - experimental-frontends/src/circom/mod.rs | 347 ---- .../test_folder/circuits/is_zero.circom | 14 - .../src/circom/test_folder/compile.sh | 4 - .../circom/test_folder/cubic_circuit.circom | 12 - .../test_folder/no_external_inputs.circom | 23 - .../test_folder/with_external_inputs.circom | 22 - experimental-frontends/src/circom/utils.rs | 167 -- experimental-frontends/src/lib.rs | 4 - experimental-frontends/src/noir/bridge.rs | 154 -- experimental-frontends/src/noir/mod.rs | 215 --- .../src/noir/test_folder/compile.sh | 7 - .../noir/test_folder/test_circuit/Nargo.toml | 8 - .../noir/test_folder/test_circuit/src/main.nr | 11 - .../src/noir/test_folder/test_mimc/Nargo.toml | 8 - .../noir/test_folder/test_mimc/src/main.nr | 4 - .../test_no_external_inputs/Nargo.toml | 8 - .../test_no_external_inputs/src/main.nr | 9 - experimental-frontends/src/noname/bridge.rs | 123 -- experimental-frontends/src/noname/mod.rs | 187 --- experimental-frontends/src/noname/utils.rs | 79 - experimental-frontends/src/utils.rs | 38 - folding-schemes/Cargo.toml | 81 - folding-schemes/src/arith/ccs/circuits.rs | 35 - folding-schemes/src/arith/ccs/mod.rs | 183 --- folding-schemes/src/arith/mod.rs | 178 --- folding-schemes/src/arith/r1cs/circuits.rs | 303 ---- folding-schemes/src/arith/r1cs/mod.rs | 294 ---- folding-schemes/src/commitment/ipa.rs | 718 --------- folding-schemes/src/commitment/kzg.rs | 311 ---- folding-schemes/src/commitment/mod.rs | 165 -- folding-schemes/src/commitment/pedersen.rs | 308 ---- folding-schemes/src/constants.rs | 5 - .../src/folding/circuits/cyclefold.rs | 943 ----------- .../src/folding/circuits/decider/mod.rs | 205 --- .../src/folding/circuits/decider/off_chain.rs | 306 ---- .../src/folding/circuits/decider/on_chain.rs | 315 ---- folding-schemes/src/folding/circuits/mod.rs | 20 - .../src/folding/circuits/nonnative/affine.rs | 241 --- .../src/folding/circuits/nonnative/mod.rs | 2 - .../src/folding/circuits/nonnative/uint.rs | 1036 ------------ .../src/folding/circuits/sum_check.rs | 259 --- folding-schemes/src/folding/circuits/utils.rs | 75 - folding-schemes/src/folding/hypernova/cccs.rs | 263 ---- .../src/folding/hypernova/circuits.rs | 1389 ----------------- .../src/folding/hypernova/decider_eth.rs | 441 ------ .../folding/hypernova/decider_eth_circuit.rs | 313 ---- .../src/folding/hypernova/lcccs.rs | 281 ---- folding-schemes/src/folding/hypernova/mod.rs | 1094 ------------- .../src/folding/hypernova/nimfs.rs | 732 --------- .../src/folding/hypernova/utils.rs | 333 ---- folding-schemes/src/folding/mod.rs | 155 -- folding-schemes/src/folding/nova/circuits.rs | 370 ----- folding-schemes/src/folding/nova/decider.rs | 429 ----- .../src/folding/nova/decider_circuits.rs | 226 --- .../src/folding/nova/decider_eth.rs | 499 ------ .../src/folding/nova/decider_eth_circuit.rs | 258 --- folding-schemes/src/folding/nova/mod.rs | 1167 -------------- folding-schemes/src/folding/nova/nifs/mod.rs | 322 ---- folding-schemes/src/folding/nova/nifs/mova.rs | 391 ----- folding-schemes/src/folding/nova/nifs/nova.rs | 290 ---- .../src/folding/nova/nifs/nova_circuits.rs | 232 --- folding-schemes/src/folding/nova/nifs/ova.rs | 301 ---- .../src/folding/nova/nifs/ova_circuits.rs | 215 --- .../src/folding/nova/nifs/pointvsline.rs | 357 ----- folding-schemes/src/folding/nova/traits.rs | 125 -- folding-schemes/src/folding/nova/zk.rs | 328 ---- .../src/folding/protogalaxy/circuits.rs | 451 ------ .../src/folding/protogalaxy/constants.rs | 4 - .../src/folding/protogalaxy/decider_eth.rs | 492 ------ .../protogalaxy/decider_eth_circuit.rs | 239 --- .../src/folding/protogalaxy/folding.rs | 573 ------- .../src/folding/protogalaxy/mod.rs | 1187 -------------- .../src/folding/protogalaxy/traits.rs | 178 --- .../src/folding/protogalaxy/utils.rs | 204 --- folding-schemes/src/folding/traits.rs | 174 --- folding-schemes/src/frontend/mod.rs | 79 - folding-schemes/src/frontend/utils.rs | 159 -- folding-schemes/src/lib.rs | 316 ---- folding-schemes/src/transcript/mod.rs | 139 -- folding-schemes/src/transcript/poseidon.rs | 294 ---- folding-schemes/src/utils/espresso/mod.rs | 3 - .../utils/espresso/multilinear_polynomial.rs | 200 --- .../src/utils/espresso/sum_check/mod.rs | 259 --- .../src/utils/espresso/sum_check/prover.rs | 226 --- .../src/utils/espresso/sum_check/structs.rs | 59 - .../src/utils/espresso/sum_check/verifier.rs | 302 ---- .../src/utils/espresso/virtual_polynomial.rs | 546 ------- folding-schemes/src/utils/gadgets.rs | 149 -- folding-schemes/src/utils/hypercube.rs | 77 - folding-schemes/src/utils/lagrange_poly.rs | 120 -- folding-schemes/src/utils/mle.rs | 189 --- folding-schemes/src/utils/mod.rs | 130 -- folding-schemes/src/utils/vec.rs | 252 --- rust-toolchain | 1 - solidity-verifiers/Cargo.toml | 58 - solidity-verifiers/README.md | 6 - solidity-verifiers/askama.toml | 3 - solidity-verifiers/src/calldata.rs | 88 -- solidity-verifiers/src/evm.rs | 163 -- solidity-verifiers/src/lib.rs | 10 - solidity-verifiers/src/utils/encoding.rs | 43 - solidity-verifiers/src/utils/eth.rs | 58 - solidity-verifiers/src/utils/mod.rs | 67 - solidity-verifiers/src/verifiers/g16.rs | 145 -- solidity-verifiers/src/verifiers/kzg.rs | 183 --- solidity-verifiers/src/verifiers/mod.rs | 122 -- .../src/verifiers/nova_cyclefold.rs | 433 ----- .../templates/groth16_verifier.askama.sol | 169 -- .../templates/header_template.askama.sol | 4 - .../templates/kzg10_verifier.askama.sol | 275 ---- .../nova_cyclefold_decider.askama.sol | 230 --- 131 files changed, 15 insertions(+), 28732 deletions(-) delete mode 100644 benches/README.md delete mode 100644 benches/common.rs delete mode 100644 benches/hypernova.rs delete mode 100644 benches/nova.rs delete mode 100644 benches/protogalaxy.rs delete mode 100644 cli/Cargo.toml delete mode 100644 cli/README.md delete mode 100644 cli/src/main.rs delete mode 100644 cli/src/settings.rs delete mode 100644 examples/circom_full_flow.rs delete mode 100644 examples/external_inputs.rs delete mode 100644 examples/full_flow.rs delete mode 100644 examples/multi_inputs.rs delete mode 100644 examples/noir_full_flow.rs delete mode 100644 examples/noname_full_flow.rs delete mode 100644 examples/sha256.rs delete mode 100644 experimental-frontends/Cargo.toml delete mode 100644 experimental-frontends/README.md delete mode 100644 experimental-frontends/src/circom/mod.rs delete mode 100644 experimental-frontends/src/circom/test_folder/circuits/is_zero.circom delete mode 100755 experimental-frontends/src/circom/test_folder/compile.sh delete mode 100644 experimental-frontends/src/circom/test_folder/cubic_circuit.circom delete mode 100644 experimental-frontends/src/circom/test_folder/no_external_inputs.circom delete mode 100644 experimental-frontends/src/circom/test_folder/with_external_inputs.circom delete mode 100644 experimental-frontends/src/circom/utils.rs delete mode 100644 experimental-frontends/src/lib.rs delete mode 100644 experimental-frontends/src/noir/bridge.rs delete mode 100644 experimental-frontends/src/noir/mod.rs delete mode 100755 experimental-frontends/src/noir/test_folder/compile.sh delete mode 100644 experimental-frontends/src/noir/test_folder/test_circuit/Nargo.toml delete mode 100644 experimental-frontends/src/noir/test_folder/test_circuit/src/main.nr delete mode 100644 experimental-frontends/src/noir/test_folder/test_mimc/Nargo.toml delete mode 100644 experimental-frontends/src/noir/test_folder/test_mimc/src/main.nr delete mode 100644 experimental-frontends/src/noir/test_folder/test_no_external_inputs/Nargo.toml delete mode 100644 experimental-frontends/src/noir/test_folder/test_no_external_inputs/src/main.nr delete mode 100644 experimental-frontends/src/noname/bridge.rs delete mode 100644 experimental-frontends/src/noname/mod.rs delete mode 100644 experimental-frontends/src/noname/utils.rs delete mode 100644 experimental-frontends/src/utils.rs delete mode 100644 folding-schemes/Cargo.toml delete mode 100644 folding-schemes/src/arith/ccs/circuits.rs delete mode 100644 folding-schemes/src/arith/ccs/mod.rs delete mode 100644 folding-schemes/src/arith/mod.rs delete mode 100644 folding-schemes/src/arith/r1cs/circuits.rs delete mode 100644 folding-schemes/src/arith/r1cs/mod.rs delete mode 100644 folding-schemes/src/commitment/ipa.rs delete mode 100644 folding-schemes/src/commitment/kzg.rs delete mode 100644 folding-schemes/src/commitment/mod.rs delete mode 100644 folding-schemes/src/commitment/pedersen.rs delete mode 100644 folding-schemes/src/constants.rs delete mode 100644 folding-schemes/src/folding/circuits/cyclefold.rs delete mode 100644 folding-schemes/src/folding/circuits/decider/mod.rs delete mode 100644 folding-schemes/src/folding/circuits/decider/off_chain.rs delete mode 100644 folding-schemes/src/folding/circuits/decider/on_chain.rs delete mode 100644 folding-schemes/src/folding/circuits/mod.rs delete mode 100644 folding-schemes/src/folding/circuits/nonnative/affine.rs delete mode 100644 folding-schemes/src/folding/circuits/nonnative/mod.rs delete mode 100644 folding-schemes/src/folding/circuits/nonnative/uint.rs delete mode 100644 folding-schemes/src/folding/circuits/sum_check.rs delete mode 100644 folding-schemes/src/folding/circuits/utils.rs delete mode 100644 folding-schemes/src/folding/hypernova/cccs.rs delete mode 100644 folding-schemes/src/folding/hypernova/circuits.rs delete mode 100644 folding-schemes/src/folding/hypernova/decider_eth.rs delete mode 100644 folding-schemes/src/folding/hypernova/decider_eth_circuit.rs delete mode 100644 folding-schemes/src/folding/hypernova/lcccs.rs delete mode 100644 folding-schemes/src/folding/hypernova/mod.rs delete mode 100644 folding-schemes/src/folding/hypernova/nimfs.rs delete mode 100644 folding-schemes/src/folding/hypernova/utils.rs delete mode 100644 folding-schemes/src/folding/mod.rs delete mode 100644 folding-schemes/src/folding/nova/circuits.rs delete mode 100644 folding-schemes/src/folding/nova/decider.rs delete mode 100644 folding-schemes/src/folding/nova/decider_circuits.rs delete mode 100644 folding-schemes/src/folding/nova/decider_eth.rs delete mode 100644 folding-schemes/src/folding/nova/decider_eth_circuit.rs delete mode 100644 folding-schemes/src/folding/nova/mod.rs delete mode 100644 folding-schemes/src/folding/nova/nifs/mod.rs delete mode 100644 folding-schemes/src/folding/nova/nifs/mova.rs delete mode 100644 folding-schemes/src/folding/nova/nifs/nova.rs delete mode 100644 folding-schemes/src/folding/nova/nifs/nova_circuits.rs delete mode 100644 folding-schemes/src/folding/nova/nifs/ova.rs delete mode 100644 folding-schemes/src/folding/nova/nifs/ova_circuits.rs delete mode 100644 folding-schemes/src/folding/nova/nifs/pointvsline.rs delete mode 100644 folding-schemes/src/folding/nova/traits.rs delete mode 100644 folding-schemes/src/folding/nova/zk.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/circuits.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/constants.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/decider_eth.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/folding.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/mod.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/traits.rs delete mode 100644 folding-schemes/src/folding/protogalaxy/utils.rs delete mode 100644 folding-schemes/src/folding/traits.rs delete mode 100644 folding-schemes/src/frontend/mod.rs delete mode 100644 folding-schemes/src/frontend/utils.rs delete mode 100644 folding-schemes/src/lib.rs delete mode 100644 folding-schemes/src/transcript/mod.rs delete mode 100644 folding-schemes/src/transcript/poseidon.rs delete mode 100644 folding-schemes/src/utils/espresso/mod.rs delete mode 100644 folding-schemes/src/utils/espresso/multilinear_polynomial.rs delete mode 100644 folding-schemes/src/utils/espresso/sum_check/mod.rs delete mode 100644 folding-schemes/src/utils/espresso/sum_check/prover.rs delete mode 100644 folding-schemes/src/utils/espresso/sum_check/structs.rs delete mode 100644 folding-schemes/src/utils/espresso/sum_check/verifier.rs delete mode 100644 folding-schemes/src/utils/espresso/virtual_polynomial.rs delete mode 100644 folding-schemes/src/utils/gadgets.rs delete mode 100644 folding-schemes/src/utils/hypercube.rs delete mode 100644 folding-schemes/src/utils/lagrange_poly.rs delete mode 100644 folding-schemes/src/utils/mle.rs delete mode 100644 folding-schemes/src/utils/mod.rs delete mode 100644 folding-schemes/src/utils/vec.rs delete mode 100644 rust-toolchain delete mode 100644 solidity-verifiers/Cargo.toml delete mode 100644 solidity-verifiers/README.md delete mode 100644 solidity-verifiers/askama.toml delete mode 100644 solidity-verifiers/src/calldata.rs delete mode 100644 solidity-verifiers/src/evm.rs delete mode 100644 solidity-verifiers/src/lib.rs delete mode 100644 solidity-verifiers/src/utils/encoding.rs delete mode 100644 solidity-verifiers/src/utils/eth.rs delete mode 100644 solidity-verifiers/src/utils/mod.rs delete mode 100644 solidity-verifiers/src/verifiers/g16.rs delete mode 100644 solidity-verifiers/src/verifiers/kzg.rs delete mode 100644 solidity-verifiers/src/verifiers/mod.rs delete mode 100644 solidity-verifiers/src/verifiers/nova_cyclefold.rs delete mode 100644 solidity-verifiers/templates/groth16_verifier.askama.sol delete mode 100644 solidity-verifiers/templates/header_template.askama.sol delete mode 100644 solidity-verifiers/templates/kzg10_verifier.askama.sol delete mode 100644 solidity-verifiers/templates/nova_cyclefold_decider.askama.sol diff --git a/.gitignore b/.gitignore index d3ba383d1..df0c112d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,7 @@ /target Cargo.lock -# Circom generated files -experimental-frontends/src/circom/test_folder/*_js/ *.r1cs *.sym - -# Noir generated files -experimental-frontends/src/noir/test_folder/*/target/* - -# generated contracts data -solidity-verifiers/generated -examples/*.sol -examples/*.calldata -examples/*.inputs *.serialized */*.serialized diff --git a/Cargo.toml b/Cargo.toml index f7f00d21a..d357cd2ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,40 +1,29 @@ [workspace] members = [ - "folding-schemes", - "solidity-verifiers", - "cli", - "experimental-frontends", ] resolver = "2" - [patch.crates-io] -# Update ark-groth16 to latest git version -ark-groth16 = { git = "https://github.com/arkworks-rs/groth16", rev = "b3b4a15" } - -# Required dependencies for latest ark-groth16 +# We depend on git versions of arkworks crates, but some of our dependencies +# depend on crates.io versions, so we need to override them here to avoid +# version conflicts. ark-ff = { git = "https://github.com/arkworks-rs/algebra" } ark-ec = { git = "https://github.com/arkworks-rs/algebra" } ark-serialize = { git = "https://github.com/arkworks-rs/algebra" } ark-poly = { git = "https://github.com/arkworks-rs/algebra" } -ark-relations = { git = "https://github.com/arkworks-rs/snark" } -ark-snark = { git = "https://github.com/arkworks-rs/snark" } -ark-crypto-primitives = { git = "https://github.com/flyingnobita/crypto-primitives", rev = "f559264" } -ark-r1cs-std = { git = "https://github.com/flyingnobita/r1cs-std_yelhousni", rev = "b4bab0c" } # "perf/sw-updated" branch ark-std = { git = "https://github.com/arkworks-rs/std" } -ark-poly-commit = { git = "https://github.com/arkworks-rs/poly-commit" } - +ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives", rev = "af003fc" } +ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" } # "sw-fix-updated" branch # Curve crates also need git versions ark-bn254 = { git = "https://github.com/arkworks-rs/algebra" } ark-grumpkin = { git = "https://github.com/arkworks-rs/algebra" } -ark-pallas = { git = "https://github.com/arkworks-rs/algebra" } -ark-vesta = { git = "https://github.com/arkworks-rs/algebra" } -ark-mnt4-298 = { git = "https://github.com/arkworks-rs/algebra" } -ark-mnt6-298 = { git = "https://github.com/arkworks-rs/algebra" } -[patch."https://github.com/arkworks-rs/circom-compat"] -ark-circom = { git = "https://github.com/dmpierre/circom-compat", rev = "0dfd773c" } +[patch."https://github.com/arkworks-rs/crypto-primitives"] +ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives", rev = "af003fc" } + +[patch."https://github.com/arkworks-rs/r1cs-std"] +ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" } # "sw-fix-updated" branch [workspace.package] edition = "2021" @@ -53,6 +42,7 @@ log = { version = "0.4" } noname = { git = "https://github.com/dmpierre/noname", rev = "c34f17" } num-bigint = { version = "0.4.3" } num-integer = { version = "0.1" } +num-traits = { version = "0.2" } pprof = { version = "0.13" } serde = { version = "^1.0.0" } serde_json = { version = "^1.0.0" } @@ -63,7 +53,7 @@ revm = { version = "19.5.0", default-features = false } rust-crypto = { version = "0.2" } thiserror = { version = "1.0" } tokio = "1.44.1" -wasmer = { version = "6.1.0-rc.2", default-features = false } +wasmer = { version = "6.1.0", default-features = false } # Arkworks family ark-bn254 = { version = "^0.5.0", default-features = false } @@ -71,7 +61,7 @@ ark-circom = { git = "https://github.com/arkworks-rs/circom-compat", default-fea ark-crypto-primitives = { version = "^0.5.0", default-features = false } ark-ec = { version = "^0.5.0", default-features = false } ark-ff = { version = "^0.5.0", default-features = false } -ark-groth16 = { version = "^0.5.0" } +ark-groth16 = { git = "https://github.com/arkworks-rs/groth16" } ark-grumpkin = { version = "^0.5.0", default-features = false } ark-mnt4-298 = { version = "^0.5.0" } ark-mnt6-298 = { version = "^0.5.0" } @@ -79,13 +69,10 @@ ark-pallas = { version = "^0.5.0" } ark-poly = { version = "^0.5.0", default-features = false } ark-poly-commit = { version = "^0.5.0" } ark-r1cs-std = { version = "^0.5.0", default-features = false } -ark-relations = { version = "^0.5.0", default-features = false } +ark-relations = { git = "https://github.com/arkworks-rs/snark", default-features = false } ark-serialize = { version = "^0.5.0" } -ark-snark = { version = "^0.5.0", default-features = false } +ark-snark = { git = "https://github.com/arkworks-rs/snark", default-features = false } ark-std = { version = "^0.5.0", default-features = false } ark-vesta = { version = "^0.5.0" } # Local crates -experimental-frontends = { path = "experimental-frontends" } -folding-schemes = { path = "folding-schemes" } -solidity-verifiers = { path = "solidity-verifiers" } diff --git a/benches/README.md b/benches/README.md deleted file mode 100644 index 6f998097e..000000000 --- a/benches/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# benchmarks -*Note: we're starting to benchmark & profile Sonobe, current results are pre-optimizations.* - -- Benchmark - - Run: `cargo bench` - - To run a specific benchmark, for example Nova's benchmark, run: `cargo bench --bench=nova` -- Profiling - - eg. `cargo bench --bench=nova -- --profile-time 3` - - diff --git a/benches/common.rs b/benches/common.rs deleted file mode 100644 index 163dbbfc6..000000000 --- a/benches/common.rs +++ /dev/null @@ -1,53 +0,0 @@ -use criterion::*; - -use folding_schemes::{ - frontend::{utils::CustomFCircuit, FCircuit}, - Curve, Error, FoldingScheme, -}; - -pub(crate) fn bench_ivc_opt< - C1: Curve, - C2: Curve, - FS: FoldingScheme>, ->( - c: &mut Criterion, - name: String, - n: usize, - prep_param: FS::PreprocessorParam, -) -> Result<(), Error> { - let fcircuit_size = 1 << n; // 2^n - - let f_circuit = CustomFCircuit::::new(fcircuit_size)?; - - let mut rng = rand::rngs::OsRng; - - // prepare the FS prover & verifier params - let fs_params = FS::preprocess(&mut rng, &prep_param)?; - - let z_0 = vec![C1::ScalarField::from(3_u32)]; - let mut fs = FS::init(&fs_params, f_circuit, z_0)?; - - // warmup steps - for _ in 0..5 { - fs.prove_step(rng, (), None)?; - } - - let mut group = c.benchmark_group(format!( - "{} - FCircuit: {} (2^{}) constraints", - name, fcircuit_size, n - )); - group.significance_level(0.1).sample_size(10); - group.bench_function("prove_step", |b| { - b.iter(|| -> Result<_, _> { black_box(fs.clone()).prove_step(rng, (), None) }) - }); - - // verify the IVCProof - let ivc_proof = fs.ivc_proof(); - group.bench_function("verify", |b| { - b.iter(|| -> Result<_, _> { - FS::verify(black_box(fs_params.1.clone()), black_box(ivc_proof.clone())) - }) - }); - group.finish(); - Ok(()) -} diff --git a/benches/hypernova.rs b/benches/hypernova.rs deleted file mode 100644 index d3f4421ca..000000000 --- a/benches/hypernova.rs +++ /dev/null @@ -1,84 +0,0 @@ -use criterion::*; -use pprof::criterion::{Output, PProfProfiler}; - -use ark_bn254::{Fr as bn_Fr, G1Projective as bn_G}; -use ark_grumpkin::Projective as grumpkin_G; -use ark_pallas::{Fr as pallas_Fr, Projective as pallas_G}; -use ark_vesta::Projective as vesta_G; - -use folding_schemes::{ - commitment::pedersen::Pedersen, - folding::{hypernova::HyperNova, nova::PreprocessorParam}, - frontend::{utils::CustomFCircuit, FCircuit}, - transcript::poseidon::poseidon_canonical_config, -}; - -mod common; -use common::bench_ivc_opt; - -fn bench_hypernova_ivc(c: &mut Criterion) { - let poseidon_config = poseidon_canonical_config::(); - - // iterate over the powers of n - for n in [0_usize, 14, 16, 18, 19, 20, 21, 22].iter() { - let fcircuit_size = 1 << n; // 2^n - let fcircuit = CustomFCircuit::::new(fcircuit_size).unwrap(); - let prep_param = PreprocessorParam::new(poseidon_config.clone(), fcircuit); - - bench_ivc_opt::< - pallas_G, - vesta_G, - HyperNova< - pallas_G, - vesta_G, - CustomFCircuit, - Pedersen, - Pedersen, - 1, - 1, - false, - >, - >( - c, - "HyperNova - Pallas-Vesta curves".to_string(), - *n, - prep_param, - ) - .unwrap(); - } - - let poseidon_config = poseidon_canonical_config::(); - for n in [0_usize, 14, 16, 18, 19, 20, 21, 22].iter() { - let fcircuit_size = 1 << n; // 2^n - let fcircuit = CustomFCircuit::::new(fcircuit_size).unwrap(); - let prep_param = PreprocessorParam::new(poseidon_config.clone(), fcircuit); - - bench_ivc_opt::< - bn_G, - grumpkin_G, - HyperNova< - bn_G, - grumpkin_G, - CustomFCircuit, - Pedersen, - Pedersen, - 1, - 1, - false, - >, - >( - c, - "HyperNova - BN254-Grumpkin curves".to_string(), - *n, - prep_param, - ) - .unwrap(); - } -} - -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = bench_hypernova_ivc -} -criterion_main!(benches); diff --git a/benches/nova.rs b/benches/nova.rs deleted file mode 100644 index eed5419e2..000000000 --- a/benches/nova.rs +++ /dev/null @@ -1,75 +0,0 @@ -use criterion::*; -use pprof::criterion::{Output, PProfProfiler}; - -use ark_bn254::{Fr as bn_Fr, G1Projective as bn_G}; -use ark_grumpkin::Projective as grumpkin_G; -use ark_pallas::{Fr as pallas_Fr, Projective as pallas_G}; -use ark_vesta::Projective as vesta_G; - -use folding_schemes::{ - commitment::pedersen::Pedersen, - folding::nova::{Nova, PreprocessorParam}, - frontend::{utils::CustomFCircuit, FCircuit}, - transcript::poseidon::poseidon_canonical_config, -}; - -mod common; -use common::bench_ivc_opt; - -fn bench_nova_ivc(c: &mut Criterion) { - let poseidon_config = poseidon_canonical_config::(); - - // iterate over the powers of n - for n in [0_usize, 14, 16, 18, 19, 20, 21, 22].iter() { - let fcircuit_size = 1 << n; // 2^n - let fcircuit = CustomFCircuit::::new(fcircuit_size).unwrap(); - let prep_param = PreprocessorParam::new(poseidon_config.clone(), fcircuit); - - bench_ivc_opt::< - pallas_G, - vesta_G, - Nova< - pallas_G, - vesta_G, - CustomFCircuit, - Pedersen, - Pedersen, - false, - >, - >(c, "Nova - Pallas-Vesta curves".to_string(), *n, prep_param) - .unwrap(); - } - - let poseidon_config = poseidon_canonical_config::(); - for n in [0_usize, 14, 16, 18, 19, 20, 21, 22].iter() { - let fcircuit_size = 1 << n; // 2^n - let fcircuit = CustomFCircuit::::new(fcircuit_size).unwrap(); - let prep_param = PreprocessorParam::new(poseidon_config.clone(), fcircuit); - - bench_ivc_opt::< - bn_G, - grumpkin_G, - Nova< - bn_G, - grumpkin_G, - CustomFCircuit, - Pedersen, - Pedersen, - false, - >, - >( - c, - "Nova - BN254-Grumpkin curves".to_string(), - *n, - prep_param, - ) - .unwrap(); - } -} - -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = bench_nova_ivc -} -criterion_main!(benches); diff --git a/benches/protogalaxy.rs b/benches/protogalaxy.rs deleted file mode 100644 index ace36c354..000000000 --- a/benches/protogalaxy.rs +++ /dev/null @@ -1,78 +0,0 @@ -use criterion::*; -use pprof::criterion::{Output, PProfProfiler}; - -use ark_bn254::{Fr as bn_Fr, G1Projective as bn_G}; -use ark_grumpkin::Projective as grumpkin_G; -use ark_pallas::{Fr as pallas_Fr, Projective as pallas_G}; -use ark_vesta::Projective as vesta_G; - -use folding_schemes::{ - commitment::pedersen::Pedersen, - folding::protogalaxy::ProtoGalaxy, - frontend::{utils::CustomFCircuit, FCircuit}, - transcript::poseidon::poseidon_canonical_config, -}; - -mod common; -use common::bench_ivc_opt; - -fn bench_protogalaxy_ivc(c: &mut Criterion) { - let poseidon_config = poseidon_canonical_config::(); - - // iterate over the powers of n - for n in [0_usize, 14, 16, 18, 19, 20, 21, 22].iter() { - let fcircuit_size = 1 << n; // 2^n - let fcircuit = CustomFCircuit::::new(fcircuit_size).unwrap(); - let prep_param = (poseidon_config.clone(), fcircuit); - - bench_ivc_opt::< - pallas_G, - vesta_G, - ProtoGalaxy< - pallas_G, - vesta_G, - CustomFCircuit, - Pedersen, - Pedersen, - >, - >( - c, - "ProtoGalaxy - Pallas-Vesta curves".to_string(), - *n, - prep_param, - ) - .unwrap(); - } - - let poseidon_config = poseidon_canonical_config::(); - for n in [0_usize, 14, 16, 18, 19, 20, 21, 22].iter() { - let fcircuit_size = 1 << n; // 2^n - let fcircuit = CustomFCircuit::::new(fcircuit_size).unwrap(); - let prep_param = (poseidon_config.clone(), fcircuit); - - bench_ivc_opt::< - bn_G, - grumpkin_G, - ProtoGalaxy< - bn_G, - grumpkin_G, - CustomFCircuit, - Pedersen, - Pedersen, - >, - >( - c, - "ProtoGalaxy - BN254-Grumpkin curves".to_string(), - *n, - prep_param, - ) - .unwrap(); - } -} - -criterion_group! { - name = benches; - config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = bench_protogalaxy_ivc -} -criterion_main!(benches); diff --git a/cli/Cargo.toml b/cli/Cargo.toml deleted file mode 100644 index d24b0b054..000000000 --- a/cli/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "solidity-verifiers-cli" -version = "0.1.0" -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -ark-serialize = { workspace = true } -solidity-verifiers = { workspace = true } -clap = { workspace = true, features = ["derive", "string"] } -clap-verbosity-flag = { workspace = true } -env_logger = { workspace = true } - -[features] -default = ["parallel"] -parallel = ["solidity-verifiers/parallel"] \ No newline at end of file diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index d34b2026b..000000000 --- a/cli/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Solidity Verifiers CLI - -Solidity Verifiers CLI is a Command-Line Interface (CLI) tool to generate the Solidity smart contracts that verify proofs of Zero Knowledge cryptographic protocols. This tool is developed by the collaborative efforts of the PSE (Privacy & Scaling Explorations) and 0xPARC teams. - -Solidity Verifiers CLI is released under the MIT license, but notice that the Solidity template for the Groth16 verification has GPL-3.0 license, hence the generated Solidity verifiers that use the Groth16 template will have that license too. - -## Supported Protocols - -Solidity Verifier currently supports the generation of Solidity smart contracts for the verification of proofs in the following Zero Knowledge protocols: - -- **Groth16:** - - Efficient and succinct zero-knowledge proof system. - - Template credit: [Jordi Baylina - Groth16 Verifier Template](https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs) - -- **KZG:** - - Uses the Kate-Zaverucha-Goldberg polynomial commitment scheme. - - Template credit: [weijiekoh - KZG10 Verifier Contract](https://github.com/weijiekoh/libkzg/blob/master/sol/KZGVerifier.sol) - -- **Nova + CycleFold Decider:** - - Implements the decider circuit verification for the Nova proof system in conjunction with the CycleFold protocol optimization. - - Template inspiration and setup credit: [Han - revm/Solidity Contract Testing Functions](https://github.com/privacy-scaling-explorations/halo2-solidity-verifier/tree/main) - -## Usage - -```bash -solidity-verifiers-cli [OPTIONS] -p -k -o -``` - -A real use case (which was used to test the tool itself): -`solidity-verifiers-cli -p groth16 -k ./solidity-verifiers/assets/G16_test_vk` -This would generate a Groth16 verifier contract for the given G16 verifier key (which consists of the G16_Vk only) and store this contract in `$pwd`. - -### Options: - -v, --verbose: Increase logging verbosity - -q, --quiet: Decrease logging verbosity - -p, --protocol : Selects the protocol for which to generate the Decider circuit Solidity Verifier (possible values: groth16, kzg, nova-cyclefold) - -o, --out : Sets the output path for all generated artifacts - -k, --protocol-vk : Sets the input path for the file containing the verifier key required by the protocol chosen such that the verification contract can be generated. - --pragma : Selects the Solidity compiler version to be set in the Solidity Verifier contract artifact - -h, --help: Print help (see a summary with '-h') - -V, --version: Print version - -## License -Solidity Verifier CLI is released under the MIT license, but notice that the Solidity template for the Groth16 verification has GPL-3.0 license, hence the generated Solidity verifiers will have that license too. - -## Contributing -Feel free to explore, use, and contribute to Solidity Verifiers CLI as we strive to enhance privacy and scalability in the blockchain space! -We welcome contributions to Solidity Verifiers CLI! If you encounter any issues, have feature requests, or want to contribute to the codebase, please check out the GitHub repository and follow the guidelines outlined in the contributing documentation. diff --git a/cli/src/main.rs b/cli/src/main.rs deleted file mode 100644 index 29479b58e..000000000 --- a/cli/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -use ark_serialize::Write; -use clap::Parser; -use settings::Cli; -use std::path::Path; -use std::{fs, io}; - -mod settings; - -fn create_or_open_then_write>(path: &Path, content: &T) -> Result<(), io::Error> { - let mut file = fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(path)?; - file.write_all(content.as_ref()) -} - -fn main() { - let cli = Cli::parse(); - - // generate a subscriber with the desired log level - env_logger::builder() - .format_timestamp_secs() - .filter_level(cli.verbosity.log_level_filter()) - .init(); - - let out_path = cli.out; - - // Fetch the exact protocol for which we need to generate the Decider verifier contract. - let protocol = cli.protocol; - // Fetch the protocol data passed by the user from the file. - let protocol_vk = std::fs::read(cli.protocol_vk).unwrap(); - - // Generate the Solidity Verifier contract for the selected protocol with the given data. - create_or_open_then_write( - &out_path, - &protocol.render(&protocol_vk, cli.pragma).unwrap(), - ) - .unwrap(); -} diff --git a/cli/src/settings.rs b/cli/src/settings.rs deleted file mode 100644 index 158ae1a13..000000000 --- a/cli/src/settings.rs +++ /dev/null @@ -1,113 +0,0 @@ -use ark_serialize::SerializationError; -use clap::{Parser, ValueEnum}; -use solidity_verifiers::{ - Groth16VerifierKey, KZG10VerifierKey, NovaCycleFoldVerifierKey, ProtocolVerifierKey, -}; -use std::{env, fmt::Display, path::PathBuf}; - -fn get_default_out_path() -> PathBuf { - let mut path = env::current_dir().unwrap(); - path.push("verifier.sol"); - path -} - -#[derive(Debug, Copy, Clone, ValueEnum)] -pub(crate) enum Protocol { - Groth16, - Kzg, - NovaCycleFold, -} - -impl Display for Protocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -// Would be nice to link this to the `Template` or `ProtocolVerifierKey` traits. -// Sadly, this requires Boxing with `dyn` or similar which would complicate the code more than is actually required. -impl Protocol { - pub(crate) fn render( - &self, - data: &[u8], - pragma: Option, - ) -> Result, SerializationError> { - match self { - Self::Groth16 => Ok(Groth16VerifierKey::deserialize_protocol_verifier_key(data)? - .render_as_template(pragma)), - - Self::Kzg => Ok(KZG10VerifierKey::deserialize_protocol_verifier_key(data)? - .render_as_template(pragma)), - Self::NovaCycleFold => Ok(NovaCycleFoldVerifierKey::deserialize_protocol_verifier_key( - data, - )? - .render_as_template(pragma)), - } - } -} - -const ABOUT: &str = "A Command-Line Interface (CLI) tool to generate the Solidity smart contracts that verify proofs of Zero Knowledge cryptographic protocols. -"; - -const LONG_ABOUT: &str = " - _____ ______ ______ ______ ______ ______ ______ -| |__| || |__| || |__| || |__| || |__| || |__| || |__| | -| () || () || () || () || () || () || () | -|______||______||______||______||______||______||______| - ______ ______ -| |__| | ____ _ _ _ _ _ | |__| | -| () | / ___| ___ | (_) __| (_) |_ _ _ | () | -|______| \\___ \\ / _ \\| | |/ _` | | __| | | | |______| - ______ ___) | (_) | | | (_| | | |_| |_| | ______ -| |__| | |____/ \\___/|_|_|\\__,_|_|\\__|\\__, | | |__| | -| () | __ __ _ __ _ |___/ | () | -|______| \\ \\ / /__ _ __(_)/ _(_) ___ _ __ |______| - ______ \\ \\ / / _ \\ '__| | |_| |/ _ \\ '__| ______ -| |__| | \\ V / __/ | | | _| | __/ | | |__| | -| () | \\_/ \\___|_| |_|_| |_|\\___|_| | () | -|______| |______| - ______ ______ ______ ______ ______ ______ ______ -| |__| || |__| || |__| || |__| || |__| || |__| || |__| | -| () || () || () || () || () || () || () | -|______||______||______||______||______||______||______| - -Welcome to Solidity Verifiers CLI, a Command-Line Interface (CLI) tool designed to simplify the generation of Solidity smart contracts that verify proofs of Zero Knowledge cryptographic protocols. This tool is developed by the collaborative efforts of the PSE (Privacy & Scaling Explorations) and 0xPARC teams. - -Solidity Verifiers CLI is released under the MIT license, but notice that the Solidity template for the Groth16 verification has GPL-3.0 license, hence the generated Solidity verifiers that use the Groth16 template will have that license too. - -Solidity Verifier currently supports the generation of Solidity smart contracts for the verification of proofs in the following Zero Knowledge protocols: - - Groth16: - Efficient and succinct zero-knowledge proof system. - - KZG: - Uses the Kate-Zaverucha-Goldberg polynomial commitment scheme. - - Nova + CycleFold Decider: - Implements the decider circuit verification for the Nova proof system in conjunction with the CycleFold protocol optimization. -"; -#[derive(Debug, Parser)] -#[command(author = "0xPARC & PSE", version, about = ABOUT, long_about = Some(LONG_ABOUT))] -#[command(propagate_version = true)] -/// A tool to create Solidity Contracts which act as verifiers for the major Folding Schemes implemented -/// within the `sonobe` repo. -pub(crate) struct Cli { - #[command(flatten)] - pub verbosity: clap_verbosity_flag::Verbosity, - - /// Selects the protocol for which we want to generate the Solidity Verifier contract. - #[arg(short = 'p', long, value_enum, rename_all = "lower")] - pub protocol: Protocol, - - #[arg(short = 'o', long, default_value=get_default_out_path().into_os_string())] - /// Sets the output path for all the artifacts generated by the command. - pub out: PathBuf, - - #[arg(short = 'k', long)] - /// Sets the input path for the file containing the verifier key required by the protocol chosen such that the verification contract can be generated. - pub protocol_vk: PathBuf, - - /// Selects the Solidity compiler version to be set in the Solidity Verifier contract artifact. - #[arg(long, default_value=None)] - pub pragma: Option, -} diff --git a/examples/circom_full_flow.rs b/examples/circom_full_flow.rs deleted file mode 100644 index c54c6a964..000000000 --- a/examples/circom_full_flow.rs +++ /dev/null @@ -1,169 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] -/// -/// This example performs the full flow: -/// - define the circuit to be folded -/// - fold the circuit with Nova+CycleFold's IVC -/// - generate a DeciderEthCircuit final proof -/// - generate the Solidity contract that verifies the proof -/// - verify the proof in the EVM -/// -use ark_bn254::{Bn254, Fr, G1Projective as G1}; - -use ark_groth16::Groth16; -use ark_grumpkin::Projective as G2; - -use std::path::PathBuf; -use std::time::Instant; - -use experimental_frontends::{circom::CircomFCircuit, utils::VecF}; -use folding_schemes::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - folding::{ - nova::{decider_eth::Decider as DeciderEth, Nova, PreprocessorParam}, - traits::CommittedInstanceOps, - }, - frontend::FCircuit, - transcript::poseidon::poseidon_canonical_config, - Decider, Error, FoldingScheme, -}; -use solidity_verifiers::calldata::{ - prepare_calldata_for_nova_cyclefold_verifier, NovaVerificationMode, -}; -use solidity_verifiers::{ - evm::{compile_solidity, Evm}, - verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, - NovaCycleFoldVerifierKey, -}; - -fn main() -> Result<(), Error> { - // set the initial state - let z_0 = vec![Fr::from(3_u32)]; - - // set the external inputs to be used at each step of the IVC, it has length of 10 since this - // is the number of steps that we will do - let external_inputs = vec![ - vec![Fr::from(6u32), Fr::from(7u32)], - vec![Fr::from(8u32), Fr::from(9u32)], - vec![Fr::from(10u32), Fr::from(11u32)], - vec![Fr::from(12u32), Fr::from(13u32)], - vec![Fr::from(14u32), Fr::from(15u32)], - vec![Fr::from(6u32), Fr::from(7u32)], - vec![Fr::from(8u32), Fr::from(9u32)], - vec![Fr::from(10u32), Fr::from(11u32)], - vec![Fr::from(12u32), Fr::from(13u32)], - vec![Fr::from(14u32), Fr::from(15u32)], - ]; - - // initialize the Circom circuit - let r1cs_path = - PathBuf::from("./experimental-frontends/src/circom/test_folder/with_external_inputs.r1cs"); - let wasm_path = PathBuf::from( - "./experimental-frontends/src/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm", - ); - - let f_circuit_params = (r1cs_path.into(), wasm_path.into()); - - const STATE_LEN: usize = 1; // state len = 1, external - const EXT_INP_LEN: usize = 2; // external inputs len = 2 - let f_circuit = CircomFCircuit::::new(f_circuit_params)?; - - pub type N = Nova< - G1, - G2, - CircomFCircuit, - KZG<'static, Bn254>, - Pedersen, - false, - >; - pub type D = DeciderEth< - G1, - G2, - CircomFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, - N, - >; - - let poseidon_config = poseidon_canonical_config::(); - let mut rng = ark_std::rand::rngs::OsRng; - - // prepare the Nova prover & verifier params - let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); - let nova_params = N::preprocess(&mut rng, &nova_preprocess_params)?; - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (nova_params.clone(), f_circuit.state_len()))?; - - // initialize the folding scheme engine, in our case we use Nova - let mut nova = N::init(&nova_params, f_circuit.clone(), z_0)?; - - // run n steps of the folding iteration - for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { - let start = Instant::now(); - nova.prove_step(rng, VecF(external_inputs_at_step.clone()), None)?; - println!("Nova::prove_step {}: {:?}", i, start.elapsed()); - } - - // verify the last IVC proof - let ivc_proof = nova.ivc_proof(); - N::verify( - nova_params.1, // Nova's verifier params - ivc_proof, - )?; - - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, nova.clone())?; - println!("generated Decider proof: {:?}", start.elapsed()); - - let verified = D::verify( - decider_vp.clone(), - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider proof verification: {}", verified); - - // Now, let's generate the Solidity code that verifies this Decider final proof - let calldata: Vec = prepare_calldata_for_nova_cyclefold_verifier( - NovaVerificationMode::Explicit, - nova.i, - nova.z_0, - nova.z_i, - &nova.U_i, - &nova.u_i, - &proof, - )?; - - // prepare the setup params for the solidity verifier - let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len())); - - // generate the solidity code - let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk); - - // verify the proof against the solidity code in the EVM - let nova_cyclefold_verifier_bytecode = compile_solidity(&decider_solidity_code, "NovaDecider"); - let mut evm = Evm::default(); - let verifier_address = evm.create(nova_cyclefold_verifier_bytecode); - let (_, output) = evm.call(verifier_address, calldata.clone()); - assert_eq!(*output.last().unwrap(), 1); - - // save smart contract and the calldata - println!("storing nova-verifier.sol and the calldata into files"); - use std::fs; - fs::write( - "./examples/nova-verifier.sol", - decider_solidity_code.clone(), - )?; - fs::write("./examples/solidity-calldata.calldata", calldata.clone())?; - let s = solidity_verifiers::calldata::get_formatted_calldata(calldata.clone()); - fs::write("./examples/solidity-calldata.inputs", s.join(",\n")).expect(""); - Ok(()) -} diff --git a/examples/external_inputs.rs b/examples/external_inputs.rs deleted file mode 100644 index f2f7dbb87..000000000 --- a/examples/external_inputs.rs +++ /dev/null @@ -1,209 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] - -use ark_bn254::{Bn254, Fr, G1Projective as Projective}; -use ark_crypto_primitives::{ - crh::{ - poseidon::constraints::{CRHGadget, CRHParametersVar}, - CRHSchemeGadget, - }, - sponge::{poseidon::PoseidonConfig, Absorb}, -}; -use ark_ff::PrimeField; -use ark_grumpkin::Projective as Projective2; -use ark_r1cs_std::alloc::AllocVar; -use ark_r1cs_std::fields::fp::FpVar; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; -use core::marker::PhantomData; -use std::time::Instant; - -use folding_schemes::commitment::{kzg::KZG, pedersen::Pedersen}; -use folding_schemes::folding::nova::{Nova, PreprocessorParam}; -use folding_schemes::frontend::FCircuit; -use folding_schemes::transcript::poseidon::poseidon_canonical_config; -use folding_schemes::{Error, FoldingScheme}; - -/// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i -/// denotes the current state which contains 1 element, and z_{i+1} denotes the next state which we -/// get by applying the step. -/// -/// In this example we set the state to be the previous state together with an external input, and -/// the new state is an array which contains the new state. -/// -/// This is useful for example if we want to fold multiple verifications of signatures, where the -/// circuit F checks the signature and is folded for each of the signatures and public keys. To -/// keep things simpler, the following example does not verify signatures but does a similar -/// approach with a chain of hashes, where each iteration hashes the previous step output (z_i) -/// together with an external input (w_i). -/// -/// w_1 w_2 w_3 w_4 -/// │ │ │ │ -/// ▼ ▼ ▼ ▼ -/// ┌─┐ ┌─┐ ┌─┐ ┌─┐ -/// ─────►│F├────►│F├────►│F├────►│F├────► -/// z_1 └─┘ z_2 └─┘ z_3 └─┘ z_4 └─┘ z_5 -/// -/// -/// where each F is: -/// w_i -/// │ ┌────────────────────┐ -/// │ │FCircuit │ -/// │ │ │ -/// └────►│ h =Hash(z_i[0],w_i)│ -/// ────────►│ │ ├───────► -/// z_i │ └──►z_{i+1}=[h] │ z_{i+1} -/// │ │ -/// └────────────────────┘ -/// -/// where each w_i value is set at the external_inputs array. -/// -/// The last state z_i is used together with the external input w_i as inputs to compute the new -/// state z_{i+1}. -#[derive(Clone, Debug)] -pub struct ExternalInputsCircuit -where - F: Absorb, -{ - _f: PhantomData, - poseidon_config: PoseidonConfig, -} -impl FCircuit for ExternalInputsCircuit -where - F: Absorb, -{ - type Params = PoseidonConfig; - type ExternalInputs = [F; 1]; - type ExternalInputsVar = [FpVar; 1]; - - fn new(params: Self::Params) -> Result { - Ok(Self { - _f: PhantomData, - poseidon_config: params, - }) - } - fn state_len(&self) -> usize { - 1 - } - /// generates the constraints and returns the next state value for the step of F for the given - /// z_i and external_inputs - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let crh_params = - CRHParametersVar::::new_constant(cs.clone(), self.poseidon_config.clone())?; - let hash_input: [FpVar; 2] = [z_i[0].clone(), external_inputs[0].clone()]; - let h = CRHGadget::::evaluate(&crh_params, &hash_input)?; - Ok(vec![h]) - } -} - -/// cargo test --example external_inputs -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::crh::{poseidon::CRH, CRHScheme}; - use ark_r1cs_std::GR1CSVar; - use ark_relations::gr1cs::ConstraintSystem; - - fn external_inputs_step_native( - z_i: Vec, - external_inputs: Vec, - poseidon_config: &PoseidonConfig, - ) -> Vec { - let hash_input: [F; 2] = [z_i[0], external_inputs[0]]; - let h = CRH::::evaluate(poseidon_config, hash_input).unwrap(); - vec![h] - } - - // test to check that the ExternalInputsCircuit computes the same values inside and outside the circuit - #[test] - fn test_f_circuit() -> Result<(), Error> { - let poseidon_config = poseidon_canonical_config::(); - - let cs = ConstraintSystem::::new_ref(); - - let circuit = ExternalInputsCircuit::::new(poseidon_config.clone())?; - let z_i = vec![Fr::from(1_u32)]; - let external_inputs = vec![Fr::from(3_u32)]; - - let z_i1 = - external_inputs_step_native(z_i.clone(), external_inputs.clone(), &poseidon_config); - - let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; - let external_inputsVar: [FpVar; 1] = - Vec::>::new_witness(cs.clone(), || Ok(external_inputs))? - .try_into() - .unwrap(); - - let computed_z_i1Var = - circuit.generate_step_constraints(cs.clone(), 0, z_iVar, external_inputsVar)?; - assert_eq!(computed_z_i1Var.value()?, z_i1); - Ok(()) - } -} - -/// cargo run --release --example external_inputs -fn main() -> Result<(), Error> { - let num_steps = 5; - let initial_state = vec![Fr::from(1_u32)]; - - // prepare the external inputs to be used at each folding step - let external_inputs = vec![ - [Fr::from(3_u32)], - [Fr::from(33_u32)], - [Fr::from(73_u32)], - [Fr::from(103_u32)], - [Fr::from(125_u32)], - ]; - assert_eq!(external_inputs.len(), num_steps); - - let poseidon_config = poseidon_canonical_config::(); - let F_circuit = ExternalInputsCircuit::::new(poseidon_config.clone())?; - - /// The idea here is that eventually we could replace the next line chunk that defines the - /// `type N = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme` - /// trait, and the rest of our code would be working without needing to be updated. - type N = Nova< - Projective, - Projective2, - ExternalInputsCircuit, - KZG<'static, Bn254>, - Pedersen, - false, - >; - - let mut rng = rand::rngs::OsRng; - - println!("Prepare Nova's ProverParams & VerifierParams"); - let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit.clone()); - let nova_params = N::preprocess(&mut rng, &nova_preprocess_params)?; - - println!("Initialize FoldingScheme"); - let mut folding_scheme = N::init(&nova_params, F_circuit, initial_state.clone())?; - - // compute a step of the IVC - for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { - let start = Instant::now(); - folding_scheme.prove_step(rng, external_inputs_at_step.clone(), None)?; - println!("Nova::prove_step {}: {:?}", i, start.elapsed()); - } - println!( - "state at last step (after {} iterations): {:?}", - num_steps, - folding_scheme.state() - ); - - println!("Run the Nova's IVC verifier"); - let ivc_proof = folding_scheme.ivc_proof(); - N::verify( - nova_params.1, // Nova's verifier params - ivc_proof, - )?; - Ok(()) -} diff --git a/examples/full_flow.rs b/examples/full_flow.rs deleted file mode 100644 index 93399b86b..000000000 --- a/examples/full_flow.rs +++ /dev/null @@ -1,154 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] -/// -/// This example performs the full flow: -/// - define the circuit to be folded -/// - fold the circuit with Nova+CycleFold's IVC -/// - generate a DeciderEthCircuit final proof -/// - generate the Solidity contract that verifies the proof -/// - verify the proof in the EVM -/// -use ark_bn254::{Bn254, Fr, G1Projective as G1}; -use ark_ff::PrimeField; -use ark_groth16::Groth16; -use ark_grumpkin::Projective as G2; -use ark_r1cs_std::alloc::AllocVar; -use ark_r1cs_std::fields::fp::FpVar; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; -use std::marker::PhantomData; -use std::time::Instant; - -use folding_schemes::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - folding::{ - nova::{decider_eth::Decider as DeciderEth, Nova, PreprocessorParam}, - traits::CommittedInstanceOps, - }, - frontend::FCircuit, - transcript::poseidon::poseidon_canonical_config, - Decider, Error, FoldingScheme, -}; -use solidity_verifiers::calldata::{ - prepare_calldata_for_nova_cyclefold_verifier, NovaVerificationMode, -}; -use solidity_verifiers::{ - evm::{compile_solidity, Evm}, - verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, - NovaCycleFoldVerifierKey, -}; - -/// Test circuit to be folded -#[derive(Clone, Copy, Debug)] -pub struct CubicFCircuit { - _f: PhantomData, -} -impl FCircuit for CubicFCircuit { - type Params = (); - type ExternalInputs = (); - type ExternalInputsVar = (); - - fn new(_params: Self::Params) -> Result { - Ok(Self { _f: PhantomData }) - } - fn state_len(&self) -> usize { - 1 - } - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; - let z_i = z_i[0].clone(); - - Ok(vec![&z_i * &z_i * &z_i + &z_i + &five]) - } -} - -fn main() -> Result<(), Error> { - let n_steps = 5; - // set the initial state - let z_0 = vec![Fr::from(3_u32)]; - - let f_circuit = CubicFCircuit::::new(())?; - - pub type N = Nova, KZG<'static, Bn254>, Pedersen, false>; - pub type D = - DeciderEth, KZG<'static, Bn254>, Pedersen, Groth16, N>; - - let poseidon_config = poseidon_canonical_config::(); - let mut rng = ark_std::rand::rngs::OsRng; - - // prepare the Nova prover & verifier params - let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit); - let nova_params = N::preprocess(&mut rng, &nova_preprocess_params)?; - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (nova_params.clone(), f_circuit.state_len()))?; - - // initialize the folding scheme engine, in our case we use Nova - let mut nova = N::init(&nova_params, f_circuit, z_0)?; - - // run n steps of the folding iteration - for i in 0..n_steps { - let start = Instant::now(); - nova.prove_step(rng, (), None)?; - println!("Nova::prove_step {}: {:?}", i, start.elapsed()); - } - - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, nova.clone())?; - println!("generated Decider proof: {:?}", start.elapsed()); - - let verified = D::verify( - decider_vp.clone(), - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider proof verification: {}", verified); - - // Now, let's generate the Solidity code that verifies this Decider final proof - let calldata: Vec = prepare_calldata_for_nova_cyclefold_verifier( - NovaVerificationMode::Explicit, - nova.i, - nova.z_0, - nova.z_i, - &nova.U_i, - &nova.u_i, - &proof, - )?; - - // prepare the setup params for the solidity verifier - let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len())); - - // generate the solidity code - let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk); - - // verify the proof against the solidity code in the EVM - let nova_cyclefold_verifier_bytecode = compile_solidity(&decider_solidity_code, "NovaDecider"); - let mut evm = Evm::default(); - let verifier_address = evm.create(nova_cyclefold_verifier_bytecode); - let (_, output) = evm.call(verifier_address, calldata.clone()); - assert_eq!(*output.last().unwrap(), 1); - - // save smart contract and the calldata - println!("storing nova-verifier.sol and the calldata into files"); - use std::fs; - fs::write( - "./examples/nova-verifier.sol", - decider_solidity_code.clone(), - )?; - fs::write("./examples/solidity-calldata.calldata", calldata.clone())?; - let s = solidity_verifiers::calldata::get_formatted_calldata(calldata.clone()); - fs::write("./examples/solidity-calldata.inputs", s.join(",\n")).expect(""); - Ok(()) -} diff --git a/examples/multi_inputs.rs b/examples/multi_inputs.rs deleted file mode 100644 index 9463d31ac..000000000 --- a/examples/multi_inputs.rs +++ /dev/null @@ -1,153 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] - -use ark_ff::PrimeField; -use ark_r1cs_std::alloc::AllocVar; -use ark_r1cs_std::fields::fp::FpVar; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; -use core::marker::PhantomData; -use std::time::Instant; - -use ark_bn254::{Bn254, Fr, G1Projective as Projective}; -use ark_grumpkin::Projective as Projective2; - -use folding_schemes::commitment::{kzg::KZG, pedersen::Pedersen}; -use folding_schemes::folding::nova::{Nova, PreprocessorParam}; -use folding_schemes::frontend::FCircuit; -use folding_schemes::transcript::poseidon::poseidon_canonical_config; -use folding_schemes::{Error, FoldingScheme}; - -/// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i -/// denotes the current state which contains 5 elements, and z_{i+1} denotes the next state which -/// we get by applying the step. -/// In this example we set z_i and z_{i+1} to have five elements, and at each step we do different -/// operations on each of them. -#[derive(Clone, Copy, Debug)] -pub struct MultiInputsFCircuit { - _f: PhantomData, -} -impl FCircuit for MultiInputsFCircuit { - type Params = (); - type ExternalInputs = (); - type ExternalInputsVar = (); - - fn new(_params: Self::Params) -> Result { - Ok(Self { _f: PhantomData }) - } - fn state_len(&self) -> usize { - 5 - } - /// generates the constraints for the step of F for the given z_i - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let four = FpVar::::new_constant(cs.clone(), F::from(4u32))?; - let forty = FpVar::::new_constant(cs.clone(), F::from(40u32))?; - let onehundred = FpVar::::new_constant(cs.clone(), F::from(100u32))?; - let a = z_i[0].clone() + four.clone(); - let b = z_i[1].clone() + forty.clone(); - let c = z_i[2].clone() * four; - let d = z_i[3].clone() * forty; - let e = z_i[4].clone() + onehundred; - - Ok(vec![a, b, c, d, e]) - } -} - -/// cargo test --example multi_inputs -#[cfg(test)] -pub mod tests { - use super::*; - use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - - fn multi_inputs_step_native(z_i: Vec) -> Vec { - let a = z_i[0] + F::from(4_u32); - let b = z_i[1] + F::from(40_u32); - let c = z_i[2] * F::from(4_u32); - let d = z_i[3] * F::from(40_u32); - let e = z_i[4] + F::from(100_u32); - - vec![a, b, c, d, e] - } - - // test to check that the MultiInputsFCircuit computes the same values inside and outside the circuit - #[test] - fn test_f_circuit() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - - let circuit = MultiInputsFCircuit::::new(())?; - let z_i = vec![ - Fr::from(1_u32), - Fr::from(1_u32), - Fr::from(1_u32), - Fr::from(1_u32), - Fr::from(1_u32), - ]; - - let z_i1 = multi_inputs_step_native(z_i.clone()); - - let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; - let computed_z_i1Var = - circuit.generate_step_constraints(cs.clone(), 0, z_iVar.clone(), ())?; - assert_eq!(computed_z_i1Var.value()?, z_i1); - Ok(()) - } -} - -/// cargo run --release --example multi_inputs -fn main() -> Result<(), Error> { - let num_steps = 10; - let initial_state = vec![ - Fr::from(1_u32), - Fr::from(1_u32), - Fr::from(1_u32), - Fr::from(1_u32), - Fr::from(1_u32), - ]; - - let F_circuit = MultiInputsFCircuit::::new(())?; - - let poseidon_config = poseidon_canonical_config::(); - let mut rng = rand::rngs::OsRng; - - /// The idea here is that eventually we could replace the next line chunk that defines the - /// `type N = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme` - /// trait, and the rest of our code would be working without needing to be updated. - type N = Nova< - Projective, - Projective2, - MultiInputsFCircuit, - KZG<'static, Bn254>, - Pedersen, - false, - >; - - println!("Prepare Nova ProverParams & VerifierParams"); - let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit); - let nova_params = N::preprocess(&mut rng, &nova_preprocess_params)?; - - println!("Initialize FoldingScheme"); - let mut folding_scheme = N::init(&nova_params, F_circuit, initial_state.clone())?; - - // compute a step of the IVC - for i in 0..num_steps { - let start = Instant::now(); - folding_scheme.prove_step(rng, (), None)?; - println!("Nova::prove_step {}: {:?}", i, start.elapsed()); - } - - println!("Run the Nova's IVC verifier"); - let ivc_proof = folding_scheme.ivc_proof(); - N::verify( - nova_params.1, // Nova's verifier params - ivc_proof, - )?; - Ok(()) -} diff --git a/examples/noir_full_flow.rs b/examples/noir_full_flow.rs deleted file mode 100644 index 0886e32de..000000000 --- a/examples/noir_full_flow.rs +++ /dev/null @@ -1,141 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] -/// -/// This example performs the full flow: -/// - define the circuit to be folded -/// - fold the circuit with Nova+CycleFold's IVC -/// - generate a DeciderEthCircuit final proof -/// - generate the Solidity contract that verifies the proof -/// - verify the proof in the EVM -/// -use ark_bn254::{Bn254, Fr, G1Projective as G1}; - -use ark_groth16::Groth16; -use ark_grumpkin::Projective as G2; - -use experimental_frontends::{noir::NoirFCircuit, utils::VecF}; -use folding_schemes::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - folding::{ - nova::{decider_eth::Decider as DeciderEth, Nova, PreprocessorParam}, - traits::CommittedInstanceOps, - }, - frontend::FCircuit, - transcript::poseidon::poseidon_canonical_config, - Decider, Error, FoldingScheme, -}; -use std::{path::Path, time::Instant}; - -use solidity_verifiers::calldata::{ - prepare_calldata_for_nova_cyclefold_verifier, NovaVerificationMode, -}; -use solidity_verifiers::{ - evm::{compile_solidity, Evm}, - verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, - NovaCycleFoldVerifierKey, -}; - -fn main() -> Result<(), Error> { - // set the initial state - let z_0 = vec![Fr::from(1)]; - - // initialize the noir fcircuit - const EXT_INP_LEN: usize = 0; - const STATE_LEN: usize = 1; - let f_circuit = NoirFCircuit::::new( - Path::new("./experimental-frontends/src/noir/test_folder/test_mimc/target/test_mimc.json") - .into(), - )?; - - pub type N = - Nova, KZG<'static, Bn254>, Pedersen>; - pub type D = DeciderEth< - G1, - G2, - NoirFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, - N, - >; - - let poseidon_config = poseidon_canonical_config::(); - let mut rng = ark_std::rand::rngs::OsRng; - - // prepare the Nova prover & verifier params - let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); - let nova_params = N::preprocess(&mut rng, &nova_preprocess_params)?; - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (nova_params.clone(), f_circuit.state_len()))?; - - // initialize the folding scheme engine, in our case we use Nova - let mut nova = N::init(&nova_params, f_circuit.clone(), z_0)?; - - // run n steps of the folding iteration - for i in 0..5 { - let start = Instant::now(); - nova.prove_step(rng, VecF(vec![]), None)?; - println!("Nova::prove_step {}: {:?}", i, start.elapsed()); - } - // verify the last IVC proof - let ivc_proof = nova.ivc_proof(); - N::verify( - nova_params.1, // Nova's verifier params - ivc_proof, - )?; - - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, nova.clone())?; - println!("generated Decider proof: {:?}", start.elapsed()); - - let verified = D::verify( - decider_vp.clone(), - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider proof verification: {}", verified); - - // Now, let's generate the Solidity code that verifies this Decider final proof - let calldata: Vec = prepare_calldata_for_nova_cyclefold_verifier( - NovaVerificationMode::Explicit, - nova.i, - nova.z_0, - nova.z_i, - &nova.U_i, - &nova.u_i, - &proof, - )?; - - // prepare the setup params for the solidity verifier - let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len())); - - // generate the solidity code - let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk); - - // verify the proof against the solidity code in the EVM - let nova_cyclefold_verifier_bytecode = compile_solidity(&decider_solidity_code, "NovaDecider"); - let mut evm = Evm::default(); - let verifier_address = evm.create(nova_cyclefold_verifier_bytecode); - let (_, output) = evm.call(verifier_address, calldata.clone()); - assert_eq!(*output.last().unwrap(), 1); - - // save smart contract and the calldata - println!("storing nova-verifier.sol and the calldata into files"); - use std::fs; - fs::write( - "./examples/nova-verifier.sol", - decider_solidity_code.clone(), - )?; - fs::write("./examples/solidity-calldata.calldata", calldata.clone())?; - let s = solidity_verifiers::calldata::get_formatted_calldata(calldata.clone()); - fs::write("./examples/solidity-calldata.inputs", s.join(",\n")).expect(""); - Ok(()) -} diff --git a/examples/noname_full_flow.rs b/examples/noname_full_flow.rs deleted file mode 100644 index fad8846ab..000000000 --- a/examples/noname_full_flow.rs +++ /dev/null @@ -1,162 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] -/// -/// This example performs the full flow: -/// - define the circuit to be folded -/// - fold the circuit with Nova+CycleFold's IVC -/// - generate a DeciderEthCircuit final proof -/// - generate the Solidity contract that verifies the proof -/// - verify the proof in the EVM -/// -use ark_bn254::{Bn254, Fr, G1Projective as G1}; -use noname::backends::r1cs::R1csBn254Field; - -use ark_groth16::Groth16; -use ark_grumpkin::Projective as G2; - -use experimental_frontends::{noname::NonameFCircuit, utils::VecF}; -use folding_schemes::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - folding::{ - nova::{decider_eth::Decider as DeciderEth, Nova, PreprocessorParam}, - traits::CommittedInstanceOps, - }, - frontend::FCircuit, - transcript::poseidon::poseidon_canonical_config, - Decider, Error, FoldingScheme, -}; -use std::time::Instant; - -use solidity_verifiers::calldata::{ - prepare_calldata_for_nova_cyclefold_verifier, NovaVerificationMode, -}; -use solidity_verifiers::{ - evm::{compile_solidity, Evm}, - verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, - NovaCycleFoldVerifierKey, -}; - -fn main() -> Result<(), Error> { - const NONAME_CIRCUIT_EXTERNAL_INPUTS: &str = - "fn main(pub ivc_inputs: [Field; 2], external_inputs: [Field; 2]) -> [Field; 2] { - let xx = external_inputs[0] + ivc_inputs[0]; - let yy = external_inputs[1] * ivc_inputs[1]; - assert_eq(yy, xx); - return [xx, yy]; -}"; - - // set the initial state - let z_0 = vec![Fr::from(2), Fr::from(5)]; - - // set the external inputs to be used at each step of the IVC, it has length of 10 since this - // is the number of steps that we will do - let external_inputs = vec![ - vec![Fr::from(8u32), Fr::from(2u32)], - vec![Fr::from(40), Fr::from(5)], - ]; - - // initialize the noname circuit - let f_circuit_params = NONAME_CIRCUIT_EXTERNAL_INPUTS.to_owned(); - const STATE_LEN: usize = 2; - const EXT_INP_LEN: usize = 2; - let f_circuit = - NonameFCircuit::::new(f_circuit_params)?; - - pub type N = Nova< - G1, - G2, - NonameFCircuit, - KZG<'static, Bn254>, - Pedersen, - >; - pub type D = DeciderEth< - G1, - G2, - NonameFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, - N, - >; - - let poseidon_config = poseidon_canonical_config::(); - let mut rng = ark_std::rand::rngs::OsRng; - - // prepare the Nova prover & verifier params - let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); - let nova_params = N::preprocess(&mut rng, &nova_preprocess_params)?; - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (nova_params.clone(), f_circuit.state_len()))?; - - // initialize the folding scheme engine, in our case we use Nova - let mut nova = N::init(&nova_params, f_circuit.clone(), z_0)?; - - // run n steps of the folding iteration - for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { - let start = Instant::now(); - nova.prove_step(rng, VecF(external_inputs_at_step.clone()), None)?; - println!("Nova::prove_step {}: {:?}", i, start.elapsed()); - } - - // verify the last IVC proof - let ivc_proof = nova.ivc_proof(); - N::verify( - nova_params.1, // Nova's verifier params - ivc_proof, - )?; - - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, nova.clone())?; - println!("generated Decider proof: {:?}", start.elapsed()); - - let verified = D::verify( - decider_vp.clone(), - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider proof verification: {}", verified); - - // Now, let's generate the Solidity code that verifies this Decider final proof - let calldata: Vec = prepare_calldata_for_nova_cyclefold_verifier( - NovaVerificationMode::Explicit, - nova.i, - nova.z_0, - nova.z_i, - &nova.U_i, - &nova.u_i, - &proof, - )?; - - // prepare the setup params for the solidity verifier - let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len())); - - // generate the solidity code - let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk); - - // verify the proof against the solidity code in the EVM - let nova_cyclefold_verifier_bytecode = compile_solidity(&decider_solidity_code, "NovaDecider"); - let mut evm = Evm::default(); - let verifier_address = evm.create(nova_cyclefold_verifier_bytecode); - let (_, output) = evm.call(verifier_address, calldata.clone()); - assert_eq!(*output.last().unwrap(), 1); - - // save smart contract and the calldata - println!("storing nova-verifier.sol and the calldata into files"); - use std::fs; - fs::write( - "./examples/nova-verifier.sol", - decider_solidity_code.clone(), - )?; - fs::write("./examples/solidity-calldata.calldata", calldata.clone())?; - let s = solidity_verifiers::calldata::get_formatted_calldata(calldata.clone()); - fs::write("./examples/solidity-calldata.inputs", s.join(",\n")).expect(""); - Ok(()) -} diff --git a/examples/sha256.rs b/examples/sha256.rs deleted file mode 100644 index 1dd8a7a04..000000000 --- a/examples/sha256.rs +++ /dev/null @@ -1,139 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] - -use ark_crypto_primitives::crh::{ - sha256::constraints::{Sha256Gadget, UnitVar}, - CRHSchemeGadget, -}; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - convert::{ToBytesGadget, ToConstraintFieldGadget}, - fields::fp::FpVar, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; -use core::marker::PhantomData; -use std::time::Instant; - -use ark_bn254::{Bn254, Fr, G1Projective as Projective}; -use ark_grumpkin::Projective as Projective2; - -use folding_schemes::commitment::{kzg::KZG, pedersen::Pedersen}; -use folding_schemes::folding::nova::{Nova, PreprocessorParam}; -use folding_schemes::frontend::FCircuit; -use folding_schemes::transcript::poseidon::poseidon_canonical_config; -use folding_schemes::{Error, FoldingScheme}; - -/// This is the circuit that we want to fold, it implements the FCircuit trait. -/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state which we get by -/// applying the step. -/// In this example we set z_i and z_{i+1} to be a single value, but the trait is made to support -/// arrays, so our state could be an array with different values. -#[derive(Clone, Copy, Debug)] -pub struct Sha256FCircuit { - _f: PhantomData, -} -impl FCircuit for Sha256FCircuit { - type Params = (); - type ExternalInputs = (); - type ExternalInputsVar = (); - - fn new(_params: Self::Params) -> Result { - Ok(Self { _f: PhantomData }) - } - fn state_len(&self) -> usize { - 1 - } - /// generates the constraints for the step of F for the given z_i - fn generate_step_constraints( - &self, - _cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let unit_var = UnitVar::default(); - let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes_le()?)?; - let out = out_bytes.0.to_constraint_field()?; - Ok(vec![out[0].clone()]) - } -} - -/// cargo test --example sha256 -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::crh::{sha256::Sha256, CRHScheme}; - use ark_ff::{BigInteger, ToConstraintField}; - use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - - fn sha256_step_native(z_i: Vec) -> Vec { - let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap(); - let out: Vec = out_bytes.to_field_elements().unwrap(); - - vec![out[0]] - } - - // test to check that the Sha256FCircuit computes the same values inside and outside the circuit - #[test] - fn test_f_circuit() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - - let circuit = Sha256FCircuit::::new(())?; - let z_i = vec![Fr::from(1_u32)]; - - let z_i1 = sha256_step_native(z_i.clone()); - - let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; - let computed_z_i1Var = - circuit.generate_step_constraints(cs.clone(), 0, z_iVar.clone(), ())?; - assert_eq!(computed_z_i1Var.value()?, z_i1); - Ok(()) - } -} - -/// cargo run --release --example sha256 -fn main() -> Result<(), Error> { - let num_steps = 10; - let initial_state = vec![Fr::from(1_u32)]; - - let F_circuit = Sha256FCircuit::::new(())?; - - /// The idea here is that eventually we could replace the next line chunk that defines the - /// `type N = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme` - /// trait, and the rest of our code would be working without needing to be updated. - type N = Nova< - Projective, - Projective2, - Sha256FCircuit, - KZG<'static, Bn254>, - Pedersen, - false, - >; - - let poseidon_config = poseidon_canonical_config::(); - let mut rng = rand::rngs::OsRng; - - println!("Prepare Nova ProverParams & VerifierParams"); - let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit); - let nova_params = N::preprocess(&mut rng, &nova_preprocess_params)?; - - println!("Initialize FoldingScheme"); - let mut folding_scheme = N::init(&nova_params, F_circuit, initial_state.clone())?; - // compute a step of the IVC - for i in 0..num_steps { - let start = Instant::now(); - folding_scheme.prove_step(rng, (), None)?; - println!("Nova::prove_step {}: {:?}", i, start.elapsed()); - } - - println!("Run the Nova's IVC verifier"); - let ivc_proof = folding_scheme.ivc_proof(); - N::verify( - nova_params.1, // Nova's verifier params - ivc_proof, - )?; - Ok(()) -} diff --git a/experimental-frontends/Cargo.toml b/experimental-frontends/Cargo.toml deleted file mode 100644 index 9fba6d4bb..000000000 --- a/experimental-frontends/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "experimental-frontends" -version = "0.1.0" -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -ark-ff = { workspace = true, features = ["parallel", "asm"] } -ark-std = { workspace = true, features = ["parallel"] } -ark-relations = { workspace = true } -ark-r1cs-std = { workspace = true, features = ["parallel"] } -ark-serialize = { workspace = true } -ark-circom = { workspace = true } -num-bigint = { workspace = true } -noname = { workspace = true } -acvm = { workspace = true } -folding-schemes = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -wasmer = { workspace = true } - -[dev-dependencies] -ark-bn254 = { workspace = true, features = ["r1cs"] } - -# This allows the crate to be built when targeting WASM. -# See more at: https://docs.rs/getrandom/#webassembly-support -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] -getrandom = { workspace = true, features = ["js"] } - -[features] -default = ["ark-circom/default", "parallel"] -parallel = [] -wasm = ["ark-circom/wasm"] diff --git a/experimental-frontends/README.md b/experimental-frontends/README.md deleted file mode 100644 index dedc9e77e..000000000 --- a/experimental-frontends/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# experimental-frontends - -This crate contains *experimental frontends* for Sonobe. -The recommended frontend is to directly use [arkworks](https://github.com/arkworks-rs) to define the FCircuit, just following the [`FCircuit` trait](https://github.com/privacy-scaling-explorations/sonobe/blob/main/folding-schemes/src/frontend/mod.rs). - -> Warning: the following frontends are experimental and some computational and time overhead is expected when using them compared to directly using the [arkworks frontend](https://github.com/privacy-scaling-explorations/sonobe/blob/main/folding-schemes/src/frontend/mod.rs). - -Available experimental frontends: -- [Circom](https://github.com/iden3/circom), iden3, 0Kims Association. Supported version`<=v2.1.9`. -- [Noir](https://github.com/noir-lang/noir), Aztec. -- [Noname](https://github.com/zksecurity/noname), zkSecurity. Partially supported. - - -Documentation about frontend interface and experimental frontends: https://privacy-scaling-explorations.github.io/sonobe-docs/usage/frontend.html - -## Implementing new frontends -Support for new frontends can be added (even from outside this repo) by implementing the [`FCircuit` trait](https://github.com/privacy-scaling-explorations/sonobe/blob/main/folding-schemes/src/frontend/mod.rs). diff --git a/experimental-frontends/src/circom/mod.rs b/experimental-frontends/src/circom/mod.rs deleted file mode 100644 index 76b8385b0..000000000 --- a/experimental-frontends/src/circom/mod.rs +++ /dev/null @@ -1,347 +0,0 @@ -use ark_circom::circom::R1CS as CircomR1CS; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - fields::fp::{AllocatedFp, FpVar}, - GR1CSVar, -}; -use ark_relations::{ - gr1cs::{ConstraintSystemRef, SynthesisError, Variable}, - lc, -}; -use ark_std::fmt::Debug; -use folding_schemes::{frontend::FCircuit, utils::PathOrBin, Error}; -use num_bigint::{BigInt, BigUint}; - -pub mod utils; -use crate::utils::{VecF, VecFpVar}; -use utils::CircomWrapper; - -/// Define CircomFCircuit. The parameter `SL` indicates the length of the state vector. -/// The parameter `EIL` indicates the length of the ExternalInputs vector of field elements. -#[derive(Clone, Debug)] -pub struct CircomFCircuit { - circom_wrapper: CircomWrapper, - r1cs: CircomR1CS, -} - -impl FCircuit for CircomFCircuit { - /// (r1cs_path, wasm_path) - type Params = (PathOrBin, PathOrBin); - type ExternalInputs = VecF; - type ExternalInputsVar = VecFpVar; - - fn new(params: Self::Params) -> Result { - let (r1cs_path, wasm_path) = params; - let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path)?; - - let r1cs = circom_wrapper.extract_r1cs()?; - Ok(Self { - circom_wrapper, - r1cs, - }) - } - - fn state_len(&self) -> usize { - SL - } - - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - #[cfg(test)] - assert_eq!(z_i.len(), SL); - #[cfg(test)] - assert_eq!(external_inputs.0.len(), EIL); - - let input_values = Self::fpvars_to_bigints(&z_i); - let mut inputs_map = vec![("ivc_input".to_string(), input_values)]; - - if EIL > 0 { - let external_inputs_bi = Self::fpvars_to_bigints(&external_inputs.0); - inputs_map.push(("external_inputs".to_string(), external_inputs_bi)); - } - - // The layout of `witness` is as follows: - // [ - // 1, // The constant 1 is implicitly allocated by Arkworks - // ...z_{i + 1}, // The next state marked as `signal output` in the circom circuit - // ...z_i, // The current state marked as `signal input` in the circom circuit - // ...external_inputs, // The optional external inputs marked as `external input` in the circom circuit - // ...aux, // The intermediate witnesses - // ] - // Here, 1, z_i, and external_inputs have already been allocated in the - // constraint system, while z_{i + 1} and aux are yet to be allocated. - let witness = self - .circom_wrapper - .extract_witness(inputs_map) - .map_err(|_| SynthesisError::AssignmentMissing)?; - - // In order to convert the indexes of variables in the circom circuit to - // those in the arkworks circuit, we adopt the tricks from - // https://github.com/arnaucube/circom-compat/pull/1 - - // Since our cs might already have allocated constraints, - // We store a mapping between circom's defined indexes and the newly obtained cs indexes - let mut circom_index_to_cs_index = vec![]; - - // Constant 1 at idx 0 is already allocated by arkworks - circom_index_to_cs_index.push(Variable::One); - - // Allocate the next state (1..1 + SL) as witness, and at the same time, - // record the allocated variable's index in `circom_index_to_cs_index`. - // Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L56-L86 - let mut z_i1 = vec![]; - for &w in witness.iter().skip(1).take(SL) { - let v = cs.new_witness_variable(|| Ok(w))?; - circom_index_to_cs_index.push(v); - z_i1.push(FpVar::Var(AllocatedFp::new(Some(w), v, cs.clone()))); - } - - // `z_i` and `external_inputs` have already been allocated as witness, - // so we just record their indexes in `circom_index_to_cs_index`. - // Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L89-L95 - for v in z_i.iter().chain(&external_inputs.0) { - match v { - FpVar::Var(v) => circom_index_to_cs_index.push(v.variable), - // safe because `z_i` and `external_inputs` are allocated as - // witness (not constant) - _ => unreachable!(), - }; - } - - // Allocate the remaining aux variables as witness. - // Also, record their indexes in `circom_index_to_cs_index`. - // Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L106-L121 - for w in witness.into_iter().skip(circom_index_to_cs_index.len()) { - circom_index_to_cs_index.push(cs.new_witness_variable(|| Ok(w))?); - } - - let fold_lc = |lc, &(i, coeff)| lc + (coeff, circom_index_to_cs_index[i]); - - // Generates the constraints for the circom_circuit. - for (a, b, c) in &self.r1cs.constraints { - cs.enforce_r1cs_constraint( - || a.iter().fold(lc!(), fold_lc), - || b.iter().fold(lc!(), fold_lc), - || c.iter().fold(lc!(), fold_lc), - )?; - } - - #[cfg(test)] - if !cs.is_in_setup_mode() && !cs.is_satisfied()? { - return Err(SynthesisError::Unsatisfiable); - } - - Ok(z_i1) - } -} - -impl CircomFCircuit { - fn fpvars_to_bigints(fpvars: &[FpVar]) -> Vec { - fpvars - .value() - .unwrap_or(vec![F::zero(); fpvars.len()]) - .into_iter() - .map(Into::::into) - .map(BigInt::from) - .collect() - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_bn254::Fr; - use ark_r1cs_std::alloc::AllocVar; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - use std::path::PathBuf; - - /// Native implementation of `src/circom/test_folder/cubic_circuit.r1cs` - fn cubic_step_native(z_i: Vec) -> Vec { - let z = z_i[0]; - vec![z * z * z + z + F::from(5)] - } - - /// Native implementation of `src/circom/test_folder/with_external_inputs.r1cs` - fn external_inputs_step_native(z_i: Vec, external_inputs: Vec) -> Vec { - let temp1 = z_i[0] * z_i[0]; - let temp2 = z_i[0] * external_inputs[0]; - vec![temp1 * z_i[0] + temp2 + external_inputs[1]] - } - - /// Native implementation of `src/circom/test_folder/no_external_inputs.r1cs` - fn no_external_inputs_step_native(z_i: Vec) -> Vec { - let temp1 = z_i[0] * z_i[1]; - let temp2 = temp1 * z_i[2]; - vec![ - temp1 * z_i[0], - temp1 * z_i[1] + temp1, - temp1 * z_i[2] + temp2, - ] - } - - // Tests the step_native function of CircomFCircuit. - #[test] - fn test_circom_step_native() -> Result<(), Error> { - let z_i = vec![Fr::from(3u32)]; - let z_i1 = cubic_step_native(z_i); - assert_eq!(z_i1, vec![Fr::from(35u32)]); - Ok(()) - } - - // Tests the generate_step_constraints function of CircomFCircuit. - #[test] - fn test_circom_step_constraints() -> Result<(), Error> { - let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs"); - let wasm_path = - PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - - let circom_fcircuit = - CircomFCircuit::::new((r1cs_path.into(), wasm_path.into()))?; // state_len:1, external_inputs_len:0 - - let cs = ConstraintSystem::::new_ref(); - - let z_i = vec![Fr::from(3u32)]; - - let z_i_var = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; - let z_i1_var = - circom_fcircuit.generate_step_constraints(cs.clone(), 1, z_i_var, VecFpVar(vec![]))?; - assert_eq!(z_i1_var.value()?, vec![Fr::from(35u32)]); - Ok(()) - } - - // Tests the WrapperCircuit with CircomFCircuit. - #[test] - fn test_wrapper_circomtofcircuit() -> Result<(), Error> { - let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs"); - let wasm_path = - PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - - let circom_fcircuit = - CircomFCircuit::::new((r1cs_path.into(), wasm_path.into()))?; // state_len:1, external_inputs_len:0 - - // Allocates z_i1 by using step_native function. - let z_i = vec![Fr::from(3_u32)]; - let wrapper_circuit = folding_schemes::frontend::utils::WrapperCircuit { - FC: circom_fcircuit.clone(), - z_i: Some(z_i.clone()), - z_i1: Some(cubic_step_native(z_i)), - }; - - let cs = ConstraintSystem::::new_ref(); - - wrapper_circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?, "Constraint system is not satisfied"); - Ok(()) - } - - #[test] - fn test_circom_external_inputs() -> Result<(), Error> { - let r1cs_path = PathBuf::from("./src/circom/test_folder/with_external_inputs.r1cs"); - let wasm_path = PathBuf::from( - "./src/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm", - ); - let circom_fcircuit = - CircomFCircuit::::new((r1cs_path.into(), wasm_path.into()))?; // state_len:1, external_inputs_len:2 - let cs = ConstraintSystem::::new_ref(); - let z_i = vec![Fr::from(3u32)]; - let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)]; - - // run native step - let z_i1_native = external_inputs_step_native(z_i.clone(), external_inputs.clone()); - - // run gadget step - let z_i_var = Vec::>::new_witness(cs.clone(), || Ok(z_i))?; - let external_inputs_var = - Vec::>::new_witness(cs.clone(), || Ok(external_inputs.clone()))?; - let z_i1_var = circom_fcircuit.generate_step_constraints( - cs.clone(), - 1, - z_i_var, - VecFpVar(external_inputs_var), - )?; - - assert_eq!(z_i1_var.value()?, z_i1_native); - - // re-init cs and run gadget step with wrong ivc inputs (first ivc should not be zero) - let cs = ConstraintSystem::::new_ref(); - let wrong_z_i = vec![Fr::from(0)]; - let wrong_z_i_var = Vec::>::new_witness(cs.clone(), || Ok(wrong_z_i))?; - let external_inputs_var = - Vec::>::new_witness(cs.clone(), || Ok(external_inputs))?; - let _z_i1_var = circom_fcircuit.generate_step_constraints( - cs.clone(), - 1, - wrong_z_i_var, - VecFpVar(external_inputs_var), - ); - // TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104 - // Disable check for now - // assert!(z_i1_var.is_err()); - Ok(()) - } - - #[test] - fn test_circom_no_external_inputs() -> Result<(), Error> { - let r1cs_path = PathBuf::from("./src/circom/test_folder/no_external_inputs.r1cs"); - let wasm_path = - PathBuf::from("./src/circom/test_folder/no_external_inputs_js/no_external_inputs.wasm"); - let circom_fcircuit = - CircomFCircuit::::new((r1cs_path.into(), wasm_path.into()))?; - let cs = ConstraintSystem::::new_ref(); - let z_i = vec![Fr::from(3u32), Fr::from(4u32), Fr::from(5u32)]; - let z_i_var = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone()))?; - - // run native step - let z_i1_native = no_external_inputs_step_native(z_i.clone()); - - // run gadget step - let z_i1_var = - circom_fcircuit.generate_step_constraints(cs.clone(), 1, z_i_var, VecFpVar(vec![]))?; - - assert_eq!(z_i1_var.value()?, z_i1_native); - - // re-init cs and run gadget step with wrong ivc inputs (first ivc input should not be zero) - let cs = ConstraintSystem::::new_ref(); - let wrong_z_i = vec![Fr::from(0u32), Fr::from(4u32), Fr::from(5u32)]; - let wrong_z_i_var = Vec::>::new_witness(cs.clone(), || Ok(wrong_z_i))?; - let _z_i1_var = circom_fcircuit.generate_step_constraints( - cs.clone(), - 1, - wrong_z_i_var, - VecFpVar(vec![]), - ); - // TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104 - // Disable check for now - // assert!(z_i1_var.is_err()) - Ok(()) - } - - #[test] - fn test_custom_code() -> Result<(), Error> { - let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs"); - let wasm_path = - PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - - let circom_fcircuit = - CircomFCircuit::::new((r1cs_path.into(), wasm_path.into()))?; // state_len:1, external_inputs_len:0 - - // Allocates z_i1 by using step_native function. - let z_i = vec![Fr::from(3_u32)]; - let wrapper_circuit = folding_schemes::frontend::utils::WrapperCircuit { - FC: circom_fcircuit.clone(), - z_i: Some(z_i.clone()), - z_i1: Some(cubic_step_native(z_i)), - }; - - let cs = ConstraintSystem::::new_ref(); - - wrapper_circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?, "Constraint system is not satisfied"); - Ok(()) - } -} diff --git a/experimental-frontends/src/circom/test_folder/circuits/is_zero.circom b/experimental-frontends/src/circom/test_folder/circuits/is_zero.circom deleted file mode 100644 index 8ec62a9eb..000000000 --- a/experimental-frontends/src/circom/test_folder/circuits/is_zero.circom +++ /dev/null @@ -1,14 +0,0 @@ -pragma circom 2.0.0; -// From: https://github.com/iden3/circomlib/blob/master/circuits/comparators.circom - -template IsZero() { - signal input in; - signal output out; - - signal inv; - - inv <-- in!=0 ? 1/in : 0; - - out <== -in*inv +1; - in*out === 0; -} \ No newline at end of file diff --git a/experimental-frontends/src/circom/test_folder/compile.sh b/experimental-frontends/src/circom/test_folder/compile.sh deleted file mode 100755 index 1993e3ce8..000000000 --- a/experimental-frontends/src/circom/test_folder/compile.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -circom ./experimental-frontends/src/circom/test_folder/cubic_circuit.circom --r1cs --sym --wasm --prime bn128 --output ./experimental-frontends/src/circom/test_folder/ -circom ./experimental-frontends/src/circom/test_folder/with_external_inputs.circom --r1cs --sym --wasm --prime bn128 --output ./experimental-frontends/src/circom/test_folder/ -circom ./experimental-frontends/src/circom/test_folder/no_external_inputs.circom --r1cs --sym --wasm --prime bn128 --output ./experimental-frontends/src/circom/test_folder/ diff --git a/experimental-frontends/src/circom/test_folder/cubic_circuit.circom b/experimental-frontends/src/circom/test_folder/cubic_circuit.circom deleted file mode 100644 index 28e206793..000000000 --- a/experimental-frontends/src/circom/test_folder/cubic_circuit.circom +++ /dev/null @@ -1,12 +0,0 @@ -pragma circom 2.0.3; - -template Example () { - signal input ivc_input[1]; - signal output ivc_output[1]; - signal temp; - - temp <== ivc_input[0] * ivc_input[0]; - ivc_output[0] <== temp * ivc_input[0] + ivc_input[0] + 5; -} - -component main {public [ivc_input]} = Example(); diff --git a/experimental-frontends/src/circom/test_folder/no_external_inputs.circom b/experimental-frontends/src/circom/test_folder/no_external_inputs.circom deleted file mode 100644 index 258121fb6..000000000 --- a/experimental-frontends/src/circom/test_folder/no_external_inputs.circom +++ /dev/null @@ -1,23 +0,0 @@ -pragma circom 2.0.3; - -include "./circuits/is_zero.circom"; - -template NoExternalInputs () { - signal input ivc_input[3]; - signal output ivc_output[3]; - - component check_input = IsZero(); - check_input.in <== ivc_input[0]; - check_input.out === 0; - - signal temp1; - signal temp2; - - temp1 <== ivc_input[0] * ivc_input[1]; - temp2 <== temp1 * ivc_input[2]; - ivc_output[0] <== temp1 * ivc_input[0]; - ivc_output[1] <== temp1 * ivc_input[1] + temp1; - ivc_output[2] <== temp1 * ivc_input[2] + temp2; -} - -component main {public [ivc_input]} = NoExternalInputs(); diff --git a/experimental-frontends/src/circom/test_folder/with_external_inputs.circom b/experimental-frontends/src/circom/test_folder/with_external_inputs.circom deleted file mode 100644 index 8614de0d2..000000000 --- a/experimental-frontends/src/circom/test_folder/with_external_inputs.circom +++ /dev/null @@ -1,22 +0,0 @@ -pragma circom 2.0.3; - -include "./circuits/is_zero.circom"; - -template WithExternalInputs () { - signal input ivc_input[1]; - signal input external_inputs[2]; - signal output ivc_output[1]; - - component check_input = IsZero(); - check_input.in <== ivc_input[0]; - check_input.out === 0; - - signal temp1; - signal temp2; - - temp1 <== ivc_input[0] * ivc_input[0]; - temp2 <== ivc_input[0] * external_inputs[0]; - ivc_output[0] <== temp1 * ivc_input[0] + temp2 + external_inputs[1]; -} - -component main {public [ivc_input]} = WithExternalInputs(); \ No newline at end of file diff --git a/experimental-frontends/src/circom/utils.rs b/experimental-frontends/src/circom/utils.rs deleted file mode 100644 index 6ea48149e..000000000 --- a/experimental-frontends/src/circom/utils.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::{fs::File, io::Cursor, path::PathBuf}; - -use ark_circom::{ - circom::{r1cs_reader, R1CS}, - WitnessCalculator, -}; -use ark_ff::PrimeField; -use ark_serialize::Read; -use num_bigint::BigInt; -use wasmer::{Module, Store}; - -use folding_schemes::{utils::PathOrBin, Error}; - -// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses -// based on file paths to Circom's .r1cs and .wasm. -#[derive(Clone, Debug)] -pub struct CircomWrapper { - r1csfile_bytes: Vec, - wasmfile_bytes: Vec, -} - -impl CircomWrapper { - // Creates a new instance of the CircomWrapper with the file paths. - pub fn new(r1cs: PathOrBin, wasm: PathOrBin) -> Result { - match (r1cs, wasm) { - (PathOrBin::Path(r1cs_path), PathOrBin::Path(wasm_path)) => { - Self::new_from_path(r1cs_path, wasm_path) - } - (PathOrBin::Bin(r1cs_bin), PathOrBin::Bin(wasm_bin)) => Ok(Self { - r1csfile_bytes: r1cs_bin, - wasmfile_bytes: wasm_bin, - }), - _ => unreachable!("You should pass the same enum branch for both inputs"), - } - } - - // Creates a new instance of the CircomWrapper with the file paths. - fn new_from_path(r1cs_file_path: PathBuf, wasm_file_path: PathBuf) -> Result { - let mut file = File::open(r1cs_file_path)?; - let metadata = File::metadata(&file)?; - let mut r1csfile_bytes = vec![0; metadata.len() as usize]; - file.read_exact(&mut r1csfile_bytes)?; - - let mut file = File::open(wasm_file_path)?; - let metadata = File::metadata(&file)?; - let mut wasmfile_bytes = vec![0; metadata.len() as usize]; - file.read_exact(&mut wasmfile_bytes)?; - - Ok(CircomWrapper { - r1csfile_bytes, - wasmfile_bytes, - }) - } - - // Aggregated function to obtain R1CS and witness from Circom. - pub fn extract_r1cs_and_witness( - &self, - inputs: Vec<(String, Vec)>, - ) -> Result<(R1CS, Option>), Error> { - // Extracts the R1CS - let r1cs_file = r1cs_reader::R1CSFile::new(Cursor::new(&self.r1csfile_bytes))?; - let r1cs = r1cs_reader::R1CS::from(r1cs_file); - - // Extracts the witness vector - let witness_vec = self.extract_witness(inputs)?; - - Ok((r1cs, Some(witness_vec))) - } - - pub fn extract_r1cs(&self) -> Result, Error> { - let r1cs_file = r1cs_reader::R1CSFile::new(Cursor::new(&self.r1csfile_bytes))?; - let mut r1cs = r1cs_reader::R1CS::from(r1cs_file); - r1cs.wire_mapping = None; - Ok(r1cs) - } - - // Extracts the witness vector as a vector of PrimeField elements. - pub fn extract_witness( - &self, - inputs: Vec<(String, Vec)>, - ) -> Result, Error> { - let witness_bigint = self.calculate_witness(inputs)?; - - witness_bigint - .into_iter() - .map(|big_int| { - big_int.to_biguint().map(F::from).ok_or_else(|| { - Error::ConversionError( - "BigInt".into(), - "BigUint".into(), - "BigInt is negative".into(), - ) - }) - }) - .collect() - } - - // Calculates the witness given the Wasm filepath and inputs. - pub fn calculate_witness( - &self, - inputs: Vec<(String, Vec)>, - ) -> Result, Error> { - let mut store = Store::default(); - let module = Module::new(&store, &self.wasmfile_bytes).map_err(|e| { - Error::WitnessCalculationError(format!("Failed to create Wasm module: {e}")) - })?; - let mut calculator = WitnessCalculator::from_module(&mut store, module).map_err(|e| { - Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {e}")) - })?; - calculator - .calculate_witness(&mut store, inputs, true) - .map_err(|e| { - Error::WitnessCalculationError(format!("Failed to calculate witness: {e}")) - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ark_bn254::Fr; - use ark_circom::circom::{CircomBuilder, CircomConfig}; - use ark_circom::CircomCircuit; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - - //To generate .r1cs and .wasm files, run the below command in the terminal. - //bash ./frontends/src/circom/test_folder/compile.sh - - // Test the satisfication by using the CircomBuilder of circom-compat - #[test] - fn test_circombuilder_satisfied() -> Result<(), Error> { - let cfg = CircomConfig::::new( - "./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm", - "./src/circom/test_folder/cubic_circuit.r1cs", - ) - .unwrap(); - let mut builder = CircomBuilder::new(cfg); - builder.push_input("ivc_input", 3); - - let circom = builder.build().unwrap(); - let cs = ConstraintSystem::::new_ref(); - circom.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - Ok(()) - } - - // Test the satisfication by using the CircomWrapper - #[test] - fn test_extract_r1cs_and_witness() -> Result<(), Error> { - let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs"); - let wasm_path = - PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - - let inputs = vec![("ivc_input".to_string(), vec![BigInt::from(3)])]; - let wrapper = CircomWrapper::new(r1cs_path.into(), wasm_path.into())?; - - let (r1cs, witness) = wrapper.extract_r1cs_and_witness(inputs)?; - - let cs = ConstraintSystem::::new_ref(); - - let circom_circuit = CircomCircuit { r1cs, witness }; - - circom_circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/experimental-frontends/src/lib.rs b/experimental-frontends/src/lib.rs deleted file mode 100644 index da1a1786f..000000000 --- a/experimental-frontends/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod circom; -pub mod noir; -pub mod noname; -pub mod utils; diff --git a/experimental-frontends/src/noir/bridge.rs b/experimental-frontends/src/noir/bridge.rs deleted file mode 100644 index e46bf0f5f..000000000 --- a/experimental-frontends/src/noir/bridge.rs +++ /dev/null @@ -1,154 +0,0 @@ -// From https://github.com/dmpierre/arkworks_backend/tree/feat/sonobe-integration -use std::collections::{BTreeMap, HashMap}; -use std::convert::TryInto; - -use acvm::acir::{ - acir_field::GenericFieldElement, - circuit::{Circuit, Opcode, PublicInputs}, - native_types::{Expression, Witness, WitnessMap}, -}; -use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::alloc::AllocVar; -use ark_r1cs_std::fields::fp::FpVar; -use ark_relations::{ - gr1cs::{ - ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable, - }, - lc, -}; - -// AcirCircuit and AcirArithGate are structs that arkworks can synthesise. -// -// The difference between these structures and the ACIR structure that the compiler uses is the following: -// - The compilers ACIR struct is currently fixed to bn254 -// - These structures only support arithmetic gates, while the compiler has other -// gate types. These can be added later once the backend knows how to deal with things like XOR -// or once ACIR is taught how to do convert these black box functions to Arithmetic gates. -// -// XXX: Ideally we want to implement `ConstraintSynthesizer` on ACIR however -// this does not seem possible since ACIR is juts a description of the constraint system and the API Asks for prover values also. -// -// Perfect API would look like: -// - index(srs, circ) -// - prove(index_pk, prover_values, rng) -// - verify(index_vk, verifier, rng) -#[derive(Clone)] -pub struct AcirCircuitSonobe<'a, F: Field + PrimeField> { - pub(crate) gates: Vec>>, - pub(crate) public_inputs: PublicInputs, - pub(crate) values: BTreeMap, - pub already_assigned_witnesses: HashMap>, -} - -impl<'a, ConstraintF: Field + PrimeField> ConstraintSynthesizer - for AcirCircuitSonobe<'a, ConstraintF> -{ - fn generate_constraints( - self, - cs: ConstraintSystemRef, - ) -> Result<(), SynthesisError> { - let mut variables = Vec::with_capacity(self.values.len()); - - // First create all of the witness indices by adding the values into the constraint system - for (i, val) in self.values.iter() { - let var = if self.already_assigned_witnesses.contains_key(i) { - let var = self.already_assigned_witnesses.get(i).unwrap(); - if let FpVar::Var(allocated) = var { - allocated.variable - } else { - return Err(SynthesisError::Unsatisfiable); - } - } else if self.public_inputs.contains(i.0.try_into().unwrap()) { - cs.new_witness_variable(|| Ok(*val))? - } else { - cs.new_witness_variable(|| Ok(*val))? - }; - variables.push(var); - } - - // Now iterate each gate and add it to the constraint system - for gate in self.gates { - let mut arith_gate = LinearCombination::::new(); - - // Process mul terms - for mul_term in gate.mul_terms { - let coeff = mul_term.0; - let left_val = self.values[&mul_term.1]; - let right_val = self.values[&mul_term.2]; - - let out_val = left_val * right_val; - let out_var = FpVar::::new_witness(cs.clone(), || Ok(out_val))?; - // out var can't be a type different from FpVar::Var - if let FpVar::Var(allocated) = out_var { - arith_gate += (coeff.into_repr(), allocated.variable); - } - } - - // Process Add terms - for add_term in gate.linear_combinations { - let coeff = add_term.0; - let add_var = &variables[add_term.1.as_usize()]; - arith_gate += (coeff.into_repr(), *add_var); - } - - // Process constant term - arith_gate += (gate.q_c.into_repr(), Variable::One); - - cs.enforce_r1cs_constraint(|| lc!() + Variable::One, || arith_gate, || lc!())?; - } - - Ok(()) - } -} - -impl<'a, F: PrimeField> - From<( - &Circuit>, - WitnessMap>, - )> for AcirCircuitSonobe<'a, F> -{ - fn from( - circ_val: ( - &Circuit>, - WitnessMap>, - ), - ) -> AcirCircuitSonobe<'a, F> { - // Currently non-arithmetic gates are not supported - // so we extract all of the arithmetic gates only - let (circuit, witness_map) = circ_val; - - let public_inputs = circuit.public_inputs(); - let arith_gates: Vec<_> = circuit - .opcodes - .iter() - .filter_map(|opcode| { - if let Opcode::AssertZero(code) = opcode { - Some(code.clone()) - } else { - None - } - }) - .collect(); - - let num_variables: usize = circuit.num_vars().try_into().unwrap(); - - let values: BTreeMap = (0..num_variables) - .map(|witness_index| { - // Get the value if it exists. If i does not, then we fill it with the zero value - let witness = Witness(witness_index as u32); - let value = witness_map - .get(&witness) - .map_or(F::zero(), |field| field.into_repr()); - - (witness, value) - }) - .collect(); - - AcirCircuitSonobe { - gates: arith_gates, - values, - public_inputs, - already_assigned_witnesses: HashMap::new(), - } - } -} diff --git a/experimental-frontends/src/noir/mod.rs b/experimental-frontends/src/noir/mod.rs deleted file mode 100644 index d590758c0..000000000 --- a/experimental-frontends/src/noir/mod.rs +++ /dev/null @@ -1,215 +0,0 @@ -use acvm::{ - acir::{ - acir_field::GenericFieldElement, - circuit::{Circuit, Program}, - native_types::{Witness as AcvmWitness, WitnessMap}, - }, - blackbox_solver::StubbedBlackBoxSolver, - pwg::ACVM, -}; -use ark_ff::PrimeField; -use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, GR1CSVar}; -use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use serde::{self, Deserialize, Serialize}; -use std::collections::HashMap; - -use self::bridge::AcirCircuitSonobe; -use crate::utils::{VecF, VecFpVar}; -use folding_schemes::{frontend::FCircuit, utils::PathOrBin, Error}; - -mod bridge; - -#[derive(Clone, Debug)] -pub struct NoirFCircuit { - pub circuit: Circuit>, -} - -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct ProgramArtifactGeneric { - #[serde( - serialize_with = "Program::serialize_program_base64", - deserialize_with = "Program::deserialize_program_base64" - )] - pub bytecode: Program>, -} - -impl FCircuit for NoirFCircuit { - type Params = PathOrBin; - type ExternalInputs = VecF; - type ExternalInputsVar = VecFpVar; - - fn new(source: Self::Params) -> Result { - let input_string = match source { - PathOrBin::Path(path) => { - let file_path = path.with_extension("json"); - std::fs::read(&file_path).map_err(|_| Error::Other(format!("{} is not a valid path\nRun either `nargo compile` to generate missing build artifacts or `nargo prove` to construct a proof", file_path.display())))? - } - PathOrBin::Bin(bin) => bin, - }; - let program: ProgramArtifactGeneric = serde_json::from_slice(&input_string) - .map_err(|err| Error::JSONSerdeError(err.to_string()))?; - let circuit: Circuit> = program.bytecode.functions[0].clone(); - let ivc_input_length = circuit.public_parameters.0.len(); - let ivc_return_length = circuit.return_values.0.len(); - - if ivc_input_length != ivc_return_length { - return Err(Error::NotSameLength( - "IVC input: ".to_string(), - ivc_input_length, - "IVC output: ".to_string(), - ivc_return_length, - )); - } - - Ok(NoirFCircuit { circuit }) - } - - fn state_len(&self) -> usize { - SL - } - - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - external_inputs: Self::ExternalInputsVar, // inputs that are not part of the state - ) -> Result>, SynthesisError> { - let mut acvm = ACVM::new( - &StubbedBlackBoxSolver, - &self.circuit.opcodes, - WitnessMap::new(), - &[], - &[], - ); - - let mut already_assigned_witness_values = HashMap::new(); - - self.circuit.public_parameters.0.iter().for_each(|witness| { - let idx: usize = witness.as_usize(); - let witness = AcvmWitness(witness.witness_index()); - already_assigned_witness_values.insert(witness, &z_i[idx]); - - let val = z_i[idx].value().unwrap_or_default(); - - let f = GenericFieldElement::::from_repr(val); - acvm.overwrite_witness(witness, f); - }); - - // write witness values for external_inputs - self.circuit.private_parameters.iter().for_each(|witness| { - let idx = witness.as_usize() - z_i.len(); - let witness = AcvmWitness(witness.witness_index()); - already_assigned_witness_values.insert(witness, &external_inputs.0[idx]); - - let val = external_inputs.0[idx].value().unwrap_or_default(); - - let f = GenericFieldElement::::from_repr(val); - acvm.overwrite_witness(witness, f); - }); - - // computes the witness - let _ = acvm.solve(); - let witness_map = acvm.finalize(); - - // get the z_{i+1} output state - let assigned_z_i1 = self - .circuit - .return_values - .0 - .iter() - .map(|witness| { - let noir_field_element = witness_map - .get(witness) - .ok_or(SynthesisError::AssignmentMissing)?; - FpVar::::new_witness(cs.clone(), || Ok(noir_field_element.into_repr())) - }) - .collect::>, SynthesisError>>()?; - - // initialize circuit and set already assigned values - let mut acir_circuit = AcirCircuitSonobe::from((&self.circuit, witness_map)); - acir_circuit.already_assigned_witnesses = already_assigned_witness_values; - - acir_circuit.generate_constraints(cs.clone())?; - - Ok(assigned_z_i1) - } -} - -#[cfg(test)] -mod tests { - use ark_bn254::Fr; - use ark_ff::PrimeField; - use ark_r1cs_std::GR1CSVar; - use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; - use ark_relations::gr1cs::ConstraintSystem; - use folding_schemes::{frontend::FCircuit, Error}; - use std::env; - - use crate::noir::NoirFCircuit; - use crate::utils::VecFpVar; - - /// Native implementation of `src/noir/test_folder/test_circuit` - fn external_inputs_step_native(z_i: Vec, external_inputs: Vec) -> Vec { - let xx = external_inputs[0] * z_i[0]; - let yy = external_inputs[1] * z_i[1]; - vec![xx, yy] - } - - #[test] - fn test_step_native() -> Result<(), Error> { - let inputs = vec![Fr::from(2), Fr::from(5)]; - let res = external_inputs_step_native(inputs.clone(), inputs); - assert_eq!(res, vec![Fr::from(4), Fr::from(25)]); - Ok(()) - } - - #[test] - fn test_step_constraints() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - let cur_path = env::current_dir()?; - // external inputs length: 2, state length: 2 - let noirfcircuit = NoirFCircuit::::new( - cur_path - .join("src/noir/test_folder/test_circuit/target/test_circuit.json") - .into(), - )?; - let inputs = vec![Fr::from(2), Fr::from(5)]; - let z_i = Vec::>::new_witness(cs.clone(), || Ok(inputs.clone()))?; - let external_inputs = Vec::>::new_witness(cs.clone(), || Ok(inputs))?; - let output = noirfcircuit.generate_step_constraints( - cs.clone(), - 0, - z_i, - VecFpVar(external_inputs), - )?; - assert_eq!(output[0].value()?, Fr::from(4)); - assert_eq!(output[1].value()?, Fr::from(25)); - Ok(()) - } - - #[test] - fn test_step_constraints_no_external_inputs() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - let cur_path = env::current_dir()?; - // external inputs length: 0, state length: 2 - let noirfcircuit = NoirFCircuit::::new( - cur_path - .join("src/noir/test_folder/test_no_external_inputs/target/test_no_external_inputs.json") - .into() -) - ?; - let inputs = vec![Fr::from(2), Fr::from(5)]; - let z_i = Vec::>::new_witness(cs.clone(), || Ok(inputs.clone()))?; - let external_inputs = vec![]; - let output = noirfcircuit.generate_step_constraints( - cs.clone(), - 0, - z_i, - VecFpVar(external_inputs), - )?; - assert_eq!(output[0].value()?, Fr::from(4)); - assert_eq!(output[1].value()?, Fr::from(25)); - Ok(()) - } -} diff --git a/experimental-frontends/src/noir/test_folder/compile.sh b/experimental-frontends/src/noir/test_folder/compile.sh deleted file mode 100755 index 598a7087a..000000000 --- a/experimental-frontends/src/noir/test_folder/compile.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -CUR_DIR=$(pwd) -TEST_PATH="${CUR_DIR}/experimental-frontends/src/noir/test_folder/" -for test_path in test_circuit test_mimc test_no_external_inputs; do - FOLDER="${TEST_PATH}${test_path}/" - cd ${FOLDER} && nargo compile && cd ${TEST_PATH} -done diff --git a/experimental-frontends/src/noir/test_folder/test_circuit/Nargo.toml b/experimental-frontends/src/noir/test_folder/test_circuit/Nargo.toml deleted file mode 100644 index 69429a856..000000000 --- a/experimental-frontends/src/noir/test_folder/test_circuit/Nargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "test_circuit" -type = "bin" -authors = [""] -compiler_version = ">=0.30.0" - -[dependencies] - diff --git a/experimental-frontends/src/noir/test_folder/test_circuit/src/main.nr b/experimental-frontends/src/noir/test_folder/test_circuit/src/main.nr deleted file mode 100644 index 4e0c90a51..000000000 --- a/experimental-frontends/src/noir/test_folder/test_circuit/src/main.nr +++ /dev/null @@ -1,11 +0,0 @@ -fn main(public_inputs: pub [Field; 2], private_inputs: [Field; 2]) -> pub [Field; 2]{ - let a_pub = public_inputs[0]; - let b_pub = public_inputs[1]; - let c_private = private_inputs[0]; - let d_private = private_inputs[1]; - - let out_1 = a_pub * c_private; - let out_2 = b_pub * d_private; - - [out_1, out_2] -} diff --git a/experimental-frontends/src/noir/test_folder/test_mimc/Nargo.toml b/experimental-frontends/src/noir/test_folder/test_mimc/Nargo.toml deleted file mode 100644 index 2c1990941..000000000 --- a/experimental-frontends/src/noir/test_folder/test_mimc/Nargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "test_mimc" -type = "bin" -authors = [""] -compiler_version = ">=0.30.0" - -[dependencies] -mimc = { tag = "v0.1.0", git = "https://github.com/noir-lang/mimc" } diff --git a/experimental-frontends/src/noir/test_folder/test_mimc/src/main.nr b/experimental-frontends/src/noir/test_folder/test_mimc/src/main.nr deleted file mode 100644 index 9956da7b0..000000000 --- a/experimental-frontends/src/noir/test_folder/test_mimc/src/main.nr +++ /dev/null @@ -1,4 +0,0 @@ -pub fn main(x: pub [Field; 1]) -> pub Field { - let hash = mimc::mimc_bn254(x); - hash -} diff --git a/experimental-frontends/src/noir/test_folder/test_no_external_inputs/Nargo.toml b/experimental-frontends/src/noir/test_folder/test_no_external_inputs/Nargo.toml deleted file mode 100644 index 22373d315..000000000 --- a/experimental-frontends/src/noir/test_folder/test_no_external_inputs/Nargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "test_no_external_inputs" -type = "bin" -authors = [""] -compiler_version = ">=0.30.0" - -[dependencies] - diff --git a/experimental-frontends/src/noir/test_folder/test_no_external_inputs/src/main.nr b/experimental-frontends/src/noir/test_folder/test_no_external_inputs/src/main.nr deleted file mode 100644 index 26f6cd7f3..000000000 --- a/experimental-frontends/src/noir/test_folder/test_no_external_inputs/src/main.nr +++ /dev/null @@ -1,9 +0,0 @@ -fn main(public_inputs: pub [Field; 2]) -> pub [Field; 2]{ - let a_pub = public_inputs[0]; - let b_pub = public_inputs[1]; - let out_1 = a_pub * a_pub; - let out_2 = b_pub * b_pub; - - [out_1, out_2] -} - diff --git a/experimental-frontends/src/noname/bridge.rs b/experimental-frontends/src/noname/bridge.rs deleted file mode 100644 index 8c53a1fa8..000000000 --- a/experimental-frontends/src/noname/bridge.rs +++ /dev/null @@ -1,123 +0,0 @@ -// From https://github.com/dmpierre/ark-noname/tree/feat/sonobe-integration -use std::collections::HashMap; - -use ark_ff::PrimeField; -use ark_r1cs_std::fields::fp::FpVar; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable, -}; -use noname::backends::{ - r1cs::{GeneratedWitness, LinearCombination as NoNameLinearCombination, R1CS}, - BackendField, -}; -use noname::witness::CompiledCircuit; -use num_bigint::BigUint; - -pub struct NonameSonobeCircuit<'a, 'b, 'c, F: PrimeField, BF: BackendField> { - pub compiled_circuit: CompiledCircuit>, - pub witness: GeneratedWitness, - pub assigned_z_i: &'a Vec>, - pub assigned_external_inputs: &'b Vec>, - pub assigned_z_i1: &'c Vec>, -} - -impl<'a, 'b, 'c, F: PrimeField, BF: BackendField> ConstraintSynthesizer - for NonameSonobeCircuit<'a, 'b, 'c, F, BF> -{ - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let public_io_length = self.assigned_z_i.len() * 2; - let external_inputs_len = self.assigned_external_inputs.len(); - - // we need to map noname r1cs indexes with sonobe - let mut idx_to_var = HashMap::new(); - - // for both the z_i, z_i1 vectors, we assume that they have been assigned in the order - // with which it will appear in the witness - let mut z_i_pointer = 0; - let mut z_i1_pointer = 0; - let mut external_inputs_pointer = 0; - - // arkworks assigns by default the 1 constant - // assumes witness is: [1, public_outputs, public_inputs, private_inputs, aux] - let witness_size = self.witness.witness.len(); - for idx in 1..witness_size { - if idx <= public_io_length { - if idx <= self.assigned_z_i.len() { - // in noname public outputs come first - // we are in the case of public outputs (z_i1 vector) - // those have already been assigned at specific indexes by sonobe - let var = match &self.assigned_z_i1[z_i1_pointer] { - FpVar::Var(allocated_fp) => allocated_fp.variable, - _ => return Err(SynthesisError::Unsatisfiable), - }; - idx_to_var.insert(idx, var); - z_i1_pointer += 1; - } else { - // we are in the case of public inputs (z_i values) - // those have already been assigned at specific indexes by sonobe - let var = match &self.assigned_z_i[z_i_pointer] { - FpVar::Var(allocated_fp) => allocated_fp.variable, - _ => return Err(SynthesisError::Unsatisfiable), - }; - idx_to_var.insert(idx, var); - z_i_pointer += 1; - } - } else if idx <= public_io_length + external_inputs_len { - // we are in the case of external inputs - // those have already been assigned at specific indexes - let var = match &self.assigned_external_inputs[external_inputs_pointer] { - FpVar::Var(allocated_fp) => allocated_fp.variable, - _ => return Err(SynthesisError::Unsatisfiable), - }; - idx_to_var.insert(idx, var); - external_inputs_pointer += 1; - } else { - // we are in the case of auxiliary private inputs - // we need to assign those - let value: BigUint = Into::into(self.witness.witness[idx]); - let field_element = F::from(value); - let var = cs.new_witness_variable(|| Ok(field_element))?; - idx_to_var.insert(idx, var); - } - } - - if (z_i_pointer != self.assigned_z_i.len()) - || (external_inputs_pointer != self.assigned_external_inputs.len()) - { - return Err(SynthesisError::AssignmentMissing); - } - let make_index = |index: usize| match index == 0 { - true => Ok(Variable::One), - false => { - let var = idx_to_var - .get(&index) - .ok_or(SynthesisError::AssignmentMissing)?; - Ok(var.to_owned()) - } - }; - - let make_lc = |lc_data: NoNameLinearCombination| { - let mut lc = LinearCombination::::zero(); - for (cellvar, coeff) in lc_data.terms.into_iter() { - let idx = make_index(cellvar.index)?; - let coeff = F::from(Into::::into(coeff)); - - lc += (coeff, idx) - } - - // add constant - let constant = F::from(Into::::into(lc_data.constant)); - lc += (constant, make_index(0)?); - Ok(lc) - }; - - for constraint in self.compiled_circuit.circuit.backend.constraints { - let lc_a = make_lc(constraint.a)?; - let lc_b = make_lc(constraint.b)?; - let lc_c = make_lc(constraint.c)?; - cs.enforce_r1cs_constraint(|| lc_a, || lc_b, || lc_c)?; - } - - Ok(()) - } -} diff --git a/experimental-frontends/src/noname/mod.rs b/experimental-frontends/src/noname/mod.rs deleted file mode 100644 index 248734e1a..000000000 --- a/experimental-frontends/src/noname/mod.rs +++ /dev/null @@ -1,187 +0,0 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::alloc::AllocVar; -use ark_r1cs_std::fields::fp::FpVar; -use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use noname::backends::{r1cs::R1CS as R1CSNoname, BackendField}; -use noname::witness::CompiledCircuit; -use num_bigint::BigUint; -use std::marker::PhantomData; - -use folding_schemes::{frontend::FCircuit, Error}; - -pub mod bridge; -pub mod utils; -use crate::utils::{VecF, VecFpVar}; - -use self::bridge::NonameSonobeCircuit; -use self::utils::{compile_source_code, NonameInputs}; - -// `L` indicates the length of the ExternalInputs vector of field elements. -#[derive(Debug, Clone)] -pub struct NonameFCircuit { - pub circuit: CompiledCircuit>, - _f: PhantomData, -} - -impl FCircuit - for NonameFCircuit -{ - type Params = String; - type ExternalInputs = VecF; - type ExternalInputsVar = VecFpVar; - - fn new(code: Self::Params) -> Result { - let compiled_circuit = compile_source_code::(&code).map_err(|_| { - Error::Other("Encountered an error while compiling a noname circuit".to_owned()) - })?; - Ok(NonameFCircuit { - circuit: compiled_circuit, - _f: PhantomData, - }) - } - - fn state_len(&self) -> usize { - SL - } - - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let wtns_external_inputs = - NonameInputs::from_fpvars((&external_inputs.0, "external_inputs".to_string())); - let wtns_ivc_inputs = NonameInputs::from_fpvars((&z_i, "ivc_inputs".to_string())); - let noname_witness = self - .circuit - .generate_witness(wtns_ivc_inputs.0, wtns_external_inputs.0) - .map_err(|_| SynthesisError::Unsatisfiable)?; - let z_i1_end_index = z_i.len() + 1; - let assigned_z_i1: Vec> = (1..z_i1_end_index) - .map(|idx| -> Result, SynthesisError> { - // the assigned zi1 is of the same size than the initial zi and is located in the - // output of the witness vector - // we prefer to assign z_i1 here since (1) we have to return it, (2) we can't return - // anything with the `generate_constraints` method used below - let value: BigUint = Into::into(noname_witness.witness[idx]); - let field_element = F::from(value); - FpVar::::new_witness(cs.clone(), || Ok(field_element)) - }) - .collect::>, SynthesisError>>()?; - - let noname_circuit = NonameSonobeCircuit { - compiled_circuit: self.circuit.clone(), - witness: noname_witness, - assigned_z_i: &z_i, - assigned_external_inputs: &external_inputs.0, - assigned_z_i1: &assigned_z_i1, - }; - noname_circuit.generate_constraints(cs.clone())?; - - Ok(assigned_z_i1) - } -} - -#[cfg(test)] -mod tests { - use ark_bn254::Fr; - use ark_ff::PrimeField; - use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - use noname::backends::r1cs::R1csBn254Field; - - use folding_schemes::{frontend::FCircuit, Error}; - - use super::NonameFCircuit; - use crate::utils::VecFpVar; - - /// Native implementation of `NONAME_CIRCUIT_EXTERNAL_INPUTS` - fn external_inputs_step_native(z_i: Vec, external_inputs: Vec) -> Vec { - let xx = external_inputs[0] + z_i[0]; - let yy = external_inputs[1] * z_i[1]; - assert_eq!(yy, xx); - vec![xx, yy] - } - - const NONAME_CIRCUIT_EXTERNAL_INPUTS: &str = - "fn main(pub ivc_inputs: [Field; 2], external_inputs: [Field; 2]) -> [Field; 2] { - let xx = external_inputs[0] + ivc_inputs[0]; - let yy = external_inputs[1] * ivc_inputs[1]; - assert_eq(yy, xx); - return [xx, yy]; -}"; - - const NONAME_CIRCUIT_NO_EXTERNAL_INPUTS: &str = - "fn main(pub ivc_inputs: [Field; 2]) -> [Field; 2] { - let out = ivc_inputs[0] * ivc_inputs[1]; - return [out, ivc_inputs[1]]; -}"; - - #[test] - fn test_step_native() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - let params = NONAME_CIRCUIT_EXTERNAL_INPUTS.to_owned(); - // state length = 2, external inputs length = 2 - let circuit = NonameFCircuit::::new(params)?; - let inputs_public = vec![Fr::from(2), Fr::from(5)]; - let inputs_private = vec![Fr::from(8), Fr::from(2)]; - - let ivc_inputs_var = - Vec::>::new_witness(cs.clone(), || Ok(inputs_public.clone()))?; - let external_inputs_var = - Vec::>::new_witness(cs.clone(), || Ok(inputs_private.clone()))?; - - let z_i1 = circuit.generate_step_constraints( - cs.clone(), - 0, - ivc_inputs_var, - VecFpVar(external_inputs_var), - )?; - let z_i1_native = external_inputs_step_native(inputs_public, inputs_private); - - assert_eq!(z_i1[0].value()?, z_i1_native[0]); - assert_eq!(z_i1[1].value()?, z_i1_native[1]); - Ok(()) - } - - #[test] - fn test_step_constraints() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - let params = NONAME_CIRCUIT_EXTERNAL_INPUTS.to_owned(); - // state length = 2, external inputs length = 2 - let circuit = NonameFCircuit::::new(params)?; - let inputs_public = vec![Fr::from(2), Fr::from(5)]; - let inputs_private = vec![Fr::from(8), Fr::from(2)]; - - let ivc_inputs_var = Vec::>::new_witness(cs.clone(), || Ok(inputs_public))?; - let external_inputs_var = Vec::>::new_witness(cs.clone(), || Ok(inputs_private))?; - - let z_i1 = circuit.generate_step_constraints( - cs.clone(), - 0, - ivc_inputs_var, - VecFpVar(external_inputs_var), - )?; - assert!(cs.is_satisfied()?); - assert_eq!(z_i1[0].value()?, Fr::from(10_u8)); - assert_eq!(z_i1[1].value()?, Fr::from(10_u8)); - Ok(()) - } - - #[test] - fn test_generate_constraints_no_external_inputs() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - let params = NONAME_CIRCUIT_NO_EXTERNAL_INPUTS.to_owned(); - let inputs_public = vec![Fr::from(2), Fr::from(5)]; - - let ivc_inputs_var = Vec::>::new_witness(cs.clone(), || Ok(inputs_public))?; - - // state length = 2, external inputs length = 0 - let f_circuit = NonameFCircuit::::new(params)?; - f_circuit.generate_step_constraints(cs.clone(), 0, ivc_inputs_var, VecFpVar(vec![]))?; - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/experimental-frontends/src/noname/utils.rs b/experimental-frontends/src/noname/utils.rs deleted file mode 100644 index b31180138..000000000 --- a/experimental-frontends/src/noname/utils.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::collections::HashMap; - -use ark_ff::PrimeField; -use ark_r1cs_std::{fields::fp::FpVar, GR1CSVar}; -use folding_schemes::Error; -use noname::{ - backends::{r1cs::R1CS, BackendField}, - circuit_writer::CircuitWriter, - compiler::{typecheck_next_file, Sources}, - inputs::JsonInputs, - type_checker::TypeChecker, - witness::CompiledCircuit, -}; -use serde_json::json; - -pub struct NonameInputs(pub JsonInputs); - -impl From<(&Vec, String)> for NonameInputs { - fn from(value: (&Vec, String)) -> Self { - let (values, key) = value; - let mut inputs = HashMap::new(); - if values.is_empty() { - NonameInputs(JsonInputs(inputs)) - } else { - let field_elements: Vec = values - .iter() - .map(|value| { - if value.is_zero() { - "0".to_string() - } else { - value.to_string() - } - }) - .collect(); - inputs.insert(key, json!(field_elements)); - NonameInputs(JsonInputs(inputs)) - } - } -} - -impl NonameInputs { - pub fn from_fpvars(value: (&Vec>, String)) -> Self { - let (values, key) = value; - let mut inputs = HashMap::new(); - if !values.is_empty() { - let field_elements: Vec = values - .iter() - .map(|var| var.value().unwrap_or_default().to_string()) - .collect::>(); - inputs.insert(key, json!(field_elements)); - } - NonameInputs(JsonInputs(inputs)) - } -} - -// from: https://github.com/zksecurity/noname/blob/main/src/tests/modules.rs -// TODO: this will not work in the case where we are using libraries -pub fn compile_source_code( - code: &str, -) -> Result>, Error> { - let mut sources = Sources::new(); - - // parse the transitive dependency - let mut checker = TypeChecker::>::new(); - let _ = typecheck_next_file( - &mut checker, - None, - &mut sources, - "main.no".to_string(), - code.to_string(), - 0, - ) - .unwrap(); - let r1cs = R1CS::::new(); - // compile - CircuitWriter::generate_circuit(checker, r1cs).map_err(|_| { - Error::Other("Encountered an error while compiling a noname circuit".to_owned()) - }) -} diff --git a/experimental-frontends/src/utils.rs b/experimental-frontends/src/utils.rs deleted file mode 100644 index ae6d52eca..000000000 --- a/experimental-frontends/src/utils.rs +++ /dev/null @@ -1,38 +0,0 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - fields::fp::FpVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::fmt::Debug; -use core::borrow::Borrow; - -#[derive(Clone, Debug)] -pub struct VecF(pub Vec); -impl Default for VecF { - fn default() -> Self { - VecF(vec![F::zero(); L]) - } -} -#[derive(Clone, Debug)] -pub struct VecFpVar(pub Vec>); -impl AllocVar, F> for VecFpVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let v = Vec::>::new_variable(cs.clone(), || Ok(val.borrow().0.clone()), mode)?; - - Ok(VecFpVar(v)) - }) - } -} -impl Default for VecFpVar { - fn default() -> Self { - VecFpVar(vec![FpVar::::Constant(F::zero()); L]) - } -} diff --git a/folding-schemes/Cargo.toml b/folding-schemes/Cargo.toml deleted file mode 100644 index 9dc674a5a..000000000 --- a/folding-schemes/Cargo.toml +++ /dev/null @@ -1,81 +0,0 @@ -[package] -name = "folding-schemes" -version = "0.1.0" -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -ark-ec = { workspace = true, features = ["parallel"] } -ark-ff = { workspace = true, features = ["parallel", "asm"] } -ark-poly = { workspace = true, features = ["parallel"] } -ark-std = { workspace = true, features = ["parallel"] } -ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh", "parallel"] } -ark-poly-commit = { workspace = true, features = ["parallel"] } -ark-relations = { workspace = true } -ark-r1cs-std = { workspace = true, features = ["parallel"] } -ark-snark = { workspace = true } -ark-serialize = { workspace = true } -ark-groth16 = { workspace = true, features = ["parallel"] } -ark-bn254 = { workspace = true } -ark-grumpkin = { workspace = true } -thiserror = { workspace = true } -rayon = { workspace = true } -num-bigint = { workspace = true } -num-integer = { workspace = true } -sha3 = { workspace = true } -log = { workspace = true } - -[dev-dependencies] -ark-pallas = { workspace = true, features = ["r1cs"] } -ark-vesta = { workspace = true, features = ["r1cs"] } -ark-bn254 = { workspace = true, features = ["r1cs"] } -ark-grumpkin = { workspace = true, features = ["r1cs"] } -# Note: do not use the MNTx_298 curves in practice due security reasons, here -# we only use them in the tests. -ark-mnt4-298 = { workspace = true, features = ["r1cs"] } -ark-mnt6-298 = { workspace = true, features = ["r1cs"] } -rand = { workspace = true } -num-bigint = { workspace = true, features = ["rand"] } - -# for benchmarks -criterion = { workspace = true } -pprof = { workspace = true, features = ["criterion", "flamegraph"] } - -# This allows the crate to be built when targeting WASM. -# See more at: https://docs.rs/getrandom/#webassembly-support -[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] -getrandom = { workspace = true, features = ["js"] } - -[features] -default = ["parallel"] -parallel = [] -light-test = [] - - -[[bench]] -name = "nova" -path = "../benches/nova.rs" -harness = false - -[[bench]] -name = "hypernova" -path = "../benches/hypernova.rs" -harness = false - -[[bench]] -name = "protogalaxy" -path = "../benches/protogalaxy.rs" -harness = false - -[[example]] -name = "sha256" -path = "../examples/sha256.rs" - -[[example]] -name = "multi_inputs" -path = "../examples/multi_inputs.rs" - -[[example]] -name = "external_inputs" -path = "../examples/external_inputs.rs" diff --git a/folding-schemes/src/arith/ccs/circuits.rs b/folding-schemes/src/arith/ccs/circuits.rs deleted file mode 100644 index fe1fdf1f3..000000000 --- a/folding-schemes/src/arith/ccs/circuits.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::CCS; -use crate::utils::gadgets::SparseMatrixVar; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - fields::fp::FpVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::borrow::Borrow; - -/// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. -#[derive(Debug, Clone)] -pub struct CCSMatricesVar { - // we only need native representation, so the constraint field==F - pub M: Vec>>, -} - -impl AllocVar, F> for CCSMatricesVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - _mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - let M: Vec>> = val - .borrow() - .M - .iter() - .map(|M| SparseMatrixVar::>::new_constant(cs.clone(), M.clone())) - .collect::>()?; - Ok(Self { M }) - }) - } -} diff --git a/folding-schemes/src/arith/ccs/mod.rs b/folding-schemes/src/arith/ccs/mod.rs deleted file mode 100644 index c1549d01e..000000000 --- a/folding-schemes/src/arith/ccs/mod.rs +++ /dev/null @@ -1,183 +0,0 @@ -use ark_ff::PrimeField; -use ark_std::log2; - -use crate::utils::vec::{ - hadamard, is_zero_vec, mat_vec_mul, vec_add, vec_scalar_mul, SparseMatrix, -}; -use crate::Error; - -use super::{r1cs::R1CS, ArithRelation}; -use super::{Arith, ArithSerializer}; - -pub mod circuits; - -/// CCS represents the Customizable Constraint Systems structure defined in -/// the [CCS paper](https://eprint.iacr.org/2023/552) -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct CCS { - /// m: number of rows in M_i (such that M_i \in F^{m, n}) - m: usize, - /// n = |z|, number of cols in M_i - n: usize, - /// l = |io|, size of public input/output - l: usize, - /// t = |M|, number of matrices - pub t: usize, - /// q = |c| = |S|, number of multisets - q: usize, - /// d: max degree in each variable - d: usize, - /// s = log(m), dimension of x - pub s: usize, - - /// vector of matrices - pub M: Vec>, - /// vector of multisets - pub S: Vec>, - /// vector of coefficients - pub c: Vec, -} - -impl CCS { - /// Evaluates the CCS relation at a given vector of assignments `z` - pub fn eval_at_z(&self, z: &[F]) -> Result, Error> { - let mut result = vec![F::zero(); self.m]; - - for i in 0..self.q { - // extract the needed M_j matrices out of S_i - let vec_M_j: Vec<&SparseMatrix> = self.S[i].iter().map(|j| &self.M[*j]).collect(); - - // complete the hadamard chain - let mut hadamard_result = vec![F::one(); self.m]; - for M_j in vec_M_j.into_iter() { - hadamard_result = hadamard(&hadamard_result, &mat_vec_mul(M_j, z)?)?; - } - - // multiply by the coefficient of this step - let c_M_j_z = vec_scalar_mul(&hadamard_result, &self.c[i]); - - // add it to the final vector - result = vec_add(&result, &c_M_j_z)?; - } - - Ok(result) - } -} - -impl Arith for CCS { - #[inline] - fn degree(&self) -> usize { - self.d - } - - #[inline] - fn n_constraints(&self) -> usize { - self.m - } - - #[inline] - fn n_variables(&self) -> usize { - self.n - } - - #[inline] - fn n_public_inputs(&self) -> usize { - self.l - } - - #[inline] - fn n_witnesses(&self) -> usize { - self.n_variables() - self.n_public_inputs() - 1 - } - - fn split_z(&self, z: &[P]) -> (Vec

, Vec

) { - (z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec()) - } -} - -impl, U: AsRef<[F]>> ArithRelation for CCS { - type Evaluation = Vec; - - fn eval_relation(&self, w: &W, u: &U) -> Result { - self.eval_at_z(&[&[F::one()], u.as_ref(), w.as_ref()].concat()) - } - - fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { - is_zero_vec(&e).then_some(()).ok_or(Error::NotSatisfied) - } -} - -impl ArithSerializer for CCS { - fn params_to_le_bytes(&self) -> Vec { - [ - (self.l as u64).to_le_bytes(), - (self.m as u64).to_le_bytes(), - (self.n as u64).to_le_bytes(), - (self.t as u64).to_le_bytes(), - (self.q as u64).to_le_bytes(), - (self.d as u64).to_le_bytes(), - ] - .concat() - } -} - -impl From> for CCS { - fn from(r1cs: R1CS) -> Self { - let m = r1cs.n_constraints(); - let n = r1cs.n_variables(); - CCS { - m, - n, - l: r1cs.n_public_inputs(), - s: log2(m) as usize, - t: 3, - q: 2, - d: r1cs.degree(), - - S: vec![vec![0, 1], vec![2]], - c: vec![F::one(), F::one().neg()], - M: vec![r1cs.A, r1cs.B, r1cs.C], - } - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::{ - arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z, get_test_z_split}, - utils::vec::is_zero_vec, - }; - use ark_pallas::Fr; - - pub fn get_test_ccs() -> CCS { - get_test_r1cs::().into() - } - pub fn get_test_z(input: usize) -> Vec { - r1cs_get_test_z(input) - } - - #[test] - fn test_eval_ccs_relation() -> Result<(), Error> { - let ccs = get_test_ccs::(); - let (_, x, mut w) = get_test_z_split(3); - - let f_w = ccs.eval_relation(&w, &x)?; - assert!(is_zero_vec(&f_w)); - - w[1] = Fr::from(111); - let f_w = ccs.eval_relation(&w, &x)?; - assert!(!is_zero_vec(&f_w)); - Ok(()) - } - - /// Test that a basic CCS relation can be satisfied - #[test] - fn test_check_ccs_relation() -> Result<(), Error> { - let ccs = get_test_ccs::(); - let (_, x, w) = get_test_z_split(3); - - ccs.check_relation(&w, &x)?; - Ok(()) - } -} diff --git a/folding-schemes/src/arith/mod.rs b/folding-schemes/src/arith/mod.rs deleted file mode 100644 index fbbff81fe..000000000 --- a/folding-schemes/src/arith/mod.rs +++ /dev/null @@ -1,178 +0,0 @@ -use ark_ff::PrimeField; -use ark_relations::gr1cs::SynthesisError; -use ark_std::rand::RngCore; - -use crate::{commitment::CommitmentScheme, folding::traits::Dummy, Curve, Error}; - -pub mod ccs; -pub mod r1cs; - -/// [`Arith`] is a trait about constraint systems (R1CS, CCS, etc.), where we -/// define methods for getting information about the constraint system. -pub trait Arith: Clone { - /// Returns the degree of the constraint system - fn degree(&self) -> usize; - - /// Returns the number of constraints in the constraint system - fn n_constraints(&self) -> usize; - - /// Returns the number of variables in the constraint system - fn n_variables(&self) -> usize; - - /// Returns the number of public inputs / public IO / instances / statements - /// in the constraint system - fn n_public_inputs(&self) -> usize; - - /// Returns the number of witnesses / secret inputs in the constraint system - fn n_witnesses(&self) -> usize; - - /// Returns a tuple containing (w, x) (witness and public inputs respectively) - fn split_z(&self, z: &[F]) -> (Vec, Vec); -} - -/// `ArithRelation` *treats a constraint system as a relation* between a witness -/// of type `W` and a statement / public input / public IO / instance of type -/// `U`, and in this trait, we define the necessary operations on the relation. -/// -/// Note that the same constraint system may support different types of `W` and -/// `U`, and the satisfiability check may vary. -/// -/// For example, both plain R1CS and relaxed R1CS are represented by 3 matrices, -/// but the types of `W` and `U` are different: -/// - The plain R1CS has `W` and `U` as vectors of field elements. -/// -/// `W = w` and `U = x` satisfy R1CS if `Az ∘ Bz = Cz`, where `z = [1, x, w]`. -/// -/// - In Nova, Relaxed R1CS has `W` as [`crate::folding::nova::Witness`], -/// and `U` as [`crate::folding::nova::CommittedInstance`]. -/// -/// `W = (w, e, ...)` and `U = (u, x, ...)` satisfy Relaxed R1CS if -/// `Az ∘ Bz = uCz + e`, where `z = [u, x, w]`. -/// (commitments in `U` are not checked here) -/// -/// Also, `W` and `U` have non-native field elements as their components when -/// used as CycleFold witness and instance. -/// -/// - In ProtoGalaxy, Relaxed R1CS has `W` as [`crate::folding::protogalaxy::Witness`], -/// and `U` as [`crate::folding::protogalaxy::CommittedInstance`]. -/// -/// `W = (w, ...)` and `U = (x, e, β, ...)` satisfy Relaxed R1CS if -/// `e = Σ pow_i(β) v_i`, where `v = Az ∘ Bz - Cz`, `z = [1, x, w]`. -/// (commitments in `U` are not checked here) -/// -/// This is also the case of CCS, where `W` and `U` may be vectors of field -/// elements, [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::lcccs::LCCCS`], -/// or [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::cccs::CCCS`]. -pub trait ArithRelation: Arith { - type Evaluation; - - /// Returns a dummy witness and instance - fn dummy_witness_instance<'a>(&'a self) -> (W, U) - where - W: Dummy<&'a Self>, - U: Dummy<&'a Self>, - { - (W::dummy(self), U::dummy(self)) - } - - /// Evaluates the constraint system `self` at witness `w` and instance `u`. - /// Returns the evaluation result. - /// - /// The evaluation result is usually a vector of field elements. - /// For instance: - /// - Evaluating the plain R1CS at `W = w` and `U = x` returns - /// `Az ∘ Bz - Cz`, where `z = [1, x, w]`. - /// - /// - Evaluating the relaxed R1CS in Nova at `W = (w, e, ...)` and - /// `U = (u, x, ...)` returns `Az ∘ Bz - uCz`, where `z = [u, x, w]`. - /// - /// - Evaluating the relaxed R1CS in ProtoGalaxy at `W = (w, ...)` and - /// `U = (x, e, β, ...)` returns `Az ∘ Bz - Cz`, where `z = [1, x, w]`. - /// - /// However, we use `Self::Evaluation` to represent the evaluation result - /// for future extensibility. - fn eval_relation(&self, w: &W, u: &U) -> Result; - - /// Checks if the evaluation result is valid. The witness `w` and instance - /// `u` are also parameters, because the validity check may need information - /// contained in `w` and/or `u`. - /// - /// For instance: - /// - The evaluation `v` of plain R1CS at satisfying `W` and `U` should be - /// an all-zero vector. - /// - /// - The evaluation `v` of relaxed R1CS in Nova at satisfying `W` and `U` - /// should be equal to the error term `e` in the witness. - /// - /// - The evaluation `v` of relaxed R1CS in ProtoGalaxy at satisfying `W` - /// and `U` should satisfy `e = Σ pow_i(β) v_i`, where `e` is the error - /// term in the committed instance. - fn check_evaluation(w: &W, u: &U, v: Self::Evaluation) -> Result<(), Error>; - - /// Checks if witness `w` and instance `u` satisfy the constraint system - /// `self` by first computing the evaluation result and then checking the - /// validity of the evaluation result. - /// - /// Used only for testing. - fn check_relation(&self, w: &W, u: &U) -> Result<(), Error> { - let e = self.eval_relation(w, u)?; - Self::check_evaluation(w, u, e) - } -} - -/// `ArithSerializer` is for serializing constraint systems. -/// -/// Currently we only support converting parameters to bytes, but in the future -/// we may consider implementing methods for serializing the actual data (e.g., -/// R1CS matrices). -pub trait ArithSerializer { - /// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of - /// public inputs, etc, without the matrices/polynomials values. - fn params_to_le_bytes(&self) -> Vec; -} - -/// `ArithSampler` allows sampling random pairs of witness and instance that -/// satisfy the constraint system `self`. -/// -/// This is useful for constructing a zero-knowledge layer for a folding-based -/// IVC. -/// An example of such a layer can be found in Appendix D of the [HyperNova] -/// paper. -/// -/// Note that we use a separate trait for sampling, because this operation may -/// not be supported by all witness-instance pairs. -/// For instance, it is difficult (if not impossible) to do this for `w` and `x` -/// in a plain R1CS. -/// -/// [HyperNova]: https://eprint.iacr.org/2023/573.pdf -pub trait ArithSampler: ArithRelation { - /// Samples a random witness and instance that satisfy the constraint system. - fn sample_witness_instance>( - &self, - params: &CS::ProverParams, - rng: impl RngCore, - ) -> Result<(W, U), Error>; -} - -/// `ArithRelationGadget` defines the in-circuit counterparts of operations -/// specified in `ArithRelation` on constraint systems. -pub trait ArithRelationGadget { - type Evaluation; - - /// Evaluates the constraint system `self` at witness `w` and instance `u`. - /// Returns the evaluation result. - fn eval_relation(&self, w: &WVar, u: &UVar) -> Result; - - /// Generates constraints for enforcing that witness `w` and instance `u` - /// satisfy the constraint system `self` by first computing the evaluation - /// result and then checking the validity of the evaluation result. - fn enforce_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError> { - let e = self.eval_relation(w, u)?; - Self::enforce_evaluation(w, u, e) - } - - /// Generates constraints for enforcing that the evaluation result is valid. - /// The witness `w` and instance `u` are also parameters, because the - /// validity check may need information contained in `w` and/or `u`. - fn enforce_evaluation(w: &WVar, u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError>; -} diff --git a/folding-schemes/src/arith/r1cs/circuits.rs b/folding-schemes/src/arith/r1cs/circuits.rs deleted file mode 100644 index 5900d2efd..000000000 --- a/folding-schemes/src/arith/r1cs/circuits.rs +++ /dev/null @@ -1,303 +0,0 @@ -use crate::{ - arith::ArithRelationGadget, - utils::gadgets::{EquivalenceGadget, MatrixGadget, SparseMatrixVar, VectorGadget}, -}; -use ark_ff::PrimeField; -use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, marker::PhantomData, One}; - -use super::R1CS; - -/// An in-circuit representation of the `R1CS` struct. -/// -/// `M` is for the modulo operation involved in the satisfiability check when -/// the underlying `FVar` is `NonNativeUintVar`. -#[derive(Debug, Clone)] -pub struct R1CSMatricesVar { - _m: PhantomData, - pub A: SparseMatrixVar, - pub B: SparseMatrixVar, - pub C: SparseMatrixVar, -} - -impl> - AllocVar, ConstraintF> for R1CSMatricesVar -{ - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - _mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - Ok(Self { - _m: PhantomData, - A: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().A)?, - B: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().B)?, - C: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().C)?, - }) - }) - } -} - -impl R1CSMatricesVar -where - SparseMatrixVar: MatrixGadget, - [FVar]: VectorGadget, -{ - pub fn eval_at_z(&self, z: &[FVar]) -> Result<(Vec, Vec), SynthesisError> { - // Multiply Cz by z[0] (u) here, allowing this method to be reused for - // both relaxed and unrelaxed R1CS. - let Az = self.A.mul_vector(z)?; - let Bz = self.B.mul_vector(z)?; - let Cz = self.C.mul_vector(z)?; - let uCz = Cz.mul_scalar(&z[0])?; - let AzBz = Az.hadamard(&Bz)?; - Ok((AzBz, uCz)) - } -} - -impl, UVar: AsRef<[FVar]>> ArithRelationGadget - for R1CSMatricesVar -where - SparseMatrixVar: MatrixGadget, - [FVar]: VectorGadget + EquivalenceGadget, - FVar: Clone + One, -{ - /// Evaluation is a tuple of two vectors (`AzBz` and `uCz`) instead of a - /// single vector `AzBz - uCz`, because subtraction is not supported for - /// `FVar = NonNativeUintVar`. - type Evaluation = (Vec, Vec); - - fn eval_relation(&self, w: &WVar, u: &UVar) -> Result { - self.eval_at_z(&[&[FVar::one()], u.as_ref(), w.as_ref()].concat()) - } - - fn enforce_evaluation( - _w: &WVar, - _u: &UVar, - (lhs, rhs): Self::Evaluation, - ) -> Result<(), SynthesisError> { - lhs.enforce_equivalent(&rhs) - } -} - -#[cfg(test)] -pub mod tests { - use ark_crypto_primitives::crh::{ - sha256::{ - constraints::{Sha256Gadget, UnitVar}, - Sha256, - }, - CRHScheme, CRHSchemeGadget, - }; - - use ark_ff::BigInteger; - use ark_pallas::{Fq, Fr, Projective}; - use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar, uint8::UInt8}; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef}; - use ark_std::{ - cmp::max, - rand::{thread_rng, Rng}, - One, UniformRand, - }; - use ark_vesta::Projective as Projective2; - - use super::*; - use crate::arith::{ - r1cs::{ - extract_r1cs, extract_w_x, - tests::{get_test_r1cs, get_test_z}, - }, - Arith, ArithRelation, - }; - use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; - use crate::folding::{ - circuits::{ - cyclefold::{CycleFoldCommittedInstanceVar, CycleFoldWitnessVar}, - nonnative::uint::NonNativeUintVar, - }, - nova::{ - decider_eth_circuit::WitnessVar, nifs::nova_circuits::CommittedInstanceVar, - CommittedInstance, Witness, - }, - }; - use crate::frontend::{ - utils::{ - cubic_step_native, custom_step_native, CubicFCircuit, CustomFCircuit, WrapperCircuit, - }, - FCircuit, - }; - use crate::{Curve, Error}; - - fn prepare_instances, R: Rng>( - mut rng: R, - r1cs: &R1CS, - z: &[C::ScalarField], - ) -> Result<(Witness, CommittedInstance), Error> { - let (w, x) = r1cs.split_z(z); - - let (cs_pp, _) = CS::setup(&mut rng, max(w.len(), r1cs.A.n_rows))?; - - let mut w = Witness::new::(w, r1cs.A.n_rows, &mut rng); - w.E = r1cs.eval_at_z(z)?; - let mut u = w.commit::(&cs_pp, x)?; - u.u = z[0]; - - Ok((w, u)) - } - - #[test] - fn test_relaxed_r1cs_small_gadget_handcrafted() -> Result<(), Error> { - let rng = &mut thread_rng(); - - let r1cs: R1CS = get_test_r1cs(); - let mut z = get_test_z(3); - z[0] = Fr::rand(rng); - let (w, u) = prepare_instances::<_, Pedersen, _>(rng, &r1cs, &z)?; - - let cs = ConstraintSystem::::new_ref(); - - let wVar = WitnessVar::new_witness(cs.clone(), || Ok(w))?; - let uVar = CommittedInstanceVar::new_witness(cs.clone(), || Ok(u))?; - let r1csVar = R1CSMatricesVar::>::new_witness(cs.clone(), || Ok(r1cs))?; - - r1csVar.enforce_relation(&wVar, &uVar)?; - assert!(cs.is_satisfied()?); - Ok(()) - } - - // gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been - // initialized. - fn test_relaxed_r1cs_gadget>(circuit: CS) -> Result<(), Error> { - let rng = &mut thread_rng(); - - let cs = ConstraintSystem::::new_ref(); - - circuit.generate_constraints(cs.clone())?; - cs.finalize(); - assert!(cs.is_satisfied()?); - - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - - let r1cs = extract_r1cs::(&cs)?; - let (w, x) = extract_w_x::(&cs); - r1cs.check_relation(&w, &x)?; - let mut z = [vec![Fr::one()], x, w].concat(); - z[0] = Fr::rand(rng); - - let (w, u) = prepare_instances::<_, Pedersen, _>(rng, &r1cs, &z)?; - r1cs.check_relation(&w, &u)?; - - // set new CS for the circuit that checks the RelaxedR1CS of our original circuit - let cs = ConstraintSystem::::new_ref(); - // prepare the inputs for our circuit - let wVar = WitnessVar::new_witness(cs.clone(), || Ok(w))?; - let uVar = CommittedInstanceVar::new_witness(cs.clone(), || Ok(u))?; - let r1csVar = R1CSMatricesVar::>::new_witness(cs.clone(), || Ok(r1cs))?; - - r1csVar.enforce_relation(&wVar, &uVar)?; - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_relaxed_r1cs_small_gadget_arkworks() -> Result<(), Error> { - let z_i = vec![Fr::from(3_u32)]; - let cubic_circuit = CubicFCircuit::::new(())?; - let circuit = WrapperCircuit::> { - FC: cubic_circuit, - z_i: Some(z_i.clone()), - z_i1: Some(cubic_step_native(z_i)), - }; - - test_relaxed_r1cs_gadget(circuit) - } - - struct Sha256TestCircuit { - _f: PhantomData, - pub x: Vec, - pub y: Vec, - } - impl ConstraintSynthesizer for Sha256TestCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let x = Vec::>::new_witness(cs.clone(), || Ok(self.x))?; - let y = Vec::>::new_input(cs.clone(), || Ok(self.y))?; - - let unitVar = UnitVar::default(); - let comp_y = as CRHSchemeGadget>::evaluate(&unitVar, &x)?; - comp_y.0.enforce_equal(&y)?; - Ok(()) - } - } - #[test] - fn test_relaxed_r1cs_medium_gadget_arkworks() -> Result<(), Error> { - let x = Fr::from(5_u32).into_bigint().to_bytes_le(); - let y = - ::evaluate(&(), x.clone()).map_err(|_| Error::EvaluationFail)?; - - let circuit = Sha256TestCircuit:: { - _f: PhantomData, - x, - y, - }; - test_relaxed_r1cs_gadget(circuit) - } - - #[test] - fn test_relaxed_r1cs_custom_circuit() -> Result<(), Error> { - let n_constraints = 10_000; - let custom_circuit = CustomFCircuit::::new(n_constraints)?; - let z_i = vec![Fr::from(5_u32)]; - let circuit = WrapperCircuit::> { - FC: custom_circuit, - z_i: Some(z_i.clone()), - z_i1: Some(custom_step_native(z_i, n_constraints)), - }; - test_relaxed_r1cs_gadget(circuit) - } - - #[test] - fn test_relaxed_r1cs_nonnative_circuit() -> Result<(), Error> { - let n_constraints = 10; - let rng = &mut thread_rng(); - - let cs = ConstraintSystem::::new_ref(); - // in practice we would use CycleFoldCircuit, but is a very big circuit (when computed - // non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a - // custom circuit. - let custom_circuit = CustomFCircuit::::new(n_constraints)?; - let z_i = vec![Fq::from(5_u32)]; - let circuit = WrapperCircuit::> { - FC: custom_circuit, - z_i: Some(z_i.clone()), - z_i1: Some(custom_step_native(z_i, n_constraints)), - }; - circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - let (w, x) = extract_w_x::(&cs); - let z = [vec![Fq::rand(rng)], x, w].concat(); - - let (w, u) = prepare_instances::<_, Pedersen, _>(rng, &r1cs, &z)?; - - // natively - let cs = ConstraintSystem::::new_ref(); - let wVar = WitnessVar::new_witness(cs.clone(), || Ok(&w))?; - let uVar = CommittedInstanceVar::new_witness(cs.clone(), || Ok(&u))?; - let r1csVar = R1CSMatricesVar::>::new_witness(cs.clone(), || Ok(&r1cs))?; - r1csVar.enforce_relation(&wVar, &uVar)?; - - // non-natively - let cs = ConstraintSystem::::new_ref(); - let wVar = CycleFoldWitnessVar::new_witness(cs.clone(), || Ok(w))?; - let uVar = CycleFoldCommittedInstanceVar::new_witness(cs.clone(), || Ok(u))?; - let r1csVar = - R1CSMatricesVar::>::new_witness(cs.clone(), || Ok(r1cs))?; - r1csVar.enforce_relation(&wVar, &uVar)?; - Ok(()) - } -} diff --git a/folding-schemes/src/arith/r1cs/mod.rs b/folding-schemes/src/arith/r1cs/mod.rs deleted file mode 100644 index db714aff4..000000000 --- a/folding-schemes/src/arith/r1cs/mod.rs +++ /dev/null @@ -1,294 +0,0 @@ -use ark_ff::PrimeField; -use ark_relations::gr1cs::ConstraintSystem; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::rand::Rng; - -use super::ccs::CCS; -use super::{Arith, ArithRelation, ArithSerializer}; -use crate::folding::traits::Dummy; -use crate::utils::vec::{ - hadamard, is_zero_vec, mat_vec_mul, vec_scalar_mul, vec_sub, SparseMatrix, -}; -use crate::Error; - -pub mod circuits; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct R1CS { - l: usize, // io len - pub A: SparseMatrix, - pub B: SparseMatrix, - pub C: SparseMatrix, -} - -impl R1CS { - /// Evaluates the R1CS relation at a given vector of variables `z` - pub fn eval_at_z(&self, z: &[F]) -> Result, Error> { - if z.len() != self.A.n_cols { - return Err(Error::NotSameLength( - "z.len()".to_string(), - z.len(), - "number of variables in R1CS".to_string(), - self.A.n_cols, - )); - } - - let Az = mat_vec_mul(&self.A, z)?; - let Bz = mat_vec_mul(&self.B, z)?; - let Cz = mat_vec_mul(&self.C, z)?; - // Multiply Cz by z[0] (u) here, allowing this method to be reused for - // both relaxed and plain R1CS. - let uCz = vec_scalar_mul(&Cz, &z[0]); - let AzBz = hadamard(&Az, &Bz)?; - vec_sub(&AzBz, &uCz) - } -} - -impl Arith for R1CS { - #[inline] - fn degree(&self) -> usize { - 2 - } - - #[inline] - fn n_constraints(&self) -> usize { - self.A.n_rows - } - - #[inline] - fn n_variables(&self) -> usize { - self.A.n_cols - } - - #[inline] - fn n_public_inputs(&self) -> usize { - self.l - } - - #[inline] - fn n_witnesses(&self) -> usize { - self.n_variables() - self.n_public_inputs() - 1 - } - - fn split_z(&self, z: &[P]) -> (Vec

, Vec

) { - (z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec()) - } -} - -impl, U: AsRef<[F]>> ArithRelation for R1CS { - type Evaluation = Vec; - - fn eval_relation(&self, w: &W, u: &U) -> Result { - self.eval_at_z(&[&[F::one()], u.as_ref(), w.as_ref()].concat()) - } - - fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { - is_zero_vec(&e).then_some(()).ok_or(Error::NotSatisfied) - } -} - -impl ArithSerializer for R1CS { - fn params_to_le_bytes(&self) -> Vec { - [ - (self.l as u64).to_le_bytes(), - (self.A.n_rows as u64).to_le_bytes(), - (self.A.n_cols as u64).to_le_bytes(), - ] - .concat() - } -} - -impl Dummy<(usize, usize, usize)> for R1CS { - fn dummy((n_constraints, n_variables, n_public_inputs): (usize, usize, usize)) -> Self { - Self { - l: n_public_inputs, - A: SparseMatrix::dummy((n_constraints, n_variables)), - B: SparseMatrix::dummy((n_constraints, n_variables)), - C: SparseMatrix::dummy((n_constraints, n_variables)), - } - } -} - -impl R1CS { - pub fn empty() -> Self { - Self::dummy((0, 0, 0)) - } - pub fn rand(rng: &mut R, n_rows: usize, n_cols: usize) -> Self { - Self { - l: 1, - A: SparseMatrix::rand(rng, n_rows, n_cols), - B: SparseMatrix::rand(rng, n_rows, n_cols), - C: SparseMatrix::rand(rng, n_rows, n_cols), - } - } -} - -impl From> for R1CS { - fn from(ccs: CCS) -> Self { - R1CS:: { - l: ccs.n_public_inputs(), - A: ccs.M[0].clone(), - B: ccs.M[1].clone(), - C: ccs.M[2].clone(), - } - } -} - -/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS -/// struct. -pub fn extract_r1cs(cs: &ConstraintSystem) -> Result, Error> { - let matrices_map = cs.to_matrices().map_err(|_| { - Error::ConversionError( - "ConstraintSystem".into(), - "ConstraintMatrices".into(), - "The matrices have not been generated yet".into(), - ) - })?; - - // Get the R1CS predicate matrices - let r1cs_matrices = matrices_map.get("R1CS").ok_or_else(|| { - Error::ConversionError( - "ConstraintSystem".into(), - "R1CS matrices".into(), - "No R1CS predicate found in constraint system".into(), - ) - })?; - - // The R1CS predicate should have exactly 3 matrices (A, B, C) - if r1cs_matrices.len() != 3 { - return Err(Error::ConversionError( - "R1CS matrices".into(), - "3 matrices (A, B, C)".into(), - format!("Found {} matrices", r1cs_matrices.len()), - )); - } - - let n_rows = cs.num_constraints(); - let n_cols = cs.num_instance_variables + cs.num_witness_variables; // cs.num_instance_variables already counts the 1 - - let A = SparseMatrix:: { - n_rows, - n_cols, - coeffs: r1cs_matrices[0].clone(), - }; - let B = SparseMatrix:: { - n_rows, - n_cols, - coeffs: r1cs_matrices[1].clone(), - }; - let C = SparseMatrix:: { - n_rows, - n_cols, - coeffs: r1cs_matrices[2].clone(), - }; - - Ok(R1CS:: { - l: cs.num_instance_variables - 1, // -1 to subtract the first '1' - A, - B, - C, - }) -} - -/// extracts the witness and the public inputs from arkworks ConstraintSystem. -pub fn extract_w_x(cs: &ConstraintSystem) -> (Vec, Vec) { - let witness = cs - .witness_assignment() - .expect("witness_assignment failed") - .to_vec(); - let instance = cs - .instance_assignment() - .expect("instance_assignment failed"); - ( - witness, - // skip the first element which is '1' - instance[1..].to_vec(), - ) -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::utils::vec::{ - is_zero_vec, - tests::{to_F_matrix, to_F_vec}, - }; - - use ark_pallas::Fr; - - pub fn get_test_r1cs() -> R1CS { - // R1CS for: x^3 + x + 5 = y (example from article - // https://www.vitalik.ca/general/2016/12/10/qap.html ) - let A = to_F_matrix::(vec![ - vec![0, 1, 0, 0, 0, 0], - vec![0, 0, 0, 1, 0, 0], - vec![0, 1, 0, 0, 1, 0], - vec![5, 0, 0, 0, 0, 1], - ]); - let B = to_F_matrix::(vec![ - vec![0, 1, 0, 0, 0, 0], - vec![0, 1, 0, 0, 0, 0], - vec![1, 0, 0, 0, 0, 0], - vec![1, 0, 0, 0, 0, 0], - ]); - let C = to_F_matrix::(vec![ - vec![0, 0, 0, 1, 0, 0], - vec![0, 0, 0, 0, 1, 0], - vec![0, 0, 0, 0, 0, 1], - vec![0, 0, 1, 0, 0, 0], - ]); - - R1CS:: { l: 1, A, B, C } - } - - pub fn get_test_z(input: usize) -> Vec { - // z = (1, io, w) - to_F_vec(vec![ - 1, - input, // io - input * input * input + input + 5, // x^3 + x + 5 - input * input, // x^2 - input * input * input, // x^2 * x - input * input * input + input, // x^3 + x - ]) - } - - pub fn get_test_z_split(input: usize) -> (F, Vec, Vec) { - // z = (1, io, w) - ( - F::one(), - to_F_vec(vec![ - input, // io - ]), - to_F_vec(vec![ - input * input * input + input + 5, // x^3 + x + 5 - input * input, // x^2 - input * input * input, // x^2 * x - input * input * input + input, // x^3 + x - ]), - ) - } - - #[test] - fn test_eval_r1cs_relation() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let r1cs = get_test_r1cs::(); - let (_, x, mut w) = get_test_z_split::(rng.gen::() as usize); - - let f_w = r1cs.eval_relation(&w, &x)?; - assert!(is_zero_vec(&f_w)); - - w[1] = Fr::from(111); - let f_w = r1cs.eval_relation(&w, &x)?; - assert!(!is_zero_vec(&f_w)); - Ok(()) - } - - #[test] - fn test_check_r1cs_relation() -> Result<(), Error> { - let r1cs = get_test_r1cs::(); - let (_, x, w) = get_test_z_split(5); - r1cs.check_relation(&w, &x)?; - Ok(()) - } -} diff --git a/folding-schemes/src/commitment/ipa.rs b/folding-schemes/src/commitment/ipa.rs deleted file mode 100644 index cf840e3b5..000000000 --- a/folding-schemes/src/commitment/ipa.rs +++ /dev/null @@ -1,718 +0,0 @@ -/// IPA implements the modified Inner Product Argument described in -/// [Halo](https://eprint.iacr.org/2019/1021.pdf). The variable names used follow the paper -/// notation in order to make it more readable. -/// -/// The implementation does the following optimizations in order to reduce the amount of -/// constraints in the circuit: -/// i. computation is done in log time following a modification of the equation 3 in section -/// 3.2 from the paper. -/// ii. s computation is done in 2^{k+1}-2 instead of k*2^k. -use ark_ec::AffineRepr; -use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - convert::ToBitsGadget, - eq::EqGadget, - fields::{emulated_fp::EmulatedFpVar, FieldVar}, - groups::CurveVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{cfg_iter, rand::RngCore, UniformRand, Zero}; -use core::{borrow::Borrow, marker::PhantomData}; -use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; - -use super::{pedersen::Params as PedersenParams, CommitmentScheme}; -use crate::folding::circuits::CF2; -use crate::transcript::Transcript; -use crate::utils::{ - powers_of, - vec::{vec_add, vec_scalar_mul}, -}; -use crate::{Curve, Error}; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Proof { - a: C::ScalarField, - l: Vec, - r: Vec, - L: Vec, - R: Vec, -} - -/// IPA implements the Inner Product Argument protocol following the CommitmentScheme trait. The -/// `H` parameter indicates if to use the commitment in hiding mode or not. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct IPA { - _c: PhantomData, -} - -/// Implements the CommitmentScheme trait for IPA -impl CommitmentScheme for IPA { - type ProverParams = PedersenParams; - type VerifierParams = PedersenParams; - type Proof = (Proof, C::ScalarField, C::ScalarField); // (proof, v=p(x), r=blinding factor) - type ProverChallenge = (C::ScalarField, C, Vec); - type Challenge = (C::ScalarField, C, Vec); - - fn is_hiding() -> bool { - if H { - return true; - } - false - } - - fn setup( - mut rng: impl RngCore, - len: usize, - ) -> Result<(Self::ProverParams, Self::VerifierParams), Error> { - let generators: Vec = std::iter::repeat_with(|| C::Affine::rand(&mut rng)) - .take(len.next_power_of_two()) - .collect(); - let p = PedersenParams:: { - h: C::rand(&mut rng), - generators, - }; - Ok((p.clone(), p)) - } - - fn commit( - params: &PedersenParams, - a: &[C::ScalarField], - r: &C::ScalarField, // blinding factor - ) -> Result { - if params.generators.len() < a.len() { - return Err(Error::PedersenParamsLen(params.generators.len(), a.len())); - } - if !H && (!r.is_zero()) { - return Err(Error::BlindingNotZero); - } - - // h⋅r + - // use msm_unchecked because we already ensured at the if that lengths match - if !H { - return Ok(C::msm_unchecked(¶ms.generators[..a.len()], a)); - } - Ok(params.h.mul(r) + C::msm_unchecked(¶ms.generators[..a.len()], a)) - } - - fn prove( - params: &Self::ProverParams, - transcript: &mut impl Transcript, - P: &C, // commitment - a: &[C::ScalarField], // vector - blind: &C::ScalarField, - rng: Option<&mut dyn RngCore>, - ) -> Result { - if !a.len().is_power_of_two() { - return Err(Error::NotPowerOfTwo("a".to_string(), a.len())); - } - if !H && (!blind.is_zero()) { - return Err(Error::BlindingNotZero); - } - let d = a.len(); - let k = (f64::from(d as u32).log2()) as usize; - - if params.generators.len() < a.len() { - return Err(Error::PedersenParamsLen(params.generators.len(), a.len())); - } - // blinding factors - let l: Vec; - let r: Vec; - if H { - let rng = rng.ok_or(Error::MissingRandomness)?; - l = std::iter::repeat_with(|| C::ScalarField::rand(rng)) - .take(k) - .collect(); - r = std::iter::repeat_with(|| C::ScalarField::rand(rng)) - .take(k) - .collect(); - } else { - l = vec![]; - r = vec![]; - } - - transcript.absorb_nonnative(P); - let x = transcript.get_challenge(); // challenge value at which we evaluate - let s = transcript.get_challenge(); - let U = C::generator().mul(s); - - let mut a = a.to_owned(); - let mut b = powers_of(x, d); - let v = inner_prod(&a, &b)?; - - let mut G = params.generators.clone(); - - let mut L: Vec = vec![C::zero(); k]; - let mut R: Vec = vec![C::zero(); k]; - - // u challenges - let mut u: Vec = vec![C::ScalarField::zero(); k]; - for j in (0..k).rev() { - let m = a.len() / 2; - - if H { - L[j] = C::msm_unchecked(&G[m..], &a[..m]) - + params.h.mul(l[j]) - + U.mul(inner_prod(&a[..m], &b[m..])?); - R[j] = C::msm_unchecked(&G[..m], &a[m..]) - + params.h.mul(r[j]) - + U.mul(inner_prod(&a[m..], &b[..m])?); - } else { - L[j] = C::msm_unchecked(&G[m..], &a[..m]) + U.mul(inner_prod(&a[..m], &b[m..])?); - R[j] = C::msm_unchecked(&G[..m], &a[m..]) + U.mul(inner_prod(&a[m..], &b[..m])?); - } - // get challenge for the j-th round - transcript.absorb_nonnative(&L[j]); - transcript.absorb_nonnative(&R[j]); - u[j] = transcript.get_challenge(); - - let uj = u[j]; - let uj_inv = u[j] - .inverse() - .ok_or(Error::Other("error on computing inverse".to_string()))?; - - // a_hi * uj^-1 + a_lo * uj - a = vec_add( - &vec_scalar_mul(&a[..m], &uj), - &vec_scalar_mul(&a[m..], &uj_inv), - )?; - // b_lo * uj^-1 + b_hi * uj - b = vec_add( - &vec_scalar_mul(&b[..m], &uj_inv), - &vec_scalar_mul(&b[m..], &uj), - )?; - // G_lo * uj^-1 + G_hi * uj - G = cfg_iter!(G[..m]) - .map(|e| e.into_group().mul(uj_inv)) - .zip(cfg_iter!(G[m..]).map(|e| e.into_group().mul(uj))) - .map(|(a, b)| (a + b).into_affine()) - .collect::>(); - } - - if a.len() != 1 { - return Err(Error::NotExpectedLength(a.len(), 1)); - } - if b.len() != 1 { - return Err(Error::NotExpectedLength(b.len(), 1)); - } - if G.len() != 1 { - return Err(Error::NotExpectedLength(G.len(), 1)); - } - - Ok(( - Proof { - a: a[0], - l: l.clone(), - r: r.clone(), - L, - R, - }, - v, // evaluation at challenge, v=p(x) - *blind, // blind factor - )) - } - - fn prove_with_challenge( - _params: &Self::ProverParams, - _challenge: Self::ProverChallenge, - _a: &[C::ScalarField], // vector - _blind: &C::ScalarField, - _rng: Option<&mut dyn RngCore>, - ) -> Result { - // not supported because the prover logic computes challenges as it advances on the logic - Err(Error::NotSupported("IPA::prove_with_challenge".to_string())) - } - - fn verify( - params: &Self::VerifierParams, - transcript: &mut impl Transcript, - P: &C, // commitment - proof: &Self::Proof, - ) -> Result<(), Error> { - let (p, _r) = (proof.0.clone(), proof.1); - let k = p.L.len(); - - transcript.absorb_nonnative(P); - let x = transcript.get_challenge(); // challenge value at which we evaluate - let s = transcript.get_challenge(); - let U = C::generator().mul(s); - let mut u: Vec = vec![C::ScalarField::zero(); k]; - for i in (0..k).rev() { - transcript.absorb_nonnative(&p.L[i]); - transcript.absorb_nonnative(&p.R[i]); - u[i] = transcript.get_challenge(); - } - let challenge = (x, U, u); - - Self::verify_with_challenge(params, challenge, P, proof) - } - - fn verify_with_challenge( - params: &Self::VerifierParams, - challenge: Self::Challenge, - P: &C, // commitment - proof: &Self::Proof, - ) -> Result<(), Error> { - let (p, v, r) = (proof.0.clone(), proof.1, proof.2); - let (x, U, u) = challenge; - - let k = p.L.len(); - if p.R.len() != k { - return Err(Error::CommitmentVerificationFail); - } - if !H && (!r.is_zero()) { - return Err(Error::BlindingNotZero); - } - if !H && (!p.l.is_empty() || !p.r.is_empty()) { - return Err(Error::CommitmentVerificationFail); - } - if H && (p.l.len() != k || p.r.len() != k) { - return Err(Error::CommitmentVerificationFail); - } - - let P = *P + U.mul(v); // where v=p(x) - - let mut q_0 = P; - let mut r = r; - - // compute u[i]^-1 once - let mut u_invs = vec![C::ScalarField::zero(); u.len()]; - for (j, u_j) in u.iter().enumerate() { - u_invs[j] = u_j - .inverse() - .ok_or(Error::Other("error on computing inverse".to_string()))?; - } - - // compute b & G from s - let s = build_s(&u, &u_invs, k)?; - // b = = - let b = s_b_inner(&u, &x)?; - let d: usize = 2_u64.pow(k as u32) as usize; - if params.generators.len() < d { - return Err(Error::PedersenParamsLen(params.generators.len(), d)); - } - let G = C::msm_unchecked(¶ms.generators, &s); - - for (j, u_j) in u.iter().enumerate() { - let uj2 = u_j.square(); - let uj_inv2 = u_invs[j].square(); - - q_0 = q_0 + p.L[j].mul(uj2) + p.R[j].mul(uj_inv2); - if H { - r = r + p.l[j] * uj2 + p.r[j] * uj_inv2; - } - } - - let q_1 = if H { - G.mul(p.a) + params.h.mul(r) + U.mul(p.a * b) - } else { - G.mul(p.a) + U.mul(p.a * b) - }; - - if q_0 != q_1 { - return Err(Error::CommitmentVerificationFail); - } - Ok(()) - } -} - -/// Computes s such that -/// s = ( -/// u₁⁻¹ u₂⁻¹ … uₖ⁻¹, -/// u₁ u₂⁻¹ … uₖ⁻¹, -/// u₁⁻¹ u₂ … uₖ⁻¹, -/// u₁ u₂ … uₖ⁻¹, -/// ⋮ ⋮ ⋮ -/// u₁ u₂ … uₖ -/// ) -/// Uses Halo2 approach computing $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$, -/// taking 2^{k+1}-2. -/// src: https://github.com/zcash/halo2/blob/81729eca91ba4755e247f49c3a72a4232864ec9e/halo2_proofs/src/poly/commitment/verifier.rs#L156 -fn build_s(u: &[F], u_invs: &[F], k: usize) -> Result, Error> { - let d: usize = 2_u64.pow(k as u32) as usize; - let mut s: Vec = vec![F::one(); d]; - for (len, (u_j, u_j_inv)) in u - .iter() - .zip(u_invs) - .enumerate() - .map(|(i, u_j)| (1 << i, u_j)) - { - let (left, right) = s.split_at_mut(len); - let right = &mut right[0..len]; - right.copy_from_slice(left); - for s in left { - *s *= u_j_inv; - } - for s in right { - *s *= u_j; - } - } - Ok(s) -} - -/// Computes (in-circuit) s such that -/// s = ( -/// u₁⁻¹ u₂⁻¹ … uₖ⁻¹, -/// u₁ u₂⁻¹ … uₖ⁻¹, -/// u₁⁻¹ u₂ … uₖ⁻¹, -/// u₁ u₂ … uₖ⁻¹, -/// ⋮ ⋮ ⋮ -/// u₁ u₂ … uₖ -/// ) -/// Uses Halo2 approach computing $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$, -/// taking 2^{k+1}-2. -/// src: https://github.com/zcash/halo2/blob/81729eca91ba4755e247f49c3a72a4232864ec9e/halo2_proofs/src/poly/commitment/verifier.rs#L156 -fn build_s_gadget( - u: &[EmulatedFpVar], - u_invs: &[EmulatedFpVar], - k: usize, -) -> Result>, SynthesisError> { - let d: usize = 2_u64.pow(k as u32) as usize; - let mut s: Vec> = vec![EmulatedFpVar::one(); d]; - for (len, (u_j, u_j_inv)) in u - .iter() - .zip(u_invs) - .enumerate() - .map(|(i, u_j)| (1 << i, u_j)) - { - let (left, right) = s.split_at_mut(len); - let right = &mut right[0..len]; - right.clone_from_slice(left); - for s in left { - *s *= u_j_inv; - } - for s in right { - *s *= u_j; - } - } - Ok(s) -} - -fn inner_prod(a: &[F], b: &[F]) -> Result { - if a.len() != b.len() { - return Err(Error::NotSameLength( - "a".to_string(), - a.len(), - "b".to_string(), - b.len(), - )); - } - let c = cfg_iter!(a) - .zip(cfg_iter!(b)) - .map(|(a_i, b_i)| *a_i * b_i) - .sum(); - Ok(c) -} - -// g(x, u_1, u_2, ..., u_k) = , naively takes linear, but can compute in log time through -// g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1 -fn s_b_inner(u: &[F], x: &F) -> Result { - let mut c: F = F::one(); - let mut x_2_i = *x; // x_2_i is x^{2^i}, starting from x^{2^0}=x - for u_i in u.iter() { - c *= (*u_i * x_2_i) - + u_i - .inverse() - .ok_or(Error::Other("error on computing inverse".to_string()))?; - x_2_i *= x_2_i; - } - Ok(c) -} - -// g(x, u_1, u_2, ..., u_k) = , naively takes linear, but can compute in log time through -// g(x, u_1, u_2, ..., u_k) = \Prod u_i x^{2^i} + u_i^-1 -fn s_b_inner_gadget( - u: &[EmulatedFpVar], - x: &EmulatedFpVar, -) -> Result, SynthesisError> { - let mut c: EmulatedFpVar = EmulatedFpVar::::one(); - let mut x_2_i = x.clone(); // x_2_i is x^{2^i}, starting from x^{2^0}=x - for u_i in u.iter() { - c *= u_i.clone() * x_2_i.clone() + u_i.inverse()?; - x_2_i *= x_2_i.clone(); - } - Ok(c) -} - -pub struct ProofVar { - a: EmulatedFpVar>, - l: Vec>>, - r: Vec>>, - L: Vec, - R: Vec, -} -impl AllocVar, CF2> for ProofVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let a = EmulatedFpVar::>::new_variable( - cs.clone(), - || Ok(val.borrow().a), - mode, - )?; - let l: Vec>> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().l.clone()), mode)?; - let r: Vec>> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().r.clone()), mode)?; - let L: Vec = - Vec::new_variable(cs.clone(), || Ok(val.borrow().L.clone()), mode)?; - let R: Vec = - Vec::new_variable(cs.clone(), || Ok(val.borrow().R.clone()), mode)?; - - Ok(Self { a, l, r, L, R }) - }) - } -} - -/// IPAGadget implements the circuit that verifies an IPA Proof. The `H` parameter indicates if to -/// use the commitment in hiding mode or not, reducing a bit the number of constraints needed in -/// the later case. -pub struct IPAGadget { - _c: PhantomData, -} - -impl IPAGadget { - /// Verify the IPA opening proof, K=log2(d), where d is the degree of the committed polynomial, - /// and H indicates if the commitment is in hiding mode and thus uses blinding factors, if not, - /// there are some constraints saved. - #[allow(clippy::too_many_arguments)] - pub fn verify( - g: &[C::Var], // params.generators - h: &C::Var, // params.h - x: &EmulatedFpVar>, // evaluation point, challenge - v: &EmulatedFpVar>, // value at evaluation point - P: &C::Var, // commitment - p: &ProofVar, - r: &EmulatedFpVar>, // blinding factor - u: &[EmulatedFpVar>; K], // challenges - U: &C::Var, // challenge - ) -> Result>, SynthesisError> { - if p.L.len() != K || p.R.len() != K { - return Err(SynthesisError::Unsatisfiable); - } - - let P_ = U.scalar_mul_le(v.to_bits_le()?.iter())? + P; - let mut q_0 = P_; - let mut r = r.clone(); - - // compute u[i]^-1 once - let mut u_invs = vec![EmulatedFpVar::>::zero(); u.len()]; - for (j, u_j) in u.iter().enumerate() { - u_invs[j] = u_j.inverse()?; - } - - // compute b & G from s - let s = build_s_gadget(u, &u_invs, K)?; - // b = = - let b = s_b_inner_gadget(u, x)?; - // ensure that generators.len() === s.len(): - if g.len() < K { - return Err(SynthesisError::Unsatisfiable); - } - - // msm: G= - let mut G = C::Var::zero(); - let n = s.len(); - if n % 2 == 1 { - G += g[n - 1].scalar_mul_le(s[n - 1].to_bits_le()?.iter())?; - } else { - G += g[n - 1].joint_scalar_mul_be( - &g[n - 2], - s[n - 1].to_bits_le()?.iter(), - s[n - 2].to_bits_le()?.iter(), - )?; - } - for i in (1..n - 2).step_by(2) { - G += g[i - 1].joint_scalar_mul_be( - &g[i], - s[i - 1].to_bits_le()?.iter(), - s[i].to_bits_le()?.iter(), - )?; - } - - for (j, u_j) in u.iter().enumerate() { - let uj2 = u_j.square()?; - let uj_inv2 = u_invs[j].square()?; // cheaper square than inversing the uj2 - - q_0 = q_0 - + p.L[j].scalar_mul_le(uj2.to_bits_le()?.iter())? - + p.R[j].scalar_mul_le(uj_inv2.to_bits_le()?.iter())?; - if H { - r = r + &p.l[j] * &uj2 + &p.r[j] * &uj_inv2; - } - } - - let q_1 = if H { - G.scalar_mul_le(p.a.to_bits_le()?.iter())? - + h.joint_scalar_mul_be( - U, - r.to_bits_le()?.iter(), - (p.a.clone() * b).to_bits_le()?.iter(), - )? - } else { - G.joint_scalar_mul_be( - U, - p.a.to_bits_le()?.iter(), - (p.a.clone() * b).to_bits_le()?.iter(), - )? - }; - // q_0 == q_1 - q_0.is_eq(&q_1) - } -} - -#[cfg(test)] -mod tests { - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use ark_ec::PrimeGroup; - use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; - use ark_r1cs_std::eq::EqGadget; - use ark_relations::gr1cs::ConstraintSystem; - - use super::*; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_ipa() -> Result<(), Error> { - let _ = test_ipa_opt::()?; - let _ = test_ipa_opt::()?; - Ok(()) - } - fn test_ipa_opt() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - - const k: usize = 4; - const d: usize = 2_u64.pow(k as u32) as usize; - - // setup params - let (params, _) = IPA::::setup(&mut rng, d)?; - - let poseidon_config = poseidon_canonical_config::(); - // init Prover's transcript - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - // init Verifier's transcript - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - // a is the vector that we're committing - let a: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(d) - .collect(); - let r_blind: Fr = if hiding { - Fr::rand(&mut rng) - } else { - Fr::zero() - }; - let cm = IPA::::commit(¶ms, &a, &r_blind)?; - - let proof = IPA::::prove( - ¶ms, - &mut transcript_p, - &cm, - &a, - &r_blind, - Some(&mut rng), - )?; - - IPA::::verify(¶ms, &mut transcript_v, &cm, &proof)?; - Ok(()) - } - - #[test] - fn test_ipa_gadget() -> Result<(), Error> { - let _ = test_ipa_gadget_opt::()?; - let _ = test_ipa_gadget_opt::()?; - Ok(()) - } - fn test_ipa_gadget_opt() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - - const k: usize = 3; - const d: usize = 2_u64.pow(k as u32) as usize; - - // setup params - let (params, _) = IPA::::setup(&mut rng, d)?; - - let poseidon_config = poseidon_canonical_config::(); - // init Prover's transcript - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - // init Verifier's transcript - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - let mut a: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(d / 2) - .collect(); - a.extend(vec![Fr::zero(); d / 2]); - let r_blind: Fr = if hiding { - Fr::rand(&mut rng) - } else { - Fr::zero() - }; - let cm = IPA::::commit(¶ms, &a, &r_blind)?; - - let proof = IPA::::prove( - ¶ms, - &mut transcript_p, - &cm, - &a, - &r_blind, - Some(&mut rng), - )?; - - IPA::::verify(¶ms, &mut transcript_v, &cm, &proof)?; - - // circuit - let cs = ConstraintSystem::::new_ref(); - - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - transcript_v.absorb_nonnative(&cm); - let challenge = transcript_v.get_challenge(); // challenge value at which we evaluate - let s = transcript_v.get_challenge(); - let U = Projective::generator() * s; - let mut u: Vec = vec![Fr::zero(); k]; - for i in (0..k).rev() { - transcript_v.absorb_nonnative(&proof.0.L[i]); - transcript_v.absorb_nonnative(&proof.0.R[i]); - u[i] = transcript_v.get_challenge(); - } - - // prepare inputs - let gVar = Vec::::new_constant(cs.clone(), params.generators)?; - let hVar = GVar::new_constant(cs.clone(), params.h)?; - let challengeVar = EmulatedFpVar::::new_witness(cs.clone(), || Ok(challenge))?; - let vVar = EmulatedFpVar::::new_witness(cs.clone(), || Ok(proof.1))?; - let cmVar = GVar::new_witness(cs.clone(), || Ok(cm))?; - let proofVar = ProofVar::::new_witness(cs.clone(), || Ok(proof.0))?; - let r_blindVar = EmulatedFpVar::::new_witness(cs.clone(), || Ok(r_blind))?; - let uVar_vec = Vec::>::new_witness(cs.clone(), || Ok(u))?; - let uVar: [EmulatedFpVar; k] = uVar_vec.try_into().map_err(|_| { - Error::ConversionError( - "Vec<_>".to_string(), - "[_; 1]".to_string(), - "variable name: uVar".to_string(), - ) - })?; - let UVar = GVar::new_witness(cs.clone(), || Ok(U))?; - - let v = IPAGadget::::verify::( - &gVar, - &hVar, - &challengeVar, - &vVar, - &cmVar, - &proofVar, - &r_blindVar, - &uVar, - &UVar, - )?; - v.enforce_equal(&Boolean::TRUE)?; - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/folding-schemes/src/commitment/kzg.rs b/folding-schemes/src/commitment/kzg.rs deleted file mode 100644 index f1873cd63..000000000 --- a/folding-schemes/src/commitment/kzg.rs +++ /dev/null @@ -1,311 +0,0 @@ -/// Adaptation of the prover methods and structs from arkworks/poly-commit's KZG10 implementation -/// into the CommitmentScheme trait. -/// -/// The motivation to do so, is that we want to be able to use KZG / Pedersen for committing to -/// vectors indistinctly, and the arkworks KZG10 implementation contains all the methods under the -/// same trait, which requires the Pairing trait, where the prover does not need access to the -/// Pairing but only to G1. -use ark_ec::{pairing::Pairing, CurveGroup, VariableBaseMSM}; -use ark_ff::PrimeField; -use ark_poly::{ - univariate::{DenseOrSparsePolynomial, DensePolynomial}, - DenseUVPolynomial, Polynomial, -}; -use ark_poly_commit::kzg10::{ - Commitment as KZG10Commitment, Proof as KZG10Proof, VerifierKey, KZG10, -}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; -use ark_std::rand::RngCore; -use ark_std::{borrow::Cow, fmt::Debug}; -use ark_std::{One, Zero}; -use core::marker::PhantomData; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; - -use super::CommitmentScheme; -use crate::transcript::Transcript; -use crate::utils::vec::poly_from_vec; -use crate::{Curve, Error}; - -/// ProverKey defines a similar struct as in ark_poly_commit::kzg10::Powers, but instead of -/// depending on the Pairing trait it depends on the SonobeCurve trait. -#[derive(Debug, Clone, Default, Eq, PartialEq)] -pub struct ProverKey<'a, C: Curve> { - /// Group elements of the form `β^i G`, for different values of `i`. - pub powers_of_g: Cow<'a, [C::Affine]>, -} - -impl<'a, C: Curve> CanonicalSerialize for ProverKey<'a, C> { - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.powers_of_g.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.powers_of_g.serialized_size(compress) - } -} - -impl<'a, C: Curve> CanonicalDeserialize for ProverKey<'a, C> { - fn deserialize_with_mode( - reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - ) -> Result { - let powers_of_g_vec = Vec::deserialize_with_mode(reader, compress, validate)?; - Ok(ProverKey { - powers_of_g: ark_std::borrow::Cow::Owned(powers_of_g_vec), - }) - } -} - -impl<'a, C: Curve> Valid for ProverKey<'a, C> { - fn check(&self) -> Result<(), ark_serialize::SerializationError> { - match self.powers_of_g.clone() { - Cow::Borrowed(powers) => powers.to_vec().check(), - Cow::Owned(powers) => powers.check(), - } - } -} - -#[derive(Debug, Clone, Default, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Proof { - pub eval: C::ScalarField, - pub proof: C, -} - -/// KZG implements the CommitmentScheme trait for the KZG commitment scheme. -#[derive(Debug, Clone, Default, Eq, PartialEq)] -pub struct KZG<'a, E: Pairing, const H: bool = false> { - _a: PhantomData<&'a ()>, - _e: PhantomData, -} - -impl<'a, E: Pairing, const H: bool> CommitmentScheme for KZG<'a, E, H> { - type ProverParams = ProverKey<'a, E::G1>; - type VerifierParams = VerifierKey; - type Proof = Proof; - type ProverChallenge = E::ScalarField; - type Challenge = E::ScalarField; - - fn is_hiding() -> bool { - if H { - return true; - } - false - } - - /// setup returns the tuple (ProverKey, VerifierKey). For real world deployments the setup must - /// be computed in the most trustless way possible, usually through an MPC ceremony. - fn setup( - mut rng: impl RngCore, - len: usize, - ) -> Result<(Self::ProverParams, Self::VerifierParams), Error> { - let len = len.next_power_of_two(); - let universal_params = - KZG10::>::setup(len, false, &mut rng) - .expect("Setup failed"); - let powers_of_g = universal_params.powers_of_g[..=len].to_vec(); - let powers = ProverKey:: { - powers_of_g: ark_std::borrow::Cow::Owned(powers_of_g), - }; - let vk = VerifierKey { - g: universal_params.powers_of_g[0], - gamma_g: universal_params.powers_of_gamma_g[&0], - h: universal_params.h, - beta_h: universal_params.beta_h, - prepared_h: universal_params.prepared_h.clone(), - prepared_beta_h: universal_params.prepared_beta_h.clone(), - }; - Ok((powers, vk)) - } - - /// commit implements the CommitmentScheme commit interface, adapting the implementation from - /// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L178 - /// with the main difference being the removal of the blinding factors and the no-dependency to - /// the Pairing trait. - fn commit( - params: &Self::ProverParams, - v: &[E::ScalarField], - _blind: &E::ScalarField, - ) -> Result { - if !_blind.is_zero() || H { - return Err(Error::NotSupportedYet("hiding".to_string())); - } - - let polynomial = poly_from_vec(v.to_vec())?; - check_degree_is_too_large(polynomial.degree(), params.powers_of_g.len())?; - - let (num_leading_zeros, plain_coeffs) = - skip_first_zero_coeffs_and_convert_to_bigints(&polynomial); - let commitment = ::msm_bigint( - ¶ms.powers_of_g[num_leading_zeros..], - &plain_coeffs, - ); - Ok(commitment) - } - - /// prove implements the CommitmentScheme prove interface, adapting the implementation from - /// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L307 - /// with the main difference being the removal of the blinding factors and the no-dependency to - /// the Pairing trait. - fn prove( - params: &Self::ProverParams, - transcript: &mut impl Transcript, - cm: &E::G1, - v: &[E::ScalarField], - _blind: &E::ScalarField, - _rng: Option<&mut dyn RngCore>, - ) -> Result { - transcript.absorb_nonnative(cm); - let challenge = transcript.get_challenge(); - Self::prove_with_challenge(params, challenge, v, _blind, _rng) - } - - fn prove_with_challenge( - params: &Self::ProverParams, - challenge: Self::ProverChallenge, - v: &[E::ScalarField], - _blind: &E::ScalarField, - _rng: Option<&mut dyn RngCore>, - ) -> Result { - if !_blind.is_zero() || H { - return Err(Error::NotSupportedYet("hiding".to_string())); - } - - let polynomial = poly_from_vec(v.to_vec())?; - check_degree_is_too_large(polynomial.degree(), params.powers_of_g.len())?; - - // Compute q(x) = (p(x) - p(z)) / (x-z). Observe that this quotient does not change with z - // because p(z) is the remainder term. We can therefore omit p(z) when computing the - // quotient. - let divisor = DensePolynomial::::from_coefficients_vec(vec![ - -challenge, - E::ScalarField::one(), - ]); - let (witness_poly, remainder_poly) = DenseOrSparsePolynomial::from(&polynomial) - .divide_with_q_and_r(&DenseOrSparsePolynomial::from(&divisor)) - // the panic inside `divide_with_q_and_r` should never be reached, since the divisor - // polynomial is constructed right before and is set to not be zero. And the `.unwrap` - // should not give an error. - .unwrap(); - - let eval = if remainder_poly.is_zero() { - E::ScalarField::zero() - } else { - remainder_poly[0] - }; - - check_degree_is_too_large(witness_poly.degree(), params.powers_of_g.len())?; - let (num_leading_zeros, witness_coeffs) = - skip_first_zero_coeffs_and_convert_to_bigints(&witness_poly); - let proof = ::msm_bigint( - ¶ms.powers_of_g[num_leading_zeros..], - &witness_coeffs, - ); - - Ok(Proof { eval, proof }) - } - - fn verify( - params: &Self::VerifierParams, - transcript: &mut impl Transcript, - cm: &E::G1, - proof: &Self::Proof, - ) -> Result<(), Error> { - transcript.absorb_nonnative(cm); - let challenge = transcript.get_challenge(); - Self::verify_with_challenge(params, challenge, cm, proof) - } - - fn verify_with_challenge( - params: &Self::VerifierParams, - challenge: Self::Challenge, - cm: &E::G1, - proof: &Self::Proof, - ) -> Result<(), Error> { - if H { - return Err(Error::NotSupportedYet("hiding".to_string())); - } - - // verify the KZG proof using arkworks method - let v = KZG10::>::check( - params, // vk - &KZG10Commitment(cm.into_affine()), - challenge, - proof.eval, - &KZG10Proof:: { - w: proof.proof.into_affine(), - random_v: None, - }, - )?; - if !v { - return Err(Error::CommitmentVerificationFail); - } - Ok(()) - } -} - -fn check_degree_is_too_large( - degree: usize, - num_powers: usize, -) -> Result<(), ark_poly_commit::error::Error> { - let num_coefficients = degree + 1; - if num_coefficients > num_powers { - Err(ark_poly_commit::error::Error::TooManyCoefficients { - num_coefficients, - num_powers, - }) - } else { - Ok(()) - } -} - -fn skip_first_zero_coeffs_and_convert_to_bigints>( - p: &P, -) -> (usize, Vec) { - let mut num_leading_zeros = 0; - while num_leading_zeros < p.coeffs().len() && p.coeffs()[num_leading_zeros].is_zero() { - num_leading_zeros += 1; - } - let coeffs = convert_to_bigints(&p.coeffs()[num_leading_zeros..]); - (num_leading_zeros, coeffs) -} - -fn convert_to_bigints(p: &[F]) -> Vec { - ark_std::cfg_iter!(p) - .map(|s| s.into_bigint()) - .collect::>() -} - -#[cfg(test)] -mod tests { - use ark_bn254::{Bn254, Fr, G1Projective as G1}; - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use ark_std::{test_rng, UniformRand}; - - use super::*; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_kzg_commitment_scheme() -> Result<(), Error> { - let mut rng = &mut test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let transcript_p = &mut PoseidonSponge::::new(&poseidon_config); - let transcript_v = &mut PoseidonSponge::::new(&poseidon_config); - - let n = 10; - let (pk, vk): (ProverKey, VerifierKey) = KZG::::setup(&mut rng, n)?; - - let v: Vec = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect(); - let cm = KZG::::commit(&pk, &v, &Fr::zero())?; - - let proof = KZG::::prove(&pk, transcript_p, &cm, &v, &Fr::zero(), None)?; - - // verify the proof: - KZG::::verify(&vk, transcript_v, &cm, &proof)?; - Ok(()) - } -} diff --git a/folding-schemes/src/commitment/mod.rs b/folding-schemes/src/commitment/mod.rs deleted file mode 100644 index 0c9301d69..000000000 --- a/folding-schemes/src/commitment/mod.rs +++ /dev/null @@ -1,165 +0,0 @@ -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; - -use crate::transcript::Transcript; -use crate::{Curve, Error}; - -pub mod ipa; -pub mod kzg; -pub mod pedersen; - -/// CommitmentScheme defines the vector commitment scheme trait. Where `H` indicates if to use the -/// commitment in hiding mode or not. -pub trait CommitmentScheme: Clone + Debug { - type ProverParams: Clone + Debug + CanonicalSerialize + CanonicalDeserialize; - type VerifierParams: Clone + Debug + CanonicalSerialize + CanonicalDeserialize; - type Proof: Clone + Debug + CanonicalSerialize + CanonicalDeserialize; - type ProverChallenge: Clone + Debug; - type Challenge: Clone + Debug; - - fn is_hiding() -> bool; - - fn setup( - rng: impl RngCore, - len: usize, - ) -> Result<(Self::ProverParams, Self::VerifierParams), Error>; - - fn commit( - params: &Self::ProverParams, - v: &[C::ScalarField], - blind: &C::ScalarField, - ) -> Result; - - fn prove( - params: &Self::ProverParams, - transcript: &mut impl Transcript, - cm: &C, - v: &[C::ScalarField], - blind: &C::ScalarField, - rng: Option<&mut dyn RngCore>, - ) -> Result; - - /// same as `prove` but instead of providing a Transcript to use, providing the already - /// computed challenge - fn prove_with_challenge( - params: &Self::ProverParams, - challenge: Self::ProverChallenge, - v: &[C::ScalarField], - blind: &C::ScalarField, - rng: Option<&mut dyn RngCore>, - ) -> Result; - - fn verify( - params: &Self::VerifierParams, - transcript: &mut impl Transcript, - cm: &C, - proof: &Self::Proof, - ) -> Result<(), Error>; - - /// same as `verify` but instead of providing a Transcript to use, providing the already - /// computed challenge - fn verify_with_challenge( - params: &Self::VerifierParams, - challenge: Self::Challenge, - cm: &C, - proof: &Self::Proof, - ) -> Result<(), Error>; -} - -#[cfg(test)] -mod tests { - use super::*; - use ark_bn254::{Bn254, Fr, G1Projective as G1}; - use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, - }; - use ark_poly_commit::kzg10::VerifierKey; - use ark_std::Zero; - use ark_std::{test_rng, UniformRand}; - - use super::ipa::IPA; - use super::kzg::{ProverKey, KZG}; - use super::pedersen::Pedersen; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_homomorphic_property_using_Commitment_trait() -> Result<(), Error> { - let mut rng = &mut test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let n: usize = 128; - - // set random vector for the test - let v_1: Vec = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect(); - let v_2: Vec = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect(); - // set a random challenge for the random linear combination - let r = Fr::rand(rng); - - // setup params for Pedersen & KZG - let (pedersen_params, _) = Pedersen::::setup(&mut rng, n)?; - let (kzg_pk, kzg_vk): (ProverKey, VerifierKey) = KZG::::setup(rng, n)?; - - // test with Pedersen - let _ = test_homomorphic_property_using_Commitment_trait_opt::>( - &poseidon_config, - &pedersen_params, - &pedersen_params, - r, - &v_1, - &v_2, - )?; - // test with IPA - let _ = test_homomorphic_property_using_Commitment_trait_opt::>( - &poseidon_config, - &pedersen_params, - &pedersen_params, - r, - &v_1, - &v_2, - )?; - // test with KZG - let _ = test_homomorphic_property_using_Commitment_trait_opt::>( - &poseidon_config, - &kzg_pk, - &kzg_vk, - r, - &v_1, - &v_2, - )?; - Ok(()) - } - - fn test_homomorphic_property_using_Commitment_trait_opt>( - poseidon_config: &PoseidonConfig, - prover_params: &CS::ProverParams, - verifier_params: &CS::VerifierParams, - r: C::ScalarField, - v_1: &[C::ScalarField], - v_2: &[C::ScalarField], - ) -> Result<(), Error> { - // compute the commitment of the two vectors using the given CommitmentScheme - let cm_1 = CS::commit(prover_params, v_1, &C::ScalarField::zero())?; - let cm_2 = CS::commit(prover_params, v_2, &C::ScalarField::zero())?; - - // random linear combination of the commitments and their witnesses (vectors v_i) - let cm_3 = cm_1 + cm_2.mul(r); - let v_3: Vec = v_1.iter().zip(v_2).map(|(a, b)| *a + (r * b)).collect(); - - // compute the proof of the cm_3 - let transcript_p = &mut PoseidonSponge::::new(poseidon_config); - let proof = CS::prove( - prover_params, - transcript_p, - &cm_3, - &v_3, - &C::ScalarField::zero(), - None, - )?; - - // verify the opening proof - let transcript_v = &mut PoseidonSponge::::new(poseidon_config); - CS::verify(verifier_params, transcript_v, &cm_3, &proof)?; - Ok(()) - } -} diff --git a/folding-schemes/src/commitment/pedersen.rs b/folding-schemes/src/commitment/pedersen.rs deleted file mode 100644 index 753861d22..000000000 --- a/folding-schemes/src/commitment/pedersen.rs +++ /dev/null @@ -1,308 +0,0 @@ -use ark_r1cs_std::{boolean::Boolean, convert::ToBitsGadget, groups::CurveVar}; -use ark_relations::gr1cs::SynthesisError; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{marker::PhantomData, rand::RngCore, UniformRand, Zero}; - -use super::CommitmentScheme; -use crate::folding::circuits::CF2; -use crate::transcript::Transcript; -use crate::utils::vec::{vec_add, vec_scalar_mul}; -use crate::{Curve, Error}; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Proof { - pub R: C, - pub u: Vec, - pub r_u: C::ScalarField, // blind -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Params { - pub h: C, - pub generators: Vec, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Pedersen { - _c: PhantomData, -} - -/// Implements the CommitmentScheme trait for Pedersen commitments -impl CommitmentScheme for Pedersen { - type ProverParams = Params; - type VerifierParams = Params; - type Proof = Proof; - type ProverChallenge = (C::ScalarField, Vec, C, C::ScalarField); - type Challenge = C::ScalarField; - - fn is_hiding() -> bool { - if H { - return true; - } - false - } - - fn setup( - mut rng: impl RngCore, - len: usize, - ) -> Result<(Self::ProverParams, Self::VerifierParams), Error> { - let generators: Vec = std::iter::repeat_with(|| C::Affine::rand(&mut rng)) - .take(len.next_power_of_two()) - .collect(); - let p = Params:: { - h: C::rand(&mut rng), - generators, - }; - Ok((p.clone(), p)) - } - - fn commit( - params: &Self::ProverParams, - v: &[C::ScalarField], - r: &C::ScalarField, // blinding factor - ) -> Result { - if params.generators.len() < v.len() { - return Err(Error::PedersenParamsLen(params.generators.len(), v.len())); - } - if !H && (!r.is_zero()) { - return Err(Error::BlindingNotZero); - } - - // h⋅r + - // use msm_unchecked because we already ensured at the if that lengths match - if !H { - return Ok(C::msm_unchecked(¶ms.generators[..v.len()], v)); - } - Ok(params.h.mul(r) + C::msm_unchecked(¶ms.generators[..v.len()], v)) - } - - fn prove( - params: &Self::ProverParams, - transcript: &mut impl Transcript, - cm: &C, - v: &[C::ScalarField], - r: &C::ScalarField, // blinding factor - _rng: Option<&mut dyn RngCore>, - ) -> Result { - transcript.absorb_nonnative(cm); - let r1 = transcript.get_challenge(); - let d = transcript.get_challenges(v.len()); - - // R = h⋅r_1 + - // use msm_unchecked because we already ensured at the if that lengths match - let mut R: C = C::msm_unchecked(¶ms.generators[..d.len()], &d); - if H { - R += params.h.mul(r1); - } - - transcript.absorb_nonnative(&R); - let e = transcript.get_challenge(); - - let challenge = (r1, d, R, e); - Self::prove_with_challenge(params, challenge, v, r, _rng) - } - - fn prove_with_challenge( - params: &Self::ProverParams, - challenge: Self::ProverChallenge, - v: &[C::ScalarField], // vector - r: &C::ScalarField, // blinding factor - _rng: Option<&mut dyn RngCore>, - ) -> Result { - if params.generators.len() < v.len() { - return Err(Error::PedersenParamsLen(params.generators.len(), v.len())); - } - if !H && (!r.is_zero()) { - return Err(Error::BlindingNotZero); - } - let (r1, d, R, e): (C::ScalarField, Vec, C, C::ScalarField) = challenge; - - // u = d + v⋅e - let u = vec_add(&vec_scalar_mul(v, &e), &d)?; - // r_u = e⋅r + r_1 - let mut r_u = C::ScalarField::zero(); - if H { - r_u = e * r + r1; - } - - Ok(Self::Proof { R, u, r_u }) - } - - fn verify( - params: &Self::VerifierParams, - transcript: &mut impl Transcript, - cm: &C, - proof: &Proof, - ) -> Result<(), Error> { - transcript.absorb_nonnative(cm); - transcript.get_challenge(); // r_1 - transcript.get_challenges(proof.u.len()); // d - transcript.absorb_nonnative(&proof.R); - let e = transcript.get_challenge(); - Self::verify_with_challenge(params, e, cm, proof) - } - - fn verify_with_challenge( - params: &Self::VerifierParams, - challenge: Self::Challenge, - cm: &C, - proof: &Proof, - ) -> Result<(), Error> { - if params.generators.len() < proof.u.len() { - return Err(Error::PedersenParamsLen( - params.generators.len(), - proof.u.len(), - )); - } - if !H && (!proof.r_u.is_zero()) { - return Err(Error::BlindingNotZero); - } - - let e = challenge; - - // check that: R + cm⋅e == h⋅r_u + - let lhs = proof.R + cm.mul(e); - // use msm_unchecked because we already ensured at the if that lengths match - let mut rhs = C::msm_unchecked(¶ms.generators[..proof.u.len()], &proof.u); - if H { - rhs += params.h.mul(proof.r_u); - } - if lhs != rhs { - return Err(Error::CommitmentVerificationFail); - } - Ok(()) - } -} - -pub struct PedersenGadget { - _c: PhantomData, -} - -impl PedersenGadget { - pub fn commit( - h: &C::Var, - g: &[C::Var], - v: &[Vec>>], - r: &[Boolean>], - ) -> Result { - let mut res = C::Var::zero(); - if H { - res += h.scalar_mul_le(r.iter())?; - } - let n = v.len(); - if n % 2 == 1 { - res += g[n - 1].scalar_mul_le(v[n - 1].to_bits_le()?.iter())?; - } else { - res += g[n - 1].joint_scalar_mul_be( - &g[n - 2], - v[n - 1].to_bits_le()?.iter(), - v[n - 2].to_bits_le()?.iter(), - )?; - } - for i in (1..n - 1).step_by(2) { - res += g[i - 1].joint_scalar_mul_be( - &g[i], - v[i - 1].to_bits_le()?.iter(), - v[i].to_bits_le()?.iter(), - )?; - } - Ok(res) - } -} - -#[cfg(test)] -mod tests { - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use ark_ff::{BigInteger, PrimeField}; - use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; - use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; - use ark_relations::gr1cs::ConstraintSystem; - - use super::*; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_pedersen() -> Result<(), Error> { - let _ = test_pedersen_opt::()?; - let _ = test_pedersen_opt::()?; - Ok(()) - } - fn test_pedersen_opt() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - - let n: usize = 10; - // setup params - let (params, _) = Pedersen::::setup(&mut rng, n)?; - let poseidon_config = poseidon_canonical_config::(); - - // init Prover's transcript - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - // init Verifier's transcript - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(n) - .collect(); - // blinding factor - let r: Fr = if hiding { - Fr::rand(&mut rng) - } else { - Fr::zero() - }; - let cm = Pedersen::::commit(¶ms, &v, &r)?; - let proof = - Pedersen::::prove(¶ms, &mut transcript_p, &cm, &v, &r, None)?; - Pedersen::::verify(¶ms, &mut transcript_v, &cm, &proof)?; - Ok(()) - } - - #[test] - fn test_pedersen_circuit() -> Result<(), Error> { - let _ = test_pedersen_circuit_opt::(8)?; - let _ = test_pedersen_circuit_opt::(8)?; - let _ = test_pedersen_circuit_opt::(9)?; - let _ = test_pedersen_circuit_opt::(9)?; - Ok(()) - } - fn test_pedersen_circuit_opt(n: usize) -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - - // setup params - let (params, _) = Pedersen::::setup(&mut rng, n)?; - - let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(n) - .collect(); - // blinding factor - let r: Fr = if hiding { - Fr::rand(&mut rng) - } else { - Fr::zero() - }; - let cm = Pedersen::::commit(¶ms, &v, &r)?; - - let v_bits: Vec> = v.iter().map(|val| val.into_bigint().to_bits_le()).collect(); - let r_bits: Vec = r.into_bigint().to_bits_le(); - - // circuit - let cs = ConstraintSystem::::new_ref(); - - // prepare inputs - let vVar: Vec>> = v_bits - .iter() - .map(|val_bits| Vec::>::new_witness(cs.clone(), || Ok(val_bits.clone()))) - .collect::>()?; - let rVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits))?; - let gVar = Vec::::new_witness(cs.clone(), || Ok(params.generators))?; - let hVar = GVar::new_witness(cs.clone(), || Ok(params.h))?; - let expected_cmVar = GVar::new_witness(cs.clone(), || Ok(cm))?; - - // use the gadget - let cmVar = PedersenGadget::::commit(&hVar, &gVar, &vVar, &rVar)?; - cmVar.enforce_equal(&expected_cmVar)?; - - assert!(cs.is_satisfied()?); - - Ok(()) - } -} diff --git a/folding-schemes/src/constants.rs b/folding-schemes/src/constants.rs deleted file mode 100644 index e256638f6..000000000 --- a/folding-schemes/src/constants.rs +++ /dev/null @@ -1,5 +0,0 @@ -// used for the RO challenges. -// From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness -// error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this -// case, we keep the size of S close to 2^128. -pub const NOVA_N_BITS_RO: usize = 128; diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs deleted file mode 100644 index edd409157..000000000 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ /dev/null @@ -1,943 +0,0 @@ -/// Contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits and functions that -/// are shared across the different folding schemes -use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge}; -use ark_ec::AffineRepr; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - convert::ToConstraintFieldGadget, - eq::EqGadget, - fields::fp::FpVar, - prelude::CurveVar, - GR1CSVar, -}; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, -}; -use ark_std::{borrow::Borrow, fmt::Debug, marker::PhantomData, rand::RngCore, One}; - -use super::{ - nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, - CF1, CF2, -}; -use crate::arith::{ - r1cs::{circuits::R1CSMatricesVar, extract_w_x, R1CS}, - ArithRelationGadget, -}; -use crate::commitment::CommitmentScheme; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::{ - nova::nifs::{nova::NIFS, NIFSTrait}, - traits::InputizeNonNative, -}; -use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; -use crate::utils::gadgets::{EquivalenceGadget, VectorGadget}; -use crate::{Curve, Error}; - -/// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and -/// witness as `CycleFoldWitness`, for clarity and consistency -pub use crate::folding::nova::{ - CommittedInstance as CycleFoldCommittedInstance, Witness as CycleFoldWitness, -}; - -impl InputizeNonNative> for CycleFoldCommittedInstance { - /// Returns the internal representation in the same order as how the value - /// is allocated in `CycleFoldCommittedInstanceVar::new_input`. - fn inputize_nonnative(&self) -> Vec> { - [ - self.u.inputize_nonnative(), - self.x.inputize_nonnative(), - self.cmE.inputize(), - self.cmW.inputize(), - ] - .concat() - } -} - -/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance represented -/// in folding verifier circuit -#[derive(Debug, Clone)] -pub struct CycleFoldCommittedInstanceVar { - pub cmE: C::Var, - pub u: NonNativeUintVar>, - pub cmW: C::Var, - pub x: Vec>>, -} - -impl AllocVar, CF2> - for CycleFoldCommittedInstanceVar -{ - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let u = - NonNativeUintVar::>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; - let x: Vec>> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - let cmE = C::Var::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; - let cmW = C::Var::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; - - Ok(Self { cmE, u, cmW, x }) - }) - } -} - -impl AbsorbNonNative for CycleFoldCommittedInstance { - // Compatible with the in-circuit `CycleFoldCommittedInstanceVar::to_native_sponge_field_elements` - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - self.u.to_native_sponge_field_elements(dest); - self.x.to_native_sponge_field_elements(dest); - let (cmE_x, cmE_y) = self.cmE.into_affine().xy().unwrap_or_default(); - let (cmW_x, cmW_y) = self.cmW.into_affine().xy().unwrap_or_default(); - cmE_x.to_sponge_field_elements(dest); - cmE_y.to_sponge_field_elements(dest); - cmW_x.to_sponge_field_elements(dest); - cmW_y.to_sponge_field_elements(dest); - } -} - -impl AbsorbNonNativeGadget for CycleFoldCommittedInstanceVar { - /// Extracts the underlying field elements from `CycleFoldCommittedInstanceVar`, in the order - /// of `u`, `x`, `cmE.x`, `cmE.y`, `cmW.x`, `cmW.y`, `cmE.is_inf || cmW.is_inf` (|| is for - /// concat). - fn to_native_sponge_field_elements(&self) -> Result>>, SynthesisError> { - let mut cmE_elems = self.cmE.to_constraint_field()?; - let mut cmW_elems = self.cmW.to_constraint_field()?; - - // See `transcript/poseidon.rs: TranscriptVar::absorb_point` for details - // why the last element is unnecessary. - cmE_elems.pop(); - cmW_elems.pop(); - - Ok([ - self.u.to_native_sponge_field_elements()?, - self.x - .iter() - .map(|i| i.to_native_sponge_field_elements()) - .collect::, _>>()? - .concat(), - cmE_elems, - cmW_elems, - ] - .concat()) - } -} - -impl CycleFoldCommittedInstanceVar { - /// Creates a new `CycleFoldCommittedInstanceVar` from the given components. - pub fn new_incoming_from_components>( - cmW: C2::Var, - r_bits: &[Boolean>], - points: Vec>, - ) -> Result { - // Construct the public inputs `x` from `r_bits` and `points`. - // Note that the underlying field can only safely store - // `CF1::::MODULUS_BIT_SIZE - 1` bits, but `r_bits` may be longer - // than that. - // Thus, we need to chunk `r_bits` into pieces and convert each piece - // to a `NonNativeUintVar`. - let x = r_bits - .chunks(CF1::::MODULUS_BIT_SIZE as usize - 1) - .map(|bits| { - let mut bits = bits.to_vec(); - bits.resize(CF1::::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }) - .chain(points.into_iter().flat_map(|p| [p.x, p.y])) - .collect::>(); - Ok(Self { - // `cmE` is always zero for incoming instances - cmE: C2::Var::zero(), - // `u` is always one for incoming instances - u: NonNativeUintVar::new_constant(ConstraintSystemRef::None, CF1::::one())?, - cmW, - x, - }) - } -} - -impl CycleFoldCommittedInstance { - /// hash_cyclefold implements the committed instance hash compatible with the - /// in-circuit implementation `CycleFoldCommittedInstanceVar::hash`. - /// Returns `H(U_i)`, where `U_i` is a `CycleFoldCommittedInstance`. - pub fn hash_cyclefold>(&self, sponge: &T) -> C::BaseField { - let mut sponge = sponge.clone(); - sponge.absorb_nonnative(self); - sponge.squeeze_field_elements(1)[0] - } -} - -impl CycleFoldCommittedInstanceVar { - /// hash implements the committed instance hash compatible with the native - /// implementation `CycleFoldCommittedInstance::hash_cyclefold`. - /// Returns `H(U_i)`, where `U` is a `CycleFoldCommittedInstanceVar`. - /// - /// Additionally it returns the vector of the field elements from the self - /// parameters, so they can be reused in other gadgets without recalculating - /// (reconstraining) them. - #[allow(clippy::type_complexity)] - pub fn hash, S>>( - &self, - sponge: &T, - ) -> Result<(FpVar>, Vec>>), SynthesisError> { - let mut sponge = sponge.clone(); - let U_vec = self.to_native_sponge_field_elements()?; - sponge.absorb(&U_vec)?; - Ok(( - // `unwrap` is safe because the sponge is guaranteed to return a single element - sponge.squeeze_field_elements(1)?.pop().unwrap(), - U_vec, - )) - } -} - -/// In-circuit representation of the Witness associated to the CommittedInstance, but with -/// non-native representation, since it is used to represent the CycleFold witness. This struct is -/// used in the Decider circuit. -#[derive(Debug, Clone)] -pub struct CycleFoldWitnessVar { - pub E: Vec>>, - pub rE: NonNativeUintVar>, - pub W: Vec>>, - pub rW: NonNativeUintVar>, -} - -impl AllocVar, CF2> for CycleFoldWitnessVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let E = Vec::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?; - let rE = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().rE), mode)?; - - let W = Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?; - let rW = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().rW), mode)?; - - Ok(Self { E, rE, W, rW }) - }) - } -} - -/// This is the gadget used in the AugmentedFCircuit to verify the CycleFold instances folding, -/// which checks the correct RLC of u,x,cmE,cmW (hence the name containing 'Full', since it checks -/// all the RLC values, not only the native ones). It assumes that ci2.cmE=0, ci2.u=1. -pub struct NIFSFullGadget { - _c: PhantomData, -} - -impl NIFSFullGadget { - pub fn fold_committed_instance( - r_bits: Vec>>, - cmT: C::Var, - ci1: CycleFoldCommittedInstanceVar, - // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) - ci2: CycleFoldCommittedInstanceVar, - ) -> Result, SynthesisError> { - // r_nonnat is equal to r_bits just that in a different format - let r_nonnat = { - let mut bits = r_bits.clone(); - bits.resize(CF1::::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }; - Ok(CycleFoldCommittedInstanceVar { - cmE: cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE, - cmW: ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?, - u: ci1.u.add_no_align(&r_nonnat)?.modulo::>()?, - x: ci1 - .x - .iter() - .zip(ci2.x) - .map(|(a, b)| { - a.add_no_align(&r_nonnat.mul_no_align(&b)?)? - .modulo::>() - }) - .collect::, _>>()?, - }) - } - - pub fn verify( - // assumes that r_bits is equal to r_nonnat just that in a different format - r_bits: Vec>>, - cmT: C::Var, - ci1: CycleFoldCommittedInstanceVar, - // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) - ci2: CycleFoldCommittedInstanceVar, - ci3: CycleFoldCommittedInstanceVar, - ) -> Result<(), SynthesisError> { - let ci = Self::fold_committed_instance(r_bits, cmT, ci1, ci2)?; - - ci.cmE.enforce_equal(&ci3.cmE)?; - ci.u.enforce_equal_unaligned(&ci3.u)?; - ci.cmW.enforce_equal(&ci3.cmW)?; - for (x, y) in ci.x.iter().zip(ci3.x.iter()) { - x.enforce_equal_unaligned(y)?; - } - - Ok(()) - } -} - -impl ArithRelationGadget, CycleFoldCommittedInstanceVar> - for R1CSMatricesVar, NonNativeUintVar>> -{ - type Evaluation = (Vec>>, Vec>>); - - fn eval_relation( - &self, - w: &CycleFoldWitnessVar, - u: &CycleFoldCommittedInstanceVar, - ) -> Result { - self.eval_at_z(&[&[u.u.clone()][..], &u.x, &w.W].concat()) - } - - fn enforce_evaluation( - w: &CycleFoldWitnessVar, - _u: &CycleFoldCommittedInstanceVar, - (AzBz, uCz): Self::Evaluation, - ) -> Result<(), SynthesisError> { - EquivalenceGadget::>::enforce_equivalent(&AzBz[..], &uCz.add(&w.E)?[..]) - } -} - -/// CycleFoldChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a -/// rust-native and a in-circuit compatible versions. -pub struct CycleFoldChallengeGadget { - _c: PhantomData, // Nova's Curve2, the one used for the CycleFold circuit -} -impl CycleFoldChallengeGadget { - pub fn get_challenge_native>( - transcript: &mut T, - U_i: &CycleFoldCommittedInstance, - u_i: &CycleFoldCommittedInstance, - cmT: C, - ) -> Vec { - transcript.absorb_nonnative(U_i); - transcript.absorb_nonnative(u_i); - transcript.absorb_point(&cmT); - transcript.squeeze_bits(NOVA_N_BITS_RO) - } - - // compatible with the native get_challenge_native - pub fn get_challenge_gadget>( - transcript: &mut T, - U_i_vec: &[FpVar], - u_i: &CycleFoldCommittedInstanceVar, - cmT: &C::Var, - ) -> Result>, SynthesisError> { - transcript.absorb(&U_i_vec)?; - transcript.absorb_nonnative(u_i)?; - transcript.absorb_point(cmT)?; - transcript.squeeze_bits(NOVA_N_BITS_RO) - } -} - -/// [`CycleFoldConfig`] controls the behavior of [`CycleFoldCircuit`]. -/// -/// Looking ahead, the circuit computes the random linear combination of points, -/// which is essentially done by iteratively computing `P = (P + p_i) * r_i`, -/// where `P` is the folded point, `p_i` is the input point, and `r_i` is the -/// randomness. -pub trait CycleFoldConfig: Sized + Default { - /// `N_INPUT_POINTS` specifies the number of input points that are folded in - /// [`CycleFoldCircuit`] via random linear combinations. - const N_INPUT_POINTS: usize; - /// `N_UNIQUE_RANDOMNESSES` specifies the number of *unique* randomnesses - /// allocated in [`CycleFoldCircuit`]. Although the linear combination in - /// general consists of multiple randomnesses, some folding schemes (such as - /// Nova and HyperNova) only need a single one. Thus, by setting this value, - /// the circuit can learn how many randomnesses are used and how long the - /// public inputs vector should be. - const N_UNIQUE_RANDOMNESSES: usize; - /// `RANDOMNESS_BIT_LENGTH` is the maximum bit length of a randomness `r_i`. - const RANDOMNESS_BIT_LENGTH: usize; - /// `FIELD_CAPACITY` is the maximum number of bits that can be stored in a - /// field element. - /// - /// By default, `FIELD_CAPACITY` is set to `MODULUS_BIT_SIZE - 1`. - /// - /// Given a randomness `r_i` with `RANDOMNESS_BIT_LENGTH` bits, we need - /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY` field elements to represent it - /// *compactly* in-circuit. - const FIELD_CAPACITY: usize = CF2::::MODULUS_BIT_SIZE as usize - 1; - - /// Public inputs length for the [`CycleFoldCircuit`], which depends on the - /// above constants defined by the concrete folding scheme. For example: - /// * In Nova, this is `|r| + |p_1| + |p_2| + |P|` - /// * In HyperNova, this is `|r| + |p_i| * n_points + |P|`. - /// * In ProtoGalaxy, this is `|[..., r_i, ...]| + |p_i| * n_points + |P|`. - /// - /// As explained above, `|r|` (i.e., the length of a single randomness) is - /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY`. - /// When there are multiple randomnesses, the length of `|[..., r_i, ...]|` - /// is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY`, as - /// the bits of all randomnesses are concatenated before being packed into - /// field elements. - /// The length of a point `p_i` when treated as public inputs is 2, as we - /// only need the `x` and `y` coordinates of the point. - /// - /// Thus, `IO_LEN` is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY + 2 * (N_INPUT_POINTS + 1)`. - const IO_LEN: usize = { - (Self::RANDOMNESS_BIT_LENGTH * Self::N_UNIQUE_RANDOMNESSES).div_ceil(Self::FIELD_CAPACITY) - + 2 * (Self::N_INPUT_POINTS + 1) - }; - - /// `alloc_points` allocates the points that are going to be folded in the - /// [`CycleFoldCircuit`] via random linear combinations. - /// - /// The implementation must allocate the points as *witness* variables (i.e. - /// by calling [`AllocVar::new_witness`]) first, then mark them as public - /// inputs by calling [`CycleFoldConfig::mark_point_as_public`], and finally - /// return the allocated witness variables. - /// - /// While it is possible to allocate the points as public inputs directly, - /// we do not use this approach because this will create a longer vector of - /// public inputs, which is not ideal for the augmented step circuit on the - /// primary curve. - fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError>; - - /// `alloc_randomnesses` allocates the randomnesses used as coefficients of - /// the random linear combinations in the `CycleFoldCircuit`. - /// - /// The implementation must allocate the randomnesses as *witness* variables - /// (i.e. by calling [`AllocVar::new_witness`]) first, then mark them as - /// public inputs by calling [`CycleFoldConfig::mark_point_as_public`], and - /// finally return the allocated witness variables. - /// - /// See [`CycleFoldConfig::alloc_points`] for the reason why they need to be - /// allocated as witness variables first and converted to public later. - /// - /// In addition, because the circuit computes `P = (P + p_i) * r_i` for each - /// `i` from `N_INPUT_POINTS - 1` down to `0`, the actual linear combination - /// is `P = r_0 * p_0 + (r_0 r_1) * p_1 + (r_0 r_1 r_2) * p_2 + ...`. Thus, - /// to compute `P = R_0 p_0 + R_1 p_1 + R_2 p_2 + ...`, the implementation - /// should return `r_0 = R_0, r_1 = R_1 / R_0, ..., r_i = R_i / R_{i - 1}`. - /// A special case is `R_i = R^i`, where the allocated randomnesses become - /// `r_0 = 1, r_1 = r_2 = ... = R`. - fn alloc_randomnesses( - &self, - cs: ConstraintSystemRef>, - ) -> Result>>>, SynthesisError>; - - /// `mark_point_as_public` marks a point as public. - /// - /// The final vector of public inputs is shorter than the result of calling - /// [`AllocVar::new_input`], because we only need the x and y coordinates of - /// the point, but the `infinity` flag is not necessary. - fn mark_point_as_public(point: &C::Var) -> Result<(), SynthesisError> { - for x in &point.to_constraint_field()?[..2] { - // This line "converts" `x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `x`. - // While comparing `x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `x` computed in-circuit. - FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(x)?; - } - Ok(()) - } - - /// `mark_randomness_as_public` marks randomness as public. - /// - /// The final vector of public inputs is shorter than the result of calling - /// [`AllocVar::new_input`], because we pack the bits of randomness into - /// a compact field elements. - fn mark_randomness_as_public(r: &[Boolean>]) -> Result<(), SynthesisError> { - for bits in r.chunks(Self::FIELD_CAPACITY) { - let x = Boolean::le_bits_to_fp(bits)?; - FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(&x)?; - } - Ok(()) - } - - /// `build_circuit` creates a new [`CycleFoldCircuit`] with `self` as the - /// configuration. - fn build_circuit(self) -> CycleFoldCircuit { - CycleFoldCircuit { - _c: PhantomData, - cfg: self, - } - } -} - -#[derive(Debug, Clone)] -pub struct CycleFoldCircuit> { - _c: PhantomData, - cfg: CFG, -} - -impl> Default for CycleFoldCircuit { - fn default() -> Self { - CFG::default().build_circuit() - } -} - -impl> ConstraintSynthesizer> for CycleFoldCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - let rs = self.cfg.alloc_randomnesses(cs.clone())?; - let points = self.cfg.alloc_points(cs.clone())?; - - #[cfg(test)] - { - assert_eq!(CFG::N_INPUT_POINTS, points.len()); - assert_eq!(CFG::N_INPUT_POINTS, rs.len()); - for r in &rs { - assert_eq!(CFG::RANDOMNESS_BIT_LENGTH, r.len()); - } - } - - // A slightly optimized version of `scalar_mul_le`. - fn point_mul( - point: &C::Var, - r: &[Boolean>], - ) -> Result { - if r.is_constant() { - let r = CF1::::from( as PrimeField>::BigInt::from_bits_le(&r.value()?)); - if r.is_one() { - return Ok(point.clone()); - } - } - point.scalar_mul_le(r.iter()) - } - - // Given a vector of points (over the primary curve) that are obtained - // from the instances of the folding scheme, we fold them *natively* in - // the CycleFold circuit (over the secondary curve). - // * In Nova, we need to compute P = p_0 + R * p_1. - // - for the cmW we're computing: U_i1.cmW = U_i.cmW + R * u_i.cmW - // - for the cmE we're computing: U_i1.cmE = U_i.cmE + R * cmT + R^2 * u_i.cmE, where u_i.cmE - // is assumed to be 0, so, U_i1.cmE = U_i.cmE + R * cmT - // * In HyperNova, we need to compute P = p_0 + R * p_1 + R^2 * p_2 + ... + R^{n-1} * p_{n-1}. - // * In ProtoGalaxy, we need to compute P = R_0 * p_0 + R_1 * p_1 + R_2 * p_2 + ... + R_{n-1} * p_{n-1}. - // - // To handle HyperNova more efficiently (with less constraints), we do - // P = ((((p_{n-1} * R) + p_{n-2}) * R + p_{n-3}) * R + ...) * R + p_0. - // This can be done iteratively by computing P = (P + p_i) * R. - // - // We further generalize this to support ProtoGalaxy, which now becomes - // P = (((((p_{n-1} * r_{n-1}) + p_{n-2}) * r_{n-2} + p_{n-3}) * r_{n-3} + ...) * r_1 + p_0) * r_0 - // - // Here, r_0 = 1, r_1 = r_2 = ... = r_{n-1} = R for Nova and HyperNova, - // and r_i = R_i / R_{i - 1} for ProtoGalaxy. - let mut p_folded = point_mul::( - &points[CFG::N_INPUT_POINTS - 1], - &rs[CFG::N_INPUT_POINTS - 1], - )?; - for i in (0..CFG::N_INPUT_POINTS - 1).rev() { - p_folded = point_mul::(&(p_folded + &points[i]), &rs[i])?; - } - - CFG::mark_point_as_public(&p_folded)?; - - Ok(()) - } -} - -impl> CycleFoldCircuit { - /// Generates a pair of incoming instance and witness for the CycleFold - /// circuit. - pub fn generate_incoming_instance_witness< - C2: Curve, BaseField = CF1>, - CS2: CommitmentScheme, - const H: bool, - >( - self, - cf_cs_params: &CS2::ProverParams, - mut rng: impl RngCore, - ) -> Result<(CycleFoldWitness, CycleFoldCommittedInstance), Error> { - let cs2 = ConstraintSystem::new_ref(); - self.generate_constraints(cs2.clone())?; - - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (cf_w_i, cf_x_i) = extract_w_x(&cs2); - - #[cfg(test)] - assert_eq!(cf_x_i.len(), CFG::IO_LEN); - - // generate cyclefold instances - let cf_w_i = CycleFoldWitness::::new::(cf_w_i, cs2.num_constraints(), &mut rng); - let cf_u_i = cf_w_i.commit::(cf_cs_params, cf_x_i)?; - - Ok((cf_w_i, cf_u_i)) - } -} - -/// [`CycleFoldAugmentationGadget`] implements methods for folding multiple -/// CycleFold instances, both natively and in the augmented step circuit. -pub struct CycleFoldAugmentationGadget; - -impl CycleFoldAugmentationGadget { - #[allow(clippy::too_many_arguments, clippy::type_complexity)] - pub fn fold_native, const H: bool>( - transcript: &mut impl Transcript>, - cf_r1cs: &R1CS, - cf_cs_params: &CS::ProverParams, - mut cf_W: CycleFoldWitness, // witness of the running instance - mut cf_U: CycleFoldCommittedInstance, // running instance - cf_ws: Vec>, // witnesses of the incoming instances - cf_us: Vec>, // incoming instances - ) -> Result< - ( - CycleFoldWitness, // W_i1 - CycleFoldCommittedInstance, // U_i1 - Vec, // cmT - ), - Error, - > { - assert_eq!(cf_ws.len(), cf_us.len()); - let mut cf_cmTs = vec![]; - - for (cf_w, cf_u) in cf_ws.into_iter().zip(cf_us) { - // compute T* and cmT* for CycleFoldCircuit - let (cf_T, cf_cmT) = NIFS::>, H>::compute_cyclefold_cmT( - cf_cs_params, - cf_r1cs, - &cf_w, - &cf_u, - &cf_W, - &cf_U, - )?; - cf_cmTs.push(cf_cmT); - - let cf_r_bits = - CycleFoldChallengeGadget::get_challenge_native(transcript, &cf_U, &cf_u, cf_cmT); - let cf_r_Fq = CF1::::from( as PrimeField>::BigInt::from_bits_le(&cf_r_bits)); - - (cf_W, cf_U) = CycleFoldNIFS::::prove( - cf_r_Fq, &cf_W, &cf_U, &cf_w, &cf_u, &cf_T, cf_cmT, - )?; - - #[cfg(test)] - { - use crate::{arith::ArithRelation, folding::traits::CommittedInstanceOps}; - cf_u.check_incoming()?; - cf_r1cs.check_relation(&cf_w, &cf_u)?; - cf_r1cs.check_relation(&cf_W, &cf_U)?; - } - } - - Ok((cf_W, cf_U, cf_cmTs)) - } - - pub fn fold_gadget( - transcript: &mut impl TranscriptVar, S>, - mut cf_U: CycleFoldCommittedInstanceVar, - cf_us: Vec>, - cf_cmTs: Vec, - ) -> Result, SynthesisError> { - assert_eq!(cf_us.len(), cf_cmTs.len()); - - // Fold the incoming CycleFold instances into the running CycleFold - // instance in a iterative way, since `NIFSFullGadget` only supports - // folding one incoming instance at a time. - for (cf_u, cmT) in cf_us.into_iter().zip(cf_cmTs) { - let cf_r_bits = CycleFoldChallengeGadget::get_challenge_gadget( - transcript, - &cf_U.to_native_sponge_field_elements()?, - &cf_u, - &cmT, - )?; - // Fold the current incoming CycleFold instance `cf_u` into the - // running CycleFold instance `cf_U`. - cf_U = NIFSFullGadget::fold_committed_instance(cf_r_bits, cmT, cf_U, cf_u)?; - } - - Ok(cf_U) - } -} - -/// CycleFoldNIFS is a wrapper on top of Nova's NIFS, which just replaces the `prove` and `verify` -/// methods to use a different ChallengeGadget, but internally reuses the other Nova's NIFS -/// methods. -/// It is a custom implementation that does not follow the NIFSTrait because it needs to work over -/// different fields than the main NIFS impls (Nova, Mova, Ova). Could be abstracted, but it's a -/// tradeoff between overcomplexity at the NIFSTrait and the (not much) need of generalization at -/// the CycleFoldNIFS. -pub struct CycleFoldNIFS, const H: bool = false> { - _c2: PhantomData, - _cs: PhantomData, -} -impl, const H: bool> CycleFoldNIFS { - fn prove( - cf_r_Fq: C2::ScalarField, // C2::Fr==C1::Fq - cf_W_i: &CycleFoldWitness, - cf_U_i: &CycleFoldCommittedInstance, - cf_w_i: &CycleFoldWitness, - cf_u_i: &CycleFoldCommittedInstance, - aux_p: &[C2::ScalarField], // = cf_T - aux_v: C2, // = cf_cmT - ) -> Result<(CycleFoldWitness, CycleFoldCommittedInstance), Error> { - let w = NIFS::, H>::fold_witness( - cf_r_Fq, - cf_W_i, - cf_w_i, - &aux_p.to_vec(), - )?; - let ci = Self::verify(cf_r_Fq, cf_U_i, cf_u_i, &aux_v)?; - Ok((w, ci)) - } - fn verify( - r: C2::ScalarField, - U_i: &CycleFoldCommittedInstance, - u_i: &CycleFoldCommittedInstance, - cmT: &C2, // VerifierAux - ) -> Result, Error> { - Ok( - NIFS::, H>::fold_committed_instances( - r, U_i, u_i, cmT, - ), - ) - } -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective}; - use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; - use ark_r1cs_std::GR1CSVar; - use ark_std::{One, UniformRand, Zero}; - - use super::*; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::CommittedInstance; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::utils::get_cm_coordinates; - - struct TestCycleFoldConfig { - r: CF1, - points: Vec, - } - - impl Default for TestCycleFoldConfig { - fn default() -> Self { - let r = CF1::::zero(); - let points = vec![C::zero(); N]; - Self { r, points } - } - } - - impl CycleFoldConfig for TestCycleFoldConfig { - const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; - const N_INPUT_POINTS: usize = N; - const N_UNIQUE_RANDOMNESSES: usize = 1; - - fn alloc_points( - &self, - cs: ConstraintSystemRef>, - ) -> Result, SynthesisError> { - let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; - for point in &points { - Self::mark_point_as_public(point)?; - } - Ok(points) - } - - fn alloc_randomnesses( - &self, - cs: ConstraintSystemRef>, - ) -> Result>>>, SynthesisError> { - let one = &CF1::::one().into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; - let r = &self.r.into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; - let one_var = Vec::new_constant(cs.clone(), one)?; - let r_var = Vec::new_witness(cs.clone(), || Ok(r))?; - Self::mark_randomness_as_public(&r_var)?; - Ok([vec![one_var], vec![r_var; N - 1]].concat()) - } - } - - #[test] - fn test_CycleFoldCircuit_n_points_constraints() -> Result<(), Error> { - const n: usize = 16; - let mut rng = ark_std::test_rng(); - - // points to random-linear-combine - let points: Vec = std::iter::repeat_with(|| Projective::rand(&mut rng)) - .take(n) - .collect(); - - use std::ops::Mul; - let rho_raw = Fq::rand(&mut rng); - let rho_bits = rho_raw.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); - let rho_Fq = - Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).ok_or(Error::OutOfBounds)?; - let rho_Fr = - Fr::from_bigint(BigInteger::from_bits_le(&rho_bits)).ok_or(Error::OutOfBounds)?; - let mut res = Projective::zero(); - use ark_std::One; - let mut rho_i = Fr::one(); - for point_i in points.iter() { - res += point_i.mul(rho_i); - rho_i *= rho_Fr; - } - - // cs is the Constraint System on the Curve Cycle auxiliary curve constraints field - // (E1::Fq=E2::Fr) - let cs = ConstraintSystem::::new_ref(); - - let x: Vec = [ - vec![rho_Fq], - points.iter().flat_map(get_cm_coordinates).collect(), - get_cm_coordinates(&res), - ] - .concat(); - let cf_circuit = TestCycleFoldConfig:: { r: rho_Fr, points }.build_circuit(); - cf_circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - // `instance_assignment[0]` is the constant term 1 - assert_eq!(&cs.borrow().unwrap().instance_assignment()?[1..], &x); - Ok(()) - } - - #[test] - fn test_nifs_full_gadget() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::rand(&mut rng); - let mut transcript_v = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - - // prepare the committed instances to test in-circuit - let ci: Vec> = (0..2) - .into_iter() - .map(|_| CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }) - .collect(); - let (ci1, mut ci2) = (ci[0].clone(), ci[1].clone()); - // make the 2nd instance a 'fresh' instance (ie. cmE=0, u=1) - ci2.cmE = Projective::zero(); - ci2.u = Fr::one(); - - let cmT = Projective::rand(&mut rng); // random only for testing - let (ci3, r_bits) = NIFS::, PoseidonSponge>::verify( - &mut transcript_v, - &ci1, - &ci2, - &cmT, - )?; - - let cs = ConstraintSystem::::new_ref(); - let r_bitsVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits))?; - let ci1Var = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(ci1.clone()) - })?; - let ci2Var = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(ci2.clone()) - })?; - let ci3Var = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(ci3.clone()) - })?; - let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT))?; - - NIFSFullGadget::::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var)?; - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_cyclefold_challenge_gadget() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fq::from(42u32); // only for test - let mut transcript = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - - let u_i = CycleFoldCommittedInstance:: { - cmE: Projective::zero(), // zero on purpose, so we test also the zero point case - u: Fr::zero(), - cmW: Projective::rand(&mut rng), - x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(TestCycleFoldConfig::::IO_LEN) - .collect(), - }; - let U_i = CycleFoldCommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(TestCycleFoldConfig::::IO_LEN) - .collect(), - }; - let cmT = Projective::rand(&mut rng); // random only for testing - - // compute the challenge natively - let r_bits = CycleFoldChallengeGadget::::get_challenge_native( - &mut transcript, - &U_i, - &u_i, - cmT, - ); - - let cs = ConstraintSystem::::new_ref(); - let u_iVar = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(u_i.clone()) - })?; - let U_iVar = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(U_i.clone()) - })?; - let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT))?; - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let mut transcript_var = - PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; - - let r_bitsVar = CycleFoldChallengeGadget::::get_challenge_gadget( - &mut transcript_var, - &U_iVar.to_native_sponge_field_elements()?, - &u_iVar, - &cmTVar, - )?; - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - let rVar = Boolean::le_bits_to_fp(&r_bitsVar)?; - let r = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds)?; - assert_eq!(rVar.value()?, r); - assert_eq!(r_bitsVar.value()?, r_bits); - Ok(()) - } - - #[test] - fn test_cyclefold_hash_gadget() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fq::from(42u32); // only for test - let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - - let U_i = CycleFoldCommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(TestCycleFoldConfig::::IO_LEN) - .collect(), - }; - let h = U_i.hash_cyclefold(&sponge); - - let cs = ConstraintSystem::::new_ref(); - let U_iVar = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(U_i.clone()) - })?; - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let (hVar, _) = U_iVar.hash(&PoseidonSpongeVar::new_with_pp_hash( - &poseidon_config, - &pp_hashVar, - )?)?; - hVar.enforce_equal(&FpVar::new_witness(cs.clone(), || Ok(h))?)?; - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/circuits/decider/mod.rs b/folding-schemes/src/folding/circuits/decider/mod.rs deleted file mode 100644 index d455b6339..000000000 --- a/folding-schemes/src/folding/circuits/decider/mod.rs +++ /dev/null @@ -1,205 +0,0 @@ -use ark_crypto_primitives::sponge::{ - poseidon::constraints::PoseidonSpongeVar, CryptographicSponge, -}; -use ark_ff::PrimeField; -use ark_poly::Polynomial; -use ark_r1cs_std::{ - fields::{fp::FpVar, FieldVar}, - poly::{domain::Radix2DomainVar, evaluations::univariate::EvaluationsVar}, -}; -use ark_relations::gr1cs::SynthesisError; -use ark_std::log2; - -use crate::folding::traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps}; -use crate::transcript::{Transcript, TranscriptVar}; -use crate::utils::vec::poly_from_vec; -use crate::{arith::ArithRelation, folding::circuits::CF1}; -use crate::{Curve, Error}; - -pub mod off_chain; -pub mod on_chain; - -/// Gadget that computes the KZG challenges. -/// It also offers the rust native implementation compatible with the gadget. -pub struct KZGChallengesGadget {} - -impl KZGChallengesGadget { - pub fn get_challenges_native>, U: CommittedInstanceOps>( - transcript: &mut T, - U_i: &U, - ) -> Vec> { - let mut challenges = vec![]; - for cm in U_i.get_commitments() { - transcript.absorb_nonnative(&cm); - challenges.push(transcript.get_challenge()); - } - challenges - } - - pub fn get_challenges_gadget< - C: Curve, - S: CryptographicSponge, - T: TranscriptVar, S>, - U: CommittedInstanceVarOps, - >( - transcript: &mut T, - U_i: &U, - ) -> Result>>, SynthesisError> { - let mut challenges = vec![]; - for cm in U_i.get_commitments() { - transcript.absorb_nonnative(&cm)?; - challenges.push(transcript.get_challenge()?); - } - Ok(challenges) - } -} - -/// Gadget that interpolates the polynomial from the given vector and returns -/// its evaluation at the given point. -/// It also offers the rust native implementation compatible with the gadget. -pub struct EvalGadget {} - -impl EvalGadget { - pub fn evaluate_native(v: &[F], point: F) -> Result { - let mut v = v.to_vec(); - v.resize(v.len().next_power_of_two(), F::zero()); - - Ok(poly_from_vec(v)?.evaluate(&point)) - } - - pub fn evaluate_gadget( - v: &[FpVar], - point: &FpVar, - ) -> Result, SynthesisError> { - let mut v = v.to_vec(); - v.resize(v.len().next_power_of_two(), FpVar::zero()); - let n = v.len() as u64; - let gen = F::get_root_of_unity(n).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; - // `unwrap` below is safe because `Radix2DomainVar::new` only fails if - // `offset.enforce_not_equal(&FpVar::zero())` returns an error. - // But in our case, `offset` is `FpVar::one()`, i.e., both operands of - // `enforce_not_equal` are constants. - // Consequently, `FpVar`'s implementation of `enforce_not_equal` will - // always return `Ok(())`. - let domain = Radix2DomainVar::new(gen, log2(v.len()) as u64, FpVar::one()).unwrap(); - - let evaluations_var = EvaluationsVar::from_vec_and_domain(v, domain, true); - evaluations_var.interpolate_and_evaluate(point) - } -} - -/// This is a temporary workaround for step 6 (running NIFS.V for group elements -/// in circuit) in an NIFS-agnostic way, because different folding schemes have -/// different interfaces of folding verification now. -/// -/// In the future, we may introduce a better solution that uses a trait for all -/// folding schemes that specifies their native and in-circuit behaviors. -pub trait DeciderEnabledNIFS< - C: Curve, - RU: CommittedInstanceOps, // Running instance - IU: CommittedInstanceOps, // Incoming instance - W: WitnessOps>, - A: ArithRelation, -> -{ - type ProofDummyCfg; - type Proof: Dummy; - type RandomnessDummyCfg; - type Randomness: Dummy; - - /// Fold the field elements in `U` and `u` inside the circuit. - /// - /// `U_vec` is `U` expressed as a vector of `FpVar`s, which can be reused - /// before or after calling this function to save constraints. - #[allow(clippy::too_many_arguments)] - fn fold_field_elements_gadget( - arith: &A, - transcript: &mut PoseidonSpongeVar>, - U: RU::Var, - U_vec: Vec>>, - u: IU::Var, - proof: Self::Proof, - randomness: Self::Randomness, - ) -> Result; - - /// Fold the group elements (i.e., commitments) in `U` and `u` outside the - /// circuit. - fn fold_group_elements_native( - U_commitments: &[C], - u_commitments: &[C], - proof: Option, - randomness: Self::Randomness, - ) -> Result, Error>; -} - -#[cfg(test)] -pub mod tests { - use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, poseidon::PoseidonSponge, - }; - use ark_pallas::{Fr, Projective}; - use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::UniformRand; - - use super::*; - use crate::folding::nova::{nifs::nova_circuits::CommittedInstanceVar, CommittedInstance}; - use crate::transcript::poseidon::poseidon_canonical_config; - - // checks that the gadget and native implementations of the challenge computation match - #[test] - fn test_kzg_challenge_gadget() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - - let U_i = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - - // compute the challenge natively - let challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i); - - let cs = ConstraintSystem::::new_ref(); - let U_iVar = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(U_i.clone()))?; - let mut transcript_var = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); - - let challenges_var = - KZGChallengesGadget::get_challenges_gadget(&mut transcript_var, &U_iVar)?; - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(challenges_var.value()?, challenges); - Ok(()) - } - - #[test] - fn test_polynomial_interpolation() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let n = 12; - let l = 1 << n; - - let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(l) - .collect(); - let challenge = Fr::rand(&mut rng); - - use ark_poly::Polynomial; - let polynomial = poly_from_vec(v.to_vec())?; - let eval = polynomial.evaluate(&challenge); - - let cs = ConstraintSystem::::new_ref(); - let vVar = Vec::>::new_witness(cs.clone(), || Ok(v))?; - let challengeVar = FpVar::::new_witness(cs.clone(), || Ok(challenge))?; - - let evalVar = EvalGadget::evaluate_gadget(&vVar, &challengeVar)?; - - assert_eq!(evalVar.value()?, eval); - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/circuits/decider/off_chain.rs b/folding-schemes/src/folding/circuits/decider/off_chain.rs deleted file mode 100644 index 3e29beab1..000000000 --- a/folding-schemes/src/folding/circuits/decider/off_chain.rs +++ /dev/null @@ -1,306 +0,0 @@ -/// This file implements a generic offchain decider circuit. -/// For ethereum use cases, use the `GenericOnchainDeciderCircuit`. -/// More details can be found at the documentation page: -/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html -use ark_crypto_primitives::sponge::{ - constraints::AbsorbGadget, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, -}; -use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::fp::FpVar}; -use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::{marker::PhantomData, Zero}; - -use crate::{ - arith::{ - r1cs::{circuits::R1CSMatricesVar, R1CS}, - ArithRelation, ArithRelationGadget, - }, - folding::{ - circuits::{ - cyclefold::{ - CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, CycleFoldWitness, - }, - decider::{EvalGadget, KZGChallengesGadget}, - nonnative::affine::NonNativeAffineVar, - CF1, CF2, - }, - nova::{decider_eth_circuit::WitnessVar, nifs::nova_circuits::CommittedInstanceVar}, - traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps}, - }, - transcript::TranscriptVar, - Curve, -}; - -use super::DeciderEnabledNIFS; - -/// Circuit that implements part of the in-circuit checks needed for the offchain verification over -/// the Curve2's BaseField (=Curve1's ScalarField). -pub struct GenericOffchainDeciderCircuit1< - C1: Curve, - C2: Curve, - RU: CommittedInstanceOps, // Running instance - IU: CommittedInstanceOps, // Incoming instance - W: WitnessOps>, // Witness - A: ArithRelation, // Constraint system - AVar: ArithRelationGadget, // In-circuit representation of `A` - D: DeciderEnabledNIFS, -> { - pub _avar: PhantomData, - /// Constraint system of the Augmented Function circuit - pub arith: A, - pub poseidon_config: PoseidonConfig>, - /// public params hash - pub pp_hash: CF1, - pub i: CF1, - /// initial state - pub z_0: Vec>, - /// current i-th state - pub z_i: Vec>, - /// Folding scheme instances - pub U_i: RU, - pub W_i: W, - pub u_i: IU, - pub w_i: W, - pub U_i1: RU, - pub W_i1: W, - - /// Helper for folding verification - pub proof: D::Proof, - pub randomness: D::Randomness, - - /// CycleFold running instance - pub cf_U_i: CycleFoldCommittedInstance, - - /// KZG challenges - pub kzg_challenges: Vec>, - pub kzg_evaluations: Vec>, -} - -impl< - C1: Curve, - C2: Curve, BaseField = CF1>, - RU: CommittedInstanceOps + for<'a> Dummy<&'a A>, - IU: CommittedInstanceOps + for<'a> Dummy<&'a A>, - W: WitnessOps> + for<'a> Dummy<&'a A>, - A: ArithRelation, - AVar: ArithRelationGadget + AllocVar>, - D: DeciderEnabledNIFS, - > - Dummy<( - A, - &R1CS>, - PoseidonConfig>, - D::ProofDummyCfg, - D::RandomnessDummyCfg, - usize, - usize, - )> for GenericOffchainDeciderCircuit1 -{ - fn dummy( - ( - arith, - cf_arith, - poseidon_config, - proof_config, - randomness_config, - state_len, - num_commitments, - ): ( - A, - &R1CS>, - PoseidonConfig>, - D::ProofDummyCfg, - D::RandomnessDummyCfg, - usize, - usize, - ), - ) -> Self { - Self { - _avar: PhantomData, - poseidon_config, - pp_hash: Zero::zero(), - i: Zero::zero(), - z_0: vec![Zero::zero(); state_len], - z_i: vec![Zero::zero(); state_len], - U_i: RU::dummy(&arith), - W_i: W::dummy(&arith), - u_i: IU::dummy(&arith), - w_i: W::dummy(&arith), - U_i1: RU::dummy(&arith), - W_i1: W::dummy(&arith), - proof: D::Proof::dummy(proof_config), - randomness: D::Randomness::dummy(randomness_config), - cf_U_i: CycleFoldCommittedInstance::dummy(cf_arith), - kzg_challenges: vec![Zero::zero(); num_commitments], - kzg_evaluations: vec![Zero::zero(); num_commitments], - arith, - } - } -} - -impl< - C1: Curve, - C2: Curve, BaseField = CF1>, - RU: CommittedInstanceOps, - IU: CommittedInstanceOps, - W: WitnessOps>, - A: ArithRelation, - AVar: ArithRelationGadget + AllocVar>, - D: DeciderEnabledNIFS, - > ConstraintSynthesizer> - for GenericOffchainDeciderCircuit1 -where - RU::Var: AbsorbGadget> + CommittedInstanceVarOps>, -{ - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - let arith = AVar::new_witness(cs.clone(), || Ok(&self.arith))?; - - let pp_hash = FpVar::new_input(cs.clone(), || Ok(self.pp_hash))?; - let i = FpVar::new_input(cs.clone(), || Ok(self.i))?; - let z_0 = Vec::new_input(cs.clone(), || Ok(self.z_0))?; - let z_i = Vec::new_input(cs.clone(), || Ok(self.z_i))?; - - let u_i = IU::Var::new_witness(cs.clone(), || Ok(self.u_i))?; - let U_i = RU::Var::new_witness(cs.clone(), || Ok(self.U_i))?; - // here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i)) - let U_i1_commitments = Vec::>::new_input(cs.clone(), || { - Ok(self.U_i1.get_commitments()) - })?; - let U_i1 = RU::Var::new_witness(cs.clone(), || Ok(self.U_i1))?; - let W_i1 = W::Var::new_witness(cs.clone(), || Ok(self.W_i1))?; - U_i1.get_commitments().enforce_equal(&U_i1_commitments)?; - - let cf_U_i = - CycleFoldCommittedInstanceVar::::new_input(cs.clone(), || Ok(self.cf_U_i))?; - - // allocate the inputs for the checks 7.1 and 7.2 - let kzg_challenges = Vec::new_input(cs.clone(), || Ok(self.kzg_challenges))?; - let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?; - - // `sponge` is for digest computation. - // notice that `pp_hash` has already been absorbed during init. - let sponge = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; - // `transcript` is for challenge generation. - let mut transcript = sponge.clone(); - - // 1. enforce `U_{i+1}` and `W_{i+1}` satisfy `arith` - arith.enforce_relation(&W_i1, &U_i1)?; - - // 2. enforce `u_i` is an incoming instance - u_i.enforce_incoming()?; - - // 3. u_i.x[0] == H(i, z_0, z_i, U_i), u_i.x[1] == H(cf_U_i) - let (u_i_x, U_i_vec) = U_i.hash(&sponge, &i, &z_0, &z_i)?; - let (cf_u_i_x, _) = cf_U_i.hash(&sponge)?; - u_i.get_public_inputs().enforce_equal(&[u_i_x, cf_u_i_x])?; - - // 6.1. partially enforce `NIFS.V(U_i, u_i) = U_{i+1}`. - D::fold_field_elements_gadget( - &self.arith, - &mut transcript, - U_i, - U_i_vec, - u_i, - self.proof, - self.randomness, - )? - .enforce_partial_equal(&U_i1)?; - - // 7.1. compute and check KZG challenges - KZGChallengesGadget::get_challenges_gadget(&mut transcript, &U_i1)? - .enforce_equal(&kzg_challenges)?; - - // 7.2. check the claimed evaluations - for (((v, _r), c), e) in W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .zip(&kzg_evaluations) - { - // The randomness `_r` is currently not used. - EvalGadget::evaluate_gadget(v, c)?.enforce_equal(e)?; - } - - Ok(()) - } -} - -/// Circuit that implements part of the in-circuit checks needed for the offchain verification over -/// the Curve1's BaseField (=Curve2's ScalarField). -pub struct GenericOffchainDeciderCircuit2 { - /// R1CS of the CycleFold circuit - pub cf_arith: R1CS>, - pub poseidon_config: PoseidonConfig>, - /// public params hash - pub pp_hash: CF1, - - /// CycleFold running instance - pub cf_U_i: CycleFoldCommittedInstance, - pub cf_W_i: CycleFoldWitness, - - /// KZG challenges - pub kzg_challenges: Vec>, - pub kzg_evaluations: Vec>, -} - -impl Dummy<(R1CS>, PoseidonConfig>, usize)> - for GenericOffchainDeciderCircuit2 -{ - fn dummy( - (cf_arith, poseidon_config, num_commitments): ( - R1CS>, - PoseidonConfig>, - usize, - ), - ) -> Self { - Self { - poseidon_config, - pp_hash: Zero::zero(), - cf_U_i: CycleFoldCommittedInstance::dummy(&cf_arith), - cf_W_i: CycleFoldWitness::dummy(&cf_arith), - kzg_challenges: vec![Zero::zero(); num_commitments], - kzg_evaluations: vec![Zero::zero(); num_commitments], - cf_arith, - } - } -} - -impl ConstraintSynthesizer> for GenericOffchainDeciderCircuit2 { - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - let cf_r1cs = R1CSMatricesVar::, FpVar>>::new_witness(cs.clone(), || { - Ok(self.cf_arith.clone()) - })?; - - let pp_hash = FpVar::new_input(cs.clone(), || Ok(self.pp_hash))?; - - let cf_U_i = CommittedInstanceVar::new_input(cs.clone(), || Ok(self.cf_U_i))?; - let cf_W_i = WitnessVar::new_witness(cs.clone(), || Ok(self.cf_W_i))?; - - // allocate the inputs for the checks 4.1 and 4.2 - let kzg_challenges = Vec::new_input(cs.clone(), || Ok(self.kzg_challenges))?; - let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?; - - // `transcript` is for challenge generation. - let mut transcript = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; - - // 5. enforce `cf_U_i` and `cf_W_i` satisfy `cf_r1cs` - cf_r1cs.enforce_relation(&cf_W_i, &cf_U_i)?; - - // 4.1. compute and check KZG challenges - KZGChallengesGadget::get_challenges_gadget(&mut transcript, &cf_U_i)? - .enforce_equal(&kzg_challenges)?; - - // 4.2. check the claimed evaluations - for (((v, _r), c), e) in cf_W_i - .get_openings() - .iter() - .zip(&kzg_challenges) - .zip(&kzg_evaluations) - { - // The randomness `_r` is currently not used. - EvalGadget::evaluate_gadget(v, c)?.enforce_equal(e)?; - } - - Ok(()) - } -} diff --git a/folding-schemes/src/folding/circuits/decider/on_chain.rs b/folding-schemes/src/folding/circuits/decider/on_chain.rs deleted file mode 100644 index 798662316..000000000 --- a/folding-schemes/src/folding/circuits/decider/on_chain.rs +++ /dev/null @@ -1,315 +0,0 @@ -/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, -/// other more efficient approaches can be used. -use ark_crypto_primitives::sponge::{ - constraints::AbsorbGadget, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, -}; -use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::fp::FpVar}; -use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::{marker::PhantomData, Zero}; - -use crate::{ - arith::{r1cs::R1CS, ArithRelation, ArithRelationGadget}, - commitment::pedersen::Params as PedersenParams, - folding::{ - circuits::{ - cyclefold::{ - CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, CycleFoldWitness, - }, - decider::{EvalGadget, KZGChallengesGadget}, - nonnative::affine::NonNativeAffineVar, - CF1, CF2, - }, - traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps}, - }, - transcript::TranscriptVar, - Curve, -}; - -use super::DeciderEnabledNIFS; - -/// A generic circuit tailored for the onchain (Ethereum's EVM) verification of -/// IVC proofs, where we support IVC built upon any folding scheme. -/// -/// Specifically, `GenericDeciderEthCircuit` implements the in-circuit version -/// of the IVC verification algorithm, which essentially checks the following: -/// - `R_arith(W_i, U_i)`: -/// The running instance `U_i` and witness `W_i` satisfy `arith`, -/// and the commitments in `U_i` open to the values in `W_i`. -/// - `R_arith(w_i, u_i)`: -/// The incoming instance `u_i` and witness `w_i` satisfy `arith`, -/// and the commitments in `u_i` open to the values in `w_i`. -/// - `R_cf_arith(cf_W_i, cf_U_i)`: -/// The CycleFold instance `cf_U_i` and witness `cf_W_i` satisfy `cf_arith`, -/// and the commitments in `cf_U_i` open to the values in `cf_W_i`. -/// - `u_i` contains the correct hash of the initial and final states. -/// -/// To reduce the number of relation checks, the prover, before invoking the -/// circuit, further folds `U_i, u_i` into `U_{i+1}`, and `W_i, w_i` into -/// `W_{i+1}`. -/// Now, the circuit only needs to perform two relation checks, i.e., -/// `R_arith(W_{i+1}, U_{i+1})` and `R_cf_arith(cf_W_i, cf_U_i)`, plus a few -/// constraints for enforcing the correct hash in `u_i` and the correct folding -/// from `U_i, u_i` to `U_{i+1}`. -/// -/// We further reduce the circuit size by avoiding the non-native commitment -/// checks involved in `R_arith(W_{i+1}, U_{i+1})`. -/// Now, we now only check the satisfiability of the constraint system `arith` -/// with the witness `W_{i+1}` and instance `U_{i+1}` in the circuit, but the -/// actual commitment checks are done with the help of KZG. -/// -/// For more details, see [https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html]. -pub struct GenericOnchainDeciderCircuit< - C1: Curve, - C2: Curve, - RU: CommittedInstanceOps, // Running instance - IU: CommittedInstanceOps, // Incoming instance - W: WitnessOps>, // Witness - A: ArithRelation, // Constraint system - AVar: ArithRelationGadget, // In-circuit representation of `A` - D: DeciderEnabledNIFS, -> { - pub _avar: PhantomData, - /// Constraint system of the Augmented Function circuit - pub arith: A, - /// R1CS of the CycleFold circuit - pub cf_arith: R1CS>, - /// CycleFold PedersenParams over C2 - pub cf_pedersen_params: PedersenParams, - pub poseidon_config: PoseidonConfig>, - /// public params hash - pub pp_hash: CF1, - pub i: CF1, - /// initial state - pub z_0: Vec>, - /// current i-th state - pub z_i: Vec>, - /// Folding scheme instances - pub U_i: RU, - pub W_i: W, - pub u_i: IU, - pub w_i: W, - pub U_i1: RU, - pub W_i1: W, - - /// Helper for folding verification - pub proof: D::Proof, - pub randomness: D::Randomness, - - /// CycleFold running instance - pub cf_U_i: CycleFoldCommittedInstance, - pub cf_W_i: CycleFoldWitness, - - /// KZG challenges - pub kzg_challenges: Vec>, - pub kzg_evaluations: Vec>, -} - -impl< - C1: Curve, - C2: Curve, BaseField = CF1>, - RU: CommittedInstanceOps + for<'a> Dummy<&'a A>, - IU: CommittedInstanceOps + for<'a> Dummy<&'a A>, - W: WitnessOps> + for<'a> Dummy<&'a A>, - A: ArithRelation, - AVar: ArithRelationGadget + AllocVar>, - D: DeciderEnabledNIFS, - > - Dummy<( - A, - R1CS>, - PedersenParams, - PoseidonConfig>, - D::ProofDummyCfg, - D::RandomnessDummyCfg, - usize, - usize, - )> for GenericOnchainDeciderCircuit -{ - fn dummy( - ( - arith, - cf_arith, - cf_pedersen_params, - poseidon_config, - proof_config, - randomness_config, - state_len, - num_commitments, - ): ( - A, - R1CS>, - PedersenParams, - PoseidonConfig>, - D::ProofDummyCfg, - D::RandomnessDummyCfg, - usize, - usize, - ), - ) -> Self { - Self { - _avar: PhantomData, - cf_pedersen_params, - poseidon_config, - pp_hash: Zero::zero(), - i: Zero::zero(), - z_0: vec![Zero::zero(); state_len], - z_i: vec![Zero::zero(); state_len], - U_i: RU::dummy(&arith), - W_i: W::dummy(&arith), - u_i: IU::dummy(&arith), - w_i: W::dummy(&arith), - U_i1: RU::dummy(&arith), - W_i1: W::dummy(&arith), - proof: D::Proof::dummy(proof_config), - randomness: D::Randomness::dummy(randomness_config), - cf_U_i: CycleFoldCommittedInstance::dummy(&cf_arith), - cf_W_i: CycleFoldWitness::dummy(&cf_arith), - kzg_challenges: vec![Zero::zero(); num_commitments], - kzg_evaluations: vec![Zero::zero(); num_commitments], - arith, - cf_arith, - } - } -} - -impl< - C1: Curve, - C2: Curve, BaseField = CF1>, - RU: CommittedInstanceOps, - IU: CommittedInstanceOps, - W: WitnessOps>, - A: ArithRelation, - AVar: ArithRelationGadget + AllocVar>, - D: DeciderEnabledNIFS, - > ConstraintSynthesizer> for GenericOnchainDeciderCircuit -where - RU::Var: AbsorbGadget> + CommittedInstanceVarOps>, -{ - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - let arith = AVar::new_witness(cs.clone(), || Ok(&self.arith))?; - - let pp_hash = FpVar::new_input(cs.clone(), || Ok(self.pp_hash))?; - let i = FpVar::new_input(cs.clone(), || Ok(self.i))?; - let z_0 = Vec::new_input(cs.clone(), || Ok(self.z_0))?; - let z_i = Vec::new_input(cs.clone(), || Ok(self.z_i))?; - - let u_i = IU::Var::new_witness(cs.clone(), || Ok(self.u_i))?; - let U_i = RU::Var::new_witness(cs.clone(), || Ok(self.U_i))?; - // here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i)) - let U_i1_commitments = Vec::>::new_input(cs.clone(), || { - Ok(self.U_i1.get_commitments()) - })?; - let U_i1 = RU::Var::new_witness(cs.clone(), || Ok(self.U_i1))?; - let W_i1 = W::Var::new_witness(cs.clone(), || Ok(self.W_i1))?; - U_i1.get_commitments().enforce_equal(&U_i1_commitments)?; - - let cf_U_i = - CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || Ok(self.cf_U_i))?; - - // allocate the inputs for the check 7.1 and 7.2 - let kzg_challenges = Vec::new_input(cs.clone(), || Ok(self.kzg_challenges))?; - let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?; - - // `sponge` is for digest computation. - let sponge = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; - // `transcript` is for challenge generation. - let mut transcript = sponge.clone(); - - // NOTE: we use the same enumeration as in - // https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html - // in order to make it easier to reason about. - - // 1. enforce `U_{i+1}` and `W_{i+1}` satisfy `arith` - arith.enforce_relation(&W_i1, &U_i1)?; - - // 2. enforce `u_i` is an incoming instance - u_i.enforce_incoming()?; - - // 3. u_i.x[0] == H(i, z_0, z_i, U_i), u_i.x[1] == H(cf_U_i) - let (u_i_x, U_i_vec) = U_i.hash(&sponge, &i, &z_0, &z_i)?; - let (cf_u_i_x, _) = cf_U_i.hash(&sponge)?; - u_i.get_public_inputs().enforce_equal(&[u_i_x, cf_u_i_x])?; - - #[cfg(feature = "light-test")] - log::warn!("[WARNING]: Running with the 'light-test' feature, skipping the big part of the DeciderEthCircuit.\n Only for testing purposes."); - - // The following two checks (and their respective allocations) are disabled for normal - // tests since they take several millions of constraints and would take several minutes - // (and RAM) to run the test. It is active by default, and not active only when - // 'light-test' feature is used. - #[cfg(not(feature = "light-test"))] - { - // imports here instead of at the top of the file, so we avoid having multiple - // `#[cfg(not(test))]` - use crate::{ - arith::r1cs::circuits::R1CSMatricesVar, - commitment::pedersen::PedersenGadget, - folding::circuits::{ - cyclefold::CycleFoldWitnessVar, nonnative::uint::NonNativeUintVar, - }, - }; - use ark_r1cs_std::{convert::ToBitsGadget, groups::CurveVar}; - let cf_W_i = CycleFoldWitnessVar::::new_witness(cs.clone(), || Ok(self.cf_W_i))?; - // 4. check Pedersen commitments of cf_U_i.{cmE, cmW} - let H = C2::Var::constant(self.cf_pedersen_params.h); - let G = self - .cf_pedersen_params - .generators - .iter() - .map(|&g| C2::Var::constant(g.into())) - .collect::>(); - let cf_W_i_E_bits = cf_W_i - .E - .iter() - .map(|E_i| E_i.to_bits_le()) - .collect::, _>>()?; - let cf_W_i_W_bits = cf_W_i - .W - .iter() - .map(|W_i| W_i.to_bits_le()) - .collect::, _>>()?; - PedersenGadget::::commit(&H, &G, &cf_W_i_E_bits, &cf_W_i.rE.to_bits_le()?)? - .enforce_equal(&cf_U_i.cmE)?; - PedersenGadget::::commit(&H, &G, &cf_W_i_W_bits, &cf_W_i.rW.to_bits_le()?)? - .enforce_equal(&cf_U_i.cmW)?; - - let cf_r1cs = R1CSMatricesVar::, NonNativeUintVar>>::new_constant( - ConstraintSystemRef::None, - self.cf_arith, - )?; - - // 5. enforce `cf_U_i` and `cf_W_i` satisfy `cf_r1cs` - cf_r1cs.enforce_relation(&cf_W_i, &cf_U_i)?; - } - - // 6.1. partially enforce `NIFS.V(U_i, u_i) = U_{i+1}`. - D::fold_field_elements_gadget( - &self.arith, - &mut transcript, - U_i, - U_i_vec, - u_i, - self.proof, - self.randomness, - )? - .enforce_partial_equal(&U_i1)?; - - // 7.1. compute and check KZG challenges - KZGChallengesGadget::get_challenges_gadget(&mut transcript, &U_i1)? - .enforce_equal(&kzg_challenges)?; - - // 7.2. check the claimed evaluations - for (((v, _r), c), e) in W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .zip(&kzg_evaluations) - { - // The randomness `_r` is currently not used. - EvalGadget::evaluate_gadget(v, c)?.enforce_equal(e)?; - } - - Ok(()) - } -} diff --git a/folding-schemes/src/folding/circuits/mod.rs b/folding-schemes/src/folding/circuits/mod.rs deleted file mode 100644 index 5b6af02bc..000000000 --- a/folding-schemes/src/folding/circuits/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -/// Circuits and gadgets shared across the different folding schemes. -use ark_ec::{CurveGroup, PrimeGroup}; -use ark_ff::Field; - -pub mod cyclefold; -pub mod decider; -pub mod nonnative; -pub mod sum_check; -pub mod utils; - -/// CF1 uses the ScalarField of the given C. CF1 represents the ConstraintField used for the main -/// folding circuit which is over E1::Fr, where E1 is the main curve where we do the folding. -/// In CF1, the points of C can not be natively represented. -pub type CF1 = ::ScalarField; -/// CF2 uses the BaseField of the given C. CF2 represents the ConstraintField used for the -/// CycleFold circuit which is over E2::Fr=E1::Fq, where E2 is the auxiliary curve (from -/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf) approach) where we check the folding of the -/// commitments (elliptic curve points). -/// In CF2, the points of C can be natively represented. -pub type CF2 = <::BaseField as Field>::BasePrimeField; diff --git a/folding-schemes/src/folding/circuits/nonnative/affine.rs b/folding-schemes/src/folding/circuits/nonnative/affine.rs deleted file mode 100644 index c7d7bb98d..000000000 --- a/folding-schemes/src/folding/circuits/nonnative/affine.rs +++ /dev/null @@ -1,241 +0,0 @@ -use ark_ec::{ - short_weierstrass::{Projective, SWCurveConfig, SWFlags}, - AffineRepr, CurveGroup, -}; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - eq::EqGadget, - fields::fp::FpVar, - prelude::Boolean, - GR1CSVar, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; -use ark_std::{borrow::Borrow, One, Zero}; - -use crate::{ - folding::traits::{Inputize, InputizeNonNative}, - transcript::{AbsorbNonNative, AbsorbNonNativeGadget}, - Curve, Field, -}; - -use super::uint::NonNativeUintVar; - -/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native -/// field, over the constraint field. It is not intended to perform operations, but just to contain -/// the affine coordinates in order to perform hash operations of the point. -#[derive(Debug, Clone)] -pub struct NonNativeAffineVar { - pub x: NonNativeUintVar, - pub y: NonNativeUintVar, -} - -impl AllocVar for NonNativeAffineVar { - fn new_variable>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let affine = val.borrow().into_affine(); - let (x, y) = affine.xy().unwrap_or_default(); - - let x = NonNativeUintVar::new_variable(cs.clone(), || Ok(x), mode)?; - let y = NonNativeUintVar::new_variable(cs.clone(), || Ok(y), mode)?; - - Ok(Self { x, y }) - }) - } -} - -impl GR1CSVar for NonNativeAffineVar { - type Value = C; - - fn cs(&self) -> ConstraintSystemRef { - self.x.cs().or(self.y.cs()) - } - - fn value(&self) -> Result { - let x = C::BaseField::from_le_bytes_mod_order(&self.x.value()?.to_bytes_le()); - let y = C::BaseField::from_le_bytes_mod_order(&self.y.value()?.to_bytes_le()); - // Below is a workaround to convert the `x` and `y` coordinates to a - // point. This is because the `SonobeCurve` trait does not provide a - // method to construct a point from `BaseField` elements. - let mut bytes = vec![]; - // `unwrap` below is safe because serialization of a `PrimeField` value - // only fails if the serialization flag has more than 8 bits, but here - // we call `serialize_uncompressed` which uses an empty flag. - x.serialize_uncompressed(&mut bytes).unwrap(); - // `unwrap` below is also safe, because the bit size of `SWFlags` is 2. - y.serialize_with_flags( - &mut bytes, - if x.is_zero() && y.is_zero() { - SWFlags::PointAtInfinity - } else if y <= -y { - SWFlags::YIsPositive - } else { - SWFlags::YIsNegative - }, - ) - .unwrap(); - // `unwrap` below is safe because `bytes` is constructed from the `x` - // and `y` coordinates of a valid point, and these coordinates are - // serialized in the same way as the `SonobeCurve` implementation. - Ok(C::deserialize_uncompressed_unchecked(&bytes[..]).unwrap()) - } -} - -impl EqGadget for NonNativeAffineVar { - fn is_eq(&self, other: &Self) -> Result, SynthesisError> { - let mut result = Boolean::TRUE; - if self.x.0.len() != other.x.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - if self.y.0.len() != other.y.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - for (l, r) in self - .x - .0 - .iter() - .chain(&self.y.0) - .zip(other.x.0.iter().chain(&other.y.0)) - { - if l.ub != r.ub { - return Err(SynthesisError::Unsatisfiable); - } - result &= l.v.is_eq(&r.v)?; - } - Ok(result) - } - - fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { - if self.x.0.len() != other.x.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - if self.y.0.len() != other.y.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - for (l, r) in self - .x - .0 - .iter() - .chain(&self.y.0) - .zip(other.x.0.iter().chain(&other.y.0)) - { - if l.ub != r.ub { - return Err(SynthesisError::Unsatisfiable); - } - l.v.enforce_equal(&r.v)?; - } - Ok(()) - } -} - -impl NonNativeAffineVar { - pub fn zero() -> Self { - // `unwrap` below is safe because we are allocating a constant value, - // which is guaranteed to succeed. - Self::new_constant(ConstraintSystemRef::None, C::zero()).unwrap() - } -} - -impl> AbsorbNonNative for Projective

{ - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - let affine = self.into_affine(); - let (x, y) = affine.xy().unwrap_or_default(); - - [x, y].to_native_sponge_field_elements(dest); - } -} - -impl AbsorbNonNativeGadget for NonNativeAffineVar { - fn to_native_sponge_field_elements( - &self, - ) -> Result>, SynthesisError> { - [&self.x, &self.y].to_native_sponge_field_elements() - } -} - -impl> Inputize for Projective

{ - /// Returns the internal representation in the same order as how the value - /// is allocated in `ProjectiveVar::new_input`. - fn inputize(&self) -> Vec { - let affine = self.into_affine(); - match affine.xy() { - Some((x, y)) => vec![x, y, One::one()], - None => vec![Zero::zero(), One::one(), Zero::zero()], - } - } -} - -impl> InputizeNonNative for Projective

{ - /// Returns the internal representation in the same order as how the value - /// is allocated in `NonNativeAffineVar::new_input`. - fn inputize_nonnative(&self) -> Vec { - let affine = self.into_affine(); - let (x, y) = affine.xy().unwrap_or_default(); - - [x, y].inputize_nonnative() - } -} - -#[cfg(test)] -mod tests { - use ark_pallas::{Fq, Fr, PallasConfig, Projective}; - use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::UniformRand; - - use super::*; - use crate::Error; - - #[test] - fn test_alloc_zero() { - let cs = ConstraintSystem::::new_ref(); - - // dealing with the 'zero' point should not panic when doing the unwrap - let p = Projective::zero(); - assert!(NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).is_ok()); - } - - #[test] - fn test_improved_to_hash_preimage() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - - // check that point_to_nonnative_limbs returns the expected values - let mut rng = ark_std::test_rng(); - let p = Projective::rand(&mut rng); - let pVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; - assert_eq!( - pVar.to_native_sponge_field_elements()?.value()?, - p.to_native_sponge_field_elements_as_vec() - ); - Ok(()) - } - - #[test] - fn test_inputize() -> Result<(), Error> { - // check that point_to_nonnative_limbs returns the expected values - let mut rng = ark_std::test_rng(); - let p = Projective::rand(&mut rng); - - let cs = ConstraintSystem::::new_ref(); - let pVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; - assert_eq!( - [pVar.x.0.value()?, pVar.y.0.value()?].concat(), - p.inputize_nonnative() - ); - - let cs = ConstraintSystem::::new_ref(); - let pVar = ProjectiveVar::>::new_witness(cs.clone(), || Ok(p))?; - assert_eq!( - vec![pVar.x.value()?, pVar.y.value()?, pVar.z.value()?], - p.inputize() - ); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/circuits/nonnative/mod.rs b/folding-schemes/src/folding/circuits/nonnative/mod.rs deleted file mode 100644 index 497b9870f..000000000 --- a/folding-schemes/src/folding/circuits/nonnative/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod affine; -pub mod uint; diff --git a/folding-schemes/src/folding/circuits/nonnative/uint.rs b/folding-schemes/src/folding/circuits/nonnative/uint.rs deleted file mode 100644 index cb3cdd444..000000000 --- a/folding-schemes/src/folding/circuits/nonnative/uint.rs +++ /dev/null @@ -1,1036 +0,0 @@ -use std::{ - borrow::Borrow, - cmp::{max, min}, -}; - -use ark_ff::{BigInteger, Fp, FpConfig, One, PrimeField, Zero}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - convert::ToBitsGadget, - fields::{fp::FpVar, FieldVar}, - prelude::EqGadget, - select::CondSelectGadget, - GR1CSVar, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use num_bigint::BigUint; -use num_integer::Integer; - -use crate::{ - folding::traits::{Inputize, InputizeNonNative}, - transcript::{AbsorbNonNative, AbsorbNonNativeGadget}, - utils::gadgets::{EquivalenceGadget, MatrixGadget, SparseMatrixVar, VectorGadget}, - Field, -}; - -/// `LimbVar` represents a single limb of a non-native unsigned integer in the -/// circuit. -/// The limb value `v` should be small enough to fit into `FpVar`, and we also -/// store an upper bound `ub` for the limb value, which is treated as a constant -/// in the circuit and is used for efficient equality checks and some arithmetic -/// operations. -#[derive(Debug, Clone)] -pub struct LimbVar { - pub v: FpVar, - pub ub: BigUint, -} - -impl]>> From for LimbVar { - fn from(bits: B) -> Self { - Self { - // `Boolean::le_bits_to_fp` will return an error if the internal - // invocation of `Boolean::enforce_in_field_le` fails. - // However, this method is only called when the length of `bits` is - // greater than `F::MODULUS_BIT_SIZE`, which should not happen in - // our case where `bits` is guaranteed to be short. - v: Boolean::le_bits_to_fp(bits.as_ref()).unwrap(), - ub: (BigUint::one() << bits.as_ref().len()) - BigUint::one(), - } - } -} - -impl Default for LimbVar { - fn default() -> Self { - Self { - v: FpVar::zero(), - ub: BigUint::zero(), - } - } -} - -impl GR1CSVar for LimbVar { - type Value = F; - - fn cs(&self) -> ConstraintSystemRef { - self.v.cs() - } - - fn value(&self) -> Result { - self.v.value() - } -} - -impl CondSelectGadget for LimbVar { - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - // We only allow selecting between two values with the same upper bound - assert_eq!(true_value.ub, false_value.ub); - Ok(Self { - v: cond.select(&true_value.v, &false_value.v)?, - ub: true_value.ub.clone(), - }) - } -} - -impl LimbVar { - /// Add two `LimbVar`s. - /// Returns `None` if the upper bound of the sum is too large, i.e., - /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. - /// Otherwise, returns the sum as a `LimbVar`. - pub fn add(&self, other: &Self) -> Option { - let ubound = &self.ub + &other.ub; - if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { - Some(Self { - v: &self.v + &other.v, - ub: ubound, - }) - } else { - None - } - } - - /// Add multiple `LimbVar`s. - /// Returns `None` if the upper bound of the sum is too large, i.e., - /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. - /// Otherwise, returns the sum as a `LimbVar`. - pub fn add_many(limbs: &[Self]) -> Option { - let ubound = limbs.iter().map(|l| &l.ub).sum(); - if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { - Some(Self { - v: if limbs.is_constant() { - FpVar::constant(limbs.value().unwrap_or_default().into_iter().sum()) - } else { - limbs.iter().map(|l| &l.v).sum() - }, - ub: ubound, - }) - } else { - None - } - } - - /// Multiply two `LimbVar`s. - /// Returns `None` if the upper bound of the product is too large, i.e., - /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. - /// Otherwise, returns the product as a `LimbVar`. - pub fn mul(&self, other: &Self) -> Option { - let ubound = &self.ub * &other.ub; - if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { - Some(Self { - v: &self.v * &other.v, - ub: ubound, - }) - } else { - None - } - } - - pub fn zero() -> Self { - Self::default() - } - - pub fn constant(v: F) -> Self { - Self { - v: FpVar::constant(v), - ub: v.into(), - } - } -} - -impl ToBitsGadget for LimbVar { - fn to_bits_le(&self) -> Result>, SynthesisError> { - let cs = self.cs(); - - let bits = &self - .v - .value() - .unwrap_or_default() - .into_bigint() - .to_bits_le()[..self.ub.bits() as usize]; - let bits = if cs.is_none() { - Vec::new_constant(cs, bits)? - } else { - Vec::new_witness(cs, || Ok(bits))? - }; - - Boolean::le_bits_to_fp(&bits)?.enforce_equal(&self.v)?; - - Ok(bits) - } -} - -/// `NonNativeUintVar` represents a non-native unsigned integer (BigUint) in the -/// circuit. -/// We apply [xJsnark](https://akosba.github.io/papers/xjsnark.pdf)'s techniques -/// for efficient operations on `NonNativeUintVar`. -/// Note that `NonNativeUintVar` is different from arkworks' `NonNativeFieldVar` -/// in that the latter runs the expensive `reduce` (`align` + `modulo` in our -/// terminology) after each arithmetic operation, while the former only reduces -/// the integer when explicitly called. -#[derive(Debug, Clone)] -pub struct NonNativeUintVar(pub Vec>); - -impl NonNativeUintVar { - pub const fn bits_per_limb() -> usize { - assert!(F::MODULUS_BIT_SIZE > 250); - // For a `F` with order > 250 bits, 55 is chosen for optimizing the most - // expensive part `Az∘Bz` when checking the R1CS relation for CycleFold. - // Consider using `NonNativeUintVar` to represent the base field `Fq`. - // Since 250 / 55 = 4.46, the `NonNativeUintVar` has 5 limbs. - // Now, the multiplication of two `NonNativeUintVar`s has 9 limbs, and - // each limb has at most 2^{55 * 2} * 5 = 112.3 bits. - // For a 1400x1400 matrix `A`, the multiplication of `A`'s row and `z` - // is the sum of 1400 `NonNativeUintVar`s, each with 9 limbs. - // Thus, the maximum bit length of limbs of each element in `Az` is - // 2^{55 * 2} * 5 * 1400 = 122.7 bits. - // Finally, in the hadamard product of `Az` and `Bz`, every element has - // 17 limbs, whose maximum bit length is (2^{55 * 2} * 5 * 1400)^2 * 9 - // = 248.7 bits and is less than the native field `Fr`. - // Thus, 55 allows us to compute `Az∘Bz` without the expensive alignment - // operation. - // - // TODO: either make it a global const, or compute an optimal value - // based on the modulus size. - 55 - } -} - -struct BoundedBigUint(BigUint, usize); - -impl AllocVar for NonNativeUintVar { - fn new_variable>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let cs = cs.into().cs(); - let v = f()?; - let BoundedBigUint(x, l) = v.borrow(); - - let mut limbs = vec![]; - for chunk in (0..*l) - .map(|i| x.bit(i as u64)) - .collect::>() - .chunks(Self::bits_per_limb()) - { - let limb = F::from(F::BigInt::from_bits_le(chunk)); - let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; - Self::enforce_bit_length(&limb, chunk.len())?; - limbs.push(LimbVar { - v: limb, - ub: (BigUint::one() << chunk.len()) - BigUint::one(), - }); - } - - Ok(Self(limbs)) - } -} - -impl AllocVar for NonNativeUintVar { - fn new_variable>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let cs = cs.into().cs(); - let v = f()?; - assert_eq!(G::extension_degree(), 1); - // `unwrap` is safe because `G` is a field with extension degree 1, and - // thus `G::to_base_prime_field_elements` should return an iterator with - // exactly one element. - let v = v.borrow().to_base_prime_field_elements().next().unwrap(); - - let mut limbs = vec![]; - - for chunk in v.into_bigint().to_bits_le().chunks(Self::bits_per_limb()) { - let limb = F::from(F::BigInt::from_bits_le(chunk)); - let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; - Self::enforce_bit_length(&limb, chunk.len())?; - limbs.push(LimbVar { - v: limb, - ub: (BigUint::one() << chunk.len()) - BigUint::one(), - }); - } - - Ok(Self(limbs)) - } -} - -impl GR1CSVar for NonNativeUintVar { - type Value = BigUint; - - fn cs(&self) -> ConstraintSystemRef { - self.0.cs() - } - - fn value(&self) -> Result { - let mut r = BigUint::zero(); - - for limb in self.0.value()?.into_iter().rev() { - r <<= Self::bits_per_limb(); - r += Into::::into(limb); - } - - Ok(r) - } -} - -impl NonNativeUintVar { - /// Enforce `self` to be less than `other`, where `self` and `other` should - /// be aligned. - /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 - pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> { - let len = max(self.0.len(), other.0.len()); - let zero = LimbVar::zero(); - - // Compute the difference between limbs of `other` and `self`. - // Denote a positive limb by `+`, a negative limb by `-`, a zero limb by - // `0`, and an unknown limb by `?`. - // Then, for `self < other`, `delta` should look like: - // ? ? ... ? ? + 0 0 ... 0 0 - let delta = (0..len) - .map(|i| { - let x = &self.0.get(i).unwrap_or(&zero).v; - let y = &other.0.get(i).unwrap_or(&zero).v; - y - x - }) - .collect::>(); - - // `helper` is a vector of booleans that indicates if the corresponding - // limb of `delta` is the first (searching from MSB) positive limb. - // For example, if `delta` is: - // - + ... + - + 0 0 ... 0 0 - // <---- search in this direction -------- - // Then `helper` should be: - // F F ... F F T F F ... F F - let helper = { - let cs = self.cs().or(other.cs()); - let mut helper = vec![false; len]; - for i in (0..len).rev() { - let delta = delta[i].value().unwrap_or_default().into_bigint(); - if !delta.is_zero() && delta < F::MODULUS_MINUS_ONE_DIV_TWO { - helper[i] = true; - break; - } - } - if cs.is_none() { - Vec::>::new_constant(cs, helper)? - } else { - Vec::new_witness(cs, || Ok(helper))? - } - }; - - // `p` is the first positive limb in `delta`. - let mut p = FpVar::::zero(); - // `r` is the sum of all bits in `helper`, which should be 1 when `self` - // is less than `other`, as there should be more than one positive limb - // in `delta`, and thus exactly one true bit in `helper`. - let mut r = FpVar::zero(); - for (b, d) in helper.into_iter().zip(delta) { - // Choose the limb `d` only if `b` is true. - p += b.select(&d, &FpVar::zero())?; - // Either `r` or `d` should be zero. - // Consider the same example as above: - // - + ... + - + 0 0 ... 0 0 - // F F ... F F T F F ... F F - // |-----------| - // `r = 0` in this range (before/when we meet the first positive limb) - // |---------| - // `d = 0` in this range (after we meet the first positive limb) - // This guarantees that for every bit after the true bit in `helper`, - // the corresponding limb in `delta` is zero. - (&r * &d).enforce_equal(&FpVar::zero())?; - // Add the current bit to `r`. - r += FpVar::from(b); - } - - // Ensure that `r` is exactly 1. This guarantees that there is exactly - // one true value in `helper`. - r.enforce_equal(&FpVar::one())?; - // Ensure that `p` is positive, i.e., - // `0 <= p - 1 < 2^bits_per_limb < F::MODULUS_MINUS_ONE_DIV_TWO`. - // This guarantees that the true value in `helper` corresponds to a - // positive limb in `delta`. - Self::enforce_bit_length(&(p - FpVar::one()), Self::bits_per_limb())?; - - Ok(()) - } - - /// Enforce `self` to be equal to `other`, where `self` and `other` are not - /// necessarily aligned. - /// - /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L562-L798 - /// Similar implementations can also be found in https://github.com/alex-ozdemir/bellman-bignat/blob/0585b9d90154603a244cba0ac80b9aafe1d57470/src/mp/bignat.rs#L566-L661 - /// and https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/emulated_fp/reduce.rs#L201-L323 - pub fn enforce_equal_unaligned(&self, other: &Self) -> Result<(), SynthesisError> { - let len = min(self.0.len(), other.0.len()); - - // Group the limbs of `self` and `other` so that each group nearly - // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. - // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` - // is the initial number of bits in a limb, just as what we do in grade - // school arithmetic, e.g., - // 5 9 - // x 7 3 - // ------------- - // 15 27 - // 35 63 - // ------------- <- When grouping 35, 15 + 63, and 27, we are computing - // 4 3 0 7 35 * 100 + (15 + 63) * 10 + 27 = 4307 - // Note that this is different from the concatenation `x_0 || x_1 ...`, - // since the bit-length of each limb is not necessarily the initial size - // `W`. - let (steps, x, y, rest) = { - // `steps` stores the size of each grouped limb. - let mut steps = vec![]; - // `x_grouped` stores the grouped limbs of `self`. - let mut x_grouped = vec![]; - // `y_grouped` stores the grouped limbs of `other`. - let mut y_grouped = vec![]; - let mut i = 0; - while i < len { - let mut j = i; - // The current grouped limbs of `self` and `other`. - let mut xx = LimbVar::zero(); - let mut yy = LimbVar::zero(); - while j < len { - let shift = BigUint::one() << (Self::bits_per_limb() * (j - i)); - assert!(shift < F::MODULUS_MINUS_ONE_DIV_TWO.into()); - let shift = LimbVar::constant(shift.into()); - match ( - // Try to group `x` and `y` into `xx` and `yy`. - self.0[j].mul(&shift).and_then(|x| xx.add(&x)), - other.0[j].mul(&shift).and_then(|y| yy.add(&y)), - ) { - // Update the result if successful. - (Some(x), Some(y)) => (xx, yy) = (x, y), - // Break the loop if the upper bound of the result exceeds - // the maximum capacity. - _ => break, - } - j += 1; - } - // Store the grouped limbs and their size. - steps.push((j - i) * Self::bits_per_limb()); - x_grouped.push(xx); - y_grouped.push(yy); - // Start the next group - i = j; - } - let remaining_limbs = &(if i < self.0.len() { self } else { other }).0[i..]; - let rest = if remaining_limbs.is_empty() { - FpVar::zero() - } else { - // If there is any remaining limb, the first one should be the - // final carry (which will be checked later), and the following - // ones should be zero. - - // Enforce the remaining limbs to be zero. - // Instead of doing that one by one, we check if their sum is - // zero using a single constraint. - // This is sound, as the upper bounds of the limbs and their sum - // are guaranteed to be less than `F::MODULUS_MINUS_ONE_DIV_TWO` - // (i.e., all of them are "non-negative"), implying that all - // limbs should be zero to make the sum zero. - LimbVar::add_many(&remaining_limbs[1..]) - .ok_or(SynthesisError::Unsatisfiable)? - .v - .enforce_equal(&FpVar::zero())?; - remaining_limbs[0].v.clone() - }; - (steps, x_grouped, y_grouped, rest) - }; - let n = steps.len(); - // `c` stores the current carry of `x_i - y_i` - let mut c = FpVar::::zero(); - // For each group, check the last `step_i` bits of `x_i` and `y_i` are - // equal. - // The intuition is to check `diff = x_i - y_i = 0 (mod 2^step_i)`. - // However, this is only true for `i = 0`, and we need to consider carry - // values `diff >> step_i` for `i > 0`. - // Therefore, we actually check `diff = x_i - y_i + c = 0 (mod 2^step_i)` - // and derive the next `c` by computing `diff >> step_i`. - // To enforce `diff = 0 (mod 2^step_i)`, we compute `diff / 2^step_i` - // and enforce it to be small (soundness holds because for `a` that does - // not divide `b`, `b / a` in the field will be very large. - for i in 0..n { - let step = steps[i]; - c = (&x[i].v - &y[i].v + &c) - .mul_by_inverse_unchecked(&FpVar::constant(F::from(BigUint::one() << step)))?; - if i != n - 1 { - // Unlike the code mentioned above which add some offset to the - // diff `x_i - y_i + c` to make it always positive, we directly - // check if the absolute value of the diff is small. - Self::enforce_abs_bit_length( - &c, - (max(&x[i].ub, &y[i].ub).bits() as usize) - .checked_sub(step) - .unwrap_or_default(), - )?; - } else { - // For the final carry, we need to ensure that it equals the - // remaining limb `rest`. - c.enforce_equal(&rest)?; - } - } - - Ok(()) - } -} - -impl ToBitsGadget for NonNativeUintVar { - fn to_bits_le(&self) -> Result>, SynthesisError> { - Ok(self - .0 - .iter() - .map(|limb| limb.to_bits_le()) - .collect::, _>>()? - .concat()) - } -} - -impl CondSelectGadget for NonNativeUintVar { - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - assert_eq!(true_value.0.len(), false_value.0.len()); - let mut v = vec![]; - for i in 0..true_value.0.len() { - v.push(cond.select(&true_value.0[i], &false_value.0[i])?); - } - Ok(Self(v)) - } -} - -impl NonNativeUintVar { - pub fn ubound(&self) -> BigUint { - let mut r = BigUint::zero(); - - for i in self.0.iter().rev() { - r <<= Self::bits_per_limb(); - r += &i.ub; - } - - r - } - - fn enforce_bit_length(x: &FpVar, length: usize) -> Result>, SynthesisError> { - let cs = x.cs(); - - let bits = &x.value().unwrap_or_default().into_bigint().to_bits_le()[..length]; - let bits = if cs.is_none() { - Vec::new_constant(cs, bits)? - } else { - Vec::new_witness(cs, || Ok(bits))? - }; - - Boolean::le_bits_to_fp(&bits)?.enforce_equal(x)?; - - Ok(bits) - } - - fn enforce_abs_bit_length( - x: &FpVar, - length: usize, - ) -> Result>, SynthesisError> { - let cs = x.cs(); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - - let is_neg = Boolean::new_variable( - cs.clone(), - || Ok(x.value().unwrap_or_default().into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO), - mode, - )?; - let bits = Vec::new_variable( - cs.clone(), - || { - Ok({ - let x = x.value().unwrap_or_default(); - let mut bits = if is_neg.value().unwrap_or_default() { - -x - } else { - x - } - .into_bigint() - .to_bits_le(); - bits.resize(length, false); - bits - }) - }, - mode, - )?; - - // Below is equivalent to but more efficient than - // `Boolean::le_bits_to_fp(&bits)?.enforce_equal(&is_neg.select(&x.negate()?, &x)?)?` - // Note that this enforces: - // 1. The claimed absolute value `is_neg.select(&x.negate()?, &x)?` has - // exactly `length` bits. - // 2. `is_neg` is indeed the sign of `x`, i.e., `is_neg = false` when - // `0 <= x < (|F| - 1) / 2`, and `is_neg = true` when - // `(|F| - 1) / 2 <= x < F`, thus the claimed absolute value is - // correct. - // If `is_neg` is incorrect, then: - // a. `0 <= x < (|F| - 1) / 2`, but `is_neg = true`, then - // `is_neg.select(&x.negate()?, &x)?` returns `|F| - x`, - // which is greater than `(|F| - 1) / 2` and cannot fit in - // `length` bits (given that `length` is small). - // b. `(|F| - 1) / 2 <= x < F`, but `is_neg = false`, then - // `is_neg.select(&x.negate()?, &x)?` returns `x`, which is - // greater than `(|F| - 1) / 2` and cannot fit in `length` - // bits. - FpVar::from(is_neg).mul_equals(&x.double()?, &(x - Boolean::le_bits_to_fp(&bits)?))?; - - Ok(bits) - } - - /// Compute `self + other`, without aligning the limbs. - pub fn add_no_align(&self, other: &Self) -> Result { - let mut z = vec![LimbVar::zero(); max(self.0.len(), other.0.len())]; - for (i, v) in self.0.iter().enumerate() { - z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?; - } - for (i, v) in other.0.iter().enumerate() { - z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?; - } - Ok(Self(z)) - } - - /// Compute `self * other`, without aligning the limbs. - /// Implements the O(n) approach described in xJsnark, Section IV.B.1) - pub fn mul_no_align(&self, other: &Self) -> Result { - let len = self.0.len() + other.0.len() - 1; - if self.is_constant() || other.is_constant() { - // Use the naive approach for constant operands, which costs no - // constraints. - let z = (0..len) - .map(|i| { - let start = max(i + 1, other.0.len()) - other.0.len(); - let end = min(i + 1, self.0.len()); - LimbVar::add_many( - &(start..end) - .map(|j| self.0[j].mul(&other.0[i - j])) - .collect::>>()?, - ) - }) - .collect::>>() - .ok_or(SynthesisError::Unsatisfiable)?; - return Ok(Self(z)); - } - let cs = self.cs().or(other.cs()); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - - // Compute the result `z` outside the circuit and provide it as hints. - let z = { - let mut z = vec![(F::zero(), BigUint::zero()); len]; - for i in 0..self.0.len() { - for j in 0..other.0.len() { - z[i + j].0 += self.0[i].value().unwrap_or_default() - * other.0[j].value().unwrap_or_default(); - z[i + j].1 += &self.0[i].ub * &other.0[j].ub; - } - } - z.into_iter() - .map(|(v, ub)| { - assert!(ub < F::MODULUS_MINUS_ONE_DIV_TWO.into()); - Ok(LimbVar { - v: FpVar::new_variable(cs.clone(), || Ok(v), mode)?, - ub, - }) - }) - .collect::, _>>()? - }; - for c in 1..=len { - let c = F::from(c as u64); - let mut t = F::one(); - let mut c_powers = vec![]; - for _ in 0..len { - c_powers.push(t); - t *= c; - } - // `l = Σ self[i] c^i` - let l = self - .0 - .iter() - .zip(&c_powers) - .map(|(v, t)| (&v.v * *t)) - .collect::>() - .iter() - .sum::>(); - // `r = Σ other[i] c^i` - let r = other - .0 - .iter() - .zip(&c_powers) - .map(|(v, t)| (&v.v * *t)) - .collect::>() - .iter() - .sum::>(); - // `o = Σ z[i] c^i` - let o = z - .iter() - .zip(&c_powers) - .map(|(v, t)| &v.v * *t) - .collect::>() - .iter() - .sum::>(); - // Enforce `o = l * r` - l.mul_equals(&r, &o)?; - } - - Ok(Self(z)) - } - - /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. - pub fn modulo(&self) -> Result { - let cs = self.cs(); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - let m: BigUint = M::MODULUS.into(); - // Provide the quotient and remainder as hints - let (q, r) = { - let v = self.value().unwrap_or_default(); - let (q, r) = v.div_rem(&m); - let q_ubound = self.ubound().div_ceil(&m); - let r_ubound = &m; - ( - Self::new_variable( - cs.clone(), - || Ok(BoundedBigUint(q, q_ubound.bits() as usize)), - mode, - )?, - Self::new_variable( - cs.clone(), - || Ok(BoundedBigUint(r, r_ubound.bits() as usize)), - mode, - )?, - ) - }; - - let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?; - // Enforce `self = q * m + r` - q.mul_no_align(&m)? - .add_no_align(&r)? - .enforce_equal_unaligned(self)?; - // Enforce `r < m` (and `r >= 0` already holds) - r.enforce_lt(&m)?; - - Ok(r) - } - - /// Enforce that `self` is congruent to `other` modulo `M::MODULUS`. - pub fn enforce_congruent(&self, other: &Self) -> Result<(), SynthesisError> { - let cs = self.cs(); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - let m: BigUint = M::MODULUS.into(); - let bits = (max(self.ubound(), other.ubound()) / &m).bits() as usize; - // Provide the quotient `|x - y| / m` and a boolean indicating if `x > y` - // as hints. - let (q, is_ge) = { - let x = self.value().unwrap_or_default(); - let y = other.value().unwrap_or_default(); - let (d, b) = if x > y { - ((x - y) / &m, true) - } else { - ((y - x) / &m, false) - }; - ( - Self::new_variable(cs.clone(), || Ok(BoundedBigUint(d, bits)), mode)?, - Boolean::new_variable(cs.clone(), || Ok(b), mode)?, - ) - }; - - let zero = Self::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), bits))?; - let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?; - let l = self.add_no_align(&is_ge.select(&zero, &q)?.mul_no_align(&m)?)?; - let r = other.add_no_align(&is_ge.select(&q, &zero)?.mul_no_align(&m)?)?; - // If `self >= other`, enforce `self = other + q * m` - // Otherwise, enforce `self + q * m = other` - // Soundness holds because if `self` and `other` are not congruent, then - // one can never find a `q` satisfying either equation above. - l.enforce_equal_unaligned(&r) - } -} - -impl EquivalenceGadget for NonNativeUintVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { - self.enforce_congruent::(other) - } -} - -impl]>> From for NonNativeUintVar { - fn from(bits: B) -> Self { - Self( - bits.as_ref() - .chunks(Self::bits_per_limb()) - .map(LimbVar::from) - .collect::>(), - ) - } -} - -impl, const N: usize> AbsorbNonNative for Fp { - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; - let num_limbs = (Fp::::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb); - - let mut limbs = self - .into_bigint() - .to_bits_le() - .chunks(bits_per_limb) - .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) - .collect::>(); - limbs.resize(num_limbs, F::zero()); - - dest.extend(&limbs) - } -} - -impl AbsorbNonNativeGadget for NonNativeUintVar { - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { - let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; - - let limbs = self - .to_bits_le()? - .chunks(bits_per_limb) - .map(Boolean::le_bits_to_fp) - .collect::, _>>()?; - - Ok(limbs) - } -} - -impl, const N: usize> Inputize for Fp { - /// Returns the internal representation in the same order as how the value - /// is allocated in `FpVar::new_input`. - fn inputize(&self) -> Vec { - vec![*self] - } -} - -impl InputizeNonNative for P { - /// Returns the internal representation in the same order as how the value - /// is allocated in `NonNativeUintVar::new_input`. - fn inputize_nonnative(&self) -> Vec { - self.into_bigint() - .to_bits_le() - .chunks(NonNativeUintVar::::bits_per_limb()) - .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) - .collect() - } -} - -impl VectorGadget> for [NonNativeUintVar] { - fn add(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.add_no_align(y)) - .collect() - } - - fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.mul_no_align(y)) - .collect() - } - - fn mul_scalar( - &self, - other: &NonNativeUintVar, - ) -> Result>, SynthesisError> { - self.iter().map(|x| x.mul_no_align(other)).collect() - } -} - -impl MatrixGadget> for SparseMatrixVar> { - fn mul_vector( - &self, - v: &[NonNativeUintVar], - ) -> Result>, SynthesisError> { - self.coeffs - .iter() - .map(|row| { - let len = row - .iter() - .map(|(value, col_i)| value.0.len() + v[*col_i].0.len() - 1) - .max() - .unwrap_or(0); - // This is a combination of `mul_no_align` and `add_no_align` - // that results in more flattened `LinearCombination`s. - // Consequently, `ConstraintSystem::inline_all_lcs` costs less - // time, thus making trusted setup and proof generation faster. - (0..len) - .map(|i| { - LimbVar::add_many( - &row.iter() - .flat_map(|(value, col_i)| { - let start = max(i + 1, v[*col_i].0.len()) - v[*col_i].0.len(); - let end = min(i + 1, value.0.len()); - (start..end).map(|j| value.0[j].mul(&v[*col_i].0[i - j])) - }) - .collect::>>()?, - ) - }) - .collect::>>() - .ok_or(SynthesisError::Unsatisfiable) - .map(NonNativeUintVar) - }) - .collect::, _>>() - } -} - -#[cfg(test)] -mod tests { - use ark_ff::Field; - use ark_pallas::{Fq, Fr}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{test_rng, UniformRand}; - use num_bigint::RandBigInt; - - use super::*; - use crate::Error; - - #[test] - fn test_mul_biguint() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - - let size = 256; - - let rng = &mut test_rng(); - let a = rng.gen_biguint(size as u64); - let b = rng.gen_biguint(size as u64); - let ab = &a * &b; - let aab = &a * &ab; - let abb = &ab * &b; - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(a, size)))?; - let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(b, size)))?; - let ab_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(ab, size * 2)))?; - let aab_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(aab, size * 3)))?; - let abb_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(abb, size * 3)))?; - - a_var - .mul_no_align(&b_var)? - .enforce_equal_unaligned(&ab_var)?; - a_var - .mul_no_align(&ab_var)? - .enforce_equal_unaligned(&aab_var)?; - ab_var - .mul_no_align(&b_var)? - .enforce_equal_unaligned(&abb_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_mul_fq() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - - let rng = &mut test_rng(); - let a = Fq::rand(rng); - let b = Fq::rand(rng); - let ab = a * b; - let aab = a * ab; - let abb = ab * b; - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; - let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(b))?; - let ab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(ab))?; - let aab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(aab))?; - let abb_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(abb))?; - - a_var - .mul_no_align(&b_var)? - .enforce_congruent::(&ab_var)?; - a_var - .mul_no_align(&ab_var)? - .enforce_congruent::(&aab_var)?; - ab_var - .mul_no_align(&b_var)? - .enforce_congruent::(&abb_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_pow() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - - let rng = &mut test_rng(); - - let a = Fq::rand(rng); - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; - - let mut r_var = a_var.clone(); - for _ in 0..16 { - r_var = r_var.mul_no_align(&r_var)?.modulo::()?; - } - r_var = r_var.mul_no_align(&a_var)?.modulo::()?; - assert_eq!(a.pow([65537u64]), Fq::from(r_var.value()?)); - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_vec_vec_mul() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - - let len = 1000; - - let rng = &mut test_rng(); - let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); - let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); - let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); - - let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; - let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; - let c_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(c))?; - - let mut r_var = - NonNativeUintVar::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), 0))?; - for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { - r_var = r_var.add_no_align(&a.mul_no_align(&b)?)?; - } - r_var.enforce_congruent::(&c_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/circuits/sum_check.rs b/folding-schemes/src/folding/circuits/sum_check.rs deleted file mode 100644 index 5a7c49d9d..000000000 --- a/folding-schemes/src/folding/circuits/sum_check.rs +++ /dev/null @@ -1,259 +0,0 @@ -/// Heavily inspired from testudo: https://github.com/cryptonetlab/testudo/tree/master -/// Some changes: -/// - Typings to better stick to ark_poly's API -/// - Uses `folding-schemes`' own `TranscriptVar` trait and `PoseidonTranscriptVar` struct -/// - API made closer to gadgets found in `folding-schemes` -use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge}; -use ark_ff::PrimeField; -use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use std::{borrow::Borrow, marker::PhantomData}; - -use crate::utils::espresso::sum_check::SumCheck; -use crate::utils::virtual_polynomial::VPAuxInfo; -use crate::{ - transcript::TranscriptVar, - utils::sum_check::{structs::IOPProof, IOPSumCheck}, -}; - -#[derive(Clone, Debug)] -pub struct DensePolynomialVar { - pub coeffs: Vec>, -} - -impl AllocVar, F> for DensePolynomialVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|c| { - let cs = cs.into(); - let cp: &DensePolynomial = c.borrow(); - let mut coeffs_var = Vec::>::with_capacity(cp.coeffs.len()); - for coeff in cp.coeffs.iter() { - let coeff_var = FpVar::::new_variable(cs.clone(), || Ok(coeff), mode)?; - coeffs_var.push(coeff_var); - } - Ok(Self { coeffs: coeffs_var }) - }) - } -} - -impl DensePolynomialVar { - pub fn eval_at_zero(&self) -> FpVar { - if self.coeffs.is_empty() { - return FpVar::::zero(); - } - self.coeffs[0].clone() - } - - pub fn eval_at_one(&self) -> FpVar { - if self.coeffs.is_empty() { - return FpVar::::zero(); - } - let mut res = self.coeffs[0].clone(); - for i in 1..self.coeffs.len() { - res = &res + &self.coeffs[i]; - } - res - } - - pub fn evaluate(&self, r: &FpVar) -> FpVar { - if self.coeffs.is_empty() { - return FpVar::::zero(); - } - let mut eval = self.coeffs[0].clone(); - let mut power = r.clone(); - - for i in 1..self.coeffs.len() { - eval += &power * &self.coeffs[i]; - power *= r; - } - eval - } -} - -#[derive(Clone, Debug)] -pub struct IOPProofVar { - // We have to be generic over a CurveGroup because instantiating a IOPProofVar will call IOPSumCheck which requires a CurveGroup - pub proofs: Vec>, - pub claim: FpVar, -} - -impl AllocVar, F> for IOPProofVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|c| { - let cs = cs.into(); - let cp: &IOPProof = c.borrow(); - let claim = IOPSumCheck::>::extract_sum(cp); - let claim = FpVar::::new_variable(cs.clone(), || Ok(claim), mode)?; - let mut proofs = Vec::>::with_capacity(cp.proofs.len()); - for proof in cp.proofs.iter() { - let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs); - let proof = DensePolynomialVar::::new_variable(cs.clone(), || Ok(poly), mode)?; - proofs.push(proof); - } - Ok(Self { proofs, claim }) - }) - } -} - -#[derive(Clone, Debug)] -pub struct VPAuxInfoVar { - pub num_variables: FpVar, - pub max_degree: FpVar, -} - -impl AllocVar, F> for VPAuxInfoVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|c| { - let cs = cs.into(); - let cp: &VPAuxInfo = c.borrow(); - let num_variables = FpVar::::new_variable( - cs.clone(), - || Ok(F::from(cp.num_variables as u64)), - mode, - )?; - let max_degree = - FpVar::::new_variable(cs.clone(), || Ok(F::from(cp.max_degree as u64)), mode)?; - Ok(Self { - num_variables, - max_degree, - }) - }) - } -} - -#[derive(Debug, Clone)] -pub struct SumCheckVerifierGadget { - _f: PhantomData, -} - -impl SumCheckVerifierGadget { - #[allow(clippy::type_complexity)] - pub fn verify>( - iop_proof_var: &IOPProofVar, - poly_aux_info_var: &VPAuxInfoVar, - transcript_var: &mut T, - enabled: Boolean, - ) -> Result<(Vec>, Vec>), SynthesisError> { - let mut e_vars = vec![iop_proof_var.claim.clone()]; - let mut r_vars: Vec> = Vec::new(); - transcript_var.absorb(&poly_aux_info_var.num_variables)?; - transcript_var.absorb(&poly_aux_info_var.max_degree)?; - - for poly_var in iop_proof_var.proofs.iter() { - let res = poly_var.eval_at_one() + poly_var.eval_at_zero(); - let e_var = e_vars.last().ok_or(SynthesisError::Unsatisfiable)?; - res.conditional_enforce_equal(e_var, &enabled)?; - transcript_var.absorb(&poly_var.coeffs)?; - let r_i_var = transcript_var.get_challenge()?; - e_vars.push(poly_var.evaluate(&r_i_var)); - r_vars.push(r_i_var); - } - - Ok((e_vars, r_vars)) - } -} - -#[cfg(test)] -mod tests { - use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, - }; - use ark_pallas::Fr; - use ark_poly::{DenseMultilinearExtension, MultilinearExtension, Polynomial}; - use ark_r1cs_std::GR1CSVar; - use ark_relations::gr1cs::ConstraintSystem; - use std::sync::Arc; - - use super::*; - use crate::{ - transcript::poseidon::poseidon_canonical_config, - utils::virtual_polynomial::VirtualPolynomial, Error, - }; - - pub type TestSumCheckProof = (VirtualPolynomial, PoseidonConfig, IOPProof); - - /// Primarily used for testing the sumcheck gadget - /// Returns a random virtual polynomial, the poseidon config used and the associated sumcheck proof - pub fn get_test_sumcheck_proof( - num_vars: usize, - ) -> Result, Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config: PoseidonConfig = poseidon_canonical_config::(); - let mut poseidon_transcript_prove = PoseidonSponge::::new(&poseidon_config); - let poly_mle = DenseMultilinearExtension::rand(num_vars, &mut rng); - let virtual_poly = VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), F::ONE); - let sum_check: IOPProof = IOPSumCheck::>::prove( - &virtual_poly, - &mut poseidon_transcript_prove, - )?; - Ok((virtual_poly, poseidon_config, sum_check)) - } - - #[test] - fn test_sum_check_circuit() -> Result<(), Error> { - for num_vars in 1..15 { - let cs = ConstraintSystem::::new_ref(); - let (virtual_poly, poseidon_config, sum_check) = - get_test_sumcheck_proof::(num_vars)?; - let mut poseidon_var: PoseidonSpongeVar = - PoseidonSpongeVar::new(cs.clone(), &poseidon_config); - let iop_proof_var = IOPProofVar::::new_witness(cs.clone(), || Ok(&sum_check))?; - let poly_aux_info_var = - VPAuxInfoVar::::new_witness(cs.clone(), || Ok(virtual_poly.aux_info))?; - let enabled = Boolean::::new_witness(cs.clone(), || Ok(true))?; - let res = SumCheckVerifierGadget::::verify( - &iop_proof_var, - &poly_aux_info_var, - &mut poseidon_var, - enabled, - ); - - assert!(res.is_ok()); - let (circuit_evals, r_challenges) = res?; - - // 1. assert claim from circuit is equal to the one from the sum-check - let claim: Fr = IOPSumCheck::>::extract_sum(&sum_check); - assert_eq!(circuit_evals[0].value()?, claim); - - // 2. assert that all in-circuit evaluations are equal to the ones from the sum-check - for ((proof, point), circuit_eval) in sum_check - .proofs - .iter() - .zip(sum_check.point.iter()) - .zip(circuit_evals.iter().skip(1)) - // we skip the first one since it's the above checked claim - { - let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs); - let eval = poly.evaluate(point); - assert_eq!(eval, circuit_eval.value()?); - } - - // 3. assert that all challenges are equal to the ones from the sum-check - for (point, r_challenge) in sum_check.point.iter().zip(r_challenges.iter()) { - assert_eq!(*point, r_challenge.value()?); - } - - assert!(cs.is_satisfied()?); - } - Ok(()) - } -} diff --git a/folding-schemes/src/folding/circuits/utils.rs b/folding-schemes/src/folding/circuits/utils.rs deleted file mode 100644 index da6e579c4..000000000 --- a/folding-schemes/src/folding/circuits/utils.rs +++ /dev/null @@ -1,75 +0,0 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; -use ark_relations::gr1cs::SynthesisError; -use std::marker::PhantomData; - -/// EqEval is a gadget for computing $\tilde{eq}(a, b) = \prod_{i=1}^{l}(a_i \cdot b_i + (1 - a_i)(1 - b_i))$ -/// :warning: This is not the ark_r1cs_std::eq::EqGadget -pub struct EqEvalGadget { - _f: PhantomData, -} - -impl EqEvalGadget { - /// Gadget to evaluate eq polynomial. - /// Follows the implementation of `eq_eval` found in this crate. - pub fn eq_eval(x: &[FpVar], y: &[FpVar]) -> Result, SynthesisError> { - if x.len() != y.len() { - return Err(SynthesisError::Unsatisfiable); - } - if x.is_empty() || y.is_empty() { - return Err(SynthesisError::AssignmentMissing); - } - let mut e = FpVar::::one(); - for (xi, yi) in x.iter().zip(y.iter()) { - let xi_yi = xi * yi; - e *= xi_yi.clone() + xi_yi - xi - yi + F::one(); - } - Ok(e) - } -} - -#[cfg(test)] -mod tests { - use ark_ff::Field; - use ark_pallas::Fr; - use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{test_rng, UniformRand}; - - use super::EqEvalGadget; - use crate::utils::virtual_polynomial::eq_eval; - use crate::Error; - - #[test] - pub fn test_eq_eval_gadget() -> Result<(), Error> { - let mut rng = test_rng(); - let cs = ConstraintSystem::::new_ref(); - - for i in 1..20 { - let x_vec: Vec = (0..i).map(|_| Fr::rand(&mut rng)).collect(); - let y_vec: Vec = (0..i).map(|_| Fr::rand(&mut rng)).collect(); - let x: Vec> = x_vec - .iter() - .map(|x| FpVar::::new_witness(cs.clone(), || Ok(x))) - .collect::, _>>()?; - let y: Vec> = y_vec - .iter() - .map(|y| FpVar::::new_witness(cs.clone(), || Ok(y))) - .collect::, _>>()?; - let expected_eq_eval = eq_eval::(&x_vec, &y_vec)?; - let gadget_eq_eval: FpVar = EqEvalGadget::::eq_eval(&x, &y)?; - assert_eq!(expected_eq_eval, gadget_eq_eval.value()?); - } - - let x: Vec> = vec![]; - let y: Vec> = vec![]; - let gadget_eq_eval = EqEvalGadget::::eq_eval(&x, &y); - assert!(gadget_eq_eval.is_err()); - - let x: Vec> = vec![]; - let y: Vec> = vec![FpVar::::new_witness(cs.clone(), || Ok(&Fr::ONE))?]; - let gadget_eq_eval = EqEvalGadget::::eq_eval(&x, &y); - assert!(gadget_eq_eval.is_err()); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/cccs.rs b/folding-schemes/src/folding/hypernova/cccs.rs deleted file mode 100644 index 3f8306598..000000000 --- a/folding-schemes/src/folding/hypernova/cccs.rs +++ /dev/null @@ -1,263 +0,0 @@ -use ark_crypto_primitives::sponge::Absorb; -use ark_ff::PrimeField; -use ark_serialize::CanonicalDeserialize; -use ark_serialize::CanonicalSerialize; -use ark_std::{rand::Rng, sync::Arc, One, Zero}; - -use super::circuits::CCCSVar; -use super::Witness; -use crate::arith::{ccs::CCS, Arith, ArithRelation}; -use crate::commitment::CommitmentScheme; -use crate::folding::circuits::CF1; -use crate::folding::traits::Inputize; -use crate::folding::traits::{CommittedInstanceOps, Dummy}; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::utils::vec::{is_zero_vec, mat_vec_mul}; -use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial}; -use crate::{Curve, Error}; - -/// Committed CCS instance -#[derive(Debug, Clone, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CCCS { - // Commitment to witness - pub C: C, - // Public input/output - pub x: Vec, -} - -impl CCS { - pub fn to_cccs, const H: bool>( - &self, - rng: &mut R, - cs_params: &CS::ProverParams, - z: &[F], - ) -> Result<(CCCS, Witness), Error> - where - // enforce that CCS's F is the C::ScalarField - C: Curve, - { - let (w, x) = self.split_z(z); - - // if the commitment scheme is set to be hiding, set the random blinding parameter - let r_w = if CS::is_hiding() { - F::rand(rng) - } else { - F::zero() - }; - let C = CS::commit(cs_params, &w, &r_w)?; - - Ok((CCCS:: { C, x }, Witness:: { w, r_w })) - } - - /// Computes q(x) = \sum^q c_i * \prod_{j \in S_i} ( \sum_{y \in {0,1}^s'} M_j(x, y) * z(y) ) - /// polynomial over x - pub fn compute_q(&self, z: &[F]) -> Result, Error> { - let mut q_x = VirtualPolynomial::::new(self.s); - for (S_i, &c_i) in self.S.iter().zip(&self.c) { - let mut Q_k = vec![]; - for &j in S_i { - Q_k.push(Arc::new(dense_vec_to_dense_mle( - self.s, - &mat_vec_mul(&self.M[j], z)?, - ))); - } - q_x.add_mle_list(Q_k, c_i)?; - } - Ok(q_x) - } - - /// Computes Q(x) = eq(beta, x) * q(x) - /// = eq(beta, x) * \sum^q c_i * \prod_{j \in S_i} ( \sum_{y \in {0,1}^s'} M_j(x, y) * z(y) ) - /// polynomial over x - pub fn compute_Q(&self, z: &[F], beta: &[F]) -> Result, Error> { - let eq_beta = build_eq_x_r_vec(beta)?; - let eq_beta_mle = Arc::new(dense_vec_to_dense_mle(self.s, &eq_beta)); - - let mut Q = VirtualPolynomial::::new(self.s); - for (S_i, &c_i) in self.S.iter().zip(&self.c) { - let mut Q_k = vec![]; - for &j in S_i { - Q_k.push(Arc::new(dense_vec_to_dense_mle( - self.s, - &mat_vec_mul(&self.M[j], z)?, - ))); - } - Q_k.push(eq_beta_mle.clone()); - Q.add_mle_list(Q_k, c_i)?; - } - Ok(Q) - } -} - -impl Dummy<&CCS>> for CCCS { - fn dummy(ccs: &CCS>) -> Self { - Self { - C: C::zero(), - x: vec![CF1::::zero(); ccs.n_public_inputs()], - } - } -} - -impl ArithRelation>, CCCS> for CCS> { - type Evaluation = Vec>; - - fn eval_relation(&self, w: &Witness>, u: &CCCS) -> Result { - // evaluate CCCS relation - self.eval_at_z(&[&[CF1::::one()][..], &u.x, &w.w].concat()) - } - - /// Perform the check of the CCCS instance described at section 4.1, - /// notice that this method does not check the commitment correctness - fn check_evaluation( - _w: &Witness>, - _u: &CCCS, - e: Self::Evaluation, - ) -> Result<(), Error> { - // A CCCS relation is satisfied if the q(x) multivariate polynomial evaluates to zero in - // the hypercube, evaluating over the whole boolean hypercube for a normal-sized instance - // would take too much, this checks the CCS relation of the CCCS. - is_zero_vec(&e).then_some(()).ok_or(Error::NotSatisfied) - } -} - -impl Absorb for CCCS { - fn to_sponge_bytes(&self, dest: &mut Vec) { - C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.C.to_native_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - } -} - -impl CommittedInstanceOps for CCCS { - type Var = CCCSVar; - - fn get_commitments(&self) -> Vec { - vec![self.C] - } - - fn is_incoming(&self) -> bool { - true - } -} - -impl Inputize> for CCCS { - /// Returns the internal representation in the same order as how the value - /// is allocated in `CCCSVar::new_input`. - fn inputize(&self) -> Vec> { - [&self.C.inputize_nonnative()[..], &self.x].concat() - } -} - -#[cfg(test)] -pub mod tests { - use ark_pallas::Fr; - use ark_std::test_rng; - use ark_std::UniformRand; - - use super::*; - use crate::arith::ccs::tests::{get_test_ccs, get_test_z}; - use crate::utils::hypercube::BooleanHypercube; - - /// Do some sanity checks on q(x). It's a multivariable polynomial and it should evaluate to zero inside the - /// hypercube, but to not-zero outside the hypercube. - #[test] - fn test_compute_q() -> Result<(), Error> { - let mut rng = test_rng(); - - let ccs = get_test_ccs::(); - let z = get_test_z(3); - - let q = ccs.compute_q(&z)?; - - // Evaluate inside the hypercube - for x in BooleanHypercube::new(ccs.s) { - assert_eq!(Fr::zero(), q.evaluate(&x)?); - } - - // Evaluate outside the hypercube - let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - assert_ne!(Fr::zero(), q.evaluate(&beta)?); - Ok(()) - } - - /// Perform some sanity checks on Q(x). - #[test] - fn test_compute_Q() -> Result<(), Error> { - let mut rng = test_rng(); - - let ccs: CCS = get_test_ccs(); - let z = get_test_z(3); - let (w, x) = ccs.split_z(&z); - ccs.check_relation(&w, &x)?; - - let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - - // Compute Q(x) = eq(beta, x) * q(x). - let Q = ccs.compute_Q(&z, &beta)?; - - // Let's consider the multilinear polynomial G(x) = \sum_{y \in {0, 1}^s} eq(x, y) q(y) - // which interpolates the multivariate polynomial q(x) inside the hypercube. - // - // Observe that summing Q(x) inside the hypercube, directly computes G(\beta). - // - // Now, G(x) is multilinear and agrees with q(x) inside the hypercube. Since q(x) vanishes inside the - // hypercube, this means that G(x) also vanishes in the hypercube. Since G(x) is multilinear and vanishes - // inside the hypercube, this makes it the zero polynomial. - // - // Hence, evaluating G(x) at a random beta should give zero. - - // Now sum Q(x) evaluations in the hypercube and expect it to be 0 - let r = BooleanHypercube::new(ccs.s) - .map(|x| Q.evaluate(&x)) - .collect::, _>>()? - .into_iter() - .fold(Fr::zero(), |acc, result| acc + result); - assert_eq!(r, Fr::zero()); - Ok(()) - } - - /// The polynomial G(x) (see above) interpolates q(x) inside the hypercube. - /// Summing Q(x) over the hypercube is equivalent to evaluating G(x) at some point. - /// This test makes sure that G(x) agrees with q(x) inside the hypercube, but not outside - #[test] - fn test_Q_against_q() -> Result<(), Error> { - let mut rng = test_rng(); - - let ccs: CCS = get_test_ccs(); - let z = get_test_z(3); - let (w, x) = ccs.split_z(&z); - ccs.check_relation(&w, &x)?; - - // Now test that if we create Q(x) with eq(d,y) where d is inside the hypercube, \sum Q(x) should be G(d) which - // should be equal to q(d), since G(x) interpolates q(x) inside the hypercube - let q = ccs.compute_q(&z)?; - for d in BooleanHypercube::new(ccs.s) { - let Q_at_d = ccs.compute_Q(&z, &d)?; - - // Get G(d) by summing over Q_d(x) over the hypercube - let G_at_d = BooleanHypercube::new(ccs.s) - .map(|x| Q_at_d.evaluate(&x)) - .collect::, _>>()? - .into_iter() - .fold(Fr::zero(), |acc, result| acc + result); - assert_eq!(G_at_d, q.evaluate(&d)?); - } - - // Now test that they should disagree outside of the hypercube - let r: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - let Q_at_r = ccs.compute_Q(&z, &r)?; - - // Get G(d) by summing over Q_d(x) over the hypercube - let G_at_r = BooleanHypercube::new(ccs.s) - .map(|x| Q_at_r.evaluate(&x)) - .collect::, _>>()? - .into_iter() - .fold(Fr::zero(), |acc, result| acc + result); - - assert_ne!(G_at_r, q.evaluate(&r)?); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs deleted file mode 100644 index c352da23f..000000000 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ /dev/null @@ -1,1389 +0,0 @@ -/// Implementation of [HyperNova](https://eprint.iacr.org/2023/573.pdf) circuits -use ark_crypto_primitives::sponge::{ - constraints::AbsorbGadget, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, - CryptographicSponge, -}; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - uint8::UInt8, - GR1CSVar, -}; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, - SynthesisMode, -}; -#[cfg(test)] -use ark_std::One; -use ark_std::{fmt::Debug, iter::Sum, Zero}; -use core::{borrow::Borrow, marker::PhantomData}; - -use super::{ - cccs::CCCS, - lcccs::LCCCS, - nimfs::{NIMFSProof, NIMFS}, - HyperNovaCycleFoldConfig, Witness, -}; -use crate::arith::{ - ccs::CCS, - r1cs::{extract_r1cs, R1CS}, - Arith, -}; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::{ - circuits::{ - cyclefold::{ - CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, - CycleFoldConfig, - }, - nonnative::affine::NonNativeAffineVar, - sum_check::{IOPProofVar, SumCheckVerifierGadget, VPAuxInfoVar}, - utils::EqEvalGadget, - CF1, - }, - nova::get_r1cs_from_cs, - traits::{CommittedInstanceVarOps, Dummy}, -}; -use crate::frontend::FCircuit; -use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; -use crate::utils::virtual_polynomial::VPAuxInfo; -use crate::{Curve, Error}; - -/// Committed CCS instance -#[derive(Debug, Clone)] -pub struct CCCSVar { - // Commitment to witness - pub C: NonNativeAffineVar, - // Public io - pub x: Vec>>, -} - -impl AllocVar, CF1> for CCCSVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let C = NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().C), mode)?; - let x: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - - Ok(Self { C, x }) - }) - } -} - -impl CommittedInstanceVarOps for CCCSVar { - type PointVar = NonNativeAffineVar; - - fn get_commitments(&self) -> Vec { - vec![self.C.clone()] - } - - fn get_public_inputs(&self) -> &[FpVar>] { - &self.x - } - - fn enforce_incoming(&self) -> Result<(), SynthesisError> { - // `CCCSVar` is always the incoming instance - Ok(()) - } - - fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { - self.x.enforce_equal(&other.x) - } -} - -impl AbsorbGadget for CCCSVar { - fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) - } - - fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { - Ok([&self.C.to_native_sponge_field_elements()?, &self.x[..]].concat()) - } -} - -/// Linearized Committed CCS instance -#[derive(Debug, Clone)] -pub struct LCCCSVar { - // Commitment to witness - pub C: NonNativeAffineVar, - // Relaxation factor of z for folded LCCCS - pub u: FpVar>, - // Public io - pub x: Vec>>, - // Random evaluation point for the v_i - pub r_x: Vec>>, - // Vector of v_i - pub v: Vec>>, -} - -impl AllocVar, CF1> for LCCCSVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let C = NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().C), mode)?; - let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; - let x: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - let r_x: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().r_x.clone()), mode)?; - let v: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().v.clone()), mode)?; - - Ok(Self { C, u, x, r_x, v }) - }) - } -} - -impl AbsorbGadget for LCCCSVar { - fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) - } - - fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { - Ok([ - &self.C.to_native_sponge_field_elements()?, - &[self.u.clone()][..], - &self.x, - &self.r_x, - &self.v, - ] - .concat()) - } -} - -impl CommittedInstanceVarOps for LCCCSVar { - type PointVar = NonNativeAffineVar; - - fn get_commitments(&self) -> Vec { - vec![self.C.clone()] - } - - fn get_public_inputs(&self) -> &[FpVar>] { - &self.x - } - - fn enforce_incoming(&self) -> Result<(), SynthesisError> { - // `LCCCSVar` is always the running instance - Err(SynthesisError::Unsatisfiable) - } - - fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { - self.u.enforce_equal(&other.u)?; - self.x.enforce_equal(&other.x)?; - self.r_x.enforce_equal(&other.r_x)?; - self.v.enforce_equal(&other.v) - } -} - -/// ProofVar defines a multifolding proof -#[derive(Debug)] -pub struct ProofVar { - pub sc_proof: IOPProofVar, - #[allow(clippy::type_complexity)] - pub sigmas_thetas: (Vec>>>, Vec>>>), -} -impl AllocVar, CF1> for ProofVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let sc_proof = IOPProofVar::::new_variable( - cs.clone(), - || Ok(val.borrow().sc_proof.clone()), - mode, - )?; - let sigmas: Vec>>> = val - .borrow() - .sigmas_thetas - .0 - .iter() - .map(|sigmas_i| Vec::new_variable(cs.clone(), || Ok(sigmas_i.clone()), mode)) - .collect::>>>, SynthesisError>>()?; - let thetas: Vec>>> = val - .borrow() - .sigmas_thetas - .1 - .iter() - .map(|thetas_i| Vec::new_variable(cs.clone(), || Ok(thetas_i.clone()), mode)) - .collect::>>>, SynthesisError>>()?; - - Ok(Self { - sc_proof, - sigmas_thetas: (sigmas.clone(), thetas.clone()), - }) - }) - } -} - -pub struct NIMFSGadget { - _c: PhantomData, -} -impl NIMFSGadget { - /// Runs (in-circuit) the NIMFS.V, which outputs the new folded LCCCS instance together with - /// the rho_powers, which will be used in other parts of the AugmentedFCircuit - #[allow(clippy::type_complexity)] - pub fn verify>( - cs: ConstraintSystemRef>, - // only used the CCS params, not the matrices - ccs: &CCS, - transcript: &mut T, - running_instances: &[LCCCSVar], // U - new_instances: &[CCCSVar], // u - proof: ProofVar, - enabled: Boolean, - ) -> Result<(LCCCSVar, Vec>>), SynthesisError> { - // absorb instances to transcript - transcript.absorb(&running_instances)?; - transcript.absorb(&new_instances)?; - - // get the challenges - let gamma_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"gamma"); - let gamma_scalar: FpVar> = - FpVar::>::new_constant(cs.clone(), gamma_scalar_raw)?; - transcript.absorb(&gamma_scalar)?; - let gamma: FpVar> = transcript.get_challenge()?; - - let beta_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"beta"); - let beta_scalar: FpVar> = - FpVar::>::new_constant(cs.clone(), beta_scalar_raw)?; - transcript.absorb(&beta_scalar)?; - let beta: Vec>> = transcript.get_challenges(ccs.s)?; - - let vp_aux_info_raw = VPAuxInfo:: { - max_degree: ccs.degree() + 1, - num_variables: ccs.s, - phantom: PhantomData::, - }; - let vp_aux_info = VPAuxInfoVar::>::new_witness(cs.clone(), || Ok(vp_aux_info_raw))?; - - // sumcheck - // first, compute the expected sumcheck sum: \sum gamma^j v_j - let mut sum_v_j_gamma = FpVar::>::zero(); - let mut gamma_j = FpVar::::one(); - for running_instance in running_instances.iter() { - for j in 0..running_instance.v.len() { - gamma_j *= gamma.clone(); - sum_v_j_gamma += running_instance.v[j].clone() * gamma_j.clone(); - } - } - - // verify the interactive part of the sumcheck - let (e_vars, r_vars) = SumCheckVerifierGadget::::verify( - &proof.sc_proof, - &vp_aux_info, - transcript, - enabled.clone(), - )?; - - // extract the randomness from the sumcheck - let r_x_prime = r_vars.clone(); - - // verify the claim c - let computed_c = compute_c_gadget( - ccs, - proof.sigmas_thetas.0.clone(), // sigmas - proof.sigmas_thetas.1.clone(), // thetas - gamma, - beta, - running_instances - .iter() - .map(|lcccs| lcccs.r_x.clone()) - .collect(), - r_x_prime.clone(), - )?; - computed_c.conditional_enforce_equal(&e_vars[e_vars.len() - 1], &enabled)?; - - // get the folding challenge - let rho_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"rho"); - let rho_scalar: FpVar> = FpVar::>::new_constant(cs.clone(), rho_scalar_raw)?; - transcript.absorb(&rho_scalar)?; - let rho_bits: Vec>> = transcript.get_challenge_nbits(NOVA_N_BITS_RO)?; - let rho = Boolean::le_bits_to_fp(&rho_bits)?; - - // Self::fold will return the folded instance - let folded_lcccs = Self::fold( - running_instances, - new_instances, - proof.sigmas_thetas, - r_x_prime, - rho, - )?; - // return the rho_bits so it can be used in other parts of the AugmentedFCircuit - Ok((folded_lcccs, rho_bits)) - } - - /// Runs (in-circuit) the verifier side of the fold, computing the new folded LCCCS instance - #[allow(clippy::type_complexity)] - fn fold( - lcccs: &[LCCCSVar], - cccs: &[CCCSVar], - sigmas_thetas: (Vec>>>, Vec>>>), - r_x_prime: Vec>>, - rho: FpVar>, - ) -> Result, SynthesisError> { - let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone()); - let mut u_folded: FpVar> = FpVar::zero(); - let mut x_folded: Vec>> = vec![FpVar::zero(); lcccs[0].x.len()]; - let mut v_folded: Vec>> = vec![FpVar::zero(); sigmas[0].len()]; - - let mut rho_i = FpVar::one(); - for i in 0..(lcccs.len() + cccs.len()) { - let u: FpVar>; - let x: Vec>>; - let v: Vec>>; - if i < lcccs.len() { - u = lcccs[i].u.clone(); - x = lcccs[i].x.clone(); - v = sigmas[i].clone(); - } else { - u = FpVar::one(); - x = cccs[i - lcccs.len()].x.clone(); - v = thetas[i - lcccs.len()].clone(); - } - - u_folded += rho_i.clone() * u; - x_folded = x_folded - .iter() - .zip( - x.iter() - .map(|x_i| x_i * rho_i.clone()) - .collect::>>>(), - ) - .map(|(a_i, b_i)| a_i + b_i) - .collect(); - - v_folded = v_folded - .iter() - .zip( - v.iter() - .map(|x_i| x_i * rho_i.clone()) - .collect::>>>(), - ) - .map(|(a_i, b_i)| a_i + b_i) - .collect(); - - // compute the next power of rho - rho_i *= rho.clone(); - } - - // return the folded instance, together with the rho's powers vector so they can be used in - // other parts of the AugmentedFCircuit - Ok(LCCCSVar:: { - // C this is later overwritten by the U_{i+1}.C value checked by the cyclefold circuit - C: lcccs[0].C.clone(), - u: u_folded, - x: x_folded, - r_x: r_x_prime, - v: v_folded, - }) - } -} - -/// Computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS -/// instances: -/// $$ -/// c = \sum_{i \in [\mu]} \left(\sum_{j \in [t]} \gamma^{i \cdot t + j} \cdot e_i \cdot \sigma_{i,j} \right) + -/// \sum_{k \in [\nu]} \gamma^{\mu \cdot t+k} \cdot e_k \cdot \left( \sum_{i=1}^q c_i \cdot \prod_{j \in S_i} -/// \theta_{k,j} \right) -/// $$ -#[allow(clippy::too_many_arguments)] -fn compute_c_gadget( - ccs: &CCS, - vec_sigmas: Vec>>, - vec_thetas: Vec>>, - gamma: FpVar, - beta: Vec>, - vec_r_x: Vec>>, - vec_r_x_prime: Vec>, -) -> Result, SynthesisError> { - let mut e_lcccs = Vec::new(); - for r_x in vec_r_x.iter() { - e_lcccs.push(EqEvalGadget::eq_eval(r_x, &vec_r_x_prime)?); - } - - let mut c = FpVar::::zero(); - let mut current_gamma = FpVar::::one(); - for i in 0..vec_sigmas.len() { - for sigma in &vec_sigmas[i] { - c += current_gamma.clone() * e_lcccs[i].clone() * sigma; - current_gamma *= gamma.clone(); - } - } - - let e_k = EqEvalGadget::eq_eval(&beta, &vec_r_x_prime)?; - #[allow(clippy::needless_range_loop)] - for k in 0..vec_thetas.len() { - let prods = ccs.S.iter().zip(&ccs.c).map(|(S_i, &c_i)| { - let mut prod = FpVar::::one(); - for &j in S_i { - prod *= &vec_thetas[k][j]; - } - prod * c_i - }); - let sum = FpVar::sum(prods); - c += current_gamma.clone() * e_k.clone() * sum; - current_gamma *= gamma.clone(); - } - Ok(c) -} - -/// `AugmentedFCircuit` enhances the original step function `F`, so that it can -/// be used in recursive arguments such as IVC. -/// -/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in -/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not -/// only invokes `F`, but also adds additional constraints for verifying the -/// correct folding of primary instances (i.e., the instances over `C1`). -/// In the paper, the primary instances are Nova's `CommittedInstance`, but we -/// extend this method to support using HyperNova's `LCCCS` and `CCCS` instances -/// as primary instances. -/// -/// Furthermore, to reduce circuit size over `C2`, we implement the constraints -/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra -/// constraints verify the correct folding of CycleFold instances. -/// -/// For multi-instance folding, one needs to specify the const generics below: -/// * `MU` - the number of LCCCS instances to be folded -/// * `NU` - the number of CCCS instances to be folded -#[derive(Debug, Clone)] -pub struct AugmentedFCircuit< - C1: Curve, - C2: Curve, - FC: FCircuit>, - const MU: usize, - const NU: usize, -> { - pub(super) poseidon_config: PoseidonConfig>, - pub(super) ccs: CCS, // CCS of the AugmentedFCircuit - pub(super) pp_hash: Option>, - pub(super) i: Option>, - pub(super) i_usize: Option, - pub(super) z_0: Option>, - pub(super) z_i: Option>, - pub(super) external_inputs: Option, - pub(super) U_i: Option>, - pub(super) Us: Option>>, // other U_i's to be folded that are not the main running instance - pub(super) u_i_C: Option, // u_i.C - pub(super) us: Option>>, // other u_i's to be folded that are not the main incoming instance - pub(super) U_i1_C: Option, // U_{i+1}.C - pub(super) F: FC, // F circuit - pub(super) nimfs_proof: Option>, - - // cyclefold verifier on C1 - pub(super) cf_u_i_cmW: Option, // input, cf_u_i.cmW - pub(super) cf_U_i: Option>, // input, RelaxedR1CS CycleFold instance - pub(super) cf_cmT: Option, -} - -impl AugmentedFCircuit -where - C1: Curve, - C2: Curve, - FC: FCircuit>, -{ - pub fn default( - poseidon_config: &PoseidonConfig>, - F_circuit: FC, - ccs: CCS, - ) -> Result { - if MU < 1 || NU < 1 { - return Err(Error::CantBeZero("mu,nu".to_string())); - } - Ok(Self { - poseidon_config: poseidon_config.clone(), - ccs, - pp_hash: None, - i: None, - i_usize: None, - z_0: None, - z_i: None, - external_inputs: None, - U_i: None, - Us: None, - u_i_C: None, - us: None, - U_i1_C: None, - F: F_circuit, - nimfs_proof: None, - cf_u_i_cmW: None, - cf_U_i: None, - cf_cmT: None, - }) - } - - pub fn empty( - poseidon_config: &PoseidonConfig>, - F: FC, // FCircuit - ccs: Option>, - ) -> Result { - // create the initial ccs by converting from a dummy r1cs with m = 0, - // n = 0, and l = 2 (i.e., 0 constraints, and 0 variables, and 2 public - // inputs). - // Here, `m` and `n` will be overwritten by the `compute_concrete_ccs` - // method. - let mut initial_ccs = CCS::from(R1CS::dummy((0, 0, 2))); - // Although `s = log(m)` is undefined for `m = 0`, we set it to 1 here - // because the circuit internally calls `IOPSumCheck::extract_sum` which - // will panic if `s = 0` (0 is arkworks' fallback value for `log(0)`). - // Similarly, `s` will also be overwritten by `compute_concrete_ccs`. - initial_ccs.s = 1; - let mut augmented_f_circuit = Self::default(poseidon_config, F, initial_ccs)?; - augmented_f_circuit.ccs = ccs - .ok_or(()) - .or_else(|_| augmented_f_circuit.compute_concrete_ccs())?; - Ok(augmented_f_circuit) - } - - /// This method computes the CCS parameters. This is used because there is a circular - /// dependency between the AugmentedFCircuit CCS and the CCS parameters m & n & s. - /// For a stable FCircuit circuit, the CCS parameters can be computed in advance and can be - /// feed in as parameter for the AugmentedFCircuit::empty method to avoid computing them there. - pub fn compute_concrete_ccs(&self) -> Result, Error> { - let r1cs = get_r1cs_from_cs::>(self.clone())?; - let mut ccs = CCS::from(r1cs); - - let z_0 = vec![C1::ScalarField::zero(); self.F.state_len()]; - let mut W_i = Witness::::dummy(&ccs); - let mut U_i = LCCCS::::dummy(&ccs); - let mut w_i = W_i.clone(); - let mut u_i = CCCS::::dummy(&ccs); - - let n_iters = 2; - for _ in 0..n_iters { - let Us = vec![U_i.clone(); MU - 1]; - let Ws = vec![W_i.clone(); MU - 1]; - let us = vec![u_i.clone(); NU - 1]; - let ws = vec![w_i.clone(); NU - 1]; - - let all_Us = [vec![U_i.clone()], Us.clone()].concat(); - let all_us = [vec![u_i.clone()], us.clone()].concat(); - let all_Ws = [vec![W_i.clone()], Ws].concat(); - let all_ws = [vec![w_i.clone()], ws].concat(); - - let mut transcript_p = PoseidonSponge::new_with_pp_hash( - &self.poseidon_config.clone(), - C1::ScalarField::zero(), - ); - let (nimfs_proof, U_i1, _, _) = NIMFS::>::prove( - &mut transcript_p, - &ccs, - &all_Us, - &all_us, - &all_Ws, - &all_ws, - )?; - - let augmented_f_circuit = Self { - poseidon_config: self.poseidon_config.clone(), - ccs: ccs.clone(), - pp_hash: Some(C1::ScalarField::zero()), - i: Some(C1::ScalarField::zero()), - i_usize: Some(0), - z_0: Some(z_0.clone()), - z_i: Some(z_0.clone()), - external_inputs: Some(FC::ExternalInputs::default()), - U_i: Some(U_i.clone()), - Us: Some(Us), - u_i_C: Some(u_i.C), - us: Some(us), - U_i1_C: Some(U_i1.C), - F: self.F.clone(), - nimfs_proof: Some(nimfs_proof), - // cyclefold values - cf_u_i_cmW: None, - cf_U_i: None, - cf_cmT: None, - }; - - ccs = augmented_f_circuit.compute_ccs()?; - // prepare instances for next loop iteration - u_i = CCCS::::dummy(&ccs); - w_i = Witness::::dummy(&ccs); - W_i = Witness::::dummy(&ccs); - U_i = LCCCS::::dummy(&ccs); - } - Ok(ccs) - } - - /// Returns the CCS out of the AugmentedFCircuit. - /// Notice that in order to be able to internally call the `extract_r1cs` function, this method - /// calls the `cs.finalize` method which consumes a noticeable portion of the time. If the CCS - /// is not needed, directly generate the ConstraintSystem without calling the `finalize` method - /// will save computing time. - #[allow(clippy::type_complexity)] - pub fn compute_ccs(&self) -> Result, Error> { - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - self.clone().generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - let ccs = CCS::from(r1cs); - - Ok(ccs) - } -} - -impl AugmentedFCircuit -where - C1: Curve, - C2: Curve, - FC: FCircuit>, -{ - pub fn compute_next_state( - self, - cs: ConstraintSystemRef>, - ) -> Result>>, SynthesisError> { - let pp_hash = FpVar::>::new_witness(cs.clone(), || { - Ok(self.pp_hash.unwrap_or_else(CF1::::zero)) - })?; - let i = FpVar::>::new_witness(cs.clone(), || { - Ok(self.i.unwrap_or_else(CF1::::zero)) - })?; - let z_0 = Vec::>>::new_witness(cs.clone(), || { - Ok(self - .z_0 - .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) - })?; - let z_i = Vec::>>::new_witness(cs.clone(), || { - Ok(self - .z_i - .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) - })?; - let external_inputs = FC::ExternalInputsVar::new_witness(cs.clone(), || { - Ok(self.external_inputs.unwrap_or_default()) - })?; - - let U_dummy = LCCCS::::dummy(&self.ccs); - let u_dummy = CCCS::::dummy(&self.ccs); - - let U_i = - LCCCSVar::::new_witness(cs.clone(), || Ok(self.U_i.unwrap_or(U_dummy.clone())))?; - let Us = Vec::>::new_witness(cs.clone(), || { - Ok(self.Us.unwrap_or(vec![U_dummy.clone(); MU - 1])) - })?; - let us = Vec::>::new_witness(cs.clone(), || { - Ok(self.us.unwrap_or(vec![u_dummy.clone(); NU - 1])) - })?; - let U_i1_C = NonNativeAffineVar::new_witness(cs.clone(), || { - Ok(self.U_i1_C.unwrap_or_else(C1::zero)) - })?; - let nimfs_proof_dummy = NIMFSProof::::dummy((&self.ccs, MU, NU)); - let nimfs_proof = ProofVar::::new_witness(cs.clone(), || { - Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy)) - })?; - - let cf_u_dummy = - CycleFoldCommittedInstance::dummy(HyperNovaCycleFoldConfig::::IO_LEN); - let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) - })?; - let cf_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf_cmT.unwrap_or_else(C2::zero)))?; - - let sponge = PoseidonSpongeVar::::new_with_pp_hash( - &self.poseidon_config, - &pp_hash, - )?; - let mut transcript = sponge.clone(); - - let is_basecase = i.is_zero()?; - let is_not_basecase = !&is_basecase; - - // Primary Part - // P.1. Compute u_i.x - // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i.clone().hash(&sponge, &i, &z_0, &z_i)?; - // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge)?; - - // P.2. Construct u_i - let u_i = CCCSVar:: { - // u_i.C is provided by the prover as witness - C: NonNativeAffineVar::::new_witness(cs.clone(), || { - Ok(self.u_i_C.unwrap_or(C1::zero())) - })?, - // u_i.x is computed in step 1 - x: vec![u_i_x, cf_u_i_x], - }; - - let all_Us = [vec![U_i.clone()], Us].concat(); - let all_us = [vec![u_i.clone()], us].concat(); - - // P.3. NIMFS.verify, obtains U_{i+1} by folding [U_i] & [u_i]. - // Notice that NIMFSGadget::fold_committed_instance does not fold C. We set `U_i1.C` to - // unconstrained witnesses `U_i1_C` respectively. Its correctness will be checked on the - // other curve. - let (mut U_i1, rho_bits) = NIMFSGadget::::verify( - cs.clone(), - &self.ccs.clone(), - &mut transcript, - &all_Us, - &all_us, - nimfs_proof, - is_not_basecase.clone(), - )?; - U_i1.C = U_i1_C; - - // P.4.a compute and check the first output of F' - - // get z_{i+1} from the F circuit - let i_usize = self.i_usize.unwrap_or(0); - let z_i1 = self - .F - .generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?; - - let (u_i1_x, _) = - U_i1.clone() - .hash(&sponge, &(i + FpVar::>::one()), &z_0, &z_i1)?; - let (u_i1_x_base, _) = LCCCSVar::new_constant(cs.clone(), U_dummy)?.hash( - &sponge, - &FpVar::>::one(), - &z_0, - &z_i1, - )?; - let x = is_basecase.select(&u_i1_x_base, &u_i1_x)?; - // This line "converts" `x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `x`. - // While comparing `x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `x` computed in-circuit. - FpVar::new_input(cs.clone(), || x.value())?.enforce_equal(&x)?; - - // CycleFold part - // C.1. Compute `cf_u_i.x` - // C.2. Construct `cf_u_i` - let cf_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( - // `cf_u_i.cmW` is provided by the prover as witness. - C2::Var::new_witness(cs.clone(), || Ok(self.cf_u_i_cmW.unwrap_or(C2::zero())))?, - // To construct `cf_u_i.x`, we need to provide the randomness - // `rho_bits` and the `C` component in LCCCS and CCCS instances - // `all_Us`, `all_us` and `U_{i+1}`. - &rho_bits, - all_Us - .into_iter() - .map(|U| U.C) - .chain(all_us.into_iter().map(|u| u.C)) - .chain(vec![U_i1.C]) - .collect(), - )?; - - // C.3. nifs.verify (fold_committed_instance), obtains cf_U_{i+1} by folding cf_u_i & cf_U_i. - let cf_U_i1 = CycleFoldAugmentationGadget::fold_gadget( - &mut transcript, - cf_U_i, - vec![cf_u_i], - vec![cf_cmT], - )?; - - // Back to Primary Part - // P.4.b compute and check the second output of F' - // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) - // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge)?; - let (cf_u_i1_x_base, _) = - CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge)?; - let cf_x = is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?; - // This line "converts" `cf_x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `cf_x`. - // While comparing `cf_x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `cf_x` computed in-circuit. - FpVar::new_input(cs.clone(), || cf_x.value())?.enforce_equal(&cf_x)?; - - Ok(z_i1) - } -} - -impl ConstraintSynthesizer> - for AugmentedFCircuit -where - C1: Curve, - C2: Curve, - FC: FCircuit>, -{ - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - self.compute_next_state(cs).map(|_| ()) - } -} - -#[cfg(test)] -mod tests { - use ark_bn254::{Fq, Fr, G1Projective as Projective}; - use ark_crypto_primitives::sponge::Absorb; - use ark_grumpkin::Projective as Projective2; - use ark_std::{cmp::max, test_rng, time::Instant, UniformRand}; - - use super::*; - use crate::{ - arith::{ - ccs::tests::{get_test_ccs, get_test_z}, - r1cs::extract_w_x, - ArithRelation, - }, - commitment::{pedersen::Pedersen, CommitmentScheme}, - folding::{ - circuits::cyclefold::{CycleFoldCircuit, CycleFoldWitness}, - hypernova::utils::{compute_c, compute_sigmas_thetas}, - traits::CommittedInstanceOps, - }, - frontend::utils::{cubic_step_native, CubicFCircuit}, - transcript::{poseidon::poseidon_canonical_config, Transcript}, - }; - - #[test] - pub fn test_compute_c_gadget() -> Result<(), Error> { - // number of LCCCS & CCCS instances to fold in a single step - let mu = 32; - let nu = 42; - - let mut z_lcccs = Vec::new(); - for i in 0..mu { - let z = get_test_z(i + 3); - z_lcccs.push(z); - } - let mut z_cccs = Vec::new(); - for i in 0..nu { - let z = get_test_z(i + 3); - z_cccs.push(z); - } - - let ccs: CCS = get_test_ccs(); - - let mut rng = test_rng(); - let gamma: Fr = Fr::rand(&mut rng); - let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - // Create the LCCCS instances out of z_lcccs - let mut lcccs_instances = Vec::new(); - for z_i in z_lcccs.iter() { - let (inst, _) = ccs.to_lcccs::<_, _, Pedersen, true>( - &mut rng, - &pedersen_params, - z_i, - )?; - lcccs_instances.push(inst); - } - // Create the CCCS instance out of z_cccs - let mut cccs_instances = Vec::new(); - for z_i in z_cccs.iter() { - let (inst, _) = - ccs.to_cccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, z_i)?; - cccs_instances.push(inst); - } - - let sigmas_thetas = compute_sigmas_thetas(&ccs, &z_lcccs, &z_cccs, &r_x_prime)?; - - let expected_c = compute_c( - &ccs, - &sigmas_thetas, - gamma, - &beta, - &lcccs_instances - .iter() - .map(|lcccs| lcccs.r_x.clone()) - .collect(), - &r_x_prime, - )?; - - let cs = ConstraintSystem::::new_ref(); - let mut vec_sigmas = Vec::new(); - let mut vec_thetas = Vec::new(); - for sigmas in sigmas_thetas.0 { - vec_sigmas.push(Vec::>::new_witness(cs.clone(), || { - Ok(sigmas.clone()) - })?); - } - for thetas in sigmas_thetas.1 { - vec_thetas.push(Vec::>::new_witness(cs.clone(), || { - Ok(thetas.clone()) - })?); - } - let vec_r_x: Vec>> = lcccs_instances - .iter() - .map(|lcccs| Vec::>::new_witness(cs.clone(), || Ok(lcccs.r_x.clone()))) - .collect::, _>>()?; - let vec_r_x_prime = Vec::>::new_witness(cs.clone(), || Ok(r_x_prime.clone()))?; - let gamma_var = FpVar::::new_witness(cs.clone(), || Ok(gamma))?; - let beta_var = Vec::>::new_witness(cs.clone(), || Ok(beta.clone()))?; - - let computed_c = compute_c_gadget( - &ccs, - vec_sigmas, - vec_thetas, - gamma_var, - beta_var, - vec_r_x, - vec_r_x_prime, - )?; - - assert_eq!(expected_c, computed_c.value()?); - Ok(()) - } - - /// Test that generates mu>1 and nu>1 instances, and folds them in a single multifolding step, - /// to verify the folding in the NIMFSGadget circuit - #[test] - pub fn test_nimfs_gadget_verify() -> Result<(), Error> { - let mut rng = test_rng(); - - // Create a basic CCS circuit - let ccs = get_test_ccs::(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let mu = 32; - let nu = 42; - - // Generate a mu LCCCS & nu CCCS satisfying witness - let mut z_lcccs = Vec::new(); - for i in 0..mu { - let z = get_test_z(i + 3); - z_lcccs.push(z); - } - let mut z_cccs = Vec::new(); - for i in 0..nu { - let z = get_test_z(nu + i + 3); - z_cccs.push(z); - } - - // Create the LCCCS instances out of z_lcccs - let mut lcccs_instances = Vec::new(); - let mut w_lcccs = Vec::new(); - for z_i in z_lcccs.iter() { - let (running_instance, w) = ccs.to_lcccs::<_, _, Pedersen, false>( - &mut rng, - &pedersen_params, - z_i, - )?; - lcccs_instances.push(running_instance); - w_lcccs.push(w); - } - // Create the CCCS instance out of z_cccs - let mut cccs_instances = Vec::new(); - let mut w_cccs = Vec::new(); - for z_i in z_cccs.iter() { - let (new_instance, w) = - ccs.to_cccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, z_i)?; - cccs_instances.push(new_instance); - w_cccs.push(w); - } - - // Prover's transcript - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from(42u32); // only for test - let mut transcript_p: PoseidonSponge = - PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - // Verifier's transcript - let mut transcript_v: PoseidonSponge = transcript_p.clone(); - - // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness, _) = - NIMFS::>::prove( - &mut transcript_p, - &ccs, - &lcccs_instances, - &cccs_instances, - &w_lcccs, - &w_cccs, - )?; - - // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::>::verify( - &mut transcript_v, - &ccs, - &lcccs_instances, - &cccs_instances, - proof.clone(), - )?; - assert_eq!(folded_lcccs, folded_lcccs_v); - - // Check that the folded LCCCS instance is a valid instance with respect to the folded witness - ccs.check_relation(&folded_witness, &folded_lcccs)?; - - // allocate circuit inputs - let cs = ConstraintSystem::::new_ref(); - let lcccs_instancesVar = - Vec::>::new_witness(cs.clone(), || Ok(lcccs_instances.clone()))?; - let cccs_instancesVar = - Vec::>::new_witness(cs.clone(), || Ok(cccs_instances.clone()))?; - let proofVar = ProofVar::::new_witness(cs.clone(), || Ok(proof.clone()))?; - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let mut transcriptVar = - PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; - - let enabled = Boolean::::new_witness(cs.clone(), || Ok(true))?; - let (folded_lcccsVar, _) = NIMFSGadget::::verify( - cs.clone(), - &ccs, - &mut transcriptVar, - &lcccs_instancesVar, - &cccs_instancesVar, - proofVar, - enabled, - )?; - assert!(cs.is_satisfied()?); - assert_eq!(folded_lcccsVar.u.value()?, folded_lcccs.u); - Ok(()) - } - - /// test that checks the native LCCCS.to_sponge_{bytes,field_elements} vs - /// the R1CS constraints version - #[test] - pub fn test_lcccs_to_sponge_preimage() -> Result<(), Error> { - let mut rng = test_rng(); - - let ccs = get_test_ccs(); - let z1 = get_test_z::(3); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let (lcccs, _) = ccs.to_lcccs::<_, _, Pedersen, true>( - &mut rng, - &pedersen_params, - &z1, - )?; - let bytes = lcccs.to_sponge_bytes_as_vec(); - let field_elements = lcccs.to_sponge_field_elements_as_vec(); - - let cs = ConstraintSystem::::new_ref(); - - let lcccsVar = LCCCSVar::::new_witness(cs.clone(), || Ok(lcccs))?; - let bytes_var = lcccsVar.to_sponge_bytes()?; - let field_elements_var = lcccsVar.to_sponge_field_elements()?; - - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(bytes_var.value()?, bytes); - assert_eq!(field_elements_var.value()?, field_elements); - Ok(()) - } - - /// test that checks the native LCCCS.hash vs the R1CS constraints version - #[test] - pub fn test_lcccs_hash() -> Result<(), Error> { - let mut rng = test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from(42u32); // only for test - let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - - let ccs = get_test_ccs(); - let z1 = get_test_z::(3); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let i = Fr::from(3_u32); - let z_0 = vec![Fr::from(3_u32)]; - let z_i = vec![Fr::from(3_u32)]; - let (lcccs, _) = ccs.to_lcccs::<_, _, Pedersen, true>( - &mut rng, - &pedersen_params, - &z1, - )?; - let h = lcccs.clone().hash(&sponge, i, &z_0, &z_i); - - let cs = ConstraintSystem::::new_ref(); - - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let spongeVar = PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; - let iVar = FpVar::::new_witness(cs.clone(), || Ok(i))?; - let z_0Var = Vec::>::new_witness(cs.clone(), || Ok(z_0.clone()))?; - let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone()))?; - let lcccsVar = LCCCSVar::::new_witness(cs.clone(), || Ok(lcccs))?; - let (hVar, _) = lcccsVar.clone().hash(&spongeVar, &iVar, &z_0Var, &z_iVar)?; - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(hVar.value()?, h); - Ok(()) - } - - #[test] - pub fn test_augmented_f_circuit() -> Result<(), Error> { - let mut rng = test_rng(); - let poseidon_config = poseidon_canonical_config::(); - // public params hash - let pp_hash = Fr::from(42u32); // only for test - let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - - const MU: usize = 3; - const NU: usize = 3; - - let start = Instant::now(); - let F_circuit = CubicFCircuit::::new(())?; - let mut augmented_f_circuit = - AugmentedFCircuit::, MU, NU>::empty( - &poseidon_config, - F_circuit, - None, - )?; - let ccs = augmented_f_circuit.ccs.clone(); - println!("AugmentedFCircuit & CCS generation: {:?}", start.elapsed()); - println!("CCS m x n: {} x {}", ccs.n_constraints(), ccs.n_variables()); - - // CycleFold circuit - let cs2 = ConstraintSystem::::new_ref(); - let cf_circuit = - CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); - cf_circuit.generate_constraints(cs2.clone())?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let cf_r1cs = extract_r1cs::(&cs2)?; - println!( - "CF m x n: {} x {}", - cf_r1cs.n_constraints(), - cf_r1cs.n_variables() - ); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - let (cf_pedersen_params, _) = Pedersen::::setup( - &mut rng, - max(cf_r1cs.n_constraints(), cf_r1cs.n_witnesses()), - )?; - - // first step - let z_0 = vec![Fr::from(3_u32)]; - let mut z_i = z_0.clone(); - - // prepare the dummy instances - let W_dummy = Witness::::dummy(&ccs); - let U_dummy = LCCCS::::dummy(&ccs); - let w_dummy = W_dummy.clone(); - let u_dummy = CCCS::::dummy(&ccs); - let (cf_W_dummy, cf_U_dummy): ( - CycleFoldWitness, - CycleFoldCommittedInstance, - ) = cf_r1cs.dummy_witness_instance(); - - // set the initial dummy instances - let mut W_i = W_dummy.clone(); - let mut U_i = U_dummy.clone(); - let mut w_i = w_dummy.clone(); - let mut u_i = u_dummy.clone(); - let mut cf_W_i = cf_W_dummy.clone(); - let mut cf_U_i = cf_U_dummy.clone(); - u_i.x = vec![ - U_i.hash(&sponge, Fr::zero(), &z_0, &z_i), - cf_U_i.hash_cyclefold(&sponge), - ]; - - let n_steps: usize = 4; - let mut iFr = Fr::zero(); - for i in 0..n_steps { - let start = Instant::now(); - - // for this test, let Us & us be just an array of copies of the U_i & u_i respectively - let Us = vec![U_i.clone(); MU - 1]; - let Ws = vec![W_i.clone(); MU - 1]; - let us = vec![u_i.clone(); NU - 1]; - let ws = vec![w_i.clone(); NU - 1]; - let all_Us = [vec![U_i.clone()], Us.clone()].concat(); - let all_us = [vec![u_i.clone()], us.clone()].concat(); - let all_Ws = [vec![W_i.clone()], Ws].concat(); - let all_ws = [vec![w_i.clone()], ws].concat(); - - let z_i1 = cubic_step_native(z_i.clone()); - - let (U_i1, W_i1); - - let u_i1_x; - let cf_u_i1_x; - - if i == 0 { - W_i1 = Witness::::dummy(&ccs); - U_i1 = LCCCS::dummy(&ccs); - - u_i1_x = U_i1.hash(&sponge, Fr::one(), &z_0, &z_i1); - - // hash the initial (dummy) CycleFold instance, which is used as the 2nd public - // input in the AugmentedFCircuit - cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge); - - augmented_f_circuit = - AugmentedFCircuit::, MU, NU> { - poseidon_config: poseidon_config.clone(), - ccs: ccs.clone(), - pp_hash: Some(pp_hash), - i: Some(Fr::zero()), - i_usize: Some(0), - z_0: Some(z_0.clone()), - z_i: Some(z_i.clone()), - external_inputs: Some(()), - U_i: Some(U_i.clone()), - Us: Some(Us.clone()), - u_i_C: Some(u_i.C), - us: Some(us.clone()), - U_i1_C: Some(U_i1.C), - F: F_circuit, - nimfs_proof: None, - - // cyclefold values - cf_u_i_cmW: None, - cf_U_i: None, - cf_cmT: None, - }; - } else { - let mut transcript_p: PoseidonSponge = sponge.clone(); - let (rho, nimfs_proof); - (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( - &mut transcript_p, - &ccs, - &all_Us, - &all_us, - &all_Ws, - &all_ws, - )?; - - // sanity check: check the folded instance relation - ccs.check_relation(&W_i1, &U_i1)?; - - u_i1_x = U_i1.hash(&sponge, iFr + Fr::one(), &z_0, &z_i1); - - // CycleFold part: - let cf_config = HyperNovaCycleFoldConfig:: { - r: rho, - points: [ - vec![U_i.clone().C], - Us.iter().map(|Us_i| Us_i.C).collect(), - vec![u_i.clone().C], - us.iter().map(|us_i| us_i.C).collect(), - ] - .concat(), - }; - - // ensure that the CycleFoldCircuit is well defined - assert_eq!( - cf_config.points.len(), - HyperNovaCycleFoldConfig::::N_INPUT_POINTS - ); - - let (cf_w_i, cf_u_i) = cf_config - .build_circuit() - .generate_incoming_instance_witness::<_, Pedersen<_>, false>( - &cf_pedersen_params, - &mut rng, - )?; - let (cf_W_i1, cf_U_i1, cf_cmTs) = - CycleFoldAugmentationGadget::fold_native::<_, Pedersen<_>, false>( - &mut transcript_p, - &cf_r1cs, - &cf_pedersen_params, - cf_W_i, - cf_U_i.clone(), - vec![cf_w_i], - vec![cf_u_i.clone()], - )?; - - // hash the CycleFold folded instance, which is used as the 2nd public input in the - // AugmentedFCircuit - cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge); - - augmented_f_circuit = - AugmentedFCircuit::, MU, NU> { - poseidon_config: poseidon_config.clone(), - ccs: ccs.clone(), - pp_hash: Some(pp_hash), - i: Some(iFr), - i_usize: Some(i), - z_0: Some(z_0.clone()), - z_i: Some(z_i.clone()), - external_inputs: Some(()), - U_i: Some(U_i.clone()), - Us: Some(Us.clone()), - u_i_C: Some(u_i.C), - us: Some(us.clone()), - U_i1_C: Some(U_i1.C), - F: F_circuit, - nimfs_proof: Some(nimfs_proof), - - // cyclefold values - cf_u_i_cmW: Some(cf_u_i.cmW), - cf_U_i: Some(cf_U_i), - cf_cmT: Some(cf_cmTs[0]), - }; - - // assign the next round instances - cf_W_i = cf_W_i1; - cf_U_i = cf_U_i1; - } - - let cs = ConstraintSystem::::new_ref(); - augmented_f_circuit - .clone() - .generate_constraints(cs.clone())?; - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - assert!(cs.is_satisfied()?); - - let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::(&cs); // includes 1 and public inputs - assert_eq!(r1cs_x_i1[0], u_i1_x); - let r1cs_z = [vec![Fr::one()], r1cs_x_i1.clone(), r1cs_w_i1.clone()].concat(); - // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we - // assign them directly to w_i, u_i. - (u_i, w_i) = ccs.to_cccs::<_, _, Pedersen, false>( - &mut rng, - &pedersen_params, - &r1cs_z, - )?; - ccs.check_relation(&w_i, &u_i)?; - - // sanity checks - assert_eq!(w_i.w, r1cs_w_i1); - assert_eq!(u_i.x, r1cs_x_i1); - assert_eq!(u_i.x[0], u_i1_x); - assert_eq!(u_i.x[1], cf_u_i1_x); - let expected_u_i1_x = U_i1.hash(&sponge, iFr + Fr::one(), &z_0, &z_i1); - let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&sponge); - // u_i is already u_i1 at this point, check that has the expected value at x[0] - assert_eq!(u_i.x[0], expected_u_i1_x); - assert_eq!(u_i.x[1], expected_cf_U_i1_x); - - // set values for next iteration - iFr += Fr::one(); - // assign z_{i+1} into z_i - z_i = z_i1.clone(); - U_i = U_i1.clone(); - W_i = W_i1.clone(); - - // check the new LCCCS instance relation - ccs.check_relation(&W_i, &U_i)?; - // check the new CCCS instance relation - ccs.check_relation(&w_i, &u_i)?; - - // check the CycleFold instance relation - cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?; - - println!("augmented_f_circuit step {}: {:?}", i, start.elapsed()); - } - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/decider_eth.rs b/folding-schemes/src/folding/hypernova/decider_eth.rs deleted file mode 100644 index 8389a1cdc..000000000 --- a/folding-schemes/src/folding/hypernova/decider_eth.rs +++ /dev/null @@ -1,441 +0,0 @@ -/// This file implements the HyperNova's onchain (Ethereum's EVM) decider. -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_snark::SNARK; -use ark_std::rand::{CryptoRng, RngCore}; -use ark_std::{One, Zero}; -use core::marker::PhantomData; - -pub use super::decider_eth_circuit::DeciderEthCircuit; -use super::decider_eth_circuit::DeciderHyperNovaGadget; -use super::HyperNova; -use crate::commitment::{ - kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme, -}; -use crate::folding::circuits::decider::DeciderEnabledNIFS; -use crate::folding::nova::decider_eth::VerifierParam; -use crate::folding::traits::{Dummy, WitnessOps}; -use crate::frontend::FCircuit; -use crate::{Curve, Error}; -use crate::{Decider as DeciderTrait, FoldingScheme}; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Proof -where - C1: Curve, - CS1: CommitmentScheme, - S: SNARK, -{ - snark_proof: S::Proof, - kzg_proof: CS1::Proof, - // rho used at the last fold, U_{i+1}=NIMFS.V(rho, U_i, u_i), it is checked in-circuit - rho: C1::ScalarField, - // the KZG challenge is provided by the prover, but in-circuit it is checked to match - // the in-circuit computed computed one. - kzg_challenge: C1::ScalarField, -} - -/// Onchain Decider, for ethereum use cases -#[derive(Clone, Debug)] -pub struct Decider { - _c1: PhantomData, - _c2: PhantomData, - _fc: PhantomData, - _cs1: PhantomData, - _cs2: PhantomData, - _s: PhantomData, - _fs: PhantomData, -} - -impl DeciderTrait - for Decider -where - C1: Curve, - C2: Curve, - FC: FCircuit, - // CS1 is a KZG commitment, where challenge is C1::Fr elem - CS1: CommitmentScheme< - C1, - ProverChallenge = C1::ScalarField, - Challenge = C1::ScalarField, - Proof = KZGProof, - >, - // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider - CS2: CommitmentScheme>, - S: SNARK, - FS: FoldingScheme, - // constrain FS into HyperNova, since this is a Decider specifically for HyperNova - HyperNova: From, - crate::folding::hypernova::ProverParams: - From<>::ProverParam>, - crate::folding::hypernova::VerifierParams: - From<>::VerifierParam>, -{ - type PreprocessorParam = ((FS::ProverParam, FS::VerifierParam), usize); - type ProverParam = (S::ProvingKey, CS1::ProverParams); - type Proof = Proof; - type VerifierParam = VerifierParam; - type PublicInput = Vec; - type CommittedInstance = Vec; - - fn preprocess( - mut rng: impl RngCore + CryptoRng, - ((pp, vp), state_len): Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - // get the FoldingScheme prover & verifier params from HyperNova - let hypernova_pp: as FoldingScheme< - C1, - C2, - FC, - >>::ProverParam = pp.into(); - let hypernova_vp: as FoldingScheme< - C1, - C2, - FC, - >>::VerifierParam = vp.into(); - let pp_hash = hypernova_vp.pp_hash()?; - - let s = hypernova_vp.ccs.s; - let t = hypernova_vp.ccs.t; - - let circuit = DeciderEthCircuit::::dummy(( - hypernova_vp.ccs, - hypernova_vp.cf_r1cs, - hypernova_pp.cf_cs_pp, - hypernova_pp.poseidon_config, - (s, t, MU, NU), - (), - state_len, - 1, // HyperNova's LCCCS contains 1 commitment - )); - - // get the Groth16 specific setup for the circuit - let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng) - .map_err(|e| Error::SNARKSetupFail(e.to_string()))?; - - let pp = (g16_pk, hypernova_pp.cs_pp); - - let vp = Self::VerifierParam { - pp_hash, - snark_vp: g16_vk, - cs_vp: hypernova_vp.cs_vp, - }; - Ok((pp, vp)) - } - - fn prove( - mut rng: impl RngCore + CryptoRng, - pp: Self::ProverParam, - folding_scheme: FS, - ) -> Result { - let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp; - - let circuit = DeciderEthCircuit::::try_from(HyperNova::from(folding_scheme))?; - - let rho = circuit.randomness; - - // get the challenges that have been already computed when preparing the circuit inputs in - // the above `try_from` call - let kzg_challenges = circuit.kzg_challenges.clone(); - - // generate KZG proofs - let kzg_proofs = circuit - .W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| { - CS1::prove_with_challenge(&cs_pk, c, v, &C1::ScalarField::zero(), None) - }) - .collect::, _>>()?; - - let snark_proof = - S::prove(&snark_pk, circuit, &mut rng).map_err(|e| Error::Other(e.to_string()))?; - - Ok(Self::Proof { - snark_proof, - rho, - kzg_proof: (kzg_proofs.len() == 1) - .then(|| kzg_proofs[0].clone()) - .ok_or(Error::NotExpectedLength(kzg_proofs.len(), 1))?, - kzg_challenge: (kzg_challenges.len() == 1) - .then(|| kzg_challenges[0]) - .ok_or(Error::NotExpectedLength(kzg_challenges.len(), 1))?, - }) - } - - fn verify( - vp: Self::VerifierParam, - i: C1::ScalarField, - z_0: Vec, - z_i: Vec, - // we don't use the instances at the verifier level, since we check them in-circuit - running_commitments: &Self::CommittedInstance, - incoming_commitments: &Self::CommittedInstance, - proof: &Self::Proof, - ) -> Result { - if i <= C1::ScalarField::one() { - return Err(Error::NotEnoughSteps); - } - - let Self::VerifierParam { - pp_hash, - snark_vp, - cs_vp, - } = vp; - - // 6.2. Fold the commitments - let C = DeciderHyperNovaGadget::fold_group_elements_native( - running_commitments, - incoming_commitments, - None, - proof.rho, - )?[0]; - - // Note: the NIMFS proof is checked inside the DeciderEthCircuit, which ensures that the - // 'proof.U_i1' is correctly computed - let public_input: Vec = [ - &[pp_hash, i][..], - &z_0, - &z_i, - &C.inputize_nonnative(), - &[proof.kzg_challenge, proof.kzg_proof.eval, proof.rho], - ] - .concat(); - - let snark_v = S::verify(&snark_vp, &public_input, &proof.snark_proof) - .map_err(|e| Error::Other(e.to_string()))?; - if !snark_v { - return Err(Error::SNARKVerificationFail); - } - - // 7.3. Verify the KZG proof - // we're at the Ethereum EVM case, so the CS1 is KZG commitments - CS1::verify_with_challenge(&cs_vp, proof.kzg_challenge, &C, &proof.kzg_proof)?; - - Ok(true) - } -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::{Bn254, Fr, G1Projective as Projective}; - use ark_groth16::Groth16; - use ark_grumpkin::Projective as Projective2; - use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate}; - - use super::*; - use crate::commitment::{kzg::KZG, pedersen::Pedersen}; - use crate::folding::hypernova::cccs::CCCS; - use crate::folding::hypernova::lcccs::LCCCS; - use crate::folding::hypernova::PreprocessorParam; - use crate::folding::traits::CommittedInstanceOps; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_decider() -> Result<(), Error> { - const MU: usize = 1; - const NU: usize = 1; - // use HyperNova as FoldingScheme - type HN = HyperNova< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - MU, - NU, - false, - >; - type D = Decider< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, // here we define the Snark to use in the decider - HN, // here we define the FoldingScheme to use - MU, - NU, - >; - - let mut rng = rand::rngs::OsRng; - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - let prep_param = PreprocessorParam::new(poseidon_config, F_circuit); - let hypernova_params = HN::preprocess(&mut rng, &prep_param)?; - - let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone())?; - hypernova.prove_step(&mut rng, (), Some((vec![], vec![])))?; - hypernova.prove_step(&mut rng, (), Some((vec![], vec![])))?; // do a 2nd step - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (hypernova_params, F_circuit.state_len()))?; - - // decider proof generation - let proof = D::prove(rng, decider_pp, hypernova.clone())?; - - // decider proof verification - let verified = D::verify( - decider_vp, - hypernova.i, - hypernova.z_0, - hypernova.z_i, - &hypernova.U_i.get_commitments(), - &hypernova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - Ok(()) - } - - #[test] - fn test_decider_serialization() -> Result<(), Error> { - const MU: usize = 1; - const NU: usize = 1; - // use HyperNova as FoldingScheme - type HN = HyperNova< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - MU, - NU, - false, - >; - type D = Decider< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, // here we define the Snark to use in the decider - HN, // here we define the FoldingScheme to use - MU, - NU, - >; - - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - let prep_param = PreprocessorParam::new(poseidon_config.clone(), F_circuit); - let hypernova_params = HN::preprocess(&mut rng, &prep_param)?; - - let mut rng = rand::rngs::OsRng; - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (hypernova_params.clone(), F_circuit.state_len()))?; - - let mut hypernova_pp_serialized = vec![]; - hypernova_params - .0 - .clone() - .serialize_compressed(&mut hypernova_pp_serialized)?; - let mut hypernova_vp_serialized = vec![]; - hypernova_params - .1 - .clone() - .serialize_compressed(&mut hypernova_vp_serialized)?; - - let hypernova_pp_deserialized = HN::pp_deserialize_with_mode( - hypernova_pp_serialized.as_slice(), - Compress::Yes, - Validate::No, - (), // FCircuit's Params - )?; - - let hypernova_vp_deserialized = HN::vp_deserialize_with_mode( - hypernova_vp_serialized.as_slice(), - Compress::Yes, - Validate::No, - (), // FCircuit's Params - )?; - - let hypernova_params = (hypernova_pp_deserialized, hypernova_vp_deserialized); - let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone())?; - - hypernova.prove_step(&mut rng, (), Some((vec![], vec![])))?; - hypernova.prove_step(&mut rng, (), Some((vec![], vec![])))?; - - // decider proof generation - let proof = D::prove(rng, decider_pp, hypernova.clone())?; - - let verified = D::verify( - decider_vp.clone(), - hypernova.i, - hypernova.z_0.clone(), - hypernova.z_i.clone(), - &hypernova.U_i.get_commitments(), - &hypernova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - - // The rest of this test will serialize the data and deserialize it back, and use it to - // verify the proof: - - // serialize the verifier_params, proof and public inputs - let mut decider_vp_serialized = vec![]; - decider_vp.serialize_compressed(&mut decider_vp_serialized)?; - let mut proof_serialized = vec![]; - proof.serialize_compressed(&mut proof_serialized)?; - // serialize the public inputs in a single packet - let mut public_inputs_serialized = vec![]; - hypernova - .i - .serialize_compressed(&mut public_inputs_serialized)?; - hypernova - .z_0 - .serialize_compressed(&mut public_inputs_serialized)?; - hypernova - .z_i - .serialize_compressed(&mut public_inputs_serialized)?; - hypernova - .U_i - .serialize_compressed(&mut public_inputs_serialized)?; - hypernova - .u_i - .serialize_compressed(&mut public_inputs_serialized)?; - - // deserialize back the verifier_params, proof and public inputs - let decider_vp_deserialized = - VerifierParam::< - Projective, - as CommitmentScheme>::VerifierParams, - as SNARK>::VerifyingKey, - >::deserialize_compressed(&mut decider_vp_serialized.as_slice())?; - - let proof_deserialized = - Proof::, Groth16>::deserialize_compressed( - &mut proof_serialized.as_slice(), - )?; - - let mut reader = public_inputs_serialized.as_slice(); - let i_deserialized = Fr::deserialize_compressed(&mut reader)?; - let z_0_deserialized = Vec::::deserialize_compressed(&mut reader)?; - let z_i_deserialized = Vec::::deserialize_compressed(&mut reader)?; - let _U_i = LCCCS::::deserialize_compressed(&mut reader)?; - let _u_i = CCCS::::deserialize_compressed(&mut reader)?; - - let verified = D::verify( - decider_vp_deserialized, - i_deserialized, - z_0_deserialized.clone(), - z_i_deserialized.clone(), - &hypernova.U_i.get_commitments(), - &hypernova.u_i.get_commitments(), - &proof_deserialized, - )?; - assert!(verified); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs deleted file mode 100644 index 218a062d3..000000000 --- a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs +++ /dev/null @@ -1,313 +0,0 @@ -/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, -/// other more efficient approaches can be used. -use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - eq::EqGadget, - fields::fp::FpVar, - GR1CSVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, log2, marker::PhantomData}; - -use super::{ - circuits::{CCCSVar, LCCCSVar, NIMFSGadget, ProofVar as NIMFSProofVar}, - nimfs::{NIMFSProof, NIMFS}, - HyperNova, Witness, CCCS, LCCCS, -}; -use crate::arith::{ - ccs::{circuits::CCSMatricesVar, CCS}, - ArithRelationGadget, -}; -use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; -use crate::folding::circuits::{ - decider::{ - on_chain::GenericOnchainDeciderCircuit, DeciderEnabledNIFS, EvalGadget, KZGChallengesGadget, - }, - CF1, -}; -use crate::folding::traits::{WitnessOps, WitnessVarOps}; -use crate::frontend::FCircuit; -use crate::transcript::Transcript; -use crate::utils::gadgets::{eval_mle, MatrixGadget}; -use crate::{Curve, Error}; - -impl ArithRelationGadget>, LCCCSVar> for CCSMatricesVar> { - type Evaluation = Vec>>; - - fn eval_relation( - &self, - w: &WitnessVar>, - u: &LCCCSVar, - ) -> Result { - let z = [&[u.u.clone()][..], &u.x, &w.w].concat(); - - self.M - .iter() - .map(|M_j| { - let s = log2(M_j.n_rows) as usize; - let Mz = M_j.mul_vector(&z)?; - Ok(eval_mle(s, Mz, u.r_x.clone())) - }) - .collect() - } - - fn enforce_evaluation( - _w: &WitnessVar>, - u: &LCCCSVar, - v: Self::Evaluation, - ) -> Result<(), SynthesisError> { - v.enforce_equal(&u.v) - } -} - -/// In-circuit representation of the Witness associated to the CommittedInstance. -#[derive(Debug, Clone)] -pub struct WitnessVar { - pub w: Vec>, - pub r_w: FpVar, -} - -impl AllocVar, F> for WitnessVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let w: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().w.clone()), mode)?; - let r_w = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?; - - Ok(Self { w, r_w }) - }) - } -} - -impl WitnessVarOps for WitnessVar { - fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { - vec![(&self.w, self.r_w.clone())] - } -} - -pub type DeciderEthCircuit = GenericOnchainDeciderCircuit< - C1, - C2, - LCCCS, - CCCS, - Witness>, - CCS>, - CCSMatricesVar>, - DeciderHyperNovaGadget, ->; - -impl< - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider - CS2: CommitmentScheme>, - const MU: usize, - const NU: usize, - const H: bool, - > TryFrom> for DeciderEthCircuit -{ - type Error = Error; - - fn try_from(hn: HyperNova) -> Result { - // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances - let mut transcript = PoseidonSponge::new_with_pp_hash(&hn.poseidon_config, hn.pp_hash); - let (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( - &mut transcript, - &hn.ccs, - &[hn.U_i.clone()], - &[hn.u_i.clone()], - &[hn.W_i.clone()], - &[hn.w_i.clone()], - )?; - - // compute the KZG challenges used as inputs in the circuit - let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1); - - // get KZG evals - let kzg_evaluations = W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c)) - .collect::, _>>()?; - - Ok(Self { - _avar: PhantomData, - arith: hn.ccs, - cf_arith: hn.cf_r1cs, - cf_pedersen_params: hn.cf_cs_pp, - poseidon_config: hn.poseidon_config, - pp_hash: hn.pp_hash, - i: hn.i, - z_0: hn.z_0, - z_i: hn.z_i, - U_i: hn.U_i, - W_i: hn.W_i, - u_i: hn.u_i, - w_i: hn.w_i, - U_i1, - W_i1, - proof: nimfs_proof, - randomness: rho, - cf_U_i: hn.cf_U_i, - cf_W_i: hn.cf_W_i, - kzg_challenges, - kzg_evaluations, - }) - } -} - -pub struct DeciderHyperNovaGadget; - -impl DeciderEnabledNIFS, CCCS, Witness, CCS>> - for DeciderHyperNovaGadget -{ - type ProofDummyCfg = (usize, usize, usize, usize); - type Proof = NIMFSProof; - type Randomness = CF1; - type RandomnessDummyCfg = (); - - fn fold_field_elements_gadget( - arith: &CCS>, - transcript: &mut PoseidonSpongeVar>, - U: LCCCSVar, - _U_vec: Vec>>, - u: CCCSVar, - proof: Self::Proof, - randomness: Self::Randomness, - ) -> Result, SynthesisError> { - let cs = U.u.cs(); - let nimfs_proof = NIMFSProofVar::::new_witness(cs.clone(), || Ok(proof))?; - let rho = FpVar::>::new_input(cs.clone(), || Ok(randomness))?; - let (computed_U_i1, rho_bits) = NIMFSGadget::::verify( - cs.clone(), - arith, - transcript, - &[U], - &[u], - nimfs_proof, - Boolean::TRUE, // enabled - )?; - Boolean::le_bits_to_fp(&rho_bits)?.enforce_equal(&rho)?; - Ok(computed_U_i1) - } - - fn fold_group_elements_native( - U_commitments: &[C], - u_commitments: &[C], - _: Option, - r: Self::Randomness, - ) -> Result, Error> { - let U_C = U_commitments[0]; - let u_C = u_commitments[0]; - let C = U_C + u_C.mul(r); - Ok(vec![C]) - } -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::{Fr, G1Projective as Projective}; - use ark_grumpkin::Projective as Projective2; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - use ark_std::{test_rng, UniformRand}; - - use super::*; - use crate::arith::{r1cs::R1CS, Arith}; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::PreprocessorParam; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::FoldingScheme; - - #[test] - fn test_lcccs_checker_gadget() -> Result<(), Error> { - let mut rng = test_rng(); - let n_rows = 2_u32.pow(5) as usize; - let n_cols = 2_u32.pow(5) as usize; - let r1cs = R1CS::::rand(&mut rng, n_rows, n_cols); - let ccs = CCS::from(r1cs); - let z: Vec = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect(); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let (lcccs, w) = ccs.to_lcccs::<_, Projective, Pedersen, false>( - &mut rng, - &pedersen_params, - &z, - )?; - - let cs = ConstraintSystem::::new_ref(); - - // CCS's (sparse) matrices are constants in the circuit - let ccs_mat = CCSMatricesVar::::new_constant(cs.clone(), ccs.clone())?; - let w_var = WitnessVar::new_witness(cs.clone(), || Ok(w))?; - let lcccs_var = LCCCSVar::new_input(cs.clone(), || Ok(lcccs))?; - - ccs_mat.enforce_relation(&w_var, &lcccs_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_decider_circuit() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - const MU: usize = 1; - const NU: usize = 1; - - type HN = HyperNova< - Projective, - Projective2, - CubicFCircuit, - Pedersen, - Pedersen, - MU, - NU, - false, - >; - let prep_param = PreprocessorParam::< - Projective, - Projective2, - CubicFCircuit, - Pedersen, - Pedersen, - false, - >::new(poseidon_config, F_circuit); - let hn_params = HN::preprocess(&mut rng, &prep_param)?; - - // generate a Nova instance and do a step of it - let mut hypernova = HN::init(&hn_params, F_circuit, z_0.clone())?; - hypernova.prove_step(&mut rng, (), None)?; - - let ivc_proof = hypernova.ivc_proof(); - HN::verify(hn_params.1, ivc_proof)?; - - // load the DeciderEthCircuit from the generated Nova instance - let decider_circuit = DeciderEthCircuit::::try_from(hypernova)?; - - let cs = ConstraintSystem::::new_ref(); - - // generate the constraints and check that are satisfied by the inputs - decider_circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - dbg!(cs.num_constraints()); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/lcccs.rs b/folding-schemes/src/folding/hypernova/lcccs.rs deleted file mode 100644 index 1336cd495..000000000 --- a/folding-schemes/src/folding/hypernova/lcccs.rs +++ /dev/null @@ -1,281 +0,0 @@ -use ark_crypto_primitives::sponge::Absorb; -use ark_ff::PrimeField; -use ark_poly::Polynomial; -use ark_serialize::CanonicalDeserialize; -use ark_serialize::CanonicalSerialize; -use ark_std::rand::Rng; -use ark_std::Zero; - -use super::circuits::LCCCSVar; -use super::Witness; -use crate::arith::ccs::CCS; -use crate::arith::{Arith, ArithRelation}; -use crate::commitment::CommitmentScheme; -use crate::folding::circuits::CF1; -use crate::folding::traits::Inputize; -use crate::folding::traits::{CommittedInstanceOps, Dummy}; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::utils::vec::mat_vec_mul; -use crate::{Curve, Error}; - -/// Linearized Committed CCS instance -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct LCCCS { - // Commitment to witness - pub C: C, - // Relaxation factor of z for folded LCCCS - pub u: C::ScalarField, - // Public input/output - pub x: Vec, - // Random evaluation point for the v_i - pub r_x: Vec, - // Vector of v_i - pub v: Vec, -} - -impl CCS { - pub fn to_lcccs, const H: bool>( - &self, - rng: &mut R, - cs_params: &CS::ProverParams, - z: &[F], - ) -> Result<(LCCCS, Witness), Error> - where - // enforce that CCS's F is the C::ScalarField - C: Curve, - { - let (w, x) = self.split_z(z); - // if the commitment scheme is set to be hiding, set the random blinding parameter - let r_w = if CS::is_hiding() { - F::rand(rng) - } else { - F::zero() - }; - let C = CS::commit(cs_params, &w, &r_w)?; - - let r_x: Vec = (0..self.s).map(|_| F::rand(rng)).collect(); - - // compute v_j - let v = self - .M - .iter() - .map(|M_j| { - let Mz = dense_vec_to_dense_mle(self.s, &mat_vec_mul(M_j, z)?); - Ok(Mz.evaluate(&r_x)) - }) - .collect::>()?; - - Ok(( - LCCCS:: { - C, - u: z[0], - x, - r_x, - v, - }, - Witness:: { w, r_w }, - )) - } -} - -impl Dummy<&CCS>> for LCCCS { - fn dummy(ccs: &CCS>) -> Self { - Self { - C: C::zero(), - u: CF1::::zero(), - x: vec![CF1::::zero(); ccs.n_public_inputs()], - r_x: vec![CF1::::zero(); ccs.s], - v: vec![CF1::::zero(); ccs.t], - } - } -} - -impl ArithRelation>, LCCCS> for CCS> { - type Evaluation = Vec>; - - /// Perform the check of the LCCCS instance described at section 4.2, - /// notice that this method does not check the commitment correctness - fn eval_relation(&self, w: &Witness>, u: &LCCCS) -> Result { - let z = [&[u.u][..], &u.x, &w.w].concat(); - - self.M - .iter() - .map(|M_j| { - let Mz_mle = dense_vec_to_dense_mle(self.s, &mat_vec_mul(M_j, &z)?); - Ok(Mz_mle.evaluate(&u.r_x)) - }) - .collect() - } - - fn check_evaluation( - _w: &Witness>, - u: &LCCCS, - e: Self::Evaluation, - ) -> Result<(), Error> { - (u.v == e).then_some(()).ok_or(Error::NotSatisfied) - } -} - -impl Absorb for LCCCS { - fn to_sponge_bytes(&self, dest: &mut Vec) { - C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.C.to_native_sponge_field_elements(dest); - self.u.to_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - self.r_x.to_sponge_field_elements(dest); - self.v.to_sponge_field_elements(dest); - } -} - -impl CommittedInstanceOps for LCCCS { - type Var = LCCCSVar; - - fn get_commitments(&self) -> Vec { - vec![self.C] - } - - fn is_incoming(&self) -> bool { - false - } -} - -impl Inputize> for LCCCS { - /// Returns the internal representation in the same order as how the value - /// is allocated in `LCCCS::new_input`. - fn inputize(&self) -> Vec> { - [ - &self.C.inputize_nonnative(), - &[self.u][..], - &self.x, - &self.r_x, - &self.v, - ] - .concat() - } -} - -#[cfg(test)] -pub mod tests { - use ark_pallas::{Fr, Projective}; - use ark_std::{sync::Arc, test_rng, One, UniformRand}; - - use super::*; - use crate::arith::{ - ccs::tests::{get_test_ccs, get_test_z}, - r1cs::R1CS, - ArithRelation, - }; - use crate::commitment::pedersen::Pedersen; - use crate::utils::hypercube::BooleanHypercube; - use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial}; - - // method for testing - pub fn compute_Ls( - ccs: &CCS, - lcccs: &LCCCS, - z: &[C::ScalarField], - ) -> Result>, Error> { - let eq_rx = build_eq_x_r_vec(&lcccs.r_x)?; - let eq_rx_mle = Arc::new(dense_vec_to_dense_mle(ccs.s, &eq_rx)); - - let Ls = ccs - .M - .iter() - .map(|M_j| { - let mut L = VirtualPolynomial::::new(ccs.s); - let Mz = vec![ - Arc::new(dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, z)?)), - eq_rx_mle.clone(), - ]; - L.add_mle_list(Mz, C::ScalarField::one())?; - Ok(L) - }) - .collect::, Error>>()?; - Ok(Ls) - } - - #[test] - /// Test linearized CCCS v_j against the L_j(x) - fn test_lcccs_v_j() -> Result<(), Error> { - let mut rng = test_rng(); - - let n_rows = 2_u32.pow(5) as usize; - let n_cols = 2_u32.pow(5) as usize; - let r1cs = R1CS::::rand(&mut rng, n_rows, n_cols); - let ccs = CCS::from(r1cs); - let z: Vec = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect(); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let (lcccs, _) = ccs.to_lcccs::<_, Projective, Pedersen, false>( - &mut rng, - &pedersen_params, - &z, - )?; - // with our test vector coming from R1CS, v should have length 3 - assert_eq!(lcccs.v.len(), 3); - - let vec_L_j_x = compute_Ls(&ccs, &lcccs, &z)?; - assert_eq!(vec_L_j_x.len(), lcccs.v.len()); - - for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) { - let sum_L_j_x = BooleanHypercube::new(ccs.s) - .map(|y| L_j_x.evaluate(&y)) - .collect::, _>>()? - .into_iter() - .fold(Fr::zero(), |acc, result| acc + result); - assert_eq!(v_i, sum_L_j_x); - } - Ok(()) - } - - /// Given a bad z, check that the v_j should not match with the L_j(x) - #[test] - fn test_bad_v_j() -> Result<(), Error> { - let mut rng = test_rng(); - - let ccs = get_test_ccs(); - let z = get_test_z(3); - let (w, x) = ccs.split_z(&z); - ccs.check_relation(&w, &x)?; - - // Mutate z so that the relation does not hold - let mut bad_z = z.clone(); - bad_z[3] = Fr::zero(); - let (bad_w, bad_x) = ccs.split_z(&bad_z); - assert!(ccs.check_relation(&bad_w, &bad_x).is_err()); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - // Compute v_j with the right z - let (lcccs, _) = ccs.to_lcccs::<_, Projective, Pedersen, false>( - &mut rng, - &pedersen_params, - &z, - )?; - // with our test vector coming from R1CS, v should have length 3 - assert_eq!(lcccs.v.len(), 3); - - // Bad compute L_j(x) with the bad z - let vec_L_j_x = compute_Ls(&ccs, &lcccs, &bad_z)?; - assert_eq!(vec_L_j_x.len(), lcccs.v.len()); - - // Make sure that the LCCCS is not satisfied given these L_j(x) - // i.e. summing L_j(x) over the hypercube should not give v_j for all j - let mut satisfied = true; - for (v_i, L_j_x) in lcccs.v.into_iter().zip(vec_L_j_x) { - let sum_L_j_x = BooleanHypercube::new(ccs.s) - .map(|y| L_j_x.evaluate(&y)) - .collect::, _>>()? - .into_iter() - .fold(Fr::zero(), |acc, result| acc + result); - if v_i != sum_L_j_x { - satisfied = false; - } - } - assert!(!satisfied); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs deleted file mode 100644 index 2cae32341..000000000 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ /dev/null @@ -1,1094 +0,0 @@ -/// Implements the scheme described in [HyperNova](https://eprint.iacr.org/2023/573.pdf) -use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, GR1CSVar}; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, -}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError}; -use ark_std::{cmp::max, fmt::Debug, rand::RngCore, One, Zero}; - -pub mod cccs; -pub mod circuits; -pub mod decider_eth; -pub mod decider_eth_circuit; -pub mod lcccs; -pub mod nimfs; -pub mod utils; - -use cccs::CCCS; -use circuits::AugmentedFCircuit; -use decider_eth_circuit::WitnessVar; -use lcccs::LCCCS; -use nimfs::NIMFS; - -use crate::arith::{ - ccs::CCS, - r1cs::{extract_w_x, R1CS}, - Arith, ArithRelation, -}; -use crate::commitment::CommitmentScheme; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::{ - circuits::{ - cyclefold::{ - CycleFoldAugmentationGadget, CycleFoldCircuit, CycleFoldCommittedInstance, - CycleFoldConfig, CycleFoldWitness, - }, - CF1, CF2, - }, - nova::{get_r1cs_from_cs, PreprocessorParam}, - traits::{CommittedInstanceOps, Dummy, WitnessOps}, -}; -use crate::frontend::FCircuit; -use crate::transcript::{poseidon::poseidon_canonical_config, Transcript}; -use crate::utils::pp_hash; -use crate::{Curve, Error, FoldingScheme, MultiFolding}; - -/// Configuration for HyperNova's CycleFold circuit -pub struct HyperNovaCycleFoldConfig { - r: CF1, - points: Vec, -} - -impl Default for HyperNovaCycleFoldConfig { - fn default() -> Self { - Self { - r: CF1::::zero(), - points: vec![C::zero(); MU + NU], - } - } -} - -impl CycleFoldConfig - for HyperNovaCycleFoldConfig -{ - const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; - const N_INPUT_POINTS: usize = MU + NU; - const N_UNIQUE_RANDOMNESSES: usize = 1; - - fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError> { - let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; - for point in &points { - Self::mark_point_as_public(point)?; - } - Ok(points) - } - - fn alloc_randomnesses( - &self, - cs: ConstraintSystemRef>, - ) -> Result>>>, SynthesisError> { - let one = &CF1::::one().into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; - let r = &self.r.into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; - let one_var = Vec::new_constant(cs.clone(), one)?; - let r_var = Vec::new_witness(cs.clone(), || Ok(r))?; - Self::mark_randomness_as_public(&r_var)?; - Ok([vec![one_var], vec![r_var; MU + NU - 1]].concat()) - } -} - -/// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment. -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Witness { - pub w: Vec, - pub r_w: F, -} - -impl Witness { - pub fn new(w: Vec) -> Self { - // note: at the current version, we don't use the blinding factors and we set them to 0 - // always. - Self { w, r_w: F::zero() } - } -} - -impl Dummy<&CCS> for Witness { - fn dummy(ccs: &CCS) -> Self { - Self::new(vec![F::zero(); ccs.n_witnesses()]) - } -} - -impl WitnessOps for Witness { - type Var = WitnessVar; - - fn get_openings(&self) -> Vec<(&[F], F)> { - vec![(&self.w, self.r_w)] - } -} - -/// Proving parameters for HyperNova-based IVC -#[derive(Debug, Clone)] -pub struct ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// Poseidon sponge configuration - pub poseidon_config: PoseidonConfig, - /// Proving parameters of the underlying commitment scheme over C1 - pub cs_pp: CS1::ProverParams, - /// Proving parameters of the underlying commitment scheme over C2 - pub cf_cs_pp: CS2::ProverParams, - /// CCS of the Augmented Function circuit - /// If ccs is set, it will be used, if not, it will be computed at runtime - pub ccs: Option>, -} - -impl< - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - > CanonicalSerialize for ProverParams -{ - fn serialize_with_mode( - &self, - mut writer: W, - compress: Compress, - ) -> Result<(), SerializationError> { - self.cs_pp.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_pp.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: Compress) -> usize { - self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress) - } -} - -/// Verification parameters for HyperNova-based IVC -#[derive(Debug, Clone)] -pub struct VerifierParams< - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, -> { - /// Poseidon sponge configuration - pub poseidon_config: PoseidonConfig, - /// CCS of the Augmented step circuit - pub ccs: CCS, - /// R1CS of the CycleFold circuit - pub cf_r1cs: R1CS, - /// Verification parameters of the underlying commitment scheme over C1 - pub cs_vp: CS1::VerifierParams, - /// Verification parameters of the underlying commitment scheme over C2 - pub cf_cs_vp: CS2::VerifierParams, -} - -impl CanonicalSerialize for VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.cs_vp.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_vp.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.cs_vp.serialized_size(compress) + self.cf_cs_vp.serialized_size(compress) - } -} - -impl VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// returns the hash of the public parameters of HyperNova - pub fn pp_hash(&self) -> Result { - pp_hash::( - &self.ccs, - &self.cf_r1cs, - &self.cs_vp, - &self.cf_cs_vp, - &self.poseidon_config, - ) - } -} - -#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] -pub struct IVCProof -where - C1: Curve, - C2: Curve, -{ - pub i: C1::ScalarField, - pub z_0: Vec, - pub z_i: Vec, - pub W_i: Witness, - pub U_i: LCCCS, - pub w_i: Witness, - pub u_i: CCCS, - pub cf_W_i: CycleFoldWitness, - pub cf_U_i: CycleFoldCommittedInstance, -} - -/// Implements HyperNova+CycleFold's IVC, described in -/// [HyperNova](https://eprint.iacr.org/2023/573.pdf) and -/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait -/// -/// For multi-instance folding, one needs to specify the const generics below: -/// * `MU` - the number of LCCCS instances to be folded -/// * `NU` - the number of CCCS instances to be folded -#[derive(Clone, Debug)] -pub struct HyperNova -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// CCS of the Augmented Function circuit - pub ccs: CCS, - /// R1CS of the CycleFold circuit - pub cf_r1cs: R1CS, - pub poseidon_config: PoseidonConfig, - /// CommitmentScheme::ProverParams over C1 - pub cs_pp: CS1::ProverParams, - /// CycleFold CommitmentScheme::ProverParams, over C2 - pub cf_cs_pp: CS2::ProverParams, - /// F circuit, the circuit that is being folded - pub F: FC, - /// public params hash - pub pp_hash: C1::ScalarField, - pub i: C1::ScalarField, - /// initial state - pub z_0: Vec, - /// current i-th state - pub z_i: Vec, - /// HyperNova instances - pub W_i: Witness, - pub U_i: LCCCS, - pub w_i: Witness, - pub u_i: CCCS, - - /// CycleFold running instance - pub cf_W_i: CycleFoldWitness, - pub cf_U_i: CycleFoldCommittedInstance, -} - -impl MultiFolding - for HyperNova -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - C1: Curve, -{ - type RunningInstance = (LCCCS, Witness); - type IncomingInstance = (CCCS, Witness); - type MultiInstance = (Vec, Vec); - - /// Creates a new LCCS instance for the given state, which satisfies the HyperNova.CCS. This - /// method can be used to generate the 'other' LCCS instances to be folded in the multi-folding - /// step. - fn new_running_instance( - &self, - mut rng: impl RngCore, - state: Vec, - external_inputs: FC::ExternalInputs, - ) -> Result { - let r1cs_z = self.new_instance_generic(state, external_inputs)?; - // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we - // assign them directly to w_i, u_i. - let (U_i, W_i) = self - .ccs - .to_lcccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?; - - #[cfg(test)] - self.ccs.check_relation(&W_i, &U_i)?; - - Ok((U_i, W_i)) - } - - /// Creates a new CCCS instance for the given state, which satisfies the HyperNova.CCS. This - /// method can be used to generate the 'other' CCCS instances to be folded in the multi-folding - /// step. - fn new_incoming_instance( - &self, - mut rng: impl RngCore, - state: Vec, - external_inputs: FC::ExternalInputs, - ) -> Result { - let r1cs_z = self.new_instance_generic(state, external_inputs)?; - // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we - // assign them directly to w_i, u_i. - let (u_i, w_i) = self - .ccs - .to_cccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?; - - #[cfg(test)] - self.ccs.check_relation(&w_i, &u_i)?; - - Ok((u_i, w_i)) - } -} - -impl - HyperNova -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - C1: Curve, -{ - /// internal helper for new_running_instance & new_incoming_instance methods, returns the R1CS - /// z=[u,x,w] vector to be used to create the LCCCS & CCCS fresh instances. - fn new_instance_generic( - &self, - state: Vec, - external_inputs: FC::ExternalInputs, - ) -> Result, Error> { - // prepare the initial dummy instances - let U_i = LCCCS::::dummy(&self.ccs); - let mut u_i = CCCS::::dummy(&self.ccs); - let (_, cf_U_i): (CycleFoldWitness, CycleFoldCommittedInstance) = - self.cf_r1cs.dummy_witness_instance(); - - let sponge = PoseidonSponge::::new_with_pp_hash( - &self.poseidon_config, - self.pp_hash, - ); - - u_i.x = vec![ - U_i.hash( - &sponge, - C1::ScalarField::zero(), // i - &self.z_0, - &state, - ), - cf_U_i.hash_cyclefold(&sponge), - ]; - let us = vec![u_i.clone(); NU - 1]; - - // compute u_{i+1}.x - let U_i1 = LCCCS::dummy(&self.ccs); - - let augmented_f_circuit = AugmentedFCircuit:: { - poseidon_config: self.poseidon_config.clone(), - ccs: self.ccs.clone(), - pp_hash: Some(self.pp_hash), - i: Some(C1::ScalarField::zero()), - i_usize: Some(0), - z_0: Some(self.z_0.clone()), - z_i: Some(state.clone()), - external_inputs: Some(external_inputs), - U_i: Some(U_i.clone()), - Us: None, - u_i_C: Some(u_i.C), - us: Some(us), - U_i1_C: Some(U_i1.C), - F: self.F.clone(), - nimfs_proof: None, - - // cyclefold values - cf_u_i_cmW: None, - cf_U_i: None, - cf_cmT: None, - }; - - let cs = ConstraintSystem::::new_ref(); - augmented_f_circuit.generate_constraints(cs.clone())?; - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - - #[cfg(test)] - assert!(cs.is_satisfied()?); - - let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::(&cs); // includes 1 and public inputs - - let r1cs_z = [ - vec![C1::ScalarField::one()], - r1cs_x_i1.clone(), - r1cs_w_i1.clone(), - ] - .concat(); - Ok(r1cs_z) - } -} - -impl - FoldingScheme for HyperNova -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - C1: Curve, -{ - /// Reuse Nova's PreprocessorParam. - type PreprocessorParam = PreprocessorParam; - type ProverParam = ProverParams; - type VerifierParam = VerifierParams; - type RunningInstance = (LCCCS, Witness); - type IncomingInstance = (CCCS, Witness); - type MultiCommittedInstanceWithWitness = - (Vec, Vec); - type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); - type IVCProof = IVCProof; - - fn pp_deserialize_with_mode( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - fc_params: FC::Params, - ) -> Result { - let poseidon_config = poseidon_canonical_config::(); - - // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing - // to serialize them, saving significant space in the VerifierParams serialized size. - - // main circuit R1CS: - let f_circuit = FC::new(fc_params)?; - let augmented_F_circuit = AugmentedFCircuit::::empty( - &poseidon_config, - f_circuit.clone(), - None, - )?; - let ccs = augmented_F_circuit.ccs; - - let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - - Ok(ProverParams { - poseidon_config, - cs_pp, - cf_cs_pp, - ccs: Some(ccs), - }) - } - - fn vp_deserialize_with_mode( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - fc_params: FC::Params, - ) -> Result { - let poseidon_config = poseidon_canonical_config::(); - - // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing - // to serialize them, saving significant space in the VerifierParams serialized size. - - // main circuit R1CS: - let f_circuit = FC::new(fc_params)?; - let augmented_F_circuit = AugmentedFCircuit::::empty( - &poseidon_config, - f_circuit.clone(), - None, - )?; - let ccs = augmented_F_circuit.ccs; - - // CycleFold circuit R1CS - let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); - let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; - - let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - - Ok(VerifierParams { - poseidon_config, - ccs, - cf_r1cs, - cs_vp, - cf_cs_vp, - }) - } - - fn preprocess( - mut rng: impl RngCore, - prep_param: &Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - if MU < 1 || NU < 1 { - return Err(Error::CantBeZero("mu,nu".to_string())); - } - - let augmented_f_circuit = AugmentedFCircuit::::empty( - &prep_param.poseidon_config, - prep_param.F.clone(), - None, - )?; - let ccs = augmented_f_circuit.ccs.clone(); - - let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); - let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; - - // if cs params exist, use them, if not, generate new ones - let (cs_pp, cs_vp) = match (&prep_param.cs_pp, &prep_param.cs_vp) { - (Some(cs_pp), Some(cs_vp)) => (cs_pp.clone(), cs_vp.clone()), - // `CS1` is for committing to HyperNova's witness vector `w`, so we - // set `len` to the number of witnesses in `r1cs`. - _ => CS1::setup(&mut rng, ccs.n_witnesses())?, - }; - let (cf_cs_pp, cf_cs_vp) = match (&prep_param.cf_cs_pp, &prep_param.cf_cs_vp) { - (Some(cf_cs_pp), Some(cf_cs_vp)) => (cf_cs_pp.clone(), cf_cs_vp.clone()), - _ => CS2::setup( - &mut rng, - // `CS2` is for committing to CycleFold's witness vector `w` and - // error term `e`, where the length of `e` is the number of - // constraints, so we set `len` to the maximum of `e` and `w`'s - // lengths. - max(cf_r1cs.n_constraints(), cf_r1cs.n_witnesses()), - )?, - }; - - let pp = ProverParams:: { - poseidon_config: prep_param.poseidon_config.clone(), - cs_pp, - cf_cs_pp, - ccs: Some(ccs.clone()), - }; - let vp = VerifierParams:: { - poseidon_config: prep_param.poseidon_config.clone(), - ccs, - cf_r1cs, - cs_vp: cs_vp.clone(), - cf_cs_vp: cf_cs_vp.clone(), - }; - Ok((pp, vp)) - } - - /// Initializes the HyperNova+CycleFold's IVC for the given parameters and initial state `z_0`. - fn init( - params: &(Self::ProverParam, Self::VerifierParam), - F: FC, - z_0: Vec, - ) -> Result { - let (pp, vp) = params; - if MU < 1 || NU < 1 { - return Err(Error::CantBeZero("mu,nu".to_string())); - } - - // compute the public params hash - let pp_hash = vp.pp_hash()?; - - // `sponge` is for digest computation. - let sponge = - PoseidonSponge::::new_with_pp_hash(&pp.poseidon_config, pp_hash); - - // prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS - // and R1CS respectively - let augmented_f_circuit = AugmentedFCircuit::::empty( - &pp.poseidon_config, - F.clone(), - pp.ccs.clone(), - )?; - let ccs = augmented_f_circuit.ccs.clone(); - - let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); - let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; - - // setup the dummy instances - let W_dummy = Witness::::dummy(&ccs); - let U_dummy = LCCCS::::dummy(&ccs); - let w_dummy = W_dummy.clone(); - let mut u_dummy = CCCS::::dummy(&ccs); - let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness, CycleFoldCommittedInstance) = - cf_r1cs.dummy_witness_instance(); - u_dummy.x = vec![ - U_dummy.hash(&sponge, C1::ScalarField::zero(), &z_0, &z_0), - cf_U_dummy.hash_cyclefold(&sponge), - ]; - - // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the - // R1CS that we're working with. - Ok(Self { - ccs, - cf_r1cs, - poseidon_config: pp.poseidon_config.clone(), - cs_pp: pp.cs_pp.clone(), - cf_cs_pp: pp.cf_cs_pp.clone(), - F, - pp_hash, - i: C1::ScalarField::zero(), - z_0: z_0.clone(), - z_i: z_0, - W_i: W_dummy, - U_i: U_dummy, - w_i: w_dummy, - u_i: u_dummy, - // cyclefold running instance - cf_W_i: cf_W_dummy, - cf_U_i: cf_U_dummy, - }) - } - - /// Implements IVC.P of HyperNova+CycleFold - fn prove_step( - &mut self, - mut rng: impl RngCore, - external_inputs: FC::ExternalInputs, - other_instances: Option, - ) -> Result<(), Error> { - // ensure that commitments are blinding if user has specified so. - - if H { - let blinding_commitments = if self.i == C1::ScalarField::zero() { - vec![self.w_i.r_w] - } else { - vec![self.w_i.r_w, self.W_i.r_w] - }; - if blinding_commitments.contains(&C1::ScalarField::zero()) { - return Err(Error::IncorrectBlinding( - H, - format!("{blinding_commitments:?}"), - )); - } - } - - let (Us, Ws, us, ws) = if MU > 1 || NU > 1 { - let other_instances = other_instances.ok_or(Error::MissingOtherInstances(MU, NU))?; - - #[allow(clippy::type_complexity)] - let (lcccs, cccs): ( - Vec<(LCCCS, Witness)>, - Vec<(CCCS, Witness)>, - ) = other_instances; - - // recall, mu & nu is the number of all the LCCCS & CCCS respectively, including the - // running and incoming instances that are not part of the 'other_instances', hence the +1 - // in the couple of following checks. - if lcccs.len() + 1 != MU { - return Err(Error::NotSameLength( - "other_instances.lcccs.len()".to_string(), - lcccs.len(), - "hypernova.mu".to_string(), - MU, - )); - } - if cccs.len() + 1 != NU { - return Err(Error::NotSameLength( - "other_instances.cccs.len()".to_string(), - cccs.len(), - "hypernova.nu".to_string(), - NU, - )); - } - - let (Us, Ws): (Vec>, Vec>) = - lcccs.into_iter().unzip(); - let (us, ws): (Vec>, Vec>) = cccs.into_iter().unzip(); - (Us, Ws, us, ws) - } else { - (vec![], vec![], vec![], vec![]) - }; - - let augmented_f_circuit: AugmentedFCircuit; - - if self.z_i.len() != self.F.state_len() { - return Err(Error::NotSameLength( - "z_i.len()".to_string(), - self.z_i.len(), - "F.state_len()".to_string(), - self.F.state_len(), - )); - } - - if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) { - return Err(Error::MaxStep); - } - - let i_usize; - - #[cfg(target_pointer_width = "64")] - { - let mut i_bytes: [u8; 8] = [0; 8]; - i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]); - i_usize = usize::from_le_bytes(i_bytes); - } - - #[cfg(target_pointer_width = "32")] - { - let mut i_bytes: [u8; 4] = [0; 4]; - i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..4]); - i_usize = usize::from_le_bytes(i_bytes); - } - - let (U_i1, mut W_i1); - - if self.i == C1::ScalarField::zero() { - W_i1 = Witness::::dummy(&self.ccs); - W_i1.r_w = self.W_i.r_w; - U_i1 = LCCCS::dummy(&self.ccs); - - augmented_f_circuit = AugmentedFCircuit:: { - poseidon_config: self.poseidon_config.clone(), - ccs: self.ccs.clone(), - pp_hash: Some(self.pp_hash), - i: Some(C1::ScalarField::zero()), - i_usize: Some(0), - z_0: Some(self.z_0.clone()), - z_i: Some(self.z_i.clone()), - external_inputs: Some(external_inputs.clone()), - U_i: Some(self.U_i.clone()), - Us: Some(Us), - u_i_C: Some(self.u_i.C), - us: Some(us), - U_i1_C: Some(U_i1.C), - F: self.F.clone(), - nimfs_proof: None, - - // cyclefold values - cf_u_i_cmW: None, - cf_U_i: None, - cf_cmT: None, - }; - } else { - let mut transcript_p: PoseidonSponge = - PoseidonSponge::::new_with_pp_hash( - &self.poseidon_config, - self.pp_hash, - ); - - let (all_Us, all_us, all_Ws, all_ws) = ( - [&[self.U_i.clone()][..], &Us].concat(), - [&[self.u_i.clone()][..], &us].concat(), - [vec![self.W_i.clone()], Ws].concat(), - [vec![self.w_i.clone()], ws].concat(), - ); - - let (rho, nimfs_proof); - (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( - &mut transcript_p, - &self.ccs, - &all_Us, - &all_us, - &all_Ws, - &all_ws, - )?; - - // sanity check: check the folded instance relation - #[cfg(test)] - self.ccs.check_relation(&W_i1, &U_i1)?; - - // CycleFold part: - let (cf_w_i, cf_u_i) = HyperNovaCycleFoldConfig:: { - r: rho, - points: [ - all_Us.iter().map(|Us_i| Us_i.C).collect::>(), - all_us.iter().map(|us_i| us_i.C).collect::>(), - ] - .concat(), - } - .build_circuit() - .generate_incoming_instance_witness::<_, CS2, H>(&self.cf_cs_pp, &mut rng)?; - - let (cf_W_i1, cf_U_i1, cf_cmTs) = CycleFoldAugmentationGadget::fold_native::<_, CS2, H>( - &mut transcript_p, - &self.cf_r1cs, - &self.cf_cs_pp, - self.cf_W_i.clone(), - self.cf_U_i.clone(), - vec![cf_w_i], - vec![cf_u_i.clone()], - )?; - - augmented_f_circuit = AugmentedFCircuit:: { - poseidon_config: self.poseidon_config.clone(), - ccs: self.ccs.clone(), - pp_hash: Some(self.pp_hash), - i: Some(self.i), - i_usize: Some(i_usize), - z_0: Some(self.z_0.clone()), - z_i: Some(self.z_i.clone()), - external_inputs: Some(external_inputs), - U_i: Some(self.U_i.clone()), - Us: Some(Us), - u_i_C: Some(self.u_i.C), - us: Some(us), - U_i1_C: Some(U_i1.C), - F: self.F.clone(), - nimfs_proof: Some(nimfs_proof), - - // cyclefold values - cf_u_i_cmW: Some(cf_u_i.cmW), - cf_U_i: Some(self.cf_U_i.clone()), - cf_cmT: Some(cf_cmTs[0]), - }; - - // assign the next round instances - self.cf_W_i = cf_W_i1; - self.cf_U_i = cf_U_i1; - } - - let cs = ConstraintSystem::::new_ref(); - let z_i1 = augmented_f_circuit - .compute_next_state(cs.clone())? - .value()?; - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - - #[cfg(test)] - assert!(cs.is_satisfied()?); - - let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::(&cs); // includes 1 and public inputs - - let r1cs_z = [ - vec![C1::ScalarField::one()], - r1cs_x_i1.clone(), - r1cs_w_i1.clone(), - ] - .concat(); - // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we - // assign them directly to w_i, u_i. - let (u_i, w_i) = self - .ccs - .to_cccs::<_, C1, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?; - self.u_i = u_i.clone(); - self.w_i = w_i.clone(); - - // set values for next iteration - self.i += C1::ScalarField::one(); - // assign z_{i+1} into z_i - self.z_i = z_i1; - self.U_i = U_i1.clone(); - self.W_i = W_i1.clone(); - - #[cfg(test)] - { - // check the new LCCCS instance relation - self.ccs.check_relation(&self.W_i, &self.U_i)?; - // check the new CCCS instance relation - self.ccs.check_relation(&self.w_i, &self.u_i)?; - } - - Ok(()) - } - - fn state(&self) -> Vec { - self.z_i.clone() - } - - fn ivc_proof(&self) -> Self::IVCProof { - Self::IVCProof { - i: self.i, - z_0: self.z_0.clone(), - z_i: self.z_i.clone(), - W_i: self.W_i.clone(), - U_i: self.U_i.clone(), - w_i: self.w_i.clone(), - u_i: self.u_i.clone(), - cf_W_i: self.cf_W_i.clone(), - cf_U_i: self.cf_U_i.clone(), - } - } - - fn from_ivc_proof( - ivc_proof: Self::IVCProof, - fcircuit_params: FC::Params, - params: (Self::ProverParam, Self::VerifierParam), - ) -> Result { - let IVCProof { - i, - z_0, - z_i, - W_i, - U_i, - w_i, - u_i, - cf_W_i, - cf_U_i, - } = ivc_proof; - let (pp, vp) = params; - - let f_circuit = FC::new(fcircuit_params)?; - let augmented_f_circuit = AugmentedFCircuit::::empty( - &pp.poseidon_config, - f_circuit.clone(), - None, - )?; - let cf_circuit = CycleFoldCircuit::<_, HyperNovaCycleFoldConfig>::default(); - - let ccs = augmented_f_circuit.ccs.clone(); - let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; - - Ok(Self { - ccs, - cf_r1cs, - poseidon_config: pp.poseidon_config, - cs_pp: pp.cs_pp, - cf_cs_pp: pp.cf_cs_pp, - F: f_circuit, - pp_hash: vp.pp_hash()?, - i, - z_0, - z_i, - w_i, - u_i, - W_i, - U_i, - cf_W_i, - cf_U_i, - }) - } - - /// Implements IVC.V of Hyp.clone()erNova+CycleFold. Notice that this method does not include the - /// commitments verification, which is done in the Decider. - fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> { - let Self::IVCProof { - i: num_steps, - z_0, - z_i, - W_i, - U_i, - w_i, - u_i, - cf_W_i, - cf_U_i, - } = ivc_proof; - - if num_steps == C1::ScalarField::zero() { - if z_0 != z_i { - return Err(Error::IVCVerificationFail); - } - return Ok(()); - } - // `sponge` is for digest computation. - let sponge = - PoseidonSponge::::new_with_pp_hash(&vp.poseidon_config, vp.pp_hash()?); - - if u_i.x.len() != 2 || U_i.x.len() != 2 { - return Err(Error::IVCVerificationFail); - } - - // check that u_i's output points to the running instance - // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, num_steps, &z_0, &z_i); - if expected_u_i_x != u_i.x[0] { - return Err(Error::IVCVerificationFail); - } - // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge); - if expected_cf_u_i_x != u_i.x[1] { - return Err(Error::IVCVerificationFail); - } - - // check LCCCS satisfiability - vp.ccs.check_relation(&W_i, &U_i)?; - // check CCCS satisfiability - vp.ccs.check_relation(&w_i, &u_i)?; - - // check CycleFold's RelaxedR1CS satisfiability - vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::commitment::kzg::KZG; - use ark_bn254::{Bn254, Fr, G1Projective as Projective}; - use ark_grumpkin::Projective as Projective2; - use ark_std::UniformRand; - - use super::*; - use crate::commitment::pedersen::Pedersen; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - pub fn test_ivc() -> Result<(), Error> { - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - - // run the test using Pedersen commitments on both sides of the curve cycle - let _ = test_ivc_opt::, Pedersen, false>( - poseidon_config.clone(), - F_circuit, - )?; - - let _ = test_ivc_opt::, Pedersen, true>( - poseidon_config.clone(), - F_circuit, - )?; - - // run the test using KZG for the commitments on the main curve, and Pedersen for the - // commitments on the secondary curve - let _ = - test_ivc_opt::, Pedersen, false>(poseidon_config, F_circuit)?; - Ok(()) - } - - #[allow(clippy::type_complexity)] - // test_ivc allowing to choose the CommitmentSchemes - pub fn test_ivc_opt< - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - >( - poseidon_config: PoseidonConfig, - F_circuit: CubicFCircuit, - ) -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - - const MU: usize = 2; - const NU: usize = 3; - - type HN = - HyperNova, CS1, CS2, MU, NU, H>; - - let prep_param = - PreprocessorParam::, CS1, CS2, H>::new( - poseidon_config.clone(), - F_circuit, - ); - let hypernova_params = HN::preprocess(&mut rng, &prep_param)?; - - let z_0 = vec![Fr::from(3_u32)]; - let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone())?; - - let (w_i_blinding, W_i_blinding) = if H { - (Fr::rand(&mut rng), Fr::rand(&mut rng)) - } else { - (Fr::zero(), Fr::zero()) - }; - hypernova.w_i.r_w = w_i_blinding; - hypernova.W_i.r_w = W_i_blinding; - - let num_steps: usize = 3; - for _ in 0..num_steps { - // prepare some new instances to fold in the multifolding step - let mut lcccs = vec![]; - for j in 0..MU - 1 { - let instance_state = vec![Fr::from(j as u32 + 85_u32)]; - let (U, W) = hypernova.new_running_instance(&mut rng, instance_state, ())?; - lcccs.push((U, W)); - } - let mut cccs = vec![]; - for j in 0..NU - 1 { - let instance_state = vec![Fr::from(j as u32 + 15_u32)]; - let (u, w) = hypernova.new_incoming_instance(&mut rng, instance_state, ())?; - cccs.push((u, w)); - } - - hypernova.prove_step(&mut rng, (), Some((lcccs, cccs)))?; - } - assert_eq!(Fr::from(num_steps as u32), hypernova.i); - - let ivc_proof = hypernova.ivc_proof(); - HN::verify( - hypernova_params.1.clone(), // verifier_params - ivc_proof, - )?; - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/nimfs.rs b/folding-schemes/src/folding/hypernova/nimfs.rs deleted file mode 100644 index 4098dc163..000000000 --- a/folding-schemes/src/folding/hypernova/nimfs.rs +++ /dev/null @@ -1,732 +0,0 @@ -use ark_ff::{BigInteger, Field, PrimeField}; -use ark_poly::univariate::DensePolynomial; -use ark_poly::{DenseUVPolynomial, Polynomial}; -use ark_std::{fmt::Debug, marker::PhantomData, One, Zero}; - -use super::{ - cccs::CCCS, - lcccs::LCCCS, - utils::{compute_c, compute_g, compute_sigmas_thetas}, - Witness, -}; -use crate::arith::{ccs::CCS, Arith}; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::circuits::CF1; -use crate::folding::traits::Dummy; -use crate::transcript::Transcript; -use crate::utils::sum_check::structs::{IOPProof as SumCheckProof, IOPProverMessage}; -use crate::utils::sum_check::{IOPSumCheck, SumCheck}; -use crate::utils::virtual_polynomial::VPAuxInfo; -use crate::{Curve, Error}; - -/// NIMFSProof defines a multifolding proof -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct NIMFSProof { - pub sc_proof: SumCheckProof, - pub sigmas_thetas: SigmasThetas, -} - -impl Dummy<(usize, usize, usize, usize)> for NIMFSProof { - fn dummy((s, t, mu, nu): (usize, usize, usize, usize)) -> Self { - // use 'C::ScalarField::one()' instead of 'zero()' to enforce the NIMFSProof to have the - // same in-circuit representation to match the number of constraints of an actual proof. - NIMFSProof:: { - sc_proof: SumCheckProof:: { - point: vec![C::ScalarField::one(); s], - proofs: vec![ - IOPProverMessage { - coeffs: vec![C::ScalarField::one(); t + 1] - }; - s - ], - }, - sigmas_thetas: SigmasThetas( - vec![vec![C::ScalarField::one(); t]; mu], - vec![vec![C::ScalarField::one(); t]; nu], - ), - } - } -} - -impl Dummy<(&CCS>, usize, usize)> for NIMFSProof { - fn dummy((ccs, mu, nu): (&CCS>, usize, usize)) -> Self { - NIMFSProof::dummy((ccs.s, ccs.t, mu, nu)) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct SigmasThetas(pub Vec>, pub Vec>); - -#[derive(Debug)] -/// Implements the Non-Interactive Multi Folding Scheme described in section 5 of -/// [HyperNova](https://eprint.iacr.org/2023/573.pdf) -pub struct NIMFS> { - pub _c: PhantomData, - pub _t: PhantomData, -} - -impl> NIMFS { - pub fn fold( - lcccs: &[LCCCS], - cccs: &[CCCS], - sigmas_thetas: &SigmasThetas, - r_x_prime: Vec, - rho: C::ScalarField, - ) -> LCCCS { - let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone()); - let mut C_folded = C::zero(); - let mut u_folded = C::ScalarField::zero(); - let mut x_folded: Vec = vec![C::ScalarField::zero(); lcccs[0].x.len()]; - let mut v_folded: Vec = vec![C::ScalarField::zero(); sigmas[0].len()]; - - let mut rho_i = C::ScalarField::one(); - for i in 0..(lcccs.len() + cccs.len()) { - let c: C; - let u: C::ScalarField; - let x: Vec; - let v: Vec; - if i < lcccs.len() { - c = lcccs[i].C; - u = lcccs[i].u; - x = lcccs[i].x.clone(); - v = sigmas[i].clone(); - } else { - c = cccs[i - lcccs.len()].C; - u = C::ScalarField::one(); - x = cccs[i - lcccs.len()].x.clone(); - v = thetas[i - lcccs.len()].clone(); - } - - C_folded += c.mul(rho_i); - u_folded += rho_i * u; - x_folded = x_folded - .iter() - .zip( - x.iter() - .map(|x_i| *x_i * rho_i) - .collect::>(), - ) - .map(|(a_i, b_i)| *a_i + b_i) - .collect(); - - v_folded = v_folded - .iter() - .zip( - v.iter() - .map(|x_i| *x_i * rho_i) - .collect::>(), - ) - .map(|(a_i, b_i)| *a_i + b_i) - .collect(); - - // compute the next power of rho - rho_i *= rho; - } - - LCCCS:: { - C: C_folded, - u: u_folded, - x: x_folded, - r_x: r_x_prime, - v: v_folded, - } - } - - pub fn fold_witness( - w_lcccs: &[Witness], - w_cccs: &[Witness], - rho: C::ScalarField, - ) -> Witness { - let mut w_folded: Vec = vec![C::ScalarField::zero(); w_lcccs[0].w.len()]; - let mut r_w_folded = C::ScalarField::zero(); - - let mut rho_i = C::ScalarField::one(); - for i in 0..(w_lcccs.len() + w_cccs.len()) { - // let rho_i = rho.pow([i as u64]); - let w: Vec; - let r_w: C::ScalarField; - - if i < w_lcccs.len() { - w = w_lcccs[i].w.clone(); - r_w = w_lcccs[i].r_w; - } else { - w = w_cccs[i - w_lcccs.len()].w.clone(); - r_w = w_cccs[i - w_lcccs.len()].r_w; - } - - w_folded = w_folded - .iter() - .zip( - w.iter() - .map(|x_i| *x_i * rho_i) - .collect::>(), - ) - .map(|(a_i, b_i)| *a_i + b_i) - .collect(); - - r_w_folded += rho_i * r_w; - - // compute the next power of rho - rho_i *= rho; - } - Witness { - w: w_folded, - r_w: r_w_folded, - } - } - - /// Performs the multifolding prover. Given μ LCCCS instances and ν CCS instances, fold them - /// into a single LCCCS instance. Since this is the prover, also fold their witness. - /// Returns the final folded LCCCS, the folded witness, and the multifolding proof, which - /// contains the sumcheck proof and the helper sumcheck claim sigmas and thetas. - #[allow(clippy::type_complexity)] - pub fn prove( - transcript: &mut impl Transcript, - ccs: &CCS, - running_instances: &[LCCCS], - new_instances: &[CCCS], - w_lcccs: &[Witness], - w_cccs: &[Witness], - ) -> Result< - ( - NIMFSProof, - LCCCS, - Witness, - C::ScalarField, // rho - ), - Error, - > { - // absorb instances to transcript - transcript.absorb(&running_instances); - transcript.absorb(&new_instances); - - if running_instances.is_empty() { - return Err(Error::Empty); - } - if new_instances.is_empty() { - return Err(Error::Empty); - } - - // construct the LCCCS z vector from the relaxation factor, public IO and witness - let mut z_lcccs = Vec::new(); - for (i, running_instance) in running_instances.iter().enumerate() { - let z_1: Vec = [ - vec![running_instance.u], - running_instance.x.clone(), - w_lcccs[i].w.to_vec(), - ] - .concat(); - z_lcccs.push(z_1); - } - // construct the CCCS z vector from the public IO and witness - let mut z_cccs = Vec::new(); - for (i, new_instance) in new_instances.iter().enumerate() { - let z_2: Vec = [ - vec![C::ScalarField::one()], - new_instance.x.clone(), - w_cccs[i].w.to_vec(), - ] - .concat(); - z_cccs.push(z_2); - } - - // Step 1: Get some challenges - let gamma_scalar = C::ScalarField::from_le_bytes_mod_order(b"gamma"); - let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); - transcript.absorb(&gamma_scalar); - let gamma: C::ScalarField = transcript.get_challenge(); - transcript.absorb(&beta_scalar); - let beta: Vec = transcript.get_challenges(ccs.s); - - // Compute g(x) - let g = compute_g(ccs, running_instances, &z_lcccs, &z_cccs, gamma, &beta)?; - - // Step 3: Run the sumcheck prover - let sumcheck_proof = IOPSumCheck::::prove(&g, transcript) - .map_err(|err| Error::SumCheckProveError(err.to_string()))?; - - // Step 2: dig into the sumcheck and extract r_x_prime - let r_x_prime = sumcheck_proof.point.clone(); - - // Step 4: compute sigmas and thetas - let sigmas_thetas = compute_sigmas_thetas(ccs, &z_lcccs, &z_cccs, &r_x_prime)?; - - // Step 6: Get the folding challenge - let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho"); - transcript.absorb(&rho_scalar); - let rho_bits: Vec = transcript.get_challenge_nbits(NOVA_N_BITS_RO); - let rho: C::ScalarField = C::ScalarField::from( - ::BigInt::from_bits_le(&rho_bits), - ); - - // Step 7: Create the folded instance - let folded_lcccs = Self::fold( - running_instances, - new_instances, - &sigmas_thetas, - r_x_prime, - rho, - ); - - // Step 8: Fold the witnesses - let folded_witness = Self::fold_witness(w_lcccs, w_cccs, rho); - - Ok(( - NIMFSProof:: { - sc_proof: sumcheck_proof, - sigmas_thetas, - }, - folded_lcccs, - folded_witness, - rho, - )) - } - - /// Performs the multifolding verifier. Given μ LCCCS instances and ν CCS instances, fold them - /// into a single LCCCS instance. - /// Returns the folded LCCCS instance. - pub fn verify( - transcript: &mut impl Transcript, - ccs: &CCS, - running_instances: &[LCCCS], - new_instances: &[CCCS], - proof: NIMFSProof, - ) -> Result, Error> { - // absorb instances to transcript - transcript.absorb(&running_instances); - transcript.absorb(&new_instances); - - if running_instances.is_empty() { - return Err(Error::Empty); - } - if new_instances.is_empty() { - return Err(Error::Empty); - } - - // Step 1: Get some challenges - let gamma_scalar = C::ScalarField::from_le_bytes_mod_order(b"gamma"); - transcript.absorb(&gamma_scalar); - let gamma: C::ScalarField = transcript.get_challenge(); - - let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); - transcript.absorb(&beta_scalar); - let beta: Vec = transcript.get_challenges(ccs.s); - - let vp_aux_info = VPAuxInfo:: { - max_degree: ccs.degree() + 1, - num_variables: ccs.s, - phantom: PhantomData::, - }; - - // Step 3: Start verifying the sumcheck - // First, compute the expected sumcheck sum: \sum gamma^j v_j - let mut sum_v_j_gamma = C::ScalarField::zero(); - for (i, running_instance) in running_instances.iter().enumerate() { - for j in 0..running_instance.v.len() { - let gamma_j = gamma.pow([(i * ccs.t + j) as u64]); - sum_v_j_gamma += running_instance.v[j] * gamma_j; - } - } - - // Verify the interactive part of the sumcheck - let sumcheck_subclaim = IOPSumCheck::::verify( - sum_v_j_gamma, - &proof.sc_proof, - &vp_aux_info, - transcript, - ) - .map_err(|err| Error::SumCheckVerifyError(err.to_string()))?; - - // Step 2: Dig into the sumcheck claim and extract the randomness used - let r_x_prime = sumcheck_subclaim.point.clone(); - - // Step 5: Finish verifying sumcheck (verify the claim c) - let c = compute_c( - ccs, - &proof.sigmas_thetas, - gamma, - &beta, - &running_instances - .iter() - .map(|lcccs| lcccs.r_x.clone()) - .collect(), - &r_x_prime, - )?; - - // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas - if c != sumcheck_subclaim.expected_evaluation { - return Err(Error::NotEqual); - } - - // Sanity check: we can also compute g(r_x') from the proof last evaluation value, and - // should be equal to the previously obtained values. - let g_on_rxprime_from_sumcheck_last_eval = DensePolynomial::from_coefficients_slice( - &proof.sc_proof.proofs.last().ok_or(Error::Empty)?.coeffs, - ) - .evaluate(r_x_prime.last().ok_or(Error::Empty)?); - if g_on_rxprime_from_sumcheck_last_eval != c { - return Err(Error::NotEqual); - } - if g_on_rxprime_from_sumcheck_last_eval != sumcheck_subclaim.expected_evaluation { - return Err(Error::NotEqual); - } - - // Step 6: Get the folding challenge - let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho"); - transcript.absorb(&rho_scalar); - let rho_bits: Vec = transcript.get_challenge_nbits(NOVA_N_BITS_RO); - let rho: C::ScalarField = C::ScalarField::from( - ::BigInt::from_bits_le(&rho_bits), - ); - - // Step 7: Compute the folded instance - Ok(Self::fold( - running_instances, - new_instances, - &proof.sigmas_thetas, - r_x_prime, - rho, - )) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_pallas::{Fr, Projective}; - use ark_std::{test_rng, UniformRand}; - - use crate::arith::{ - ccs::tests::{get_test_ccs, get_test_z}, - ArithRelation, - }; - use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_fold() -> Result<(), Error> { - let ccs = get_test_ccs(); - let z1 = get_test_z::(3); - let z2 = get_test_z::(4); - let (w1, x1) = ccs.split_z(&z1); - let (w2, x2) = ccs.split_z(&z2); - ccs.check_relation(&w1, &x1)?; - ccs.check_relation(&w2, &x2)?; - - let mut rng = test_rng(); - let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - - let sigmas_thetas = compute_sigmas_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime)?; - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let (lcccs, w1) = ccs.to_lcccs::<_, Projective, Pedersen, false>( - &mut rng, - &pedersen_params, - &z1, - )?; - let (cccs, w2) = ccs.to_cccs::<_, Projective, Pedersen, false>( - &mut rng, - &pedersen_params, - &z2, - )?; - - ccs.check_relation(&w1, &lcccs)?; - ccs.check_relation(&w2, &cccs)?; - - let mut rng = test_rng(); - let rho = Fr::rand(&mut rng); - - let folded = NIMFS::>::fold( - &[lcccs], - &[cccs], - &sigmas_thetas, - r_x_prime, - rho, - ); - - let w_folded = NIMFS::>::fold_witness(&[w1], &[w2], rho); - - // check lcccs relation - ccs.check_relation(&w_folded, &folded)?; - Ok(()) - } - - /// Perform multifolding of an LCCCS instance with a CCCS instance (as described in the paper) - #[test] - pub fn test_basic_multifolding() -> Result<(), Error> { - let mut rng = test_rng(); - - // Create a basic CCS circuit - let ccs = get_test_ccs::(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - // Generate a satisfying witness - let z_1 = get_test_z(3); - // Generate another satisfying witness - let z_2 = get_test_z(4); - - // Create the LCCCS instance out of z_1 - let (running_instance, w1) = - ccs.to_lcccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, &z_1)?; - // Create the CCCS instance out of z_2 - let (new_instance, w2) = - ccs.to_cccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, &z_2)?; - - // Prover's transcript - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); - let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); - // Verifier's transcript - let mut transcript_v = transcript_p.clone(); - - // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness, _) = - NIMFS::>::prove( - &mut transcript_p, - &ccs, - &[running_instance.clone()], - &[new_instance.clone()], - &[w1], - &[w2], - )?; - - // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::>::verify( - &mut transcript_v, - &ccs, - &[running_instance.clone()], - &[new_instance.clone()], - proof, - )?; - assert_eq!(folded_lcccs, folded_lcccs_v); - - // Check that the folded LCCCS instance is a valid instance with respect to the folded witness - ccs.check_relation(&folded_witness, &folded_lcccs)?; - Ok(()) - } - - /// Perform multiple steps of multifolding of an LCCCS instance with a CCCS instance - #[test] - pub fn test_multifolding_two_instances_multiple_steps() -> Result<(), Error> { - let mut rng = test_rng(); - - let ccs = get_test_ccs::(); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - // LCCCS witness - let z_1 = get_test_z(2); - let (mut running_instance, mut w1) = - ccs.to_lcccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, &z_1)?; - - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); - let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); - // Verifier's transcript - let mut transcript_v = transcript_p.clone(); - - let n: usize = 10; - for i in 3..n { - // CCS witness - let z_2 = get_test_z(i); - - let (new_instance, w2) = - ccs.to_cccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, &z_2)?; - - // run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness, _) = - NIMFS::>::prove( - &mut transcript_p, - &ccs, - &[running_instance.clone()], - &[new_instance.clone()], - &[w1], - &[w2], - )?; - - // run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::>::verify( - &mut transcript_v, - &ccs, - &[running_instance.clone()], - &[new_instance.clone()], - proof, - )?; - assert_eq!(folded_lcccs, folded_lcccs_v); - - // check that the folded instance with the folded witness holds the LCCCS relation - ccs.check_relation(&folded_witness, &folded_lcccs)?; - - running_instance = folded_lcccs; - w1 = folded_witness; - } - Ok(()) - } - - /// Test that generates mu>1 and nu>1 instances, and folds them in a single multifolding step. - #[test] - pub fn test_multifolding_mu_nu_instances() -> Result<(), Error> { - let mut rng = test_rng(); - - // Create a basic CCS circuit - let ccs = get_test_ccs::(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let mu = 10; - let nu = 15; - - // Generate a mu LCCCS & nu CCCS satisfying witness - let mut z_lcccs = Vec::new(); - for i in 0..mu { - let z = get_test_z(i + 3); - z_lcccs.push(z); - } - let mut z_cccs = Vec::new(); - for i in 0..nu { - let z = get_test_z(nu + i + 3); - z_cccs.push(z); - } - - // Create the LCCCS instances out of z_lcccs - let mut lcccs_instances = Vec::new(); - let mut w_lcccs = Vec::new(); - for z_i in z_lcccs.iter() { - let (running_instance, w) = - ccs.to_lcccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, z_i)?; - lcccs_instances.push(running_instance); - w_lcccs.push(w); - } - // Create the CCCS instance out of z_cccs - let mut cccs_instances = Vec::new(); - let mut w_cccs = Vec::new(); - for z_i in z_cccs.iter() { - let (new_instance, w) = - ccs.to_cccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, z_i)?; - cccs_instances.push(new_instance); - w_cccs.push(w); - } - - // Prover's transcript - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); - let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); - // Verifier's transcript - let mut transcript_v = transcript_p.clone(); - - // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness, _) = - NIMFS::>::prove( - &mut transcript_p, - &ccs, - &lcccs_instances, - &cccs_instances, - &w_lcccs, - &w_cccs, - )?; - - // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::>::verify( - &mut transcript_v, - &ccs, - &lcccs_instances, - &cccs_instances, - proof, - )?; - assert_eq!(folded_lcccs, folded_lcccs_v); - - // Check that the folded LCCCS instance is a valid instance with respect to the folded witness - ccs.check_relation(&folded_witness, &folded_lcccs)?; - Ok(()) - } - - /// Test that generates mu>1 and nu>1 instances, and folds them in a single multifolding step - /// and repeats the process by doing multiple steps. - #[test] - pub fn test_multifolding_mu_nu_instances_multiple_steps() -> Result<(), Error> { - let mut rng = test_rng(); - - // Create a basic CCS circuit - let ccs = get_test_ccs::(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from_le_bytes_mod_order(b"init init"); - // Prover's transcript - let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); - // Verifier's transcript - let mut transcript_v = transcript_p.clone(); - - let n_steps = 3; - - // number of LCCCS & CCCS instances in each multifolding step - let mu = 10; - let nu = 15; - - // Generate a mu LCCCS & nu CCCS satisfying witness, for each step - for step in 0..n_steps { - let mut z_lcccs = Vec::new(); - for i in 0..mu { - let z = get_test_z(step + i + 3); - z_lcccs.push(z); - } - let mut z_cccs = Vec::new(); - for i in 0..nu { - let z = get_test_z(nu + i + 3); - z_cccs.push(z); - } - - // Create the LCCCS instances out of z_lcccs - let mut lcccs_instances = Vec::new(); - let mut w_lcccs = Vec::new(); - for z_i in z_lcccs.iter() { - let (running_instance, w) = ccs.to_lcccs::<_, _, Pedersen, false>( - &mut rng, - &pedersen_params, - z_i, - )?; - lcccs_instances.push(running_instance); - w_lcccs.push(w); - } - // Create the CCCS instance out of z_cccs - let mut cccs_instances = Vec::new(); - let mut w_cccs = Vec::new(); - for z_i in z_cccs.iter() { - let (new_instance, w) = ccs.to_cccs::<_, _, Pedersen, false>( - &mut rng, - &pedersen_params, - z_i, - )?; - cccs_instances.push(new_instance); - w_cccs.push(w); - } - - // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness, _) = - NIMFS::>::prove( - &mut transcript_p, - &ccs, - &lcccs_instances, - &cccs_instances, - &w_lcccs, - &w_cccs, - )?; - - // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::>::verify( - &mut transcript_v, - &ccs, - &lcccs_instances, - &cccs_instances, - proof, - )?; - - assert_eq!(folded_lcccs, folded_lcccs_v); - - // Check that the folded LCCCS instance is a valid instance with respect to the folded witness - ccs.check_relation(&folded_witness, &folded_lcccs)?; - } - Ok(()) - } -} diff --git a/folding-schemes/src/folding/hypernova/utils.rs b/folding-schemes/src/folding/hypernova/utils.rs deleted file mode 100644 index 2ed0f07a7..000000000 --- a/folding-schemes/src/folding/hypernova/utils.rs +++ /dev/null @@ -1,333 +0,0 @@ -use ark_ff::PrimeField; -use ark_poly::MultilinearExtension; -use ark_std::One; -use std::sync::Arc; - -use super::lcccs::LCCCS; -use super::nimfs::SigmasThetas; -use crate::arith::ccs::CCS; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::utils::vec::mat_vec_mul; -use crate::utils::virtual_polynomial::{build_eq_x_r_vec, eq_eval, VirtualPolynomial}; -use crate::{Curve, Error}; - -/// Compute the arrays of sigma_i and theta_i from step 4 corresponding to the LCCCS and CCCS -/// instances -pub fn compute_sigmas_thetas( - ccs: &CCS, - z_lcccs: &[Vec], - z_cccs: &[Vec], - r_x_prime: &[F], -) -> Result, Error> { - let mut sigmas: Vec> = Vec::new(); - for z_lcccs_i in z_lcccs { - let sigma_i = ccs - .M - .iter() - .map(|M_j| { - let Mz = dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, z_lcccs_i)?); - Ok(Mz.fix_variables(r_x_prime)[0]) - }) - .collect::, Error>>()?; - sigmas.push(sigma_i); - } - - let mut thetas: Vec> = Vec::new(); - for z_cccs_i in z_cccs { - let theta_i = ccs - .M - .iter() - .map(|M_j| { - let Mz = dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, z_cccs_i)?); - Ok(Mz.fix_variables(r_x_prime)[0]) - }) - .collect::, Error>>()?; - thetas.push(theta_i); - } - Ok(SigmasThetas(sigmas, thetas)) -} - -/// Computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS -/// instances: -/// $$ -/// c = \sum_{i \in [\mu]} \left(\sum_{j \in [t]} \gamma^{i \cdot t + j} \cdot e_i \cdot \sigma_{i,j} \right) + -/// \sum_{k \in [\nu]} \gamma^{\mu \cdot t+k} \cdot e_k \cdot \left( \sum_{i=1}^q c_i \cdot \prod_{j \in S_i} -/// \theta_{k,j} \right) -/// $$ -pub fn compute_c( - ccs: &CCS, - st: &SigmasThetas, - gamma: F, - beta: &[F], - vec_r_x: &Vec>, - r_x_prime: &[F], -) -> Result { - let (vec_sigmas, vec_thetas) = (st.0.clone(), st.1.clone()); - let mut c = F::zero(); - - let mut e_lcccs = Vec::new(); - for r_x in vec_r_x { - e_lcccs.push(eq_eval(r_x, r_x_prime)?); - } - for (i, sigmas) in vec_sigmas.iter().enumerate() { - // (sum gamma^j * e_i * sigma_j) - for (j, sigma_j) in sigmas.iter().enumerate() { - let gamma_j = gamma.pow([((i * ccs.t + j) as u64)]); - c += gamma_j * e_lcccs[i] * sigma_j; - } - } - - let mu = vec_sigmas.len(); - let e2 = eq_eval(beta, r_x_prime)?; - for (k, thetas) in vec_thetas.iter().enumerate() { - // + gamma^{t+1} * e2 * sum c_i * prod theta_j - let prods = ccs.S.iter().zip(&ccs.c).map(|(S_i, &c_i)| { - let mut prod = F::one(); - for &j in S_i { - prod *= thetas[j]; - } - c_i * prod - }); - let lhs = F::sum(prods); - let gamma_t1 = gamma.pow([(mu * ccs.t + k) as u64]); - c += gamma_t1 * e2 * lhs; - } - Ok(c) -} - -/// Compute g(x) polynomial for the given inputs. -pub fn compute_g( - ccs: &CCS, - running_instances: &[LCCCS], - z_lcccs: &[Vec], - z_cccs: &[Vec], - gamma: C::ScalarField, - beta: &[C::ScalarField], -) -> Result, Error> { - assert_eq!(running_instances.len(), z_lcccs.len()); - - let mut g = VirtualPolynomial::::new(ccs.s); - - let mu = z_lcccs.len(); - let nu = z_cccs.len(); - - let mut gamma_pow = C::ScalarField::one(); - for i in 0..mu { - // L_j - let eq_rx = build_eq_x_r_vec(&running_instances[i].r_x)?; - let eq_rx_mle = dense_vec_to_dense_mle(ccs.s, &eq_rx); - for M_j in ccs.M.iter() { - let mut L_i_j = vec![dense_vec_to_dense_mle( - ccs.s, - &mat_vec_mul(M_j, &z_lcccs[i])?, - )]; - L_i_j.push(eq_rx_mle.clone()); - g.add_mle_list(L_i_j.iter().map(|v| Arc::new(v.clone())), gamma_pow)?; - gamma_pow *= gamma; - } - } - - let eq_beta = build_eq_x_r_vec(beta)?; - let eq_beta_mle = Arc::new(dense_vec_to_dense_mle(ccs.s, &eq_beta)); - - #[allow(clippy::needless_range_loop)] - for k in 0..nu { - // Q_k - for (S_i, &c_i) in ccs.S.iter().zip(&ccs.c) { - let mut Q_k = vec![]; - for &j in S_i { - Q_k.push(Arc::new(dense_vec_to_dense_mle( - ccs.s, - &mat_vec_mul(&ccs.M[j], &z_cccs[k])?, - ))); - } - Q_k.push(eq_beta_mle.clone()); - g.add_mle_list(Q_k, c_i * gamma_pow)?; - } - gamma_pow *= gamma; - } - - Ok(g) -} - -#[cfg(test)] -pub mod tests { - use ark_ff::Field; - use ark_pallas::{Fr, Projective}; - use ark_std::test_rng; - use ark_std::UniformRand; - use ark_std::Zero; - - use super::*; - use crate::arith::{ - ccs::tests::{get_test_ccs, get_test_z}, - Arith, ArithRelation, - }; - use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; - use crate::folding::hypernova::lcccs::tests::compute_Ls; - use crate::utils::hypercube::BooleanHypercube; - use crate::utils::mle::matrix_to_dense_mle; - use crate::utils::multilinear_polynomial::tests::fix_last_variables; - - /// Given M(x,y) matrix and a random field element `r`, test that ~M(r,y) is an s'-variable polynomial which - /// compresses every column j of the M(x,y) matrix by performing a random linear combination between the elements - /// of the column and the values eq_i(r) where i is the row of that element - /// - /// For example, for matrix M: - /// - /// [2, 3, 4, 4 - /// 4, 4, 3, 2 - /// 2, 8, 9, 2 - /// 9, 4, 2, 0] - /// - /// The polynomial ~M(r,y) is a polynomial in F^2 which evaluates to the following values in the hypercube: - /// - M(00) = 2*eq_00(r) + 4*eq_10(r) + 2*eq_01(r) + 9*eq_11(r) - /// - M(10) = 3*eq_00(r) + 4*eq_10(r) + 8*eq_01(r) + 4*eq_11(r) - /// - M(01) = 4*eq_00(r) + 3*eq_10(r) + 9*eq_01(r) + 2*eq_11(r) - /// - M(11) = 4*eq_00(r) + 2*eq_10(r) + 2*eq_01(r) + 0*eq_11(r) - /// - /// This is used by HyperNova in LCCCS to perform a verifier-chosen random linear combination between the columns - /// of the matrix and the z vector. This technique is also used extensively in "An Algebraic Framework for - /// Universal and Updatable SNARKs". - #[test] - fn test_compute_M_r_y_compression() -> Result<(), Error> { - let mut rng = test_rng(); - - // s = 2, s' = 3 - let ccs = get_test_ccs::(); - - let M = ccs.M[0].clone().to_dense(); - let M_mle = matrix_to_dense_mle(ccs.M[0].clone()); - - // Fix the polynomial ~M(r,y) - let r: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - let M_r_y = fix_last_variables(&M_mle, &r); - - // compute M_r_y the other way around - for j in 0..M[0].len() { - // Go over every column of M - let column_j: Vec = M.clone().iter().map(|x| x[j]).collect(); - // and perform the random lincomb between the elements of the column and eq_i(r) - let rlc = BooleanHypercube::new(ccs.s) - .enumerate() - .map(|(i, x)| column_j[i] * eq_eval(&x, &r).unwrap()) - .fold(Fr::zero(), |acc, result| acc + result); - - assert_eq!(M_r_y.evaluations[j], rlc); - } - Ok(()) - } - - #[test] - fn test_compute_sigmas_thetas() -> Result<(), Error> { - let ccs = get_test_ccs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); - let (w1, x1) = ccs.split_z(&z1); - let (w2, x2) = ccs.split_z(&z2); - ccs.check_relation(&w1, &x1)?; - ccs.check_relation(&w2, &x2)?; - - let mut rng = test_rng(); - let gamma: Fr = Fr::rand(&mut rng); - let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - - // Initialize a multifolding object - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - let (lcccs_instance, _) = - ccs.to_lcccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, &z1)?; - - let sigmas_thetas = compute_sigmas_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime)?; - - let g = compute_g( - &ccs, - &[lcccs_instance.clone()], - &[z1.clone()], - &[z2.clone()], - gamma, - &beta, - )?; - - // we expect g(r_x_prime) to be equal to: - // c = (sum gamma^j * e1 * sigma_j) + gamma^{t+1} * e2 * sum c_i * prod theta_j - // from compute_c - let expected_c = g.evaluate(&r_x_prime)?; - let c = compute_c::( - &ccs, - &sigmas_thetas, - gamma, - &beta, - &vec![lcccs_instance.r_x], - &r_x_prime, - )?; - assert_eq!(c, expected_c); - Ok(()) - } - - #[test] - fn test_compute_g() -> Result<(), Error> { - let mut rng = test_rng(); - - // generate test CCS & z vectors - let ccs: CCS = get_test_ccs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); - let (w1, x1) = ccs.split_z(&z1); - let (w2, x2) = ccs.split_z(&z2); - ccs.check_relation(&w1, &x1)?; - ccs.check_relation(&w2, &x2)?; - - let gamma: Fr = Fr::rand(&mut rng); - let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); - - // Initialize a multifolding object - let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n_witnesses())?; - let (lcccs_instance, _) = - ccs.to_lcccs::<_, _, Pedersen, false>(&mut rng, &pedersen_params, &z1)?; - - // Compute g(x) with that r_x - let g = compute_g::( - &ccs, - &[lcccs_instance.clone()], - &[z1.clone()], - &[z2.clone()], - gamma, - &beta, - )?; - - // evaluate g(x) over x \in {0,1}^s - let mut g_on_bhc = Fr::zero(); - for x in BooleanHypercube::new(ccs.s) { - g_on_bhc += g.evaluate(&x)?; - } - - // Q(x) over bhc is assumed to be zero, as checked in the test 'test_compute_Q' - assert_ne!(g_on_bhc, Fr::zero()); - - let mut sum_v_j_gamma = Fr::zero(); - for j in 0..lcccs_instance.v.len() { - let gamma_j = gamma.pow([j as u64]); - sum_v_j_gamma += lcccs_instance.v[j] * gamma_j; - } - - // evaluating g(x) over the boolean hypercube should give the same result as evaluating the - // sum of gamma^j * v_j over j \in [t] - assert_eq!(g_on_bhc, sum_v_j_gamma); - - // evaluate sum_{j \in [t]} (gamma^j * Lj(x)) over x \in {0,1}^s - let mut sum_Lj_on_bhc = Fr::zero(); - let vec_L = compute_Ls(&ccs, &lcccs_instance, &z1)?; - for x in BooleanHypercube::new(ccs.s) { - for (j, Lj) in vec_L.iter().enumerate() { - let gamma_j = gamma.pow([j as u64]); - sum_Lj_on_bhc += Lj.evaluate(&x)? * gamma_j; - } - } - - // evaluating g(x) over the boolean hypercube should give the same result as evaluating the - // sum of gamma^j * Lj(x) over the boolean hypercube - assert_eq!(g_on_bhc, sum_Lj_on_bhc); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs deleted file mode 100644 index a2b23a8d7..000000000 --- a/folding-schemes/src/folding/mod.rs +++ /dev/null @@ -1,155 +0,0 @@ -pub mod circuits; -pub mod hypernova; -pub mod nova; -pub mod protogalaxy; -pub mod traits; - -#[cfg(test)] -pub mod tests { - - use ark_pallas::{Fr, Projective as G1}; - use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; - use ark_vesta::Projective as G2; - use std::io::Write; - - use crate::commitment::pedersen::Pedersen; - use crate::folding::{ - hypernova::HyperNova, - nova::{Nova, PreprocessorParam as NovaPreprocessorParam}, - protogalaxy::ProtoGalaxy, - }; - use crate::frontend::utils::CubicFCircuit; - use crate::frontend::FCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::FoldingScheme; - use crate::{Curve, Error}; - - /// tests the IVC proofs and its serializers for the 3 implemented IVCs: Nova, HyperNova and - /// ProtoGalaxy. - #[test] - fn test_serialize_ivc_nova_hypernova_protogalaxy() -> Result<(), Error> { - let poseidon_config = poseidon_canonical_config::(); - type FC = CubicFCircuit; - let f_circuit = FC::new(())?; - - // test Nova - type N = Nova, Pedersen, false>; - let prep_param = NovaPreprocessorParam::new(poseidon_config.clone(), f_circuit); - test_serialize_ivc_opt::("nova".to_string(), prep_param.clone())?; - - // test HyperNova - type HN = HyperNova< - G1, - G2, - FC, - Pedersen, - Pedersen, - 1, // mu - 1, // nu - false, - >; - test_serialize_ivc_opt::("hypernova".to_string(), prep_param)?; - - // test ProtoGalaxy - type P = ProtoGalaxy, Pedersen>; - let prep_param = (poseidon_config, f_circuit); - test_serialize_ivc_opt::("protogalaxy".to_string(), prep_param)?; - Ok(()) - } - - fn test_serialize_ivc_opt< - C1: Curve, - C2: Curve, - FC: FCircuit, - FS: FoldingScheme, - >( - name: String, - prep_param: FS::PreprocessorParam, - ) -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let F_circuit = FC::new(())?; - - let fs_params = FS::preprocess(&mut rng, &prep_param)?; - - let z_0 = vec![C1::ScalarField::from(3_u32)]; - let mut fs = FS::init(&fs_params, F_circuit, z_0.clone())?; - - // perform multiple IVC steps (internally folding) - let num_steps: usize = 3; - for _ in 0..num_steps { - fs.prove_step(&mut rng, FC::ExternalInputs::default(), None)?; - } - - // verify the IVCProof - let ivc_proof: FS::IVCProof = fs.ivc_proof(); - FS::verify(fs_params.1.clone(), ivc_proof.clone())?; - - // serialize the IVCProof and store it in a file - let mut writer = vec![]; - assert!(ivc_proof.serialize_compressed(&mut writer).is_ok()); - - let mut file = std::fs::OpenOptions::new() - .create(true) - .write(true) - .open(format!("./ivc_proof-{}.serialized", name))?; - file.write_all(&writer)?; - - // read the IVCProof from the file deserializing it - let bytes = std::fs::read(format!("./ivc_proof-{}.serialized", name))?; - let deserialized_ivc_proof = FS::IVCProof::deserialize_compressed(bytes.as_slice())?; - // verify deserialized IVCProof - FS::verify(fs_params.1.clone(), deserialized_ivc_proof.clone())?; - - // build the FS from the given IVCProof, FC::Params, ProverParams and VerifierParams - let mut new_fs = FS::from_ivc_proof(deserialized_ivc_proof, (), fs_params.clone())?; - - // serialize the Nova params - let mut fs_pp_serialized = vec![]; - fs_params.0.serialize_compressed(&mut fs_pp_serialized)?; - let mut fs_vp_serialized = vec![]; - fs_params.1.serialize_compressed(&mut fs_vp_serialized)?; - - // deserialize the Nova params. This would be done by the client reading from a file - let _fs_pp_deserialized = FS::pp_deserialize_with_mode( - &mut fs_pp_serialized.as_slice(), - ark_serialize::Compress::Yes, - ark_serialize::Validate::Yes, - (), // FCircuit's Params - )?; - - // perform several IVC steps on both the original FS instance and the recovered from the - // serialization new FS instance - let num_steps: usize = 3; - for _ in 0..num_steps { - new_fs.prove_step(&mut rng, FC::ExternalInputs::default(), None)?; - fs.prove_step(&mut rng, FC::ExternalInputs::default(), None)?; - } - - // check that the IVCProofs from both FS instances are equal - assert_eq!(new_fs.ivc_proof(), fs.ivc_proof()); - - let fs_vp_deserialized = FS::vp_deserialize_with_mode( - &mut fs_vp_serialized.as_slice(), - ark_serialize::Compress::Yes, - ark_serialize::Validate::Yes, - (), // fcircuit_params - )?; - - // get the IVCProof - let ivc_proof: FS::IVCProof = new_fs.ivc_proof(); - - // serialize IVCProof - let mut ivc_proof_serialized = vec![]; - assert!(ivc_proof - .serialize_compressed(&mut ivc_proof_serialized) - .is_ok()); - // deserialize IVCProof - let ivc_proof_deserialized = - FS::IVCProof::deserialize_compressed(ivc_proof_serialized.as_slice())?; - - // verify the last IVCProof from the recovered from serialization FS - FS::verify(fs_vp_deserialized.clone(), ivc_proof_deserialized)?; - - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs deleted file mode 100644 index 9cea0284b..000000000 --- a/folding-schemes/src/folding/nova/circuits.rs +++ /dev/null @@ -1,370 +0,0 @@ -/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits -use ark_crypto_primitives::sponge::poseidon::{ - constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge, -}; -use ark_r1cs_std::{ - alloc::AllocVar, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - GR1CSVar, -}; -use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::{fmt::Debug, Zero}; - -use super::{ - nifs::{ - nova_circuits::{CommittedInstanceVar, NIFSGadget}, - NIFSGadgetTrait, - }, - CommittedInstance, NovaCycleFoldConfig, -}; -use crate::folding::circuits::{ - cyclefold::{ - CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, - CycleFoldConfig, - }, - nonnative::affine::NonNativeAffineVar, - CF1, -}; -use crate::folding::traits::{CommittedInstanceVarOps, Dummy}; -use crate::frontend::FCircuit; -use crate::transcript::TranscriptVar; -use crate::Curve; - -/// `AugmentedFCircuit` enhances the original step function `F`, so that it can -/// be used in recursive arguments such as IVC. -/// -/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in -/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not -/// only invokes `F`, but also adds additional constraints for verifying the -/// correct folding of primary instances (i.e., Nova's `CommittedInstance`s over -/// `C1`). -/// -/// Furthermore, to reduce circuit size over `C2`, we implement the constraints -/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra -/// constraints verify the correct folding of CycleFold instances. -#[derive(Debug, Clone)] -pub struct AugmentedFCircuit>> { - pub(super) poseidon_config: PoseidonConfig>, - pub(super) pp_hash: Option>, - pub(super) i: Option>, - pub(super) i_usize: Option, - pub(super) z_0: Option>, - pub(super) z_i: Option>, - pub(super) external_inputs: Option, - pub(super) u_i_cmW: Option, - pub(super) U_i: Option>, - pub(super) U_i1_cmE: Option, - pub(super) U_i1_cmW: Option, - pub(super) cmT: Option, - pub(super) F: FC, // F circuit - - // cyclefold verifier on C1 - // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and - // cmE respectively - pub(super) cf1_u_i_cmW: Option, // input - pub(super) cf2_u_i_cmW: Option, // input - pub(super) cf_U_i: Option>, // input - pub(super) cf1_cmT: Option, - pub(super) cf2_cmT: Option, -} - -impl>> AugmentedFCircuit { - pub fn empty(poseidon_config: &PoseidonConfig>, F_circuit: FC) -> Self { - Self { - poseidon_config: poseidon_config.clone(), - pp_hash: None, - i: None, - i_usize: None, - z_0: None, - z_i: None, - external_inputs: None, - u_i_cmW: None, - U_i: None, - U_i1_cmE: None, - U_i1_cmW: None, - cmT: None, - F: F_circuit, - // cyclefold values - cf1_u_i_cmW: None, - cf2_u_i_cmW: None, - cf_U_i: None, - cf1_cmT: None, - cf2_cmT: None, - } - } -} - -impl AugmentedFCircuit -where - C1: Curve, - C2: Curve, - FC: FCircuit>, -{ - pub fn compute_next_state( - self, - cs: ConstraintSystemRef>, - ) -> Result>>, SynthesisError> { - let pp_hash = FpVar::>::new_witness(cs.clone(), || { - Ok(self.pp_hash.unwrap_or_else(CF1::::zero)) - })?; - let i = FpVar::>::new_witness(cs.clone(), || { - Ok(self.i.unwrap_or_else(CF1::::zero)) - })?; - let z_0 = Vec::>>::new_witness(cs.clone(), || { - Ok(self - .z_0 - .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) - })?; - let z_i = Vec::>>::new_witness(cs.clone(), || { - Ok(self - .z_i - .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) - })?; - let external_inputs = FC::ExternalInputsVar::new_witness(cs.clone(), || { - Ok(self.external_inputs.unwrap_or_default()) - })?; - - let u_dummy = CommittedInstance::dummy(2); - let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(self.U_i.unwrap_or(u_dummy.clone())) - })?; - let U_i1_cmE = NonNativeAffineVar::new_witness(cs.clone(), || { - Ok(self.U_i1_cmE.unwrap_or_else(C1::zero)) - })?; - let U_i1_cmW = NonNativeAffineVar::new_witness(cs.clone(), || { - Ok(self.U_i1_cmW.unwrap_or_else(C1::zero)) - })?; - - let cmT = - NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; - - let cf_u_dummy = CycleFoldCommittedInstance::dummy(NovaCycleFoldConfig::::IO_LEN); - let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) - })?; - let cf1_cmT = - C2::Var::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?; - let cf2_cmT = - C2::Var::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?; - - // `sponge` is for digest computation. - let sponge = PoseidonSpongeVar::::new_with_pp_hash( - &self.poseidon_config, - &pp_hash, - )?; - // `transcript` is for challenge generation. - let mut transcript = sponge.clone(); - - let is_basecase = i.is_zero()?; - - // Primary Part - // P.1. Compute u_i.x - // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &i, &z_0, &z_i)?; - // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge)?; - - // P.2. Construct u_i - let u_i = CommittedInstanceVar { - // u_i.cmE = cm(0) - cmE: NonNativeAffineVar::new_constant(cs.clone(), C1::zero())?, - // u_i.u = 1 - u: FpVar::one(), - // u_i.cmW is provided by the prover as witness - cmW: NonNativeAffineVar::new_witness(cs.clone(), || { - Ok(self.u_i_cmW.unwrap_or(C1::zero())) - })?, - // u_i.x is computed in step 1 - x: vec![u_i_x, cf_u_i_x], - }; - - // P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i. - // Notice that NIFSGadget::verify does not fold cmE & cmW. - // We set `U_i1.cmE` and `U_i1.cmW` to unconstrained witnesses `U_i1_cmE` and `U_i1_cmW` - // respectively. - // The correctness of them will be checked on the other curve. - let (mut U_i1, r_bits) = NIFSGadget::< - C1, - PoseidonSponge, - PoseidonSpongeVar, - >::verify( - &mut transcript, - U_i.clone(), - U_i_vec, - u_i.clone(), - Some(cmT.clone()), - )?; - U_i1.cmE = U_i1_cmE; - U_i1.cmW = U_i1_cmW; - - // P.4.a compute and check the first output of F' - - // get z_{i+1} from the F circuit - let i_usize = self.i_usize.unwrap_or(0); - let z_i1 = self - .F - .generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?; - - // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot}) - // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1}) - let (u_i1_x, _) = - U_i1.clone() - .hash(&sponge, &(i + FpVar::>::one()), &z_0, &z_i1)?; - let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash( - &sponge, - &FpVar::>::one(), - &z_0, - &z_i1, - )?; - let x = is_basecase.select(&u_i1_x_base, &u_i1_x)?; - // This line "converts" `x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `x`. - // While comparing `x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `x` computed in-circuit. - FpVar::new_input(cs.clone(), || x.value())?.enforce_equal(&x)?; - - // CycleFold part - // C.1. Compute cf1_u_i.x and cf2_u_i.x - // C.2. Construct `cf1_u_i` and `cf2_u_i` - let cf1_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( - // `cf1_u_i.cmW` is provided by the prover as witness. - C2::Var::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW.unwrap_or(C2::zero())))?, - // To construct `cf1_u_i.x`, we need to provide the randomness - // `r_bits` and the `cmW` component in committed instances `U_i`, - // `u_i`, and `U_{i+1}`. - &r_bits, - vec![U_i.cmW, u_i.cmW, U_i1.cmW], - )?; - let cf2_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( - // `cf2_u_i.cmW` is provided by the prover as witness. - C2::Var::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW.unwrap_or(C2::zero())))?, - // To construct `cf2_u_i.x`, we need to provide the randomness - // `r_bits`, the `cmE` component in running instances `U_i` and - // `U_{i+1}`, and the cross term commitment `cmT`. - &r_bits, - vec![U_i.cmE, cmT, U_i1.cmE], - )?; - - // C.3. nifs.verify, obtains cf_U_{i+1} by folding cf1_u_i and cf2_u_i into cf_U. - let cf_U_i1 = CycleFoldAugmentationGadget::fold_gadget( - &mut transcript, - cf_U_i, - vec![cf1_u_i, cf2_u_i], - vec![cf1_cmT, cf2_cmT], - )?; - - // Back to Primary Part - // P.4.b compute and check the second output of F' - // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) - // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge)?; - let (cf_u_i1_x_base, _) = - CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge)?; - let cf_x = is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?; - // This line "converts" `cf_x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `cf_x`. - // While comparing `cf_x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `cf_x` computed in-circuit. - FpVar::new_input(cs.clone(), || cf_x.value())?.enforce_equal(&cf_x)?; - - Ok(z_i1) - } -} - -impl ConstraintSynthesizer> for AugmentedFCircuit -where - C1: Curve, - C2: Curve, - FC: FCircuit>, -{ - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - self.compute_next_state(cs).map(|_| ()) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_bn254::{Fr, G1Projective as Projective}; - use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, poseidon::PoseidonSponge}; - use ark_ff::{BigInteger, PrimeField}; - - use ark_r1cs_std::prelude::Boolean; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::UniformRand; - - use crate::folding::nova::nifs::nova::ChallengeGadget; - use crate::transcript::{poseidon::poseidon_canonical_config, Transcript}; - use crate::Error; - - // checks that the gadget and native implementations of the challenge computation match - #[test] - fn test_challenge_gadget() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from(42u32); // only for testing - let mut transcript = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - - let u_i = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - let U_i = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - let cmT = Projective::rand(&mut rng); - - // compute the challenge natively - let r_bits = - ChallengeGadget::>::get_challenge_native( - &mut transcript, - &U_i, - &u_i, - Some(&cmT), - ); - let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds)?; - - let cs = ConstraintSystem::::new_ref(); - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let u_iVar = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(u_i.clone()))?; - let U_iVar = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(U_i.clone()))?; - let cmTVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(cmT))?; - let mut transcriptVar = - PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; - - // compute the challenge in-circuit - let r_bitsVar = - ChallengeGadget::>::get_challenge_gadget( - &mut transcriptVar, - U_iVar.to_sponge_field_elements()?, - u_iVar, - Some(cmTVar), - )?; - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - let rVar = Boolean::le_bits_to_fp(&r_bitsVar)?; - assert_eq!(rVar.value()?, r); - assert_eq!(r_bitsVar.value()?, r_bits); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/decider.rs b/folding-schemes/src/folding/nova/decider.rs deleted file mode 100644 index 20641b1b7..000000000 --- a/folding-schemes/src/folding/nova/decider.rs +++ /dev/null @@ -1,429 +0,0 @@ -/// This file implements the offchain decider. For ethereum use cases, use the -/// DeciderEth from decider_eth.rs file. -/// More details can be found at the documentation page: -/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html -use ark_ff::{BigInteger, PrimeField}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_snark::SNARK; -use ark_std::rand::{CryptoRng, RngCore}; -use ark_std::{One, Zero}; -use core::marker::PhantomData; - -use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2}; -use super::decider_eth_circuit::DeciderNovaGadget; -use super::Nova; -use crate::commitment::CommitmentScheme; -use crate::folding::circuits::cyclefold::CycleFoldCommittedInstance; -use crate::folding::circuits::decider::DeciderEnabledNIFS; -use crate::folding::traits::{ - CommittedInstanceOps, Dummy, Inputize, InputizeNonNative, WitnessOps, -}; -use crate::frontend::FCircuit; -use crate::transcript::poseidon::poseidon_custom_config; -use crate::{Curve, Error}; -use crate::{Decider as DeciderTrait, FoldingScheme}; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Proof -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - S1: SNARK, - S2: SNARK, -{ - c1_snark_proof: S1::Proof, - c2_snark_proof: S2::Proof, - cs1_proofs: [CS1::Proof; 2], - cs2_proofs: [CS2::Proof; 2], - // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are - // checked in-circuit - cmT: C1, - r: C1::ScalarField, - // cyclefold committed instance - cf_U_final: CycleFoldCommittedInstance, - // the CS challenges are provided by the prover, but in-circuit they are checked to match the - // in-circuit computed computed ones. - cs1_challenges: [C1::ScalarField; 2], - cs2_challenges: [C2::ScalarField; 2], -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct ProverParam -where - CS1_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - S1_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - CS2_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - S2_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, -{ - pub c1_snark_pp: S1_ProvingKey, - pub c1_cs_pp: CS1_ProvingKey, - pub c2_snark_pp: S2_ProvingKey, - pub c2_cs_pp: CS2_ProvingKey, -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct VerifierParam -where - C1: Curve, - CS1_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - S1_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - CS2_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - S2_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, -{ - pub pp_hash: C1::ScalarField, - pub c1_snark_vp: S1_VerifyingKey, - pub c1_cs_vp: CS1_VerifyingKey, - pub c2_snark_vp: S2_VerifyingKey, - pub c2_cs_vp: CS2_VerifyingKey, -} - -/// Onchain Decider, for ethereum use cases -#[derive(Clone, Debug)] -pub struct Decider { - _c1: PhantomData, - _c2: PhantomData, - _fc: PhantomData, - _cs1: PhantomData, - _cs2: PhantomData, - _s1: PhantomData, - _s2: PhantomData, - _fs: PhantomData, -} - -impl DeciderTrait - for Decider -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme< - C1, - ProverChallenge = C1::ScalarField, - Challenge = C1::ScalarField, - Proof = crate::commitment::kzg::Proof, - >, - CS2: CommitmentScheme< - C2, - ProverChallenge = C2::ScalarField, - Challenge = C2::ScalarField, - Proof = crate::commitment::kzg::Proof, - >, - S1: SNARK, - S2: SNARK, - FS: FoldingScheme, - // constrain FS into Nova, since this is a Decider specifically for Nova - Nova: From, - crate::folding::nova::ProverParams: - From<>::ProverParam>, - crate::folding::nova::VerifierParams: - From<>::VerifierParam>, -{ - type PreprocessorParam = ((FS::ProverParam, FS::VerifierParam), usize); - type ProverParam = - ProverParam; - type Proof = Proof; - type VerifierParam = VerifierParam< - C1, - CS1::VerifierParams, - S1::VerifyingKey, - CS2::VerifierParams, - S2::VerifyingKey, - >; - type PublicInput = Vec; - type CommittedInstance = Vec; - - fn preprocess( - mut rng: impl RngCore + CryptoRng, - ((pp, vp), state_len): Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - // get the FoldingScheme prover & verifier params from Nova - let nova_pp: as FoldingScheme>::ProverParam = - pp.into(); - let nova_vp: as FoldingScheme< - C1, - C2, - FC, - >>::VerifierParam = vp.into(); - let pp_hash = nova_vp.pp_hash()?; - - let poseidon_config1 = nova_vp.poseidon_config; - // Create a poseidon config on `C2`'s scalar field for `circuit2`, with - // the same parameters (`full_rounds` etc.) as `circuit1` to ensure the - // security level is the same. - // Note: `ark` and `mds` will be different because they depend on the - // field, but they will not affect the security level. - let poseidon_config2 = poseidon_custom_config( - poseidon_config1.full_rounds, - poseidon_config1.partial_rounds, - poseidon_config1.alpha, - poseidon_config1.rate, - poseidon_config1.capacity, - ); - - let circuit1 = DeciderCircuit1::::dummy(( - nova_vp.r1cs, - &nova_vp.cf_r1cs, - poseidon_config1, - (), - (), - state_len, - 2, // Nova's running CommittedInstance contains 2 commitments - )); - let circuit2 = DeciderCircuit2::::dummy(( - nova_vp.cf_r1cs, - poseidon_config2, - 2, // Nova's running CommittedInstance contains 2 commitments - )); - - // get the Groth16 specific setup for the circuits - let (c1_g16_pk, c1_g16_vk) = S1::circuit_specific_setup(circuit1, &mut rng) - .map_err(|e| Error::SNARKSetupFail(e.to_string()))?; - let (c2_g16_pk, c2_g16_vk) = S2::circuit_specific_setup(circuit2, &mut rng) - .map_err(|e| Error::SNARKSetupFail(e.to_string()))?; - - let pp = Self::ProverParam { - c1_snark_pp: c1_g16_pk, - c1_cs_pp: nova_pp.cs_pp, - c2_snark_pp: c2_g16_pk, - c2_cs_pp: nova_pp.cf_cs_pp, - }; - let vp = Self::VerifierParam { - pp_hash, - c1_snark_vp: c1_g16_vk, - c1_cs_vp: nova_vp.cs_vp, - c2_snark_vp: c2_g16_vk, - c2_cs_vp: nova_vp.cf_cs_vp, - }; - Ok((pp, vp)) - } - - fn prove( - mut rng: impl RngCore + CryptoRng, - pp: Self::ProverParam, - fs: FS, - ) -> Result { - let circuit1 = DeciderCircuit1::::try_from(Nova::from(fs.clone()))?; - let circuit2 = DeciderCircuit2::::try_from(Nova::from(fs))?; - - let cmT = circuit1.proof; - let r = circuit1.randomness; - let cf_U_final = circuit1.cf_U_i.clone(); - - let c1_kzg_challenges = circuit1.kzg_challenges.clone(); - let c1_kzg_proofs = circuit1 - .W_i1 - .get_openings() - .iter() - .zip(&c1_kzg_challenges) - .map(|((v, _), &c)| { - CS1::prove_with_challenge(&pp.c1_cs_pp, c, v, &C1::ScalarField::zero(), None) - }) - .collect::, _>>()?; - let c2_kzg_challenges = circuit2.kzg_challenges.clone(); - let c2_kzg_proofs = circuit2 - .cf_W_i - .get_openings() - .iter() - .zip(&c2_kzg_challenges) - .map(|((v, _), &c)| { - CS2::prove_with_challenge(&pp.c2_cs_pp, c, v, &C2::ScalarField::zero(), None) - }) - .collect::, _>>()?; - - let c1_snark_proof = S1::prove(&pp.c1_snark_pp, circuit1, &mut rng) - .map_err(|e| Error::Other(e.to_string()))?; - let c2_snark_proof = S2::prove(&pp.c2_snark_pp, circuit2, &mut rng) - .map_err(|e| Error::Other(e.to_string()))?; - - Ok(Self::Proof { - c1_snark_proof, - c2_snark_proof, - cs1_proofs: c1_kzg_proofs - .try_into() - .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?, - cs2_proofs: c2_kzg_proofs - .try_into() - .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?, - cmT, - r, - cf_U_final, - cs1_challenges: c1_kzg_challenges - .try_into() - .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?, - cs2_challenges: c2_kzg_challenges - .try_into() - .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?, - }) - } - - fn verify( - vp: Self::VerifierParam, - i: C1::ScalarField, - z_0: Vec, - z_i: Vec, - // we don't use the instances at the verifier level, since we check them in-circuit - running_commitments: &Self::CommittedInstance, - incoming_commitments: &Self::CommittedInstance, - proof: &Self::Proof, - ) -> Result { - if i <= C1::ScalarField::one() { - return Err(Error::NotEnoughSteps); - } - - // 6.2. Fold the commitments - let U_final_commitments = DeciderNovaGadget::fold_group_elements_native( - running_commitments, - incoming_commitments, - Some(proof.cmT), - proof.r, - )?; - let cf_U = proof.cf_U_final.clone(); - - // snark proof 1 - let c1_public_input = [ - &[vp.pp_hash, i][..], - &z_0, - &z_i, - &U_final_commitments.inputize_nonnative(), - &cf_U.inputize_nonnative(), - &proof.cs1_challenges, - &proof.cs1_proofs.iter().map(|p| p.eval).collect::>(), - &proof.cmT.inputize_nonnative(), - ] - .concat(); - - let c1_snark_v = S1::verify(&vp.c1_snark_vp, &c1_public_input, &proof.c1_snark_proof) - .map_err(|e| Error::Other(e.to_string()))?; - if !c1_snark_v { - return Err(Error::SNARKVerificationFail); - } - - // snark proof 2 - // migrate pp_hash from C1::Fr to C1::Fq - let pp_hash_Fq = - C2::ScalarField::from_le_bytes_mod_order(&vp.pp_hash.into_bigint().to_bytes_le()); - let c2_public_input: Vec = [ - &[pp_hash_Fq][..], - &cf_U.inputize(), - &proof.cs2_challenges, - &proof.cs2_proofs.iter().map(|p| p.eval).collect::>(), - ] - .concat(); - - let c2_snark_v = S2::verify(&vp.c2_snark_vp, &c2_public_input, &proof.c2_snark_proof) - .map_err(|e| Error::Other(e.to_string()))?; - if !c2_snark_v { - return Err(Error::SNARKVerificationFail); - } - - // 7.3. check C1 commitments (main instance commitments) - for ((cm, &c), pi) in U_final_commitments - .iter() - .zip(&proof.cs1_challenges) - .zip(&proof.cs1_proofs) - { - CS1::verify_with_challenge(&vp.c1_cs_vp, c, cm, pi)?; - } - - // 4.3. check C2 commitments (CycleFold instance commitments) - for ((cm, &c), pi) in cf_U - .get_commitments() - .iter() - .zip(&proof.cs2_challenges) - .zip(&proof.cs2_proofs) - { - CS2::verify_with_challenge(&vp.c2_cs_vp, c, cm, pi)?; - } - - Ok(true) - } -} - -#[cfg(test)] -pub mod tests { - use ark_groth16::Groth16; - - // Note: do not use the MNTx_298 curves in practice, these are just for tests. Use the MNTx_753 - // curves instead. - use ark_mnt4_298::{Fr, G1Projective as Projective, MNT4_298 as MNT4}; - use ark_mnt6_298::{G1Projective as Projective2, MNT6_298 as MNT6}; - use std::time::Instant; - - use super::*; - use crate::commitment::kzg::KZG; - use crate::folding::nova::PreprocessorParam; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_decider() -> Result<(), Error> { - // use Nova as FoldingScheme - type N = Nova< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, MNT4>, - KZG<'static, MNT6>, - false, - >; - type D = Decider< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, MNT4>, - KZG<'static, MNT6>, - Groth16, - Groth16, - N, // here we define the FoldingScheme to use - >; - - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - let start = Instant::now(); - let prep_param = PreprocessorParam::new(poseidon_config, F_circuit); - let nova_params = N::preprocess(&mut rng, &prep_param)?; - println!("Nova preprocess, {:?}", start.elapsed()); - - let start = Instant::now(); - let mut nova = N::init(&nova_params, F_circuit, z_0.clone())?; - println!("Nova initialized, {:?}", start.elapsed()); - let start = Instant::now(); - nova.prove_step(&mut rng, (), None)?; - println!("prove_step, {:?}", start.elapsed()); - nova.prove_step(&mut rng, (), None)?; // do a 2nd step - - let mut rng = rand::rngs::OsRng; - - // prepare the Decider prover & verifier params - let start = Instant::now(); - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (nova_params, F_circuit.state_len()))?; - println!("Decider preprocess, {:?}", start.elapsed()); - - // decider proof generation - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, nova.clone())?; - println!("Decider prove, {:?}", start.elapsed()); - - // decider proof verification - let start = Instant::now(); - let verified = D::verify( - decider_vp, - nova.i, - nova.z_0, - nova.z_i, - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider verify, {:?}", start.elapsed()); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs deleted file mode 100644 index 707691ce4..000000000 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ /dev/null @@ -1,226 +0,0 @@ -/// This file implements the offchain decider circuit. For ethereum use cases, use the -/// DeciderEthCircuit. -/// More details can be found at the documentation page: -/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html -use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::fields::fp::FpVar; -use core::marker::PhantomData; - -use super::{ - decider_eth_circuit::DeciderNovaGadget, - nifs::{nova::NIFS, NIFSTrait}, - CommittedInstance, Nova, Witness, -}; -use crate::{ - arith::r1cs::{circuits::R1CSMatricesVar, R1CS}, - commitment::CommitmentScheme, - folding::{ - circuits::{ - decider::{ - off_chain::{GenericOffchainDeciderCircuit1, GenericOffchainDeciderCircuit2}, - EvalGadget, KZGChallengesGadget, - }, - CF1, - }, - traits::WitnessOps, - }, - frontend::FCircuit, - transcript::{poseidon::poseidon_custom_config, Transcript}, - Curve, Error, -}; - -/// Circuit that implements part of the in-circuit checks needed for the offchain verification over -/// the Curve2's BaseField (=Curve1's ScalarField). -pub type DeciderCircuit1 = GenericOffchainDeciderCircuit1< - C1, - C2, - CommittedInstance, - CommittedInstance, - Witness, - R1CS>, - R1CSMatricesVar, FpVar>>, - DeciderNovaGadget, ->; - -impl< - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - > TryFrom> for DeciderCircuit1 -{ - type Error = Error; - - fn try_from(nova: Nova) -> Result { - let mut transcript = PoseidonSponge::new_with_pp_hash(&nova.poseidon_config, nova.pp_hash); - // pp_hash is absorbed to transcript at the NIFS::prove call - - // compute the U_{i+1}, W_{i+1} - let (W_i1, U_i1, cmT, r_bits) = NIFS::, H>::prove( - &nova.cs_pp, - &nova.r1cs.clone(), - &mut transcript, - &nova.W_i, - &nova.U_i, - &nova.w_i, - &nova.u_i, - )?; - let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - // compute the KZG challenges used as inputs in the circuit - let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1); - - // get KZG evals - let kzg_evaluations = W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c)) - .collect::, _>>()?; - - Ok(Self { - _avar: PhantomData, - arith: nova.r1cs, - poseidon_config: nova.poseidon_config, - pp_hash: nova.pp_hash, - i: nova.i, - z_0: nova.z_0, - z_i: nova.z_i, - U_i: nova.U_i, - W_i: nova.W_i, - u_i: nova.u_i, - w_i: nova.w_i, - U_i1, - W_i1, - proof: cmT, - randomness: r_Fr, - cf_U_i: nova.cf_U_i, - kzg_challenges, - kzg_evaluations, - }) - } -} - -/// Circuit that implements part of the in-circuit checks needed for the offchain verification over -/// the Curve1's BaseField (=Curve2's ScalarField). -pub type DeciderCircuit2 = GenericOffchainDeciderCircuit2; - -impl< - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - > TryFrom> for DeciderCircuit2 -{ - type Error = Error; - - fn try_from(nova: Nova) -> Result { - // Create a poseidon config on `C2`'s scalar field for `circuit2`, with - // the same parameters (`full_rounds` etc.) as `circuit1` to ensure the - // security level is the same. - // Note: `ark` and `mds` will be different because they depend on the - // field, but they will not affect the security level. - let poseidon_config = poseidon_custom_config( - nova.poseidon_config.full_rounds, - nova.poseidon_config.partial_rounds, - nova.poseidon_config.alpha, - nova.poseidon_config.rate, - nova.poseidon_config.capacity, - ); - let pp_hash_Fq = - C2::ScalarField::from_le_bytes_mod_order(&nova.pp_hash.into_bigint().to_bytes_le()); - let mut transcript = - PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash_Fq); - - // compute the KZG challenges used as inputs in the circuit - let kzg_challenges = - KZGChallengesGadget::get_challenges_native(&mut transcript, &nova.cf_U_i); - - // get KZG evals - let kzg_evaluations = nova - .cf_W_i - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c)) - .collect::, _>>()?; - - Ok(Self { - cf_arith: nova.cf_r1cs, - poseidon_config, - pp_hash: pp_hash_Fq, - cf_U_i: nova.cf_U_i, - cf_W_i: nova.cf_W_i, - kzg_challenges, - kzg_evaluations, - }) - } -} - -#[cfg(test)] -pub mod tests { - use ark_pallas::{Fq, Fr, Projective}; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - use ark_vesta::Projective as Projective2; - - use super::*; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::PreprocessorParam; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::FoldingScheme; - - #[test] - fn test_decider_circuits() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - type N = Nova< - Projective, - Projective2, - CubicFCircuit, - Pedersen, - Pedersen, - false, - >; - - let prep_param = PreprocessorParam::< - Projective, - Projective2, - CubicFCircuit, - Pedersen, - Pedersen, - false, - >::new(poseidon_config, F_circuit); - let nova_params = N::preprocess(&mut rng, &prep_param)?; - - // generate a Nova instance and do a step of it - let mut nova = N::init(&nova_params, F_circuit, z_0.clone())?; - nova.prove_step(&mut rng, (), None)?; - // verify the IVC - let ivc_proof = nova.ivc_proof(); - N::verify(nova_params.1, ivc_proof)?; - - // load the DeciderCircuit 1 & 2 from the Nova instance - let decider_circuit1 = DeciderCircuit1::::try_from(nova.clone())?; - let decider_circuit2 = DeciderCircuit2::::try_from(nova)?; - - // generate the constraints of both circuits and check that are satisfied by the inputs - let cs1 = ConstraintSystem::::new_ref(); - decider_circuit1.generate_constraints(cs1.clone())?; - assert!(cs1.is_satisfied()?); - let cs2 = ConstraintSystem::::new_ref(); - decider_circuit2.generate_constraints(cs2.clone())?; - assert!(cs2.is_satisfied()?); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs deleted file mode 100644 index 155d9e278..000000000 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ /dev/null @@ -1,499 +0,0 @@ -/// This file implements the Nova's onchain (Ethereum's EVM) decider. For non-ethereum use cases, -/// the Decider from decider.rs file will be more efficient. -/// More details can be found at the documentation page: -/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_snark::SNARK; -use ark_std::{ - rand::{CryptoRng, RngCore}, - One, Zero, -}; -use core::marker::PhantomData; - -pub use super::decider_eth_circuit::DeciderEthCircuit; -use super::decider_eth_circuit::DeciderNovaGadget; -use super::Nova; -use crate::folding::circuits::decider::DeciderEnabledNIFS; -use crate::folding::traits::{InputizeNonNative, WitnessOps}; -use crate::frontend::FCircuit; -use crate::{ - commitment::{kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme}, - folding::traits::Dummy, -}; -use crate::{Curve, Error}; -use crate::{Decider as DeciderTrait, FoldingScheme}; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Proof -where - C: Curve, - CS: CommitmentScheme, - S: SNARK, -{ - snark_proof: S::Proof, - kzg_proofs: [CS::Proof; 2], - // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are - // checked in-circuit - cmT: C, - r: C::ScalarField, - // the KZG challenges are provided by the prover, but in-circuit they are checked to match - // the in-circuit computed ones. - kzg_challenges: [C::ScalarField; 2], -} - -impl Proof -where - C: Curve, - CS: CommitmentScheme, - S: SNARK, -{ - pub fn snark_proof(&self) -> &S::Proof { - &self.snark_proof - } - - pub fn kzg_proofs(&self) -> &[CS::Proof; 2] { - &self.kzg_proofs - } - - pub fn cmT(&self) -> &C { - &self.cmT - } - - pub fn r(&self) -> C::ScalarField { - self.r - } - - pub fn kzg_challenges(&self) -> [C::ScalarField; 2] { - self.kzg_challenges - } -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct VerifierParam -where - C1: Curve, - CS_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - S_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, -{ - pub pp_hash: C1::ScalarField, - pub snark_vp: S_VerifyingKey, - pub cs_vp: CS_VerifyingKey, -} - -/// Onchain Decider, for ethereum use cases -#[derive(Clone, Debug)] -pub struct Decider { - _c1: PhantomData, - _c2: PhantomData, - _fc: PhantomData, - _cs1: PhantomData, - _cs2: PhantomData, - _s: PhantomData, - _fs: PhantomData, -} - -impl DeciderTrait - for Decider -where - C1: Curve, - C2: Curve, - FC: FCircuit, - // CS1 is a KZG commitment, where challenge is C1::Fr elem - CS1: CommitmentScheme< - C1, - ProverChallenge = C1::ScalarField, - Challenge = C1::ScalarField, - Proof = KZGProof, - >, - // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider - CS2: CommitmentScheme>, - S: SNARK, - FS: FoldingScheme, - // constrain FS into Nova, since this is a Decider specifically for Nova - Nova: From, - crate::folding::nova::ProverParams: - From<>::ProverParam>, - crate::folding::nova::VerifierParams: - From<>::VerifierParam>, -{ - type PreprocessorParam = ((FS::ProverParam, FS::VerifierParam), usize); - type ProverParam = (S::ProvingKey, CS1::ProverParams); - type Proof = Proof; - type VerifierParam = VerifierParam; - type PublicInput = Vec; - type CommittedInstance = Vec; - - fn preprocess( - mut rng: impl RngCore + CryptoRng, - ((pp, vp), state_len): Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - // get the FoldingScheme prover & verifier params from Nova - let nova_pp: as FoldingScheme>::ProverParam = - pp.into(); - let nova_vp: as FoldingScheme< - C1, - C2, - FC, - >>::VerifierParam = vp.into(); - - let pp_hash = nova_vp.pp_hash()?; - - let circuit = DeciderEthCircuit::::dummy(( - nova_vp.r1cs, - nova_vp.cf_r1cs, - nova_pp.cf_cs_pp, - nova_pp.poseidon_config, - (), - (), - state_len, - 2, // Nova's running CommittedInstance contains 2 commitments - )); - - // get the Groth16 specific setup for the circuit - let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng) - .map_err(|e| Error::SNARKSetupFail(e.to_string()))?; - - let pp = (g16_pk, nova_pp.cs_pp); - let vp = Self::VerifierParam { - pp_hash, - snark_vp: g16_vk, - cs_vp: nova_vp.cs_vp, - }; - Ok((pp, vp)) - } - - fn prove( - mut rng: impl RngCore + CryptoRng, - pp: Self::ProverParam, - folding_scheme: FS, - ) -> Result { - let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp; - - let circuit = DeciderEthCircuit::::try_from(Nova::from(folding_scheme))?; - - let cmT = circuit.proof; - let r = circuit.randomness; - - // get the challenges that have been already computed when preparing the circuit inputs in - // the above `try_from` call - let kzg_challenges = circuit.kzg_challenges.clone(); - - // generate KZG proofs - let kzg_proofs = circuit - .W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| { - CS1::prove_with_challenge(&cs_pk, c, v, &C1::ScalarField::zero(), None) - }) - .collect::, _>>()?; - - let snark_proof = - S::prove(&snark_pk, circuit, &mut rng).map_err(|e| Error::Other(e.to_string()))?; - - Ok(Self::Proof { - snark_proof, - cmT, - r, - kzg_proofs: kzg_proofs - .try_into() - .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?, - kzg_challenges: kzg_challenges - .try_into() - .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?, - }) - } - - fn verify( - vp: Self::VerifierParam, - i: C1::ScalarField, - z_0: Vec, - z_i: Vec, - // we don't use the instances at the verifier level, since we check them in-circuit - running_commitments: &Self::CommittedInstance, - incoming_commitments: &Self::CommittedInstance, - proof: &Self::Proof, - ) -> Result { - if i <= C1::ScalarField::one() { - return Err(Error::NotEnoughSteps); - } - - let Self::VerifierParam { - pp_hash, - snark_vp, - cs_vp, - } = vp; - - // 6.2. Fold the commitments - let U_final_commitments = DeciderNovaGadget::fold_group_elements_native( - running_commitments, - incoming_commitments, - Some(proof.cmT), - proof.r, - )?; - - let public_input = [ - &[pp_hash, i][..], - &z_0, - &z_i, - &U_final_commitments.inputize_nonnative(), - &proof.kzg_challenges, - &proof.kzg_proofs.iter().map(|p| p.eval).collect::>(), - &proof.cmT.inputize_nonnative(), - ] - .concat(); - - let snark_v = S::verify(&snark_vp, &public_input, &proof.snark_proof) - .map_err(|e| Error::Other(e.to_string()))?; - if !snark_v { - return Err(Error::SNARKVerificationFail); - } - - // 7.3. Verify the KZG proofs - for ((cm, &c), pi) in U_final_commitments - .iter() - .zip(&proof.kzg_challenges) - .zip(&proof.kzg_proofs) - { - // we're at the Ethereum EVM case, so the CS1 is KZG commitments - CS1::verify_with_challenge(&cs_vp, c, cm, pi)?; - } - - Ok(true) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::commitment::kzg::KZG; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::{PreprocessorParam, ProverParams as NovaProverParams}; - use crate::folding::traits::CommittedInstanceOps; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - use ark_bn254::{Bn254, Fr, G1Projective as Projective}; - use ark_groth16::Groth16; - use ark_grumpkin::Projective as Projective2; - use std::time::Instant; - - #[test] - fn test_decider() -> Result<(), Error> { - // use Nova as FoldingScheme - type N = Nova< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - false, - >; - type D = Decider< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, // here we define the Snark to use in the decider - N, // here we define the FoldingScheme to use - >; - - let mut rng = rand::rngs::OsRng; - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - let preprocessor_param = PreprocessorParam::new(poseidon_config, F_circuit); - let nova_params = N::preprocess(&mut rng, &preprocessor_param)?; - - let start = Instant::now(); - let mut nova = N::init(&nova_params, F_circuit, z_0.clone())?; - println!("Nova initialized, {:?}", start.elapsed()); - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (nova_params, F_circuit.state_len()))?; - - let start = Instant::now(); - nova.prove_step(&mut rng, (), None)?; - println!("prove_step, {:?}", start.elapsed()); - nova.prove_step(&mut rng, (), None)?; // do a 2nd step - - // decider proof generation - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, nova.clone())?; - println!("Decider prove, {:?}", start.elapsed()); - - // decider proof verification - let start = Instant::now(); - let verified = D::verify( - decider_vp.clone(), - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider verify, {:?}", start.elapsed()); - - // decider proof verification using the deserialized data - let verified = D::verify( - decider_vp, - nova.i, - nova.z_0, - nova.z_i, - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - Ok(()) - } - - // Test to check the serialization and deserialization of diverse Decider related parameters. - // This test is the same test as `test_decider` but it serializes values and then uses the - // deserialized values to continue the checks. - #[test] - fn test_decider_serialization() -> Result<(), Error> { - // use Nova as FoldingScheme - type N = Nova< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - false, - >; - type D = Decider< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, // here we define the Snark to use in the decider - N, // here we define the FoldingScheme to use - >; - - let mut rng = rand::rngs::OsRng; - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - let preprocessor_param = PreprocessorParam::new(poseidon_config, F_circuit); - let nova_params = N::preprocess(&mut rng, &preprocessor_param)?; - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (nova_params.clone(), F_circuit.state_len()))?; - - // serialize the Nova params. These params are the trusted setup of the commitment schemes used - // (ie. KZG & Pedersen in this case) - let mut nova_pp_serialized = vec![]; - nova_params - .0 - .serialize_compressed(&mut nova_pp_serialized)?; - let mut nova_vp_serialized = vec![]; - nova_params - .1 - .serialize_compressed(&mut nova_vp_serialized)?; - // deserialize the Nova params. This would be done by the client reading from a file - let nova_pp_deserialized = NovaProverParams::< - Projective, - Projective2, - KZG<'static, Bn254>, - Pedersen, - >::deserialize_compressed( - &mut nova_pp_serialized.as_slice() - )?; - let nova_vp_deserialized = , - >>::vp_deserialize_with_mode( - &mut nova_vp_serialized.as_slice(), - ark_serialize::Compress::Yes, - ark_serialize::Validate::Yes, - (), // fcircuit_params - )?; - - // initialize nova again, but from the deserialized parameters - let nova_params = (nova_pp_deserialized, nova_vp_deserialized); - let mut nova = N::init(&nova_params, F_circuit, z_0)?; - - let start = Instant::now(); - nova.prove_step(&mut rng, (), None)?; - println!("prove_step, {:?}", start.elapsed()); - nova.prove_step(&mut rng, (), None)?; // do a 2nd step - - // decider proof generation - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, nova.clone())?; - println!("Decider prove, {:?}", start.elapsed()); - - // decider proof verification - let start = Instant::now(); - let verified = D::verify( - decider_vp.clone(), - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider verify, {:?}", start.elapsed()); - - // The rest of this test will serialize the data and deserialize it back, and use it to - // verify the proof: - - // serialize the verifier_params, proof and public inputs - let mut decider_vp_serialized = vec![]; - decider_vp.serialize_compressed(&mut decider_vp_serialized)?; - let mut proof_serialized = vec![]; - proof.serialize_compressed(&mut proof_serialized)?; - // serialize the public inputs in a single packet - let mut public_inputs_serialized = vec![]; - nova.i.serialize_compressed(&mut public_inputs_serialized)?; - nova.z_0 - .serialize_compressed(&mut public_inputs_serialized)?; - nova.z_i - .serialize_compressed(&mut public_inputs_serialized)?; - - // deserialize back the verifier_params, proof and public inputs - let decider_vp_deserialized = - VerifierParam::< - Projective, - as CommitmentScheme>::VerifierParams, - as SNARK>::VerifyingKey, - >::deserialize_compressed(&mut decider_vp_serialized.as_slice())?; - let proof_deserialized = - Proof::, Groth16>::deserialize_compressed( - &mut proof_serialized.as_slice(), - )?; - - // deserialize the public inputs from the single packet 'public_inputs_serialized' - let mut reader = public_inputs_serialized.as_slice(); - let i_deserialized = Fr::deserialize_compressed(&mut reader)?; - let z_0_deserialized = Vec::::deserialize_compressed(&mut reader)?; - let z_i_deserialized = Vec::::deserialize_compressed(&mut reader)?; - - // decider proof verification using the deserialized data - let verified = D::verify( - decider_vp_deserialized, - i_deserialized, - z_0_deserialized, - z_i_deserialized, - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof_deserialized, - )?; - assert!(verified); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs deleted file mode 100644 index e03853fa1..000000000 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ /dev/null @@ -1,258 +0,0 @@ -/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, -/// other more efficient approaches can be used. -/// More details can be found at the documentation page: -/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html -use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - fields::fp::FpVar, - GR1CSVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, marker::PhantomData}; - -use super::{ - nifs::nova_circuits::{CommittedInstanceVar, NIFSGadget}, - nifs::{nova::NIFS, NIFSGadgetTrait, NIFSTrait}, - CommittedInstance, Nova, Witness, -}; -use crate::{ - arith::r1cs::{circuits::R1CSMatricesVar, R1CS}, - commitment::{pedersen::Params as PedersenParams, CommitmentScheme}, - folding::{ - circuits::{ - decider::{ - on_chain::GenericOnchainDeciderCircuit, DeciderEnabledNIFS, EvalGadget, - KZGChallengesGadget, - }, - nonnative::affine::NonNativeAffineVar, - CF1, - }, - traits::{WitnessOps, WitnessVarOps}, - }, - frontend::FCircuit, - transcript::Transcript, - Curve, Error, -}; - -/// In-circuit representation of the Witness associated to the CommittedInstance. -#[derive(Debug, Clone)] -pub struct WitnessVar { - pub E: Vec>, - pub rE: FpVar, - pub W: Vec>, - pub rW: FpVar, -} - -impl AllocVar, CF1> for WitnessVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let E: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?; - let rE = - FpVar::::new_variable(cs.clone(), || Ok(val.borrow().rE), mode)?; - - let W: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?; - let rW = - FpVar::::new_variable(cs.clone(), || Ok(val.borrow().rW), mode)?; - - Ok(Self { E, rE, W, rW }) - }) - } -} - -impl WitnessVarOps for WitnessVar { - fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { - vec![(&self.W, self.rW.clone()), (&self.E, self.rE.clone())] - } -} - -pub type DeciderEthCircuit = GenericOnchainDeciderCircuit< - C1, - C2, - CommittedInstance, - CommittedInstance, - Witness, - R1CS>, - R1CSMatricesVar, FpVar>>, - DeciderNovaGadget, ->; - -/// returns an instance of the DeciderEthCircuit from the given Nova struct -impl< - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider - CS2: CommitmentScheme>, - const H: bool, - > TryFrom> for DeciderEthCircuit -{ - type Error = Error; - - fn try_from(nova: Nova) -> Result { - let mut transcript = PoseidonSponge::new_with_pp_hash(&nova.poseidon_config, nova.pp_hash); - - // compute the U_{i+1}, W_{i+1} - let (W_i1, U_i1, cmT, r_bits) = NIFS::, H>::prove( - &nova.cs_pp, - &nova.r1cs.clone(), - &mut transcript, - &nova.W_i, - &nova.U_i, - &nova.w_i, - &nova.u_i, - )?; - let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - // compute the KZG challenges used as inputs in the circuit - let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1); - - // get KZG evals - let kzg_evaluations = W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c)) - .collect::, _>>()?; - - Ok(Self { - _avar: PhantomData, - arith: nova.r1cs, - cf_arith: nova.cf_r1cs, - cf_pedersen_params: nova.cf_cs_pp, - poseidon_config: nova.poseidon_config, - pp_hash: nova.pp_hash, - i: nova.i, - z_0: nova.z_0, - z_i: nova.z_i, - U_i: nova.U_i, - W_i: nova.W_i, - u_i: nova.u_i, - w_i: nova.w_i, - U_i1, - W_i1, - proof: cmT, - randomness: r_Fr, - cf_U_i: nova.cf_U_i, - cf_W_i: nova.cf_W_i, - kzg_challenges, - kzg_evaluations, - }) - } -} - -pub struct DeciderNovaGadget; - -impl - DeciderEnabledNIFS, CommittedInstance, Witness, R1CS>> - for DeciderNovaGadget -{ - type ProofDummyCfg = (); - type Proof = C; - type RandomnessDummyCfg = (); - type Randomness = CF1; - - fn fold_field_elements_gadget( - _arith: &R1CS>, - transcript: &mut PoseidonSpongeVar>, - U: CommittedInstanceVar, - U_vec: Vec>>, - u: CommittedInstanceVar, - proof: C, - _randomness: CF1, - ) -> Result, SynthesisError> { - let cs = U.u.cs(); - let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(proof))?; - let (new_U, _) = NIFSGadget::verify(transcript, U, U_vec, u, Some(cmT))?; - Ok(new_U) - } - - fn fold_group_elements_native( - U_commitments: &[C], - u_commitments: &[C], - cmT: Option, - r: Self::Randomness, - ) -> Result, Error> { - let cmT = cmT.ok_or(Error::Empty)?; - let U_cmW = U_commitments[0]; - let U_cmE = U_commitments[1]; - let u_cmW = u_commitments[0]; - let u_cmE = u_commitments[1]; - if !u_cmE.is_zero() { - return Err(Error::NotIncomingCommittedInstance); - } - let cmW = U_cmW + u_cmW.mul(r); - let cmE = U_cmE + cmT.mul(r); - Ok(vec![cmW, cmE]) - } -} - -#[cfg(test)] -pub mod tests { - use ark_pallas::{Fr, Projective}; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - use ark_vesta::Projective as Projective2; - - use super::*; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::PreprocessorParam; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::FoldingScheme; - - #[test] - fn test_decider_circuit() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - type N = Nova< - Projective, - Projective2, - CubicFCircuit, - Pedersen, - Pedersen, - false, - >; - - let prep_param = PreprocessorParam::< - Projective, - Projective2, - CubicFCircuit, - Pedersen, - Pedersen, - false, - >::new(poseidon_config, F_circuit); - let nova_params = N::preprocess(&mut rng, &prep_param)?; - - // generate a Nova instance and do a step of it - let mut nova = N::init(&nova_params, F_circuit, z_0.clone())?; - nova.prove_step(&mut rng, (), None)?; - let ivc_proof = nova.ivc_proof(); - N::verify(nova_params.1, ivc_proof)?; - - // load the DeciderEthCircuit from the generated Nova instance - let decider_circuit = DeciderEthCircuit::::try_from(nova)?; - - let cs = ConstraintSystem::::new_ref(); - - // generate the constraints and check that are satisfied by the inputs - decider_circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs deleted file mode 100644 index 817d0d321..000000000 --- a/folding-schemes/src/folding/nova/mod.rs +++ /dev/null @@ -1,1167 +0,0 @@ -/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and -/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). -/// -/// The structure of the Nova code is the following: -/// - NIFS implementation for Nova (nifs.rs), Mova (mova.rs), Ova (ova.rs) -/// - IVC and the Decider (offchain Decider & onchain Decider) implementations for Nova -use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - Absorb, -}; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{alloc::AllocVar, prelude::Boolean, GR1CSVar}; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, -}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; -use ark_std::{cmp::max, fmt::Debug, rand::RngCore, One, UniformRand, Zero}; - -use crate::arith::{ - r1cs::{extract_r1cs, extract_w_x, R1CS}, - Arith, ArithRelation, -}; -use crate::commitment::CommitmentScheme; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::{ - circuits::{ - cyclefold::{ - CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldConfig, - CycleFoldWitness, - }, - CF1, - }, - traits::Dummy, -}; -use crate::frontend::FCircuit; -use crate::transcript::{poseidon::poseidon_canonical_config, Transcript}; -use crate::utils::{pp_hash, vec::is_zero_vec}; -use crate::{Curve, Error, FoldingScheme}; -use decider_eth_circuit::WitnessVar; - -pub mod circuits; -pub mod traits; -pub mod zk; - -// NIFS related: -pub mod nifs; - -use circuits::AugmentedFCircuit; -use nifs::{nova::NIFS, nova_circuits::CommittedInstanceVar, NIFSTrait}; - -// offchain decider -pub mod decider; -pub mod decider_circuits; -// onchain decider -pub mod decider_eth; -pub mod decider_eth_circuit; - -use super::{ - circuits::{cyclefold::CycleFoldCircuit, CF2}, - traits::{CommittedInstanceOps, Inputize, WitnessOps}, -}; - -/// Configuration for Nova's CycleFold circuit -pub struct NovaCycleFoldConfig { - r: Vec, - points: Vec, -} - -impl Default for NovaCycleFoldConfig { - fn default() -> Self { - Self { - r: vec![false; NOVA_N_BITS_RO], - points: vec![C::zero(); 2], - } - } -} - -impl CycleFoldConfig for NovaCycleFoldConfig { - const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; - // Number of points to be folded in the CycleFold circuit, in Nova's case, this is a fixed - // amount: - // 2 points to be folded. - const N_INPUT_POINTS: usize = 2; - const N_UNIQUE_RANDOMNESSES: usize = 1; - - fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError> { - let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; - for point in &points { - Self::mark_point_as_public(point)?; - } - Ok(points) - } - - fn alloc_randomnesses( - &self, - cs: ConstraintSystemRef>, - ) -> Result>>>, SynthesisError> { - let one = &CF1::::one().into_bigint().to_bits_le()[..NOVA_N_BITS_RO]; - let one_var = Vec::new_constant(cs.clone(), one)?; - let r_var = Vec::new_witness(cs.clone(), || Ok(self.r.clone()))?; - Self::mark_randomness_as_public(&r_var)?; - Ok(vec![one_var, r_var]) - } -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CommittedInstance { - pub cmE: C, - pub u: C::ScalarField, - pub cmW: C, - pub x: Vec, -} - -impl Dummy for CommittedInstance { - fn dummy(io_len: usize) -> Self { - Self { - cmE: C::zero(), - u: CF1::::zero(), - cmW: C::zero(), - x: vec![CF1::::zero(); io_len], - } - } -} - -impl Dummy<&R1CS>> for CommittedInstance { - fn dummy(r1cs: &R1CS>) -> Self { - Self::dummy(r1cs.n_public_inputs()) - } -} - -impl Absorb for CommittedInstance { - fn to_sponge_bytes(&self, dest: &mut Vec) { - C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.u.to_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - self.cmE.to_native_sponge_field_elements(dest); - self.cmW.to_native_sponge_field_elements(dest); - } -} - -impl CommittedInstanceOps for CommittedInstance { - type Var = CommittedInstanceVar; - - fn get_commitments(&self) -> Vec { - vec![self.cmW, self.cmE] - } - - fn is_incoming(&self) -> bool { - self.cmE == C::zero() && self.u == One::one() - } -} - -impl Inputize> for CommittedInstance { - /// Returns the internal representation in the same order as how the value - /// is allocated in `CommittedInstanceVar::new_input`. - fn inputize(&self) -> Vec> { - [ - &[self.u][..], - &self.x, - &self.cmE.inputize_nonnative(), - &self.cmW.inputize_nonnative(), - ] - .concat() - } -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Witness { - pub E: Vec, - pub rE: C::ScalarField, - pub W: Vec, - pub rW: C::ScalarField, -} - -impl Witness { - pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { - let (rW, rE) = if H { - ( - C::ScalarField::rand(&mut rng), - C::ScalarField::rand(&mut rng), - ) - } else { - (C::ScalarField::zero(), C::ScalarField::zero()) - }; - - Self { - E: vec![C::ScalarField::zero(); e_len], - rE, - W: w, - rW, - } - } - - pub fn commit, const HC: bool>( - &self, - params: &CS::ProverParams, - x: Vec, - ) -> Result, Error> { - let mut cmE = C::zero(); - if !is_zero_vec::(&self.E) { - cmE = CS::commit(params, &self.E, &self.rE)?; - } - let cmW = CS::commit(params, &self.W, &self.rW)?; - Ok(CommittedInstance { - cmE, - u: C::ScalarField::one(), - cmW, - x, - }) - } -} - -impl Dummy<&R1CS>> for Witness { - fn dummy(r1cs: &R1CS>) -> Self { - Self { - E: vec![C::ScalarField::zero(); r1cs.n_constraints()], - rE: C::ScalarField::zero(), - W: vec![C::ScalarField::zero(); r1cs.n_witnesses()], - rW: C::ScalarField::zero(), - } - } -} - -impl WitnessOps for Witness { - type Var = WitnessVar; - - fn get_openings(&self) -> Vec<(&[C::ScalarField], C::ScalarField)> { - vec![(&self.W, self.rW), (&self.E, self.rE)] - } -} - -#[derive(Debug, Clone)] -pub struct PreprocessorParam -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - pub poseidon_config: PoseidonConfig, - pub F: FC, - // cs params if not provided, will be generated at the preprocess method - pub cs_pp: Option, - pub cs_vp: Option, - pub cf_cs_pp: Option, - pub cf_cs_vp: Option, -} - -impl PreprocessorParam -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - pub fn new(poseidon_config: PoseidonConfig, F: FC) -> Self { - Self { - poseidon_config, - F, - cs_pp: None, - cs_vp: None, - cf_cs_pp: None, - cf_cs_vp: None, - } - } -} - -/// Proving parameters for Nova-based IVC -#[derive(Debug, Clone)] -pub struct ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// Poseidon sponge configuration - pub poseidon_config: PoseidonConfig, - /// Proving parameters of the underlying commitment scheme over C1 - pub cs_pp: CS1::ProverParams, - /// Proving parameters of the underlying commitment scheme over C2 - pub cf_cs_pp: CS2::ProverParams, -} - -impl Valid for ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn check(&self) -> Result<(), ark_serialize::SerializationError> { - self.poseidon_config.full_rounds.check()?; - self.poseidon_config.partial_rounds.check()?; - self.poseidon_config.alpha.check()?; - self.poseidon_config.ark.check()?; - self.poseidon_config.mds.check()?; - self.poseidon_config.rate.check()?; - self.poseidon_config.capacity.check()?; - self.cs_pp.check()?; - self.cf_cs_pp.check()?; - Ok(()) - } -} -impl CanonicalSerialize for ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.cs_pp.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_pp.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress) - } -} -impl CanonicalDeserialize for ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn deserialize_with_mode( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - ) -> Result { - let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - Ok(ProverParams { - poseidon_config: poseidon_canonical_config::(), - cs_pp, - cf_cs_pp, - }) - } -} - -/// Verification parameters for Nova-based IVC -#[derive(Debug, Clone)] -pub struct VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// Poseidon sponge configuration - pub poseidon_config: PoseidonConfig, - /// R1CS of the Augmented step circuit - pub r1cs: R1CS, - /// R1CS of the CycleFold circuit - pub cf_r1cs: R1CS, - /// Verification parameters of the underlying commitment scheme over C1 - pub cs_vp: CS1::VerifierParams, - /// Verification parameters of the underlying commitment scheme over C2 - pub cf_cs_vp: CS2::VerifierParams, -} - -impl Valid for VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn check(&self) -> Result<(), ark_serialize::SerializationError> { - self.cs_vp.check()?; - self.cf_cs_vp.check()?; - Ok(()) - } -} -impl CanonicalSerialize for VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.cs_vp.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_vp.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.cs_vp.serialized_size(compress) + self.cf_cs_vp.serialized_size(compress) - } -} - -impl VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// returns the hash of the public parameters of Nova - pub fn pp_hash(&self) -> Result { - pp_hash::( - &self.r1cs, - &self.cf_r1cs, - &self.cs_vp, - &self.cf_cs_vp, - &self.poseidon_config, - ) - } -} - -#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] -pub struct IVCProof -where - C1: Curve, - C2: Curve, -{ - // current step of the IVC - pub i: C1::ScalarField, - // initial state - pub z_0: Vec, - // current state - pub z_i: Vec, - // running instance - pub W_i: Witness, - pub U_i: CommittedInstance, - // incoming instance - pub w_i: Witness, - pub u_i: CommittedInstance, - // CycleFold instances - pub cf_W_i: CycleFoldWitness, - pub cf_U_i: CycleFoldCommittedInstance, -} - -/// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and -/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait -/// The `H` const generic specifies whether the homorphic commitment scheme is blinding -#[derive(Clone, Debug)] -pub struct Nova -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// R1CS of the Augmented Function circuit - pub r1cs: R1CS, - /// R1CS of the CycleFold circuit - pub cf_r1cs: R1CS, - pub poseidon_config: PoseidonConfig, - /// CommitmentScheme::ProverParams over C1 - pub cs_pp: CS1::ProverParams, - /// CycleFold CommitmentScheme::ProverParams, over C2 - pub cf_cs_pp: CS2::ProverParams, - /// F circuit, the circuit that is being folded - pub F: FC, - /// public params hash - pub pp_hash: C1::ScalarField, - pub i: C1::ScalarField, - /// initial state - pub z_0: Vec, - /// current i-th state - pub z_i: Vec, - /// Nova instances - pub w_i: Witness, - pub u_i: CommittedInstance, - pub W_i: Witness, - pub U_i: CommittedInstance, - - /// CycleFold running instance - pub cf_W_i: CycleFoldWitness, - pub cf_U_i: CycleFoldCommittedInstance, -} - -impl FoldingScheme - for Nova -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - C1: Curve, -{ - type PreprocessorParam = PreprocessorParam; - type ProverParam = ProverParams; - type VerifierParam = VerifierParams; - type RunningInstance = (CommittedInstance, Witness); - type IncomingInstance = (CommittedInstance, Witness); - type MultiCommittedInstanceWithWitness = (); - type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); - type IVCProof = IVCProof; - - fn pp_deserialize_with_mode( - reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - _fc_params: FC::Params, // FCircuit params - ) -> Result { - Ok(Self::ProverParam::deserialize_with_mode( - reader, compress, validate, - )?) - } - fn vp_deserialize_with_mode( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - fc_params: FC::Params, - ) -> Result { - let poseidon_config = poseidon_canonical_config::(); - - // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing - // to serialize them, saving significant space in the VerifierParams serialized size. - - // main circuit R1CS: - let f_circuit = FC::new(fc_params)?; - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - let augmented_F_circuit = - AugmentedFCircuit::::empty(&poseidon_config, f_circuit.clone()); - augmented_F_circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - - // CycleFold circuit R1CS - let cs2 = ConstraintSystem::::new_ref(); - cs2.set_mode(SynthesisMode::Setup); - let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); - cf_circuit.generate_constraints(cs2.clone())?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let cf_r1cs = extract_r1cs::(&cs2)?; - - let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - - Ok(Self::VerifierParam { - poseidon_config, - r1cs, - cf_r1cs, - cs_vp, - cf_cs_vp, - }) - } - - fn preprocess( - mut rng: impl RngCore, - prep_param: &Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - let (r1cs, cf_r1cs) = - get_r1cs::(&prep_param.poseidon_config, prep_param.F.clone())?; - - // if cs params exist, use them, if not, generate new ones - let (cs_pp, cs_vp) = match (&prep_param.cs_pp, &prep_param.cs_vp) { - (Some(cs_pp), Some(cs_vp)) => (cs_pp.clone(), cs_vp.clone()), - _ => CS1::setup( - &mut rng, - // `CS1` is for committing to Nova's witness vector `w` and - // error term `e`, where the length of `e` is the number of - // constraints, so we set `len` to the maximum of `e` and `w`'s - // lengths. - max(r1cs.n_constraints(), r1cs.n_witnesses()), - )?, - }; - let (cf_cs_pp, cf_cs_vp) = match (&prep_param.cf_cs_pp, &prep_param.cf_cs_vp) { - (Some(cf_cs_pp), Some(cf_cs_vp)) => (cf_cs_pp.clone(), cf_cs_vp.clone()), - _ => CS2::setup( - &mut rng, - // `CS2` is for committing to CycleFold's witness vector `w` and - // error term `e`, where the length of `e` is the number of - // constraints, so we set `len` to the maximum of `e` and `w`'s - // lengths. - max(cf_r1cs.n_constraints(), cf_r1cs.n_witnesses()), - )?, - }; - - let prover_params = ProverParams:: { - poseidon_config: prep_param.poseidon_config.clone(), - cs_pp: cs_pp.clone(), - cf_cs_pp: cf_cs_pp.clone(), - }; - let verifier_params = VerifierParams:: { - poseidon_config: prep_param.poseidon_config.clone(), - r1cs, - cf_r1cs, - cs_vp, - cf_cs_vp, - }; - - Ok((prover_params, verifier_params)) - } - - /// Initializes the Nova+CycleFold's IVC for the given parameters and initial state `z_0`. - fn init( - params: &(Self::ProverParam, Self::VerifierParam), - F: FC, - z_0: Vec, - ) -> Result { - let (pp, vp) = params; - - // prepare the circuit to obtain its R1CS - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - let cs2 = ConstraintSystem::::new_ref(); - cs2.set_mode(SynthesisMode::Setup); - - let augmented_F_circuit = - AugmentedFCircuit::::empty(&pp.poseidon_config, F.clone()); - let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); - - augmented_F_circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - - cf_circuit.generate_constraints(cs2.clone())?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let cf_r1cs = extract_r1cs::(&cs2)?; - - // compute the public params hash - let pp_hash = vp.pp_hash()?; - - // setup the dummy instances - let (W_dummy, U_dummy) = r1cs.dummy_witness_instance(); - let (w_dummy, u_dummy) = r1cs.dummy_witness_instance(); - let (cf_W_dummy, cf_U_dummy) = cf_r1cs.dummy_witness_instance(); - - // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the - // R1CS that we're working with. - Ok(Self { - r1cs, - cf_r1cs, - poseidon_config: pp.poseidon_config.clone(), - cs_pp: pp.cs_pp.clone(), - cf_cs_pp: pp.cf_cs_pp.clone(), - F, - pp_hash, - i: C1::ScalarField::zero(), - z_0: z_0.clone(), - z_i: z_0, - w_i: w_dummy, - u_i: u_dummy, - W_i: W_dummy, - U_i: U_dummy, - // cyclefold running instance - cf_W_i: cf_W_dummy, - cf_U_i: cf_U_dummy, - }) - } - - /// Implements IVC.P of Nova+CycleFold - fn prove_step( - &mut self, - mut rng: impl RngCore, - external_inputs: FC::ExternalInputs, - // Nova does not support multi-instances folding (by design) - _other_instances: Option, - ) -> Result<(), Error> { - // ensure that commitments are blinding if user has specified so. - if H && self.i >= C1::ScalarField::one() { - let blinding_commitments = if self.i == C1::ScalarField::one() { - // blinding values of the running instances are zero at the first iteration - vec![self.w_i.rW, self.w_i.rE] - } else { - vec![self.w_i.rW, self.w_i.rE, self.W_i.rW, self.W_i.rE] - }; - if blinding_commitments.contains(&C1::ScalarField::zero()) { - return Err(Error::IncorrectBlinding( - H, - format!("{blinding_commitments:?}"), - )); - } - } - // `sponge` is for digest computation. - let sponge = PoseidonSponge::::new_with_pp_hash( - &self.poseidon_config, - self.pp_hash, - ); - // `transcript` is for challenge generation. - let mut transcript = sponge.clone(); - - let augmented_F_circuit: AugmentedFCircuit; - - // Nova does not support (by design) multi-instances folding - if _other_instances.is_some() { - return Err(Error::NoMultiInstances); - } - - if self.z_i.len() != self.F.state_len() { - return Err(Error::NotSameLength( - "z_i.len()".to_string(), - self.z_i.len(), - "F.state_len()".to_string(), - self.F.state_len(), - )); - } - - if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) { - return Err(Error::MaxStep); - } - - let i_usize; - - #[cfg(target_pointer_width = "64")] - { - let mut i_bytes: [u8; 8] = [0; 8]; - i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]); - i_usize = usize::from_le_bytes(i_bytes); - } - - #[cfg(target_pointer_width = "32")] - { - let mut i_bytes: [u8; 4] = [0; 4]; - i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..4]); - i_usize = usize::from_le_bytes(i_bytes); - } - - // fold Nova instances - let (W_i1, U_i1, cmT, r_bits): (Witness, CommittedInstance, C1, Vec) = - NIFS::, H>::prove( - &self.cs_pp, - &self.r1cs, - &mut transcript, - &self.W_i, - &self.U_i, - &self.w_i, - &self.u_i, - )?; - - if self.i == C1::ScalarField::zero() { - // base case - augmented_F_circuit = AugmentedFCircuit:: { - poseidon_config: self.poseidon_config.clone(), - pp_hash: Some(self.pp_hash), - i: Some(C1::ScalarField::zero()), // = i=0 - i_usize: Some(0), - z_0: Some(self.z_0.clone()), // = z_i - z_i: Some(self.z_i.clone()), - external_inputs: Some(external_inputs.clone()), - u_i_cmW: Some(self.u_i.cmW), // = dummy - U_i: Some(self.U_i.clone()), // = dummy - U_i1_cmE: Some(U_i1.cmE), - U_i1_cmW: Some(U_i1.cmW), - cmT: Some(cmT), - F: self.F.clone(), - cf1_u_i_cmW: None, - cf2_u_i_cmW: None, - cf_U_i: None, - cf1_cmT: None, - cf2_cmT: None, - }; - - #[cfg(test)] - { - let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - let expected = - NIFS::, H>::fold_committed_instances( - r_Fr, &self.U_i, &self.u_i, &cmT, - ); - assert_eq!(U_i1, expected); - } - } else { - // CycleFold part: - let (cfW_w_i, cfW_u_i) = NovaCycleFoldConfig { - r: r_bits.clone(), - points: vec![self.U_i.clone().cmW, self.u_i.clone().cmW], - } - .build_circuit() - .generate_incoming_instance_witness::<_, CS2, H>(&self.cf_cs_pp, &mut rng)?; - let (cfE_w_i, cfE_u_i) = NovaCycleFoldConfig { - r: r_bits.clone(), - points: vec![self.U_i.clone().cmE, cmT], - } - .build_circuit() - .generate_incoming_instance_witness::<_, CS2, H>(&self.cf_cs_pp, &mut rng)?; - - let (cf_W_i1, cf_U_i1, cf_cmTs) = CycleFoldAugmentationGadget::fold_native::<_, CS2, H>( - &mut transcript, - &self.cf_r1cs, - &self.cf_cs_pp, - self.cf_W_i.clone(), - self.cf_U_i.clone(), - vec![cfW_w_i, cfE_w_i], - vec![cfW_u_i.clone(), cfE_u_i.clone()], - )?; - - augmented_F_circuit = AugmentedFCircuit:: { - poseidon_config: self.poseidon_config.clone(), - pp_hash: Some(self.pp_hash), - i: Some(self.i), - i_usize: Some(i_usize), - z_0: Some(self.z_0.clone()), - z_i: Some(self.z_i.clone()), - external_inputs: Some(external_inputs.clone()), - u_i_cmW: Some(self.u_i.cmW), - U_i: Some(self.U_i.clone()), - U_i1_cmE: Some(U_i1.cmE), - U_i1_cmW: Some(U_i1.cmW), - cmT: Some(cmT), - F: self.F.clone(), - // cyclefold values - cf1_u_i_cmW: Some(cfW_u_i.cmW), - cf2_u_i_cmW: Some(cfE_u_i.cmW), - cf_U_i: Some(self.cf_U_i.clone()), - cf1_cmT: Some(cf_cmTs[0]), - cf2_cmT: Some(cf_cmTs[1]), - }; - - self.cf_W_i = cf_W_i1; - self.cf_U_i = cf_U_i1; - } - - let cs = ConstraintSystem::::new_ref(); - - let z_i1 = augmented_F_circuit - .compute_next_state(cs.clone())? - .value()?; - - #[cfg(test)] - assert!(cs.is_satisfied()?); - - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (w_i1, x_i1) = extract_w_x::(&cs); - - #[cfg(test)] - if x_i1.len() != 2 { - return Err(Error::NotExpectedLength(x_i1.len(), 2)); - } - - // set values for next iteration - self.i += C1::ScalarField::one(); - self.z_i = z_i1; - self.w_i = Witness::::new::(w_i1, self.r1cs.n_constraints(), &mut rng); - self.u_i = self.w_i.commit::(&self.cs_pp, x_i1)?; - self.W_i = W_i1; - self.U_i = U_i1; - - #[cfg(test)] - { - self.u_i.check_incoming()?; - self.r1cs.check_relation(&self.w_i, &self.u_i)?; - self.r1cs.check_relation(&self.W_i, &self.U_i)?; - } - - Ok(()) - } - - fn state(&self) -> Vec { - self.z_i.clone() - } - - fn ivc_proof(&self) -> Self::IVCProof { - Self::IVCProof { - i: self.i, - z_0: self.z_0.clone(), - z_i: self.z_i.clone(), - W_i: self.W_i.clone(), - U_i: self.U_i.clone(), - w_i: self.w_i.clone(), - u_i: self.u_i.clone(), - cf_W_i: self.cf_W_i.clone(), - cf_U_i: self.cf_U_i.clone(), - } - } - - fn from_ivc_proof( - ivc_proof: IVCProof, - fcircuit_params: FC::Params, - params: (Self::ProverParam, Self::VerifierParam), - ) -> Result { - let IVCProof { - i, - z_0, - z_i, - W_i, - U_i, - w_i, - u_i, - cf_W_i, - cf_U_i, - } = ivc_proof; - let (pp, vp) = params; - - let f_circuit = FC::new(fcircuit_params)?; - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - let cs2 = ConstraintSystem::::new_ref(); - cs2.set_mode(SynthesisMode::Setup); - let augmented_F_circuit = - AugmentedFCircuit::::empty(&pp.poseidon_config, f_circuit.clone()); - let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); - - augmented_F_circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - - cf_circuit.generate_constraints(cs2.clone())?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let cf_r1cs = extract_r1cs::(&cs2)?; - - Ok(Self { - r1cs, - cf_r1cs, - poseidon_config: pp.poseidon_config, - cs_pp: pp.cs_pp, - cf_cs_pp: pp.cf_cs_pp, - F: f_circuit, - pp_hash: vp.pp_hash()?, - i, - z_0, - z_i, - w_i, - u_i, - W_i, - U_i, - cf_W_i, - cf_U_i, - }) - } - - /// Implements IVC.V of Nov.clone()a+CycleFold. Notice that this method does not include the - /// commitments verification, which is done in the Decider. - fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> { - let Self::IVCProof { - i: num_steps, - z_0, - z_i, - W_i, - U_i, - w_i, - u_i, - cf_W_i, - cf_U_i, - } = ivc_proof; - - let sponge = - PoseidonSponge::::new_with_pp_hash(&vp.poseidon_config, vp.pp_hash()?); - - if num_steps == C1::ScalarField::zero() { - if z_0 != z_i { - return Err(Error::IVCVerificationFail); - } - return Ok(()); - } - - if u_i.x.len() != 2 || U_i.x.len() != 2 { - return Err(Error::IVCVerificationFail); - } - - // check that u_i's output points to the running instance - // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, num_steps, &z_0, &z_i); - if expected_u_i_x != u_i.x[0] { - return Err(Error::IVCVerificationFail); - } - // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge); - if expected_cf_u_i_x != u_i.x[1] { - return Err(Error::IVCVerificationFail); - } - - // check R1CS satisfiability, which is equivalent to checking if `u_i` - // is an incoming instance and if `w_i` and `u_i` satisfy RelaxedR1CS - u_i.check_incoming()?; - vp.r1cs.check_relation(&w_i, &u_i)?; - // check RelaxedR1CS satisfiability - vp.r1cs.check_relation(&W_i, &U_i)?; - - // check CycleFold RelaxedR1CS satisfiability - vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?; - - Ok(()) - } -} - -/// helper method to get the r1cs from the ConstraintSynthesizer -pub fn get_r1cs_from_cs( - circuit: impl ConstraintSynthesizer, -) -> Result, Error> { - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - Ok(r1cs) -} - -/// helper method to get the R1CS for both the AugmentedFCircuit and the CycleFold circuit -#[allow(clippy::type_complexity)] -pub fn get_r1cs( - poseidon_config: &PoseidonConfig, - F_circuit: FC, -) -> Result<(R1CS, R1CS), Error> -where - C1: Curve, - C2: Curve, - FC: FCircuit, - C1: Curve, -{ - let augmented_F_circuit = AugmentedFCircuit::::empty(poseidon_config, F_circuit); - let cf_circuit = CycleFoldCircuit::<_, NovaCycleFoldConfig>::default(); - let r1cs = get_r1cs_from_cs::(augmented_F_circuit)?; - let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; - Ok((r1cs, cf_r1cs)) -} - -#[cfg(test)] -pub mod tests { - use crate::commitment::kzg::KZG; - use ark_bn254::{Bn254, Fr, G1Projective as Projective}; - use ark_grumpkin::Projective as Projective2; - - use super::*; - use crate::commitment::pedersen::Pedersen; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - - /// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the - /// AugmentedFCircuit - #[test] - fn test_ivc() -> Result<(), Error> { - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - - // run the test using Pedersen commitments on both sides of the curve cycle - let _ = test_ivc_opt::, Pedersen, false>( - poseidon_config.clone(), - F_circuit, - 3, - )?; - - let _ = test_ivc_opt::, Pedersen, true>( - poseidon_config.clone(), - F_circuit, - 3, - )?; - - // run the test using KZG for the commitments on the main curve, and Pedersen for the - // commitments on the secondary curve - let _ = test_ivc_opt::, Pedersen, false>( - poseidon_config, - F_circuit, - 3, - )?; - Ok(()) - } - - // test_ivc allowing to choose the CommitmentSchemes - #[allow(clippy::type_complexity)] - pub(crate) fn test_ivc_opt< - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - >( - poseidon_config: PoseidonConfig, - F_circuit: CubicFCircuit, - num_steps: usize, - ) -> Result< - ( - Vec, - Nova, CS1, CS2, H>, - ), - Error, - > { - let mut rng = ark_std::test_rng(); - - let prep_param = - PreprocessorParam::, CS1, CS2, H> { - poseidon_config, - F: F_circuit, - cs_pp: None, - cs_vp: None, - cf_cs_pp: None, - cf_cs_vp: None, - }; - let nova_params = - Nova::, CS1, CS2, H>::preprocess( - &mut rng, - &prep_param, - )?; - - let z_0 = vec![Fr::from(3_u32)]; - let mut nova = Nova::, CS1, CS2, H>::init( - &nova_params, - F_circuit, - z_0.clone(), - )?; - - for _ in 0..num_steps { - nova.prove_step(&mut rng, (), None)?; - } - assert_eq!(Fr::from(num_steps as u32), nova.i); - - // serialize the Nova Prover & Verifier params. These params are the trusted setup of the commitment schemes used - let mut nova_pp_serialized = vec![]; - nova_params - .0 - .serialize_compressed(&mut nova_pp_serialized)?; - let mut nova_vp_serialized = vec![]; - nova_params - .1 - .serialize_compressed(&mut nova_vp_serialized)?; - - // deserialize the Nova params - let _nova_pp_deserialized = - ProverParams::::deserialize_compressed( - &mut nova_pp_serialized.as_slice(), - )?; - let nova_vp_deserialized = Nova::< - Projective, - Projective2, - CubicFCircuit, - CS1, - CS2, - H, - >::vp_deserialize_with_mode( - &mut nova_vp_serialized.as_slice(), - ark_serialize::Compress::Yes, - ark_serialize::Validate::Yes, - (), // fcircuit_params - )?; - - let ivc_proof = nova.ivc_proof(); - - // serialize IVCProof - let mut ivc_proof_serialized = vec![]; - assert!(ivc_proof - .serialize_compressed(&mut ivc_proof_serialized) - .is_ok()); - // deserialize IVCProof - let ivc_proof_deserialized = - , CS1, CS2, H> as FoldingScheme< - Projective, - Projective2, - CubicFCircuit, - >>::IVCProof::deserialize_compressed(ivc_proof_serialized.as_slice())?; - - // verify the deserialized IVCProof with the deserialized VerifierParams - Nova::, CS1, CS2, H>::verify( - nova_vp_deserialized, // Nova's verifier params - ivc_proof_deserialized, - )?; - Ok((z_0, nova)) - } -} diff --git a/folding-schemes/src/folding/nova/nifs/mod.rs b/folding-schemes/src/folding/nova/nifs/mod.rs deleted file mode 100644 index 652649a4e..000000000 --- a/folding-schemes/src/folding/nova/nifs/mod.rs +++ /dev/null @@ -1,322 +0,0 @@ -/// This module defines the traits related to the NIFS (Non-Interactive Folding Scheme). -/// - NIFSTrait, which implements the NIFS interface -/// - NIFSGadget, which implements the NIFS in-circuit -/// -/// Both traits implemented by the various Nova variants schemes; ie. -/// - [Nova](https://eprint.iacr.org/2021/370.pdf) -/// - [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) -/// - [Mova](https://eprint.iacr.org/2024/1220.pdf) -use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb, CryptographicSponge}; -use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, fields::fp::FpVar}; -use ark_relations::gr1cs::SynthesisError; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; - -use crate::arith::r1cs::R1CS; -use crate::commitment::CommitmentScheme; -use crate::folding::circuits::CF1; -use crate::folding::traits::{CommittedInstanceOps, CommittedInstanceVarOps}; -use crate::transcript::{Transcript, TranscriptVar}; -use crate::{Curve, Error}; - -pub mod mova; -pub mod nova; -pub mod nova_circuits; -pub mod ova; -pub mod ova_circuits; -pub mod pointvsline; - -/// Defines the NIFS (Non-Interactive Folding Scheme) trait, initially defined in -/// [Nova](https://eprint.iacr.org/2021/370.pdf), and it's variants -/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) and -/// [Mova](https://eprint.iacr.org/2024/1220.pdf). -/// `H` specifies whether the NIFS will use a blinding factor. -pub trait NIFSTrait< - C: Curve, - CS: CommitmentScheme, - T: Transcript, - const H: bool = false, -> -{ - type CommittedInstance: Debug + Clone + Absorb; // + CommittedInstanceOps; - type Witness: Debug + Clone; - type ProverAux: Debug + Clone; // Prover's aux params. eg. in Nova is T - type Proof: Debug + Clone; // proof. eg. in Nova is cmT - - fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness; - - fn new_instance( - rng: impl RngCore, - params: &CS::ProverParams, - w: &Self::Witness, - x: Vec, - aux: Vec, // t_or_e in Ova, empty for Nova - ) -> Result; - - fn fold_witness( - r: C::ScalarField, - W: &Self::Witness, // running witness - w: &Self::Witness, // incoming witness - aux: &Self::ProverAux, - ) -> Result; - - /// NIFS.P. Returns a tuple containing the folded Witness, the folded CommittedInstance, and - /// the used challenge `r` as a vector of bits, so that it can be reused in other methods. - #[allow(clippy::type_complexity)] - #[allow(clippy::too_many_arguments)] - fn prove( - cs_prover_params: &CS::ProverParams, - r1cs: &R1CS, - transcript: &mut T, - W_i: &Self::Witness, // running witness - U_i: &Self::CommittedInstance, // running committed instance - w_i: &Self::Witness, // incoming witness - u_i: &Self::CommittedInstance, // incoming committed instance - ) -> Result< - ( - Self::Witness, - Self::CommittedInstance, - Self::Proof, - Vec, - ), - Error, - >; - - /// NIFS.V. Returns the folded CommittedInstance and the used challenge `r` as a vector of - /// bits, so that it can be reused in other methods. - fn verify( - transcript: &mut T, - U_i: &Self::CommittedInstance, - u_i: &Self::CommittedInstance, - proof: &Self::Proof, - ) -> Result<(Self::CommittedInstance, Vec), Error>; -} - -/// Defines the NIFS (Non-Interactive Folding Scheme) Gadget trait, which specifies the in-circuit -/// logic of the NIFS.Verify defined in [Nova](https://eprint.iacr.org/2021/370.pdf) and it's -/// variants [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) and -/// [Mova](https://eprint.iacr.org/2024/1220.pdf). -pub trait NIFSGadgetTrait, S>> { - type CommittedInstance: Debug + Clone + Absorb + CommittedInstanceOps; - type CommittedInstanceVar: Debug - + Clone - + AbsorbGadget - + AllocVar> - + CommittedInstanceVarOps; - type Proof: Debug + Clone; - type ProofVar: Debug + Clone + AllocVar>; - - /// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to - /// the CycleFold circuit. - #[allow(clippy::type_complexity)] - fn verify( - transcript: &mut T, - U_i: Self::CommittedInstanceVar, - // U_i_vec is passed to reuse the already computed U_i_vec from previous methods - U_i_vec: Vec>>, - u_i: Self::CommittedInstanceVar, - proof: Option, - ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError>; -} - -/// These tests are the generic tests so that in the tests of Nova, Mova, Ova, we just need to -/// instantiate these tests to test both the NIFSTrait and NIFSGadgetTrait implementations for each -/// of the schemes. -#[cfg(test)] -pub mod tests { - use ark_crypto_primitives::sponge::{ - constraints::AbsorbGadget, - poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, - Absorb, - }; - use ark_pallas::{Fr, Projective}; - use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{cmp::max, test_rng, UniformRand}; - - use super::NIFSTrait; - use super::*; - use crate::arith::{ - r1cs::tests::{get_test_r1cs, get_test_z}, - Arith, - }; - use crate::commitment::pedersen::Pedersen; - use crate::folding::traits::{CommittedInstanceOps, CommittedInstanceVarOps}; - use crate::transcript::poseidon::poseidon_canonical_config; - - /// Test method used to test the different implementations of the NIFSTrait (ie. Nova, Mova, - /// Ova). Runs a loop using the NIFS trait, and returns the last Witness and CommittedInstance - /// so that their relation can be checked. - pub(crate) fn test_nifs_opt< - N: NIFSTrait, PoseidonSponge>, - >() -> Result<(N::Witness, N::CommittedInstance), Error> { - let r1cs: R1CS = get_test_r1cs(); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = - Pedersen::::setup(&mut rng, max(r1cs.n_constraints(), r1cs.n_witnesses()))?; - - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::rand(&mut rng); - let mut transcript_p = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - let mut transcript_v = transcript_p.clone(); - - // prepare the running instance - let z = get_test_z(3); - let (w, x) = r1cs.split_z(&z); - let mut W_i = N::new_witness(w.clone(), r1cs.n_constraints(), test_rng()); - let mut U_i = N::new_instance(&mut rng, &pedersen_params, &W_i, x, vec![])?; - - let num_iters = 10; - for i in 0..num_iters { - // prepare the incoming instance - let incoming_instance_z = get_test_z(i + 4); - let (w, x) = r1cs.split_z(&incoming_instance_z); - let w_i = N::new_witness(w.clone(), r1cs.n_constraints(), test_rng()); - let u_i = N::new_instance(&mut rng, &pedersen_params, &w_i, x, vec![])?; - - // NIFS.P - let (folded_witness, _, proof, _) = N::prove( - &pedersen_params, - &r1cs, - &mut transcript_p, - &W_i, - &U_i, - &w_i, - &u_i, - )?; - - // NIFS.V - let (folded_committed_instance, _) = N::verify(&mut transcript_v, &U_i, &u_i, &proof)?; - - // set running_instance for next loop iteration - W_i = folded_witness; - U_i = folded_committed_instance; - } - - Ok((W_i, U_i)) - } - - /// Test method used to test the different implementations of the NIFSGadgetTrait (ie. Nova, - /// Mova, Ova). It returns the last Witness and CommittedInstance so that it can be checked at - /// the parent test that their values match. - pub(crate) fn test_nifs_gadget_opt( - ci: Vec, - proof: NG::Proof, - ) -> Result<(NG::CommittedInstance, NG::CommittedInstanceVar), Error> - where - N: NIFSTrait, PoseidonSponge>, - NG: NIFSGadgetTrait< - Projective, - PoseidonSponge, - PoseidonSpongeVar, - CommittedInstance = N::CommittedInstance, // constrain that N::CI==NG::CI - Proof = N::Proof, // constrain that N::Proof==NG::Proof - >, - { - let mut rng = ark_std::test_rng(); - - let (U_i, u_i) = (ci[0].clone(), ci[1].clone()); - let pp_hash = Fr::rand(&mut rng); - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - let (ci3, _) = N::verify(&mut transcript, &U_i, &u_i, &proof)?; - - let cs = ConstraintSystem::::new_ref(); - - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let mut transcriptVar = - PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; - let ci1Var = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(U_i.clone()))?; - let ci2Var = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(u_i.clone()))?; - let proofVar = NG::ProofVar::new_witness(cs.clone(), || Ok(proof))?; - - let ci1Var_vec = ci1Var.to_sponge_field_elements()?; - let (out, _) = NG::verify( - &mut transcriptVar, - ci1Var.clone(), - ci1Var_vec, - ci2Var.clone(), - Some(proofVar.clone()), - )?; - assert!(cs.is_satisfied()?); - - // return the NIFS.V and the NIFSGadget.V obtained values, so that they are checked at the - // parent test - Ok((ci3, out)) - } - - /// test that checks the native CommittedInstance.to_sponge_{bytes,field_elements} - /// vs the R1CS constraints version - pub(crate) fn test_committed_instance_to_sponge_preimage_opt( - ci: N::CommittedInstance, - ) -> Result<(), Error> - where - N: NIFSTrait, PoseidonSponge>, - NG: NIFSGadgetTrait< - Projective, - PoseidonSponge, - PoseidonSpongeVar, - CommittedInstance = N::CommittedInstance, // constrain that N::CI==NG::CI - >, - { - let bytes = ci.to_sponge_bytes_as_vec(); - let field_elements = ci.to_sponge_field_elements_as_vec(); - - let cs = ConstraintSystem::::new_ref(); - - let ciVar = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(ci.clone()))?; - let bytes_var = ciVar.to_sponge_bytes()?; - let field_elements_var = ciVar.to_sponge_field_elements()?; - - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(bytes_var.value()?, bytes); - assert_eq!(field_elements_var.value()?, field_elements); - Ok(()) - } - - pub(crate) fn test_committed_instance_hash_opt( - ci: NG::CommittedInstance, - ) -> Result<(), Error> - where - N: NIFSTrait, PoseidonSponge>, - NG: NIFSGadgetTrait< - Projective, - PoseidonSponge, - PoseidonSpongeVar, - CommittedInstance = N::CommittedInstance, // constrain that N::CI==NG::CI - >, - N::CommittedInstance: CommittedInstanceOps, - { - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from(42u32); // only for test - let sponge = PoseidonSponge::::new_with_pp_hash(&poseidon_config, pp_hash); - - let i = Fr::from(3_u32); - let z_0 = vec![Fr::from(3_u32)]; - let z_i = vec![Fr::from(3_u32)]; - - // compute the CommittedInstance hash natively - let h = ci.hash(&sponge, i, &z_0, &z_i); - - let cs = ConstraintSystem::::new_ref(); - - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; - let iVar = FpVar::::new_witness(cs.clone(), || Ok(i))?; - let z_0Var = Vec::>::new_witness(cs.clone(), || Ok(z_0.clone()))?; - let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone()))?; - let ciVar = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(ci.clone()))?; - - let sponge = PoseidonSpongeVar::::new_with_pp_hash(&poseidon_config, &pp_hashVar)?; - - // compute the CommittedInstance hash in-circuit - let (hVar, _) = ciVar.hash(&sponge, &iVar, &z_0Var, &z_iVar)?; - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(hVar.value()?, h); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/nifs/mova.rs b/folding-schemes/src/folding/nova/nifs/mova.rs deleted file mode 100644 index 06c511be4..000000000 --- a/folding-schemes/src/folding/nova/nifs/mova.rs +++ /dev/null @@ -1,391 +0,0 @@ -/// This module contains the implementation the NIFSTrait for the -/// [Mova](https://eprint.iacr.org/2024/1220.pdf) NIFS (Non-Interactive Folding Scheme). -use ark_crypto_primitives::sponge::Absorb; -use ark_ff::PrimeField; -use ark_poly::Polynomial; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{log2, marker::PhantomData, rand::RngCore, One, UniformRand, Zero}; - -use super::{ - nova::NIFS as NovaNIFS, - pointvsline::{PointVsLine, PointVsLineProof, PointvsLineEvaluationClaim}, - NIFSTrait, -}; -use crate::arith::{r1cs::R1CS, Arith, ArithRelation}; -use crate::commitment::CommitmentScheme; -use crate::folding::circuits::CF1; -use crate::folding::traits::Dummy; -use crate::transcript::Transcript; -use crate::utils::{ - mle::dense_vec_to_dense_mle, - vec::{is_zero_vec, vec_add, vec_scalar_mul}, -}; -use crate::{Curve, Error}; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CommittedInstance { - // Random evaluation point for the E - pub rE: Vec, - // mleE is the evaluation of the MLE of E at r_E - pub mleE: C::ScalarField, - pub u: C::ScalarField, - pub cmW: C, - pub x: Vec, -} - -impl Absorb for CommittedInstance { - fn to_sponge_bytes(&self, dest: &mut Vec) { - C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.u.to_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - self.rE.to_sponge_field_elements(dest); - self.mleE.to_sponge_field_elements(dest); - self.cmW.to_native_sponge_field_elements(dest); - } -} - -impl Dummy for CommittedInstance { - fn dummy(io_len: usize) -> Self { - Self { - rE: vec![C::ScalarField::zero(); io_len], - mleE: C::ScalarField::zero(), - u: C::ScalarField::zero(), - cmW: C::zero(), - x: vec![C::ScalarField::zero(); io_len], - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Witness { - pub E: Vec, - pub W: Vec, - pub rW: C::ScalarField, -} - -impl Dummy<&R1CS> for Witness { - fn dummy(r1cs: &R1CS) -> Self { - Self { - E: vec![C::ScalarField::zero(); r1cs.n_constraints()], - W: vec![C::ScalarField::zero(); r1cs.n_witnesses()], - rW: C::ScalarField::zero(), - } - } -} - -impl Witness { - pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { - let rW = if H { - C::ScalarField::rand(&mut rng) - } else { - C::ScalarField::zero() - }; - - Self { - E: vec![C::ScalarField::zero(); e_len], - W: w, - rW, - } - } - - pub fn commit, const H: bool>( - &self, - params: &CS::ProverParams, - x: Vec, - rE: Vec, - ) -> Result, Error> { - let mut mleE = C::ScalarField::zero(); - if !is_zero_vec::(&self.E) { - let E = dense_vec_to_dense_mle(log2(self.E.len()) as usize, &self.E); - mleE = E.evaluate(&rE); - } - let cmW = CS::commit(params, &self.W, &self.rW)?; - Ok(CommittedInstance { - rE, - mleE, - u: C::ScalarField::one(), - cmW, - x, - }) - } -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Proof { - pub h_proof: PointVsLineProof, - pub mleE1_prime: C::ScalarField, - pub mleE2_prime: C::ScalarField, - pub mleT: C::ScalarField, - pub rE_prime: Vec, -} - -/// Implements the Non-Interactive Folding Scheme described in section 4 of -/// [Mova](https://eprint.iacr.org/2024/1220.pdf). -/// `H` specifies whether the NIFS will use a blinding factor -pub struct NIFS< - C: Curve, - CS: CommitmentScheme, - T: Transcript, - const H: bool = false, -> { - _c: PhantomData, - _cp: PhantomData, - _ct: PhantomData, -} - -impl, T: Transcript, const H: bool> - NIFSTrait for NIFS -{ - type CommittedInstance = CommittedInstance; - type Witness = Witness; - type ProverAux = Vec; // T in Mova's notation - type Proof = Proof; - - fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness { - Witness::new::(w, e_len, rng) - } - - fn new_instance( - mut rng: impl RngCore, - params: &CS::ProverParams, - W: &Self::Witness, - x: Vec, - aux: Vec, // = r_E - ) -> Result { - let mut rE = aux.clone(); - if is_zero_vec(&rE) { - // means that we're in a fresh instance, so generate random value - rE = (0..log2(W.E.len())) - .map(|_| C::ScalarField::rand(&mut rng)) - .collect(); - } - - W.commit::(params, x, rE) - } - - // Protocol 7 - point 3 (16) - fn fold_witness( - a: C::ScalarField, - W_i: &Witness, - w_i: &Witness, - aux: &Vec, // T in Mova's notation - ) -> Result, Error> { - let a2 = a * a; - let E: Vec = vec_add( - &vec_add(&W_i.E, &vec_scalar_mul(aux, &a))?, - &vec_scalar_mul(&w_i.E, &a2), - )?; - let W: Vec = W_i - .W - .iter() - .zip(&w_i.W) - .map(|(i1, i2)| *i1 + (a * i2)) - .collect(); - - let rW = W_i.rW + a * w_i.rW; - Ok(Witness:: { E, W, rW }) - } - - /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. Protocol 8 - /// Returns a proof for the pt-vs-line operations along with the folded committed instance - /// instances and witness - #[allow(clippy::type_complexity)] - fn prove( - _cs_prover_params: &CS::ProverParams, // not used in Mova since we don't commit to T - r1cs: &R1CS, - transcript: &mut T, - W_i: &Witness, - U_i: &CommittedInstance, - w_i: &Witness, - u_i: &CommittedInstance, - ) -> Result< - ( - Self::Witness, - Self::CommittedInstance, - Self::Proof, - Vec, - ), - Error, - > { - // Protocol 5 is pre-processing - transcript.absorb(U_i); - transcript.absorb(u_i); - - // Protocol 6 - let ( - h_proof, - PointvsLineEvaluationClaim { - mleE1_prime, - mleE2_prime, - rE_prime, - }, - ) = PointVsLine::::prove(transcript, U_i, u_i, W_i, w_i)?; - - // Protocol 7 - - transcript.absorb(&mleE1_prime); - transcript.absorb(&mleE2_prime); - - // compute the cross terms - let z1: Vec = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat(); - let z2: Vec = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat(); - let T = NovaNIFS::::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2, &W_i.E, &w_i.E)?; - - let n_vars: usize = log2(W_i.E.len()) as usize; - if log2(T.len()) as usize != n_vars { - return Err(Error::NotExpectedLength(T.len(), n_vars)); - } - - let mleT = dense_vec_to_dense_mle(n_vars, &T); - let mleT_evaluated = mleT.evaluate(&rE_prime); - - transcript.absorb(&mleT_evaluated); - - let alpha: C::ScalarField = transcript.get_challenge(); - - let ci = Self::fold_committed_instance( - alpha, - U_i, - u_i, - &rE_prime, - &mleE1_prime, - &mleE2_prime, - &mleT_evaluated, - )?; - let w = Self::fold_witness(alpha, W_i, w_i, &T)?; - - let proof = Self::Proof { - h_proof, - mleE1_prime, - mleE2_prime, - mleT: mleT_evaluated, - rE_prime, - }; - Ok(( - w, - ci, - proof, - vec![], // r_bits, returned to be passed as inputs to the circuit, not used at the - // current impl status - )) - } - - /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. It verifies the results from the proof - /// Both the folding and the pt-vs-line proof - /// returns the folded committed instance - fn verify( - transcript: &mut T, - U_i: &CommittedInstance, - u_i: &CommittedInstance, - proof: &Proof, - ) -> Result<(Self::CommittedInstance, Vec), Error> { - transcript.absorb(U_i); - transcript.absorb(u_i); - let rE_prime = PointVsLine::::verify( - transcript, - U_i, - u_i, - &proof.h_proof, - &proof.mleE1_prime, - &proof.mleE2_prime, - &proof.rE_prime, - )?; - - transcript.absorb(&proof.mleE1_prime); - transcript.absorb(&proof.mleE2_prime); - transcript.absorb(&proof.mleT); - - let alpha: C::ScalarField = transcript.get_challenge(); - - Ok(( - Self::fold_committed_instance( - alpha, - U_i, - u_i, - &rE_prime, - &proof.mleE1_prime, - &proof.mleE2_prime, - &proof.mleT, - )?, - vec![], - )) - } -} - -impl, T: Transcript, const H: bool> - NIFS -{ - // Protocol 7 - point 3 (15) - fn fold_committed_instance( - a: C::ScalarField, - U_i: &CommittedInstance, - u_i: &CommittedInstance, - rE_prime: &[C::ScalarField], - mleE1_prime: &C::ScalarField, - mleE2_prime: &C::ScalarField, - mleT: &C::ScalarField, - ) -> Result, Error> { - let a2 = a * a; - let mleE = *mleE1_prime + a * mleT + a2 * mleE2_prime; - let u = U_i.u + a * u_i.u; - let cmW = U_i.cmW + u_i.cmW.mul(a); - let x = U_i - .x - .iter() - .zip(&u_i.x) - .map(|(i1, i2)| *i1 + (a * i2)) - .collect::>(); - - Ok(CommittedInstance:: { - rE: rE_prime.to_vec(), - mleE, - u, - cmW, - x, - }) - } -} - -impl ArithRelation, CommittedInstance> for R1CS> { - type Evaluation = Vec>; - - fn eval_relation( - &self, - w: &Witness, - u: &CommittedInstance, - ) -> Result { - self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat()) - } - - fn check_evaluation( - w: &Witness, - _u: &CommittedInstance, - e: Self::Evaluation, - ) -> Result<(), Error> { - (w.E == e).then_some(()).ok_or(Error::NotSatisfied) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_pallas::{Fr, Projective}; - - use crate::arith::{r1cs::tests::get_test_r1cs, ArithRelation}; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::tests::test_nifs_opt; - - #[test] - fn test_nifs_mova() -> Result<(), Error> { - let (W, U) = test_nifs_opt::, PoseidonSponge>>()?; - - // check the last folded instance relation - let r1cs = get_test_r1cs(); - r1cs.check_relation(&W, &U)?; - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/nifs/nova.rs b/folding-schemes/src/folding/nova/nifs/nova.rs deleted file mode 100644 index c4f4274c6..000000000 --- a/folding-schemes/src/folding/nova/nifs/nova.rs +++ /dev/null @@ -1,290 +0,0 @@ -/// This module contains the implementation the NIFSTrait for the -/// [Nova](https://eprint.iacr.org/2021/370.pdf) NIFS (Non-Interactive Folding Scheme). -use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb, CryptographicSponge}; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; -use ark_relations::gr1cs::SynthesisError; -use ark_std::rand::RngCore; -use ark_std::Zero; -use std::marker::PhantomData; - -use super::NIFSTrait; -use crate::arith::r1cs::R1CS; -use crate::commitment::CommitmentScheme; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::circuits::{ - cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}, - nonnative::affine::NonNativeAffineVar, - CF1, -}; -use crate::folding::nova::{CommittedInstance, Witness}; -use crate::transcript::{Transcript, TranscriptVar}; -use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub}; -use crate::{Curve, Error}; - -/// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a -/// rust-native and an in-circuit compatible versions. -pub struct ChallengeGadget { - _c: PhantomData, - _ci: PhantomData, -} -impl ChallengeGadget { - pub fn get_challenge_native>( - transcript: &mut T, - U_i: &CI, - u_i: &CI, - cmT: Option<&C>, - ) -> Vec { - transcript.absorb(&U_i); - transcript.absorb(&u_i); - // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. - if let Some(cmT_value) = cmT { - transcript.absorb_nonnative(cmT_value); - } - transcript.squeeze_bits(NOVA_N_BITS_RO) - } - - // compatible with the native get_challenge_native - pub fn get_challenge_gadget< - S: CryptographicSponge, - T: TranscriptVar, S>, - CIVar: AbsorbGadget>, - >( - transcript: &mut T, - U_i_vec: Vec>>, // apready processed input, so we don't have to recompute these values - u_i: CIVar, - cmT: Option>, - ) -> Result>, SynthesisError> { - transcript.absorb(&U_i_vec)?; - transcript.absorb(&u_i)?; - // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. - if let Some(cmT_value) = cmT { - transcript.absorb_nonnative(&cmT_value)?; - } - transcript.squeeze_bits(NOVA_N_BITS_RO) - } -} - -/// Implements the Non-Interactive Folding Scheme described in section 4 of -/// [Nova](https://eprint.iacr.org/2021/370.pdf). -/// `H` specifies whether the NIFS will use a blinding factor -pub struct NIFS< - C: Curve, - CS: CommitmentScheme, - T: Transcript, - const H: bool = false, -> { - _c: PhantomData, - _cp: PhantomData, - _t: PhantomData, -} - -impl, T: Transcript, const H: bool> - NIFSTrait for NIFS -{ - type CommittedInstance = CommittedInstance; - type Witness = Witness; - type ProverAux = Vec; - type Proof = C; - - fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness { - Witness::new::(w, e_len, rng) - } - - fn new_instance( - _rng: impl RngCore, - params: &CS::ProverParams, - W: &Self::Witness, - x: Vec, - _aux: Vec, - ) -> Result { - W.commit::(params, x) - } - - fn fold_witness( - r: C::ScalarField, - W_i: &Self::Witness, - w_i: &Self::Witness, - aux: &Self::ProverAux, // T in Nova's notation - ) -> Result { - let r2 = r * r; - let E: Vec = vec_add( - &vec_add(&W_i.E, &vec_scalar_mul(aux, &r))?, // aux is Nova's T - &vec_scalar_mul(&w_i.E, &r2), - )?; - // use r_T=0 since we don't need hiding property for cm(T) - let rT = C::ScalarField::zero(); - let rE = W_i.rE + r * rT + r2 * w_i.rE; - let W: Vec = W_i - .W - .iter() - .zip(&w_i.W) - .map(|(a, b)| *a + (r * b)) - .collect(); - - let rW = W_i.rW + r * w_i.rW; - Ok(Self::Witness { E, rE, W, rW }) - } - - fn prove( - cs_prover_params: &CS::ProverParams, - r1cs: &R1CS, - transcript: &mut T, - W_i: &Self::Witness, - U_i: &Self::CommittedInstance, - w_i: &Self::Witness, - u_i: &Self::CommittedInstance, - ) -> Result< - ( - Self::Witness, - Self::CommittedInstance, - Self::Proof, - Vec, - ), - Error, - > { - // compute the cross terms - let z1: Vec = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat(); - let z2: Vec = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat(); - let T = Self::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2, &W_i.E, &w_i.E)?; - - // use r_T=0 since we don't need hiding property for cm(T) - let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; - - let r_bits = ChallengeGadget::::get_challenge_native( - transcript, - U_i, - u_i, - Some(&cmT), - ); - let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - let w = Self::fold_witness(r_Fr, W_i, w_i, &T)?; - - let ci = Self::fold_committed_instances(r_Fr, U_i, u_i, &cmT); - - Ok((w, ci, cmT, r_bits)) - } - - fn verify( - transcript: &mut T, - U_i: &Self::CommittedInstance, - u_i: &Self::CommittedInstance, - cmT: &C, // Proof - ) -> Result<(Self::CommittedInstance, Vec), Error> { - let r_bits = ChallengeGadget::::get_challenge_native( - transcript, - U_i, - u_i, - Some(cmT), - ); - let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - Ok((Self::fold_committed_instances(r, U_i, u_i, cmT), r_bits)) - } -} - -impl, T: Transcript, const H: bool> - NIFS -{ - /// compute_T: compute cross-terms T. We use the approach described in - /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. - pub fn compute_T( - r1cs: &R1CS, - u1: C::ScalarField, - u2: C::ScalarField, - z1: &[C::ScalarField], - z2: &[C::ScalarField], - E1: &[C::ScalarField], - E2: &[C::ScalarField], - ) -> Result, Error> { - let z = vec_add(z1, z2)?; - - // this is parallelizable (for the future) - let Az = mat_vec_mul(&r1cs.A, &z)?; - let Bz = mat_vec_mul(&r1cs.B, &z)?; - let Cz = mat_vec_mul(&r1cs.C, &z)?; - let u = u1 + u2; - let uCz = vec_scalar_mul(&Cz, &u); - let AzBz = hadamard(&Az, &Bz)?; - let lhs = vec_sub(&AzBz, &uCz)?; - vec_sub(&vec_sub(&lhs, E1)?, E2) - } - - pub fn compute_cyclefold_cmT( - cs_prover_params: &CS::ProverParams, - r1cs: &R1CS, // R1CS over C2.Fr=C1.Fq (here C=C2) - w1: &CycleFoldWitness, - ci1: &CycleFoldCommittedInstance, - w2: &CycleFoldWitness, - ci2: &CycleFoldCommittedInstance, - ) -> Result<(Vec, C), Error> { - let z1: Vec = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat(); - let z2: Vec = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat(); - - // compute cross terms - let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2, &w1.E, &w2.E)?; - // use r_T=0 since we don't need hiding property for cm(T) - let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; - Ok((T, cmT)) - } - - /// folds two committed instances with the given r and cmT. This method is used by - /// Nova::verify, but also by Nova::prove and the CycleFoldNIFS::verify. - pub fn fold_committed_instances( - r: C::ScalarField, - U_i: &CommittedInstance, - u_i: &CommittedInstance, - cmT: &C, - ) -> CommittedInstance { - let r2 = r * r; - let cmE = U_i.cmE + cmT.mul(r) + u_i.cmE.mul(r2); - let u = U_i.u + r * u_i.u; - let cmW = U_i.cmW + u_i.cmW.mul(r); - let x = U_i - .x - .iter() - .zip(&u_i.x) - .map(|(a, b)| *a + (r * b)) - .collect::>(); - - CommittedInstance { cmE, u, cmW, x } - } - - pub fn prove_commitments( - tr: &mut impl Transcript, - cs_prover_params: &CS::ProverParams, - w: &Witness, - ci: &CommittedInstance, - T: Vec, - cmT: &C, - ) -> Result<[CS::Proof; 3], Error> { - let cmE_proof = CS::prove(cs_prover_params, tr, &ci.cmE, &w.E, &w.rE, None)?; - let cmW_proof = CS::prove(cs_prover_params, tr, &ci.cmW, &w.W, &w.rW, None)?; - let cmT_proof = CS::prove(cs_prover_params, tr, cmT, &T, &C::ScalarField::zero(), None)?; // cm(T) is committed with rT=0 - Ok([cmE_proof, cmW_proof, cmT_proof]) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_pallas::{Fr, Projective}; - - use crate::arith::{r1cs::tests::get_test_r1cs, ArithRelation}; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::tests::test_nifs_opt; - - #[test] - fn test_nifs_nova() -> Result<(), Error> { - let (W, U) = test_nifs_opt::, PoseidonSponge>>()?; - - // check the last folded instance relation - let r1cs = get_test_r1cs(); - r1cs.check_relation(&W, &U)?; - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/nifs/nova_circuits.rs b/folding-schemes/src/folding/nova/nifs/nova_circuits.rs deleted file mode 100644 index 0ac5e28b2..000000000 --- a/folding-schemes/src/folding/nova/nifs/nova_circuits.rs +++ /dev/null @@ -1,232 +0,0 @@ -/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) NIFS related circuits -use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, CryptographicSponge}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - uint8::UInt8, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{fmt::Debug, Zero}; -use core::{borrow::Borrow, marker::PhantomData}; - -use super::NIFSGadgetTrait; -use crate::folding::traits::CommittedInstanceVarOps; -use crate::transcript::TranscriptVar; -use crate::{ - folding::circuits::{ - nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, - CF1, CF2, - }, - Curve, -}; -use crate::{folding::nova::CommittedInstance, transcript::AbsorbNonNativeGadget}; - -use super::nova::ChallengeGadget; - -/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova -/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are -/// represented non-natively over the constraint field. -#[derive(Debug, Clone)] -pub struct CommittedInstanceVar { - pub u: FpVar, - pub x: Vec>, - pub cmE: NonNativeAffineVar, - pub cmW: NonNativeAffineVar, -} - -impl AllocVar, CF1> for CommittedInstanceVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; - let x: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - - let cmE = - NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; - let cmW = - NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; - - Ok(Self { u, x, cmE, cmW }) - }) - } -} - -impl AbsorbGadget for CommittedInstanceVar { - fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) - } - - fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { - Ok([ - vec![self.u.clone()], - self.x.clone(), - self.cmE.to_native_sponge_field_elements()?, - self.cmW.to_native_sponge_field_elements()?, - ] - .concat()) - } -} - -impl CommittedInstanceVarOps for CommittedInstanceVar { - type PointVar = NonNativeAffineVar; - - fn get_commitments(&self) -> Vec { - vec![self.cmW.clone(), self.cmE.clone()] - } - - fn get_public_inputs(&self) -> &[FpVar>] { - &self.x - } - - fn enforce_incoming(&self) -> Result<(), SynthesisError> { - let zero = NonNativeUintVar::new_constant(ConstraintSystemRef::None, CF2::::zero())?; - self.cmE.x.enforce_equal_unaligned(&zero)?; - self.cmE.y.enforce_equal_unaligned(&zero)?; - self.u.enforce_equal(&FpVar::one()) - } - - fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { - self.u.enforce_equal(&other.u)?; - self.x.enforce_equal(&other.x) - } -} - -/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier -/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are -/// delegated to the NIFSCycleFoldGadget. -pub struct NIFSGadget, S>> { - _c: PhantomData, - _s: PhantomData, - _t: PhantomData, -} - -impl NIFSGadgetTrait for NIFSGadget -where - C: Curve, - S: CryptographicSponge, - T: TranscriptVar, S>, -{ - type CommittedInstance = CommittedInstance; - type CommittedInstanceVar = CommittedInstanceVar; - type Proof = C; - type ProofVar = NonNativeAffineVar; - - fn verify( - transcript: &mut T, - U_i: Self::CommittedInstanceVar, - // U_i_vec is passed to reuse the already computed U_i_vec from previous methods - U_i_vec: Vec>>, - u_i: Self::CommittedInstanceVar, - cmT: Option, - ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError> { - let r_bits = ChallengeGadget::>::get_challenge_gadget( - transcript, - U_i_vec, - u_i.clone(), - cmT.clone(), - )?; - let r = Boolean::le_bits_to_fp(&r_bits)?; - - Ok(( - Self::CommittedInstanceVar { - cmE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, - cmW: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, - // ci3.u = U_i.u + r * u_i.u - u: U_i.u + &r * u_i.u, - // ci3.x = U_i.x + r * u_i.x - x: U_i - .x - .iter() - .zip(u_i.x) - .map(|(a, b)| a + &r * &b) - .collect::>>>(), - }, - r_bits, - )) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::sponge::poseidon::constraints::PoseidonSpongeVar; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_pallas::{Fr, Projective}; - use ark_r1cs_std::GR1CSVar; - use ark_std::UniformRand; - - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::{ - nova::NIFS, - tests::{ - test_committed_instance_hash_opt, test_committed_instance_to_sponge_preimage_opt, - test_nifs_gadget_opt, - }, - }; - use crate::Error; - - #[test] - fn test_nifs_gadget() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - // prepare the committed instances to test in-circuit - let ci: Vec> = (0..2) - .into_iter() - .map(|_| CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }) - .collect(); - let cmT = Projective::rand(&mut rng); - - let (ci_out, ciVar_out) = test_nifs_gadget_opt::< - NIFS, PoseidonSponge>, - NIFSGadget, PoseidonSpongeVar>, - >(ci, cmT)?; - assert_eq!(ciVar_out.u.value()?, ci_out.u); - assert_eq!(ciVar_out.x.value()?, ci_out.x); - Ok(()) - } - - #[test] - fn test_committed_instance_to_sponge_preimage() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let ci = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - - test_committed_instance_to_sponge_preimage_opt::< - NIFS, PoseidonSponge>, - NIFSGadget, PoseidonSpongeVar>, - >(ci)?; - Ok(()) - } - - #[test] - fn test_committed_instance_hash() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let ci = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - test_committed_instance_hash_opt::< - NIFS, PoseidonSponge>, - NIFSGadget, PoseidonSpongeVar>, - >(ci)?; - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/nifs/ova.rs b/folding-schemes/src/folding/nova/nifs/ova.rs deleted file mode 100644 index 1f8d9ef0b..000000000 --- a/folding-schemes/src/folding/nova/nifs/ova.rs +++ /dev/null @@ -1,301 +0,0 @@ -/// This module contains the implementation the NIFSTrait for the -/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme). -use ark_crypto_primitives::sponge::Absorb; -use ark_ff::{BigInteger, PrimeField}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; -use ark_std::{One, UniformRand, Zero}; -use std::marker::PhantomData; - -use super::nova::ChallengeGadget; -use super::ova_circuits::CommittedInstanceVar; -use super::NIFSTrait; -use crate::arith::{r1cs::R1CS, Arith}; -use crate::commitment::CommitmentScheme; -use crate::folding::traits::{CommittedInstanceOps, Inputize}; -use crate::folding::{circuits::CF1, traits::Dummy}; -use crate::transcript::Transcript; -use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub}; -use crate::{Curve, Error}; - -/// A CommittedInstance in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) is represented by `W` or -/// `W'`. It is the result of the commitment to a vector that contains the witness `w` concatenated -/// with `t` or `e` + the public inputs `x` and a relaxation factor `u`. (Notice that in the Ova -/// document `u` is denoted as `mu`, in this implementation we use `u` so it follows the original -/// Nova notation, so code is easier to follow). -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CommittedInstance { - pub u: C::ScalarField, // in the Ova document is denoted as `mu` - pub x: Vec, - pub cmWE: C, -} - -impl Absorb for CommittedInstance { - fn to_sponge_bytes(&self, dest: &mut Vec) { - C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.u.to_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - self.cmWE.to_native_sponge_field_elements(dest); - } -} - -impl CommittedInstanceOps for CommittedInstance { - type Var = CommittedInstanceVar; - - fn get_commitments(&self) -> Vec { - vec![self.cmWE] - } - - fn is_incoming(&self) -> bool { - self.u == One::one() - } -} - -impl Inputize> for CommittedInstance { - /// Returns the internal representation in the same order as how the value - /// is allocated in `CommittedInstanceVar::new_input`. - fn inputize(&self) -> Vec> { - [&[self.u][..], &self.x, &self.cmWE.inputize_nonnative()].concat() - } -} - -/// A Witness in Ova is represented by `w`. It also contains a blinder which can or not be used -/// when committing to the witness itself. -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Witness { - pub w: Vec, - pub rW: C::ScalarField, -} - -impl Witness { - /// Generates a new `Witness` instance from a given witness vector. - /// If `H = true`, then we assume we want to blind it at commitment time, - /// hence sampling `rW` from the randomness passed. - pub fn new(w: Vec, mut rng: impl RngCore) -> Self { - Self { - w, - rW: if H { - C::ScalarField::rand(&mut rng) - } else { - C::ScalarField::zero() - }, - } - } - - /// Given `x` (public inputs) and `t` or `e` (which we always concatenate in Ova) and the - /// public inputs `x`, generates a [`CommittedInstance`] as a result which will or not be - /// blinded depending on how the const generic `HC` is set up. - pub fn commit, const HC: bool>( - &self, - params: &CS::ProverParams, - x: Vec, - t_or_e: Vec, - ) -> Result, Error> { - let cmWE = CS::commit(params, &[self.w.clone(), t_or_e].concat(), &self.rW)?; - Ok(CommittedInstance { - u: C::ScalarField::one(), - cmWE, - x, - }) - } -} - -impl Dummy<&R1CS>> for Witness { - fn dummy(r1cs: &R1CS>) -> Self { - Self { - w: vec![C::ScalarField::zero(); r1cs.n_witnesses()], - rW: C::ScalarField::zero(), - } - } -} - -/// Implements the NIFS (Non-Interactive Folding Scheme) trait for Ova. -pub struct NIFS< - C: Curve, - CS: CommitmentScheme, - T: Transcript, - const H: bool = false, -> { - _c: PhantomData, - _cp: PhantomData, - _t: PhantomData, -} - -impl, T: Transcript, const H: bool> - NIFSTrait for NIFS -{ - type CommittedInstance = CommittedInstance; - type Witness = Witness; - type ProverAux = (); - // Proof is unused, but set to C::ScalarField so that the NIFSGadgetTrait abstraction can - // define the ProofsVar implementing the AllocVar from Proof - type Proof = C::ScalarField; - - fn new_witness(w: Vec, _e_len: usize, rng: impl RngCore) -> Self::Witness { - Witness::new::(w, rng) - } - - fn new_instance( - _rng: impl RngCore, - params: &CS::ProverParams, - W: &Self::Witness, - x: Vec, - aux: Vec, // t_or_e - ) -> Result { - W.commit::(params, x, aux) - } - - fn fold_witness( - r: C::ScalarField, // in Ova's hackmd denoted as `alpha` - W_i: &Self::Witness, - w_i: &Self::Witness, - _aux: &Self::ProverAux, - ) -> Result { - let w: Vec = W_i - .w - .iter() - .zip(&w_i.w) - .map(|(a, b)| *a + (r * b)) - .collect(); - - let rW = W_i.rW + r * w_i.rW; - Ok(Self::Witness { w, rW }) - } - - fn prove( - _cs_prover_params: &CS::ProverParams, - _r1cs: &R1CS, - transcript: &mut T, - W_i: &Self::Witness, - U_i: &Self::CommittedInstance, - w_i: &Self::Witness, - u_i: &Self::CommittedInstance, - ) -> Result< - ( - Self::Witness, - Self::CommittedInstance, - Self::Proof, - Vec, - ), - Error, - > { - let mut transcript_v = transcript.clone(); - - let r_bits = ChallengeGadget::::get_challenge_native( - transcript, U_i, u_i, None, // cmT not used in Ova - ); - let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - let w = Self::fold_witness(r_Fr, W_i, w_i, &())?; - - let proof = C::ScalarField::zero(); - let (ci, _r_bits_v) = Self::verify(&mut transcript_v, U_i, u_i, &proof)?; - #[cfg(test)] - assert_eq!(_r_bits_v, r_bits); - - Ok((w, ci, proof, r_bits)) - } - - fn verify( - transcript: &mut T, - U_i: &Self::CommittedInstance, - u_i: &Self::CommittedInstance, - _proof: &Self::Proof, // unused in Ova - ) -> Result<(Self::CommittedInstance, Vec), Error> { - let r_bits = ChallengeGadget::::get_challenge_native( - transcript, U_i, u_i, None, // cmT not used in Ova - ); - let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - // recall that r=alpha, and u=mu between Nova and Ova respectively - let u = U_i.u + r; // u_i.u is always 1 in Ova as we just can do IVC (not PCD). - let cmWE = U_i.cmWE + u_i.cmWE.mul(r); - let x = U_i - .x - .iter() - .zip(&u_i.x) - .map(|(a, b)| *a + (r * b)) - .collect::>(); - - Ok((Self::CommittedInstance { cmWE, u, x }, r_bits)) - } -} - -/// Computes the E parameter (error terms) for the given R1CS and the instance's z and u. This -/// method is used by the verifier to obtain E in order to check the RelaxedR1CS relation. -pub fn compute_E( - r1cs: &R1CS, - z: &[C::ScalarField], - u: C::ScalarField, -) -> Result, Error> { - let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone()); - - // this is parallelizable (for the future) - let Az = mat_vec_mul(&A, z)?; - let Bz = mat_vec_mul(&B, z)?; - let Cz = mat_vec_mul(&C, z)?; - - let Az_Bz = hadamard(&Az, &Bz)?; - let uCz = vec_scalar_mul(&Cz, &u); - - vec_sub(&Az_Bz, &uCz) -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_pallas::{Fr, Projective}; - - use crate::arith::{r1cs::tests::get_test_r1cs, ArithRelation}; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::tests::test_nifs_opt; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - - // Simple auxiliary structure mainly used to help pass a witness for which we can check - // easily an R1CS relation. - // Notice that checking it requires us to have `E` as per [`ArithRelation`] trait definition. - // But since we don't hold `E` nor `e` within the NIFS, we create this structure to pass - // `e` such that the check can be done. - #[derive(Debug, Clone)] - pub(crate) struct TestingWitness { - pub(crate) w: Vec, - pub(crate) e: Vec, - } - impl ArithRelation, CommittedInstance> for R1CS> { - type Evaluation = Vec>; - - fn eval_relation( - &self, - w: &TestingWitness, - u: &CommittedInstance, - ) -> Result { - self.eval_at_z(&[&[u.u], u.x.as_slice(), &w.w].concat()) - } - - fn check_evaluation( - w: &TestingWitness, - _u: &CommittedInstance, - e: Self::Evaluation, - ) -> Result<(), Error> { - (w.e == e).then_some(()).ok_or(Error::NotSatisfied) - } - } - - #[test] - fn test_nifs_ova() -> Result<(), Error> { - let (W, U) = test_nifs_opt::, PoseidonSponge>>()?; - - // check the last folded instance relation - let r1cs = get_test_r1cs(); - let z: Vec = [&[U.u][..], &U.x, &W.w].concat(); - let e = compute_E::(&r1cs, &z, U.u)?; - r1cs.check_relation(&TestingWitness:: { e, w: W.w.clone() }, &U)?; - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/nifs/ova_circuits.rs b/folding-schemes/src/folding/nova/nifs/ova_circuits.rs deleted file mode 100644 index 1e5a2b6b0..000000000 --- a/folding-schemes/src/folding/nova/nifs/ova_circuits.rs +++ /dev/null @@ -1,215 +0,0 @@ -/// contains [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS related circuits -use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, CryptographicSponge}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - uint8::UInt8, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::fmt::Debug; -use core::{borrow::Borrow, marker::PhantomData}; - -use super::ova::CommittedInstance; -use super::NIFSGadgetTrait; -use crate::folding::traits::CommittedInstanceVarOps; -use crate::transcript::TranscriptVar; -use crate::{ - folding::circuits::{nonnative::affine::NonNativeAffineVar, CF1}, - transcript::AbsorbNonNativeGadget, -}; - -use crate::folding::nova::nifs::nova::ChallengeGadget; -use crate::Curve; - -#[derive(Debug, Clone)] -pub struct CommittedInstanceVar { - pub u: FpVar, - pub x: Vec>, - pub cmWE: NonNativeAffineVar, -} - -impl AllocVar, CF1> for CommittedInstanceVar { - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; - let x: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - - let cmWE = - NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmWE), mode)?; - - Ok(Self { u, x, cmWE }) - }) - } -} - -impl AbsorbGadget for CommittedInstanceVar { - fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) - } - - fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { - Ok([ - vec![self.u.clone()], - self.x.clone(), - self.cmWE.to_native_sponge_field_elements()?, - ] - .concat()) - } -} - -impl CommittedInstanceVarOps for CommittedInstanceVar { - type PointVar = NonNativeAffineVar; - - fn get_commitments(&self) -> Vec { - vec![self.cmWE.clone()] - } - - fn get_public_inputs(&self) -> &[FpVar>] { - &self.x - } - - fn enforce_incoming(&self) -> Result<(), SynthesisError> { - self.u.enforce_equal(&FpVar::one()) - } - - fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { - self.u.enforce_equal(&other.u)?; - self.x.enforce_equal(&other.x) - } -} - -/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier -/// described of the Ova variant, where the cmWE check is delegated to the NIFSCycleFoldGadget. -pub struct NIFSGadget, S>> { - _c: PhantomData, - _s: PhantomData, - _t: PhantomData, -} - -impl NIFSGadgetTrait for NIFSGadget -where - C: Curve, - S: CryptographicSponge, - T: TranscriptVar, S>, -{ - type CommittedInstance = CommittedInstance; - type CommittedInstanceVar = CommittedInstanceVar; - type Proof = C::ScalarField; - type ProofVar = FpVar; // unused - - fn verify( - transcript: &mut T, - U_i: Self::CommittedInstanceVar, - // U_i_vec is passed to reuse the already computed U_i_vec from previous methods - U_i_vec: Vec>>, - u_i: Self::CommittedInstanceVar, - _proof: Option, - ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError> { - let r_bits = ChallengeGadget::>::get_challenge_gadget( - transcript, - U_i_vec, - u_i.clone(), - None, - )?; - let r = Boolean::le_bits_to_fp(&r_bits)?; - - Ok(( - Self::CommittedInstanceVar { - cmWE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, - // ci3.u = U_i.u + r * u_i.u (u_i.u is always 1 in Ova) - u: U_i.u + &r, - // ci3.x = U_i.x + r * u_i.x - x: U_i - .x - .iter() - .zip(u_i.x) - .map(|(a, b)| a + &r * &b) - .collect::>>>(), - }, - r_bits, - )) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::sponge::poseidon::constraints::PoseidonSpongeVar; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_pallas::{Fr, Projective}; - use ark_r1cs_std::GR1CSVar; - use ark_std::UniformRand; - use ark_std::Zero; - - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::{ - ova::NIFS, - tests::{ - test_committed_instance_hash_opt, test_committed_instance_to_sponge_preimage_opt, - test_nifs_gadget_opt, - }, - }; - use crate::Error; - - #[test] - fn test_nifs_gadget() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - // prepare the committed instances to test in-circuit - let ci: Vec> = (0..2) - .into_iter() - .map(|_| CommittedInstance:: { - u: Fr::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - cmWE: Projective::rand(&mut rng), - }) - .collect(); - - let (ci_out, ciVar_out) = test_nifs_gadget_opt::< - NIFS, PoseidonSponge>, - NIFSGadget, PoseidonSpongeVar>, - >(ci, Fr::zero())?; - assert_eq!(ciVar_out.u.value()?, ci_out.u); - assert_eq!(ciVar_out.x.value()?, ci_out.x); - Ok(()) - } - - #[test] - fn test_committed_instance_to_sponge_preimage() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let ci = CommittedInstance:: { - u: Fr::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - cmWE: Projective::rand(&mut rng), - }; - - test_committed_instance_to_sponge_preimage_opt::< - NIFS, PoseidonSponge>, - NIFSGadget, PoseidonSpongeVar>, - >(ci)?; - Ok(()) - } - - #[test] - fn test_committed_instance_hash() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let ci = CommittedInstance:: { - u: Fr::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - cmWE: Projective::rand(&mut rng), - }; - test_committed_instance_hash_opt::< - NIFS, PoseidonSponge>, - NIFSGadget, PoseidonSpongeVar>, - >(ci)?; - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/nifs/pointvsline.rs b/folding-schemes/src/folding/nova/nifs/pointvsline.rs deleted file mode 100644 index 735880ea7..000000000 --- a/folding-schemes/src/folding/nova/nifs/pointvsline.rs +++ /dev/null @@ -1,357 +0,0 @@ -use ark_ff::{One, PrimeField}; -use ark_poly::univariate::DensePolynomial; -use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{log2, Zero}; - -use super::mova::{CommittedInstance, Witness}; -use crate::transcript::Transcript; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::{Curve, Error}; - -/// Implements the Points vs Line as described in -/// [Mova](https://eprint.iacr.org/2024/1220.pdf) and Section 4.5.2 from Thaler’s book -/// Claim from step 3 protocol 6 -pub struct PointvsLineEvaluationClaim { - pub mleE1_prime: C::ScalarField, - pub mleE2_prime: C::ScalarField, - pub rE_prime: Vec, -} -/// Proof from step 1 protocol 6 -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct PointVsLineProof { - pub h1: DensePolynomial, - pub h2: DensePolynomial, -} - -#[derive(Clone, Debug, Default)] -pub struct PointVsLine> { - _phantom_C: std::marker::PhantomData, - _phantom_T: std::marker::PhantomData, -} - -/// Protocol 6 from Mova -impl> PointVsLine { - pub fn prove( - transcript: &mut T, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - w1: &Witness, - w2: &Witness, - ) -> Result<(PointVsLineProof, PointvsLineEvaluationClaim), Error> { - let n_vars: usize = log2(w1.E.len()) as usize; - - let mleE1 = dense_vec_to_dense_mle(n_vars, &w1.E); - let mleE2 = dense_vec_to_dense_mle(n_vars, &w2.E); - - // We have l(0) = r1, l(1) = r2 so we know that l(x) = r1 + x(r2-r1) that's why we need r2-r1 - let r2_sub_r1: Vec<::ScalarField> = ci1 - .rE - .iter() - .zip(&ci2.rE) - .map(|(&r1, r2)| *r2 - r1) - .collect(); - - let h1 = compute_h(&mleE1, &ci1.rE, &r2_sub_r1)?; - let h2 = compute_h(&mleE2, &ci1.rE, &r2_sub_r1)?; - - transcript.absorb(&h1.coeffs()); - transcript.absorb(&h2.coeffs()); - - let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); - transcript.absorb(&beta_scalar); - let beta = transcript.get_challenge(); - - let mleE1_prime = h1.evaluate(&beta); - let mleE2_prime = h2.evaluate(&beta); - - let rE_prime = compute_l(&ci1.rE, &r2_sub_r1, beta)?; - - Ok(( - PointVsLineProof { h1, h2 }, - PointvsLineEvaluationClaim { - mleE1_prime, - mleE2_prime, - rE_prime, - }, - )) - } - - pub fn verify( - transcript: &mut T, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - proof: &PointVsLineProof, - mleE1_prime: &::ScalarField, - mleE2_prime: &::ScalarField, - rE_prime_p: &[::ScalarField], // the rE_prime of the prover - ) -> Result< - Vec<::ScalarField>, // rE=rE1'=rE2'. - Error, - > { - if proof.h1.evaluate(&C::ScalarField::zero()) != ci1.mleE { - return Err(Error::NotEqual); - } - - if proof.h2.evaluate(&C::ScalarField::one()) != ci2.mleE { - return Err(Error::NotEqual); - } - - transcript.absorb(&proof.h1.coeffs()); - transcript.absorb(&proof.h2.coeffs()); - - let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); - transcript.absorb(&beta_scalar); - let beta = transcript.get_challenge(); - - if *mleE1_prime != proof.h1.evaluate(&beta) { - return Err(Error::NotEqual); - } - - if *mleE2_prime != proof.h2.evaluate(&beta) { - return Err(Error::NotEqual); - } - - let r2_sub_r1: Vec<::ScalarField> = ci1 - .rE - .iter() - .zip(&ci2.rE) - .map(|(&r1, r2)| *r2 - r1) - .collect(); - let rE_prime = compute_l(&ci1.rE, &r2_sub_r1, beta)?; - if rE_prime != rE_prime_p { - return Err(Error::NotEqual); - } - - Ok(rE_prime) - } -} - -fn compute_h( - mle: &DenseMultilinearExtension, - r1: &[F], - r2_sub_r1: &[F], -) -> Result, Error> { - let n_vars: usize = mle.num_vars; - if r1.len() != r2_sub_r1.len() || r1.len() != n_vars { - return Err(Error::NotEqual); - } - - // Initialize the polynomial vector from the evaluations in the multilinear extension. - // Each evaluation is turned into a constant polynomial. - let mut poly: Vec> = mle - .evaluations - .iter() - .map(|&x| DensePolynomial::from_coefficients_slice(&[x])) - .collect(); - - for (i, (&r1_i, &r2_sub_r1_i)) in r1.iter().zip(r2_sub_r1.iter()).enumerate().take(n_vars) { - // Create a linear polynomial r(X) = r1_i + (r2_sub_r1_i) * X (basically l) - let r = DensePolynomial::from_coefficients_slice(&[r1_i, r2_sub_r1_i]); - let half_len = 1 << (n_vars - i - 1); - - for b in 0..half_len { - let left = &poly[b << 1]; - let right = &poly[(b << 1) + 1]; - poly[b] = left + &(&r * &(right - left)); - } - } - - // After the loop, we should be left with a single polynomial, so return it. - Ok(poly.swap_remove(0)) -} - -fn compute_l(r1: &[F], r2_sub_r1: &[F], x: F) -> Result, Error> { - if r1.len() != r2_sub_r1.len() { - return Err(Error::NotEqual); - } - - // we have l(x) = r1 + x(r2-r1) so return the result - Ok(r1 - .iter() - .zip(r2_sub_r1) - .map(|(&r1, &r1_sub_r0)| r1 + x * r1_sub_r0) - .collect()) -} - -#[cfg(test)] -mod tests { - use super::{compute_h, compute_l, PointVsLine}; - use crate::commitment::pedersen::Pedersen; - use crate::commitment::CommitmentScheme; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::Error; - use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial}; - use ark_std::{log2, UniformRand}; - - use crate::folding::nova::nifs::mova::Witness; - - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_crypto_primitives::sponge::CryptographicSponge; - use ark_ff::Zero; - use ark_pallas::{Fq, Fr, Projective}; - - #[test] - fn test_compute_h() -> Result<(), Error> { - let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); - let r0 = [Fq::from(5)]; - let r1 = [Fq::from(6)]; - let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); - - let result = compute_h(&mle, &r0, &r1_sub_r0)?; - assert_eq!( - result, - DenseUVPolynomial::from_coefficients_slice(&[Fq::from(6), Fq::from(1)]) - ); - - let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); - let r0 = [Fq::from(4)]; - let r1 = [Fq::from(7)]; - let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); - - let result = compute_h(&mle, &r0, &r1_sub_r0)?; - assert_eq!( - result, - DenseUVPolynomial::from_coefficients_slice(&[Fq::from(5), Fq::from(3)]) - ); - - let mle = DenseMultilinearExtension::from_evaluations_slice( - 2, - &[Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)], - ); - let r0 = [Fq::from(5), Fq::from(4)]; - let r1 = [Fq::from(2), Fq::from(7)]; - let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); - - let result = compute_h(&mle, &r0, &r1_sub_r0)?; - assert_eq!( - result, - DenseUVPolynomial::from_coefficients_slice(&[Fq::from(14), Fq::from(3)]) - ); - let mle = DenseMultilinearExtension::from_evaluations_slice( - 3, - &[ - Fq::from(1), - Fq::from(2), - Fq::from(3), - Fq::from(4), - Fq::from(5), - Fq::from(6), - Fq::from(7), - Fq::from(8), - ], - ); - let r0 = [Fq::from(1), Fq::from(2), Fq::from(3)]; - let r1 = [Fq::from(5), Fq::from(6), Fq::from(7)]; - let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); - - let result = compute_h(&mle, &r0, &r1_sub_r0)?; - assert_eq!( - result, - DenseUVPolynomial::from_coefficients_slice(&[Fq::from(18), Fq::from(28)]) - ); - Ok(()) - } - - #[test] - fn test_compute_h_errors() { - let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); - let r0 = [Fq::from(5)]; - let r1_sub_r0 = []; - let result = compute_h(&mle, &r0, &r1_sub_r0); - assert!(result.is_err()); - - let mle = DenseMultilinearExtension::from_evaluations_slice( - 2, - &[Fq::from(1), Fq::from(2), Fq::from(1), Fq::from(2)], - ); - let r0 = [Fq::from(4)]; - let r1 = [Fq::from(7)]; - let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); - - let result = compute_h(&mle, &r0, &r1_sub_r0); - assert!(result.is_err()) - } - - #[test] - fn test_compute_l() -> Result<(), Error> { - // Test with simple non-zero values - let r1 = vec![Fq::from(1), Fq::from(2), Fq::from(3)]; - let r2_sub_r1 = vec![Fq::from(4), Fq::from(5), Fq::from(6)]; - let x = Fq::from(2); - - let expected = vec![ - Fq::from(1) + Fq::from(2) * Fq::from(4), - Fq::from(2) + Fq::from(2) * Fq::from(5), - Fq::from(3) + Fq::from(2) * Fq::from(6), - ]; - - let result = compute_l(&r1, &r2_sub_r1, x)?; - assert_eq!(result, expected); - Ok(()) - } - - #[test] - fn test_evaluations_R1CS() -> Result<(), Error> { - // Basic test with no zero error term to ensure that the folding is correct. - // This test mainly focuses on if the evaluation of h0 and h1 are correct. - let mut rng = ark_std::test_rng(); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, 4)?; - let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - let W_i = Witness { - E: vec![Fr::from(25), Fr::from(50), Fr::from(0), Fr::from(0)], - W: vec![Fr::from(35), Fr::from(9), Fr::from(27), Fr::from(30)], - rW: Fr::zero(), - }; - let rE = (0..log2(W_i.E.len())).map(|_| Fr::rand(&mut rng)).collect(); - // x is not important - let x = vec![Fr::from(35), Fr::from(9), Fr::from(27), Fr::from(30)]; - let U_i = - Witness::commit::, false>(&W_i, &pedersen_params, x.clone(), rE)?; - - let w_i = Witness { - E: vec![Fr::from(75), Fr::from(100), Fr::from(0), Fr::from(0)], - W: vec![Fr::from(35), Fr::from(9), Fr::from(27), Fr::from(30)], - rW: Fr::zero(), - }; - let rE = (0..log2(W_i.E.len())).map(|_| Fr::rand(&mut rng)).collect(); - let u_i = Witness::commit::, false>(&w_i, &pedersen_params, x, rE)?; - - let (proof, claim) = PointVsLine::prove(&mut transcript_p, &U_i, &u_i, &W_i, &w_i)?; - - let result = PointVsLine::verify( - &mut transcript_v, - &U_i, - &u_i, - &proof, - &claim.mleE1_prime, - &claim.mleE2_prime, - &claim.rE_prime, - ); - - assert!(result.is_ok(), "Verification failed"); - // Check if the re_prime is the same - let re_verified = result.unwrap(); - assert!(re_verified == claim.rE_prime); - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - // Pass the wrong committed instance which should result in a wrong evaluation in h returning an error - let result = PointVsLine::verify( - &mut transcript_v, - &U_i, - &U_i, - &proof, - &claim.mleE1_prime, - &claim.mleE2_prime, - &claim.rE_prime, - ); - - assert!(result.is_err(), "Verification was okay when it should fail"); - - Ok(()) - } -} diff --git a/folding-schemes/src/folding/nova/traits.rs b/folding-schemes/src/folding/nova/traits.rs deleted file mode 100644 index b187d7ab9..000000000 --- a/folding-schemes/src/folding/nova/traits.rs +++ /dev/null @@ -1,125 +0,0 @@ -use ark_r1cs_std::fields::fp::FpVar; -use ark_relations::gr1cs::SynthesisError; -use ark_std::{rand::RngCore, UniformRand}; - -use super::decider_eth_circuit::WitnessVar; -use super::nifs::nova_circuits::CommittedInstanceVar; -use super::{CommittedInstance, Witness}; -use crate::arith::{ - r1cs::{circuits::R1CSMatricesVar, R1CS}, - Arith, ArithRelation, ArithRelationGadget, ArithSampler, -}; -use crate::commitment::CommitmentScheme; -use crate::folding::circuits::CF1; -use crate::utils::gadgets::{EquivalenceGadget, VectorGadget}; -use crate::{Curve, Error}; - -/// Implements [`ArithRelation`] for R1CS, where the witness is of type -/// [`Witness`], and the committed instance is of type [`CommittedInstance`]. -/// -/// Due to the error terms `Witness.E` and `CommittedInstance.u`, R1CS here is -/// considered as a relaxed R1CS. -/// -/// One may wonder why we do not provide distinct structs for R1CS and relaxed -/// R1CS. -/// This is because both plain R1CS and relaxed R1CS have the same structure: -/// they are both represented by three matrices. -/// What makes them different is the error terms, which are not part of the R1CS -/// struct, but are part of the witness and committed instance. -/// -/// As a follow-up, one may further ask why not providing a trait for relaxed -/// R1CS and implement it for the [`R1CS`] struct, where the relaxed R1CS trait -/// has methods for relaxed satisfiability check, while the [`ArithRelation`] -/// trait that [`R1CS`] implements has methods for plain satisfiability check. -/// However, it would be more ideal if we have a single method that can smartly -/// choose the type of satisfiability check, which would make the code more -/// generic and easier to maintain. -/// -/// This is achieved thanks to the new design of the [`ArithRelation`] trait, -/// where we can implement the trait for the same constraint system with -/// different types of witnesses and committed instances. -/// For R1CS, whether it is relaxed or not is now determined by the types of `W` -/// and `U`: the satisfiability check is relaxed if `W` and `U` are defined by -/// folding schemes, and plain if they are vectors of field elements. -impl ArithRelation, CommittedInstance> for R1CS> { - type Evaluation = Vec>; - - fn eval_relation( - &self, - w: &Witness, - u: &CommittedInstance, - ) -> Result { - self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat()) - } - - fn check_evaluation( - w: &Witness, - _u: &CommittedInstance, - e: Self::Evaluation, - ) -> Result<(), Error> { - (w.E == e).then_some(()).ok_or(Error::NotSatisfied) - } -} - -impl ArithSampler, CommittedInstance> for R1CS> { - fn sample_witness_instance>( - &self, - params: &CS::ProverParams, - mut rng: impl RngCore, - ) -> Result<(Witness, CommittedInstance), Error> { - // Implements sampling a (committed) RelaxedR1CS - // See construction 5 in https://eprint.iacr.org/2023/573.pdf - let u = C::ScalarField::rand(&mut rng); - let rE = C::ScalarField::rand(&mut rng); - let rW = C::ScalarField::rand(&mut rng); - - let W = (0..self.n_witnesses()) - .map(|_| C::ScalarField::rand(&mut rng)) - .collect(); - let x = (0..self.n_public_inputs()) - .map(|_| C::ScalarField::rand(&mut rng)) - .collect::>(); - let mut z = vec![u]; - z.extend(&x); - z.extend(&W); - - let E = self.eval_at_z(&z)?; - - let witness = Witness { E, rE, W, rW }; - let mut cm_witness = witness.commit::(params, x)?; - - // witness.commit() sets u to 1, we set it to the sampled u value - cm_witness.u = u; - - debug_assert!( - self.check_relation(&witness, &cm_witness).is_ok(), - "Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}", - u, - witness.E - ); - - Ok((witness, cm_witness)) - } -} - -impl ArithRelationGadget, CommittedInstanceVar> - for R1CSMatricesVar> -{ - type Evaluation = (Vec>, Vec>); - - fn eval_relation( - &self, - w: &WitnessVar, - u: &CommittedInstanceVar, - ) -> Result { - self.eval_at_z(&[&[u.u.clone()][..], &u.x, &w.W].concat()) - } - - fn enforce_evaluation( - w: &WitnessVar, - _u: &CommittedInstanceVar, - (AzBz, uCz): Self::Evaluation, - ) -> Result<(), SynthesisError> { - EquivalenceGadget::::enforce_equivalent(&AzBz[..], &uCz.add(&w.E)?[..]) - } -} diff --git a/folding-schemes/src/folding/nova/zk.rs b/folding-schemes/src/folding/nova/zk.rs deleted file mode 100644 index b6ecd0f6a..000000000 --- a/folding-schemes/src/folding/nova/zk.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! Implements Nova's zero-knowledge layer, as described in https://eprint.iacr.org/2023/573.pdf. -//! -//! Remark: this zk layer implementation only covers a subset of the use cases: -//! -//! We identify 3 interesting places to use the nova zk-layer: one before all the folding pipeline -//! (Use-case-1), one at the end of the folding pipeline right before the final Decider SNARK -//! proof (Use-case-2), and a third one for cases where compressed SNARK proofs are not needed, and -//! just IVC proofs (bigger than SNARK proofs) suffice (Use-case-3): -//! -//! * Use-case-1: at the beginning of the folding pipeline, right when the user has their original -//! instance prior to be folded into the running instance, the user can fold it with the -//! random-satisfying-instance to then have a blinded instance that can be sent to a server that -//! will fold it with the running instance. -//! -//! --> In this one, the user could externalize all the IVC folding and also the Decider final -//! proof generation to a server. -//! -//! * Use-case-2: at the end of all the IVC folding steps (after n iterations of nova.prove_step), -//! to 'blind' the IVC proof so then it can be sent to a server that will generate the final -//! decider SNARK proof. -//! -//! --> In this one, the user could offload the Decider final proof generation to a server. -//! -//! * Use-case-3: the user does not care about the Decider (final compressed SNARK proof), and -//! wants to generate a zk-proof of the IVC state to an IVC verifier (without any SNARK proof -//! involved). In this use-case, the zk is only added at the last IVCProof. Note that this proof -//! will be much bigger and expensive to verify than a Decider SNARK proof. -//! -//! The current implementation covers the Use-case-3. -//! Use-case-1 can be achieved directly by a simpler version of the zk IVC scheme skipping steps -//! and implemented directly at the app level by folding the original instance with a randomized -//! instance (steps 2,3,4 from section D.4 of the [HyperNova](https://eprint.iacr.org/2023/573.pdf) -//! paper). -//! And the Use-case-2 would require a modified version of the Decider circuits. -use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; -use ark_std::{rand::RngCore, One, Zero}; - -use super::{ - nifs::{nova::NIFS, NIFSTrait}, - CommittedInstance, Nova, Witness, -}; -use crate::{ - arith::{r1cs::R1CS, ArithRelation, ArithSampler}, - commitment::CommitmentScheme, - folding::traits::CommittedInstanceOps, - frontend::FCircuit, - transcript::Transcript, - Curve, Error, -}; - -pub struct RandomizedIVCProof { - pub U_i: CommittedInstance, - pub u_i: CommittedInstance, - pub U_r: CommittedInstance, - pub pi: C1, // proof = cmT - pub pi_prime: C1, // proof' = cmT' - pub W_i_prime: Witness, - pub cf_U_i: CommittedInstance, - pub cf_W_i: Witness, -} - -impl RandomizedIVCProof { - /// Compute a zero-knowledge proof of a Nova IVC proof - /// It implements the prover of appendix D.4.in https://eprint.iacr.org/2023/573.pdf - /// For further details on why folding is hiding, see lemma 9 - pub fn new< - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - >( - nova: &Nova, - mut rng: impl RngCore, - ) -> Result, Error> { - let mut transcript = PoseidonSponge::::new_with_pp_hash( - &nova.poseidon_config, - nova.pp_hash, - ); - - // I. Compute proof for 'regular' instances - // 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i) - let (W_f, U_f, cmT, _) = NIFS::, true>::prove( - &nova.cs_pp, - &nova.r1cs, - &mut transcript, - &nova.w_i, - &nova.u_i, - &nova.W_i, - &nova.U_i, - )?; - - // 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r) - let (W_r, U_r) = nova - .r1cs - .sample_witness_instance::(&nova.cs_pp, &mut rng)?; - - // 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r) - let (W_i_prime, _, cmT_i_prime, _) = - NIFS::, true>::prove( - &nova.cs_pp, - &nova.r1cs, - &mut transcript, - &W_f, - &U_f, - &W_r, - &U_r, - )?; - - Ok(RandomizedIVCProof { - U_i: nova.U_i.clone(), - u_i: nova.u_i.clone(), - U_r, - pi: cmT, - pi_prime: cmT_i_prime, - W_i_prime, - cf_U_i: nova.cf_U_i.clone(), - cf_W_i: nova.cf_W_i.clone(), - }) - } - - /// Verify a zero-knowledge proof of a Nova IVC proof - /// It implements the verifier of appendix D.4. in https://eprint.iacr.org/2023/573.pdf - #[allow(clippy::too_many_arguments)] - pub fn verify, CS2: CommitmentScheme>( - r1cs: &R1CS, - cf_r1cs: &R1CS, - pp_hash: C1::ScalarField, - poseidon_config: &PoseidonConfig, - i: C1::ScalarField, - z_0: Vec, - z_i: Vec, - proof: &RandomizedIVCProof, - ) -> Result<(), Error> - where - C1: Curve, - { - // Handles case where i=0 - if i == C1::ScalarField::zero() { - if z_0 == z_i { - return Ok(()); - } else { - return Err(Error::zkIVCVerificationFail); - } - } - - // 1. Check that u_i.x is correct - including the cyclefold running instance - // a. Check length - if proof.u_i.x.len() != 2 { - return Err(Error::IVCVerificationFail); - } - - // b. Check computed hashes are correct - let sponge = PoseidonSponge::::new_with_pp_hash(poseidon_config, pp_hash); - let mut transcript = sponge.clone(); - let expected_u_i_x = proof.U_i.hash(&sponge, i, &z_0, &z_i); - if expected_u_i_x != proof.u_i.x[0] { - return Err(Error::zkIVCVerificationFail); - } - - let expected_cf_u_i_x = proof.cf_U_i.hash_cyclefold(&sponge); - if expected_cf_u_i_x != proof.u_i.x[1] { - return Err(Error::IVCVerificationFail); - } - - // 2. Check that u_i values are correct - if !proof.u_i.cmE.is_zero() || proof.u_i.u != C1::ScalarField::one() { - return Err(Error::zkIVCVerificationFail); - } - - // 3. Obtain the U_f folded instance - let (U_f, _) = NIFS::, true>::verify( - &mut transcript, - &proof.u_i, - &proof.U_i, - &proof.pi, - )?; - - // 4. Obtain the U^{\prime}_i folded instance - let (U_i_prime, _) = NIFS::, true>::verify( - &mut transcript, - &U_f, - &proof.U_r, - &proof.pi_prime, - )?; - - // 5. Check that W^{\prime}_i is a satisfying witness - r1cs.check_relation(&proof.W_i_prime, &U_i_prime)?; - - // 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs - cf_r1cs.check_relation(&proof.cf_W_i, &proof.cf_U_i)?; - - Ok(()) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::tests::test_ivc_opt; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - use ark_bn254::{Fr, G1Projective as Projective}; - use ark_grumpkin::Projective as Projective2; - use rand::rngs::OsRng; - - // Tests zk proof generation and verification for a valid nova IVC proof - #[test] - fn test_zk_nova_ivc() -> Result<(), Error> { - let mut rng = OsRng; - let poseidon_config = poseidon_canonical_config::(); - let F_circuit = CubicFCircuit::::new(())?; - let (_, nova) = test_ivc_opt::< - Pedersen, - Pedersen, - true, - >(poseidon_config.clone(), F_circuit, 3)?; - - let proof = RandomizedIVCProof::new(&nova, &mut rng)?; - let verify = - RandomizedIVCProof::verify::, Pedersen>( - &nova.r1cs, - &nova.cf_r1cs, - nova.pp_hash, - &nova.poseidon_config, - nova.i, - nova.z_0, - nova.z_i, - &proof, - ); - assert!(verify.is_ok()); - Ok(()) - } - - #[test] - fn test_zk_nova_when_i_is_zero() -> Result<(), Error> { - let mut rng = OsRng; - let poseidon_config = poseidon_canonical_config::(); - let F_circuit = CubicFCircuit::::new(())?; - let (_, nova) = test_ivc_opt::< - Pedersen, - Pedersen, - true, - >(poseidon_config.clone(), F_circuit, 0)?; - - let proof = RandomizedIVCProof::new(&nova, &mut rng)?; - let verify = - RandomizedIVCProof::verify::, Pedersen>( - &nova.r1cs, - &nova.cf_r1cs, - nova.pp_hash, - &nova.poseidon_config, - nova.i, - nova.z_0, - nova.z_i, - &proof, - ); - assert!(verify.is_ok()); - Ok(()) - } - - #[test] - fn test_zk_nova_verification_fails_with_wrong_running_instance() -> Result<(), Error> { - let mut rng = OsRng; - let poseidon_config = poseidon_canonical_config::(); - let F_circuit = CubicFCircuit::::new(())?; - let (_, nova) = test_ivc_opt::< - Pedersen, - Pedersen, - true, - >(poseidon_config.clone(), F_circuit, 3)?; - let (_, sampled_committed_instance) = nova - .r1cs - .sample_witness_instance::>(&nova.cs_pp, rng)?; - - // proof verification fails with incorrect running instance - let mut nova_with_incorrect_running_instance = nova.clone(); - nova_with_incorrect_running_instance.U_i = sampled_committed_instance; - let incorrect_proof = - RandomizedIVCProof::new(&nova_with_incorrect_running_instance, &mut rng)?; - let verify = - RandomizedIVCProof::verify::, Pedersen>( - &nova_with_incorrect_running_instance.r1cs, - &nova_with_incorrect_running_instance.cf_r1cs, - nova_with_incorrect_running_instance.pp_hash, - &nova_with_incorrect_running_instance.poseidon_config, - nova_with_incorrect_running_instance.i, - nova_with_incorrect_running_instance.z_0, - nova_with_incorrect_running_instance.z_i, - &incorrect_proof, - ); - assert!(verify.is_err()); - Ok(()) - } - - #[test] - fn test_zk_nova_verification_fails_with_wrong_running_witness() -> Result<(), Error> { - let mut rng = OsRng; - let poseidon_config = poseidon_canonical_config::(); - let F_circuit = CubicFCircuit::::new(())?; - let (_, nova) = test_ivc_opt::< - Pedersen, - Pedersen, - true, - >(poseidon_config.clone(), F_circuit, 3)?; - let (sampled_committed_witness, _) = nova - .r1cs - .sample_witness_instance::>(&nova.cs_pp, rng)?; - - // proof generation fails with incorrect running witness - let mut nova_with_incorrect_running_witness = nova.clone(); - nova_with_incorrect_running_witness.W_i = sampled_committed_witness; - let incorrect_proof = - RandomizedIVCProof::new(&nova_with_incorrect_running_witness, &mut rng)?; - let verify = - RandomizedIVCProof::verify::, Pedersen>( - &nova_with_incorrect_running_witness.r1cs, - &nova_with_incorrect_running_witness.cf_r1cs, - nova_with_incorrect_running_witness.pp_hash, - &nova_with_incorrect_running_witness.poseidon_config, - nova_with_incorrect_running_witness.i, - nova_with_incorrect_running_witness.z_0, - nova_with_incorrect_running_witness.z_i, - &incorrect_proof, - ); - assert!(verify.is_err()); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/protogalaxy/circuits.rs b/folding-schemes/src/folding/protogalaxy/circuits.rs deleted file mode 100644 index ae178e2fb..000000000 --- a/folding-schemes/src/folding/protogalaxy/circuits.rs +++ /dev/null @@ -1,451 +0,0 @@ -use ark_crypto_primitives::sponge::{ - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, - CryptographicSponge, -}; -use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain}; -use ark_r1cs_std::{ - alloc::AllocVar, - convert::ToBitsGadget, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - poly::polynomial::univariate::dense::DensePolynomialVar, - GR1CSVar, -}; -use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::{fmt::Debug, Zero}; - -use super::{ - folding::lagrange_polys, - utils::{all_powers_var, betas_star_var, exponential_powers_var}, - CommittedInstance, CommittedInstanceVar, ProtoGalaxyCycleFoldConfig, -}; -use crate::{ - folding::{ - circuits::{ - cyclefold::{ - CycleFoldAugmentationGadget, CycleFoldCommittedInstance, - CycleFoldCommittedInstanceVar, CycleFoldConfig, - }, - nonnative::affine::NonNativeAffineVar, - CF1, - }, - traits::{CommittedInstanceVarOps, Dummy}, - }, - frontend::FCircuit, - transcript::TranscriptVar, - utils::gadgets::VectorGadget, - Curve, -}; - -pub struct FoldingGadget {} - -impl FoldingGadget { - #[allow(clippy::type_complexity)] - pub fn fold_committed_instance( - transcript: &mut impl TranscriptVar, - // running instance - instance: &CommittedInstanceVar, - // incoming instances - vec_instances: &[CommittedInstanceVar], - // polys from P - F_coeffs: Vec>, - K_coeffs: Vec>, - ) -> Result<(CommittedInstanceVar, Vec>), SynthesisError> { - let t = instance.betas.len(); - - // absorb the committed instances - transcript.absorb(instance)?; - transcript.absorb(&vec_instances)?; - - let delta = transcript.get_challenge()?; - let deltas = exponential_powers_var(delta, t); - - transcript.absorb(&F_coeffs)?; - - let alpha = transcript.get_challenge()?; - let alphas = all_powers_var(alpha.clone(), t); - - // F(alpha) = e + \sum_t F_i * alpha^i - let mut F_alpha = instance.e.clone(); - for (i, F_i) in F_coeffs.iter().skip(1).enumerate() { - F_alpha += F_i * &alphas[i + 1]; - } - - let betas_star = betas_star_var(&instance.betas, &deltas, &alpha); - - let k = vec_instances.len(); - let H = - GeneralEvaluationDomain::new(k + 1).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; - let L_X = lagrange_polys(H) - .into_iter() - .map(|poly| { - DensePolynomialVar::from_coefficients_vec( - poly.coeffs - .into_iter() - .map(FpVar::constant) - .collect::>(), - ) - }) - .collect::>(); - let Z_X = DensePolynomialVar::from_coefficients_vec( - DensePolynomial::from(H.vanishing_polynomial()) - .coeffs - .into_iter() - .map(FpVar::constant) - .collect::>(), - ); - let K_X = DensePolynomialVar { coeffs: K_coeffs }; - - transcript.absorb(&K_X.coeffs)?; - - let gamma = transcript.get_challenge()?; - - let L_X_evals = L_X - .iter() - .take(k + 1) - .map(|L| L.evaluate(&gamma)) - .collect::, _>>()?; - - let e_star = F_alpha * &L_X_evals[0] + Z_X.evaluate(&gamma)? * K_X.evaluate(&gamma)?; - - let mut x_star = instance.x.mul_scalar(&L_X_evals[0])?; - for i in 0..k { - x_star = x_star.add(&vec_instances[i].x.mul_scalar(&L_X_evals[i + 1])?)?; - } - - // return the folded instance - Ok(( - CommittedInstanceVar { - betas: betas_star, - // phi will be computed in CycleFold - phi: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, - e: e_star, - x: x_star, - }, - L_X_evals, - )) - } -} - -pub struct AugmentationGadget; - -impl AugmentationGadget { - #[allow(clippy::type_complexity)] - pub fn prepare_and_fold_primary( - transcript: &mut impl TranscriptVar, S>, - U: CommittedInstanceVar, - u_phis: Vec>, - u_xs: Vec>>>, - new_U_phi: NonNativeAffineVar, - F_coeffs: Vec>>, - K_coeffs: Vec>>, - ) -> Result<(CommittedInstanceVar, Vec>>), SynthesisError> { - assert_eq!(u_phis.len(), u_xs.len()); - - // Prepare the incoming instances. - // For each instance `u`, we have `u.betas = []`, `u.e = 0`. - let us = u_phis - .into_iter() - .zip(u_xs) - .map(|(phi, x)| CommittedInstanceVar { - phi, - betas: vec![], - e: FpVar::zero(), - x, - }) - .collect::>(); - - // Fold the incoming instances `us` into the running instance `U`. - let (mut U, L_X_evals) = - FoldingGadget::fold_committed_instance(transcript, &U, &us, F_coeffs, K_coeffs)?; - // Notice that FoldingGadget::fold_committed_instance does not fold phi. - // We set `U.phi` to unconstrained witnesses `U_phi` here, whose - // correctness will be checked on the other curve. - U.phi = new_U_phi; - - Ok((U, L_X_evals)) - } -} - -/// `AugmentedFCircuit` enhances the original step function `F`, so that it can -/// be used in recursive arguments such as IVC. -/// -/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in -/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not -/// only invokes `F`, but also adds additional constraints for verifying the -/// correct folding of primary instances (i.e., the instances over `C1`). -/// In the paper, the primary instances are Nova's `CommittedInstance`, but we -/// extend this method to support using ProtoGalaxy's `CommittedInstance` as -/// primary instances. -/// -/// Furthermore, to reduce circuit size over `C2`, we implement the constraints -/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra -/// constraints verify the correct folding of CycleFold instances. -#[derive(Debug, Clone)] -pub struct AugmentedFCircuit>> { - pub(super) poseidon_config: PoseidonConfig>, - pub(super) pp_hash: CF1, - pub(super) i: CF1, - pub(super) i_usize: usize, - pub(super) z_0: Vec>, - pub(super) z_i: Vec>, - pub(super) external_inputs: FC::ExternalInputs, - pub(super) F: FC, // F circuit - pub(super) u_i_phi: C1, - pub(super) U_i: CommittedInstance, - pub(super) U_i1_phi: C1, - pub(super) F_coeffs: Vec>, - pub(super) K_coeffs: Vec>, - - pub(super) cf_u_i_cmW: C2, // input - pub(super) cf_U_i: CycleFoldCommittedInstance, // input - pub(super) cf_cmT: C2, -} - -impl>> AugmentedFCircuit { - pub fn empty( - poseidon_config: &PoseidonConfig>, - F_circuit: FC, - t: usize, - d: usize, - k: usize, - ) -> Self { - let u_dummy = CommittedInstance::dummy((2, t)); - let cf_u_dummy = - CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::::IO_LEN); - - Self { - poseidon_config: poseidon_config.clone(), - pp_hash: CF1::::zero(), - i: CF1::::zero(), - i_usize: 0, - z_0: vec![CF1::::zero(); F_circuit.state_len()], - z_i: vec![CF1::::zero(); F_circuit.state_len()], - external_inputs: FC::ExternalInputs::default(), - u_i_phi: C1::zero(), - U_i: u_dummy, - U_i1_phi: C1::zero(), - F_coeffs: vec![CF1::::zero(); t], - K_coeffs: vec![CF1::::zero(); d * k + 1], - F: F_circuit, - // cyclefold values - cf_u_i_cmW: C2::zero(), - cf_U_i: cf_u_dummy, - cf_cmT: C2::zero(), - } - } -} - -impl AugmentedFCircuit -where - C1: Curve, - C2: Curve, - FC: FCircuit>, -{ - pub fn compute_next_state( - self, - cs: ConstraintSystemRef>, - ) -> Result>>, SynthesisError> { - let pp_hash = FpVar::>::new_witness(cs.clone(), || Ok(self.pp_hash))?; - let i = FpVar::>::new_witness(cs.clone(), || Ok(self.i))?; - let z_0 = Vec::>>::new_witness(cs.clone(), || Ok(self.z_0))?; - let z_i = Vec::>>::new_witness(cs.clone(), || Ok(self.z_i))?; - let external_inputs = - FC::ExternalInputsVar::new_witness(cs.clone(), || Ok(self.external_inputs))?; - - let u_dummy = CommittedInstance::::dummy((2, self.U_i.betas.len())); - let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(self.U_i))?; - let u_i_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.u_i_phi))?; - let U_i1_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.U_i1_phi))?; - - let cf_u_dummy = - CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::::IO_LEN); - let cf_U_i = - CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || Ok(self.cf_U_i))?; - let cf_cmT = C2::Var::new_witness(cs.clone(), || Ok(self.cf_cmT))?; - - let F_coeffs = Vec::new_witness(cs.clone(), || Ok(self.F_coeffs))?; - let K_coeffs = Vec::new_witness(cs.clone(), || Ok(self.K_coeffs))?; - - // `sponge` is for digest computation. - let sponge = PoseidonSpongeVar::new_with_pp_hash(&self.poseidon_config, &pp_hash)?; - // `transcript` is for challenge generation. - let mut transcript = sponge.clone(); - - let is_basecase = i.is_zero()?; - - // Primary Part - // P.1. Compute u_i.x - // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i.clone().hash(&sponge, &i, &z_0, &z_i)?; - // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge)?; - - // P.2. Prepare incoming primary instances - // P.3. Fold incoming primary instances into the running instance - let (U_i1, r) = AugmentationGadget::prepare_and_fold_primary( - &mut transcript, - U_i.clone(), - vec![u_i_phi.clone()], - vec![vec![u_i_x, cf_u_i_x]], - U_i1_phi, - F_coeffs, - K_coeffs, - )?; - - // P.4.a compute and check the first output of F' - - // get z_{i+1} from the F circuit - let z_i1 = - self.F - .generate_step_constraints(cs.clone(), self.i_usize, z_i, external_inputs)?; - - // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot}) - // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1}) - let (u_i1_x, _) = - U_i1.clone() - .hash(&sponge, &(i + FpVar::>::one()), &z_0, &z_i1)?; - let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash( - &sponge, - &FpVar::>::one(), - &z_0, - &z_i1, - )?; - let x = is_basecase.select(&u_i1_x_base, &u_i1_x)?; - // This line "converts" `x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `x`. - // While comparing `x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `x` computed in-circuit. - FpVar::new_input(cs.clone(), || x.value())?.enforce_equal(&x)?; - - // CycleFold part - // C.1. Compute `cf_u_i.x` - // C.2. Construct `cf_u_i` - let cf_u_i = CycleFoldCommittedInstanceVar::new_incoming_from_components( - // `cf_u_i.cmW` is provided by the prover as witness. - C2::Var::new_witness(cs.clone(), || Ok(self.cf_u_i_cmW))?, - // To construct `cf_u_i.x`, we need to provide the randomness `r` as - // well as the `phi` component in committed instances `U_i`, `u_i`, - // and `U_{i+1}`. - // Note that the randomness `r` is converted to `r_0, r_1 / r_0` due - // to how `ProtoGalaxyCycleFoldConfig::alloc_randomnesses` creates - // randomness in the CycleFold circuit. - &[ - r[0].to_bits_le()?, - r[1].mul_by_inverse(&r[0])?.to_bits_le()?, - ] - .concat(), - vec![U_i.phi, u_i_phi, U_i1.phi], - )?; - - // C.2. Prepare incoming CycleFold instances - // C.3. Fold incoming CycleFold instances into the running instance - let cf_U_i1 = CycleFoldAugmentationGadget::fold_gadget( - &mut transcript, - cf_U_i, - vec![cf_u_i], - vec![cf_cmT], - )?; - - // Back to Primary Part - // P.4.b compute and check the second output of F' - // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) - // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge)?; - let (cf_u_i1_x_base, _) = - CycleFoldCommittedInstanceVar::::new_constant(cs.clone(), cf_u_dummy)? - .hash(&sponge)?; - let cf_x = is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?; - // This line "converts" `cf_x` from a witness to a public input. - // Instead of directly modifying the constraint system, we explicitly - // allocate a public input and enforce that its value is indeed `cf_x`. - // While comparing `cf_x` with itself seems redundant, this is necessary - // because: - // - `.value()` allows an honest prover to extract public inputs without - // computing them outside the circuit. - // - `.enforce_equal()` prevents a malicious prover from claiming wrong - // public inputs that are not the honest `cf_x` computed in-circuit. - FpVar::new_input(cs.clone(), || cf_x.value())?.enforce_equal(&cf_x)?; - - Ok(z_i1) - } -} - -impl ConstraintSynthesizer> for AugmentedFCircuit -where - C1: Curve, - C2: Curve, - FC: FCircuit>, -{ - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - self.compute_next_state(cs).map(|_| ()) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - use crate::{ - arith::r1cs::tests::get_test_r1cs, - folding::protogalaxy::folding::{tests::prepare_inputs, Folding}, - transcript::{poseidon::poseidon_canonical_config, Transcript}, - Error, - }; - - use ark_bn254::{Fr, G1Projective as Projective}; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_relations::gr1cs::ConstraintSystem; - - #[test] - fn test_folding_gadget() -> Result<(), Error> { - let k = 7; - let (witness, instance, witnesses, instances) = prepare_inputs(k)?; - let r1cs = get_test_r1cs::(); - - // init Prover & Verifier's transcript - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from(42u32); // only for testing - let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); - let mut transcript_v = transcript_p.clone(); - - let (_, _, proof, _) = Folding::::prove( - &mut transcript_p, - &r1cs, - &instance, - &witness, - &instances, - &witnesses, - )?; - - let folded_instance = - Folding::::verify(&mut transcript_v, &instance, &instances, proof.clone())?; - - let cs = ConstraintSystem::new_ref(); - let pp_hash_var = FpVar::new_witness(cs.clone(), || Ok(pp_hash))?; - let mut transcript_var = - PoseidonSpongeVar::new_with_pp_hash(&poseidon_config, &pp_hash_var)?; - let instance_var = CommittedInstanceVar::new_witness(cs.clone(), || Ok(instance))?; - let instances_var = Vec::new_witness(cs.clone(), || Ok(instances))?; - let F_coeffs_var = Vec::new_witness(cs.clone(), || Ok(proof.F_coeffs))?; - let K_coeffs_var = Vec::new_witness(cs.clone(), || Ok(proof.K_coeffs))?; - - let (folded_instance_var, _) = FoldingGadget::fold_committed_instance( - &mut transcript_var, - &instance_var, - &instances_var, - F_coeffs_var, - K_coeffs_var, - )?; - assert_eq!(folded_instance.betas, folded_instance_var.betas.value()?); - assert_eq!(folded_instance.e, folded_instance_var.e.value()?); - assert_eq!(folded_instance.x, folded_instance_var.x.value()?); - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/protogalaxy/constants.rs b/folding-schemes/src/folding/protogalaxy/constants.rs deleted file mode 100644 index cadbf103d..000000000 --- a/folding-schemes/src/folding/protogalaxy/constants.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// `RUNNING` indicates that the committed instance is a running instance. -pub const RUNNING: bool = true; -/// `INCOMING` indicates that the committed instance is an incoming instance. -pub const INCOMING: bool = false; diff --git a/folding-schemes/src/folding/protogalaxy/decider_eth.rs b/folding-schemes/src/folding/protogalaxy/decider_eth.rs deleted file mode 100644 index 54613e246..000000000 --- a/folding-schemes/src/folding/protogalaxy/decider_eth.rs +++ /dev/null @@ -1,492 +0,0 @@ -/// This file implements the Protogalaxy's onchain (Ethereum's EVM) decider. For non-ethereum use cases, -/// the Decider from decider.rs file will be more efficient. -/// More details can be found at the documentation page: -/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_snark::SNARK; -use ark_std::{ - log2, - marker::PhantomData, - rand::{CryptoRng, RngCore}, - One, Zero, -}; - -pub use super::decider_eth_circuit::DeciderEthCircuit; -use super::decider_eth_circuit::DeciderProtoGalaxyGadget; -use super::ProtoGalaxy; -use crate::arith::Arith; -use crate::folding::traits::{InputizeNonNative, WitnessOps}; -use crate::folding::{circuits::decider::DeciderEnabledNIFS, traits::Dummy}; -use crate::frontend::FCircuit; -use crate::Error; -use crate::{ - commitment::{kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme}, - Curve, -}; -use crate::{Decider as DeciderTrait, FoldingScheme}; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Proof -where - C: Curve, - CS: CommitmentScheme, - S: SNARK, -{ - snark_proof: S::Proof, - kzg_proofs: [CS::Proof; 1], - L_X_evals: Vec, - // the KZG challenges are provided by the prover, but in-circuit they are checked to match - // the in-circuit computed computed ones. - kzg_challenges: [C::ScalarField; 1], -} - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct VerifierParam -where - C1: Curve, - CS_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, - S_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, -{ - pub pp_hash: C1::ScalarField, - pub snark_vp: S_VerifyingKey, - pub cs_vp: CS_VerifyingKey, -} - -/// Onchain Decider, for ethereum use cases -#[derive(Clone, Debug)] -pub struct Decider { - _c1: PhantomData, - _c2: PhantomData, - _fc: PhantomData, - _cs1: PhantomData, - _cs2: PhantomData, - _s: PhantomData, - _fs: PhantomData, -} - -impl DeciderTrait - for Decider -where - C1: Curve, - C2: Curve, - FC: FCircuit, - // CS1 is a KZG commitment, where challenge is C1::Fr elem - CS1: CommitmentScheme< - C1, - ProverChallenge = C1::ScalarField, - Challenge = C1::ScalarField, - Proof = KZGProof, - >, - // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider - CS2: CommitmentScheme>, - S: SNARK, - FS: FoldingScheme, - // constrain FS into ProtoGalaxy, since this is a Decider specifically for ProtoGalaxy - ProtoGalaxy: From, - crate::folding::protogalaxy::ProverParams: - From<>::ProverParam>, - crate::folding::protogalaxy::VerifierParams: - From<>::VerifierParam>, -{ - type PreprocessorParam = ((FS::ProverParam, FS::VerifierParam), usize); - type ProverParam = (S::ProvingKey, CS1::ProverParams); - type Proof = Proof; - type VerifierParam = VerifierParam; - type PublicInput = Vec; - type CommittedInstance = Vec; - - fn preprocess( - mut rng: impl RngCore + CryptoRng, - ((pp, vp), state_len): Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - // get the FoldingScheme prover & verifier params from ProtoGalaxy - let protogalaxy_pp: as FoldingScheme< - C1, - C2, - FC, - >>::ProverParam = pp.into(); - let protogalaxy_vp: as FoldingScheme< - C1, - C2, - FC, - >>::VerifierParam = vp.into(); - let pp_hash = protogalaxy_vp.pp_hash()?; - - // We fix `k`, the number of incoming instances, to 1, because - // multi-instances folding is not supported yet. - // TODO: Support multi-instances folding and make `k` a constant generic parameter (as in - // HyperNova). Tracking issue: - // https://github.com/privacy-scaling-explorations/sonobe/issues/82 - let k = 1; - let d = protogalaxy_vp.r1cs.degree(); - let t = log2(protogalaxy_vp.r1cs.n_constraints()) as usize; - - let circuit = DeciderEthCircuit::::dummy(( - protogalaxy_vp.r1cs, - protogalaxy_vp.cf_r1cs, - protogalaxy_pp.cf_cs_params, - protogalaxy_pp.poseidon_config, - (t, d, k), - k + 1, // `k + 1` is the length of `L_X_evals` - state_len, - 1, // ProtoGalaxy's running CommittedInstance contains 1 commitment - )); - - // get the Groth16 specific setup for the circuit - let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng) - .map_err(|e| Error::SNARKSetupFail(e.to_string()))?; - - let pp = (g16_pk, protogalaxy_pp.cs_params); - let vp = Self::VerifierParam { - pp_hash, - snark_vp: g16_vk, - cs_vp: protogalaxy_vp.cs_vp, - }; - Ok((pp, vp)) - } - - fn prove( - mut rng: impl RngCore + CryptoRng, - pp: Self::ProverParam, - folding_scheme: FS, - ) -> Result { - let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp; - - let circuit = DeciderEthCircuit::::try_from(ProtoGalaxy::from(folding_scheme))?; - - let L_X_evals = circuit.randomness.clone(); - - // get the challenges that have been already computed when preparing the circuit inputs in - // the above `try_from` call - let kzg_challenges = circuit.kzg_challenges.clone(); - - // generate KZG proofs - let kzg_proofs = circuit - .W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| { - CS1::prove_with_challenge(&cs_pk, c, v, &C1::ScalarField::zero(), None) - }) - .collect::, _>>()?; - - let snark_proof = - S::prove(&snark_pk, circuit, &mut rng).map_err(|e| Error::Other(e.to_string()))?; - - Ok(Self::Proof { - snark_proof, - L_X_evals, - kzg_proofs: kzg_proofs.try_into().map_err(|_| { - Error::ConversionError( - "Vec<_>".to_string(), - "[_; 1]".to_string(), - "variable name: kzg_proofs".to_string(), - ) - })?, - kzg_challenges: kzg_challenges.try_into().map_err(|_| { - Error::ConversionError( - "Vec<_>".to_string(), - "[_; 1]".to_string(), - "variable name: kzg_challenges".to_string(), - ) - })?, - }) - } - - fn verify( - vp: Self::VerifierParam, - i: C1::ScalarField, - z_0: Vec, - z_i: Vec, - // we don't use the instances at the verifier level, since we check them in-circuit - running_commitments: &Self::CommittedInstance, - incoming_commitments: &Self::CommittedInstance, - proof: &Self::Proof, - ) -> Result { - if i <= C1::ScalarField::one() { - return Err(Error::NotEnoughSteps); - } - - let Self::VerifierParam { - pp_hash, - snark_vp, - cs_vp, - } = vp; - - // 6.2. Fold the commitments - let U_final_commitments = DeciderProtoGalaxyGadget::fold_group_elements_native( - running_commitments, - incoming_commitments, - None, - proof.L_X_evals.clone(), - )?; - - let public_input = [ - &[pp_hash, i][..], - &z_0, - &z_i, - &U_final_commitments.inputize_nonnative(), - &proof.kzg_challenges, - &proof.kzg_proofs.iter().map(|p| p.eval).collect::>(), - &proof.L_X_evals, - ] - .concat(); - - let snark_v = S::verify(&snark_vp, &public_input, &proof.snark_proof) - .map_err(|e| Error::Other(e.to_string()))?; - if !snark_v { - return Err(Error::SNARKVerificationFail); - } - - // 7.3. Verify the KZG proofs - for ((cm, &c), pi) in U_final_commitments - .iter() - .zip(&proof.kzg_challenges) - .zip(&proof.kzg_proofs) - { - // we're at the Ethereum EVM case, so the CS1 is KZG commitments - CS1::verify_with_challenge(&cs_vp, c, cm, pi)?; - } - - Ok(true) - } -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::Bn254; - use ark_bn254::{Fr, G1Projective as Projective}; - use ark_groth16::Groth16; - use ark_grumpkin::Projective as Projective2; - use std::time::Instant; - - use super::*; - use crate::commitment::kzg::KZG; - use crate::commitment::pedersen::Pedersen; - use crate::folding::protogalaxy::ProverParams; - use crate::folding::traits::CommittedInstanceOps; - use crate::frontend::utils::CubicFCircuit; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::Error; - - #[test] - fn test_decider() -> Result<(), Error> { - // use ProtoGalaxy as FoldingScheme - type PG = ProtoGalaxy< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - >; - type D = Decider< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, // here we define the Snark to use in the decider - PG, // here we define the FoldingScheme to use - >; - - let mut rng = rand::rngs::OsRng; - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - let preprocessor_param = (poseidon_config, F_circuit); - let protogalaxy_params = PG::preprocess(&mut rng, &preprocessor_param)?; - - let start = Instant::now(); - let mut protogalaxy = PG::init(&protogalaxy_params, F_circuit, z_0.clone())?; - println!("ProtoGalaxy initialized, {:?}", start.elapsed()); - protogalaxy.prove_step(&mut rng, (), None)?; - protogalaxy.prove_step(&mut rng, (), None)?; // do a 2nd step - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = - D::preprocess(&mut rng, (protogalaxy_params, F_circuit.state_len()))?; - - // decider proof generation - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, protogalaxy.clone())?; - println!("Decider prove, {:?}", start.elapsed()); - - // decider proof verification - let start = Instant::now(); - let verified = D::verify( - decider_vp.clone(), - protogalaxy.i, - protogalaxy.z_0.clone(), - protogalaxy.z_i.clone(), - &protogalaxy.U_i.get_commitments(), - &protogalaxy.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider verify, {:?}", start.elapsed()); - - // decider proof verification using the deserialized data - let verified = D::verify( - decider_vp, - protogalaxy.i, - protogalaxy.z_0, - protogalaxy.z_i, - &protogalaxy.U_i.get_commitments(), - &protogalaxy.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - Ok(()) - } - - // Test to check the serialization and deserialization of diverse Decider related parameters. - // This test is the same test as `test_decider` but it serializes values and then uses the - // deserialized values to continue the checks. - #[test] - fn test_decider_serialization() -> Result<(), Error> { - // use ProtoGalaxy as FoldingScheme - type PG = ProtoGalaxy< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - >; - type D = Decider< - Projective, - Projective2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - Groth16, // here we define the Snark to use in the decider - PG, // here we define the FoldingScheme to use - >; - - let mut rng = rand::rngs::OsRng; - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - let preprocessor_param = (poseidon_config, F_circuit); - let protogalaxy_params = PG::preprocess(&mut rng, &preprocessor_param)?; - - // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = D::preprocess( - &mut rng, - (protogalaxy_params.clone(), F_circuit.state_len()), - )?; - - // serialize the Nova params. These params are the trusted setup of the commitment schemes used - // (ie. KZG & Pedersen in this case) - let mut protogalaxy_pp_serialized = vec![]; - protogalaxy_params - .0 - .serialize_compressed(&mut protogalaxy_pp_serialized)?; - let mut protogalaxy_vp_serialized = vec![]; - protogalaxy_params - .1 - .serialize_compressed(&mut protogalaxy_vp_serialized)?; - // deserialize the Nova params. This would be done by the client reading from a file - let protogalaxy_pp_deserialized = ProverParams::< - Projective, - Projective2, - KZG<'static, Bn254>, - Pedersen, - >::deserialize_compressed( - &mut protogalaxy_pp_serialized.as_slice() - )?; - let protogalaxy_vp_deserialized = , - >>::vp_deserialize_with_mode( - &mut protogalaxy_vp_serialized.as_slice(), - ark_serialize::Compress::Yes, - ark_serialize::Validate::Yes, - (), // fcircuit_params - )?; - - // initialize protogalaxy again, but from the deserialized parameters - let protogalaxy_params = (protogalaxy_pp_deserialized, protogalaxy_vp_deserialized); - let mut protogalaxy = PG::init(&protogalaxy_params, F_circuit, z_0)?; - - let start = Instant::now(); - protogalaxy.prove_step(&mut rng, (), None)?; - println!("prove_step, {:?}", start.elapsed()); - protogalaxy.prove_step(&mut rng, (), None)?; // do a 2nd step - - // decider proof generation - let start = Instant::now(); - let proof = D::prove(rng, decider_pp, protogalaxy.clone())?; - println!("Decider prove, {:?}", start.elapsed()); - - // decider proof verification - let start = Instant::now(); - let verified = D::verify( - decider_vp.clone(), - protogalaxy.i, - protogalaxy.z_0.clone(), - protogalaxy.z_i.clone(), - &protogalaxy.U_i.get_commitments(), - &protogalaxy.u_i.get_commitments(), - &proof, - )?; - assert!(verified); - println!("Decider verify, {:?}", start.elapsed()); - - // The rest of this test will serialize the data and deserialize it back, and use it to - // verify the proof: - - // serialize the verifier_params, proof and public inputs - let mut decider_vp_serialized = vec![]; - decider_vp.serialize_compressed(&mut decider_vp_serialized)?; - let mut proof_serialized = vec![]; - proof.serialize_compressed(&mut proof_serialized)?; - // serialize the public inputs in a single packet - let mut public_inputs_serialized = vec![]; - protogalaxy - .i - .serialize_compressed(&mut public_inputs_serialized)?; - protogalaxy - .z_0 - .serialize_compressed(&mut public_inputs_serialized)?; - protogalaxy - .z_i - .serialize_compressed(&mut public_inputs_serialized)?; - - // deserialize back the verifier_params, proof and public inputs - let decider_vp_deserialized = - VerifierParam::< - Projective, - as CommitmentScheme>::VerifierParams, - as SNARK>::VerifyingKey, - >::deserialize_compressed(&mut decider_vp_serialized.as_slice())?; - let proof_deserialized = - Proof::, Groth16>::deserialize_compressed( - &mut proof_serialized.as_slice(), - )?; - - // deserialize the public inputs from the single packet 'public_inputs_serialized' - let mut reader = public_inputs_serialized.as_slice(); - let i_deserialized = Fr::deserialize_compressed(&mut reader)?; - let z_0_deserialized = Vec::::deserialize_compressed(&mut reader)?; - let z_i_deserialized = Vec::::deserialize_compressed(&mut reader)?; - - // decider proof verification using the deserialized data - let verified = D::verify( - decider_vp_deserialized, - i_deserialized, - z_0_deserialized, - z_i_deserialized, - &protogalaxy.U_i.get_commitments(), - &protogalaxy.u_i.get_commitments(), - &proof_deserialized, - )?; - assert!(verified); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs b/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs deleted file mode 100644 index 874325307..000000000 --- a/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs +++ /dev/null @@ -1,239 +0,0 @@ -/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, -/// other more efficient approaches can be used. -use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - eq::EqGadget, - fields::fp::FpVar, - GR1CSVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, marker::PhantomData}; - -use crate::{ - arith::r1cs::{circuits::R1CSMatricesVar, R1CS}, - commitment::{pedersen::Params as PedersenParams, CommitmentScheme}, - folding::{ - circuits::{ - decider::{ - on_chain::GenericOnchainDeciderCircuit, DeciderEnabledNIFS, EvalGadget, - KZGChallengesGadget, - }, - CF1, - }, - traits::{WitnessOps, WitnessVarOps}, - }, - frontend::FCircuit, - transcript::Transcript, - Curve, Error, -}; - -use super::{ - circuits::FoldingGadget, - constants::{INCOMING, RUNNING}, - folding::{Folding, ProtoGalaxyProof}, - CommittedInstance, CommittedInstanceVar, ProtoGalaxy, Witness, -}; - -/// In-circuit representation of the Witness associated to the CommittedInstance. -#[derive(Debug, Clone)] -pub struct WitnessVar { - pub W: Vec>, - pub rW: FpVar, -} - -impl AllocVar, F> for WitnessVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let W = Vec::new_variable(cs.clone(), || Ok(val.borrow().w.to_vec()), mode)?; - let rW = FpVar::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?; - - Ok(Self { W, rW }) - }) - } -} - -impl WitnessVarOps for WitnessVar { - fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { - vec![(&self.W, self.rW.clone())] - } -} - -pub type DeciderEthCircuit = GenericOnchainDeciderCircuit< - C1, - C2, - CommittedInstance, - CommittedInstance, - Witness>, - R1CS>, - R1CSMatricesVar, FpVar>>, - DeciderProtoGalaxyGadget, ->; - -/// returns an instance of the DeciderEthCircuit from the given ProtoGalaxy struct -impl< - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider - CS2: CommitmentScheme>, - > TryFrom> for DeciderEthCircuit -{ - type Error = Error; - - fn try_from(protogalaxy: ProtoGalaxy) -> Result { - let mut transcript = - PoseidonSponge::new_with_pp_hash(&protogalaxy.poseidon_config, protogalaxy.pp_hash); - - let (U_i1, W_i1, proof, aux) = Folding::prove( - &mut transcript, - &protogalaxy.r1cs, - &protogalaxy.U_i, - &protogalaxy.W_i, - &[protogalaxy.u_i.clone()], - &[protogalaxy.w_i.clone()], - )?; - - // compute the KZG challenges used as inputs in the circuit - let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1); - - // get KZG evals - let kzg_evaluations = W_i1 - .get_openings() - .iter() - .zip(&kzg_challenges) - .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c)) - .collect::, _>>()?; - - Ok(Self { - _avar: PhantomData, - arith: protogalaxy.r1cs, - cf_arith: protogalaxy.cf_r1cs, - cf_pedersen_params: protogalaxy.cf_cs_params, - poseidon_config: protogalaxy.poseidon_config, - pp_hash: protogalaxy.pp_hash, - i: protogalaxy.i, - z_0: protogalaxy.z_0, - z_i: protogalaxy.z_i, - U_i: protogalaxy.U_i, - W_i: protogalaxy.W_i, - u_i: protogalaxy.u_i, - w_i: protogalaxy.w_i, - U_i1, - W_i1, - proof, - randomness: aux.L_X_evals, - cf_U_i: protogalaxy.cf_U_i, - cf_W_i: protogalaxy.cf_W_i, - kzg_challenges, - kzg_evaluations, - }) - } -} - -pub struct DeciderProtoGalaxyGadget; - -impl - DeciderEnabledNIFS< - C, - CommittedInstance, - CommittedInstance, - Witness>, - R1CS>, - > for DeciderProtoGalaxyGadget -{ - type Proof = ProtoGalaxyProof>; - type ProofDummyCfg = (usize, usize, usize); - type Randomness = Vec>; - type RandomnessDummyCfg = usize; - - fn fold_field_elements_gadget( - _arith: &R1CS>, - transcript: &mut PoseidonSpongeVar>, - U: CommittedInstanceVar, - _U_vec: Vec>>, - u: CommittedInstanceVar, - proof: Self::Proof, - randomness: Self::Randomness, - ) -> Result, SynthesisError> { - let cs = U.e.cs(); - let F_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.F_coeffs[..]))?; - let K_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.K_coeffs[..]))?; - let randomness = Vec::new_input(cs.clone(), || Ok(randomness))?; - - let (U_next, L_X_evals) = - FoldingGadget::fold_committed_instance(transcript, &U, &[u], F_coeffs, K_coeffs)?; - L_X_evals.enforce_equal(&randomness)?; - - Ok(U_next) - } - - fn fold_group_elements_native( - U_commitments: &[C], - u_commitments: &[C], - _: Option, - L_X_evals: Self::Randomness, - ) -> Result, Error> { - let U_phi = U_commitments[0]; - let u_phi = u_commitments[0]; - Ok(vec![U_phi * L_X_evals[0] + u_phi * L_X_evals[1]]) - } -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::{Fr, G1Projective as Projective}; - use ark_grumpkin::Projective as Projective2; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - - use super::*; - use crate::commitment::pedersen::Pedersen; - use crate::folding::protogalaxy::ProtoGalaxy; - use crate::frontend::{utils::CubicFCircuit, FCircuit}; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::FoldingScheme; - - #[test] - fn test_decider_circuit() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - let z_0 = vec![Fr::from(3_u32)]; - - type PG = ProtoGalaxy< - Projective, - Projective2, - CubicFCircuit, - Pedersen, - Pedersen, - >; - let pg_params = PG::preprocess(&mut rng, &(poseidon_config, F_circuit))?; - - // generate a Nova instance and do a step of it - let mut protogalaxy = PG::init(&pg_params, F_circuit, z_0.clone())?; - protogalaxy.prove_step(&mut rng, (), None)?; - - let ivc_proof = protogalaxy.ivc_proof(); - PG::verify(pg_params.1, ivc_proof)?; - - // load the DeciderEthCircuit from the generated Nova instance - let decider_circuit = DeciderEthCircuit::::try_from(protogalaxy)?; - - let cs = ConstraintSystem::::new_ref(); - - // generate the constraints and check that are satisfied by the inputs - decider_circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - dbg!(cs.num_constraints()); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/protogalaxy/folding.rs b/folding-schemes/src/folding/protogalaxy/folding.rs deleted file mode 100644 index e8a018889..000000000 --- a/folding-schemes/src/folding/protogalaxy/folding.rs +++ /dev/null @@ -1,573 +0,0 @@ -/// Implements the scheme described in [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) -use ark_ff::PrimeField; -use ark_poly::{ - univariate::{DensePolynomial, SparsePolynomial}, - DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial, -}; -use ark_std::{cfg_into_iter, log2, One, Zero}; -use rayon::prelude::*; -use std::marker::PhantomData; - -use super::utils::{all_powers, betas_star, exponential_powers, pow_i}; -use super::ProtoGalaxyError; -use super::{CommittedInstance, Witness}; - -use crate::transcript::Transcript; -use crate::utils::vec::*; -use crate::Error; -use crate::{arith::r1cs::R1CS, Curve}; -use crate::{arith::Arith, folding::traits::Dummy}; - -#[derive(Debug, Clone)] -pub struct ProtoGalaxyProof { - pub F_coeffs: Vec, - pub K_coeffs: Vec, -} - -impl Dummy<(usize, usize, usize)> for ProtoGalaxyProof { - fn dummy((t, d, k): (usize, usize, usize)) -> Self { - Self { - F_coeffs: vec![F::zero(); t], - K_coeffs: vec![F::zero(); d * k + 1], - } - } -} - -#[derive(Debug, Clone)] -pub struct ProtoGalaxyAux { - pub L_X_evals: Vec, - pub phi_stars: Vec, -} - -#[derive(Clone, Debug)] -/// Implements the protocol described in section 4 of -/// [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) -pub struct Folding { - _phantom: PhantomData, -} -impl Folding { - #![allow(clippy::type_complexity)] - /// implements the non-interactive Prover from the folding scheme described in section 4 - pub fn prove( - transcript: &mut impl Transcript, - r1cs: &R1CS, - // running instance - instance: &CommittedInstance, - w: &Witness, - // incoming instances - vec_instances: &[CommittedInstance], - vec_w: &[Witness], - ) -> Result< - ( - CommittedInstance, - Witness, - ProtoGalaxyProof, - ProtoGalaxyAux, - ), - Error, - > { - if vec_instances.len() != vec_w.len() { - return Err(Error::NotSameLength( - "vec_instances.len()".to_string(), - vec_instances.len(), - "vec_w.len()".to_string(), - vec_w.len(), - )); - } - let d = r1cs.degree(); - let k = vec_instances.len(); - let t = instance.betas.len(); - let n = r1cs.n_variables(); - let m = r1cs.n_constraints(); - - let z = [vec![C::ScalarField::one()], instance.x.clone(), w.w.clone()].concat(); - - if z.len() != n { - return Err(Error::NotSameLength( - "z.len()".to_string(), - z.len(), - "number of variables in R1CS".to_string(), // hardcoded to R1CS - n, - )); - } - if log2(m) as usize != t { - return Err(Error::NotSameLength( - "log2(number of constraints in R1CS)".to_string(), - log2(m) as usize, - "instance.betas.len()".to_string(), - t, - )); - } - if !(k + 1).is_power_of_two() { - return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongNumInstances(k))); - } - - // absorb the committed instances - transcript.absorb(instance); - transcript.absorb(&vec_instances); - - let delta = transcript.get_challenge(); - let deltas = exponential_powers(delta, t); - - let mut f_z = r1cs.eval_at_z(&z)?; - if f_z.len() != m { - return Err(Error::NotSameLength( - "number of constraints in R1CS".to_string(), - m, - "f_z.len()".to_string(), - f_z.len(), - )); - } - f_z.resize(1 << t, C::ScalarField::zero()); - - // F(X) - let F_X: SparsePolynomial = - calc_f_from_btree(&f_z, &instance.betas, &deltas).expect("Error calculating F[x]"); - let F_X_dense = DensePolynomial::from(F_X.clone()); - let mut F_coeffs = F_X_dense.coeffs; - F_coeffs.resize(t, C::ScalarField::zero()); - transcript.absorb(&F_coeffs); - - let alpha = transcript.get_challenge(); - - // eval F(alpha) - let F_alpha = F_X.evaluate(&alpha); - - // betas* - let betas_star = betas_star(&instance.betas, &deltas, alpha); - - // sanity check: check that the new randomized instance (the original instance but with - // 'refreshed' randomness) satisfies the relation. - #[cfg(test)] - { - use crate::arith::ArithRelation; - r1cs.check_relation( - w, - &CommittedInstance::<_, true> { - phi: instance.phi, - betas: betas_star.clone(), - e: F_alpha, - x: instance.x.clone(), - }, - )?; - } - - let zs: Vec> = std::iter::once(z.clone()) - .chain( - vec_w - .iter() - .zip(vec_instances) - .map(|(wj, uj)| { - let zj = [vec![C::ScalarField::one()], uj.x.clone(), wj.w.clone()].concat(); - if zj.len() != n { - return Err(Error::NotSameLength( - "zj.len()".to_string(), - zj.len(), - "number of variables in R1CS".to_string(), - n, - )); - } - Ok(zj) - }) - .collect::>, Error>>()?, - ) - .collect::>>(); - - let H = - GeneralEvaluationDomain::::new(k + 1).ok_or(Error::NewDomainFail)?; - let G_domain = GeneralEvaluationDomain::::new((d * k) + 1) - .ok_or(Error::NewDomainFail)?; - let L_X: Vec> = lagrange_polys(H); - - // K(X) computation in a naive way, next iterations will compute K(X) as described in Claim - // 4.5 of the paper. - let mut G_evals: Vec = vec![C::ScalarField::zero(); G_domain.size()]; - for (hi, h) in G_domain.elements().enumerate() { - // each iteration evaluates G(h) - // inner = L_0(x) * z + \sum_k L_i(x) * z_j - let mut inner: Vec = vec![C::ScalarField::zero(); zs[0].len()]; - for (z, L) in zs.iter().zip(&L_X) { - // Li_z_h = (Li(X)*zj)(h) = Li(h) * zj - let Lh = L.evaluate(&h); - for (j, zj) in z.iter().enumerate() { - inner[j] += Lh * zj; - } - } - let f_ev = r1cs.eval_at_z(&inner)?; - - G_evals[hi] = cfg_into_iter!(f_ev) - .enumerate() - .map(|(i, f_ev_i)| pow_i(i, &betas_star) * f_ev_i) - .sum(); - } - let G_X: DensePolynomial = - Evaluations::::from_vec_and_domain(G_evals, G_domain).interpolate(); - let Z_X: DensePolynomial = H.vanishing_polynomial().into(); - // K(X) = (G(X) - F(alpha)*L_0(X)) / Z(X) - // Notice that L0(X)*F(a) will be 0 in the native case (the instance of the first folding - // iteration case). - let L0_e = &L_X[0] * F_alpha; - let G_L0e = &G_X - &L0_e; - // Pending optimization: move division by Z_X to the prev loop - let (K_X, remainder) = G_L0e.divide_by_vanishing_poly(H); - if !remainder.is_zero() { - return Err(Error::ProtoGalaxy(ProtoGalaxyError::RemainderNotZero)); - } - - let mut K_coeffs = K_X.coeffs.clone(); - K_coeffs.resize(d * k + 1, C::ScalarField::zero()); - transcript.absorb(&K_coeffs); - - let gamma = transcript.get_challenge(); - - let L_X_evals = L_X - .iter() - .take(k + 1) - .map(|L| L.evaluate(&gamma)) - .collect::>(); - - let mut phi_stars = vec![]; - - let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma); - let mut w_star = vec_scalar_mul(&w.w, &L_X_evals[0]); - let mut r_w_star = w.r_w * L_X_evals[0]; - let mut phi_star = instance.phi * L_X_evals[0]; - let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]); - for i in 0..k { - w_star = vec_add(&w_star, &vec_scalar_mul(&vec_w[i].w, &L_X_evals[i + 1]))?; - r_w_star += vec_w[i].r_w * L_X_evals[i + 1]; - phi_stars.push(phi_star); // Push before updating. We don't need the last one - phi_star += vec_instances[i].phi * L_X_evals[i + 1]; - x_star = vec_add( - &x_star, - &vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]), - )?; - } - - Ok(( - CommittedInstance { - betas: betas_star, - phi: phi_star, - e: e_star, - x: x_star, - }, - Witness { - w: w_star, - r_w: r_w_star, - }, - ProtoGalaxyProof { F_coeffs, K_coeffs }, - ProtoGalaxyAux { - L_X_evals, - phi_stars, - }, - )) - } - - /// implements the non-interactive Verifier from the folding scheme described in section 4 - pub fn verify( - transcript: &mut impl Transcript, - // running instance - instance: &CommittedInstance, - // incoming instances - vec_instances: &[CommittedInstance], - // polys from P - proof: ProtoGalaxyProof, - ) -> Result, Error> { - let t = instance.betas.len(); - - // absorb the committed instances - transcript.absorb(instance); - transcript.absorb(&vec_instances); - - let delta = transcript.get_challenge(); - let deltas = exponential_powers(delta, t); - - transcript.absorb(&proof.F_coeffs); - - let alpha = transcript.get_challenge(); - let alphas = all_powers(alpha, t); - - // F(alpha) = e + \sum_t F_i * alpha^i - let mut F_alpha = instance.e; - for (i, F_i) in proof.F_coeffs.iter().skip(1).enumerate() { - F_alpha += *F_i * alphas[i + 1]; - } - - let betas_star = betas_star(&instance.betas, &deltas, alpha); - - transcript.absorb(&proof.K_coeffs); - - let k = vec_instances.len(); - let H = - GeneralEvaluationDomain::::new(k + 1).ok_or(Error::NewDomainFail)?; - let L_X: Vec> = lagrange_polys(H); - let Z_X: DensePolynomial = H.vanishing_polynomial().into(); - let K_X: DensePolynomial = - DensePolynomial::::from_coefficients_vec(proof.K_coeffs); - - let gamma = transcript.get_challenge(); - - let L_X_evals = L_X - .iter() - .take(k + 1) - .map(|L| L.evaluate(&gamma)) - .collect::>(); - - let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma); - - let mut phi_star = instance.phi * L_X_evals[0]; - let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]); - for i in 0..k { - phi_star += vec_instances[i].phi * L_X_evals[i + 1]; - x_star = vec_add( - &x_star, - &vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]), - )?; - } - - // return the folded instance - Ok(CommittedInstance { - betas: betas_star, - phi: phi_star, - e: e_star, - x: x_star, - }) - } -} - -/// calculates F[x] using the optimized binary-tree technique -/// described in Claim 4.4 -/// of [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) -fn calc_f_from_btree( - fw: &[F], - betas: &[F], - deltas: &[F], -) -> Result, Error> { - let fw_len = fw.len(); - let betas_len = betas.len(); - let deltas_len = deltas.len(); - - // ensure our binary tree is full - if !fw_len.is_power_of_two() { - return Err(Error::ProtoGalaxy(ProtoGalaxyError::BTreeNotFull(fw_len))); - } - - if betas_len != deltas_len { - return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongLenBetas( - betas_len, deltas_len, - ))); - } - - let mut layers: Vec>> = Vec::new(); - let leaves: Vec> = fw - .iter() - .copied() - .map(|e| SparsePolynomial::::from_coefficients_slice(&[(0, e)])) - .collect(); - layers.push(leaves.to_vec()); - let mut currentNodes = leaves.clone(); - while currentNodes.len() > 1 { - let index = layers.len(); - layers.push(vec![]); - for (i, ni) in currentNodes.iter().enumerate().step_by(2) { - let left = ni.clone(); - let right = SparsePolynomial::::from_coefficients_vec(vec![ - (0, betas[layers.len() - 2]), - (1, deltas[layers.len() - 2]), - ]) - .mul(¤tNodes[i + 1]); - - layers[index].push(left + right); - } - currentNodes = layers[index].clone(); - } - let root_index = layers.len() - 1; - Ok(layers[root_index][0].clone()) -} - -// lagrange_polys method from caulk: https://github.com/caulk-crypto/caulk/tree/8210b51fb8a9eef4335505d1695c44ddc7bf8170/src/multi/setup.rs#L300 -pub fn lagrange_polys( - domain_n: GeneralEvaluationDomain, -) -> Vec> { - let mut lagrange_polynomials: Vec> = Vec::new(); - for i in 0..domain_n.size() { - let evals: Vec = cfg_into_iter!(0..domain_n.size()) - .map(|k| if k == i { F::one() } else { F::zero() }) - .collect(); - lagrange_polynomials.push(Evaluations::from_vec_and_domain(evals, domain_n).interpolate()); - } - lagrange_polynomials -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_pallas::{Fr, Projective}; - use ark_std::{rand::Rng, UniformRand}; - - use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z_split}; - use crate::arith::ArithRelation; - use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; - use crate::transcript::poseidon::poseidon_canonical_config; - - #[test] - fn test_pow_i() { - let mut rng = ark_std::test_rng(); - let t = 4; - let n = 16; - let beta = Fr::rand(&mut rng); - let betas = exponential_powers(beta, t); - let not_betas = all_powers(beta, n); - - #[allow(clippy::needless_range_loop)] - for i in 0..n { - assert_eq!(pow_i(i, &betas), not_betas[i]); - } - } - - // k represents the number of instances to be fold, apart from the running instance - #[allow(clippy::type_complexity)] - pub fn prepare_inputs( - k: usize, - ) -> Result< - ( - Witness, - CommittedInstance, - Vec>, - Vec>, - ), - Error, - > { - let mut rng = ark_std::test_rng(); - - let (_, x, w) = get_test_z_split::(rng.gen::() as usize); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, w.len())?; - - let t = log2(get_test_r1cs::().n_constraints()) as usize; - - let beta = C::ScalarField::rand(&mut rng); - let betas = exponential_powers(beta, t); - - let witness = Witness:: { - w, - r_w: C::ScalarField::zero(), - }; - let phi = Pedersen::::commit(&pedersen_params, &witness.w, &witness.r_w)?; - let instance = CommittedInstance:: { - phi, - betas: betas.clone(), - e: C::ScalarField::zero(), - x, - }; - // same for the other instances - let mut witnesses: Vec> = Vec::new(); - let mut instances: Vec> = Vec::new(); - #[allow(clippy::needless_range_loop)] - for _ in 0..k { - let (_, x_i, w_i) = get_test_z_split::(rng.gen::() as usize); - let witness_i = Witness:: { - w: w_i, - r_w: C::ScalarField::zero(), - }; - let phi_i = Pedersen::::commit(&pedersen_params, &witness_i.w, &witness_i.r_w)?; - let instance_i = CommittedInstance:: { - phi: phi_i, - betas: vec![], - e: C::ScalarField::zero(), - x: x_i, - }; - witnesses.push(witness_i); - instances.push(instance_i); - } - - Ok((witness, instance, witnesses, instances)) - } - - #[test] - fn test_fold() -> Result<(), Error> { - let k = 7; - let (witness, instance, witnesses, instances) = prepare_inputs(k)?; - let r1cs = get_test_r1cs::(); - - // init Prover & Verifier's transcript - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from(42u32); // only for testing - let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); - let mut transcript_v = transcript_p.clone(); - - let (folded_instance, folded_witness, proof, _) = Folding::::prove( - &mut transcript_p, - &r1cs, - &instance, - &witness, - &instances, - &witnesses, - )?; - - // verifier - let folded_instance_v = - Folding::::verify(&mut transcript_v, &instance, &instances, proof)?; - - // check that prover & verifier folded instances are the same values - assert_eq!(folded_instance.phi, folded_instance_v.phi); - assert_eq!(folded_instance.betas, folded_instance_v.betas); - assert_eq!(folded_instance.e, folded_instance_v.e); - assert!(!folded_instance.e.is_zero()); - - // check that the folded instance satisfies the relation - r1cs.check_relation(&folded_witness, &folded_instance)?; - Ok(()) - } - - #[test] - fn test_fold_various_iterations() -> Result<(), Error> { - let r1cs = get_test_r1cs::(); - - // init Prover & Verifier's transcript - let poseidon_config = poseidon_canonical_config::(); - let pp_hash = Fr::from(42u32); // only for testing - let mut transcript_p = PoseidonSponge::new_with_pp_hash(&poseidon_config, pp_hash); - let mut transcript_v = transcript_p.clone(); - - let (mut running_witness, mut running_instance, _, _) = prepare_inputs(0)?; - - // fold k instances on each of num_iters iterations - let k = 7; - let num_iters = 10; - for _ in 0..num_iters { - // generate the instances to be fold - let (_, _, witnesses, instances) = prepare_inputs(k)?; - - let (folded_instance, folded_witness, proof, _) = Folding::::prove( - &mut transcript_p, - &r1cs, - &running_instance, - &running_witness, - &instances, - &witnesses, - )?; - - // verifier - let folded_instance_v = Folding::::verify( - &mut transcript_v, - &running_instance, - &instances, - proof, - )?; - - // check that prover & verifier folded instances are the same values - assert_eq!(folded_instance, folded_instance_v); - - assert!(!folded_instance.e.is_zero()); - - // check that the folded instance satisfies the relation - r1cs.check_relation(&folded_witness, &folded_instance)?; - - running_witness = folded_witness; - running_instance = folded_instance; - } - Ok(()) - } -} diff --git a/folding-schemes/src/folding/protogalaxy/mod.rs b/folding-schemes/src/folding/protogalaxy/mod.rs deleted file mode 100644 index 6887e0cf8..000000000 --- a/folding-schemes/src/folding/protogalaxy/mod.rs +++ /dev/null @@ -1,1187 +0,0 @@ -/// Implements the scheme described in [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) -use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - prelude::Boolean, - GR1CSVar, -}; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, - SynthesisMode, -}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; -use ark_std::{borrow::Borrow, cmp::max, fmt::Debug, log2, rand::RngCore, One, Zero}; -use constants::{INCOMING, RUNNING}; -use num_bigint::BigUint; - -use crate::{ - arith::{ - r1cs::{extract_r1cs, extract_w_x, R1CS}, - Arith, ArithRelation, - }, - commitment::CommitmentScheme, - folding::circuits::{ - cyclefold::{ - CycleFoldAugmentationGadget, CycleFoldCommittedInstance, CycleFoldConfig, - CycleFoldWitness, - }, - nonnative::affine::NonNativeAffineVar, - CF1, - }, - frontend::{utils::DummyCircuit, FCircuit}, - transcript::{poseidon::poseidon_canonical_config, Transcript}, - utils::pp_hash, - Curve, Error, FoldingScheme, -}; - -pub mod circuits; -pub mod constants; -pub mod decider_eth; -pub mod decider_eth_circuit; -pub mod folding; -pub mod traits; -pub(crate) mod utils; - -use circuits::AugmentedFCircuit; -use folding::Folding; - -use super::{ - circuits::{cyclefold::CycleFoldCircuit, CF2}, - traits::{ - CommittedInstanceOps, CommittedInstanceVarOps, Dummy, Inputize, WitnessOps, WitnessVarOps, - }, -}; - -/// Configuration for ProtoGalaxy's CycleFold circuit -pub struct ProtoGalaxyCycleFoldConfig { - rs: Vec>, - points: Vec, -} - -impl Default for ProtoGalaxyCycleFoldConfig { - fn default() -> Self { - Self { - rs: vec![CF1::::one(); 2], - points: vec![C::zero(); 2], - } - } -} - -impl CycleFoldConfig for ProtoGalaxyCycleFoldConfig { - const RANDOMNESS_BIT_LENGTH: usize = C::ScalarField::MODULUS_BIT_SIZE as usize; - const N_UNIQUE_RANDOMNESSES: usize = 2; - const N_INPUT_POINTS: usize = 2; - - fn alloc_points(&self, cs: ConstraintSystemRef>) -> Result, SynthesisError> { - let points = Vec::new_witness(cs.clone(), || Ok(self.points.clone()))?; - for point in &points { - Self::mark_point_as_public(point)?; - } - Ok(points) - } - - fn alloc_randomnesses( - &self, - cs: ConstraintSystemRef>, - ) -> Result>>>, SynthesisError> { - let rs = vec![self.rs[0]] - .into_iter() - .chain(self.rs.windows(2).map(|r| r[1] / r[0])) - .map(|r| { - let mut bits = r.into_bigint().to_bits_le(); - bits.resize(CF1::::MODULUS_BIT_SIZE as usize, false); - Vec::new_witness(cs.clone(), || Ok(bits)) - }) - .collect::, _>>()?; - Self::mark_randomness_as_public(&rs.concat())?; - Ok(rs) - } -} - -/// The committed instance of ProtoGalaxy. -/// -/// We use `TYPE` to distinguish between incoming and running instances, as -/// they have slightly different structures (e.g., length of `betas`) and -/// behaviors (e.g., in satisfiability checks). -#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CommittedInstance { - phi: C, - betas: Vec, - e: C::ScalarField, - x: Vec, -} - -impl Dummy<(usize, usize)> for CommittedInstance { - fn dummy((io_len, t): (usize, usize)) -> Self { - if TYPE == INCOMING { - assert_eq!(t, 0); - } - Self { - phi: C::zero(), - betas: vec![Zero::zero(); t], - e: Zero::zero(), - x: vec![Zero::zero(); io_len], - } - } -} - -impl Dummy<&R1CS>> for CommittedInstance { - fn dummy(r1cs: &R1CS>) -> Self { - let t = if TYPE == RUNNING { - log2(r1cs.n_constraints()) as usize - } else { - 0 - }; - Self::dummy((r1cs.n_public_inputs(), t)) - } -} - -impl CommittedInstanceOps for CommittedInstance { - type Var = CommittedInstanceVar; - - fn get_commitments(&self) -> Vec { - vec![self.phi] - } - - fn is_incoming(&self) -> bool { - TYPE == INCOMING - } -} - -impl Inputize> for CommittedInstance { - /// Returns the internal representation in the same order as how the value - /// is allocated in `CommittedInstanceVar::new_input`. - fn inputize(&self) -> Vec> { - [ - &self.phi.inputize_nonnative(), - &self.betas, - &[self.e][..], - &self.x, - ] - .concat() - } -} - -#[derive(Clone, Debug)] -pub struct CommittedInstanceVar { - phi: NonNativeAffineVar, - betas: Vec>, - e: FpVar, - x: Vec>, -} - -impl AllocVar, C::ScalarField> - for CommittedInstanceVar -{ - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|u| { - let cs = cs.into(); - - let u = u.borrow(); - - Ok(Self { - phi: NonNativeAffineVar::new_variable(cs.clone(), || Ok(u.phi), mode)?, - betas: Vec::new_variable(cs.clone(), || Ok(u.betas.clone()), mode)?, - e: if TYPE == RUNNING { - FpVar::new_variable(cs.clone(), || Ok(u.e), mode)? - } else { - FpVar::zero() - }, - x: Vec::new_variable(cs.clone(), || Ok(u.x.clone()), mode)?, - }) - }) - } -} - -impl GR1CSVar for CommittedInstanceVar { - type Value = CommittedInstance; - - fn cs(&self) -> ConstraintSystemRef { - self.phi - .cs() - .or(self.betas.cs()) - .or(self.e.cs()) - .or(self.x.cs()) - } - - fn value(&self) -> Result { - Ok(CommittedInstance { - phi: self.phi.value()?, - betas: self - .betas - .iter() - .map(|v| v.value()) - .collect::>()?, - e: self.e.value()?, - x: self.x.iter().map(|v| v.value()).collect::>()?, - }) - } -} - -impl CommittedInstanceVarOps for CommittedInstanceVar { - type PointVar = NonNativeAffineVar; - - fn get_commitments(&self) -> Vec { - vec![self.phi.clone()] - } - - fn get_public_inputs(&self) -> &[FpVar>] { - &self.x - } - - fn enforce_incoming(&self) -> Result<(), SynthesisError> { - // We don't need to check if `self` is an incoming instance in-circuit, - // because incoming instances and running instances already have - // different types of `e` (constant vs witness) when we allocate them - // in-circuit. - (TYPE == INCOMING) - .then_some(()) - .ok_or(SynthesisError::Unsatisfiable) - } - - fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { - self.betas.enforce_equal(&other.betas)?; - self.e.enforce_equal(&other.e)?; - self.x.enforce_equal(&other.x) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Witness { - w: Vec, - r_w: F, -} - -impl Witness { - pub fn new(w: Vec) -> Self { - // note: at the current version, we don't use the blinding factors and we set them to 0 - // always. - // Tracking issue: https://github.com/privacy-scaling-explorations/sonobe/issues/82 - Self { w, r_w: F::zero() } - } - - pub fn commit, C: Curve>( - &self, - params: &CS::ProverParams, - x: Vec, - ) -> Result, crate::Error> { - let phi = CS::commit(params, &self.w, &self.r_w)?; - Ok(CommittedInstance:: { - phi, - x, - e: F::zero(), - betas: vec![], - }) - } -} - -impl Dummy<&R1CS> for Witness { - fn dummy(r1cs: &R1CS) -> Self { - Self { - w: vec![F::zero(); r1cs.n_witnesses()], - r_w: F::zero(), - } - } -} - -impl WitnessOps for Witness { - type Var = WitnessVar; - - fn get_openings(&self) -> Vec<(&[F], F)> { - vec![(&self.w, self.r_w)] - } -} - -/// In-circuit representation of the Witness associated to the CommittedInstance. -#[derive(Debug, Clone)] -pub struct WitnessVar { - pub W: Vec>, - pub rW: FpVar, -} - -impl AllocVar, F> for WitnessVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let W = Vec::new_variable(cs.clone(), || Ok(val.borrow().w.to_vec()), mode)?; - let rW = FpVar::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?; - - Ok(Self { W, rW }) - }) - } -} - -impl WitnessVarOps for WitnessVar { - fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { - vec![(&self.W, self.rW.clone())] - } -} - -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum ProtoGalaxyError { - #[error("The remainder from G(X)-F(α)*L_0(X)) / Z(X) should be zero")] - RemainderNotZero, - #[error("Could not divide by vanishing polynomial")] - CouldNotDivideByVanishing, - #[error("The number of incoming instances + 1 should be a power of two, current number of instances: {0}")] - WrongNumInstances(usize), - #[error("The number of incoming items should be a power of two, current number of coefficients: {0}")] - BTreeNotFull(usize), - #[error("The lengths of β and δ do not equal: |β| = {0}, |δ|={0}")] - WrongLenBetas(usize, usize), -} - -/// Proving parameters for ProtoGalaxy-based IVC -#[derive(Debug, Clone)] -pub struct ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// Poseidon sponge configuration - pub poseidon_config: PoseidonConfig, - /// Proving parameters of the underlying commitment scheme over C1 - pub cs_params: CS1::ProverParams, - /// Proving parameters of the underlying commitment scheme over C2 - pub cf_cs_params: CS2::ProverParams, -} -impl CanonicalSerialize for ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.cs_params.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_params.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.cs_params.serialized_size(compress) + self.cf_cs_params.serialized_size(compress) - } -} -impl Valid for ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn check(&self) -> Result<(), ark_serialize::SerializationError> { - self.poseidon_config.full_rounds.check()?; - self.poseidon_config.partial_rounds.check()?; - self.poseidon_config.alpha.check()?; - self.poseidon_config.ark.check()?; - self.poseidon_config.mds.check()?; - self.poseidon_config.rate.check()?; - self.poseidon_config.capacity.check()?; - self.cs_params.check()?; - self.cf_cs_params.check()?; - Ok(()) - } -} -impl CanonicalDeserialize for ProverParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn deserialize_with_mode( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - ) -> Result { - let cs_params = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_params = - CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - Ok(ProverParams { - poseidon_config: poseidon_canonical_config::(), - cs_params, - cf_cs_params, - }) - } -} - -/// Verification parameters for ProtoGalaxy-based IVC -#[derive(Debug, Clone)] -pub struct VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// Poseidon sponge configuration - pub poseidon_config: PoseidonConfig, - /// R1CS of the Augmented step circuit - pub r1cs: R1CS, - /// R1CS of the CycleFold circuit - pub cf_r1cs: R1CS, - /// Verification parameters of the underlying commitment scheme over C1 - pub cs_vp: CS1::VerifierParams, - /// Verification parameters of the underlying commitment scheme over C2 - pub cf_cs_vp: CS2::VerifierParams, -} - -impl Valid for VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn check(&self) -> Result<(), ark_serialize::SerializationError> { - self.cs_vp.check()?; - self.cf_cs_vp.check()?; - Ok(()) - } -} -impl CanonicalSerialize for VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.cs_vp.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_vp.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.cs_vp.serialized_size(compress) + self.cf_cs_vp.serialized_size(compress) - } -} - -impl VerifierParams -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// returns the hash of the public parameters of ProtoGalaxy - pub fn pp_hash(&self) -> Result { - // TODO: support hiding commitments in ProtoGalaxy. For now, `H` is set to false. Tracking - // issue: https://github.com/privacy-scaling-explorations/sonobe/issues/82 - pp_hash::( - &self.r1cs, - &self.cf_r1cs, - &self.cs_vp, - &self.cf_cs_vp, - &self.poseidon_config, - ) - } -} - -#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] -pub struct IVCProof { - pub i: C1::ScalarField, - pub z_0: Vec, - pub z_i: Vec, - pub W_i: Witness, - pub U_i: CommittedInstance, - pub w_i: Witness, - pub u_i: CommittedInstance, - pub cf_W_i: CycleFoldWitness, - pub cf_U_i: CycleFoldCommittedInstance, -} - -/// Implements ProtoGalaxy+CycleFold's IVC, described in [ProtoGalaxy] and -/// [CycleFold], following the FoldingScheme trait -/// -/// [ProtoGalaxy]: https://eprint.iacr.org/2023/1106.pdf -/// [CycleFold]: https://eprint.iacr.org/2023/1192.pdf -#[derive(Clone, Debug)] -pub struct ProtoGalaxy -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// R1CS of the Augmented Function circuit - pub r1cs: R1CS, - /// R1CS of the CycleFold circuit - pub cf_r1cs: R1CS, - pub poseidon_config: PoseidonConfig, - /// CommitmentScheme::ProverParams over C1 - pub cs_params: CS1::ProverParams, - /// CycleFold CommitmentScheme::ProverParams, over C2 - pub cf_cs_params: CS2::ProverParams, - /// F circuit, the circuit that is being folded - pub F: FC, - /// public params hash - pub pp_hash: C1::ScalarField, - pub i: C1::ScalarField, - /// initial state - pub z_0: Vec, - /// current i-th state - pub z_i: Vec, - /// ProtoGalaxy instances - pub w_i: Witness, - pub u_i: CommittedInstance, - pub W_i: Witness, - pub U_i: CommittedInstance, - - /// CycleFold running instance - pub cf_W_i: CycleFoldWitness, - pub cf_U_i: CycleFoldCommittedInstance, -} - -impl ProtoGalaxy -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - /// This method computes the parameter `t` in ProtoGalaxy for folding `F'`, - /// the augmented circuit of `F` - fn compute_t( - poseidon_config: &PoseidonConfig>, - F: &FC, - d: usize, - k: usize, - ) -> Result { - // In ProtoGalaxy, prover and verifier are parameterized by `t = log(n)` - // where `n` is the number of constraints in the circuit (known as the - // mapping `f` in the paper). - // For IVC, `f` is the augmented circuit `F'`, which not only includes - // the original computation `F`, but also the in-circuit verifier of - // ProtoGalaxy. - // Therefore, `t` depends on the size of `F'`, but the size of `F'` in - // turn depends on `t`. - // To address this circular dependency, we first find `t_lower_bound`, - // the lower bound of `t`. Then we incrementally increase `t` and build - // the circuit `F'` with `t` as ProtoGalaxy's parameter, until `t` is - // the smallest integer that equals the logarithm of the number of - // constraints. - - // For `t_lower_bound`, we configure `F'` with `t = 1` and compute log2 - // of the size of `F'`. - let state_len = F.state_len(); - - // `F'` includes `F` and `ProtoGalaxy.V`, where `F` might be costly. - // Observing that the cost of `F` is constant with respect to `t`, we - // separately compute `step_constraints`, the size of `F`. - // Later, we only need to re-run the rest of `F'` with updated `t` to - // get the size of `F'`. - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - F.generate_step_constraints( - cs.clone(), - 0, - Vec::new_witness(cs.clone(), || Ok(vec![Zero::zero(); state_len]))?, - FC::ExternalInputsVar::new_witness(cs.clone(), || Ok(FC::ExternalInputs::default()))?, - )?; - let step_constraints = cs.num_constraints(); - - // Create a dummy circuit with the same state length and external inputs - // length as `F`, which replaces `F` in the augmented circuit `F'`. - let dummy_circuit: DummyCircuit = FCircuit::::new(state_len)?; - - // Compute `augmentation_constraints`, the size of `F'` without `F`. - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - AugmentedFCircuit::::empty( - poseidon_config, - dummy_circuit.clone(), - 1, - d, - k, - ) - .generate_constraints(cs.clone())?; - let augmentation_constraints = cs.num_constraints(); - - // The sum of `step_constraints` and `augmentation_constraints` is the - // size of `F'` with `t = 1`, and hence the actual `t` should have lower - // bound `log2(step_constraints + augmentation_constraints)`. - let t_lower_bound = log2(step_constraints + augmentation_constraints) as usize; - // Optimization: we in fact only need to try two values of `t`. - // This is because increasing `t` will only slightly affect the size of - // `F'` (more specifically, the size of `F'` will never be doubled). - // Thus, `t_lower_bound` (the log2 size of `F'` with `t = 1`) is very - // close to the actual `t` (either `t` or `t - 1`). - let t_upper_bound = t_lower_bound + 1; - - for t in t_lower_bound..=t_upper_bound { - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - AugmentedFCircuit::::empty( - poseidon_config, - dummy_circuit.clone(), - t, - d, - k, - ) - .generate_constraints(cs.clone())?; - let augmentation_constraints = cs.num_constraints(); - if t == log2(step_constraints + augmentation_constraints) as usize { - return Ok(t); - } - } - unreachable!() - } -} - -impl FoldingScheme for ProtoGalaxy -where - C1: Curve, - C2: Curve, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - type PreprocessorParam = (PoseidonConfig>, FC); - type ProverParam = ProverParams; - type VerifierParam = VerifierParams; - type RunningInstance = (CommittedInstance, Witness); - type IncomingInstance = (CommittedInstance, Witness); - type MultiCommittedInstanceWithWitness = - (CommittedInstance, Witness); - type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); - type IVCProof = IVCProof; - - fn pp_deserialize_with_mode( - reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - _fc_params: FC::Params, // FCircuit params - ) -> Result { - Ok(Self::ProverParam::deserialize_with_mode( - reader, compress, validate, - )?) - } - - fn vp_deserialize_with_mode( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - fc_params: FC::Params, - ) -> Result { - let poseidon_config = poseidon_canonical_config::(); - - // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing - // to serialize them, saving significant space in the VerifierParams serialized size. - - let f_circuit = FC::new(fc_params)?; - let k = 1; - let d = R1CS::>::empty().degree(); - let t = Self::compute_t(&poseidon_config, &f_circuit, d, k)?; - - // main circuit R1CS: - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - let augmented_F_circuit = - AugmentedFCircuit::::empty(&poseidon_config, f_circuit.clone(), t, d, k); - augmented_F_circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - - // CycleFold circuit R1CS - let cs2 = ConstraintSystem::::new_ref(); - cs2.set_mode(SynthesisMode::Setup); - let cf_circuit = CycleFoldCircuit::<_, ProtoGalaxyCycleFoldConfig>::default(); - cf_circuit.generate_constraints(cs2.clone())?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let cf_r1cs = extract_r1cs::(&cs2)?; - - let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - - Ok(Self::VerifierParam { - poseidon_config, - r1cs, - cf_r1cs, - cs_vp, - cf_cs_vp, - }) - } - - fn preprocess( - mut rng: impl RngCore, - (poseidon_config, F): &Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - // We fix `k`, the number of incoming instances, to 1, because - // multi-instances folding is not supported yet. - // TODO: Support multi-instances folding and make `k` a constant generic parameter (as in - // HyperNova). Tracking issue: - // https://github.com/privacy-scaling-explorations/sonobe/issues/82 - let k = 1; - let d = R1CS::>::empty().degree(); - let t = Self::compute_t(poseidon_config, F, d, k)?; - - // prepare the circuit to obtain its R1CS - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(SynthesisMode::Setup); - let cs2 = ConstraintSystem::::new_ref(); - cs2.set_mode(SynthesisMode::Setup); - - let augmented_F_circuit = - AugmentedFCircuit::::empty(poseidon_config, F.clone(), t, d, k); - let cf_circuit = CycleFoldCircuit::<_, ProtoGalaxyCycleFoldConfig>::default(); - - augmented_F_circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs)?; - - cf_circuit.generate_constraints(cs2.clone())?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let cf_r1cs = extract_r1cs::(&cs2)?; - - // `CS1` is for committing to ProtoGalaxy's witness vector `w`, so we - // set `len` to the number of witnesses in `r1cs`. - let (cs_pp, cs_vp) = CS1::setup(&mut rng, r1cs.n_witnesses())?; - // `CS2` is for committing to CycleFold's witness vector `w` and error - // term `e`, where the length of `e` is the number of constraints, so we - // set `len` to the maximum of `e` and `w`'s lengths. - let (cf_cs_pp, cf_cs_vp) = CS2::setup( - &mut rng, - max(cf_r1cs.n_constraints(), cf_r1cs.n_witnesses()), - )?; - - Ok(( - Self::ProverParam { - poseidon_config: poseidon_config.clone(), - cs_params: cs_pp, - cf_cs_params: cf_cs_pp, - }, - Self::VerifierParam { - poseidon_config: poseidon_config.clone(), - r1cs, - cf_r1cs, - cs_vp, - cf_cs_vp, - }, - )) - } - - /// Initializes the ProtoGalaxy+CycleFold's IVC for the given parameters and - /// initial state `z_0`. - fn init( - (pp, vp): &(Self::ProverParam, Self::VerifierParam), - F: FC, - z_0: Vec, - ) -> Result { - // compute the public params hash - let pp_hash = vp.pp_hash()?; - - // setup the dummy instances - let (w_dummy, u_dummy) = vp.r1cs.dummy_witness_instance(); - let (W_dummy, U_dummy) = vp.r1cs.dummy_witness_instance(); - let (cf_W_dummy, cf_U_dummy) = vp.cf_r1cs.dummy_witness_instance(); - - // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the - // R1CS that we're working with. - Ok(Self { - r1cs: vp.r1cs.clone(), - cf_r1cs: vp.cf_r1cs.clone(), - poseidon_config: pp.poseidon_config.clone(), - cs_params: pp.cs_params.clone(), - cf_cs_params: pp.cf_cs_params.clone(), - F, - pp_hash, - i: C1::ScalarField::zero(), - z_0: z_0.clone(), - z_i: z_0, - w_i: w_dummy, - u_i: u_dummy, - W_i: W_dummy, - U_i: U_dummy, - // cyclefold running instance - cf_W_i: cf_W_dummy, - cf_U_i: cf_U_dummy, - }) - } - - /// Implements IVC.P of ProtoGalaxy+CycleFold - fn prove_step( - &mut self, - mut rng: impl RngCore, - external_inputs: FC::ExternalInputs, - _other_instances: Option, - ) -> Result<(), Error> { - // Multi-instances folding is not supported yet. - if _other_instances.is_some() { - return Err(Error::NoMultiInstances); - } - // We fix `k`, the number of incoming instances, to 1, because - // multi-instances folding is not supported yet. - // TODO: Support multi-instances folding and make `k` a constant generic parameter (as in - // HyperNova). Tracking issue: - // https://github.com/privacy-scaling-explorations/sonobe/issues/82 - let k = 1; - let d = self.r1cs.degree(); - - // `sponge` is for digest computation. - let sponge = PoseidonSponge::::new_with_pp_hash( - &self.poseidon_config, - self.pp_hash, - ); - // `transcript` is for challenge generation. - let mut transcript_prover = sponge.clone(); - - let mut augmented_F_circuit: AugmentedFCircuit; - - if self.z_i.len() != self.F.state_len() { - return Err(Error::NotSameLength( - "z_i.len()".to_string(), - self.z_i.len(), - "F.state_len()".to_string(), - self.F.state_len(), - )); - } - - let i_bn: BigUint = self.i.into(); - let i_usize: usize = i_bn.try_into().map_err(|_| Error::MaxStep)?; - - if self.i.is_zero() { - augmented_F_circuit = AugmentedFCircuit::empty( - &self.poseidon_config, - self.F.clone(), - self.U_i.betas.len(), - d, - k, - ); - augmented_F_circuit.pp_hash = self.pp_hash; - augmented_F_circuit.z_0.clone_from(&self.z_0); - augmented_F_circuit.z_i.clone_from(&self.z_i); - augmented_F_circuit - .external_inputs - .clone_from(&external_inputs); - - // There is no need to update `self.U_i` etc. as they are unchanged. - } else { - // Primary part: - // Compute `U_{i+1}` by folding `u_i` into `U_i`. - let (U_i1, W_i1, proof, aux) = Folding::prove( - &mut transcript_prover, - &self.r1cs, - &self.U_i, - &self.W_i, - &[self.u_i.clone()], - &[self.w_i.clone()], - )?; - - // CycleFold part: - // Create cyclefold circuit for enforcing: - // U_i.phi * L_evals[0] + u_i.phi * L_evals[1] = U_i1.phi - let (cf_w_i, cf_u_i) = ProtoGalaxyCycleFoldConfig { - rs: aux.L_X_evals, - points: vec![self.U_i.phi, self.u_i.phi], - } - .build_circuit() - .generate_incoming_instance_witness::<_, CS2, false>(&self.cf_cs_params, &mut rng)?; - - // fold cf_U_i + cf_u_i -> folded running instance cf_U_i1 - let (cf_W_i1, cf_U_i1, cf_cmTs) = - CycleFoldAugmentationGadget::fold_native::<_, CS2, false>( - &mut transcript_prover, - &self.cf_r1cs, - &self.cf_cs_params, - self.cf_W_i.clone(), - self.cf_U_i.clone(), - vec![cf_w_i], - vec![cf_u_i.clone()], - )?; - - augmented_F_circuit = AugmentedFCircuit { - poseidon_config: self.poseidon_config.clone(), - pp_hash: self.pp_hash, - i: self.i, - i_usize, - z_0: self.z_0.clone(), - z_i: self.z_i.clone(), - external_inputs: external_inputs.clone(), - u_i_phi: self.u_i.phi, - U_i: self.U_i.clone(), - U_i1_phi: U_i1.phi, - F_coeffs: proof.F_coeffs.clone(), - K_coeffs: proof.K_coeffs.clone(), - F: self.F.clone(), - // cyclefold values - cf_u_i_cmW: cf_u_i.cmW, - cf_U_i: self.cf_U_i.clone(), - cf_cmT: cf_cmTs[0], - }; - - #[cfg(test)] - { - let mut transcript_verifier = sponge.clone(); - assert_eq!( - Folding::verify( - &mut transcript_verifier, - &self.U_i, - &[self.u_i.clone()], - proof - )?, - U_i1 - ); - } - - self.W_i = W_i1; - self.U_i = U_i1; - self.cf_W_i = cf_W_i1; - self.cf_U_i = cf_U_i1; - } - - let cs = ConstraintSystem::::new_ref(); - - let z_i1 = augmented_F_circuit - .compute_next_state(cs.clone())? - .value()?; - - #[cfg(test)] - assert!(cs.is_satisfied()?); - - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (w_i1, x_i1) = extract_w_x::(&cs); - - #[cfg(test)] - if x_i1.len() != 2 { - return Err(Error::NotExpectedLength(x_i1.len(), 2)); - } - - // set values for next iteration - self.i += C1::ScalarField::one(); - self.z_i = z_i1; - self.w_i = Witness::new(w_i1); - self.u_i = self.w_i.commit::(&self.cs_params, x_i1)?; - - #[cfg(test)] - { - self.u_i.check_incoming()?; - self.r1cs.check_relation(&self.w_i, &self.u_i)?; - self.r1cs.check_relation(&self.W_i, &self.U_i)?; - } - - Ok(()) - } - - fn state(&self) -> Vec { - self.z_i.clone() - } - - fn ivc_proof(&self) -> Self::IVCProof { - Self::IVCProof { - i: self.i, - z_0: self.z_0.clone(), - z_i: self.z_i.clone(), - W_i: self.W_i.clone(), - U_i: self.U_i.clone(), - w_i: self.w_i.clone(), - u_i: self.u_i.clone(), - cf_W_i: self.cf_W_i.clone(), - cf_U_i: self.cf_U_i.clone(), - } - } - - fn from_ivc_proof( - ivc_proof: Self::IVCProof, - fcircuit_params: FC::Params, - params: (Self::ProverParam, Self::VerifierParam), - ) -> Result { - let IVCProof { - i, - z_0, - z_i, - W_i, - U_i, - w_i, - u_i, - cf_W_i, - cf_U_i, - } = ivc_proof; - let (pp, vp) = params; - - let f_circuit = FC::new(fcircuit_params)?; - - Ok(Self { - r1cs: vp.r1cs.clone(), - cf_r1cs: vp.cf_r1cs.clone(), - poseidon_config: pp.poseidon_config, - cs_params: pp.cs_params, - cf_cs_params: pp.cf_cs_params, - F: f_circuit, - pp_hash: vp.pp_hash()?, - i, - z_0, - z_i, - w_i, - u_i, - W_i, - U_i, - cf_W_i, - cf_U_i, - }) - } - - /// Implements IVC.V of ProtoGalaxy+CycleFold - fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> { - let Self::IVCProof { - i: num_steps, - z_0, - z_i, - W_i, - U_i, - w_i, - u_i, - cf_W_i, - cf_U_i, - } = ivc_proof; - - let sponge = PoseidonSponge::new_with_pp_hash(&vp.poseidon_config, vp.pp_hash()?); - - if u_i.x.len() != 2 || U_i.x.len() != 2 { - return Err(Error::IVCVerificationFail); - } - - // check that u_i's output points to the running instance - // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, num_steps, &z_0, &z_i); - if expected_u_i_x != u_i.x[0] { - return Err(Error::IVCVerificationFail); - } - // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge); - if expected_cf_u_i_x != u_i.x[1] { - return Err(Error::IVCVerificationFail); - } - - // check R1CS satisfiability, which is equivalent to checking if `u_i` - // is an incoming instance and if `w_i` and `u_i` satisfy RelaxedR1CS - u_i.check_incoming()?; - vp.r1cs.check_relation(&w_i, &u_i)?; - // check RelaxedR1CS satisfiability - vp.r1cs.check_relation(&W_i, &U_i)?; - - // check CycleFold RelaxedR1CS satisfiability - vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use ark_bn254::{Bn254, Fr, G1Projective as Projective}; - use ark_grumpkin::Projective as Projective2; - use ark_std::test_rng; - use rayon::prelude::*; - - use crate::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - frontend::utils::CubicFCircuit, - transcript::poseidon::poseidon_canonical_config, - }; - - /// This test tests the ProtoGalaxy+CycleFold IVC, and by consequence it is - /// also testing the AugmentedFCircuit - #[test] - fn test_ivc() -> Result<(), Error> { - let poseidon_config = poseidon_canonical_config::(); - - let F_circuit = CubicFCircuit::::new(())?; - - // run the test using Pedersen commitments on both sides of the curve cycle - let _ = test_ivc_opt::, Pedersen>( - poseidon_config.clone(), - F_circuit, - )?; - // run the test using KZG for the commitments on the main curve, and Pedersen for the - // commitments on the secondary curve - let _ = test_ivc_opt::, Pedersen>(poseidon_config, F_circuit)?; - Ok(()) - } - - // test_ivc allowing to choose the CommitmentSchemes - fn test_ivc_opt, CS2: CommitmentScheme>( - poseidon_config: PoseidonConfig, - F_circuit: CubicFCircuit, - ) -> Result<(), Error> { - type PG = ProtoGalaxy, CS1, CS2>; - - let params = PG::::preprocess(&mut test_rng(), &(poseidon_config, F_circuit))?; - - let z_0 = vec![Fr::from(3_u32)]; - let mut protogalaxy = PG::init(¶ms, F_circuit, z_0.clone())?; - - let num_steps: usize = 3; - for _ in 0..num_steps { - protogalaxy.prove_step(&mut test_rng(), (), None)?; - } - assert_eq!(Fr::from(num_steps as u32), protogalaxy.i); - - let ivc_proof = protogalaxy.ivc_proof(); - PG::::verify(params.1, ivc_proof)?; - Ok(()) - } - - #[ignore] - #[test] - fn test_t_bounds() -> Result<(), Error> { - let d = R1CS::::empty().degree(); - let k = 1; - - let poseidon_config = poseidon_canonical_config::(); - for state_len in [1, 10, 100] { - let dummy_circuit: DummyCircuit = FCircuit::::new(state_len)?; - - let costs: Vec = (1..32) - .into_par_iter() - .map(|t| { - let cs = ConstraintSystem::::new_ref(); - AugmentedFCircuit::::empty( - &poseidon_config, - dummy_circuit.clone(), - t, - d, - k, - ) - .generate_constraints(cs.clone())?; - Ok(cs.num_constraints()) - }) - .collect::, Error>>()?; - - for t_lower_bound in log2(costs[0]) as usize..32 { - let num_constraints = (1 << t_lower_bound) - costs[0] + costs[t_lower_bound - 1]; - let t = log2(num_constraints) as usize; - assert!(t == t_lower_bound || t == t_lower_bound + 1); - } - } - Ok(()) - } -} diff --git a/folding-schemes/src/folding/protogalaxy/traits.rs b/folding-schemes/src/folding/protogalaxy/traits.rs deleted file mode 100644 index a98eb982f..000000000 --- a/folding-schemes/src/folding/protogalaxy/traits.rs +++ /dev/null @@ -1,178 +0,0 @@ -use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb}; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - uint8::UInt8, -}; -use ark_relations::gr1cs::SynthesisError; -use ark_std::{cfg_into_iter, log2, One}; -use rayon::prelude::*; - -use super::{ - constants::RUNNING, - utils::{pow_i, pow_i_var}, - CommittedInstance, CommittedInstanceVar, Witness, WitnessVar, -}; -use crate::{ - arith::{ - r1cs::{circuits::R1CSMatricesVar, R1CS}, - ArithRelation, ArithRelationGadget, - }, - folding::circuits::CF1, - transcript::AbsorbNonNativeGadget, - utils::vec::is_zero_vec, - Curve, Error, -}; - -// Implements the trait for absorbing ProtoGalaxy's CommittedInstance. -impl Absorb for CommittedInstance { - fn to_sponge_bytes(&self, dest: &mut Vec) { - C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.phi.to_native_sponge_field_elements(dest); - self.betas.to_sponge_field_elements(dest); - self.e.to_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - } -} - -// Implements the trait for absorbing ProtoGalaxy's CommittedInstanceVar in-circuit. -impl AbsorbGadget for CommittedInstanceVar { - fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) - } - - fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { - Ok([ - self.phi.to_native_sponge_field_elements()?, - self.betas.to_sponge_field_elements()?, - self.e.to_sponge_field_elements()?, - self.x.to_sponge_field_elements()?, - ] - .concat()) - } -} - -/// Implements [`ArithRelation`] for R1CS, where the witness is of type -/// [`Witness`], and the committed instance is of type [`CommittedInstance`]. -/// -/// Due to the error term `CommittedInstance.e`, R1CS here is considered as a -/// relaxed R1CS. -/// -/// See `nova/traits.rs` for the rationale behind the design. -impl ArithRelation>, CommittedInstance> - for R1CS> -{ - type Evaluation = Vec>; - - fn eval_relation( - &self, - w: &Witness>, - u: &CommittedInstance, - ) -> Result { - self.eval_at_z(&[&[C::ScalarField::one()][..], &u.x, &w.w].concat()) - } - - fn check_evaluation( - _w: &Witness, - u: &CommittedInstance, - e: Vec, - ) -> Result<(), Error> { - let ok = if TYPE == RUNNING { - if u.betas.len() != log2(e.len()) as usize { - return Err(Error::NotSameLength( - "instance.betas.len()".to_string(), - u.betas.len(), - "log2(e.len())".to_string(), - log2(e.len()) as usize, - )); - } - - u.e == cfg_into_iter!(e) - .enumerate() - .map(|(i, e_i)| pow_i(i, &u.betas) * e_i) - .sum::>() - } else { - is_zero_vec(&e) - }; - ok.then_some(()).ok_or(Error::NotSatisfied) - } -} - -/// Unlike its native counterpart, we only need to support running instances in -/// circuit, as the decider circuit only checks running instance satisfiability. -impl ArithRelationGadget>, CommittedInstanceVar> - for R1CSMatricesVar, FpVar>> -{ - type Evaluation = (Vec>>, Vec>>); - - fn eval_relation( - &self, - w: &WitnessVar>, - u: &CommittedInstanceVar, - ) -> Result { - self.eval_at_z(&[&[FpVar::one()][..], &u.x, &w.W].concat()) - } - - fn enforce_evaluation( - _w: &WitnessVar, - u: &CommittedInstanceVar, - (AzBz, uCz): Self::Evaluation, - ) -> Result<(), SynthesisError> { - let mut e = vec![]; - for (i, (l, r)) in AzBz.iter().zip(uCz).enumerate() { - e.push(pow_i_var(i, &u.betas) * (l - r)); - } - // Call `sum` on a vector instead of computing the sum in the above loop - // to avoid stack overflow (the cause of this is similar to issue #80 - // https://github.com/privacy-scaling-explorations/sonobe/issues/80) - e.iter().sum::>().enforce_equal(&u.e) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_bn254::{Fr, G1Projective as Projective}; - use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::UniformRand; - use rand::Rng; - - /// test that checks the native CommittedInstance.to_sponge_{bytes,field_elements} - /// vs the R1CS constraints version - #[test] - pub fn test_committed_instance_to_sponge_preimage() -> Result<(), Error> { - let mut rng = ark_std::test_rng(); - - let t = rng.gen::() as usize; - let io_len = rng.gen::() as usize; - - let ci = CommittedInstance:: { - phi: Projective::rand(&mut rng), - betas: (0..t).map(|_| Fr::rand(&mut rng)).collect(), - e: Fr::rand(&mut rng), - x: (0..io_len).map(|_| Fr::rand(&mut rng)).collect(), - }; - - let bytes = ci.to_sponge_bytes_as_vec(); - let field_elements = ci.to_sponge_field_elements_as_vec(); - - let cs = ConstraintSystem::::new_ref(); - - let ciVar = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci.clone()))?; - let bytes_var = ciVar.to_sponge_bytes()?; - let field_elements_var = ciVar.to_sponge_field_elements()?; - - assert!(cs.is_satisfied()?); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(bytes_var.value()?, bytes); - assert_eq!(field_elements_var.value()?, field_elements); - Ok(()) - } -} diff --git a/folding-schemes/src/folding/protogalaxy/utils.rs b/folding-schemes/src/folding/protogalaxy/utils.rs deleted file mode 100644 index d89a5ed23..000000000 --- a/folding-schemes/src/folding/protogalaxy/utils.rs +++ /dev/null @@ -1,204 +0,0 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; -use num_integer::Integer; - -/// Returns (b, b^2, b^4, ..., b^{2^{t-1}}) -pub fn exponential_powers(b: F, t: usize) -> Vec { - let mut r = vec![F::zero(); t]; - r[0] = b; - for i in 1..t { - r[i] = r[i - 1].square(); - } - r -} - -/// The in-circuit version of `exponential_powers` -pub fn exponential_powers_var(b: FpVar, t: usize) -> Vec> { - let mut r = vec![FpVar::zero(); t]; - r[0] = b; - for i in 1..t { - r[i] = &r[i - 1] * &r[i - 1]; - } - r -} - -/// Returns (a, a^2, a^3, ..., a^{n-1}) -pub fn all_powers(a: F, n: usize) -> Vec { - let mut r = vec![F::zero(); n]; - for (i, r_i) in r.iter_mut().enumerate() { - *r_i = a.pow([i as u64]); - } - r -} - -/// The in-circuit version of `all_powers` -pub fn all_powers_var(a: FpVar, n: usize) -> Vec> { - if n == 0 { - return vec![]; - } - let mut r = vec![FpVar::zero(); n]; - r[0] = FpVar::one(); - for i in 1..n { - r[i] = &r[i - 1] * &a; - } - r -} - -/// returns a vector containing βᵢ* = βᵢ + α ⋅ δᵢ -pub fn betas_star(betas: &[F], deltas: &[F], alpha: F) -> Vec { - betas - .iter() - .zip( - deltas - .iter() - .map(|delta_i| alpha * delta_i) - .collect::>(), - ) - .map(|(beta_i, delta_i_alpha)| *beta_i + delta_i_alpha) - .collect() -} - -/// The in-circuit version of `betas_star` -pub fn betas_star_var( - betas: &[FpVar], - deltas: &[FpVar], - alpha: &FpVar, -) -> Vec> { - betas - .iter() - .zip(deltas) - .map(|(beta_i, delta_i)| beta_i + alpha * delta_i) - .collect::>>() -} - -/// Returns the product of selected elements in `betas`. -/// For every index `j`, whether `betas[j]` is selected is determined by the -/// `j`-th bit in the binary (little endian) representation of `i`. -/// -/// If `betas = (β, β^2, β^4, ..., β^{2^{t-1}})`, then the result is equal to -/// `β^i`. -pub fn pow_i(mut i: usize, betas: &[F]) -> F { - let mut j = 0; - let mut r = F::one(); - while i > 0 { - if i.is_odd() { - r *= betas[j]; - } - i >>= 1; - j += 1; - } - r -} - -/// The in-circuit version of `pow_i` -#[allow(dead_code)] // Will remove this once we have the decider circuit for Protogalaxy -pub fn pow_i_var(mut i: usize, betas: &[FpVar]) -> FpVar { - let mut j = 0; - let mut r = FieldVar::one(); - while i > 0 { - if i.is_odd() { - r *= &betas[j]; - } - i >>= 1; - j += 1; - } - r -} - -#[cfg(test)] -mod tests { - - use ark_bn254::Fr; - use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{test_rng, UniformRand}; - use rand::Rng; - - use super::*; - use crate::Error; - - #[test] - fn test_exponential_powers() -> Result<(), Error> { - let rng = &mut test_rng(); - - for t in 1..10 { - let cs = ConstraintSystem::::new_ref(); - - let b = Fr::rand(rng); - let b_var = FpVar::new_witness(cs.clone(), || Ok(b))?; - - let r = exponential_powers(b, t); - let r_var = exponential_powers_var(b_var, t); - - assert_eq!(r, r_var.value()?); - assert!(cs.is_satisfied()?); - } - - Ok(()) - } - - #[test] - fn test_all_powers() -> Result<(), Error> { - let rng = &mut test_rng(); - - for n in 1..10 { - let cs = ConstraintSystem::::new_ref(); - - let a = Fr::rand(rng); - let a_var = FpVar::new_witness(cs.clone(), || Ok(a))?; - - let r = all_powers(a, n); - let r_var = all_powers_var(a_var, n); - - assert_eq!(r, r_var.value()?); - assert!(cs.is_satisfied()?); - } - - Ok(()) - } - - #[test] - fn test_betas_star() -> Result<(), Error> { - let rng = &mut test_rng(); - - for t in 1..10 { - let cs = ConstraintSystem::::new_ref(); - - let betas = (0..t).map(|_| Fr::rand(rng)).collect::>(); - let deltas = (0..t).map(|_| Fr::rand(rng)).collect::>(); - let alpha = Fr::rand(rng); - - let betas_var = Vec::new_witness(cs.clone(), || Ok(betas.clone()))?; - let deltas_var = Vec::new_witness(cs.clone(), || Ok(deltas.clone()))?; - let alpha_var = FpVar::new_witness(cs.clone(), || Ok(alpha))?; - - let r = betas_star(&betas, &deltas, alpha); - let r_var = betas_star_var(&betas_var, &deltas_var, &alpha_var); - assert_eq!(r, r_var.value()?); - assert!(cs.is_satisfied()?); - } - - Ok(()) - } - - #[test] - fn test_pow_i() -> Result<(), Error> { - let rng = &mut test_rng(); - - for t in 1..10 { - let cs = ConstraintSystem::::new_ref(); - - let betas = (0..t).map(|_| Fr::rand(rng)).collect::>(); - let i = rng.gen_range(0..(1 << t)); - - let betas_var = Vec::new_witness(cs.clone(), || Ok(betas.clone()))?; - - let r = pow_i(i, &betas); - let r_var = pow_i_var(i, &betas_var); - assert_eq!(r, r_var.value()?); - assert!(cs.is_satisfied()?); - } - - Ok(()) - } -} diff --git a/folding-schemes/src/folding/traits.rs b/folding-schemes/src/folding/traits.rs deleted file mode 100644 index b2e8e864d..000000000 --- a/folding-schemes/src/folding/traits.rs +++ /dev/null @@ -1,174 +0,0 @@ -use ark_crypto_primitives::sponge::{ - constraints::{AbsorbGadget, CryptographicSpongeVar}, - poseidon::constraints::PoseidonSpongeVar, - Absorb, -}; -use ark_ff::PrimeField; -use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; -use ark_relations::gr1cs::SynthesisError; - -use crate::{ - transcript::{AbsorbNonNativeGadget, Transcript}, - Curve, Error, -}; - -use super::circuits::CF1; - -pub trait CommittedInstanceOps: Inputize> { - /// The in-circuit representation of the committed instance. - type Var: AllocVar> + CommittedInstanceVarOps; - /// `hash` implements the committed instance hash compatible with the - /// in-circuit implementation from `CommittedInstanceVarOps::hash`. - /// - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and - /// `U_i` is the committed instance `self`. - fn hash>>( - &self, - sponge: &T, - i: CF1, - z_0: &[CF1], - z_i: &[CF1], - ) -> CF1 - where - Self: Sized + Absorb, - { - let mut sponge = sponge.clone(); - sponge.absorb(&i); - sponge.absorb(&z_0); - sponge.absorb(&z_i); - sponge.absorb(&self); - sponge.squeeze_field_elements(1)[0] - } - - /// Returns the commitments contained in the committed instance. - fn get_commitments(&self) -> Vec; - - /// Returns `true` if the committed instance is an incoming instance, and - /// `false` if it is a running instance. - fn is_incoming(&self) -> bool; - - /// Checks if the committed instance is an incoming instance. - fn check_incoming(&self) -> Result<(), Error> { - self.is_incoming() - .then_some(()) - .ok_or(Error::NotIncomingCommittedInstance) - } -} - -pub trait CommittedInstanceVarOps { - type PointVar: AbsorbNonNativeGadget>; - /// `hash` implements the in-circuit committed instance hash compatible with - /// the native implementation from `CommittedInstanceOps::hash`. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and - /// `U_i` is the committed instance `self`. - /// - /// Additionally it returns the in-circuit representation of the committed - /// instance `self` as a vector of field elements, so they can be reused in - /// other gadgets avoiding recalculating (reconstraining) them. - #[allow(clippy::type_complexity)] - fn hash( - &self, - sponge: &PoseidonSpongeVar>, - i: &FpVar>, - z_0: &[FpVar>], - z_i: &[FpVar>], - ) -> Result<(FpVar>, Vec>>), SynthesisError> - where - Self: AbsorbGadget>, - { - let mut sponge = sponge.clone(); - let U_vec = self.to_sponge_field_elements()?; - sponge.absorb(&i)?; - sponge.absorb(&z_0)?; - sponge.absorb(&z_i)?; - sponge.absorb(&U_vec)?; - Ok(( - // `unwrap` is safe because the sponge is guaranteed to return a single element - sponge.squeeze_field_elements(1)?.pop().unwrap(), - U_vec, - )) - } - - /// Returns the commitments contained in the committed instance. - fn get_commitments(&self) -> Vec; - - /// Returns the public inputs contained in the committed instance. - fn get_public_inputs(&self) -> &[FpVar>]; - - /// Generates constraints to enforce that the committed instance is an - /// incoming instance. - fn enforce_incoming(&self) -> Result<(), SynthesisError>; - - /// Generates constraints to enforce that the committed instance `self` is - /// partially equal to another committed instance `other`. - /// Here, only field elements are compared, while commitments (points) are - /// not. - fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError>; -} - -pub trait WitnessOps { - /// The in-circuit representation of the witness. - type Var: AllocVar + WitnessVarOps; - - /// Returns the openings (i.e., the values being committed to and the - /// randomness) contained in the witness. - fn get_openings(&self) -> Vec<(&[F], F)>; -} - -pub trait WitnessVarOps { - /// Returns the openings (i.e., the values being committed to and the - /// randomness) contained in the witness. - fn get_openings(&self) -> Vec<(&[FpVar], FpVar)>; -} - -pub trait Dummy { - fn dummy(cfg: Cfg) -> Self; -} - -impl Dummy for Vec { - fn dummy(cfg: usize) -> Self { - vec![Default::default(); cfg] - } -} - -impl Dummy<()> for T { - fn dummy(_: ()) -> Self { - Default::default() - } -} - -/// Converts a value `self` into a vector of field elements, ordered in the same -/// way as how a variable of type `Var` would be represented *natively* in the -/// circuit. -/// -/// This is useful for the verifier to compute the public inputs. -pub trait Inputize { - fn inputize(&self) -> Vec; -} - -/// Converts a value `self` into a vector of field elements, ordered in the same -/// way as how a variable of type `Var` would be represented *non-natively* in -/// the circuit. -/// -/// This is useful for the verifier to compute the public inputs. -/// -/// Note that we require this trait because we need to distinguish between some -/// data types that are represented both natively and non-natively in-circuit -/// (e.g., field elements can have type `FpVar` and `NonNativeUintVar`). -pub trait InputizeNonNative { - fn inputize_nonnative(&self) -> Vec; -} - -impl> Inputize for [T] { - fn inputize(&self) -> Vec { - self.iter().flat_map(Inputize::::inputize).collect() - } -} - -impl> InputizeNonNative for [T] { - fn inputize_nonnative(&self) -> Vec { - self.iter() - .flat_map(InputizeNonNative::::inputize_nonnative) - .collect() - } -} diff --git a/folding-schemes/src/frontend/mod.rs b/folding-schemes/src/frontend/mod.rs deleted file mode 100644 index f19e14055..000000000 --- a/folding-schemes/src/frontend/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::Error; -use ark_ff::PrimeField; -use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; -use ark_std::fmt::Debug; - -pub mod utils; - -/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. -/// inside the agmented F' function). -/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying -/// the step. -/// Note that the external inputs for the specific circuit are defined at the implementation of -/// both `FCircuit::ExternalInputs` and `FCircuit::ExternalInputsVar`, where the `Default` trait -/// implementation for the `ExternalInputs` returns the initialized data structure (ie. if the type -/// contains a vector, it is initialized at the expected length). -pub trait FCircuit: Clone + Debug { - type Params: Debug; - type ExternalInputs: Clone + Default + Debug; - type ExternalInputsVar: Clone + Debug + AllocVar; - - /// returns a new FCircuit instance - fn new(params: Self::Params) -> Result; - - /// returns the number of elements in the state of the FCircuit, which corresponds to the - /// FCircuit inputs. - fn state_len(&self) -> usize; - - /// generates the constraints for the step of F for the given z_i - fn generate_step_constraints( - // this method uses self, so that each FCircuit implementation (and different frontends) - // can hold a state if needed to store data to generate the constraints. - &self, - cs: ConstraintSystemRef, - i: usize, - z_i: Vec>, - external_inputs: Self::ExternalInputsVar, // inputs that are not part of the state - ) -> Result>, SynthesisError>; -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_bn254::Fr; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - - use utils::{custom_step_native, CubicFCircuit, CustomFCircuit, WrapperCircuit}; - - #[test] - fn test_testfcircuit() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - let F_circuit = CubicFCircuit::::new(())?; - - let wrapper_circuit = WrapperCircuit::> { - FC: F_circuit, - z_i: Some(vec![Fr::from(3_u32)]), - z_i1: Some(vec![Fr::from(35_u32)]), - }; - wrapper_circuit.generate_constraints(cs.clone())?; - assert_eq!(cs.num_constraints(), 3); - Ok(()) - } - - #[test] - fn test_customtestfcircuit() -> Result<(), Error> { - let cs = ConstraintSystem::::new_ref(); - let n_constraints = 1000; - let custom_circuit = CustomFCircuit::::new(n_constraints)?; - let z_i = vec![Fr::from(5_u32)]; - let wrapper_circuit = WrapperCircuit::> { - FC: custom_circuit, - z_i: Some(z_i.clone()), - z_i1: Some(custom_step_native(z_i, n_constraints)), - }; - wrapper_circuit.generate_constraints(cs.clone())?; - assert_eq!(cs.num_constraints(), n_constraints); - Ok(()) - } -} diff --git a/folding-schemes/src/frontend/utils.rs b/folding-schemes/src/frontend/utils.rs deleted file mode 100644 index 6298f76a1..000000000 --- a/folding-schemes/src/frontend/utils.rs +++ /dev/null @@ -1,159 +0,0 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::AllocVar, - fields::{fp::FpVar, FieldVar}, -}; -use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::marker::PhantomData; -use ark_std::{fmt::Debug, Zero}; - -use super::FCircuit; -use crate::Error; - -/// DummyCircuit is a circuit that has dummy state whose length is specified in the `state_len` -/// parameter, without any constraints. -#[derive(Clone, Debug)] -pub struct DummyCircuit { - state_len: usize, -} -impl FCircuit for DummyCircuit { - type Params = usize; - type ExternalInputs = (); - type ExternalInputsVar = (); - - fn new(state_len: Self::Params) -> Result { - Ok(Self { state_len }) - } - fn state_len(&self) -> usize { - self.state_len - } - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - _z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - Vec::new_witness(cs.clone(), || Ok(vec![Zero::zero(); self.state_len])) - } -} - -/// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit -/// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`. -/// `z_i` is used as `x`, and `z_{i+1}` is used as `y`, and at the next step, `z_{i+1}` will be -/// assigned to `z_i`, and a new `z+{i+1}` will be computted. -#[cfg(test)] -#[derive(Clone, Copy, Debug)] -pub struct CubicFCircuit { - _f: PhantomData, -} - -#[cfg(test)] -impl FCircuit for CubicFCircuit { - type Params = (); - type ExternalInputs = (); - type ExternalInputsVar = (); - - fn new(_params: Self::Params) -> Result { - Ok(Self { _f: PhantomData }) - } - fn state_len(&self) -> usize { - 1 - } - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; - let z_i = z_i[0].clone(); - - Ok(vec![&z_i * &z_i * &z_i + &z_i + &five]) - } -} - -/// Native implementation of `CubicFCircuit` -#[cfg(test)] -pub fn cubic_step_native(z_i: Vec) -> Vec { - let z = z_i[0]; - vec![z * z * z + z + F::from(5)] -} - -/// CustomFCircuit is a circuit that has the number of constraints specified in the -/// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices. -#[derive(Clone, Copy, Debug)] -pub struct CustomFCircuit { - _f: PhantomData, - pub n_constraints: usize, -} - -impl FCircuit for CustomFCircuit { - type Params = usize; - type ExternalInputs = (); - type ExternalInputsVar = (); - - fn new(params: Self::Params) -> Result { - Ok(Self { - _f: PhantomData, - n_constraints: params, - }) - } - fn state_len(&self) -> usize { - 1 - } - fn generate_step_constraints( - &self, - _cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let mut z_i1 = z_i[0].clone(); - for _ in 0..self.n_constraints - 1 { - z_i1 = z_i1.square()?; - } - - Ok(vec![z_i1]) - } -} - -/// Native implementation of `CubicFCircuit` -#[cfg(test)] -pub fn custom_step_native(z_i: Vec, n_constraints: usize) -> Vec { - let mut z_i1 = z_i[0]; - for _ in 0..n_constraints - 1 { - z_i1 = z_i1.square(); - } - vec![z_i1] -} - -/// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This -/// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping -/// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints -/// of the AugmentedF circuit logic, in order to run lighter tests when we're not interested in -/// the AugmentedF logic but in the wrapping of the circuits. -pub struct WrapperCircuit> { - pub FC: FC, // F circuit - pub z_i: Option>, - pub z_i1: Option>, -} - -impl> ConstraintSynthesizer for WrapperCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let z_i = - Vec::>::new_witness(cs.clone(), || Ok(self.z_i.unwrap_or(vec![F::zero()])))?; - let z_i1 = - Vec::>::new_input(cs.clone(), || Ok(self.z_i1.unwrap_or(vec![F::zero()])))?; - let external_inputs = - FC::ExternalInputsVar::new_input(cs.clone(), || Ok(FC::ExternalInputs::default()))?; - let computed_z_i1 = - self.FC - .generate_step_constraints(cs.clone(), 0, z_i.clone(), external_inputs)?; - - use ark_r1cs_std::eq::EqGadget; - computed_z_i1.enforce_equal(&z_i1)?; - Ok(()) - } -} diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs deleted file mode 100644 index 65f90ec9c..000000000 --- a/folding-schemes/src/lib.rs +++ /dev/null @@ -1,316 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] - -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{ - short_weierstrass::{Projective, SWCurveConfig}, - CurveGroup, -}; -use ark_ff::{Fp, FpConfig, PrimeField}; -use ark_r1cs_std::{ - fields::{fp::FpVar, FieldVar}, - groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, -}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{ - fmt::Debug, - rand::{CryptoRng, RngCore}, -}; -use thiserror::Error; - -use crate::folding::traits::{Inputize, InputizeNonNative}; -use crate::frontend::FCircuit; -use crate::transcript::AbsorbNonNative; - -pub mod arith; -pub mod commitment; -pub mod constants; -pub mod folding; -pub mod frontend; -pub mod transcript; -pub mod utils; - -#[derive(Debug, Error)] -pub enum Error { - // Wrappers on top of other errors - #[error("ark_relations::gr1cs::SynthesisError")] - SynthesisError(#[from] ark_relations::gr1cs::SynthesisError), - #[error("ark_serialize::SerializationError")] - SerializationError(#[from] ark_serialize::SerializationError), - #[error("ark_poly_commit::Error")] - PolyCommitError(#[from] ark_poly_commit::Error), - #[error("crate::utils::espresso::virtual_polynomial::ArithErrors")] - ArithError(#[from] utils::espresso::virtual_polynomial::ArithErrors), - #[error(transparent)] - ProtoGalaxy(folding::protogalaxy::ProtoGalaxyError), - #[error("std::io::Error")] - IOError(#[from] std::io::Error), - - // Relation errors - #[error("Relation not satisfied")] - NotSatisfied, - #[error("SNARK setup failed: {0}")] - SNARKSetupFail(String), - #[error("SNARK verification failed")] - SNARKVerificationFail, - #[error("IVC verification failed")] - IVCVerificationFail, - #[error("zkIVC verification failed")] - zkIVCVerificationFail, - #[error("Committed instance is expected to be an incoming (fresh) instance")] - NotIncomingCommittedInstance, - #[error("R1CS instance is expected to not be relaxed")] - R1CSUnrelaxedFail, - #[error("Could not find the inner ConstraintSystem")] - NoInnerConstraintSystem, - #[error("Sum-check prove failed: {0}")] - SumCheckProveError(String), - #[error("Sum-check verify failed: {0}")] - SumCheckVerifyError(String), - - // Comparators errors - #[error("Not equal")] - NotEqual, - #[error("Vectors should have the same length ({0}: {1}, {2}: {3})")] - NotSameLength(String, usize, String, usize), - #[error("Vector's length ({0}) is not the expected ({1})")] - NotExpectedLength(usize, usize), - #[error("Vector ({0}) length ({1}) is not a power of two")] - NotPowerOfTwo(String, usize), - #[error("Can not be empty")] - Empty, - #[error("Value out of bounds")] - OutOfBounds, - #[error("Could not construct the Evaluation Domain")] - NewDomainFail, - #[error("The number of folded steps must be greater than 1")] - NotEnoughSteps, - #[error("Evaluation failed")] - EvaluationFail, - #[error("{0} can not be zero")] - CantBeZero(String), - - // Commitment errors - #[error("Pedersen parameters length is not sufficient (generators.len={0} < vector.len={1} unsatisfied)")] - PedersenParamsLen(usize, usize), - #[error("Blinding factor not 0 for Commitment without hiding")] - BlindingNotZero, - #[error("Blinding factors incorrect, blinding is set to {0} but blinding values are {1}")] - IncorrectBlinding(bool, String), - #[error("Commitment verification failed")] - CommitmentVerificationFail, - - // Polynomial IOP errors, from https://github.com/EspressoSystems/hyperplonk/blob/main/subroutines/src/poly_iop/errors.rs - #[error("Invalid Polynomial IOP Prover: {0}")] - InvalidPolyIOPProver(String), - #[error("Invalid Polynomial IOP Verifier: {0}")] - InvalidPolyIOPVerifier(String), - #[error("Invalid Polynomial IOP Proof: {0}")] - InvalidPolyIOPProof(String), - #[error("Invalid Polynomial IOP Parameters: {0}")] - InvalidPolyIOPParameters(String), - - // Other - #[error("{0}")] - Other(String), - #[error("Randomness for blinding not found")] - MissingRandomness, - #[error("Missing value: {0}")] - MissingValue(String), - #[error("Feature '{0}' not supported yet")] - NotSupportedYet(String), - #[error("Feature '{0}' is not supported and it will not be")] - NotSupported(String), - #[error("max i-th step reached (usize limit reached)")] - MaxStep, - #[error("Witness calculation error: {0}")] - WitnessCalculationError(String), - #[error("Failed to convert {0} into {1}: {2}")] - ConversionError(String, String, String), - #[error("Failed to serde: {0}")] - JSONSerdeError(String), - #[error("Multi instances folding not supported in this scheme")] - NoMultiInstances, - #[error("Missing 'other' instances, since this is a multi-instances folding scheme. Expected number of instances, mu:{0}, nu:{1}")] - MissingOtherInstances(usize, usize), -} - -/// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined -/// over a cycle of curves (C1, C2), where: -/// - C1 is the main curve, which ScalarField we use as our F for all the field operations -/// - C2 is the auxiliary curve, which we use for the commitments, whose BaseField (for point -/// coordinates) are in the C1::ScalarField. -/// -/// In other words, C1.Fq == C2.Fr, and C1.Fr == C2.Fq. -pub trait FoldingScheme< - C1: Curve, - C2: Curve, - FC: FCircuit, ->: Clone + Debug -{ - type PreprocessorParam: Debug + Clone; - type ProverParam: Debug + Clone + CanonicalSerialize; - type VerifierParam: Debug + Clone + CanonicalSerialize; - type RunningInstance: Debug; // contains the CommittedInstance + Witness - type IncomingInstance: Debug; // contains the CommittedInstance + Witness - type MultiCommittedInstanceWithWitness: Debug; // type used for the extra instances in the multi-instance folding setting - type CFInstance: Debug; // CycleFold CommittedInstance & Witness - type IVCProof: PartialEq + Eq + Clone + Debug + CanonicalSerialize + CanonicalDeserialize; - - /// deserialize Self::ProverParam and recover the not serialized data that is recomputed on the - /// fly to save serialized bytes. - /// Internally it generates the r1cs/ccs & cf_r1cs needed for the VerifierParams. In this way - /// we avoid needing to serialize them, saving significant space in the VerifierParams - /// serialized size. - fn pp_deserialize_with_mode( - reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - fc_params: FC::Params, // FCircuit params - ) -> Result; - - /// deserialize Self::VerifierParam and recover the not serialized data that is recomputed on - /// the fly to save serialized bytes. - /// Internally it generates the r1cs/ccs & cf_r1cs needed for the VerifierParams. In this way - /// we avoid needing to serialize them, saving significant space in the VerifierParams - /// serialized size. - fn vp_deserialize_with_mode( - reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - fc_params: FC::Params, // FCircuit params - ) -> Result; - - fn preprocess( - rng: impl RngCore, - prep_param: &Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; - - fn init( - params: &(Self::ProverParam, Self::VerifierParam), - step_circuit: FC, - z_0: Vec, // initial state - ) -> Result; - - fn prove_step( - &mut self, - rng: impl RngCore, - external_inputs: FC::ExternalInputs, - other_instances: Option, - ) -> Result<(), Error>; - - /// returns the state at the current step - fn state(&self) -> Vec; - - /// returns the last IVC state proof, which can be verified in the `verify` method - fn ivc_proof(&self) -> Self::IVCProof; - - /// constructs the FoldingScheme instance from the given IVCProof, ProverParams, VerifierParams - /// and PoseidonConfig. - /// This method is useful for when the IVCProof is sent between different parties, so that they - /// can continue iterating the IVC from the received IVCProof. - fn from_ivc_proof( - ivc_proof: Self::IVCProof, - fcircuit_params: FC::Params, - params: (Self::ProverParam, Self::VerifierParam), - ) -> Result; - - fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error>; -} - -/// Trait with auxiliary methods for multi-folding schemes (ie. HyperNova, ProtoGalaxy, etc), -/// allowing to create new instances for the multifold. -pub trait MultiFolding< - C1: Curve, - C2: Curve, - FC: FCircuit, ->: Clone + Debug -{ - type RunningInstance: Debug; - type IncomingInstance: Debug; - type MultiInstance: Debug; - - /// Creates a new RunningInstance for the given state, to be folded in the multi-folding step. - fn new_running_instance( - &self, - rng: impl RngCore, - state: Vec, - external_inputs: FC::ExternalInputs, - ) -> Result; - - /// Creates a new IncomingInstance for the given state, to be folded in the multi-folding step. - fn new_incoming_instance( - &self, - rng: impl RngCore, - state: Vec, - external_inputs: FC::ExternalInputs, - ) -> Result; -} - -pub trait Decider< - C1: Curve, - C2: Curve, - FC: FCircuit, - FS: FoldingScheme, -> -{ - type PreprocessorParam: Debug; - type ProverParam: Clone; - type Proof; - type VerifierParam; - type PublicInput: Debug; - type CommittedInstance: Clone + Debug; - - fn preprocess( - rng: impl RngCore + CryptoRng, - prep_param: Self::PreprocessorParam, - ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; - - fn prove( - rng: impl RngCore + CryptoRng, - pp: Self::ProverParam, - folding_scheme: FS, - ) -> Result; - - fn verify( - vp: Self::VerifierParam, - i: C1::ScalarField, - z_0: Vec, - z_i: Vec, - running_instance: &Self::CommittedInstance, - incoming_instance: &Self::CommittedInstance, - proof: &Self::Proof, - // returns `Result` to differentiate between an error occurred while performing - // the verification steps, and the verification logic of the scheme not passing. - ) -> Result; -} - -/// `Field` trait is a wrapper around `PrimeField` that also includes the -/// necessary bounds for the field to be used conveniently in folding schemes. -pub trait Field: - PrimeField + Absorb + AbsorbNonNative + Inputize -{ - /// The in-circuit variable type for this field. - type Var: FieldVar; -} - -impl, const N: usize> Field for Fp { - type Var = FpVar; -} - -/// `Curve` trait is a wrapper around `CurveGroup` that also includes the -/// necessary bounds for the curve to be used conveniently in folding schemes. -pub trait Curve: - CurveGroup - + AbsorbNonNative - + Inputize - + InputizeNonNative -{ - /// The in-circuit variable type for this curve. - type Var: CurveVar; -} - -impl> Curve for Projective

{ - type Var = ProjectiveVar>; -} diff --git a/folding-schemes/src/transcript/mod.rs b/folding-schemes/src/transcript/mod.rs deleted file mode 100644 index 608201f8e..000000000 --- a/folding-schemes/src/transcript/mod.rs +++ /dev/null @@ -1,139 +0,0 @@ -use ark_crypto_primitives::sponge::{constraints::CryptographicSpongeVar, CryptographicSponge}; -use ark_ec::CurveGroup; -use ark_ff::PrimeField; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; -use ark_relations::gr1cs::SynthesisError; - -pub mod poseidon; - -/// An interface for objects that can be absorbed by a `Transcript`. -/// -/// Matches `Absorb` in `ark-crypto-primitives`. -pub trait AbsorbNonNative { - /// Converts the object into field elements that can be absorbed by a `Transcript`. - /// Append the list to `dest` - fn to_native_sponge_field_elements(&self, dest: &mut Vec); - - /// Converts the object into field elements that can be absorbed by a `Transcript`. - /// Return the list as `Vec` - fn to_native_sponge_field_elements_as_vec(&self) -> Vec { - let mut result = Vec::new(); - self.to_native_sponge_field_elements(&mut result); - result - } -} - -/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field -/// is `F`. -/// -/// Matches `AbsorbGadget` in `ark-crypto-primitives`. -pub trait AbsorbNonNativeGadget { - /// Converts the object into field elements that can be absorbed by a `TranscriptVar`. - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError>; -} - -impl AbsorbNonNative for [T] { - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - for t in self.iter() { - t.to_native_sponge_field_elements(dest); - } - } -} - -impl> AbsorbNonNativeGadget for &T { - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { - T::to_native_sponge_field_elements(self) - } -} - -impl> AbsorbNonNativeGadget for [T] { - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { - let mut result = Vec::new(); - for t in self.iter() { - result.extend(t.to_native_sponge_field_elements()?); - } - Ok(result) - } -} - -pub trait Transcript: CryptographicSponge { - /// `new_with_pp_hash` creates a new transcript / sponge with the given - /// hash of the public parameters. - fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self; - - /// `absorb_point` is for absorbing points whose `BaseField` is the field of - /// the sponge, i.e., the type `C` of these points should satisfy - /// `C::BaseField = F`. - /// - /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` - /// instead. - fn absorb_point>(&mut self, v: &C); - /// `absorb_nonnative` is for structs that contain non-native (field or - /// group) elements, including: - /// - /// - A field element of type `T: PrimeField` that will be absorbed into a - /// sponge that operates in another field `F != T`. - /// - A group element of type `C: CurveGroup` that will be absorbed into a - /// sponge that operates in another field `F != C::BaseField`, e.g., - /// `F = C::ScalarField`. - /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that - /// will be absorbed into a sponge that operates in the (scalar field of - /// the) primary curve. - /// - /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on - /// the primary curve also contains non-native elements, we still regard - /// it as native, because the sponge is on the same curve. - fn absorb_nonnative(&mut self, v: &V); - - fn get_challenge(&mut self) -> F; - /// get_challenge_nbits returns a field element of size nbits - fn get_challenge_nbits(&mut self, nbits: usize) -> Vec; - fn get_challenges(&mut self, n: usize) -> Vec; -} - -pub trait TranscriptVar: - CryptographicSpongeVar -{ - /// `new_with_pp_hash` creates a new transcript / sponge with the given - /// hash of the public parameters. - fn new_with_pp_hash( - config: &Self::Parameters, - pp_hash: &FpVar, - ) -> Result; - - /// `absorb_point` is for absorbing points whose `BaseField` is the field of - /// the sponge, i.e., the type `C` of these points should satisfy - /// `C::BaseField = F`. - /// - /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` - /// instead. - fn absorb_point, GC: CurveVar>( - &mut self, - v: &GC, - ) -> Result<(), SynthesisError>; - /// `absorb_nonnative` is for structs that contain non-native (field or - /// group) elements, including: - /// - /// - A field element of type `T: PrimeField` that will be absorbed into a - /// sponge that operates in another field `F != T`. - /// - A group element of type `C: CurveGroup` that will be absorbed into a - /// sponge that operates in another field `F != C::BaseField`, e.g., - /// `F = C::ScalarField`. - /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that - /// will be absorbed into a sponge that operates in the (scalar field of - /// the) primary curve. - /// - /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on - /// the primary curve also contains non-native elements, we still regard - /// it as native, because the sponge is on the same curve. - fn absorb_nonnative>( - &mut self, - v: &V, - ) -> Result<(), SynthesisError>; - - fn get_challenge(&mut self) -> Result, SynthesisError>; - /// returns the bit representation of the challenge, we use its output in-circuit for the - /// `GC.scalar_mul_le` method. - fn get_challenge_nbits(&mut self, nbits: usize) -> Result>, SynthesisError>; - fn get_challenges(&mut self, n: usize) -> Result>, SynthesisError>; -} diff --git a/folding-schemes/src/transcript/poseidon.rs b/folding-schemes/src/transcript/poseidon.rs deleted file mode 100644 index 1d8fcc13d..000000000 --- a/folding-schemes/src/transcript/poseidon.rs +++ /dev/null @@ -1,294 +0,0 @@ -use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{ - constraints::PoseidonSpongeVar, find_poseidon_ark_and_mds, PoseidonConfig, PoseidonSponge, - }, - Absorb, CryptographicSponge, -}; -use ark_ec::{AffineRepr, CurveGroup}; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; - -use super::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; - -impl Transcript for PoseidonSponge { - fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self { - let mut sponge = Self::new(config); - sponge.absorb(&pp_hash); - sponge - } - - // Compatible with the in-circuit `TranscriptVar::absorb_point` - fn absorb_point>(&mut self, p: &C) { - let (x, y) = p.into_affine().xy().unwrap_or_default(); - self.absorb(&x); - self.absorb(&y); - } - fn absorb_nonnative(&mut self, v: &V) { - self.absorb(&v.to_native_sponge_field_elements_as_vec::()); - } - fn get_challenge(&mut self) -> F { - let c = self.squeeze_field_elements(1); - self.absorb(&c[0]); - c[0] - } - fn get_challenge_nbits(&mut self, nbits: usize) -> Vec { - let bits = self.squeeze_bits(nbits); - self.absorb(&F::from(F::BigInt::from_bits_le(&bits))); - bits - } - fn get_challenges(&mut self, n: usize) -> Vec { - let c = self.squeeze_field_elements(n); - self.absorb(&c); - c - } -} - -impl TranscriptVar> for PoseidonSpongeVar { - fn new_with_pp_hash( - config: &Self::Parameters, - pp_hash: &FpVar, - ) -> Result { - let mut sponge = Self::new(ConstraintSystemRef::None, config); - sponge.absorb(&pp_hash)?; - Ok(sponge) - } - - fn absorb_point, GC: CurveVar>( - &mut self, - v: &GC, - ) -> Result<(), SynthesisError> { - let mut vec = v.to_constraint_field()?; - // The last element in the vector tells whether the point is infinity, - // but we can in fact avoid absorbing it without loss of soundness. - // This is because the `to_constraint_field` method internally invokes - // [`ProjectiveVar::to_afine`](https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/curves/short_weierstrass/mod.rs#L160-L195), - // which guarantees that an infinity point is represented as `(0, 0)`, - // but the y-coordinate of a non-infinity point is never 0 (for why, see - // https://crypto.stackexchange.com/a/108242 ). - vec.pop(); - self.absorb(&vec) - } - fn absorb_nonnative>( - &mut self, - v: &V, - ) -> Result<(), SynthesisError> { - self.absorb(&v.to_native_sponge_field_elements()?) - } - fn get_challenge(&mut self) -> Result, SynthesisError> { - let c = self.squeeze_field_elements(1)?; - self.absorb(&c[0])?; - Ok(c[0].clone()) - } - - /// returns the bit representation of the challenge, we use its output in-circuit for the - /// `GC.scalar_mul_le` method. - fn get_challenge_nbits(&mut self, nbits: usize) -> Result>, SynthesisError> { - let bits = self.squeeze_bits(nbits)?; - self.absorb(&Boolean::le_bits_to_fp(&bits)?)?; - Ok(bits) - } - fn get_challenges(&mut self, n: usize) -> Result>, SynthesisError> { - let c = self.squeeze_field_elements(n)?; - self.absorb(&c)?; - Ok(c) - } -} - -/// This Poseidon configuration generator produces a Poseidon configuration with custom parameters -pub fn poseidon_custom_config( - full_rounds: usize, - partial_rounds: usize, - alpha: u64, - rate: usize, - capacity: usize, -) -> PoseidonConfig { - let (ark, mds) = find_poseidon_ark_and_mds::( - F::MODULUS_BIT_SIZE as u64, - rate, - full_rounds as u64, - partial_rounds as u64, - 0, - ); - - PoseidonConfig::new(full_rounds, partial_rounds, alpha, mds, ark, rate, capacity) -} - -/// This Poseidon configuration generator agrees with Circom's Poseidon(4) in the case of BN254's scalar field -pub fn poseidon_canonical_config() -> PoseidonConfig { - // 120 bit security target as in - // https://eprint.iacr.org/2019/458.pdf - // t = rate + 1 - - let full_rounds = 8; - let partial_rounds = 60; - let alpha = 5; - let rate = 4; - - poseidon_custom_config(full_rounds, partial_rounds, alpha, rate, 1) -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; - use ark_ec::PrimeGroup; - use ark_ff::UniformRand; - use ark_r1cs_std::{ - alloc::AllocVar, groups::curves::short_weierstrass::ProjectiveVar, GR1CSVar, - }; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::test_rng; - - use super::*; - use crate::folding::circuits::nonnative::affine::NonNativeAffineVar; - use crate::Error; - - // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 - #[test] - fn check_against_circom_poseidon() -> Result<(), Error> { - use ark_bn254::Fr; - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use std::str::FromStr; - - let config = poseidon_canonical_config::(); - let mut poseidon_sponge: PoseidonSponge<_> = CryptographicSponge::new(&config); - let v: Vec = vec!["1", "2", "3", "4"] - .into_iter() - .map(|x| { - Fr::from_str(x).map_err(|_| { - Error::ConversionError("str".to_string(), "Fr".to_string(), x.to_string()) - }) - }) - .collect::, Error>>()?; - poseidon_sponge.absorb(&v); - poseidon_sponge.squeeze_field_elements::(1); - assert!( - poseidon_sponge.state[0] - == Fr::from_str( - "18821383157269793795438455681495246036402687001665670618754263018637548127333" - ) - .map_err(|_| { - Error::ConversionError( - "str".to_string(), - "Fr".to_string(), - "hardcoded string".to_string(), - ) - })? - ); - Ok(()) - } - - #[test] - fn test_transcript_and_transcriptvar_absorb_native_point() -> Result<(), Error> { - // use 'native' transcript - let config = poseidon_canonical_config::(); - let mut tr = PoseidonSponge::::new(&config); - let rng = &mut test_rng(); - - let p = G1::rand(rng); - tr.absorb_point(&p); - let c = tr.get_challenge(); - - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); - let p_var = ProjectiveVar::>::new_witness( - ConstraintSystem::::new_ref(), - || Ok(p), - )?; - tr_var.absorb_point(&p_var)?; - let c_var = tr_var.get_challenge()?; - - // assert that native & gadget transcripts return the same challenge - assert_eq!(c, c_var.value()?); - Ok(()) - } - - #[test] - fn test_transcript_and_transcriptvar_absorb_nonnative_point() -> Result<(), Error> { - // use 'native' transcript - let config = poseidon_canonical_config::(); - let mut tr = PoseidonSponge::::new(&config); - let rng = &mut test_rng(); - - let p = G1::rand(rng); - tr.absorb_nonnative(&p); - let c = tr.get_challenge(); - - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); - let p_var = - NonNativeAffineVar::::new_witness(ConstraintSystem::::new_ref(), || Ok(p))?; - tr_var.absorb_nonnative(&p_var)?; - let c_var = tr_var.get_challenge()?; - - // assert that native & gadget transcripts return the same challenge - assert_eq!(c, c_var.value()?); - Ok(()) - } - - #[test] - fn test_transcript_and_transcriptvar_get_challenge() -> Result<(), Error> { - // use 'native' transcript - let config = poseidon_canonical_config::(); - let mut tr = PoseidonSponge::::new(&config); - tr.absorb(&Fr::from(42_u32)); - let c = tr.get_challenge(); - - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); - let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; - tr_var.absorb(&v)?; - let c_var = tr_var.get_challenge()?; - - // assert that native & gadget transcripts return the same challenge - assert_eq!(c, c_var.value()?); - Ok(()) - } - - #[test] - fn test_transcript_and_transcriptvar_nbits() -> Result<(), Error> { - let nbits = crate::constants::NOVA_N_BITS_RO; - - // use 'native' transcript - let config = poseidon_canonical_config::(); - let mut tr = PoseidonSponge::::new(&config); - tr.absorb(&Fq::from(42_u32)); - - // get challenge from native transcript - let c_bits = tr.get_challenge_nbits(nbits); - - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); - let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; - tr_var.absorb(&v)?; - - // get challenge from circuit transcript - let c_var = tr_var.get_challenge_nbits(nbits)?; - - let P = G1::generator(); - let PVar = GVar::new_witness(cs.clone(), || Ok(P))?; - - // multiply point P by the challenge in different formats, to ensure that we get the same - // result natively and in-circuit - - // native c*P - let c_Fr = Fr::from_bigint(BigInteger::from_bits_le(&c_bits)).ok_or(Error::OutOfBounds)?; - let cP_native = P * c_Fr; - - // native c*P using mul_bits_be (notice the .rev to convert the LE to BE) - let cP_native_bits = P.mul_bits_be(c_bits.into_iter().rev()); - - // in-circuit c*P using scalar_mul_le - let cPVar = PVar.scalar_mul_le(c_var.iter())?; - - // check that they are equal - assert_eq!(cP_native.into_affine(), cPVar.value()?.into_affine()); - assert_eq!(cP_native_bits.into_affine(), cPVar.value()?.into_affine()); - Ok(()) - } -} diff --git a/folding-schemes/src/utils/espresso/mod.rs b/folding-schemes/src/utils/espresso/mod.rs deleted file mode 100644 index 8c11fd081..000000000 --- a/folding-schemes/src/utils/espresso/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod multilinear_polynomial; -pub mod sum_check; -pub mod virtual_polynomial; diff --git a/folding-schemes/src/utils/espresso/multilinear_polynomial.rs b/folding-schemes/src/utils/espresso/multilinear_polynomial.rs deleted file mode 100644 index da5d39af7..000000000 --- a/folding-schemes/src/utils/espresso/multilinear_polynomial.rs +++ /dev/null @@ -1,200 +0,0 @@ -// code forked from -// https://github.com/EspressoSystems/hyperplonk/blob/main/arithmetic/src/multilinear_polynomial.rs -// -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . - -use ark_ff::Field; -#[cfg(feature = "parallel")] -use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; - -pub use ark_poly::DenseMultilinearExtension; - -pub fn fix_variables( - poly: &DenseMultilinearExtension, - partial_point: &[F], -) -> DenseMultilinearExtension { - assert!( - partial_point.len() <= poly.num_vars, - "invalid size of partial point" - ); - let nv = poly.num_vars; - let mut poly = poly.evaluations.to_vec(); - let dim = partial_point.len(); - // evaluate single variable of partial point from left to right - for (i, point) in partial_point.iter().enumerate().take(dim) { - poly = fix_one_variable_helper(&poly, nv - i, point); - } - - DenseMultilinearExtension::::from_evaluations_slice(nv - dim, &poly[..(1 << (nv - dim))]) -} - -fn fix_one_variable_helper(data: &[F], nv: usize, point: &F) -> Vec { - let mut res = vec![F::zero(); 1 << (nv - 1)]; - - // evaluate single variable of partial point from left to right - #[cfg(not(feature = "parallel"))] - for i in 0..(1 << (nv - 1)) { - res[i] = data[i << 1] + (data[(i << 1) + 1] - data[i << 1]) * point; - } - - #[cfg(feature = "parallel")] - res.par_iter_mut().enumerate().for_each(|(i, x)| { - *x = data[i << 1] + (data[(i << 1) + 1] - data[i << 1]) * point; - }); - - res -} - -pub fn evaluate_no_par(poly: &DenseMultilinearExtension, point: &[F]) -> F { - assert_eq!(poly.num_vars, point.len()); - fix_variables_no_par(poly, point).evaluations[0] -} - -fn fix_variables_no_par( - poly: &DenseMultilinearExtension, - partial_point: &[F], -) -> DenseMultilinearExtension { - assert!( - partial_point.len() <= poly.num_vars, - "invalid size of partial point" - ); - let nv = poly.num_vars; - let mut poly = poly.evaluations.to_vec(); - let dim = partial_point.len(); - // evaluate single variable of partial point from left to right - for i in 1..dim + 1 { - let r = partial_point[i - 1]; - for b in 0..(1 << (nv - i)) { - poly[b] = poly[b << 1] + (poly[(b << 1) + 1] - poly[b << 1]) * r; - } - } - DenseMultilinearExtension::from_evaluations_slice(nv - dim, &poly[..(1 << (nv - dim))]) -} - -/// Given multilinear polynomial `p(x)` and s `s`, compute `s*p(x)` -pub fn scalar_mul( - poly: &DenseMultilinearExtension, - s: &F, -) -> DenseMultilinearExtension { - DenseMultilinearExtension { - evaluations: poly.evaluations.iter().map(|e| *e * s).collect(), - num_vars: poly.num_vars, - } -} - -/// Test-only methods used in virtual_polynomial.rs -#[cfg(test)] -pub mod tests { - use super::*; - use ark_ff::PrimeField; - use ark_std::rand::RngCore; - use ark_std::{end_timer, start_timer}; - use std::sync::Arc; - - pub fn fix_last_variables( - poly: &DenseMultilinearExtension, - partial_point: &[F], - ) -> DenseMultilinearExtension { - assert!( - partial_point.len() <= poly.num_vars, - "invalid size of partial point" - ); - let nv = poly.num_vars; - let mut poly = poly.evaluations.to_vec(); - let dim = partial_point.len(); - // evaluate single variable of partial point from left to right - for (i, point) in partial_point.iter().rev().enumerate().take(dim) { - poly = fix_last_variable_helper(&poly, nv - i, point); - } - - DenseMultilinearExtension::::from_evaluations_slice(nv - dim, &poly[..(1 << (nv - dim))]) - } - - fn fix_last_variable_helper(data: &[F], nv: usize, point: &F) -> Vec { - let half_len = 1 << (nv - 1); - let mut res = vec![F::zero(); half_len]; - - // evaluate single variable of partial point from left to right - #[cfg(not(feature = "parallel"))] - for b in 0..half_len { - res[b] = data[b] + (data[b + half_len] - data[b]) * point; - } - - #[cfg(feature = "parallel")] - res.par_iter_mut().enumerate().for_each(|(i, x)| { - *x = data[i] + (data[i + half_len] - data[i]) * point; - }); - - res - } - - /// Sample a random list of multilinear polynomials. - /// Returns - /// - the list of polynomials, - /// - its sum of polynomial evaluations over the boolean hypercube. - #[cfg(test)] - pub fn random_mle_list( - nv: usize, - degree: usize, - rng: &mut R, - ) -> (Vec>>, F) { - let start = start_timer!(|| "sample random mle list"); - let mut multiplicands = Vec::with_capacity(degree); - for _ in 0..degree { - multiplicands.push(Vec::with_capacity(1 << nv)) - } - let mut sum = F::zero(); - - for _ in 0..(1 << nv) { - let mut product = F::one(); - - for e in multiplicands.iter_mut() { - let val = F::rand(rng); - e.push(val); - product *= val; - } - sum += product; - } - - let list = multiplicands - .into_iter() - .map(|x| Arc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) - .collect(); - - end_timer!(start); - (list, sum) - } - - // Build a randomize list of mle-s whose sum is zero. - #[cfg(test)] - pub fn random_zero_mle_list( - nv: usize, - degree: usize, - rng: &mut R, - ) -> Vec>> { - let start = start_timer!(|| "sample random zero mle list"); - - let mut multiplicands = Vec::with_capacity(degree); - for _ in 0..degree { - multiplicands.push(Vec::with_capacity(1 << nv)) - } - for _ in 0..(1 << nv) { - multiplicands[0].push(F::zero()); - for e in multiplicands.iter_mut().skip(1) { - e.push(F::rand(rng)); - } - } - - let list = multiplicands - .into_iter() - .map(|x| Arc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) - .collect(); - - end_timer!(start); - list - } -} diff --git a/folding-schemes/src/utils/espresso/sum_check/mod.rs b/folding-schemes/src/utils/espresso/sum_check/mod.rs deleted file mode 100644 index 2d472250e..000000000 --- a/folding-schemes/src/utils/espresso/sum_check/mod.rs +++ /dev/null @@ -1,259 +0,0 @@ -// code forked from: -// https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check -// -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . - -//! This module implements the sum check protocol. - -use crate::{ - transcript::Transcript, - utils::virtual_polynomial::{VPAuxInfo, VirtualPolynomial}, - Error, -}; -use ark_crypto_primitives::sponge::Absorb; -use ark_ff::PrimeField; -use ark_poly::univariate::DensePolynomial; -use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial}; -use ark_std::{end_timer, start_timer}; -use std::{fmt::Debug, marker::PhantomData, sync::Arc}; - -use crate::utils::sum_check::structs::IOPProverMessage; -use crate::utils::sum_check::structs::IOPVerifierState; -use structs::{IOPProof, IOPProverState}; - -mod prover; -pub mod structs; -pub mod verifier; - -/// A generic sum-check trait over a curve group -pub trait SumCheck { - type VirtualPolynomial; - type VPAuxInfo; - type MultilinearExtension; - - type SumCheckProof: Clone + Debug + Default + PartialEq; - type SumCheckSubClaim: Clone + Debug + Default + PartialEq; - - /// Extract sum from the proof - fn extract_sum(proof: &Self::SumCheckProof) -> F; - - /// Generate proof of the sum of polynomial over {0,1}^`num_vars` - /// - /// The polynomial is represented in the form of a VirtualPolynomial. - fn prove( - poly: &Self::VirtualPolynomial, - transcript: &mut impl Transcript, - ) -> Result; - - /// Verify the claimed sum using the proof - fn verify( - sum: F, - proof: &Self::SumCheckProof, - aux_info: &Self::VPAuxInfo, - transcript: &mut impl Transcript, - ) -> Result; -} - -/// Trait for sum check protocol prover side APIs. -pub trait SumCheckProver: Sized { - type VirtualPolynomial; - type ProverMessage; - - /// Initialize the prover state to argue for the sum of the input polynomial - /// over {0,1}^`num_vars`. - fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result; - - /// Receive message from verifier, generate prover message, and proceed to - /// next round. - /// - /// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2). - fn prove_round_and_update_state( - &mut self, - challenge: &Option, - ) -> Result; -} - -/// Trait for sum check protocol verifier side APIs. -pub trait SumCheckVerifier { - type VPAuxInfo; - type ProverMessage; - type Challenge; - type SumCheckSubClaim; - - /// Initialize the verifier's state. - fn verifier_init(index_info: &Self::VPAuxInfo) -> Self; - - /// Run verifier for the current round, given a prover message. - /// - /// Note that `verify_round_and_update_state` only samples and stores - /// challenges; and update the verifier's state accordingly. The actual - /// verifications are deferred (in batch) to `check_and_generate_subclaim` - /// at the last step. - fn verify_round_and_update_state( - &mut self, - prover_msg: &Self::ProverMessage, - transcript: &mut impl Transcript, - ) -> Result; - - /// This function verifies the deferred checks in the interactive version of - /// the protocol; and generate the subclaim. Returns an error if the - /// proof failed to verify. - /// - /// If the asserted sum is correct, then the multilinear polynomial - /// evaluated at `subclaim.point` will be `subclaim.expected_evaluation`. - /// Otherwise, it is highly unlikely that those two will be equal. - /// Larger field size guarantees smaller soundness error. - fn check_and_generate_subclaim( - &self, - asserted_sum: &F, - ) -> Result; -} - -/// A SumCheckSubClaim is a claim generated by the verifier at the end of -/// verification when it is convinced. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct SumCheckSubClaim { - /// the multi-dimensional point that this multilinear extension is evaluated - /// to - pub point: Vec, - /// the expected evaluation - pub expected_evaluation: F, -} - -#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] -pub struct IOPSumCheck> { - #[doc(hidden)] - phantom: PhantomData, - #[doc(hidden)] - phantom2: PhantomData, -} - -impl> SumCheck for IOPSumCheck { - type SumCheckProof = IOPProof; - type VirtualPolynomial = VirtualPolynomial; - type VPAuxInfo = VPAuxInfo; - type MultilinearExtension = Arc>; - type SumCheckSubClaim = SumCheckSubClaim; - - fn extract_sum(proof: &Self::SumCheckProof) -> F { - let start = start_timer!(|| "extract sum"); - let poly = DensePolynomial::from_coefficients_vec(proof.proofs[0].coeffs.clone()); - let res = poly.evaluate(&F::ONE) + poly.evaluate(&F::ZERO); - end_timer!(start); - res - } - - fn prove( - poly: &VirtualPolynomial, - transcript: &mut impl Transcript, - ) -> Result, Error> { - transcript.absorb(&F::from(poly.aux_info.num_variables as u64)); - transcript.absorb(&F::from(poly.aux_info.max_degree as u64)); - let mut prover_state: IOPProverState = IOPProverState::prover_init(poly)?; - let mut challenge: Option = None; - let mut prover_msgs: Vec> = - Vec::with_capacity(poly.aux_info.num_variables); - for _ in 0..poly.aux_info.num_variables { - let prover_msg: IOPProverMessage = - IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?; - transcript.absorb(&prover_msg.coeffs); - prover_msgs.push(prover_msg); - challenge = Some(transcript.get_challenge()); - } - if let Some(p) = challenge { - prover_state.challenges.push(p) - }; - Ok(IOPProof { - point: prover_state.challenges, - proofs: prover_msgs, - }) - } - - fn verify( - claimed_sum: F, - proof: &IOPProof, - aux_info: &VPAuxInfo, - transcript: &mut impl Transcript, - ) -> Result, Error> { - transcript.absorb(&F::from(aux_info.num_variables as u64)); - transcript.absorb(&F::from(aux_info.max_degree as u64)); - let mut verifier_state = IOPVerifierState::verifier_init(aux_info); - for i in 0..aux_info.num_variables { - let prover_msg = proof.proofs.get(i).expect("proof is incomplete"); - transcript.absorb(&prover_msg.coeffs); - IOPVerifierState::verify_round_and_update_state( - &mut verifier_state, - prover_msg, - transcript, - )?; - } - - IOPVerifierState::check_and_generate_subclaim(&verifier_state, &claimed_sum) - } -} - -#[cfg(test)] -pub mod tests { - use std::sync::Arc; - - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; - use ark_crypto_primitives::sponge::CryptographicSponge; - use ark_ff::Field; - use ark_pallas::Fr; - use ark_poly::DenseMultilinearExtension; - use ark_poly::MultilinearExtension; - use ark_std::{test_rng, Zero}; - - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::utils::sum_check::SumCheck; - use crate::utils::virtual_polynomial::VirtualPolynomial; - use crate::Error; - - use super::IOPSumCheck; - - #[test] - pub fn sumcheck_poseidon() -> Result<(), Error> { - let n_vars = 5; - - let mut rng = test_rng(); - let poly_mle = DenseMultilinearExtension::rand(n_vars, &mut rng); - let virtual_poly = VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), Fr::ONE); - - let _ = sumcheck_poseidon_opt(virtual_poly)?; - - // test with zero poly - let poly_mle = DenseMultilinearExtension::from_evaluations_vec( - n_vars, - vec![Fr::zero(); 2u32.pow(n_vars as u32) as usize], - ); - let virtual_poly = VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), Fr::ONE); - let _ = sumcheck_poseidon_opt(virtual_poly)?; - Ok(()) - } - - fn sumcheck_poseidon_opt(virtual_poly: VirtualPolynomial) -> Result<(), Error> { - let poseidon_config = poseidon_canonical_config::(); - - // sum-check prove - let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let sum_check = - IOPSumCheck::>::prove(&virtual_poly, &mut transcript_p)?; - - // sum-check verify - let claimed_sum = IOPSumCheck::>::extract_sum(&sum_check); - let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let res_verify = IOPSumCheck::>::verify( - claimed_sum, - &sum_check, - &virtual_poly.aux_info, - &mut transcript_v, - ); - - assert!(res_verify.is_ok()); - Ok(()) - } -} diff --git a/folding-schemes/src/utils/espresso/sum_check/prover.rs b/folding-schemes/src/utils/espresso/sum_check/prover.rs deleted file mode 100644 index d9824b37a..000000000 --- a/folding-schemes/src/utils/espresso/sum_check/prover.rs +++ /dev/null @@ -1,226 +0,0 @@ -// code forked from: -// https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check -// -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . - -//! Prover subroutines for a SumCheck protocol. - -use super::SumCheckProver; -use crate::{ - utils::{ - lagrange_poly::compute_lagrange_interpolated_poly, multilinear_polynomial::fix_variables, - virtual_polynomial::VirtualPolynomial, - }, - Error, -}; -use ark_ff::{batch_inversion, PrimeField}; -use ark_poly::DenseMultilinearExtension; -use ark_std::{cfg_into_iter, end_timer, start_timer}; -use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator}; -use std::sync::Arc; - -use super::structs::{IOPProverMessage, IOPProverState}; - -// #[cfg(feature = "parallel")] -use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; - -impl SumCheckProver for IOPProverState { - type VirtualPolynomial = VirtualPolynomial; - type ProverMessage = IOPProverMessage; - - /// Initialize the prover state to argue for the sum of the input polynomial - /// over {0,1}^`num_vars`. - fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result { - let start = start_timer!(|| "sum check prover init"); - if polynomial.aux_info.num_variables == 0 { - return Err(Error::InvalidPolyIOPParameters( - "Attempt to prove a constant.".to_string(), - )); - } - end_timer!(start); - - Ok(Self { - challenges: Vec::with_capacity(polynomial.aux_info.num_variables), - round: 0, - poly: polynomial.clone(), - extrapolation_aux: (1..polynomial.aux_info.max_degree) - .map(|degree| { - let points = (0..1 + degree as u64).map(F::from).collect::>(); - let weights = barycentric_weights(&points); - (points, weights) - }) - .collect(), - }) - } - - /// Receive message from verifier, generate prover message, and proceed to - /// next round. - /// - /// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2). - fn prove_round_and_update_state( - &mut self, - challenge: &Option, - ) -> Result { - // let start = - // start_timer!(|| format!("sum check prove {}-th round and update state", - // self.round)); - - if self.round >= self.poly.aux_info.num_variables { - return Err(Error::InvalidPolyIOPProver( - "Prover is not active".to_string(), - )); - } - - // let fix_argument = start_timer!(|| "fix argument"); - - // Step 1: - // fix argument and evaluate f(x) over x_m = r; where r is the challenge - // for the current round, and m is the round number, indexed from 1 - // - // i.e.: - // at round m <= n, for each mle g(x_1, ... x_n) within the flattened_mle - // which has already been evaluated to - // - // g(r_1, ..., r_{m-1}, x_m ... x_n) - // - // eval g over r_m, and mutate g to g(r_1, ... r_m,, x_{m+1}... x_n) - let mut flattened_ml_extensions: Vec> = self - .poly - .flattened_ml_extensions - .par_iter() - .map(|x| x.as_ref().clone()) - .collect(); - - if let Some(chal) = challenge { - if self.round == 0 { - return Err(Error::InvalidPolyIOPProver( - "first round should be prover first.".to_string(), - )); - } - self.challenges.push(*chal); - - let r = self.challenges[self.round - 1]; - // #[cfg(feature = "parallel")] - flattened_ml_extensions - .par_iter_mut() - .for_each(|mle| *mle = fix_variables(mle, &[r])); - // #[cfg(not(feature = "parallel"))] - // flattened_ml_extensions - // .iter_mut() - // .for_each(|mle| *mle = fix_variables(mle, &[r])); - } else if self.round > 0 { - return Err(Error::InvalidPolyIOPProver( - "verifier message is empty".to_string(), - )); - } - // end_timer!(fix_argument); - - self.round += 1; - - let products_list = self.poly.products.clone(); - let mut products_sum = vec![F::ZERO; self.poly.aux_info.max_degree + 1]; - - // Step 2: generate sum for the partial evaluated polynomial: - // f(r_1, ... r_m,, x_{m+1}... x_n) - - products_list.iter().for_each(|(coefficient, products)| { - let mut sum = cfg_into_iter!(0..1 << (self.poly.aux_info.num_variables - self.round)) - .fold( - || { - ( - vec![(F::ZERO, F::ZERO); products.len()], - vec![F::ZERO; products.len() + 1], - ) - }, - |(mut buf, mut acc), b| { - buf.iter_mut() - .zip(products.iter()) - .for_each(|((eval, step), f)| { - let table = &flattened_ml_extensions[*f]; - *eval = table[b << 1]; - *step = table[(b << 1) + 1] - table[b << 1]; - }); - acc[0] += buf.iter().map(|(eval, _)| eval).product::(); - acc[1..].iter_mut().for_each(|acc| { - buf.iter_mut().for_each(|(eval, step)| *eval += step as &_); - *acc += buf.iter().map(|(eval, _)| eval).product::(); - }); - (buf, acc) - }, - ) - .map(|(_, partial)| partial) - .reduce( - || vec![F::ZERO; products.len() + 1], - |mut sum, partial| { - sum.iter_mut() - .zip(partial.iter()) - .for_each(|(sum, partial)| *sum += partial); - sum - }, - ); - sum.iter_mut().for_each(|sum| *sum *= coefficient); - let extraploation = cfg_into_iter!(0..self.poly.aux_info.max_degree - products.len()) - .map(|i| { - let (points, weights) = &self.extrapolation_aux[products.len() - 1]; - let at = F::from((products.len() + 1 + i) as u64); - extrapolate(points, weights, &sum, &at) - }) - .collect::>(); - products_sum - .iter_mut() - .zip(sum.iter().chain(extraploation.iter())) - .for_each(|(products_sum, sum)| *products_sum += sum); - }); - - // update prover's state to the partial evaluated polynomial - self.poly.flattened_ml_extensions = flattened_ml_extensions - .par_iter() - .map(|x| Arc::new(x.clone())) - .collect(); - - let prover_poly = compute_lagrange_interpolated_poly::(&products_sum); - Ok(IOPProverMessage { - coeffs: prover_poly.coeffs, - }) - } -} - -#[allow(clippy::filter_map_bool_then)] -fn barycentric_weights(points: &[F]) -> Vec { - let mut weights = points - .iter() - .enumerate() - .map(|(j, point_j)| { - points - .iter() - .enumerate() - .filter_map(|(i, point_i)| (i != j).then(|| *point_j - point_i)) - .reduce(|acc, value| acc * value) - .unwrap_or_else(F::one) - }) - .collect::>(); - batch_inversion(&mut weights); - weights -} - -fn extrapolate(points: &[F], weights: &[F], evals: &[F], at: &F) -> F { - let (coeffs, sum_inv) = { - let mut coeffs = points.iter().map(|point| *at - point).collect::>(); - batch_inversion(&mut coeffs); - coeffs.iter_mut().zip(weights).for_each(|(coeff, weight)| { - *coeff *= weight; - }); - let sum_inv = coeffs.iter().sum::().inverse().unwrap_or_default(); - (coeffs, sum_inv) - }; - coeffs - .iter() - .zip(evals) - .map(|(coeff, eval)| *coeff * eval) - .sum::() - * sum_inv -} diff --git a/folding-schemes/src/utils/espresso/sum_check/structs.rs b/folding-schemes/src/utils/espresso/sum_check/structs.rs deleted file mode 100644 index de487d93c..000000000 --- a/folding-schemes/src/utils/espresso/sum_check/structs.rs +++ /dev/null @@ -1,59 +0,0 @@ -// code forked from: -// https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check -// -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . - -//! This module defines structs that are shared by all sub protocols. - -use crate::utils::virtual_polynomial::VirtualPolynomial; -use ark_ff::PrimeField; -use ark_serialize::CanonicalSerialize; - -/// An IOP proof is a collections of -/// - messages from prover to verifier at each round through the interactive -/// protocol. -/// - a point that is generated by the transcript for evaluation -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct IOPProof { - pub point: Vec, - pub proofs: Vec>, -} - -/// A message from the prover to the verifier at a given round -/// is a list of coeffs. -#[derive(Clone, Debug, Default, PartialEq, Eq, CanonicalSerialize)] -pub struct IOPProverMessage { - pub(crate) coeffs: Vec, -} - -/// Prover State of a PolyIOP. -#[derive(Debug)] -pub struct IOPProverState { - /// sampled randomness given by the verifier - pub challenges: Vec, - /// the current round number - pub(crate) round: usize, - /// pointer to the virtual polynomial - pub(crate) poly: VirtualPolynomial, - /// points with precomputed barycentric weights for extrapolating smaller - /// degree uni-polys to `max_degree + 1` evaluations. - #[allow(clippy::type_complexity)] - pub(crate) extrapolation_aux: Vec<(Vec, Vec)>, -} - -/// Verifier State of a PolyIOP, generic over a curve group -#[derive(Debug)] -pub struct IOPVerifierState { - pub(crate) round: usize, - pub(crate) num_vars: usize, - pub(crate) finished: bool, - /// a list storing the univariate polynomial in evaluation form sent by the - /// prover at each round - pub(crate) polynomials_received: Vec>, - /// a list storing the randomness sampled by the verifier at each round - pub(crate) challenges: Vec, -} diff --git a/folding-schemes/src/utils/espresso/sum_check/verifier.rs b/folding-schemes/src/utils/espresso/sum_check/verifier.rs deleted file mode 100644 index 7fb6e9813..000000000 --- a/folding-schemes/src/utils/espresso/sum_check/verifier.rs +++ /dev/null @@ -1,302 +0,0 @@ -// code forked from: -// https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check -// -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . - -//! Verifier subroutines for a SumCheck protocol. - -use super::{ - structs::{IOPProverMessage, IOPVerifierState}, - SumCheckSubClaim, SumCheckVerifier, -}; -use crate::{transcript::Transcript, utils::virtual_polynomial::VPAuxInfo, Error}; -use ark_crypto_primitives::sponge::Absorb; -use ark_ff::PrimeField; -use ark_poly::Polynomial; -use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; -use ark_std::{end_timer, start_timer}; - -#[cfg(feature = "parallel")] -use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; - -impl SumCheckVerifier for IOPVerifierState { - type VPAuxInfo = VPAuxInfo; - type ProverMessage = IOPProverMessage; - type Challenge = F; - type SumCheckSubClaim = SumCheckSubClaim; - - /// Initialize the verifier's state. - fn verifier_init(index_info: &Self::VPAuxInfo) -> Self { - let start = start_timer!(|| "sum check verifier init"); - let res = Self { - round: 1, - num_vars: index_info.num_variables, - finished: false, - polynomials_received: Vec::with_capacity(index_info.num_variables), - challenges: Vec::with_capacity(index_info.num_variables), - }; - end_timer!(start); - res - } - - fn verify_round_and_update_state( - &mut self, - prover_msg: & as SumCheckVerifier>::ProverMessage, - transcript: &mut impl Transcript, - ) -> Result< as SumCheckVerifier>::Challenge, Error> { - let start = - start_timer!(|| format!("sum check verify {}-th round and update state", self.round)); - - if self.finished { - return Err(Error::InvalidPolyIOPVerifier( - "Incorrect verifier state: Verifier is already finished.".to_string(), - )); - } - - // In an interactive protocol, the verifier should - // - // 1. check if the received 'P(0) + P(1) = expected`. - // 2. set `expected` to P(r)` - // - // When we turn the protocol to a non-interactive one, it is sufficient to defer - // such checks to `check_and_generate_subclaim` after the last round. - let challenge = transcript.get_challenge(); - self.challenges.push(challenge); - self.polynomials_received.push(prover_msg.coeffs.to_vec()); - - if self.round == self.num_vars { - // accept and close - self.finished = true; - } else { - // proceed to the next round - self.round += 1; - } - - end_timer!(start); - Ok(challenge) - } - - fn check_and_generate_subclaim( - &self, - asserted_sum: &F, - ) -> Result { - let start = start_timer!(|| "sum check check and generate subclaim"); - if !self.finished { - return Err(Error::InvalidPolyIOPVerifier( - "Incorrect verifier state: Verifier has not finished.".to_string(), - )); - } - - if self.polynomials_received.len() != self.num_vars { - return Err(Error::InvalidPolyIOPVerifier( - "insufficient rounds".to_string(), - )); - } - - // the deferred check during the interactive phase: - // 2. set `expected` to P(r)` - #[cfg(feature = "parallel")] - let mut expected_vec = self - .polynomials_received - .clone() - .into_par_iter() - .zip(self.challenges.clone().into_par_iter()) - .map(|(coeffs, challenge)| { - // Removed check on number of evaluations here since verifier receives polynomial in coeffs form - let prover_poly = DensePolynomial::from_coefficients_slice(&coeffs); - prover_poly.evaluate(&challenge) - }) - .collect::>(); - - #[cfg(not(feature = "parallel"))] - let mut expected_vec = self - .polynomials_received - .clone() - .into_iter() - .zip(self.challenges.clone().into_iter()) - .map(|(coeffs, challenge)| { - // Removed check on number of evaluations here since verifier receives polynomial in coeffs form - let prover_poly = DensePolynomial::from_coefficients_slice(&coeffs); - prover_poly.evaluate(&challenge) - }) - .collect::>(); - - // insert the asserted_sum to the first position of the expected vector - expected_vec.insert(0, *asserted_sum); - - for (coeffs, &expected) in self - .polynomials_received - .iter() - .zip(expected_vec.iter()) - .take(self.num_vars) - { - let poly = DensePolynomial::from_coefficients_slice(coeffs); - let eval_at_one: F = poly.iter().sum(); - let eval_at_zero: F = if poly.coeffs.is_empty() { - F::zero() - } else { - poly.coeffs[0] - }; - let eval = eval_at_one + eval_at_zero; - - // the deferred check during the interactive phase: - // 1. check if the received 'P(0) + P(1) = expected`. - if eval != expected { - return Err(Error::InvalidPolyIOPProof( - "Prover message is not consistent with the claim.".to_string(), - )); - } - } - end_timer!(start); - Ok(SumCheckSubClaim { - point: self.challenges.clone(), - // the last expected value (not checked within this function) will be included in the - // subclaim - expected_evaluation: expected_vec[self.num_vars], - }) - } -} - -/// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this -/// polynomial at `eval_at`: -/// -/// \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) ) -/// -/// This implementation is linear in number of inputs in terms of field -/// operations. It also has a quadratic term in primitive operations which is -/// negligible compared to field operations. -/// TODO: The quadratic term can be removed by precomputing the lagrange -/// coefficients. -pub fn interpolate_uni_poly(p_i: &[F], eval_at: F) -> F { - let start = start_timer!(|| "sum check interpolate uni poly opt"); - - let len = p_i.len(); - let mut evals = vec![]; - let mut prod = eval_at; - evals.push(eval_at); - - // `prod = \prod_{j} (eval_at - j)` - for e in 1..len { - let tmp = eval_at - F::from(e as u64); - evals.push(tmp); - prod *= tmp; - } - let mut res = F::zero(); - // we want to compute \prod (j!=i) (i-j) for a given i - // - // we start from the last step, which is - // denom[len-1] = (len-1) * (len-2) *... * 2 * 1 - // the step before that is - // denom[len-2] = (len-2) * (len-3) * ... * 2 * 1 * -1 - // and the step before that is - // denom[len-3] = (len-3) * (len-4) * ... * 2 * 1 * -1 * -2 - // - // i.e., for any i, the one before this will be derived from - // denom[i-1] = denom[i] * (len-i) / i - // - // that is, we only need to store - // - the last denom for i = len-1, and - // - the ratio between current step and fhe last step, which is the product of - // (len-i) / i from all previous steps and we store this product as a fraction - // number to reduce field divisions. - - // We know - // - 2^61 < factorial(20) < 2^62 - // - 2^122 < factorial(33) < 2^123 - // so we will be able to compute the ratio - // - for len <= 20 with i64 - // - for len <= 33 with i128 - // - for len > 33 with BigInt - if p_i.len() <= 20 { - let last_denominator = F::from(u64_factorial(len - 1)); - let mut ratio_numerator = 1i64; - let mut ratio_denominator = 1u64; - - for i in (0..len).rev() { - let ratio_numerator_f = if ratio_numerator < 0 { - -F::from((-ratio_numerator) as u64) - } else { - F::from(ratio_numerator as u64) - }; - - res += p_i[i] * prod * F::from(ratio_denominator) - / (last_denominator * ratio_numerator_f * evals[i]); - - // compute denom for the next step is current_denom * (len-i)/i - if i != 0 { - ratio_numerator *= -(len as i64 - i as i64); - ratio_denominator *= i as u64; - } - } - } else if p_i.len() <= 33 { - let last_denominator = F::from(u128_factorial(len - 1)); - let mut ratio_numerator = 1i128; - let mut ratio_denominator = 1u128; - - for i in (0..len).rev() { - let ratio_numerator_f = if ratio_numerator < 0 { - -F::from((-ratio_numerator) as u128) - } else { - F::from(ratio_numerator as u128) - }; - - res += p_i[i] * prod * F::from(ratio_denominator) - / (last_denominator * ratio_numerator_f * evals[i]); - - // compute denom for the next step is current_denom * (len-i)/i - if i != 0 { - ratio_numerator *= -(len as i128 - i as i128); - ratio_denominator *= i as u128; - } - } - } else { - let mut denom_up = field_factorial::(len - 1); - let mut denom_down = F::one(); - - for i in (0..len).rev() { - res += p_i[i] * prod * denom_down / (denom_up * evals[i]); - - // compute denom for the next step is current_denom * (len-i)/i - if i != 0 { - denom_up *= -F::from((len - i) as u64); - denom_down *= F::from(i as u64); - } - } - } - end_timer!(start); - res -} - -/// compute the factorial(a) = 1 * 2 * ... * a -#[inline] -fn field_factorial(a: usize) -> F { - let mut res = F::one(); - for i in 2..=a { - res *= F::from(i as u64); - } - res -} - -/// compute the factorial(a) = 1 * 2 * ... * a -#[inline] -fn u128_factorial(a: usize) -> u128 { - let mut res = 1u128; - for i in 2..=a { - res *= i as u128; - } - res -} - -/// compute the factorial(a) = 1 * 2 * ... * a -#[inline] -fn u64_factorial(a: usize) -> u64 { - let mut res = 1u64; - for i in 2..=a { - res *= i as u64; - } - res -} diff --git a/folding-schemes/src/utils/espresso/virtual_polynomial.rs b/folding-schemes/src/utils/espresso/virtual_polynomial.rs deleted file mode 100644 index 0d16ae8b0..000000000 --- a/folding-schemes/src/utils/espresso/virtual_polynomial.rs +++ /dev/null @@ -1,546 +0,0 @@ -// code forked from -// https://github.com/privacy-scaling-explorations/multifolding-poc/blob/main/src/espresso/virtual_polynomial.rs -// -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . - -//! This module defines our main mathematical object `VirtualPolynomial`; and -//! various functions associated with it. - -use ark_ff::PrimeField; -use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; -use ark_serialize::CanonicalSerialize; -use ark_std::{end_timer, start_timer}; -use rayon::prelude::*; -use std::{cmp::max, collections::HashMap, marker::PhantomData, ops::Add, sync::Arc}; -use thiserror::Error; - -//-- aritherrors -/// A `enum` specifying the possible failure modes of the arithmetic. -#[derive(Error, Debug)] -pub enum ArithErrors { - #[error("Invalid parameters: {0}")] - InvalidParameters(String), - #[error("Should not arrive to this point")] - ShouldNotArrive, - #[error("An error during (de)serialization: {0}")] - SerializationErrors(ark_serialize::SerializationError), -} - -impl From for ArithErrors { - fn from(e: ark_serialize::SerializationError) -> Self { - Self::SerializationErrors(e) - } -} -//-- aritherrors - -#[rustfmt::skip] -/// A virtual polynomial is a sum of products of multilinear polynomials; -/// where the multilinear polynomials are stored via their multilinear -/// extensions: `(coefficient, DenseMultilinearExtension)` -/// -/// * Number of products n = `polynomial.products.len()`, -/// * Number of multiplicands of ith product m_i = -/// `polynomial.products[i].1.len()`, -/// * Coefficient of ith product c_i = `polynomial.products[i].0` -/// -/// The resulting polynomial is -/// -/// $$ \sum_{i=0}^{n} c_i \cdot \prod_{j=0}^{m_i} P_{ij} $$ -/// -/// Example: -/// f = c0 * f0 * f1 * f2 + c1 * f3 * f4 -/// where f0 ... f4 are multilinear polynomials -/// -/// - `flattened_ml_extensions` stores the multilinear extension representation -/// of f0, f1, f2, f3 and f4 -/// - `products` is `[(c0, [0, 1, 2]), (c1, [3, 4])]` -/// - raw_pointers_lookup_table maps fi to i -/// -#[derive(Clone, Debug, Default, PartialEq)] -pub struct VirtualPolynomial { - /// Aux information about the multilinear polynomial - pub aux_info: VPAuxInfo, - /// list of reference to products (as usize) of multilinear extension - pub products: Vec<(F, Vec)>, - /// Stores multilinear extensions in which product multiplicand can refer - /// to. - pub flattened_ml_extensions: Vec>>, - /// Pointers to the above poly extensions - raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension, usize>, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, CanonicalSerialize)] -/// Auxiliary information about the multilinear polynomial -pub struct VPAuxInfo { - /// max number of multiplicands in each product - pub max_degree: usize, - /// number of variables of the polynomial - pub num_variables: usize, - /// Associated field - #[doc(hidden)] - pub phantom: PhantomData, -} - -impl Add for &VirtualPolynomial { - type Output = VirtualPolynomial; - fn add(self, other: &VirtualPolynomial) -> Self::Output { - let start = start_timer!(|| "virtual poly add"); - let mut res = self.clone(); - for products in other.products.iter() { - let cur: Vec>> = products - .1 - .iter() - .map(|&x| other.flattened_ml_extensions[x].clone()) - .collect(); - - res.add_mle_list(cur, products.0) - .expect("add product failed"); - } - end_timer!(start); - res - } -} - -// TODO: convert this into a trait -impl VirtualPolynomial { - /// Creates an empty virtual polynomial with `num_variables`. - pub fn new(num_variables: usize) -> Self { - VirtualPolynomial { - aux_info: VPAuxInfo { - max_degree: 0, - num_variables, - phantom: PhantomData, - }, - products: Vec::new(), - flattened_ml_extensions: Vec::new(), - raw_pointers_lookup_table: HashMap::new(), - } - } - - /// Creates a new virtual polynomial from a MLE and its coefficient. - pub fn new_from_mle(mle: &Arc>, coefficient: F) -> Self { - let mle_ptr: *const DenseMultilinearExtension = Arc::as_ptr(mle); - let mut hm = HashMap::new(); - hm.insert(mle_ptr, 0); - - VirtualPolynomial { - aux_info: VPAuxInfo { - // The max degree is the max degree of any individual variable - max_degree: 1, - num_variables: mle.num_vars, - phantom: PhantomData, - }, - // here `0` points to the first polynomial of `flattened_ml_extensions` - products: vec![(coefficient, vec![0])], - flattened_ml_extensions: vec![mle.clone()], - raw_pointers_lookup_table: hm, - } - } - - /// Add a product of list of multilinear extensions to self - /// Returns an error if the list is empty, or the MLE has a different - /// `num_vars` from self. - /// - /// The MLEs will be multiplied together, and then multiplied by the scalar - /// `coefficient`. - pub fn add_mle_list( - &mut self, - mle_list: impl IntoIterator>>, - coefficient: F, - ) -> Result<(), ArithErrors> { - let mle_list: Vec>> = mle_list.into_iter().collect(); - let mut indexed_product = Vec::with_capacity(mle_list.len()); - - if mle_list.is_empty() { - return Err(ArithErrors::InvalidParameters( - "input mle_list is empty".to_string(), - )); - } - - self.aux_info.max_degree = max(self.aux_info.max_degree, mle_list.len()); - - for mle in mle_list { - if mle.num_vars != self.aux_info.num_variables { - return Err(ArithErrors::InvalidParameters(format!( - "product has a multiplicand with wrong number of variables {} vs {}", - mle.num_vars, self.aux_info.num_variables - ))); - } - - let mle_ptr: *const DenseMultilinearExtension = Arc::as_ptr(&mle); - if let Some(index) = self.raw_pointers_lookup_table.get(&mle_ptr) { - indexed_product.push(*index) - } else { - let curr_index = self.flattened_ml_extensions.len(); - self.flattened_ml_extensions.push(mle.clone()); - self.raw_pointers_lookup_table.insert(mle_ptr, curr_index); - indexed_product.push(curr_index); - } - } - self.products.push((coefficient, indexed_product)); - Ok(()) - } - - /// Multiple the current VirtualPolynomial by an MLE: - /// - add the MLE to the MLE list; - /// - multiple each product by MLE and its coefficient. - /// - /// Returns an error if the MLE has a different `num_vars` from self. - pub fn mul_by_mle( - &mut self, - mle: Arc>, - coefficient: F, - ) -> Result<(), ArithErrors> { - let start = start_timer!(|| "mul by mle"); - - if mle.num_vars != self.aux_info.num_variables { - return Err(ArithErrors::InvalidParameters(format!( - "product has a multiplicand with wrong number of variables {} vs {}", - mle.num_vars, self.aux_info.num_variables - ))); - } - - let mle_ptr: *const DenseMultilinearExtension = Arc::as_ptr(&mle); - - // check if this mle already exists in the virtual polynomial - let mle_index = match self.raw_pointers_lookup_table.get(&mle_ptr) { - Some(&p) => p, - None => { - self.raw_pointers_lookup_table - .insert(mle_ptr, self.flattened_ml_extensions.len()); - self.flattened_ml_extensions.push(mle); - self.flattened_ml_extensions.len() - 1 - } - }; - - for (prod_coef, indices) in self.products.iter_mut() { - // - add the MLE to the MLE list; - // - multiple each product by MLE and its coefficient. - indices.push(mle_index); - *prod_coef *= coefficient; - } - - // increase the max degree by one as the MLE has degree 1. - self.aux_info.max_degree += 1; - end_timer!(start); - Ok(()) - } - - /// Given virtual polynomial `p(x)` and scalar `s`, compute `s*p(x)` - pub fn scalar_mul(&mut self, s: &F) { - for (prod_coef, _) in self.products.iter_mut() { - *prod_coef *= s; - } - } - - /// Evaluate the virtual polynomial at point `point`. - /// Returns an error is point.len() does not match `num_variables`. - pub fn evaluate(&self, point: &[F]) -> Result { - let start = start_timer!(|| "evaluation"); - - if self.aux_info.num_variables != point.len() { - return Err(ArithErrors::InvalidParameters(format!( - "wrong number of variables {} vs {}", - self.aux_info.num_variables, - point.len() - ))); - } - - // Evaluate all the MLEs at `point` - let evals: Vec = self - .flattened_ml_extensions - .iter() - .map(|x| x.fix_variables(point)[0]) - .collect(); - - let res = self - .products - .iter() - .map(|(c, p)| *c * p.iter().map(|&i| evals[i]).product::()) - .sum(); - - end_timer!(start); - Ok(res) - } - - // Input poly f(x) and a random vector r, output - // \hat f(x) = \sum_{x_i \in eval_x} f(x_i) eq(x, r) - // where - // eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) - // - // This function is used in ZeroCheck. - pub fn build_f_hat(&self, r: &[F]) -> Result { - let start = start_timer!(|| "zero check build hat f"); - - if self.aux_info.num_variables != r.len() { - return Err(ArithErrors::InvalidParameters(format!( - "r.len() is different from number of variables: {} vs {}", - r.len(), - self.aux_info.num_variables - ))); - } - - let eq_x_r = build_eq_x_r(r)?; - let mut res = self.clone(); - res.mul_by_mle(eq_x_r, F::one())?; - - end_timer!(start); - Ok(res) - } -} - -/// Evaluate eq polynomial. -pub fn eq_eval(x: &[F], y: &[F]) -> Result { - if x.len() != y.len() { - return Err(ArithErrors::InvalidParameters( - "x and y have different length".to_string(), - )); - } - let start = start_timer!(|| "eq_eval"); - let mut res = F::one(); - for (&xi, &yi) in x.iter().zip(y.iter()) { - let xi_yi = xi * yi; - res *= xi_yi + xi_yi - xi - yi + F::one(); - } - end_timer!(start); - Ok(res) -} - -/// This function build the eq(x, r) polynomial for any given r. -/// -/// Evaluate -/// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) -/// over r, which is -/// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i)) -pub fn build_eq_x_r( - r: &[F], -) -> Result>, ArithErrors> { - let evals = build_eq_x_r_vec(r)?; - let mle = DenseMultilinearExtension::from_evaluations_vec(r.len(), evals); - - Ok(Arc::new(mle)) -} - -/// This function build the eq(x, r) polynomial for any given r, and output the -/// evaluation of eq(x, r) in its vector form. -/// -/// Evaluate -/// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) -/// over r, which is -/// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i)) -pub fn build_eq_x_r_vec(r: &[F]) -> Result, ArithErrors> { - // we build eq(x,r) from its evaluations - // we want to evaluate eq(x,r) over x \in {0, 1}^num_vars - // for example, with num_vars = 4, x is a binary vector of 4, then - // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3) - // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3) - // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3) - // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3) - // .... - // 1 1 1 1 -> r0 * r1 * r2 * r3 - // we will need 2^num_var evaluations - - let mut eval = Vec::new(); - build_eq_x_r_helper(r, &mut eval)?; - - Ok(eval) -} - -/// A helper function to build eq(x, r) recursively. -/// This function takes `r.len()` steps, and for each step it requires a maximum -/// `r.len()-1` multiplications. -fn build_eq_x_r_helper(r: &[F], buf: &mut Vec) -> Result<(), ArithErrors> { - if r.is_empty() { - return Err(ArithErrors::InvalidParameters("r length is 0".to_string())); - } else if r.len() == 1 { - // initializing the buffer with [1-r_0, r_0] - buf.push(F::one() - r[0]); - buf.push(r[0]); - } else { - build_eq_x_r_helper(&r[1..], buf)?; - - // suppose at the previous step we received [b_1, ..., b_k] - // for the current step we will need - // if x_0 = 0: (1-r0) * [b_1, ..., b_k] - // if x_0 = 1: r0 * [b_1, ..., b_k] - // let mut res = vec![]; - // for &b_i in buf.iter() { - // let tmp = r[0] * b_i; - // res.push(b_i - tmp); - // res.push(tmp); - // } - // *buf = res; - - let mut res = vec![F::zero(); buf.len() << 1]; - res.par_iter_mut().enumerate().for_each(|(i, val)| { - let bi = buf[i >> 1]; - let tmp = r[0] * bi; - if i & 1 == 0 { - *val = bi - tmp; - } else { - *val = tmp; - } - }); - *buf = res; - } - - Ok(()) -} - -/// Decompose an integer into a binary vector in little endian. -pub fn bit_decompose(input: u64, num_var: usize) -> Vec { - let mut res = Vec::with_capacity(num_var); - let mut i = input; - for _ in 0..num_var { - res.push(i & 1 == 1); - i >>= 1; - } - res -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::utils::multilinear_polynomial::tests::random_mle_list; - use crate::Error; - use ark_ff::UniformRand; - use ark_pallas::Fr; - use ark_std::{ - rand::{Rng, RngCore}, - test_rng, - }; - - impl VirtualPolynomial { - /// Sample a random virtual polynomial, return the polynomial and its sum. - fn rand( - nv: usize, - num_multiplicands_range: (usize, usize), - num_products: usize, - rng: &mut R, - ) -> Result<(Self, F), ArithErrors> { - let start = start_timer!(|| "sample random virtual polynomial"); - - let mut sum = F::zero(); - let mut poly = VirtualPolynomial::new(nv); - for _ in 0..num_products { - let num_multiplicands = - rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1); - let (product, product_sum) = random_mle_list(nv, num_multiplicands, rng); - let coefficient = F::rand(rng); - poly.add_mle_list(product.into_iter(), coefficient)?; - sum += product_sum * coefficient; - } - - end_timer!(start); - Ok((poly, sum)) - } - } - - #[test] - fn test_virtual_polynomial_additions() -> Result<(), ArithErrors> { - let mut rng = test_rng(); - for nv in 2..5 { - for num_products in 2..5 { - let base: Vec = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); - - let (a, _a_sum) = - VirtualPolynomial::::rand(nv, (2, 3), num_products, &mut rng)?; - let (b, _b_sum) = - VirtualPolynomial::::rand(nv, (2, 3), num_products, &mut rng)?; - let c = &a + &b; - - assert_eq!( - a.evaluate(base.as_ref())? + b.evaluate(base.as_ref())?, - c.evaluate(base.as_ref())? - ); - } - } - - Ok(()) - } - - #[test] - fn test_virtual_polynomial_mul_by_mle() -> Result<(), ArithErrors> { - let mut rng = test_rng(); - for nv in 2..5 { - for num_products in 2..5 { - let base: Vec = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); - - let (a, _a_sum) = - VirtualPolynomial::::rand(nv, (2, 3), num_products, &mut rng)?; - let (b, _b_sum) = random_mle_list(nv, 1, &mut rng); - let b_mle = b[0].clone(); - let coeff = Fr::rand(&mut rng); - let b_vp = VirtualPolynomial::new_from_mle(&b_mle, coeff); - - let mut c = a.clone(); - - c.mul_by_mle(b_mle, coeff)?; - - assert_eq!( - a.evaluate(base.as_ref())? * b_vp.evaluate(base.as_ref())?, - c.evaluate(base.as_ref())? - ); - } - } - - Ok(()) - } - - #[test] - fn test_eq_xr() -> Result<(), Error> { - let mut rng = test_rng(); - for nv in 4..10 { - let r: Vec = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); - let eq_x_r = build_eq_x_r(r.as_ref())?; - let eq_x_r2 = build_eq_x_r_for_test(r.as_ref()); - assert_eq!(eq_x_r, eq_x_r2); - } - Ok(()) - } - - /// Naive method to build eq(x, r). - /// Only used for testing purpose. - // Evaluate - // eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) - // over r, which is - // eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i)) - fn build_eq_x_r_for_test(r: &[F]) -> Arc> { - // we build eq(x,r) from its evaluations - // we want to evaluate eq(x,r) over x \in {0, 1}^num_vars - // for example, with num_vars = 4, x is a binary vector of 4, then - // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3) - // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3) - // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3) - // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3) - // .... - // 1 1 1 1 -> r0 * r1 * r2 * r3 - // we will need 2^num_var evaluations - - // First, we build array for {1 - r_i} - let one_minus_r: Vec = r.iter().map(|ri| F::one() - ri).collect(); - - let num_var = r.len(); - let mut eval = vec![]; - - for i in 0..1 << num_var { - let mut current_eval = F::one(); - let bit_sequence = bit_decompose(i, num_var); - - for (&bit, (ri, one_minus_ri)) in - bit_sequence.iter().zip(r.iter().zip(one_minus_r.iter())) - { - current_eval *= if bit { *ri } else { *one_minus_ri }; - } - eval.push(current_eval); - } - - let mle = DenseMultilinearExtension::from_evaluations_vec(num_var, eval); - - Arc::new(mle) - } -} diff --git a/folding-schemes/src/utils/gadgets.rs b/folding-schemes/src/utils/gadgets.rs deleted file mode 100644 index 1433b71cc..000000000 --- a/folding-schemes/src/utils/gadgets.rs +++ /dev/null @@ -1,149 +0,0 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - GR1CSVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use core::borrow::Borrow; - -use crate::utils::vec::SparseMatrix; - -/// `EquivalenceGadget` enforces that two in-circuit variables are equivalent, -/// where the equivalence relation is parameterized by `M`: -/// - For `FpVar`, it is simply an equality relation, and `M` is unused. -/// - For `NonNativeUintVar`, we consider equivalence as a congruence relation, -/// in terms of modular arithmetic, so `M` specifies the modulus. -pub trait EquivalenceGadget { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError>; -} -impl EquivalenceGadget for FpVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { - self.enforce_equal(other) - } -} -impl> EquivalenceGadget for [T] { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { - self.iter() - .zip(other) - .try_for_each(|(a, b)| a.enforce_equivalent(b)) - } -} - -pub trait MatrixGadget { - fn mul_vector(&self, v: &[FV]) -> Result, SynthesisError>; -} - -pub trait VectorGadget { - fn add(&self, other: &Self) -> Result, SynthesisError>; - - fn mul_scalar(&self, other: &FV) -> Result, SynthesisError>; - - fn hadamard(&self, other: &Self) -> Result, SynthesisError>; -} - -impl VectorGadget> for [FpVar] { - fn add(&self, other: &Self) -> Result>, SynthesisError> { - if self.len() != other.len() { - return Err(SynthesisError::Unsatisfiable); - } - Ok(self.iter().zip(other.iter()).map(|(a, b)| a + b).collect()) - } - - fn mul_scalar(&self, c: &FpVar) -> Result>, SynthesisError> { - Ok(self.iter().map(|a| a * c).collect()) - } - - fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { - if self.len() != other.len() { - return Err(SynthesisError::Unsatisfiable); - } - Ok(self.iter().zip(other.iter()).map(|(a, b)| a * b).collect()) - } -} - -#[derive(Debug, Clone)] -pub struct SparseMatrixVar { - pub n_rows: usize, - pub n_cols: usize, - // same format as the native SparseMatrix (which follows ark_relations::gr1cs::Matrix format - pub coeffs: Vec>, -} - -impl> AllocVar, CF> - for SparseMatrixVar -{ - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let mut coeffs: Vec> = Vec::new(); - for row in val.borrow().coeffs.iter() { - let mut rowVar: Vec<(FV, usize)> = Vec::new(); - for &(value, col_i) in row.iter() { - let coeffVar = FV::new_variable(cs.clone(), || Ok(value), mode)?; - rowVar.push((coeffVar, col_i)); - } - coeffs.push(rowVar); - } - - Ok(Self { - n_rows: val.borrow().n_rows, - n_cols: val.borrow().n_cols, - coeffs, - }) - }) - } -} - -impl MatrixGadget> for SparseMatrixVar> { - fn mul_vector(&self, v: &[FpVar]) -> Result>, SynthesisError> { - Ok(self - .coeffs - .iter() - .map(|row| { - let products = row - .iter() - .map(|(value, col_i)| value * &v[*col_i]) - .collect::>(); - if products.is_constant() { - FpVar::constant(products.value().unwrap_or_default().into_iter().sum()) - } else { - products.iter().sum() - } - }) - .collect()) - } -} - -/// Interprets the given vector v as the evaluations of a dense multilinear extension of n_vars, -/// and evaluates it at the given point. This method mimics the behavior of -/// `utils/mle.rs#dense_vec_to_dense_mle` + `DenseMultilinearExtension::evaluate` but in R1CS -/// constraints, since dense multilinear extensions are not supported in ark_r1cs_std. -pub fn eval_mle( - // n_vars indicates the number of variables in the MLE - n_vars: usize, - // v is the vector of the evaluations of the dense multilinear extension (MLE) - v: Vec>, - // point is the point at which we want to evaluate the MLE - point: Vec>, -) -> FpVar { - // pad to 2^n_vars - let mut poly = v; - poly.resize(1 << n_vars, FpVar::zero()); - - for i in 1..n_vars + 1 { - let r = point[i - 1].clone(); - for b in 0..(1 << (n_vars - 1)) { - let left = poly[b << 1].clone(); - let right = poly[(b << 1) + 1].clone(); - poly[b] = left.clone() + r.clone() * (right - left); - } - } - poly[0].clone() -} diff --git a/folding-schemes/src/utils/hypercube.rs b/folding-schemes/src/utils/hypercube.rs deleted file mode 100644 index 673df1a7d..000000000 --- a/folding-schemes/src/utils/hypercube.rs +++ /dev/null @@ -1,77 +0,0 @@ -/// A boolean hypercube structure to create an ergonomic evaluation domain -use crate::utils::virtual_polynomial::bit_decompose; -use ark_ff::PrimeField; - -use std::marker::PhantomData; - -/// A boolean hypercube that returns its points as an iterator -/// If you iterate on it for 3 variables you will get points in little-endian order: -/// 000 -> 100 -> 010 -> 110 -> 001 -> 101 -> 011 -> 111 -#[derive(Debug, Clone)] -pub struct BooleanHypercube { - _f: PhantomData, - n_vars: usize, - current: u64, - max: u64, -} - -impl BooleanHypercube { - pub fn new(n_vars: usize) -> Self { - BooleanHypercube:: { - _f: PhantomData::, - n_vars, - current: 0, - max: 2_u32.pow(n_vars as u32) as u64, - } - } - - /// returns the entry at given i (which is the little-endian bit representation of i) - pub fn at_i(&self, i: usize) -> Vec { - assert!(i < self.max as usize); - let bits = bit_decompose((i) as u64, self.n_vars); - bits.iter().map(|&x| F::from(x)).collect() - } -} - -impl Iterator for BooleanHypercube { - type Item = Vec; - - fn next(&mut self) -> Option { - let bits = bit_decompose(self.current, self.n_vars); - let result: Vec = bits.iter().map(|&x| F::from(x)).collect(); - self.current += 1; - - if self.current > self.max { - return None; - } - - Some(result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::utils::vec::tests::to_F_dense_matrix; - use ark_pallas::Fr; - - #[test] - fn test_hypercube() { - let expected_results = to_F_dense_matrix(vec![ - vec![0, 0, 0], - vec![1, 0, 0], - vec![0, 1, 0], - vec![1, 1, 0], - vec![0, 0, 1], - vec![1, 0, 1], - vec![0, 1, 1], - vec![1, 1, 1], - ]); - - let bhc = BooleanHypercube::::new(3); - for (i, point) in bhc.clone().enumerate() { - assert_eq!(point, expected_results[i]); - assert_eq!(point, bhc.at_i(i)); - } - } -} diff --git a/folding-schemes/src/utils/lagrange_poly.rs b/folding-schemes/src/utils/lagrange_poly.rs deleted file mode 100644 index c2237108c..000000000 --- a/folding-schemes/src/utils/lagrange_poly.rs +++ /dev/null @@ -1,120 +0,0 @@ -use ark_ff::PrimeField; -use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; - -/// Computes the lagrange interpolated polynomial from the given points `p_i` -pub fn compute_lagrange_interpolated_poly(p_i: &[F]) -> DensePolynomial { - // domain is 0..p_i.len(), to fit `interpolate_uni_poly` from hyperplonk - let domain: Vec = (0..p_i.len()).collect(); - - // compute l(x), common to every basis polynomial - let mut l_x = DensePolynomial::from_coefficients_vec(vec![F::ONE]); - for x_m in domain.clone() { - let prod_m = DensePolynomial::from_coefficients_vec(vec![-F::from(x_m as u64), F::ONE]); - l_x = &l_x * &prod_m; - } - - // compute each w_j - barycentric weights - let mut w_j_vector: Vec = vec![]; - for x_j in domain.clone() { - let mut w_j = F::ONE; - for x_m in domain.clone() { - if x_m != x_j { - let prod = (F::from(x_j as u64) - F::from(x_m as u64)) - .inverse() - .unwrap(); // an inverse always exists since x_j != x_m (!=0) - // hence, we call unwrap() here without checking the Option's content - w_j *= prod; - } - } - w_j_vector.push(w_j); - } - - // compute each polynomial within the sum L(x) - let mut lagrange_poly = DensePolynomial::from_coefficients_vec(vec![F::ZERO]); - for (j, w_j) in w_j_vector.iter().enumerate() { - let x_j = domain[j]; - let y_j = p_i[j]; - // we multiply by l(x) here, otherwise the below division will not work - deg(0)/deg(d) - let poly_numerator = &(&l_x * (*w_j)) * (y_j); - let poly_denominator = - DensePolynomial::from_coefficients_vec(vec![-F::from(x_j as u64), F::ONE]); - let poly = &poly_numerator / &poly_denominator; - lagrange_poly = &lagrange_poly + &poly; - } - - lagrange_poly -} - -#[cfg(test)] -mod tests { - - use crate::utils::espresso::sum_check::verifier::interpolate_uni_poly; - use crate::utils::lagrange_poly::compute_lagrange_interpolated_poly; - use ark_pallas::Fr; - use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; - use ark_std::UniformRand; - - #[test] - fn test_compute_lagrange_interpolated_poly() { - let mut prng = ark_std::test_rng(); - for degree in 1..30 { - let poly = DensePolynomial::::rand(degree, &mut prng); - // range (which is exclusive) is from 0 to degree + 1, since we need degree + 1 evaluations - let evals = (0..(degree + 1)) - .map(|i| poly.evaluate(&Fr::from(i as u64))) - .collect::>(); - let lagrange_poly = compute_lagrange_interpolated_poly(&evals); - for _ in 0..10 { - let query = Fr::rand(&mut prng); - let lagrange_eval = lagrange_poly.evaluate(&query); - let eval = poly.evaluate(&query); - assert_eq!(eval, lagrange_eval); - assert_eq!(lagrange_poly.degree(), poly.degree()); - } - } - } - - #[test] - fn test_interpolation() { - let mut prng = ark_std::test_rng(); - - // test a polynomial with 20 known points, i.e., with degree 19 - let poly = DensePolynomial::::rand(20 - 1, &mut prng); - let evals = (0..20) - .map(|i| poly.evaluate(&Fr::from(i))) - .collect::>(); - let query = Fr::rand(&mut prng); - - assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)); - assert_eq!( - compute_lagrange_interpolated_poly(&evals).evaluate(&query), - interpolate_uni_poly(&evals, query) - ); - - // test a polynomial with 33 known points, i.e., with degree 32 - let poly = DensePolynomial::::rand(33 - 1, &mut prng); - let evals = (0..33) - .map(|i| poly.evaluate(&Fr::from(i))) - .collect::>(); - let query = Fr::rand(&mut prng); - - assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)); - assert_eq!( - compute_lagrange_interpolated_poly(&evals).evaluate(&query), - interpolate_uni_poly(&evals, query) - ); - - // test a polynomial with 64 known points, i.e., with degree 63 - let poly = DensePolynomial::::rand(64 - 1, &mut prng); - let evals = (0..64) - .map(|i| poly.evaluate(&Fr::from(i))) - .collect::>(); - let query = Fr::rand(&mut prng); - - assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)); - assert_eq!( - compute_lagrange_interpolated_poly(&evals).evaluate(&query), - interpolate_uni_poly(&evals, query) - ); - } -} diff --git a/folding-schemes/src/utils/mle.rs b/folding-schemes/src/utils/mle.rs deleted file mode 100644 index e8a638aaf..000000000 --- a/folding-schemes/src/utils/mle.rs +++ /dev/null @@ -1,189 +0,0 @@ -/// Some basic MLE utilities -use ark_ff::PrimeField; -use ark_poly::{DenseMultilinearExtension, SparseMultilinearExtension}; -use ark_std::log2; - -use super::vec::SparseMatrix; - -/// Pad matrix so that its columns and rows are powers of two -pub fn pad_matrix(m: &SparseMatrix) -> SparseMatrix { - let mut r = m.clone(); - r.n_rows = m.n_rows.next_power_of_two(); - r.n_cols = m.n_cols.next_power_of_two(); - r -} - -/// Returns the dense multilinear extension from the given matrix, without modifying the original -/// matrix. -pub fn matrix_to_dense_mle(matrix: SparseMatrix) -> DenseMultilinearExtension { - let n_vars: usize = (log2(matrix.n_rows) + log2(matrix.n_cols)) as usize; // n_vars = s + s' - - // Matrices might need to get padded before turned into an MLE - let padded_matrix = pad_matrix(&matrix); - - // build dense vector representing the sparse padded matrix - let mut v: Vec = vec![F::zero(); padded_matrix.n_rows * padded_matrix.n_cols]; - for (row_i, row) in padded_matrix.coeffs.iter().enumerate() { - for &(value, col_i) in row.iter() { - v[(padded_matrix.n_cols * row_i) + col_i] = value; - } - } - - // convert the dense vector into a mle - vec_to_dense_mle(n_vars, &v) -} - -/// Takes the n_vars and a dense vector and returns its dense MLE. -pub fn vec_to_dense_mle(n_vars: usize, v: &[F]) -> DenseMultilinearExtension { - let mut v_padded = v.to_owned(); - v_padded.resize(1 << n_vars, F::zero()); - DenseMultilinearExtension::::from_evaluations_vec(n_vars, v_padded) -} - -/// Returns the sparse multilinear extension from the given matrix, without modifying the original -/// matrix. -pub fn matrix_to_mle(m: SparseMatrix) -> SparseMultilinearExtension { - let n_rows = m.n_rows.next_power_of_two(); - let n_cols = m.n_cols.next_power_of_two(); - let n_vars: usize = (log2(n_rows) + log2(n_cols)) as usize; // n_vars = s + s' - - // build the sparse vec representing the sparse matrix - let mut v: Vec<(usize, F)> = Vec::new(); - for (i, row) in m.coeffs.iter().enumerate() { - for (val, j) in row.iter() { - v.push((i * n_cols + j, *val)); - } - } - - // convert the dense vector into a mle - vec_to_mle(n_vars, &v) -} - -/// Takes the n_vars and a sparse vector and returns its sparse MLE. -pub fn vec_to_mle(n_vars: usize, v: &[(usize, F)]) -> SparseMultilinearExtension { - SparseMultilinearExtension::::from_evaluations(n_vars, v) -} - -/// Takes the n_vars and a dense vector and returns its dense MLE. -pub fn dense_vec_to_dense_mle( - n_vars: usize, - v: &[F], -) -> DenseMultilinearExtension { - // Pad to 2^n_vars - let mut v_padded = v.to_owned(); - v_padded.resize(1 << n_vars, F::zero()); - DenseMultilinearExtension::::from_evaluations_vec(n_vars, v_padded) -} - -/// Takes the n_vars and a dense vector and returns its sparse MLE. -pub fn dense_vec_to_mle(n_vars: usize, v: &[F]) -> SparseMultilinearExtension { - let v_sparse = v - .iter() - .enumerate() - .map(|(i, v_i)| (i, *v_i)) - .collect::>(); - SparseMultilinearExtension::::from_evaluations(n_vars, &v_sparse) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - arith::ccs::tests::get_test_z, - utils::multilinear_polynomial::fix_variables, - utils::multilinear_polynomial::tests::fix_last_variables, - utils::{hypercube::BooleanHypercube, vec::tests::to_F_matrix}, - }; - use ark_poly::MultilinearExtension; - use ark_std::Zero; - - use ark_pallas::Fr; - - #[test] - fn test_matrix_to_mle() { - let A = to_F_matrix::(vec![ - vec![2, 3, 4, 4], - vec![4, 11, 14, 14], - vec![2, 8, 17, 17], - vec![420, 4, 2, 0], - ]); - - let A_mle = matrix_to_mle(A); - assert_eq!(A_mle.evaluations.len(), 15); // 15 non-zero elements - assert_eq!(A_mle.num_vars, 4); // 4x4 matrix, thus 2bit x 2bit, thus 2^4=16 evals - - let A = to_F_matrix::(vec![ - vec![2, 3, 4, 4, 1], - vec![4, 11, 14, 14, 2], - vec![2, 8, 17, 17, 3], - vec![420, 4, 2, 0, 4], - vec![420, 4, 2, 0, 5], - ]); - let A_mle = matrix_to_mle(A.clone()); - assert_eq!(A_mle.evaluations.len(), 23); // 23 non-zero elements - assert_eq!(A_mle.num_vars, 6); // 5x5 matrix, thus 3bit x 3bit, thus 2^6=64 evals - - // check that the A_mle evaluated over the boolean hypercube equals the matrix A_i_j values - let bhc = BooleanHypercube::new(A_mle.num_vars); - let A_padded = pad_matrix(&A); - let A_padded_dense = A_padded.to_dense(); - for (i, A_row) in A_padded_dense.iter().enumerate() { - for (j, _) in A_row.iter().enumerate() { - let s_i_j = bhc.at_i(i * A_row.len() + j); - assert_eq!(A_mle.fix_variables(&s_i_j)[0], A_padded_dense[i][j]); - } - } - } - - #[test] - fn test_vec_to_mle() { - let z = get_test_z::(3); - let n_vars = 3; - let z_mle = dense_vec_to_mle(n_vars, &z); - - // check that the z_mle evaluated over the boolean hypercube equals the vec z_i values - let bhc = BooleanHypercube::new(z_mle.num_vars); - for (i, z_i) in z.iter().enumerate() { - let s_i = bhc.at_i(i); - assert_eq!(z_mle.fix_variables(&s_i)[0], z_i.clone()); - } - // for the rest of elements of the boolean hypercube, expect it to evaluate to zero - for i in (z.len())..(1 << z_mle.num_vars) { - let s_i = bhc.at_i(i); - assert_eq!(z_mle.fix_variables(&s_i)[0], Fr::zero()); - } - } - - #[test] - fn test_fix_variables() { - let A = to_F_matrix(vec![ - vec![2, 3, 4, 4], - vec![4, 11, 14, 14], - vec![2, 8, 17, 17], - vec![420, 4, 2, 0], - ]); - - let A_mle = matrix_to_dense_mle(A.clone()); - let A = A.to_dense(); - let bhc = BooleanHypercube::new(2); - for (i, y) in bhc.enumerate() { - // First check that the arkworks and espresso funcs match - let expected_fix_left = A_mle.fix_variables(&y); // try arkworks fix_variables - let fix_left = fix_variables(&A_mle, &y); // try espresso fix_variables - assert_eq!(fix_left, expected_fix_left); - - // Check that fixing first variables pins down a column - // i.e. fixing x to 0 will return the first column - // fixing x to 1 will return the second column etc. - let column_i: Vec = A.clone().iter().map(|x| x[i]).collect(); - assert_eq!(fix_left.evaluations, column_i); - - // Now check that fixing last variables pins down a row - // i.e. fixing y to 0 will return the first row - // fixing y to 1 will return the second row etc. - let row_i: Vec = A[i].clone(); - let fix_right = fix_last_variables(&A_mle, &y); - assert_eq!(fix_right.evaluations, row_i); - } - } -} diff --git a/folding-schemes/src/utils/mod.rs b/folding-schemes/src/utils/mod.rs deleted file mode 100644 index ed9c6f58a..000000000 --- a/folding-schemes/src/utils/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::path::Path; -use std::path::PathBuf; - -use ark_crypto_primitives::sponge::poseidon::PoseidonConfig; -use ark_ec::AffineRepr; -use ark_ff::PrimeField; -use ark_serialize::CanonicalSerialize; -use sha3::{Digest, Sha3_256}; - -use crate::arith::ArithSerializer; -use crate::commitment::CommitmentScheme; -use crate::{Curve, Error}; - -pub mod gadgets; -pub mod hypercube; -pub mod lagrange_poly; -pub mod mle; -pub mod vec; - -// expose espresso local modules -pub mod espresso; -pub use crate::utils::espresso::multilinear_polynomial; -pub use crate::utils::espresso::sum_check; -pub use crate::utils::espresso::virtual_polynomial; - -/// For a given x, returns [1, x^1, x^2, ..., x^n-1]; -pub fn powers_of(x: F, n: usize) -> Vec { - let mut c: Vec = vec![F::zero(); n]; - c[0] = F::one(); - for i in 1..n { - c[i] = c[i - 1] * x; - } - c -} - -/// returns the coordinates of a commitment point. This is compatible with the arkworks -/// GC.to_constraint_field()[..2] -pub fn get_cm_coordinates(cm: &C) -> Vec { - let (cm_x, cm_y) = cm.into_affine().xy().unwrap_or_default(); - vec![cm_x, cm_y] -} - -/// returns the hash of the given public parameters of the Folding Scheme -pub fn pp_hash( - arith: &impl ArithSerializer, - cf_arith: &impl ArithSerializer, - cs_vp: &CS1::VerifierParams, - cf_cs_vp: &CS2::VerifierParams, - poseidon_config: &PoseidonConfig, -) -> Result -where - C1: Curve, - C2: Curve, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - let mut hasher = Sha3_256::new(); - - // Fr & Fq modulus bit size - hasher.update(C1::ScalarField::MODULUS_BIT_SIZE.to_le_bytes()); - hasher.update(C2::ScalarField::MODULUS_BIT_SIZE.to_le_bytes()); - // AugmentedFCircuit Arith params - hasher.update(arith.params_to_le_bytes()); - // CycleFold Circuit Arith params - hasher.update(cf_arith.params_to_le_bytes()); - // cs_vp & cf_cs_vp (commitments setup) - let mut cs_vp_bytes = Vec::new(); - cs_vp.serialize_uncompressed(&mut cs_vp_bytes)?; - hasher.update(cs_vp_bytes); - let mut cf_cs_vp_bytes = Vec::new(); - cf_cs_vp.serialize_uncompressed(&mut cf_cs_vp_bytes)?; - hasher.update(cf_cs_vp_bytes); - // poseidon params - let mut poseidon_config_bytes = Vec::new(); - poseidon_config - .full_rounds - .serialize_uncompressed(&mut poseidon_config_bytes)?; - poseidon_config - .partial_rounds - .serialize_uncompressed(&mut poseidon_config_bytes)?; - poseidon_config - .alpha - .serialize_uncompressed(&mut poseidon_config_bytes)?; - poseidon_config - .ark - .serialize_uncompressed(&mut poseidon_config_bytes)?; - poseidon_config - .mds - .serialize_uncompressed(&mut poseidon_config_bytes)?; - poseidon_config - .rate - .serialize_uncompressed(&mut poseidon_config_bytes)?; - poseidon_config - .capacity - .serialize_uncompressed(&mut poseidon_config_bytes)?; - hasher.update(poseidon_config_bytes); - - let public_params_hash = hasher.finalize(); - Ok(C1::ScalarField::from_le_bytes_mod_order( - &public_params_hash, - )) -} - -/// Tiny utility enum that allows to import circuits and wasm modules from files by passing their path -/// or passing their content already read. -/// -/// This enum implements the [`From`] trait for both [`Path`], [`PathBuf`] and [`Vec`]. -#[derive(Debug, Clone)] -pub enum PathOrBin { - Path(PathBuf), - Bin(Vec), -} - -impl From<&Path> for PathOrBin { - fn from(value: &Path) -> Self { - PathOrBin::Path(value.into()) - } -} - -impl From for PathOrBin { - fn from(value: PathBuf) -> Self { - PathOrBin::Path(value) - } -} - -impl From> for PathOrBin { - fn from(value: Vec) -> Self { - PathOrBin::Bin(value) - } -} diff --git a/folding-schemes/src/utils/vec.rs b/folding-schemes/src/utils/vec.rs deleted file mode 100644 index 9c4938333..000000000 --- a/folding-schemes/src/utils/vec.rs +++ /dev/null @@ -1,252 +0,0 @@ -use ark_ff::PrimeField; -use ark_poly::{ - univariate::DensePolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, -}; -pub use ark_relations::gr1cs::Matrix as R1CSMatrix; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::cfg_iter; -use ark_std::rand::Rng; -use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; - -use crate::{folding::traits::Dummy, Error}; - -#[derive(Clone, Debug, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct SparseMatrix { - pub n_rows: usize, - pub n_cols: usize, - /// coeffs = R1CSMatrix = Vec>, which contains each row and the F is the value - /// of the coefficient and the usize indicates the column position - pub coeffs: R1CSMatrix, -} - -impl Dummy<(usize, usize)> for SparseMatrix { - fn dummy((n_rows, n_cols): (usize, usize)) -> Self { - Self { - n_rows, - n_cols, - // unnecessary to allocate each row as the matrix is sparse - coeffs: vec![vec![]; n_rows], - } - } -} - -impl SparseMatrix { - pub fn empty() -> Self { - Self::dummy((0, 0)) - } - - pub fn rand(rng: &mut R, n_rows: usize, n_cols: usize) -> Self { - const ZERO_VAL_PROBABILITY: f64 = 0.8f64; - - let dense = (0..n_rows) - .map(|_| { - (0..n_cols) - .map(|_| { - if !rng.gen_bool(ZERO_VAL_PROBABILITY) { - return F::rand(rng); - } - F::zero() - }) - .collect::>() - }) - .collect::>>(); - dense_matrix_to_sparse(dense) - } - pub fn to_dense(&self) -> Vec> { - let mut r: Vec> = vec![vec![F::zero(); self.n_cols]; self.n_rows]; - for (row_i, row) in self.coeffs.iter().enumerate() { - for &(value, col_i) in row.iter() { - r[row_i][col_i] = value; - } - } - r - } -} - -pub fn dense_matrix_to_sparse(m: Vec>) -> SparseMatrix { - let mut r = SparseMatrix:: { - n_rows: m.len(), - n_cols: m[0].len(), - coeffs: Vec::new(), - }; - for m_row in m.iter() { - let mut row: Vec<(F, usize)> = Vec::new(); - for (col_i, value) in m_row.iter().enumerate() { - if !value.is_zero() { - row.push((*value, col_i)); - } - } - r.coeffs.push(row); - } - r -} - -pub fn vec_add(a: &[F], b: &[F]) -> Result, Error> { - if a.len() != b.len() { - return Err(Error::NotSameLength( - "a.len()".to_string(), - a.len(), - "b.len()".to_string(), - b.len(), - )); - } - Ok(cfg_iter!(a).zip(b).map(|(x, y)| *x + y).collect()) -} - -pub fn vec_sub(a: &[F], b: &[F]) -> Result, Error> { - if a.len() != b.len() { - return Err(Error::NotSameLength( - "a.len()".to_string(), - a.len(), - "b.len()".to_string(), - b.len(), - )); - } - Ok(cfg_iter!(a).zip(b).map(|(x, y)| *x - y).collect()) -} - -pub fn vec_scalar_mul(vec: &[F], c: &F) -> Vec { - cfg_iter!(vec).map(|a| *a * c).collect() -} - -pub fn is_zero_vec(vec: &[F]) -> bool { - cfg_iter!(vec).all(|a| a.is_zero()) -} - -pub fn mat_vec_mul_dense(M: &[Vec], z: &[F]) -> Result, Error> { - if M.is_empty() { - return Err(Error::Empty); - } - if M[0].len() != z.len() { - return Err(Error::NotSameLength( - "M[0].len()".to_string(), - M[0].len(), - "z.len()".to_string(), - z.len(), - )); - } - - Ok(cfg_iter!(M) - .map(|row| row.iter().zip(z).map(|(a, b)| *a * b).sum()) - .collect()) -} - -pub fn mat_vec_mul(M: &SparseMatrix, z: &[F]) -> Result, Error> { - if M.n_cols != z.len() { - return Err(Error::NotSameLength( - "M.n_cols".to_string(), - M.n_cols, - "z.len()".to_string(), - z.len(), - )); - } - Ok(cfg_iter!(M.coeffs) - .map(|row| row.iter().map(|(value, col_i)| *value * z[*col_i]).sum()) - .collect()) -} - -pub fn mat_from_str_mat(str_mat: Vec>) -> Result>, Error> { - str_mat - .into_iter() - .map(|row| { - row.into_iter() - .map(|s| { - F::from_str(s).map_err(|_| Error::Other("Invalid decimal string".to_string())) - }) - .collect() - }) - .collect() -} - -pub fn hadamard(a: &[F], b: &[F]) -> Result, Error> { - if a.len() != b.len() { - return Err(Error::NotSameLength( - "a.len()".to_string(), - a.len(), - "b.len()".to_string(), - b.len(), - )); - } - Ok(cfg_iter!(a).zip(b).map(|(a, b)| *a * b).collect()) -} - -/// returns the interpolated polynomial of degree=v.len().next_power_of_two(), which passes through all -/// the given elements of v. -pub fn poly_from_vec(v: Vec) -> Result, Error> { - let D = GeneralEvaluationDomain::::new(v.len()).ok_or(Error::NewDomainFail)?; - Ok(Evaluations::from_vec_and_domain(v, D).interpolate()) -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_pallas::Fr; - - pub fn to_F_matrix(M: Vec>) -> SparseMatrix { - dense_matrix_to_sparse(to_F_dense_matrix(M)) - } - pub fn to_F_dense_matrix(M: Vec>) -> Vec> { - M.iter() - .map(|m| m.iter().map(|r| F::from(*r as u64)).collect()) - .collect() - } - pub fn to_F_vec(z: Vec) -> Vec { - z.iter().map(|c| F::from(*c as u64)).collect() - } - - #[test] - fn test_dense_sparse_conversions() { - let A = to_F_dense_matrix::(vec![ - vec![0, 1, 0, 0, 0, 0], - vec![0, 0, 0, 1, 0, 0], - vec![0, 1, 0, 0, 1, 0], - vec![5, 0, 0, 0, 0, 1], - ]); - let A_sparse = dense_matrix_to_sparse(A.clone()); - assert_eq!(A_sparse.to_dense(), A); - } - - // test mat_vec_mul & mat_vec_mul_sparse - #[test] - fn test_mat_vec_mul() -> Result<(), Error> { - let A = to_F_matrix::(vec![ - vec![0, 1, 0, 0, 0, 0], - vec![0, 0, 0, 1, 0, 0], - vec![0, 1, 0, 0, 1, 0], - vec![5, 0, 0, 0, 0, 1], - ]) - .to_dense(); - let z = to_F_vec(vec![1, 3, 35, 9, 27, 30]); - assert_eq!(mat_vec_mul_dense(&A, &z)?, to_F_vec(vec![3, 9, 30, 35])); - assert_eq!( - mat_vec_mul(&dense_matrix_to_sparse(A), &z)?, - to_F_vec(vec![3, 9, 30, 35]) - ); - - let A = to_F_matrix::(vec![vec![2, 3, 4, 5], vec![4, 8, 12, 14], vec![9, 8, 7, 6]]); - let v = to_F_vec(vec![19, 55, 50, 3]); - - assert_eq!( - mat_vec_mul_dense(&A.to_dense(), &v)?, - to_F_vec(vec![418, 1158, 979]) - ); - assert_eq!(mat_vec_mul(&A, &v)?, to_F_vec(vec![418, 1158, 979])); - Ok(()) - } - - #[test] - fn test_hadamard_product() -> Result<(), Error> { - let a = to_F_vec::(vec![1, 2, 3, 4, 5, 6]); - let b = to_F_vec(vec![7, 8, 9, 10, 11, 12]); - assert_eq!(hadamard(&a, &b)?, to_F_vec(vec![7, 16, 27, 40, 55, 72])); - Ok(()) - } - - #[test] - fn test_vec_add() -> Result<(), Error> { - let a: Vec = to_F_vec::(vec![1, 2, 3, 4, 5, 6]); - let b: Vec = to_F_vec(vec![7, 8, 9, 10, 11, 12]); - assert_eq!(vec_add(&a, &b)?, to_F_vec(vec![8, 10, 12, 14, 16, 18])); - Ok(()) - } -} diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 59be59214..000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.88.0 diff --git a/solidity-verifiers/Cargo.toml b/solidity-verifiers/Cargo.toml deleted file mode 100644 index eccecef24..000000000 --- a/solidity-verifiers/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -[package] -name = "solidity-verifiers" -version = "0.1.0" -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -ark-ec = { workspace = true } -ark-ff = { workspace = true } -ark-groth16 = { workspace = true } -ark-bn254 = { workspace = true, features = ["r1cs"] } -ark-poly-commit = { workspace = true } -ark-serialize = { workspace = true } -askama = { workspace = true, features = ["config"] } -revm = { workspace = true, features = ["std"] } -rust-crypto = { workspace = true } -num-bigint = { workspace = true } -folding-schemes = { workspace = true } # without 'light-test' enabled - -[dev-dependencies] -ark-ec = { workspace = true, features = ["parallel"] } -ark-ff = { workspace = true, features = ["parallel", "asm"] } -ark-std = { workspace = true, features = ["parallel"] } -ark-crypto-primitives = { workspace = true, features = ["sponge", "parallel"] } -ark-snark = { workspace = true } -ark-relations = { workspace = true } -ark-r1cs-std = { workspace = true, features = ["parallel"] } -ark-grumpkin = { workspace = true, features = ["r1cs"] } -folding-schemes = { workspace = true, features = ["light-test"] } -experimental-frontends = { workspace = true } -noname = { workspace = true } - -[features] -default = ["parallel"] - -parallel = [ - "ark-groth16/parallel", - "ark-poly-commit/parallel", - "folding-schemes/parallel", -] - -[[example]] -name = "full_flow" -path = "../examples/full_flow.rs" - -[[example]] -name = "circom_full_flow" -path = "../examples/circom_full_flow.rs" - -[[example]] -name = "noname_full_flow" -path = "../examples/noname_full_flow.rs" - -[[example]] -name = "noir_full_flow" -path = "../examples/noir_full_flow.rs" - diff --git a/solidity-verifiers/README.md b/solidity-verifiers/README.md deleted file mode 100644 index eb28bfd7d..000000000 --- a/solidity-verifiers/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# `solidity-verifiers` - -This crate implements templating logic to output verifier contracts for `sonobe`-generated decider proofs. -This crate is accompanied by the [cli](https://github.com/privacy-scaling-explorations/sonobe/tree/main/cli) crate, which allows to generate the Solidity contracts from the command line. - -To run the tests it needs [solc](https://docs.soliditylang.org/en/latest/installing-solidity.html) installed. diff --git a/solidity-verifiers/askama.toml b/solidity-verifiers/askama.toml deleted file mode 100644 index c596edf82..000000000 --- a/solidity-verifiers/askama.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[escaper]] -path = "askama::Text" -extensions = ["sol"] \ No newline at end of file diff --git a/solidity-verifiers/src/calldata.rs b/solidity-verifiers/src/calldata.rs deleted file mode 100644 index f3ea66901..000000000 --- a/solidity-verifiers/src/calldata.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::utils::eth::ToEth; -use ark_bn254::Bn254; -use ark_groth16::Groth16; -use crypto::digest::Digest; -use crypto::sha3::Sha3; -use folding_schemes::commitment::kzg::KZG; -use folding_schemes::folding::nova::decider_eth::Proof; -use folding_schemes::folding::nova::CommittedInstance; -use folding_schemes::Error; -use num_bigint::BigUint; - -/// Specifies which API to use for a proof verification in a contract. -#[derive(Copy, Clone, Debug, Default)] -pub enum NovaVerificationMode { - /// Use the `verifyNovaProof` function. - #[default] - Explicit, - /// Use the `verifyOpaqueNovaProof` function. - Opaque, - /// Use the `verifyOpaqueNovaProofWithInputs` function. - OpaqueWithInputs, -} - -/// Formats call data from a vec of bytes to a hashmap -/// Useful for debugging directly on the EVM -/// !! Should follow the contract's function signature, we assume the order of arguments is correct -pub fn get_formatted_calldata(calldata: Vec) -> Vec { - let mut formatted_calldata = vec![]; - for i in (4..calldata.len()).step_by(32) { - let val = BigUint::from_bytes_be(&calldata[i..i + 32]); - formatted_calldata.push(format!("{val}")); - } - formatted_calldata -} - -/// Prepares solidity calldata for calling the NovaDecider contract -pub fn prepare_calldata_for_nova_cyclefold_verifier( - verification_mode: NovaVerificationMode, - i: ark_bn254::Fr, - z_0: Vec, - z_i: Vec, - running_instance: &CommittedInstance, - incoming_instance: &CommittedInstance, - proof: &Proof, Groth16>, -) -> Result, Error> { - let selector = get_function_selector(verification_mode, z_0.len()); - - Ok([ - selector.to_eth(), - i.to_eth(), // i - z_0.to_eth(), // z_0 - z_i.to_eth(), // z_i - running_instance.cmW.to_eth(), - running_instance.cmE.to_eth(), - incoming_instance.cmW.to_eth(), - proof.cmT().to_eth(), // cmT - proof.r().to_eth(), // r - proof.snark_proof().to_eth(), // pA, pB, pC - proof.kzg_challenges().to_eth(), // challenge_W, challenge_E - proof.kzg_proofs()[0].eval.to_eth(), // eval W - proof.kzg_proofs()[1].eval.to_eth(), // eval E - proof.kzg_proofs()[0].proof.to_eth(), // W kzg_proof - proof.kzg_proofs()[1].proof.to_eth(), // E kzg_proof - ] - .concat()) -} - -/// Computes the function selector for the nova cyclefold verifier. -/// It is computed on the fly since it depends on the IVC state length. -fn get_function_selector(mode: NovaVerificationMode, state_len: usize) -> [u8; 4] { - let fn_sig = match mode { - NovaVerificationMode::Explicit => - format!( - "verifyNovaProof(uint256[{}],uint256[4],uint256[2],uint256[3],uint256[2],uint256[2][2],uint256[2],uint256[4],uint256[2][2])", - state_len * 2 + 1 - ), - NovaVerificationMode::Opaque => - format!("verifyOpaqueNovaProof(uint256[{}])", 26 + 2 * state_len), - NovaVerificationMode::OpaqueWithInputs => - format!("verifyOpaqueNovaProofWithInputs(uint256,uint256[{state_len}],uint256[{state_len}],uint256[25])"), - }; - - let mut hasher = Sha3::keccak256(); - hasher.input_str(&fn_sig); - let hash = &mut [0u8; 32]; - hasher.result(hash); - [hash[0], hash[1], hash[2], hash[3]] -} diff --git a/solidity-verifiers/src/evm.rs b/solidity-verifiers/src/evm.rs deleted file mode 100644 index 04441b38c..000000000 --- a/solidity-verifiers/src/evm.rs +++ /dev/null @@ -1,163 +0,0 @@ -pub use revm; -use revm::{ - primitives::{hex, Address, ExecutionResult, Output, TransactTo, TxEnv}, - Evm as EVM, EvmBuilder, InMemoryDB, -}; -use std::{ - fmt::Debug, - fs::{self, create_dir_all, File}, - io::{self, Write}, - path::PathBuf, - process::{Command, Stdio}, - str, -}; - -// from: https://github.com/privacy-scaling-explorations/halo2-solidity-verifier/blob/85cb77b171ce3ee493628007c7a1cfae2ea878e6/examples/separately.rs#L56 -pub fn save_solidity(name: impl AsRef, solidity: &str) { - let curdir = PathBuf::from("."); - let curdir_abs_path = fs::canonicalize(curdir).expect("Failed to get current directory"); - let curdir_abs_path = curdir_abs_path - .to_str() - .expect("Failed to convert path to string"); - let dir_generated = format!("{curdir_abs_path}/generated"); - create_dir_all(dir_generated.clone()).unwrap(); - File::create(format!("{}/{}", dir_generated, name.as_ref())) - .unwrap() - .write_all(solidity.as_bytes()) - .unwrap(); -} - -/// Compile solidity with `--via-ir` flag, then return creation bytecode. -/// -/// # Panics -/// Panics if executable `solc` can not be found, or compilation fails. -pub fn compile_solidity(solidity: impl AsRef<[u8]>, contract_name: &str) -> Vec { - let mut process = match Command::new("solc") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .arg("--bin") - .arg("--optimize") - .arg("-") - .spawn() - { - Ok(process) => process, - Err(err) if err.kind() == io::ErrorKind::NotFound => { - panic!("Command 'solc' not found"); - } - Err(err) => { - panic!("Failed to spawn process with command 'solc':\n{err}"); - } - }; - process - .stdin - .take() - .unwrap() - .write_all(solidity.as_ref()) - .unwrap(); - let output = process.wait_with_output().unwrap(); - let stdout = str::from_utf8(&output.stdout).unwrap(); - if let Some(binary) = find_binary(stdout, contract_name) { - binary - } else { - panic!( - "Compilation fails:\n{}", - str::from_utf8(&output.stderr).unwrap() - ) - } -} - -/// Find binary from `stdout` with given `contract_name`. -/// `contract_name` is provided since `solc` may compile multiple contracts or libraries. -/// hence, we need to find the correct binary. -fn find_binary(stdout: &str, contract_name: &str) -> Option> { - let intro_str = format!("======= :{contract_name} =======\nBinary:\n"); - let start = stdout.find(&intro_str)?; - let end = stdout[start + intro_str.len()..] - .find('\n') - .map(|pos| pos + start + intro_str.len()) - .unwrap_or(stdout.len()); - let binary_section = stdout[start + intro_str.len()..end].trim(); - Some(hex::decode(binary_section).unwrap()) -} - -/// Evm runner. -#[derive(Debug)] -pub struct Evm<'a> { - evm: EVM<'a, (), InMemoryDB>, -} - -impl<'a> Default for Evm<'a> { - fn default() -> Self { - Self { - evm: EvmBuilder::default().with_db(InMemoryDB::default()).build(), - } - } -} - -impl<'a> Evm<'a> { - /// Apply create transaction with given `bytecode` as creation bytecode. - /// Return created `address`. - /// - /// # Panics - /// Panics if execution reverts or halts unexpectedly. - pub fn create(&mut self, bytecode: Vec) -> Address { - let (_, output) = self.transact_success_or_panic(TxEnv { - gas_limit: u64::MAX, - transact_to: TransactTo::Create, - data: bytecode.into(), - ..Default::default() - }); - match output { - Output::Create(_, Some(address)) => address, - _ => unreachable!(), - } - } - - /// Apply call transaction to given `address` with `calldata`. - /// Returns `gas_used` and `return_data`. - /// - /// # Panics - /// Panics if execution reverts or halts unexpectedly. - pub fn call(&mut self, address: Address, calldata: Vec) -> (u64, Vec) { - let (gas_used, output) = self.transact_success_or_panic(TxEnv { - gas_limit: u64::MAX, - transact_to: TransactTo::Call(address), - data: calldata.into(), - ..Default::default() - }); - match output { - Output::Call(output) => (gas_used, output.into()), - _ => unreachable!(), - } - } - - fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) { - *self.evm.tx_mut() = tx; - let result = self.evm.transact_commit().unwrap(); - match result { - ExecutionResult::Success { - gas_used, - output, - logs, - .. - } => { - if !logs.is_empty() { - println!("--- logs from {} ---", logs[0].address); - for (log_idx, log) in logs.iter().enumerate() { - println!("log#{log_idx}"); - for (topic_idx, topic) in log.topics().iter().enumerate() { - println!(" topic{topic_idx}: {topic:?}"); - } - } - println!("--- end ---"); - } - (gas_used, output) - } - ExecutionResult::Revert { gas_used, output } => (gas_used, Output::Call(output)), - ExecutionResult::Halt { reason, gas_used } => panic!( - "Transaction halts unexpectedly with gas_used {gas_used} and reason {reason:?}" - ), - } - } -} diff --git a/solidity-verifiers/src/lib.rs b/solidity-verifiers/src/lib.rs deleted file mode 100644 index 2e831eb04..000000000 --- a/solidity-verifiers/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod calldata; -pub mod evm; -pub mod utils; -pub mod verifiers; - -pub use verifiers::*; -pub use verifiers::{ - get_decider_template_for_cyclefold_decider, Groth16VerifierKey, KZG10VerifierKey, - NovaCycleFoldVerifierKey, ProtocolVerifierKey, -}; diff --git a/solidity-verifiers/src/utils/encoding.rs b/solidity-verifiers/src/utils/encoding.rs deleted file mode 100644 index c9f7b486a..000000000 --- a/solidity-verifiers/src/utils/encoding.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// Defines encodings of G1 and G2 elements for use in Solidity templates. -use ark_bn254::{Fq, G1Affine, G2Affine}; -use std::fmt::{self, Display}; - -#[derive(Debug, Default)] -pub struct FqWrapper(pub Fq); - -impl Display for FqWrapper { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[derive(Debug, Default)] -pub struct G1Repr(pub [FqWrapper; 2]); - -impl Display for G1Repr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:#?}", self.0) - } -} - -/// Converts a G1 element to a representation that can be used in Solidity templates. -pub fn g1_to_fq_repr(g1: G1Affine) -> G1Repr { - G1Repr([FqWrapper(g1.x), FqWrapper(g1.y)]) -} - -#[derive(Debug, Default)] -pub struct G2Repr(pub [[FqWrapper; 2]; 2]); - -impl Display for G2Repr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:#?}", self.0) - } -} - -/// Converts a G2 element to a representation that can be used in Solidity templates. -pub fn g2_to_fq_repr(g2: G2Affine) -> G2Repr { - G2Repr([ - [FqWrapper(g2.x.c0), FqWrapper(g2.x.c1)], - [FqWrapper(g2.y.c0), FqWrapper(g2.y.c1)], - ]) -} diff --git a/solidity-verifiers/src/utils/eth.rs b/solidity-verifiers/src/utils/eth.rs deleted file mode 100644 index 765d493f1..000000000 --- a/solidity-verifiers/src/utils/eth.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! This module provides a trait and implementations for converting Rust types -//! to EVM calldata. -use ark_ec::{ - pairing::Pairing, - short_weierstrass::{Affine, Projective, SWCurveConfig}, - AffineRepr, CurveGroup, -}; -use ark_ff::{BigInteger, Fp, Fp2, Fp2Config, FpConfig, PrimeField}; -use ark_groth16::Proof; - -pub trait ToEth { - fn to_eth(&self) -> Vec; -} - -impl ToEth for [T] { - fn to_eth(&self) -> Vec { - self.iter().flat_map(ToEth::to_eth).collect() - } -} - -impl ToEth for u8 { - fn to_eth(&self) -> Vec { - vec![*self] - } -} - -impl, const N: usize> ToEth for Fp { - fn to_eth(&self) -> Vec { - self.into_bigint().to_bytes_be() - } -} - -impl> ToEth for Fp2

{ - fn to_eth(&self) -> Vec { - [self.c1.to_eth(), self.c0.to_eth()].concat() - } -} - -impl> ToEth for Affine

{ - fn to_eth(&self) -> Vec { - // the encoding of the additive identity is [0, 0] on the EVM - let (x, y) = self.xy().unwrap_or_default(); - - [x.to_eth(), y.to_eth()].concat() - } -} - -impl> ToEth for Projective

{ - fn to_eth(&self) -> Vec { - self.into_affine().to_eth() - } -} - -impl> ToEth for Proof { - fn to_eth(&self) -> Vec { - [self.a.to_eth(), self.b.to_eth(), self.c.to_eth()].concat() - } -} diff --git a/solidity-verifiers/src/utils/mod.rs b/solidity-verifiers/src/utils/mod.rs deleted file mode 100644 index cd7c3a716..000000000 --- a/solidity-verifiers/src/utils/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::{GPL3_SDPX_IDENTIFIER, PRAGMA_GROTH16_VERIFIER}; -use askama::Template; - -pub mod encoding; -pub mod eth; - -#[derive(Template)] -#[template(path = "header_template.askama.sol", ext = "sol")] -pub struct HeaderInclusion { - /// SPDX-License-Identifier - pub sdpx: String, - /// The `pragma` statement. - pub pragma_version: String, - /// The template to render alongside the header. - pub template: T, -} - -impl HeaderInclusion { - pub fn builder() -> HeaderInclusionBuilder { - HeaderInclusionBuilder::default() - } -} - -#[derive(Debug)] -pub struct HeaderInclusionBuilder { - /// SPDX-License-Identifier - sdpx: String, - /// The `pragma` statement. - pragma_version: String, - /// The template to render alongside the header. - template: T, -} - -impl Default for HeaderInclusionBuilder { - fn default() -> Self { - Self { - sdpx: GPL3_SDPX_IDENTIFIER.to_string(), - pragma_version: PRAGMA_GROTH16_VERIFIER.to_string(), - template: T::default(), - } - } -} - -impl HeaderInclusionBuilder { - pub fn sdpx>(mut self, sdpx: S) -> Self { - self.sdpx = sdpx.into(); - self - } - - pub fn pragma_version>(mut self, pragma_version: S) -> Self { - self.pragma_version = pragma_version.into(); - self - } - - pub fn template(mut self, template: impl Into) -> Self { - self.template = template.into(); - self - } - - pub fn build(self) -> HeaderInclusion { - HeaderInclusion { - sdpx: self.sdpx, - pragma_version: self.pragma_version, - template: self.template, - } - } -} diff --git a/solidity-verifiers/src/verifiers/g16.rs b/solidity-verifiers/src/verifiers/g16.rs deleted file mode 100644 index ae38e8dde..000000000 --- a/solidity-verifiers/src/verifiers/g16.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::utils::encoding::{g1_to_fq_repr, g2_to_fq_repr}; -use crate::utils::encoding::{G1Repr, G2Repr}; -use crate::utils::HeaderInclusion; -use crate::{ProtocolVerifierKey, GPL3_SDPX_IDENTIFIER}; -use ark_bn254::Bn254; -use ark_groth16::VerifyingKey as ArkVerifyingKey; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use askama::Template; - -use super::PRAGMA_GROTH16_VERIFIER; - -#[derive(Template, Default)] -#[template(path = "groth16_verifier.askama.sol", ext = "sol")] -pub struct Groth16Verifier { - /// The `alpha * G`, where `G` is the generator of `G1`. - pub vkey_alpha_g1: G1Repr, - /// The `alpha * H`, where `H` is the generator of `G2`. - pub vkey_beta_g2: G2Repr, - /// The `gamma * H`, where `H` is the generator of `G2`. - pub vkey_gamma_g2: G2Repr, - /// The `delta * H`, where `H` is the generator of `G2`. - pub vkey_delta_g2: G2Repr, - /// Length of the `gamma_abc_g1` vector. - pub gamma_abc_len: usize, - /// The `gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H`, where `H` is the generator of `E::G1`. - pub gamma_abc_g1: Vec, -} - -impl From for Groth16Verifier { - fn from(g16_vk: Groth16VerifierKey) -> Self { - Self { - vkey_alpha_g1: g1_to_fq_repr(g16_vk.0.alpha_g1), - vkey_beta_g2: g2_to_fq_repr(g16_vk.0.beta_g2), - vkey_gamma_g2: g2_to_fq_repr(g16_vk.0.gamma_g2), - vkey_delta_g2: g2_to_fq_repr(g16_vk.0.delta_g2), - gamma_abc_len: g16_vk.0.gamma_abc_g1.len(), - gamma_abc_g1: g16_vk - .0 - .gamma_abc_g1 - .iter() - .copied() - .map(g1_to_fq_repr) - .collect(), - } - } -} - -// Ideally this would be linked to the `Decider` trait in FoldingSchemes. -// For now, this is the easiest as NovaCycleFold isn't clear target from where we can get all it's needed arguments. -#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)] -pub struct Groth16VerifierKey(pub(crate) ArkVerifyingKey); - -impl From> for Groth16VerifierKey { - fn from(value: ArkVerifyingKey) -> Self { - Self(value) - } -} - -impl ProtocolVerifierKey for Groth16VerifierKey { - const PROTOCOL_NAME: &'static str = "Groth16"; - - fn render_as_template(self, pragma: Option) -> Vec { - HeaderInclusion::::builder() - .sdpx(GPL3_SDPX_IDENTIFIER.to_string()) - .pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string())) - .template(self) - .build() - .render() - .unwrap() - .into_bytes() - } -} - -#[cfg(test)] -mod tests { - use super::Groth16VerifierKey; - use crate::{ - evm::{compile_solidity, save_solidity, Evm}, - ProtocolVerifierKey, - }; - use ark_bn254::{Bn254, Fr}; - use ark_ec::AffineRepr; - use ark_ff::{BigInt, BigInteger, PrimeField}; - use ark_groth16::Groth16; - use ark_snark::SNARK; - use ark_std::rand::{RngCore, SeedableRng}; - use ark_std::test_rng; - use askama::Template; - - use super::Groth16Verifier; - use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN}; - - pub const FUNCTION_SELECTOR_GROTH16_VERIFY_PROOF: [u8; 4] = [0x43, 0x75, 0x3b, 0x4d]; - - #[test] - fn groth16_vk_serde_roundtrip() { - let (_, _, _, _, vk, _) = setup(DEFAULT_SETUP_LEN); - - let g16_vk = Groth16VerifierKey::from(vk); - let mut bytes = vec![]; - g16_vk.serialize_protocol_verifier_key(&mut bytes).unwrap(); - let obtained_g16_vk = - Groth16VerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap(); - - assert_eq!(g16_vk, obtained_g16_vk) - } - - #[test] - fn test_groth16_verifier_accepts_and_rejects_proofs() { - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let (_, _, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN); - let g16_vk = Groth16VerifierKey::from(g16_vk); - - let proof = Groth16::::prove(&g16_pk, circuit, &mut rng).unwrap(); - let res = Groth16Verifier::from(g16_vk).render().unwrap(); - save_solidity("groth16_verifier.sol", &res); - let groth16_verifier_bytecode = compile_solidity(&res, "Groth16Verifier"); - let mut evm = Evm::default(); - let verifier_address = evm.create(groth16_verifier_bytecode); - let (a_x, a_y) = proof.a.xy().unwrap(); - let (b_x, b_y) = proof.b.xy().unwrap(); - let (c_x, c_y) = proof.c.xy().unwrap(); - let mut calldata: Vec = [ - &FUNCTION_SELECTOR_GROTH16_VERIFY_PROOF[..], - &a_x.into_bigint().to_bytes_be(), - &a_y.into_bigint().to_bytes_be(), - &b_x.c1.into_bigint().to_bytes_be(), - &b_x.c0.into_bigint().to_bytes_be(), - &b_y.c1.into_bigint().to_bytes_be(), - &b_y.c0.into_bigint().to_bytes_be(), - &c_x.into_bigint().to_bytes_be(), - &c_y.into_bigint().to_bytes_be(), - &BigInt::from(Fr::from(circuit.z)).to_bytes_be(), - ] - .concat(); - let (_, output) = evm.call(verifier_address, calldata.clone()); - assert_eq!(*output.last().unwrap(), 1); - - // change calldata to make it invalid - let last_calldata_element = calldata.last_mut().unwrap(); - *last_calldata_element = 0; - let (_, output) = evm.call(verifier_address, calldata); - assert_eq!(*output.last().unwrap(), 0); - } -} diff --git a/solidity-verifiers/src/verifiers/kzg.rs b/solidity-verifiers/src/verifiers/kzg.rs deleted file mode 100644 index 808ac9650..000000000 --- a/solidity-verifiers/src/verifiers/kzg.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::utils::encoding::{g1_to_fq_repr, g2_to_fq_repr}; -use crate::utils::encoding::{G1Repr, G2Repr}; -use crate::utils::HeaderInclusion; -use crate::{ProtocolVerifierKey, MIT_SDPX_IDENTIFIER}; -use ark_bn254::{Bn254, G1Affine}; -use ark_poly_commit::kzg10::VerifierKey; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use askama::Template; - -use super::PRAGMA_KZG10_VERIFIER; - -#[derive(Template, Default)] -#[template(path = "kzg10_verifier.askama.sol", ext = "sol")] -pub struct KZG10Verifier { - /// The generator of `G1`. - pub(crate) g1: G1Repr, - /// The generator of `G2`. - pub(crate) g2: G2Repr, - /// The verification key - pub(crate) vk: G2Repr, - /// Length of the trusted setup vector. - pub(crate) g1_crs_len: usize, - /// The trusted setup vector. - pub(crate) g1_crs: Vec, -} - -impl From for KZG10Verifier { - fn from(data: KZG10VerifierKey) -> Self { - Self { - g1: g1_to_fq_repr(data.vk.g), - g2: g2_to_fq_repr(data.vk.h), - vk: g2_to_fq_repr(data.vk.beta_h), - g1_crs_len: data.g1_crs_batch_points.len(), - g1_crs: data - .g1_crs_batch_points - .iter() - .map(|g1| g1_to_fq_repr(*g1)) - .collect(), - } - } -} - -#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)] -pub struct KZG10VerifierKey { - pub vk: VerifierKey, - pub g1_crs_batch_points: Vec, -} - -impl From<(VerifierKey, Vec)> for KZG10VerifierKey { - fn from(value: (VerifierKey, Vec)) -> Self { - Self { - vk: value.0, - g1_crs_batch_points: value.1, - } - } -} - -impl ProtocolVerifierKey for KZG10VerifierKey { - const PROTOCOL_NAME: &'static str = "KZG"; - - fn render_as_template(self, pragma: Option) -> Vec { - HeaderInclusion::::builder() - .sdpx(MIT_SDPX_IDENTIFIER.to_string()) - .pragma_version(pragma.unwrap_or(PRAGMA_KZG10_VERIFIER.to_string())) - .template(self) - .build() - .render() - .unwrap() - .into_bytes() - } -} - -#[cfg(test)] -mod tests { - use super::KZG10VerifierKey; - use crate::{ - evm::{compile_solidity, Evm}, - utils::HeaderInclusion, - ProtocolVerifierKey, - }; - use ark_bn254::{Bn254, Fr}; - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use ark_ec::{AffineRepr, CurveGroup}; - use ark_ff::{BigInteger, PrimeField}; - use ark_std::rand::{RngCore, SeedableRng}; - use ark_std::Zero; - use ark_std::{test_rng, UniformRand}; - use askama::Template; - - use folding_schemes::{ - commitment::{kzg::KZG, CommitmentScheme}, - transcript::{poseidon::poseidon_canonical_config, Transcript}, - }; - - use super::KZG10Verifier; - use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN}; - - const FUNCTION_SELECTOR_KZG10_CHECK: [u8; 4] = [0x9e, 0x78, 0xcc, 0xf7]; - - #[test] - fn kzg_vk_serde_roundtrip() { - let (_, pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN); - - let kzg_vk = KZG10VerifierKey::from((vk, pk.powers_of_g[0..3].to_vec())); - let mut bytes = vec![]; - kzg_vk.serialize_protocol_verifier_key(&mut bytes).unwrap(); - let obtained_kzg_vk = - KZG10VerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap(); - - assert_eq!(kzg_vk, obtained_kzg_vk) - } - - #[test] - fn kzg_verifier_compiles() { - let (_, kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN); - let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec())); - - let res = HeaderInclusion::::builder() - .template(kzg_vk) - .build() - .render() - .unwrap(); - - let kzg_verifier_bytecode = compile_solidity(res, "KZG10Verifier"); - let mut evm = Evm::default(); - _ = evm.create(kzg_verifier_bytecode); - } - - #[test] - fn kzg_verifier_accepts_and_rejects_proofs() { - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let poseidon_config = poseidon_canonical_config::(); - let transcript_p = &mut PoseidonSponge::::new(&poseidon_config); - let transcript_v = &mut PoseidonSponge::::new(&poseidon_config); - - let (_, kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN); - let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec())); - - let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(DEFAULT_SETUP_LEN) - .collect(); - let cm = KZG::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); - let proof = KZG::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); - let template = HeaderInclusion::::builder() - .template(kzg_vk) - .build() - .render() - .unwrap(); - - let kzg_verifier_bytecode = compile_solidity(template, "KZG10Verifier"); - let mut evm = Evm::default(); - let verifier_address = evm.create(kzg_verifier_bytecode); - - let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine()); - let (x_comm, y_comm) = cm_affine.xy().unwrap(); - let (x_proof, y_proof) = proof_affine.xy().unwrap(); - let y = proof.eval.into_bigint().to_bytes_be(); - - transcript_v.absorb_nonnative(&cm); - let x = transcript_v.get_challenge(); - - let x = x.into_bigint().to_bytes_be(); - let mut calldata: Vec = [ - &FUNCTION_SELECTOR_KZG10_CHECK[..], - &x_comm.into_bigint().to_bytes_be(), - &y_comm.into_bigint().to_bytes_be(), - &x_proof.into_bigint().to_bytes_be(), - &y_proof.into_bigint().to_bytes_be(), - &x, - &y, - ] - .concat(); - - let (_, output) = evm.call(verifier_address, calldata.clone()); - assert_eq!(*output.last().unwrap(), 1); - - // change calldata to make it invalid - let last_calldata_element = calldata.last_mut().unwrap(); - *last_calldata_element = 0; - let (_, output) = evm.call(verifier_address, calldata); - assert_eq!(*output.last().unwrap(), 0); - } -} diff --git a/solidity-verifiers/src/verifiers/mod.rs b/solidity-verifiers/src/verifiers/mod.rs deleted file mode 100644 index 332c03a2d..000000000 --- a/solidity-verifiers/src/verifiers/mod.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Solidity templates for the verifier contracts. -//! We use askama for templating and define which variables are required for each template. - -// Pragma statements for verifiers -pub const PRAGMA_GROTH16_VERIFIER: &str = "pragma solidity >=0.7.0 <0.9.0;"; // from snarkjs, avoid changing -pub const PRAGMA_KZG10_VERIFIER: &str = "pragma solidity >=0.8.1 <=0.8.4;"; - -/// Default SDPX License identifier -pub const GPL3_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: GPL-3.0"; -pub const MIT_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: MIT"; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; - -pub mod g16; -pub mod kzg; -pub mod nova_cyclefold; - -pub use g16::Groth16VerifierKey; -pub use kzg::KZG10VerifierKey; -pub use nova_cyclefold::{get_decider_template_for_cyclefold_decider, NovaCycleFoldVerifierKey}; - -pub trait ProtocolVerifierKey: CanonicalDeserialize + CanonicalSerialize { - const PROTOCOL_NAME: &'static str; - - fn serialize_name(&self, writer: &mut W) -> Result<(), SerializationError> { - Self::PROTOCOL_NAME - .to_string() - .serialize_uncompressed(writer) - } - - fn serialize_protocol_verifier_key( - &self, - writer: &mut W, - ) -> Result<(), SerializationError> { - self.serialize_name(writer)?; - self.serialize_compressed(writer) - } - fn deserialize_protocol_verifier_key( - mut reader: R, - ) -> Result { - let name: String = String::deserialize_uncompressed(&mut reader)?; - let data = Self::deserialize_compressed(&mut reader)?; - - if name != Self::PROTOCOL_NAME { - return Err(SerializationError::InvalidData); - } - - Ok(data) - } - - fn render_as_template(self, pragma: Option) -> Vec; -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::{Bn254, Fr, G1Projective as G1}; - use ark_ff::PrimeField; - use ark_groth16::Groth16; - use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey; - use ark_r1cs_std::alloc::AllocVar; - use ark_r1cs_std::eq::EqGadget; - use ark_r1cs_std::fields::fp::FpVar; - use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; - use ark_snark::CircuitSpecificSetupSNARK; - use ark_std::rand::{RngCore, SeedableRng}; - use ark_std::test_rng; - use std::marker::PhantomData; - - use folding_schemes::commitment::{ - kzg::{ProverKey as KZGProverKey, KZG}, - CommitmentScheme, - }; - - /// Default setup length for testing. - pub const DEFAULT_SETUP_LEN: usize = 5; - - /// Test circuit used to test the Groth16 proof generation - #[derive(Debug, Clone, Copy)] - pub struct TestAddCircuit { - _f: PhantomData, - pub x: u8, - pub y: u8, - pub z: u8, - } - - impl ConstraintSynthesizer for TestAddCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let x = FpVar::::new_witness(cs.clone(), || Ok(F::from(self.x)))?; - let y = FpVar::::new_witness(cs.clone(), || Ok(F::from(self.y)))?; - let z = FpVar::::new_input(cs.clone(), || Ok(F::from(self.z)))?; - let comp_z = x.clone() + y.clone(); - comp_z.enforce_equal(&z)?; - Ok(()) - } - } - - #[allow(clippy::type_complexity)] - pub fn setup<'a>( - n: usize, - ) -> ( - Fr, // public params hash - KZGProverKey<'a, G1>, - KZGVerifierKey, - ark_groth16::ProvingKey, - ark_groth16::VerifyingKey, - TestAddCircuit, - ) { - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let (x, y, z) = (21, 21, 42); - let circuit = TestAddCircuit:: { - _f: PhantomData, - x, - y, - z, - }; - let (g16_pk, g16_vk) = Groth16::::setup(circuit, &mut rng).unwrap(); - - let (kzg_pk, kzg_vk): (KZGProverKey, KZGVerifierKey) = - KZG::::setup(&mut rng, n).unwrap(); - let pp_hash = Fr::from(42u32); // only for test - (pp_hash, kzg_pk, kzg_vk, g16_pk, g16_vk, circuit) - } -} diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs deleted file mode 100644 index d592dad6d..000000000 --- a/solidity-verifiers/src/verifiers/nova_cyclefold.rs +++ /dev/null @@ -1,433 +0,0 @@ -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(clippy::upper_case_acronyms)] - -use ark_bn254::{Bn254, Fq, Fr, G1Affine, G1Projective}; -use ark_groth16::VerifyingKey as ArkG16VerifierKey; -use ark_poly_commit::kzg10::VerifierKey as ArkKZG10VerifierKey; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use askama::Template; - -use folding_schemes::folding::circuits::nonnative::uint::NonNativeUintVar; -use folding_schemes::folding::nova::decider_eth::VerifierParam as DeciderVerifierParam; - -use super::g16::Groth16Verifier; -use super::kzg::KZG10Verifier; -use crate::utils::HeaderInclusion; -use crate::{Groth16VerifierKey, KZG10VerifierKey, ProtocolVerifierKey, PRAGMA_GROTH16_VERIFIER}; - -pub fn get_decider_template_for_cyclefold_decider( - nova_cyclefold_vk: NovaCycleFoldVerifierKey, -) -> String { - HeaderInclusion::::builder() - .template(nova_cyclefold_vk) - .build() - .render() - .unwrap() -} - -#[derive(Template, Default)] -#[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")] -pub struct NovaCycleFoldDecider { - pp_hash: Fr, // public params hash - groth16_verifier: Groth16Verifier, - kzg10_verifier: KZG10Verifier, - // z_len denotes the FCircuit state (z_i) length - z_len: usize, - public_inputs_len: usize, - num_limbs: usize, - bits_per_limb: usize, -} - -impl From for NovaCycleFoldDecider { - fn from(value: NovaCycleFoldVerifierKey) -> Self { - let groth16_verifier = Groth16Verifier::from(value.g16_vk); - let public_inputs_len = groth16_verifier.gamma_abc_len; - let bits_per_limb = NonNativeUintVar::::bits_per_limb(); - Self { - pp_hash: value.pp_hash, - groth16_verifier, - kzg10_verifier: KZG10Verifier::from(value.kzg_vk), - z_len: value.z_len, - public_inputs_len, - num_limbs: (250_f32 / (bits_per_limb as f32)).ceil() as usize, - bits_per_limb, - } - } -} - -#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug, Clone)] -pub struct NovaCycleFoldVerifierKey { - pp_hash: Fr, - g16_vk: Groth16VerifierKey, - kzg_vk: KZG10VerifierKey, - z_len: usize, -} - -impl ProtocolVerifierKey for NovaCycleFoldVerifierKey { - const PROTOCOL_NAME: &'static str = "NovaCycleFold"; - - fn render_as_template(self, pragma: Option) -> Vec { - HeaderInclusion::::builder() - .pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string())) - .template(self) - .build() - .render() - .unwrap() - .into_bytes() - } -} - -impl From<(Fr, Groth16VerifierKey, KZG10VerifierKey, usize)> for NovaCycleFoldVerifierKey { - fn from(value: (Fr, Groth16VerifierKey, KZG10VerifierKey, usize)) -> Self { - Self { - pp_hash: value.0, - g16_vk: value.1, - kzg_vk: value.2, - z_len: value.3, - } - } -} - -// implements From assuming that the 'batchCheck' method from the KZG10 template will not be used -// in the NovaCycleFoldDecider verifier contract -impl - From<( - DeciderVerifierParam, ArkG16VerifierKey>, - usize, - )> for NovaCycleFoldVerifierKey -{ - fn from( - value: ( - DeciderVerifierParam< - G1Projective, - ArkKZG10VerifierKey, - ArkG16VerifierKey, - >, - usize, - ), - ) -> Self { - let decider_vp = value.0; - let g16_vk = Groth16VerifierKey::from(decider_vp.snark_vp); - // pass `Vec::new()` since batchCheck will not be used - let kzg_vk = KZG10VerifierKey::from((decider_vp.cs_vp, Vec::new())); - Self { - pp_hash: decider_vp.pp_hash, - g16_vk, - kzg_vk, - z_len: value.1, - } - } -} - -impl NovaCycleFoldVerifierKey { - pub fn new( - pp_hash: Fr, - vkey_g16: ArkG16VerifierKey, - vkey_kzg: ArkKZG10VerifierKey, - crs_points: Vec, - z_len: usize, - ) -> Self { - Self { - pp_hash, - g16_vk: Groth16VerifierKey::from(vkey_g16), - kzg_vk: KZG10VerifierKey::from((vkey_kzg, crs_points)), - z_len, - } - } -} - -#[cfg(test)] -mod tests { - use ark_bn254::{Bn254, Fr, G1Projective as G1, G1Projective}; - use ark_ff::PrimeField; - use ark_groth16::Groth16; - use ark_grumpkin::Projective as G2; - use ark_r1cs_std::alloc::AllocVar; - use ark_r1cs_std::fields::fp::FpVar; - use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; - use askama::Template; - use std::marker::PhantomData; - use std::time::Instant; - - use super::{DeciderVerifierParam, NovaCycleFoldDecider}; - use crate::calldata::NovaVerificationMode::{Explicit, Opaque, OpaqueWithInputs}; - use crate::calldata::{prepare_calldata_for_nova_cyclefold_verifier, NovaVerificationMode}; - use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN}; - use crate::{ - evm::{compile_solidity, save_solidity, Evm}, - utils::HeaderInclusion, - verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, - NovaCycleFoldVerifierKey, ProtocolVerifierKey, - }; - use folding_schemes::folding::nova::decider_eth::Proof; - use folding_schemes::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - folding::{ - nova::{decider_eth::Decider as DeciderEth, Nova, PreprocessorParam}, - traits::CommittedInstanceOps, - }, - frontend::FCircuit, - transcript::poseidon::poseidon_canonical_config, - Decider, Error, FoldingScheme, - }; - - type NOVA = Nova, Pedersen, false>; - type DECIDER = - DeciderEth, Pedersen, Groth16, NOVA>; - - type FS_PP = as FoldingScheme>::ProverParam; - type FS_VP = as FoldingScheme>::VerifierParam; - type DECIDER_PP = as Decider>>::ProverParam; - type DECIDER_VP = as Decider>>::VerifierParam; - - /// Test circuit to be folded - #[derive(Clone, Copy, Debug)] - pub struct CubicFCircuit { - _f: PhantomData, - } - impl FCircuit for CubicFCircuit { - type Params = (); - type ExternalInputs = (); - type ExternalInputsVar = (); - fn new(_params: Self::Params) -> Result { - Ok(Self { _f: PhantomData }) - } - fn state_len(&self) -> usize { - 1 - } - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; - let z_i = z_i[0].clone(); - - Ok(vec![&z_i * &z_i * &z_i + &z_i + &five]) - } - } - - /// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i - /// denotes the current state which contains 5 elements, and z_{i+1} denotes the next state which - /// we get by applying the step. - /// In this example we set z_i and z_{i+1} to have five elements, and at each step we do different - /// operations on each of them. - #[derive(Clone, Copy, Debug)] - pub struct MultiInputsFCircuit { - _f: PhantomData, - } - impl FCircuit for MultiInputsFCircuit { - type Params = (); - type ExternalInputs = (); - type ExternalInputsVar = (); - - fn new(_params: Self::Params) -> Result { - Ok(Self { _f: PhantomData }) - } - fn state_len(&self) -> usize { - 5 - } - /// generates the constraints for the step of F for the given z_i - fn generate_step_constraints( - &self, - cs: ConstraintSystemRef, - _i: usize, - z_i: Vec>, - _external_inputs: Self::ExternalInputsVar, - ) -> Result>, SynthesisError> { - let four = FpVar::::new_constant(cs.clone(), F::from(4u32))?; - let forty = FpVar::::new_constant(cs.clone(), F::from(40u32))?; - let onehundred = FpVar::::new_constant(cs.clone(), F::from(100u32))?; - let a = z_i[0].clone() + four.clone(); - let b = z_i[1].clone() + forty.clone(); - let c = z_i[2].clone() * four; - let d = z_i[3].clone() * forty; - let e = z_i[4].clone() + onehundred; - - Ok(vec![a, b, c, d, e]) - } - } - - #[test] - fn nova_cyclefold_vk_serde_roundtrip() { - let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN); - - let decider_vp = DeciderVerifierParam { - pp_hash, - snark_vp: g16_vk, - cs_vp: kzg_vk, - }; - let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, 1)); - - let mut bytes = vec![]; - nova_cyclefold_vk - .serialize_protocol_verifier_key(&mut bytes) - .unwrap(); - let obtained_nova_cyclefold_vk = - NovaCycleFoldVerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap(); - - assert_eq!(nova_cyclefold_vk, obtained_nova_cyclefold_vk) - } - - #[test] - fn nova_cyclefold_decider_template_renders() { - let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN); - let decider_vp = DeciderVerifierParam { - pp_hash, - snark_vp: g16_vk, - cs_vp: kzg_vk, - }; - let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, 1)); - - let decider_solidity_code = HeaderInclusion::::builder() - .template(nova_cyclefold_vk) - .build(); - - save_solidity("NovaDecider.sol", &decider_solidity_code.render().unwrap()); - } - - /// Initializes Nova parameters and DeciderEth parameters. Only for test purposes. - #[allow(clippy::type_complexity)] - fn init_params>( - ) -> ((FS_PP, FS_VP), (DECIDER_PP, DECIDER_VP)) { - let mut rng = ark_std::rand::rngs::OsRng; - let poseidon_config = poseidon_canonical_config::(); - - let f_circuit = FC::new(()).unwrap(); - let prep_param = - PreprocessorParam::, Pedersen, false>::new( - poseidon_config, - f_circuit.clone(), - ); - let nova_params = NOVA::preprocess(&mut rng, &prep_param).unwrap(); - let decider_params = - DECIDER::::preprocess(&mut rng, (nova_params.clone(), f_circuit.state_len())) - .unwrap(); - - (nova_params, decider_params) - } - - fn interact_with_contract<'a, FC: FCircuit>( - nova_cyclefold_verifier_bytecode: &[u8], - nova: &NOVA, - proof: &Proof, Groth16>, - mode: NovaVerificationMode, - ) { - let mut evm = Evm::default(); - let verifier_address = evm.create(nova_cyclefold_verifier_bytecode.to_vec()); - - let calldata: Vec = prepare_calldata_for_nova_cyclefold_verifier( - mode, - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i, - &nova.u_i, - proof, - ) - .unwrap(); - - let (_, output) = evm.call(verifier_address, calldata.clone()); - assert_eq!(*output.last().unwrap(), 1); - - // change i to make calldata invalid, placed between bytes 4 - 35 - let mut invalid_calldata = calldata.clone(); - invalid_calldata[35] += 1; - let (_, output) = evm.call(verifier_address, invalid_calldata.clone()); - assert_eq!(*output.last().unwrap(), 0); - - // change z_0 to make the EVM check fail, placed between bytes 35 - 67 - let mut invalid_calldata = calldata.clone(); - invalid_calldata[67] += 1; - let (_, output) = evm.call(verifier_address, invalid_calldata.clone()); - assert_eq!(*output.last().unwrap(), 0); - - // change z_i to make the EVM check fail, placed between bytes 68 - 100 - let mut invalid_calldata = calldata.clone(); - invalid_calldata[99] += 1; - let (_, output) = evm.call(verifier_address, invalid_calldata.clone()); - assert_eq!(*output.last().unwrap(), 0); - } - - /// This function allows to define which FCircuit to use for the test, and how many prove_step - /// rounds to perform. - /// Actions performed by this test: - /// - runs the NovaCycleFold folding scheme for the given FCircuit and n_steps times - /// - generates a DeciderEth proof, and executes it through the EVM - /// - modifies the calldata and checks that it does not pass the EVM check - /// - modifies the z_0 and checks that it does not pass the EVM check - #[allow(clippy::type_complexity)] - fn nova_cyclefold_solidity_verifier_opt>( - fs_params: (FS_PP, FS_VP), - decider_params: (DECIDER_PP, DECIDER_VP), - z_0: Vec, - n_steps: usize, - ) { - let (decider_pp, decider_vp) = decider_params; - - let f_circuit = FC::new(()).unwrap(); - - let nova_cyclefold_vk = - NovaCycleFoldVerifierKey::from((decider_vp.clone(), f_circuit.state_len())); - - let mut rng = ark_std::rand::rngs::OsRng; - - let mut nova = NOVA::::init(&fs_params, f_circuit, z_0).unwrap(); - for _ in 0..n_steps { - nova.prove_step(&mut rng, FC::ExternalInputs::default(), None) - .unwrap(); - } - - let start = Instant::now(); - let proof = DECIDER::::prove(rng, decider_pp, nova.clone()).unwrap(); - println!("generated Decider proof: {:?}", start.elapsed()); - - let verified = DECIDER::::verify( - decider_vp, - nova.i, - nova.z_0.clone(), - nova.z_i.clone(), - &nova.U_i.get_commitments(), - &nova.u_i.get_commitments(), - &proof, - ) - .unwrap(); - assert!(verified); - - let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk); - - let nova_cyclefold_verifier_bytecode = - compile_solidity(decider_solidity_code, "NovaDecider"); - - for mode in [Explicit, Opaque, OpaqueWithInputs] { - interact_with_contract(&nova_cyclefold_verifier_bytecode, &nova, &proof, mode); - } - } - - /// Given an `FCircuit` type and initial IVC state `z_0`, this function tests the `NovaCycleFold` - /// verifier with a few different folding steps. - fn nova_cyclefold_solidity_verifier_test>(z_0: Vec) { - let (nova_params, decider_params) = init_params::(); - for num_steps in [2, 3] { - nova_cyclefold_solidity_verifier_opt::( - nova_params.clone(), - decider_params.clone(), - z_0.clone(), - num_steps, - ) - } - } - - #[test] - fn nova_cyclefold_solidity_verifier_single_input() { - nova_cyclefold_solidity_verifier_test::>(vec![Fr::from(3_u32)]); - } - - #[test] - fn nova_cyclefold_solidity_verifier_multi_input() { - nova_cyclefold_solidity_verifier_test::>(vec![Fr::from(1_u32); 5]); - } -} diff --git a/solidity-verifiers/templates/groth16_verifier.askama.sol b/solidity-verifiers/templates/groth16_verifier.askama.sol deleted file mode 100644 index e8e12035c..000000000 --- a/solidity-verifiers/templates/groth16_verifier.askama.sol +++ /dev/null @@ -1,169 +0,0 @@ -/* - Copyright 2021 0KIMS association. - - * `solidity-verifiers` added comment - This file is a template built out of [snarkJS](https://github.com/iden3/snarkjs) groth16 verifier. - See the original ejs template [here](https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs) - * - - snarkJS is a free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - snarkJS is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public - License for more details. - - You should have received a copy of the GNU General Public License - along with snarkJS. If not, see . -*/ - -contract Groth16Verifier { - // Scalar field size - uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - // Base field size - uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; - - // Verification Key data - uint256 constant alphax = {{ vkey_alpha_g1.0[0] }}; - uint256 constant alphay = {{ vkey_alpha_g1.0[1] }}; - uint256 constant betax1 = {{ vkey_beta_g2.0[0][1] }}; - uint256 constant betax2 = {{ vkey_beta_g2.0[0][0] }}; - uint256 constant betay1 = {{ vkey_beta_g2.0[1][1] }}; - uint256 constant betay2 = {{ vkey_beta_g2.0[1][0] }}; - uint256 constant gammax1 = {{ vkey_gamma_g2.0[0][1] }}; - uint256 constant gammax2 = {{ vkey_gamma_g2.0[0][0] }}; - uint256 constant gammay1 = {{ vkey_gamma_g2.0[1][1] }}; - uint256 constant gammay2 = {{ vkey_gamma_g2.0[1][0] }}; - uint256 constant deltax1 = {{ vkey_delta_g2.0[0][1] }}; - uint256 constant deltax2 = {{ vkey_delta_g2.0[0][0] }}; - uint256 constant deltay1 = {{ vkey_delta_g2.0[1][1] }}; - uint256 constant deltay2 = {{ vkey_delta_g2.0[1][0] }}; - - {% for (i, point) in gamma_abc_g1.iter().enumerate() %} - uint256 constant IC{{i}}x = {{ point.0[0] }}; - uint256 constant IC{{i}}y = {{ point.0[1] }}; - {% endfor %} - - // Memory data - uint16 constant pVk = 0; - uint16 constant pPairing = 128; - - uint16 constant pLastMem = 896; - - function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[{{ gamma_abc_len - 1 }}] calldata _pubSignals) public view returns (bool) { - assembly { - function checkField(v) { - if iszero(lt(v, r)) { - mstore(0, 0) - return(0, 0x20) - } - } - - // G1 function to multiply a G1 value(x,y) to value in an address - function g1_mulAccC(pR, x, y, s) { - let success - let mIn := mload(0x40) - mstore(mIn, x) - mstore(add(mIn, 32), y) - mstore(add(mIn, 64), s) - - success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) - - if iszero(success) { - mstore(0, 0) - return(0, 0x20) - } - - mstore(add(mIn, 64), mload(pR)) - mstore(add(mIn, 96), mload(add(pR, 32))) - - success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) - - if iszero(success) { - mstore(0, 0) - return(0, 0x20) - } - } - - function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { - let _pPairing := add(pMem, pPairing) - let _pVk := add(pMem, pVk) - - mstore(_pVk, IC0x) - mstore(add(_pVk, 32), IC0y) - - // Compute the linear combination vk_x - {% for (i, _) in gamma_abc_g1.iter().enumerate() %} - {% if loop.first -%} - {%- else -%} - g1_mulAccC(_pVk, IC{{i}}x, IC{{i}}y, calldataload(add(pubSignals, {{(i-1)*32}}))) - {%- endif -%} - {% endfor %} - - // -A - mstore(_pPairing, calldataload(pA)) - mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) - - // B - mstore(add(_pPairing, 64), calldataload(pB)) - mstore(add(_pPairing, 96), calldataload(add(pB, 32))) - mstore(add(_pPairing, 128), calldataload(add(pB, 64))) - mstore(add(_pPairing, 160), calldataload(add(pB, 96))) - - // alpha1 - mstore(add(_pPairing, 192), alphax) - mstore(add(_pPairing, 224), alphay) - - // beta2 - mstore(add(_pPairing, 256), betax1) - mstore(add(_pPairing, 288), betax2) - mstore(add(_pPairing, 320), betay1) - mstore(add(_pPairing, 352), betay2) - - // vk_x - mstore(add(_pPairing, 384), mload(add(pMem, pVk))) - mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) - - - // gamma2 - mstore(add(_pPairing, 448), gammax1) - mstore(add(_pPairing, 480), gammax2) - mstore(add(_pPairing, 512), gammay1) - mstore(add(_pPairing, 544), gammay2) - - // C - mstore(add(_pPairing, 576), calldataload(pC)) - mstore(add(_pPairing, 608), calldataload(add(pC, 32))) - - // delta2 - mstore(add(_pPairing, 640), deltax1) - mstore(add(_pPairing, 672), deltax2) - mstore(add(_pPairing, 704), deltay1) - mstore(add(_pPairing, 736), deltay2) - - - let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) - - isOk := and(success, mload(_pPairing)) - } - - let pMem := mload(0x40) - mstore(0x40, add(pMem, pLastMem)) - - // Validate that all evaluations ∈ F - {% for (i, _) in gamma_abc_g1.iter().enumerate() %} - checkField(calldataload(add(_pubSignals, {{i*32}}))) - {% endfor %} - - // Validate all evaluations - let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) - - mstore(0, isValid) - - return(0, 0x20) - } - } -} diff --git a/solidity-verifiers/templates/header_template.askama.sol b/solidity-verifiers/templates/header_template.askama.sol deleted file mode 100644 index a9f6683be..000000000 --- a/solidity-verifiers/templates/header_template.askama.sol +++ /dev/null @@ -1,4 +0,0 @@ -{{ sdpx }} -{{ pragma_version }} - -{{template}} \ No newline at end of file diff --git a/solidity-verifiers/templates/kzg10_verifier.askama.sol b/solidity-verifiers/templates/kzg10_verifier.askama.sol deleted file mode 100644 index d55802e6e..000000000 --- a/solidity-verifiers/templates/kzg10_verifier.askama.sol +++ /dev/null @@ -1,275 +0,0 @@ -/** - * @author Privacy and Scaling Explorations team - pse.dev - * @dev Contains utility functions for ops in BN254; in G_1 mostly. - * @notice Forked from https://github.com/weijiekoh/libkzg. - * Among others, a few of the changes we did on this fork were: - * - Templating the pragma version - * - Removing type wrappers and use uints instead - * - Performing changes on arg types - * - Update some of the `require` statements - * - Use the bn254 scalar field instead of checking for overflow on the babyjub prime - * - In batch checking, we compute auxiliary polynomials and their commitments at the same time. - */ -contract KZG10Verifier { - - // prime of field F_p over which y^2 = x^3 + 3 is defined - uint256 public constant BN254_PRIME_FIELD = - 21888242871839275222246405745257275088696311157297823662689037894645226208583; - uint256 public constant BN254_SCALAR_FIELD = - 21888242871839275222246405745257275088548364400416034343698204186575808495617; - - /** - * @notice Performs scalar multiplication in G_1. - * @param p G_1 point to multiply - * @param s Scalar to multiply by - * @return r G_1 point p multiplied by scalar s - */ - function mulScalar(uint256[2] memory p, uint256 s) internal view returns (uint256[2] memory r) { - uint256[3] memory input; - input[0] = p[0]; - input[1] = p[1]; - input[2] = s; - bool success; - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40) - switch success - case 0 { invalid() } - } - require(success, "bn254: scalar mul failed"); - } - - /** - * @notice Negates a point in G_1. - * @param p G_1 point to negate - * @return uint256[2] G_1 point -p - */ - function negate(uint256[2] memory p) internal pure returns (uint256[2] memory) { - if (p[0] == 0 && p[1] == 0) { - return p; - } - return [p[0], BN254_PRIME_FIELD - (p[1] % BN254_PRIME_FIELD)]; - } - - /** - * @notice Adds two points in G_1. - * @param p1 G_1 point 1 - * @param p2 G_1 point 2 - * @return r G_1 point p1 + p2 - */ - function add(uint256[2] memory p1, uint256[2] memory p2) internal view returns (uint256[2] memory r) { - bool success; - uint256[4] memory input = [p1[0], p1[1], p2[0], p2[1]]; - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40) - switch success - case 0 { invalid() } - } - - require(success, "bn254: point add failed"); - } - - /** - * @notice Computes the pairing check e(p1, p2) * e(p3, p4) == 1 - * @dev Note that G_2 points a*i + b are encoded as two elements of F_p, (a, b) - * @param a_1 G_1 point 1 - * @param a_2 G_2 point 1 - * @param b_1 G_1 point 2 - * @param b_2 G_2 point 2 - * @return result true if pairing check is successful - */ - function pairing(uint256[2] memory a_1, uint256[2][2] memory a_2, uint256[2] memory b_1, uint256[2][2] memory b_2) - internal - view - returns (bool result) - { - uint256[12] memory input = [ - a_1[0], - a_1[1], - a_2[0][1], // imaginary part first - a_2[0][0], - a_2[1][1], // imaginary part first - a_2[1][0], - b_1[0], - b_1[1], - b_2[0][1], // imaginary part first - b_2[0][0], - b_2[1][1], // imaginary part first - b_2[1][0] - ]; - - uint256[1] memory out; - bool success; - - assembly { - success := staticcall(sub(gas(), 2000), 8, input, 0x180, out, 0x20) - switch success - case 0 { invalid() } - } - - require(success, "bn254: pairing failed"); - - return out[0] == 1; - } - - uint256[2] G_1 = [ - {{ g1.0[0] }}, - {{ g1.0[1] }} - ]; - uint256[2][2] G_2 = [ - [ - {{ g2.0[0][0] }}, - {{ g2.0[0][1] }} - ], - [ - {{ g2.0[1][0] }}, - {{ g2.0[1][1] }} - ] - ]; - uint256[2][2] VK = [ - [ - {{ vk.0[0][0] }}, - {{ vk.0[0][1] }} - ], - [ - {{ vk.0[1][0] }}, - {{ vk.0[1][1] }} - ] - ]; - - {% if g1_crs_len>0 %} // only enabled if g1_crs_len>0, for batch_check - uint256[2][{{ g1_crs_len }}] G1_CRS = [ - {%- for (i, point) in g1_crs.iter().enumerate() %} - [ - {{ point.0[0] }}, - {{ point.0[1] }} - {% if loop.last -%} - ] - {%- else -%} - ], - {%- endif -%} - {% endfor -%} - ]; - {%~ endif %} - - /** - * @notice Verifies a single point evaluation proof. Function name follows `ark-poly`. - * @dev To avoid ops in G_2, we slightly tweak how the verification is done. - * @param c G_1 point commitment to polynomial. - * @param pi G_1 point proof. - * @param x Value to prove evaluation of polynomial at. - * @param y Evaluation poly(x). - * @return result Indicates if KZG proof is correct. - */ - function check(uint256[2] calldata c, uint256[2] calldata pi, uint256 x, uint256 y) - public - view - returns (bool result) - { - // - // we want to: - // 1. avoid gas intensive ops in G2 - // 2. format the pairing check in line with what the evm opcode expects. - // - // we can do this by tweaking the KZG check to be: - // - // e(pi, vk - x * g2) = e(c - y * g1, g2) [initial check] - // e(pi, vk - x * g2) * e(c - y * g1, g2)^{-1} = 1 - // e(pi, vk - x * g2) * e(-c + y * g1, g2) = 1 [bilinearity of pairing for all subsequent steps] - // e(pi, vk) * e(pi, -x * g2) * e(-c + y * g1, g2) = 1 - // e(pi, vk) * e(-x * pi, g2) * e(-c + y * g1, g2) = 1 - // e(pi, vk) * e(x * -pi - c + y * g1, g2) = 1 [done] - // |_ rhs_pairing _| - // - uint256[2] memory rhs_pairing = - add(mulScalar(negate(pi), x), add(negate(c), mulScalar(G_1, y))); - return pairing(pi, VK, rhs_pairing, G_2); - } - - function evalPolyAt(uint256[] memory _coefficients, uint256 _index) public pure returns (uint256) { - uint256 m = BN254_SCALAR_FIELD; - uint256 result = 0; - uint256 powerOfX = 1; - - for (uint256 i = 0; i < _coefficients.length; i++) { - uint256 coeff = _coefficients[i]; - assembly { - result := addmod(result, mulmod(powerOfX, coeff, m), m) - powerOfX := mulmod(powerOfX, _index, m) - } - } - return result; - } - - {% if g1_crs_len>0 %} // only enabled if g1_crs_len>0, for batch_check - /** - * @notice Ensures that z(x) == 0 and l(x) == y for all x in x_vals and y in y_vals. It returns the commitment to z(x) and l(x). - * @param z_coeffs coefficients of the zero polynomial z(x) = (x - x_1)(x - x_2)...(x - x_n). - * @param l_coeffs coefficients of the lagrange polynomial l(x). - * @param x_vals x values to evaluate the polynomials at. - * @param y_vals y values to which l(x) should evaluate to. - * @return uint256[2] commitment to z(x). - * @return uint256[2] commitment to l(x). - */ - function checkAndCommitAuxPolys( - uint256[] memory z_coeffs, - uint256[] memory l_coeffs, - uint256[] memory x_vals, - uint256[] memory y_vals - ) public view returns (uint256[2] memory, uint256[2] memory) { - // z(x) is of degree len(x_vals), it is a product of linear polynomials (x - x_i) - // l(x) is of degree len(x_vals) - 1 - uint256[2] memory z_commit; - uint256[2] memory l_commit; - for (uint256 i = 0; i < x_vals.length; i++) { - z_commit = add(z_commit, mulScalar(G1_CRS[i], z_coeffs[i])); // update commitment to z(x) - l_commit = add(l_commit, mulScalar(G1_CRS[i], l_coeffs[i])); // update commitment to l(x) - - uint256 eval_z = evalPolyAt(z_coeffs, x_vals[i]); - uint256 eval_l = evalPolyAt(l_coeffs, x_vals[i]); - - require(eval_z == 0, "checkAndCommitAuxPolys: wrong zero poly"); - require(eval_l == y_vals[i], "checkAndCommitAuxPolys: wrong lagrange poly"); - } - // z(x) has len(x_vals) + 1 coeffs, we add to the commitment the last coeff of z(x) - z_commit = add(z_commit, mulScalar(G1_CRS[z_coeffs.length - 1], z_coeffs[z_coeffs.length - 1])); - - return (z_commit, l_commit); - } - - /** - * @notice Verifies a batch of point evaluation proofs. Function name follows `ark-poly`. - * @dev To avoid ops in G_2, we slightly tweak how the verification is done. - * @param c G1 point commitment to polynomial. - * @param pi G2 point proof. - * @param x_vals Values to prove evaluation of polynomial at. - * @param y_vals Evaluation poly(x). - * @param l_coeffs Coefficients of the lagrange polynomial. - * @param z_coeffs Coefficients of the zero polynomial z(x) = (x - x_1)(x - x_2)...(x - x_n). - * @return result Indicates if KZG proof is correct. - */ - function batchCheck( - uint256[2] calldata c, - uint256[2][2] calldata pi, - uint256[] calldata x_vals, - uint256[] calldata y_vals, - uint256[] calldata l_coeffs, - uint256[] calldata z_coeffs - ) public view returns (bool result) { - // - // we want to: - // 1. avoid gas intensive ops in G2 - // 2. format the pairing check in line with what the evm opcode expects. - // - // we can do this by tweaking the KZG check to be: - // - // e(z(r) * g1, pi) * e(g1, l(r) * g2) = e(c, g2) [initial check] - // e(z(r) * g1, pi) * e(l(r) * g1, g2) * e(c, g2)^{-1} = 1 [bilinearity of pairing] - // e(z(r) * g1, pi) * e(l(r) * g1 - c, g2) = 1 [done] - // - (uint256[2] memory z_commit, uint256[2] memory l_commit) = - checkAndCommitAuxPolys(z_coeffs, l_coeffs, x_vals, y_vals); - uint256[2] memory neg_commit = negate(c); - return pairing(z_commit, pi, add(l_commit, neg_commit), G_2); - } - {%~ endif %} -} diff --git a/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol b/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol deleted file mode 100644 index a7f61fbad..000000000 --- a/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol +++ /dev/null @@ -1,230 +0,0 @@ -/* - Sonobe's Nova + CycleFold decider verifier. - Joint effort by 0xPARC & PSE. - - More details at https://github.com/privacy-scaling-explorations/sonobe - Usage and design documentation at https://privacy-scaling-explorations.github.io/sonobe-docs/ - - Uses the https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs - Groth16 verifier implementation and a KZG10 Solidity template adapted from - https://github.com/weijiekoh/libkzg. - Additionally we implement the NovaDecider contract, which combines the - Groth16 and KZG10 verifiers to verify the zkSNARK proofs coming from - Nova+CycleFold folding. -*/ - - -/* =============================== */ -/* KZG10 verifier methods */ -{{ kzg10_verifier }} - -/* =============================== */ -/* Groth16 verifier methods */ -{{ groth16_verifier }} - - -/* =============================== */ -/* Nova+CycleFold Decider verifier */ -/** - * @notice Computes the decomposition of a `uint256` into num_limbs limbs of bits_per_limb bits each. - * @dev Compatible with sonobe::folding-schemes::folding::circuits::nonnative::nonnative_field_to_field_elements. - */ -library LimbsDecomposition { - function decompose(uint256 x) internal pure returns (uint256[{{num_limbs}}] memory) { - uint256[{{num_limbs}}] memory limbs; - for (uint8 i = 0; i < {{num_limbs}}; i++) { - limbs[i] = (x >> ({{bits_per_limb}} * i)) & ((1 << {{bits_per_limb}}) - 1); - } - return limbs; - } -} - -/** - * @author PSE & 0xPARC - * @title Interface for the NovaDecider contract hiding proof details. - * @dev This interface enables calling the verifyNovaProof function without exposing the proof details. - */ -interface OpaqueDecider { - /** - * @notice Verifies a Nova+CycleFold proof given initial and final IVC states, number of steps and the rest proof inputs concatenated. - * @dev This function should simply reorganize arguments and pass them to the proper verification function. - */ - function verifyOpaqueNovaProofWithInputs( - uint256 steps, // number of folded steps (i) - uint256[{{ z_len }}] calldata initial_state, // initial IVC state (z0) - uint256[{{ z_len }}] calldata final_state, // IVC state after i steps (zi) - uint256[25] calldata proof // the rest of the decider inputs - ) external view returns (bool); - - /** - * @notice Verifies a Nova+CycleFold proof given all the proof inputs collected in a single array. - * @dev This function should simply reorganize arguments and pass them to the proper verification function. - */ - function verifyOpaqueNovaProof(uint256[{{ 26 + z_len * 2 }}] calldata proof) external view returns (bool); -} - -/** - * @author PSE & 0xPARC - * @title NovaDecider contract, for verifying Nova IVC SNARK proofs. - * @dev This is an askama template which, when templated, features a Groth16 and KZG10 verifiers from which this contract inherits. - */ -contract NovaDecider is Groth16Verifier, KZG10Verifier, OpaqueDecider { - /** - * @notice Computes the linear combination of a and b with r as the coefficient. - * @dev All ops are done mod the BN254 scalar field prime - */ - function rlc(uint256 a, uint256 r, uint256 b) internal pure returns (uint256 result) { - assembly { - result := addmod(a, mulmod(r, b, BN254_SCALAR_FIELD), BN254_SCALAR_FIELD) - } - } - - /** - * @notice Verifies a nova cyclefold proof consisting of two KZG proofs and of a groth16 proof. - * @dev The selector of this function is "dynamic", since it depends on `z_len`. - */ - function verifyNovaProof( - // inputs are grouped to prevent errors due stack too deep - uint256[{{ 1 + z_len * 2 }}] calldata i_z0_zi, // [i, z0, zi] where |z0| == |zi| - uint256[4] calldata U_i_cmW_U_i_cmE, // [U_i_cmW[2], U_i_cmE[2]] - uint256[2] calldata u_i_cmW, // [u_i_cmW[2]] - uint256[3] calldata cmT_r, // [cmT[2], r] - uint256[2] calldata pA, // groth16 - uint256[2][2] calldata pB, // groth16 - uint256[2] calldata pC, // groth16 - uint256[4] calldata challenge_W_challenge_E_kzg_evals, // [challenge_W, challenge_E, eval_W, eval_E] - uint256[2][2] calldata kzg_proof // [proof_W, proof_E] - ) public view returns (bool) { - - require(i_z0_zi[0] >= 2, "Folding: the number of folded steps should be at least 2"); - - // from gamma_abc_len, we subtract 1. - uint256[{{ public_inputs_len - 1 }}] memory public_inputs; - - public_inputs[0] = {{pp_hash}}; - public_inputs[1] = i_z0_zi[0]; - - for (uint i = 0; i < {{ z_len * 2 }}; i++) { - public_inputs[2 + i] = i_z0_zi[1 + i]; - } - - { - // U_i.cmW + r * u_i.cmW - uint256[2] memory mulScalarPoint = super.mulScalar([u_i_cmW[0], u_i_cmW[1]], cmT_r[2]); - uint256[2] memory cmW = super.add([U_i_cmW_U_i_cmE[0], U_i_cmW_U_i_cmE[1]], mulScalarPoint); - - { - uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]); - uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]); - - for (uint8 k = 0; k < {{num_limbs}}; k++) { - public_inputs[{{ z_len * 2 + 2 }} + k] = cmW_x_limbs[k]; - public_inputs[{{ z_len * 2 + 2 + num_limbs }} + k] = cmW_y_limbs[k]; - } - } - - require(this.check(cmW, kzg_proof[0], challenge_W_challenge_E_kzg_evals[0], challenge_W_challenge_E_kzg_evals[2]), "KZG: verifying proof for challenge W failed"); - } - - { - // U_i.cmE + r * cmT - uint256[2] memory mulScalarPoint = super.mulScalar([cmT_r[0], cmT_r[1]], cmT_r[2]); - uint256[2] memory cmE = super.add([U_i_cmW_U_i_cmE[2], U_i_cmW_U_i_cmE[3]], mulScalarPoint); - - { - uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]); - uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]); - - for (uint8 k = 0; k < {{num_limbs}}; k++) { - public_inputs[{{ z_len * 2 + 2 + num_limbs * 2 }} + k] = cmE_x_limbs[k]; - public_inputs[{{ z_len * 2 + 2 + num_limbs * 3 }} + k] = cmE_y_limbs[k]; - } - } - - require(this.check(cmE, kzg_proof[1], challenge_W_challenge_E_kzg_evals[1], challenge_W_challenge_E_kzg_evals[3]), "KZG: verifying proof for challenge E failed"); - } - - { - // add challenges - public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0]; - public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1]; - public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2]; - public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3]; - - uint256[{{num_limbs}}] memory cmT_x_limbs; - uint256[{{num_limbs}}] memory cmT_y_limbs; - - cmT_x_limbs = LimbsDecomposition.decompose(cmT_r[0]); - cmT_y_limbs = LimbsDecomposition.decompose(cmT_r[1]); - - for (uint8 k = 0; k < {{num_limbs}}; k++) { - public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k]; - public_inputs[{{ z_len * 2 + 2 + num_limbs * 5 }} + 4 + k] = cmT_y_limbs[k]; - } - - bool success_g16 = this.verifyProof(pA, pB, pC, public_inputs); - require(success_g16 == true, "Groth16: verifying proof failed"); - } - - return(true); - } - - /** - * @notice Verifies a Nova+CycleFold proof given initial and final IVC states, number of steps and the rest proof inputs concatenated. - * @dev Simply reorganization of arguments and call to the `verifyNovaProof` function. - */ - function verifyOpaqueNovaProofWithInputs( - uint256 steps, - uint256[{{ z_len }}] calldata initial_state, - uint256[{{ z_len }}] calldata final_state, - uint256[25] calldata proof - ) public override view returns (bool) { - uint256[1 + 2 * {{ z_len }}] memory i_z0_zi; - i_z0_zi[0] = steps; - for (uint256 i = 0; i < {{ z_len }}; i++) { - i_z0_zi[i + 1] = initial_state[i]; - i_z0_zi[i + 1 + {{ z_len }}] = final_state[i]; - } - - uint256[4] memory U_i_cmW_U_i_cmE = [proof[0], proof[1], proof[2], proof[3]]; - uint256[2] memory u_i_cmW = [proof[4], proof[5]]; - uint256[3] memory cmT_r = [proof[6], proof[7], proof[8]]; - uint256[2] memory pA = [proof[9], proof[10]]; - uint256[2][2] memory pB = [[proof[11], proof[12]], [proof[13], proof[14]]]; - uint256[2] memory pC = [proof[15], proof[16]]; - uint256[4] memory challenge_W_challenge_E_kzg_evals = [proof[17], proof[18], proof[19], proof[20]]; - uint256[2][2] memory kzg_proof = [[proof[21], proof[22]], [proof[23], proof[24]]]; - - return this.verifyNovaProof( - i_z0_zi, - U_i_cmW_U_i_cmE, - u_i_cmW, - cmT_r, - pA, - pB, - pC, - challenge_W_challenge_E_kzg_evals, - kzg_proof - ); - } - - /** - * @notice Verifies a Nova+CycleFold proof given all proof inputs concatenated. - * @dev Simply reorganization of arguments and call to the `verifyNovaProof` function. - */ - function verifyOpaqueNovaProof(uint256[{{ 26 + z_len * 2 }}] calldata proof) public override view returns (bool) { - uint256[{{ z_len }}] memory z0; - uint256[{{ z_len }}] memory zi; - for (uint256 i = 0; i < {{ z_len }}; i++) { - z0[i] = proof[i + 1]; - zi[i] = proof[i + 1 + {{ z_len }}]; - } - - uint256[25] memory extracted_proof; - for (uint256 i = 0; i < 25; i++) { - extracted_proof[i] = proof[{{ 1 + 2 * z_len }} + i]; - } - - return this.verifyOpaqueNovaProofWithInputs(proof[0], z0, zi, extracted_proof); - } -} From 5c2632b08fd306a5ca6c32ecd78f75c925395ad1 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 6 Oct 2025 03:37:15 +0800 Subject: [PATCH 02/99] Refactor: new crate for traits --- Cargo.toml | 2 + crates/traits/Cargo.toml | 21 ++++ crates/traits/src/lib.rs | 245 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 crates/traits/Cargo.toml create mode 100644 crates/traits/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index d357cd2ec..8a8354703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/traits", ] resolver = "2" @@ -76,3 +77,4 @@ ark-std = { version = "^0.5.0", default-features = false } ark-vesta = { version = "^0.5.0" } # Local crates +sonobe-traits = { path = "crates/traits" } \ No newline at end of file diff --git a/crates/traits/Cargo.toml b/crates/traits/Cargo.toml new file mode 100644 index 000000000..f2ee8164e --- /dev/null +++ b/crates/traits/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sonobe-traits" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +ark-ec = { workspace = true } +ark-ff = { workspace = true, features = ["asm"] } +ark-std = { workspace = true, features = ["getrandom"] } +ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } +ark-relations = { workspace = true } +ark-r1cs-std = { workspace = true } + +[features] +default = ["parallel"] +parallel = [ + "ark-relations/parallel", + "ark-r1cs-std/parallel", +] \ No newline at end of file diff --git a/crates/traits/src/lib.rs b/crates/traits/src/lib.rs new file mode 100644 index 000000000..1106def43 --- /dev/null +++ b/crates/traits/src/lib.rs @@ -0,0 +1,245 @@ +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{ + short_weierstrass::{Projective, SWCurveConfig}, + AffineRepr, CurveGroup, PrimeGroup, +}; +use ark_ff::{BigInteger, Field as ArkField, Fp, FpConfig, One, PrimeField, Zero}; +use ark_r1cs_std::{ + fields::{fp::FpVar, FieldVar}, + groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; + +pub type CF1 = ::ScalarField; +pub type CF2 = <::BaseField as ArkField>::BasePrimeField; + +pub trait Dummy { + fn dummy(cfg: Cfg) -> Self; +} + +impl Dummy for Vec { + fn dummy(cfg: usize) -> Self { + vec![Default::default(); cfg] + } +} + +impl Dummy<()> for T { + fn dummy(_: ()) -> Self { + Default::default() + } +} + +/// Converts a value `self` into a vector of field elements, ordered in the same +/// way as how a variable of type `Var` would be represented *natively* in the +/// circuit. +/// +/// This is useful for the verifier to compute the public inputs. +pub trait Inputize { + fn inputize(&self) -> Vec; +} + +/// Converts a value `self` into a vector of field elements, ordered in the same +/// way as how a variable of type `Var` would be represented *non-natively* in +/// the circuit. +/// +/// This is useful for the verifier to compute the public inputs. +/// +/// Note that we require this trait because we need to distinguish between some +/// data types that are represented both natively and non-natively in-circuit +/// (e.g., field elements can have type `FpVar` and `NonNativeUintVar`). +pub trait InputizeNonNative { + fn inputize_nonnative(&self) -> Vec; +} + +impl> Inputize for [T] { + fn inputize(&self) -> Vec { + self.iter().flat_map(Inputize::::inputize).collect() + } +} + +impl> InputizeNonNative for [T] { + fn inputize_nonnative(&self) -> Vec { + self.iter() + .flat_map(InputizeNonNative::::inputize_nonnative) + .collect() + } +} + +impl, const N: usize> Inputize for Fp { + /// Returns the internal representation in the same order as how the value + /// is allocated in `FpVar::new_input`. + fn inputize(&self) -> Vec { + vec![*self] + } +} + +impl> Inputize for Projective

{ + /// Returns the internal representation in the same order as how the value + /// is allocated in `ProjectiveVar::new_input`. + fn inputize(&self) -> Vec { + let affine = self.into_affine(); + match affine.xy() { + Some((x, y)) => vec![x, y, One::one()], + None => vec![Zero::zero(), One::one(), Zero::zero()], + } + } +} + +impl InputizeNonNative for P { + /// Returns the internal representation in the same order as how the value + /// is allocated in `NonNativeUintVar::new_input`. + fn inputize_nonnative(&self) -> Vec { + self.into_bigint() + .to_bits_le() + .chunks(F::BITS_PER_LIMB) + .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) + .collect() + } +} + +impl> InputizeNonNative + for Projective

+{ + /// Returns the internal representation in the same order as how the value + /// is allocated in `NonNativeAffineVar::new_input`. + fn inputize_nonnative(&self) -> Vec { + let affine = self.into_affine(); + let (x, y) = affine.xy().unwrap_or_default(); + + [x, y].inputize_nonnative() + } +} + +/// `Field` trait is a wrapper around `PrimeField` that also includes the +/// necessary bounds for the field to be used conveniently in folding schemes. +pub trait Field: + PrimeField + Absorb + AbsorbNonNative + Inputize +{ + const BITS_PER_LIMB: usize; + /// The in-circuit variable type for this field. + type Var: FieldVar; +} + +impl, const N: usize> Field for Fp { + const BITS_PER_LIMB: usize = 55; // TODO: make this configurable + type Var = FpVar; +} + +/// `Curve` trait is a wrapper around `CurveGroup` that also includes the +/// necessary bounds for the curve to be used conveniently in folding schemes. +pub trait Curve: + CurveGroup + + AbsorbNonNative + + Inputize + + InputizeNonNative +{ + /// The in-circuit variable type for this curve. + type Var: CurveVar; +} + +impl> Curve for Projective

{ + type Var = ProjectiveVar>; +} + +/// An interface for objects that can be absorbed by a `Transcript`. +/// +/// Matches `Absorb` in `ark-crypto-primitives`. +pub trait AbsorbNonNative { + /// Converts the object into field elements that can be absorbed by a `Transcript`. + /// Append the list to `dest` + fn to_native_sponge_field_elements(&self, dest: &mut Vec); + + /// Converts the object into field elements that can be absorbed by a `Transcript`. + /// Return the list as `Vec` + fn to_native_sponge_field_elements_as_vec(&self) -> Vec { + let mut result = Vec::new(); + self.to_native_sponge_field_elements(&mut result); + result + } +} + +/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field +/// is `F`. +/// +/// Matches `AbsorbGadget` in `ark-crypto-primitives`. +pub trait AbsorbNonNativeGadget { + /// Converts the object into field elements that can be absorbed by a `TranscriptVar`. + fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError>; +} + +impl AbsorbNonNative for [T] { + fn to_native_sponge_field_elements(&self, dest: &mut Vec) { + for t in self.iter() { + t.to_native_sponge_field_elements(dest); + } + } +} + +impl> AbsorbNonNativeGadget for &T { + fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { + T::to_native_sponge_field_elements(self) + } +} + +impl> AbsorbNonNativeGadget for [T] { + fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { + let mut result = Vec::new(); + for t in self.iter() { + result.extend(t.to_native_sponge_field_elements()?); + } + Ok(result) + } +} + +impl, const N: usize> AbsorbNonNative for Fp { + fn to_native_sponge_field_elements(&self, dest: &mut Vec) { + let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; + let num_limbs = (Fp::::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb); + + let mut limbs = self + .into_bigint() + .to_bits_le() + .chunks(bits_per_limb) + .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) + .collect::>(); + limbs.resize(num_limbs, F::zero()); + + dest.extend(&limbs) + } +} + +impl> AbsorbNonNative for Projective

{ + fn to_native_sponge_field_elements(&self, dest: &mut Vec) { + let affine = self.into_affine(); + let (x, y) = affine.xy().unwrap_or_default(); + + [x, y].to_native_sponge_field_elements(dest); + } +} + +/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. +/// inside the agmented F' function). +/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying +/// the step. +/// Note that the external inputs for the specific circuit are defined at the implementation of +/// both `FCircuit::ExternalInputs` and `FCircuit::ExternalInputsVar`, where the `Default` trait +/// implementation for the `ExternalInputs` returns the initialized data structure (ie. if the type +/// contains a vector, it is initialized at the expected length). +pub trait FCircuit { + type ExternalInputs; + + /// returns the number of elements in the state of the FCircuit, which corresponds to the + /// FCircuit inputs. + fn state_len(&self) -> usize; + + /// generates the constraints for the step of F for the given z_i + fn generate_step_constraints( + // this method uses self, so that each FCircuit implementation (and different frontends) + // can hold a state if needed to store data to generate the constraints. + &self, + cs: ConstraintSystemRef, + i: usize, + z_i: Vec>, + external_inputs: Self::ExternalInputs, // inputs that are not part of the state + ) -> Result>, SynthesisError>; +} From 2b5ebb6ecd9da22a5cc7aa86e329f5dea36c0f9d Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 6 Oct 2025 03:37:48 +0800 Subject: [PATCH 03/99] Refactor: new crate for primitives --- Cargo.toml | 2 + crates/primitives/Cargo.toml | 36 + .../src/arithmetizations/ccs/circuits.rs | 36 + .../src/arithmetizations/ccs/mod.rs | 160 +++ crates/primitives/src/arithmetizations/mod.rs | 262 +++++ .../src/arithmetizations/r1cs/circuits.rs | 94 ++ .../src/arithmetizations/r1cs/mod.rs | 205 ++++ crates/primitives/src/commitments/mod.rs | 67 ++ crates/primitives/src/commitments/pedersen.rs | 150 +++ crates/primitives/src/gadgets/math/eq.rs | 24 + crates/primitives/src/gadgets/math/matrix.rs | 68 ++ crates/primitives/src/gadgets/math/mod.rs | 3 + crates/primitives/src/gadgets/math/vector.rs | 31 + crates/primitives/src/gadgets/mod.rs | 2 + .../src/gadgets/nonnative/affine.rs | 202 ++++ .../primitives/src/gadgets/nonnative/mod.rs | 2 + .../primitives/src/gadgets/nonnative/uint.rs | 994 ++++++++++++++++++ crates/primitives/src/lib.rs | 4 + crates/primitives/src/transcripts/mod.rs | 90 ++ crates/primitives/src/transcripts/poseidon.rs | 277 +++++ 20 files changed, 2709 insertions(+) create mode 100644 crates/primitives/Cargo.toml create mode 100644 crates/primitives/src/arithmetizations/ccs/circuits.rs create mode 100644 crates/primitives/src/arithmetizations/ccs/mod.rs create mode 100644 crates/primitives/src/arithmetizations/mod.rs create mode 100644 crates/primitives/src/arithmetizations/r1cs/circuits.rs create mode 100644 crates/primitives/src/arithmetizations/r1cs/mod.rs create mode 100644 crates/primitives/src/commitments/mod.rs create mode 100644 crates/primitives/src/commitments/pedersen.rs create mode 100644 crates/primitives/src/gadgets/math/eq.rs create mode 100644 crates/primitives/src/gadgets/math/matrix.rs create mode 100644 crates/primitives/src/gadgets/math/mod.rs create mode 100644 crates/primitives/src/gadgets/math/vector.rs create mode 100644 crates/primitives/src/gadgets/mod.rs create mode 100644 crates/primitives/src/gadgets/nonnative/affine.rs create mode 100644 crates/primitives/src/gadgets/nonnative/mod.rs create mode 100644 crates/primitives/src/gadgets/nonnative/uint.rs create mode 100644 crates/primitives/src/lib.rs create mode 100644 crates/primitives/src/transcripts/mod.rs create mode 100644 crates/primitives/src/transcripts/poseidon.rs diff --git a/Cargo.toml b/Cargo.toml index 8a8354703..e8fe65f39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/primitives", "crates/traits", ] resolver = "2" @@ -77,4 +78,5 @@ ark-std = { version = "^0.5.0", default-features = false } ark-vesta = { version = "^0.5.0" } # Local crates +sonobe-primitives = { path = "crates/primitives", default-features = false } sonobe-traits = { path = "crates/traits" } \ No newline at end of file diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml new file mode 100644 index 000000000..feca812a6 --- /dev/null +++ b/crates/primitives/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "sonobe-primitives" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } +ark-ec = { workspace = true } +ark-ff = { workspace = true, features = ["asm"] } +ark-std = { workspace = true, features = ["getrandom"] } +ark-poly = { workspace = true } +ark-poly-commit = { workspace = true } +ark-relations = { workspace = true } +ark-r1cs-std = { workspace = true } +ark-serialize = { workspace = true } +num-bigint = { workspace = true, features = ["rand"] } +num-integer = { workspace = true } +rayon = { workspace = true } +thiserror = { workspace = true } + +sonobe-traits = { workspace = true } + +[dev-dependencies] +ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } +ark-pallas = { workspace = true, features = ["curve", "r1cs"] } +ark-vesta = { workspace = true, features = ["r1cs"] } + +[features] +default = ["parallel"] +parallel = [ + "ark-relations/parallel", + "ark-r1cs-std/parallel", + "sonobe-traits/parallel", +] \ No newline at end of file diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs new file mode 100644 index 000000000..f239d691f --- /dev/null +++ b/crates/primitives/src/arithmetizations/ccs/circuits.rs @@ -0,0 +1,36 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + fields::fp::FpVar, +}; +use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_std::borrow::Borrow; + +use super::CCS; +use crate::gadgets::math::matrix::SparseMatrixVar; + +/// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. +#[derive(Debug, Clone)] +pub struct CCSMatricesVar { + // we only need native representation, so the constraint field==F + pub M: Vec>>, +} + +impl AllocVar, F> for CCSMatricesVar { + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + _mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + let M: Vec>> = val + .borrow() + .M + .iter() + .map(|M| SparseMatrixVar::>::new_constant(cs.clone(), M.clone())) + .collect::>()?; + Ok(Self { M }) + }) + } +} diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs new file mode 100644 index 000000000..32357dd01 --- /dev/null +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -0,0 +1,160 @@ +use ark_ff::Field; +use ark_relations::gr1cs::Matrix; +use ark_std::{cfg_into_iter, log2}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::arithmetizations::{Assignments, Error}; + +use super::{r1cs::R1CS, Arith, ArithRelation, ArithSerializer}; + +pub mod circuits; + +/// CCS represents the Customizable Constraint Systems structure defined in +/// the [CCS paper](https://eprint.iacr.org/2023/552) +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct CCS { + /// m: number of rows in M_i (such that M_i \in F^{m, n}) + m: usize, + /// n = |z|, number of cols in M_i + n: usize, + /// l = |io|, size of public input/output + l: usize, + /// t = |M|, number of matrices + pub t: usize, + /// q = |c| = |S|, number of multisets + q: usize, + /// d: max degree in each variable + d: usize, + /// s = log(m), dimension of x + pub s: usize, + + /// vector of matrices + pub M: Vec>, + /// vector of multisets + pub S: Vec>, + /// vector of coefficients + pub c: Vec, +} + +impl CCS { + /// Evaluates the CCS relation at a given vector of assignments `z` + pub fn eval_at_z(&self, z: Assignments) -> Result, Error> { + // Recall that the evaluation of CCS at z is defined as: + // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i * z))$, + // where $\prod$ denotes the Hadamard product. + // + // Below, we manually expand the vector and matrix operations for less + // allocations and better efficiency. + // Specifically, we independently compute each entry of the resulting + // vector, and collect them at the end. + // We parallelize the outer loop over rows (when the `parallel` feature + // is enabled), because `m`, the number of constraints in the CCS, is + // typically large in practice. + Ok(cfg_into_iter!(0..self.m) + .map(|row| { + // The row-th entry of the resulting vector is: + // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i[row] * z))$ + self.S + .iter() + .zip(&self.c) + .map(|(s, &c)| { + // Each term in the sum is: + // $c_j * \prod_{i \in S_j} (M_i[row] * z)$ + c * s + .iter() + .map(|&i| { + // Each factor in the product is $M_i[row] * z$, + // i.e., the dot product of $M_i[row]$ and $z$. + self.M[i][row] + .iter() + .map(|(val, col)| z[*col] * val) + .sum::() + }) + .product::() + }) + .sum() + }) + .collect()) + } +} + +impl Arith for CCS { + #[inline] + fn degree(&self) -> usize { + self.d + } + + #[inline] + fn n_constraints(&self) -> usize { + self.m + } + + #[inline] + fn n_variables(&self) -> usize { + self.n + } + + #[inline] + fn n_public_inputs(&self) -> usize { + self.l + } + + #[inline] + fn n_witnesses(&self) -> usize { + self.n_variables() - self.n_public_inputs() - 1 + } +} + +impl, U: AsRef<[F]>> ArithRelation for CCS { + type Evaluation = Vec; + + fn eval_relation(&self, w: &W, u: &U) -> Result { + self.eval_at_z((F::one(), u.as_ref(), w.as_ref()).into()) + } + + fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { + cfg_into_iter!(e) + .all(|i| i.is_zero()) + .then_some(()) + .ok_or(Error::UnsatisfiedAssignments( + "Evaluation contains non-zero values".into(), + )) + } +} + +impl ArithSerializer for CCS { + fn params_to_le_bytes(&self) -> Vec { + [ + self.l.to_le_bytes(), + self.m.to_le_bytes(), + self.n.to_le_bytes(), + self.t.to_le_bytes(), + self.q.to_le_bytes(), + self.d.to_le_bytes(), + ] + .concat() + } +} + +impl From> for CCS { + fn from(r1cs: R1CS) -> Self { + let m = r1cs.n_constraints(); + let n = r1cs.n_variables(); + CCS { + m, + n, + l: r1cs.n_public_inputs(), + s: log2(m) as usize, + t: 3, + q: 2, + d: r1cs.degree(), + + S: vec![vec![0, 1], vec![2]], + c: vec![F::one(), F::one().neg()], + M: vec![r1cs.A, r1cs.B, r1cs.C], + } + } +} + +// TODO: add back tests \ No newline at end of file diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs new file mode 100644 index 000000000..24adb29e3 --- /dev/null +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -0,0 +1,262 @@ +use std::ops::Index; + +use ark_relations::gr1cs::SynthesisError; +use ark_std::rand::RngCore; +use sonobe_traits::Dummy; +use thiserror::Error; + +pub mod ccs; +pub mod r1cs; + +#[derive(Debug, Error)] +pub enum Error { + #[error("The provided assignments have incorrect shape: {0}")] + MalformedAssignments(String), + #[error("The provided assignments do not satisfy the constraint system: {0}")] + UnsatisfiedAssignments(String), + #[error("Failed to extract constraints from the constraint system: {0}")] + ConstraintExtractionFailure(String), +} + +pub struct Assignments<'a, F> { + pub constant: F, + pub public: &'a [F], + pub private: &'a [F], +} + +impl<'a, F> From<(F, &'a [F], &'a [F])> for Assignments<'a, F> { + fn from((u, x, w): (F, &'a [F], &'a [F])) -> Self { + Self { + constant: u, + public: x, + private: w, + } + } +} + +impl<'a, F> Index for Assignments<'a, F> { + type Output = F; + + fn index(&self, index: usize) -> &Self::Output { + if index == 0 { + &self.constant + } else if index <= self.public.len() { + &self.public[index - 1] + } else { + &self.private[index - 1 - self.public.len()] + } + } +} + +pub struct AssignmentsVar<'a, FV> { + pub constant: FV, + pub public: &'a [FV], + pub private: &'a [FV], +} + +impl<'a, FV> From<(FV, &'a [FV], &'a [FV])> for AssignmentsVar<'a, FV> { + fn from((u, x, w): (FV, &'a [FV], &'a [FV])) -> Self { + Self { + constant: u, + public: x, + private: w, + } + } +} + +impl<'a, FV> Index for AssignmentsVar<'a, FV> { + type Output = FV; + + fn index(&self, index: usize) -> &Self::Output { + if index == 0 { + &self.constant + } else if index <= self.public.len() { + &self.public[index - 1] + } else { + &self.private[index - 1 - self.public.len()] + } + } +} + +/// [`Arith`] is a trait about constraint systems (R1CS, CCS, etc.), where we +/// define methods for getting information about the constraint system. +pub trait Arith: Clone { + /// Returns the degree of the constraint system + fn degree(&self) -> usize; + + /// Returns the number of constraints in the constraint system + fn n_constraints(&self) -> usize; + + /// Returns the number of variables in the constraint system + fn n_variables(&self) -> usize; + + /// Returns the number of public inputs / public IO / instances / statements + /// in the constraint system + fn n_public_inputs(&self) -> usize; + + /// Returns the number of witnesses / secret inputs in the constraint system + fn n_witnesses(&self) -> usize; +} + +/// `ArithRelation` *treats a constraint system as a relation* between a witness +/// of type `W` and a statement / public input / public IO / instance of type +/// `U`, and in this trait, we define the necessary operations on the relation. +/// +/// Note that the same constraint system may support different types of `W` and +/// `U`, and the satisfiability check may vary. +/// +/// For example, both plain R1CS and relaxed R1CS are represented by 3 matrices, +/// but the types of `W` and `U` are different: +/// - The plain R1CS has `W` and `U` as vectors of field elements. +/// +/// `W = w` and `U = x` satisfy R1CS if `Az ∘ Bz = Cz`, where `z = [1, x, w]`. +/// +/// - In Nova, Relaxed R1CS has `W` as [`crate::folding::nova::Witness`], +/// and `U` as [`crate::folding::nova::CommittedInstance`]. +/// +/// `W = (w, e, ...)` and `U = (u, x, ...)` satisfy Relaxed R1CS if +/// `Az ∘ Bz = uCz + e`, where `z = [u, x, w]`. +/// (commitments in `U` are not checked here) +/// +/// Also, `W` and `U` have non-native field elements as their components when +/// used as CycleFold witness and instance. +/// +/// - In ProtoGalaxy, Relaxed R1CS has `W` as [`crate::folding::protogalaxy::Witness`], +/// and `U` as [`crate::folding::protogalaxy::CommittedInstance`]. +/// +/// `W = (w, ...)` and `U = (x, e, β, ...)` satisfy Relaxed R1CS if +/// `e = Σ pow_i(β) v_i`, where `v = Az ∘ Bz - Cz`, `z = [1, x, w]`. +/// (commitments in `U` are not checked here) +/// +/// This is also the case of CCS, where `W` and `U` may be vectors of field +/// elements, [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::lcccs::LCCCS`], +/// or [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::cccs::CCCS`]. +pub trait ArithRelation: Arith { + type Evaluation; + + /// Evaluates the constraint system `self` at witness `w` and instance `u`. + /// Returns the evaluation result. + /// + /// The evaluation result is usually a vector of field elements. + /// For instance: + /// - Evaluating the plain R1CS at `W = w` and `U = x` returns + /// `Az ∘ Bz - Cz`, where `z = [1, x, w]`. + /// + /// - Evaluating the relaxed R1CS in Nova at `W = (w, e, ...)` and + /// `U = (u, x, ...)` returns `Az ∘ Bz - uCz`, where `z = [u, x, w]`. + /// + /// - Evaluating the relaxed R1CS in ProtoGalaxy at `W = (w, ...)` and + /// `U = (x, e, β, ...)` returns `Az ∘ Bz - Cz`, where `z = [1, x, w]`. + /// + /// However, we use `Self::Evaluation` to represent the evaluation result + /// for future extensibility. + fn eval_relation(&self, w: &W, u: &U) -> Result; + + /// Checks if the evaluation result is valid. The witness `w` and instance + /// `u` are also parameters, because the validity check may need information + /// contained in `w` and/or `u`. + /// + /// For instance: + /// - The evaluation `v` of plain R1CS at satisfying `W` and `U` should be + /// an all-zero vector. + /// + /// - The evaluation `v` of relaxed R1CS in Nova at satisfying `W` and `U` + /// should be equal to the error term `e` in the witness. + /// + /// - The evaluation `v` of relaxed R1CS in ProtoGalaxy at satisfying `W` + /// and `U` should satisfy `e = Σ pow_i(β) v_i`, where `e` is the error + /// term in the committed instance. + fn check_evaluation(w: &W, u: &U, v: Self::Evaluation) -> Result<(), Error>; +} + +pub trait Relation { + type Error; + + /// Returns a dummy witness and instance + fn dummy_witness_instance<'a>(&'a self) -> (W, U) + where + W: Dummy<&'a Self>, + U: Dummy<&'a Self>, + { + (W::dummy(self), U::dummy(self)) + } + + /// Checks if witness `w` and instance `u` satisfy the relation `self` + fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error>; +} + +impl> Relation for A { + type Error = Error; + + /// Checks if witness `w` and instance `u` satisfy the constraint system + /// `self` by first computing the evaluation result and then checking the + /// validity of the evaluation result. + /// + /// Used only for testing. + fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error> { + let e = self.eval_relation(w, u)?; + Self::check_evaluation(w, u, e) + } +} + +/// `ArithSerializer` is for serializing constraint systems. +/// +/// Currently we only support converting parameters to bytes, but in the future +/// we may consider implementing methods for serializing the actual data (e.g., +/// R1CS matrices). +pub trait ArithSerializer { + /// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of + /// public inputs, etc, without the matrices/polynomials values. + fn params_to_le_bytes(&self) -> Vec; +} + +/// `ArithSampler` allows sampling random pairs of witness and instance that +/// satisfy the constraint system `self`. +/// +/// This is useful for constructing a zero-knowledge layer for a folding-based +/// IVC. +/// An example of such a layer can be found in Appendix D of the [HyperNova] +/// paper. +/// +/// Note that we use a separate trait for sampling, because this operation may +/// not be supported by all witness-instance pairs. +/// For instance, it is difficult (if not impossible) to do this for `w` and `x` +/// in a plain R1CS. +/// +/// [HyperNova]: https://eprint.iacr.org/2023/573.pdf +pub trait ArithSampler { + fn sample_witness_instance() { + todo!() + } +} +// pub trait ArithSampler: ArithRelation { +// /// Samples a random witness and instance that satisfy the constraint system. +// fn sample_witness_instance>( +// &self, +// params: &CS::ProverParams, +// rng: impl RngCore, +// ) -> Result<(W, U), Error>; +// } + +/// `ArithRelationGadget` defines the in-circuit counterparts of operations +/// specified in `ArithRelation` on constraint systems. +pub trait ArithRelationGadget { + type Evaluation; + + /// Evaluates the constraint system `self` at witness `w` and instance `u`. + /// Returns the evaluation result. + fn eval_relation(&self, w: &WVar, u: &UVar) -> Result; + + /// Generates constraints for enforcing that witness `w` and instance `u` + /// satisfy the constraint system `self` by first computing the evaluation + /// result and then checking the validity of the evaluation result. + fn enforce_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError> { + let e = self.eval_relation(w, u)?; + Self::enforce_evaluation(w, u, e) + } + + /// Generates constraints for enforcing that the evaluation result is valid. + /// The witness `w` and instance `u` are also parameters, because the + /// validity check may need information contained in `w` and/or `u`. + fn enforce_evaluation(w: &WVar, u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError>; +} diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs new file mode 100644 index 000000000..9aadc648b --- /dev/null +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -0,0 +1,94 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; +use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_std::{borrow::Borrow, marker::PhantomData, One}; + +use super::R1CS; +use crate::{ + arithmetizations::{ArithRelationGadget, AssignmentsVar}, + gadgets::math::{ + eq::EquivalenceGadget, + matrix::{MatrixGadget, SparseMatrixVar}, + vector::VectorGadget, + }, +}; + +/// An in-circuit representation of the `R1CS` struct. +/// +/// `M` is for the modulo operation involved in the satisfiability check when +/// the underlying `FVar` is `NonNativeUintVar`. +#[derive(Debug, Clone)] +pub struct R1CSMatricesVar { + _m: PhantomData, + pub A: SparseMatrixVar, + pub B: SparseMatrixVar, + pub C: SparseMatrixVar, +} + +impl> + AllocVar, ConstraintF> for R1CSMatricesVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + _mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + + Ok(Self { + _m: PhantomData, + A: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().A)?, + B: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().B)?, + C: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().C)?, + }) + }) + } +} + +impl R1CSMatricesVar +where + SparseMatrixVar: MatrixGadget, + [FVar]: VectorGadget, +{ + pub fn eval_at_z( + &self, + z: AssignmentsVar, + ) -> Result<(Vec, Vec), SynthesisError> { + // Multiply Cz by z[0] (u) here, allowing this method to be reused for + // both relaxed and unrelaxed R1CS. + let Az = self.A.mul_vector(&z)?; + let Bz = self.B.mul_vector(&z)?; + let Cz = self.C.mul_vector(&z)?; + let uCz = Cz.scale(&z[0])?; + let AzBz = Az.hadamard(&Bz)?; + Ok((AzBz, uCz)) + } +} + +impl, UVar: AsRef<[FVar]>> ArithRelationGadget + for R1CSMatricesVar +where + SparseMatrixVar: MatrixGadget, + [FVar]: VectorGadget + EquivalenceGadget, + FVar: Clone + One, +{ + /// Evaluation is a tuple of two vectors (`AzBz` and `uCz`) instead of a + /// single vector `AzBz - uCz`, because subtraction is not supported for + /// `FVar = NonNativeUintVar`. + type Evaluation = (Vec, Vec); + + fn eval_relation(&self, w: &WVar, u: &UVar) -> Result { + self.eval_at_z((FVar::one(), u.as_ref(), w.as_ref()).into()) + } + + fn enforce_evaluation( + _w: &WVar, + _u: &UVar, + (lhs, rhs): Self::Evaluation, + ) -> Result<(), SynthesisError> { + lhs.enforce_equivalent(&rhs) + } +} + +// TODO: add back tests \ No newline at end of file diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs new file mode 100644 index 000000000..4804ee19e --- /dev/null +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -0,0 +1,205 @@ +use ark_ff::Field; +use ark_relations::gr1cs::{ConstraintSystem, Matrix}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, cfg_iter, rand::Rng}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use sonobe_traits::Dummy; + +use super::{ccs::CCS, Arith, ArithRelation, ArithSerializer}; +use crate::arithmetizations::{Assignments, Error}; + +pub mod circuits; + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct R1CS { + l: usize, // io len + m: usize, // number of constraints + n: usize, // number of variables + pub A: Matrix, + pub B: Matrix, + pub C: Matrix, +} + +impl R1CS { + /// Evaluates the R1CS relation at a given vector of variables `z` + pub fn eval_at_z(&self, z: Assignments) -> Result, Error> { + if z.public.len() != self.n_public_inputs() { + return Err(Error::MalformedAssignments( + format!("The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", self.n_public_inputs(), z.public.len()) + )); + } + if z.private.len() != self.n_witnesses() { + return Err(Error::MalformedAssignments( + format!("The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", self.n_witnesses(), z.private.len()) + )); + } + + Ok(cfg_iter!(self.A) + .zip(&self.B) + .zip(&self.C) + .map(|((a, b), c)| { + let az = a.iter().map(|(val, col)| z[*col] * val).sum::(); + let bz = b.iter().map(|(val, col)| z[*col] * val).sum::(); + let cz = c.iter().map(|(val, col)| z[*col] * val).sum::(); + az * bz - z[0] * cz + }) + .collect()) + } +} + +impl Arith for R1CS { + #[inline] + fn degree(&self) -> usize { + 2 + } + + #[inline] + fn n_constraints(&self) -> usize { + self.m + } + + #[inline] + fn n_variables(&self) -> usize { + self.n + } + + #[inline] + fn n_public_inputs(&self) -> usize { + self.l + } + + #[inline] + fn n_witnesses(&self) -> usize { + self.n_variables() - self.n_public_inputs() - 1 + } +} + +impl, U: AsRef<[F]>> ArithRelation for R1CS { + type Evaluation = Vec; + + fn eval_relation(&self, w: &W, u: &U) -> Result { + self.eval_at_z((F::one(), u.as_ref(), w.as_ref()).into()) + } + + fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { + cfg_into_iter!(e) + .all(|i| i.is_zero()) + .then_some(()) + .ok_or(Error::UnsatisfiedAssignments( + "Evaluation contains non-zero values".into(), + )) + } +} + +impl ArithSerializer for R1CS { + fn params_to_le_bytes(&self) -> Vec { + [ + self.l.to_le_bytes(), + self.m.to_le_bytes(), + self.n.to_le_bytes(), + ] + .concat() + } +} + +impl Dummy<(usize, usize, usize)> for R1CS { + fn dummy((n_constraints, n_variables, n_public_inputs): (usize, usize, usize)) -> Self { + Self { + m: n_constraints, + n: n_variables, + l: n_public_inputs, + A: vec![], + B: vec![], + C: vec![], + } + } +} + +impl R1CS { + pub fn empty() -> Self { + Self::dummy((0, 0, 0)) + } + + pub fn new( + (n_constraints, n_variables, n_public_inputs): (usize, usize, usize), + mut matrices: Vec>, + ) -> Result { + // R1CS should have exactly 3 matrices (A, B, C) + if matrices.len() != 3 { + return Err(Error::ConstraintExtractionFailure(format!( + "R1CS should only have 3 matrices (A, B, C) but found {} matrices", + matrices.len() + ))); + } + + let C = matrices.pop().unwrap(); + let B = matrices.pop().unwrap(); + let A = matrices.pop().unwrap(); + + Ok(Self { + m: n_constraints, + n: n_variables, + l: n_public_inputs, + A, + B, + C, + }) + } +} + +impl TryFrom> for R1CS { + type Error = Error; + + fn try_from(ccs: CCS) -> Result { + Self::new( + ( + ccs.n_constraints(), + ccs.n_variables(), + ccs.n_public_inputs(), + ), + ccs.M, + ) + } +} + +/// Extracts R1CS from arkworks ConstraintSystem matrices +impl TryFrom<&ConstraintSystem> for R1CS { + type Error = Error; + + fn try_from(cs: &ConstraintSystem) -> Result { + // Get the R1CS predicate matrices + let r1cs_predicate = cs.predicate_constraint_systems.get("R1CS").ok_or_else(|| { + Error::ConstraintExtractionFailure( + "No R1CS predicate found in constraint system".into(), + ) + })?; + Self::new( + ( + cs.num_constraints(), + cs.num_instance_variables + cs.num_witness_variables, + cs.num_instance_variables - 1, // -1 to subtract the first '1' + ), + r1cs_predicate.to_matrices(cs), + ) + } +} + +/// extracts the witness and the public inputs from arkworks ConstraintSystem. +pub fn extract_w_x(cs: &ConstraintSystem) -> (Vec, Vec) { + let witness = cs + .witness_assignment() + .expect("witness_assignment failed") + .to_vec(); + let instance = cs + .instance_assignment() + .expect("instance_assignment failed"); + ( + witness, + // skip the first element which is '1' + instance[1..].to_vec(), + ) +} + +// TODO: add back tests \ No newline at end of file diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs new file mode 100644 index 000000000..dac037619 --- /dev/null +++ b/crates/primitives/src/commitments/mod.rs @@ -0,0 +1,67 @@ +use ark_r1cs_std::alloc::AllocVar; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::fmt::Debug; +use ark_std::rand::RngCore; +use thiserror::Error; + +use sonobe_traits::Curve; + +pub mod pedersen; +// TODO: add back other commitment schemes + +#[derive(Debug, Error)] +pub enum Error { + // Commitment errors + #[error("The message being committed to has length {1}, which exceeds the maximum supported length of {0}")] + MessageTooLong(usize, usize), + #[error("Blinding factor not 0 for Commitment without hiding")] + BlindingNotZero, + #[error("Blinding factors incorrect, blinding is set to {0} but blinding values are {1}")] + IncorrectBlinding(bool, String), + #[error("Commitment verification failed")] + CommitmentVerificationFail, +} + +pub trait VectorCommitment { + const IS_HIDING: bool; + + type Key; + type Scalar; + type Commitment; + type Randomness; + + fn generate_key(rng: &mut impl RngCore, len: usize) -> Result; + + fn commit( + ck: &Self::Key, + v: &[Self::Scalar], + rng: &mut impl RngCore, + ) -> Result<(Self::Commitment, Self::Randomness), Error>; + + fn open( + ck: &Self::Key, + v: &[Self::Scalar], + r: &Self::Randomness, + cm: &Self::Commitment, + ) -> Result; +} + +#[cfg(test)] +mod tests { + use ark_ff::UniformRand; + use ark_std::error::Error; + + use super::*; + + pub fn test_commitment_opt>( + rng: &mut impl RngCore, + len: usize, + ) -> Result<(), Box> { + let v = (0..len).map(|_| VC::Scalar::rand(rng)).collect::>(); + + let ck = VC::generate_key(rng, len)?; + let (cm, r) = VC::commit(&ck, &v, rng)?; + assert!(VC::open(&ck, &v, &r, &cm)?); + Ok(()) + } +} diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs new file mode 100644 index 000000000..0f97cca9c --- /dev/null +++ b/crates/primitives/src/commitments/pedersen.rs @@ -0,0 +1,150 @@ +use ark_r1cs_std::{boolean::Boolean, convert::ToBitsGadget, groups::CurveVar}; +use ark_relations::gr1cs::SynthesisError; +use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; + +use super::{Error, VectorCommitment}; +use sonobe_traits::{Curve, CF2}; + +#[derive(Debug)] +pub struct Pedersen { + _c: PhantomData, +} + +impl Pedersen { + fn msm(g: &[C::Affine], v: &[C::ScalarField]) -> Result { + if g.len() < v.len() { + return Err(Error::MessageTooLong(g.len(), v.len())); + } + // + // use msm_unchecked because we already ensured at the if that generators are long enough + Ok(C::msm_unchecked(g, v)) + } +} + +impl VectorCommitment for Pedersen { + const IS_HIDING: bool = false; + + type Key = Vec; + type Scalar = C::ScalarField; + type Commitment = C; + type Randomness = (); + + fn generate_key(rng: &mut impl RngCore, len: usize) -> Result { + let generators = repeat_with(|| C::rand(rng)) + .take(len.next_power_of_two()) + .collect::>(); + Ok(C::normalize_batch(&generators)) + } + + fn commit( + g: &Self::Key, + v: &[Self::Scalar], + _rng: &mut impl RngCore, + ) -> Result<(Self::Commitment, Self::Randomness), Error> { + Ok((Self::msm(g, v)?, ())) + } + + fn open( + ck: &Self::Key, + v: &[Self::Scalar], + _r: &Self::Randomness, + cm: &Self::Commitment, + ) -> Result { + Ok(&Self::msm(ck, v)? == cm) + } +} + +impl VectorCommitment for Pedersen { + const IS_HIDING: bool = true; + + type Key = (Vec, C); + type Scalar = C::ScalarField; + type Commitment = C; + type Randomness = C::ScalarField; + + fn generate_key(rng: &mut impl RngCore, len: usize) -> Result { + Ok((Pedersen::::generate_key(rng, len)?, C::rand(rng))) + } + + fn commit( + (g, h): &Self::Key, + v: &[Self::Scalar], + rng: &mut impl RngCore, + ) -> Result<(Self::Commitment, Self::Randomness), Error> { + let r = C::ScalarField::rand(rng); + Ok((Self::msm(g, v)? + h.mul(r), r)) + } + + fn open( + (g, h): &Self::Key, + v: &[Self::Scalar], + r: &Self::Randomness, + cm: &Self::Commitment, + ) -> Result { + Ok(&(Self::msm(g, v)? + h.mul(r)) == cm) + } +} + +pub struct PedersenGadget { + _c: PhantomData, +} + +impl PedersenGadget { + pub fn commit( + h: &C::Var, + g: &[C::Var], + v: &[Vec>>], + r: &[Boolean>], + ) -> Result { + let mut res = C::Var::zero(); + if H { + res += h.scalar_mul_le(r.iter())?; + } + let n = v.len(); + if n % 2 == 1 { + res += g[n - 1].scalar_mul_le(v[n - 1].to_bits_le()?.iter())?; + } else { + res += g[n - 1].joint_scalar_mul_be( + &g[n - 2], + v[n - 1].to_bits_le()?.iter(), + v[n - 2].to_bits_le()?.iter(), + )?; + } + for i in (1..n - 1).step_by(2) { + res += g[i - 1].joint_scalar_mul_be( + &g[i], + v[i - 1].to_bits_le()?.iter(), + v[i].to_bits_le()?.iter(), + )?; + } + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective}; + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; + use ark_ff::{BigInteger, PrimeField}; + use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{error::Error, rand::Rng, test_rng}; + + use crate::commitments::tests::test_commitment_opt; + + use super::*; + use crate::transcripts::poseidon::poseidon_canonical_config; + + #[test] + fn test_pedersen_commitment() -> Result<(), Box> { + let rng = &mut test_rng(); + for i in 0..10 { + let len = rng.gen_range((1 << i)..(1 << (i + 1))); + test_commitment_opt::>(rng, len)?; + test_commitment_opt::>(rng, len)?; + } + Ok(()) + } + + // TODO: add back gadget tests +} diff --git a/crates/primitives/src/gadgets/math/eq.rs b/crates/primitives/src/gadgets/math/eq.rs new file mode 100644 index 000000000..a63373fa1 --- /dev/null +++ b/crates/primitives/src/gadgets/math/eq.rs @@ -0,0 +1,24 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar}; +use ark_relations::gr1cs::SynthesisError; + +/// `EquivalenceGadget` enforces that two in-circuit variables are equivalent, +/// where the equivalence relation is parameterized by `M`: +/// - For `FpVar`, it is simply an equality relation, and `M` is unused. +/// - For `NonNativeUintVar`, we consider equivalence as a congruence relation, +/// in terms of modular arithmetic, so `M` specifies the modulus. +pub trait EquivalenceGadget { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError>; +} +impl EquivalenceGadget for FpVar { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_equal(other) + } +} +impl> EquivalenceGadget for [T] { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.iter() + .zip(other) + .try_for_each(|(a, b)| a.enforce_equivalent(b)) + } +} diff --git a/crates/primitives/src/gadgets/math/matrix.rs b/crates/primitives/src/gadgets/math/matrix.rs new file mode 100644 index 000000000..bc9d524f3 --- /dev/null +++ b/crates/primitives/src/gadgets/math/matrix.rs @@ -0,0 +1,68 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + fields::{fp::FpVar, FieldVar}, + GR1CSVar, +}; +use ark_relations::gr1cs::{Matrix, Namespace, SynthesisError}; +use ark_std::borrow::Borrow; + +use std::ops::Index; + +pub trait MatrixGadget { + fn mul_vector(&self, v: &impl Index) -> Result, SynthesisError>; +} + +// same format as the native SparseMatrix (which follows ark_relations::gr1cs::Matrix format) +#[derive(Debug, Clone)] +pub struct SparseMatrixVar(pub Vec>); + +impl> AllocVar, CF> + for SparseMatrixVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + + let mut coeffs: Vec> = Vec::new(); + for row in val.borrow().iter() { + coeffs.push( + row.iter() + .map(|&(value, col)| { + Ok((FV::new_variable(cs.clone(), || Ok(value), mode)?, col)) + }) + .collect::, _>>()?, + ); + } + + Ok(Self(coeffs)) + }) + } +} + +impl MatrixGadget> for SparseMatrixVar> { + fn mul_vector( + &self, + v: &impl Index>, + ) -> Result>, SynthesisError> { + Ok(self + .0 + .iter() + .map(|row| { + let products = row + .iter() + .map(|(value, col_i)| value * &v[*col_i]) + .collect::>(); + if products.is_constant() { + FpVar::constant(products.value().unwrap_or_default().into_iter().sum()) + } else { + products.iter().sum() + } + }) + .collect()) + } +} diff --git a/crates/primitives/src/gadgets/math/mod.rs b/crates/primitives/src/gadgets/math/mod.rs new file mode 100644 index 000000000..40ed4e53e --- /dev/null +++ b/crates/primitives/src/gadgets/math/mod.rs @@ -0,0 +1,3 @@ +pub mod eq; +pub mod matrix; +pub mod vector; \ No newline at end of file diff --git a/crates/primitives/src/gadgets/math/vector.rs b/crates/primitives/src/gadgets/math/vector.rs new file mode 100644 index 000000000..8a7019a6d --- /dev/null +++ b/crates/primitives/src/gadgets/math/vector.rs @@ -0,0 +1,31 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::gr1cs::SynthesisError; + +pub trait VectorGadget { + fn add(&self, other: &Self) -> Result, SynthesisError>; + + fn scale(&self, scalar: &FV) -> Result, SynthesisError>; + + fn hadamard(&self, other: &Self) -> Result, SynthesisError>; +} + +impl VectorGadget> for [FpVar] { + fn add(&self, other: &Self) -> Result>, SynthesisError> { + if self.len() != other.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(self.iter().zip(other.iter()).map(|(a, b)| a + b).collect()) + } + + fn scale(&self, scalar: &FpVar) -> Result>, SynthesisError> { + Ok(self.iter().map(|a| a * scalar).collect()) + } + + fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { + if self.len() != other.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(self.iter().zip(other.iter()).map(|(a, b)| a * b).collect()) + } +} diff --git a/crates/primitives/src/gadgets/mod.rs b/crates/primitives/src/gadgets/mod.rs new file mode 100644 index 000000000..89346e75c --- /dev/null +++ b/crates/primitives/src/gadgets/mod.rs @@ -0,0 +1,2 @@ +pub mod math; +pub mod nonnative; diff --git a/crates/primitives/src/gadgets/nonnative/affine.rs b/crates/primitives/src/gadgets/nonnative/affine.rs new file mode 100644 index 000000000..182525cc9 --- /dev/null +++ b/crates/primitives/src/gadgets/nonnative/affine.rs @@ -0,0 +1,202 @@ +use ark_ec::{short_weierstrass::SWFlags, AffineRepr}; +use ark_ff::PrimeField; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + eq::EqGadget, + fields::fp::FpVar, + prelude::Boolean, + GR1CSVar, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; +use ark_std::{borrow::Borrow, Zero}; + +use sonobe_traits::{AbsorbNonNativeGadget, Curve}; + +use super::uint::NonNativeUintVar; + +/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native +/// field, over the constraint field. It is not intended to perform operations, but just to contain +/// the affine coordinates in order to perform hash operations of the point. +#[derive(Debug, Clone)] +pub struct NonNativeAffineVar { + pub x: NonNativeUintVar, + pub y: NonNativeUintVar, +} + +impl AllocVar for NonNativeAffineVar { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + + let affine = val.borrow().into_affine(); + let (x, y) = affine.xy().unwrap_or_default(); + + let x = NonNativeUintVar::new_variable(cs.clone(), || Ok(x), mode)?; + let y = NonNativeUintVar::new_variable(cs.clone(), || Ok(y), mode)?; + + Ok(Self { x, y }) + }) + } +} + +impl GR1CSVar for NonNativeAffineVar { + type Value = C; + + fn cs(&self) -> ConstraintSystemRef { + self.x.cs().or(self.y.cs()) + } + + fn value(&self) -> Result { + let x = C::BaseField::from_le_bytes_mod_order(&self.x.value()?.to_bytes_le()); + let y = C::BaseField::from_le_bytes_mod_order(&self.y.value()?.to_bytes_le()); + // Below is a workaround to convert the `x` and `y` coordinates to a + // point. This is because the `SonobeCurve` trait does not provide a + // method to construct a point from `BaseField` elements. + let mut bytes = vec![]; + // `unwrap` below is safe because serialization of a `PrimeField` value + // only fails if the serialization flag has more than 8 bits, but here + // we call `serialize_uncompressed` which uses an empty flag. + x.serialize_uncompressed(&mut bytes).unwrap(); + // `unwrap` below is also safe, because the bit size of `SWFlags` is 2. + y.serialize_with_flags( + &mut bytes, + if x.is_zero() && y.is_zero() { + SWFlags::PointAtInfinity + } else if y <= -y { + SWFlags::YIsPositive + } else { + SWFlags::YIsNegative + }, + ) + .unwrap(); + // `unwrap` below is safe because `bytes` is constructed from the `x` + // and `y` coordinates of a valid point, and these coordinates are + // serialized in the same way as the `SonobeCurve` implementation. + Ok(C::deserialize_uncompressed_unchecked(&bytes[..]).unwrap()) + } +} + +impl EqGadget for NonNativeAffineVar { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + let mut result = Boolean::TRUE; + if self.x.0.len() != other.x.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.y.0.len() != other.y.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + for (l, r) in self + .x + .0 + .iter() + .chain(&self.y.0) + .zip(other.x.0.iter().chain(&other.y.0)) + { + if l.ub != r.ub { + return Err(SynthesisError::Unsatisfiable); + } + result &= l.v.is_eq(&r.v)?; + } + Ok(result) + } + + fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { + if self.x.0.len() != other.x.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.y.0.len() != other.y.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + for (l, r) in self + .x + .0 + .iter() + .chain(&self.y.0) + .zip(other.x.0.iter().chain(&other.y.0)) + { + if l.ub != r.ub { + return Err(SynthesisError::Unsatisfiable); + } + l.v.enforce_equal(&r.v)?; + } + Ok(()) + } +} + +impl NonNativeAffineVar { + pub fn zero() -> Self { + // `unwrap` below is safe because we are allocating a constant value, + // which is guaranteed to succeed. + Self::new_constant(ConstraintSystemRef::None, C::zero()).unwrap() + } +} + +impl AbsorbNonNativeGadget for NonNativeAffineVar { + fn to_native_sponge_field_elements( + &self, + ) -> Result>, SynthesisError> { + [&self.x, &self.y].to_native_sponge_field_elements() + } +} + +#[cfg(test)] +mod tests { + use ark_pallas::{Fq, Fr, PallasConfig, Projective}; + use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{error::Error, UniformRand}; + use sonobe_traits::{AbsorbNonNative, Inputize, InputizeNonNative}; + + use super::*; + + #[test] + fn test_alloc_zero() { + let cs = ConstraintSystem::::new_ref(); + + // dealing with the 'zero' point should not panic when doing the unwrap + let p = Projective::zero(); + assert!(NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).is_ok()); + } + + #[test] + fn test_improved_to_hash_preimage() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + // check that point_to_nonnative_limbs returns the expected values + let mut rng = ark_std::test_rng(); + let p = Projective::rand(&mut rng); + let p_var = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; + assert_eq!( + p_var.to_native_sponge_field_elements()?.value()?, + p.to_native_sponge_field_elements_as_vec() + ); + Ok(()) + } + + #[test] + fn test_inputize() -> Result<(), Box> { + // check that point_to_nonnative_limbs returns the expected values + let mut rng = ark_std::test_rng(); + let p = Projective::rand(&mut rng); + + let cs = ConstraintSystem::::new_ref(); + let p_var = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; + assert_eq!( + [p_var.x.0.value()?, p_var.y.0.value()?].concat(), + p.inputize_nonnative() + ); + + let cs = ConstraintSystem::::new_ref(); + let p_var = ProjectiveVar::>::new_witness(cs.clone(), || Ok(p))?; + assert_eq!( + vec![p_var.x.value()?, p_var.y.value()?, p_var.z.value()?], + p.inputize() + ); + Ok(()) + } +} diff --git a/crates/primitives/src/gadgets/nonnative/mod.rs b/crates/primitives/src/gadgets/nonnative/mod.rs new file mode 100644 index 000000000..497b9870f --- /dev/null +++ b/crates/primitives/src/gadgets/nonnative/mod.rs @@ -0,0 +1,2 @@ +pub mod affine; +pub mod uint; diff --git a/crates/primitives/src/gadgets/nonnative/uint.rs b/crates/primitives/src/gadgets/nonnative/uint.rs new file mode 100644 index 000000000..754908392 --- /dev/null +++ b/crates/primitives/src/gadgets/nonnative/uint.rs @@ -0,0 +1,994 @@ +use std::ops::Index; + +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, + convert::ToBitsGadget, + fields::{fp::FpVar, FieldVar}, + prelude::EqGadget, + select::CondSelectGadget, + GR1CSVar, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{ + borrow::Borrow, + cmp::{max, min}, +}; +use num_bigint::BigUint; +use num_integer::Integer; + +use sonobe_traits::{AbsorbNonNativeGadget, Field}; + +use crate::gadgets::math::{ + eq::EquivalenceGadget, + matrix::{MatrixGadget, SparseMatrixVar}, + vector::VectorGadget, +}; + +/// `LimbVar` represents a single limb of a non-native unsigned integer in the +/// circuit. +/// The limb value `v` should be small enough to fit into `FpVar`, and we also +/// store an upper bound `ub` for the limb value, which is treated as a constant +/// in the circuit and is used for efficient equality checks and some arithmetic +/// operations. +#[derive(Debug, Clone)] +pub struct LimbVar { + pub v: FpVar, + pub ub: BigUint, +} + +impl]>> From for LimbVar { + fn from(bits: B) -> Self { + Self { + // `Boolean::le_bits_to_fp` will return an error if the internal + // invocation of `Boolean::enforce_in_field_le` fails. + // However, this method is only called when the length of `bits` is + // greater than `F::MODULUS_BIT_SIZE`, which should not happen in + // our case where `bits` is guaranteed to be short. + v: Boolean::le_bits_to_fp(bits.as_ref()).unwrap(), + ub: (BigUint::one() << bits.as_ref().len()) - BigUint::one(), + } + } +} + +impl Default for LimbVar { + fn default() -> Self { + Self { + v: FpVar::zero(), + ub: BigUint::zero(), + } + } +} + +impl GR1CSVar for LimbVar { + type Value = F; + + fn cs(&self) -> ConstraintSystemRef { + self.v.cs() + } + + fn value(&self) -> Result { + self.v.value() + } +} + +impl CondSelectGadget for LimbVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + // We only allow selecting between two values with the same upper bound + assert_eq!(true_value.ub, false_value.ub); + Ok(Self { + v: cond.select(&true_value.v, &false_value.v)?, + ub: true_value.ub.clone(), + }) + } +} + +impl LimbVar { + /// Add two `LimbVar`s. + /// Returns `None` if the upper bound of the sum is too large, i.e., + /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. + /// Otherwise, returns the sum as a `LimbVar`. + pub fn add(&self, other: &Self) -> Option { + let ubound = &self.ub + &other.ub; + if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { + Some(Self { + v: &self.v + &other.v, + ub: ubound, + }) + } else { + None + } + } + + /// Add multiple `LimbVar`s. + /// Returns `None` if the upper bound of the sum is too large, i.e., + /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. + /// Otherwise, returns the sum as a `LimbVar`. + pub fn add_many(limbs: &[Self]) -> Option { + let ubound = limbs.iter().map(|l| &l.ub).sum(); + if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { + Some(Self { + v: if limbs.is_constant() { + FpVar::constant(limbs.value().unwrap_or_default().into_iter().sum()) + } else { + limbs.iter().map(|l| &l.v).sum() + }, + ub: ubound, + }) + } else { + None + } + } + + /// Multiply two `LimbVar`s. + /// Returns `None` if the upper bound of the product is too large, i.e., + /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. + /// Otherwise, returns the product as a `LimbVar`. + pub fn mul(&self, other: &Self) -> Option { + let ubound = &self.ub * &other.ub; + if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { + Some(Self { + v: &self.v * &other.v, + ub: ubound, + }) + } else { + None + } + } + + pub fn zero() -> Self { + Self::default() + } + + pub fn constant(v: F) -> Self { + Self { + v: FpVar::constant(v), + ub: v.into(), + } + } +} + +impl ToBitsGadget for LimbVar { + fn to_bits_le(&self) -> Result>, SynthesisError> { + let cs = self.cs(); + + let bits = &self + .v + .value() + .unwrap_or_default() + .into_bigint() + .to_bits_le()[..self.ub.bits() as usize]; + let bits = if cs.is_none() { + Vec::new_constant(cs, bits)? + } else { + Vec::new_witness(cs, || Ok(bits))? + }; + + Boolean::le_bits_to_fp(&bits)?.enforce_equal(&self.v)?; + + Ok(bits) + } +} + +/// `NonNativeUintVar` represents a non-native unsigned integer (BigUint) in the +/// circuit. +/// We apply [xJsnark](https://akosba.github.io/papers/xjsnark.pdf)'s techniques +/// for efficient operations on `NonNativeUintVar`. +/// Note that `NonNativeUintVar` is different from arkworks' `NonNativeFieldVar` +/// in that the latter runs the expensive `reduce` (`align` + `modulo` in our +/// terminology) after each arithmetic operation, while the former only reduces +/// the integer when explicitly called. +#[derive(Debug, Clone)] +pub struct NonNativeUintVar(pub Vec>); + +impl NonNativeUintVar { + pub const fn bits_per_limb() -> usize { + assert!(F::MODULUS_BIT_SIZE > 250); + // For a `F` with order > 250 bits, 55 is chosen for optimizing the most + // expensive part `Az∘Bz` when checking the R1CS relation for CycleFold. + // Consider using `NonNativeUintVar` to represent the base field `Fq`. + // Since 250 / 55 = 4.46, the `NonNativeUintVar` has 5 limbs. + // Now, the multiplication of two `NonNativeUintVar`s has 9 limbs, and + // each limb has at most 2^{55 * 2} * 5 = 112.3 bits. + // For a 1400x1400 matrix `A`, the multiplication of `A`'s row and `z` + // is the sum of 1400 `NonNativeUintVar`s, each with 9 limbs. + // Thus, the maximum bit length of limbs of each element in `Az` is + // 2^{55 * 2} * 5 * 1400 = 122.7 bits. + // Finally, in the hadamard product of `Az` and `Bz`, every element has + // 17 limbs, whose maximum bit length is (2^{55 * 2} * 5 * 1400)^2 * 9 + // = 248.7 bits and is less than the native field `Fr`. + // Thus, 55 allows us to compute `Az∘Bz` without the expensive alignment + // operation. + // + // TODO: either make it a global const, or compute an optimal value + // based on the modulus size. + 55 + } +} + +struct BoundedBigUint(BigUint, usize); + +impl AllocVar for NonNativeUintVar { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let BoundedBigUint(x, l) = v.borrow(); + + let mut limbs = vec![]; + for chunk in (0..*l) + .map(|i| x.bit(i as u64)) + .collect::>() + .chunks(Self::bits_per_limb()) + { + let limb = F::from(F::BigInt::from_bits_le(chunk)); + let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; + Self::enforce_bit_length(&limb, chunk.len())?; + limbs.push(LimbVar { + v: limb, + ub: (BigUint::one() << chunk.len()) - BigUint::one(), + }); + } + + Ok(Self(limbs)) + } +} + +impl AllocVar for NonNativeUintVar { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + assert_eq!(G::extension_degree(), 1); + // `unwrap` is safe because `G` is a field with extension degree 1, and + // thus `G::to_base_prime_field_elements` should return an iterator with + // exactly one element. + let v = v.borrow().to_base_prime_field_elements().next().unwrap(); + + let mut limbs = vec![]; + + for chunk in v.into_bigint().to_bits_le().chunks(Self::bits_per_limb()) { + let limb = F::from(F::BigInt::from_bits_le(chunk)); + let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; + Self::enforce_bit_length(&limb, chunk.len())?; + limbs.push(LimbVar { + v: limb, + ub: (BigUint::one() << chunk.len()) - BigUint::one(), + }); + } + + Ok(Self(limbs)) + } +} + +impl GR1CSVar for NonNativeUintVar { + type Value = BigUint; + + fn cs(&self) -> ConstraintSystemRef { + self.0.cs() + } + + fn value(&self) -> Result { + let mut r = BigUint::zero(); + + for limb in self.0.value()?.into_iter().rev() { + r <<= Self::bits_per_limb(); + r += Into::::into(limb); + } + + Ok(r) + } +} + +impl NonNativeUintVar { + /// Enforce `self` to be less than `other`, where `self` and `other` should + /// be aligned. + /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 + pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> { + let len = max(self.0.len(), other.0.len()); + let zero = LimbVar::zero(); + + // Compute the difference between limbs of `other` and `self`. + // Denote a positive limb by `+`, a negative limb by `-`, a zero limb by + // `0`, and an unknown limb by `?`. + // Then, for `self < other`, `delta` should look like: + // ? ? ... ? ? + 0 0 ... 0 0 + let delta = (0..len) + .map(|i| { + let x = &self.0.get(i).unwrap_or(&zero).v; + let y = &other.0.get(i).unwrap_or(&zero).v; + y - x + }) + .collect::>(); + + // `helper` is a vector of booleans that indicates if the corresponding + // limb of `delta` is the first (searching from MSB) positive limb. + // For example, if `delta` is: + // - + ... + - + 0 0 ... 0 0 + // <---- search in this direction -------- + // Then `helper` should be: + // F F ... F F T F F ... F F + let helper = { + let cs = self.cs().or(other.cs()); + let mut helper = vec![false; len]; + for i in (0..len).rev() { + let delta = delta[i].value().unwrap_or_default().into_bigint(); + if !delta.is_zero() && delta < F::MODULUS_MINUS_ONE_DIV_TWO { + helper[i] = true; + break; + } + } + if cs.is_none() { + Vec::>::new_constant(cs, helper)? + } else { + Vec::new_witness(cs, || Ok(helper))? + } + }; + + // `p` is the first positive limb in `delta`. + let mut p = FpVar::::zero(); + // `r` is the sum of all bits in `helper`, which should be 1 when `self` + // is less than `other`, as there should be more than one positive limb + // in `delta`, and thus exactly one true bit in `helper`. + let mut r = FpVar::zero(); + for (b, d) in helper.into_iter().zip(delta) { + // Choose the limb `d` only if `b` is true. + p += b.select(&d, &FpVar::zero())?; + // Either `r` or `d` should be zero. + // Consider the same example as above: + // - + ... + - + 0 0 ... 0 0 + // F F ... F F T F F ... F F + // |-----------| + // `r = 0` in this range (before/when we meet the first positive limb) + // |---------| + // `d = 0` in this range (after we meet the first positive limb) + // This guarantees that for every bit after the true bit in `helper`, + // the corresponding limb in `delta` is zero. + (&r * &d).enforce_equal(&FpVar::zero())?; + // Add the current bit to `r`. + r += FpVar::from(b); + } + + // Ensure that `r` is exactly 1. This guarantees that there is exactly + // one true value in `helper`. + r.enforce_equal(&FpVar::one())?; + // Ensure that `p` is positive, i.e., + // `0 <= p - 1 < 2^bits_per_limb < F::MODULUS_MINUS_ONE_DIV_TWO`. + // This guarantees that the true value in `helper` corresponds to a + // positive limb in `delta`. + Self::enforce_bit_length(&(p - FpVar::one()), Self::bits_per_limb())?; + + Ok(()) + } + + /// Enforce `self` to be equal to `other`, where `self` and `other` are not + /// necessarily aligned. + /// + /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L562-L798 + /// Similar implementations can also be found in https://github.com/alex-ozdemir/bellman-bignat/blob/0585b9d90154603a244cba0ac80b9aafe1d57470/src/mp/bignat.rs#L566-L661 + /// and https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/emulated_fp/reduce.rs#L201-L323 + pub fn enforce_equal_unaligned(&self, other: &Self) -> Result<(), SynthesisError> { + let len = min(self.0.len(), other.0.len()); + + // Group the limbs of `self` and `other` so that each group nearly + // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. + // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` + // is the initial number of bits in a limb, just as what we do in grade + // school arithmetic, e.g., + // 5 9 + // x 7 3 + // ------------- + // 15 27 + // 35 63 + // ------------- <- When grouping 35, 15 + 63, and 27, we are computing + // 4 3 0 7 35 * 100 + (15 + 63) * 10 + 27 = 4307 + // Note that this is different from the concatenation `x_0 || x_1 ...`, + // since the bit-length of each limb is not necessarily the initial size + // `W`. + let (steps, x, y, rest) = { + // `steps` stores the size of each grouped limb. + let mut steps = vec![]; + // `x_grouped` stores the grouped limbs of `self`. + let mut x_grouped = vec![]; + // `y_grouped` stores the grouped limbs of `other`. + let mut y_grouped = vec![]; + let mut i = 0; + while i < len { + let mut j = i; + // The current grouped limbs of `self` and `other`. + let mut xx = LimbVar::zero(); + let mut yy = LimbVar::zero(); + while j < len { + let shift = BigUint::one() << (Self::bits_per_limb() * (j - i)); + assert!(shift < F::MODULUS_MINUS_ONE_DIV_TWO.into()); + let shift = LimbVar::constant(shift.into()); + match ( + // Try to group `x` and `y` into `xx` and `yy`. + self.0[j].mul(&shift).and_then(|x| xx.add(&x)), + other.0[j].mul(&shift).and_then(|y| yy.add(&y)), + ) { + // Update the result if successful. + (Some(x), Some(y)) => (xx, yy) = (x, y), + // Break the loop if the upper bound of the result exceeds + // the maximum capacity. + _ => break, + } + j += 1; + } + // Store the grouped limbs and their size. + steps.push((j - i) * Self::bits_per_limb()); + x_grouped.push(xx); + y_grouped.push(yy); + // Start the next group + i = j; + } + let remaining_limbs = &(if i < self.0.len() { self } else { other }).0[i..]; + let rest = if remaining_limbs.is_empty() { + FpVar::zero() + } else { + // If there is any remaining limb, the first one should be the + // final carry (which will be checked later), and the following + // ones should be zero. + + // Enforce the remaining limbs to be zero. + // Instead of doing that one by one, we check if their sum is + // zero using a single constraint. + // This is sound, as the upper bounds of the limbs and their sum + // are guaranteed to be less than `F::MODULUS_MINUS_ONE_DIV_TWO` + // (i.e., all of them are "non-negative"), implying that all + // limbs should be zero to make the sum zero. + LimbVar::add_many(&remaining_limbs[1..]) + .ok_or(SynthesisError::Unsatisfiable)? + .v + .enforce_equal(&FpVar::zero())?; + remaining_limbs[0].v.clone() + }; + (steps, x_grouped, y_grouped, rest) + }; + let n = steps.len(); + // `c` stores the current carry of `x_i - y_i` + let mut c = FpVar::::zero(); + // For each group, check the last `step_i` bits of `x_i` and `y_i` are + // equal. + // The intuition is to check `diff = x_i - y_i = 0 (mod 2^step_i)`. + // However, this is only true for `i = 0`, and we need to consider carry + // values `diff >> step_i` for `i > 0`. + // Therefore, we actually check `diff = x_i - y_i + c = 0 (mod 2^step_i)` + // and derive the next `c` by computing `diff >> step_i`. + // To enforce `diff = 0 (mod 2^step_i)`, we compute `diff / 2^step_i` + // and enforce it to be small (soundness holds because for `a` that does + // not divide `b`, `b / a` in the field will be very large. + for i in 0..n { + let step = steps[i]; + c = (&x[i].v - &y[i].v + &c) + .mul_by_inverse_unchecked(&FpVar::constant(F::from(BigUint::one() << step)))?; + if i != n - 1 { + // Unlike the code mentioned above which add some offset to the + // diff `x_i - y_i + c` to make it always positive, we directly + // check if the absolute value of the diff is small. + Self::enforce_abs_bit_length( + &c, + (max(&x[i].ub, &y[i].ub).bits() as usize) + .checked_sub(step) + .unwrap_or_default(), + )?; + } else { + // For the final carry, we need to ensure that it equals the + // remaining limb `rest`. + c.enforce_equal(&rest)?; + } + } + + Ok(()) + } +} + +impl ToBitsGadget for NonNativeUintVar { + fn to_bits_le(&self) -> Result>, SynthesisError> { + Ok(self + .0 + .iter() + .map(|limb| limb.to_bits_le()) + .collect::, _>>()? + .concat()) + } +} + +impl CondSelectGadget for NonNativeUintVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + assert_eq!(true_value.0.len(), false_value.0.len()); + let mut v = vec![]; + for i in 0..true_value.0.len() { + v.push(cond.select(&true_value.0[i], &false_value.0[i])?); + } + Ok(Self(v)) + } +} + +impl NonNativeUintVar { + pub fn ubound(&self) -> BigUint { + let mut r = BigUint::zero(); + + for i in self.0.iter().rev() { + r <<= Self::bits_per_limb(); + r += &i.ub; + } + + r + } + + fn enforce_bit_length(x: &FpVar, length: usize) -> Result>, SynthesisError> { + let cs = x.cs(); + + let bits = &x.value().unwrap_or_default().into_bigint().to_bits_le()[..length]; + let bits = if cs.is_none() { + Vec::new_constant(cs, bits)? + } else { + Vec::new_witness(cs, || Ok(bits))? + }; + + Boolean::le_bits_to_fp(&bits)?.enforce_equal(x)?; + + Ok(bits) + } + + fn enforce_abs_bit_length( + x: &FpVar, + length: usize, + ) -> Result>, SynthesisError> { + let cs = x.cs(); + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; + + let is_neg = Boolean::new_variable( + cs.clone(), + || Ok(x.value().unwrap_or_default().into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO), + mode, + )?; + let bits = Vec::new_variable( + cs.clone(), + || { + Ok({ + let x = x.value().unwrap_or_default(); + let mut bits = if is_neg.value().unwrap_or_default() { + -x + } else { + x + } + .into_bigint() + .to_bits_le(); + bits.resize(length, false); + bits + }) + }, + mode, + )?; + + // Below is equivalent to but more efficient than + // `Boolean::le_bits_to_fp(&bits)?.enforce_equal(&is_neg.select(&x.negate()?, &x)?)?` + // Note that this enforces: + // 1. The claimed absolute value `is_neg.select(&x.negate()?, &x)?` has + // exactly `length` bits. + // 2. `is_neg` is indeed the sign of `x`, i.e., `is_neg = false` when + // `0 <= x < (|F| - 1) / 2`, and `is_neg = true` when + // `(|F| - 1) / 2 <= x < F`, thus the claimed absolute value is + // correct. + // If `is_neg` is incorrect, then: + // a. `0 <= x < (|F| - 1) / 2`, but `is_neg = true`, then + // `is_neg.select(&x.negate()?, &x)?` returns `|F| - x`, + // which is greater than `(|F| - 1) / 2` and cannot fit in + // `length` bits (given that `length` is small). + // b. `(|F| - 1) / 2 <= x < F`, but `is_neg = false`, then + // `is_neg.select(&x.negate()?, &x)?` returns `x`, which is + // greater than `(|F| - 1) / 2` and cannot fit in `length` + // bits. + FpVar::from(is_neg).mul_equals(&x.double()?, &(x - Boolean::le_bits_to_fp(&bits)?))?; + + Ok(bits) + } + + /// Compute `self + other`, without aligning the limbs. + pub fn add_no_align(&self, other: &Self) -> Result { + let mut z = vec![LimbVar::zero(); max(self.0.len(), other.0.len())]; + for (i, v) in self.0.iter().enumerate() { + z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?; + } + for (i, v) in other.0.iter().enumerate() { + z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?; + } + Ok(Self(z)) + } + + /// Compute `self * other`, without aligning the limbs. + /// Implements the O(n) approach described in xJsnark, Section IV.B.1) + pub fn mul_no_align(&self, other: &Self) -> Result { + let len = self.0.len() + other.0.len() - 1; + if self.is_constant() || other.is_constant() { + // Use the naive approach for constant operands, which costs no + // constraints. + let z = (0..len) + .map(|i| { + let start = max(i + 1, other.0.len()) - other.0.len(); + let end = min(i + 1, self.0.len()); + LimbVar::add_many( + &(start..end) + .map(|j| self.0[j].mul(&other.0[i - j])) + .collect::>>()?, + ) + }) + .collect::>>() + .ok_or(SynthesisError::Unsatisfiable)?; + return Ok(Self(z)); + } + let cs = self.cs().or(other.cs()); + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; + + // Compute the result `z` outside the circuit and provide it as hints. + let z = { + let mut z = vec![(F::zero(), BigUint::zero()); len]; + for i in 0..self.0.len() { + for j in 0..other.0.len() { + z[i + j].0 += self.0[i].value().unwrap_or_default() + * other.0[j].value().unwrap_or_default(); + z[i + j].1 += &self.0[i].ub * &other.0[j].ub; + } + } + z.into_iter() + .map(|(v, ub)| { + assert!(ub < F::MODULUS_MINUS_ONE_DIV_TWO.into()); + Ok(LimbVar { + v: FpVar::new_variable(cs.clone(), || Ok(v), mode)?, + ub, + }) + }) + .collect::, _>>()? + }; + for c in 1..=len { + let c = F::from(c as u64); + let mut t = F::one(); + let mut c_powers = vec![]; + for _ in 0..len { + c_powers.push(t); + t *= c; + } + // `l = Σ self[i] c^i` + let l = self + .0 + .iter() + .zip(&c_powers) + .map(|(v, t)| &v.v * *t) + .sum::>(); + // `r = Σ other[i] c^i` + let r = other + .0 + .iter() + .zip(&c_powers) + .map(|(v, t)| &v.v * *t) + .sum::>(); + // `o = Σ z[i] c^i` + let o = z + .iter() + .zip(&c_powers) + .map(|(v, t)| &v.v * *t) + .sum::>(); + // Enforce `o = l * r` + l.mul_equals(&r, &o)?; + } + + Ok(Self(z)) + } + + /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. + pub fn modulo(&self) -> Result { + let cs = self.cs(); + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; + let m: BigUint = M::MODULUS.into(); + // Provide the quotient and remainder as hints + let (q, r) = { + let v = self.value().unwrap_or_default(); + let (q, r) = v.div_rem(&m); + let q_ubound = self.ubound().div_ceil(&m); + let r_ubound = &m; + ( + Self::new_variable( + cs.clone(), + || Ok(BoundedBigUint(q, q_ubound.bits() as usize)), + mode, + )?, + Self::new_variable( + cs.clone(), + || Ok(BoundedBigUint(r, r_ubound.bits() as usize)), + mode, + )?, + ) + }; + + let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?; + // Enforce `self = q * m + r` + q.mul_no_align(&m)? + .add_no_align(&r)? + .enforce_equal_unaligned(self)?; + // Enforce `r < m` (and `r >= 0` already holds) + r.enforce_lt(&m)?; + + Ok(r) + } + + /// Enforce that `self` is congruent to `other` modulo `M::MODULUS`. + pub fn enforce_congruent(&self, other: &Self) -> Result<(), SynthesisError> { + let cs = self.cs(); + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; + let m: BigUint = M::MODULUS.into(); + let bits = (max(self.ubound(), other.ubound()) / &m).bits() as usize; + // Provide the quotient `|x - y| / m` and a boolean indicating if `x > y` + // as hints. + let (q, is_ge) = { + let x = self.value().unwrap_or_default(); + let y = other.value().unwrap_or_default(); + let (d, b) = if x > y { + ((x - y) / &m, true) + } else { + ((y - x) / &m, false) + }; + ( + Self::new_variable(cs.clone(), || Ok(BoundedBigUint(d, bits)), mode)?, + Boolean::new_variable(cs.clone(), || Ok(b), mode)?, + ) + }; + + let zero = Self::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), bits))?; + let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?; + let l = self.add_no_align(&is_ge.select(&zero, &q)?.mul_no_align(&m)?)?; + let r = other.add_no_align(&is_ge.select(&q, &zero)?.mul_no_align(&m)?)?; + // If `self >= other`, enforce `self = other + q * m` + // Otherwise, enforce `self + q * m = other` + // Soundness holds because if `self` and `other` are not congruent, then + // one can never find a `q` satisfying either equation above. + l.enforce_equal_unaligned(&r) + } +} + +impl EquivalenceGadget for NonNativeUintVar { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_congruent::(other) + } +} + +impl]>> From for NonNativeUintVar { + fn from(bits: B) -> Self { + Self( + bits.as_ref() + .chunks(Self::bits_per_limb()) + .map(LimbVar::from) + .collect::>(), + ) + } +} + +impl AbsorbNonNativeGadget for NonNativeUintVar { + fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { + let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; + + self + .to_bits_le()? + .chunks(bits_per_limb) + .map(Boolean::le_bits_to_fp) + .collect() + } +} + +impl VectorGadget> for [NonNativeUintVar] { + fn add(&self, other: &Self) -> Result>, SynthesisError> { + self.iter() + .zip(other.iter()) + .map(|(x, y)| x.add_no_align(y)) + .collect() + } + + fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { + self.iter() + .zip(other.iter()) + .map(|(x, y)| x.mul_no_align(y)) + .collect() + } + + fn scale( + &self, + other: &NonNativeUintVar, + ) -> Result>, SynthesisError> { + self.iter().map(|x| x.mul_no_align(other)).collect() + } +} + +impl MatrixGadget> for SparseMatrixVar> { + fn mul_vector( + &self, + v: &impl Index>, + ) -> Result>, SynthesisError> { + self.0 + .iter() + .map(|row| { + let len = row + .iter() + .map(|(value, col_i)| value.0.len() + v[*col_i].0.len() - 1) + .max() + .unwrap_or(0); + // This is a combination of `mul_no_align` and `add_no_align` + // that results in more flattened `LinearCombination`s. + // Consequently, `ConstraintSystem::inline_all_lcs` costs less + // time, thus making trusted setup and proof generation faster. + (0..len) + .map(|i| { + LimbVar::add_many( + &row.iter() + .flat_map(|(value, col_i)| { + let start = max(i + 1, v[*col_i].0.len()) - v[*col_i].0.len(); + let end = min(i + 1, value.0.len()); + (start..end).map(|j| value.0[j].mul(&v[*col_i].0[i - j])) + }) + .collect::>>()?, + ) + }) + .collect::>>() + .ok_or(SynthesisError::Unsatisfiable) + .map(NonNativeUintVar) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use std::error::Error; + + use ark_ff::Field; + use ark_pallas::{Fq, Fr}; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{test_rng, UniformRand}; + use num_bigint::RandBigInt; + + use super::*; + + #[test] + fn test_mul_biguint() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let size = 256; + + let rng = &mut test_rng(); + let a = rng.gen_biguint(size as u64); + let b = rng.gen_biguint(size as u64); + let ab = &a * &b; + let aab = &a * &ab; + let abb = &ab * &b; + + let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(a, size)))?; + let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(b, size)))?; + let ab_var = + NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(ab, size * 2)))?; + let aab_var = + NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(aab, size * 3)))?; + let abb_var = + NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(abb, size * 3)))?; + + a_var + .mul_no_align(&b_var)? + .enforce_equal_unaligned(&ab_var)?; + a_var + .mul_no_align(&ab_var)? + .enforce_equal_unaligned(&aab_var)?; + ab_var + .mul_no_align(&b_var)? + .enforce_equal_unaligned(&abb_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_mul_fq() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let rng = &mut test_rng(); + let a = Fq::rand(rng); + let b = Fq::rand(rng); + let ab = a * b; + let aab = a * ab; + let abb = ab * b; + + let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; + let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(b))?; + let ab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(ab))?; + let aab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(aab))?; + let abb_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(abb))?; + + a_var + .mul_no_align(&b_var)? + .enforce_congruent::(&ab_var)?; + a_var + .mul_no_align(&ab_var)? + .enforce_congruent::(&aab_var)?; + ab_var + .mul_no_align(&b_var)? + .enforce_congruent::(&abb_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_pow() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let rng = &mut test_rng(); + + let a = Fq::rand(rng); + + let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; + + let mut r_var = a_var.clone(); + for _ in 0..16 { + r_var = r_var.mul_no_align(&r_var)?.modulo::()?; + } + r_var = r_var.mul_no_align(&a_var)?.modulo::()?; + assert_eq!(a.pow([65537u64]), Fq::from(r_var.value()?)); + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_vec_vec_mul() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let len = 1000; + + let rng = &mut test_rng(); + let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); + let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); + let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); + + let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; + let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; + let c_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(c))?; + + let mut r_var = + NonNativeUintVar::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), 0))?; + for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { + r_var = r_var.add_no_align(&a.mul_no_align(&b)?)?; + } + r_var.enforce_congruent::(&c_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs new file mode 100644 index 000000000..9400c51d2 --- /dev/null +++ b/crates/primitives/src/lib.rs @@ -0,0 +1,4 @@ +pub mod commitments; +pub mod arithmetizations; +pub mod gadgets; +pub mod transcripts; diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs new file mode 100644 index 000000000..a7239a11d --- /dev/null +++ b/crates/primitives/src/transcripts/mod.rs @@ -0,0 +1,90 @@ +use ark_crypto_primitives::sponge::{constraints::CryptographicSpongeVar, CryptographicSponge}; +use ark_ec::CurveGroup; +use ark_ff::PrimeField; +use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; +use ark_relations::gr1cs::SynthesisError; +use sonobe_traits::{AbsorbNonNative, AbsorbNonNativeGadget}; + +pub mod poseidon; + +pub trait Transcript: CryptographicSponge { + /// `new_with_pp_hash` creates a new transcript / sponge with the given + /// hash of the public parameters. + fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self; + + /// `absorb_point` is for absorbing points whose `BaseField` is the field of + /// the sponge, i.e., the type `C` of these points should satisfy + /// `C::BaseField = F`. + /// + /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` + /// instead. + fn absorb_point>(&mut self, v: &C); + /// `absorb_nonnative` is for structs that contain non-native (field or + /// group) elements, including: + /// + /// - A field element of type `T: PrimeField` that will be absorbed into a + /// sponge that operates in another field `F != T`. + /// - A group element of type `C: CurveGroup` that will be absorbed into a + /// sponge that operates in another field `F != C::BaseField`, e.g., + /// `F = C::ScalarField`. + /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that + /// will be absorbed into a sponge that operates in the (scalar field of + /// the) primary curve. + /// + /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on + /// the primary curve also contains non-native elements, we still regard + /// it as native, because the sponge is on the same curve. + fn absorb_nonnative(&mut self, v: &V); + + fn get_challenge(&mut self) -> F; + /// get_challenge_nbits returns a field element of size nbits + fn get_challenge_nbits(&mut self, nbits: usize) -> Vec; + fn get_challenges(&mut self, n: usize) -> Vec; +} + +pub trait TranscriptVar: + CryptographicSpongeVar +{ + /// `new_with_pp_hash` creates a new transcript / sponge with the given + /// hash of the public parameters. + fn new_with_pp_hash( + config: &Self::Parameters, + pp_hash: &FpVar, + ) -> Result; + + /// `absorb_point` is for absorbing points whose `BaseField` is the field of + /// the sponge, i.e., the type `C` of these points should satisfy + /// `C::BaseField = F`. + /// + /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` + /// instead. + fn absorb_point, GC: CurveVar>( + &mut self, + v: &GC, + ) -> Result<(), SynthesisError>; + /// `absorb_nonnative` is for structs that contain non-native (field or + /// group) elements, including: + /// + /// - A field element of type `T: PrimeField` that will be absorbed into a + /// sponge that operates in another field `F != T`. + /// - A group element of type `C: CurveGroup` that will be absorbed into a + /// sponge that operates in another field `F != C::BaseField`, e.g., + /// `F = C::ScalarField`. + /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that + /// will be absorbed into a sponge that operates in the (scalar field of + /// the) primary curve. + /// + /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on + /// the primary curve also contains non-native elements, we still regard + /// it as native, because the sponge is on the same curve. + fn absorb_nonnative>( + &mut self, + v: &V, + ) -> Result<(), SynthesisError>; + + fn get_challenge(&mut self) -> Result, SynthesisError>; + /// returns the bit representation of the challenge, we use its output in-circuit for the + /// `GC.scalar_mul_le` method. + fn get_challenge_nbits(&mut self, nbits: usize) -> Result>, SynthesisError>; + fn get_challenges(&mut self, n: usize) -> Result>, SynthesisError>; +} diff --git a/crates/primitives/src/transcripts/poseidon.rs b/crates/primitives/src/transcripts/poseidon.rs new file mode 100644 index 000000000..7eb281113 --- /dev/null +++ b/crates/primitives/src/transcripts/poseidon.rs @@ -0,0 +1,277 @@ +use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, + poseidon::{ + constraints::PoseidonSpongeVar, find_poseidon_ark_and_mds, PoseidonConfig, PoseidonSponge, + }, + Absorb, CryptographicSponge, +}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; +use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; + +use super::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; + +impl Transcript for PoseidonSponge { + fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self { + let mut sponge = Self::new(config); + sponge.absorb(&pp_hash); + sponge + } + + // Compatible with the in-circuit `TranscriptVar::absorb_point` + fn absorb_point>(&mut self, p: &C) { + let (x, y) = p.into_affine().xy().unwrap_or_default(); + self.absorb(&x); + self.absorb(&y); + } + fn absorb_nonnative(&mut self, v: &V) { + self.absorb(&v.to_native_sponge_field_elements_as_vec::()); + } + fn get_challenge(&mut self) -> F { + let c = self.squeeze_field_elements(1); + self.absorb(&c[0]); + c[0] + } + fn get_challenge_nbits(&mut self, nbits: usize) -> Vec { + let bits = self.squeeze_bits(nbits); + self.absorb(&F::from(F::BigInt::from_bits_le(&bits))); + bits + } + fn get_challenges(&mut self, n: usize) -> Vec { + let c = self.squeeze_field_elements(n); + self.absorb(&c); + c + } +} + +impl TranscriptVar> for PoseidonSpongeVar { + fn new_with_pp_hash( + config: &Self::Parameters, + pp_hash: &FpVar, + ) -> Result { + let mut sponge = Self::new(ConstraintSystemRef::None, config); + sponge.absorb(&pp_hash)?; + Ok(sponge) + } + + fn absorb_point, GC: CurveVar>( + &mut self, + v: &GC, + ) -> Result<(), SynthesisError> { + let mut vec = v.to_constraint_field()?; + // The last element in the vector tells whether the point is infinity, + // but we can in fact avoid absorbing it without loss of soundness. + // This is because the `to_constraint_field` method internally invokes + // [`ProjectiveVar::to_afine`](https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/curves/short_weierstrass/mod.rs#L160-L195), + // which guarantees that an infinity point is represented as `(0, 0)`, + // but the y-coordinate of a non-infinity point is never 0 (for why, see + // https://crypto.stackexchange.com/a/108242 ). + vec.pop(); + self.absorb(&vec) + } + fn absorb_nonnative>( + &mut self, + v: &V, + ) -> Result<(), SynthesisError> { + self.absorb(&v.to_native_sponge_field_elements()?) + } + fn get_challenge(&mut self) -> Result, SynthesisError> { + let c = self.squeeze_field_elements(1)?; + self.absorb(&c[0])?; + Ok(c[0].clone()) + } + + /// returns the bit representation of the challenge, we use its output in-circuit for the + /// `GC.scalar_mul_le` method. + fn get_challenge_nbits(&mut self, nbits: usize) -> Result>, SynthesisError> { + let bits = self.squeeze_bits(nbits)?; + self.absorb(&Boolean::le_bits_to_fp(&bits)?)?; + Ok(bits) + } + fn get_challenges(&mut self, n: usize) -> Result>, SynthesisError> { + let c = self.squeeze_field_elements(n)?; + self.absorb(&c)?; + Ok(c) + } +} + +/// This Poseidon configuration generator produces a Poseidon configuration with custom parameters +pub fn poseidon_custom_config( + full_rounds: usize, + partial_rounds: usize, + alpha: u64, + rate: usize, + capacity: usize, +) -> PoseidonConfig { + let (ark, mds) = find_poseidon_ark_and_mds::( + F::MODULUS_BIT_SIZE as u64, + rate, + full_rounds as u64, + partial_rounds as u64, + 0, + ); + + PoseidonConfig::new(full_rounds, partial_rounds, alpha, mds, ark, rate, capacity) +} + +/// This Poseidon configuration generator agrees with Circom's Poseidon(4) in the case of BN254's scalar field +pub fn poseidon_canonical_config() -> PoseidonConfig { + // 120 bit security target as in + // https://eprint.iacr.org/2019/458.pdf + // t = rate + 1 + + let full_rounds = 8; + let partial_rounds = 60; + let alpha = 5; + let rate = 4; + + poseidon_custom_config(full_rounds, partial_rounds, alpha, rate, 1) +} + +#[cfg(test)] +pub mod tests { + use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; + use ark_ec::PrimeGroup; + use ark_ff::UniformRand; + use ark_r1cs_std::{ + alloc::AllocVar, groups::curves::short_weierstrass::ProjectiveVar, GR1CSVar, + }; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{error::Error, test_rng}; + + use super::*; + use crate::gadgets::nonnative::affine::NonNativeAffineVar; + + // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 + #[test] + fn check_against_circom_poseidon() -> Result<(), Box> { + use std::str::FromStr; + + let config = poseidon_canonical_config::(); + let mut poseidon_sponge: PoseidonSponge<_> = CryptographicSponge::new(&config); + let v: Vec = vec![1, 2, 3, 4] + .into_iter() + .map(|x| Fr::from(x)) + .collect::>(); + poseidon_sponge.absorb(&v); + poseidon_sponge.squeeze_field_elements::(1); + assert!( + poseidon_sponge.state[0] + == Fr::from_str( + "18821383157269793795438455681495246036402687001665670618754263018637548127333" + ) + .unwrap() + ); + Ok(()) + } + + #[test] + fn test_transcript_and_transcriptvar_absorb_native_point() -> Result<(), Box> { + // use 'native' transcript + let config = poseidon_canonical_config::(); + let mut tr = PoseidonSponge::::new(&config); + let rng = &mut test_rng(); + + let p = G1::rand(rng); + tr.absorb_point(&p); + let c = tr.get_challenge(); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); + let p_var = ProjectiveVar::>::new_witness( + ConstraintSystem::::new_ref(), + || Ok(p), + )?; + tr_var.absorb_point(&p_var)?; + let c_var = tr_var.get_challenge()?; + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } + + #[test] + fn test_transcript_and_transcriptvar_absorb_nonnative_point() -> Result<(), Box> { + // use 'native' transcript + let config = poseidon_canonical_config::(); + let mut tr = PoseidonSponge::::new(&config); + let rng = &mut test_rng(); + + let p = G1::rand(rng); + tr.absorb_nonnative(&p); + let c = tr.get_challenge(); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); + let p_var = + NonNativeAffineVar::::new_witness(ConstraintSystem::::new_ref(), || Ok(p))?; + tr_var.absorb_nonnative(&p_var)?; + let c_var = tr_var.get_challenge()?; + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } + + #[test] + fn test_transcript_and_transcriptvar_get_challenge() -> Result<(), Box> { + // use 'native' transcript + let config = poseidon_canonical_config::(); + let mut tr = PoseidonSponge::::new(&config); + tr.absorb(&Fr::from(42_u32)); + let c = tr.get_challenge(); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); + let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; + tr_var.absorb(&v)?; + let c_var = tr_var.get_challenge()?; + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } + + #[test] + fn test_transcript_and_transcriptvar_nbits() -> Result<(), Box> { + let nbits = 128; + + // use 'native' transcript + let config = poseidon_canonical_config::(); + let mut tr = PoseidonSponge::::new(&config); + tr.absorb(&Fq::from(42_u32)); + + // get challenge from native transcript + let c_bits = tr.get_challenge_nbits(nbits); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); + let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; + tr_var.absorb(&v)?; + + // get challenge from circuit transcript + let c_var = tr_var.get_challenge_nbits(nbits)?; + + let p = G1::generator(); + let p_var = GVar::new_witness(cs.clone(), || Ok(p))?; + + // multiply point P by the challenge in different formats, to ensure that we get the same + // result natively and in-circuit + let c = Fr::from(::BigInt::from_bits_le(&c_bits)); + + // check that native c*P and in-circuit c*P using scalar_mul_le are equal + assert_eq!(p * c, p_var.scalar_mul_le(c_var.iter())?.value()?); + // check that native c*P using mul_bits_be and in-circuit c*P using scalar_mul_le are equal + // (notice the .rev to convert the LE to BE) + assert_eq!( + p.mul_bits_be(c_bits.into_iter().rev()), + p_var.scalar_mul_le(c_var.iter())?.value()? + ); + Ok(()) + } +} From 4fd44723a55512ff09a5d5002e996c1c42aa0d6f Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 04/99] Refactor: port sumcheck, circuit traits, and more --- Cargo.toml | 2 - crates/primitives/Cargo.toml | 5 +- .../src/arithmetizations/ccs/circuits.rs | 18 +- .../src/arithmetizations/ccs/mod.rs | 73 ++-- crates/primitives/src/arithmetizations/mod.rs | 133 +----- .../src/arithmetizations/r1cs/circuits.rs | 14 +- .../src/arithmetizations/r1cs/mod.rs | 282 +++++++++---- crates/primitives/src/circuits/mod.rs | 161 +++++++ crates/primitives/src/circuits/utils.rs | 80 ++++ crates/primitives/src/commitments/mod.rs | 47 ++- crates/primitives/src/commitments/pedersen.rs | 52 ++- .../src/gadgets/nonnative/affine.rs | 16 +- .../primitives/src/gadgets/nonnative/uint.rs | 21 +- crates/primitives/src/lib.rs | 6 +- crates/primitives/src/relations/mod.rs | 52 +++ crates/primitives/src/sumcheck/mod.rs | 291 +++++++++++++ crates/primitives/src/sumcheck/utils.rs | 399 ++++++++++++++++++ .../lib.rs => primitives/src/traits/mod.rs} | 139 ++++-- crates/primitives/src/transcripts/mod.rs | 3 +- crates/primitives/src/transcripts/poseidon.rs | 4 +- crates/traits/Cargo.toml | 21 - 21 files changed, 1437 insertions(+), 382 deletions(-) create mode 100644 crates/primitives/src/circuits/mod.rs create mode 100644 crates/primitives/src/circuits/utils.rs create mode 100644 crates/primitives/src/relations/mod.rs create mode 100644 crates/primitives/src/sumcheck/mod.rs create mode 100644 crates/primitives/src/sumcheck/utils.rs rename crates/{traits/src/lib.rs => primitives/src/traits/mod.rs} (73%) delete mode 100644 crates/traits/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index e8fe65f39..b56b4da35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "crates/primitives", - "crates/traits", ] resolver = "2" @@ -79,4 +78,3 @@ ark-vesta = { version = "^0.5.0" } # Local crates sonobe-primitives = { path = "crates/primitives", default-features = false } -sonobe-traits = { path = "crates/traits" } \ No newline at end of file diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index feca812a6..efea822bc 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -20,17 +20,14 @@ num-integer = { workspace = true } rayon = { workspace = true } thiserror = { workspace = true } -sonobe-traits = { workspace = true } - [dev-dependencies] ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } ark-pallas = { workspace = true, features = ["curve", "r1cs"] } -ark-vesta = { workspace = true, features = ["r1cs"] } [features] default = ["parallel"] parallel = [ + "ark-poly-commit/parallel", "ark-relations/parallel", "ark-r1cs-std/parallel", - "sonobe-traits/parallel", ] \ No newline at end of file diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs index f239d691f..60fec5c33 100644 --- a/crates/primitives/src/arithmetizations/ccs/circuits.rs +++ b/crates/primitives/src/arithmetizations/ccs/circuits.rs @@ -6,9 +6,10 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::borrow::Borrow; -use super::CCS; use crate::gadgets::math::matrix::SparseMatrixVar; +use super::CCS; + /// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. #[derive(Debug, Clone)] pub struct CCSMatricesVar { @@ -24,13 +25,14 @@ impl AllocVar, F> for CCSMatricesVar { ) -> Result { f().and_then(|val| { let cs = cs.into(); - let M: Vec>> = val - .borrow() - .M - .iter() - .map(|M| SparseMatrixVar::>::new_constant(cs.clone(), M.clone())) - .collect::>()?; - Ok(Self { M }) + Ok(Self { + M: val + .borrow() + .M + .iter() + .map(|M| SparseMatrixVar::>::new_constant(cs.clone(), M.clone())) + .collect::>()?, + }) }) } } diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 32357dd01..8418f8a4b 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -1,12 +1,13 @@ use ark_ff::Field; +use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::Matrix; use ark_std::{cfg_into_iter, log2}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use crate::arithmetizations::{Assignments, Error}; +use crate::circuits::Assignments; -use super::{r1cs::R1CS, Arith, ArithRelation, ArithSerializer}; +use super::{r1cs::R1CS, Arith, ArithRelation, Error}; pub mod circuits; @@ -15,17 +16,15 @@ pub mod circuits; #[derive(Debug, Clone, Eq, PartialEq)] pub struct CCS { /// m: number of rows in M_i (such that M_i \in F^{m, n}) - m: usize, + pub m: usize, /// n = |z|, number of cols in M_i - n: usize, + pub n: usize, /// l = |io|, size of public input/output - l: usize, + pub l: usize, /// t = |M|, number of matrices pub t: usize, - /// q = |c| = |S|, number of multisets - q: usize, /// d: max degree in each variable - d: usize, + pub d: usize, /// s = log(m), dimension of x pub s: usize, @@ -39,7 +38,23 @@ pub struct CCS { impl CCS { /// Evaluates the CCS relation at a given vector of assignments `z` - pub fn eval_at_z(&self, z: Assignments) -> Result, Error> { + pub fn eval_assignments( + &self, + z: Assignments + Sync>, + ) -> Result, Error> { + let public_len = z.public.as_ref().len(); + let private_len = z.private.as_ref().len(); + if public_len != self.n_public_inputs() { + return Err(Error::MalformedAssignments( + format!("The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", self.n_public_inputs(), public_len) + )); + } + if private_len != self.n_witnesses() { + return Err(Error::MalformedAssignments( + format!("The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", self.n_witnesses(), private_len) + )); + } + // Recall that the evaluation of CCS at z is defined as: // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i * z))$, // where $\prod$ denotes the Hadamard product. @@ -77,6 +92,21 @@ impl CCS { }) .collect()) } + + pub fn mle( + &self, + i: usize, + z: Assignments>, + ) -> DenseMultilinearExtension { + DenseMultilinearExtension { + num_vars: self.s, + evaluations: self.M[i] + .iter() + .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) + .chain(vec![F::zero(); (1 << self.s) - self.m]) + .collect(), + } + } } impl Arith for CCS { @@ -106,14 +136,14 @@ impl Arith for CCS { } } -impl, U: AsRef<[F]>> ArithRelation for CCS { +impl ArithRelation, Vec> for CCS { type Evaluation = Vec; - fn eval_relation(&self, w: &W, u: &U) -> Result { - self.eval_at_z((F::one(), u.as_ref(), w.as_ref()).into()) + fn eval_relation(&self, w: &[F], u: &[F]) -> Result { + self.eval_assignments((F::one(), u, w).into()) } - fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { + fn check_evaluation(_w: &[F], _u: &[F], e: Self::Evaluation) -> Result<(), Error> { cfg_into_iter!(e) .all(|i| i.is_zero()) .then_some(()) @@ -123,20 +153,6 @@ impl, U: AsRef<[F]>> ArithRelation for CCS { } } -impl ArithSerializer for CCS { - fn params_to_le_bytes(&self) -> Vec { - [ - self.l.to_le_bytes(), - self.m.to_le_bytes(), - self.n.to_le_bytes(), - self.t.to_le_bytes(), - self.q.to_le_bytes(), - self.d.to_le_bytes(), - ] - .concat() - } -} - impl From> for CCS { fn from(r1cs: R1CS) -> Self { let m = r1cs.n_constraints(); @@ -147,7 +163,6 @@ impl From> for CCS { l: r1cs.n_public_inputs(), s: log2(m) as usize, t: 3, - q: 2, d: r1cs.degree(), S: vec![vec![0, 1], vec![2]], @@ -157,4 +172,4 @@ impl From> for CCS { } } -// TODO: add back tests \ No newline at end of file +// TODO: add back tests diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 24adb29e3..5567ef644 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -1,10 +1,8 @@ -use std::ops::Index; - use ark_relations::gr1cs::SynthesisError; -use ark_std::rand::RngCore; -use sonobe_traits::Dummy; use thiserror::Error; +use crate::relations::{Referenceable, Relation}; + pub mod ccs; pub mod r1cs; @@ -16,66 +14,8 @@ pub enum Error { UnsatisfiedAssignments(String), #[error("Failed to extract constraints from the constraint system: {0}")] ConstraintExtractionFailure(String), -} - -pub struct Assignments<'a, F> { - pub constant: F, - pub public: &'a [F], - pub private: &'a [F], -} - -impl<'a, F> From<(F, &'a [F], &'a [F])> for Assignments<'a, F> { - fn from((u, x, w): (F, &'a [F], &'a [F])) -> Self { - Self { - constant: u, - public: x, - private: w, - } - } -} - -impl<'a, F> Index for Assignments<'a, F> { - type Output = F; - - fn index(&self, index: usize) -> &Self::Output { - if index == 0 { - &self.constant - } else if index <= self.public.len() { - &self.public[index - 1] - } else { - &self.private[index - 1 - self.public.len()] - } - } -} - -pub struct AssignmentsVar<'a, FV> { - pub constant: FV, - pub public: &'a [FV], - pub private: &'a [FV], -} - -impl<'a, FV> From<(FV, &'a [FV], &'a [FV])> for AssignmentsVar<'a, FV> { - fn from((u, x, w): (FV, &'a [FV], &'a [FV])) -> Self { - Self { - constant: u, - public: x, - private: w, - } - } -} - -impl<'a, FV> Index for AssignmentsVar<'a, FV> { - type Output = FV; - - fn index(&self, index: usize) -> &Self::Output { - if index == 0 { - &self.constant - } else if index <= self.public.len() { - &self.public[index - 1] - } else { - &self.private[index - 1 - self.public.len()] - } - } + #[error("Synthesis error: {0}")] + SynthesisError(#[from] SynthesisError), } /// [`Arith`] is a trait about constraint systems (R1CS, CCS, etc.), where we @@ -131,7 +71,7 @@ pub trait Arith: Clone { /// This is also the case of CCS, where `W` and `U` may be vectors of field /// elements, [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::lcccs::LCCCS`], /// or [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::cccs::CCCS`]. -pub trait ArithRelation: Arith { +pub trait ArithRelation: Arith { type Evaluation; /// Evaluates the constraint system `self` at witness `w` and instance `u`. @@ -150,7 +90,7 @@ pub trait ArithRelation: Arith { /// /// However, we use `Self::Evaluation` to represent the evaluation result /// for future extensibility. - fn eval_relation(&self, w: &W, u: &U) -> Result; + fn eval_relation(&self, w: W::Ref<'_>, u: U::Ref<'_>) -> Result; /// Checks if the evaluation result is valid. The witness `w` and instance /// `u` are also parameters, because the validity check may need information @@ -166,26 +106,10 @@ pub trait ArithRelation: Arith { /// - The evaluation `v` of relaxed R1CS in ProtoGalaxy at satisfying `W` /// and `U` should satisfy `e = Σ pow_i(β) v_i`, where `e` is the error /// term in the committed instance. - fn check_evaluation(w: &W, u: &U, v: Self::Evaluation) -> Result<(), Error>; + fn check_evaluation(w: W::Ref<'_>, u: U::Ref<'_>, v: Self::Evaluation) -> Result<(), Error>; } -pub trait Relation { - type Error; - - /// Returns a dummy witness and instance - fn dummy_witness_instance<'a>(&'a self) -> (W, U) - where - W: Dummy<&'a Self>, - U: Dummy<&'a Self>, - { - (W::dummy(self), U::dummy(self)) - } - - /// Checks if witness `w` and instance `u` satisfy the relation `self` - fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error>; -} - -impl> Relation for A { +impl> Relation for A { type Error = Error; /// Checks if witness `w` and instance `u` satisfy the constraint system @@ -193,51 +117,12 @@ impl> Relation for A { /// validity of the evaluation result. /// /// Used only for testing. - fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error> { + fn check_relation(&self, w: W::Ref<'_>, u: U::Ref<'_>) -> Result<(), Self::Error> { let e = self.eval_relation(w, u)?; Self::check_evaluation(w, u, e) } } -/// `ArithSerializer` is for serializing constraint systems. -/// -/// Currently we only support converting parameters to bytes, but in the future -/// we may consider implementing methods for serializing the actual data (e.g., -/// R1CS matrices). -pub trait ArithSerializer { - /// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of - /// public inputs, etc, without the matrices/polynomials values. - fn params_to_le_bytes(&self) -> Vec; -} - -/// `ArithSampler` allows sampling random pairs of witness and instance that -/// satisfy the constraint system `self`. -/// -/// This is useful for constructing a zero-knowledge layer for a folding-based -/// IVC. -/// An example of such a layer can be found in Appendix D of the [HyperNova] -/// paper. -/// -/// Note that we use a separate trait for sampling, because this operation may -/// not be supported by all witness-instance pairs. -/// For instance, it is difficult (if not impossible) to do this for `w` and `x` -/// in a plain R1CS. -/// -/// [HyperNova]: https://eprint.iacr.org/2023/573.pdf -pub trait ArithSampler { - fn sample_witness_instance() { - todo!() - } -} -// pub trait ArithSampler: ArithRelation { -// /// Samples a random witness and instance that satisfy the constraint system. -// fn sample_witness_instance>( -// &self, -// params: &CS::ProverParams, -// rng: impl RngCore, -// ) -> Result<(W, U), Error>; -// } - /// `ArithRelationGadget` defines the in-circuit counterparts of operations /// specified in `ArithRelation` on constraint systems. pub trait ArithRelationGadget { diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index 9aadc648b..f31fd632f 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -3,9 +3,9 @@ use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData, One}; -use super::R1CS; use crate::{ - arithmetizations::{ArithRelationGadget, AssignmentsVar}, + arithmetizations::ArithRelationGadget, + circuits::Assignments, gadgets::math::{ eq::EquivalenceGadget, matrix::{MatrixGadget, SparseMatrixVar}, @@ -13,6 +13,8 @@ use crate::{ }, }; +use super::R1CS; + /// An in-circuit representation of the `R1CS` struct. /// /// `M` is for the modulo operation involved in the satisfiability check when @@ -51,9 +53,9 @@ where SparseMatrixVar: MatrixGadget, [FVar]: VectorGadget, { - pub fn eval_at_z( + pub fn eval_assignments( &self, - z: AssignmentsVar, + z: Assignments>, ) -> Result<(Vec, Vec), SynthesisError> { // Multiply Cz by z[0] (u) here, allowing this method to be reused for // both relaxed and unrelaxed R1CS. @@ -79,7 +81,7 @@ where type Evaluation = (Vec, Vec); fn eval_relation(&self, w: &WVar, u: &UVar) -> Result { - self.eval_at_z((FVar::one(), u.as_ref(), w.as_ref()).into()) + self.eval_assignments((FVar::one(), u.as_ref(), w.as_ref()).into()) } fn enforce_evaluation( @@ -91,4 +93,4 @@ where } } -// TODO: add back tests \ No newline at end of file +// TODO: add back tests diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 4804ee19e..76eae96ce 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -1,14 +1,17 @@ use ark_ff::Field; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{cfg_into_iter, cfg_iter, rand::Rng}; +use ark_std::{cfg_into_iter, cfg_iter}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use sonobe_traits::Dummy; +use crate::{ + circuits::{Assignments, ConstraintSystemExt}, + relations::{Referenceable, WitnessInstanceExtractor}, + traits::Dummy, +}; -use super::{ccs::CCS, Arith, ArithRelation, ArithSerializer}; -use crate::arithmetizations::{Assignments, Error}; +use super::{ccs::CCS, Arith, ArithRelation, Error}; pub mod circuits; @@ -23,16 +26,21 @@ pub struct R1CS { } impl R1CS { - /// Evaluates the R1CS relation at a given vector of variables `z` - pub fn eval_at_z(&self, z: Assignments) -> Result, Error> { - if z.public.len() != self.n_public_inputs() { + /// Evaluates the R1CS relation at a given vector of assignments `z` + pub fn eval_assignments( + &self, + z: Assignments + Sync>, + ) -> Result, Error> { + let public_len = z.public.as_ref().len(); + let private_len = z.private.as_ref().len(); + if public_len != self.n_public_inputs() { return Err(Error::MalformedAssignments( - format!("The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", self.n_public_inputs(), z.public.len()) + format!("The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", self.n_public_inputs(), public_len) )); } - if z.private.len() != self.n_witnesses() { + if private_len != self.n_witnesses() { return Err(Error::MalformedAssignments( - format!("The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", self.n_witnesses(), z.private.len()) + format!("The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", self.n_witnesses(), private_len) )); } @@ -76,34 +84,6 @@ impl Arith for R1CS { } } -impl, U: AsRef<[F]>> ArithRelation for R1CS { - type Evaluation = Vec; - - fn eval_relation(&self, w: &W, u: &U) -> Result { - self.eval_at_z((F::one(), u.as_ref(), w.as_ref()).into()) - } - - fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { - cfg_into_iter!(e) - .all(|i| i.is_zero()) - .then_some(()) - .ok_or(Error::UnsatisfiedAssignments( - "Evaluation contains non-zero values".into(), - )) - } -} - -impl ArithSerializer for R1CS { - fn params_to_le_bytes(&self) -> Vec { - [ - self.l.to_le_bytes(), - self.m.to_le_bytes(), - self.n.to_le_bytes(), - ] - .concat() - } -} - impl Dummy<(usize, usize, usize)> for R1CS { fn dummy((n_constraints, n_variables, n_public_inputs): (usize, usize, usize)) -> Self { Self { @@ -124,28 +104,20 @@ impl R1CS { pub fn new( (n_constraints, n_variables, n_public_inputs): (usize, usize, usize), - mut matrices: Vec>, - ) -> Result { - // R1CS should have exactly 3 matrices (A, B, C) - if matrices.len() != 3 { - return Err(Error::ConstraintExtractionFailure(format!( - "R1CS should only have 3 matrices (A, B, C) but found {} matrices", - matrices.len() - ))); - } - + matrices: [Matrix; 3], + ) -> Self { + let mut matrices = matrices.to_vec(); let C = matrices.pop().unwrap(); let B = matrices.pop().unwrap(); let A = matrices.pop().unwrap(); - - Ok(Self { + Self { m: n_constraints, n: n_variables, l: n_public_inputs, A, B, C, - }) + } } } @@ -153,53 +125,193 @@ impl TryFrom> for R1CS { type Error = Error; fn try_from(ccs: CCS) -> Result { - Self::new( + if ccs.t != 3 { + return Err(Error::ConstraintExtractionFailure(format!( + "R1CS should only have 3 matrices (A, B, C) but found {} matrices", + ccs.t + ))); + } + Ok(Self::new( ( ccs.n_constraints(), ccs.n_variables(), ccs.n_public_inputs(), ), - ccs.M, - ) + ccs.M.try_into().unwrap(), + )) + } +} + +impl ArithRelation, Vec> for R1CS { + type Evaluation = Vec; + + fn eval_relation(&self, w: &[F], x: &[F]) -> Result { + self.eval_assignments((F::one(), x.as_ref(), w.as_ref()).into()) + } + + fn check_evaluation(_w: &[F], _x: &[F], e: Self::Evaluation) -> Result<(), Error> { + cfg_into_iter!(e) + .all(|i| i.is_zero()) + .then_some(()) + .ok_or(Error::UnsatisfiedAssignments( + "Evaluation contains non-zero values".into(), + )) + } +} + +impl WitnessInstanceExtractor, Vec> for R1CS { + type Source = Assignments>; + type Error = Error; + + fn extract(&self, z: Self::Source) -> Result<(Vec, Vec), Error> { + Ok((z.private, z.public)) + } +} + +pub struct RelaxedWitness { + pub w: Vec, + pub e: Vec, +} + +impl Referenceable for RelaxedWitness { + type Ref<'a> = (&'a [F], &'a [F]); + + fn reference(&self) -> Self::Ref<'_> { + (&self.w, &self.e) + } +} + +pub struct RelaxedInstance { + pub x: Vec, + pub u: F, +} + +impl Referenceable for RelaxedInstance { + type Ref<'a> = (&'a [F], F); + + fn reference(&self) -> Self::Ref<'_> { + (&self.x, self.u) + } +} + +impl ArithRelation, RelaxedInstance> for R1CS { + type Evaluation = Vec; + + fn eval_relation( + &self, + (w, _e): (&[F], &[F]), + (x, u): (&[F], F), + ) -> Result { + self.eval_assignments((u, x, w).into()) + } + + fn check_evaluation( + (_w, e): (&[F], &[F]), + _: (&[F], F), + v: Self::Evaluation, + ) -> Result<(), Error> { + cfg_iter!(e) + .zip(&v) + .all(|(e, v)| e == v) + .then_some(()) + .ok_or(Error::UnsatisfiedAssignments( + "Evaluation does not match error term".into(), + )) } } -/// Extracts R1CS from arkworks ConstraintSystem matrices -impl TryFrom<&ConstraintSystem> for R1CS { +impl WitnessInstanceExtractor, RelaxedInstance> for R1CS { + type Source = Assignments>; type Error = Error; - fn try_from(cs: &ConstraintSystem) -> Result { + fn extract(&self, z: Self::Source) -> Result<(RelaxedWitness, RelaxedInstance), Error> { + let (w, x) = self.extract(z)?; + let e = vec![F::zero(); self.n_constraints()]; + Ok((RelaxedWitness { w, e }, RelaxedInstance { x, u: F::one() })) + } +} + +impl ConstraintSystemExt for ConstraintSystem { + type Arith = R1CS; + type Error = Error; + + fn constraints(&self) -> Result, Error> { // Get the R1CS predicate matrices - let r1cs_predicate = cs.predicate_constraint_systems.get("R1CS").ok_or_else(|| { - Error::ConstraintExtractionFailure( - "No R1CS predicate found in constraint system".into(), - ) - })?; - Self::new( + let r1cs_predicate = self + .predicate_constraint_systems + .get("R1CS") + .ok_or_else(|| { + Error::ConstraintExtractionFailure( + "No R1CS predicate found in constraint system".into(), + ) + })?; + let matrices = r1cs_predicate.to_matrices(self); + if matrices.len() != 3 { + return Err(Error::ConstraintExtractionFailure(format!( + "R1CS should only have 3 matrices (A, B, C) but found {} matrices", + matrices.len() + ))); + } + Ok(R1CS::new( ( - cs.num_constraints(), - cs.num_instance_variables + cs.num_witness_variables, - cs.num_instance_variables - 1, // -1 to subtract the first '1' + self.num_constraints(), + self.num_instance_variables + self.num_witness_variables, + self.num_instance_variables - 1, // -1 to subtract the first '1' ), - r1cs_predicate.to_matrices(cs), - ) - } -} - -/// extracts the witness and the public inputs from arkworks ConstraintSystem. -pub fn extract_w_x(cs: &ConstraintSystem) -> (Vec, Vec) { - let witness = cs - .witness_assignment() - .expect("witness_assignment failed") - .to_vec(); - let instance = cs - .instance_assignment() - .expect("instance_assignment failed"); - ( - witness, + matrices.try_into().unwrap(), + )) + } + + fn assignments(&self) -> Result>, Error> { + let witness = self.witness_assignment()?.to_vec(); // skip the first element which is '1' - instance[1..].to_vec(), - ) + let instance = self.instance_assignment()?[1..].to_vec(); + + Ok((F::one(), instance, witness).into()) + } } -// TODO: add back tests \ No newline at end of file +#[cfg(test)] +pub mod tests { + use ark_bn254::Fr; + use ark_ff::UniformRand; + use ark_relations::gr1cs::ConstraintSynthesizer; + use ark_std::{error::Error, test_rng}; + + use crate::circuits::utils::{ + constraints_for_test, satisfying_assignments_for_test, CircuitForTest, + }; + + use super::*; + + #[test] + fn test_constraint_extraction() -> Result<(), Box> { + let mut rng = test_rng(); + let circuit = CircuitForTest:: { + x: Fr::rand(&mut rng), + }; + let cs = ConstraintSystem::new_ref(); + circuit.generate_constraints(cs.clone()).unwrap(); + assert!(cs.is_satisfied()?); + cs.finalize(); + let cs = cs.into_inner().unwrap(); + + assert_eq!(cs.constraints()?, constraints_for_test()); + Ok(()) + } + + #[test] + fn test_witness_extraction() -> Result<(), Box> { + let mut rng = test_rng(); + let x = Fr::rand(&mut rng); + let circuit = CircuitForTest:: { x }; + let cs = ConstraintSystem::new_ref(); + circuit.generate_constraints(cs.clone()).unwrap(); + assert!(cs.is_satisfied()?); + cs.finalize(); + let cs = cs.into_inner().unwrap(); + + assert_eq!(cs.assignments()?, satisfying_assignments_for_test(x)); + Ok(()) + } +} diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs new file mode 100644 index 000000000..0a0069ea1 --- /dev/null +++ b/crates/primitives/src/circuits/mod.rs @@ -0,0 +1,161 @@ +use ark_ff::{Field, PrimeField}; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::gr1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, +}; +use ark_std::{marker::PhantomData, ops::{Index, IndexMut}}; + +pub mod utils; + +/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. +/// inside the agmented F' function). +/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying +/// the step. +/// Note that the external inputs for the specific circuit are defined at the implementation of +/// both `FCircuit::ExternalInputs` and `FCircuit::ExternalInputsVar`, where the `Default` trait +/// implementation for the `ExternalInputs` returns the initialized data structure (ie. if the type +/// contains a vector, it is initialized at the expected length). +pub trait FCircuit { + type ExternalInputs; + + /// returns the number of elements in the state of the FCircuit, which corresponds to the + /// FCircuit inputs. + fn state_len(&self) -> usize; + + /// generates the constraints for the step of F for the given z_i + fn generate_step_constraints( + // this method uses self, so that each FCircuit implementation (and different frontends) + // can hold a state if needed to store data to generate the constraints. + &self, + cs: ConstraintSystemRef, + i: usize, + z_i: Vec>, + external_inputs: Self::ExternalInputs, // inputs that are not part of the state + ) -> Result>, SynthesisError>; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Assignments { + pub constant: F, + pub public: V, + pub private: V, +} + +pub type AssignmentsOwned = Assignments>; +pub type AssignmentsRef<'a, F> = Assignments; + +impl From<(F, V, V)> for Assignments { + fn from((u, x, w): (F, V, V)) -> Self { + Self { + constant: u, + public: x, + private: w, + } + } +} + +impl> Index for Assignments { + type Output = F; + + fn index(&self, index: usize) -> &Self::Output { + let public = self.public.as_ref(); + let private = self.private.as_ref(); + if index == 0 { + &self.constant + } else if index <= public.len() { + &public[index - 1] + } else { + &private[index - 1 - public.len()] + } + } +} + +impl + AsMut<[F]>> IndexMut for Assignments { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + let public = self.public.as_mut(); + let private = self.private.as_mut(); + if index == 0 { + &mut self.constant + } else if index <= public.len() { + &mut public[index - 1] + } else { + &mut private[index - 1 - public.len()] + } + } +} + +pub struct ConstraintSystemBuilder { + _f: PhantomData, + mode: SynthesisMode, + circuit: C, +} + +impl ConstraintSystemBuilder<(), ()> { + pub fn new() -> Self { + Self { + _f: PhantomData, + mode: SynthesisMode::Prove { + construct_matrices: true, + generate_lc_assignments: true, + }, + circuit: (), + } + } +} + +impl Default for ConstraintSystemBuilder<(), ()> { + fn default() -> Self { + Self::new() + } +} + +impl ConstraintSystemBuilder { + pub fn with_setup_mode(self) -> Self { + Self { + _f: PhantomData, + mode: SynthesisMode::Setup, + circuit: self.circuit, + } + } + + pub fn with_prove_mode(self) -> Self { + Self { + _f: PhantomData, + mode: SynthesisMode::Prove { + construct_matrices: true, + generate_lc_assignments: true, + }, + circuit: self.circuit, + } + } + + pub fn with_circuit>( + self, + circuit: C, + ) -> ConstraintSystemBuilder { + ConstraintSystemBuilder { + _f: PhantomData, + mode: self.mode, + circuit, + } + } +} + +impl> ConstraintSystemBuilder { + pub fn synthesize(self) -> Result, SynthesisError> { + let cs = ConstraintSystem::::new_ref(); + cs.set_mode(self.mode); + self.circuit.generate_constraints(cs.clone())?; + cs.finalize(); + Ok(cs.into_inner().unwrap()) + } +} + +pub trait ConstraintSystemExt: Sized { + type Error; + type Arith; + + fn constraints(&self) -> Result; + + fn assignments(&self) -> Result>, Self::Error>; +} diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs new file mode 100644 index 000000000..2b01cf1ba --- /dev/null +++ b/crates/primitives/src/circuits/utils.rs @@ -0,0 +1,80 @@ +use ark_ff::{Field, PrimeField}; +use ark_r1cs_std::{alloc::AllocVar, fields::fp::AllocatedFp}; +use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable}; + +use crate::arithmetizations::r1cs::R1CS; + +use super::Assignments; + +pub struct CircuitForTest { + pub x: F, +} +impl ConstraintSynthesizer for CircuitForTest { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + // Variable 0 (implicitly added by arkworks as 1) + // Variable 1 + let x = AllocatedFp::new_input(cs.clone(), || Ok(self.x))?; + // Variable 2 + let y = AllocatedFp::new_witness(cs.clone(), || Ok(self.x.pow([3]) + self.x + F::from(5)))?; + + // Variable 3, Constraint 0 + let x_square = x.square()?; + // Variable 4, Constraint 1 + let x_cube = x_square.mul(&x); + // Variable 5 + let t = AllocatedFp::new_witness(cs.clone(), || Ok(self.x.pow([3]) + self.x))?; + let x_cube_plus_x = x.add(&x_cube); + // Constraint 2 + cs.enforce_r1cs_constraint( + || x_cube_plus_x.variable.into(), + || Variable::one().into(), + || t.variable.into(), + )?; + let x_cube_plus_x_plus_5 = t.add_constant(F::from(5)); + // Constraint 3 + cs.enforce_r1cs_constraint( + || x_cube_plus_x_plus_5.variable.into(), + || Variable::one().into(), + || y.variable.into(), + )?; + Ok(()) + } +} + +pub fn constraints_for_test() -> R1CS { + // R1CS for: x^3 + x + 5 = y (example from article + // https://vitalik.eth.limo/general/2016/12/10/qap.html) + let A = vec![ + vec![(F::one(), 1)], + vec![(F::one(), 3)], + vec![(F::one(), 1), (F::one(), 4)], + vec![(F::from(5), 0), (F::one(), 5)], + ]; + let B = vec![ + vec![(F::one(), 1)], + vec![(F::one(), 1)], + vec![(F::one(), 0)], + vec![(F::one(), 0)], + ]; + let C = vec![ + vec![(F::one(), 3)], + vec![(F::one(), 4)], + vec![(F::one(), 5)], + vec![(F::one(), 2)], + ]; + + R1CS::::new((4, 6, 1), [A, B, C]) +} + +pub fn satisfying_assignments_for_test(x: F) -> Assignments> { + Assignments::from(( + F::one(), + vec![x], + vec![ + x * x * x + x + F::from(5), // x^3 + x + 5 + x * x, // x^2 + x * x * x, // x^2 * x + x * x * x + x, // x^3 + x + ], + )) +} diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index dac037619..593ba7879 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,11 +1,11 @@ -use ark_r1cs_std::alloc::AllocVar; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; +use ark_std::{ + fmt::Debug, + iter::Sum, + ops::{Add, Mul}, + rand::RngCore, +}; use thiserror::Error; -use sonobe_traits::Curve; - pub mod pedersen; // TODO: add back other commitment schemes @@ -22,20 +22,31 @@ pub enum Error { CommitmentVerificationFail, } -pub trait VectorCommitment { +pub trait VectorCommitment: 'static + Debug + PartialEq { const IS_HIDING: bool; type Key; - type Scalar; - type Commitment; - type Randomness; + type Scalar: Clone + Copy + Debug + PartialEq + Sync; + type Commitment: Default + Debug + PartialEq + Sync; + type Randomness: Clone + + Copy + + Default + + Debug + + Sync + + Add + + Mul + + for<'a> Add<&'a Self::Scalar, Output = Self::Randomness> + + for<'a> Mul<&'a Self::Scalar, Output = Self::Randomness> + + Add + + Mul + + Sum; - fn generate_key(rng: &mut impl RngCore, len: usize) -> Result; + fn generate_key(rng: impl RngCore, len: usize) -> Result; fn commit( ck: &Self::Key, v: &[Self::Scalar], - rng: &mut impl RngCore, + rng: impl RngCore, ) -> Result<(Self::Commitment, Self::Randomness), Error>; fn open( @@ -53,14 +64,16 @@ mod tests { use super::*; - pub fn test_commitment_opt>( - rng: &mut impl RngCore, + pub fn test_commitment_correctness>( + mut rng: impl RngCore, len: usize, ) -> Result<(), Box> { - let v = (0..len).map(|_| VC::Scalar::rand(rng)).collect::>(); + let v = (0..len) + .map(|_| VC::Scalar::rand(&mut rng)) + .collect::>(); - let ck = VC::generate_key(rng, len)?; - let (cm, r) = VC::commit(&ck, &v, rng)?; + let ck = VC::generate_key(&mut rng, len)?; + let (cm, r) = VC::commit(&ck, &v, &mut rng)?; assert!(VC::open(&ck, &v, &r, &cm)?); Ok(()) } diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 0f97cca9c..3d53d7a61 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -3,14 +3,14 @@ use ark_relations::gr1cs::SynthesisError; use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; use super::{Error, VectorCommitment}; -use sonobe_traits::{Curve, CF2}; +use crate::traits::{Null, SonobeCurve, CF2}; -#[derive(Debug)] -pub struct Pedersen { +#[derive(Debug, PartialEq)] +pub struct Pedersen { _c: PhantomData, } -impl Pedersen { +impl Pedersen { fn msm(g: &[C::Affine], v: &[C::ScalarField]) -> Result { if g.len() < v.len() { return Err(Error::MessageTooLong(g.len(), v.len())); @@ -21,16 +21,16 @@ impl Pedersen { } } -impl VectorCommitment for Pedersen { +impl VectorCommitment for Pedersen { const IS_HIDING: bool = false; type Key = Vec; type Scalar = C::ScalarField; type Commitment = C; - type Randomness = (); + type Randomness = Null; - fn generate_key(rng: &mut impl RngCore, len: usize) -> Result { - let generators = repeat_with(|| C::rand(rng)) + fn generate_key(mut rng: impl RngCore, len: usize) -> Result { + let generators = repeat_with(|| C::rand(&mut rng)) .take(len.next_power_of_two()) .collect::>(); Ok(C::normalize_batch(&generators)) @@ -39,9 +39,9 @@ impl VectorCommitment for Pedersen { fn commit( g: &Self::Key, v: &[Self::Scalar], - _rng: &mut impl RngCore, + _rng: impl RngCore, ) -> Result<(Self::Commitment, Self::Randomness), Error> { - Ok((Self::msm(g, v)?, ())) + Ok((Self::msm(g, v)?, Null)) } fn open( @@ -54,7 +54,7 @@ impl VectorCommitment for Pedersen { } } -impl VectorCommitment for Pedersen { +impl VectorCommitment for Pedersen { const IS_HIDING: bool = true; type Key = (Vec, C); @@ -62,16 +62,19 @@ impl VectorCommitment for Pedersen { type Commitment = C; type Randomness = C::ScalarField; - fn generate_key(rng: &mut impl RngCore, len: usize) -> Result { - Ok((Pedersen::::generate_key(rng, len)?, C::rand(rng))) + fn generate_key(mut rng: impl RngCore, len: usize) -> Result { + Ok(( + Pedersen::::generate_key(&mut rng, len)?, + C::rand(&mut rng), + )) } fn commit( (g, h): &Self::Key, v: &[Self::Scalar], - rng: &mut impl RngCore, + mut rng: impl RngCore, ) -> Result<(Self::Commitment, Self::Randomness), Error> { - let r = C::ScalarField::rand(rng); + let r = C::ScalarField::rand(&mut rng); Ok((Self::msm(g, v)? + h.mul(r), r)) } @@ -85,11 +88,11 @@ impl VectorCommitment for Pedersen { } } -pub struct PedersenGadget { +pub struct PedersenGadget { _c: PhantomData, } -impl PedersenGadget { +impl PedersenGadget { pub fn commit( h: &C::Var, g: &[C::Var], @@ -123,25 +126,20 @@ impl PedersenGadget { #[cfg(test)] mod tests { - use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective}; - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use ark_ff::{BigInteger, PrimeField}; - use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; - use ark_relations::gr1cs::ConstraintSystem; + use ark_bn254::G1Projective; use ark_std::{error::Error, rand::Rng, test_rng}; - use crate::commitments::tests::test_commitment_opt; + use crate::commitments::tests::test_commitment_correctness; use super::*; - use crate::transcripts::poseidon::poseidon_canonical_config; #[test] fn test_pedersen_commitment() -> Result<(), Box> { - let rng = &mut test_rng(); + let mut rng = test_rng(); for i in 0..10 { let len = rng.gen_range((1 << i)..(1 << (i + 1))); - test_commitment_opt::>(rng, len)?; - test_commitment_opt::>(rng, len)?; + test_commitment_correctness::>(&mut rng, len)?; + test_commitment_correctness::>(&mut rng, len)?; } Ok(()) } diff --git a/crates/primitives/src/gadgets/nonnative/affine.rs b/crates/primitives/src/gadgets/nonnative/affine.rs index 182525cc9..c290925d6 100644 --- a/crates/primitives/src/gadgets/nonnative/affine.rs +++ b/crates/primitives/src/gadgets/nonnative/affine.rs @@ -11,7 +11,7 @@ use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; use ark_std::{borrow::Borrow, Zero}; -use sonobe_traits::{AbsorbNonNativeGadget, Curve}; +use crate::traits::{AbsorbNonNativeGadget, SonobeCurve}; use super::uint::NonNativeUintVar; @@ -19,12 +19,12 @@ use super::uint::NonNativeUintVar; /// field, over the constraint field. It is not intended to perform operations, but just to contain /// the affine coordinates in order to perform hash operations of the point. #[derive(Debug, Clone)] -pub struct NonNativeAffineVar { +pub struct NonNativeAffineVar { pub x: NonNativeUintVar, pub y: NonNativeUintVar, } -impl AllocVar for NonNativeAffineVar { +impl AllocVar for NonNativeAffineVar { fn new_variable>( cs: impl Into>, f: impl FnOnce() -> Result, @@ -44,7 +44,7 @@ impl AllocVar for NonNativeAffineVar { } } -impl GR1CSVar for NonNativeAffineVar { +impl GR1CSVar for NonNativeAffineVar { type Value = C; fn cs(&self) -> ConstraintSystemRef { @@ -81,7 +81,7 @@ impl GR1CSVar for NonNativeAffineVar { } } -impl EqGadget for NonNativeAffineVar { +impl EqGadget for NonNativeAffineVar { fn is_eq(&self, other: &Self) -> Result, SynthesisError> { let mut result = Boolean::TRUE; if self.x.0.len() != other.x.0.len() { @@ -128,7 +128,7 @@ impl EqGadget for NonNativeAffineVar { } } -impl NonNativeAffineVar { +impl NonNativeAffineVar { pub fn zero() -> Self { // `unwrap` below is safe because we are allocating a constant value, // which is guaranteed to succeed. @@ -136,7 +136,7 @@ impl NonNativeAffineVar { } } -impl AbsorbNonNativeGadget for NonNativeAffineVar { +impl AbsorbNonNativeGadget for NonNativeAffineVar { fn to_native_sponge_field_elements( &self, ) -> Result>, SynthesisError> { @@ -150,7 +150,7 @@ mod tests { use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, UniformRand}; - use sonobe_traits::{AbsorbNonNative, Inputize, InputizeNonNative}; + use crate::traits::{AbsorbNonNative, Inputize, InputizeNonNative}; use super::*; diff --git a/crates/primitives/src/gadgets/nonnative/uint.rs b/crates/primitives/src/gadgets/nonnative/uint.rs index 754908392..fa0db0e69 100644 --- a/crates/primitives/src/gadgets/nonnative/uint.rs +++ b/crates/primitives/src/gadgets/nonnative/uint.rs @@ -1,5 +1,3 @@ -use std::ops::Index; - use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, @@ -14,16 +12,18 @@ use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{ borrow::Borrow, cmp::{max, min}, + ops::Index, }; use num_bigint::BigUint; use num_integer::Integer; -use sonobe_traits::{AbsorbNonNativeGadget, Field}; - -use crate::gadgets::math::{ - eq::EquivalenceGadget, - matrix::{MatrixGadget, SparseMatrixVar}, - vector::VectorGadget, +use crate::{ + gadgets::math::{ + eq::EquivalenceGadget, + matrix::{MatrixGadget, SparseMatrixVar}, + vector::VectorGadget, + }, + traits::{AbsorbNonNativeGadget, SonobeField}, }; /// `LimbVar` represents a single limb of a non-native unsigned integer in the @@ -242,7 +242,7 @@ impl AllocVar for NonNativeUintVar { } } -impl AllocVar for NonNativeUintVar { +impl AllocVar for NonNativeUintVar { fn new_variable>( cs: impl Into>, f: impl FnOnce() -> Result, @@ -799,8 +799,7 @@ impl AbsorbNonNativeGadget for NonNativeUintVar { fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; - self - .to_bits_le()? + self.to_bits_le()? .chunks(bits_per_limb) .map(Boolean::le_bits_to_fp) .collect() diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 9400c51d2..b6ced84d8 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,4 +1,8 @@ -pub mod commitments; pub mod arithmetizations; +pub mod circuits; +pub mod commitments; pub mod gadgets; +pub mod relations; +pub mod sumcheck; +pub mod traits; pub mod transcripts; diff --git a/crates/primitives/src/relations/mod.rs b/crates/primitives/src/relations/mod.rs new file mode 100644 index 000000000..c9349c2dd --- /dev/null +++ b/crates/primitives/src/relations/mod.rs @@ -0,0 +1,52 @@ +use ark_std::{error::Error, rand::RngCore}; + +use crate::traits::Dummy; + +pub trait Referenceable { + type Ref<'a>: Copy + where + Self: 'a; + + fn reference(&self) -> Self::Ref<'_>; +} + +impl Referenceable for Vec { + type Ref<'a> = &'a [T]; + + fn reference(&self) -> Self::Ref<'_> { + self + } +} + +pub trait Relation { + type Error: Error; + + /// Checks if witness `w` and instance `u` satisfy the relation `self` + fn check_relation(&self, w: W::Ref<'_>, u: U::Ref<'_>) -> Result<(), Self::Error>; +} + +pub trait WitnessInstanceExtractor { + type Source; + type Error: Error + 'static; + + fn extract(&self, source: Self::Source) -> Result<(W, U), Self::Error>; +} + +pub trait WitnessInstanceInitializer { + fn dummy_witness_instance<'a>(&'a self) -> (W, U) + where + W: Dummy<&'a Self>, + U: Dummy<&'a Self>, + { + (W::dummy(self), U::dummy(self)) + } +} + +/// `WitnessInstanceSampler` allows sampling a random witness-instance pair that +/// satisfies the relation `self`. +pub trait WitnessInstanceSampler { + type Source; + type Error: Error + 'static; + + fn sample(&self, source: Self::Source, rng: impl RngCore) -> Result<(W, U), Self::Error>; +} diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs new file mode 100644 index 000000000..2868f5828 --- /dev/null +++ b/crates/primitives/src/sumcheck/mod.rs @@ -0,0 +1,291 @@ +// code forked from: +// https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check +// +// Copyright (c) 2023 Espresso Systems (espressosys.com) +// This file is part of the HyperPlonk library. + +// You should have received a copy of the MIT License +// along with the HyperPlonk library. If not, see . + +//! This module implements the sum check protocol. + +use ark_crypto_primitives::sponge::Absorb; +use ark_ff::PrimeField; +use ark_poly::{ + univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, Polynomial, +}; +use ark_std::{cfg_chunks, cfg_into_iter, cfg_iter, fmt::Debug}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use thiserror::Error; + +use crate::transcripts::Transcript; + +use utils::{ + barycentric_weights, compute_lagrange_interpolated_poly, extrapolate, VPAuxInfo, + VirtualPolynomial, +}; + +pub mod utils; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Invalid Polynomial IOP Prover: {0}")] + InvalidPolyIOPProver(String), + #[error("Invalid Polynomial IOP Verifier: {0}")] + InvalidPolyIOPVerifier(String), + #[error("Invalid Polynomial IOP Proof: {0}")] + InvalidPolyIOPProof(String), + #[error("Invalid Polynomial IOP Parameters: {0}")] + InvalidPolyIOPParameters(String), +} + +/// An IOP proof is a collections of +/// - messages from prover to verifier at each round through the interactive +/// protocol. +/// - a point that is generated by the transcript for evaluation +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct IOPProof { + pub point: Vec, + pub proofs: Vec>, +} + +/// A SumCheckSubClaim is a claim generated by the verifier at the end of +/// verification when it is convinced. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SumCheckSubClaim { + /// the multi-dimensional point that this multilinear extension is evaluated + /// to + pub point: Vec, + /// the expected evaluation + pub expected_evaluation: F, +} + +#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] +pub struct IOPSumCheck; + +impl IOPSumCheck { + pub fn prove( + mut poly: VirtualPolynomial, + transcript: &mut impl Transcript, + ) -> Result<(IOPProof, Vec>), Error> { + transcript.absorb(&F::from(poly.aux_info.num_variables as u64)); + transcript.absorb(&F::from(poly.aux_info.max_degree as u64)); + let extrapolation_aux = (1..poly.aux_info.max_degree) + .map(|degree| { + let points = (0..1 + degree as u64).map(F::from).collect::>(); + let weights = barycentric_weights(&points); + (points, weights) + }) + .collect::>(); + let mut prover_msgs = Vec::with_capacity(poly.aux_info.num_variables); + let mut challenges = Vec::with_capacity(poly.aux_info.num_variables); + for i in 0..poly.aux_info.num_variables { + let mut products_sum = vec![F::ZERO; poly.aux_info.max_degree + 1]; + + // Step 2: generate sum for the partial evaluated polynomial: + // f(r_1, ... r_m,, x_{m+1}... x_n) + + poly.products.iter().for_each(|(coefficient, products)| { + #[cfg(feature = "parallel")] + let mut sum = cfg_into_iter!(0..1 << (poly.aux_info.num_variables - i - 1)) + .fold( + || { + ( + vec![(F::ZERO, F::ZERO); products.len()], + vec![F::ZERO; products.len() + 1], + ) + }, + |(mut buf, mut acc), b| { + buf.iter_mut() + .zip(products.iter()) + .for_each(|((eval, step), f)| { + let table = &poly.flattened_ml_extensions[*f]; + *eval = table[b << 1]; + *step = table[(b << 1) + 1] - table[b << 1]; + }); + acc[0] += buf.iter().map(|(eval, _)| eval).product::(); + acc[1..].iter_mut().for_each(|acc| { + buf.iter_mut().for_each(|(eval, step)| *eval += step); + *acc += buf.iter().map(|(eval, _)| eval).product::(); + }); + (buf, acc) + }, + ) + .map(|(_, partial)| partial) + .reduce( + || vec![F::ZERO; products.len() + 1], + |mut sum, partial| { + sum.iter_mut() + .zip(partial.iter()) + .for_each(|(sum, partial)| *sum += partial); + sum + }, + ); + #[cfg(not(feature = "parallel"))] + let mut sum = cfg_into_iter!(0..1 << (poly.aux_info.num_variables - i - 1)) + .fold( + ( + vec![(F::ZERO, F::ZERO); products.len()], + vec![F::ZERO; products.len() + 1], + ), + |(mut buf, mut acc), b| { + buf.iter_mut() + .zip(products.iter()) + .for_each(|((eval, step), f)| { + let table = &poly.flattened_ml_extensions[*f]; + *eval = table[b << 1]; + *step = table[(b << 1) + 1] - table[b << 1]; + }); + acc[0] += buf.iter().map(|(eval, _)| eval).product::(); + acc[1..].iter_mut().for_each(|acc| { + buf.iter_mut().for_each(|(eval, step)| *eval += step as &_); + *acc += buf.iter().map(|(eval, _)| eval).product::(); + }); + (buf, acc) + }, + ) + .1; + sum.iter_mut().for_each(|sum| *sum *= coefficient); + let extraploation = cfg_into_iter!(0..poly.aux_info.max_degree - products.len()) + .map(|i| { + let (points, weights) = &extrapolation_aux[products.len() - 1]; + let at = F::from((products.len() + 1 + i) as u64); + extrapolate(points, weights, &sum, &at) + }) + .collect::>(); + products_sum + .iter_mut() + .zip(sum.iter().chain(extraploation.iter())) + .for_each(|(products_sum, sum)| *products_sum += sum); + }); + + let prover_poly = compute_lagrange_interpolated_poly(&products_sum).coeffs; + transcript.absorb(&prover_poly); + prover_msgs.push(prover_poly); + + let challenge = transcript.get_challenge(); + challenges.push(challenge); + poly.flattened_ml_extensions.iter_mut().for_each(|mle| { + mle.evaluations = cfg_chunks!(mle.evaluations, 2) + .map(|chunk| chunk[0] + challenge * (chunk[1] - chunk[0])) + .collect(); + mle.num_vars -= 1; + }); + } + + Ok(( + IOPProof { + point: challenges, + proofs: prover_msgs, + }, + poly.flattened_ml_extensions, + )) + } + + pub fn verify( + claimed_sum: F, + proof: &IOPProof, + aux_info: &VPAuxInfo, + transcript: &mut impl Transcript, + ) -> Result, Error> { + transcript.absorb(&F::from(aux_info.num_variables as u64)); + transcript.absorb(&F::from(aux_info.max_degree as u64)); + assert_eq!(aux_info.num_variables, proof.proofs.len()); + + let challenges = proof + .proofs + .iter() + .map(|msg| { + transcript.absorb(&msg); + transcript.get_challenge() + }) + .collect::>(); + + // the deferred check during the interactive phase: + // 2. set `expected` to P(r)` + let mut expected_vec = cfg_iter!(proof.proofs) + .zip(&challenges) + .map(|(coeffs, challenge)| { + DensePolynomial::from_coefficients_slice(coeffs).evaluate(challenge) + }) + .collect::>(); + + // insert the asserted_sum to the first position of the expected vector + expected_vec.insert(0, claimed_sum); + + for (coeffs, &expected) in proof.proofs.iter().zip(expected_vec.iter()) { + let eval_at_one: F = coeffs.iter().sum(); + let eval_at_zero: F = if coeffs.is_empty() { + F::zero() + } else { + coeffs[0] + }; + + // the deferred check during the interactive phase: + // 1. check if the received 'P(0) + P(1) = expected`. + if eval_at_one + eval_at_zero != expected { + return Err(Error::InvalidPolyIOPProof( + "Prover message is not consistent with the claim.".to_string(), + )); + } + } + Ok(SumCheckSubClaim { + point: challenges, + // the last expected value (not checked within this function) will be included in the + // subclaim + expected_evaluation: expected_vec[expected_vec.len() - 1], + }) + } +} + +#[cfg(test)] +pub mod tests { + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; + use ark_ff::Field; + use ark_pallas::Fr; + use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; + use ark_std::{test_rng, One, Zero}; + + use crate::transcripts::poseidon::poseidon_canonical_config; + + use super::*; + + #[test] + pub fn sumcheck_poseidon() -> Result<(), Error> { + let n_vars = 10; + + let mut rng = test_rng(); + let poly_mle = DenseMultilinearExtension::rand(n_vars, &mut rng); + let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, Fr::ONE); + + sumcheck_poseidon_opt(virtual_poly)?; + + // test with zero poly + let poly_mle = DenseMultilinearExtension::from_evaluations_vec( + n_vars, + vec![Fr::zero(); 2u32.pow(n_vars as u32) as usize], + ); + let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, Fr::ONE); + sumcheck_poseidon_opt(virtual_poly)?; + Ok(()) + } + + fn sumcheck_poseidon_opt(virtual_poly: VirtualPolynomial) -> Result<(), Error> { + let aux_info = virtual_poly.aux_info.clone(); + let poseidon_config = poseidon_canonical_config::(); + + // sum-check prove + let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); + let (sum_check, _) = IOPSumCheck::prove(virtual_poly, &mut transcript_p)?; + + // sum-check verify + let poly = DensePolynomial::from_coefficients_vec(sum_check.proofs[0].clone()); + let claimed_sum = poly.evaluate(&Fr::one()) + poly.evaluate(&Fr::zero()); + let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); + let res_verify = IOPSumCheck::verify(claimed_sum, &sum_check, &aux_info, &mut transcript_v); + + assert!(res_verify.is_ok()); + Ok(()) + } +} diff --git a/crates/primitives/src/sumcheck/utils.rs b/crates/primitives/src/sumcheck/utils.rs new file mode 100644 index 000000000..d75a973b4 --- /dev/null +++ b/crates/primitives/src/sumcheck/utils.rs @@ -0,0 +1,399 @@ +// code forked from +// https://github.com/privacy-scaling-explorations/multifolding-poc/blob/main/src/espresso/virtual_polynomial.rs +// +// Copyright (c) 2023 Espresso Systems (espressosys.com) +// This file is part of the HyperPlonk library. + +// You should have received a copy of the MIT License +// along with the HyperPlonk library. If not, see . + +//! This module defines our main mathematical object `VirtualPolynomial`; and +//! various functions associated with it. + +use ark_ff::{batch_inversion, PrimeField}; +use ark_poly::{univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial}; +use ark_serialize::CanonicalSerialize; +use ark_std::cfg_into_iter; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +/// A virtual polynomial is a sum of products of multilinear polynomials; +/// where the multilinear polynomials are stored via their multilinear +/// extensions: `(coefficient, DenseMultilinearExtension)` +/// +/// * Number of products n = `polynomial.products.len()`, +/// * Number of multiplicands of ith product m_i = +/// `polynomial.products[i].1.len()`, +/// * Coefficient of ith product c_i = `polynomial.products[i].0` +/// +/// The resulting polynomial is +/// +/// $$ \sum_{i=0}^{n} c_i \cdot \prod_{j=0}^{m_i} P_{ij} $$ +/// +/// Example: +/// f = c0 * f0 * f1 * f2 + c1 * f3 * f4 +/// where f0 ... f4 are multilinear polynomials +/// +/// - `flattened_ml_extensions` stores the multilinear extension representation +/// of f0, f1, f2, f3 and f4 +/// - `products` is `[(c0, [0, 1, 2]), (c1, [3, 4])]` +/// - raw_pointers_lookup_table maps fi to i +/// +#[derive(Clone, Debug, Default, PartialEq)] +pub struct VirtualPolynomial { + /// Aux information about the multilinear polynomial + pub aux_info: VPAuxInfo, + pub flattened_ml_extensions: Vec>, + /// list of reference to products (as usize) of multilinear extension + pub products: Vec<(F, Vec)>, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, CanonicalSerialize)] +/// Auxiliary information about the multilinear polynomial +pub struct VPAuxInfo { + /// max number of multiplicands in each product + pub max_degree: usize, + /// number of variables of the polynomial + pub num_variables: usize, +} + +// TODO: convert this into a trait +impl VirtualPolynomial { + /// Creates a new virtual polynomial from a MLE and its coefficient. + pub fn new_from_mle(mle: DenseMultilinearExtension, coefficient: F) -> Self { + VirtualPolynomial { + aux_info: VPAuxInfo { + // The max degree is the max degree of any individual variable + max_degree: 1, + num_variables: mle.num_vars, + }, + // here `0` points to the first polynomial of `flattened_ml_extensions` + products: vec![(coefficient, vec![0])], + flattened_ml_extensions: vec![mle], + } + } +} + +/// Evaluate eq polynomial. +pub fn eq_eval(x: &[F], y: &[F]) -> F { + debug_assert_eq!(x.len(), y.len()); + x.iter() + .zip(y.iter()) + .map(|(xi, yi)| xi.double() * yi - xi - yi + F::one()) + .product::() +} + +/// This function build the eq(x, r) polynomial for any given r, and output the +/// evaluation of eq(x, r) in its vector form. +/// +/// Evaluate +/// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) +/// over r, which is +/// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i)) +pub fn build_eq_x_r_vec(r: &[F]) -> Vec { + // we build eq(x,r) from its evaluations + // we want to evaluate eq(x,r) over x \in {0, 1}^num_vars + // for example, with num_vars = 4, x is a binary vector of 4, then + // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3) + // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3) + // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3) + // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3) + // .... + // 1 1 1 1 -> r0 * r1 * r2 * r3 + // we will need 2^num_var evaluations + + // initializing the buffer with [1] + let mut buf = vec![F::one()]; + + for i in r.iter().rev() { + // suppose at the previous step we received [b_1, ..., b_k] + // for the current step we will need + // if x_i = 0: (1-ri) * [b_1, ..., b_k] + // if x_i = 1: ri * [b_1, ..., b_k] + buf = cfg_into_iter!(buf) + .flat_map(|j| { + let v = j * i; + [j - v, v] + }) + .collect(); + } + + buf +} + +#[allow(clippy::filter_map_bool_then)] +pub fn barycentric_weights(points: &[F]) -> Vec { + let mut weights = points + .iter() + .enumerate() + .map(|(j, point_j)| { + points + .iter() + .enumerate() + .filter_map(|(i, point_i)| (i != j).then(|| *point_j - point_i)) + .reduce(|acc, value| acc * value) + .unwrap_or_else(F::one) + }) + .collect::>(); + batch_inversion(&mut weights); + weights +} + +pub fn extrapolate(points: &[F], weights: &[F], evals: &[F], at: &F) -> F { + let (coeffs, sum_inv) = { + let mut coeffs = points.iter().map(|point| *at - point).collect::>(); + batch_inversion(&mut coeffs); + coeffs.iter_mut().zip(weights).for_each(|(coeff, weight)| { + *coeff *= weight; + }); + let sum_inv = coeffs.iter().sum::().inverse().unwrap_or_default(); + (coeffs, sum_inv) + }; + coeffs + .iter() + .zip(evals) + .map(|(coeff, eval)| *coeff * eval) + .sum::() + * sum_inv +} + +/// Computes the lagrange interpolated polynomial from the given points `p_i` +pub fn compute_lagrange_interpolated_poly(p_i: &[F]) -> DensePolynomial { + let v = (0..p_i.len()) + .map(|i| F::from(i as u64)) + .collect::>(); + + // compute l(x), common to every basis polynomial + let mut l_x = DensePolynomial::from_coefficients_vec(vec![F::ONE]); + for i in &v { + let prod_m = DensePolynomial::from_coefficients_vec(vec![-*i, F::ONE]); + l_x = &l_x * &prod_m; + } + + // compute each w_j - barycentric weights + let w_j_vector = barycentric_weights(&v); + + // compute each polynomial within the sum L(x) + let mut lagrange_poly = DensePolynomial::from_coefficients_vec(vec![F::ZERO]); + for (j, w_j) in w_j_vector.iter().enumerate() { + let x_j = j; + let y_j = p_i[j]; + // we multiply by l(x) here, otherwise the below division will not work - deg(0)/deg(d) + let poly_numerator = &(&l_x * (*w_j)) * (y_j); + let poly_denominator = DensePolynomial::from_coefficients_vec(vec![-v[x_j], F::ONE]); + let poly = &poly_numerator / &poly_denominator; + lagrange_poly = &lagrange_poly + &poly; + } + + lagrange_poly +} + +#[cfg(test)] +mod tests { + use ark_pallas::Fr; + use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; + use ark_std::UniformRand; + + use super::*; + + /// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this + /// polynomial at `eval_at`: + /// + /// \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) ) + /// + /// This implementation is linear in number of inputs in terms of field + /// operations. It also has a quadratic term in primitive operations which is + /// negligible compared to field operations. + /// TODO: The quadratic term can be removed by precomputing the lagrange + /// coefficients. + fn interpolate_uni_poly(p_i: &[F], eval_at: F) -> F { + let len = p_i.len(); + let mut evals = vec![]; + let mut prod = eval_at; + evals.push(eval_at); + + // `prod = \prod_{j} (eval_at - j)` + for e in 1..len { + let tmp = eval_at - F::from(e as u64); + evals.push(tmp); + prod *= tmp; + } + let mut res = F::zero(); + // we want to compute \prod (j!=i) (i-j) for a given i + // + // we start from the last step, which is + // denom[len-1] = (len-1) * (len-2) *... * 2 * 1 + // the step before that is + // denom[len-2] = (len-2) * (len-3) * ... * 2 * 1 * -1 + // and the step before that is + // denom[len-3] = (len-3) * (len-4) * ... * 2 * 1 * -1 * -2 + // + // i.e., for any i, the one before this will be derived from + // denom[i-1] = denom[i] * (len-i) / i + // + // that is, we only need to store + // - the last denom for i = len-1, and + // - the ratio between current step and fhe last step, which is the product of + // (len-i) / i from all previous steps and we store this product as a fraction + // number to reduce field divisions. + + // We know + // - 2^61 < factorial(20) < 2^62 + // - 2^122 < factorial(33) < 2^123 + // so we will be able to compute the ratio + // - for len <= 20 with i64 + // - for len <= 33 with i128 + // - for len > 33 with BigInt + if p_i.len() <= 20 { + let last_denominator = F::from(u64_factorial(len - 1)); + let mut ratio_numerator = 1i64; + let mut ratio_denominator = 1u64; + + for i in (0..len).rev() { + let ratio_numerator_f = if ratio_numerator < 0 { + -F::from((-ratio_numerator) as u64) + } else { + F::from(ratio_numerator as u64) + }; + + res += p_i[i] * prod * F::from(ratio_denominator) + / (last_denominator * ratio_numerator_f * evals[i]); + + // compute denom for the next step is current_denom * (len-i)/i + if i != 0 { + ratio_numerator *= -(len as i64 - i as i64); + ratio_denominator *= i as u64; + } + } + } else if p_i.len() <= 33 { + let last_denominator = F::from(u128_factorial(len - 1)); + let mut ratio_numerator = 1i128; + let mut ratio_denominator = 1u128; + + for i in (0..len).rev() { + let ratio_numerator_f = if ratio_numerator < 0 { + -F::from((-ratio_numerator) as u128) + } else { + F::from(ratio_numerator as u128) + }; + + res += p_i[i] * prod * F::from(ratio_denominator) + / (last_denominator * ratio_numerator_f * evals[i]); + + // compute denom for the next step is current_denom * (len-i)/i + if i != 0 { + ratio_numerator *= -(len as i128 - i as i128); + ratio_denominator *= i as u128; + } + } + } else { + let mut denom_up = field_factorial::(len - 1); + let mut denom_down = F::one(); + + for i in (0..len).rev() { + res += p_i[i] * prod * denom_down / (denom_up * evals[i]); + + // compute denom for the next step is current_denom * (len-i)/i + if i != 0 { + denom_up *= -F::from((len - i) as u64); + denom_down *= F::from(i as u64); + } + } + } + res + } + + /// compute the factorial(a) = 1 * 2 * ... * a + #[inline] + fn field_factorial(a: usize) -> F { + let mut res = F::one(); + for i in 2..=a { + res *= F::from(i as u64); + } + res + } + + /// compute the factorial(a) = 1 * 2 * ... * a + #[inline] + fn u128_factorial(a: usize) -> u128 { + let mut res = 1u128; + for i in 2..=a { + res *= i as u128; + } + res + } + + /// compute the factorial(a) = 1 * 2 * ... * a + #[inline] + fn u64_factorial(a: usize) -> u64 { + let mut res = 1u64; + for i in 2..=a { + res *= i as u64; + } + res + } + + #[test] + fn test_compute_lagrange_interpolated_poly() { + let mut prng = ark_std::test_rng(); + for degree in 1..30 { + let poly = DensePolynomial::::rand(degree, &mut prng); + // range (which is exclusive) is from 0 to degree + 1, since we need degree + 1 evaluations + let evals = (0..(degree + 1)) + .map(|i| poly.evaluate(&Fr::from(i as u64))) + .collect::>(); + let lagrange_poly = compute_lagrange_interpolated_poly(&evals); + for _ in 0..10 { + let query = Fr::rand(&mut prng); + let lagrange_eval = lagrange_poly.evaluate(&query); + let eval = poly.evaluate(&query); + assert_eq!(eval, lagrange_eval); + assert_eq!(lagrange_poly.degree(), poly.degree()); + } + } + } + + #[test] + fn test_interpolation() { + let mut prng = ark_std::test_rng(); + + // test a polynomial with 20 known points, i.e., with degree 19 + let poly = DensePolynomial::::rand(20 - 1, &mut prng); + let evals = (0..20) + .map(|i| poly.evaluate(&Fr::from(i))) + .collect::>(); + let query = Fr::rand(&mut prng); + + assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)); + assert_eq!( + compute_lagrange_interpolated_poly(&evals).evaluate(&query), + interpolate_uni_poly(&evals, query) + ); + + // test a polynomial with 33 known points, i.e., with degree 32 + let poly = DensePolynomial::::rand(33 - 1, &mut prng); + let evals = (0..33) + .map(|i| poly.evaluate(&Fr::from(i))) + .collect::>(); + let query = Fr::rand(&mut prng); + + assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)); + assert_eq!( + compute_lagrange_interpolated_poly(&evals).evaluate(&query), + interpolate_uni_poly(&evals, query) + ); + + // test a polynomial with 64 known points, i.e., with degree 63 + let poly = DensePolynomial::::rand(64 - 1, &mut prng); + let evals = (0..64) + .map(|i| poly.evaluate(&Fr::from(i))) + .collect::>(); + let query = Fr::rand(&mut prng); + + assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)); + assert_eq!( + compute_lagrange_interpolated_poly(&evals).evaluate(&query), + interpolate_uni_poly(&evals, query) + ); + } +} diff --git a/crates/traits/src/lib.rs b/crates/primitives/src/traits/mod.rs similarity index 73% rename from crates/traits/src/lib.rs rename to crates/primitives/src/traits/mod.rs index 1106def43..18e1c1256 100644 --- a/crates/traits/src/lib.rs +++ b/crates/primitives/src/traits/mod.rs @@ -8,7 +8,11 @@ use ark_r1cs_std::{ fields::{fp::FpVar, FieldVar}, groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, }; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; +use ark_relations::gr1cs::SynthesisError; +use ark_std::{ + iter::Sum, + ops::{Add, Mul}, +}; pub type CF1 = ::ScalarField; pub type CF2 = <::BaseField as ArkField>::BasePrimeField; @@ -73,7 +77,7 @@ impl, const N: usize> Inputize for Fp { } } -impl> Inputize for Projective

{ +impl> Inputize for Projective

{ /// Returns the internal representation in the same order as how the value /// is allocated in `ProjectiveVar::new_input`. fn inputize(&self) -> Vec { @@ -85,7 +89,7 @@ impl> Inputize for Projective

InputizeNonNative for P { +impl InputizeNonNative for P { /// Returns the internal representation in the same order as how the value /// is allocated in `NonNativeUintVar::new_input`. fn inputize_nonnative(&self) -> Vec { @@ -97,8 +101,8 @@ impl InputizeNonNative for P { } } -impl> InputizeNonNative - for Projective

+impl> + InputizeNonNative for Projective

{ /// Returns the internal representation in the same order as how the value /// is allocated in `NonNativeAffineVar::new_input`. @@ -112,7 +116,7 @@ impl> InputizeNonNative

+ Absorb + AbsorbNonNative + Inputize { const BITS_PER_LIMB: usize; @@ -120,15 +124,15 @@ pub trait Field: type Var: FieldVar; } -impl, const N: usize> Field for Fp { +impl, const N: usize> SonobeField for Fp { const BITS_PER_LIMB: usize = 55; // TODO: make this configurable type Var = FpVar; } /// `Curve` trait is a wrapper around `CurveGroup` that also includes the /// necessary bounds for the curve to be used conveniently in folding schemes. -pub trait Curve: - CurveGroup +pub trait SonobeCurve: + CurveGroup + AbsorbNonNative + Inputize + InputizeNonNative @@ -137,7 +141,9 @@ pub trait Curve: type Var: CurveVar; } -impl> Curve for Projective

{ +impl> SonobeCurve + for Projective

+{ type Var = ProjectiveVar>; } @@ -175,6 +181,13 @@ impl AbsorbNonNative for [T] { } } +impl AbsorbNonNative for (T, T) { + fn to_native_sponge_field_elements(&self, dest: &mut Vec) { + self.0.to_native_sponge_field_elements(dest); + self.1.to_native_sponge_field_elements(dest); + } +} + impl> AbsorbNonNativeGadget for &T { fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { T::to_native_sponge_field_elements(self) @@ -208,7 +221,7 @@ impl, const N: usize> AbsorbNonNative for Fp { } } -impl> AbsorbNonNative for Projective

{ +impl> AbsorbNonNative for Projective

{ fn to_native_sponge_field_elements(&self, dest: &mut Vec) { let affine = self.into_affine(); let (x, y) = affine.xy().unwrap_or_default(); @@ -217,29 +230,83 @@ impl> AbsorbNonNative for Projective

{ } } -/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. -/// inside the agmented F' function). -/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying -/// the step. -/// Note that the external inputs for the specific circuit are defined at the implementation of -/// both `FCircuit::ExternalInputs` and `FCircuit::ExternalInputsVar`, where the `Default` trait -/// implementation for the `ExternalInputs` returns the initialized data structure (ie. if the type -/// contains a vector, it is initialized at the expected length). -pub trait FCircuit { - type ExternalInputs; - - /// returns the number of elements in the state of the FCircuit, which corresponds to the - /// FCircuit inputs. - fn state_len(&self) -> usize; - - /// generates the constraints for the step of F for the given z_i - fn generate_step_constraints( - // this method uses self, so that each FCircuit implementation (and different frontends) - // can hold a state if needed to store data to generate the constraints. - &self, - cs: ConstraintSystemRef, - i: usize, - z_i: Vec>, - external_inputs: Self::ExternalInputs, // inputs that are not part of the state - ) -> Result>, SynthesisError>; +#[derive(Clone, Copy, Default, Debug)] +pub struct Null; + +impl Add for Null { + type Output = Null; + + fn add(self, _: F) -> Null { + Null + } +} + +impl Add for &Null { + type Output = Null; + + fn add(self, _: F) -> Null { + Null + } +} + +impl Mul for Null { + type Output = Self; + + fn mul(self, _: F) -> Null { + Null + } +} + +impl Mul for &Null { + type Output = Null; + + fn mul(self, _: F) -> Null { + Null + } +} + +impl Sum for Null { + fn sum>(_: I) -> Self { + Null + } +} + +pub trait ScalarRLC { + type Value; + + fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value; +} + +impl ScalarRLC for I +where + I::Item: Add + Sum + for<'a> Mul<&'a Coeff, Output = I::Item>, +{ + type Value = I::Item; + + fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value { + self.zip(coeffs).map(|(v, c)| v * c).sum::() + } +} + +pub trait SliceRLC { + type Value; + + fn slice_rlc(self, coeffs: &[Coeff]) -> Vec; +} + +impl<'a, T: 'a, I: Iterator, Coeff> SliceRLC for I +where + T: Add + Copy, + for<'x> T: Mul<&'x Coeff, Output = T>, +{ + type Value = T; + + fn slice_rlc(self, coeffs: &[Coeff]) -> Vec { + let mut iter = self.zip(coeffs).map(|(v, c)| v.iter().map(|x| *x * c)); + let first = iter.next().unwrap(); + + iter.fold(first.collect(), |acc, v| { + acc.into_iter().zip(v).map(|(a, b)| a + b).collect() + }) + } } diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index a7239a11d..a4331c63e 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -3,7 +3,8 @@ use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; use ark_relations::gr1cs::SynthesisError; -use sonobe_traits::{AbsorbNonNative, AbsorbNonNativeGadget}; + +use crate::traits::{AbsorbNonNative, AbsorbNonNativeGadget}; pub mod poseidon; diff --git a/crates/primitives/src/transcripts/poseidon.rs b/crates/primitives/src/transcripts/poseidon.rs index 7eb281113..0d6519beb 100644 --- a/crates/primitives/src/transcripts/poseidon.rs +++ b/crates/primitives/src/transcripts/poseidon.rs @@ -150,9 +150,9 @@ pub mod tests { let config = poseidon_canonical_config::(); let mut poseidon_sponge: PoseidonSponge<_> = CryptographicSponge::new(&config); - let v: Vec = vec![1, 2, 3, 4] + let v = vec![1, 2, 3, 4] .into_iter() - .map(|x| Fr::from(x)) + .map(Fr::from) .collect::>(); poseidon_sponge.absorb(&v); poseidon_sponge.squeeze_field_elements::(1); diff --git a/crates/traits/Cargo.toml b/crates/traits/Cargo.toml deleted file mode 100644 index f2ee8164e..000000000 --- a/crates/traits/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "sonobe-traits" -version = "0.1.0" -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -ark-ec = { workspace = true } -ark-ff = { workspace = true, features = ["asm"] } -ark-std = { workspace = true, features = ["getrandom"] } -ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } -ark-relations = { workspace = true } -ark-r1cs-std = { workspace = true } - -[features] -default = ["parallel"] -parallel = [ - "ark-relations/parallel", - "ark-r1cs-std/parallel", -] \ No newline at end of file From 5e8d3783bbf9bbba195ebb2d0d9631a16959cf5d Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:27 +0800 Subject: [PATCH 05/99] Refactor: A unifed absorb trait for native and nonnative --- crates/primitives/src/gadgets/math/mod.rs | 2 +- .../src/gadgets/nonnative/affine.rs | 5 +- crates/primitives/src/sumcheck/mod.rs | 23 ++- crates/primitives/src/traits/mod.rs | 139 +++++++++++------- crates/primitives/src/transcripts/mod.rs | 103 +++++++++---- crates/primitives/src/transcripts/poseidon.rs | 87 ++++++----- 6 files changed, 220 insertions(+), 139 deletions(-) diff --git a/crates/primitives/src/gadgets/math/mod.rs b/crates/primitives/src/gadgets/math/mod.rs index 40ed4e53e..c4e275c5a 100644 --- a/crates/primitives/src/gadgets/math/mod.rs +++ b/crates/primitives/src/gadgets/math/mod.rs @@ -1,3 +1,3 @@ pub mod eq; pub mod matrix; -pub mod vector; \ No newline at end of file +pub mod vector; diff --git a/crates/primitives/src/gadgets/nonnative/affine.rs b/crates/primitives/src/gadgets/nonnative/affine.rs index c290925d6..f24bf8968 100644 --- a/crates/primitives/src/gadgets/nonnative/affine.rs +++ b/crates/primitives/src/gadgets/nonnative/affine.rs @@ -150,7 +150,8 @@ mod tests { use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, UniformRand}; - use crate::traits::{AbsorbNonNative, Inputize, InputizeNonNative}; + + use crate::traits::{Absorbable, Inputize, InputizeNonNative}; use super::*; @@ -173,7 +174,7 @@ mod tests { let p_var = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; assert_eq!( p_var.to_native_sponge_field_elements()?.value()?, - p.to_native_sponge_field_elements_as_vec() + p.extract_absorbed() ); Ok(()) } diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index 2868f5828..717597cbc 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -9,7 +9,6 @@ //! This module implements the sum check protocol. -use ark_crypto_primitives::sponge::Absorb; use ark_ff::PrimeField; use ark_poly::{ univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, Polynomial, @@ -19,7 +18,7 @@ use ark_std::{cfg_chunks, cfg_into_iter, cfg_iter, fmt::Debug}; use rayon::prelude::*; use thiserror::Error; -use crate::transcripts::Transcript; +use crate::{traits::Absorbable, transcripts::Transcript}; use utils::{ barycentric_weights, compute_lagrange_interpolated_poly, extrapolate, VPAuxInfo, @@ -65,12 +64,12 @@ pub struct SumCheckSubClaim { pub struct IOPSumCheck; impl IOPSumCheck { - pub fn prove( + pub fn prove>( mut poly: VirtualPolynomial, transcript: &mut impl Transcript, ) -> Result<(IOPProof, Vec>), Error> { - transcript.absorb(&F::from(poly.aux_info.num_variables as u64)); - transcript.absorb(&F::from(poly.aux_info.max_degree as u64)); + transcript.add(&F::from(poly.aux_info.num_variables as u64)); + transcript.add(&F::from(poly.aux_info.max_degree as u64)); let extrapolation_aux = (1..poly.aux_info.max_degree) .map(|degree| { let points = (0..1 + degree as u64).map(F::from).collect::>(); @@ -161,10 +160,10 @@ impl IOPSumCheck { }); let prover_poly = compute_lagrange_interpolated_poly(&products_sum).coeffs; - transcript.absorb(&prover_poly); + transcript.add(&prover_poly); prover_msgs.push(prover_poly); - let challenge = transcript.get_challenge(); + let challenge = transcript.challenge_field_element(); challenges.push(challenge); poly.flattened_ml_extensions.iter_mut().for_each(|mle| { mle.evaluations = cfg_chunks!(mle.evaluations, 2) @@ -183,22 +182,22 @@ impl IOPSumCheck { )) } - pub fn verify( + pub fn verify>( claimed_sum: F, proof: &IOPProof, aux_info: &VPAuxInfo, transcript: &mut impl Transcript, ) -> Result, Error> { - transcript.absorb(&F::from(aux_info.num_variables as u64)); - transcript.absorb(&F::from(aux_info.max_degree as u64)); + transcript.add(&F::from(aux_info.num_variables as u64)); + transcript.add(&F::from(aux_info.max_degree as u64)); assert_eq!(aux_info.num_variables, proof.proofs.len()); let challenges = proof .proofs .iter() .map(|msg| { - transcript.absorb(&msg); - transcript.get_challenge() + transcript.add(msg); + transcript.challenge_field_element() }) .collect::>(); diff --git a/crates/primitives/src/traits/mod.rs b/crates/primitives/src/traits/mod.rs index 18e1c1256..aecf72258 100644 --- a/crates/primitives/src/traits/mod.rs +++ b/crates/primitives/src/traits/mod.rs @@ -1,4 +1,3 @@ -use ark_crypto_primitives::sponge::Absorb; use ark_ec::{ short_weierstrass::{Projective, SWCurveConfig}, AffineRepr, CurveGroup, PrimeGroup, @@ -10,7 +9,9 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::SynthesisError; use ark_std::{ + any::TypeId, iter::Sum, + mem::transmute_copy, ops::{Add, Mul}, }; @@ -117,7 +118,7 @@ impl> /// `Field` trait is a wrapper around `PrimeField` that also includes the /// necessary bounds for the field to be used conveniently in folding schemes. pub trait SonobeField: - PrimeField + Absorb + AbsorbNonNative + Inputize + PrimeField + Absorbable + Inputize { const BITS_PER_LIMB: usize; /// The in-circuit variable type for this field. @@ -133,7 +134,7 @@ impl, const N: usize> SonobeField for Fp { /// necessary bounds for the curve to be used conveniently in folding schemes. pub trait SonobeCurve: CurveGroup - + AbsorbNonNative + + Absorbable + Inputize + InputizeNonNative { @@ -147,47 +148,105 @@ impl> SonobeC type Var = ProjectiveVar>; } -/// An interface for objects that can be absorbed by a `Transcript`. -/// -/// Matches `Absorb` in `ark-crypto-primitives`. -pub trait AbsorbNonNative { - /// Converts the object into field elements that can be absorbed by a `Transcript`. +pub trait Absorbable { + /// Converts the object into field elements that can be absorbed by a `CryptographicSponge`. /// Append the list to `dest` - fn to_native_sponge_field_elements(&self, dest: &mut Vec); + fn absorb_into(&self, dest: &mut Vec); - /// Converts the object into field elements that can be absorbed by a `Transcript`. + /// Converts the object into field elements that can be absorbed by a `CryptographicSponge`. /// Return the list as `Vec` - fn to_native_sponge_field_elements_as_vec(&self) -> Vec { + fn extract_absorbed(&self) -> Vec { let mut result = Vec::new(); - self.to_native_sponge_field_elements(&mut result); + self.absorb_into(&mut result); result } } -/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field -/// is `F`. -/// -/// Matches `AbsorbGadget` in `ark-crypto-primitives`. -pub trait AbsorbNonNativeGadget { - /// Converts the object into field elements that can be absorbed by a `TranscriptVar`. - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError>; +impl, const N: usize> Absorbable for Fp { + fn absorb_into(&self, dest: &mut Vec) { + if TypeId::of::() == TypeId::of::() { + // Safe because `F` and `Self` have the same type + // TODO (@winderica): specialization when??? + dest.push(unsafe { transmute_copy::(self) }); + } else { + let bits_per_limb = F::MODULUS_BIT_SIZE - 1; + let num_limbs = Self::MODULUS_BIT_SIZE.div_ceil(bits_per_limb); + + let mut limbs = self + .into_bigint() + .to_bits_le() + .chunks(bits_per_limb as usize) + .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) + .collect::>(); + limbs.resize(num_limbs as usize, F::zero()); + + dest.extend(&limbs) + } + } } -impl AbsorbNonNative for [T] { - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - for t in self.iter() { - t.to_native_sponge_field_elements(dest); +impl>> Absorbable for Projective

{ + fn absorb_into(&self, dest: &mut Vec) { + let affine = self.into_affine(); + let (x, y) = affine.xy().unwrap_or_default(); + [x, y].absorb_into(dest); + } +} + +impl Absorbable for usize { + fn absorb_into(&self, dest: &mut Vec) { + dest.push(F::from(*self as u64)); + } +} + +impl> Absorbable for &T { + fn absorb_into(&self, dest: &mut Vec) { + >::absorb_into(self, dest); + } +} + +impl> Absorbable for (T, T) { + fn absorb_into(&self, dest: &mut Vec) { + self.0.absorb_into(dest); + self.1.absorb_into(dest); + } +} + +impl + 'static> Absorbable for [T] { + fn absorb_into(&self, dest: &mut Vec) { + if TypeId::of::() == TypeId::of::() { + // Safe because `F` and `T` have the same type + dest.extend(unsafe { transmute_copy::<&[T], &[F]>(&self) }); + } else { + for t in self.iter() { + t.absorb_into(dest); + } } } } -impl AbsorbNonNative for (T, T) { - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - self.0.to_native_sponge_field_elements(dest); - self.1.to_native_sponge_field_elements(dest); +impl + 'static, const N: usize> Absorbable for [T; N] { + fn absorb_into(&self, dest: &mut Vec) { + <[T] as Absorbable>::absorb_into(self, dest); } } +impl + 'static> Absorbable for Vec { + fn absorb_into(&self, dest: &mut Vec) { + <[T] as Absorbable>::absorb_into(self, dest); + } +} + +// TODO: rework this +/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field +/// is `F`. +/// +/// Matches `AbsorbGadget` in `ark-crypto-primitives`. +pub trait AbsorbNonNativeGadget { + /// Converts the object into field elements that can be absorbed by a `TranscriptVar`. + fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError>; +} + impl> AbsorbNonNativeGadget for &T { fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { T::to_native_sponge_field_elements(self) @@ -204,32 +263,6 @@ impl> AbsorbNonNativeGadget for [T } } -impl, const N: usize> AbsorbNonNative for Fp { - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; - let num_limbs = (Fp::::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb); - - let mut limbs = self - .into_bigint() - .to_bits_le() - .chunks(bits_per_limb) - .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) - .collect::>(); - limbs.resize(num_limbs, F::zero()); - - dest.extend(&limbs) - } -} - -impl> AbsorbNonNative for Projective

{ - fn to_native_sponge_field_elements(&self, dest: &mut Vec) { - let affine = self.into_affine(); - let (x, y) = affine.xy().unwrap_or_default(); - - [x, y].to_native_sponge_field_elements(dest); - } -} - #[derive(Clone, Copy, Default, Debug)] pub struct Null; diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index a4331c63e..504559a7e 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -1,46 +1,85 @@ -use ark_crypto_primitives::sponge::{constraints::CryptographicSpongeVar, CryptographicSponge}; +use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, CryptographicSponge, FieldElementSize, +}; use ark_ec::CurveGroup; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; use ark_relations::gr1cs::SynthesisError; -use crate::traits::{AbsorbNonNative, AbsorbNonNativeGadget}; +use crate::traits::{AbsorbNonNativeGadget, Absorbable}; pub mod poseidon; -pub trait Transcript: CryptographicSponge { +pub trait Transcript { /// `new_with_pp_hash` creates a new transcript / sponge with the given /// hash of the public parameters. - fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self; + fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self + where + F: Absorbable, + Self: CryptographicSponge, + { + let mut sponge = Self::new(config); + sponge.add(&pp_hash); + sponge + } - /// `absorb_point` is for absorbing points whose `BaseField` is the field of - /// the sponge, i.e., the type `C` of these points should satisfy - /// `C::BaseField = F`. - /// - /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` - /// instead. - fn absorb_point>(&mut self, v: &C); - /// `absorb_nonnative` is for structs that contain non-native (field or - /// group) elements, including: - /// - /// - A field element of type `T: PrimeField` that will be absorbed into a - /// sponge that operates in another field `F != T`. - /// - A group element of type `C: CurveGroup` that will be absorbed into a - /// sponge that operates in another field `F != C::BaseField`, e.g., - /// `F = C::ScalarField`. - /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that - /// will be absorbed into a sponge that operates in the (scalar field of - /// the) primary curve. - /// - /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on - /// the primary curve also contains non-native elements, we still regard - /// it as native, because the sponge is on the same curve. - fn absorb_nonnative(&mut self, v: &V); + fn add + ?Sized>(&mut self, input: &A); + + /// Squeeze `num_bytes` bytes from the sponge. + fn get_bytes(&mut self, num_bytes: usize) -> Vec; + + /// Squeeze `num_bits` bits from the sponge. + fn get_bits(&mut self, num_bits: usize) -> Vec; + + fn get_field_elements_with_sizes(&mut self, sizes: &[FieldElementSize]) -> Vec; + + fn get_field_elements(&mut self, num_elements: usize) -> Vec; + + /// Creates a new sponge with applied domain separation. + fn separate_domain(&self, domain: &[u8]) -> Self + where + F: Absorbable, + Self: CryptographicSponge, + { + let mut new_sponge = self.clone(); + + let mut input = domain.len().to_le_bytes().to_vec(); + input.extend_from_slice(domain); + + let limbs = input + .chunks(F::MODULUS_BIT_SIZE.div_ceil(8) as usize) + .map(|chunk| F::from_le_bytes_mod_order(chunk)) + .collect::>(); + + new_sponge.add(&limbs); + + new_sponge + } - fn get_challenge(&mut self) -> F; - /// get_challenge_nbits returns a field element of size nbits - fn get_challenge_nbits(&mut self, nbits: usize) -> Vec; - fn get_challenges(&mut self, n: usize) -> Vec; + fn challenge_field_element(&mut self) -> F + where + F: Absorbable, + { + let c = self.get_field_elements(1); + self.add(&c[0]); + c[0] + } + fn challenge_bits(&mut self, nbits: usize) -> Vec + where + F: Absorbable, + { + let bits = self.get_bits(nbits); + self.add(&F::from(F::BigInt::from_bits_le(&bits))); + bits + } + fn challenge_field_elements(&mut self, n: usize) -> Vec + where + F: Absorbable, + { + let c = self.get_field_elements(n); + self.add(&c); + c + } } pub trait TranscriptVar: diff --git a/crates/primitives/src/transcripts/poseidon.rs b/crates/primitives/src/transcripts/poseidon.rs index 0d6519beb..a3a298108 100644 --- a/crates/primitives/src/transcripts/poseidon.rs +++ b/crates/primitives/src/transcripts/poseidon.rs @@ -1,47 +1,56 @@ +use std::mem::transmute_copy; + use ark_crypto_primitives::sponge::{ constraints::CryptographicSpongeVar, poseidon::{ constraints::PoseidonSpongeVar, find_poseidon_ark_and_mds, PoseidonConfig, PoseidonSponge, }, - Absorb, CryptographicSponge, + Absorb, CryptographicSponge, FieldBasedCryptographicSponge, }; -use ark_ec::{AffineRepr, CurveGroup}; -use ark_ff::{BigInteger, PrimeField}; +use ark_ec::CurveGroup; +use ark_ff::PrimeField; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; -use super::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; - -impl Transcript for PoseidonSponge { - fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self { - let mut sponge = Self::new(config); - sponge.absorb(&pp_hash); - sponge +use crate::transcripts::{Absorbable, FieldElementSize}; + +use super::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; + +impl Transcript for PoseidonSponge { + fn add + ?Sized>(&mut self, input: &A) { + struct Hack(I); + impl Absorb for Hack> { + fn to_sponge_bytes(&self, _: &mut Vec) { + // Unreachable because `PoseidonSponge::absorb` only calls + // `to_sponge_field_elements_as_vec::` + unreachable!() + } + + fn to_sponge_field_elements(&self, dest: &mut Vec) { + // Safe because `F` in `to_sponge_field_elements_as_vec::`, + // which is called by `PoseidonSponge::absorb`, is the same as + // `T` here. + dest.extend(unsafe { transmute_copy::<&[F], &[T]>(&self.0.as_ref()) }); + } + } + let v = input.extract_absorbed(); + CryptographicSponge::absorb(self, &Hack(v)); } - // Compatible with the in-circuit `TranscriptVar::absorb_point` - fn absorb_point>(&mut self, p: &C) { - let (x, y) = p.into_affine().xy().unwrap_or_default(); - self.absorb(&x); - self.absorb(&y); - } - fn absorb_nonnative(&mut self, v: &V) { - self.absorb(&v.to_native_sponge_field_elements_as_vec::()); + fn get_bits(&mut self, num_bits: usize) -> Vec { + CryptographicSponge::squeeze_bits(self, num_bits) } - fn get_challenge(&mut self) -> F { - let c = self.squeeze_field_elements(1); - self.absorb(&c[0]); - c[0] + + fn get_bytes(&mut self, num_bytes: usize) -> Vec { + CryptographicSponge::squeeze_bytes(self, num_bytes) } - fn get_challenge_nbits(&mut self, nbits: usize) -> Vec { - let bits = self.squeeze_bits(nbits); - self.absorb(&F::from(F::BigInt::from_bits_le(&bits))); - bits + + fn get_field_elements_with_sizes(&mut self, sizes: &[FieldElementSize]) -> Vec { + self.squeeze_native_field_elements_with_sizes(sizes) } - fn get_challenges(&mut self, n: usize) -> Vec { - let c = self.squeeze_field_elements(n); - self.absorb(&c); - c + + fn get_field_elements(&mut self, num_elements: usize) -> Vec { + self.squeeze_native_field_elements(num_elements) } } @@ -133,7 +142,7 @@ pub fn poseidon_canonical_config() -> PoseidonConfig { pub mod tests { use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; use ark_ec::PrimeGroup; - use ark_ff::UniformRand; + use ark_ff::{BigInteger, UniformRand}; use ark_r1cs_std::{ alloc::AllocVar, groups::curves::short_weierstrass::ProjectiveVar, GR1CSVar, }; @@ -174,8 +183,8 @@ pub mod tests { let rng = &mut test_rng(); let p = G1::rand(rng); - tr.absorb_point(&p); - let c = tr.get_challenge(); + tr.add(&p); + let c = tr.challenge_field_element(); // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); @@ -200,8 +209,8 @@ pub mod tests { let rng = &mut test_rng(); let p = G1::rand(rng); - tr.absorb_nonnative(&p); - let c = tr.get_challenge(); + tr.add(&p); + let c = tr.challenge_field_element(); // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); @@ -221,8 +230,8 @@ pub mod tests { // use 'native' transcript let config = poseidon_canonical_config::(); let mut tr = PoseidonSponge::::new(&config); - tr.absorb(&Fr::from(42_u32)); - let c = tr.get_challenge(); + tr.add(&Fr::from(42_u32)); + let c = tr.challenge_field_element(); // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); @@ -243,10 +252,10 @@ pub mod tests { // use 'native' transcript let config = poseidon_canonical_config::(); let mut tr = PoseidonSponge::::new(&config); - tr.absorb(&Fq::from(42_u32)); + tr.add(&Fq::from(42_u32)); // get challenge from native transcript - let c_bits = tr.get_challenge_nbits(nbits); + let c_bits = tr.challenge_bits(nbits); // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); From 4d01dfe3caa8ecd9f0ce893f01e4033ce1e1afd9 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:52 +0800 Subject: [PATCH 06/99] Refactor: move `FCircuit`'s field bound to associated type --- crates/primitives/src/circuits/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 0a0069ea1..1e8f34fb8 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -3,7 +3,10 @@ use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; -use ark_std::{marker::PhantomData, ops::{Index, IndexMut}}; +use ark_std::{ + marker::PhantomData, + ops::{Index, IndexMut}, +}; pub mod utils; @@ -15,7 +18,8 @@ pub mod utils; /// both `FCircuit::ExternalInputs` and `FCircuit::ExternalInputsVar`, where the `Default` trait /// implementation for the `ExternalInputs` returns the initialized data structure (ie. if the type /// contains a vector, it is initialized at the expected length). -pub trait FCircuit { +pub trait FCircuit { + type Field: PrimeField; type ExternalInputs; /// returns the number of elements in the state of the FCircuit, which corresponds to the @@ -27,11 +31,11 @@ pub trait FCircuit { // this method uses self, so that each FCircuit implementation (and different frontends) // can hold a state if needed to store data to generate the constraints. &self, - cs: ConstraintSystemRef, + cs: ConstraintSystemRef, i: usize, - z_i: Vec>, + z_i: Vec>, external_inputs: Self::ExternalInputs, // inputs that are not part of the state - ) -> Result>, SynthesisError>; + ) -> Result>, SynthesisError>; } #[derive(Clone, Debug, PartialEq)] From 644d11c82b385cfc3b16118dd4480cbc17e578da Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 20:49:56 +0800 Subject: [PATCH 07/99] Refactor: move various traits to their dedicated submodules --- crates/primitives/Cargo.toml | 1 + crates/primitives/src/algebra/field/mod.rs | 95 ++ .../primitives/src/algebra/field/nonnative.rs | 1234 +++++++++++++++++ .../src/algebra/field/nonnative2.rs | 1159 ++++++++++++++++ crates/primitives/src/algebra/group/mod.rs | 174 +++ .../affine.rs => algebra/group/nonnative.rs} | 76 +- crates/primitives/src/algebra/mod.rs | 3 + crates/primitives/src/algebra/ops/bits.rs | 33 + .../src/{gadgets/math => algebra/ops}/eq.rs | 0 .../{gadgets/math => algebra/ops}/matrix.rs | 0 .../src/{gadgets/math => algebra/ops}/mod.rs | 2 + crates/primitives/src/algebra/ops/rlc.rs | 44 + .../{gadgets/math => algebra/ops}/vector.rs | 0 .../src/arithmetizations/ccs/circuits.rs | 2 +- .../src/arithmetizations/ccs/mod.rs | 7 +- .../src/arithmetizations/r1cs/circuits.rs | 6 +- crates/primitives/src/commitments/mod.rs | 76 +- crates/primitives/src/commitments/pedersen.rs | 182 ++- crates/primitives/src/gadgets/mod.rs | 2 - .../primitives/src/gadgets/nonnative/mod.rs | 2 - .../primitives/src/gadgets/nonnative/uint.rs | 993 ------------- crates/primitives/src/lib.rs | 2 +- crates/primitives/src/sumcheck/mod.rs | 2 +- crates/primitives/src/traits.rs | 56 + crates/primitives/src/traits/mod.rs | 345 ----- .../primitives/src/transcripts/absorbable.rs | 98 ++ crates/primitives/src/transcripts/mod.rs | 108 +- crates/primitives/src/transcripts/poseidon.rs | 88 +- 28 files changed, 3256 insertions(+), 1534 deletions(-) create mode 100644 crates/primitives/src/algebra/field/mod.rs create mode 100644 crates/primitives/src/algebra/field/nonnative.rs create mode 100644 crates/primitives/src/algebra/field/nonnative2.rs create mode 100644 crates/primitives/src/algebra/group/mod.rs rename crates/primitives/src/{gadgets/nonnative/affine.rs => algebra/group/nonnative.rs} (74%) create mode 100644 crates/primitives/src/algebra/mod.rs create mode 100644 crates/primitives/src/algebra/ops/bits.rs rename crates/primitives/src/{gadgets/math => algebra/ops}/eq.rs (100%) rename crates/primitives/src/{gadgets/math => algebra/ops}/matrix.rs (100%) rename crates/primitives/src/{gadgets/math => algebra/ops}/mod.rs (61%) create mode 100644 crates/primitives/src/algebra/ops/rlc.rs rename crates/primitives/src/{gadgets/math => algebra/ops}/vector.rs (100%) delete mode 100644 crates/primitives/src/gadgets/mod.rs delete mode 100644 crates/primitives/src/gadgets/nonnative/mod.rs delete mode 100644 crates/primitives/src/gadgets/nonnative/uint.rs create mode 100644 crates/primitives/src/traits.rs delete mode 100644 crates/primitives/src/traits/mod.rs create mode 100644 crates/primitives/src/transcripts/absorbable.rs diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index efea822bc..7bdb8fc6f 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -17,6 +17,7 @@ ark-r1cs-std = { workspace = true } ark-serialize = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } +num-traits = { workspace = true } rayon = { workspace = true } thiserror = { workspace = true } diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs new file mode 100644 index 000000000..4f836b807 --- /dev/null +++ b/crates/primitives/src/algebra/field/mod.rs @@ -0,0 +1,95 @@ +use ark_ff::{BigInteger, Fp, FpConfig, PrimeField}; +use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; +use ark_relations::gr1cs::SynthesisError; +use ark_std::{any::TypeId, mem::transmute_copy}; + +use crate::{ + traits::{Inputize, InputizeNonNative}, + transcripts::{Absorbable, AbsorbableGadget}, +}; + +pub mod nonnative; +pub mod nonnative2; + +/// `Field` trait is a wrapper around `PrimeField` that also includes the +/// necessary bounds for the field to be used conveniently in folding schemes. +pub trait SonobeField: + PrimeField + Absorbable + Inputize +{ + const BITS_PER_LIMB: usize; + /// The in-circuit variable type for this field. + type Var: FieldVar; +} + +impl, const N: usize> SonobeField for Fp { + // For a `F` with order > 250 bits, 55 is chosen for optimizing the most + // expensive part `Az∘Bz` when checking the R1CS relation for CycleFold. + // Consider using `NonNativeUintVar` to represent the base field `Fq`. + // Since 250 / 55 = 4.46, the `NonNativeUintVar` has 5 limbs. + // Now, the multiplication of two `NonNativeUintVar`s has 9 limbs, and + // each limb has at most 2^{55 * 2} * 5 = 112.3 bits. + // For a 1400x1400 matrix `A`, the multiplication of `A`'s row and `z` + // is the sum of 1400 `NonNativeUintVar`s, each with 9 limbs. + // Thus, the maximum bit length of limbs of each element in `Az` is + // 2^{55 * 2} * 5 * 1400 = 122.7 bits. + // Finally, in the hadamard product of `Az` and `Bz`, every element has + // 17 limbs, whose maximum bit length is (2^{55 * 2} * 5 * 1400)^2 * 9 + // = 248.7 bits and is less than the native field `Fr`. + // Thus, 55 allows us to compute `Az∘Bz` without the expensive alignment + // operation. + // + // TODO: either make it a global const, or compute an optimal value + // based on the modulus size. + const BITS_PER_LIMB: usize = 55; // TODO: make this configurable + type Var = FpVar; +} + +impl, const N: usize> Absorbable for Fp { + fn absorb_into(&self, dest: &mut Vec) { + if TypeId::of::() == TypeId::of::() { + // Safe because `F` and `Self` have the same type + // TODO (@winderica): specialization when??? + dest.push(unsafe { transmute_copy::(self) }); + } else { + let bits_per_limb = F::MODULUS_BIT_SIZE - 1; + let num_limbs = Self::MODULUS_BIT_SIZE.div_ceil(bits_per_limb); + + let mut limbs = self + .into_bigint() + .to_bits_le() + .chunks(bits_per_limb as usize) + .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) + .collect::>(); + limbs.resize(num_limbs as usize, F::zero()); + + dest.extend(&limbs) + } + } +} + +impl AbsorbableGadget> for FpVar { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + dest.push(self.clone()); + Ok(()) + } +} + +impl, const N: usize> Inputize for Fp { + /// Returns the internal representation in the same order as how the value + /// is allocated in `FpVar::new_input`. + fn inputize(&self) -> Vec { + vec![*self] + } +} + +impl InputizeNonNative for P { + /// Returns the internal representation in the same order as how the value + /// is allocated in `NonNativeUintVar::new_input`. + fn inputize_nonnative(&self) -> Vec { + self.into_bigint() + .to_bits_le() + .chunks(F::BITS_PER_LIMB) + .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) + .collect() + } +} diff --git a/crates/primitives/src/algebra/field/nonnative.rs b/crates/primitives/src/algebra/field/nonnative.rs new file mode 100644 index 000000000..ef051fef1 --- /dev/null +++ b/crates/primitives/src/algebra/field/nonnative.rs @@ -0,0 +1,1234 @@ +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, + convert::ToBitsGadget, + fields::{fp::FpVar, FieldVar}, + prelude::EqGadget, + select::CondSelectGadget, + GR1CSVar, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{ + borrow::Borrow, + cmp::{max, min}, + ops::Index, +}; +use num_bigint::{BigInt, BigUint, Sign}; +use num_integer::Integer; +use num_traits::Signed; + +use crate::algebra::ops::bits::{FromBitsGadget, ToBitsGadgetExt}; +use crate::{ + algebra::{ + field::SonobeField, + ops::{ + eq::EquivalenceGadget, + matrix::{MatrixGadget, SparseMatrixVar}, + vector::VectorGadget, + }, + }, + transcripts::AbsorbableGadget, +}; + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Bound { + pub lb: BigInt, + pub ub: BigInt, +} + +impl Bound { + pub fn zero() -> Self { + Self::default() + } + + pub fn new_ub(ub: BigInt) -> Self { + Self { + lb: BigInt::zero(), + ub, + } + } + + pub fn new_n_bits(n: usize) -> Self { + Self { + lb: BigInt::zero(), + ub: (BigInt::one() << n) - BigInt::one(), + } + } + + pub fn new(lb: BigInt, ub: BigInt) -> Self { + Self { lb, ub } + } +} + +impl Bound { + pub fn add(&self, other: &Self) -> Self { + Self { + lb: &self.lb + &other.lb, + ub: &self.ub + &other.ub, + } + } + + pub fn sub(&self, other: &Self) -> Self { + Self { + lb: &self.lb - &other.ub, + ub: &self.ub - &other.lb, + } + } + + pub fn add_many(limbs: &[Self]) -> Self { + Self { + lb: limbs.iter().map(|l| &l.lb).sum(), + ub: limbs.iter().map(|l| &l.ub).sum(), + } + } + + pub fn mul(&self, other: &Self) -> Self { + let ll = &self.lb * &other.lb; + let lu = &self.lb * &other.ub; + let ul = &self.ub * &other.lb; + let uu = &self.ub * &other.ub; + + Self { + lb: min(min(&ll, &lu), min(&ul, &uu)).clone(), + ub: max(max(&ll, &lu), max(&ul, &uu)).clone(), + } + } + + pub fn shl(&self, shift: usize) -> Self { + Self { + lb: &self.lb << shift, + ub: &self.ub << shift, + } + } + + pub fn filter_safe(self) -> Option { + let limit = BigInt::from_biguint(Sign::Plus, F::MODULUS_MINUS_ONE_DIV_TWO.into()); + (self.ub <= limit && self.lb >= -limit).then_some(self) + } +} + +// /// `LimbVar` represents a single limb of a non-native unsigned integer in the +// /// circuit. +// /// The limb value `v` should be small enough to fit into `FpVar`, and we also +// /// store an upper bound `ub` for the limb value, which is treated as a constant +// /// in the circuit and is used for efficient equality checks and some arithmetic +// /// operations. +// #[derive(Debug, Clone)] +// pub struct LimbVar { +// pub v: FpVar, +// pub lb: BigInt, +// pub ub: BigInt, +// } + +// impl]>> From for LimbVar { +// fn from(bits: B) -> Self { +// Self { +// // `Boolean::le_bits_to_fp` will return an error if the internal +// // invocation of `Boolean::enforce_in_field_le` fails. +// // However, this method is only called when the length of `bits` is +// // greater than `F::MODULUS_BIT_SIZE`, which should not happen in +// // our case where `bits` is guaranteed to be short. +// v: Boolean::le_bits_to_fp(bits.as_ref()).unwrap(), +// lb: BigInt::zero(), +// ub: (BigInt::one() << bits.as_ref().len()) - BigInt::one(), +// } +// } +// } + +// impl Default for LimbVar { +// fn default() -> Self { +// Self { +// v: FpVar::zero(), +// lb: BigInt::zero(), +// ub: BigInt::zero(), +// } +// } +// } + +// impl GR1CSVar for LimbVar { +// type Value = F; + +// fn cs(&self) -> ConstraintSystemRef { +// self.v.cs() +// } + +// fn value(&self) -> Result { +// self.v.value() +// } +// } + +// impl CondSelectGadget for LimbVar { +// fn conditionally_select( +// cond: &Boolean, +// true_value: &Self, +// false_value: &Self, +// ) -> Result { +// // We only allow selecting between two values with the same upper bound +// assert_eq!(true_value.lb, false_value.lb); +// assert_eq!(true_value.ub, false_value.ub); +// Ok(Self { +// v: cond.select(&true_value.v, &false_value.v)?, +// lb: true_value.lb.clone(), +// ub: true_value.ub.clone(), +// }) +// } +// } + +// impl LimbVar { +// /// Add two `LimbVar`s. +// /// Returns `None` if the upper bound of the sum is too large, i.e., +// /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. +// /// Otherwise, returns the sum as a `LimbVar`. +// pub fn add(&self, other: &Self) -> Option { +// let lbound = &self.lb + &other.lb; +// let ubound = &self.ub + &other.ub; +// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); +// if ubound > limit || lbound < -limit { +// None +// } else { +// Some(Self { +// v: &self.v + &other.v, +// lb: lbound, +// ub: ubound, +// }) +// } +// } + +// /// Add multiple `LimbVar`s. +// /// Returns `None` if the upper bound of the sum is too large, i.e., +// /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. +// /// Otherwise, returns the sum as a `LimbVar`. +// pub fn add_many(limbs: &[Self]) -> Option { +// let lbound = limbs.iter().map(|l| &l.lb).sum::(); +// let ubound = limbs.iter().map(|l| &l.ub).sum::(); +// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); +// if ubound > limit || lbound < -limit { +// None +// } else { +// Some(Self { +// v: if limbs.is_constant() { +// FpVar::constant(limbs.value().unwrap_or_default().into_iter().sum()) +// } else { +// limbs.iter().map(|l| &l.v).sum() +// }, +// lb: lbound, +// ub: ubound, +// }) +// } +// } + +// /// Multiply two `LimbVar`s. +// /// Returns `None` if the upper bound of the product is too large, i.e., +// /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. +// /// Otherwise, returns the product as a `LimbVar`. +// pub fn mul(&self, other: &Self) -> Option { +// let ll = &self.lb * &other.lb; +// let lu = &self.lb * &other.ub; +// let ul = &self.ub * &other.lb; +// let uu = &self.ub * &other.ub; + +// let lbound = min(min(&ll, &lu), min(&ul, &uu)).clone(); +// let ubound = max(max(&ll, &lu), max(&ul, &uu)).clone(); +// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); +// if ubound > limit || lbound < -limit { +// None +// } else { +// Some(Self { +// v: &self.v * &other.v, +// lb: lbound, +// ub: ubound, +// }) +// } +// } + +// pub fn zero() -> Self { +// Self::default() +// } + +// pub fn constant(v: BigInt) -> Self { +// let (v_sign, v_abs) = v.clone().into_parts(); +// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); +// assert!(v_abs <= limit); +// Self { +// v: if v_sign == Sign::Minus { +// FpVar::constant(-F::from(v_abs)) +// } else { +// FpVar::constant(F::from(v_abs)) +// }, +// lb: v.clone(), +// ub: v, +// } +// } +// } + +// impl ToBitsGadget for LimbVar { +// fn to_bits_le(&self) -> Result>, SynthesisError> { +// let cs = self.cs(); + +// assert_eq!(self.lb, BigInt::zero()); + +// let bits = &self +// .v +// .value() +// .unwrap_or_default() +// .into_bigint() +// .to_bits_le()[..self.ub.bits() as usize]; +// let bits = if cs.is_none() { +// Vec::new_constant(cs, bits)? +// } else { +// Vec::new_witness(cs, || Ok(bits))? +// }; + +// Boolean::le_bits_to_fp(&bits)?.enforce_equal(&self.v)?; + +// Ok(bits) +// } +// } + +/// `NonNativeUintVar` represents a non-native unsigned integer (BigUint) in the +/// circuit. +/// We apply [xJsnark](https://akosba.github.io/papers/xjsnark.pdf)'s techniques +/// for efficient operations on `NonNativeUintVar`. +/// Note that `NonNativeUintVar` is different from arkworks' `NonNativeFieldVar` +/// in that the latter runs the expensive `reduce` (`align` + `modulo` in our +/// terminology) after each arithmetic operation, while the former only reduces +/// the integer when explicitly called. +#[derive(Debug, Clone)] +pub struct NonNativeUintVar { + pub(crate) limbs: Vec>, + bounds: Vec, +} + +impl AllocVar<(BigInt, Bound), F> for NonNativeUintVar { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + todo!(); + let cs = cs.into().cs(); + let v = f()?; + let (x, b) = v.borrow(); + + if x > &b.ub || x < &b.lb { + return Err(SynthesisError::DivisionByZero); + } + + let (x_sign, mut x_abs) = x.clone().into_parts(); + + let mut limbs = vec![]; + let mut bounds = vec![]; + + let l = max(b.lb.bits(), b.ub.bits()); + + if l == 0 { + return Ok(Self { limbs, bounds }); + } + + let (num_full_chunks, final_chunk_size) = (l as usize).div_rem(&F::BITS_PER_LIMB); + + let mask = (BigUint::one() << F::BITS_PER_LIMB) - BigUint::one(); + + loop { + let limb = FpVar::new_variable(cs.clone(), || Ok(F::from(&x_abs & &mask)), mode)?; + Self::enforce_bit_length(&limb, F::BITS_PER_LIMB)?; + limbs.push(limb); + bounds.push(Bound::new_n_bits(F::BITS_PER_LIMB)); + x_abs >>= F::BITS_PER_LIMB; + if x_abs.is_zero() { + let is_neg = Boolean::new_variable(cs.clone(), || Ok(x_sign == Sign::Minus), { + if b.lb >= BigInt::zero() || b.ub <= BigInt::zero() { + AllocationMode::Constant + } else { + mode + } + })?; + *limbs.last_mut().unwrap() *= + is_neg.select(&FpVar::one().negate()?, &FpVar::one())?; + break; + } + } + + if final_chunk_size > 0 { + let limb = FpVar::new_variable(cs.clone(), || Ok(F::from(x_abs)), mode)?; + Self::enforce_bit_length(&limb, F::BITS_PER_LIMB)?; + } + + for chunk in (0..l) + .map(|i| x.bit(i)) + .collect::>() + .chunks(F::BITS_PER_LIMB) + { + let limb = F::from(F::BigInt::from_bits_le(chunk)); + let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; + Self::enforce_bit_length(&limb, chunk.len())?; + limbs.push(limb); + bounds.push(Bound::new_n_bits(chunk.len())); + } + let s = Boolean::new_variable(cs.clone(), || Ok(x.sign() == Sign::Minus), { + if b.lb >= BigInt::zero() || b.ub <= BigInt::zero() { + AllocationMode::Constant + } else { + mode + } + })?; + limbs[num_full_chunks] = + s.select(&limbs[num_full_chunks].negate()?, &limbs[num_full_chunks])?; + + let t = BigInt::one() << ((bounds.len() - 1) * F::BITS_PER_LIMB); + bounds[num_full_chunks] = Bound::new(b.lb.div_floor(&t), b.ub.div_ceil(&t)); + + Ok(Self { limbs, bounds }) + } +} + +impl AllocVar for NonNativeUintVar { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + + let v = BigInt::from_biguint(Sign::Plus, v.borrow().clone().into()); + let m = BigInt::from_biguint(Sign::Plus, G::MODULUS.into()); + + match mode { + AllocationMode::Constant => Self::constant(v), + _ => Self::new_variable(cs, || Ok((v, Bound::new(BigInt::zero(), m))), mode), + } + } +} + +impl EqGadget for NonNativeUintVar { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + let mut result = Boolean::TRUE; + if self.limbs.len() != other.limbs.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.bounds.len() != other.bounds.len() { + return Err(SynthesisError::Unsatisfiable); + } + for i in 0..self.limbs.len() { + if self.bounds[i] != other.bounds[i] { + return Err(SynthesisError::Unsatisfiable); + } + result &= self.limbs[i].is_eq(&other.limbs[i])?; + } + Ok(result) + } + + fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { + if self.limbs.len() != other.limbs.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.bounds.len() != other.bounds.len() { + return Err(SynthesisError::Unsatisfiable); + } + for i in 0..self.limbs.len() { + if self.bounds[i] != other.bounds[i] { + return Err(SynthesisError::Unsatisfiable); + } + self.limbs[i].enforce_equal(&other.limbs[i])?; + } + Ok(()) + } +} + +impl GR1CSVar for NonNativeUintVar { + type Value = BigInt; + + fn cs(&self) -> ConstraintSystemRef { + self.limbs.cs() + } + + fn value(&self) -> Result { + let mut r = BigInt::zero(); + + for limb in self.limbs.value()?.into_iter().rev() { + r <<= F::BITS_PER_LIMB; + r += if limb.into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO { + BigInt::from_biguint(Sign::Minus, (-limb).into()) + } else { + BigInt::from_biguint(Sign::Plus, limb.into()) + }; + } + + Ok(r) + } +} + +impl CondSelectGadget for NonNativeUintVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.limbs.len() != false_value.limbs.len() { + return Err(SynthesisError::Unsatisfiable); + } + if true_value.bounds.len() != false_value.bounds.len() { + return Err(SynthesisError::Unsatisfiable); + } + let mut v = vec![]; + let mut bounds = vec![]; + for i in 0..true_value.limbs.len() { + if true_value.bounds[i] != false_value.bounds[i] { + return Err(SynthesisError::Unsatisfiable); + } + v.push(cond.select(&true_value.limbs[i], &false_value.limbs[i])?); + bounds.push(true_value.bounds[i].clone()); + } + Ok(Self { + limbs: v, + bounds: bounds, + }) + } +} + +impl NonNativeUintVar { + fn constant(v: BigInt) -> Result { + Self::new_constant( + ConstraintSystemRef::None, + (v.clone(), Bound::new(v.clone(), v)), + ) + } + + fn ubound(&self) -> BigInt { + let mut r = BigInt::zero(); + + for i in self.bounds.iter().rev() { + r <<= F::BITS_PER_LIMB; + r += &i.ub; + } + + r + } + + fn lbound(&self) -> BigInt { + let mut r = BigInt::zero(); + + for i in self.bounds.iter().rev() { + r <<= F::BITS_PER_LIMB; + r += &i.lb; + } + + r + } +} + +impl NonNativeUintVar { + /// Enforce `self` to be less than `other`, where `self` and `other` should + /// be aligned. + /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 + pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> { + let len = max(self.limbs.len(), other.limbs.len()); + let zero = FpVar::zero(); + + // Compute the difference between limbs of `other` and `self`. + // Denote a positive limb by `+`, a negative limb by `-`, a zero limb by + // `0`, and an unknown limb by `?`. + // Then, for `self < other`, `delta` should look like: + // ? ? ... ? ? + 0 0 ... 0 0 + let delta = (0..len) + .map(|i| { + let x = self.limbs.get(i).unwrap_or(&zero); + let y = other.limbs.get(i).unwrap_or(&zero); + y - x + }) + .collect::>(); + + // `helper` is a vector of booleans that indicates if the corresponding + // limb of `delta` is the first (searching from MSB) positive limb. + // For example, if `delta` is: + // - + ... + - + 0 0 ... 0 0 + // <---- search in this direction -------- + // Then `helper` should be: + // F F ... F F T F F ... F F + let helper = { + let cs = self.cs().or(other.cs()); + let mut helper = vec![false; len]; + for i in (0..len).rev() { + let delta = delta[i].value().unwrap_or_default().into_bigint(); + if !delta.is_zero() && delta < F::MODULUS_MINUS_ONE_DIV_TWO { + helper[i] = true; + break; + } + } + Vec::>::new_variable_with_inferred_mode(cs, || Ok(helper))? + }; + + // `p` is the first positive limb in `delta`. + let mut p = FpVar::::zero(); + // `r` is the sum of all bits in `helper`, which should be 1 when `self` + // is less than `other`, as there should be more than one positive limb + // in `delta`, and thus exactly one true bit in `helper`. + let mut r = FpVar::zero(); + for (b, d) in helper.into_iter().zip(delta) { + // Choose the limb `d` only if `b` is true. + p += b.select(&d, &FpVar::zero())?; + // Either `r` or `d` should be zero. + // Consider the same example as above: + // - + ... + - + 0 0 ... 0 0 + // F F ... F F T F F ... F F + // |-----------| + // `r = 0` in this range (before/when we meet the first positive limb) + // |---------| + // `d = 0` in this range (after we meet the first positive limb) + // This guarantees that for every bit after the true bit in `helper`, + // the corresponding limb in `delta` is zero. + (&r * &d).enforce_equal(&FpVar::zero())?; + // Add the current bit to `r`. + r += FpVar::from(b); + } + + // Ensure that `r` is exactly 1. This guarantees that there is exactly + // one true value in `helper`. + r.enforce_equal(&FpVar::one())?; + // Ensure that `p` is positive, i.e., + // `0 <= p - 1 < 2^bits_per_limb < F::MODULUS_MINUS_ONE_DIV_TWO`. + // This guarantees that the true value in `helper` corresponds to a + // positive limb in `delta`. + Self::enforce_bit_length(&(p - FpVar::one()), F::BITS_PER_LIMB)?; + + Ok(()) + } + + /// Enforce `self` to be equal to `other`, where `self` and `other` are not + /// necessarily aligned. + /// + /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L562-L798 + /// Similar implementations can also be found in https://github.com/alex-ozdemir/bellman-bignat/blob/0585b9d90154603a244cba0ac80b9aafe1d57470/src/mp/bignat.rs#L566-L661 + /// and https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/emulated_fp/reduce.rs#L201-L323 + pub fn enforce_equal_unaligned(&self, other: &Self) -> Result<(), SynthesisError> { + let len = min(self.limbs.len(), other.limbs.len()); + + // Group the limbs of `self` and `other` so that each group nearly + // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. + // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` + // is the initial number of bits in a limb, just as what we do in grade + // school arithmetic, e.g., + // 5 9 + // x 7 3 + // ------------- + // 15 27 + // 35 63 + // ------------- <- When grouping 35, 15 + 63, and 27, we are computing + // 4 3 0 7 35 * 100 + (15 + 63) * 10 + 27 = 4307 + // Note that this is different from the concatenation `x_0 || x_1 ...`, + // since the bit-length of each limb is not necessarily the initial size + // `W`. + + let mut i = 0; + // `c` stores the current carry of `x_i - y_i` + let mut c = FpVar::::zero(); + while i < len { + let mut j = i; + // The current grouped limbs of `self` and `other`. + let mut p_limb = FpVar::zero(); + let mut q_limb = FpVar::zero(); + let mut p_bound = Bound::zero(); + let mut q_bound = Bound::zero(); + let mut step = 0; + let mut weight = F::one(); + while j < len { + match ( + self.bounds[j].shl(step).add(&p_bound).filter_safe::(), + other.bounds[j].shl(step).add(&q_bound).filter_safe::(), + ) { + (Some(new_p_bound), Some(new_q_bound)) => { + p_limb += &self.limbs[j] * weight; + q_limb += &other.limbs[j] * weight; + p_bound = new_p_bound; + q_bound = new_q_bound; + } + _ => break, + } + + j += 1; + step += F::BITS_PER_LIMB; + weight *= F::from(BigUint::one() << F::BITS_PER_LIMB); + } + // For each group, check the last `step_i` bits of `x_i` and `y_i` are + // equal. + // The intuition is to check `diff = x_i - y_i = 0 (mod 2^step_i)`. + // However, this is only true for `i = 0`, and we need to consider carry + // values `diff >> step_i` for `i > 0`. + // Therefore, we actually check `diff = x_i - y_i + c = 0 (mod 2^step_i)` + // and derive the next `c` by computing `diff >> step_i`. + // To enforce `diff = 0 (mod 2^step_i)`, we compute `diff / 2^step_i` + // and enforce it to be small (soundness holds because for `a` that does + // not divide `b`, `b / a` in the field will be very large). + c = (&p_limb - &q_limb + &c) * weight.inverse().unwrap(); + if j < len { + // Unlike the code mentioned above which add some offset to the + // diff `x_i - y_i + c` to make it always positive, we directly + // check if the absolute value of the diff is small. + Self::enforce_abs_bit_length( + &c, + (max( + min(&p_bound.lb, &q_bound.lb).bits(), + max(&p_bound.ub, &q_bound.ub).bits(), + ) as usize) + .checked_sub(step) + .unwrap_or_default(), + )?; + } else { + let remaining_limbs = &(if j < self.limbs.len() { self } else { other }).limbs[j..]; + let remaining_bounds = + &(if j < self.bounds.len() { self } else { other }).bounds[j..]; + if remaining_limbs.is_empty() { + c.enforce_equal(&FpVar::zero())?; + } else { + // If there is any remaining limb, the first one should be the + // final carry (which will be checked later), and the following + // ones should be zero. + + // Enforce the remaining limbs to be zero. + // Instead of doing that one by one, we check if their sum is + // zero using a single constraint. + // This is sound, as the upper bounds of the limbs and their sum + // are guaranteed to be less than `F::MODULUS_MINUS_ONE_DIV_TWO` + // (i.e., all of them are "non-negative"), implying that all + // limbs should be zero to make the sum zero. + remaining_limbs[1..] + .iter() + .sum::>() + .enforce_equal(&FpVar::zero())?; + Bound::add_many(remaining_bounds) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + // For the final carry, we need to ensure that it equals the + // remaining limb `rest`. + c.enforce_equal(&remaining_limbs[0])?; + }; + } + // Start the next group + i = j; + } + + Ok(()) + } +} + +impl NonNativeUintVar { + fn enforce_bit_length(x: &FpVar, length: usize) -> Result>, SynthesisError> { + let cs = x.cs(); + + let bits = &x.value().unwrap_or_default().into_bigint().to_bits_le()[..length]; + let bits = if cs.is_none() { + Vec::new_constant(cs, bits)? + } else { + Vec::new_witness(cs, || Ok(bits))? + }; + + Boolean::le_bits_to_fp(&bits)?.enforce_equal(x)?; + + Ok(bits) + } + + fn enforce_abs_bit_length( + x: &FpVar, + length: usize, + ) -> Result>, SynthesisError> { + let cs = x.cs(); + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; + + let is_neg = Boolean::new_variable( + cs.clone(), + || Ok(x.value().unwrap_or_default().into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO), + mode, + )?; + let bits = Vec::new_variable( + cs.clone(), + || { + Ok({ + let x = x.value().unwrap_or_default(); + let mut bits = if is_neg.value().unwrap_or_default() { + -x + } else { + x + } + .into_bigint() + .to_bits_le(); + bits.resize(length, false); + bits + }) + }, + mode, + )?; + + // Below is equivalent to but more efficient than + // `Boolean::le_bits_to_fp(&bits)?.enforce_equal(&is_neg.select(&x.negate()?, &x)?)?` + // Note that this enforces: + // 1. The claimed absolute value `is_neg.select(&x.negate()?, &x)?` has + // exactly `length` bits. + // 2. `is_neg` is indeed the sign of `x`, i.e., `is_neg = false` when + // `0 <= x < (|F| - 1) / 2`, and `is_neg = true` when + // `(|F| - 1) / 2 <= x < F`, thus the claimed absolute value is + // correct. + // If `is_neg` is incorrect, then: + // a. `0 <= x < (|F| - 1) / 2`, but `is_neg = true`, then + // `is_neg.select(&x.negate()?, &x)?` returns `|F| - x`, + // which is greater than `(|F| - 1) / 2` and cannot fit in + // `length` bits (given that `length` is small). + // b. `(|F| - 1) / 2 <= x < F`, but `is_neg = false`, then + // `is_neg.select(&x.negate()?, &x)?` returns `x`, which is + // greater than `(|F| - 1) / 2` and cannot fit in `length` + // bits. + FpVar::from(is_neg).mul_equals(&x.double()?, &(x - Boolean::le_bits_to_fp(&bits)?))?; + + Ok(bits) + } + + /// Compute `self + other`, without aligning the limbs. + pub fn add_no_align(&self, other: &Self) -> Result { + let mut z = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; + let mut bounds = vec![Bound::zero(); z.len()]; + for (i, v) in self.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .add(&self.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + z[i] += v; + } + for (i, v) in other.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .add(&other.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + z[i] += v; + } + Ok(Self { + limbs: z, + bounds: bounds, + }) + } + + pub fn sub_no_align(&self, other: &Self) -> Result { + let mut z = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; + let mut bounds = vec![Bound::zero(); z.len()]; + for (i, v) in self.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .add(&self.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + z[i] += v; + } + for (i, v) in other.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .sub(&other.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + z[i] -= v; + } + Ok(Self { + limbs: z, + bounds: bounds, + }) + } + + /// Compute `self * other`, without aligning the limbs. + /// Implements the O(n) approach described in xJsnark, Section IV.B.1) + pub fn mul_no_align(&self, other: &Self) -> Result { + let len = self.limbs.len() + other.limbs.len() - 1; + if self.is_constant() || other.is_constant() { + // Use the naive approach for constant operands, which costs no + // constraints. + let bounds = (0..len) + .map(|i| { + let start = max(i + 1, other.bounds.len()) - other.bounds.len(); + let end = min(i + 1, self.bounds.len()); + Bound::add_many( + &(start..end) + .map(|j| self.bounds[j].mul(&other.bounds[i - j])) + .collect::>(), + ) + .filter_safe::() + }) + .collect::>>() + .ok_or(SynthesisError::Unsatisfiable)?; + + let z = (0..len) + .map(|i| { + let start = max(i + 1, other.limbs.len()) - other.limbs.len(); + let end = min(i + 1, self.limbs.len()); + (start..end) + .map(|j| &self.limbs[j] * &other.limbs[i - j]) + .sum() + }) + .collect(); + return Ok(Self { + limbs: z, + bounds: bounds, + }); + } + let cs = self.cs().or(other.cs()); + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; + + // Compute the result `z` outside the circuit and provide it as hints. + let (z, bounds) = { + let mut z = vec![F::zero(); len]; + let mut bounds = vec![Bound::zero(); len]; + for i in 0..self.limbs.len() { + for j in 0..other.limbs.len() { + z[i + j] += self.limbs[i].value().unwrap_or_default() + * other.limbs[j].value().unwrap_or_default(); + bounds[i + j] = bounds[i + j].add(&self.bounds[i].mul(&other.bounds[j])) + } + } + ( + Vec::new_variable(cs.clone(), || Ok(z), mode)?, + bounds + .into_iter() + .map(|b| b.filter_safe::()) + .collect::>() + .ok_or(SynthesisError::Unsatisfiable)?, + ) + }; + for c in 1..=len { + let c = F::from(c as u64); + let mut t = F::one(); + let mut c_powers = vec![]; + for _ in 0..len { + c_powers.push(t); + t *= c; + } + // `l = Σ self[i] c^i` + let l = self + .limbs + .iter() + .zip(&c_powers) + .map(|(v, t)| v * *t) + .sum::>(); + // `r = Σ other[i] c^i` + let r = other + .limbs + .iter() + .zip(&c_powers) + .map(|(v, t)| v * *t) + .sum::>(); + // `o = Σ z[i] c^i` + let o = z + .iter() + .zip(&c_powers) + .map(|(v, t)| v * *t) + .sum::>(); + // Enforce `o = l * r` + l.mul_equals(&r, &o)?; + } + + Ok(Self { + limbs: z, + bounds: bounds, + }) + } + + /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. + pub fn modulo(&self) -> Result { + let cs = self.cs(); + let m = BigInt::from_biguint(Sign::Plus, M::MODULUS.into()); + // Provide the quotient and remainder as hints + let q = Self::new_variable_with_inferred_mode(cs.clone(), || { + let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); + Ok((self.value()?.div_floor(&m), Bound::new(lb, ub))) + })?; + let r = Self::new_variable_with_inferred_mode(cs.clone(), || { + Ok((self.value()?.abs() % &m, Bound::new_ub(m.clone()))) + })?; + + let m = Self::constant(m)?; + + // Enforce `self = q * m + r` + q.mul_no_align(&m)? + .add_no_align(&r)? + .enforce_equal_unaligned(self)?; + // Enforce `r < m` (and `r >= 0` already holds) + r.enforce_lt(&m)?; + + Ok(r) + } + + /// Enforce that `self` is congruent to `other` modulo `M::MODULUS`. + pub fn enforce_congruent(&self, other: &Self) -> Result<(), SynthesisError> { + let cs = self.cs(); + let m = BigInt::from_biguint(Sign::Plus, M::MODULUS.into()); + // Provide the quotient as hint + let q = Self::new_variable_with_inferred_mode(cs.clone(), || { + let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); + Ok((self.value()?.div_floor(&m), Bound::new(lb, ub))) + })?; + + let m = Self::constant(m)?; + + // Enforce `self - other = q * m` + self.sub_no_align(other)? + .enforce_equal_unaligned(&q.mul_no_align(&m)?) + } +} + +impl EquivalenceGadget for NonNativeUintVar { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_congruent::(other) + } +} + +impl FromBitsGadget for NonNativeUintVar { + fn from_bits_le(bits: &[Boolean]) -> Result { + Ok(Self { + limbs: bits + .as_ref() + .chunks(F::BITS_PER_LIMB) + .map(Boolean::le_bits_to_fp) + .collect::>()?, + bounds: bits + .as_ref() + .chunks(F::BITS_PER_LIMB) + .map(|i| Bound::new_n_bits(i.len())) + .collect(), + }) + } +} + +impl ToBitsGadget for NonNativeUintVar { + fn to_bits_le(&self) -> Result>, SynthesisError> { + Ok(self + .limbs + .iter() + .map(|limb| limb.to_n_bits_le(F::BITS_PER_LIMB)) + .collect::, _>>()? + .concat()) + } +} + +impl AbsorbableGadget> for NonNativeUintVar { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; + + self.to_bits_le()? + .chunks(bits_per_limb) + .try_for_each(|i| Ok(dest.push(Boolean::le_bits_to_fp(i)?))) + } +} + +impl VectorGadget> for [NonNativeUintVar] { + fn add(&self, other: &Self) -> Result>, SynthesisError> { + self.iter() + .zip(other.iter()) + .map(|(x, y)| x.add_no_align(y)) + .collect() + } + + fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { + self.iter() + .zip(other.iter()) + .map(|(x, y)| x.mul_no_align(y)) + .collect() + } + + fn scale( + &self, + other: &NonNativeUintVar, + ) -> Result>, SynthesisError> { + self.iter().map(|x| x.mul_no_align(other)).collect() + } +} + +impl MatrixGadget> for SparseMatrixVar> { + fn mul_vector( + &self, + v: &impl Index>, + ) -> Result>, SynthesisError> { + self.0 + .iter() + .map(|row| { + let len = row + .iter() + .map(|(value, col_i)| value.limbs.len() + v[*col_i].limbs.len() - 1) + .max() + .unwrap_or(0); + // This is a combination of `mul_no_align` and `add_no_align` + // that results in more flattened `LinearCombination`s. + // Consequently, `ConstraintSystem::inline_all_lcs` costs less + // time, thus making trusted setup and proof generation faster. + let bounds = (0..len) + .map(|i| { + Bound::add_many( + &row.iter() + .flat_map(|(value, col_i)| { + let start = + max(i + 1, v[*col_i].bounds.len()) - v[*col_i].bounds.len(); + let end = min(i + 1, value.bounds.len()); + (start..end) + .map(|j| value.bounds[j].mul(&v[*col_i].bounds[i - j])) + }) + .collect::>(), + ) + .filter_safe::() + }) + .collect::>>() + .ok_or(SynthesisError::Unsatisfiable)?; + let v = (0..len) + .map(|i| { + row.iter() + .flat_map(|(value, col_i)| { + let start = + max(i + 1, v[*col_i].limbs.len()) - v[*col_i].limbs.len(); + let end = min(i + 1, value.limbs.len()); + (start..end).map(|j| &value.limbs[j] * &v[*col_i].limbs[i - j]) + }) + .sum() + }) + .collect(); + Ok(NonNativeUintVar { + limbs: v, + bounds: bounds, + }) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use std::error::Error; + + use ark_ff::Field; + use ark_pallas::{Fq, Fr}; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{test_rng, UniformRand}; + use num_bigint::RandBigInt; + + use super::*; + + #[test] + fn test_mul_biguint() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let size = 256; + + let rng = &mut test_rng(); + let a = rng.gen_biguint(size as u64); + let b = rng.gen_biguint(size as u64); + let ab = &a * &b; + let aab = &a * &ab; + let abb = &ab * &b; + + let a_var = + NonNativeUintVar::new_witness(cs.clone(), || Ok((a.into(), Bound::new_n_bits(size))))?; + let b_var = + NonNativeUintVar::new_witness(cs.clone(), || Ok((b.into(), Bound::new_n_bits(size))))?; + let ab_var = NonNativeUintVar::new_witness(cs.clone(), || { + Ok((ab.into(), Bound::new_n_bits(size * 2))) + })?; + let aab_var = NonNativeUintVar::new_witness(cs.clone(), || { + Ok((aab.into(), Bound::new_n_bits(size * 3))) + })?; + let abb_var = NonNativeUintVar::new_witness(cs.clone(), || { + Ok((abb.into(), Bound::new_n_bits(size * 3))) + })?; + + a_var + .mul_no_align(&b_var)? + .enforce_equal_unaligned(&ab_var)?; + a_var + .mul_no_align(&ab_var)? + .enforce_equal_unaligned(&aab_var)?; + ab_var + .mul_no_align(&b_var)? + .enforce_equal_unaligned(&abb_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_mul_fq() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let rng = &mut test_rng(); + let a = Fq::rand(rng); + let b = Fq::rand(rng); + let ab = a * b; + let aab = a * ab; + let abb = ab * b; + + let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; + let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(b))?; + let ab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(ab))?; + let aab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(aab))?; + let abb_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(abb))?; + + a_var + .mul_no_align(&b_var)? + .enforce_congruent::(&ab_var)?; + a_var + .mul_no_align(&ab_var)? + .enforce_congruent::(&aab_var)?; + ab_var + .mul_no_align(&b_var)? + .enforce_congruent::(&abb_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_pow() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let rng = &mut test_rng(); + + let a = Fq::rand(rng); + + let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; + + let mut r_var = a_var.clone(); + for _ in 0..16 { + r_var = r_var.mul_no_align(&r_var)?.modulo::()?; + } + r_var = r_var.mul_no_align(&a_var)?.modulo::()?; + assert_eq!( + BigInt::from_biguint(Sign::Plus, a.pow([65537u64]).into()), + r_var.value()? + ); + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_vec_vec_mul() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let len = 1000; + + let rng = &mut test_rng(); + let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); + let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); + let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); + + let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; + let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; + let c_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(c))?; + + let mut r_var = NonNativeUintVar::constant(BigUint::zero().into())?; + for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { + r_var = r_var.add_no_align(&a.mul_no_align(&b)?)?; + } + r_var.enforce_congruent::(&c_var)?; + println!("{}", cs.num_constraints()); + + assert!(cs.is_satisfied()?); + Ok(()) + } +} diff --git a/crates/primitives/src/algebra/field/nonnative2.rs b/crates/primitives/src/algebra/field/nonnative2.rs new file mode 100644 index 000000000..f16daf950 --- /dev/null +++ b/crates/primitives/src/algebra/field/nonnative2.rs @@ -0,0 +1,1159 @@ +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, + convert::ToBitsGadget, + fields::{fp::FpVar, FieldVar}, + prelude::EqGadget, + select::CondSelectGadget, + GR1CSVar, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{ + borrow::Borrow, + cmp::{max, min}, + marker::PhantomData, + ops::Index, +}; +use num_bigint::{BigInt, BigUint, Sign}; +use num_integer::Integer; +use num_traits::Signed; + +use crate::algebra::ops::bits::{FromBitsGadget, ToBitsGadgetExt}; +use crate::{ + algebra::{ + field::SonobeField, + ops::{ + eq::EquivalenceGadget, + matrix::{MatrixGadget, SparseMatrixVar}, + vector::VectorGadget, + }, + }, + transcripts::AbsorbableGadget, +}; + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Bound(pub BigInt, pub BigInt); + +impl Bound { + pub fn zero() -> Self { + Self::default() + } +} + +impl Bound { + pub fn add(&self, other: &Self) -> Self { + Self(&self.0 + &other.0, &self.1 + &other.1) + } + + pub fn sub(&self, other: &Self) -> Self { + Self(&self.0 - &other.1, &self.1 - &other.0) + } + + pub fn add_many(limbs: &[Self]) -> Self { + Self( + limbs.iter().map(|l| &l.0).sum(), + limbs.iter().map(|l| &l.1).sum(), + ) + } + + pub fn mul(&self, other: &Self) -> Self { + let ll = &self.0 * &other.0; + let lu = &self.0 * &other.1; + let ul = &self.1 * &other.0; + let uu = &self.1 * &other.1; + + Self( + min(min(&ll, &lu), min(&ul, &uu)).clone(), + max(max(&ll, &lu), max(&ul, &uu)).clone(), + ) + } + + pub fn shl(&self, shift: usize) -> Self { + Self(&self.0 << shift, &self.1 << shift) + } + + pub fn filter_safe(self) -> Option { + let limit = BigInt::from_biguint(Sign::Plus, F::MODULUS_MINUS_ONE_DIV_TWO.into()); + (self.0 >= -&limit && self.1 <= limit).then_some(self) + } +} + +#[derive(Clone)] +pub struct IntVarInner { + _cfg: PhantomData, + limbs: Vec>, + bounds: Vec, +} + +pub type BigIntVar = IntVarInner; +pub type NonNativeFieldVar = + IntVarInner; + +impl GR1CSVar for IntVarInner { + type Value = BigInt; + + fn cs(&self) -> ConstraintSystemRef { + self.limbs.cs() + } + + fn value(&self) -> Result { + let mut r = BigInt::zero(); + + for limb in self.limbs.value()?.into_iter().rev() { + r <<= F::BITS_PER_LIMB; + r += if limb.into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO { + BigInt::from_biguint(Sign::Minus, (-limb).into()) + } else { + BigInt::from_biguint(Sign::Plus, limb.into()) + }; + } + + Ok(r) + } +} + +impl IntVarInner { + fn new(limbs: Vec>, bounds: Vec) -> Self { + Self { + _cfg: PhantomData, + limbs, + bounds, + } + } + + fn ubound(&self) -> BigInt { + let mut r = BigInt::zero(); + + for i in self.bounds.iter().rev() { + r <<= F::BITS_PER_LIMB; + r += &i.1; + } + + r + } + + fn lbound(&self) -> BigInt { + let mut r = BigInt::zero(); + + for i in self.bounds.iter().rev() { + r <<= F::BITS_PER_LIMB; + r += &i.0; + } + + r + } +} + +impl IntVarInner { + /// Enforce `self` to be less than `other`, where `self` and `other` should + /// be aligned. + /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 + pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> { + let len = max(self.limbs.len(), other.limbs.len()); + let zero = FpVar::zero(); + + // Compute the difference between limbs of `other` and `self`. + // Denote a positive limb by `+`, a negative limb by `-`, a zero limb by + // `0`, and an unknown limb by `?`. + // Then, for `self < other`, `delta` should look like: + // ? ? ... ? ? + 0 0 ... 0 0 + let delta = (0..len) + .map(|i| { + let x = self.limbs.get(i).unwrap_or(&zero); + let y = other.limbs.get(i).unwrap_or(&zero); + y - x + }) + .collect::>(); + + // `helper` is a vector of booleans that indicates if the corresponding + // limb of `delta` is the first (searching from MSB) positive limb. + // For example, if `delta` is: + // - + ... + - + 0 0 ... 0 0 + // <---- search in this direction -------- + // Then `helper` should be: + // F F ... F F T F F ... F F + let helper = { + let cs = self.limbs.cs().or(other.limbs.cs()); + let mut helper = vec![false; len]; + for i in (0..len).rev() { + let delta = delta[i].value().unwrap_or_default().into_bigint(); + if !delta.is_zero() && delta < F::MODULUS_MINUS_ONE_DIV_TWO { + helper[i] = true; + break; + } + } + Vec::>::new_variable_with_inferred_mode(cs, || Ok(helper))? + }; + + // `p` is the first positive limb in `delta`. + let mut p = FpVar::::zero(); + // `r` is the sum of all bits in `helper`, which should be 1 when `self` + // is less than `other`, as there should be more than one positive limb + // in `delta`, and thus exactly one true bit in `helper`. + let mut r = FpVar::zero(); + for (b, d) in helper.into_iter().zip(delta) { + // Choose the limb `d` only if `b` is true. + p += b.select(&d, &FpVar::zero())?; + // Either `r` or `d` should be zero. + // Consider the same example as above: + // - + ... + - + 0 0 ... 0 0 + // F F ... F F T F F ... F F + // |-----------| + // `r = 0` in this range (before/when we meet the first positive limb) + // |---------| + // `d = 0` in this range (after we meet the first positive limb) + // This guarantees that for every bit after the true bit in `helper`, + // the corresponding limb in `delta` is zero. + (&r * &d).enforce_equal(&FpVar::zero())?; + // Add the current bit to `r`. + r += FpVar::from(b); + } + + // Ensure that `r` is exactly 1. This guarantees that there is exactly + // one true value in `helper`. + r.enforce_equal(&FpVar::one())?; + // Ensure that `p` is positive, i.e., + // `0 <= p - 1 < 2^bits_per_limb < F::MODULUS_MINUS_ONE_DIV_TWO`. + // This guarantees that the true value in `helper` corresponds to a + // positive limb in `delta`. + (p - FpVar::one()).enforce_bit_length(F::BITS_PER_LIMB)?; + + Ok(()) + } +} + +impl From> for IntVarInner { + fn from(v: IntVarInner) -> Self { + Self::new(v.limbs, v.bounds) + } +} + +impl IntVarInner { + /// Compute `self + other`, without aligning the limbs. + pub fn add_unaligned( + &self, + other: &IntVarInner, + ) -> Result, SynthesisError> { + let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; + let mut bounds = vec![Bound::zero(); limbs.len()]; + for (i, v) in self.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .add(&self.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + limbs[i] += v; + } + for (i, v) in other.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .add(&other.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + limbs[i] += v; + } + Ok(IntVarInner::new(limbs, bounds)) + } + + pub fn sub_unaligned( + &self, + other: &IntVarInner, + ) -> Result, SynthesisError> { + let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; + let mut bounds = vec![Bound::zero(); limbs.len()]; + for (i, v) in self.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .add(&self.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + limbs[i] += v; + } + for (i, v) in other.limbs.iter().enumerate() { + bounds[i] = bounds[i] + .sub(&other.bounds[i]) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + limbs[i] -= v; + } + Ok(IntVarInner::new(limbs, bounds)) + } + + /// Compute `self * other`, without aligning the limbs. + /// Implements the O(n) approach described in xJsnark, Section IV.B.1) + pub fn mul_unaligned( + &self, + other: &IntVarInner, + ) -> Result, SynthesisError> { + let len = self.limbs.len() + other.limbs.len() - 1; + if self.limbs.is_constant() || other.limbs.is_constant() { + // Use the naive approach for constant operands, which costs no + // constraints. + let bounds = (0..len) + .map(|i| { + let start = max(i + 1, other.bounds.len()) - other.bounds.len(); + let end = min(i + 1, self.bounds.len()); + Bound::add_many( + &(start..end) + .map(|j| self.bounds[j].mul(&other.bounds[i - j])) + .collect::>(), + ) + .filter_safe::() + }) + .collect::>>() + .ok_or(SynthesisError::Unsatisfiable)?; + + let limbs = (0..len) + .map(|i| { + let start = max(i + 1, other.limbs.len()) - other.limbs.len(); + let end = min(i + 1, self.limbs.len()); + (start..end) + .map(|j| &self.limbs[j] * &other.limbs[i - j]) + .sum() + }) + .collect(); + return Ok(IntVarInner::new(limbs, bounds)); + } + // Compute the product `limbs` outside the circuit and provide it as + // hints. + let (limbs, bounds) = { + let cs = self.limbs.cs().or(other.limbs.cs()); + let mut limbs = vec![F::zero(); len]; + let mut bounds = vec![Bound::zero(); len]; + for i in 0..self.limbs.len() { + for j in 0..other.limbs.len() { + limbs[i + j] += self.limbs[i].value().unwrap_or_default() + * other.limbs[j].value().unwrap_or_default(); + bounds[i + j] = bounds[i + j].add(&self.bounds[i].mul(&other.bounds[j])) + } + } + ( + Vec::new_variable_with_inferred_mode(cs, || Ok(limbs))?, + bounds + .into_iter() + .map(|b| b.filter_safe::()) + .collect::>() + .ok_or(SynthesisError::Unsatisfiable)?, + ) + }; + for c in 1..=len { + let c = F::from(c as u64); + let mut t = F::one(); + let mut c_powers = vec![]; + for _ in 0..len { + c_powers.push(t); + t *= c; + } + // `l = Σ self[i] c^i` + let l = self + .limbs + .iter() + .zip(&c_powers) + .map(|(v, t)| v * *t) + .sum::>(); + // `r = Σ other[i] c^i` + let r = other + .limbs + .iter() + .zip(&c_powers) + .map(|(v, t)| v * *t) + .sum::>(); + // `o = Σ z[i] c^i` + let o = limbs + .iter() + .zip(&c_powers) + .map(|(v, t)| v * *t) + .sum::>(); + // Enforce `o = l * r` + l.mul_equals(&r, &o)?; + } + + Ok(IntVarInner::new(limbs, bounds)) + } + + /// Enforce `self` to be equal to `other`, where `self` and `other` are not + /// necessarily aligned. + /// + /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L562-L798 + /// Similar implementations can also be found in https://github.com/alex-ozdemir/bellman-bignat/blob/0585b9d90154603a244cba0ac80b9aafe1d57470/src/mp/bignat.rs#L566-L661 + /// and https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/emulated_fp/reduce.rs#L201-L323 + pub fn enforce_equal_unaligned( + &self, + other: &IntVarInner, + ) -> Result<(), SynthesisError> { + let len = min(self.limbs.len(), other.limbs.len()); + + // Group the limbs of `self` and `other` so that each group nearly + // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. + // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` + // is the initial number of bits in a limb, just as what we do in grade + // school arithmetic, e.g., + // 5 9 + // x 7 3 + // ------------- + // 15 27 + // 35 63 + // ------------- <- When grouping 35, 15 + 63, and 27, we are computing + // 4 3 0 7 35 * 100 + (15 + 63) * 10 + 27 = 4307 + // Note that this is different from the concatenation `x_0 || x_1 ...`, + // since the bit-length of each limb is not necessarily the initial size + // `W`. + + let mut i = 0; + let mut diff = FpVar::zero(); + let mut x_bound = Bound::zero(); + let mut y_bound = Bound::zero(); + let mut step = 0; + let inv = F::from(BigUint::one() << F::BITS_PER_LIMB) + .inverse() + .unwrap(); + + while i < len { + if let (Some(new_x_bound), Some(new_y_bound)) = ( + self.bounds[i].shl(step).add(&x_bound).filter_safe::(), + other.bounds[i].shl(step).add(&y_bound).filter_safe::(), + ) { + diff = (diff + &self.limbs[i] - &other.limbs[i]) * inv; + x_bound = new_x_bound; + y_bound = new_y_bound; + + i += 1; + step += F::BITS_PER_LIMB; + continue; + } + // For each group, check the last `step_i` bits of `x_i` and `y_i` are + // equal. + // The intuition is to check `diff = x_i - y_i = 0 (mod 2^step_i)`. + // However, this is only true for `i = 0`, and we need to consider carry + // values `diff >> step_i` for `i > 0`. + // Therefore, we actually check `diff = x_i - y_i + c = 0 (mod 2^step_i)` + // and derive the next `c` by computing `diff >> step_i`. + // To enforce `diff = 0 (mod 2^step_i)`, we compute `diff / 2^step_i` + // and enforce it to be small (soundness holds because for `a` that does + // not divide `b`, `b / a` in the field will be very large). + let bits = (max( + min(&x_bound.0, &y_bound.0).bits(), + max(&x_bound.1, &y_bound.1).bits(), + ) as usize) + .checked_sub(step) + .unwrap_or_default(); + + (&diff + F::from(BigUint::one() << bits)).enforce_bit_length(bits + 1)?; + + x_bound = Bound::zero(); + y_bound = Bound::zero(); + step = 0; + } + + let remaining_limbs = if i < self.limbs.len() { + &self.limbs[i..] + } else { + &other.limbs[i..] + }; + let remaining_bounds = if i < self.bounds.len() { + &self.bounds[i..] + } else { + &other.bounds[i..] + }; + if remaining_limbs.is_empty() { + diff.enforce_equal(&FpVar::zero())?; + } else { + // If there is any remaining limb, the first one should be the + // final carry (which will be checked later), and the following + // ones should be zero. + + // Enforce the remaining limbs to be zero. + // Instead of doing that one by one, we check if their sum is + // zero using a single constraint. + // This is sound, as the upper bounds of the limbs and their sum + // are guaranteed to be less than `F::MODULUS_MINUS_ONE_DIV_TWO` + // (i.e., all of them are "non-negative"), implying that all + // limbs should be zero to make the sum zero. + remaining_limbs[1..] + .iter() + .sum::>() + .enforce_equal(&FpVar::zero())?; + Bound::add_many(remaining_bounds) + .filter_safe::() + .ok_or(SynthesisError::Unsatisfiable)?; + // For the final carry, we need to ensure that it equals the + // remaining limb `rest`. + diff.enforce_equal(&remaining_limbs[0])?; + } + + Ok(()) + } +} + +impl + IntVarInner +{ + /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. + pub fn modulo(&self) -> Result, SynthesisError> { + let cs = self.cs(); + let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into()); + // Provide the quotient and remainder as hints + let q = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { + let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); + Ok((self.value()?.div_floor(&m), Bound(lb, ub))) + })?; + let r = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { + Ok((self.value()?.abs() % &m, Bound(Zero::zero(), m.clone()))) + })?; + + let m = IntVarInner::constant(m); + + // Enforce `self = q * m + r` + q.mul_unaligned(&m)? + .add_unaligned(&r)? + .enforce_equal_unaligned(self)?; + // Enforce `r < m` (and `r >= 0` already holds) + r.enforce_lt(&m)?; + + Ok(r) + } + + /// Enforce that `self` is congruent to `other` modulo `M::MODULUS`. + pub fn enforce_congruent( + &self, + other: &IntVarInner, + ) -> Result<(), SynthesisError> { + let cs = self.cs(); + let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into()); + // Provide the quotient as hint + let q = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { + let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); + Ok((self.value()?.div_floor(&m), Bound(lb, ub))) + })?; + + let m = IntVarInner::constant(m); + + // Enforce `self - other = q * m` + self.sub_unaligned(other)? + .enforce_equal_unaligned(&q.mul_unaligned(&m)?) + } +} + +impl TryFrom> + for IntVarInner +{ + type Error = SynthesisError; + + fn try_from(v: IntVarInner) -> Result { + v.modulo() + } +} + +// pub enum NonNativeUintVar { +// Aligned(UintVarInner), +// Unaligned(UintVarInner), +// } + +impl FromBitsGadget for IntVarInner { + fn from_bits_le(bits: &[Boolean]) -> Result { + Ok(Self::new( + bits.chunks(F::BITS_PER_LIMB) + .map(Boolean::le_bits_to_fp) + .collect::>()?, + bits.chunks(F::BITS_PER_LIMB) + .map(|i| Bound(BigInt::zero(), (BigInt::one() << i.len()) - BigInt::one())) + .collect(), + )) + } +} + +impl ToBitsGadget for IntVarInner { + fn to_bits_le(&self) -> Result>, SynthesisError> { + for bound in &self.bounds { + assert!(bound.0 >= BigInt::zero()); + } + Ok(self + .limbs + .iter() + .zip(&self.bounds) + .map(|(limb, bound)| limb.to_n_bits_le(bound.1.bits() as usize)) + .collect::, _>>()? + .concat()) + } +} + +impl AbsorbableGadget> for IntVarInner { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; + + self.to_bits_le()? + .chunks(bits_per_limb) + .try_for_each(|i| Ok(dest.push(Boolean::le_bits_to_fp(i)?))) + } +} + + +impl VectorGadget> for [IntVarInner] { + fn add(&self, other: &Self) -> Result>, SynthesisError> { + self.iter() + .zip(other.iter()) + .map(|(x, y)| x.add_unaligned(y)) + .collect() + } + + fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { + self.iter() + .zip(other.iter()) + .map(|(x, y)| x.mul_unaligned(y)) + .collect() + } + + fn scale( + &self, + other: &IntVarInner, + ) -> Result>, SynthesisError> { + self.iter().map(|x| x.mul_unaligned(other)).collect() + } +} + +impl MatrixGadget> for SparseMatrixVar> { + fn mul_vector( + &self, + v: &impl Index>, + ) -> Result>, SynthesisError> { + self.0 + .iter() + .map(|row| { + let len = row + .iter() + .map(|(value, col_i)| value.limbs.len() + v[*col_i].limbs.len() - 1) + .max() + .unwrap_or(0); + // This is a combination of `mul_no_align` and `add_no_align` + // that results in more flattened `LinearCombination`s. + // Consequently, `ConstraintSystem::inline_all_lcs` costs less + // time, thus making trusted setup and proof generation faster. + let bounds = (0..len) + .map(|i| { + Bound::add_many( + &row.iter() + .flat_map(|(value, col_i)| { + let start = + max(i + 1, v[*col_i].bounds.len()) - v[*col_i].bounds.len(); + let end = min(i + 1, value.bounds.len()); + (start..end) + .map(|j| value.bounds[j].mul(&v[*col_i].bounds[i - j])) + }) + .collect::>(), + ) + .filter_safe::() + }) + .collect::>>() + .ok_or(SynthesisError::Unsatisfiable)?; + let limbs = (0..len) + .map(|i| { + row.iter() + .flat_map(|(value, col_i)| { + let start = + max(i + 1, v[*col_i].limbs.len()) - v[*col_i].limbs.len(); + let end = min(i + 1, value.limbs.len()); + (start..end).map(|j| &value.limbs[j] * &v[*col_i].limbs[i - j]) + }) + .sum() + }) + .collect(); + Ok(IntVarInner::new(limbs, bounds)) + }) + .collect() + } +} + +impl AllocVar<(BigInt, Bound), F> for IntVarInner { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let (x, Bound(lb, ub)) = v.borrow(); + + if x < lb || x > ub { + return Err(SynthesisError::Unsatisfiable); + } + + let len = max(lb.bits(), ub.bits()) as usize; + + let x_is_neg = x.is_negative(); + let mut x_bits = x + .magnitude() + .to_radix_le(2) + .into_iter() + .map(|i| i == 1) + .collect::>(); + x_bits.resize(len, false); + + let x_is_neg = if !lb.is_negative() { + Boolean::FALSE + } else if !ub.is_positive() { + Boolean::TRUE + } else { + Boolean::new_variable(cs.clone(), || Ok(x_is_neg), mode)? + }; + let x_bits = Vec::new_variable(cs, || Ok(x_bits), mode)?; + + let (n_full_limbs, n_remaining_bits) = len.div_rem(&F::BITS_PER_LIMB); + + let limbs = x_bits + .chunks(F::BITS_PER_LIMB) + .map(|chunk| { + let limb_abs = Boolean::le_bits_to_fp(chunk)?; + x_is_neg.select(&limb_abs.negate()?, &limb_abs) + }) + .collect::>()?; + + let mut bounds = vec![ + Bound( + if lb.is_negative() { + BigInt::one() - (BigInt::one() << F::BITS_PER_LIMB) + } else { + BigInt::zero() + }, + if ub.is_positive() { + (BigInt::one() << F::BITS_PER_LIMB) - BigInt::one() + } else { + BigInt::zero() + }, + ); + n_full_limbs + ]; + + if !n_remaining_bits.is_zero() { + let d = BigInt::one() << (len - n_remaining_bits); + bounds.push(Bound(lb.div_floor(&d), ub.div_ceil(&d))); + } + + let var = Self::new(limbs, bounds); + + // At this point, we are confident that: + // * If `lb >= 0`, then `0 <= var <= 2^len - 1`. + // * If `ub <= 0`, then `-2^len + 1 <= var <= 0`. + // * Otherwise, `-2^len + 1 <= var <= 2^len - 1`. + // + // However, for soundness, we need to enforce `lb <= var <= ub`, which + // is already guaranteed only if: + // * `lb = 0` and `ub = 2^len - 1` + // * `lb = -2^len + 1` and `ub = 0` + // * `lb = -2^len + 1` and `ub = 2^len - 1` + // + // For other cases, we additionally check: + // * `var <= ub` + // * `var >= lb` + if lb.is_zero() && ub + BigInt::one() == BigInt::one() << len { + } else if BigInt::one() - lb == BigInt::one() << len && ub.is_zero() { + } else if BigInt::one() - lb == BigInt::one() << len + && ub + BigInt::one() == BigInt::one() << len + { + } else { + var.enforce_lt(&Self::constant(ub + BigInt::one()))?; + Self::constant(lb - BigInt::one()).enforce_lt(&var)?; + } + + Ok(var) + } + + fn new_constant( + _cs: impl Into>, + t: impl Borrow<(BigInt, Bound)>, + ) -> Result { + let (x, Bound(lb, ub)) = t.borrow(); + + if x < lb || x > ub { + return Err(SynthesisError::Unsatisfiable); + } + + // Ignore `lb` and `ub` from now on, as a constant `x` will be bounded + // by itself. + let bits = x + .magnitude() + .to_radix_le(2) + .into_iter() + .map(|i| i == 1) + .collect::>(); + + let (limbs, bounds) = bits + .chunks(F::BITS_PER_LIMB) + .map(F::BigInt::from_bits_le) + .map(|v| { + let v_field = if x.is_negative() { + -F::from(v) + } else { + F::from(v) + }; + let v_bigint = BigInt::from_biguint(x.sign(), v.into()); + (FpVar::constant(v_field), Bound(v_bigint.clone(), v_bigint)) + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + + Ok(Self::new(limbs, bounds)) + } +} + +impl AllocVar for IntVarInner { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + Self::new_variable( + cs, + || { + f().map(|v| { + ( + BigInt::from_biguint(Sign::Plus, (*v.borrow()).into()), + Bound(Zero::zero(), G::MODULUS.into().into()), + ) + }) + }, + mode, + ) + } +} + +impl IntVarInner { + fn constant(x: BigInt) -> Self { + Self::new_constant(ConstraintSystemRef::None, (x.clone(), Bound(x.clone(), x))).unwrap() + } +} + +macro_rules! impl_binary_op { + ( + $trait: ident, + $fn: ident, + |$lhs_i:tt : &$lhs:ty, $rhs_i:tt : &$rhs:ty| -> $out:ty $body:block, + ($($params:tt)+), + ) => { + impl<$($params)+> core::ops::$trait<&$rhs> for &$lhs + { + type Output = $out; + + fn $fn(self, other: &$rhs) -> Self::Output { + let $lhs_i = self; + let $rhs_i = other; + $body + } + } + + impl<$($params)+> core::ops::$trait<$rhs> for &$lhs + { + type Output = $out; + + fn $fn(self, other: $rhs) -> Self::Output { + core::ops::$trait::$fn(self, &other) + } + } + + impl<$($params)+> core::ops::$trait<&$rhs> for $lhs + { + type Output = $out; + + fn $fn(self, other: &$rhs) -> Self::Output { + core::ops::$trait::$fn(&self, other) + } + } + + impl<$($params)+> core::ops::$trait<$rhs> for $lhs + { + type Output = $out; + + fn $fn(self, other: $rhs) -> Self::Output { + core::ops::$trait::$fn(&self, &other) + } + } + } +} + +macro_rules! impl_assignment_op { + ( + $assign_trait: ident, + $assign_fn: ident, + |$lhs_i:tt : &mut $lhs:ty, $rhs_i:tt : &$rhs:ty| $body:block, + ($($params:tt)+), + ) => { + impl<$($params)+> core::ops::$assign_trait<$rhs> for $lhs + { + fn $assign_fn(&mut self, other: $rhs) { + core::ops::$assign_trait::$assign_fn(self, &other) + } + } + + impl<$($params)+> core::ops::$assign_trait<&$rhs> for $lhs + { + fn $assign_fn(&mut self, other: &$rhs) { + let $lhs_i = self; + let $rhs_i = other; + $body + } + } + } +} + +impl_binary_op!( + Add, + add, + |a: &IntVarInner, b: &IntVarInner| -> IntVarInner { + a.add_unaligned(b).unwrap() + }, + (F: SonobeField, Cfg, const LHS_ALIGNED: bool, const RHS_ALIGNED: bool), +); + +impl_assignment_op!( + AddAssign, + add_assign, + |a: &mut IntVarInner, b: &IntVarInner| { + *a = a.add_unaligned(b).unwrap() + }, + (F: SonobeField, Cfg, const ALIGNED: bool), +); + +impl_binary_op!( + Sub, + sub, + |a: &IntVarInner, b: &IntVarInner| -> IntVarInner { + a.sub_unaligned(b).unwrap() + }, + (F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool), +); + +impl_assignment_op!( + SubAssign, + sub_assign, + |a: &mut IntVarInner, b: &IntVarInner| { + *a = a.sub_unaligned(b).unwrap() + }, + (F: SonobeField, Cfg, const OTHER_ALIGNED: bool), +); + +impl_binary_op!( + Mul, + mul, + |a: &IntVarInner, b: &IntVarInner| -> IntVarInner { + a.mul_unaligned(b).unwrap() + }, + (F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool), +); + +impl_assignment_op!( + MulAssign, + mul_assign, + |a: &mut IntVarInner, b: &IntVarInner| { + *a = a.mul_unaligned(b).unwrap() + }, + (F: SonobeField, Cfg, const OTHER_ALIGNED: bool), +); + +#[cfg(test)] +mod tests { + use std::error::Error; + + use ark_ff::Field; + use ark_pallas::{Fq, Fr}; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{test_rng, UniformRand}; + use num_bigint::RandBigInt; + + use super::*; + + #[test] + fn test_alloc() -> Result<(), Box> { + let rng = &mut test_rng(); + + let size = 1024; + let mut lbs = vec![BigInt::zero()]; + let mut ubs: Vec = vec![(BigInt::one() << size) - BigInt::one()]; + lbs.push(-ubs[0].clone()); + ubs.push(BigInt::zero()); + lbs.push(-ubs[0].clone()); + ubs.push(ubs[0].clone()); + lbs.push(rng.gen_bigint_range(&-&ubs[0], &BigInt::zero())); + ubs.push(BigInt::zero()); + lbs.push(BigInt::zero()); + ubs.push(rng.gen_bigint_range(&BigInt::zero(), &ubs[0])); + lbs.push(rng.gen_bigint_range(&-&ubs[0], &BigInt::zero())); + ubs.push(rng.gen_bigint_range(&BigInt::zero(), &ubs[0])); + lbs.push(rng.gen_bigint_range(&-&ubs[0], &BigInt::zero())); + ubs.push(rng.gen_bigint_range(lbs.last().unwrap(), &BigInt::zero())); + lbs.push(rng.gen_bigint_range(&BigInt::zero(), &ubs[0])); + ubs.push(rng.gen_bigint_range(lbs.last().unwrap(), &ubs[0])); + + for (lb, ub) in lbs.into_iter().zip(ubs.into_iter()) { + let mut v = vec![ + lb.clone(), + ub.clone(), + &lb + BigInt::one(), + &ub - BigInt::one(), + ]; + if BigInt::zero() >= lb && BigInt::zero() <= ub { + v.push(BigInt::zero()); + } + for _ in 0..10 { + v.push(rng.gen_bigint_range(&lb, &ub)); + } + for a in v { + let cs = ConstraintSystem::::new_ref(); + + let a_var = BigIntVar::new_witness(cs.clone(), || { + Ok((a.clone(), Bound(lb.clone(), ub.clone()))) + })?; + + let a_const = BigIntVar::::constant(a.clone()); + + assert_eq!(a, a_var.value()?); + assert_eq!(a, a_const.value()?); + assert!(cs.is_satisfied()?); + } + } + + Ok(()) + } + + #[test] + fn test_mul_bigint() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let size = 2048; + + let rng = &mut test_rng(); + let a = rng.gen_bigint(size as u64); + let b = rng.gen_bigint(size as u64); + let ab = &a * &b; + let aab = &a * &ab; + let abb = &ab * &b; + + let a_var = BigIntVar::new_witness(cs.clone(), || { + Ok(( + a, + Bound( + BigInt::one() - (BigInt::one() << size), + (BigInt::one() << size) - BigInt::one(), + ), + )) + })?; + let b_var = BigIntVar::new_witness(cs.clone(), || { + Ok(( + b, + Bound( + BigInt::one() - (BigInt::one() << size), + (BigInt::one() << size) - BigInt::one(), + ), + )) + })?; + let ab_var = BigIntVar::new_witness(cs.clone(), || { + Ok(( + ab, + Bound( + BigInt::one() - (BigInt::one() << (size * 2)), + (BigInt::one() << (size * 2)) - BigInt::one(), + ), + )) + })?; + let aab_var = BigIntVar::new_witness(cs.clone(), || { + Ok(( + aab, + Bound( + BigInt::one() - (BigInt::one() << (size * 3)), + (BigInt::one() << (size * 3)) - BigInt::one(), + ), + )) + })?; + let abb_var = BigIntVar::new_witness(cs.clone(), || { + Ok(( + abb, + Bound( + BigInt::one() - (BigInt::one() << (size * 3)), + (BigInt::one() << (size * 3)) - BigInt::one(), + ), + )) + })?; + + a_var + .mul_unaligned(&b_var)? + .enforce_equal_unaligned(&ab_var)?; + a_var + .mul_unaligned(&ab_var)? + .enforce_equal_unaligned(&aab_var)?; + ab_var + .mul_unaligned(&b_var)? + .enforce_equal_unaligned(&abb_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_mul_fq() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let rng = &mut test_rng(); + let a = Fq::rand(rng); + let b = Fq::rand(rng); + let ab = a * b; + let aab = a * ab; + let abb = ab * b; + + let a_var = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(a))?; + let b_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(b))?; + let ab_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(ab))?; + let aab_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(aab))?; + let abb_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(abb))?; + + a_var.mul_unaligned(&b_var)?.enforce_congruent(&ab_var)?; + a_var.mul_unaligned(&ab_var)?.enforce_congruent(&aab_var)?; + ab_var.mul_unaligned(&b_var)?.enforce_congruent(&abb_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_pow() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let rng = &mut test_rng(); + + let a = Fq::rand(rng); + + let a_var = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(a))?; + + let mut r_var = a_var.clone(); + for _ in 0..16 { + r_var = r_var.mul_unaligned(&r_var)?.modulo()?; + } + r_var = r_var.mul_unaligned(&a_var)?.modulo()?; + assert_eq!( + BigInt::from_biguint(Sign::Plus, a.pow([65537u64]).into()), + r_var.value()? + ); + assert!(cs.is_satisfied()?); + Ok(()) + } + + #[test] + fn test_vec_vec_mul() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let len = 1000; + + let rng = &mut test_rng(); + let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); + let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); + let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); + + let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; + let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; + let c_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(c))?; + + let mut r_var: NonNativeFieldVar = + NonNativeFieldVar::constant(BigUint::zero().into()).into(); + for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { + r_var = r_var.add_unaligned(&a.mul_unaligned(&b)?)?; + } + r_var.enforce_congruent(&c_var)?; + + assert!(cs.is_satisfied()?); + Ok(()) + } +} diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs new file mode 100644 index 000000000..1f04e1472 --- /dev/null +++ b/crates/primitives/src/algebra/group/mod.rs @@ -0,0 +1,174 @@ +use ark_ec::{ + short_weierstrass::{Projective, SWCurveConfig}, + AffineRepr, CurveGroup, PrimeGroup, +}; +use ark_ff::{BigInteger, Field, One, PrimeField, Zero}; +use ark_r1cs_std::convert::ToBitsGadget; +use ark_r1cs_std::{ + alloc::AllocVar, + convert::ToConstraintFieldGadget, + fields::fp::FpVar, + groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, + prelude::Boolean, + GR1CSVar, +}; +use ark_relations::gr1cs::SynthesisError; +use ark_std::mem::swap; +use num_bigint::{BigInt, BigUint, Sign}; +use num_integer::Integer; + +use crate::algebra::field::nonnative2::IntVarInner; +use crate::{ + algebra::field::{ + nonnative::{Bound, NonNativeUintVar}, + SonobeField, + }, + traits::{Inputize, InputizeNonNative}, + transcripts::{Absorbable, AbsorbableGadget}, +}; + +pub mod nonnative; + +pub type CF1 = ::ScalarField; +pub type CF2 = <::BaseField as Field>::BasePrimeField; +pub type CI1 = <::ScalarField as PrimeField>::BigInt; +pub type CI2 = <<::BaseField as Field>::BasePrimeField as PrimeField>::BigInt; + +/// `Curve` trait is a wrapper around `CurveGroup` that also includes the +/// necessary bounds for the curve to be used conveniently in folding schemes. +pub trait SonobeCurve: + CurveGroup + + Absorbable + + Inputize + + InputizeNonNative +{ + /// The in-circuit variable type for this curve. + type Var: CurveVar; +} + +impl> SonobeCurve + for Projective

+{ + type Var = ProjectiveVar>; +} + +impl>> Absorbable for Projective

{ + fn absorb_into(&self, dest: &mut Vec) { + let affine = self.into_affine(); + let (x, y) = affine.xy().unwrap_or_default(); + [x, y].absorb_into(dest); + } +} + +impl> AbsorbableGadget> + for ProjectiveVar> +{ + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + let mut vec = self.to_constraint_field()?; + // The last element in the vector tells whether the point is infinity, + // but we can in fact avoid absorbing it without loss of soundness. + // This is because the `to_constraint_field` method internally invokes + // [`ProjectiveVar::to_afine`](https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/curves/short_weierstrass/mod.rs#L160-L195), + // which guarantees that an infinity point is represented as `(0, 0)`, + // but the y-coordinate of a non-infinity point is never 0 (for why, see + // https://crypto.stackexchange.com/a/108242 ). + vec.pop(); + dest.extend(vec); + Ok(()) + } +} + +impl> Inputize for Projective

{ + /// Returns the internal representation in the same order as how the value + /// is allocated in `ProjectiveVar::new_input`. + fn inputize(&self) -> Vec { + let affine = self.into_affine(); + match affine.xy() { + Some((x, y)) => vec![x, y, One::one()], + None => vec![Zero::zero(), One::one(), Zero::zero()], + } + } +} + +impl> + InputizeNonNative for Projective

+{ + /// Returns the internal representation in the same order as how the value + /// is allocated in `NonNativeAffineVar::new_input`. + fn inputize_nonnative(&self) -> Vec { + let affine = self.into_affine(); + let (x, y) = affine.xy().unwrap_or_default(); + + [x, y].inputize_nonnative() + } +} + +fn lattice_reduction_2x2( + mut b1: (BigInt, BigInt), + mut b2: (BigInt, BigInt), +) -> ((BigInt, BigInt), (BigInt, BigInt)) { + loop { + let mut b1_norm_sq = &b1.0 * &b1.0 + &b1.1 * &b1.1; + let mut b2_norm_sq = &b2.0 * &b2.0 + &b2.1 * &b2.1; + + if b1_norm_sq > b2_norm_sq { + swap(&mut b1, &mut b2); + swap(&mut b1_norm_sq, &mut b2_norm_sq); + } + + let (mut m, r) = (&b1.0 * &b2.0 + &b1.1 * &b2.1).div_rem(&b1_norm_sq); + if &r + &r >= b1_norm_sq { + m += BigInt::one(); + } + + if m.is_zero() { + break; + } + + b2.0 -= &m * &b1.0; + b2.1 -= &m * &b1.1; + } + + (b1, b2) +} + +pub trait PointScalarMulGadget: Sized { + fn mul_scalar(&self, scalar: &impl ToBitsGadget) -> Result; +} + +impl PointScalarMulGadget> for C { + fn mul_scalar(&self, scalar: &impl ToBitsGadget>) -> Result { + let scalar = scalar.to_bits_le()?; + + let cs = scalar.cs(); + + let m = BigInt::from_biguint(Sign::Plus, CF1::::MODULUS.into()); + let m_sqrt = m.sqrt(); + + let (a, b) = lattice_reduction_2x2( + (m, Zero::zero()), + ( + CI2::::from_bits_le(&scalar.value().unwrap_or_default()) + .into() + .into(), + One::one(), + ), + ) + .0; + let (a_sign, a_abs) = a.into_parts(); + let (b_sign, b_abs) = b.into_parts(); + let a_is_negative = + Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(a_sign == Sign::Minus))?; + let b_is_negative = + Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(b_sign == Sign::Minus))?; + + let a = NonNativeUintVar::new_variable_with_inferred_mode(cs.clone(), || { + Ok((a_abs.into(), Bound::new_ub(m_sqrt.clone()))) + })?; + let b = NonNativeUintVar::new_variable_with_inferred_mode(cs, || { + Ok((b_abs.into(), Bound::new_ub(m_sqrt))) + })?; + + todo!() + } +} diff --git a/crates/primitives/src/gadgets/nonnative/affine.rs b/crates/primitives/src/algebra/group/nonnative.rs similarity index 74% rename from crates/primitives/src/gadgets/nonnative/affine.rs rename to crates/primitives/src/algebra/group/nonnative.rs index f24bf8968..4663ba43d 100644 --- a/crates/primitives/src/gadgets/nonnative/affine.rs +++ b/crates/primitives/src/algebra/group/nonnative.rs @@ -1,5 +1,5 @@ use ark_ec::{short_weierstrass::SWFlags, AffineRepr}; -use ark_ff::PrimeField; +use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, eq::EqGadget, @@ -9,11 +9,12 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; -use ark_std::{borrow::Borrow, Zero}; +use ark_std::borrow::Borrow; -use crate::traits::{AbsorbNonNativeGadget, SonobeCurve}; - -use super::uint::NonNativeUintVar; +use crate::{ + algebra::{field::nonnative::NonNativeUintVar, group::SonobeCurve}, + transcripts::AbsorbableGadget, +}; /// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native /// field, over the constraint field. It is not intended to perform operations, but just to contain @@ -52,8 +53,8 @@ impl GR1CSVar for NonNativeAffineVar { } fn value(&self) -> Result { - let x = C::BaseField::from_le_bytes_mod_order(&self.x.value()?.to_bytes_le()); - let y = C::BaseField::from_le_bytes_mod_order(&self.y.value()?.to_bytes_le()); + let x = C::BaseField::from_le_bytes_mod_order(&self.x.value()?.magnitude().to_bytes_le()); + let y = C::BaseField::from_le_bytes_mod_order(&self.y.value()?.magnitude().to_bytes_le()); // Below is a workaround to convert the `x` and `y` coordinates to a // point. This is because the `SonobeCurve` trait does not provide a // method to construct a point from `BaseField` elements. @@ -83,47 +84,12 @@ impl GR1CSVar for NonNativeAffineVar { impl EqGadget for NonNativeAffineVar { fn is_eq(&self, other: &Self) -> Result, SynthesisError> { - let mut result = Boolean::TRUE; - if self.x.0.len() != other.x.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - if self.y.0.len() != other.y.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - for (l, r) in self - .x - .0 - .iter() - .chain(&self.y.0) - .zip(other.x.0.iter().chain(&other.y.0)) - { - if l.ub != r.ub { - return Err(SynthesisError::Unsatisfiable); - } - result &= l.v.is_eq(&r.v)?; - } - Ok(result) + Ok(self.x.is_eq(&other.x)? & self.y.is_eq(&other.y)?) } fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { - if self.x.0.len() != other.x.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - if self.y.0.len() != other.y.0.len() { - return Err(SynthesisError::Unsatisfiable); - } - for (l, r) in self - .x - .0 - .iter() - .chain(&self.y.0) - .zip(other.x.0.iter().chain(&other.y.0)) - { - if l.ub != r.ub { - return Err(SynthesisError::Unsatisfiable); - } - l.v.enforce_equal(&r.v)?; - } + self.x.enforce_equal(&other.x)?; + self.y.enforce_equal(&other.y)?; Ok(()) } } @@ -136,11 +102,9 @@ impl NonNativeAffineVar { } } -impl AbsorbNonNativeGadget for NonNativeAffineVar { - fn to_native_sponge_field_elements( - &self, - ) -> Result>, SynthesisError> { - [&self.x, &self.y].to_native_sponge_field_elements() +impl AbsorbableGadget> for NonNativeAffineVar { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + (&self.x, &self.y).absorb_into(dest) } } @@ -151,7 +115,10 @@ mod tests { use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, UniformRand}; - use crate::traits::{Absorbable, Inputize, InputizeNonNative}; + use crate::{ + traits::{Inputize, InputizeNonNative}, + transcripts::Absorbable, + }; use super::*; @@ -172,10 +139,7 @@ mod tests { let mut rng = ark_std::test_rng(); let p = Projective::rand(&mut rng); let p_var = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; - assert_eq!( - p_var.to_native_sponge_field_elements()?.value()?, - p.extract_absorbed() - ); + assert_eq!(p_var.to_absorbable()?.value()?, p.to_absorbable()); Ok(()) } @@ -188,7 +152,7 @@ mod tests { let cs = ConstraintSystem::::new_ref(); let p_var = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; assert_eq!( - [p_var.x.0.value()?, p_var.y.0.value()?].concat(), + [p_var.x.limbs.value()?, p_var.y.limbs.value()?].concat(), p.inputize_nonnative() ); diff --git a/crates/primitives/src/algebra/mod.rs b/crates/primitives/src/algebra/mod.rs new file mode 100644 index 000000000..82d6047fb --- /dev/null +++ b/crates/primitives/src/algebra/mod.rs @@ -0,0 +1,3 @@ +pub mod field; +pub mod group; +pub mod ops; diff --git a/crates/primitives/src/algebra/ops/bits.rs b/crates/primitives/src/algebra/ops/bits.rs new file mode 100644 index 000000000..1688a227c --- /dev/null +++ b/crates/primitives/src/algebra/ops/bits.rs @@ -0,0 +1,33 @@ +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, eq::EqGadget, fields::fp::FpVar, GR1CSVar}; +use ark_relations::gr1cs::SynthesisError; + +pub trait FromBitsGadget: Sized { + fn from_bits_le(bits: &[Boolean]) -> Result; +} + +pub trait ToBitsGadgetExt: Sized { + fn to_n_bits_le(&self, n: usize) -> Result>, SynthesisError>; + + fn enforce_bit_length(&self, n: usize) -> Result<(), SynthesisError> { + self.to_n_bits_le(n)?; + Ok(()) + } +} +impl FromBitsGadget for FpVar { + fn from_bits_le(bits: &[Boolean]) -> Result { + Boolean::le_bits_to_fp(bits) + } +} + +impl ToBitsGadgetExt for FpVar { + fn to_n_bits_le(&self, n: usize) -> Result>, SynthesisError> { + let mut bits = self.value().unwrap_or_default().into_bigint().to_bits_le(); + bits.resize(n, false); + let bits = Vec::new_variable_with_inferred_mode(self.cs(), || Ok(bits))?; + + Boolean::le_bits_to_fp(&bits)?.enforce_equal(self)?; + + Ok(bits) + } +} diff --git a/crates/primitives/src/gadgets/math/eq.rs b/crates/primitives/src/algebra/ops/eq.rs similarity index 100% rename from crates/primitives/src/gadgets/math/eq.rs rename to crates/primitives/src/algebra/ops/eq.rs diff --git a/crates/primitives/src/gadgets/math/matrix.rs b/crates/primitives/src/algebra/ops/matrix.rs similarity index 100% rename from crates/primitives/src/gadgets/math/matrix.rs rename to crates/primitives/src/algebra/ops/matrix.rs diff --git a/crates/primitives/src/gadgets/math/mod.rs b/crates/primitives/src/algebra/ops/mod.rs similarity index 61% rename from crates/primitives/src/gadgets/math/mod.rs rename to crates/primitives/src/algebra/ops/mod.rs index c4e275c5a..2646ce8c0 100644 --- a/crates/primitives/src/gadgets/math/mod.rs +++ b/crates/primitives/src/algebra/ops/mod.rs @@ -1,3 +1,5 @@ +pub mod bits; pub mod eq; pub mod matrix; +pub mod rlc; pub mod vector; diff --git a/crates/primitives/src/algebra/ops/rlc.rs b/crates/primitives/src/algebra/ops/rlc.rs new file mode 100644 index 000000000..3c94d01f6 --- /dev/null +++ b/crates/primitives/src/algebra/ops/rlc.rs @@ -0,0 +1,44 @@ +use ark_std::{ + iter::Sum, + ops::{Add, Mul}, +}; + +pub trait ScalarRLC { + type Value; + + fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value; +} + +impl ScalarRLC for I +where + I::Item: Add + Sum + for<'a> Mul<&'a Coeff, Output = I::Item>, +{ + type Value = I::Item; + + fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value { + self.zip(coeffs).map(|(v, c)| v * c).sum::() + } +} + +pub trait SliceRLC { + type Value; + + fn slice_rlc(self, coeffs: &[Coeff]) -> Vec; +} + +impl<'a, T: 'a, I: Iterator, Coeff> SliceRLC for I +where + T: Add + Copy, + for<'x> T: Mul<&'x Coeff, Output = T>, +{ + type Value = T; + + fn slice_rlc(self, coeffs: &[Coeff]) -> Vec { + let mut iter = self.zip(coeffs).map(|(v, c)| v.iter().map(|x| *x * c)); + let first = iter.next().unwrap(); + + iter.fold(first.collect(), |acc, v| { + acc.into_iter().zip(v).map(|(a, b)| a + b).collect() + }) + } +} diff --git a/crates/primitives/src/gadgets/math/vector.rs b/crates/primitives/src/algebra/ops/vector.rs similarity index 100% rename from crates/primitives/src/gadgets/math/vector.rs rename to crates/primitives/src/algebra/ops/vector.rs diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs index 60fec5c33..6877f48d9 100644 --- a/crates/primitives/src/arithmetizations/ccs/circuits.rs +++ b/crates/primitives/src/arithmetizations/ccs/circuits.rs @@ -6,7 +6,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::borrow::Borrow; -use crate::gadgets::math::matrix::SparseMatrixVar; +use crate::algebra::ops::matrix::SparseMatrixVar; use super::CCS; diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 8418f8a4b..9fa101602 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -1,7 +1,7 @@ use ark_ff::Field; use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::Matrix; -use ark_std::{cfg_into_iter, log2}; +use ark_std::{cfg_into_iter, cfg_iter, log2}; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -96,12 +96,11 @@ impl CCS { pub fn mle( &self, i: usize, - z: Assignments>, + z: Assignments + Sync>, ) -> DenseMultilinearExtension { DenseMultilinearExtension { num_vars: self.s, - evaluations: self.M[i] - .iter() + evaluations: cfg_iter!(self.M[i]) .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) .chain(vec![F::zero(); (1 << self.s) - self.m]) .collect(), diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index f31fd632f..afeec6272 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -4,13 +4,13 @@ use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData, One}; use crate::{ - arithmetizations::ArithRelationGadget, - circuits::Assignments, - gadgets::math::{ + algebra::ops::{ eq::EquivalenceGadget, matrix::{MatrixGadget, SparseMatrixVar}, vector::VectorGadget, }, + arithmetizations::ArithRelationGadget, + circuits::Assignments, }; use super::R1CS; diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 593ba7879..18da7f641 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,3 +1,6 @@ +use ark_ff::Field; +use ark_r1cs_std::alloc::AllocVar; +use ark_relations::gr1cs::SynthesisError; use ark_std::{ fmt::Debug, iter::Sum, @@ -12,7 +15,7 @@ pub mod pedersen; #[derive(Debug, Error)] pub enum Error { // Commitment errors - #[error("The message being committed to has length {1}, which exceeds the maximum supported length of {0}")] + #[error("The message being committed to has length {1}, exceeding the maximum supported length of {0}")] MessageTooLong(usize, usize), #[error("Blinding factor not 0 for Commitment without hiding")] BlindingNotZero, @@ -57,6 +60,77 @@ pub trait VectorCommitment: 'static + Debug + PartialEq { ) -> Result; } +pub trait VectorCommitmentGadget { + type Native: VectorCommitment; + + type KeyVar; + type ScalarVar: Clone + + Add + + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> + + Mul + + for<'a> Mul<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar>; + type IntermediateScalarVar: Clone + + TryInto + + Add + + for<'a> Add<&'a Self::IntermediateScalarVar, Output = Self::IntermediateScalarVar> + + Mul + + for<'a> Mul<&'a Self::IntermediateScalarVar, Output = Self::IntermediateScalarVar> + + Add + + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> + + Mul + + for<'a> Mul<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar>; + type CommitmentVar: Clone; + type RandomnessVar; + + fn open( + ck: &Self::KeyVar, + v: &[Self::ScalarVar], + r: &Self::RandomnessVar, + cm: &Self::CommitmentVar, + ) -> Result<(), SynthesisError>; +} + +#[derive(Clone, Copy, Default, Debug)] +pub struct Null; + +impl Add for Null { + type Output = Null; + + fn add(self, _: F) -> Null { + Null + } +} + +impl Add for &Null { + type Output = Null; + + fn add(self, _: F) -> Null { + Null + } +} + +impl Mul for Null { + type Output = Self; + + fn mul(self, _: F) -> Null { + Null + } +} + +impl Mul for &Null { + type Output = Null; + + fn mul(self, _: F) -> Null { + Null + } +} + +impl Sum for Null { + fn sum>(_: I) -> Self { + Null + } +} + #[cfg(test)] mod tests { use ark_ff::UniformRand; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 3d53d7a61..1716a1d53 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -1,9 +1,30 @@ -use ark_r1cs_std::{boolean::Boolean, convert::ToBitsGadget, groups::CurveVar}; +use ark_ec::{ + short_weierstrass::{Projective, SWCurveConfig}, + CurveGroup, +}; +use ark_ff::{AdditiveGroup, PrimeField}; +use ark_r1cs_std::{ + boolean::Boolean, + convert::ToBitsGadget, + eq::EqGadget, + fields::{fp::FpVar, FieldVar}, + groups::{ + curves::short_weierstrass::{non_zero_affine::NonZeroAffineVar, ProjectiveVar}, + CurveVar, + }, + GR1CSVar, +}; use ark_relations::gr1cs::SynthesisError; use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; use super::{Error, VectorCommitment}; -use crate::traits::{Null, SonobeCurve, CF2}; +use crate::{ + algebra::field::{nonnative::NonNativeUintVar, nonnative2::IntVarInner}, + commitments::{Null, VectorCommitmentGadget}, + traits::{CF2, SonobeCurve}, +}; +use crate::algebra::field::nonnative2::NonNativeFieldVar; +use crate::traits::CF1; #[derive(Debug, PartialEq)] pub struct Pedersen { @@ -88,21 +109,99 @@ impl VectorCommitment for Pedersen { } } -pub struct PedersenGadget { +pub struct PedersenGadget { _c: PhantomData, } +// fn joint_scalar_mul_be>( +// p: &Projective

, +// q: &Projective

, +// bits1: impl Iterator>, +// bits2: impl Iterator>, +// ) -> Result>, SynthesisError> { +// // prepare bits decomposition +// let mut bits1 = bits1.collect::>(); +// if bits1.len() == 0 { +// return Ok(ProjectiveVar::zero()); +// } +// // Remove unnecessary constant zeros in the most-significant positions. +// bits1 = bits1 +// .into_iter() +// // We iterate from the MSB down. +// .rev() +// // Skip leading zeros, if they are constants. +// .skip_while(|b| b.is_constant() && (b.value().unwrap() == false)) +// .collect(); + +// let mut bits2 = bits2.collect::>(); +// if bits2.len() == 0 { +// return Ok(ProjectiveVar::zero()); +// } +// // Remove unnecessary constant zeros in the most-significant positions. +// bits2 = bits2 +// .into_iter() +// // We iterate from the MSB down. +// .rev() +// // Skip leading zeros, if they are constants. +// .skip_while(|b| b.is_constant() && (b.value().unwrap() == false)) +// .collect(); + +// let acc = p.double().into_affine(); +// let sum = (p + q).into_affine(); +// let diff = (p - q).into_affine(); + +// let (sum_x, sum_y) = (FpVar::Constant(sum.x), FpVar::Constant(sum.y)); +// let (diff_x, diff_y) = (FpVar::Constant(diff.x), FpVar::Constant(diff.y)); +// let (mut x, mut y) = (FpVar::Constant(acc.x), FpVar::Constant(acc.y)); + +// // double-and-add loop +// for (bit1, bit2) in (bits1.iter().rev().skip(1).rev()).zip(bits2.iter().rev().skip(1).rev()) { +// let xor = *bit1 ^ *bit2; +// let xx = xor.select(&diff_x, &sum_x)?; +// let yy = xor.select(&diff_y, &sum_y)?; +// let yy = bit1.select(&yy, &yy.negate()?)?; + +// if [&x, &y].is_constant() || ([&xx, &yy].is_constant()) { +// let p = NonZeroAffineVar::new(x.clone(), y.clone()) +// .double()? +// .add_unchecked(&NonZeroAffineVar::new(xx, yy))?; +// x = p.x; +// y = p.y; +// } else { +// let lambda_1 = (&yy - &y).mul_by_inverse_unchecked(&(&xx - &x))?; +// let lambda_1_square = lambda_1.square()?; + +// let lambda_2 = y +// .mul_by_inverse_unchecked(&(&x.double()? + &xx - &lambda_1_square))? +// .double()? +// - lambda_1; + +// let x4 = lambda_2.square()? - lambda_1_square + &xx; +// let y4 = lambda_2 * &(&x - &x4) - &y; +// x = x4; +// y = y4; +// }; +// } + +// let mut acc = NonZeroAffineVar::new(x, y); +// // last bit +// aff1_neg = aff1_neg.add_unchecked(&acc)?; +// acc = bits1[bits1.len() - 1].select(&acc, &aff1_neg)?; +// aff2_neg = aff2_neg.add_unchecked(&acc)?; +// acc = bits2[bits1.len() - 1].select(&acc, &aff2_neg)?; + +// acc.into_projective().add_mixed(&{ +// let mut p = diff; +// for _ in 0..bits1.len() - 1 { +// p = p.double()?; +// } +// NonZeroAffineVar::new(p.x, p.y.negate()?) +// }) +// } + impl PedersenGadget { - pub fn commit( - h: &C::Var, - g: &[C::Var], - v: &[Vec>>], - r: &[Boolean>], - ) -> Result { + fn msm(g: &[C::Var], v: &[Vec>>]) -> Result { let mut res = C::Var::zero(); - if H { - res += h.scalar_mul_le(r.iter())?; - } let n = v.len(); if n % 2 == 1 { res += g[n - 1].scalar_mul_le(v[n - 1].to_bits_le()?.iter())?; @@ -124,6 +223,65 @@ impl PedersenGadget { } } +impl VectorCommitmentGadget for PedersenGadget { + type Native = Pedersen; + + type KeyVar = Vec; + + type ScalarVar = NonNativeFieldVar, CF1, true>; + + type IntermediateScalarVar = NonNativeFieldVar, CF1, false>; + + type CommitmentVar = C::Var; + + type RandomnessVar = (); + + fn open( + ck: &Self::KeyVar, + v: &[Self::ScalarVar], + _r: &Self::RandomnessVar, + cm: &Self::CommitmentVar, + ) -> Result<(), SynthesisError> { + Self::msm( + ck, + &v.iter() + .map(|i| i.to_bits_le()) + .collect::, _>>()?, + )? + .enforce_equal(cm) + } +} + +impl VectorCommitmentGadget for PedersenGadget { + type Native = Pedersen; + + type KeyVar = (Vec, C::Var); + + type ScalarVar = NonNativeFieldVar, CF1, true>; + + type IntermediateScalarVar = NonNativeFieldVar, CF1, false>; + + type CommitmentVar = C::Var; + + type RandomnessVar = NonNativeFieldVar, CF1, true>; + + fn open( + (g, h): &Self::KeyVar, + v: &[Self::ScalarVar], + r: &Self::RandomnessVar, + cm: &Self::CommitmentVar, + ) -> Result<(), SynthesisError> { + let gv = Self::msm( + g, + &v.iter() + .map(|i| i.to_bits_le()) + .collect::, _>>()?, + )?; + let hr = h.scalar_mul_le(r.to_bits_le()?.iter())?; + (gv + hr).enforce_equal(cm) + } +} + #[cfg(test)] mod tests { use ark_bn254::G1Projective; diff --git a/crates/primitives/src/gadgets/mod.rs b/crates/primitives/src/gadgets/mod.rs deleted file mode 100644 index 89346e75c..000000000 --- a/crates/primitives/src/gadgets/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod math; -pub mod nonnative; diff --git a/crates/primitives/src/gadgets/nonnative/mod.rs b/crates/primitives/src/gadgets/nonnative/mod.rs deleted file mode 100644 index 497b9870f..000000000 --- a/crates/primitives/src/gadgets/nonnative/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod affine; -pub mod uint; diff --git a/crates/primitives/src/gadgets/nonnative/uint.rs b/crates/primitives/src/gadgets/nonnative/uint.rs deleted file mode 100644 index fa0db0e69..000000000 --- a/crates/primitives/src/gadgets/nonnative/uint.rs +++ /dev/null @@ -1,993 +0,0 @@ -use ark_ff::{BigInteger, One, PrimeField, Zero}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - convert::ToBitsGadget, - fields::{fp::FpVar, FieldVar}, - prelude::EqGadget, - select::CondSelectGadget, - GR1CSVar, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{ - borrow::Borrow, - cmp::{max, min}, - ops::Index, -}; -use num_bigint::BigUint; -use num_integer::Integer; - -use crate::{ - gadgets::math::{ - eq::EquivalenceGadget, - matrix::{MatrixGadget, SparseMatrixVar}, - vector::VectorGadget, - }, - traits::{AbsorbNonNativeGadget, SonobeField}, -}; - -/// `LimbVar` represents a single limb of a non-native unsigned integer in the -/// circuit. -/// The limb value `v` should be small enough to fit into `FpVar`, and we also -/// store an upper bound `ub` for the limb value, which is treated as a constant -/// in the circuit and is used for efficient equality checks and some arithmetic -/// operations. -#[derive(Debug, Clone)] -pub struct LimbVar { - pub v: FpVar, - pub ub: BigUint, -} - -impl]>> From for LimbVar { - fn from(bits: B) -> Self { - Self { - // `Boolean::le_bits_to_fp` will return an error if the internal - // invocation of `Boolean::enforce_in_field_le` fails. - // However, this method is only called when the length of `bits` is - // greater than `F::MODULUS_BIT_SIZE`, which should not happen in - // our case where `bits` is guaranteed to be short. - v: Boolean::le_bits_to_fp(bits.as_ref()).unwrap(), - ub: (BigUint::one() << bits.as_ref().len()) - BigUint::one(), - } - } -} - -impl Default for LimbVar { - fn default() -> Self { - Self { - v: FpVar::zero(), - ub: BigUint::zero(), - } - } -} - -impl GR1CSVar for LimbVar { - type Value = F; - - fn cs(&self) -> ConstraintSystemRef { - self.v.cs() - } - - fn value(&self) -> Result { - self.v.value() - } -} - -impl CondSelectGadget for LimbVar { - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - // We only allow selecting between two values with the same upper bound - assert_eq!(true_value.ub, false_value.ub); - Ok(Self { - v: cond.select(&true_value.v, &false_value.v)?, - ub: true_value.ub.clone(), - }) - } -} - -impl LimbVar { - /// Add two `LimbVar`s. - /// Returns `None` if the upper bound of the sum is too large, i.e., - /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. - /// Otherwise, returns the sum as a `LimbVar`. - pub fn add(&self, other: &Self) -> Option { - let ubound = &self.ub + &other.ub; - if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { - Some(Self { - v: &self.v + &other.v, - ub: ubound, - }) - } else { - None - } - } - - /// Add multiple `LimbVar`s. - /// Returns `None` if the upper bound of the sum is too large, i.e., - /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. - /// Otherwise, returns the sum as a `LimbVar`. - pub fn add_many(limbs: &[Self]) -> Option { - let ubound = limbs.iter().map(|l| &l.ub).sum(); - if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { - Some(Self { - v: if limbs.is_constant() { - FpVar::constant(limbs.value().unwrap_or_default().into_iter().sum()) - } else { - limbs.iter().map(|l| &l.v).sum() - }, - ub: ubound, - }) - } else { - None - } - } - - /// Multiply two `LimbVar`s. - /// Returns `None` if the upper bound of the product is too large, i.e., - /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. - /// Otherwise, returns the product as a `LimbVar`. - pub fn mul(&self, other: &Self) -> Option { - let ubound = &self.ub * &other.ub; - if ubound < F::MODULUS_MINUS_ONE_DIV_TWO.into() { - Some(Self { - v: &self.v * &other.v, - ub: ubound, - }) - } else { - None - } - } - - pub fn zero() -> Self { - Self::default() - } - - pub fn constant(v: F) -> Self { - Self { - v: FpVar::constant(v), - ub: v.into(), - } - } -} - -impl ToBitsGadget for LimbVar { - fn to_bits_le(&self) -> Result>, SynthesisError> { - let cs = self.cs(); - - let bits = &self - .v - .value() - .unwrap_or_default() - .into_bigint() - .to_bits_le()[..self.ub.bits() as usize]; - let bits = if cs.is_none() { - Vec::new_constant(cs, bits)? - } else { - Vec::new_witness(cs, || Ok(bits))? - }; - - Boolean::le_bits_to_fp(&bits)?.enforce_equal(&self.v)?; - - Ok(bits) - } -} - -/// `NonNativeUintVar` represents a non-native unsigned integer (BigUint) in the -/// circuit. -/// We apply [xJsnark](https://akosba.github.io/papers/xjsnark.pdf)'s techniques -/// for efficient operations on `NonNativeUintVar`. -/// Note that `NonNativeUintVar` is different from arkworks' `NonNativeFieldVar` -/// in that the latter runs the expensive `reduce` (`align` + `modulo` in our -/// terminology) after each arithmetic operation, while the former only reduces -/// the integer when explicitly called. -#[derive(Debug, Clone)] -pub struct NonNativeUintVar(pub Vec>); - -impl NonNativeUintVar { - pub const fn bits_per_limb() -> usize { - assert!(F::MODULUS_BIT_SIZE > 250); - // For a `F` with order > 250 bits, 55 is chosen for optimizing the most - // expensive part `Az∘Bz` when checking the R1CS relation for CycleFold. - // Consider using `NonNativeUintVar` to represent the base field `Fq`. - // Since 250 / 55 = 4.46, the `NonNativeUintVar` has 5 limbs. - // Now, the multiplication of two `NonNativeUintVar`s has 9 limbs, and - // each limb has at most 2^{55 * 2} * 5 = 112.3 bits. - // For a 1400x1400 matrix `A`, the multiplication of `A`'s row and `z` - // is the sum of 1400 `NonNativeUintVar`s, each with 9 limbs. - // Thus, the maximum bit length of limbs of each element in `Az` is - // 2^{55 * 2} * 5 * 1400 = 122.7 bits. - // Finally, in the hadamard product of `Az` and `Bz`, every element has - // 17 limbs, whose maximum bit length is (2^{55 * 2} * 5 * 1400)^2 * 9 - // = 248.7 bits and is less than the native field `Fr`. - // Thus, 55 allows us to compute `Az∘Bz` without the expensive alignment - // operation. - // - // TODO: either make it a global const, or compute an optimal value - // based on the modulus size. - 55 - } -} - -struct BoundedBigUint(BigUint, usize); - -impl AllocVar for NonNativeUintVar { - fn new_variable>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let cs = cs.into().cs(); - let v = f()?; - let BoundedBigUint(x, l) = v.borrow(); - - let mut limbs = vec![]; - for chunk in (0..*l) - .map(|i| x.bit(i as u64)) - .collect::>() - .chunks(Self::bits_per_limb()) - { - let limb = F::from(F::BigInt::from_bits_le(chunk)); - let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; - Self::enforce_bit_length(&limb, chunk.len())?; - limbs.push(LimbVar { - v: limb, - ub: (BigUint::one() << chunk.len()) - BigUint::one(), - }); - } - - Ok(Self(limbs)) - } -} - -impl AllocVar for NonNativeUintVar { - fn new_variable>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let cs = cs.into().cs(); - let v = f()?; - assert_eq!(G::extension_degree(), 1); - // `unwrap` is safe because `G` is a field with extension degree 1, and - // thus `G::to_base_prime_field_elements` should return an iterator with - // exactly one element. - let v = v.borrow().to_base_prime_field_elements().next().unwrap(); - - let mut limbs = vec![]; - - for chunk in v.into_bigint().to_bits_le().chunks(Self::bits_per_limb()) { - let limb = F::from(F::BigInt::from_bits_le(chunk)); - let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; - Self::enforce_bit_length(&limb, chunk.len())?; - limbs.push(LimbVar { - v: limb, - ub: (BigUint::one() << chunk.len()) - BigUint::one(), - }); - } - - Ok(Self(limbs)) - } -} - -impl GR1CSVar for NonNativeUintVar { - type Value = BigUint; - - fn cs(&self) -> ConstraintSystemRef { - self.0.cs() - } - - fn value(&self) -> Result { - let mut r = BigUint::zero(); - - for limb in self.0.value()?.into_iter().rev() { - r <<= Self::bits_per_limb(); - r += Into::::into(limb); - } - - Ok(r) - } -} - -impl NonNativeUintVar { - /// Enforce `self` to be less than `other`, where `self` and `other` should - /// be aligned. - /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 - pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> { - let len = max(self.0.len(), other.0.len()); - let zero = LimbVar::zero(); - - // Compute the difference between limbs of `other` and `self`. - // Denote a positive limb by `+`, a negative limb by `-`, a zero limb by - // `0`, and an unknown limb by `?`. - // Then, for `self < other`, `delta` should look like: - // ? ? ... ? ? + 0 0 ... 0 0 - let delta = (0..len) - .map(|i| { - let x = &self.0.get(i).unwrap_or(&zero).v; - let y = &other.0.get(i).unwrap_or(&zero).v; - y - x - }) - .collect::>(); - - // `helper` is a vector of booleans that indicates if the corresponding - // limb of `delta` is the first (searching from MSB) positive limb. - // For example, if `delta` is: - // - + ... + - + 0 0 ... 0 0 - // <---- search in this direction -------- - // Then `helper` should be: - // F F ... F F T F F ... F F - let helper = { - let cs = self.cs().or(other.cs()); - let mut helper = vec![false; len]; - for i in (0..len).rev() { - let delta = delta[i].value().unwrap_or_default().into_bigint(); - if !delta.is_zero() && delta < F::MODULUS_MINUS_ONE_DIV_TWO { - helper[i] = true; - break; - } - } - if cs.is_none() { - Vec::>::new_constant(cs, helper)? - } else { - Vec::new_witness(cs, || Ok(helper))? - } - }; - - // `p` is the first positive limb in `delta`. - let mut p = FpVar::::zero(); - // `r` is the sum of all bits in `helper`, which should be 1 when `self` - // is less than `other`, as there should be more than one positive limb - // in `delta`, and thus exactly one true bit in `helper`. - let mut r = FpVar::zero(); - for (b, d) in helper.into_iter().zip(delta) { - // Choose the limb `d` only if `b` is true. - p += b.select(&d, &FpVar::zero())?; - // Either `r` or `d` should be zero. - // Consider the same example as above: - // - + ... + - + 0 0 ... 0 0 - // F F ... F F T F F ... F F - // |-----------| - // `r = 0` in this range (before/when we meet the first positive limb) - // |---------| - // `d = 0` in this range (after we meet the first positive limb) - // This guarantees that for every bit after the true bit in `helper`, - // the corresponding limb in `delta` is zero. - (&r * &d).enforce_equal(&FpVar::zero())?; - // Add the current bit to `r`. - r += FpVar::from(b); - } - - // Ensure that `r` is exactly 1. This guarantees that there is exactly - // one true value in `helper`. - r.enforce_equal(&FpVar::one())?; - // Ensure that `p` is positive, i.e., - // `0 <= p - 1 < 2^bits_per_limb < F::MODULUS_MINUS_ONE_DIV_TWO`. - // This guarantees that the true value in `helper` corresponds to a - // positive limb in `delta`. - Self::enforce_bit_length(&(p - FpVar::one()), Self::bits_per_limb())?; - - Ok(()) - } - - /// Enforce `self` to be equal to `other`, where `self` and `other` are not - /// necessarily aligned. - /// - /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L562-L798 - /// Similar implementations can also be found in https://github.com/alex-ozdemir/bellman-bignat/blob/0585b9d90154603a244cba0ac80b9aafe1d57470/src/mp/bignat.rs#L566-L661 - /// and https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/emulated_fp/reduce.rs#L201-L323 - pub fn enforce_equal_unaligned(&self, other: &Self) -> Result<(), SynthesisError> { - let len = min(self.0.len(), other.0.len()); - - // Group the limbs of `self` and `other` so that each group nearly - // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. - // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` - // is the initial number of bits in a limb, just as what we do in grade - // school arithmetic, e.g., - // 5 9 - // x 7 3 - // ------------- - // 15 27 - // 35 63 - // ------------- <- When grouping 35, 15 + 63, and 27, we are computing - // 4 3 0 7 35 * 100 + (15 + 63) * 10 + 27 = 4307 - // Note that this is different from the concatenation `x_0 || x_1 ...`, - // since the bit-length of each limb is not necessarily the initial size - // `W`. - let (steps, x, y, rest) = { - // `steps` stores the size of each grouped limb. - let mut steps = vec![]; - // `x_grouped` stores the grouped limbs of `self`. - let mut x_grouped = vec![]; - // `y_grouped` stores the grouped limbs of `other`. - let mut y_grouped = vec![]; - let mut i = 0; - while i < len { - let mut j = i; - // The current grouped limbs of `self` and `other`. - let mut xx = LimbVar::zero(); - let mut yy = LimbVar::zero(); - while j < len { - let shift = BigUint::one() << (Self::bits_per_limb() * (j - i)); - assert!(shift < F::MODULUS_MINUS_ONE_DIV_TWO.into()); - let shift = LimbVar::constant(shift.into()); - match ( - // Try to group `x` and `y` into `xx` and `yy`. - self.0[j].mul(&shift).and_then(|x| xx.add(&x)), - other.0[j].mul(&shift).and_then(|y| yy.add(&y)), - ) { - // Update the result if successful. - (Some(x), Some(y)) => (xx, yy) = (x, y), - // Break the loop if the upper bound of the result exceeds - // the maximum capacity. - _ => break, - } - j += 1; - } - // Store the grouped limbs and their size. - steps.push((j - i) * Self::bits_per_limb()); - x_grouped.push(xx); - y_grouped.push(yy); - // Start the next group - i = j; - } - let remaining_limbs = &(if i < self.0.len() { self } else { other }).0[i..]; - let rest = if remaining_limbs.is_empty() { - FpVar::zero() - } else { - // If there is any remaining limb, the first one should be the - // final carry (which will be checked later), and the following - // ones should be zero. - - // Enforce the remaining limbs to be zero. - // Instead of doing that one by one, we check if their sum is - // zero using a single constraint. - // This is sound, as the upper bounds of the limbs and their sum - // are guaranteed to be less than `F::MODULUS_MINUS_ONE_DIV_TWO` - // (i.e., all of them are "non-negative"), implying that all - // limbs should be zero to make the sum zero. - LimbVar::add_many(&remaining_limbs[1..]) - .ok_or(SynthesisError::Unsatisfiable)? - .v - .enforce_equal(&FpVar::zero())?; - remaining_limbs[0].v.clone() - }; - (steps, x_grouped, y_grouped, rest) - }; - let n = steps.len(); - // `c` stores the current carry of `x_i - y_i` - let mut c = FpVar::::zero(); - // For each group, check the last `step_i` bits of `x_i` and `y_i` are - // equal. - // The intuition is to check `diff = x_i - y_i = 0 (mod 2^step_i)`. - // However, this is only true for `i = 0`, and we need to consider carry - // values `diff >> step_i` for `i > 0`. - // Therefore, we actually check `diff = x_i - y_i + c = 0 (mod 2^step_i)` - // and derive the next `c` by computing `diff >> step_i`. - // To enforce `diff = 0 (mod 2^step_i)`, we compute `diff / 2^step_i` - // and enforce it to be small (soundness holds because for `a` that does - // not divide `b`, `b / a` in the field will be very large. - for i in 0..n { - let step = steps[i]; - c = (&x[i].v - &y[i].v + &c) - .mul_by_inverse_unchecked(&FpVar::constant(F::from(BigUint::one() << step)))?; - if i != n - 1 { - // Unlike the code mentioned above which add some offset to the - // diff `x_i - y_i + c` to make it always positive, we directly - // check if the absolute value of the diff is small. - Self::enforce_abs_bit_length( - &c, - (max(&x[i].ub, &y[i].ub).bits() as usize) - .checked_sub(step) - .unwrap_or_default(), - )?; - } else { - // For the final carry, we need to ensure that it equals the - // remaining limb `rest`. - c.enforce_equal(&rest)?; - } - } - - Ok(()) - } -} - -impl ToBitsGadget for NonNativeUintVar { - fn to_bits_le(&self) -> Result>, SynthesisError> { - Ok(self - .0 - .iter() - .map(|limb| limb.to_bits_le()) - .collect::, _>>()? - .concat()) - } -} - -impl CondSelectGadget for NonNativeUintVar { - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - assert_eq!(true_value.0.len(), false_value.0.len()); - let mut v = vec![]; - for i in 0..true_value.0.len() { - v.push(cond.select(&true_value.0[i], &false_value.0[i])?); - } - Ok(Self(v)) - } -} - -impl NonNativeUintVar { - pub fn ubound(&self) -> BigUint { - let mut r = BigUint::zero(); - - for i in self.0.iter().rev() { - r <<= Self::bits_per_limb(); - r += &i.ub; - } - - r - } - - fn enforce_bit_length(x: &FpVar, length: usize) -> Result>, SynthesisError> { - let cs = x.cs(); - - let bits = &x.value().unwrap_or_default().into_bigint().to_bits_le()[..length]; - let bits = if cs.is_none() { - Vec::new_constant(cs, bits)? - } else { - Vec::new_witness(cs, || Ok(bits))? - }; - - Boolean::le_bits_to_fp(&bits)?.enforce_equal(x)?; - - Ok(bits) - } - - fn enforce_abs_bit_length( - x: &FpVar, - length: usize, - ) -> Result>, SynthesisError> { - let cs = x.cs(); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - - let is_neg = Boolean::new_variable( - cs.clone(), - || Ok(x.value().unwrap_or_default().into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO), - mode, - )?; - let bits = Vec::new_variable( - cs.clone(), - || { - Ok({ - let x = x.value().unwrap_or_default(); - let mut bits = if is_neg.value().unwrap_or_default() { - -x - } else { - x - } - .into_bigint() - .to_bits_le(); - bits.resize(length, false); - bits - }) - }, - mode, - )?; - - // Below is equivalent to but more efficient than - // `Boolean::le_bits_to_fp(&bits)?.enforce_equal(&is_neg.select(&x.negate()?, &x)?)?` - // Note that this enforces: - // 1. The claimed absolute value `is_neg.select(&x.negate()?, &x)?` has - // exactly `length` bits. - // 2. `is_neg` is indeed the sign of `x`, i.e., `is_neg = false` when - // `0 <= x < (|F| - 1) / 2`, and `is_neg = true` when - // `(|F| - 1) / 2 <= x < F`, thus the claimed absolute value is - // correct. - // If `is_neg` is incorrect, then: - // a. `0 <= x < (|F| - 1) / 2`, but `is_neg = true`, then - // `is_neg.select(&x.negate()?, &x)?` returns `|F| - x`, - // which is greater than `(|F| - 1) / 2` and cannot fit in - // `length` bits (given that `length` is small). - // b. `(|F| - 1) / 2 <= x < F`, but `is_neg = false`, then - // `is_neg.select(&x.negate()?, &x)?` returns `x`, which is - // greater than `(|F| - 1) / 2` and cannot fit in `length` - // bits. - FpVar::from(is_neg).mul_equals(&x.double()?, &(x - Boolean::le_bits_to_fp(&bits)?))?; - - Ok(bits) - } - - /// Compute `self + other`, without aligning the limbs. - pub fn add_no_align(&self, other: &Self) -> Result { - let mut z = vec![LimbVar::zero(); max(self.0.len(), other.0.len())]; - for (i, v) in self.0.iter().enumerate() { - z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?; - } - for (i, v) in other.0.iter().enumerate() { - z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?; - } - Ok(Self(z)) - } - - /// Compute `self * other`, without aligning the limbs. - /// Implements the O(n) approach described in xJsnark, Section IV.B.1) - pub fn mul_no_align(&self, other: &Self) -> Result { - let len = self.0.len() + other.0.len() - 1; - if self.is_constant() || other.is_constant() { - // Use the naive approach for constant operands, which costs no - // constraints. - let z = (0..len) - .map(|i| { - let start = max(i + 1, other.0.len()) - other.0.len(); - let end = min(i + 1, self.0.len()); - LimbVar::add_many( - &(start..end) - .map(|j| self.0[j].mul(&other.0[i - j])) - .collect::>>()?, - ) - }) - .collect::>>() - .ok_or(SynthesisError::Unsatisfiable)?; - return Ok(Self(z)); - } - let cs = self.cs().or(other.cs()); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - - // Compute the result `z` outside the circuit and provide it as hints. - let z = { - let mut z = vec![(F::zero(), BigUint::zero()); len]; - for i in 0..self.0.len() { - for j in 0..other.0.len() { - z[i + j].0 += self.0[i].value().unwrap_or_default() - * other.0[j].value().unwrap_or_default(); - z[i + j].1 += &self.0[i].ub * &other.0[j].ub; - } - } - z.into_iter() - .map(|(v, ub)| { - assert!(ub < F::MODULUS_MINUS_ONE_DIV_TWO.into()); - Ok(LimbVar { - v: FpVar::new_variable(cs.clone(), || Ok(v), mode)?, - ub, - }) - }) - .collect::, _>>()? - }; - for c in 1..=len { - let c = F::from(c as u64); - let mut t = F::one(); - let mut c_powers = vec![]; - for _ in 0..len { - c_powers.push(t); - t *= c; - } - // `l = Σ self[i] c^i` - let l = self - .0 - .iter() - .zip(&c_powers) - .map(|(v, t)| &v.v * *t) - .sum::>(); - // `r = Σ other[i] c^i` - let r = other - .0 - .iter() - .zip(&c_powers) - .map(|(v, t)| &v.v * *t) - .sum::>(); - // `o = Σ z[i] c^i` - let o = z - .iter() - .zip(&c_powers) - .map(|(v, t)| &v.v * *t) - .sum::>(); - // Enforce `o = l * r` - l.mul_equals(&r, &o)?; - } - - Ok(Self(z)) - } - - /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. - pub fn modulo(&self) -> Result { - let cs = self.cs(); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - let m: BigUint = M::MODULUS.into(); - // Provide the quotient and remainder as hints - let (q, r) = { - let v = self.value().unwrap_or_default(); - let (q, r) = v.div_rem(&m); - let q_ubound = self.ubound().div_ceil(&m); - let r_ubound = &m; - ( - Self::new_variable( - cs.clone(), - || Ok(BoundedBigUint(q, q_ubound.bits() as usize)), - mode, - )?, - Self::new_variable( - cs.clone(), - || Ok(BoundedBigUint(r, r_ubound.bits() as usize)), - mode, - )?, - ) - }; - - let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?; - // Enforce `self = q * m + r` - q.mul_no_align(&m)? - .add_no_align(&r)? - .enforce_equal_unaligned(self)?; - // Enforce `r < m` (and `r >= 0` already holds) - r.enforce_lt(&m)?; - - Ok(r) - } - - /// Enforce that `self` is congruent to `other` modulo `M::MODULUS`. - pub fn enforce_congruent(&self, other: &Self) -> Result<(), SynthesisError> { - let cs = self.cs(); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - let m: BigUint = M::MODULUS.into(); - let bits = (max(self.ubound(), other.ubound()) / &m).bits() as usize; - // Provide the quotient `|x - y| / m` and a boolean indicating if `x > y` - // as hints. - let (q, is_ge) = { - let x = self.value().unwrap_or_default(); - let y = other.value().unwrap_or_default(); - let (d, b) = if x > y { - ((x - y) / &m, true) - } else { - ((y - x) / &m, false) - }; - ( - Self::new_variable(cs.clone(), || Ok(BoundedBigUint(d, bits)), mode)?, - Boolean::new_variable(cs.clone(), || Ok(b), mode)?, - ) - }; - - let zero = Self::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), bits))?; - let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?; - let l = self.add_no_align(&is_ge.select(&zero, &q)?.mul_no_align(&m)?)?; - let r = other.add_no_align(&is_ge.select(&q, &zero)?.mul_no_align(&m)?)?; - // If `self >= other`, enforce `self = other + q * m` - // Otherwise, enforce `self + q * m = other` - // Soundness holds because if `self` and `other` are not congruent, then - // one can never find a `q` satisfying either equation above. - l.enforce_equal_unaligned(&r) - } -} - -impl EquivalenceGadget for NonNativeUintVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { - self.enforce_congruent::(other) - } -} - -impl]>> From for NonNativeUintVar { - fn from(bits: B) -> Self { - Self( - bits.as_ref() - .chunks(Self::bits_per_limb()) - .map(LimbVar::from) - .collect::>(), - ) - } -} - -impl AbsorbNonNativeGadget for NonNativeUintVar { - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { - let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; - - self.to_bits_le()? - .chunks(bits_per_limb) - .map(Boolean::le_bits_to_fp) - .collect() - } -} - -impl VectorGadget> for [NonNativeUintVar] { - fn add(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.add_no_align(y)) - .collect() - } - - fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.mul_no_align(y)) - .collect() - } - - fn scale( - &self, - other: &NonNativeUintVar, - ) -> Result>, SynthesisError> { - self.iter().map(|x| x.mul_no_align(other)).collect() - } -} - -impl MatrixGadget> for SparseMatrixVar> { - fn mul_vector( - &self, - v: &impl Index>, - ) -> Result>, SynthesisError> { - self.0 - .iter() - .map(|row| { - let len = row - .iter() - .map(|(value, col_i)| value.0.len() + v[*col_i].0.len() - 1) - .max() - .unwrap_or(0); - // This is a combination of `mul_no_align` and `add_no_align` - // that results in more flattened `LinearCombination`s. - // Consequently, `ConstraintSystem::inline_all_lcs` costs less - // time, thus making trusted setup and proof generation faster. - (0..len) - .map(|i| { - LimbVar::add_many( - &row.iter() - .flat_map(|(value, col_i)| { - let start = max(i + 1, v[*col_i].0.len()) - v[*col_i].0.len(); - let end = min(i + 1, value.0.len()); - (start..end).map(|j| value.0[j].mul(&v[*col_i].0[i - j])) - }) - .collect::>>()?, - ) - }) - .collect::>>() - .ok_or(SynthesisError::Unsatisfiable) - .map(NonNativeUintVar) - }) - .collect() - } -} - -#[cfg(test)] -mod tests { - use std::error::Error; - - use ark_ff::Field; - use ark_pallas::{Fq, Fr}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{test_rng, UniformRand}; - use num_bigint::RandBigInt; - - use super::*; - - #[test] - fn test_mul_biguint() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let size = 256; - - let rng = &mut test_rng(); - let a = rng.gen_biguint(size as u64); - let b = rng.gen_biguint(size as u64); - let ab = &a * &b; - let aab = &a * &ab; - let abb = &ab * &b; - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(a, size)))?; - let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(b, size)))?; - let ab_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(ab, size * 2)))?; - let aab_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(aab, size * 3)))?; - let abb_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok(BoundedBigUint(abb, size * 3)))?; - - a_var - .mul_no_align(&b_var)? - .enforce_equal_unaligned(&ab_var)?; - a_var - .mul_no_align(&ab_var)? - .enforce_equal_unaligned(&aab_var)?; - ab_var - .mul_no_align(&b_var)? - .enforce_equal_unaligned(&abb_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_mul_fq() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let rng = &mut test_rng(); - let a = Fq::rand(rng); - let b = Fq::rand(rng); - let ab = a * b; - let aab = a * ab; - let abb = ab * b; - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; - let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(b))?; - let ab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(ab))?; - let aab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(aab))?; - let abb_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(abb))?; - - a_var - .mul_no_align(&b_var)? - .enforce_congruent::(&ab_var)?; - a_var - .mul_no_align(&ab_var)? - .enforce_congruent::(&aab_var)?; - ab_var - .mul_no_align(&b_var)? - .enforce_congruent::(&abb_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_pow() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let rng = &mut test_rng(); - - let a = Fq::rand(rng); - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; - - let mut r_var = a_var.clone(); - for _ in 0..16 { - r_var = r_var.mul_no_align(&r_var)?.modulo::()?; - } - r_var = r_var.mul_no_align(&a_var)?.modulo::()?; - assert_eq!(a.pow([65537u64]), Fq::from(r_var.value()?)); - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_vec_vec_mul() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let len = 1000; - - let rng = &mut test_rng(); - let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); - let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); - let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); - - let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; - let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; - let c_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(c))?; - - let mut r_var = - NonNativeUintVar::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), 0))?; - for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { - r_var = r_var.add_no_align(&a.mul_no_align(&b)?)?; - } - r_var.enforce_congruent::(&c_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index b6ced84d8..c91d06afb 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,7 +1,7 @@ +pub mod algebra; pub mod arithmetizations; pub mod circuits; pub mod commitments; -pub mod gadgets; pub mod relations; pub mod sumcheck; pub mod traits; diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index 717597cbc..141370f47 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -18,7 +18,7 @@ use ark_std::{cfg_chunks, cfg_into_iter, cfg_iter, fmt::Debug}; use rayon::prelude::*; use thiserror::Error; -use crate::{traits::Absorbable, transcripts::Transcript}; +use crate::transcripts::{Absorbable, Transcript}; use utils::{ barycentric_weights, compute_lagrange_interpolated_poly, extrapolate, VPAuxInfo, diff --git a/crates/primitives/src/traits.rs b/crates/primitives/src/traits.rs new file mode 100644 index 000000000..35d9be14e --- /dev/null +++ b/crates/primitives/src/traits.rs @@ -0,0 +1,56 @@ +pub use crate::algebra::{ + field::SonobeField, + group::{SonobeCurve, CF1, CF2}, +}; + +pub trait Dummy { + fn dummy(cfg: Cfg) -> Self; +} + +impl Dummy for Vec { + fn dummy(cfg: usize) -> Self { + vec![Default::default(); cfg] + } +} + +impl Dummy<()> for T { + fn dummy(_: ()) -> Self { + Default::default() + } +} + +/// Converts a value `self` into a vector of field elements, ordered in the same +/// way as how a variable of type `Var` would be represented *natively* in the +/// circuit. +/// +/// This is useful for the verifier to compute the public inputs. +pub trait Inputize { + fn inputize(&self) -> Vec; +} + +impl> Inputize for [T] { + fn inputize(&self) -> Vec { + self.iter().flat_map(Inputize::::inputize).collect() + } +} + +/// Converts a value `self` into a vector of field elements, ordered in the same +/// way as how a variable of type `Var` would be represented *non-natively* in +/// the circuit. +/// +/// This is useful for the verifier to compute the public inputs. +/// +/// Note that we require this trait because we need to distinguish between some +/// data types that are represented both natively and non-natively in-circuit +/// (e.g., field elements can have type `FpVar` and `NonNativeUintVar`). +pub trait InputizeNonNative { + fn inputize_nonnative(&self) -> Vec; +} + +impl> InputizeNonNative for [T] { + fn inputize_nonnative(&self) -> Vec { + self.iter() + .flat_map(InputizeNonNative::::inputize_nonnative) + .collect() + } +} diff --git a/crates/primitives/src/traits/mod.rs b/crates/primitives/src/traits/mod.rs deleted file mode 100644 index aecf72258..000000000 --- a/crates/primitives/src/traits/mod.rs +++ /dev/null @@ -1,345 +0,0 @@ -use ark_ec::{ - short_weierstrass::{Projective, SWCurveConfig}, - AffineRepr, CurveGroup, PrimeGroup, -}; -use ark_ff::{BigInteger, Field as ArkField, Fp, FpConfig, One, PrimeField, Zero}; -use ark_r1cs_std::{ - fields::{fp::FpVar, FieldVar}, - groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, -}; -use ark_relations::gr1cs::SynthesisError; -use ark_std::{ - any::TypeId, - iter::Sum, - mem::transmute_copy, - ops::{Add, Mul}, -}; - -pub type CF1 = ::ScalarField; -pub type CF2 = <::BaseField as ArkField>::BasePrimeField; - -pub trait Dummy { - fn dummy(cfg: Cfg) -> Self; -} - -impl Dummy for Vec { - fn dummy(cfg: usize) -> Self { - vec![Default::default(); cfg] - } -} - -impl Dummy<()> for T { - fn dummy(_: ()) -> Self { - Default::default() - } -} - -/// Converts a value `self` into a vector of field elements, ordered in the same -/// way as how a variable of type `Var` would be represented *natively* in the -/// circuit. -/// -/// This is useful for the verifier to compute the public inputs. -pub trait Inputize { - fn inputize(&self) -> Vec; -} - -/// Converts a value `self` into a vector of field elements, ordered in the same -/// way as how a variable of type `Var` would be represented *non-natively* in -/// the circuit. -/// -/// This is useful for the verifier to compute the public inputs. -/// -/// Note that we require this trait because we need to distinguish between some -/// data types that are represented both natively and non-natively in-circuit -/// (e.g., field elements can have type `FpVar` and `NonNativeUintVar`). -pub trait InputizeNonNative { - fn inputize_nonnative(&self) -> Vec; -} - -impl> Inputize for [T] { - fn inputize(&self) -> Vec { - self.iter().flat_map(Inputize::::inputize).collect() - } -} - -impl> InputizeNonNative for [T] { - fn inputize_nonnative(&self) -> Vec { - self.iter() - .flat_map(InputizeNonNative::::inputize_nonnative) - .collect() - } -} - -impl, const N: usize> Inputize for Fp { - /// Returns the internal representation in the same order as how the value - /// is allocated in `FpVar::new_input`. - fn inputize(&self) -> Vec { - vec![*self] - } -} - -impl> Inputize for Projective

{ - /// Returns the internal representation in the same order as how the value - /// is allocated in `ProjectiveVar::new_input`. - fn inputize(&self) -> Vec { - let affine = self.into_affine(); - match affine.xy() { - Some((x, y)) => vec![x, y, One::one()], - None => vec![Zero::zero(), One::one(), Zero::zero()], - } - } -} - -impl InputizeNonNative for P { - /// Returns the internal representation in the same order as how the value - /// is allocated in `NonNativeUintVar::new_input`. - fn inputize_nonnative(&self) -> Vec { - self.into_bigint() - .to_bits_le() - .chunks(F::BITS_PER_LIMB) - .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) - .collect() - } -} - -impl> - InputizeNonNative for Projective

-{ - /// Returns the internal representation in the same order as how the value - /// is allocated in `NonNativeAffineVar::new_input`. - fn inputize_nonnative(&self) -> Vec { - let affine = self.into_affine(); - let (x, y) = affine.xy().unwrap_or_default(); - - [x, y].inputize_nonnative() - } -} - -/// `Field` trait is a wrapper around `PrimeField` that also includes the -/// necessary bounds for the field to be used conveniently in folding schemes. -pub trait SonobeField: - PrimeField + Absorbable + Inputize -{ - const BITS_PER_LIMB: usize; - /// The in-circuit variable type for this field. - type Var: FieldVar; -} - -impl, const N: usize> SonobeField for Fp { - const BITS_PER_LIMB: usize = 55; // TODO: make this configurable - type Var = FpVar; -} - -/// `Curve` trait is a wrapper around `CurveGroup` that also includes the -/// necessary bounds for the curve to be used conveniently in folding schemes. -pub trait SonobeCurve: - CurveGroup - + Absorbable - + Inputize - + InputizeNonNative -{ - /// The in-circuit variable type for this curve. - type Var: CurveVar; -} - -impl> SonobeCurve - for Projective

-{ - type Var = ProjectiveVar>; -} - -pub trait Absorbable { - /// Converts the object into field elements that can be absorbed by a `CryptographicSponge`. - /// Append the list to `dest` - fn absorb_into(&self, dest: &mut Vec); - - /// Converts the object into field elements that can be absorbed by a `CryptographicSponge`. - /// Return the list as `Vec` - fn extract_absorbed(&self) -> Vec { - let mut result = Vec::new(); - self.absorb_into(&mut result); - result - } -} - -impl, const N: usize> Absorbable for Fp { - fn absorb_into(&self, dest: &mut Vec) { - if TypeId::of::() == TypeId::of::() { - // Safe because `F` and `Self` have the same type - // TODO (@winderica): specialization when??? - dest.push(unsafe { transmute_copy::(self) }); - } else { - let bits_per_limb = F::MODULUS_BIT_SIZE - 1; - let num_limbs = Self::MODULUS_BIT_SIZE.div_ceil(bits_per_limb); - - let mut limbs = self - .into_bigint() - .to_bits_le() - .chunks(bits_per_limb as usize) - .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) - .collect::>(); - limbs.resize(num_limbs as usize, F::zero()); - - dest.extend(&limbs) - } - } -} - -impl>> Absorbable for Projective

{ - fn absorb_into(&self, dest: &mut Vec) { - let affine = self.into_affine(); - let (x, y) = affine.xy().unwrap_or_default(); - [x, y].absorb_into(dest); - } -} - -impl Absorbable for usize { - fn absorb_into(&self, dest: &mut Vec) { - dest.push(F::from(*self as u64)); - } -} - -impl> Absorbable for &T { - fn absorb_into(&self, dest: &mut Vec) { - >::absorb_into(self, dest); - } -} - -impl> Absorbable for (T, T) { - fn absorb_into(&self, dest: &mut Vec) { - self.0.absorb_into(dest); - self.1.absorb_into(dest); - } -} - -impl + 'static> Absorbable for [T] { - fn absorb_into(&self, dest: &mut Vec) { - if TypeId::of::() == TypeId::of::() { - // Safe because `F` and `T` have the same type - dest.extend(unsafe { transmute_copy::<&[T], &[F]>(&self) }); - } else { - for t in self.iter() { - t.absorb_into(dest); - } - } - } -} - -impl + 'static, const N: usize> Absorbable for [T; N] { - fn absorb_into(&self, dest: &mut Vec) { - <[T] as Absorbable>::absorb_into(self, dest); - } -} - -impl + 'static> Absorbable for Vec { - fn absorb_into(&self, dest: &mut Vec) { - <[T] as Absorbable>::absorb_into(self, dest); - } -} - -// TODO: rework this -/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field -/// is `F`. -/// -/// Matches `AbsorbGadget` in `ark-crypto-primitives`. -pub trait AbsorbNonNativeGadget { - /// Converts the object into field elements that can be absorbed by a `TranscriptVar`. - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError>; -} - -impl> AbsorbNonNativeGadget for &T { - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { - T::to_native_sponge_field_elements(self) - } -} - -impl> AbsorbNonNativeGadget for [T] { - fn to_native_sponge_field_elements(&self) -> Result>, SynthesisError> { - let mut result = Vec::new(); - for t in self.iter() { - result.extend(t.to_native_sponge_field_elements()?); - } - Ok(result) - } -} - -#[derive(Clone, Copy, Default, Debug)] -pub struct Null; - -impl Add for Null { - type Output = Null; - - fn add(self, _: F) -> Null { - Null - } -} - -impl Add for &Null { - type Output = Null; - - fn add(self, _: F) -> Null { - Null - } -} - -impl Mul for Null { - type Output = Self; - - fn mul(self, _: F) -> Null { - Null - } -} - -impl Mul for &Null { - type Output = Null; - - fn mul(self, _: F) -> Null { - Null - } -} - -impl Sum for Null { - fn sum>(_: I) -> Self { - Null - } -} - -pub trait ScalarRLC { - type Value; - - fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value; -} - -impl ScalarRLC for I -where - I::Item: Add + Sum + for<'a> Mul<&'a Coeff, Output = I::Item>, -{ - type Value = I::Item; - - fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value { - self.zip(coeffs).map(|(v, c)| v * c).sum::() - } -} - -pub trait SliceRLC { - type Value; - - fn slice_rlc(self, coeffs: &[Coeff]) -> Vec; -} - -impl<'a, T: 'a, I: Iterator, Coeff> SliceRLC for I -where - T: Add + Copy, - for<'x> T: Mul<&'x Coeff, Output = T>, -{ - type Value = T; - - fn slice_rlc(self, coeffs: &[Coeff]) -> Vec { - let mut iter = self.zip(coeffs).map(|(v, c)| v.iter().map(|x| *x * c)); - let first = iter.next().unwrap(); - - iter.fold(first.collect(), |acc, v| { - acc.into_iter().zip(v).map(|(a, b)| a + b).collect() - }) - } -} diff --git a/crates/primitives/src/transcripts/absorbable.rs b/crates/primitives/src/transcripts/absorbable.rs new file mode 100644 index 000000000..c19f7660e --- /dev/null +++ b/crates/primitives/src/transcripts/absorbable.rs @@ -0,0 +1,98 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::gr1cs::SynthesisError; +use ark_std::{any::TypeId, mem::transmute_copy}; + +pub trait Absorbable { + fn absorb_into(&self, dest: &mut Vec); + + fn to_absorbable(&self) -> Vec { + let mut result = Vec::new(); + self.absorb_into(&mut result); + result + } +} + +impl> Absorbable for usize { + fn absorb_into(&self, dest: &mut Vec) { + dest.push(F::from(*self as u64)); + } +} + +impl> Absorbable for &T { + fn absorb_into(&self, dest: &mut Vec) { + >::absorb_into(self, dest); + } +} + +impl> Absorbable for (T, T) { + fn absorb_into(&self, dest: &mut Vec) { + self.0.absorb_into(dest); + self.1.absorb_into(dest); + } +} + +impl> Absorbable for [T] { + fn absorb_into(&self, dest: &mut Vec) { + for t in self.iter() { + t.absorb_into(dest); + } + } +} + +impl, const N: usize> Absorbable for [T; N] { + fn absorb_into(&self, dest: &mut Vec) { + <[T] as Absorbable>::absorb_into(self, dest); + } +} + +impl> Absorbable for Vec { + fn absorb_into(&self, dest: &mut Vec) { + <[T] as Absorbable>::absorb_into(self, dest); + } +} + +/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field +/// is `F`. +/// +/// Matches `AbsorbGadget` in `ark-crypto-primitives`. +pub trait AbsorbableGadget { + fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError>; + + fn to_absorbable(&self) -> Result, SynthesisError> { + let mut result = Vec::new(); + self.absorb_into(&mut result)?; + Ok(result) + } +} + +impl> AbsorbableGadget for &T { + fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { + >::absorb_into(self, dest) + } +} + +impl> AbsorbableGadget for (T, T) { + fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { + self.0.absorb_into(dest)?; + self.1.absorb_into(dest) + } +} + +impl> AbsorbableGadget for [T] { + fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { + self.iter().try_for_each(|t| t.absorb_into(dest)) + } +} + +impl, const N: usize> AbsorbableGadget for [T; N] { + fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { + <[T] as AbsorbableGadget>::absorb_into(self, dest) + } +} + +impl> AbsorbableGadget for Vec { + fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { + <[T] as AbsorbableGadget>::absorb_into(self, dest) + } +} diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 504559a7e..21c709f19 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -4,10 +4,11 @@ use ark_crypto_primitives::sponge::{ use ark_ec::CurveGroup; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; -use ark_relations::gr1cs::SynthesisError; +use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; -use crate::traits::{AbsorbNonNativeGadget, Absorbable}; +pub use absorbable::{Absorbable, AbsorbableGadget}; +pub mod absorbable; pub mod poseidon; pub trait Transcript { @@ -25,14 +26,9 @@ pub trait Transcript { fn add + ?Sized>(&mut self, input: &A); - /// Squeeze `num_bytes` bytes from the sponge. - fn get_bytes(&mut self, num_bytes: usize) -> Vec; - /// Squeeze `num_bits` bits from the sponge. fn get_bits(&mut self, num_bits: usize) -> Vec; - fn get_field_elements_with_sizes(&mut self, sizes: &[FieldElementSize]) -> Vec; - fn get_field_elements(&mut self, num_elements: usize) -> Vec; /// Creates a new sponge with applied domain separation. @@ -49,7 +45,7 @@ pub trait Transcript { let limbs = input .chunks(F::MODULUS_BIT_SIZE.div_ceil(8) as usize) .map(|chunk| F::from_le_bytes_mod_order(chunk)) - .collect::>(); + .collect::>(); new_sponge.add(&limbs); @@ -82,49 +78,65 @@ pub trait Transcript { } } -pub trait TranscriptVar: - CryptographicSpongeVar -{ +pub trait TranscriptVar { + type Native; + /// `new_with_pp_hash` creates a new transcript / sponge with the given /// hash of the public parameters. fn new_with_pp_hash( config: &Self::Parameters, pp_hash: &FpVar, - ) -> Result; - - /// `absorb_point` is for absorbing points whose `BaseField` is the field of - /// the sponge, i.e., the type `C` of these points should satisfy - /// `C::BaseField = F`. - /// - /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` - /// instead. - fn absorb_point, GC: CurveVar>( - &mut self, - v: &GC, - ) -> Result<(), SynthesisError>; - /// `absorb_nonnative` is for structs that contain non-native (field or - /// group) elements, including: - /// - /// - A field element of type `T: PrimeField` that will be absorbed into a - /// sponge that operates in another field `F != T`. - /// - A group element of type `C: CurveGroup` that will be absorbed into a - /// sponge that operates in another field `F != C::BaseField`, e.g., - /// `F = C::ScalarField`. - /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that - /// will be absorbed into a sponge that operates in the (scalar field of - /// the) primary curve. - /// - /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on - /// the primary curve also contains non-native elements, we still regard - /// it as native, because the sponge is on the same curve. - fn absorb_nonnative>( - &mut self, - v: &V, - ) -> Result<(), SynthesisError>; - - fn get_challenge(&mut self) -> Result, SynthesisError>; - /// returns the bit representation of the challenge, we use its output in-circuit for the - /// `GC.scalar_mul_le` method. - fn get_challenge_nbits(&mut self, nbits: usize) -> Result>, SynthesisError>; - fn get_challenges(&mut self, n: usize) -> Result>, SynthesisError>; + ) -> Result + where + Self: CryptographicSpongeVar, + Self::Native: CryptographicSponge, + { + let mut sponge = Self::new(ConstraintSystemRef::None, config); + sponge.add(&pp_hash)?; + Ok(sponge) + } + + fn add>>(&mut self, input: &A) -> Result<(), SynthesisError>; + + /// Squeeze `num_bits` bits from the sponge. + fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError>; + + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError>; + + /// Creates a new sponge with applied domain separation. + fn separate_domain(&self, domain: &[u8]) -> Result + where + Self: CryptographicSponge, + { + let mut new_sponge = self.clone(); + + let mut input = domain.len().to_le_bytes().to_vec(); + input.extend_from_slice(domain); + + let limbs = input + .chunks(F::MODULUS_BIT_SIZE.div_ceil(8) as usize) + .map(|chunk| FpVar::Constant(F::from_le_bytes_mod_order(chunk))) + .collect::>(); + + new_sponge.add(&limbs)?; + + Ok(new_sponge) + } + + fn challenge_field_element(&mut self) -> Result, SynthesisError> { + let mut c = self.get_field_elements(1)?; + self.add(&c[0])?; + Ok(c.pop().unwrap()) + } + fn challenge_bits(&mut self, nbits: usize) -> Result>, SynthesisError> { + let bits = self.get_bits(nbits)?; + self.add(&Boolean::le_bits_to_fp(&bits)?)?; + Ok(bits) + } + + fn challenge_field_elements(&mut self, n: usize) -> Result>, SynthesisError> { + let c = self.get_field_elements(n)?; + self.add(&c)?; + Ok(c) + } } diff --git a/crates/primitives/src/transcripts/poseidon.rs b/crates/primitives/src/transcripts/poseidon.rs index a3a298108..3607e95e7 100644 --- a/crates/primitives/src/transcripts/poseidon.rs +++ b/crates/primitives/src/transcripts/poseidon.rs @@ -14,7 +14,7 @@ use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; use crate::transcripts::{Absorbable, FieldElementSize}; -use super::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; +use super::{AbsorbableGadget, Transcript, TranscriptVar}; impl Transcript for PoseidonSponge { fn add + ?Sized>(&mut self, input: &A) { @@ -33,7 +33,7 @@ impl Transcript for PoseidonSponge { dest.extend(unsafe { transmute_copy::<&[F], &[T]>(&self.0.as_ref()) }); } } - let v = input.extract_absorbed(); + let v = input.to_absorbable(); CryptographicSponge::absorb(self, &Hack(v)); } @@ -41,67 +41,24 @@ impl Transcript for PoseidonSponge { CryptographicSponge::squeeze_bits(self, num_bits) } - fn get_bytes(&mut self, num_bytes: usize) -> Vec { - CryptographicSponge::squeeze_bytes(self, num_bytes) - } - - fn get_field_elements_with_sizes(&mut self, sizes: &[FieldElementSize]) -> Vec { - self.squeeze_native_field_elements_with_sizes(sizes) - } - fn get_field_elements(&mut self, num_elements: usize) -> Vec { self.squeeze_native_field_elements(num_elements) } } -impl TranscriptVar> for PoseidonSpongeVar { - fn new_with_pp_hash( - config: &Self::Parameters, - pp_hash: &FpVar, - ) -> Result { - let mut sponge = Self::new(ConstraintSystemRef::None, config); - sponge.absorb(&pp_hash)?; - Ok(sponge) - } +impl TranscriptVar for PoseidonSpongeVar { + type Native = PoseidonSponge; - fn absorb_point, GC: CurveVar>( - &mut self, - v: &GC, - ) -> Result<(), SynthesisError> { - let mut vec = v.to_constraint_field()?; - // The last element in the vector tells whether the point is infinity, - // but we can in fact avoid absorbing it without loss of soundness. - // This is because the `to_constraint_field` method internally invokes - // [`ProjectiveVar::to_afine`](https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/curves/short_weierstrass/mod.rs#L160-L195), - // which guarantees that an infinity point is represented as `(0, 0)`, - // but the y-coordinate of a non-infinity point is never 0 (for why, see - // https://crypto.stackexchange.com/a/108242 ). - vec.pop(); - self.absorb(&vec) - } - fn absorb_nonnative>( - &mut self, - v: &V, - ) -> Result<(), SynthesisError> { - self.absorb(&v.to_native_sponge_field_elements()?) - } - fn get_challenge(&mut self) -> Result, SynthesisError> { - let c = self.squeeze_field_elements(1)?; - self.absorb(&c[0])?; - Ok(c[0].clone()) + fn add>>(&mut self, input: &A) -> Result<(), SynthesisError> { + self.absorb(&input.to_absorbable()?) } - /// returns the bit representation of the challenge, we use its output in-circuit for the - /// `GC.scalar_mul_le` method. - fn get_challenge_nbits(&mut self, nbits: usize) -> Result>, SynthesisError> { - let bits = self.squeeze_bits(nbits)?; - self.absorb(&Boolean::le_bits_to_fp(&bits)?)?; - Ok(bits) + fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { + self.squeeze_bits(num_bits) } - fn get_challenges(&mut self, n: usize) -> Result>, SynthesisError> { - let c = self.squeeze_field_elements(n)?; - self.absorb(&c)?; - Ok(c) + + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { + self.squeeze_field_elements(num_elements) } } @@ -149,8 +106,9 @@ pub mod tests { use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, test_rng}; + use crate::algebra::group::nonnative::NonNativeAffineVar; + use super::*; - use crate::gadgets::nonnative::affine::NonNativeAffineVar; // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 #[test] @@ -163,8 +121,8 @@ pub mod tests { .into_iter() .map(Fr::from) .collect::>(); - poseidon_sponge.absorb(&v); - poseidon_sponge.squeeze_field_elements::(1); + poseidon_sponge.add(&v); + poseidon_sponge.get_field_elements(1); assert!( poseidon_sponge.state[0] == Fr::from_str( @@ -193,8 +151,8 @@ pub mod tests { ConstraintSystem::::new_ref(), || Ok(p), )?; - tr_var.absorb_point(&p_var)?; - let c_var = tr_var.get_challenge()?; + tr_var.add(&p_var)?; + let c_var = tr_var.challenge_field_element()?; // assert that native & gadget transcripts return the same challenge assert_eq!(c, c_var.value()?); @@ -217,8 +175,8 @@ pub mod tests { let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); let p_var = NonNativeAffineVar::::new_witness(ConstraintSystem::::new_ref(), || Ok(p))?; - tr_var.absorb_nonnative(&p_var)?; - let c_var = tr_var.get_challenge()?; + tr_var.add(&p_var)?; + let c_var = tr_var.challenge_field_element()?; // assert that native & gadget transcripts return the same challenge assert_eq!(c, c_var.value()?); @@ -237,8 +195,8 @@ pub mod tests { let cs = ConstraintSystem::::new_ref(); let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; - tr_var.absorb(&v)?; - let c_var = tr_var.get_challenge()?; + tr_var.add(&v)?; + let c_var = tr_var.challenge_field_element()?; // assert that native & gadget transcripts return the same challenge assert_eq!(c, c_var.value()?); @@ -261,10 +219,10 @@ pub mod tests { let cs = ConstraintSystem::::new_ref(); let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; - tr_var.absorb(&v)?; + tr_var.add(&v)?; // get challenge from circuit transcript - let c_var = tr_var.get_challenge_nbits(nbits)?; + let c_var = tr_var.challenge_bits(nbits)?; let p = G1::generator(); let p_var = GVar::new_witness(cs.clone(), || Ok(p))?; From bded87265864afe7619f9eae06811a584c55f288 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 22:09:08 +0800 Subject: [PATCH 08/99] Remove referenceable --- .../src/arithmetizations/ccs/mod.rs | 8 +-- crates/primitives/src/arithmetizations/mod.rs | 12 ++-- .../src/arithmetizations/r1cs/mod.rs | 61 ++++++++----------- crates/primitives/src/relations/mod.rs | 20 +----- 4 files changed, 38 insertions(+), 63 deletions(-) diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 9fa101602..ac97cb9f7 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -135,14 +135,14 @@ impl Arith for CCS { } } -impl ArithRelation, Vec> for CCS { +impl, U: AsRef<[F]>> ArithRelation for CCS { type Evaluation = Vec; - fn eval_relation(&self, w: &[F], u: &[F]) -> Result { - self.eval_assignments((F::one(), u, w).into()) + fn eval_relation(&self, w: &W, u: &U) -> Result { + self.eval_assignments((F::one(), u.as_ref(), w.as_ref()).into()) } - fn check_evaluation(_w: &[F], _u: &[F], e: Self::Evaluation) -> Result<(), Error> { + fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { cfg_into_iter!(e) .all(|i| i.is_zero()) .then_some(()) diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 5567ef644..21cdc5cca 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -1,7 +1,7 @@ use ark_relations::gr1cs::SynthesisError; use thiserror::Error; -use crate::relations::{Referenceable, Relation}; +use crate::relations::{Relation}; pub mod ccs; pub mod r1cs; @@ -71,7 +71,7 @@ pub trait Arith: Clone { /// This is also the case of CCS, where `W` and `U` may be vectors of field /// elements, [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::lcccs::LCCCS`], /// or [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::cccs::CCCS`]. -pub trait ArithRelation: Arith { +pub trait ArithRelation: Arith { type Evaluation; /// Evaluates the constraint system `self` at witness `w` and instance `u`. @@ -90,7 +90,7 @@ pub trait ArithRelation: Arith { /// /// However, we use `Self::Evaluation` to represent the evaluation result /// for future extensibility. - fn eval_relation(&self, w: W::Ref<'_>, u: U::Ref<'_>) -> Result; + fn eval_relation(&self, w: &W, u: &U) -> Result; /// Checks if the evaluation result is valid. The witness `w` and instance /// `u` are also parameters, because the validity check may need information @@ -106,10 +106,10 @@ pub trait ArithRelation: Arith { /// - The evaluation `v` of relaxed R1CS in ProtoGalaxy at satisfying `W` /// and `U` should satisfy `e = Σ pow_i(β) v_i`, where `e` is the error /// term in the committed instance. - fn check_evaluation(w: W::Ref<'_>, u: U::Ref<'_>, v: Self::Evaluation) -> Result<(), Error>; + fn check_evaluation(w: &W, u: &U, v: Self::Evaluation) -> Result<(), Error>; } -impl> Relation for A { +impl> Relation for A { type Error = Error; /// Checks if witness `w` and instance `u` satisfy the constraint system @@ -117,7 +117,7 @@ impl> Relation /// validity of the evaluation result. /// /// Used only for testing. - fn check_relation(&self, w: W::Ref<'_>, u: U::Ref<'_>) -> Result<(), Self::Error> { + fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error> { let e = self.eval_relation(w, u)?; Self::check_evaluation(w, u, e) } diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 76eae96ce..9e39b7585 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -1,13 +1,15 @@ +use std::ops::Index; use ark_ff::Field; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{cfg_into_iter, cfg_iter}; +use ark_std::iterable::Iterable; #[cfg(feature = "parallel")] use rayon::prelude::*; use crate::{ circuits::{Assignments, ConstraintSystemExt}, - relations::{Referenceable, WitnessInstanceExtractor}, + relations::{WitnessInstanceExtractor}, traits::Dummy, }; @@ -142,14 +144,14 @@ impl TryFrom> for R1CS { } } -impl ArithRelation, Vec> for R1CS { +impl, U: AsRef<[F]>> ArithRelation for R1CS { type Evaluation = Vec; - fn eval_relation(&self, w: &[F], x: &[F]) -> Result { + fn eval_relation(&self, w: &W, x: &U) -> Result { self.eval_assignments((F::one(), x.as_ref(), w.as_ref()).into()) } - fn check_evaluation(_w: &[F], _x: &[F], e: Self::Evaluation) -> Result<(), Error> { + fn check_evaluation(_w: &W, _x: &U, e: Self::Evaluation) -> Result<(), Error> { cfg_into_iter!(e) .all(|i| i.is_zero()) .then_some(()) @@ -168,49 +170,33 @@ impl WitnessInstanceExtractor, Vec> for R1CS { } } -pub struct RelaxedWitness { - pub w: Vec, - pub e: Vec, +pub struct RelaxedWitness { + pub w: V, + pub e: V, } -impl Referenceable for RelaxedWitness { - type Ref<'a> = (&'a [F], &'a [F]); - - fn reference(&self) -> Self::Ref<'_> { - (&self.w, &self.e) - } +pub struct RelaxedInstance { + pub x: V, + pub u: V::Item, } -pub struct RelaxedInstance { - pub x: Vec, - pub u: F, -} - -impl Referenceable for RelaxedInstance { - type Ref<'a> = (&'a [F], F); - - fn reference(&self) -> Self::Ref<'_> { - (&self.x, self.u) - } -} - -impl ArithRelation, RelaxedInstance> for R1CS { +impl ArithRelation, RelaxedInstance<&[F]>> for R1CS { type Evaluation = Vec; fn eval_relation( &self, - (w, _e): (&[F], &[F]), - (x, u): (&[F], F), + w: &RelaxedWitness<&[F]>, + u: &RelaxedInstance<&[F]>, ) -> Result { - self.eval_assignments((u, x, w).into()) + self.eval_assignments((*u.u, u.x, w.w).into()) } fn check_evaluation( - (_w, e): (&[F], &[F]), - _: (&[F], F), + w: &RelaxedWitness<&[F]>, + _u: &RelaxedInstance<&[F]>, v: Self::Evaluation, ) -> Result<(), Error> { - cfg_iter!(e) + cfg_iter!(w.e) .zip(&v) .all(|(e, v)| e == v) .then_some(()) @@ -220,11 +206,16 @@ impl ArithRelation, RelaxedInstance> for R1CS } } -impl WitnessInstanceExtractor, RelaxedInstance> for R1CS { +impl WitnessInstanceExtractor>, RelaxedInstance>> + for R1CS +{ type Source = Assignments>; type Error = Error; - fn extract(&self, z: Self::Source) -> Result<(RelaxedWitness, RelaxedInstance), Error> { + fn extract( + &self, + z: Self::Source, + ) -> Result<(RelaxedWitness>, RelaxedInstance>), Error> { let (w, x) = self.extract(z)?; let e = vec![F::zero(); self.n_constraints()]; Ok((RelaxedWitness { w, e }, RelaxedInstance { x, u: F::one() })) diff --git a/crates/primitives/src/relations/mod.rs b/crates/primitives/src/relations/mod.rs index c9349c2dd..6c3241700 100644 --- a/crates/primitives/src/relations/mod.rs +++ b/crates/primitives/src/relations/mod.rs @@ -2,27 +2,11 @@ use ark_std::{error::Error, rand::RngCore}; use crate::traits::Dummy; -pub trait Referenceable { - type Ref<'a>: Copy - where - Self: 'a; - - fn reference(&self) -> Self::Ref<'_>; -} - -impl Referenceable for Vec { - type Ref<'a> = &'a [T]; - - fn reference(&self) -> Self::Ref<'_> { - self - } -} - -pub trait Relation { +pub trait Relation { type Error: Error; /// Checks if witness `w` and instance `u` satisfy the relation `self` - fn check_relation(&self, w: W::Ref<'_>, u: U::Ref<'_>) -> Result<(), Self::Error>; + fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error>; } pub trait WitnessInstanceExtractor { From 7df4760b7cb3bce225a2a52b8debc372b4f98841 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 25 Oct 2025 23:27:13 +0800 Subject: [PATCH 09/99] Cleanup --- crates/primitives/src/algebra/field/mod.rs | 2 +- .../primitives/src/algebra/field/nonnative.rs | 4 +- .../src/algebra/field/nonnative2.rs | 59 ++++++++++++++----- crates/primitives/src/algebra/group/mod.rs | 20 +++---- .../primitives/src/algebra/group/nonnative.rs | 11 ++-- crates/primitives/src/algebra/ops/matrix.rs | 4 +- crates/primitives/src/arithmetizations/mod.rs | 2 +- .../src/arithmetizations/r1cs/mod.rs | 6 +- crates/primitives/src/commitments/mod.rs | 28 +++++++-- crates/primitives/src/commitments/pedersen.rs | 11 ++-- .../primitives/src/transcripts/absorbable.rs | 3 - crates/primitives/src/transcripts/poseidon.rs | 23 ++++---- 12 files changed, 104 insertions(+), 69 deletions(-) diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 4f836b807..ce2d734f4 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -8,7 +8,7 @@ use crate::{ transcripts::{Absorbable, AbsorbableGadget}, }; -pub mod nonnative; +// pub mod nonnative; pub mod nonnative2; /// `Field` trait is a wrapper around `PrimeField` that also includes the diff --git a/crates/primitives/src/algebra/field/nonnative.rs b/crates/primitives/src/algebra/field/nonnative.rs index ef051fef1..006d8fd0f 100644 --- a/crates/primitives/src/algebra/field/nonnative.rs +++ b/crates/primitives/src/algebra/field/nonnative.rs @@ -1101,12 +1101,10 @@ impl MatrixGadget> for SparseMatrixVar { _cfg: PhantomData, - limbs: Vec>, - bounds: Vec, + pub limbs: Vec>, + pub bounds: Vec, } pub type BigIntVar = IntVarInner; @@ -542,10 +542,40 @@ impl TryFrom { -// Aligned(UintVarInner), -// Unaligned(UintVarInner), -// } +impl EqGadget for IntVarInner { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + let mut result = Boolean::TRUE; + if self.limbs.len() != other.limbs.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.bounds.len() != other.bounds.len() { + return Err(SynthesisError::Unsatisfiable); + } + for i in 0..self.limbs.len() { + if self.bounds[i] != other.bounds[i] { + return Err(SynthesisError::Unsatisfiable); + } + result &= self.limbs[i].is_eq(&other.limbs[i])?; + } + Ok(result) + } + + fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { + if self.limbs.len() != other.limbs.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.bounds.len() != other.bounds.len() { + return Err(SynthesisError::Unsatisfiable); + } + for i in 0..self.limbs.len() { + if self.bounds[i] != other.bounds[i] { + return Err(SynthesisError::Unsatisfiable); + } + self.limbs[i].enforce_equal(&other.limbs[i])?; + } + Ok(()) + } +} impl FromBitsGadget for IntVarInner { fn from_bits_le(bits: &[Boolean]) -> Result { @@ -585,8 +615,9 @@ impl AbsorbableGadget> for IntVarInner VectorGadget> for [IntVarInner] { +impl VectorGadget> + for [IntVarInner] +{ fn add(&self, other: &Self) -> Result>, SynthesisError> { self.iter() .zip(other.iter()) @@ -609,7 +640,9 @@ impl VectorGadget> for [IntVarIn } } -impl MatrixGadget> for SparseMatrixVar> { +impl MatrixGadget> + for SparseMatrixVar> +{ fn mul_vector( &self, v: &impl Index>, @@ -639,7 +672,7 @@ impl MatrixGadget> for SparseM }) .collect::>(), ) - .filter_safe::() + .filter_safe::() }) .collect::>>() .ok_or(SynthesisError::Unsatisfiable)?; @@ -947,12 +980,10 @@ impl_assignment_op!( #[cfg(test)] mod tests { - use std::error::Error; - use ark_ff::Field; use ark_pallas::{Fq, Fr}; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{test_rng, UniformRand}; + use ark_std::{error::Error, test_rng, UniformRand}; use num_bigint::RandBigInt; use super::*; diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 1f04e1472..3f4b0fb34 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -14,15 +14,11 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::SynthesisError; use ark_std::mem::swap; -use num_bigint::{BigInt, BigUint, Sign}; +use num_bigint::{BigInt, Sign}; use num_integer::Integer; -use crate::algebra::field::nonnative2::IntVarInner; use crate::{ - algebra::field::{ - nonnative::{Bound, NonNativeUintVar}, - SonobeField, - }, + algebra::field::SonobeField, traits::{Inputize, InputizeNonNative}, transcripts::{Absorbable, AbsorbableGadget}, }; @@ -162,12 +158,12 @@ impl PointScalarMulGadget> for C { let b_is_negative = Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(b_sign == Sign::Minus))?; - let a = NonNativeUintVar::new_variable_with_inferred_mode(cs.clone(), || { - Ok((a_abs.into(), Bound::new_ub(m_sqrt.clone()))) - })?; - let b = NonNativeUintVar::new_variable_with_inferred_mode(cs, || { - Ok((b_abs.into(), Bound::new_ub(m_sqrt))) - })?; + // let a = NonNativeUintVar::new_variable_with_inferred_mode(cs.clone(), || { + // Ok((a_abs.into(), Bound::new_ub(m_sqrt.clone()))) + // })?; + // let b = NonNativeUintVar::new_variable_with_inferred_mode(cs, || { + // Ok((b_abs.into(), Bound::new_ub(m_sqrt))) + // })?; todo!() } diff --git a/crates/primitives/src/algebra/group/nonnative.rs b/crates/primitives/src/algebra/group/nonnative.rs index 4663ba43d..fd4a11bd3 100644 --- a/crates/primitives/src/algebra/group/nonnative.rs +++ b/crates/primitives/src/algebra/group/nonnative.rs @@ -12,17 +12,18 @@ use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; use ark_std::borrow::Borrow; use crate::{ - algebra::{field::nonnative::NonNativeUintVar, group::SonobeCurve}, + algebra::{group::SonobeCurve}, transcripts::AbsorbableGadget, }; +use crate::algebra::field::nonnative2::BigIntVar; /// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native /// field, over the constraint field. It is not intended to perform operations, but just to contain /// the affine coordinates in order to perform hash operations of the point. #[derive(Debug, Clone)] pub struct NonNativeAffineVar { - pub x: NonNativeUintVar, - pub y: NonNativeUintVar, + pub x: BigIntVar, + pub y: BigIntVar, } impl AllocVar for NonNativeAffineVar { @@ -37,8 +38,8 @@ impl AllocVar for NonNativeAffineVar { let affine = val.borrow().into_affine(); let (x, y) = affine.xy().unwrap_or_default(); - let x = NonNativeUintVar::new_variable(cs.clone(), || Ok(x), mode)?; - let y = NonNativeUintVar::new_variable(cs.clone(), || Ok(y), mode)?; + let x = BigIntVar::new_variable(cs.clone(), || Ok(x), mode)?; + let y = BigIntVar::new_variable(cs.clone(), || Ok(y), mode)?; Ok(Self { x, y }) }) diff --git a/crates/primitives/src/algebra/ops/matrix.rs b/crates/primitives/src/algebra/ops/matrix.rs index bc9d524f3..38ed5d44c 100644 --- a/crates/primitives/src/algebra/ops/matrix.rs +++ b/crates/primitives/src/algebra/ops/matrix.rs @@ -5,9 +5,7 @@ use ark_r1cs_std::{ GR1CSVar, }; use ark_relations::gr1cs::{Matrix, Namespace, SynthesisError}; -use ark_std::borrow::Borrow; - -use std::ops::Index; +use ark_std::{borrow::Borrow, ops::Index}; pub trait MatrixGadget { fn mul_vector(&self, v: &impl Index) -> Result, SynthesisError>; diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 21cdc5cca..01a51d8a8 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -1,7 +1,7 @@ use ark_relations::gr1cs::SynthesisError; use thiserror::Error; -use crate::relations::{Relation}; +use crate::relations::Relation; pub mod ccs; pub mod r1cs; diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 9e39b7585..00a75740c 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -1,15 +1,13 @@ -use std::ops::Index; use ark_ff::Field; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{cfg_into_iter, cfg_iter}; -use ark_std::iterable::Iterable; +use ark_std::{cfg_into_iter, cfg_iter, iterable::Iterable}; #[cfg(feature = "parallel")] use rayon::prelude::*; use crate::{ circuits::{Assignments, ConstraintSystemExt}, - relations::{WitnessInstanceExtractor}, + relations::WitnessInstanceExtractor, traits::Dummy, }; diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 18da7f641..a8b1a9b13 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,7 +1,8 @@ use ark_ff::Field; -use ark_r1cs_std::alloc::AllocVar; -use ark_relations::gr1cs::SynthesisError; +use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; +use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::{ + borrow::Borrow, fmt::Debug, iter::Sum, ops::{Add, Mul}, @@ -15,7 +16,8 @@ pub mod pedersen; #[derive(Debug, Error)] pub enum Error { // Commitment errors - #[error("The message being committed to has length {1}, exceeding the maximum supported length of {0}")] + #[error("The message being committed to has length {1}, exceeding the maximum supported length of {0}" + )] MessageTooLong(usize, usize), #[error("Blinding factor not 0 for Commitment without hiding")] BlindingNotZero, @@ -62,9 +64,11 @@ pub trait VectorCommitment: 'static + Debug + PartialEq { pub trait VectorCommitmentGadget { type Native: VectorCommitment; + type ConstraintField: Field; type KeyVar; type ScalarVar: Clone + + AllocVar<::Scalar, Self::ConstraintField> + Add + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> + Mul @@ -79,8 +83,12 @@ pub trait VectorCommitmentGadget { + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> + Mul + for<'a> Mul<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar>; - type CommitmentVar: Clone; - type RandomnessVar; + type CommitmentVar: Clone + + AllocVar<::Commitment, Self::ConstraintField>; + type RandomnessVar: AllocVar< + ::Randomness, + Self::ConstraintField, + >; fn open( ck: &Self::KeyVar, @@ -131,6 +139,16 @@ impl Sum for Null { } } +impl AllocVar for Null { + fn new_variable>( + _cs: impl Into>, + _f: impl FnOnce() -> Result, + _mode: AllocationMode, + ) -> Result { + Ok(Self) + } +} + #[cfg(test)] mod tests { use ark_ff::UniformRand; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 1716a1d53..bac75c6e8 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -18,13 +18,12 @@ use ark_relations::gr1cs::SynthesisError; use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; use super::{Error, VectorCommitment}; +use crate::traits::CF1; use crate::{ - algebra::field::{nonnative::NonNativeUintVar, nonnative2::IntVarInner}, + algebra::field::nonnative2::NonNativeFieldVar, commitments::{Null, VectorCommitmentGadget}, - traits::{CF2, SonobeCurve}, + traits::{SonobeCurve, CF2}, }; -use crate::algebra::field::nonnative2::NonNativeFieldVar; -use crate::traits::CF1; #[derive(Debug, PartialEq)] pub struct Pedersen { @@ -225,6 +224,7 @@ impl PedersenGadget { impl VectorCommitmentGadget for PedersenGadget { type Native = Pedersen; + type ConstraintField = CF2; type KeyVar = Vec; @@ -234,7 +234,7 @@ impl VectorCommitmentGadget for PedersenGadget { type CommitmentVar = C::Var; - type RandomnessVar = (); + type RandomnessVar = Null; fn open( ck: &Self::KeyVar, @@ -254,6 +254,7 @@ impl VectorCommitmentGadget for PedersenGadget { impl VectorCommitmentGadget for PedersenGadget { type Native = Pedersen; + type ConstraintField = CF2; type KeyVar = (Vec, C::Var); diff --git a/crates/primitives/src/transcripts/absorbable.rs b/crates/primitives/src/transcripts/absorbable.rs index c19f7660e..31c80dea7 100644 --- a/crates/primitives/src/transcripts/absorbable.rs +++ b/crates/primitives/src/transcripts/absorbable.rs @@ -1,7 +1,4 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::SynthesisError; -use ark_std::{any::TypeId, mem::transmute_copy}; pub trait Absorbable { fn absorb_into(&self, dest: &mut Vec); diff --git a/crates/primitives/src/transcripts/poseidon.rs b/crates/primitives/src/transcripts/poseidon.rs index 3607e95e7..1b10ef38a 100644 --- a/crates/primitives/src/transcripts/poseidon.rs +++ b/crates/primitives/src/transcripts/poseidon.rs @@ -1,5 +1,3 @@ -use std::mem::transmute_copy; - use ark_crypto_primitives::sponge::{ constraints::CryptographicSpongeVar, poseidon::{ @@ -10,9 +8,10 @@ use ark_crypto_primitives::sponge::{ use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; +use ark_relations::gr1cs::SynthesisError; +use ark_std::mem::transmute_copy; -use crate::transcripts::{Absorbable, FieldElementSize}; +use crate::transcripts::Absorbable; use super::{AbsorbableGadget, Transcript, TranscriptVar}; @@ -104,7 +103,7 @@ pub mod tests { alloc::AllocVar, groups::curves::short_weierstrass::ProjectiveVar, GR1CSVar, }; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{error::Error, test_rng}; + use ark_std::{error::Error, str::FromStr, test_rng}; use crate::algebra::group::nonnative::NonNativeAffineVar; @@ -113,8 +112,6 @@ pub mod tests { // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 #[test] fn check_against_circom_poseidon() -> Result<(), Box> { - use std::str::FromStr; - let config = poseidon_canonical_config::(); let mut poseidon_sponge: PoseidonSponge<_> = CryptographicSponge::new(&config); let v = vec![1, 2, 3, 4] @@ -123,12 +120,12 @@ pub mod tests { .collect::>(); poseidon_sponge.add(&v); poseidon_sponge.get_field_elements(1); - assert!( - poseidon_sponge.state[0] - == Fr::from_str( - "18821383157269793795438455681495246036402687001665670618754263018637548127333" - ) - .unwrap() + assert_eq!( + poseidon_sponge.state[0], + Fr::from_str( + "18821383157269793795438455681495246036402687001665670618754263018637548127333" + ) + .unwrap() ); Ok(()) } From 3101c7fde992d684bde58b7ef51352f736c4b3ea Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 27 Oct 2025 17:19:35 +0800 Subject: [PATCH 10/99] Better design & convenient traits and utils --- crates/primitives/Cargo.toml | 1 + .../field/{nonnative2.rs => emulated.rs} | 229 ++- crates/primitives/src/algebra/field/mod.rs | 26 +- .../primitives/src/algebra/field/nonnative.rs | 1232 ----------------- .../group/{nonnative.rs => emulated.rs} | 79 +- crates/primitives/src/algebra/group/mod.rs | 123 +- crates/primitives/src/algebra/mod.rs | 15 + crates/primitives/src/algebra/ops/bits.rs | 6 +- crates/primitives/src/algebra/ops/mod.rs | 1 + crates/primitives/src/algebra/ops/pow.rs | 30 + crates/primitives/src/algebra/ops/rlc.rs | 8 +- .../src/arithmetizations/ccs/circuits.rs | 10 +- .../src/arithmetizations/ccs/mod.rs | 209 ++- crates/primitives/src/arithmetizations/mod.rs | 50 +- .../src/arithmetizations/r1cs/circuits.rs | 5 +- .../src/arithmetizations/r1cs/mod.rs | 236 ++-- crates/primitives/src/circuits/mod.rs | 21 +- crates/primitives/src/circuits/utils.rs | 69 +- crates/primitives/src/commitments/mod.rs | 123 +- crates/primitives/src/commitments/pedersen.rs | 107 +- crates/primitives/src/lib.rs | 1 + crates/primitives/src/sumcheck/circuits.rs | 117 ++ crates/primitives/src/sumcheck/mod.rs | 146 +- crates/primitives/src/sumcheck/utils.rs | 104 +- crates/primitives/src/traits.rs | 22 +- .../primitives/src/transcripts/absorbable.rs | 70 +- .../primitives/src/transcripts/griffin/mod.rs | 567 ++++++++ .../src/transcripts/griffin/sponge.rs | 471 +++++++ crates/primitives/src/transcripts/mod.rs | 99 +- .../src/transcripts/poseidon/mod.rs | 37 + .../{poseidon.rs => poseidon/sponge.rs} | 110 +- crates/primitives/src/utils/mod.rs | 2 + crates/primitives/src/utils/null.rs | 75 + crates/primitives/src/utils/vec.rs | 109 ++ 34 files changed, 2535 insertions(+), 1975 deletions(-) rename crates/primitives/src/algebra/field/{nonnative2.rs => emulated.rs} (85%) delete mode 100644 crates/primitives/src/algebra/field/nonnative.rs rename crates/primitives/src/algebra/group/{nonnative.rs => emulated.rs} (65%) create mode 100644 crates/primitives/src/algebra/ops/pow.rs create mode 100644 crates/primitives/src/sumcheck/circuits.rs create mode 100644 crates/primitives/src/transcripts/griffin/mod.rs create mode 100644 crates/primitives/src/transcripts/griffin/sponge.rs create mode 100644 crates/primitives/src/transcripts/poseidon/mod.rs rename crates/primitives/src/transcripts/{poseidon.rs => poseidon/sponge.rs} (71%) create mode 100644 crates/primitives/src/utils/mod.rs create mode 100644 crates/primitives/src/utils/null.rs create mode 100644 crates/primitives/src/utils/vec.rs diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 7bdb8fc6f..bf252b6a2 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -19,6 +19,7 @@ num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } num-traits = { workspace = true } rayon = { workspace = true } +sha3 = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/crates/primitives/src/algebra/field/nonnative2.rs b/crates/primitives/src/algebra/field/emulated.rs similarity index 85% rename from crates/primitives/src/algebra/field/nonnative2.rs rename to crates/primitives/src/algebra/field/emulated.rs index b854d1daf..73a5ec9cc 100644 --- a/crates/primitives/src/algebra/field/nonnative2.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -12,6 +12,7 @@ use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{ borrow::Borrow, cmp::{max, min}, + fmt::Debug, marker::PhantomData, ops::Index, }; @@ -19,12 +20,11 @@ use num_bigint::{BigInt, BigUint, Sign}; use num_integer::Integer; use num_traits::Signed; -use crate::algebra::ops::bits::{FromBitsGadget, ToBitsGadgetExt}; use crate::{ algebra::{ field::SonobeField, ops::{ - eq::EquivalenceGadget, + bits::{FromBitsGadget, ToBitsGadgetExt}, matrix::{MatrixGadget, SparseMatrixVar}, vector::VectorGadget, }, @@ -79,6 +79,20 @@ impl Bound { } } +fn compose>(limbs: V) -> BigInt { + let mut r = BigInt::zero(); + + for &limb in limbs.borrow().iter().rev() { + r <<= F::BITS_PER_LIMB; + r += if limb.into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO { + BigInt::from_biguint(Sign::Minus, (-limb).into()) + } else { + BigInt::from_biguint(Sign::Plus, limb.into()) + }; + } + r +} + #[derive(Debug, Clone)] pub struct IntVarInner { _cfg: PhantomData, @@ -86,11 +100,10 @@ pub struct IntVarInner { pub bounds: Vec, } -pub type BigIntVar = IntVarInner; -pub type NonNativeFieldVar = - IntVarInner; +pub type BigIntVar = IntVarInner; +pub type EmulatedFieldVar = IntVarInner; -impl GR1CSVar for IntVarInner { +impl GR1CSVar for IntVarInner { type Value = BigInt; fn cs(&self) -> ConstraintSystemRef { @@ -98,23 +111,33 @@ impl GR1CSVar for IntVarInner Result { - let mut r = BigInt::zero(); + self.limbs.value().map(compose) + } +} - for limb in self.limbs.value()?.into_iter().rev() { - r <<= F::BITS_PER_LIMB; - r += if limb.into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO { - BigInt::from_biguint(Sign::Minus, (-limb).into()) - } else { - BigInt::from_biguint(Sign::Plus, limb.into()) - }; - } +impl GR1CSVar + for IntVarInner +{ + type Value = Target; - Ok(r) + fn cs(&self) -> ConstraintSystemRef { + self.limbs.cs() + } + + fn value(&self) -> Result { + self.limbs.value().map(compose).map(|v| { + let (sign, abs) = v.into_parts(); + assert!(abs < Target::MODULUS.into()); + match sign { + Sign::Plus | Sign::NoSign => Target::from(abs), + Sign::Minus => Target::zero() - Target::from(abs), + } + }) } } impl IntVarInner { - fn new(limbs: Vec>, bounds: Vec) -> Self { + pub fn new(limbs: Vec>, bounds: Vec) -> Self { Self { _cfg: PhantomData, limbs, @@ -483,7 +506,7 @@ impl IntVarInner +impl IntVarInner { /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. @@ -493,10 +516,16 @@ impl // Provide the quotient and remainder as hints let q = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); - Ok((self.value()?.div_floor(&m), Bound(lb, ub))) + Ok(( + compose(self.limbs.value().unwrap_or_default()).div_floor(&m), + Bound(lb, ub), + )) })?; let r = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { - Ok((self.value()?.abs() % &m, Bound(Zero::zero(), m.clone()))) + Ok(( + compose(self.limbs.value().unwrap_or_default()).abs() % &m, + Bound(Zero::zero(), m.clone()), + )) })?; let m = IntVarInner::constant(m); @@ -521,7 +550,10 @@ impl // Provide the quotient as hint let q = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); - Ok((self.value()?.div_floor(&m), Bound(lb, ub))) + Ok(( + compose(self.limbs.value().unwrap_or_default()).div_floor(&m), + Bound(lb, ub), + )) })?; let m = IntVarInner::constant(m); @@ -532,7 +564,7 @@ impl } } -impl TryFrom> +impl TryFrom> for IntVarInner { type Error = SynthesisError; @@ -575,22 +607,81 @@ impl EqGadget for IntVarInner { } Ok(()) } + + fn enforce_not_equal(&self, other: &Self) -> Result<(), SynthesisError> { + if self.limbs.len() != other.limbs.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.bounds.len() != other.bounds.len() { + return Err(SynthesisError::Unsatisfiable); + } + for i in 0..self.limbs.len() { + if self.bounds[i] != other.bounds[i] { + return Err(SynthesisError::Unsatisfiable); + } + self.limbs[i].enforce_not_equal(&other.limbs[i])?; + } + Ok(()) + } + + fn conditional_enforce_equal( + &self, + other: &Self, + should_enforce: &Boolean, + ) -> Result<(), SynthesisError> { + if should_enforce.is_constant() { + if should_enforce.value()? { + return self.enforce_equal(other); + } else { + return self.enforce_not_equal(other); + } + } + self.is_eq(other)? + .conditional_enforce_equal(&Boolean::TRUE, should_enforce) + } } impl FromBitsGadget for IntVarInner { - fn from_bits_le(bits: &[Boolean]) -> Result { + fn from_bits_le(bits: &[Boolean], bound: Bound) -> Result { Ok(Self::new( bits.chunks(F::BITS_PER_LIMB) .map(Boolean::le_bits_to_fp) .collect::>()?, - bits.chunks(F::BITS_PER_LIMB) - .map(|i| Bound(BigInt::zero(), (BigInt::one() << i.len()) - BigInt::one())) - .collect(), + compute_bounds(&bound.0, &bound.1, F::BITS_PER_LIMB), )) } } -impl ToBitsGadget for IntVarInner { +impl CondSelectGadget for IntVarInner { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.limbs.len() != false_value.limbs.len() { + return Err(SynthesisError::Unsatisfiable); + } + if true_value.bounds.len() != false_value.bounds.len() { + return Err(SynthesisError::Unsatisfiable); + } + let mut limbs = vec![]; + let mut bounds = vec![]; + for i in 0..true_value.limbs.len() { + if true_value.bounds[i] != false_value.bounds[i] { + return Err(SynthesisError::Unsatisfiable); + } + limbs.push(cond.select(&true_value.limbs[i], &false_value.limbs[i])?); + bounds.push(true_value.bounds[i].clone()); + } + Ok(Self { + _cfg: PhantomData, + limbs, + bounds, + }) + } +} + +impl ToBitsGadget for IntVarInner { fn to_bits_le(&self) -> Result>, SynthesisError> { for bound in &self.bounds { assert!(bound.0 >= BigInt::zero()); @@ -605,13 +696,13 @@ impl ToBitsGadget for IntVarInner { } } -impl AbsorbableGadget> for IntVarInner { +impl AbsorbableGadget for IntVarInner { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; self.to_bits_le()? .chunks(bits_per_limb) - .try_for_each(|i| Ok(dest.push(Boolean::le_bits_to_fp(i)?))) + .try_for_each(|i| Boolean::le_bits_to_fp(i).map(|v| dest.push(v))) } } @@ -694,6 +785,34 @@ impl MatrixGadget> } } +pub fn compute_bounds(lb: &BigInt, ub: &BigInt, bits_per_limb: usize) -> Vec { + let len = max(lb.bits(), ub.bits()) as usize; + let (n_full_limbs, n_remaining_bits) = len.div_rem(&bits_per_limb); + + let mut bounds = vec![ + Bound( + if lb.is_negative() { + BigInt::one() - (BigInt::one() << bits_per_limb) + } else { + BigInt::zero() + }, + if ub.is_positive() { + (BigInt::one() << bits_per_limb) - BigInt::one() + } else { + BigInt::zero() + }, + ); + n_full_limbs + ]; + + if !n_remaining_bits.is_zero() { + let d = BigInt::one() << (len - n_remaining_bits); + bounds.push(Bound(lb.div_floor(&d), ub.div_ceil(&d))); + } + + bounds +} + impl AllocVar<(BigInt, Bound), F> for IntVarInner { fn new_variable>( cs: impl Into>, @@ -728,8 +847,6 @@ impl AllocVar<(BigInt, Bound), F> for IntVarInner AllocVar<(BigInt, Bound), F> for IntVarInner>()?; - let mut bounds = vec![ - Bound( - if lb.is_negative() { - BigInt::one() - (BigInt::one() << F::BITS_PER_LIMB) - } else { - BigInt::zero() - }, - if ub.is_positive() { - (BigInt::one() << F::BITS_PER_LIMB) - BigInt::one() - } else { - BigInt::zero() - }, - ); - n_full_limbs - ]; - - if !n_remaining_bits.is_zero() { - let d = BigInt::one() << (len - n_remaining_bits); - bounds.push(Bound(lb.div_floor(&d), ub.div_ceil(&d))); - } + let bounds = compute_bounds(&lb, &ub, F::BITS_PER_LIMB); let var = Self::new(limbs, bounds); @@ -847,7 +945,7 @@ impl AllocVar for IntVarInner IntVarInner { - fn constant(x: BigInt) -> Self { + pub fn constant(x: BigInt) -> Self { Self::new_constant(ConstraintSystemRef::None, (x.clone(), Bound(x.clone(), x))).unwrap() } } @@ -1125,11 +1223,11 @@ mod tests { let aab = a * ab; let abb = ab * b; - let a_var = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(a))?; - let b_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(b))?; - let ab_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(ab))?; - let aab_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(aab))?; - let abb_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(abb))?; + let a_var = EmulatedFieldVar::::new_witness(cs.clone(), || Ok(a))?; + let b_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(b))?; + let ab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(ab))?; + let aab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(aab))?; + let abb_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(abb))?; a_var.mul_unaligned(&b_var)?.enforce_congruent(&ab_var)?; a_var.mul_unaligned(&ab_var)?.enforce_congruent(&aab_var)?; @@ -1147,17 +1245,14 @@ mod tests { let a = Fq::rand(rng); - let a_var = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(a))?; + let a_var = EmulatedFieldVar::::new_witness(cs.clone(), || Ok(a))?; let mut r_var = a_var.clone(); for _ in 0..16 { r_var = r_var.mul_unaligned(&r_var)?.modulo()?; } r_var = r_var.mul_unaligned(&a_var)?.modulo()?; - assert_eq!( - BigInt::from_biguint(Sign::Plus, a.pow([65537u64]).into()), - r_var.value()? - ); + assert_eq!(a.pow([65537u64]), r_var.value()?); assert!(cs.is_satisfied()?); Ok(()) } @@ -1173,12 +1268,12 @@ mod tests { let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); - let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; - let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; - let c_var = NonNativeFieldVar::new_witness(cs.clone(), || Ok(c))?; + let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; + let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; + let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?; - let mut r_var: NonNativeFieldVar = - NonNativeFieldVar::constant(BigUint::zero().into()).into(); + let mut r_var: EmulatedFieldVar = + EmulatedFieldVar::constant(BigUint::zero().into()).into(); for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { r_var = r_var.add_unaligned(&a.mul_unaligned(&b)?)?; } diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index ce2d734f4..4cd35cd6e 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -4,21 +4,19 @@ use ark_relations::gr1cs::SynthesisError; use ark_std::{any::TypeId, mem::transmute_copy}; use crate::{ - traits::{Inputize, InputizeNonNative}, + algebra::{field::emulated::EmulatedFieldVar, Val}, + traits::{Inputize, InputizeEmulated}, transcripts::{Absorbable, AbsorbableGadget}, }; -// pub mod nonnative; -pub mod nonnative2; +pub mod emulated; /// `Field` trait is a wrapper around `PrimeField` that also includes the /// necessary bounds for the field to be used conveniently in folding schemes. pub trait SonobeField: - PrimeField + Absorbable + Inputize + PrimeField + Absorbable + Inputize + Val> { const BITS_PER_LIMB: usize; - /// The in-circuit variable type for this field. - type Var: FieldVar; } impl, const N: usize> SonobeField for Fp { @@ -41,11 +39,17 @@ impl, const N: usize> SonobeField for Fp { // TODO: either make it a global const, or compute an optimal value // based on the modulus size. const BITS_PER_LIMB: usize = 55; // TODO: make this configurable +} + +impl, const N: usize> Val for Fp { + type ConstraintField = Self; type Var = FpVar; + + type EmulatedVar = EmulatedFieldVar; } -impl, const N: usize> Absorbable for Fp { - fn absorb_into(&self, dest: &mut Vec) { +impl, const N: usize> Absorbable for Fp { + fn absorb_into(&self, dest: &mut Vec) { if TypeId::of::() == TypeId::of::() { // Safe because `F` and `Self` have the same type // TODO (@winderica): specialization when??? @@ -67,7 +71,7 @@ impl, const N: usize> Absorbable for Fp { } } -impl AbsorbableGadget> for FpVar { +impl AbsorbableGadget for FpVar { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { dest.push(self.clone()); Ok(()) @@ -82,10 +86,10 @@ impl, const N: usize> Inputize for Fp { } } -impl InputizeNonNative for P { +impl InputizeEmulated for P { /// Returns the internal representation in the same order as how the value /// is allocated in `NonNativeUintVar::new_input`. - fn inputize_nonnative(&self) -> Vec { + fn inputize_emulated(&self) -> Vec { self.into_bigint() .to_bits_le() .chunks(F::BITS_PER_LIMB) diff --git a/crates/primitives/src/algebra/field/nonnative.rs b/crates/primitives/src/algebra/field/nonnative.rs deleted file mode 100644 index 006d8fd0f..000000000 --- a/crates/primitives/src/algebra/field/nonnative.rs +++ /dev/null @@ -1,1232 +0,0 @@ -use ark_ff::{BigInteger, One, PrimeField, Zero}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - convert::ToBitsGadget, - fields::{fp::FpVar, FieldVar}, - prelude::EqGadget, - select::CondSelectGadget, - GR1CSVar, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{ - borrow::Borrow, - cmp::{max, min}, - ops::Index, -}; -use num_bigint::{BigInt, BigUint, Sign}; -use num_integer::Integer; -use num_traits::Signed; - -use crate::algebra::ops::bits::{FromBitsGadget, ToBitsGadgetExt}; -use crate::{ - algebra::{ - field::SonobeField, - ops::{ - eq::EquivalenceGadget, - matrix::{MatrixGadget, SparseMatrixVar}, - vector::VectorGadget, - }, - }, - transcripts::AbsorbableGadget, -}; - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Bound { - pub lb: BigInt, - pub ub: BigInt, -} - -impl Bound { - pub fn zero() -> Self { - Self::default() - } - - pub fn new_ub(ub: BigInt) -> Self { - Self { - lb: BigInt::zero(), - ub, - } - } - - pub fn new_n_bits(n: usize) -> Self { - Self { - lb: BigInt::zero(), - ub: (BigInt::one() << n) - BigInt::one(), - } - } - - pub fn new(lb: BigInt, ub: BigInt) -> Self { - Self { lb, ub } - } -} - -impl Bound { - pub fn add(&self, other: &Self) -> Self { - Self { - lb: &self.lb + &other.lb, - ub: &self.ub + &other.ub, - } - } - - pub fn sub(&self, other: &Self) -> Self { - Self { - lb: &self.lb - &other.ub, - ub: &self.ub - &other.lb, - } - } - - pub fn add_many(limbs: &[Self]) -> Self { - Self { - lb: limbs.iter().map(|l| &l.lb).sum(), - ub: limbs.iter().map(|l| &l.ub).sum(), - } - } - - pub fn mul(&self, other: &Self) -> Self { - let ll = &self.lb * &other.lb; - let lu = &self.lb * &other.ub; - let ul = &self.ub * &other.lb; - let uu = &self.ub * &other.ub; - - Self { - lb: min(min(&ll, &lu), min(&ul, &uu)).clone(), - ub: max(max(&ll, &lu), max(&ul, &uu)).clone(), - } - } - - pub fn shl(&self, shift: usize) -> Self { - Self { - lb: &self.lb << shift, - ub: &self.ub << shift, - } - } - - pub fn filter_safe(self) -> Option { - let limit = BigInt::from_biguint(Sign::Plus, F::MODULUS_MINUS_ONE_DIV_TWO.into()); - (self.ub <= limit && self.lb >= -limit).then_some(self) - } -} - -// /// `LimbVar` represents a single limb of a non-native unsigned integer in the -// /// circuit. -// /// The limb value `v` should be small enough to fit into `FpVar`, and we also -// /// store an upper bound `ub` for the limb value, which is treated as a constant -// /// in the circuit and is used for efficient equality checks and some arithmetic -// /// operations. -// #[derive(Debug, Clone)] -// pub struct LimbVar { -// pub v: FpVar, -// pub lb: BigInt, -// pub ub: BigInt, -// } - -// impl]>> From for LimbVar { -// fn from(bits: B) -> Self { -// Self { -// // `Boolean::le_bits_to_fp` will return an error if the internal -// // invocation of `Boolean::enforce_in_field_le` fails. -// // However, this method is only called when the length of `bits` is -// // greater than `F::MODULUS_BIT_SIZE`, which should not happen in -// // our case where `bits` is guaranteed to be short. -// v: Boolean::le_bits_to_fp(bits.as_ref()).unwrap(), -// lb: BigInt::zero(), -// ub: (BigInt::one() << bits.as_ref().len()) - BigInt::one(), -// } -// } -// } - -// impl Default for LimbVar { -// fn default() -> Self { -// Self { -// v: FpVar::zero(), -// lb: BigInt::zero(), -// ub: BigInt::zero(), -// } -// } -// } - -// impl GR1CSVar for LimbVar { -// type Value = F; - -// fn cs(&self) -> ConstraintSystemRef { -// self.v.cs() -// } - -// fn value(&self) -> Result { -// self.v.value() -// } -// } - -// impl CondSelectGadget for LimbVar { -// fn conditionally_select( -// cond: &Boolean, -// true_value: &Self, -// false_value: &Self, -// ) -> Result { -// // We only allow selecting between two values with the same upper bound -// assert_eq!(true_value.lb, false_value.lb); -// assert_eq!(true_value.ub, false_value.ub); -// Ok(Self { -// v: cond.select(&true_value.v, &false_value.v)?, -// lb: true_value.lb.clone(), -// ub: true_value.ub.clone(), -// }) -// } -// } - -// impl LimbVar { -// /// Add two `LimbVar`s. -// /// Returns `None` if the upper bound of the sum is too large, i.e., -// /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. -// /// Otherwise, returns the sum as a `LimbVar`. -// pub fn add(&self, other: &Self) -> Option { -// let lbound = &self.lb + &other.lb; -// let ubound = &self.ub + &other.ub; -// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); -// if ubound > limit || lbound < -limit { -// None -// } else { -// Some(Self { -// v: &self.v + &other.v, -// lb: lbound, -// ub: ubound, -// }) -// } -// } - -// /// Add multiple `LimbVar`s. -// /// Returns `None` if the upper bound of the sum is too large, i.e., -// /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. -// /// Otherwise, returns the sum as a `LimbVar`. -// pub fn add_many(limbs: &[Self]) -> Option { -// let lbound = limbs.iter().map(|l| &l.lb).sum::(); -// let ubound = limbs.iter().map(|l| &l.ub).sum::(); -// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); -// if ubound > limit || lbound < -limit { -// None -// } else { -// Some(Self { -// v: if limbs.is_constant() { -// FpVar::constant(limbs.value().unwrap_or_default().into_iter().sum()) -// } else { -// limbs.iter().map(|l| &l.v).sum() -// }, -// lb: lbound, -// ub: ubound, -// }) -// } -// } - -// /// Multiply two `LimbVar`s. -// /// Returns `None` if the upper bound of the product is too large, i.e., -// /// greater than `F::MODULUS_MINUS_ONE_DIV_TWO`. -// /// Otherwise, returns the product as a `LimbVar`. -// pub fn mul(&self, other: &Self) -> Option { -// let ll = &self.lb * &other.lb; -// let lu = &self.lb * &other.ub; -// let ul = &self.ub * &other.lb; -// let uu = &self.ub * &other.ub; - -// let lbound = min(min(&ll, &lu), min(&ul, &uu)).clone(); -// let ubound = max(max(&ll, &lu), max(&ul, &uu)).clone(); -// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); -// if ubound > limit || lbound < -limit { -// None -// } else { -// Some(Self { -// v: &self.v * &other.v, -// lb: lbound, -// ub: ubound, -// }) -// } -// } - -// pub fn zero() -> Self { -// Self::default() -// } - -// pub fn constant(v: BigInt) -> Self { -// let (v_sign, v_abs) = v.clone().into_parts(); -// let limit = Into::::into(F::MODULUS_MINUS_ONE_DIV_TWO).into(); -// assert!(v_abs <= limit); -// Self { -// v: if v_sign == Sign::Minus { -// FpVar::constant(-F::from(v_abs)) -// } else { -// FpVar::constant(F::from(v_abs)) -// }, -// lb: v.clone(), -// ub: v, -// } -// } -// } - -// impl ToBitsGadget for LimbVar { -// fn to_bits_le(&self) -> Result>, SynthesisError> { -// let cs = self.cs(); - -// assert_eq!(self.lb, BigInt::zero()); - -// let bits = &self -// .v -// .value() -// .unwrap_or_default() -// .into_bigint() -// .to_bits_le()[..self.ub.bits() as usize]; -// let bits = if cs.is_none() { -// Vec::new_constant(cs, bits)? -// } else { -// Vec::new_witness(cs, || Ok(bits))? -// }; - -// Boolean::le_bits_to_fp(&bits)?.enforce_equal(&self.v)?; - -// Ok(bits) -// } -// } - -/// `NonNativeUintVar` represents a non-native unsigned integer (BigUint) in the -/// circuit. -/// We apply [xJsnark](https://akosba.github.io/papers/xjsnark.pdf)'s techniques -/// for efficient operations on `NonNativeUintVar`. -/// Note that `NonNativeUintVar` is different from arkworks' `NonNativeFieldVar` -/// in that the latter runs the expensive `reduce` (`align` + `modulo` in our -/// terminology) after each arithmetic operation, while the former only reduces -/// the integer when explicitly called. -#[derive(Debug, Clone)] -pub struct NonNativeUintVar { - pub(crate) limbs: Vec>, - bounds: Vec, -} - -impl AllocVar<(BigInt, Bound), F> for NonNativeUintVar { - fn new_variable>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - todo!(); - let cs = cs.into().cs(); - let v = f()?; - let (x, b) = v.borrow(); - - if x > &b.ub || x < &b.lb { - return Err(SynthesisError::DivisionByZero); - } - - let (x_sign, mut x_abs) = x.clone().into_parts(); - - let mut limbs = vec![]; - let mut bounds = vec![]; - - let l = max(b.lb.bits(), b.ub.bits()); - - if l == 0 { - return Ok(Self { limbs, bounds }); - } - - let (num_full_chunks, final_chunk_size) = (l as usize).div_rem(&F::BITS_PER_LIMB); - - let mask = (BigUint::one() << F::BITS_PER_LIMB) - BigUint::one(); - - loop { - let limb = FpVar::new_variable(cs.clone(), || Ok(F::from(&x_abs & &mask)), mode)?; - Self::enforce_bit_length(&limb, F::BITS_PER_LIMB)?; - limbs.push(limb); - bounds.push(Bound::new_n_bits(F::BITS_PER_LIMB)); - x_abs >>= F::BITS_PER_LIMB; - if x_abs.is_zero() { - let is_neg = Boolean::new_variable(cs.clone(), || Ok(x_sign == Sign::Minus), { - if b.lb >= BigInt::zero() || b.ub <= BigInt::zero() { - AllocationMode::Constant - } else { - mode - } - })?; - *limbs.last_mut().unwrap() *= - is_neg.select(&FpVar::one().negate()?, &FpVar::one())?; - break; - } - } - - if final_chunk_size > 0 { - let limb = FpVar::new_variable(cs.clone(), || Ok(F::from(x_abs)), mode)?; - Self::enforce_bit_length(&limb, F::BITS_PER_LIMB)?; - } - - for chunk in (0..l) - .map(|i| x.bit(i)) - .collect::>() - .chunks(F::BITS_PER_LIMB) - { - let limb = F::from(F::BigInt::from_bits_le(chunk)); - let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?; - Self::enforce_bit_length(&limb, chunk.len())?; - limbs.push(limb); - bounds.push(Bound::new_n_bits(chunk.len())); - } - let s = Boolean::new_variable(cs.clone(), || Ok(x.sign() == Sign::Minus), { - if b.lb >= BigInt::zero() || b.ub <= BigInt::zero() { - AllocationMode::Constant - } else { - mode - } - })?; - limbs[num_full_chunks] = - s.select(&limbs[num_full_chunks].negate()?, &limbs[num_full_chunks])?; - - let t = BigInt::one() << ((bounds.len() - 1) * F::BITS_PER_LIMB); - bounds[num_full_chunks] = Bound::new(b.lb.div_floor(&t), b.ub.div_ceil(&t)); - - Ok(Self { limbs, bounds }) - } -} - -impl AllocVar for NonNativeUintVar { - fn new_variable>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let cs = cs.into().cs(); - let v = f()?; - - let v = BigInt::from_biguint(Sign::Plus, v.borrow().clone().into()); - let m = BigInt::from_biguint(Sign::Plus, G::MODULUS.into()); - - match mode { - AllocationMode::Constant => Self::constant(v), - _ => Self::new_variable(cs, || Ok((v, Bound::new(BigInt::zero(), m))), mode), - } - } -} - -impl EqGadget for NonNativeUintVar { - fn is_eq(&self, other: &Self) -> Result, SynthesisError> { - let mut result = Boolean::TRUE; - if self.limbs.len() != other.limbs.len() { - return Err(SynthesisError::Unsatisfiable); - } - if self.bounds.len() != other.bounds.len() { - return Err(SynthesisError::Unsatisfiable); - } - for i in 0..self.limbs.len() { - if self.bounds[i] != other.bounds[i] { - return Err(SynthesisError::Unsatisfiable); - } - result &= self.limbs[i].is_eq(&other.limbs[i])?; - } - Ok(result) - } - - fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { - if self.limbs.len() != other.limbs.len() { - return Err(SynthesisError::Unsatisfiable); - } - if self.bounds.len() != other.bounds.len() { - return Err(SynthesisError::Unsatisfiable); - } - for i in 0..self.limbs.len() { - if self.bounds[i] != other.bounds[i] { - return Err(SynthesisError::Unsatisfiable); - } - self.limbs[i].enforce_equal(&other.limbs[i])?; - } - Ok(()) - } -} - -impl GR1CSVar for NonNativeUintVar { - type Value = BigInt; - - fn cs(&self) -> ConstraintSystemRef { - self.limbs.cs() - } - - fn value(&self) -> Result { - let mut r = BigInt::zero(); - - for limb in self.limbs.value()?.into_iter().rev() { - r <<= F::BITS_PER_LIMB; - r += if limb.into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO { - BigInt::from_biguint(Sign::Minus, (-limb).into()) - } else { - BigInt::from_biguint(Sign::Plus, limb.into()) - }; - } - - Ok(r) - } -} - -impl CondSelectGadget for NonNativeUintVar { - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - if true_value.limbs.len() != false_value.limbs.len() { - return Err(SynthesisError::Unsatisfiable); - } - if true_value.bounds.len() != false_value.bounds.len() { - return Err(SynthesisError::Unsatisfiable); - } - let mut v = vec![]; - let mut bounds = vec![]; - for i in 0..true_value.limbs.len() { - if true_value.bounds[i] != false_value.bounds[i] { - return Err(SynthesisError::Unsatisfiable); - } - v.push(cond.select(&true_value.limbs[i], &false_value.limbs[i])?); - bounds.push(true_value.bounds[i].clone()); - } - Ok(Self { - limbs: v, - bounds: bounds, - }) - } -} - -impl NonNativeUintVar { - fn constant(v: BigInt) -> Result { - Self::new_constant( - ConstraintSystemRef::None, - (v.clone(), Bound::new(v.clone(), v)), - ) - } - - fn ubound(&self) -> BigInt { - let mut r = BigInt::zero(); - - for i in self.bounds.iter().rev() { - r <<= F::BITS_PER_LIMB; - r += &i.ub; - } - - r - } - - fn lbound(&self) -> BigInt { - let mut r = BigInt::zero(); - - for i in self.bounds.iter().rev() { - r <<= F::BITS_PER_LIMB; - r += &i.lb; - } - - r - } -} - -impl NonNativeUintVar { - /// Enforce `self` to be less than `other`, where `self` and `other` should - /// be aligned. - /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 - pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> { - let len = max(self.limbs.len(), other.limbs.len()); - let zero = FpVar::zero(); - - // Compute the difference between limbs of `other` and `self`. - // Denote a positive limb by `+`, a negative limb by `-`, a zero limb by - // `0`, and an unknown limb by `?`. - // Then, for `self < other`, `delta` should look like: - // ? ? ... ? ? + 0 0 ... 0 0 - let delta = (0..len) - .map(|i| { - let x = self.limbs.get(i).unwrap_or(&zero); - let y = other.limbs.get(i).unwrap_or(&zero); - y - x - }) - .collect::>(); - - // `helper` is a vector of booleans that indicates if the corresponding - // limb of `delta` is the first (searching from MSB) positive limb. - // For example, if `delta` is: - // - + ... + - + 0 0 ... 0 0 - // <---- search in this direction -------- - // Then `helper` should be: - // F F ... F F T F F ... F F - let helper = { - let cs = self.cs().or(other.cs()); - let mut helper = vec![false; len]; - for i in (0..len).rev() { - let delta = delta[i].value().unwrap_or_default().into_bigint(); - if !delta.is_zero() && delta < F::MODULUS_MINUS_ONE_DIV_TWO { - helper[i] = true; - break; - } - } - Vec::>::new_variable_with_inferred_mode(cs, || Ok(helper))? - }; - - // `p` is the first positive limb in `delta`. - let mut p = FpVar::::zero(); - // `r` is the sum of all bits in `helper`, which should be 1 when `self` - // is less than `other`, as there should be more than one positive limb - // in `delta`, and thus exactly one true bit in `helper`. - let mut r = FpVar::zero(); - for (b, d) in helper.into_iter().zip(delta) { - // Choose the limb `d` only if `b` is true. - p += b.select(&d, &FpVar::zero())?; - // Either `r` or `d` should be zero. - // Consider the same example as above: - // - + ... + - + 0 0 ... 0 0 - // F F ... F F T F F ... F F - // |-----------| - // `r = 0` in this range (before/when we meet the first positive limb) - // |---------| - // `d = 0` in this range (after we meet the first positive limb) - // This guarantees that for every bit after the true bit in `helper`, - // the corresponding limb in `delta` is zero. - (&r * &d).enforce_equal(&FpVar::zero())?; - // Add the current bit to `r`. - r += FpVar::from(b); - } - - // Ensure that `r` is exactly 1. This guarantees that there is exactly - // one true value in `helper`. - r.enforce_equal(&FpVar::one())?; - // Ensure that `p` is positive, i.e., - // `0 <= p - 1 < 2^bits_per_limb < F::MODULUS_MINUS_ONE_DIV_TWO`. - // This guarantees that the true value in `helper` corresponds to a - // positive limb in `delta`. - Self::enforce_bit_length(&(p - FpVar::one()), F::BITS_PER_LIMB)?; - - Ok(()) - } - - /// Enforce `self` to be equal to `other`, where `self` and `other` are not - /// necessarily aligned. - /// - /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L562-L798 - /// Similar implementations can also be found in https://github.com/alex-ozdemir/bellman-bignat/blob/0585b9d90154603a244cba0ac80b9aafe1d57470/src/mp/bignat.rs#L566-L661 - /// and https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/emulated_fp/reduce.rs#L201-L323 - pub fn enforce_equal_unaligned(&self, other: &Self) -> Result<(), SynthesisError> { - let len = min(self.limbs.len(), other.limbs.len()); - - // Group the limbs of `self` and `other` so that each group nearly - // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. - // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` - // is the initial number of bits in a limb, just as what we do in grade - // school arithmetic, e.g., - // 5 9 - // x 7 3 - // ------------- - // 15 27 - // 35 63 - // ------------- <- When grouping 35, 15 + 63, and 27, we are computing - // 4 3 0 7 35 * 100 + (15 + 63) * 10 + 27 = 4307 - // Note that this is different from the concatenation `x_0 || x_1 ...`, - // since the bit-length of each limb is not necessarily the initial size - // `W`. - - let mut i = 0; - // `c` stores the current carry of `x_i - y_i` - let mut c = FpVar::::zero(); - while i < len { - let mut j = i; - // The current grouped limbs of `self` and `other`. - let mut p_limb = FpVar::zero(); - let mut q_limb = FpVar::zero(); - let mut p_bound = Bound::zero(); - let mut q_bound = Bound::zero(); - let mut step = 0; - let mut weight = F::one(); - while j < len { - match ( - self.bounds[j].shl(step).add(&p_bound).filter_safe::(), - other.bounds[j].shl(step).add(&q_bound).filter_safe::(), - ) { - (Some(new_p_bound), Some(new_q_bound)) => { - p_limb += &self.limbs[j] * weight; - q_limb += &other.limbs[j] * weight; - p_bound = new_p_bound; - q_bound = new_q_bound; - } - _ => break, - } - - j += 1; - step += F::BITS_PER_LIMB; - weight *= F::from(BigUint::one() << F::BITS_PER_LIMB); - } - // For each group, check the last `step_i` bits of `x_i` and `y_i` are - // equal. - // The intuition is to check `diff = x_i - y_i = 0 (mod 2^step_i)`. - // However, this is only true for `i = 0`, and we need to consider carry - // values `diff >> step_i` for `i > 0`. - // Therefore, we actually check `diff = x_i - y_i + c = 0 (mod 2^step_i)` - // and derive the next `c` by computing `diff >> step_i`. - // To enforce `diff = 0 (mod 2^step_i)`, we compute `diff / 2^step_i` - // and enforce it to be small (soundness holds because for `a` that does - // not divide `b`, `b / a` in the field will be very large). - c = (&p_limb - &q_limb + &c) * weight.inverse().unwrap(); - if j < len { - // Unlike the code mentioned above which add some offset to the - // diff `x_i - y_i + c` to make it always positive, we directly - // check if the absolute value of the diff is small. - Self::enforce_abs_bit_length( - &c, - (max( - min(&p_bound.lb, &q_bound.lb).bits(), - max(&p_bound.ub, &q_bound.ub).bits(), - ) as usize) - .checked_sub(step) - .unwrap_or_default(), - )?; - } else { - let remaining_limbs = &(if j < self.limbs.len() { self } else { other }).limbs[j..]; - let remaining_bounds = - &(if j < self.bounds.len() { self } else { other }).bounds[j..]; - if remaining_limbs.is_empty() { - c.enforce_equal(&FpVar::zero())?; - } else { - // If there is any remaining limb, the first one should be the - // final carry (which will be checked later), and the following - // ones should be zero. - - // Enforce the remaining limbs to be zero. - // Instead of doing that one by one, we check if their sum is - // zero using a single constraint. - // This is sound, as the upper bounds of the limbs and their sum - // are guaranteed to be less than `F::MODULUS_MINUS_ONE_DIV_TWO` - // (i.e., all of them are "non-negative"), implying that all - // limbs should be zero to make the sum zero. - remaining_limbs[1..] - .iter() - .sum::>() - .enforce_equal(&FpVar::zero())?; - Bound::add_many(remaining_bounds) - .filter_safe::() - .ok_or(SynthesisError::Unsatisfiable)?; - // For the final carry, we need to ensure that it equals the - // remaining limb `rest`. - c.enforce_equal(&remaining_limbs[0])?; - }; - } - // Start the next group - i = j; - } - - Ok(()) - } -} - -impl NonNativeUintVar { - fn enforce_bit_length(x: &FpVar, length: usize) -> Result>, SynthesisError> { - let cs = x.cs(); - - let bits = &x.value().unwrap_or_default().into_bigint().to_bits_le()[..length]; - let bits = if cs.is_none() { - Vec::new_constant(cs, bits)? - } else { - Vec::new_witness(cs, || Ok(bits))? - }; - - Boolean::le_bits_to_fp(&bits)?.enforce_equal(x)?; - - Ok(bits) - } - - fn enforce_abs_bit_length( - x: &FpVar, - length: usize, - ) -> Result>, SynthesisError> { - let cs = x.cs(); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - - let is_neg = Boolean::new_variable( - cs.clone(), - || Ok(x.value().unwrap_or_default().into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO), - mode, - )?; - let bits = Vec::new_variable( - cs.clone(), - || { - Ok({ - let x = x.value().unwrap_or_default(); - let mut bits = if is_neg.value().unwrap_or_default() { - -x - } else { - x - } - .into_bigint() - .to_bits_le(); - bits.resize(length, false); - bits - }) - }, - mode, - )?; - - // Below is equivalent to but more efficient than - // `Boolean::le_bits_to_fp(&bits)?.enforce_equal(&is_neg.select(&x.negate()?, &x)?)?` - // Note that this enforces: - // 1. The claimed absolute value `is_neg.select(&x.negate()?, &x)?` has - // exactly `length` bits. - // 2. `is_neg` is indeed the sign of `x`, i.e., `is_neg = false` when - // `0 <= x < (|F| - 1) / 2`, and `is_neg = true` when - // `(|F| - 1) / 2 <= x < F`, thus the claimed absolute value is - // correct. - // If `is_neg` is incorrect, then: - // a. `0 <= x < (|F| - 1) / 2`, but `is_neg = true`, then - // `is_neg.select(&x.negate()?, &x)?` returns `|F| - x`, - // which is greater than `(|F| - 1) / 2` and cannot fit in - // `length` bits (given that `length` is small). - // b. `(|F| - 1) / 2 <= x < F`, but `is_neg = false`, then - // `is_neg.select(&x.negate()?, &x)?` returns `x`, which is - // greater than `(|F| - 1) / 2` and cannot fit in `length` - // bits. - FpVar::from(is_neg).mul_equals(&x.double()?, &(x - Boolean::le_bits_to_fp(&bits)?))?; - - Ok(bits) - } - - /// Compute `self + other`, without aligning the limbs. - pub fn add_no_align(&self, other: &Self) -> Result { - let mut z = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; - let mut bounds = vec![Bound::zero(); z.len()]; - for (i, v) in self.limbs.iter().enumerate() { - bounds[i] = bounds[i] - .add(&self.bounds[i]) - .filter_safe::() - .ok_or(SynthesisError::Unsatisfiable)?; - z[i] += v; - } - for (i, v) in other.limbs.iter().enumerate() { - bounds[i] = bounds[i] - .add(&other.bounds[i]) - .filter_safe::() - .ok_or(SynthesisError::Unsatisfiable)?; - z[i] += v; - } - Ok(Self { - limbs: z, - bounds: bounds, - }) - } - - pub fn sub_no_align(&self, other: &Self) -> Result { - let mut z = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; - let mut bounds = vec![Bound::zero(); z.len()]; - for (i, v) in self.limbs.iter().enumerate() { - bounds[i] = bounds[i] - .add(&self.bounds[i]) - .filter_safe::() - .ok_or(SynthesisError::Unsatisfiable)?; - z[i] += v; - } - for (i, v) in other.limbs.iter().enumerate() { - bounds[i] = bounds[i] - .sub(&other.bounds[i]) - .filter_safe::() - .ok_or(SynthesisError::Unsatisfiable)?; - z[i] -= v; - } - Ok(Self { - limbs: z, - bounds: bounds, - }) - } - - /// Compute `self * other`, without aligning the limbs. - /// Implements the O(n) approach described in xJsnark, Section IV.B.1) - pub fn mul_no_align(&self, other: &Self) -> Result { - let len = self.limbs.len() + other.limbs.len() - 1; - if self.is_constant() || other.is_constant() { - // Use the naive approach for constant operands, which costs no - // constraints. - let bounds = (0..len) - .map(|i| { - let start = max(i + 1, other.bounds.len()) - other.bounds.len(); - let end = min(i + 1, self.bounds.len()); - Bound::add_many( - &(start..end) - .map(|j| self.bounds[j].mul(&other.bounds[i - j])) - .collect::>(), - ) - .filter_safe::() - }) - .collect::>>() - .ok_or(SynthesisError::Unsatisfiable)?; - - let z = (0..len) - .map(|i| { - let start = max(i + 1, other.limbs.len()) - other.limbs.len(); - let end = min(i + 1, self.limbs.len()); - (start..end) - .map(|j| &self.limbs[j] * &other.limbs[i - j]) - .sum() - }) - .collect(); - return Ok(Self { - limbs: z, - bounds: bounds, - }); - } - let cs = self.cs().or(other.cs()); - let mode = if cs.is_none() { - AllocationMode::Constant - } else { - AllocationMode::Witness - }; - - // Compute the result `z` outside the circuit and provide it as hints. - let (z, bounds) = { - let mut z = vec![F::zero(); len]; - let mut bounds = vec![Bound::zero(); len]; - for i in 0..self.limbs.len() { - for j in 0..other.limbs.len() { - z[i + j] += self.limbs[i].value().unwrap_or_default() - * other.limbs[j].value().unwrap_or_default(); - bounds[i + j] = bounds[i + j].add(&self.bounds[i].mul(&other.bounds[j])) - } - } - ( - Vec::new_variable(cs.clone(), || Ok(z), mode)?, - bounds - .into_iter() - .map(|b| b.filter_safe::()) - .collect::>() - .ok_or(SynthesisError::Unsatisfiable)?, - ) - }; - for c in 1..=len { - let c = F::from(c as u64); - let mut t = F::one(); - let mut c_powers = vec![]; - for _ in 0..len { - c_powers.push(t); - t *= c; - } - // `l = Σ self[i] c^i` - let l = self - .limbs - .iter() - .zip(&c_powers) - .map(|(v, t)| v * *t) - .sum::>(); - // `r = Σ other[i] c^i` - let r = other - .limbs - .iter() - .zip(&c_powers) - .map(|(v, t)| v * *t) - .sum::>(); - // `o = Σ z[i] c^i` - let o = z - .iter() - .zip(&c_powers) - .map(|(v, t)| v * *t) - .sum::>(); - // Enforce `o = l * r` - l.mul_equals(&r, &o)?; - } - - Ok(Self { - limbs: z, - bounds: bounds, - }) - } - - /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. - pub fn modulo(&self) -> Result { - let cs = self.cs(); - let m = BigInt::from_biguint(Sign::Plus, M::MODULUS.into()); - // Provide the quotient and remainder as hints - let q = Self::new_variable_with_inferred_mode(cs.clone(), || { - let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); - Ok((self.value()?.div_floor(&m), Bound::new(lb, ub))) - })?; - let r = Self::new_variable_with_inferred_mode(cs.clone(), || { - Ok((self.value()?.abs() % &m, Bound::new_ub(m.clone()))) - })?; - - let m = Self::constant(m)?; - - // Enforce `self = q * m + r` - q.mul_no_align(&m)? - .add_no_align(&r)? - .enforce_equal_unaligned(self)?; - // Enforce `r < m` (and `r >= 0` already holds) - r.enforce_lt(&m)?; - - Ok(r) - } - - /// Enforce that `self` is congruent to `other` modulo `M::MODULUS`. - pub fn enforce_congruent(&self, other: &Self) -> Result<(), SynthesisError> { - let cs = self.cs(); - let m = BigInt::from_biguint(Sign::Plus, M::MODULUS.into()); - // Provide the quotient as hint - let q = Self::new_variable_with_inferred_mode(cs.clone(), || { - let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); - Ok((self.value()?.div_floor(&m), Bound::new(lb, ub))) - })?; - - let m = Self::constant(m)?; - - // Enforce `self - other = q * m` - self.sub_no_align(other)? - .enforce_equal_unaligned(&q.mul_no_align(&m)?) - } -} - -impl EquivalenceGadget for NonNativeUintVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { - self.enforce_congruent::(other) - } -} - -impl FromBitsGadget for NonNativeUintVar { - fn from_bits_le(bits: &[Boolean]) -> Result { - Ok(Self { - limbs: bits - .as_ref() - .chunks(F::BITS_PER_LIMB) - .map(Boolean::le_bits_to_fp) - .collect::>()?, - bounds: bits - .as_ref() - .chunks(F::BITS_PER_LIMB) - .map(|i| Bound::new_n_bits(i.len())) - .collect(), - }) - } -} - -impl ToBitsGadget for NonNativeUintVar { - fn to_bits_le(&self) -> Result>, SynthesisError> { - Ok(self - .limbs - .iter() - .map(|limb| limb.to_n_bits_le(F::BITS_PER_LIMB)) - .collect::, _>>()? - .concat()) - } -} - -impl AbsorbableGadget> for NonNativeUintVar { - fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { - let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; - - self.to_bits_le()? - .chunks(bits_per_limb) - .try_for_each(|i| Ok(dest.push(Boolean::le_bits_to_fp(i)?))) - } -} - -impl VectorGadget> for [NonNativeUintVar] { - fn add(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.add_no_align(y)) - .collect() - } - - fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.mul_no_align(y)) - .collect() - } - - fn scale( - &self, - other: &NonNativeUintVar, - ) -> Result>, SynthesisError> { - self.iter().map(|x| x.mul_no_align(other)).collect() - } -} - -impl MatrixGadget> for SparseMatrixVar> { - fn mul_vector( - &self, - v: &impl Index>, - ) -> Result>, SynthesisError> { - self.0 - .iter() - .map(|row| { - let len = row - .iter() - .map(|(value, col_i)| value.limbs.len() + v[*col_i].limbs.len() - 1) - .max() - .unwrap_or(0); - // This is a combination of `mul_no_align` and `add_no_align` - // that results in more flattened `LinearCombination`s. - // Consequently, `ConstraintSystem::inline_all_lcs` costs less - // time, thus making trusted setup and proof generation faster. - let bounds = (0..len) - .map(|i| { - Bound::add_many( - &row.iter() - .flat_map(|(value, col_i)| { - let start = - max(i + 1, v[*col_i].bounds.len()) - v[*col_i].bounds.len(); - let end = min(i + 1, value.bounds.len()); - (start..end) - .map(|j| value.bounds[j].mul(&v[*col_i].bounds[i - j])) - }) - .collect::>(), - ) - .filter_safe::() - }) - .collect::>>() - .ok_or(SynthesisError::Unsatisfiable)?; - let v = (0..len) - .map(|i| { - row.iter() - .flat_map(|(value, col_i)| { - let start = - max(i + 1, v[*col_i].limbs.len()) - v[*col_i].limbs.len(); - let end = min(i + 1, value.limbs.len()); - (start..end).map(|j| &value.limbs[j] * &v[*col_i].limbs[i - j]) - }) - .sum() - }) - .collect(); - Ok(NonNativeUintVar { - limbs: v, - bounds: bounds, - }) - }) - .collect() - } -} - -#[cfg(test)] -mod tests { - use ark_ff::Field; - use ark_pallas::{Fq, Fr}; - use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{test_rng, UniformRand, error::Error}; - use num_bigint::RandBigInt; - - use super::*; - - #[test] - fn test_mul_biguint() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let size = 256; - - let rng = &mut test_rng(); - let a = rng.gen_biguint(size as u64); - let b = rng.gen_biguint(size as u64); - let ab = &a * &b; - let aab = &a * &ab; - let abb = &ab * &b; - - let a_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok((a.into(), Bound::new_n_bits(size))))?; - let b_var = - NonNativeUintVar::new_witness(cs.clone(), || Ok((b.into(), Bound::new_n_bits(size))))?; - let ab_var = NonNativeUintVar::new_witness(cs.clone(), || { - Ok((ab.into(), Bound::new_n_bits(size * 2))) - })?; - let aab_var = NonNativeUintVar::new_witness(cs.clone(), || { - Ok((aab.into(), Bound::new_n_bits(size * 3))) - })?; - let abb_var = NonNativeUintVar::new_witness(cs.clone(), || { - Ok((abb.into(), Bound::new_n_bits(size * 3))) - })?; - - a_var - .mul_no_align(&b_var)? - .enforce_equal_unaligned(&ab_var)?; - a_var - .mul_no_align(&ab_var)? - .enforce_equal_unaligned(&aab_var)?; - ab_var - .mul_no_align(&b_var)? - .enforce_equal_unaligned(&abb_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_mul_fq() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let rng = &mut test_rng(); - let a = Fq::rand(rng); - let b = Fq::rand(rng); - let ab = a * b; - let aab = a * ab; - let abb = ab * b; - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; - let b_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(b))?; - let ab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(ab))?; - let aab_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(aab))?; - let abb_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(abb))?; - - a_var - .mul_no_align(&b_var)? - .enforce_congruent::(&ab_var)?; - a_var - .mul_no_align(&ab_var)? - .enforce_congruent::(&aab_var)?; - ab_var - .mul_no_align(&b_var)? - .enforce_congruent::(&abb_var)?; - - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_pow() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let rng = &mut test_rng(); - - let a = Fq::rand(rng); - - let a_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(a))?; - - let mut r_var = a_var.clone(); - for _ in 0..16 { - r_var = r_var.mul_no_align(&r_var)?.modulo::()?; - } - r_var = r_var.mul_no_align(&a_var)?.modulo::()?; - assert_eq!( - BigInt::from_biguint(Sign::Plus, a.pow([65537u64]).into()), - r_var.value()? - ); - assert!(cs.is_satisfied()?); - Ok(()) - } - - #[test] - fn test_vec_vec_mul() -> Result<(), Box> { - let cs = ConstraintSystem::::new_ref(); - - let len = 1000; - - let rng = &mut test_rng(); - let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); - let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); - let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); - - let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; - let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; - let c_var = NonNativeUintVar::new_witness(cs.clone(), || Ok(c))?; - - let mut r_var = NonNativeUintVar::constant(BigUint::zero().into())?; - for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { - r_var = r_var.add_no_align(&a.mul_no_align(&b)?)?; - } - r_var.enforce_congruent::(&c_var)?; - println!("{}", cs.num_constraints()); - - assert!(cs.is_satisfied()?); - Ok(()) - } -} diff --git a/crates/primitives/src/algebra/group/nonnative.rs b/crates/primitives/src/algebra/group/emulated.rs similarity index 65% rename from crates/primitives/src/algebra/group/nonnative.rs rename to crates/primitives/src/algebra/group/emulated.rs index fd4a11bd3..733dc2a45 100644 --- a/crates/primitives/src/algebra/group/nonnative.rs +++ b/crates/primitives/src/algebra/group/emulated.rs @@ -1,10 +1,11 @@ use ark_ec::{short_weierstrass::SWFlags, AffineRepr}; -use ark_ff::{PrimeField, Zero}; +use ark_ff::Zero; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, eq::EqGadget, fields::fp::FpVar, prelude::Boolean, + select::CondSelectGadget, GR1CSVar, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; @@ -12,23 +13,25 @@ use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; use ark_std::borrow::Borrow; use crate::{ - algebra::{group::SonobeCurve}, + algebra::{field::emulated::EmulatedFieldVar, group::SonobeCurve}, + traits::SonobeField, transcripts::AbsorbableGadget, }; -use crate::algebra::field::nonnative2::BigIntVar; /// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native /// field, over the constraint field. It is not intended to perform operations, but just to contain /// the affine coordinates in order to perform hash operations of the point. #[derive(Debug, Clone)] -pub struct NonNativeAffineVar { - pub x: BigIntVar, - pub y: BigIntVar, +pub struct EmulatedAffineVar { + pub x: EmulatedFieldVar, + pub y: EmulatedFieldVar, } -impl AllocVar for NonNativeAffineVar { - fn new_variable>( - cs: impl Into>, +impl AllocVar + for EmulatedAffineVar +{ + fn new_variable>( + cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { @@ -38,24 +41,24 @@ impl AllocVar for NonNativeAffineVar { let affine = val.borrow().into_affine(); let (x, y) = affine.xy().unwrap_or_default(); - let x = BigIntVar::new_variable(cs.clone(), || Ok(x), mode)?; - let y = BigIntVar::new_variable(cs.clone(), || Ok(y), mode)?; + let x = EmulatedFieldVar::new_variable(cs.clone(), || Ok(x), mode)?; + let y = EmulatedFieldVar::new_variable(cs.clone(), || Ok(y), mode)?; Ok(Self { x, y }) }) } } -impl GR1CSVar for NonNativeAffineVar { - type Value = C; +impl GR1CSVar for EmulatedAffineVar { + type Value = Target; - fn cs(&self) -> ConstraintSystemRef { + fn cs(&self) -> ConstraintSystemRef { self.x.cs().or(self.y.cs()) } fn value(&self) -> Result { - let x = C::BaseField::from_le_bytes_mod_order(&self.x.value()?.magnitude().to_bytes_le()); - let y = C::BaseField::from_le_bytes_mod_order(&self.y.value()?.magnitude().to_bytes_le()); + let x = self.x.value()?; + let y = self.y.value()?; // Below is a workaround to convert the `x` and `y` coordinates to a // point. This is because the `SonobeCurve` trait does not provide a // method to construct a point from `BaseField` elements. @@ -79,12 +82,12 @@ impl GR1CSVar for NonNativeAffineVar { // `unwrap` below is safe because `bytes` is constructed from the `x` // and `y` coordinates of a valid point, and these coordinates are // serialized in the same way as the `SonobeCurve` implementation. - Ok(C::deserialize_uncompressed_unchecked(&bytes[..]).unwrap()) + Ok(Target::deserialize_uncompressed_unchecked(&bytes[..]).unwrap()) } } -impl EqGadget for NonNativeAffineVar { - fn is_eq(&self, other: &Self) -> Result, SynthesisError> { +impl EqGadget for EmulatedAffineVar { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { Ok(self.x.is_eq(&other.x)? & self.y.is_eq(&other.y)?) } @@ -95,20 +98,37 @@ impl EqGadget for NonNativeAffineVar { } } -impl NonNativeAffineVar { +impl EmulatedAffineVar { pub fn zero() -> Self { // `unwrap` below is safe because we are allocating a constant value, // which is guaranteed to succeed. - Self::new_constant(ConstraintSystemRef::None, C::zero()).unwrap() + Self::new_constant(ConstraintSystemRef::None, Target::zero()).unwrap() } } -impl AbsorbableGadget> for NonNativeAffineVar { - fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { +impl AbsorbableGadget + for EmulatedAffineVar +{ + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { (&self.x, &self.y).absorb_into(dest) } } +impl CondSelectGadget + for EmulatedAffineVar +{ + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + Ok(Self { + x: cond.select(&true_value.x, &false_value.x)?, + y: cond.select(&true_value.y, &false_value.y)?, + }) + } +} + #[cfg(test)] mod tests { use ark_pallas::{Fq, Fr, PallasConfig, Projective}; @@ -116,20 +136,19 @@ mod tests { use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, UniformRand}; + use super::*; use crate::{ - traits::{Inputize, InputizeNonNative}, + traits::{Inputize, InputizeEmulated}, transcripts::Absorbable, }; - use super::*; - #[test] fn test_alloc_zero() { let cs = ConstraintSystem::::new_ref(); // dealing with the 'zero' point should not panic when doing the unwrap let p = Projective::zero(); - assert!(NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).is_ok()); + assert!(EmulatedAffineVar::::new_witness(cs.clone(), || Ok(p)).is_ok()); } #[test] @@ -139,7 +158,7 @@ mod tests { // check that point_to_nonnative_limbs returns the expected values let mut rng = ark_std::test_rng(); let p = Projective::rand(&mut rng); - let p_var = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; + let p_var = EmulatedAffineVar::::new_witness(cs.clone(), || Ok(p))?; assert_eq!(p_var.to_absorbable()?.value()?, p.to_absorbable()); Ok(()) } @@ -151,10 +170,10 @@ mod tests { let p = Projective::rand(&mut rng); let cs = ConstraintSystem::::new_ref(); - let p_var = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p))?; + let p_var = EmulatedAffineVar::::new_witness(cs.clone(), || Ok(p))?; assert_eq!( [p_var.x.limbs.value()?, p_var.y.limbs.value()?].concat(), - p.inputize_nonnative() + p.inputize_emulated() ); let cs = ConstraintSystem::::new_ref(); diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 3f4b0fb34..2dbf59ae5 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -2,28 +2,24 @@ use ark_ec::{ short_weierstrass::{Projective, SWCurveConfig}, AffineRepr, CurveGroup, PrimeGroup, }; -use ark_ff::{BigInteger, Field, One, PrimeField, Zero}; -use ark_r1cs_std::convert::ToBitsGadget; +use ark_ff::{Field, One, PrimeField, Zero}; use ark_r1cs_std::{ - alloc::AllocVar, convert::ToConstraintFieldGadget, fields::fp::FpVar, groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, - prelude::Boolean, - GR1CSVar, }; use ark_relations::gr1cs::SynthesisError; use ark_std::mem::swap; -use num_bigint::{BigInt, Sign}; +use num_bigint::BigInt; use num_integer::Integer; use crate::{ - algebra::field::SonobeField, - traits::{Inputize, InputizeNonNative}, + algebra::{field::SonobeField, group::emulated::EmulatedAffineVar, Val}, + traits::{Dummy, Inputize, InputizeEmulated}, transcripts::{Absorbable, AbsorbableGadget}, }; -pub mod nonnative; +pub mod emulated; pub type CF1 = ::ScalarField; pub type CF2 = <::BaseField as Field>::BasePrimeField; @@ -33,30 +29,41 @@ pub type CI2 = <<::BaseField as Field>::BasePrimeField as Pr /// `Curve` trait is a wrapper around `CurveGroup` that also includes the /// necessary bounds for the curve to be used conveniently in folding schemes. pub trait SonobeCurve: - CurveGroup - + Absorbable + CurveGroup + + Absorbable + Inputize - + InputizeNonNative + + InputizeEmulated + + Val + AbsorbableGadget> { - /// The in-circuit variable type for this curve. - type Var: CurveVar; } impl> SonobeCurve for Projective

{ +} + +impl> Val for Projective

{ + type ConstraintField = P::BaseField; type Var = ProjectiveVar>; + + type EmulatedVar = EmulatedAffineVar; +} + +impl Dummy for C { + fn dummy(_: T) -> Self { + Default::default() + } } -impl>> Absorbable for Projective

{ - fn absorb_into(&self, dest: &mut Vec) { +impl> Absorbable for Projective

{ + fn absorb_into(&self, dest: &mut Vec) { let affine = self.into_affine(); let (x, y) = affine.xy().unwrap_or_default(); [x, y].absorb_into(dest); } } -impl> AbsorbableGadget> +impl> AbsorbableGadget for ProjectiveVar> { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { @@ -87,15 +94,15 @@ impl> Inputize for Projec } impl> - InputizeNonNative for Projective

+ InputizeEmulated for Projective

{ /// Returns the internal representation in the same order as how the value /// is allocated in `NonNativeAffineVar::new_input`. - fn inputize_nonnative(&self) -> Vec { + fn inputize_emulated(&self) -> Vec { let affine = self.into_affine(); let (x, y) = affine.xy().unwrap_or_default(); - [x, y].inputize_nonnative() + [x, y].inputize_emulated() } } @@ -128,43 +135,39 @@ fn lattice_reduction_2x2( (b1, b2) } -pub trait PointScalarMulGadget: Sized { - fn mul_scalar(&self, scalar: &impl ToBitsGadget) -> Result; -} - -impl PointScalarMulGadget> for C { - fn mul_scalar(&self, scalar: &impl ToBitsGadget>) -> Result { - let scalar = scalar.to_bits_le()?; - - let cs = scalar.cs(); - - let m = BigInt::from_biguint(Sign::Plus, CF1::::MODULUS.into()); - let m_sqrt = m.sqrt(); - - let (a, b) = lattice_reduction_2x2( - (m, Zero::zero()), - ( - CI2::::from_bits_le(&scalar.value().unwrap_or_default()) - .into() - .into(), - One::one(), - ), - ) - .0; - let (a_sign, a_abs) = a.into_parts(); - let (b_sign, b_abs) = b.into_parts(); - let a_is_negative = - Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(a_sign == Sign::Minus))?; - let b_is_negative = - Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(b_sign == Sign::Minus))?; - - // let a = NonNativeUintVar::new_variable_with_inferred_mode(cs.clone(), || { - // Ok((a_abs.into(), Bound::new_ub(m_sqrt.clone()))) - // })?; - // let b = NonNativeUintVar::new_variable_with_inferred_mode(cs, || { - // Ok((b_abs.into(), Bound::new_ub(m_sqrt))) - // })?; - - todo!() - } -} +// impl PointScalarMulGadget> for C { +// fn mul_scalar(&self, scalar: &impl ToBitsGadget>) -> Result { +// let scalar = scalar.to_bits_le()?; + +// let cs = scalar.cs(); + +// let m = BigInt::from_biguint(Sign::Plus, CF1::::MODULUS.into()); +// let m_sqrt = m.sqrt(); + +// let (a, b) = lattice_reduction_2x2( +// (m, Zero::zero()), +// ( +// CI2::::from_bits_le(&scalar.value().unwrap_or_default()) +// .into() +// .into(), +// One::one(), +// ), +// ) +// .0; +// let (a_sign, a_abs) = a.into_parts(); +// let (b_sign, b_abs) = b.into_parts(); +// let a_is_negative = +// Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(a_sign == Sign::Minus))?; +// let b_is_negative = +// Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(b_sign == Sign::Minus))?; + +// // let a = NonNativeUintVar::new_variable_with_inferred_mode(cs.clone(), || { +// // Ok((a_abs.into(), Bound::new_ub(m_sqrt.clone()))) +// // })?; +// // let b = NonNativeUintVar::new_variable_with_inferred_mode(cs, || { +// // Ok((b_abs.into(), Bound::new_ub(m_sqrt))) +// // })?; + +// todo!() +// } +// } diff --git a/crates/primitives/src/algebra/mod.rs b/crates/primitives/src/algebra/mod.rs index 82d6047fb..0a9d623dd 100644 --- a/crates/primitives/src/algebra/mod.rs +++ b/crates/primitives/src/algebra/mod.rs @@ -1,3 +1,18 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; + +use crate::traits::SonobeField; + pub mod field; pub mod group; pub mod ops; + +pub trait Val { + type ConstraintField: PrimeField; + type Var: AllocVar + GR1CSVar; + + type EmulatedVar: AllocVar + GR1CSVar; +} + +pub type Var = ::Var; +pub type EmulatedVar = ::EmulatedVar; \ No newline at end of file diff --git a/crates/primitives/src/algebra/ops/bits.rs b/crates/primitives/src/algebra/ops/bits.rs index 1688a227c..1e431730b 100644 --- a/crates/primitives/src/algebra/ops/bits.rs +++ b/crates/primitives/src/algebra/ops/bits.rs @@ -2,8 +2,10 @@ use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, eq::EqGadget, fields::fp::FpVar, GR1CSVar}; use ark_relations::gr1cs::SynthesisError; +use crate::algebra::field::emulated::Bound; + pub trait FromBitsGadget: Sized { - fn from_bits_le(bits: &[Boolean]) -> Result; + fn from_bits_le(bits: &[Boolean], bound: Bound) -> Result; } pub trait ToBitsGadgetExt: Sized { @@ -15,7 +17,7 @@ pub trait ToBitsGadgetExt: Sized { } } impl FromBitsGadget for FpVar { - fn from_bits_le(bits: &[Boolean]) -> Result { + fn from_bits_le(bits: &[Boolean], _bound: Bound) -> Result { Boolean::le_bits_to_fp(bits) } } diff --git a/crates/primitives/src/algebra/ops/mod.rs b/crates/primitives/src/algebra/ops/mod.rs index 2646ce8c0..a0f820298 100644 --- a/crates/primitives/src/algebra/ops/mod.rs +++ b/crates/primitives/src/algebra/ops/mod.rs @@ -1,5 +1,6 @@ pub mod bits; pub mod eq; pub mod matrix; +pub mod pow; pub mod rlc; pub mod vector; diff --git a/crates/primitives/src/algebra/ops/pow.rs b/crates/primitives/src/algebra/ops/pow.rs new file mode 100644 index 000000000..0406ba855 --- /dev/null +++ b/crates/primitives/src/algebra/ops/pow.rs @@ -0,0 +1,30 @@ +use ark_ff::{Field, PrimeField}; +use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; + +pub trait Pow: Sized { + fn powers(&self, n: usize) -> Vec; +} + +impl Pow for F { + fn powers(&self, n: usize) -> Vec { + let mut res = vec![F::one(); n]; + for i in 1..n { + res[i] = res[i - 1] * self; + } + res + } +} + +pub trait PowGadget: Sized { + fn powers(&self, n: usize) -> Vec; +} + +impl PowGadget for FpVar { + fn powers(&self, n: usize) -> Vec { + let mut res = vec![FpVar::one(); n]; + for i in 1..n { + res[i] = &res[i - 1] * self; + } + res + } +} diff --git a/crates/primitives/src/algebra/ops/rlc.rs b/crates/primitives/src/algebra/ops/rlc.rs index 3c94d01f6..3e43ade12 100644 --- a/crates/primitives/src/algebra/ops/rlc.rs +++ b/crates/primitives/src/algebra/ops/rlc.rs @@ -26,15 +26,17 @@ pub trait SliceRLC { fn slice_rlc(self, coeffs: &[Coeff]) -> Vec; } -impl<'a, T: 'a, I: Iterator, Coeff> SliceRLC for I +impl<'a, T, I: Iterator, Coeff> SliceRLC for I where - T: Add + Copy, + T: 'a + Add + Clone, for<'x> T: Mul<&'x Coeff, Output = T>, { type Value = T; fn slice_rlc(self, coeffs: &[Coeff]) -> Vec { - let mut iter = self.zip(coeffs).map(|(v, c)| v.iter().map(|x| *x * c)); + let mut iter = self + .zip(coeffs) + .map(|(v, c)| v.iter().map(|x| x.clone() * c)); let first = iter.next().unwrap(); iter.fold(first.collect(), |acc, v| { diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs index 6877f48d9..2abb1271f 100644 --- a/crates/primitives/src/arithmetizations/ccs/circuits.rs +++ b/crates/primitives/src/arithmetizations/ccs/circuits.rs @@ -6,19 +6,19 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::borrow::Borrow; +use super::{CCSVariant, CCS}; use crate::algebra::ops::matrix::SparseMatrixVar; -use super::CCS; - /// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. +#[allow(non_snake_case)] #[derive(Debug, Clone)] pub struct CCSMatricesVar { // we only need native representation, so the constraint field==F pub M: Vec>>, } -impl AllocVar, F> for CCSMatricesVar { - fn new_variable>>( +impl AllocVar, F> for CCSMatricesVar { + fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, _mode: AllocationMode, @@ -30,7 +30,7 @@ impl AllocVar, F> for CCSMatricesVar { .borrow() .M .iter() - .map(|M| SparseMatrixVar::>::new_constant(cs.clone(), M.clone())) + .map(|m| SparseMatrixVar::new_constant(cs.clone(), m)) .collect::>()?, }) }) diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index ac97cb9f7..dee3af2de 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -1,42 +1,114 @@ +use std::fmt::Debug; + use ark_ff::Field; use ark_poly::DenseMultilinearExtension; -use ark_relations::gr1cs::Matrix; -use ark_std::{cfg_into_iter, cfg_iter, log2}; +use ark_relations::gr1cs::{ConstraintSystem, Matrix}; +use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, log2, marker::PhantomData}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use crate::circuits::Assignments; - use super::{r1cs::R1CS, Arith, ArithRelation, Error}; +use crate::{ + arithmetizations::{r1cs::R1CSConfig, ArithConfig}, + circuits::Assignments, +}; pub mod circuits; -/// CCS represents the Customizable Constraint Systems structure defined in -/// the [CCS paper](https://eprint.iacr.org/2023/552) -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct CCS { +pub trait CCSVariant: Clone + Debug + PartialEq + Sync { + fn n_matrices() -> usize; + + fn degree() -> usize; + + fn multisets_vec() -> Vec>; + + fn coefficients_vec() -> Vec; +} + +#[allow(non_snake_case)] +#[derive(Clone, Debug, PartialEq)] +pub struct CCSConfig { + _v: PhantomData, /// m: number of rows in M_i (such that M_i \in F^{m, n}) - pub m: usize, + m: usize, /// n = |z|, number of cols in M_i - pub n: usize, + n: usize, /// l = |io|, size of public input/output - pub l: usize, - /// t = |M|, number of matrices - pub t: usize, - /// d: max degree in each variable - pub d: usize, - /// s = log(m), dimension of x - pub s: usize, + l: usize, +} + +impl ArithConfig for CCSConfig { + #[inline] + fn empty() -> Self { + Self { + _v: PhantomData, + m: 0, + n: 0, + l: 0, + } + } + + #[inline] + fn degree(&self) -> usize { + V::degree() + } + + #[inline] + fn n_constraints(&self) -> usize { + self.m + } + + #[inline] + fn n_variables(&self) -> usize { + self.n + } + + #[inline] + fn n_public_inputs(&self) -> usize { + self.l + } + + #[inline] + fn n_witnesses(&self) -> usize { + self.n_variables() - self.n_public_inputs() - 1 + } + + #[inline] + fn set_n_public_inputs(&mut self, l: usize) { + self.l = l; + } +} + +impl, V: CCSVariant> From for CCSConfig { + fn from(cfg: Cfg) -> Self { + let cfg = cfg.borrow(); + Self { + _v: PhantomData, + m: cfg.n_constraints(), + n: cfg.n_variables(), + l: cfg.n_public_inputs(), + } + } +} + +impl From<&ConstraintSystem> for CCSConfig { + fn from(cs: &ConstraintSystem) -> Self { + R1CSConfig::from(cs).into() + } +} + +/// CCS represents the Customizable Constraint Systems structure defined in +/// the [CCS paper](https://eprint.iacr.org/2023/552) +#[allow(non_snake_case)] +#[derive(Clone)] +pub struct CCS { + cfg: CCSConfig, /// vector of matrices pub M: Vec>, - /// vector of multisets - pub S: Vec>, - /// vector of coefficients - pub c: Vec, } -impl CCS { +impl CCS { /// Evaluates the CCS relation at a given vector of assignments `z` pub fn eval_assignments( &self, @@ -55,6 +127,9 @@ impl CCS { )); } + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + // Recall that the evaluation of CCS at z is defined as: // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i * z))$, // where $\prod$ denotes the Hadamard product. @@ -64,15 +139,14 @@ impl CCS { // Specifically, we independently compute each entry of the resulting // vector, and collect them at the end. // We parallelize the outer loop over rows (when the `parallel` feature - // is enabled), because `m`, the number of constraints in the CCS, is - // typically large in practice. - Ok(cfg_into_iter!(0..self.m) + // is enabled), since the number of constraints in the CCS is typically + // large in practice. + Ok(cfg_into_iter!(0..self.n_constraints()) .map(|row| { // The row-th entry of the resulting vector is: // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i[row] * z))$ - self.S - .iter() - .zip(&self.c) + S.iter() + .zip(c) .map(|(s, &c)| { // Each term in the sum is: // $c_j * \prod_{i \in S_j} (M_i[row] * z)$ @@ -93,49 +167,46 @@ impl CCS { .collect()) } - pub fn mle( + pub fn mles( &self, - i: usize, z: Assignments + Sync>, - ) -> DenseMultilinearExtension { - DenseMultilinearExtension { - num_vars: self.s, - evaluations: cfg_iter!(self.M[i]) - .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) - .chain(vec![F::zero(); (1 << self.s) - self.m]) - .collect(), - } + ) -> Vec> { + let s = log2(self.n_constraints()) as usize; + (0..V::n_matrices()) + .map(|i| DenseMultilinearExtension { + num_vars: s, + evaluations: cfg_iter!(self.M[i]) + .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) + .chain(vec![F::zero(); (1 << s) - self.n_constraints()]) + .collect(), + }) + .collect() } } -impl Arith for CCS { - #[inline] - fn degree(&self) -> usize { - self.d - } - - #[inline] - fn n_constraints(&self) -> usize { - self.m - } +impl Arith for CCS { + type Config = CCSConfig; #[inline] - fn n_variables(&self) -> usize { - self.n + fn empty() -> Self { + Self { + cfg: CCSConfig::empty(), + M: vec![vec![]; V::n_matrices()], + } } #[inline] - fn n_public_inputs(&self) -> usize { - self.l + fn config(&self) -> &Self::Config { + &self.cfg } #[inline] - fn n_witnesses(&self) -> usize { - self.n_variables() - self.n_public_inputs() - 1 + fn config_mut(&mut self) -> &mut Self::Config { + &mut self.cfg } } -impl, U: AsRef<[F]>> ArithRelation for CCS { +impl, U: AsRef<[F]>, V: CCSVariant> ArithRelation for CCS { type Evaluation = Vec; fn eval_relation(&self, w: &W, u: &U) -> Result { @@ -152,23 +223,25 @@ impl, U: AsRef<[F]>> ArithRelation for CCS { } } -impl From> for CCS { +impl From> for CCS { fn from(r1cs: R1CS) -> Self { - let m = r1cs.n_constraints(); - let n = r1cs.n_variables(); - CCS { - m, - n, - l: r1cs.n_public_inputs(), - s: log2(m) as usize, - t: 3, - d: r1cs.degree(), - - S: vec![vec![0, 1], vec![2]], - c: vec![F::one(), F::one().neg()], + Self { + cfg: r1cs.config().into(), M: vec![r1cs.A, r1cs.B, r1cs.C], } } } +impl From<&ConstraintSystem> for CCS { + fn from(cs: &ConstraintSystem) -> Self { + R1CS::from(cs).into() + } +} + +impl From> for CCS { + fn from(cs: ConstraintSystem) -> Self { + Self::from(&cs) + } +} + // TODO: add back tests diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 01a51d8a8..28073b34d 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -1,3 +1,5 @@ +use std::fmt::Debug; + use ark_relations::gr1cs::SynthesisError; use thiserror::Error; @@ -14,13 +16,13 @@ pub enum Error { UnsatisfiedAssignments(String), #[error("Failed to extract constraints from the constraint system: {0}")] ConstraintExtractionFailure(String), - #[error("Synthesis error: {0}")] + #[error(transparent)] SynthesisError(#[from] SynthesisError), } -/// [`Arith`] is a trait about constraint systems (R1CS, CCS, etc.), where we -/// define methods for getting information about the constraint system. -pub trait Arith: Clone { +pub trait ArithConfig: Clone + Debug + PartialEq { + fn empty() -> Self; + /// Returns the degree of the constraint system fn degree(&self) -> usize; @@ -36,6 +38,46 @@ pub trait Arith: Clone { /// Returns the number of witnesses / secret inputs in the constraint system fn n_witnesses(&self) -> usize; + + fn set_n_public_inputs(&mut self, l: usize); +} + +/// [`Arith`] is a trait about constraint systems (R1CS, CCS, etc.), where we +/// define methods for getting information about the constraint system. +pub trait Arith: Clone { + type Config: ArithConfig; + + fn config(&self) -> &Self::Config; + + fn config_mut(&mut self) -> &mut Self::Config; + + fn empty() -> Self; + + /// Returns the degree of the constraint system + fn degree(&self) -> usize { + self.config().degree() + } + + /// Returns the number of constraints in the constraint system + fn n_constraints(&self) -> usize { + self.config().n_constraints() + } + + /// Returns the number of variables in the constraint system + fn n_variables(&self) -> usize { + self.config().n_variables() + } + + /// Returns the number of public inputs / public IO / instances / statements + /// in the constraint system + fn n_public_inputs(&self) -> usize { + self.config().n_public_inputs() + } + + /// Returns the number of witnesses / secret inputs in the constraint system + fn n_witnesses(&self) -> usize { + self.config().n_witnesses() + } } /// `ArithRelation` *treats a constraint system as a relation* between a witness diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index afeec6272..9469f2407 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -3,6 +3,7 @@ use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData, One}; +use super::R1CS; use crate::{ algebra::ops::{ eq::EquivalenceGadget, @@ -13,12 +14,11 @@ use crate::{ circuits::Assignments, }; -use super::R1CS; - /// An in-circuit representation of the `R1CS` struct. /// /// `M` is for the modulo operation involved in the satisfiability check when /// the underlying `FVar` is `NonNativeUintVar`. +#[allow(non_snake_case)] #[derive(Debug, Clone)] pub struct R1CSMatricesVar { _m: PhantomData, @@ -53,6 +53,7 @@ where SparseMatrixVar: MatrixGadget, [FVar]: VectorGadget, { + #[allow(non_snake_case)] pub fn eval_assignments( &self, z: Assignments>, diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 00a75740c..2733a90fa 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -1,25 +1,105 @@ use ark_ff::Field; -use ark_relations::gr1cs::{ConstraintSystem, Matrix}; +use ark_relations::gr1cs::{ConstraintSystem, Matrix, R1CS_PREDICATE_LABEL}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{cfg_into_iter, cfg_iter, iterable::Iterable}; #[cfg(feature = "parallel")] use rayon::prelude::*; +use super::{ccs::CCS, Arith, ArithRelation, Error}; use crate::{ - circuits::{Assignments, ConstraintSystemExt}, + arithmetizations::{ccs::CCSVariant, ArithConfig}, + circuits::Assignments, relations::WitnessInstanceExtractor, - traits::Dummy, }; -use super::{ccs::CCS, Arith, ArithRelation, Error}; - pub mod circuits; #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct R1CS { - l: usize, // io len +pub struct R1CSConfig { m: usize, // number of constraints n: usize, // number of variables + l: usize, // io len +} + +impl R1CSConfig { + pub fn new(n_constraints: usize, n_variables: usize, n_public_inputs: usize) -> Self { + Self { + m: n_constraints, + n: n_variables, + l: n_public_inputs, + } + } +} + +impl ArithConfig for R1CSConfig { + #[inline] + fn empty() -> Self { + Self { m: 0, n: 0, l: 0 } + } + + #[inline] + fn degree(&self) -> usize { + 2 + } + + #[inline] + fn n_constraints(&self) -> usize { + self.m + } + + #[inline] + fn n_variables(&self) -> usize { + self.n + } + + #[inline] + fn n_public_inputs(&self) -> usize { + self.l + } + + #[inline] + fn n_witnesses(&self) -> usize { + self.n_variables() - self.n_public_inputs() - 1 + } + + #[inline] + fn set_n_public_inputs(&mut self, l: usize) { + self.l = l; + } +} + +impl From<&ConstraintSystem> for R1CSConfig { + fn from(cs: &ConstraintSystem) -> Self { + Self::new( + cs.num_constraints(), + cs.num_instance_variables + cs.num_witness_variables, + cs.num_instance_variables - 1, // -1 to subtract the first '1' + ) + } +} + +impl CCSVariant for R1CSConfig { + fn n_matrices() -> usize { + 3 + } + + fn degree() -> usize { + 2 + } + + fn multisets_vec() -> Vec> { + vec![vec![0, 1], vec![2]] + } + + fn coefficients_vec() -> Vec { + vec![F::one(), -F::one()] + } +} + +#[allow(non_snake_case)] +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct R1CS { + cfg: R1CSConfig, pub A: Matrix, pub B: Matrix, pub C: Matrix, @@ -58,90 +138,68 @@ impl R1CS { } impl Arith for R1CS { - #[inline] - fn degree(&self) -> usize { - 2 - } - - #[inline] - fn n_constraints(&self) -> usize { - self.m - } + type Config = R1CSConfig; #[inline] - fn n_variables(&self) -> usize { - self.n + fn empty() -> Self { + Self { + cfg: R1CSConfig::empty(), + A: vec![], + B: vec![], + C: vec![], + } } #[inline] - fn n_public_inputs(&self) -> usize { - self.l + fn config(&self) -> &Self::Config { + &self.cfg } #[inline] - fn n_witnesses(&self) -> usize { - self.n_variables() - self.n_public_inputs() - 1 - } -} - -impl Dummy<(usize, usize, usize)> for R1CS { - fn dummy((n_constraints, n_variables, n_public_inputs): (usize, usize, usize)) -> Self { - Self { - m: n_constraints, - n: n_variables, - l: n_public_inputs, - A: vec![], - B: vec![], - C: vec![], - } + fn config_mut(&mut self) -> &mut Self::Config { + &mut self.cfg } } impl R1CS { - pub fn empty() -> Self { - Self::dummy((0, 0, 0)) - } - - pub fn new( - (n_constraints, n_variables, n_public_inputs): (usize, usize, usize), - matrices: [Matrix; 3], - ) -> Self { - let mut matrices = matrices.to_vec(); - let C = matrices.pop().unwrap(); - let B = matrices.pop().unwrap(); - let A = matrices.pop().unwrap(); - Self { - m: n_constraints, - n: n_variables, - l: n_public_inputs, - A, - B, - C, - } + #[allow(non_snake_case)] + pub fn new(cfg: R1CSConfig, [A, B, C]: [Matrix; 3]) -> Self { + Self { cfg, A, B, C } } } -impl TryFrom> for R1CS { +impl TryFrom> for R1CS { type Error = Error; - fn try_from(ccs: CCS) -> Result { - if ccs.t != 3 { - return Err(Error::ConstraintExtractionFailure(format!( - "R1CS should only have 3 matrices (A, B, C) but found {} matrices", - ccs.t - ))); - } + fn try_from(ccs: CCS) -> Result { Ok(Self::new( - ( + R1CSConfig::new( ccs.n_constraints(), ccs.n_variables(), ccs.n_public_inputs(), ), + // `unwrap` is safe here because the type parameter T = 3 ccs.M.try_into().unwrap(), )) } } +impl From<&ConstraintSystem> for R1CS { + fn from(cs: &ConstraintSystem) -> Self { + // Get the R1CS predicate matrices + let r1cs_predicate = &cs.predicate_constraint_systems[R1CS_PREDICATE_LABEL]; + let matrices = r1cs_predicate.to_matrices(cs); + // `unwrap` is safe here because R1CS always has 3 matrices + R1CS::new(cs.into(), matrices.try_into().unwrap()) + } +} + +impl From> for R1CS { + fn from(cs: ConstraintSystem) -> Self { + Self::from(&cs) + } +} + impl, U: AsRef<[F]>> ArithRelation for R1CS { type Evaluation = Vec; @@ -220,46 +278,6 @@ impl WitnessInstanceExtractor>, RelaxedInstance< } } -impl ConstraintSystemExt for ConstraintSystem { - type Arith = R1CS; - type Error = Error; - - fn constraints(&self) -> Result, Error> { - // Get the R1CS predicate matrices - let r1cs_predicate = self - .predicate_constraint_systems - .get("R1CS") - .ok_or_else(|| { - Error::ConstraintExtractionFailure( - "No R1CS predicate found in constraint system".into(), - ) - })?; - let matrices = r1cs_predicate.to_matrices(self); - if matrices.len() != 3 { - return Err(Error::ConstraintExtractionFailure(format!( - "R1CS should only have 3 matrices (A, B, C) but found {} matrices", - matrices.len() - ))); - } - Ok(R1CS::new( - ( - self.num_constraints(), - self.num_instance_variables + self.num_witness_variables, - self.num_instance_variables - 1, // -1 to subtract the first '1' - ), - matrices.try_into().unwrap(), - )) - } - - fn assignments(&self) -> Result>, Error> { - let witness = self.witness_assignment()?.to_vec(); - // skip the first element which is '1' - let instance = self.instance_assignment()?[1..].to_vec(); - - Ok((F::one(), instance, witness).into()) - } -} - #[cfg(test)] pub mod tests { use ark_bn254::Fr; @@ -267,11 +285,11 @@ pub mod tests { use ark_relations::gr1cs::ConstraintSynthesizer; use ark_std::{error::Error, test_rng}; - use crate::circuits::utils::{ - constraints_for_test, satisfying_assignments_for_test, CircuitForTest, - }; - use super::*; + use crate::circuits::{ + utils::{constraints_for_test, satisfying_assignments_for_test, CircuitForTest}, + ConstraintSystemExt, + }; #[test] fn test_constraint_extraction() -> Result<(), Box> { @@ -285,7 +303,7 @@ pub mod tests { cs.finalize(); let cs = cs.into_inner().unwrap(); - assert_eq!(cs.constraints()?, constraints_for_test()); + assert_eq!(R1CS::from(&cs), constraints_for_test()); Ok(()) } diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 1e8f34fb8..c38edbd69 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -1,3 +1,5 @@ +use std::fmt::Debug; + use ark_ff::{Field, PrimeField}; use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::{ @@ -22,6 +24,8 @@ pub trait FCircuit { type Field: PrimeField; type ExternalInputs; + fn dummy_external_inputs(&self) -> Self::ExternalInputs; + /// returns the number of elements in the state of the FCircuit, which corresponds to the /// FCircuit inputs. fn state_len(&self) -> usize; @@ -32,7 +36,7 @@ pub trait FCircuit { // can hold a state if needed to store data to generate the constraints. &self, cs: ConstraintSystemRef, - i: usize, + i: FpVar, z_i: Vec>, external_inputs: Self::ExternalInputs, // inputs that are not part of the state ) -> Result>, SynthesisError>; @@ -155,11 +159,16 @@ impl> ConstraintSystemBuilder { } } -pub trait ConstraintSystemExt: Sized { - type Error; - type Arith; +pub trait ConstraintSystemExt { + fn assignments(&self) -> Result>, SynthesisError>; +} - fn constraints(&self) -> Result; +impl ConstraintSystemExt for ConstraintSystem { + fn assignments(&self) -> Result>, SynthesisError> { + let witness = self.witness_assignment()?.to_vec(); + // skip the first element which is '1' + let instance = self.instance_assignment()?[1..].to_vec(); - fn assignments(&self) -> Result>, Self::Error>; + Ok((F::one(), instance, witness).into()) + } } diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 2b01cf1ba..304c2119f 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -1,14 +1,20 @@ use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::{alloc::AllocVar, fields::fp::AllocatedFp}; +use ark_r1cs_std::{ + alloc::AllocVar, + fields::fp::{AllocatedFp, FpVar}, +}; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable}; -use crate::arithmetizations::r1cs::R1CS; - use super::Assignments; +use crate::{ + arithmetizations::r1cs::{R1CSConfig, R1CS}, + circuits::FCircuit, +}; pub struct CircuitForTest { pub x: F, } + impl ConstraintSynthesizer for CircuitForTest { fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { // Variable 0 (implicitly added by arkworks as 1) @@ -41,6 +47,61 @@ impl ConstraintSynthesizer for CircuitForTest { } } +impl FCircuit for CircuitForTest { + type Field = F; + + type ExternalInputs = (); + + fn dummy_external_inputs(&self) -> Self::ExternalInputs {} + + fn state_len(&self) -> usize { + 1 + } + + fn generate_step_constraints( + &self, + cs: ConstraintSystemRef, + _i: FpVar, + z_i: Vec>, + _external_inputs: Self::ExternalInputs, + ) -> Result>, SynthesisError> { + // Variable 0 (implicitly added by arkworks as 1) + // Variable 1 + let x = if let FpVar::Var(x) = z_i[0].clone() { + x + } else { + unreachable!() + }; + // Variable 2 + let y = AllocatedFp::new_witness(cs.clone(), || { + Ok(x.value()?.pow([3]) + x.value()? + F::from(5)) + })?; + + // Variable 3, Constraint 0 + let x_square = x.square()?; + // Variable 4, Constraint 1 + let x_cube = x_square.mul(&x); + // Variable 5 + let t = AllocatedFp::new_witness(cs.clone(), || Ok(x.value()?.pow([3]) + x.value()?))?; + let x_cube_plus_x = x.add(&x_cube); + // Constraint 2 + cs.enforce_r1cs_constraint( + || x_cube_plus_x.variable.into(), + || Variable::one().into(), + || t.variable.into(), + )?; + let x_cube_plus_x_plus_5 = t.add_constant(F::from(5)); + // Constraint 3 + cs.enforce_r1cs_constraint( + || x_cube_plus_x_plus_5.variable.into(), + || Variable::one().into(), + || y.variable.into(), + )?; + Ok(vec![FpVar::Var(x_cube_plus_x_plus_5)]) + } +} + +#[allow(non_snake_case)] pub fn constraints_for_test() -> R1CS { // R1CS for: x^3 + x + 5 = y (example from article // https://vitalik.eth.limo/general/2016/12/10/qap.html) @@ -63,7 +124,7 @@ pub fn constraints_for_test() -> R1CS { vec![(F::one(), 2)], ]; - R1CS::::new((4, 6, 1), [A, B, C]) + R1CS::::new(R1CSConfig::new(4, 6, 1), [A, B, C]) } pub fn satisfying_assignments_for_test(x: F) -> Assignments> { diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index a8b1a9b13..110a633f6 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,8 +1,8 @@ -use ark_ff::Field; -use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_r1cs_std::{ + alloc::AllocVar, eq::EqGadget, fields::fp::FpVar, select::CondSelectGadget, GR1CSVar, +}; +use ark_relations::gr1cs::SynthesisError; use ark_std::{ - borrow::Borrow, fmt::Debug, iter::Sum, ops::{Add, Mul}, @@ -10,14 +10,22 @@ use ark_std::{ }; use thiserror::Error; +use crate::{ + algebra::{ + field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar, + ops::bits::FromBitsGadget, Var, + }, + traits::{SonobeCurve, SonobeField, CF1, CF2}, + transcripts::{Absorbable, AbsorbableGadget}, +}; + pub mod pedersen; // TODO: add back other commitment schemes #[derive(Debug, Error)] pub enum Error { // Commitment errors - #[error("The message being committed to has length {1}, exceeding the maximum supported length of {0}" - )] + #[error("The message being committed to has length {1}, exceeding the maximum supported length ({0})")] MessageTooLong(usize, usize), #[error("Blinding factor not 0 for Commitment without hiding")] BlindingNotZero, @@ -27,16 +35,20 @@ pub enum Error { CommitmentVerificationFail, } -pub trait VectorCommitment: 'static + Debug + PartialEq { +pub trait VectorCommitment: 'static + Clone + Debug + PartialEq + Eq { const IS_HIDING: bool; - type Key; - type Scalar: Clone + Copy + Debug + PartialEq + Sync; - type Commitment: Default + Debug + PartialEq + Sync; + type Gadget: VectorCommitmentGadget; + + type Key: Clone; + type Scalar: Clone + Copy + Default + Debug + PartialEq + Eq + Sync + Absorbable; + type Commitment: Clone + Default + Debug + PartialEq + Eq + Sync + Absorbable; type Randomness: Clone + Copy + Default + Debug + + PartialEq + + Eq + Sync + Add + Mul @@ -62,13 +74,38 @@ pub trait VectorCommitment: 'static + Debug + PartialEq { ) -> Result; } -pub trait VectorCommitmentGadget { +pub trait GroupBasedVectorCommitment: + VectorCommitment< + Gadget: VectorCommitmentGadget< + Native = Self, + ConstraintField = CF2, + ScalarVar = EmulatedFieldVar, Self::Scalar, true>, + CommitmentVar = Var, + >, + Commitment: SonobeCurve, + Scalar = CF1<::Commitment>, +> +{ + type EmulatedGadget: VectorCommitmentGadget< + Native = Self, + ConstraintField = Self::Scalar, + ScalarVar = FpVar, + CommitmentVar = EmulatedAffineVar, + >; +} + +pub trait VectorCommitmentGadget: Clone { type Native: VectorCommitment; - type ConstraintField: Field; + type ConstraintField: SonobeField; type KeyVar; type ScalarVar: Clone + + EqGadget + + AbsorbableGadget + + CondSelectGadget + + FromBitsGadget + AllocVar<::Scalar, Self::ConstraintField> + + GR1CSVar::Scalar> + Add + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> + Mul @@ -84,11 +121,12 @@ pub trait VectorCommitmentGadget { + Mul + for<'a> Mul<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar>; type CommitmentVar: Clone - + AllocVar<::Commitment, Self::ConstraintField>; - type RandomnessVar: AllocVar< - ::Randomness, - Self::ConstraintField, - >; + + AbsorbableGadget + + CondSelectGadget + + AllocVar<::Commitment, Self::ConstraintField> + + GR1CSVar::Commitment>; + type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> + + GR1CSVar::Randomness>; fn open( ck: &Self::KeyVar, @@ -98,57 +136,6 @@ pub trait VectorCommitmentGadget { ) -> Result<(), SynthesisError>; } -#[derive(Clone, Copy, Default, Debug)] -pub struct Null; - -impl Add for Null { - type Output = Null; - - fn add(self, _: F) -> Null { - Null - } -} - -impl Add for &Null { - type Output = Null; - - fn add(self, _: F) -> Null { - Null - } -} - -impl Mul for Null { - type Output = Self; - - fn mul(self, _: F) -> Null { - Null - } -} - -impl Mul for &Null { - type Output = Null; - - fn mul(self, _: F) -> Null { - Null - } -} - -impl Sum for Null { - fn sum>(_: I) -> Self { - Null - } -} - -impl AllocVar for Null { - fn new_variable>( - _cs: impl Into>, - _f: impl FnOnce() -> Result, - _mode: AllocationMode, - ) -> Result { - Ok(Self) - } -} - #[cfg(test)] mod tests { use ark_ff::UniformRand; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index bac75c6e8..4eb08956d 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -1,31 +1,18 @@ -use ark_ec::{ - short_weierstrass::{Projective, SWCurveConfig}, - CurveGroup, -}; -use ark_ff::{AdditiveGroup, PrimeField}; use ark_r1cs_std::{ - boolean::Boolean, - convert::ToBitsGadget, - eq::EqGadget, - fields::{fp::FpVar, FieldVar}, - groups::{ - curves::short_weierstrass::{non_zero_affine::NonZeroAffineVar, ProjectiveVar}, - CurveVar, - }, - GR1CSVar, + boolean::Boolean, convert::ToBitsGadget, eq::EqGadget, fields::fp::FpVar, groups::CurveVar, }; use ark_relations::gr1cs::SynthesisError; use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; use super::{Error, VectorCommitment}; -use crate::traits::CF1; use crate::{ - algebra::field::nonnative2::NonNativeFieldVar, - commitments::{Null, VectorCommitmentGadget}, - traits::{SonobeCurve, CF2}, + algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, + commitments::{GroupBasedVectorCommitment, VectorCommitmentGadget}, + traits::{CF1, CF2, SonobeCurve}, + utils::null::Null, }; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Pedersen { _c: PhantomData, } @@ -44,6 +31,8 @@ impl Pedersen { impl VectorCommitment for Pedersen { const IS_HIDING: bool = false; + type Gadget = PedersenGadget; + type Key = Vec; type Scalar = C::ScalarField; type Commitment = C; @@ -77,6 +66,8 @@ impl VectorCommitment for Pedersen { impl VectorCommitment for Pedersen { const IS_HIDING: bool = true; + type Gadget = PedersenGadget; + type Key = (Vec, C); type Scalar = C::ScalarField; type Commitment = C; @@ -108,6 +99,15 @@ impl VectorCommitment for Pedersen { } } +impl GroupBasedVectorCommitment for Pedersen { + type EmulatedGadget = PedersenEmulatedGadget; +} + +impl GroupBasedVectorCommitment for Pedersen { + type EmulatedGadget = PedersenEmulatedGadget; +} + +#[derive(Clone)] pub struct PedersenGadget { _c: PhantomData, } @@ -228,9 +228,9 @@ impl VectorCommitmentGadget for PedersenGadget { type KeyVar = Vec; - type ScalarVar = NonNativeFieldVar, CF1, true>; + type ScalarVar = EmulatedFieldVar, CF1, true>; - type IntermediateScalarVar = NonNativeFieldVar, CF1, false>; + type IntermediateScalarVar = EmulatedFieldVar, CF1, false>; type CommitmentVar = C::Var; @@ -258,13 +258,13 @@ impl VectorCommitmentGadget for PedersenGadget { type KeyVar = (Vec, C::Var); - type ScalarVar = NonNativeFieldVar, CF1, true>; + type ScalarVar = EmulatedFieldVar, CF1, true>; - type IntermediateScalarVar = NonNativeFieldVar, CF1, false>; + type IntermediateScalarVar = EmulatedFieldVar, CF1, false>; type CommitmentVar = C::Var; - type RandomnessVar = NonNativeFieldVar, CF1, true>; + type RandomnessVar = EmulatedFieldVar, CF1, true>; fn open( (g, h): &Self::KeyVar, @@ -283,14 +283,69 @@ impl VectorCommitmentGadget for PedersenGadget { } } +#[derive(Clone)] +pub struct PedersenEmulatedGadget { + _c: PhantomData, +} + +impl VectorCommitmentGadget for PedersenEmulatedGadget { + type Native = Pedersen; + type ConstraintField = CF1; + + type KeyVar = Vec, C>>; + + type ScalarVar = FpVar>; + + type IntermediateScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = Null; + + fn open( + ck: &Self::KeyVar, + v: &[Self::ScalarVar], + _r: &Self::RandomnessVar, + cm: &Self::CommitmentVar, + ) -> Result<(), SynthesisError> { + unimplemented!() + } +} + +impl VectorCommitmentGadget for PedersenEmulatedGadget { + type Native = Pedersen; + type ConstraintField = CF1; + + type KeyVar = ( + Vec, C>>, + EmulatedAffineVar, C>, + ); + + type ScalarVar = FpVar>; + + type IntermediateScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = FpVar>; + + fn open( + (g, h): &Self::KeyVar, + v: &[Self::ScalarVar], + r: &Self::RandomnessVar, + cm: &Self::CommitmentVar, + ) -> Result<(), SynthesisError> { + unimplemented!() + } +} + #[cfg(test)] mod tests { use ark_bn254::G1Projective; use ark_std::{error::Error, rand::Rng, test_rng}; - use crate::commitments::tests::test_commitment_correctness; - use super::*; + use crate::commitments::tests::test_commitment_correctness; #[test] fn test_pedersen_commitment() -> Result<(), Box> { diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index c91d06afb..8e76b75ab 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -6,3 +6,4 @@ pub mod relations; pub mod sumcheck; pub mod traits; pub mod transcripts; +pub mod utils; diff --git a/crates/primitives/src/sumcheck/circuits.rs b/crates/primitives/src/sumcheck/circuits.rs new file mode 100644 index 000000000..640e59725 --- /dev/null +++ b/crates/primitives/src/sumcheck/circuits.rs @@ -0,0 +1,117 @@ +/// Heavily inspired from testudo: https://github.com/cryptonetlab/testudo/tree/master +/// Some changes: +/// - Typings to better stick to ark_poly's API +/// - Uses `folding-schemes`' own `TranscriptVar` trait and `PoseidonTranscriptVar` struct +/// - API made closer to gadgets found in `folding-schemes` +use ark_ff::PrimeField; +use ark_r1cs_std::{ + eq::EqGadget, + fields::{fp::FpVar, FieldVar}, + poly::polynomial::univariate::dense::DensePolynomialVar, +}; +use ark_relations::gr1cs::SynthesisError; + +use crate::{sumcheck::utils::VPAuxInfo, transcripts::TranscriptVar}; + +pub struct IOPSumCheckGadget; + +impl IOPSumCheckGadget { + pub fn verify( + claimed_sum: FpVar, + proofs: &Vec>>, + aux_info: &VPAuxInfo, + transcript: &mut impl TranscriptVar, + ) -> Result<(FpVar, Vec>), SynthesisError> { + transcript.add(&FpVar::constant(F::from(aux_info.num_variables as u64)))?; + transcript.add(&FpVar::constant(F::from(aux_info.max_degree as u64)))?; + if proofs.len() != aux_info.num_variables { + return Err(SynthesisError::Unsatisfiable); + } + + let mut challenges = Vec::with_capacity(aux_info.num_variables); + let mut expected = claimed_sum; + + for coeffs in proofs { + if coeffs.len() - 1 != aux_info.max_degree { + return Err(SynthesisError::Unsatisfiable); + } + + let eval_at_zero = &coeffs[0]; + let eval_at_one = coeffs.iter().sum::>(); + + (eval_at_zero + eval_at_one).enforce_equal(&expected)?; + + transcript.add(&coeffs)?; + let challenge = transcript.challenge_field_element()?; + + expected = DensePolynomialVar::from_coefficients_slice(coeffs).evaluate(&challenge)?; + challenges.push(challenge); + } + + Ok((expected, challenges)) + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::Fr; + use ark_crypto_primitives::sponge::{ + poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, + CryptographicSponge, + }; + use ark_ff::{One, Zero}; + use ark_poly::{ + univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, + MultilinearExtension, Polynomial, + }; + use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{error::Error, test_rng}; + + use super::*; + use crate::{ + sumcheck::{utils::VirtualPolynomial, IOPSumCheck}, + transcripts::poseidon::poseidon_canonical_config, + }; + + #[test] + fn test_sum_check_circuit() -> Result<(), Box> { + let mut rng = test_rng(); + let poseidon_config = poseidon_canonical_config::(); + for num_vars in 1..15 { + let mut transcript_p = PoseidonSponge::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::new(&poseidon_config); + + let poly_mle = DenseMultilinearExtension::rand(num_vars, &mut rng); + let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, One::one()); + let aux_info = virtual_poly.aux_info.clone(); + + let (proofs, challenges, _) = IOPSumCheck::prove(virtual_poly, &mut transcript_p)?; + + let poly = DensePolynomial::from_coefficients_slice(&proofs[0]); + let claimed_sum = poly.evaluate(&One::one()) + poly.evaluate(&Zero::zero()); + + let (expected, _) = + IOPSumCheck::verify(claimed_sum, &proofs, &aux_info, &mut transcript_v)?; + + let cs = ConstraintSystem::new_ref(); + let mut transcript_var = PoseidonSpongeVar::new(&poseidon_config); + + let (expected_var, challenges_var) = IOPSumCheckGadget::verify( + FpVar::new_witness(cs.clone(), || Ok(claimed_sum))?, + &proofs + .into_iter() + .map(|v| Vec::new_witness(cs.clone(), || Ok(v))) + .collect::>()?, + &aux_info, + &mut transcript_var, + )?; + + assert!(cs.is_satisfied()?); + + assert_eq!(expected_var.value()?, expected); + assert_eq!(challenges_var.value()?, challenges); + } + Ok(()) + } +} diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index 141370f47..4b32465ad 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -13,61 +13,38 @@ use ark_ff::PrimeField; use ark_poly::{ univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, Polynomial, }; -use ark_std::{cfg_chunks, cfg_into_iter, cfg_iter, fmt::Debug}; +use ark_std::{cfg_chunks, cfg_into_iter, fmt::Debug}; #[cfg(feature = "parallel")] use rayon::prelude::*; use thiserror::Error; -use crate::transcripts::{Absorbable, Transcript}; - -use utils::{ +use self::utils::{ barycentric_weights, compute_lagrange_interpolated_poly, extrapolate, VPAuxInfo, VirtualPolynomial, }; +use crate::transcripts::{Absorbable, Transcript}; +pub mod circuits; pub mod utils; #[derive(Debug, Error)] pub enum Error { - #[error("Invalid Polynomial IOP Prover: {0}")] - InvalidPolyIOPProver(String), - #[error("Invalid Polynomial IOP Verifier: {0}")] - InvalidPolyIOPVerifier(String), - #[error("Invalid Polynomial IOP Proof: {0}")] - InvalidPolyIOPProof(String), - #[error("Invalid Polynomial IOP Parameters: {0}")] - InvalidPolyIOPParameters(String), -} - -/// An IOP proof is a collections of -/// - messages from prover to verifier at each round through the interactive -/// protocol. -/// - a point that is generated by the transcript for evaluation -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct IOPProof { - pub point: Vec, - pub proofs: Vec>, -} - -/// A SumCheckSubClaim is a claim generated by the verifier at the end of -/// verification when it is convinced. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct SumCheckSubClaim { - /// the multi-dimensional point that this multilinear extension is evaluated - /// to - pub point: Vec, - /// the expected evaluation - pub expected_evaluation: F, + #[error("Incorrect evaluations: {0} + {1} != {2}")] + IncorrectEvaluations(String, String, String), + #[error("Incorrect proof length: expected {0}, got {1}")] + UnexpectedProofLength(usize, usize), + #[error("Unexpected polynomial degree: expected at most {0}, got {1}")] + UnexpectedPolynomialDegree(usize, usize), } #[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] pub struct IOPSumCheck; impl IOPSumCheck { - pub fn prove>( + pub fn prove( mut poly: VirtualPolynomial, transcript: &mut impl Transcript, - ) -> Result<(IOPProof, Vec>), Error> { + ) -> Result<(Vec>, Vec, Vec>), Error> { transcript.add(&F::from(poly.aux_info.num_variables as u64)); transcript.add(&F::from(poly.aux_info.max_degree as u64)); let extrapolation_aux = (1..poly.aux_info.max_degree) @@ -159,7 +136,8 @@ impl IOPSumCheck { .for_each(|(products_sum, sum)| *products_sum += sum); }); - let prover_poly = compute_lagrange_interpolated_poly(&products_sum).coeffs; + let mut prover_poly = compute_lagrange_interpolated_poly(&products_sum).coeffs; + prover_poly.resize(poly.aux_info.max_degree + 1, F::ZERO); transcript.add(&prover_poly); prover_msgs.push(prover_poly); @@ -173,82 +151,72 @@ impl IOPSumCheck { }); } - Ok(( - IOPProof { - point: challenges, - proofs: prover_msgs, - }, - poly.flattened_ml_extensions, - )) + Ok((prover_msgs, challenges, poly.flattened_ml_extensions)) } - pub fn verify>( + pub fn verify( claimed_sum: F, - proof: &IOPProof, + proofs: &[Vec], aux_info: &VPAuxInfo, transcript: &mut impl Transcript, - ) -> Result, Error> { + ) -> Result<(F, Vec), Error> { transcript.add(&F::from(aux_info.num_variables as u64)); transcript.add(&F::from(aux_info.max_degree as u64)); - assert_eq!(aux_info.num_variables, proof.proofs.len()); + if proofs.len() != aux_info.num_variables { + return Err(Error::UnexpectedProofLength( + aux_info.num_variables, + proofs.len(), + )); + } - let challenges = proof - .proofs - .iter() - .map(|msg| { - transcript.add(msg); - transcript.challenge_field_element() - }) - .collect::>(); + let mut challenges = Vec::with_capacity(aux_info.num_variables); + let mut expected = claimed_sum; - // the deferred check during the interactive phase: - // 2. set `expected` to P(r)` - let mut expected_vec = cfg_iter!(proof.proofs) - .zip(&challenges) - .map(|(coeffs, challenge)| { - DensePolynomial::from_coefficients_slice(coeffs).evaluate(challenge) - }) - .collect::>(); - - // insert the asserted_sum to the first position of the expected vector - expected_vec.insert(0, claimed_sum); + // Outer loop is not parallelized because `DensePolynomial::evaluate` is + // already parallelized internally. + for coeffs in proofs { + if coeffs.len() - 1 != aux_info.max_degree { + return Err(Error::UnexpectedPolynomialDegree( + aux_info.max_degree, + coeffs.len() - 1, + )); + } - for (coeffs, &expected) in proof.proofs.iter().zip(expected_vec.iter()) { - let eval_at_one: F = coeffs.iter().sum(); - let eval_at_zero: F = if coeffs.is_empty() { - F::zero() - } else { - coeffs[0] - }; + let eval_at_zero = coeffs[0]; + let eval_at_one = coeffs.iter().sum::(); // the deferred check during the interactive phase: // 1. check if the received 'P(0) + P(1) = expected`. - if eval_at_one + eval_at_zero != expected { - return Err(Error::InvalidPolyIOPProof( - "Prover message is not consistent with the claim.".to_string(), + if eval_at_zero + eval_at_one != expected { + return Err(Error::IncorrectEvaluations( + eval_at_zero.to_string(), + eval_at_one.to_string(), + expected.to_string(), )); } + + transcript.add(coeffs); + let challenge = transcript.challenge_field_element(); + + // 2. set `expected` to `P(r)` + expected = DensePolynomial::from_coefficients_slice(coeffs).evaluate(&challenge); + challenges.push(challenge); } - Ok(SumCheckSubClaim { - point: challenges, - // the last expected value (not checked within this function) will be included in the - // subclaim - expected_evaluation: expected_vec[expected_vec.len() - 1], - }) + + Ok((expected, challenges)) } } #[cfg(test)] pub mod tests { - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_ff::Field; use ark_pallas::Fr; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::{test_rng, One, Zero}; - use crate::transcripts::poseidon::poseidon_canonical_config; - use super::*; + use crate::transcripts::poseidon::poseidon_canonical_config; #[test] pub fn sumcheck_poseidon() -> Result<(), Error> { @@ -263,7 +231,7 @@ pub mod tests { // test with zero poly let poly_mle = DenseMultilinearExtension::from_evaluations_vec( n_vars, - vec![Fr::zero(); 2u32.pow(n_vars as u32) as usize], + vec![Fr::zero(); 2usize.pow(n_vars as u32)], ); let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, Fr::ONE); sumcheck_poseidon_opt(virtual_poly)?; @@ -276,13 +244,13 @@ pub mod tests { // sum-check prove let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let (sum_check, _) = IOPSumCheck::prove(virtual_poly, &mut transcript_p)?; + let (proofs, _, _) = IOPSumCheck::prove(virtual_poly, &mut transcript_p)?; // sum-check verify - let poly = DensePolynomial::from_coefficients_vec(sum_check.proofs[0].clone()); + let poly = DensePolynomial::from_coefficients_slice(&proofs[0]); let claimed_sum = poly.evaluate(&Fr::one()) + poly.evaluate(&Fr::zero()); let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let res_verify = IOPSumCheck::verify(claimed_sum, &sum_check, &aux_info, &mut transcript_v); + let res_verify = IOPSumCheck::verify(claimed_sum, &proofs, &aux_info, &mut transcript_v); assert!(res_verify.is_ok()); Ok(()) diff --git a/crates/primitives/src/sumcheck/utils.rs b/crates/primitives/src/sumcheck/utils.rs index d75a973b4..2cc71f748 100644 --- a/crates/primitives/src/sumcheck/utils.rs +++ b/crates/primitives/src/sumcheck/utils.rs @@ -10,8 +10,9 @@ //! This module defines our main mathematical object `VirtualPolynomial`; and //! various functions associated with it. -use ark_ff::{batch_inversion, PrimeField}; +use ark_ff::{batch_inversion, Field, PrimeField}; use ark_poly::{univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial}; +use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; use ark_serialize::CanonicalSerialize; use ark_std::cfg_into_iter; #[cfg(feature = "parallel")] @@ -57,7 +58,6 @@ pub struct VPAuxInfo { pub num_variables: usize, } -// TODO: convert this into a trait impl VirtualPolynomial { /// Creates a new virtual polynomial from a MLE and its coefficient. pub fn new_from_mle(mle: DenseMultilinearExtension, coefficient: F) -> Self { @@ -74,51 +74,67 @@ impl VirtualPolynomial { } } -/// Evaluate eq polynomial. -pub fn eq_eval(x: &[F], y: &[F]) -> F { - debug_assert_eq!(x.len(), y.len()); - x.iter() - .zip(y.iter()) - .map(|(xi, yi)| xi.double() * yi - xi - yi + F::one()) - .product::() -} - -/// This function build the eq(x, r) polynomial for any given r, and output the -/// evaluation of eq(x, r) in its vector form. +/// `EqPoly` represents the following polynomial: /// -/// Evaluate -/// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) -/// over r, which is -/// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i)) -pub fn build_eq_x_r_vec(r: &[F]) -> Vec { - // we build eq(x,r) from its evaluations - // we want to evaluate eq(x,r) over x \in {0, 1}^num_vars - // for example, with num_vars = 4, x is a binary vector of 4, then - // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3) - // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3) - // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3) - // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3) - // .... - // 1 1 1 1 -> r0 * r1 * r2 * r3 - // we will need 2^num_var evaluations - - // initializing the buffer with [1] - let mut buf = vec![F::one()]; - - for i in r.iter().rev() { - // suppose at the previous step we received [b_1, ..., b_k] - // for the current step we will need - // if x_i = 0: (1-ri) * [b_1, ..., b_k] - // if x_i = 1: ri * [b_1, ..., b_k] - buf = cfg_into_iter!(buf) - .flat_map(|j| { - let v = j * i; - [j - v, v] - }) - .collect(); +/// `eq(x, y) = \prod_{i=1}^n (x_i * y_i + (1 - x_i) * (1 - y_i))` +pub struct EqPoly; + +impl EqPoly { + /// This function builds `eq(x, y)` by fixing `y = r` and outputting the + /// evaluations over all `x` in `[0, 2^n)`. + pub fn fix_y_evals(r: &[F]) -> Vec { + // we build eq(x,r) from its evaluations + // we want to evaluate eq(x,r) over all binary strings `x` of length `n` + // for example, with n = 4, x is a binary string of length 4, then + // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3) + // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3) + // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3) + // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3) + // .... + // 1 1 1 1 -> r0 * r1 * r2 * r3 + // we will need 2^num_var evaluations + + // initializing the buffer with [1] + let mut buf = vec![F::one()]; + + for i in r.iter().rev() { + // suppose at the previous step we received [b_1, ..., b_k] + // for the current step we will need + // if x_i = 0: (1-ri) * [b_1, ..., b_k] + // if x_i = 1: ri * [b_1, ..., b_k] + buf = cfg_into_iter!(buf) + .flat_map(|j| { + let v = j * i; + [j - v, v] + }) + .collect(); + } + + buf + } + + /// Evaluate eq polynomial. + pub fn fix_xy_eval(x: &[F], y: &[F]) -> F { + debug_assert_eq!(x.len(), y.len()); + x.iter() + .zip(y.iter()) + .map(|(xi, yi)| xi.double() * yi - xi - yi + F::one()) + .product() } +} + +pub struct EqPolyVar; - buf +impl EqPolyVar { + /// Evaluate eq polynomial in circuit. + pub fn fix_xy_eval(x: &[FpVar], y: &[FpVar]) -> FpVar { + debug_assert_eq!(x.len(), y.len()); + let mut eval = FpVar::::one(); + for (xi, yi) in x.iter().zip(y.iter()) { + eval *= (xi + xi) * yi - xi - yi + F::one(); + } + eval + } } #[allow(clippy::filter_map_bool_then)] diff --git a/crates/primitives/src/traits.rs b/crates/primitives/src/traits.rs index 35d9be14e..9c97b277f 100644 --- a/crates/primitives/src/traits.rs +++ b/crates/primitives/src/traits.rs @@ -13,9 +13,15 @@ impl Dummy for Vec { } } -impl Dummy<()> for T { - fn dummy(_: ()) -> Self { - Default::default() +impl + Copy, const N: usize> Dummy for [T; N] { + fn dummy(cfg: Cfg) -> Self { + [T::dummy(cfg); N] + } +} + +impl, B: Dummy> Dummy for (A, B) { + fn dummy(cfg: Cfg) -> Self { + (A::dummy(cfg), B::dummy(cfg)) } } @@ -43,14 +49,14 @@ impl> Inputize for [T] { /// Note that we require this trait because we need to distinguish between some /// data types that are represented both natively and non-natively in-circuit /// (e.g., field elements can have type `FpVar` and `NonNativeUintVar`). -pub trait InputizeNonNative { - fn inputize_nonnative(&self) -> Vec; +pub trait InputizeEmulated { + fn inputize_emulated(&self) -> Vec; } -impl> InputizeNonNative for [T] { - fn inputize_nonnative(&self) -> Vec { +impl> InputizeEmulated for [T] { + fn inputize_emulated(&self) -> Vec { self.iter() - .flat_map(InputizeNonNative::::inputize_nonnative) + .flat_map(InputizeEmulated::::inputize_emulated) .collect() } } diff --git a/crates/primitives/src/transcripts/absorbable.rs b/crates/primitives/src/transcripts/absorbable.rs index 31c80dea7..8143a76d1 100644 --- a/crates/primitives/src/transcripts/absorbable.rs +++ b/crates/primitives/src/transcripts/absorbable.rs @@ -1,51 +1,53 @@ +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::SynthesisError; -pub trait Absorbable { - fn absorb_into(&self, dest: &mut Vec); +pub trait Absorbable { + fn absorb_into(&self, dest: &mut Vec); - fn to_absorbable(&self) -> Vec { + fn to_absorbable(&self) -> Vec { let mut result = Vec::new(); self.absorb_into(&mut result); result } } -impl> Absorbable for usize { - fn absorb_into(&self, dest: &mut Vec) { +impl Absorbable for usize { + fn absorb_into(&self, dest: &mut Vec) { dest.push(F::from(*self as u64)); } } -impl> Absorbable for &T { - fn absorb_into(&self, dest: &mut Vec) { - >::absorb_into(self, dest); +impl Absorbable for &T { + fn absorb_into(&self, dest: &mut Vec) { + (*self).absorb_into(dest); } } -impl> Absorbable for (T, T) { - fn absorb_into(&self, dest: &mut Vec) { +impl Absorbable for (T, T) { + fn absorb_into(&self, dest: &mut Vec) { self.0.absorb_into(dest); self.1.absorb_into(dest); } } -impl> Absorbable for [T] { - fn absorb_into(&self, dest: &mut Vec) { +impl Absorbable for [T] { + fn absorb_into(&self, dest: &mut Vec) { for t in self.iter() { t.absorb_into(dest); } } } -impl, const N: usize> Absorbable for [T; N] { - fn absorb_into(&self, dest: &mut Vec) { - <[T] as Absorbable>::absorb_into(self, dest); +impl Absorbable for [T; N] { + fn absorb_into(&self, dest: &mut Vec) { + self.as_ref().absorb_into(dest); } } -impl> Absorbable for Vec { - fn absorb_into(&self, dest: &mut Vec) { - <[T] as Absorbable>::absorb_into(self, dest); +impl Absorbable for Vec { + fn absorb_into(&self, dest: &mut Vec) { + self.as_slice().absorb_into(dest); } } @@ -53,43 +55,43 @@ impl> Absorbable for Vec { /// is `F`. /// /// Matches `AbsorbGadget` in `ark-crypto-primitives`. -pub trait AbsorbableGadget { - fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError>; +pub trait AbsorbableGadget { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError>; - fn to_absorbable(&self) -> Result, SynthesisError> { + fn to_absorbable(&self) -> Result>, SynthesisError> { let mut result = Vec::new(); self.absorb_into(&mut result)?; Ok(result) } } -impl> AbsorbableGadget for &T { - fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { - >::absorb_into(self, dest) +impl> AbsorbableGadget for &T { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + (*self).absorb_into(dest) } } -impl> AbsorbableGadget for (T, T) { - fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { +impl> AbsorbableGadget for (T, T) { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.0.absorb_into(dest)?; self.1.absorb_into(dest) } } -impl> AbsorbableGadget for [T] { - fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { +impl> AbsorbableGadget for [T] { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.iter().try_for_each(|t| t.absorb_into(dest)) } } -impl, const N: usize> AbsorbableGadget for [T; N] { - fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { - <[T] as AbsorbableGadget>::absorb_into(self, dest) +impl, const N: usize> AbsorbableGadget for [T; N] { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + self.as_ref().absorb_into(dest) } } -impl> AbsorbableGadget for Vec { - fn absorb_into(&self, dest: &mut Vec) -> Result<(), SynthesisError> { - <[T] as AbsorbableGadget>::absorb_into(self, dest) +impl> AbsorbableGadget for Vec { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + self.as_slice().absorb_into(dest) } } diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs new file mode 100644 index 000000000..a16300dba --- /dev/null +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -0,0 +1,567 @@ +use ark_ff::{LegendreSymbol, PrimeField}; +use ark_r1cs_std::{ + alloc::AllocVar, + fields::{fp::FpVar, FieldVar}, + GR1CSVar, +}; +use ark_relations::gr1cs::SynthesisError; +use num_bigint::BigUint; +use sha3::{ + digest::{ExtendableOutput, Update, XofReader}, + Shake128, Shake128Reader, +}; + +pub mod sponge; + +pub fn field_element_from_shake(reader: &mut impl XofReader) -> F { + let mut buf = vec![0u8; F::MODULUS_BIT_SIZE.div_ceil(8) as usize]; + + loop { + reader.read(&mut buf); + if let Some(el) = F::from_random_bytes(&buf) { + return el; + } + } +} + +pub fn field_element_from_shake_without_0(reader: &mut impl XofReader) -> F { + loop { + let element = field_element_from_shake::(reader); + if !element.is_zero() { + return element; + } + } +} + +#[derive(Clone, Debug)] +pub struct GriffinParams { + pub(crate) round_constants: Vec>, + pub(crate) t: usize, + pub(crate) d: usize, + pub(crate) d_inv: Vec, + pub(crate) rounds: usize, + pub(crate) alpha_beta: Vec<[F; 2]>, + pub(crate) mat: Vec>, + pub rate: usize, + pub capacity: usize, +} + +impl GriffinParams { + pub const INIT_SHAKE: &'static str = "Griffin"; + + pub fn new(t: usize, d: usize, rounds: usize) -> Self { + assert!(t == 3 || t.is_multiple_of(4)); + assert!(d == 3 || d == 5); + assert!(rounds >= 1); + + let mut shake = Self::init_shake(); + + let d_inv = Self::calculate_d_inv(d as u64) + .to_radix_be(2) + .into_iter() + .map(|i| i != 0) + .skip_while(|i| !i) + .collect(); + let round_constants = Self::instantiate_rc(t, rounds, &mut shake); + let alpha_beta = Self::instantiate_alpha_beta(t, &mut shake); + + let mat = Self::instantiate_matrix(t); + + GriffinParams { + round_constants, + t, + d, + d_inv, + rounds, + alpha_beta, + mat, + rate: t - 1, + capacity: 1, + } + } + + fn calculate_d_inv(d: u64) -> BigUint { + let p_1 = -F::one(); + BigUint::from(d).modinv(&p_1.into()).unwrap() + } + + fn init_shake() -> Shake128Reader { + let mut shake = Shake128::default(); + shake.update(Self::INIT_SHAKE.as_bytes()); + for i in F::characteristic() { + shake.update(&i.to_le_bytes()); + } + shake.finalize_xof() + } + + fn instantiate_rc(t: usize, rounds: usize, shake: &mut Shake128Reader) -> Vec> { + (0..rounds - 1) + .map(|_| (0..t).map(|_| field_element_from_shake(shake)).collect()) + .collect() + } + + fn instantiate_alpha_beta(t: usize, shake: &mut Shake128Reader) -> Vec<[F; 2]> { + let mut alpha_beta = Vec::with_capacity(t - 2); + + // random alpha/beta + loop { + let alpha = field_element_from_shake_without_0::(shake); + let mut beta = field_element_from_shake_without_0::(shake); + // distinct + while alpha == beta { + beta = field_element_from_shake_without_0::(shake); + } + let mut symbol = alpha; + symbol.square_in_place(); + let mut tmp = beta; + tmp.double_in_place(); + tmp.double_in_place(); + symbol.sub_assign(&tmp); + if symbol.legendre() == LegendreSymbol::QuadraticNonResidue { + alpha_beta.push([alpha, beta]); + break; + } + } + + // other alphas/betas + for i in 2..t - 1 { + let mut alpha = alpha_beta[0][0]; + let mut beta = alpha_beta[0][1]; + alpha.mul_assign(&F::from(i as u64)); + beta.mul_assign(&F::from((i * i) as u64)); + // distinct + while alpha == beta { + beta = field_element_from_shake_without_0::(shake); + } + + #[cfg(debug_assertions)] + { + // check if really ok + let mut symbol = alpha; + symbol.square_in_place(); + let mut tmp = beta; + tmp.double_in_place(); + tmp.double_in_place(); + symbol.sub_assign(&tmp); + assert_eq!(symbol.legendre(), LegendreSymbol::QuadraticNonResidue); + } + + alpha_beta.push([alpha, beta]); + } + + alpha_beta + } + + fn circ_mat(row: &[F]) -> Vec> { + let t = row.len(); + let mut mat: Vec> = Vec::with_capacity(t); + let mut rot = row.to_owned(); + mat.push(rot.clone()); + for _ in 1..t { + rot.rotate_right(1); + mat.push(rot.clone()); + } + mat + } + + fn instantiate_matrix(t: usize) -> Vec> { + if t == 3 { + let row = vec![F::from(2), F::from(1), F::from(1)]; + Self::circ_mat(&row) + } else { + let row1 = vec![F::from(5), F::from(7), F::from(1), F::from(3)]; + let row2 = vec![F::from(4), F::from(6), F::from(1), F::from(1)]; + let row3 = vec![F::from(1), F::from(3), F::from(5), F::from(7)]; + let row4 = vec![F::from(1), F::from(1), F::from(4), F::from(6)]; + let c_mat = vec![row1, row2, row3, row4]; + if t == 4 { + c_mat + } else { + assert_eq!(t % 4, 0); + let mut mat: Vec> = vec![vec![F::zero(); t]; t]; + for (row, matrow) in mat.iter_mut().enumerate().take(t) { + for (col, matitem) in matrow.iter_mut().enumerate().take(t) { + let row_mod = row % 4; + let col_mod = col % 4; + *matitem = c_mat[row_mod][col_mod]; + if row / 4 == col / 4 { + matitem.add_assign(&c_mat[row_mod][col_mod]); + } + } + } + mat + } + } + } +} + +impl GriffinParams { + fn affine_3(&self, input: &mut [S], round: usize) { + // multiplication by circ(2 1 1) is equal to state + sum(state) + let mut sum = input[0]; + input.iter().skip(1).for_each(|el| sum.add_assign(el)); + + if round < self.rounds - 1 { + for (el, rc) in input.iter_mut().zip(self.round_constants[round].iter()) { + el.add_assign(&sum); + el.add_assign(rc); // add round constant + } + } else { + // no round constant + for el in input.iter_mut() { + el.add_assign(&sum); + } + } + } + + fn affine_4(&self, input: &mut [S], round: usize) { + let mut t_0 = input[0]; + t_0.add_assign(&input[1]); + let mut t_1 = input[2]; + t_1.add_assign(&input[3]); + let mut t_2 = input[1]; + t_2.double_in_place(); + t_2.add_assign(&t_1); + let mut t_3 = input[3]; + t_3.double_in_place(); + t_3.add_assign(&t_0); + let mut t_4 = t_1; + t_4.double_in_place(); + t_4.double_in_place(); + t_4.add_assign(&t_3); + let mut t_5 = t_0; + t_5.double_in_place(); + t_5.double_in_place(); + t_5.add_assign(&t_2); + let mut t_6 = t_3; + t_6.add_assign(&t_5); + let mut t_7 = t_2; + t_7.add_assign(&t_4); + input[0] = t_6; + input[1] = t_5; + input[2] = t_7; + input[3] = t_4; + + if round < self.rounds - 1 { + for (i, rc) in input.iter_mut().zip(self.round_constants[round].iter()) { + i.add_assign(rc); + } + } + } + + fn affine(&self, input: &mut [S], round: usize) { + if self.t == 3 { + self.affine_3(input, round); + return; + } + if self.t == 4 { + self.affine_4(input, round); + return; + } + + // first matrix + let t4 = self.t / 4; + for i in 0..t4 { + let start_index = i * 4; + let mut t_0 = input[start_index]; + t_0.add_assign(&input[start_index + 1]); + let mut t_1 = input[start_index + 2]; + t_1.add_assign(&input[start_index + 3]); + let mut t_2 = input[start_index + 1]; + t_2.double_in_place(); + t_2.add_assign(&t_1); + let mut t_3 = input[start_index + 3]; + t_3.double_in_place(); + t_3.add_assign(&t_0); + let mut t_4: S = t_1; + t_4.double_in_place(); + t_4.double_in_place(); + t_4.add_assign(&t_3); + let mut t_5 = t_0; + t_5.double_in_place(); + t_5.double_in_place(); + t_5.add_assign(&t_2); + input[start_index] = t_3 + t_5; + input[start_index + 1] = t_5; + input[start_index + 2] = t_2 + t_4; + input[start_index + 3] = t_4; + } + + // second matrix + let mut stored = [S::zero(); 4]; + for l in 0..4 { + stored[l] = input[l]; + for j in 1..t4 { + stored[l].add_assign(&input[4 * j + l]); + } + } + + for i in 0..input.len() { + input[i].add_assign(&stored[i % 4]); + if round < self.rounds - 1 { + input[i].add_assign(&self.round_constants[round][i]); // add round constant + } + } + } + + fn non_linear(&self, input: &mut [S]) { + // first two state words + input[0] = { + let mut res = S::one(); + for &i in &self.d_inv { + res.square_in_place(); + if i { + res *= input[0]; + } + } + res + }; + + let mut state = input[1]; + + input[1].square_in_place(); + match self.d { + 3 => {} + 5 => { + input[1].square_in_place(); + } + _ => panic!(), + } + input[1].mul_assign(&state); + + let mut y01_i = input[1]; + // rest of the state + for i in 2..input.len() { + y01_i += input[0]; + let l = if i == 2 { y01_i } else { y01_i + state }; + let ab = &self.alpha_beta[i - 2]; + state = input[i]; + input[i] *= l.square() + l * ab[0] + ab[1]; + } + } + + pub fn permute(&self, input: &mut [S]) { + self.affine(input, self.rounds); // no RC + + for r in 0..self.rounds { + self.non_linear(input); + self.affine(input, r); + } + } + + pub fn hash(&self, message: &[S]) -> S { + let mut state = vec![S::zero(); self.t]; + for chunk in message.chunks(self.rate) { + for i in 0..chunk.len() { + state[i] += &chunk[i]; + } + self.permute(&mut state) + } + state[0] + } +} + +impl GriffinParams { + fn non_linear_gadget(&self, state: &[FpVar]) -> Result>, SynthesisError> { + let cs = state.cs(); + let mut result = state.to_owned(); + // x0 + result[0] = FpVar::new_variable_with_inferred_mode(cs, || { + Ok({ + { + let v = result[0].value().unwrap_or_default(); + let mut res = F::one(); + for &i in &self.d_inv { + res.square_in_place(); + if i { + res *= v; + } + } + res + } + }) + })?; + + let mut sq = result[0].square()?; + if self.d == 5 { + sq = sq.square()?; + } + result[0].mul_equals(&sq, &state[0])?; + + // x1 + let mut sq = result[1].square()?; + if self.d == 5 { + sq = sq.square()?; + } + result[1] *= sq; + + let mut y01_i = result[1].clone(); + + // rest of the state + for i in 2..result.len() { + y01_i += &result[0]; + let l = if i == 2 { + y01_i.clone() + } else { + &y01_i + &state[i - 1] + }; + let ab = &self.alpha_beta[i - 2]; + result[i] *= l.square()? + l * ab[0] + ab[1]; + } + + Ok(result) + } + + pub fn permute_gadget(&self, state: &[FpVar]) -> Result>, SynthesisError> { + let mut current_state = state.to_owned(); + current_state = self + .mat + .iter() + .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) + .collect(); + + for r in 0..self.rounds { + current_state = self.non_linear_gadget(¤t_state)?; + current_state = self + .mat + .iter() + .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) + .collect(); + if r < self.rounds - 1 { + current_state = current_state + .iter() + .zip(&self.round_constants[r]) + .map(|(c, rc)| c + *rc) + .collect(); + } + } + Ok(current_state) + } + + pub fn hash_gadget(&self, message: &[FpVar]) -> Result, SynthesisError> { + let mut state = vec![FpVar::zero(); self.t]; + for chunk in message.chunks(self.rate) { + for i in 0..chunk.len() { + state[i] += &chunk[i]; + } + state = self.permute_gadget(&state)?; + } + Ok(state[0].clone()) + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::Fr; + use ark_ff::UniformRand; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::rand::thread_rng; + + use super::*; + + #[test] + fn test() { + let rng = &mut thread_rng(); + let griffin = GriffinParams::new(24, 5, 9); + let t = griffin.t; + let x: Vec = (0..t).map(|_| Fr::rand(rng)).collect(); + + let y = griffin.hash(&x); + + let cs = ConstraintSystem::new_ref(); + let x_var = Vec::new_witness(cs.clone(), || Ok(x.clone())).unwrap(); + let y_var = griffin.hash_gadget(&x_var).unwrap(); + assert_eq!(y, y_var.value().unwrap()); + println!("{}", cs.num_constraints()); + assert!(cs.is_satisfied().unwrap()); + } +} + +#[cfg(test)] +mod griffin_tests_bn256 { + use ark_bn254::Fr; + use ark_ff::UniformRand; + use ark_std::rand::thread_rng; + + use super::*; + + static TESTRUNS: usize = 5; + + #[test] + fn consistent_perm() { + let rng = &mut thread_rng(); + let griffin = GriffinParams::new(3, 5, 12); + let t = griffin.t; + for _ in 0..TESTRUNS { + let input1: Vec<_> = (0..t).map(|_| Fr::rand(rng)).collect(); + + let mut input2: Vec<_>; + loop { + input2 = (0..t).map(|_| Fr::rand(rng)).collect(); + if input1 != input2 { + break; + } + } + + let mut perm1 = input1.clone(); + let mut perm2 = input1.clone(); + let mut perm3 = input2.clone(); + griffin.permute(&mut perm1); + griffin.permute(&mut perm2); + griffin.permute(&mut perm3); + assert_eq!(perm1, perm2); + assert_ne!(perm1, perm3); + } + } + + fn matmul(input: &[F], mat: &[Vec]) -> Vec { + let t = mat.len(); + debug_assert!(t == input.len()); + let mut out = vec![F::zero(); t]; + for row in 0..t { + for (col, inp) in input.iter().enumerate() { + let mut tmp = mat[row][col]; + tmp *= inp; + out[row] += &tmp; + } + } + out + } + + fn affine_test(t: usize) { + let rng = &mut thread_rng(); + let griffin = GriffinParams::::new(t, 5, 1); + + let mat = &griffin.mat; + + for _ in 0..TESTRUNS { + let input: Vec = (0..t).map(|_| F::rand(rng)).collect(); + + // affine 1 + let output1 = matmul(&input, mat); + let mut output2 = input.to_owned(); + griffin.affine(&mut output2, 1); + assert_eq!(output1, output2); + } + } + + #[test] + fn affine_3() { + affine_test::(3); + } + + #[test] + fn affine_4() { + affine_test::(4); + } + + #[test] + fn affine_8() { + affine_test::(8); + } + + #[test] + fn affine_60() { + affine_test::(60); + } +} diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs new file mode 100644 index 000000000..314486d68 --- /dev/null +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -0,0 +1,471 @@ +use ark_crypto_primitives::sponge::DuplexSpongeMode; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{ + fields::{fp::FpVar, FieldVar}, + prelude::{Boolean, ToBitsGadget}, +}; +use ark_relations::gr1cs::SynthesisError; +use ark_std::sync::Arc; + +use crate::transcripts::{griffin::GriffinParams, AbsorbableGadget, Transcript, TranscriptVar}; + +#[derive(Clone)] +pub struct GriffinSponge { + /// Sponge Config + pub griffin: Arc>, + + // Sponge State + /// Current sponge's state (current elements in the permutation block) + pub state: Vec, + /// Current mode (whether its absorbing or squeezing) + pub mode: DuplexSpongeMode, +} + +impl GriffinSponge { + fn permute(&mut self) { + self.griffin.permute(&mut self.state); + } + + // Absorbs everything in elements, this does not end in an absorbtion. + fn absorb_internal(&mut self, mut rate_start_index: usize, elements: &[F]) { + let mut remaining_elements = elements; + + loop { + // if we can finish in this call + if rate_start_index + remaining_elements.len() <= self.griffin.rate { + for (i, element) in remaining_elements.iter().enumerate() { + self.state[self.griffin.capacity + i + rate_start_index] += element; + } + self.mode = DuplexSpongeMode::Absorbing { + next_absorb_index: rate_start_index + remaining_elements.len(), + }; + + return; + } + // otherwise absorb (rate - rate_start_index) elements + let num_elements_absorbed = self.griffin.rate - rate_start_index; + for (i, element) in remaining_elements + .iter() + .enumerate() + .take(num_elements_absorbed) + { + self.state[self.griffin.capacity + i + rate_start_index] += element; + } + self.permute(); + // the input elements got truncated by num elements absorbed + remaining_elements = &remaining_elements[num_elements_absorbed..]; + rate_start_index = 0; + } + } + + // Squeeze |output| many elements. This does not end in a squeeze + fn squeeze_internal(&mut self, mut rate_start_index: usize, output: &mut [F]) { + let mut output_remaining = output; + loop { + // if we can finish in this call + if rate_start_index + output_remaining.len() <= self.griffin.rate { + output_remaining.clone_from_slice( + &self.state[self.griffin.capacity + rate_start_index + ..(self.griffin.capacity + output_remaining.len() + rate_start_index)], + ); + self.mode = DuplexSpongeMode::Squeezing { + next_squeeze_index: rate_start_index + output_remaining.len(), + }; + return; + } + // otherwise squeeze (rate - rate_start_index) elements + let num_elements_squeezed = self.griffin.rate - rate_start_index; + output_remaining[..num_elements_squeezed].clone_from_slice( + &self.state[self.griffin.capacity + rate_start_index + ..(self.griffin.capacity + num_elements_squeezed + rate_start_index)], + ); + + // Repeat with updated output slices + output_remaining = &mut output_remaining[num_elements_squeezed..]; + // Unless we are done with squeezing in this call, permute. + if !output_remaining.is_empty() { + self.permute(); + } + + rate_start_index = 0; + } + } +} + +#[derive(Clone)] +pub struct GriffinSpongeVar { + /// Sponge Parameters + pub griffin: Arc>, + + // Sponge State + /// The sponge's state + pub state: Vec>, + /// The mode + pub mode: DuplexSpongeMode, +} + +impl GriffinSpongeVar { + fn permute(&mut self) -> Result<(), SynthesisError> { + self.state = self.griffin.permute_gadget(&self.state)?; + Ok(()) + } + + fn absorb_internal( + &mut self, + mut rate_start_index: usize, + elements: &[FpVar], + ) -> Result<(), SynthesisError> { + let mut remaining_elements = elements; + loop { + // if we can finish in this call + if rate_start_index + remaining_elements.len() <= self.griffin.rate { + for (i, element) in remaining_elements.iter().enumerate() { + self.state[self.griffin.capacity + i + rate_start_index] += element; + } + self.mode = DuplexSpongeMode::Absorbing { + next_absorb_index: rate_start_index + remaining_elements.len(), + }; + + return Ok(()); + } + // otherwise absorb (rate - rate_start_index) elements + let num_elements_absorbed = self.griffin.rate - rate_start_index; + for (i, element) in remaining_elements + .iter() + .enumerate() + .take(num_elements_absorbed) + { + self.state[self.griffin.capacity + i + rate_start_index] += element; + } + self.permute()?; + // the input elements got truncated by num elements absorbed + remaining_elements = &remaining_elements[num_elements_absorbed..]; + rate_start_index = 0; + } + } + + // Squeeze |output| many elements. This does not end in a squeeze + fn squeeze_internal( + &mut self, + mut rate_start_index: usize, + output: &mut [FpVar], + ) -> Result<(), SynthesisError> { + let mut remaining_output = output; + loop { + // if we can finish in this call + if rate_start_index + remaining_output.len() <= self.griffin.rate { + remaining_output.clone_from_slice( + &self.state[self.griffin.capacity + rate_start_index + ..(self.griffin.capacity + remaining_output.len() + rate_start_index)], + ); + self.mode = DuplexSpongeMode::Squeezing { + next_squeeze_index: rate_start_index + remaining_output.len(), + }; + return Ok(()); + } + // otherwise squeeze (rate - rate_start_index) elements + let num_elements_squeezed = self.griffin.rate - rate_start_index; + remaining_output[..num_elements_squeezed].clone_from_slice( + &self.state[self.griffin.capacity + rate_start_index + ..(self.griffin.capacity + num_elements_squeezed + rate_start_index)], + ); + + // Repeat with updated output slices and rate start index + remaining_output = &mut remaining_output[num_elements_squeezed..]; + + // Unless we are done with squeezing in this call, permute. + if !remaining_output.is_empty() { + self.permute()?; + } + rate_start_index = 0; + } + } +} + +impl Transcript for GriffinSponge { + type Config = Arc>; + + fn new(parameters: &Arc>) -> Self { + let state = vec![F::zero(); parameters.rate + parameters.capacity]; + let mode = DuplexSpongeMode::Absorbing { + next_absorb_index: 0, + }; + + Self { + griffin: parameters.clone(), + state, + mode, + } + } + + fn add_field_elements(&mut self, elems: &[F]) -> &mut Self { + if elems.is_empty() { + return self; + } + + match self.mode { + DuplexSpongeMode::Absorbing { next_absorb_index } => { + let mut absorb_index = next_absorb_index; + if absorb_index == self.griffin.rate { + self.permute(); + absorb_index = 0; + } + self.absorb_internal(absorb_index, elems); + } + DuplexSpongeMode::Squeezing { + next_squeeze_index: _, + } => { + self.absorb_internal(0, elems); + } + }; + self + } + + fn get_bits(&mut self, num_bits: usize) -> Vec { + let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; + + let num_elements = num_bits.div_ceil(usable_bits); + let src_elements = self.get_field_elements(num_elements); + + let mut bits: Vec = Vec::with_capacity(usable_bits * num_elements); + for elem in &src_elements { + let elem_bits = elem.into_bigint().to_bits_le(); + bits.extend_from_slice(&elem_bits[..usable_bits]); + } + + bits.truncate(num_bits); + bits + } + + fn get_field_elements(&mut self, num_elements: usize) -> Vec { + let mut squeezed_elems = vec![F::zero(); num_elements]; + match self.mode { + DuplexSpongeMode::Absorbing { + next_absorb_index: _, + } => { + self.permute(); + self.squeeze_internal(0, &mut squeezed_elems); + } + DuplexSpongeMode::Squeezing { next_squeeze_index } => { + let mut squeeze_index = next_squeeze_index; + if squeeze_index == self.griffin.rate { + self.permute(); + squeeze_index = 0; + } + self.squeeze_internal(squeeze_index, &mut squeezed_elems); + } + }; + + squeezed_elems + } +} + +impl TranscriptVar for GriffinSpongeVar { + type Native = GriffinSponge; + + fn new(parameters: &Arc>) -> Self + where + Self: Sized, + { + let zero = FpVar::::zero(); + let state = vec![zero; parameters.rate + parameters.capacity]; + let mode = DuplexSpongeMode::Absorbing { + next_absorb_index: 0, + }; + + Self { + griffin: parameters.clone(), + state, + mode, + } + } + + fn add + ?Sized>( + &mut self, + input: &A, + ) -> Result<&mut Self, SynthesisError> { + let input = input.to_absorbable()?; + if input.is_empty() { + return Ok(self); + } + + match self.mode { + DuplexSpongeMode::Absorbing { next_absorb_index } => { + let mut absorb_index = next_absorb_index; + if absorb_index == self.griffin.rate { + self.permute()?; + absorb_index = 0; + } + self.absorb_internal(absorb_index, input.as_slice())?; + } + DuplexSpongeMode::Squeezing { + next_squeeze_index: _, + } => { + self.absorb_internal(0, input.as_slice())?; + } + }; + + Ok(self) + } + + fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { + let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; + + let num_elements = num_bits.div_ceil(usable_bits); + let src_elements = self.get_field_elements(num_elements)?; + + let mut bits: Vec> = Vec::with_capacity(usable_bits * num_elements); + for elem in &src_elements { + bits.extend_from_slice(&elem.to_bits_le()?[..usable_bits]); + } + + bits.truncate(num_bits); + Ok(bits) + } + + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { + let zero = FpVar::zero(); + let mut squeezed_elems = vec![zero; num_elements]; + match self.mode { + DuplexSpongeMode::Absorbing { + next_absorb_index: _, + } => { + self.permute()?; + self.squeeze_internal(0, &mut squeezed_elems)?; + } + DuplexSpongeMode::Squeezing { next_squeeze_index } => { + let mut squeeze_index = next_squeeze_index; + if squeeze_index == self.griffin.rate { + self.permute()?; + squeeze_index = 0; + } + self.squeeze_internal(squeeze_index, &mut squeezed_elems)?; + } + }; + + Ok(squeezed_elems) + } +} + +#[cfg(test)] +pub mod tests { + use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; + use ark_ec::PrimeGroup; + use ark_ff::{BigInteger, PrimeField, UniformRand}; + use ark_r1cs_std::{ + alloc::AllocVar, + fields::fp::FpVar, + groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, + GR1CSVar, + }; + use ark_relations::gr1cs::ConstraintSystem; + use ark_std::{error::Error, test_rng}; + + use super::*; + use crate::algebra::group::emulated::EmulatedAffineVar; + + #[test] + fn test_transcript_and_transcriptvar_absorb_native_point() -> Result<(), Box> { + // use 'native' transcript + let config = Arc::new(GriffinParams::::new(3, 5, 12)); + let mut tr = GriffinSponge::::new(&config); + let rng = &mut test_rng(); + + let p = G1::rand(rng); + tr.add(&p); + let c = tr.challenge_field_element(); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = GriffinSpongeVar::::new(&config); + let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; + tr_var.add(&p_var)?; + let c_var = tr_var.challenge_field_element()?; + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } + + #[test] + fn test_transcript_and_transcriptvar_absorb_nonnative_point() -> Result<(), Box> { + // use 'native' transcript + let config = Arc::new(GriffinParams::::new(3, 5, 12)); + let mut tr = GriffinSponge::::new(&config); + let rng = &mut test_rng(); + + let p = G1::rand(rng); + tr.add(&p); + let c = tr.challenge_field_element(); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = GriffinSpongeVar::::new(&config); + let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; + tr_var.add(&p_var)?; + let c_var = tr_var.challenge_field_element()?; + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } + + #[test] + fn test_transcript_and_transcriptvar_get_challenge() -> Result<(), Box> { + // use 'native' transcript + let config = Arc::new(GriffinParams::::new(3, 5, 12)); + let mut tr = GriffinSponge::::new(&config); + tr.add(&Fr::from(42_u32)); + let c = tr.challenge_field_element(); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = GriffinSpongeVar::::new(&config); + let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; + tr_var.add(&v)?; + let c_var = tr_var.challenge_field_element()?; + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } + + #[test] + fn test_transcript_and_transcriptvar_nbits() -> Result<(), Box> { + let nbits = 128; + + // use 'native' transcript + let config = Arc::new(GriffinParams::::new(3, 5, 12)); + let mut tr = GriffinSponge::::new(&config); + tr.add(&Fq::from(42_u32)); + + // get challenge from native transcript + let c_bits = tr.challenge_bits(nbits); + + // use 'gadget' transcript + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = GriffinSpongeVar::::new(&config); + let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; + tr_var.add(&v)?; + + // get challenge from circuit transcript + let c_var = tr_var.challenge_bits(nbits)?; + + let p = G1::generator(); + let p_var = GVar::new_witness(cs.clone(), || Ok(p))?; + + // multiply point P by the challenge in different formats, to ensure that we get the same + // result natively and in-circuit + let c = Fr::from(::BigInt::from_bits_le(&c_bits)); + + // check that native c*P and in-circuit c*P using scalar_mul_le are equal + assert_eq!(p * c, p_var.scalar_mul_le(c_var.iter())?.value()?); + // check that native c*P using mul_bits_be and in-circuit c*P using scalar_mul_le are equal + // (notice the .rev to convert the LE to BE) + assert_eq!( + p.mul_bits_be(c_bits.into_iter().rev()), + p_var.scalar_mul_le(c_var.iter())?.value()? + ); + Ok(()) + } +} diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 21c709f19..4b6820a04 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -1,41 +1,49 @@ -use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, CryptographicSponge, FieldElementSize, -}; -use ark_ec::CurveGroup; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; -use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; - pub use absorbable::{Absorbable, AbsorbableGadget}; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; +use ark_relations::gr1cs::SynthesisError; pub mod absorbable; +pub mod griffin; pub mod poseidon; pub trait Transcript { + type Config; + + fn new(config: &Self::Config) -> Self; + /// `new_with_pp_hash` creates a new transcript / sponge with the given /// hash of the public parameters. fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self where - F: Absorbable, - Self: CryptographicSponge, + Self: Sized, { let mut sponge = Self::new(config); - sponge.add(&pp_hash); + sponge.add_field_elements(&[pp_hash]); sponge } - fn add + ?Sized>(&mut self, input: &A); + fn add(&mut self, input: &A) -> &mut Self { + let elems = input.to_absorbable(); + + self.add_field_elements(&elems) + } + + fn add_field_elements(&mut self, input: &[F]) -> &mut Self; /// Squeeze `num_bits` bits from the sponge. fn get_bits(&mut self, num_bits: usize) -> Vec; + fn get_field_element(&mut self) -> F { + self.get_field_elements(1)[0] + } + fn get_field_elements(&mut self, num_elements: usize) -> Vec; /// Creates a new sponge with applied domain separation. fn separate_domain(&self, domain: &[u8]) -> Self where - F: Absorbable, - Self: CryptographicSponge, + Self: Clone, { let mut new_sponge = self.clone(); @@ -47,66 +55,75 @@ pub trait Transcript { .map(|chunk| F::from_le_bytes_mod_order(chunk)) .collect::>(); - new_sponge.add(&limbs); + new_sponge.add_field_elements(&limbs); new_sponge } - fn challenge_field_element(&mut self) -> F - where - F: Absorbable, - { + fn challenge_field_element(&mut self) -> F { let c = self.get_field_elements(1); - self.add(&c[0]); + self.add_field_elements(&c); c[0] } - fn challenge_bits(&mut self, nbits: usize) -> Vec - where - F: Absorbable, - { + + fn challenge_bits(&mut self, nbits: usize) -> Vec { let bits = self.get_bits(nbits); - self.add(&F::from(F::BigInt::from_bits_le(&bits))); + self.add_field_elements( + &bits + .chunks(F::MODULUS_BIT_SIZE as usize - 1) + .map(F::BigInt::from_bits_le) + .map(F::from) + .collect::>(), + ); bits } - fn challenge_field_elements(&mut self, n: usize) -> Vec - where - F: Absorbable, - { + + fn challenge_field_elements(&mut self, n: usize) -> Vec { let c = self.get_field_elements(n); - self.add(&c); + self.add_field_elements(&c); c } } pub trait TranscriptVar { - type Native; + type Native: Transcript; + + fn new(config: &>::Config) -> Self + where + Self: Sized; /// `new_with_pp_hash` creates a new transcript / sponge with the given /// hash of the public parameters. fn new_with_pp_hash( - config: &Self::Parameters, + config: &>::Config, pp_hash: &FpVar, ) -> Result where - Self: CryptographicSpongeVar, - Self::Native: CryptographicSponge, + Self: Sized, { - let mut sponge = Self::new(ConstraintSystemRef::None, config); + let mut sponge = Self::new(config); sponge.add(&pp_hash)?; Ok(sponge) } - fn add>>(&mut self, input: &A) -> Result<(), SynthesisError>; + fn add + ?Sized>( + &mut self, + input: &A, + ) -> Result<&mut Self, SynthesisError>; /// Squeeze `num_bits` bits from the sponge. fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError>; + fn get_field_element(&mut self) -> Result, SynthesisError> { + Ok(self.get_field_elements(1)?.pop().unwrap()) + } + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError>; /// Creates a new sponge with applied domain separation. fn separate_domain(&self, domain: &[u8]) -> Result where - Self: CryptographicSponge, + Self: Clone, { let mut new_sponge = self.clone(); @@ -128,9 +145,15 @@ pub trait TranscriptVar { self.add(&c[0])?; Ok(c.pop().unwrap()) } + fn challenge_bits(&mut self, nbits: usize) -> Result>, SynthesisError> { let bits = self.get_bits(nbits)?; - self.add(&Boolean::le_bits_to_fp(&bits)?)?; + self.add( + &bits + .chunks(F::MODULUS_BIT_SIZE as usize - 1) + .map(Boolean::le_bits_to_fp) + .collect::, _>>()?, + )?; Ok(bits) } diff --git a/crates/primitives/src/transcripts/poseidon/mod.rs b/crates/primitives/src/transcripts/poseidon/mod.rs new file mode 100644 index 000000000..5693fb77c --- /dev/null +++ b/crates/primitives/src/transcripts/poseidon/mod.rs @@ -0,0 +1,37 @@ +use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, find_poseidon_ark_and_mds}; +use ark_ff::PrimeField; + +pub mod sponge; + +/// This Poseidon configuration generator produces a Poseidon configuration with custom parameters +pub fn poseidon_custom_config( + full_rounds: usize, + partial_rounds: usize, + alpha: u64, + rate: usize, + capacity: usize, +) -> PoseidonConfig { + let (ark, mds) = find_poseidon_ark_and_mds::( + F::MODULUS_BIT_SIZE as u64, + rate, + full_rounds as u64, + partial_rounds as u64, + 0, + ); + + PoseidonConfig::new(full_rounds, partial_rounds, alpha, mds, ark, rate, capacity) +} + +/// This Poseidon configuration generator agrees with Circom's Poseidon(4) in the case of BN254's scalar field +pub fn poseidon_canonical_config() -> PoseidonConfig { + // 120 bit security target as in + // https://eprint.iacr.org/2019/458.pdf + // t = rate + 1 + + let full_rounds = 8; + let partial_rounds = 60; + let alpha = 5; + let rate = 4; + + poseidon_custom_config(full_rounds, partial_rounds, alpha, rate, 1) +} diff --git a/crates/primitives/src/transcripts/poseidon.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs similarity index 71% rename from crates/primitives/src/transcripts/poseidon.rs rename to crates/primitives/src/transcripts/poseidon/sponge.rs index 1b10ef38a..9601d8bbe 100644 --- a/crates/primitives/src/transcripts/poseidon.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -1,24 +1,25 @@ use ark_crypto_primitives::sponge::{ constraints::CryptographicSpongeVar, - poseidon::{ - constraints::PoseidonSpongeVar, find_poseidon_ark_and_mds, PoseidonConfig, PoseidonSponge, - }, + poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, FieldBasedCryptographicSponge, }; -use ark_ec::CurveGroup; use ark_ff::PrimeField; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar, groups::CurveVar}; -use ark_relations::gr1cs::SynthesisError; +use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; +use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; use ark_std::mem::transmute_copy; -use crate::transcripts::Absorbable; - -use super::{AbsorbableGadget, Transcript, TranscriptVar}; +use crate::transcripts::{AbsorbableGadget, Transcript, TranscriptVar}; impl Transcript for PoseidonSponge { - fn add + ?Sized>(&mut self, input: &A) { + type Config = PoseidonConfig; + + fn new(config: &Self::Config) -> Self { + CryptographicSponge::new(config) + } + + fn add_field_elements(&mut self, input: &[F]) -> &mut Self { struct Hack(I); - impl Absorb for Hack> { + impl Absorb for Hack<&[F]> { fn to_sponge_bytes(&self, _: &mut Vec) { // Unreachable because `PoseidonSponge::absorb` only calls // `to_sponge_field_elements_as_vec::` @@ -29,11 +30,11 @@ impl Transcript for PoseidonSponge { // Safe because `F` in `to_sponge_field_elements_as_vec::`, // which is called by `PoseidonSponge::absorb`, is the same as // `T` here. - dest.extend(unsafe { transmute_copy::<&[F], &[T]>(&self.0.as_ref()) }); + dest.extend(unsafe { transmute_copy::<&[F], &[T]>(&self.0) }); } } - let v = input.to_absorbable(); - CryptographicSponge::absorb(self, &Hack(v)); + CryptographicSponge::absorb(self, &Hack(input)); + self } fn get_bits(&mut self, num_bits: usize) -> Vec { @@ -48,8 +49,19 @@ impl Transcript for PoseidonSponge { impl TranscriptVar for PoseidonSpongeVar { type Native = PoseidonSponge; - fn add>>(&mut self, input: &A) -> Result<(), SynthesisError> { - self.absorb(&input.to_absorbable()?) + fn new(config: &PoseidonConfig) -> Self + where + Self: Sized, + { + CryptographicSpongeVar::new(ConstraintSystemRef::None, config) + } + + fn add + ?Sized>( + &mut self, + input: &A, + ) -> Result<&mut Self, SynthesisError> { + self.absorb(&input.to_absorbable()?)?; + Ok(self) } fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { @@ -61,59 +73,31 @@ impl TranscriptVar for PoseidonSpongeVar { } } -/// This Poseidon configuration generator produces a Poseidon configuration with custom parameters -pub fn poseidon_custom_config( - full_rounds: usize, - partial_rounds: usize, - alpha: u64, - rate: usize, - capacity: usize, -) -> PoseidonConfig { - let (ark, mds) = find_poseidon_ark_and_mds::( - F::MODULUS_BIT_SIZE as u64, - rate, - full_rounds as u64, - partial_rounds as u64, - 0, - ); - - PoseidonConfig::new(full_rounds, partial_rounds, alpha, mds, ark, rate, capacity) -} - -/// This Poseidon configuration generator agrees with Circom's Poseidon(4) in the case of BN254's scalar field -pub fn poseidon_canonical_config() -> PoseidonConfig { - // 120 bit security target as in - // https://eprint.iacr.org/2019/458.pdf - // t = rate + 1 - - let full_rounds = 8; - let partial_rounds = 60; - let alpha = 5; - let rate = 4; - - poseidon_custom_config(full_rounds, partial_rounds, alpha, rate, 1) -} - #[cfg(test)] pub mod tests { use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; + use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; use ark_ec::PrimeGroup; - use ark_ff::{BigInteger, UniformRand}; + use ark_ff::{BigInteger, PrimeField, UniformRand}; use ark_r1cs_std::{ - alloc::AllocVar, groups::curves::short_weierstrass::ProjectiveVar, GR1CSVar, + alloc::AllocVar, + fields::fp::FpVar, + groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, + GR1CSVar, }; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, str::FromStr, test_rng}; - use crate::algebra::group::nonnative::NonNativeAffineVar; - - use super::*; + use crate::{ + algebra::group::emulated::EmulatedAffineVar, + transcripts::{poseidon::poseidon_canonical_config, Transcript, TranscriptVar}, + }; // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 #[test] fn check_against_circom_poseidon() -> Result<(), Box> { let config = poseidon_canonical_config::(); - let mut poseidon_sponge: PoseidonSponge<_> = CryptographicSponge::new(&config); + let mut poseidon_sponge = PoseidonSponge::new(&config); let v = vec![1, 2, 3, 4] .into_iter() .map(Fr::from) @@ -143,11 +127,8 @@ pub mod tests { // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); - let p_var = ProjectiveVar::>::new_witness( - ConstraintSystem::::new_ref(), - || Ok(p), - )?; + let mut tr_var = PoseidonSpongeVar::::new(&config); + let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; @@ -169,9 +150,8 @@ pub mod tests { // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); - let p_var = - NonNativeAffineVar::::new_witness(ConstraintSystem::::new_ref(), || Ok(p))?; + let mut tr_var = PoseidonSpongeVar::::new(&config); + let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; @@ -190,7 +170,7 @@ pub mod tests { // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); + let mut tr_var = PoseidonSpongeVar::::new(&config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; tr_var.add(&v)?; let c_var = tr_var.challenge_field_element()?; @@ -214,7 +194,7 @@ pub mod tests { // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(cs.clone(), &config); + let mut tr_var = PoseidonSpongeVar::::new(&config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; tr_var.add(&v)?; diff --git a/crates/primitives/src/utils/mod.rs b/crates/primitives/src/utils/mod.rs new file mode 100644 index 000000000..1e8ac48a7 --- /dev/null +++ b/crates/primitives/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod null; +// pub mod vec; diff --git a/crates/primitives/src/utils/null.rs b/crates/primitives/src/utils/null.rs new file mode 100644 index 000000000..6d043534b --- /dev/null +++ b/crates/primitives/src/utils/null.rs @@ -0,0 +1,75 @@ +use ark_ff::Field; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + GR1CSVar, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{ + borrow::Borrow, + fmt::Debug, + iter::Sum, + ops::{Add, Mul}, +}; + +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +pub struct Null; + +impl Add for Null { + type Output = Null; + + fn add(self, _: F) -> Null { + Null + } +} + +impl Add for &Null { + type Output = Null; + + fn add(self, _: F) -> Null { + Null + } +} + +impl Mul for Null { + type Output = Self; + + fn mul(self, _: F) -> Null { + Null + } +} + +impl Mul for &Null { + type Output = Null; + + fn mul(self, _: F) -> Null { + Null + } +} + +impl Sum for Null { + fn sum>(_: I) -> Self { + Null + } +} + +impl AllocVar for Null { + fn new_variable>( + _cs: impl Into>, + _f: impl FnOnce() -> Result, + _mode: AllocationMode, + ) -> Result { + Ok(Self) + } +} + +impl GR1CSVar for Null { + type Value = Null; + + fn cs(&self) -> ConstraintSystemRef { + ConstraintSystemRef::None + } + + fn value(&self) -> Result { + Ok(Null) + } +} diff --git a/crates/primitives/src/utils/vec.rs b/crates/primitives/src/utils/vec.rs new file mode 100644 index 000000000..f1c9fec21 --- /dev/null +++ b/crates/primitives/src/utils/vec.rs @@ -0,0 +1,109 @@ +use ark_ff::{Field, PrimeField}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + fields::fp::FpVar, + prelude::Boolean, + select::CondSelectGadget, + GR1CSVar, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{ + borrow::Borrow, + fmt::Debug, + ops::{Deref, DerefMut}, +}; + +use crate::{ + arithmetizations::ArithConfig, + circuits::var::Var, + traits::Dummy, + transcripts::{Absorbable, AbsorbableGadget}, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WrappedVec(Vec); + +impl Deref for WrappedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for WrappedVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for WrappedVec { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl Absorbable for WrappedVec { + fn absorb_into(&self, dest: &mut Vec) { + self.0.absorb_into(dest) + } +} + +impl> AbsorbableGadget for WrappedVec { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + self.0.absorb_into(dest) + } +} + +impl, Y, F: Field> AllocVar, F> for WrappedVec { + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let v = f()?; + Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(|v| Self(v)) + } +} + +impl> CondSelectGadget for WrappedVec { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.len() != false_value.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(WrappedVec( + true_value + .0 + .iter() + .zip(false_value.0.iter()) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + )) + } +} + +impl> GR1CSVar for WrappedVec { + type Value = WrappedVec; + + fn cs(&self) -> ConstraintSystemRef { + self.0.cs() + } + + fn value(&self) -> Result { + self.0.value().map(WrappedVec) + } +} + +impl> Var for WrappedVec { + type Native = WrappedVec; +} + +impl Dummy<&A> for WrappedVec { + fn dummy(cfg: &A) -> Self { + vec![V::default(); cfg.n_public_inputs()].into() + } +} From 638f248569dd01dc9f3e66c62a405d92b5bf4416 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 03:02:00 +0800 Subject: [PATCH 11/99] More polynomial and power utils --- crates/primitives/src/algebra/ops/mod.rs | 1 + crates/primitives/src/algebra/ops/poly.rs | 44 ++++++++++++++++++ crates/primitives/src/algebra/ops/pow.rs | 54 +++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 crates/primitives/src/algebra/ops/poly.rs diff --git a/crates/primitives/src/algebra/ops/mod.rs b/crates/primitives/src/algebra/ops/mod.rs index a0f820298..bbd850498 100644 --- a/crates/primitives/src/algebra/ops/mod.rs +++ b/crates/primitives/src/algebra/ops/mod.rs @@ -1,6 +1,7 @@ pub mod bits; pub mod eq; pub mod matrix; +pub mod poly; pub mod pow; pub mod rlc; pub mod vector; diff --git a/crates/primitives/src/algebra/ops/poly.rs b/crates/primitives/src/algebra/ops/poly.rs new file mode 100644 index 000000000..53b5c345a --- /dev/null +++ b/crates/primitives/src/algebra/ops/poly.rs @@ -0,0 +1,44 @@ +use ark_ff::PrimeField; +use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; +use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; +use ark_relations::gr1cs::SynthesisError; + +use super::pow::Pow; + +pub trait EvaluationDomainGadget { + fn evaluate_all_lagrange_coefficients_var( + &self, + tau: &FpVar, + ) -> Result>, SynthesisError>; + + fn evaluate_vanishing_polynomial_var(&self, tau: &FpVar) + -> Result, SynthesisError>; +} + +impl EvaluationDomainGadget for GeneralEvaluationDomain { + fn evaluate_all_lagrange_coefficients_var( + &self, + tau: &FpVar, + ) -> Result>, SynthesisError> { + let size = self.size() as u64; + let size_inv = self.size_inv(); + let offset = self.coset_offset(); + let offset_inv = self.coset_offset_inv(); + let group_gen = self.group_gen(); + + let l_i = (tau.pow_by_constant([size])? * offset_inv.pow([size - 1]) - offset) * size_inv; + + group_gen + .powers(size as usize) + .into_iter() + .map(|g| (&l_i * g).mul_by_inverse(&(tau - offset * g))) + .collect() + } + + fn evaluate_vanishing_polynomial_var( + &self, + tau: &FpVar, + ) -> Result, SynthesisError> { + Ok(tau.pow_by_constant([self.size() as u64])? - self.coset_offset_pow_size()) + } +} diff --git a/crates/primitives/src/algebra/ops/pow.rs b/crates/primitives/src/algebra/ops/pow.rs index 0406ba855..918577115 100644 --- a/crates/primitives/src/algebra/ops/pow.rs +++ b/crates/primitives/src/algebra/ops/pow.rs @@ -2,7 +2,14 @@ use ark_ff::{Field, PrimeField}; use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; pub trait Pow: Sized { + /// Compute `self^0, self^1, ..., self^{n-1}` fn powers(&self, n: usize) -> Vec; + + /// Compute `self^{2^0}, self^{2^1}, ..., self^{2^{n-1}}` + fn repeated_squares(&self, n: usize) -> Vec; + + /// Compute `self^0, self^1, ..., self^{2^n - 1}` from repeated squares + fn powers_from_repeated_squares(squares: &[Self]) -> Vec; } impl Pow for F { @@ -13,10 +20,34 @@ impl Pow for F { } res } + + fn repeated_squares(&self, n: usize) -> Vec { + if n == 0 { + return vec![]; + } + let mut res = vec![F::zero(); n]; + res[0] = *self; + for i in 1..n { + res[i] = res[i - 1].square(); + } + res + } + + fn powers_from_repeated_squares(squares: &[Self]) -> Vec { + let mut pows = vec![F::one()]; + for square in squares.iter().rev() { + pows = pows.into_iter().flat_map(|e| [e, e * square]).collect(); + } + pows + } } pub trait PowGadget: Sized { fn powers(&self, n: usize) -> Vec; + + fn repeated_squares(&self, n: usize) -> Vec; + + fn powers_from_repeated_squares(squares: &[Self]) -> Vec; } impl PowGadget for FpVar { @@ -27,4 +58,27 @@ impl PowGadget for FpVar { } res } + + fn repeated_squares(&self, n: usize) -> Vec { + if n == 0 { + return vec![]; + } + let mut res = vec![FpVar::zero(); n]; + res[0] = self.clone(); + for i in 1..n { + res[i] = &res[i - 1] * &res[i - 1]; + } + res + } + + fn powers_from_repeated_squares(squares: &[Self]) -> Vec { + let mut pows = vec![FpVar::one()]; + for square in squares.iter().rev() { + pows = pows + .into_iter() + .flat_map(|e| [e.clone(), e * square]) + .collect(); + } + pows + } } From 44d66285f9e147bf11cb7239bd77cb8eec3b0363 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 03:04:37 +0800 Subject: [PATCH 12/99] Prefer ark_std over std --- crates/primitives/src/arithmetizations/ccs/mod.rs | 4 +--- crates/primitives/src/arithmetizations/mod.rs | 3 +-- crates/primitives/src/circuits/mod.rs | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index dee3af2de..6bed62774 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -1,9 +1,7 @@ -use std::fmt::Debug; - use ark_ff::Field; use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; -use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, log2, marker::PhantomData}; +use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, fmt::Debug, log2, marker::PhantomData}; #[cfg(feature = "parallel")] use rayon::prelude::*; diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 28073b34d..0866a7d11 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -1,6 +1,5 @@ -use std::fmt::Debug; - use ark_relations::gr1cs::SynthesisError; +use ark_std::fmt::Debug; use thiserror::Error; use crate::relations::Relation; diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index c38edbd69..38bf99314 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -1,11 +1,10 @@ -use std::fmt::Debug; - use ark_ff::{Field, PrimeField}; use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; use ark_std::{ + fmt::Debug, marker::PhantomData, ops::{Index, IndexMut}, }; From 7317892737a16f993a2f4b9d0b399a050c2701e8 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 04:30:45 +0800 Subject: [PATCH 13/99] Cleanup & reduce verbosity --- .../primitives/src/algebra/field/emulated.rs | 39 ++++--- crates/primitives/src/algebra/field/mod.rs | 7 +- .../primitives/src/algebra/group/emulated.rs | 4 +- crates/primitives/src/algebra/group/mod.rs | 5 +- crates/primitives/src/algebra/ops/poly.rs | 17 ++- .../src/arithmetizations/ccs/mod.rs | 42 +++---- crates/primitives/src/arithmetizations/mod.rs | 24 ++-- .../src/arithmetizations/r1cs/mod.rs | 50 +-------- crates/primitives/src/circuits/mod.rs | 3 +- crates/primitives/src/commitments/mod.rs | 19 ++-- crates/primitives/src/commitments/pedersen.rs | 104 ++++++++++++------ crates/primitives/src/relations/mod.rs | 19 ---- crates/primitives/src/sumcheck/mod.rs | 24 ++-- .../primitives/src/transcripts/griffin/mod.rs | 14 ++- .../src/transcripts/griffin/sponge.rs | 1 + crates/primitives/src/transcripts/mod.rs | 37 ++----- .../src/transcripts/poseidon/sponge.rs | 1 + 17 files changed, 202 insertions(+), 208 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 73a5ec9cc..015140d01 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -100,8 +100,8 @@ pub struct IntVarInner { pub bounds: Vec, } -pub type BigIntVar = IntVarInner; -pub type EmulatedFieldVar = IntVarInner; +pub type BigIntVar = IntVarInner; +pub type EmulatedFieldVar = IntVarInner; impl GR1CSVar for IntVarInner { type Value = BigInt; @@ -125,14 +125,15 @@ impl GR1CSVar } fn value(&self) -> Result { - self.limbs.value().map(compose).map(|v| { - let (sign, abs) = v.into_parts(); - assert!(abs < Target::MODULUS.into()); - match sign { - Sign::Plus | Sign::NoSign => Target::from(abs), - Sign::Minus => Target::zero() - Target::from(abs), - } - }) + let v = compose(self.limbs.value()?); + let (sign, abs) = v.into_parts(); + if abs >= Target::MODULUS.into() { + return Err(SynthesisError::Unsatisfiable); + } + match sign { + Sign::Plus | Sign::NoSign => Ok(Target::from(abs)), + Sign::Minus => Ok(Target::zero() - Target::from(abs)), + } } } @@ -684,7 +685,9 @@ impl CondSelectGadget for IntVarInner ToBitsGadget for IntVarInner { fn to_bits_le(&self) -> Result>, SynthesisError> { for bound in &self.bounds { - assert!(bound.0 >= BigInt::zero()); + if bound.0 < BigInt::zero() { + return Err(SynthesisError::Unsatisfiable); + } } Ok(self .limbs @@ -946,6 +949,8 @@ impl AllocVar for IntVarInner IntVarInner { pub fn constant(x: BigInt) -> Self { + // `unwrap` below is safe because we are allocating a constant value, + // which is guaranteed to succeed. Self::new_constant(ConstraintSystemRef::None, (x.clone(), Bound(x.clone(), x))).unwrap() } } @@ -1128,7 +1133,7 @@ mod tests { Ok((a.clone(), Bound(lb.clone(), ub.clone()))) })?; - let a_const = BigIntVar::::constant(a.clone()); + let a_const = BigIntVar::::constant(a.clone()); assert_eq!(a, a_var.value()?); assert_eq!(a, a_const.value()?); @@ -1223,7 +1228,7 @@ mod tests { let aab = a * ab; let abb = ab * b; - let a_var = EmulatedFieldVar::::new_witness(cs.clone(), || Ok(a))?; + let a_var = EmulatedFieldVar::::new_witness(cs.clone(), || Ok(a))?; let b_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(b))?; let ab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(ab))?; let aab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(aab))?; @@ -1245,7 +1250,7 @@ mod tests { let a = Fq::rand(rng); - let a_var = EmulatedFieldVar::::new_witness(cs.clone(), || Ok(a))?; + let a_var = EmulatedFieldVar::::new_witness(cs.clone(), || Ok(a))?; let mut r_var = a_var.clone(); for _ in 0..16 { @@ -1268,11 +1273,11 @@ mod tests { let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); - let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; - let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; + let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; + let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?; - let mut r_var: EmulatedFieldVar = + let mut r_var: IntVarInner = EmulatedFieldVar::constant(BigUint::zero().into()).into(); for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { r_var = r_var.add_unaligned(&a.mul_unaligned(&b)?)?; diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 4cd35cd6e..5caac15b2 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -14,7 +14,10 @@ pub mod emulated; /// `Field` trait is a wrapper around `PrimeField` that also includes the /// necessary bounds for the field to be used conveniently in folding schemes. pub trait SonobeField: - PrimeField + Absorbable + Inputize + Val> + PrimeField + + Absorbable + + Inputize + + Val, EmulatedVar = EmulatedFieldVar> { const BITS_PER_LIMB: usize; } @@ -45,7 +48,7 @@ impl, const N: usize> Val for Fp { type ConstraintField = Self; type Var = FpVar; - type EmulatedVar = EmulatedFieldVar; + type EmulatedVar = EmulatedFieldVar; } impl, const N: usize> Absorbable for Fp { diff --git a/crates/primitives/src/algebra/group/emulated.rs b/crates/primitives/src/algebra/group/emulated.rs index 733dc2a45..4ebd0cf79 100644 --- a/crates/primitives/src/algebra/group/emulated.rs +++ b/crates/primitives/src/algebra/group/emulated.rs @@ -23,8 +23,8 @@ use crate::{ /// the affine coordinates in order to perform hash operations of the point. #[derive(Debug, Clone)] pub struct EmulatedAffineVar { - pub x: EmulatedFieldVar, - pub y: EmulatedFieldVar, + pub x: EmulatedFieldVar, + pub y: EmulatedFieldVar, } impl AllocVar diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 2dbf59ae5..c9ccf37d6 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -33,7 +33,10 @@ pub trait SonobeCurve: + Absorbable + Inputize + InputizeEmulated - + Val + AbsorbableGadget> + + Val< + Var: CurveVar + AbsorbableGadget, + EmulatedVar = EmulatedAffineVar + > { } diff --git a/crates/primitives/src/algebra/ops/poly.rs b/crates/primitives/src/algebra/ops/poly.rs index 53b5c345a..aec303013 100644 --- a/crates/primitives/src/algebra/ops/poly.rs +++ b/crates/primitives/src/algebra/ops/poly.rs @@ -1,10 +1,23 @@ -use ark_ff::PrimeField; -use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; +use ark_ff::{Field, PrimeField, Zero}; +use ark_poly::{DenseMultilinearExtension, EvaluationDomain, GeneralEvaluationDomain}; use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; use ark_relations::gr1cs::SynthesisError; +use ark_std::log2; use super::pow::Pow; +pub trait MLEHelper { + fn from_evaluations(evaluations: &[F]) -> Self; +} + +impl MLEHelper for DenseMultilinearExtension { + fn from_evaluations(evaluations: &[F]) -> Self { + let l = evaluations.len(); + let pad = vec![Zero::zero(); l.next_power_of_two() - l]; + Self::from_evaluations_vec(log2(l) as usize, [evaluations, &pad].concat()) + } +} + pub trait EvaluationDomainGadget { fn evaluate_all_lagrange_coefficients_var( &self, diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 6bed62774..a0fc6355a 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -1,19 +1,20 @@ use ark_ff::Field; use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; -use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, fmt::Debug, log2, marker::PhantomData}; +use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, fmt::Debug, marker::PhantomData}; #[cfg(feature = "parallel")] use rayon::prelude::*; use super::{r1cs::R1CS, Arith, ArithRelation, Error}; use crate::{ + algebra::ops::poly::MLEHelper, arithmetizations::{r1cs::R1CSConfig, ArithConfig}, circuits::Assignments, }; pub mod circuits; -pub trait CCSVariant: Clone + Debug + PartialEq + Sync { +pub trait CCSVariant: Clone + Debug + PartialEq + Default + Sync { fn n_matrices() -> usize; fn degree() -> usize; @@ -24,7 +25,7 @@ pub trait CCSVariant: Clone + Debug + PartialEq + Sync { } #[allow(non_snake_case)] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct CCSConfig { _v: PhantomData, /// m: number of rows in M_i (such that M_i \in F^{m, n}) @@ -36,16 +37,6 @@ pub struct CCSConfig { } impl ArithConfig for CCSConfig { - #[inline] - fn empty() -> Self { - Self { - _v: PhantomData, - m: 0, - n: 0, - l: 0, - } - } - #[inline] fn degree(&self) -> usize { V::degree() @@ -169,29 +160,30 @@ impl CCS { &self, z: Assignments + Sync>, ) -> Vec> { - let s = log2(self.n_constraints()) as usize; (0..V::n_matrices()) - .map(|i| DenseMultilinearExtension { - num_vars: s, - evaluations: cfg_iter!(self.M[i]) - .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) - .chain(vec![F::zero(); (1 << s) - self.n_constraints()]) - .collect(), + .map(|i| { + DenseMultilinearExtension::from_evaluations( + &cfg_iter!(self.M[i]) + .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) + .collect::>(), + ) }) .collect() } } -impl Arith for CCS { - type Config = CCSConfig; - +impl Default for CCS { #[inline] - fn empty() -> Self { + fn default() -> Self { Self { - cfg: CCSConfig::empty(), + cfg: CCSConfig::default(), M: vec![vec![]; V::n_matrices()], } } +} + +impl Arith for CCS { + type Config = CCSConfig; #[inline] fn config(&self) -> &Self::Config { diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 0866a7d11..f5f391209 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -1,5 +1,5 @@ use ark_relations::gr1cs::SynthesisError; -use ark_std::fmt::Debug; +use ark_std::{fmt::Debug, log2}; use thiserror::Error; use crate::relations::Relation; @@ -19,15 +19,17 @@ pub enum Error { SynthesisError(#[from] SynthesisError), } -pub trait ArithConfig: Clone + Debug + PartialEq { - fn empty() -> Self; - +pub trait ArithConfig: Clone + Debug + Default + PartialEq { /// Returns the degree of the constraint system fn degree(&self) -> usize; /// Returns the number of constraints in the constraint system fn n_constraints(&self) -> usize; + fn log_constraints(&self) -> usize { + log2(self.n_constraints()) as usize + } + /// Returns the number of variables in the constraint system fn n_variables(&self) -> usize; @@ -43,37 +45,45 @@ pub trait ArithConfig: Clone + Debug + PartialEq { /// [`Arith`] is a trait about constraint systems (R1CS, CCS, etc.), where we /// define methods for getting information about the constraint system. -pub trait Arith: Clone { +pub trait Arith: Clone + Default { type Config: ArithConfig; fn config(&self) -> &Self::Config; fn config_mut(&mut self) -> &mut Self::Config; - fn empty() -> Self; - /// Returns the degree of the constraint system + #[inline] fn degree(&self) -> usize { self.config().degree() } /// Returns the number of constraints in the constraint system + #[inline] fn n_constraints(&self) -> usize { self.config().n_constraints() } + #[inline] + fn log_constraints(&self) -> usize { + self.config().log_constraints() + } + /// Returns the number of variables in the constraint system + #[inline] fn n_variables(&self) -> usize { self.config().n_variables() } /// Returns the number of public inputs / public IO / instances / statements /// in the constraint system + #[inline] fn n_public_inputs(&self) -> usize { self.config().n_public_inputs() } /// Returns the number of witnesses / secret inputs in the constraint system + #[inline] fn n_witnesses(&self) -> usize { self.config().n_witnesses() } diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 2733a90fa..571ac6313 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -9,12 +9,11 @@ use super::{ccs::CCS, Arith, ArithRelation, Error}; use crate::{ arithmetizations::{ccs::CCSVariant, ArithConfig}, circuits::Assignments, - relations::WitnessInstanceExtractor, }; pub mod circuits; -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct R1CSConfig { m: usize, // number of constraints n: usize, // number of variables @@ -32,11 +31,6 @@ impl R1CSConfig { } impl ArithConfig for R1CSConfig { - #[inline] - fn empty() -> Self { - Self { m: 0, n: 0, l: 0 } - } - #[inline] fn degree(&self) -> usize { 2 @@ -97,7 +91,7 @@ impl CCSVariant for R1CSConfig { } #[allow(non_snake_case)] -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct R1CS { cfg: R1CSConfig, pub A: Matrix, @@ -140,16 +134,6 @@ impl R1CS { impl Arith for R1CS { type Config = R1CSConfig; - #[inline] - fn empty() -> Self { - Self { - cfg: R1CSConfig::empty(), - A: vec![], - B: vec![], - C: vec![], - } - } - #[inline] fn config(&self) -> &Self::Config { &self.cfg @@ -217,15 +201,6 @@ impl, U: AsRef<[F]>> ArithRelation for R1CS { } } -impl WitnessInstanceExtractor, Vec> for R1CS { - type Source = Assignments>; - type Error = Error; - - fn extract(&self, z: Self::Source) -> Result<(Vec, Vec), Error> { - Ok((z.private, z.public)) - } -} - pub struct RelaxedWitness { pub w: V, pub e: V, @@ -262,22 +237,6 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 } } -impl WitnessInstanceExtractor>, RelaxedInstance>> - for R1CS -{ - type Source = Assignments>; - type Error = Error; - - fn extract( - &self, - z: Self::Source, - ) -> Result<(RelaxedWitness>, RelaxedInstance>), Error> { - let (w, x) = self.extract(z)?; - let e = vec![F::zero(); self.n_constraints()]; - Ok((RelaxedWitness { w, e }, RelaxedInstance { x, u: F::one() })) - } -} - #[cfg(test)] pub mod tests { use ark_bn254::Fr; @@ -298,7 +257,7 @@ pub mod tests { x: Fr::rand(&mut rng), }; let cs = ConstraintSystem::new_ref(); - circuit.generate_constraints(cs.clone()).unwrap(); + circuit.generate_constraints(cs.clone())?; assert!(cs.is_satisfied()?); cs.finalize(); let cs = cs.into_inner().unwrap(); @@ -313,10 +272,9 @@ pub mod tests { let x = Fr::rand(&mut rng); let circuit = CircuitForTest:: { x }; let cs = ConstraintSystem::new_ref(); - circuit.generate_constraints(cs.clone()).unwrap(); + circuit.generate_constraints(cs.clone())?; assert!(cs.is_satisfied()?); cs.finalize(); - let cs = cs.into_inner().unwrap(); assert_eq!(cs.assignments()?, satisfying_assignments_for_test(x)); Ok(()) diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 38bf99314..3bcdf6416 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -49,7 +49,6 @@ pub struct Assignments { } pub type AssignmentsOwned = Assignments>; -pub type AssignmentsRef<'a, F> = Assignments; impl From<(F, V, V)> for Assignments { fn from((u, x, w): (F, V, V)) -> Self { @@ -162,7 +161,7 @@ pub trait ConstraintSystemExt { fn assignments(&self) -> Result>, SynthesisError>; } -impl ConstraintSystemExt for ConstraintSystem { +impl ConstraintSystemExt for ConstraintSystemRef { fn assignments(&self) -> Result>, SynthesisError> { let witness = self.witness_assignment()?.to_vec(); // skip the first element which is '1' diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 110a633f6..0c222042f 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,3 +1,4 @@ +use ark_ff::UniformRand; use ark_r1cs_std::{ alloc::AllocVar, eq::EqGadget, fields::fp::FpVar, select::CondSelectGadget, GR1CSVar, }; @@ -35,13 +36,17 @@ pub enum Error { CommitmentVerificationFail, } +pub trait CommitmentKey: Clone { + fn max_scalars_len(&self) -> usize; +} + pub trait VectorCommitment: 'static + Clone + Debug + PartialEq + Eq { const IS_HIDING: bool; type Gadget: VectorCommitmentGadget; - type Key: Clone; - type Scalar: Clone + Copy + Default + Debug + PartialEq + Eq + Sync + Absorbable; + type Key: CommitmentKey; + type Scalar: Clone + Copy + Default + Debug + PartialEq + Eq + Sync + Absorbable + UniformRand; type Commitment: Clone + Default + Debug + PartialEq + Eq + Sync + Absorbable; type Randomness: Clone + Copy @@ -58,7 +63,7 @@ pub trait VectorCommitment: 'static + Clone + Debug + PartialEq + Eq { + Mul + Sum; - fn generate_key(rng: impl RngCore, len: usize) -> Result; + fn generate_key(len: usize, rng: impl RngCore) -> Result; fn commit( ck: &Self::Key, @@ -71,7 +76,7 @@ pub trait VectorCommitment: 'static + Clone + Debug + PartialEq + Eq { v: &[Self::Scalar], r: &Self::Randomness, cm: &Self::Commitment, - ) -> Result; + ) -> Result<(), Error>; } pub trait GroupBasedVectorCommitment: @@ -79,7 +84,7 @@ pub trait GroupBasedVectorCommitment: Gadget: VectorCommitmentGadget< Native = Self, ConstraintField = CF2, - ScalarVar = EmulatedFieldVar, Self::Scalar, true>, + ScalarVar = EmulatedFieldVar, Self::Scalar>, CommitmentVar = Var, >, Commitment: SonobeCurve, @@ -151,9 +156,9 @@ mod tests { .map(|_| VC::Scalar::rand(&mut rng)) .collect::>(); - let ck = VC::generate_key(&mut rng, len)?; + let ck = VC::generate_key(len, &mut rng)?; let (cm, r) = VC::commit(&ck, &v, &mut rng)?; - assert!(VC::open(&ck, &v, &r, &cm)?); + VC::open(&ck, &v, &r, &cm)?; Ok(()) } } diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 4eb08956d..c3d4de26a 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -6,9 +6,12 @@ use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand use super::{Error, VectorCommitment}; use crate::{ - algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, - commitments::{GroupBasedVectorCommitment, VectorCommitmentGadget}, - traits::{CF1, CF2, SonobeCurve}, + algebra::{ + field::emulated::{EmulatedFieldVar, IntVarInner}, + group::emulated::EmulatedAffineVar, + }, + commitments::{CommitmentKey, GroupBasedVectorCommitment, VectorCommitmentGadget}, + traits::{SonobeCurve, CF1, CF2}, utils::null::Null, }; @@ -17,14 +20,49 @@ pub struct Pedersen { _c: PhantomData, } -impl Pedersen { - fn msm(g: &[C::Affine], v: &[C::ScalarField]) -> Result { - if g.len() < v.len() { - return Err(Error::MessageTooLong(g.len(), v.len())); +#[derive(Clone)] +pub struct PedersenKey { + pub g: Vec, + pub h: C, +} + +impl CommitmentKey for PedersenKey { + fn max_scalars_len(&self) -> usize { + self.g.len() + } +} + +impl PedersenKey { + fn new(len: usize, mut rng: impl RngCore) -> Self { + let generators = repeat_with(|| C::rand(&mut rng)) + .take(len.next_power_of_two()) + .collect::>(); + Self { + g: C::normalize_batch(&generators), + h: if H { C::rand(&mut rng) } else { C::zero() }, + } + } +} + +impl PedersenKey { + fn commit(&self, v: &[C::ScalarField], r: &C::ScalarField) -> Result { + if self.g.len() < v.len() { + return Err(Error::MessageTooLong(self.g.len(), v.len())); + } + // + h * r + // use msm_unchecked because we already ensured at the if that generators are long enough + Ok(C::msm_unchecked(&self.g, v) + self.h.mul(r)) + } +} + +impl PedersenKey { + fn commit(&self, v: &[C::ScalarField]) -> Result { + if self.g.len() < v.len() { + return Err(Error::MessageTooLong(self.g.len(), v.len())); } // // use msm_unchecked because we already ensured at the if that generators are long enough - Ok(C::msm_unchecked(g, v)) + Ok(C::msm_unchecked(&self.g, v)) } } @@ -33,24 +71,21 @@ impl VectorCommitment for Pedersen { type Gadget = PedersenGadget; - type Key = Vec; + type Key = PedersenKey; type Scalar = C::ScalarField; type Commitment = C; type Randomness = Null; - fn generate_key(mut rng: impl RngCore, len: usize) -> Result { - let generators = repeat_with(|| C::rand(&mut rng)) - .take(len.next_power_of_two()) - .collect::>(); - Ok(C::normalize_batch(&generators)) + fn generate_key(len: usize, rng: impl RngCore) -> Result { + Ok(PedersenKey::new(len, rng)) } fn commit( - g: &Self::Key, + ck: &Self::Key, v: &[Self::Scalar], _rng: impl RngCore, ) -> Result<(Self::Commitment, Self::Randomness), Error> { - Ok((Self::msm(g, v)?, Null)) + Ok((ck.commit(v)?, Null)) } fn open( @@ -58,8 +93,10 @@ impl VectorCommitment for Pedersen { v: &[Self::Scalar], _r: &Self::Randomness, cm: &Self::Commitment, - ) -> Result { - Ok(&Self::msm(ck, v)? == cm) + ) -> Result<(), Error> { + (&ck.commit(v)? == cm) + .then_some(()) + .ok_or(Error::CommitmentVerificationFail) } } @@ -68,34 +105,33 @@ impl VectorCommitment for Pedersen { type Gadget = PedersenGadget; - type Key = (Vec, C); + type Key = PedersenKey; type Scalar = C::ScalarField; type Commitment = C; type Randomness = C::ScalarField; - fn generate_key(mut rng: impl RngCore, len: usize) -> Result { - Ok(( - Pedersen::::generate_key(&mut rng, len)?, - C::rand(&mut rng), - )) + fn generate_key(len: usize, rng: impl RngCore) -> Result { + Ok(PedersenKey::new(len, rng)) } fn commit( - (g, h): &Self::Key, + ck: &Self::Key, v: &[Self::Scalar], mut rng: impl RngCore, ) -> Result<(Self::Commitment, Self::Randomness), Error> { let r = C::ScalarField::rand(&mut rng); - Ok((Self::msm(g, v)? + h.mul(r), r)) + Ok((ck.commit(v, &r)?, r)) } fn open( - (g, h): &Self::Key, + ck: &Self::Key, v: &[Self::Scalar], r: &Self::Randomness, cm: &Self::Commitment, - ) -> Result { - Ok(&(Self::msm(g, v)? + h.mul(r)) == cm) + ) -> Result<(), Error> { + (&(ck.commit(v, r)?) == cm) + .then_some(()) + .ok_or(Error::CommitmentVerificationFail) } } @@ -228,9 +264,9 @@ impl VectorCommitmentGadget for PedersenGadget { type KeyVar = Vec; - type ScalarVar = EmulatedFieldVar, CF1, true>; + type ScalarVar = EmulatedFieldVar, CF1>; - type IntermediateScalarVar = EmulatedFieldVar, CF1, false>; + type IntermediateScalarVar = IntVarInner, CF1, false>; type CommitmentVar = C::Var; @@ -258,13 +294,13 @@ impl VectorCommitmentGadget for PedersenGadget { type KeyVar = (Vec, C::Var); - type ScalarVar = EmulatedFieldVar, CF1, true>; + type ScalarVar = EmulatedFieldVar, CF1>; - type IntermediateScalarVar = EmulatedFieldVar, CF1, false>; + type IntermediateScalarVar = IntVarInner, CF1, false>; type CommitmentVar = C::Var; - type RandomnessVar = EmulatedFieldVar, CF1, true>; + type RandomnessVar = EmulatedFieldVar, CF1>; fn open( (g, h): &Self::KeyVar, diff --git a/crates/primitives/src/relations/mod.rs b/crates/primitives/src/relations/mod.rs index 6c3241700..0e3467057 100644 --- a/crates/primitives/src/relations/mod.rs +++ b/crates/primitives/src/relations/mod.rs @@ -1,7 +1,5 @@ use ark_std::{error::Error, rand::RngCore}; -use crate::traits::Dummy; - pub trait Relation { type Error: Error; @@ -9,23 +7,6 @@ pub trait Relation { fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error>; } -pub trait WitnessInstanceExtractor { - type Source; - type Error: Error + 'static; - - fn extract(&self, source: Self::Source) -> Result<(W, U), Self::Error>; -} - -pub trait WitnessInstanceInitializer { - fn dummy_witness_instance<'a>(&'a self) -> (W, U) - where - W: Dummy<&'a Self>, - U: Dummy<&'a Self>, - { - (W::dummy(self), U::dummy(self)) - } -} - /// `WitnessInstanceSampler` allows sampling a random witness-instance pair that /// satisfies the relation `self`. pub trait WitnessInstanceSampler { diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index 4b32465ad..610102b4e 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -29,8 +29,8 @@ pub mod utils; #[derive(Debug, Error)] pub enum Error { - #[error("Incorrect evaluations: {0} + {1} != {2}")] - IncorrectEvaluations(String, String, String), + #[error("Incorrect evaluation: claimed {0}, got {1}")] + IncorrectEvaluation(String, String), #[error("Incorrect proof length: expected {0}, got {1}")] UnexpectedProofLength(usize, usize), #[error("Unexpected polynomial degree: expected at most {0}, got {1}")] @@ -155,7 +155,7 @@ impl IOPSumCheck { } pub fn verify( - claimed_sum: F, + mut claimed_sum: F, proofs: &[Vec], aux_info: &VPAuxInfo, transcript: &mut impl Transcript, @@ -170,7 +170,6 @@ impl IOPSumCheck { } let mut challenges = Vec::with_capacity(aux_info.num_variables); - let mut expected = claimed_sum; // Outer loop is not parallelized because `DensePolynomial::evaluate` is // already parallelized internally. @@ -186,12 +185,11 @@ impl IOPSumCheck { let eval_at_one = coeffs.iter().sum::(); // the deferred check during the interactive phase: - // 1. check if the received 'P(0) + P(1) = expected`. - if eval_at_zero + eval_at_one != expected { - return Err(Error::IncorrectEvaluations( - eval_at_zero.to_string(), - eval_at_one.to_string(), - expected.to_string(), + // 1. check if the received 'P(0) + P(1) = claimed_sum`. + if eval_at_zero + eval_at_one != claimed_sum { + return Err(Error::IncorrectEvaluation( + claimed_sum.to_string(), + format!("{} + {}", eval_at_zero, eval_at_one), )); } @@ -199,11 +197,11 @@ impl IOPSumCheck { let challenge = transcript.challenge_field_element(); // 2. set `expected` to `P(r)` - expected = DensePolynomial::from_coefficients_slice(coeffs).evaluate(&challenge); + claimed_sum = DensePolynomial::from_coefficients_slice(coeffs).evaluate(&challenge); challenges.push(challenge); } - Ok((expected, challenges)) + Ok((claimed_sum, challenges)) } } @@ -212,7 +210,7 @@ pub mod tests { use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_ff::Field; use ark_pallas::Fr; - use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; + use ark_poly::MultilinearExtension; use ark_std::{test_rng, One, Zero}; use super::*; diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index a16300dba..b4c931d92 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -455,12 +455,12 @@ mod tests { use ark_bn254::Fr; use ark_ff::UniformRand; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::rand::thread_rng; + use ark_std::{error::Error, rand::thread_rng}; use super::*; #[test] - fn test() { + fn test() -> Result<(), Box> { let rng = &mut thread_rng(); let griffin = GriffinParams::new(24, 5, 9); let t = griffin.t; @@ -469,11 +469,13 @@ mod tests { let y = griffin.hash(&x); let cs = ConstraintSystem::new_ref(); - let x_var = Vec::new_witness(cs.clone(), || Ok(x.clone())).unwrap(); - let y_var = griffin.hash_gadget(&x_var).unwrap(); - assert_eq!(y, y_var.value().unwrap()); + let x_var = Vec::new_witness(cs.clone(), || Ok(x.clone()))?; + let y_var = griffin.hash_gadget(&x_var)?; + assert_eq!(y, y_var.value()?); println!("{}", cs.num_constraints()); - assert!(cs.is_satisfied().unwrap()); + assert!(cs.is_satisfied()?); + + Ok(()) } } diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index 314486d68..c290a25ca 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -184,6 +184,7 @@ impl GriffinSpongeVar { impl Transcript for GriffinSponge { type Config = Arc>; + type Var = GriffinSpongeVar; fn new(parameters: &Arc>) -> Self { let state = vec![F::zero(); parameters.rate + parameters.capacity]; diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 4b6820a04..7f5ebc8e3 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -7,17 +7,15 @@ pub mod absorbable; pub mod griffin; pub mod poseidon; -pub trait Transcript { - type Config; +pub trait Transcript: Clone { + type Config: Clone; + type Var: TranscriptVar; fn new(config: &Self::Config) -> Self; /// `new_with_pp_hash` creates a new transcript / sponge with the given /// hash of the public parameters. - fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self - where - Self: Sized, - { + fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self { let mut sponge = Self::new(config); sponge.add_field_elements(&[pp_hash]); sponge @@ -41,10 +39,7 @@ pub trait Transcript { fn get_field_elements(&mut self, num_elements: usize) -> Vec; /// Creates a new sponge with applied domain separation. - fn separate_domain(&self, domain: &[u8]) -> Self - where - Self: Clone, - { + fn separate_domain(&self, domain: &[u8]) -> Self { let mut new_sponge = self.clone(); let mut input = domain.len().to_le_bytes().to_vec(); @@ -85,22 +80,17 @@ pub trait Transcript { } } -pub trait TranscriptVar { - type Native: Transcript; +pub trait TranscriptVar: Clone { + type Native: Transcript; - fn new(config: &>::Config) -> Self - where - Self: Sized; + fn new(config: &>::Config) -> Self; /// `new_with_pp_hash` creates a new transcript / sponge with the given /// hash of the public parameters. fn new_with_pp_hash( config: &>::Config, pp_hash: &FpVar, - ) -> Result - where - Self: Sized, - { + ) -> Result { let mut sponge = Self::new(config); sponge.add(&pp_hash)?; Ok(sponge) @@ -115,16 +105,13 @@ pub trait TranscriptVar { fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError>; fn get_field_element(&mut self) -> Result, SynthesisError> { - Ok(self.get_field_elements(1)?.pop().unwrap()) + Ok(self.get_field_elements(1)?.swap_remove(0)) } fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError>; /// Creates a new sponge with applied domain separation. - fn separate_domain(&self, domain: &[u8]) -> Result - where - Self: Clone, - { + fn separate_domain(&self, domain: &[u8]) -> Result { let mut new_sponge = self.clone(); let mut input = domain.len().to_le_bytes().to_vec(); @@ -143,7 +130,7 @@ pub trait TranscriptVar { fn challenge_field_element(&mut self) -> Result, SynthesisError> { let mut c = self.get_field_elements(1)?; self.add(&c[0])?; - Ok(c.pop().unwrap()) + Ok(c.swap_remove(0)) } fn challenge_bits(&mut self, nbits: usize) -> Result>, SynthesisError> { diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index 9601d8bbe..a9123a20b 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -12,6 +12,7 @@ use crate::transcripts::{AbsorbableGadget, Transcript, TranscriptVar}; impl Transcript for PoseidonSponge { type Config = PoseidonConfig; + type Var = PoseidonSpongeVar; fn new(config: &Self::Config) -> Self { CryptographicSponge::new(config) From a3ad72bc5bf5614bc7ddbed28312612551a9c258 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 22:33:18 +0800 Subject: [PATCH 14/99] Allow step circuit to have states of any shape --- crates/primitives/src/circuits/mod.rs | 21 ++++++++++++++------- crates/primitives/src/circuits/utils.rs | 22 ++++++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 3bcdf6416..845dd2924 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -1,5 +1,5 @@ use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::fields::fp::FpVar; +use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, GR1CSVar}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; @@ -9,6 +9,11 @@ use ark_std::{ ops::{Index, IndexMut}, }; +use crate::{ + traits::Dummy, + transcripts::{Absorbable, AbsorbableGadget}, +}; + pub mod utils; /// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. @@ -21,13 +26,15 @@ pub mod utils; /// contains a vector, it is initialized at the expected length). pub trait FCircuit { type Field: PrimeField; + type State: Clone + PartialEq + Absorbable; + type StateVar: GR1CSVar + + AllocVar + + AbsorbableGadget; type ExternalInputs; - fn dummy_external_inputs(&self) -> Self::ExternalInputs; + fn dummy_state(&self) -> Self::State; - /// returns the number of elements in the state of the FCircuit, which corresponds to the - /// FCircuit inputs. - fn state_len(&self) -> usize; + fn dummy_external_inputs(&self) -> Self::ExternalInputs; /// generates the constraints for the step of F for the given z_i fn generate_step_constraints( @@ -36,9 +43,9 @@ pub trait FCircuit { &self, cs: ConstraintSystemRef, i: FpVar, - z_i: Vec>, + z_i: Self::StateVar, external_inputs: Self::ExternalInputs, // inputs that are not part of the state - ) -> Result>, SynthesisError>; + ) -> Result; } #[derive(Clone, Debug, PartialEq)] diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 304c2119f..dccbe34a1 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -7,8 +7,8 @@ use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, Synthesis use super::Assignments; use crate::{ - arithmetizations::r1cs::{R1CSConfig, R1CS}, - circuits::FCircuit, + arithmetizations::r1cs::{R1CS, R1CSConfig}, + circuits::FCircuit, traits::SonobeField, }; pub struct CircuitForTest { @@ -47,24 +47,26 @@ impl ConstraintSynthesizer for CircuitForTest { } } -impl FCircuit for CircuitForTest { +impl FCircuit for CircuitForTest { type Field = F; + type State = [F; 1]; + type StateVar = [FpVar; 1]; type ExternalInputs = (); - fn dummy_external_inputs(&self) -> Self::ExternalInputs {} - - fn state_len(&self) -> usize { - 1 + fn dummy_state(&self) -> Self::State { + [F::zero(); 1] } + fn dummy_external_inputs(&self) -> Self::ExternalInputs {} + fn generate_step_constraints( &self, cs: ConstraintSystemRef, _i: FpVar, - z_i: Vec>, + z_i: Self::StateVar, _external_inputs: Self::ExternalInputs, - ) -> Result>, SynthesisError> { + ) -> Result { // Variable 0 (implicitly added by arkworks as 1) // Variable 1 let x = if let FpVar::Var(x) = z_i[0].clone() { @@ -97,7 +99,7 @@ impl FCircuit for CircuitForTest { || Variable::one().into(), || y.variable.into(), )?; - Ok(vec![FpVar::Var(x_cube_plus_x_plus_5)]) + Ok([FpVar::Var(x_cube_plus_x_plus_5)]) } } From 5f693fdf89d56180dd401129e8192c912cc48d0f Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 05:40:41 +0800 Subject: [PATCH 15/99] Separate VC and FS into Def and Ops --- crates/primitives/src/commitments/mod.rs | 86 +++--- crates/primitives/src/commitments/pedersen.rs | 264 ++++++++---------- 2 files changed, 176 insertions(+), 174 deletions(-) diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 0c222042f..64abea149 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -13,8 +13,10 @@ use thiserror::Error; use crate::{ algebra::{ - field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar, - ops::bits::FromBitsGadget, Var, + field::emulated::{EmulatedFieldVar, IntVarInner}, + group::emulated::EmulatedAffineVar, + ops::bits::FromBitsGadget, + Var, }, traits::{SonobeCurve, SonobeField, CF1, CF2}, transcripts::{Absorbable, AbsorbableGadget}, @@ -40,11 +42,9 @@ pub trait CommitmentKey: Clone { fn max_scalars_len(&self) -> usize; } -pub trait VectorCommitment: 'static + Clone + Debug + PartialEq + Eq { +pub trait VectorCommitmentDef: 'static + Clone + Debug + PartialEq + Eq { const IS_HIDING: bool; - type Gadget: VectorCommitmentGadget; - type Key: CommitmentKey; type Scalar: Clone + Copy + Default + Debug + PartialEq + Eq + Sync + Absorbable + UniformRand; type Commitment: Clone + Default + Debug + PartialEq + Eq + Sync + Absorbable; @@ -62,7 +62,9 @@ pub trait VectorCommitment: 'static + Clone + Debug + PartialEq + Eq { + Add + Mul + Sum; +} +pub trait VectorCommitmentOps: VectorCommitmentDef { fn generate_key(len: usize, rng: impl RngCore) -> Result; fn commit( @@ -79,28 +81,7 @@ pub trait VectorCommitment: 'static + Clone + Debug + PartialEq + Eq { ) -> Result<(), Error>; } -pub trait GroupBasedVectorCommitment: - VectorCommitment< - Gadget: VectorCommitmentGadget< - Native = Self, - ConstraintField = CF2, - ScalarVar = EmulatedFieldVar, Self::Scalar>, - CommitmentVar = Var, - >, - Commitment: SonobeCurve, - Scalar = CF1<::Commitment>, -> -{ - type EmulatedGadget: VectorCommitmentGadget< - Native = Self, - ConstraintField = Self::Scalar, - ScalarVar = FpVar, - CommitmentVar = EmulatedAffineVar, - >; -} - -pub trait VectorCommitmentGadget: Clone { - type Native: VectorCommitment; +pub trait VectorCommitmentGadgetDef: Clone { type ConstraintField: SonobeField; type KeyVar; @@ -109,8 +90,8 @@ pub trait VectorCommitmentGadget: Clone { + AbsorbableGadget + CondSelectGadget + FromBitsGadget - + AllocVar<::Scalar, Self::ConstraintField> - + GR1CSVar::Scalar> + + AllocVar<::Scalar, Self::ConstraintField> + + GR1CSVar::Scalar> + Add + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> + Mul @@ -128,11 +109,15 @@ pub trait VectorCommitmentGadget: Clone { type CommitmentVar: Clone + AbsorbableGadget + CondSelectGadget - + AllocVar<::Commitment, Self::ConstraintField> - + GR1CSVar::Commitment>; - type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> - + GR1CSVar::Randomness>; + + AllocVar<::Commitment, Self::ConstraintField> + + GR1CSVar::Commitment>; + type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> + + GR1CSVar::Randomness>; + type Native: VectorCommitmentDef; +} + +pub trait VectorCommitmentGadgetOps: VectorCommitmentGadgetDef { fn open( ck: &Self::KeyVar, v: &[Self::ScalarVar], @@ -141,6 +126,39 @@ pub trait VectorCommitmentGadget: Clone { ) -> Result<(), SynthesisError>; } +pub trait GroupBasedVectorCommitment: + VectorCommitmentDef< + Commitment: SonobeCurve, + Scalar = CF1<::Commitment>, + > + VectorCommitmentOps +{ + type Gadget1: VectorCommitmentGadgetOps + + VectorCommitmentGadgetDef< + ConstraintField = CF2<::Commitment>, + ScalarVar = EmulatedFieldVar< + CF2<::Commitment>, + ::Scalar, + >, + IntermediateScalarVar = IntVarInner< + CF2<::Commitment>, + ::Scalar, + false, + >, + CommitmentVar = Var<::Commitment>, + Native = Self, + >; + type Gadget2: VectorCommitmentGadgetDef< + ConstraintField = ::Scalar, + ScalarVar = FpVar<::Scalar>, + IntermediateScalarVar = FpVar<::Scalar>, + CommitmentVar = EmulatedAffineVar< + ::Scalar, + ::Commitment, + >, + Native = Self, + >; +} + #[cfg(test)] mod tests { use ark_ff::UniformRand; @@ -148,7 +166,7 @@ mod tests { use super::*; - pub fn test_commitment_correctness>( + pub fn test_commitment_correctness( mut rng: impl RngCore, len: usize, ) -> Result<(), Box> { diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index c3d4de26a..5ec639358 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -4,22 +4,19 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::SynthesisError; use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; -use super::{Error, VectorCommitment}; +use super::{ + CommitmentKey, Error, VectorCommitmentDef, VectorCommitmentGadgetDef, VectorCommitmentOps, +}; use crate::{ algebra::{ field::emulated::{EmulatedFieldVar, IntVarInner}, group::emulated::EmulatedAffineVar, }, - commitments::{CommitmentKey, GroupBasedVectorCommitment, VectorCommitmentGadget}, + commitments::{GroupBasedVectorCommitment, VectorCommitmentGadgetOps}, traits::{SonobeCurve, CF1, CF2}, utils::null::Null, }; -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Pedersen { - _c: PhantomData, -} - #[derive(Clone)] pub struct PedersenKey { pub g: Vec, @@ -66,83 +63,152 @@ impl PedersenKey { } } -impl VectorCommitment for Pedersen { - const IS_HIDING: bool = false; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Pedersen { + _c: PhantomData, +} - type Gadget = PedersenGadget; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PedersenEmulatedGadget { + _c: PhantomData, +} + +impl VectorCommitmentDef for Pedersen { + const IS_HIDING: bool = false; type Key = PedersenKey; type Scalar = C::ScalarField; type Commitment = C; type Randomness = Null; +} + +impl VectorCommitmentDef for Pedersen { + const IS_HIDING: bool = true; + + type Key = PedersenKey; + type Scalar = C::ScalarField; + type Commitment = C; + type Randomness = C::ScalarField; +} + +impl VectorCommitmentGadgetDef for PedersenGadget { + type ConstraintField = CF2; + + type KeyVar = Vec; + + type ScalarVar = EmulatedFieldVar, CF1>; - fn generate_key(len: usize, rng: impl RngCore) -> Result { + type IntermediateScalarVar = IntVarInner, CF1, false>; + + type CommitmentVar = C::Var; + + type RandomnessVar = Null; + + type Native = Pedersen; +} + +impl VectorCommitmentGadgetDef for PedersenGadget { + type ConstraintField = CF2; + + type KeyVar = (Vec, C::Var); + + type ScalarVar = EmulatedFieldVar, CF1>; + + type IntermediateScalarVar = IntVarInner, CF1, false>; + + type CommitmentVar = C::Var; + + type RandomnessVar = EmulatedFieldVar, CF1>; + + type Native = Pedersen; +} + +impl VectorCommitmentGadgetDef for PedersenEmulatedGadget { + type ConstraintField = CF1; + + type KeyVar = Vec, C>>; + + type ScalarVar = FpVar>; + + type IntermediateScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = Null; + + type Native = Pedersen; +} + +impl VectorCommitmentGadgetDef for PedersenEmulatedGadget { + type ConstraintField = CF1; + + type KeyVar = ( + Vec, C>>, + EmulatedAffineVar, C>, + ); + + type ScalarVar = FpVar>; + + type IntermediateScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = FpVar>; + + type Native = Pedersen; +} + +impl GroupBasedVectorCommitment for Pedersen { + type Gadget1 = PedersenGadget; + type Gadget2 = PedersenEmulatedGadget; +} + +impl GroupBasedVectorCommitment for Pedersen { + type Gadget1 = PedersenGadget; + type Gadget2 = PedersenEmulatedGadget; +} + +impl VectorCommitmentOps for Pedersen { + fn generate_key(len: usize, rng: impl RngCore) -> Result, Error> { Ok(PedersenKey::new(len, rng)) } fn commit( - ck: &Self::Key, - v: &[Self::Scalar], + ck: &PedersenKey, + v: &[CF1], _rng: impl RngCore, - ) -> Result<(Self::Commitment, Self::Randomness), Error> { + ) -> Result<(C, Null), Error> { Ok((ck.commit(v)?, Null)) } - fn open( - ck: &Self::Key, - v: &[Self::Scalar], - _r: &Self::Randomness, - cm: &Self::Commitment, - ) -> Result<(), Error> { + fn open(ck: &PedersenKey, v: &[CF1], _r: &Null, cm: &C) -> Result<(), Error> { (&ck.commit(v)? == cm) .then_some(()) .ok_or(Error::CommitmentVerificationFail) } } -impl VectorCommitment for Pedersen { - const IS_HIDING: bool = true; - - type Gadget = PedersenGadget; - - type Key = PedersenKey; - type Scalar = C::ScalarField; - type Commitment = C; - type Randomness = C::ScalarField; - - fn generate_key(len: usize, rng: impl RngCore) -> Result { +impl VectorCommitmentOps for Pedersen { + fn generate_key(len: usize, rng: impl RngCore) -> Result, Error> { Ok(PedersenKey::new(len, rng)) } fn commit( - ck: &Self::Key, - v: &[Self::Scalar], + ck: &PedersenKey, + v: &[CF1], mut rng: impl RngCore, - ) -> Result<(Self::Commitment, Self::Randomness), Error> { + ) -> Result<(C, CF1), Error> { let r = C::ScalarField::rand(&mut rng); Ok((ck.commit(v, &r)?, r)) } - fn open( - ck: &Self::Key, - v: &[Self::Scalar], - r: &Self::Randomness, - cm: &Self::Commitment, - ) -> Result<(), Error> { + fn open(ck: &PedersenKey, v: &[CF1], r: &CF1, cm: &C) -> Result<(), Error> { (&(ck.commit(v, r)?) == cm) .then_some(()) .ok_or(Error::CommitmentVerificationFail) } } -impl GroupBasedVectorCommitment for Pedersen { - type EmulatedGadget = PedersenEmulatedGadget; -} - -impl GroupBasedVectorCommitment for Pedersen { - type EmulatedGadget = PedersenEmulatedGadget; -} - #[derive(Clone)] pub struct PedersenGadget { _c: PhantomData, @@ -258,25 +324,12 @@ impl PedersenGadget { } } -impl VectorCommitmentGadget for PedersenGadget { - type Native = Pedersen; - type ConstraintField = CF2; - - type KeyVar = Vec; - - type ScalarVar = EmulatedFieldVar, CF1>; - - type IntermediateScalarVar = IntVarInner, CF1, false>; - - type CommitmentVar = C::Var; - - type RandomnessVar = Null; - +impl VectorCommitmentGadgetOps for PedersenGadget { fn open( - ck: &Self::KeyVar, - v: &[Self::ScalarVar], - _r: &Self::RandomnessVar, - cm: &Self::CommitmentVar, + ck: &Vec, + v: &[EmulatedFieldVar, CF1>], + _r: &Null, + cm: &C::Var, ) -> Result<(), SynthesisError> { Self::msm( ck, @@ -288,25 +341,12 @@ impl VectorCommitmentGadget for PedersenGadget { } } -impl VectorCommitmentGadget for PedersenGadget { - type Native = Pedersen; - type ConstraintField = CF2; - - type KeyVar = (Vec, C::Var); - - type ScalarVar = EmulatedFieldVar, CF1>; - - type IntermediateScalarVar = IntVarInner, CF1, false>; - - type CommitmentVar = C::Var; - - type RandomnessVar = EmulatedFieldVar, CF1>; - +impl VectorCommitmentGadgetOps for PedersenGadget { fn open( - (g, h): &Self::KeyVar, - v: &[Self::ScalarVar], - r: &Self::RandomnessVar, - cm: &Self::CommitmentVar, + (g, h): &(Vec, C::Var), + v: &[EmulatedFieldVar, CF1>], + r: &EmulatedFieldVar, CF1>, + cm: &C::Var, ) -> Result<(), SynthesisError> { let gv = Self::msm( g, @@ -319,62 +359,6 @@ impl VectorCommitmentGadget for PedersenGadget { } } -#[derive(Clone)] -pub struct PedersenEmulatedGadget { - _c: PhantomData, -} - -impl VectorCommitmentGadget for PedersenEmulatedGadget { - type Native = Pedersen; - type ConstraintField = CF1; - - type KeyVar = Vec, C>>; - - type ScalarVar = FpVar>; - - type IntermediateScalarVar = FpVar>; - - type CommitmentVar = EmulatedAffineVar, C>; - - type RandomnessVar = Null; - - fn open( - ck: &Self::KeyVar, - v: &[Self::ScalarVar], - _r: &Self::RandomnessVar, - cm: &Self::CommitmentVar, - ) -> Result<(), SynthesisError> { - unimplemented!() - } -} - -impl VectorCommitmentGadget for PedersenEmulatedGadget { - type Native = Pedersen; - type ConstraintField = CF1; - - type KeyVar = ( - Vec, C>>, - EmulatedAffineVar, C>, - ); - - type ScalarVar = FpVar>; - - type IntermediateScalarVar = FpVar>; - - type CommitmentVar = EmulatedAffineVar, C>; - - type RandomnessVar = FpVar>; - - fn open( - (g, h): &Self::KeyVar, - v: &[Self::ScalarVar], - r: &Self::RandomnessVar, - cm: &Self::CommitmentVar, - ) -> Result<(), SynthesisError> { - unimplemented!() - } -} - #[cfg(test)] mod tests { use ark_bn254::G1Projective; From 3e8206b3e85aa78bafa524087245c0e88a561f3d Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 03:11:21 +0800 Subject: [PATCH 16/99] Cleanup --- crates/primitives/src/algebra/group/mod.rs | 2 -- crates/primitives/src/algebra/ops/bits.rs | 10 +++++++ .../src/arithmetizations/ccs/mod.rs | 10 +++---- .../src/arithmetizations/r1cs/mod.rs | 5 +++- crates/primitives/src/circuits/mod.rs | 5 +--- crates/primitives/src/commitments/mod.rs | 26 ++++++------------- .../src/transcripts/griffin/sponge.rs | 4 +-- .../src/transcripts/poseidon/sponge.rs | 8 +++--- 8 files changed, 33 insertions(+), 37 deletions(-) diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index c9ccf37d6..a429f8f2e 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -23,8 +23,6 @@ pub mod emulated; pub type CF1 = ::ScalarField; pub type CF2 = <::BaseField as Field>::BasePrimeField; -pub type CI1 = <::ScalarField as PrimeField>::BigInt; -pub type CI2 = <<::BaseField as Field>::BasePrimeField as PrimeField>::BigInt; /// `Curve` trait is a wrapper around `CurveGroup` that also includes the /// necessary bounds for the curve to be used conveniently in folding schemes. diff --git a/crates/primitives/src/algebra/ops/bits.rs b/crates/primitives/src/algebra/ops/bits.rs index 1e431730b..12a0c5dee 100644 --- a/crates/primitives/src/algebra/ops/bits.rs +++ b/crates/primitives/src/algebra/ops/bits.rs @@ -4,6 +4,16 @@ use ark_relations::gr1cs::SynthesisError; use crate::algebra::field::emulated::Bound; +pub trait FromBits { + fn from_bits_le(bits: &[bool]) -> Self; +} + +impl FromBits for F { + fn from_bits_le(bits: &[bool]) -> Self { + F::from(F::BigInt::from_bits_le(bits)) + } +} + pub trait FromBitsGadget: Sized { fn from_bits_le(bits: &[Boolean], bound: Bound) -> Result; } diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index a0fc6355a..83424f422 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -116,9 +116,6 @@ impl CCS { )); } - let S = &V::multisets_vec(); - let c = &V::coefficients_vec::(); - // Recall that the evaluation of CCS at z is defined as: // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i * z))$, // where $\prod$ denotes the Hadamard product. @@ -134,9 +131,10 @@ impl CCS { .map(|row| { // The row-th entry of the resulting vector is: // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i[row] * z))$ - S.iter() - .zip(c) - .map(|(s, &c)| { + V::multisets_vec() + .into_iter() + .zip(V::coefficients_vec::()) + .map(|(s, c)| { // Each term in the sum is: // $c_j * \prod_{i \in S_j} (M_i[row] * z)$ c * s diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 571ac6313..1def67afc 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -1,6 +1,5 @@ use ark_ff::Field; use ark_relations::gr1cs::{ConstraintSystem, Matrix, R1CS_PREDICATE_LABEL}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{cfg_into_iter, cfg_iter, iterable::Iterable}; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -73,18 +72,22 @@ impl From<&ConstraintSystem> for R1CSConfig { } impl CCSVariant for R1CSConfig { + #[inline] fn n_matrices() -> usize { 3 } + #[inline] fn degree() -> usize { 2 } + #[inline] fn multisets_vec() -> Vec> { vec![vec![0, 1], vec![2]] } + #[inline] fn coefficients_vec() -> Vec { vec![F::one(), -F::one()] } diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 845dd2924..ca0b2967b 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -9,10 +9,7 @@ use ark_std::{ ops::{Index, IndexMut}, }; -use crate::{ - traits::Dummy, - transcripts::{Absorbable, AbsorbableGadget}, -}; +use crate::transcripts::{Absorbable, AbsorbableGadget}; pub mod utils; diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 64abea149..816c0b4be 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -134,27 +134,17 @@ pub trait GroupBasedVectorCommitment: { type Gadget1: VectorCommitmentGadgetOps + VectorCommitmentGadgetDef< - ConstraintField = CF2<::Commitment>, - ScalarVar = EmulatedFieldVar< - CF2<::Commitment>, - ::Scalar, - >, - IntermediateScalarVar = IntVarInner< - CF2<::Commitment>, - ::Scalar, - false, - >, - CommitmentVar = Var<::Commitment>, + ConstraintField = CF2, + ScalarVar = EmulatedFieldVar, Self::Scalar>, + IntermediateScalarVar = IntVarInner, Self::Scalar, false>, + CommitmentVar = Var, Native = Self, >; type Gadget2: VectorCommitmentGadgetDef< - ConstraintField = ::Scalar, - ScalarVar = FpVar<::Scalar>, - IntermediateScalarVar = FpVar<::Scalar>, - CommitmentVar = EmulatedAffineVar< - ::Scalar, - ::Commitment, - >, + ConstraintField = Self::Scalar, + ScalarVar = FpVar, + IntermediateScalarVar = FpVar, + CommitmentVar = EmulatedAffineVar, Native = Self, >; } diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index c290a25ca..28d89efa7 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -363,7 +363,7 @@ pub mod tests { use ark_std::{error::Error, test_rng}; use super::*; - use crate::algebra::group::emulated::EmulatedAffineVar; + use crate::algebra::{group::emulated::EmulatedAffineVar, ops::bits::FromBits}; #[test] fn test_transcript_and_transcriptvar_absorb_native_point() -> Result<(), Box> { @@ -457,7 +457,7 @@ pub mod tests { // multiply point P by the challenge in different formats, to ensure that we get the same // result natively and in-circuit - let c = Fr::from(::BigInt::from_bits_le(&c_bits)); + let c = Fr::from_bits_le(&c_bits); // check that native c*P and in-circuit c*P using scalar_mul_le are equal assert_eq!(p * c, p_var.scalar_mul_le(c_var.iter())?.value()?); diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index a9123a20b..f1f58cc76 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -79,7 +79,7 @@ pub mod tests { use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; use ark_ec::PrimeGroup; - use ark_ff::{BigInteger, PrimeField, UniformRand}; + use ark_ff::UniformRand; use ark_r1cs_std::{ alloc::AllocVar, fields::fp::FpVar, @@ -90,8 +90,8 @@ pub mod tests { use ark_std::{error::Error, str::FromStr, test_rng}; use crate::{ - algebra::group::emulated::EmulatedAffineVar, - transcripts::{poseidon::poseidon_canonical_config, Transcript, TranscriptVar}, + algebra::{group::emulated::EmulatedAffineVar, ops::bits::FromBits}, + transcripts::{Transcript, TranscriptVar, poseidon::poseidon_canonical_config}, }; // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 @@ -207,7 +207,7 @@ pub mod tests { // multiply point P by the challenge in different formats, to ensure that we get the same // result natively and in-circuit - let c = Fr::from(::BigInt::from_bits_le(&c_bits)); + let c = Fr::from_bits_le(&c_bits); // check that native c*P and in-circuit c*P using scalar_mul_le are equal assert_eq!(p * c, p_var.scalar_mul_le(c_var.iter())?.value()?); From f7505487dae61e8ea5501d12963ef68502ef861e Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 05:30:24 +0800 Subject: [PATCH 17/99] FCircuit now allows external outputs --- crates/primitives/src/circuits/mod.rs | 3 ++- crates/primitives/src/circuits/utils.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index ca0b2967b..28d40afaf 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -28,6 +28,7 @@ pub trait FCircuit { + AllocVar + AbsorbableGadget; type ExternalInputs; + type ExternalOutputs; fn dummy_state(&self) -> Self::State; @@ -42,7 +43,7 @@ pub trait FCircuit { i: FpVar, z_i: Self::StateVar, external_inputs: Self::ExternalInputs, // inputs that are not part of the state - ) -> Result; + ) -> Result<(Self::StateVar, Self::ExternalOutputs), SynthesisError>; } #[derive(Clone, Debug, PartialEq)] diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index dccbe34a1..463bbaa65 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -53,6 +53,7 @@ impl FCircuit for CircuitForTest { type StateVar = [FpVar; 1]; type ExternalInputs = (); + type ExternalOutputs = (); fn dummy_state(&self) -> Self::State { [F::zero(); 1] @@ -66,7 +67,7 @@ impl FCircuit for CircuitForTest { _i: FpVar, z_i: Self::StateVar, _external_inputs: Self::ExternalInputs, - ) -> Result { + ) -> Result<(Self::StateVar, Self::ExternalOutputs), SynthesisError> { // Variable 0 (implicitly added by arkworks as 1) // Variable 1 let x = if let FpVar::Var(x) = z_i[0].clone() { @@ -99,7 +100,7 @@ impl FCircuit for CircuitForTest { || Variable::one().into(), || y.variable.into(), )?; - Ok([FpVar::Var(x_cube_plus_x_plus_5)]) + Ok(([FpVar::Var(x_cube_plus_x_plus_5)], ())) } } From 3f1ed979d8d11de982ec29384e2c3714d7b5135f Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 25 Nov 2025 00:52:37 +0800 Subject: [PATCH 18/99] Refactor code for in-circuit relation checks --- .../primitives/src/algebra/field/emulated.rs | 65 ++++++++++++++++++- crates/primitives/src/algebra/field/mod.rs | 30 ++++++++- crates/primitives/src/algebra/mod.rs | 3 - crates/primitives/src/algebra/ops/eq.rs | 18 ++--- crates/primitives/src/arithmetizations/mod.rs | 19 +++--- .../src/arithmetizations/r1cs/circuits.rs | 31 ++++----- crates/primitives/src/commitments/mod.rs | 33 ++-------- crates/primitives/src/commitments/pedersen.rs | 13 +--- crates/primitives/src/relations/mod.rs | 8 ++- .../src/transcripts/griffin/sponge.rs | 2 +- 10 files changed, 141 insertions(+), 81 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 015140d01..fcfb45b76 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -22,9 +22,10 @@ use num_traits::Signed; use crate::{ algebra::{ - field::SonobeField, + field::{SonobeField, TwoStageFieldVar}, ops::{ bits::{FromBitsGadget, ToBitsGadgetExt}, + eq::EquivalenceGadget, matrix::{MatrixGadget, SparseMatrixVar}, vector::VectorGadget, }, @@ -565,6 +566,62 @@ impl } } +impl + EquivalenceGadget> for IntVarInner +{ + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_equal(other) + } +} + +impl + EquivalenceGadget> for IntVarInner +{ + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_congruent(other) + } +} + +impl + EquivalenceGadget> for IntVarInner +{ + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_congruent(other) + } +} + +impl + EquivalenceGadget> for IntVarInner +{ + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_congruent(other) + } +} + +impl EquivalenceGadget> for IntVarInner { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_equal(other) + } +} + +impl EquivalenceGadget> for IntVarInner { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_equal_unaligned(other) + } +} + +impl EquivalenceGadget> for IntVarInner { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_equal_unaligned(other) + } +} + +impl EquivalenceGadget> for IntVarInner { + fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + self.enforce_equal_unaligned(other) + } +} + impl TryFrom> for IntVarInner { @@ -575,6 +632,10 @@ impl TryFrom TwoStageFieldVar for IntVarInner { + type Intermediate = IntVarInner; +} + impl EqGadget for IntVarInner { fn is_eq(&self, other: &Self) -> Result, SynthesisError> { let mut result = Boolean::TRUE; @@ -858,7 +919,7 @@ impl AllocVar<(BigInt, Bound), F> for IntVarInner>()?; - let bounds = compute_bounds(&lb, &ub, F::BITS_PER_LIMB); + let bounds = compute_bounds(lb, ub, F::BITS_PER_LIMB); let var = Self::new(limbs, bounds); diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 5caac15b2..1037c875f 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -1,7 +1,11 @@ use ark_ff::{BigInteger, Fp, FpConfig, PrimeField}; use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; use ark_relations::gr1cs::SynthesisError; -use ark_std::{any::TypeId, mem::transmute_copy}; +use ark_std::{ + any::TypeId, + mem::transmute_copy, + ops::{Add, Mul}, +}; use crate::{ algebra::{field::emulated::EmulatedFieldVar, Val}, @@ -100,3 +104,27 @@ impl InputizeEmulated for P { .collect() } } + +pub trait TwoStageFieldVar: + Clone + + Add + + for<'a> Add<&'a Self, Output = Self::Intermediate> + + Mul + + for<'a> Mul<&'a Self, Output = Self::Intermediate> +{ + type Intermediate: Clone + + From + + TryInto + + Add + + for<'a> Add<&'a Self::Intermediate, Output = Self::Intermediate> + + Mul + + for<'a> Mul<&'a Self::Intermediate, Output = Self::Intermediate> + + Add + + for<'a> Add<&'a Self, Output = Self::Intermediate> + + Mul + + for<'a> Mul<&'a Self, Output = Self::Intermediate>; +} + +impl TwoStageFieldVar for FpVar { + type Intermediate = Self; +} diff --git a/crates/primitives/src/algebra/mod.rs b/crates/primitives/src/algebra/mod.rs index 0a9d623dd..91f50165f 100644 --- a/crates/primitives/src/algebra/mod.rs +++ b/crates/primitives/src/algebra/mod.rs @@ -13,6 +13,3 @@ pub trait Val { type EmulatedVar: AllocVar + GR1CSVar; } - -pub type Var = ::Var; -pub type EmulatedVar = ::EmulatedVar; \ No newline at end of file diff --git a/crates/primitives/src/algebra/ops/eq.rs b/crates/primitives/src/algebra/ops/eq.rs index a63373fa1..67be1e997 100644 --- a/crates/primitives/src/algebra/ops/eq.rs +++ b/crates/primitives/src/algebra/ops/eq.rs @@ -2,20 +2,22 @@ use ark_ff::PrimeField; use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; -/// `EquivalenceGadget` enforces that two in-circuit variables are equivalent, -/// where the equivalence relation is parameterized by `M`: -/// - For `FpVar`, it is simply an equality relation, and `M` is unused. -/// - For `NonNativeUintVar`, we consider equivalence as a congruence relation, -/// in terms of modular arithmetic, so `M` specifies the modulus. -pub trait EquivalenceGadget { +/// `EquivalenceGadget` enforces that two in-circuit variables are "equivalent". +/// +/// This does not only allow us to ensure the equality of two variables of the +/// same type, but can also be used for guaranteeing variables of different +/// types represent the "same" (depending on the context) value. +pub trait EquivalenceGadget { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError>; } -impl EquivalenceGadget for FpVar { + +impl EquivalenceGadget> for FpVar { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { self.enforce_equal(other) } } -impl> EquivalenceGadget for [T] { + +impl> EquivalenceGadget<[T]> for [T] { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { self.iter() .zip(other) diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index f5f391209..a9dd944ae 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -2,7 +2,7 @@ use ark_relations::gr1cs::SynthesisError; use ark_std::{fmt::Debug, log2}; use thiserror::Error; -use crate::relations::Relation; +use crate::relations::{Relation, RelationGadget}; pub mod ccs; pub mod r1cs; @@ -183,16 +183,15 @@ pub trait ArithRelationGadget { /// Returns the evaluation result. fn eval_relation(&self, w: &WVar, u: &UVar) -> Result; - /// Generates constraints for enforcing that witness `w` and instance `u` - /// satisfy the constraint system `self` by first computing the evaluation - /// result and then checking the validity of the evaluation result. - fn enforce_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError> { - let e = self.eval_relation(w, u)?; - Self::enforce_evaluation(w, u, e) - } - /// Generates constraints for enforcing that the evaluation result is valid. /// The witness `w` and instance `u` are also parameters, because the /// validity check may need information contained in `w` and/or `u`. - fn enforce_evaluation(w: &WVar, u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError>; + fn check_evaluation(w: &WVar, u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError>; +} + +impl> RelationGadget for A { + fn check_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError> { + let e = self.eval_relation(w, u)?; + Self::check_evaluation(w, u, e) + } } diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index 9469f2407..e45f19853 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -1,7 +1,7 @@ use ark_ff::PrimeField; use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, marker::PhantomData, One}; +use ark_std::{borrow::Borrow, One}; use super::R1CS; use crate::{ @@ -15,40 +15,37 @@ use crate::{ }; /// An in-circuit representation of the `R1CS` struct. -/// -/// `M` is for the modulo operation involved in the satisfiability check when -/// the underlying `FVar` is `NonNativeUintVar`. #[allow(non_snake_case)] #[derive(Debug, Clone)] -pub struct R1CSMatricesVar { - _m: PhantomData, +pub struct R1CSMatricesVar { pub A: SparseMatrixVar, pub B: SparseMatrixVar, pub C: SparseMatrixVar, } impl> - AllocVar, ConstraintF> for R1CSMatricesVar + AllocVar, ConstraintF> for R1CSMatricesVar { fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, - _mode: AllocationMode, + mode: AllocationMode, ) -> Result { f().and_then(|val| { let cs = cs.into(); + let val = val.borrow(); + Ok(Self { - _m: PhantomData, - A: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().A)?, - B: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().B)?, - C: SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().C)?, + A: SparseMatrixVar::::new_variable(cs.clone(), || Ok(&val.A), mode)?, + B: SparseMatrixVar::::new_variable(cs.clone(), || Ok(&val.B), mode)?, + C: SparseMatrixVar::::new_variable(cs.clone(), || Ok(&val.C), mode)?, }) }) } } -impl R1CSMatricesVar +impl R1CSMatricesVar where SparseMatrixVar: MatrixGadget, [FVar]: VectorGadget, @@ -69,11 +66,11 @@ where } } -impl, UVar: AsRef<[FVar]>> ArithRelationGadget - for R1CSMatricesVar +impl, UVar: AsRef<[FVar]>> ArithRelationGadget + for R1CSMatricesVar where SparseMatrixVar: MatrixGadget, - [FVar]: VectorGadget + EquivalenceGadget, + [FVar]: VectorGadget + EquivalenceGadget, FVar: Clone + One, { /// Evaluation is a tuple of two vectors (`AzBz` and `uCz`) instead of a @@ -85,7 +82,7 @@ where self.eval_assignments((FVar::one(), u.as_ref(), w.as_ref()).into()) } - fn enforce_evaluation( + fn check_evaluation( _w: &WVar, _u: &UVar, (lhs, rhs): Self::Evaluation, diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 816c0b4be..a78023d70 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,6 +1,6 @@ use ark_ff::UniformRand; use ark_r1cs_std::{ - alloc::AllocVar, eq::EqGadget, fields::fp::FpVar, select::CondSelectGadget, GR1CSVar, + alloc::AllocVar, fields::fp::FpVar, select::CondSelectGadget, GR1CSVar, }; use ark_relations::gr1cs::SynthesisError; use ark_std::{ @@ -13,12 +13,10 @@ use thiserror::Error; use crate::{ algebra::{ - field::emulated::{EmulatedFieldVar, IntVarInner}, - group::emulated::EmulatedAffineVar, - ops::bits::FromBitsGadget, - Var, + Val, field::{TwoStageFieldVar, emulated::{EmulatedFieldVar, IntVarInner}}, group::emulated::EmulatedAffineVar, ops::bits::FromBitsGadget + // Var, }, - traits::{SonobeCurve, SonobeField, CF1, CF2}, + traits::{CF1, CF2, SonobeCurve, SonobeField}, transcripts::{Absorbable, AbsorbableGadget}, }; @@ -85,27 +83,12 @@ pub trait VectorCommitmentGadgetDef: Clone { type ConstraintField: SonobeField; type KeyVar; - type ScalarVar: Clone - + EqGadget - + AbsorbableGadget + type ScalarVar: AbsorbableGadget + CondSelectGadget + FromBitsGadget + AllocVar<::Scalar, Self::ConstraintField> + GR1CSVar::Scalar> - + Add - + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> - + Mul - + for<'a> Mul<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar>; - type IntermediateScalarVar: Clone - + TryInto - + Add - + for<'a> Add<&'a Self::IntermediateScalarVar, Output = Self::IntermediateScalarVar> - + Mul - + for<'a> Mul<&'a Self::IntermediateScalarVar, Output = Self::IntermediateScalarVar> - + Add - + for<'a> Add<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar> - + Mul - + for<'a> Mul<&'a Self::ScalarVar, Output = Self::IntermediateScalarVar>; + + TwoStageFieldVar; type CommitmentVar: Clone + AbsorbableGadget + CondSelectGadget @@ -136,14 +119,12 @@ pub trait GroupBasedVectorCommitment: + VectorCommitmentGadgetDef< ConstraintField = CF2, ScalarVar = EmulatedFieldVar, Self::Scalar>, - IntermediateScalarVar = IntVarInner, Self::Scalar, false>, - CommitmentVar = Var, + CommitmentVar = ::Var, Native = Self, >; type Gadget2: VectorCommitmentGadgetDef< ConstraintField = Self::Scalar, ScalarVar = FpVar, - IntermediateScalarVar = FpVar, CommitmentVar = EmulatedAffineVar, Native = Self, >; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 5ec639358..0eb825bb3 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -8,10 +8,7 @@ use super::{ CommitmentKey, Error, VectorCommitmentDef, VectorCommitmentGadgetDef, VectorCommitmentOps, }; use crate::{ - algebra::{ - field::emulated::{EmulatedFieldVar, IntVarInner}, - group::emulated::EmulatedAffineVar, - }, + algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, commitments::{GroupBasedVectorCommitment, VectorCommitmentGadgetOps}, traits::{SonobeCurve, CF1, CF2}, utils::null::Null, @@ -98,8 +95,6 @@ impl VectorCommitmentGadgetDef for PedersenGadget { type ScalarVar = EmulatedFieldVar, CF1>; - type IntermediateScalarVar = IntVarInner, CF1, false>; - type CommitmentVar = C::Var; type RandomnessVar = Null; @@ -114,8 +109,6 @@ impl VectorCommitmentGadgetDef for PedersenGadget { type ScalarVar = EmulatedFieldVar, CF1>; - type IntermediateScalarVar = IntVarInner, CF1, false>; - type CommitmentVar = C::Var; type RandomnessVar = EmulatedFieldVar, CF1>; @@ -130,8 +123,6 @@ impl VectorCommitmentGadgetDef for PedersenEmulatedGadget>; - type IntermediateScalarVar = FpVar>; - type CommitmentVar = EmulatedAffineVar, C>; type RandomnessVar = Null; @@ -149,8 +140,6 @@ impl VectorCommitmentGadgetDef for PedersenEmulatedGadget>; - type IntermediateScalarVar = FpVar>; - type CommitmentVar = EmulatedAffineVar, C>; type RandomnessVar = FpVar>; diff --git a/crates/primitives/src/relations/mod.rs b/crates/primitives/src/relations/mod.rs index 0e3467057..529af6565 100644 --- a/crates/primitives/src/relations/mod.rs +++ b/crates/primitives/src/relations/mod.rs @@ -1,3 +1,4 @@ +use ark_relations::gr1cs::SynthesisError; use ark_std::{error::Error, rand::RngCore}; pub trait Relation { @@ -7,11 +8,16 @@ pub trait Relation { fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error>; } +pub trait RelationGadget { + /// Checks if witness `w` and instance `u` satisfy the relation `self` + fn check_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError>; +} + /// `WitnessInstanceSampler` allows sampling a random witness-instance pair that /// satisfies the relation `self`. pub trait WitnessInstanceSampler { type Source; - type Error: Error + 'static; + type Error: Error; fn sample(&self, source: Self::Source, rng: impl RngCore) -> Result<(W, U), Self::Error>; } diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index 28d89efa7..fc732eadf 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -352,7 +352,7 @@ impl TranscriptVar for GriffinSpongeVar { pub mod tests { use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; use ark_ec::PrimeGroup; - use ark_ff::{BigInteger, PrimeField, UniformRand}; + use ark_ff::UniformRand; use ark_r1cs_std::{ alloc::AllocVar, fields::fp::FpVar, From f7a73b3b2e8ac3d789aae705abbdcab29ca3cc8b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 19/99] Adjust the naming of traits for gadgets --- crates/primitives/src/commitments/mod.rs | 22 +++++++++---------- crates/primitives/src/commitments/pedersen.rs | 16 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index a78023d70..b9a948ab5 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,7 +1,5 @@ use ark_ff::UniformRand; -use ark_r1cs_std::{ - alloc::AllocVar, fields::fp::FpVar, select::CondSelectGadget, GR1CSVar, -}; +use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, select::CondSelectGadget, GR1CSVar}; use ark_relations::gr1cs::SynthesisError; use ark_std::{ fmt::Debug, @@ -13,10 +11,12 @@ use thiserror::Error; use crate::{ algebra::{ - Val, field::{TwoStageFieldVar, emulated::{EmulatedFieldVar, IntVarInner}}, group::emulated::EmulatedAffineVar, ops::bits::FromBitsGadget - // Var, + field::{emulated::EmulatedFieldVar, TwoStageFieldVar}, + group::emulated::EmulatedAffineVar, + ops::bits::FromBitsGadget, + Val, }, - traits::{CF1, CF2, SonobeCurve, SonobeField}, + traits::{SonobeCurve, SonobeField, CF1, CF2}, transcripts::{Absorbable, AbsorbableGadget}, }; @@ -79,7 +79,7 @@ pub trait VectorCommitmentOps: VectorCommitmentDef { ) -> Result<(), Error>; } -pub trait VectorCommitmentGadgetDef: Clone { +pub trait VectorCommitmentDefGadget: Clone { type ConstraintField: SonobeField; type KeyVar; @@ -100,7 +100,7 @@ pub trait VectorCommitmentGadgetDef: Clone { type Native: VectorCommitmentDef; } -pub trait VectorCommitmentGadgetOps: VectorCommitmentGadgetDef { +pub trait VectorCommitmentOpsGadget: VectorCommitmentDefGadget { fn open( ck: &Self::KeyVar, v: &[Self::ScalarVar], @@ -115,14 +115,14 @@ pub trait GroupBasedVectorCommitment: Scalar = CF1<::Commitment>, > + VectorCommitmentOps { - type Gadget1: VectorCommitmentGadgetOps - + VectorCommitmentGadgetDef< + type Gadget1: VectorCommitmentOpsGadget + + VectorCommitmentDefGadget< ConstraintField = CF2, ScalarVar = EmulatedFieldVar, Self::Scalar>, CommitmentVar = ::Var, Native = Self, >; - type Gadget2: VectorCommitmentGadgetDef< + type Gadget2: VectorCommitmentDefGadget< ConstraintField = Self::Scalar, ScalarVar = FpVar, CommitmentVar = EmulatedAffineVar, diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 0eb825bb3..7d470b933 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -5,11 +5,11 @@ use ark_relations::gr1cs::SynthesisError; use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; use super::{ - CommitmentKey, Error, VectorCommitmentDef, VectorCommitmentGadgetDef, VectorCommitmentOps, + CommitmentKey, Error, VectorCommitmentDef, VectorCommitmentDefGadget, VectorCommitmentOps, }; use crate::{ algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, - commitments::{GroupBasedVectorCommitment, VectorCommitmentGadgetOps}, + commitments::{GroupBasedVectorCommitment, VectorCommitmentOpsGadget}, traits::{SonobeCurve, CF1, CF2}, utils::null::Null, }; @@ -88,7 +88,7 @@ impl VectorCommitmentDef for Pedersen { type Randomness = C::ScalarField; } -impl VectorCommitmentGadgetDef for PedersenGadget { +impl VectorCommitmentDefGadget for PedersenGadget { type ConstraintField = CF2; type KeyVar = Vec; @@ -102,7 +102,7 @@ impl VectorCommitmentGadgetDef for PedersenGadget { type Native = Pedersen; } -impl VectorCommitmentGadgetDef for PedersenGadget { +impl VectorCommitmentDefGadget for PedersenGadget { type ConstraintField = CF2; type KeyVar = (Vec, C::Var); @@ -116,7 +116,7 @@ impl VectorCommitmentGadgetDef for PedersenGadget { type Native = Pedersen; } -impl VectorCommitmentGadgetDef for PedersenEmulatedGadget { +impl VectorCommitmentDefGadget for PedersenEmulatedGadget { type ConstraintField = CF1; type KeyVar = Vec, C>>; @@ -130,7 +130,7 @@ impl VectorCommitmentGadgetDef for PedersenEmulatedGadget; } -impl VectorCommitmentGadgetDef for PedersenEmulatedGadget { +impl VectorCommitmentDefGadget for PedersenEmulatedGadget { type ConstraintField = CF1; type KeyVar = ( @@ -313,7 +313,7 @@ impl PedersenGadget { } } -impl VectorCommitmentGadgetOps for PedersenGadget { +impl VectorCommitmentOpsGadget for PedersenGadget { fn open( ck: &Vec, v: &[EmulatedFieldVar, CF1>], @@ -330,7 +330,7 @@ impl VectorCommitmentGadgetOps for PedersenGadget { } } -impl VectorCommitmentGadgetOps for PedersenGadget { +impl VectorCommitmentOpsGadget for PedersenGadget { fn open( (g, h): &(Vec, C::Var), v: &[EmulatedFieldVar, CF1>], From 5c9a8c4e223022895b25029b50921bba93d7c13e Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 20/99] Clean up --- crates/primitives/src/algebra/group/mod.rs | 57 +++++------ crates/primitives/src/transcripts/mod.rs | 3 +- crates/primitives/src/utils/mod.rs | 1 - crates/primitives/src/utils/vec.rs | 109 --------------------- 4 files changed, 29 insertions(+), 141 deletions(-) delete mode 100644 crates/primitives/src/utils/vec.rs diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index a429f8f2e..5ab14a05d 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -9,9 +9,6 @@ use ark_r1cs_std::{ groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, }; use ark_relations::gr1cs::SynthesisError; -use ark_std::mem::swap; -use num_bigint::BigInt; -use num_integer::Integer; use crate::{ algebra::{field::SonobeField, group::emulated::EmulatedAffineVar, Val}, @@ -107,34 +104,34 @@ impl> } } -fn lattice_reduction_2x2( - mut b1: (BigInt, BigInt), - mut b2: (BigInt, BigInt), -) -> ((BigInt, BigInt), (BigInt, BigInt)) { - loop { - let mut b1_norm_sq = &b1.0 * &b1.0 + &b1.1 * &b1.1; - let mut b2_norm_sq = &b2.0 * &b2.0 + &b2.1 * &b2.1; - - if b1_norm_sq > b2_norm_sq { - swap(&mut b1, &mut b2); - swap(&mut b1_norm_sq, &mut b2_norm_sq); - } - - let (mut m, r) = (&b1.0 * &b2.0 + &b1.1 * &b2.1).div_rem(&b1_norm_sq); - if &r + &r >= b1_norm_sq { - m += BigInt::one(); - } - - if m.is_zero() { - break; - } - - b2.0 -= &m * &b1.0; - b2.1 -= &m * &b1.1; - } +// fn lattice_reduction_2x2( +// mut b1: (BigInt, BigInt), +// mut b2: (BigInt, BigInt), +// ) -> ((BigInt, BigInt), (BigInt, BigInt)) { +// loop { +// let mut b1_norm_sq = &b1.0 * &b1.0 + &b1.1 * &b1.1; +// let mut b2_norm_sq = &b2.0 * &b2.0 + &b2.1 * &b2.1; + +// if b1_norm_sq > b2_norm_sq { +// swap(&mut b1, &mut b2); +// swap(&mut b1_norm_sq, &mut b2_norm_sq); +// } + +// let (mut m, r) = (&b1.0 * &b2.0 + &b1.1 * &b2.1).div_rem(&b1_norm_sq); +// if &r + &r >= b1_norm_sq { +// m += BigInt::one(); +// } + +// if m.is_zero() { +// break; +// } + +// b2.0 -= &m * &b1.0; +// b2.1 -= &m * &b1.1; +// } - (b1, b2) -} +// (b1, b2) +// } // impl PointScalarMulGadget> for C { // fn mul_scalar(&self, scalar: &impl ToBitsGadget>) -> Result { diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 7f5ebc8e3..4fdaffa78 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -1,8 +1,9 @@ -pub use absorbable::{Absorbable, AbsorbableGadget}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; +pub use self::absorbable::{Absorbable, AbsorbableGadget}; + pub mod absorbable; pub mod griffin; pub mod poseidon; diff --git a/crates/primitives/src/utils/mod.rs b/crates/primitives/src/utils/mod.rs index 1e8ac48a7..2cf111496 100644 --- a/crates/primitives/src/utils/mod.rs +++ b/crates/primitives/src/utils/mod.rs @@ -1,2 +1 @@ pub mod null; -// pub mod vec; diff --git a/crates/primitives/src/utils/vec.rs b/crates/primitives/src/utils/vec.rs deleted file mode 100644 index f1c9fec21..000000000 --- a/crates/primitives/src/utils/vec.rs +++ /dev/null @@ -1,109 +0,0 @@ -use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - fields::fp::FpVar, - prelude::Boolean, - select::CondSelectGadget, - GR1CSVar, -}; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{ - borrow::Borrow, - fmt::Debug, - ops::{Deref, DerefMut}, -}; - -use crate::{ - arithmetizations::ArithConfig, - circuits::var::Var, - traits::Dummy, - transcripts::{Absorbable, AbsorbableGadget}, -}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct WrappedVec(Vec); - -impl Deref for WrappedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for WrappedVec { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From> for WrappedVec { - fn from(v: Vec) -> Self { - Self(v) - } -} - -impl Absorbable for WrappedVec { - fn absorb_into(&self, dest: &mut Vec) { - self.0.absorb_into(dest) - } -} - -impl> AbsorbableGadget for WrappedVec { - fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { - self.0.absorb_into(dest) - } -} - -impl, Y, F: Field> AllocVar, F> for WrappedVec { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let v = f()?; - Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(|v| Self(v)) - } -} - -impl> CondSelectGadget for WrappedVec { - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - if true_value.len() != false_value.len() { - return Err(SynthesisError::Unsatisfiable); - } - Ok(WrappedVec( - true_value - .0 - .iter() - .zip(false_value.0.iter()) - .map(|(t, f)| cond.select(t, f)) - .collect::>()?, - )) - } -} - -impl> GR1CSVar for WrappedVec { - type Value = WrappedVec; - - fn cs(&self) -> ConstraintSystemRef { - self.0.cs() - } - - fn value(&self) -> Result { - self.0.value().map(WrappedVec) - } -} - -impl> Var for WrappedVec { - type Native = WrappedVec; -} - -impl Dummy<&A> for WrappedVec { - fn dummy(cfg: &A) -> Self { - vec![V::default(); cfg.n_public_inputs()].into() - } -} From a4c50000fe9e621ab869d2d26fec2b748bce1467 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 21/99] Discussion about `Absorbable`'s design --- .../primitives/src/transcripts/absorbable.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/primitives/src/transcripts/absorbable.rs b/crates/primitives/src/transcripts/absorbable.rs index 8143a76d1..ba7de434c 100644 --- a/crates/primitives/src/transcripts/absorbable.rs +++ b/crates/primitives/src/transcripts/absorbable.rs @@ -2,6 +2,39 @@ use ark_ff::PrimeField; use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::SynthesisError; +// TODO (@winderica): +// +// Ideally this trait should be defined as follows, so that we can use it for +// absorbing values into bits/bytes/etc., in addition to field elements. +// (Although Arkworks' `Absorb` trait covers both bytes and field elements, it +// requires downstream types to support absorbing into both as well, even if the +// downstream type doesn't support/is unrelated to one absorbing target.) +// +// ```rs +// pub trait Absorbable { +// fn absorb_into(&self, dest: &mut Vec); + +// fn to_absorbable(&self) -> Vec { +// let mut result = Vec::new(); +// self.absorb_into(&mut result); +// result +// } +// } +// ``` +// +// But my attempt was unsuccessful. In our use case, `SonobeField` needs to be +// absorbed into prime fields that are unknown when making the definition. Due +// to the `F` type parameter in `Absorbable`, I have three options: +// 1. Define `SonobeField` as `SonobeField: Absorbable`. This means that +// I need to add `F` to everywhere `SonobeField` is used, making the codebase +// much more verbose. +// 2. Remove the `Absorbable` bound from `SonobeField`, but instead manually add +// `Absorbable` to `T: SonobeField`'s bounds whenever we need `T` to be +// absorbable. This also increases the verbosity a lot. +// 3. Wait for https://github.com/rust-lang/rust/issues/108185 to be resolved, +// so I can define `SonobeField: for Absorbable`. +// Personally I think the best option is 3. File an issue or submit a PR if you +// have better solution :) pub trait Absorbable { fn absorb_into(&self, dest: &mut Vec); From a36bf463818ea7c00bd67545188431c79ea900a6 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 22/99] Specify MSRV --- Cargo.toml | 3 ++- crates/primitives/src/transcripts/griffin/mod.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b56b4da35..2c8772be8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,9 +27,10 @@ ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives" ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" } # "sw-fix-updated" branch [workspace.package] -edition = "2021" +edition = "2024" license = "MIT" repository = "https://github.com/privacy-scaling-explorations/sonobe/" +rust-version = "1.85.1" [workspace.dependencies] acvm = { git = "https://github.com/winderica/noir", rev = "fc9e99", default-features = false } # "arkworks-next" branch diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index b4c931d92..1b7134234 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -50,7 +50,7 @@ impl GriffinParams { pub const INIT_SHAKE: &'static str = "Griffin"; pub fn new(t: usize, d: usize, rounds: usize) -> Self { - assert!(t == 3 || t.is_multiple_of(4)); + assert!(t == 3 || t % 4 == 0); assert!(d == 3 || d == 5); assert!(rounds >= 1); From ebae8c4f016cd296be077b701da6b3d06fd57d12 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 15:50:26 +0800 Subject: [PATCH 23/99] Improve naming and API design --- .../primitives/src/algebra/field/emulated.rs | 22 ++-- crates/primitives/src/algebra/field/mod.rs | 6 +- .../primitives/src/algebra/group/emulated.rs | 16 ++- crates/primitives/src/algebra/group/mod.rs | 10 +- crates/primitives/src/algebra/mod.rs | 2 +- crates/primitives/src/algebra/ops/bits.rs | 2 +- crates/primitives/src/algebra/ops/matrix.rs | 4 +- crates/primitives/src/algebra/ops/poly.rs | 4 +- crates/primitives/src/algebra/ops/pow.rs | 2 +- .../src/arithmetizations/ccs/circuits.rs | 2 +- .../src/arithmetizations/ccs/mod.rs | 20 +-- .../src/arithmetizations/r1cs/circuits.rs | 2 +- .../src/arithmetizations/r1cs/mod.rs | 47 ++++--- crates/primitives/src/circuits/mod.rs | 115 +++++++++--------- crates/primitives/src/circuits/utils.rs | 3 +- crates/primitives/src/commitments/mod.rs | 68 +++++------ crates/primitives/src/commitments/pedersen.rs | 34 +++--- crates/primitives/src/sumcheck/circuits.rs | 12 +- crates/primitives/src/sumcheck/mod.rs | 8 +- crates/primitives/src/sumcheck/utils.rs | 8 +- crates/primitives/src/traits.rs | 2 +- .../primitives/src/transcripts/griffin/mod.rs | 6 +- .../src/transcripts/griffin/sponge.rs | 10 +- .../src/transcripts/poseidon/sponge.rs | 12 +- crates/primitives/src/utils/null.rs | 2 +- 25 files changed, 216 insertions(+), 203 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index fcfb45b76..a4c11e64f 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -1,12 +1,12 @@ use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_r1cs_std::{ + GR1CSVar, alloc::{AllocVar, AllocationMode}, boolean::Boolean, convert::ToBitsGadget, - fields::{fp::FpVar, FieldVar}, + fields::{FieldVar, fp::FpVar}, prelude::EqGadget, select::CondSelectGadget, - GR1CSVar, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{ @@ -566,32 +566,32 @@ impl } } -impl - EquivalenceGadget> for IntVarInner +impl EquivalenceGadget> + for IntVarInner { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { self.enforce_equal(other) } } -impl - EquivalenceGadget> for IntVarInner +impl EquivalenceGadget> + for IntVarInner { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { self.enforce_congruent(other) } } -impl - EquivalenceGadget> for IntVarInner +impl EquivalenceGadget> + for IntVarInner { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { self.enforce_congruent(other) } } -impl - EquivalenceGadget> for IntVarInner +impl EquivalenceGadget> + for IntVarInner { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { self.enforce_congruent(other) @@ -1147,7 +1147,7 @@ mod tests { use ark_ff::Field; use ark_pallas::{Fq, Fr}; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{error::Error, test_rng, UniformRand}; + use ark_std::{UniformRand, error::Error, test_rng}; use num_bigint::RandBigInt; use super::*; diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 1037c875f..64b1316b9 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -1,5 +1,5 @@ use ark_ff::{BigInteger, Fp, FpConfig, PrimeField}; -use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_relations::gr1cs::SynthesisError; use ark_std::{ any::TypeId, @@ -8,14 +8,14 @@ use ark_std::{ }; use crate::{ - algebra::{field::emulated::EmulatedFieldVar, Val}, + algebra::{Val, field::emulated::EmulatedFieldVar}, traits::{Inputize, InputizeEmulated}, transcripts::{Absorbable, AbsorbableGadget}, }; pub mod emulated; -/// `Field` trait is a wrapper around `PrimeField` that also includes the +/// `SonobeField` trait is a wrapper around `PrimeField` that also includes the /// necessary bounds for the field to be used conveniently in folding schemes. pub trait SonobeField: PrimeField diff --git a/crates/primitives/src/algebra/group/emulated.rs b/crates/primitives/src/algebra/group/emulated.rs index 4ebd0cf79..42b93e3e4 100644 --- a/crates/primitives/src/algebra/group/emulated.rs +++ b/crates/primitives/src/algebra/group/emulated.rs @@ -1,12 +1,12 @@ -use ark_ec::{short_weierstrass::SWFlags, AffineRepr}; +use ark_ec::{AffineRepr, short_weierstrass::SWFlags}; use ark_ff::Zero; use ark_r1cs_std::{ + GR1CSVar, alloc::{AllocVar, AllocationMode}, eq::EqGadget, fields::fp::FpVar, prelude::Boolean, select::CondSelectGadget, - GR1CSVar, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; @@ -18,9 +18,13 @@ use crate::{ transcripts::AbsorbableGadget, }; -/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native -/// field, over the constraint field. It is not intended to perform operations, but just to contain -/// the affine coordinates in order to perform hash operations of the point. +/// `EmulatedAffineVar` defines an in-circuit elliptic curve point in its affine +/// representation, where the coordinates are non-native field variables in the +/// curve's base field `Target::BaseField`, emulated over the constraint field +/// `Base`. +/// +/// It is not intended to perform operations, but just to record the coordinates +/// in order to perform hash operations of the point. #[derive(Debug, Clone)] pub struct EmulatedAffineVar { pub x: EmulatedFieldVar, @@ -134,7 +138,7 @@ mod tests { use ark_pallas::{Fq, Fr, PallasConfig, Projective}; use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{error::Error, UniformRand}; + use ark_std::{UniformRand, error::Error}; use super::*; use crate::{ diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 5ab14a05d..3abf989aa 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -1,17 +1,17 @@ use ark_ec::{ - short_weierstrass::{Projective, SWCurveConfig}, AffineRepr, CurveGroup, PrimeGroup, + short_weierstrass::{Projective, SWCurveConfig}, }; use ark_ff::{Field, One, PrimeField, Zero}; use ark_r1cs_std::{ convert::ToConstraintFieldGadget, fields::fp::FpVar, - groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, + groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, }; use ark_relations::gr1cs::SynthesisError; use crate::{ - algebra::{field::SonobeField, group::emulated::EmulatedAffineVar, Val}, + algebra::{Val, field::SonobeField, group::emulated::EmulatedAffineVar}, traits::{Dummy, Inputize, InputizeEmulated}, transcripts::{Absorbable, AbsorbableGadget}, }; @@ -21,7 +21,7 @@ pub mod emulated; pub type CF1 = ::ScalarField; pub type CF2 = <::BaseField as Field>::BasePrimeField; -/// `Curve` trait is a wrapper around `CurveGroup` that also includes the +/// `SonobeCurve` trait is a wrapper around `CurveGroup` that also includes the /// necessary bounds for the curve to be used conveniently in folding schemes. pub trait SonobeCurve: CurveGroup @@ -30,7 +30,7 @@ pub trait SonobeCurve: + InputizeEmulated + Val< Var: CurveVar + AbsorbableGadget, - EmulatedVar = EmulatedAffineVar + EmulatedVar = EmulatedAffineVar, > { } diff --git a/crates/primitives/src/algebra/mod.rs b/crates/primitives/src/algebra/mod.rs index 91f50165f..438d26fca 100644 --- a/crates/primitives/src/algebra/mod.rs +++ b/crates/primitives/src/algebra/mod.rs @@ -1,5 +1,5 @@ use ark_ff::PrimeField; -use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use crate::traits::SonobeField; diff --git a/crates/primitives/src/algebra/ops/bits.rs b/crates/primitives/src/algebra/ops/bits.rs index 12a0c5dee..e438db2c0 100644 --- a/crates/primitives/src/algebra/ops/bits.rs +++ b/crates/primitives/src/algebra/ops/bits.rs @@ -1,5 +1,5 @@ use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, eq::EqGadget, fields::fp::FpVar, GR1CSVar}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, boolean::Boolean, eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; use crate::algebra::field::emulated::Bound; diff --git a/crates/primitives/src/algebra/ops/matrix.rs b/crates/primitives/src/algebra/ops/matrix.rs index 38ed5d44c..e1347f30e 100644 --- a/crates/primitives/src/algebra/ops/matrix.rs +++ b/crates/primitives/src/algebra/ops/matrix.rs @@ -1,8 +1,8 @@ use ark_ff::PrimeField; use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - fields::{fp::FpVar, FieldVar}, GR1CSVar, + alloc::{AllocVar, AllocationMode}, + fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::{Matrix, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, ops::Index}; diff --git a/crates/primitives/src/algebra/ops/poly.rs b/crates/primitives/src/algebra/ops/poly.rs index aec303013..91696240a 100644 --- a/crates/primitives/src/algebra/ops/poly.rs +++ b/crates/primitives/src/algebra/ops/poly.rs @@ -1,6 +1,6 @@ use ark_ff::{Field, PrimeField, Zero}; use ark_poly::{DenseMultilinearExtension, EvaluationDomain, GeneralEvaluationDomain}; -use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_relations::gr1cs::SynthesisError; use ark_std::log2; @@ -25,7 +25,7 @@ pub trait EvaluationDomainGadget { ) -> Result>, SynthesisError>; fn evaluate_vanishing_polynomial_var(&self, tau: &FpVar) - -> Result, SynthesisError>; + -> Result, SynthesisError>; } impl EvaluationDomainGadget for GeneralEvaluationDomain { diff --git a/crates/primitives/src/algebra/ops/pow.rs b/crates/primitives/src/algebra/ops/pow.rs index 918577115..ac78ba6a4 100644 --- a/crates/primitives/src/algebra/ops/pow.rs +++ b/crates/primitives/src/algebra/ops/pow.rs @@ -1,5 +1,5 @@ use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; pub trait Pow: Sized { /// Compute `self^0, self^1, ..., self^{n-1}` diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs index 2abb1271f..f9917cb35 100644 --- a/crates/primitives/src/arithmetizations/ccs/circuits.rs +++ b/crates/primitives/src/arithmetizations/ccs/circuits.rs @@ -6,7 +6,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::borrow::Borrow; -use super::{CCSVariant, CCS}; +use super::{CCS, CCSVariant}; use crate::algebra::ops::matrix::SparseMatrixVar; /// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 83424f422..722581134 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -5,10 +5,10 @@ use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, fmt::Debug, marker::Phant #[cfg(feature = "parallel")] use rayon::prelude::*; -use super::{r1cs::R1CS, Arith, ArithRelation, Error}; +use super::{Arith, ArithRelation, Error, r1cs::R1CS}; use crate::{ algebra::ops::poly::MLEHelper, - arithmetizations::{r1cs::R1CSConfig, ArithConfig}, + arithmetizations::{ArithConfig, r1cs::R1CSConfig}, circuits::Assignments, }; @@ -106,14 +106,18 @@ impl CCS { let public_len = z.public.as_ref().len(); let private_len = z.private.as_ref().len(); if public_len != self.n_public_inputs() { - return Err(Error::MalformedAssignments( - format!("The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", self.n_public_inputs(), public_len) - )); + return Err(Error::MalformedAssignments(format!( + "The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", + self.n_public_inputs(), + public_len + ))); } if private_len != self.n_witnesses() { - return Err(Error::MalformedAssignments( - format!("The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", self.n_witnesses(), private_len) - )); + return Err(Error::MalformedAssignments(format!( + "The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", + self.n_witnesses(), + private_len + ))); } // Recall that the evaluation of CCS at z is defined as: diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index e45f19853..54b1e0563 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -1,7 +1,7 @@ use ark_ff::PrimeField; use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, One}; +use ark_std::{One, borrow::Borrow}; use super::R1CS; use crate::{ diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 1def67afc..80d6cf8d3 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -4,9 +4,9 @@ use ark_std::{cfg_into_iter, cfg_iter, iterable::Iterable}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use super::{ccs::CCS, Arith, ArithRelation, Error}; +use super::{Arith, ArithRelation, Error, ccs::CCS}; use crate::{ - arithmetizations::{ccs::CCSVariant, ArithConfig}, + arithmetizations::{ArithConfig, ccs::CCSVariant}, circuits::Assignments, }; @@ -111,14 +111,18 @@ impl R1CS { let public_len = z.public.as_ref().len(); let private_len = z.private.as_ref().len(); if public_len != self.n_public_inputs() { - return Err(Error::MalformedAssignments( - format!("The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", self.n_public_inputs(), public_len) - )); + return Err(Error::MalformedAssignments(format!( + "The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", + self.n_public_inputs(), + public_len + ))); } if private_len != self.n_witnesses() { - return Err(Error::MalformedAssignments( - format!("The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", self.n_witnesses(), private_len) - )); + return Err(Error::MalformedAssignments(format!( + "The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", + self.n_witnesses(), + private_len + ))); } Ok(cfg_iter!(self.A) @@ -249,12 +253,12 @@ pub mod tests { use super::*; use crate::circuits::{ - utils::{constraints_for_test, satisfying_assignments_for_test, CircuitForTest}, - ConstraintSystemExt, + ArithExtractor, AssignmentsExtractor, + utils::{CircuitForTest, constraints_for_test, satisfying_assignments_for_test}, }; #[test] - fn test_constraint_extraction() -> Result<(), Box> { + fn test_satisfiability() -> Result<(), Box> { let mut rng = test_rng(); let circuit = CircuitForTest:: { x: Fr::rand(&mut rng), @@ -262,10 +266,19 @@ pub mod tests { let cs = ConstraintSystem::new_ref(); circuit.generate_constraints(cs.clone())?; assert!(cs.is_satisfied()?); - cs.finalize(); - let cs = cs.into_inner().unwrap(); - assert_eq!(R1CS::from(&cs), constraints_for_test()); + Ok(()) + } + + #[test] + fn test_constraint_extraction() -> Result<(), Box> { + let mut rng = test_rng(); + let circuit = CircuitForTest:: { + x: Fr::rand(&mut rng), + }; + let cs = ArithExtractor::new(); + cs.execute_synthesizer(circuit)?; + assert_eq!(cs.arith::>()?, constraints_for_test()); Ok(()) } @@ -274,11 +287,9 @@ pub mod tests { let mut rng = test_rng(); let x = Fr::rand(&mut rng); let circuit = CircuitForTest:: { x }; - let cs = ConstraintSystem::new_ref(); - circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - cs.finalize(); + let cs = AssignmentsExtractor::new(); + cs.execute_synthesizer(circuit)?; assert_eq!(cs.assignments()?, satisfying_assignments_for_test(x)); Ok(()) } diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 28d40afaf..78e5b060d 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -1,12 +1,11 @@ use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, GR1CSVar}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, fields::fp::FpVar}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; use ark_std::{ fmt::Debug, - marker::PhantomData, - ops::{Index, IndexMut}, + ops::{Deref, Index, IndexMut}, }; use crate::transcripts::{Absorbable, AbsorbableGadget}; @@ -95,82 +94,78 @@ impl + AsMut<[F]>> IndexMut for Assignments { } } -pub struct ConstraintSystemBuilder { - _f: PhantomData, - mode: SynthesisMode, - circuit: C, +pub struct ConstraintSystemExt +{ + cs: ConstraintSystemRef, } +impl Deref + for ConstraintSystemExt +{ + type Target = ConstraintSystemRef; -impl ConstraintSystemBuilder<(), ()> { - pub fn new() -> Self { - Self { - _f: PhantomData, - mode: SynthesisMode::Prove { - construct_matrices: true, - generate_lc_assignments: true, - }, - circuit: (), - } + fn deref(&self) -> &Self::Target { + &self.cs } } -impl Default for ConstraintSystemBuilder<(), ()> { - fn default() -> Self { - Self::new() - } -} - -impl ConstraintSystemBuilder { - pub fn with_setup_mode(self) -> Self { - Self { - _f: PhantomData, - mode: SynthesisMode::Setup, - circuit: self.circuit, - } +impl + ConstraintSystemExt +{ + pub fn new() -> Self { + let cs = ConstraintSystem::::new_ref(); + let mode = if ASSIGNMENTS_ENABLED { + SynthesisMode::Prove { + construct_matrices: ARITH_ENABLED, + generate_lc_assignments: ARITH_ENABLED, + } + } else { + SynthesisMode::Setup + }; + cs.set_mode(mode); + Self { cs } } - pub fn with_prove_mode(self) -> Self { - Self { - _f: PhantomData, - mode: SynthesisMode::Prove { - construct_matrices: true, - generate_lc_assignments: true, - }, - circuit: self.circuit, - } + pub fn execute_synthesizer( + &self, + circuit: impl ConstraintSynthesizer, + ) -> Result<(), SynthesisError> { + self.execute_fn(|cs| circuit.generate_constraints(cs)) } - pub fn with_circuit>( - self, - circuit: C, - ) -> ConstraintSystemBuilder { - ConstraintSystemBuilder { - _f: PhantomData, - mode: self.mode, - circuit, + pub fn execute_fn( + &self, + circuit: impl FnOnce(ConstraintSystemRef) -> Result, + ) -> Result { + let result = circuit(self.cs.clone())?; + if ARITH_ENABLED { + self.cs.finalize(); } + Ok(result) } } -impl> ConstraintSystemBuilder { - pub fn synthesize(self) -> Result, SynthesisError> { - let cs = ConstraintSystem::::new_ref(); - cs.set_mode(self.mode); - self.circuit.generate_constraints(cs.clone())?; - cs.finalize(); - Ok(cs.into_inner().unwrap()) +impl Default + for ConstraintSystemExt +{ + fn default() -> Self { + Self::new() } } -pub trait ConstraintSystemExt { - fn assignments(&self) -> Result>, SynthesisError>; +pub type ArithExtractor = ConstraintSystemExt; +pub type AssignmentsExtractor = ConstraintSystemExt; + +impl ArithExtractor { + pub fn arith>>(self) -> Result { + Ok(self.cs.into_inner().unwrap().into()) + } } -impl ConstraintSystemExt for ConstraintSystemRef { - fn assignments(&self) -> Result>, SynthesisError> { - let witness = self.witness_assignment()?.to_vec(); +impl AssignmentsExtractor { + pub fn assignments(self) -> Result>, SynthesisError> { + let witness = self.cs.witness_assignment()?.to_vec(); // skip the first element which is '1' - let instance = self.instance_assignment()?[1..].to_vec(); + let instance = self.cs.instance_assignment()?[1..].to_vec(); Ok((F::one(), instance, witness).into()) } diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 463bbaa65..cd345b9d2 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -8,7 +8,8 @@ use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, Synthesis use super::Assignments; use crate::{ arithmetizations::r1cs::{R1CS, R1CSConfig}, - circuits::FCircuit, traits::SonobeField, + circuits::FCircuit, + traits::SonobeField, }; pub struct CircuitForTest { diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index b9a948ab5..5e1f2f256 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,5 +1,5 @@ use ark_ff::UniformRand; -use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, select::CondSelectGadget, GR1CSVar}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, fields::fp::FpVar, select::CondSelectGadget}; use ark_relations::gr1cs::SynthesisError; use ark_std::{ fmt::Debug, @@ -11,12 +11,12 @@ use thiserror::Error; use crate::{ algebra::{ - field::{emulated::EmulatedFieldVar, TwoStageFieldVar}, + Val, + field::{TwoStageFieldVar, emulated::EmulatedFieldVar}, group::emulated::EmulatedAffineVar, ops::bits::FromBitsGadget, - Val, }, - traits::{SonobeCurve, SonobeField, CF1, CF2}, + traits::{CF1, CF2, SonobeCurve, SonobeField}, transcripts::{Absorbable, AbsorbableGadget}, }; @@ -26,7 +26,9 @@ pub mod pedersen; #[derive(Debug, Error)] pub enum Error { // Commitment errors - #[error("The message being committed to has length {1}, exceeding the maximum supported length ({0})")] + #[error( + "The message being committed to has length {1}, exceeding the maximum supported length ({0})" + )] MessageTooLong(usize, usize), #[error("Blinding factor not 0 for Commitment without hiding")] BlindingNotZero, @@ -40,7 +42,7 @@ pub trait CommitmentKey: Clone { fn max_scalars_len(&self) -> usize; } -pub trait VectorCommitmentDef: 'static + Clone + Debug + PartialEq + Eq { +pub trait CommitmentDef: 'static + Clone + Debug + PartialEq + Eq { const IS_HIDING: bool; type Key: CommitmentKey; @@ -62,7 +64,7 @@ pub trait VectorCommitmentDef: 'static + Clone + Debug + PartialEq + Eq { + Sum; } -pub trait VectorCommitmentOps: VectorCommitmentDef { +pub trait CommitmentOps: CommitmentDef { fn generate_key(len: usize, rng: impl RngCore) -> Result; fn commit( @@ -79,28 +81,28 @@ pub trait VectorCommitmentOps: VectorCommitmentDef { ) -> Result<(), Error>; } -pub trait VectorCommitmentDefGadget: Clone { +pub trait CommitmentDefGadget: Clone { type ConstraintField: SonobeField; type KeyVar; type ScalarVar: AbsorbableGadget + CondSelectGadget + FromBitsGadget - + AllocVar<::Scalar, Self::ConstraintField> - + GR1CSVar::Scalar> + + AllocVar<::Scalar, Self::ConstraintField> + + GR1CSVar::Scalar> + TwoStageFieldVar; type CommitmentVar: Clone + AbsorbableGadget + CondSelectGadget - + AllocVar<::Commitment, Self::ConstraintField> - + GR1CSVar::Commitment>; - type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> - + GR1CSVar::Randomness>; + + AllocVar<::Commitment, Self::ConstraintField> + + GR1CSVar::Commitment>; + type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> + + GR1CSVar::Randomness>; - type Native: VectorCommitmentDef; + type Native: CommitmentDef; } -pub trait VectorCommitmentOpsGadget: VectorCommitmentDefGadget { +pub trait CommitmentOpsGadget: CommitmentDefGadget { fn open( ck: &Self::KeyVar, v: &[Self::ScalarVar], @@ -109,25 +111,23 @@ pub trait VectorCommitmentOpsGadget: VectorCommitmentDefGadget { ) -> Result<(), SynthesisError>; } -pub trait GroupBasedVectorCommitment: - VectorCommitmentDef< - Commitment: SonobeCurve, - Scalar = CF1<::Commitment>, - > + VectorCommitmentOps +pub trait GroupBasedCommitment: + CommitmentDef::Commitment>> + + CommitmentOps { - type Gadget1: VectorCommitmentOpsGadget - + VectorCommitmentDefGadget< + type Gadget1: CommitmentOpsGadget + + CommitmentDefGadget< ConstraintField = CF2, ScalarVar = EmulatedFieldVar, Self::Scalar>, CommitmentVar = ::Var, Native = Self, >; - type Gadget2: VectorCommitmentDefGadget< - ConstraintField = Self::Scalar, - ScalarVar = FpVar, - CommitmentVar = EmulatedAffineVar, - Native = Self, - >; + type Gadget2: CommitmentDefGadget< + ConstraintField = Self::Scalar, + ScalarVar = FpVar, + CommitmentVar = EmulatedAffineVar, + Native = Self, + >; } #[cfg(test)] @@ -137,17 +137,17 @@ mod tests { use super::*; - pub fn test_commitment_correctness( + pub fn test_commitment_correctness( mut rng: impl RngCore, len: usize, ) -> Result<(), Box> { let v = (0..len) - .map(|_| VC::Scalar::rand(&mut rng)) + .map(|_| CM::Scalar::rand(&mut rng)) .collect::>(); - let ck = VC::generate_key(len, &mut rng)?; - let (cm, r) = VC::commit(&ck, &v, &mut rng)?; - VC::open(&ck, &v, &r, &cm)?; + let ck = CM::generate_key(len, &mut rng)?; + let (cm, r) = CM::commit(&ck, &v, &mut rng)?; + CM::open(&ck, &v, &r, &cm)?; Ok(()) } } diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 7d470b933..381bb74eb 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -2,15 +2,13 @@ use ark_r1cs_std::{ boolean::Boolean, convert::ToBitsGadget, eq::EqGadget, fields::fp::FpVar, groups::CurveVar, }; use ark_relations::gr1cs::SynthesisError; -use ark_std::{iter::repeat_with, marker::PhantomData, rand::RngCore, UniformRand}; +use ark_std::{UniformRand, iter::repeat_with, marker::PhantomData, rand::RngCore}; -use super::{ - CommitmentKey, Error, VectorCommitmentDef, VectorCommitmentDefGadget, VectorCommitmentOps, -}; +use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error}; use crate::{ algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, - commitments::{GroupBasedVectorCommitment, VectorCommitmentOpsGadget}, - traits::{SonobeCurve, CF1, CF2}, + commitments::{CommitmentOpsGadget, GroupBasedCommitment}, + traits::{CF1, CF2, SonobeCurve}, utils::null::Null, }; @@ -70,7 +68,7 @@ pub struct PedersenEmulatedGadget { _c: PhantomData, } -impl VectorCommitmentDef for Pedersen { +impl CommitmentDef for Pedersen { const IS_HIDING: bool = false; type Key = PedersenKey; @@ -79,7 +77,7 @@ impl VectorCommitmentDef for Pedersen { type Randomness = Null; } -impl VectorCommitmentDef for Pedersen { +impl CommitmentDef for Pedersen { const IS_HIDING: bool = true; type Key = PedersenKey; @@ -88,7 +86,7 @@ impl VectorCommitmentDef for Pedersen { type Randomness = C::ScalarField; } -impl VectorCommitmentDefGadget for PedersenGadget { +impl CommitmentDefGadget for PedersenGadget { type ConstraintField = CF2; type KeyVar = Vec; @@ -102,7 +100,7 @@ impl VectorCommitmentDefGadget for PedersenGadget { type Native = Pedersen; } -impl VectorCommitmentDefGadget for PedersenGadget { +impl CommitmentDefGadget for PedersenGadget { type ConstraintField = CF2; type KeyVar = (Vec, C::Var); @@ -116,7 +114,7 @@ impl VectorCommitmentDefGadget for PedersenGadget { type Native = Pedersen; } -impl VectorCommitmentDefGadget for PedersenEmulatedGadget { +impl CommitmentDefGadget for PedersenEmulatedGadget { type ConstraintField = CF1; type KeyVar = Vec, C>>; @@ -130,7 +128,7 @@ impl VectorCommitmentDefGadget for PedersenEmulatedGadget; } -impl VectorCommitmentDefGadget for PedersenEmulatedGadget { +impl CommitmentDefGadget for PedersenEmulatedGadget { type ConstraintField = CF1; type KeyVar = ( @@ -147,17 +145,17 @@ impl VectorCommitmentDefGadget for PedersenEmulatedGadget; } -impl GroupBasedVectorCommitment for Pedersen { +impl GroupBasedCommitment for Pedersen { type Gadget1 = PedersenGadget; type Gadget2 = PedersenEmulatedGadget; } -impl GroupBasedVectorCommitment for Pedersen { +impl GroupBasedCommitment for Pedersen { type Gadget1 = PedersenGadget; type Gadget2 = PedersenEmulatedGadget; } -impl VectorCommitmentOps for Pedersen { +impl CommitmentOps for Pedersen { fn generate_key(len: usize, rng: impl RngCore) -> Result, Error> { Ok(PedersenKey::new(len, rng)) } @@ -177,7 +175,7 @@ impl VectorCommitmentOps for Pedersen { } } -impl VectorCommitmentOps for Pedersen { +impl CommitmentOps for Pedersen { fn generate_key(len: usize, rng: impl RngCore) -> Result, Error> { Ok(PedersenKey::new(len, rng)) } @@ -313,7 +311,7 @@ impl PedersenGadget { } } -impl VectorCommitmentOpsGadget for PedersenGadget { +impl CommitmentOpsGadget for PedersenGadget { fn open( ck: &Vec, v: &[EmulatedFieldVar, CF1>], @@ -330,7 +328,7 @@ impl VectorCommitmentOpsGadget for PedersenGadget { } } -impl VectorCommitmentOpsGadget for PedersenGadget { +impl CommitmentOpsGadget for PedersenGadget { fn open( (g, h): &(Vec, C::Var), v: &[EmulatedFieldVar, CF1>], diff --git a/crates/primitives/src/sumcheck/circuits.rs b/crates/primitives/src/sumcheck/circuits.rs index 640e59725..c8f8ce2f5 100644 --- a/crates/primitives/src/sumcheck/circuits.rs +++ b/crates/primitives/src/sumcheck/circuits.rs @@ -6,7 +6,7 @@ use ark_ff::PrimeField; use ark_r1cs_std::{ eq::EqGadget, - fields::{fp::FpVar, FieldVar}, + fields::{FieldVar, fp::FpVar}, poly::polynomial::univariate::dense::DensePolynomialVar, }; use ark_relations::gr1cs::SynthesisError; @@ -56,21 +56,21 @@ impl IOPSumCheckGadget { mod tests { use ark_bn254::Fr; use ark_crypto_primitives::sponge::{ - poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, CryptographicSponge, + poseidon::{PoseidonSponge, constraints::PoseidonSpongeVar}, }; use ark_ff::{One, Zero}; use ark_poly::{ - univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, - MultilinearExtension, Polynomial, + DenseMultilinearExtension, DenseUVPolynomial, MultilinearExtension, Polynomial, + univariate::DensePolynomial, }; - use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; + use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, test_rng}; use super::*; use crate::{ - sumcheck::{utils::VirtualPolynomial, IOPSumCheck}, + sumcheck::{IOPSumCheck, utils::VirtualPolynomial}, transcripts::poseidon::poseidon_canonical_config, }; diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index 610102b4e..29259e6c4 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -11,7 +11,7 @@ use ark_ff::PrimeField; use ark_poly::{ - univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, Polynomial, + DenseMultilinearExtension, DenseUVPolynomial, Polynomial, univariate::DensePolynomial, }; use ark_std::{cfg_chunks, cfg_into_iter, fmt::Debug}; #[cfg(feature = "parallel")] @@ -19,8 +19,8 @@ use rayon::prelude::*; use thiserror::Error; use self::utils::{ - barycentric_weights, compute_lagrange_interpolated_poly, extrapolate, VPAuxInfo, - VirtualPolynomial, + VPAuxInfo, VirtualPolynomial, barycentric_weights, compute_lagrange_interpolated_poly, + extrapolate, }; use crate::transcripts::{Absorbable, Transcript}; @@ -211,7 +211,7 @@ pub mod tests { use ark_ff::Field; use ark_pallas::Fr; use ark_poly::MultilinearExtension; - use ark_std::{test_rng, One, Zero}; + use ark_std::{One, Zero, test_rng}; use super::*; use crate::transcripts::poseidon::poseidon_canonical_config; diff --git a/crates/primitives/src/sumcheck/utils.rs b/crates/primitives/src/sumcheck/utils.rs index 2cc71f748..6d3ad719b 100644 --- a/crates/primitives/src/sumcheck/utils.rs +++ b/crates/primitives/src/sumcheck/utils.rs @@ -10,9 +10,9 @@ //! This module defines our main mathematical object `VirtualPolynomial`; and //! various functions associated with it. -use ark_ff::{batch_inversion, Field, PrimeField}; -use ark_poly::{univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial}; -use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; +use ark_ff::{Field, PrimeField, batch_inversion}; +use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, univariate::DensePolynomial}; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_serialize::CanonicalSerialize; use ark_std::cfg_into_iter; #[cfg(feature = "parallel")] @@ -207,7 +207,7 @@ pub fn compute_lagrange_interpolated_poly(p_i: &[F]) -> DensePoly #[cfg(test)] mod tests { use ark_pallas::Fr; - use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; + use ark_poly::{DenseUVPolynomial, Polynomial, univariate::DensePolynomial}; use ark_std::UniformRand; use super::*; diff --git a/crates/primitives/src/traits.rs b/crates/primitives/src/traits.rs index 9c97b277f..6162c5c4c 100644 --- a/crates/primitives/src/traits.rs +++ b/crates/primitives/src/traits.rs @@ -1,6 +1,6 @@ pub use crate::algebra::{ field::SonobeField, - group::{SonobeCurve, CF1, CF2}, + group::{CF1, CF2, SonobeCurve}, }; pub trait Dummy { diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index 1b7134234..fa706085d 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -1,14 +1,14 @@ use ark_ff::{LegendreSymbol, PrimeField}; use ark_r1cs_std::{ - alloc::AllocVar, - fields::{fp::FpVar, FieldVar}, GR1CSVar, + alloc::AllocVar, + fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::SynthesisError; use num_bigint::BigUint; use sha3::{ - digest::{ExtendableOutput, Update, XofReader}, Shake128, Shake128Reader, + digest::{ExtendableOutput, Update, XofReader}, }; pub mod sponge; diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index fc732eadf..7205b74d7 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -1,13 +1,13 @@ use ark_crypto_primitives::sponge::DuplexSpongeMode; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ - fields::{fp::FpVar, FieldVar}, + fields::{FieldVar, fp::FpVar}, prelude::{Boolean, ToBitsGadget}, }; use ark_relations::gr1cs::SynthesisError; use ark_std::sync::Arc; -use crate::transcripts::{griffin::GriffinParams, AbsorbableGadget, Transcript, TranscriptVar}; +use crate::transcripts::{AbsorbableGadget, Transcript, TranscriptVar, griffin::GriffinParams}; #[derive(Clone)] pub struct GriffinSponge { @@ -350,14 +350,14 @@ impl TranscriptVar for GriffinSpongeVar { #[cfg(test)] pub mod tests { - use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; + use ark_bn254::{Fq, Fr, G1Projective as G1, constraints::GVar, g1::Config}; use ark_ec::PrimeGroup; use ark_ff::UniformRand; use ark_r1cs_std::{ + GR1CSVar, alloc::AllocVar, fields::fp::FpVar, - groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, - GR1CSVar, + groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, }; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, test_rng}; diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index f1f58cc76..5110fe4bb 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -1,7 +1,7 @@ use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, FieldBasedCryptographicSponge, + constraints::CryptographicSpongeVar, + poseidon::{PoseidonConfig, PoseidonSponge, constraints::PoseidonSpongeVar}, }; use ark_ff::PrimeField; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; @@ -76,15 +76,15 @@ impl TranscriptVar for PoseidonSpongeVar { #[cfg(test)] pub mod tests { - use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; - use ark_crypto_primitives::sponge::poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}; + use ark_bn254::{Fq, Fr, G1Projective as G1, constraints::GVar, g1::Config}; + use ark_crypto_primitives::sponge::poseidon::{PoseidonSponge, constraints::PoseidonSpongeVar}; use ark_ec::PrimeGroup; use ark_ff::UniformRand; use ark_r1cs_std::{ + GR1CSVar, alloc::AllocVar, fields::fp::FpVar, - groups::{curves::short_weierstrass::ProjectiveVar, CurveVar}, - GR1CSVar, + groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, }; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, str::FromStr, test_rng}; diff --git a/crates/primitives/src/utils/null.rs b/crates/primitives/src/utils/null.rs index 6d043534b..bc6a9bb09 100644 --- a/crates/primitives/src/utils/null.rs +++ b/crates/primitives/src/utils/null.rs @@ -1,7 +1,7 @@ use ark_ff::Field; use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, GR1CSVar, + alloc::{AllocVar, AllocationMode}, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{ From 1dbdf21119625027ae9ddc62b75e0e05deb8e8c5 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:19:13 +0800 Subject: [PATCH 24/99] Fix CI --- crates/primitives/Cargo.toml | 3 +++ crates/primitives/src/transcripts/griffin/sponge.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index bf252b6a2..ab95fe398 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -26,6 +26,9 @@ thiserror = { workspace = true } ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } ark-pallas = { workspace = true, features = ["curve", "r1cs"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom = { version = "0.2", features = ["js"] } + [features] default = ["parallel"] parallel = [ diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index 7205b74d7..2181a42ad 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -26,7 +26,7 @@ impl GriffinSponge { self.griffin.permute(&mut self.state); } - // Absorbs everything in elements, this does not end in an absorbtion. + // Absorbs everything in elements, this does not end in an absorption. fn absorb_internal(&mut self, mut rate_start_index: usize, elements: &[F]) { let mut remaining_elements = elements; From da29e88845ae12ec113b2e684d40dc0834c758a9 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:44:30 +0800 Subject: [PATCH 25/99] Fix clippy --- crates/primitives/src/algebra/field/emulated.rs | 4 ++-- crates/primitives/src/sumcheck/mod.rs | 1 + crates/primitives/src/transcripts/griffin/mod.rs | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index a4c11e64f..b22fd8ad7 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -458,8 +458,7 @@ impl IntVarInner AllocVar<(BigInt, Bound), F> for IntVarInner= lb` + #[allow(clippy::if_same_then_else)] if lb.is_zero() && ub + BigInt::one() == BigInt::one() << len { } else if BigInt::one() - lb == BigInt::one() << len && ub.is_zero() { } else if BigInt::one() - lb == BigInt::one() << len diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index 29259e6c4..4a58a89b8 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -41,6 +41,7 @@ pub enum Error { pub struct IOPSumCheck; impl IOPSumCheck { + #[allow(clippy::type_complexity)] pub fn prove( mut poly: VirtualPolynomial, transcript: &mut impl Transcript, diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index fa706085d..ca80f4642 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -50,7 +50,9 @@ impl GriffinParams { pub const INIT_SHAKE: &'static str = "Griffin"; pub fn new(t: usize, d: usize, rounds: usize) -> Self { - assert!(t == 3 || t % 4 == 0); + // Equivalent to `assert!(t == 3 || t % 4 == 0);`, but bypass clippy's + // warning about `is_multiple_of`. + assert!(t == 3 || t & 3 == 0); assert!(d == 3 || d == 5); assert!(rounds >= 1); From 11fb8b194c240c4bf99e367f6c22cb36f95cd633 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 26/99] Actually test wasm targets --- Cargo.toml | 50 +++++++------------ crates/primitives/Cargo.toml | 5 +- .../primitives/src/algebra/field/emulated.rs | 14 +++--- .../primitives/src/algebra/group/emulated.rs | 8 +-- .../src/arithmetizations/r1cs/mod.rs | 10 ++-- crates/primitives/src/commitments/pedersen.rs | 9 +++- crates/primitives/src/sumcheck/circuits.rs | 6 ++- crates/primitives/src/sumcheck/mod.rs | 6 ++- crates/primitives/src/sumcheck/utils.rs | 8 +-- .../primitives/src/transcripts/griffin/mod.rs | 2 + .../src/transcripts/griffin/sponge.rs | 8 +-- .../src/transcripts/poseidon/sponge.rs | 8 +-- 12 files changed, 72 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c8772be8..6d1e89a20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,49 +33,33 @@ repository = "https://github.com/privacy-scaling-explorations/sonobe/" rust-version = "1.85.1" [workspace.dependencies] -acvm = { git = "https://github.com/winderica/noir", rev = "fc9e99", default-features = false } # "arkworks-next" branch -askama = { version = "0.12.0", default-features = false } -clap = { version = "4.4" } -clap-verbosity-flag = { version = "2.1" } -criterion = { version = "0.5" } -env_logger = { version = "0.10" } -getrandom = { version = "0.2" } -log = { version = "0.4" } -noname = { git = "https://github.com/dmpierre/noname", rev = "c34f17" } num-bigint = { version = "0.4.3" } num-integer = { version = "0.1" } num-traits = { version = "0.2" } -pprof = { version = "0.13" } -serde = { version = "^1.0.0" } -serde_json = { version = "^1.0.0" } sha3 = { version = "0.10" } rand = { version = "0.8.5" } rayon = { version = "1" } -revm = { version = "19.5.0", default-features = false } -rust-crypto = { version = "0.2" } -thiserror = { version = "1.0" } -tokio = "1.44.1" -wasmer = { version = "6.1.0", default-features = false } +thiserror = { version = "2.0.16" } +wasm-bindgen-test = { version = "0.3" } # Arkworks family -ark-bn254 = { version = "^0.5.0", default-features = false } -ark-circom = { git = "https://github.com/arkworks-rs/circom-compat", default-features = false } -ark-crypto-primitives = { version = "^0.5.0", default-features = false } -ark-ec = { version = "^0.5.0", default-features = false } -ark-ff = { version = "^0.5.0", default-features = false } -ark-groth16 = { git = "https://github.com/arkworks-rs/groth16" } -ark-grumpkin = { version = "^0.5.0", default-features = false } -ark-mnt4-298 = { version = "^0.5.0" } -ark-mnt6-298 = { version = "^0.5.0" } -ark-pallas = { version = "^0.5.0" } -ark-poly = { version = "^0.5.0", default-features = false } -ark-poly-commit = { version = "^0.5.0" } -ark-r1cs-std = { version = "^0.5.0", default-features = false } +ark-crypto-primitives = { git = "https://github.com/arkworks-rs/crypto-primitives", default-features = false } +ark-ec = { git = "https://github.com/arkworks-rs/algebra", default-features = false } +ark-ff = { git = "https://github.com/arkworks-rs/algebra", default-features = false } +ark-groth16 = { git = "https://github.com/arkworks-rs/groth16", default-features = false } +ark-poly = { git = "https://github.com/arkworks-rs/algebra", default-features = false } +ark-poly-commit = { git = "https://github.com/arkworks-rs/poly-commit", default-features = false } +ark-r1cs-std = { git = "https://github.com/arkworks-rs/r1cs-std", default-features = false } ark-relations = { git = "https://github.com/arkworks-rs/snark", default-features = false } -ark-serialize = { version = "^0.5.0" } +ark-serialize = { git = "https://github.com/arkworks-rs/algebra", default-features = false } ark-snark = { git = "https://github.com/arkworks-rs/snark", default-features = false } -ark-std = { version = "^0.5.0", default-features = false } -ark-vesta = { version = "^0.5.0" } +ark-std = { git = "https://github.com/arkworks-rs/std", default-features = false } + +# Ark curves +ark-bn254 = { git = "https://github.com/arkworks-rs/algebra", default-features = false } +ark-grumpkin = { git = "https://github.com/arkworks-rs/algebra", default-features = false } +ark-pallas = { git = "https://github.com/arkworks-rs/algebra", default-features = false } +ark-vesta = { git = "https://github.com/arkworks-rs/algebra", default-features = false } # Local crates sonobe-primitives = { path = "crates/primitives", default-features = false } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index ab95fe398..15b3e513f 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -29,8 +29,11 @@ ark-pallas = { workspace = true, features = ["curve", "r1cs"] } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] getrandom = { version = "0.2", features = ["js"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +wasm-bindgen-test = { workspace = true } + [features] -default = ["parallel"] +default = [] parallel = [ "ark-poly-commit/parallel", "ark-relations/parallel", diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index b22fd8ad7..1081044e8 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -1147,14 +1147,16 @@ mod tests { use ark_ff::Field; use ark_pallas::{Fq, Fr}; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{UniformRand, error::Error, test_rng}; + use ark_std::{UniformRand, error::Error, rand::thread_rng}; use num_bigint::RandBigInt; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; #[test] fn test_alloc() -> Result<(), Box> { - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let size = 1024; let mut lbs = vec![BigInt::zero()]; @@ -1211,7 +1213,7 @@ mod tests { let size = 2048; - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let a = rng.gen_bigint(size as u64); let b = rng.gen_bigint(size as u64); let ab = &a * &b; @@ -1282,7 +1284,7 @@ mod tests { fn test_mul_fq() -> Result<(), Box> { let cs = ConstraintSystem::::new_ref(); - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let a = Fq::rand(rng); let b = Fq::rand(rng); let ab = a * b; @@ -1307,7 +1309,7 @@ mod tests { fn test_pow() -> Result<(), Box> { let cs = ConstraintSystem::::new_ref(); - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let a = Fq::rand(rng); @@ -1329,7 +1331,7 @@ mod tests { let len = 1000; - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); diff --git a/crates/primitives/src/algebra/group/emulated.rs b/crates/primitives/src/algebra/group/emulated.rs index 42b93e3e4..477de2b0e 100644 --- a/crates/primitives/src/algebra/group/emulated.rs +++ b/crates/primitives/src/algebra/group/emulated.rs @@ -138,7 +138,9 @@ mod tests { use ark_pallas::{Fq, Fr, PallasConfig, Projective}; use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{UniformRand, error::Error}; + use ark_std::{UniformRand, error::Error, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::{ @@ -160,7 +162,7 @@ mod tests { let cs = ConstraintSystem::::new_ref(); // check that point_to_nonnative_limbs returns the expected values - let mut rng = ark_std::test_rng(); + let mut rng = thread_rng(); let p = Projective::rand(&mut rng); let p_var = EmulatedAffineVar::::new_witness(cs.clone(), || Ok(p))?; assert_eq!(p_var.to_absorbable()?.value()?, p.to_absorbable()); @@ -170,7 +172,7 @@ mod tests { #[test] fn test_inputize() -> Result<(), Box> { // check that point_to_nonnative_limbs returns the expected values - let mut rng = ark_std::test_rng(); + let mut rng = thread_rng(); let p = Projective::rand(&mut rng); let cs = ConstraintSystem::::new_ref(); diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 80d6cf8d3..84c1c6278 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -249,7 +249,9 @@ pub mod tests { use ark_bn254::Fr; use ark_ff::UniformRand; use ark_relations::gr1cs::ConstraintSynthesizer; - use ark_std::{error::Error, test_rng}; + use ark_std::{error::Error, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::circuits::{ @@ -259,7 +261,7 @@ pub mod tests { #[test] fn test_satisfiability() -> Result<(), Box> { - let mut rng = test_rng(); + let mut rng = thread_rng(); let circuit = CircuitForTest:: { x: Fr::rand(&mut rng), }; @@ -272,7 +274,7 @@ pub mod tests { #[test] fn test_constraint_extraction() -> Result<(), Box> { - let mut rng = test_rng(); + let mut rng = thread_rng(); let circuit = CircuitForTest:: { x: Fr::rand(&mut rng), }; @@ -284,7 +286,7 @@ pub mod tests { #[test] fn test_witness_extraction() -> Result<(), Box> { - let mut rng = test_rng(); + let mut rng = thread_rng(); let x = Fr::rand(&mut rng); let circuit = CircuitForTest:: { x }; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 381bb74eb..e51ef8edf 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -349,14 +349,19 @@ impl CommitmentOpsGadget for PedersenGadget { #[cfg(test)] mod tests { use ark_bn254::G1Projective; - use ark_std::{error::Error, rand::Rng, test_rng}; + use ark_std::{ + error::Error, + rand::{Rng, thread_rng}, + }; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::commitments::tests::test_commitment_correctness; #[test] fn test_pedersen_commitment() -> Result<(), Box> { - let mut rng = test_rng(); + let mut rng = thread_rng(); for i in 0..10 { let len = rng.gen_range((1 << i)..(1 << (i + 1))); test_commitment_correctness::>(&mut rng, len)?; diff --git a/crates/primitives/src/sumcheck/circuits.rs b/crates/primitives/src/sumcheck/circuits.rs index c8f8ce2f5..b79967e9e 100644 --- a/crates/primitives/src/sumcheck/circuits.rs +++ b/crates/primitives/src/sumcheck/circuits.rs @@ -66,7 +66,9 @@ mod tests { }; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{error::Error, test_rng}; + use ark_std::{error::Error, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::{ @@ -76,7 +78,7 @@ mod tests { #[test] fn test_sum_check_circuit() -> Result<(), Box> { - let mut rng = test_rng(); + let mut rng = thread_rng(); let poseidon_config = poseidon_canonical_config::(); for num_vars in 1..15 { let mut transcript_p = PoseidonSponge::new(&poseidon_config); diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index 4a58a89b8..d42d78a1c 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -212,7 +212,9 @@ pub mod tests { use ark_ff::Field; use ark_pallas::Fr; use ark_poly::MultilinearExtension; - use ark_std::{One, Zero, test_rng}; + use ark_std::{One, Zero, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::transcripts::poseidon::poseidon_canonical_config; @@ -221,7 +223,7 @@ pub mod tests { pub fn sumcheck_poseidon() -> Result<(), Error> { let n_vars = 10; - let mut rng = test_rng(); + let mut rng = thread_rng(); let poly_mle = DenseMultilinearExtension::rand(n_vars, &mut rng); let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, Fr::ONE); diff --git a/crates/primitives/src/sumcheck/utils.rs b/crates/primitives/src/sumcheck/utils.rs index 6d3ad719b..917ba6d2e 100644 --- a/crates/primitives/src/sumcheck/utils.rs +++ b/crates/primitives/src/sumcheck/utils.rs @@ -208,7 +208,9 @@ pub fn compute_lagrange_interpolated_poly(p_i: &[F]) -> DensePoly mod tests { use ark_pallas::Fr; use ark_poly::{DenseUVPolynomial, Polynomial, univariate::DensePolynomial}; - use ark_std::UniformRand; + use ark_std::{UniformRand, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; @@ -351,7 +353,7 @@ mod tests { #[test] fn test_compute_lagrange_interpolated_poly() { - let mut prng = ark_std::test_rng(); + let mut prng = thread_rng(); for degree in 1..30 { let poly = DensePolynomial::::rand(degree, &mut prng); // range (which is exclusive) is from 0 to degree + 1, since we need degree + 1 evaluations @@ -371,7 +373,7 @@ mod tests { #[test] fn test_interpolation() { - let mut prng = ark_std::test_rng(); + let mut prng = thread_rng(); // test a polynomial with 20 known points, i.e., with degree 19 let poly = DensePolynomial::::rand(20 - 1, &mut prng); diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index ca80f4642..518d69547 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -458,6 +458,8 @@ mod tests { use ark_ff::UniformRand; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index 2181a42ad..ecf2e75e5 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -360,7 +360,9 @@ pub mod tests { groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, }; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{error::Error, test_rng}; + use ark_std::{error::Error, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::algebra::{group::emulated::EmulatedAffineVar, ops::bits::FromBits}; @@ -370,7 +372,7 @@ pub mod tests { // use 'native' transcript let config = Arc::new(GriffinParams::::new(3, 5, 12)); let mut tr = GriffinSponge::::new(&config); - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let p = G1::rand(rng); tr.add(&p); @@ -393,7 +395,7 @@ pub mod tests { // use 'native' transcript let config = Arc::new(GriffinParams::::new(3, 5, 12)); let mut tr = GriffinSponge::::new(&config); - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let p = G1::rand(rng); tr.add(&p); diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index 5110fe4bb..e595247c8 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -87,7 +87,9 @@ pub mod tests { groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, }; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{error::Error, str::FromStr, test_rng}; + use ark_std::{error::Error, rand::thread_rng, str::FromStr}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use crate::{ algebra::{group::emulated::EmulatedAffineVar, ops::bits::FromBits}, @@ -120,7 +122,7 @@ pub mod tests { // use 'native' transcript let config = poseidon_canonical_config::(); let mut tr = PoseidonSponge::::new(&config); - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let p = G1::rand(rng); tr.add(&p); @@ -143,7 +145,7 @@ pub mod tests { // use 'native' transcript let config = poseidon_canonical_config::(); let mut tr = PoseidonSponge::::new(&config); - let rng = &mut test_rng(); + let rng = &mut thread_rng(); let p = G1::rand(rng); tr.add(&p); From 0f4a90dce159cde80dfb28edf0cd2fba8b2ee049 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 9 Feb 2026 19:36:40 +0800 Subject: [PATCH 27/99] Add docs for primitives --- .../primitives/src/algebra/field/emulated.rs | 415 +++++++++++------- crates/primitives/src/algebra/field/mod.rs | 50 ++- .../primitives/src/algebra/group/emulated.rs | 42 +- crates/primitives/src/algebra/group/mod.rs | 85 +--- crates/primitives/src/algebra/mod.rs | 22 +- crates/primitives/src/algebra/ops/bits.rs | 34 +- crates/primitives/src/algebra/ops/eq.rs | 14 +- crates/primitives/src/algebra/ops/matrix.rs | 20 +- crates/primitives/src/algebra/ops/mod.rs | 11 + crates/primitives/src/algebra/ops/poly.rs | 21 +- crates/primitives/src/algebra/ops/pow.rs | 22 +- crates/primitives/src/algebra/ops/rlc.rs | 18 + crates/primitives/src/algebra/ops/vector.rs | 42 +- .../src/arithmetizations/ccs/circuits.rs | 13 +- .../src/arithmetizations/ccs/mod.rs | 78 ++-- crates/primitives/src/arithmetizations/mod.rs | 203 ++++----- .../src/arithmetizations/r1cs/circuits.rs | 52 ++- .../src/arithmetizations/r1cs/mod.rs | 87 ++-- crates/primitives/src/circuits/mod.rs | 126 +++++- crates/primitives/src/circuits/utils.rs | 17 +- crates/primitives/src/commitments/mod.rs | 103 ++++- crates/primitives/src/commitments/pedersen.rs | 239 ++++------ crates/primitives/src/lib.rs | 10 + crates/primitives/src/relations/mod.rs | 27 +- crates/primitives/src/sumcheck/circuits.rs | 81 +++- crates/primitives/src/sumcheck/mod.rs | 114 ++++- crates/primitives/src/sumcheck/utils.rs | 86 ++-- crates/primitives/src/traits.rs | 32 +- .../primitives/src/transcripts/absorbable.rs | 48 +- .../primitives/src/transcripts/griffin/mod.rs | 322 ++++++++------ .../src/transcripts/griffin/sponge.rs | 240 +++++----- crates/primitives/src/transcripts/mod.rs | 114 ++++- .../src/transcripts/poseidon/mod.rs | 9 +- .../src/transcripts/poseidon/sponge.rs | 153 +++---- crates/primitives/src/utils/mod.rs | 2 + crates/primitives/src/utils/null.rs | 8 + 36 files changed, 1838 insertions(+), 1122 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 1081044e8..d75d61923 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -1,3 +1,14 @@ +//! This module provides implementation of in-circuit variables for emulated +//! integers or field elements. +//! +//! This is useful when we want to express or perform operations over a ring or +//! field in a circuit defined over a different field. +//! +//! Note that the implementation here is dedicated to Sonobe's use cases and the +//! priorities are efficiency instead of generality or usability, e.g., the user +//! needs to manually ensure the variables do not overflow the field capacity. +//! Therefore, be cautious if you want to use it in other contexts. + use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_r1cs_std::{ GR1CSVar, @@ -27,30 +38,50 @@ use crate::{ bits::{FromBitsGadget, ToBitsGadgetExt}, eq::EquivalenceGadget, matrix::{MatrixGadget, SparseMatrixVar}, - vector::VectorGadget, }, }, - transcripts::AbsorbableGadget, + transcripts::AbsorbableVar, }; +/// [`Bounds`] records the lower and upper bounds (inclusive) of an integer. +/// +/// When allocating an emulated field element, we need to decompose it into +/// several limbs, each represented as a variable in the constraint field. +/// Operations over the emulated field element are translated into operations +/// over its limbs. +/// After several operations, the limbs may grow larger than the capacity of the +/// constraint field, and to prevent that, we track the bounds of each limb +/// using this struct, so that we can take action before the limbs overflow. #[derive(Debug, Default, Clone, PartialEq)] -pub struct Bound(pub BigInt, pub BigInt); +pub struct Bounds(pub BigInt, pub BigInt); -impl Bound { +impl Bounds { + /// [`Bounds::zero`] returns the bounds `[0, 0]`. pub fn zero() -> Self { Self::default() } } -impl Bound { +impl Bounds { + /// [`Bounds::add`] computes the sum of two pairs of bounds. pub fn add(&self, other: &Self) -> Self { + // Consider two values `x` and `y`. + // For `z = x + y`, its lower bound is the sum of the lower bounds of + // `x` and `y`, and its upper bound is the sum of the upper bounds of + // `x` and `y`. Self(&self.0 + &other.0, &self.1 + &other.1) } + /// [`Bounds::sub`] computes the difference of two pairs of bounds. pub fn sub(&self, other: &Self) -> Self { + // Consider two values `x` and `y`. + // For `z = x - y`, its lower bound is the difference of the lower bound + // of `x` and the upper bound of `y`, and its upper bound is the + // difference of the upper bound of `x` and the lower bound of `y`. Self(&self.0 - &other.1, &self.1 - &other.0) } + /// [`Bounds::add_many`] computes the sum of multiple pairs of bounds. pub fn add_many(limbs: &[Self]) -> Self { Self( limbs.iter().map(|l| &l.0).sum(), @@ -58,29 +89,45 @@ impl Bound { ) } + /// [`Bounds::mul`] computes the product of two pairs of bounds. pub fn mul(&self, other: &Self) -> Self { + // Consider two values `x` and `y`. + // To compute the bounds of `z = x * y`, we need to take into account + // the signs of `x` and `y`. + // + // Therefore, we first compute the following 4 products formed by the + // possible combinations of the bounds of `x` and `y`: let ll = &self.0 * &other.0; let lu = &self.0 * &other.1; let ul = &self.1 * &other.0; let uu = &self.1 * &other.1; + // `z`'s lower bound is the minimum of these products, and its upper + // bound is the maximum of these products. Self( min(min(&ll, &lu), min(&ul, &uu)).clone(), max(max(&ll, &lu), max(&ul, &uu)).clone(), ) } + /// [`Bounds::shl`] computes the bounds after left-shifting by `shift` bits. pub fn shl(&self, shift: usize) -> Self { + // Given `x`, the bounds of `x << shift` can simply be computed by + // shifting the bounds of `x`. Self(&self.0 << shift, &self.1 << shift) } + /// [`Bounds::filter_safe`] checks if the bounds fit within the capacity of + /// a prime field `F`, and returns `Some(self)` if so, or `None` otherwise. pub fn filter_safe(self) -> Option { + // For a field `F`, we consider an integer `x` to be safe if and only if + // `-(|F| - 1) / 2 <= x <= (|F| - 1) / 2`. let limit = BigInt::from_biguint(Sign::Plus, F::MODULUS_MINUS_ONE_DIV_TWO.into()); (self.0 >= -&limit && self.1 <= limit).then_some(self) } } -fn compose>(limbs: V) -> BigInt { +fn compose(limbs: impl Borrow<[F]>) -> BigInt { let mut r = BigInt::zero(); for &limb in limbs.borrow().iter().rev() { @@ -94,18 +141,44 @@ fn compose>(limbs: V) -> BigInt { r } +/// [`LimbedVar`] represents an in-circuit variable for an emulated integer or +/// field element, whose value is decomposed into several limbs, each being +/// created as a [`FpVar`] in the constraint field and tracked with its bounds. +/// +/// The generic parameter `Cfg` can be used to customize the behavior of ops on +/// `LimbedVar`, for instance, by specifying the modulus when emulating a field +/// element. +/// +/// The const generic parameter `ALIGNED` indicates if the limbs are "aligned". +/// When allocating a [`LimbedVar`], each limb has a predefined bit-length, but +/// after several operations, the actual bit-length of each limb may grow beyond +/// that. +/// It is usually fine to have larger limbs, but if they becomes larger than the +/// field capacity, we can no longer do operations on them. +/// Therefore, we sometimes need to "align" the limbs, i.e., reduce each limb +/// back to the predefined bit-length. +/// We say the limbs are "aligned" if the actual bit-length of each limb equals +/// the predefined bit-length, and "unaligned" otherwise. #[derive(Debug, Clone)] -pub struct IntVarInner { +pub struct LimbedVar { _cfg: PhantomData, - pub limbs: Vec>, - pub bounds: Vec, + pub(crate) limbs: Vec>, + bounds: Vec, } -pub type BigIntVar = IntVarInner; -pub type EmulatedFieldVar = IntVarInner; +/// [`EmulatedIntVar`] is a type alias for emulated integer variables. +/// +/// We only expose aligned variables because unaligned integer variables only +/// appear as intermediate results during computations. +pub type EmulatedIntVar = LimbedVar; +/// [`EmulatedFieldVar`] is a type alias for emulated field element variables. +/// +/// We only expose aligned variables because unaligned integer variables only +/// appear as intermediate results during computations. +pub type EmulatedFieldVar = LimbedVar; -impl GR1CSVar for IntVarInner { - type Value = BigInt; +impl GR1CSVar for LimbedVar { + type Value = BigInt; // For integers, their values are `BigInt`. fn cs(&self) -> ConstraintSystemRef { self.limbs.cs() @@ -117,9 +190,9 @@ impl GR1CSVar for IntVarInner GR1CSVar - for IntVarInner + for LimbedVar { - type Value = Target; + type Value = Target; // For field elements, their values are in `Target`. fn cs(&self) -> ConstraintSystemRef { self.limbs.cs() @@ -138,8 +211,10 @@ impl GR1CSVar } } -impl IntVarInner { - pub fn new(limbs: Vec>, bounds: Vec) -> Self { +impl LimbedVar { + /// [`LimbedVar::new`] creates a new [`LimbedVar`] from the pre-allocated + /// limbs and their bounds. + pub fn new(limbs: Vec>, bounds: Vec) -> Self { Self { _cfg: PhantomData, limbs, @@ -147,6 +222,8 @@ impl IntVarInner { } } + /// [`LimbedVar::ubound`] computes the upper bound of the represented value + /// from the upper bounds of its limbs. fn ubound(&self) -> BigInt { let mut r = BigInt::zero(); @@ -158,6 +235,8 @@ impl IntVarInner { r } + /// [`LimbedVar::lbound`] computes the lower bound of the represented value + /// from the lower bounds of its limbs. fn lbound(&self) -> BigInt { let mut r = BigInt::zero(); @@ -170,10 +249,13 @@ impl IntVarInner { } } -impl IntVarInner { - /// Enforce `self` to be less than `other`, where `self` and `other` should - /// be aligned. - /// Adapted from https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 +impl LimbedVar { + /// [`LimbedVar::enforce_lt`] enforces `self` to be less than `other`, where + /// both should be aligned (as indicated by the const generic). + /// Adapted from the xJsnark [paper] and its [implementation]. + /// + /// [paper]: https://www.cs.yale.edu/homes/cpap/published/xjsnark.pdf + /// [implementation]: https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872 pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> { let len = max(self.limbs.len(), other.limbs.len()); let zero = FpVar::zero(); @@ -248,20 +330,21 @@ impl IntVarInner { } } -impl From> for IntVarInner { - fn from(v: IntVarInner) -> Self { +impl From> for LimbedVar { + fn from(v: LimbedVar) -> Self { Self::new(v.limbs, v.bounds) } } -impl IntVarInner { - /// Compute `self + other`, without aligning the limbs. +impl LimbedVar { + /// [`LimbedVar::add_unaligned`] computes `self + other`, without aligning + /// the limbs. pub fn add_unaligned( &self, - other: &IntVarInner, - ) -> Result, SynthesisError> { + other: &LimbedVar, + ) -> Result, SynthesisError> { let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; - let mut bounds = vec![Bound::zero(); limbs.len()]; + let mut bounds = vec![Bounds::zero(); limbs.len()]; for (i, v) in self.limbs.iter().enumerate() { bounds[i] = bounds[i] .add(&self.bounds[i]) @@ -276,15 +359,17 @@ impl IntVarInner( &self, - other: &IntVarInner, - ) -> Result, SynthesisError> { + other: &LimbedVar, + ) -> Result, SynthesisError> { let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())]; - let mut bounds = vec![Bound::zero(); limbs.len()]; + let mut bounds = vec![Bounds::zero(); limbs.len()]; for (i, v) in self.limbs.iter().enumerate() { bounds[i] = bounds[i] .add(&self.bounds[i]) @@ -299,15 +384,18 @@ impl IntVarInner( &self, - other: &IntVarInner, - ) -> Result, SynthesisError> { + other: &LimbedVar, + ) -> Result, SynthesisError> { let len = self.limbs.len() + other.limbs.len() - 1; if self.limbs.is_constant() || other.limbs.is_constant() { // Use the naive approach for constant operands, which costs no @@ -316,7 +404,7 @@ impl IntVarInner>(), @@ -335,14 +423,14 @@ impl IntVarInner IntVarInner( &self, - other: &IntVarInner, + other: &LimbedVar, ) -> Result<(), SynthesisError> { let len = min(self.limbs.len(), other.limbs.len()); // Group the limbs of `self` and `other` so that each group nearly // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` - // is the initial number of bits in a limb, just as what we do in grade - // school arithmetic, e.g., + // is `F::BITS_PER_LIMB`, the initial number of bits in a limb. + // This is just as what we do in grade school arithmetic, e.g., // 5 9 // x 7 3 // ------------- @@ -424,9 +512,10 @@ impl IntVarInner 2`. let inv = F::from(BigUint::one() << F::BITS_PER_LIMB) .inverse() .unwrap(); @@ -462,8 +551,8 @@ impl IntVarInner IntVarInner>() .enforce_equal(&FpVar::zero())?; - Bound::add_many(remaining_bounds) + Bounds::add_many(remaining_bounds) .filter_safe::() .ok_or(SynthesisError::Unsatisfiable)?; // For the final carry, we need to ensure that it equals the @@ -508,28 +597,34 @@ impl IntVarInner - IntVarInner + LimbedVar { - /// Convert `Self` to an element in `M`, i.e., compute `Self % M::MODULUS`. - pub fn modulo(&self) -> Result, SynthesisError> { + /// [`LimbedVar::modulo`] computes `self % Target::MODULUS` and returns the + /// result as an aligned [`LimbedVar`]. + /// + /// Note that we allow emulated field elements to be larger than the modulus + /// temporarily during computations, but the final result must be reduced + /// modulo `Target::MODULUS`, and for efficiency, this needs to be done by + /// the caller explicitly. + pub fn modulo(&self) -> Result, SynthesisError> { let cs = self.cs(); let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into()); // Provide the quotient and remainder as hints - let q = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { + let q = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); Ok(( compose(self.limbs.value().unwrap_or_default()).div_floor(&m), - Bound(lb, ub), + Bounds(lb, ub), )) })?; - let r = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { + let r = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { Ok(( compose(self.limbs.value().unwrap_or_default()).abs() % &m, - Bound(Zero::zero(), m.clone()), + Bounds(Zero::zero(), m.clone()), )) })?; - let m = IntVarInner::constant(m); + let m = LimbedVar::constant(m); // Enforce `self = q * m + r` q.mul_unaligned(&m)? @@ -541,23 +636,24 @@ impl Ok(r) } - /// Enforce that `self` is congruent to `other` modulo `M::MODULUS`. + /// [`LimbedVar::enforce_congruent`] enforce that `self` is congruent to + /// `other` modulo `Target::MODULUS`. pub fn enforce_congruent( &self, - other: &IntVarInner, + other: &LimbedVar, ) -> Result<(), SynthesisError> { let cs = self.cs(); let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into()); // Provide the quotient as hint - let q = IntVarInner::new_variable_with_inferred_mode(cs.clone(), || { + let q = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); Ok(( compose(self.limbs.value().unwrap_or_default()).div_floor(&m), - Bound(lb, ub), + Bounds(lb, ub), )) })?; - let m = IntVarInner::constant(m); + let m = LimbedVar::constant(m); // Enforce `self - other = q * m` self.sub_unaligned(other)? @@ -565,77 +661,89 @@ impl } } -impl EquivalenceGadget> - for IntVarInner +// The following lines are quite repetitive, but we have to implement them all +// to make the compiler happy. +impl EquivalenceGadget> + for LimbedVar { fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { self.enforce_equal(other) } } -impl EquivalenceGadget> - for IntVarInner +impl EquivalenceGadget> + for LimbedVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + fn enforce_equivalent( + &self, + other: &LimbedVar, + ) -> Result<(), SynthesisError> { self.enforce_congruent(other) } } -impl EquivalenceGadget> - for IntVarInner +impl EquivalenceGadget> + for LimbedVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + fn enforce_equivalent( + &self, + other: &LimbedVar, + ) -> Result<(), SynthesisError> { self.enforce_congruent(other) } } -impl EquivalenceGadget> - for IntVarInner +impl EquivalenceGadget> + for LimbedVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + fn enforce_equivalent( + &self, + other: &LimbedVar, + ) -> Result<(), SynthesisError> { self.enforce_congruent(other) } } -impl EquivalenceGadget> for IntVarInner { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { +impl EquivalenceGadget> for LimbedVar { + fn enforce_equivalent(&self, other: &LimbedVar) -> Result<(), SynthesisError> { self.enforce_equal(other) } } -impl EquivalenceGadget> for IntVarInner { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { +impl EquivalenceGadget> for LimbedVar { + fn enforce_equivalent(&self, other: &LimbedVar) -> Result<(), SynthesisError> { self.enforce_equal_unaligned(other) } } -impl EquivalenceGadget> for IntVarInner { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { +impl EquivalenceGadget> for LimbedVar { + fn enforce_equivalent(&self, other: &LimbedVar) -> Result<(), SynthesisError> { self.enforce_equal_unaligned(other) } } -impl EquivalenceGadget> for IntVarInner { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { +impl EquivalenceGadget> for LimbedVar { + fn enforce_equivalent(&self, other: &LimbedVar) -> Result<(), SynthesisError> { self.enforce_equal_unaligned(other) } } -impl TryFrom> - for IntVarInner +impl TryFrom> + for LimbedVar { type Error = SynthesisError; - fn try_from(v: IntVarInner) -> Result { + fn try_from(v: LimbedVar) -> Result { v.modulo() } } -impl TwoStageFieldVar for IntVarInner { - type Intermediate = IntVarInner; +impl TwoStageFieldVar for LimbedVar { + type Intermediate = LimbedVar; } -impl EqGadget for IntVarInner { +// Only implement `EqGadget` for aligned variables. +impl EqGadget for LimbedVar { fn is_eq(&self, other: &Self) -> Result, SynthesisError> { let mut result = Boolean::TRUE; if self.limbs.len() != other.limbs.len() { @@ -702,18 +810,28 @@ impl EqGadget for IntVarInner { } } -impl FromBitsGadget for IntVarInner { - fn from_bits_le(bits: &[Boolean], bound: Bound) -> Result { +impl FromBitsGadget for LimbedVar { + fn from_bits_le(bits: &[Boolean]) -> Result { + Self::from_bounded_bits_le( + bits, + Bounds( + BigInt::zero(), + (BigInt::one() << bits.len()) - BigInt::one(), + ), + ) + } + + fn from_bounded_bits_le(bits: &[Boolean], bounds: Bounds) -> Result { Ok(Self::new( bits.chunks(F::BITS_PER_LIMB) .map(Boolean::le_bits_to_fp) .collect::>()?, - compute_bounds(&bound.0, &bound.1, F::BITS_PER_LIMB), + compute_bounds(&bounds.0, &bounds.1, F::BITS_PER_LIMB), )) } } -impl CondSelectGadget for IntVarInner { +impl CondSelectGadget for LimbedVar { fn conditionally_select( cond: &Boolean, true_value: &Self, @@ -742,7 +860,7 @@ impl CondSelectGadget for IntVarInner ToBitsGadget for IntVarInner { +impl ToBitsGadget for LimbedVar { fn to_bits_le(&self) -> Result>, SynthesisError> { for bound in &self.bounds { if bound.0 < BigInt::zero() { @@ -759,7 +877,7 @@ impl ToBitsGadget for IntVarInner { } } -impl AbsorbableGadget for IntVarInner { +impl AbsorbableVar for LimbedVar { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1; @@ -769,38 +887,13 @@ impl AbsorbableGadget for IntVarInner { } } -impl VectorGadget> - for [IntVarInner] -{ - fn add(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.add_unaligned(y)) - .collect() - } - - fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { - self.iter() - .zip(other.iter()) - .map(|(x, y)| x.mul_unaligned(y)) - .collect() - } - - fn scale( - &self, - other: &IntVarInner, - ) -> Result>, SynthesisError> { - self.iter().map(|x| x.mul_unaligned(other)).collect() - } -} - -impl MatrixGadget> - for SparseMatrixVar> +impl MatrixGadget> + for SparseMatrixVar> { fn mul_vector( &self, - v: &impl Index>, - ) -> Result>, SynthesisError> { + v: &impl Index>, + ) -> Result>, SynthesisError> { self.0 .iter() .map(|row| { @@ -809,13 +902,13 @@ impl MatrixGadget> .map(|(value, col_i)| value.limbs.len() + v[*col_i].limbs.len() - 1) .max() .unwrap_or(0); - // This is a combination of `mul_no_align` and `add_no_align` + // This is a combination of `mul_unaligned` and `add_unaligned` // that results in more flattened `LinearCombination`s. // Consequently, `ConstraintSystem::inline_all_lcs` costs less // time, thus making trusted setup and proof generation faster. let bounds = (0..len) .map(|i| { - Bound::add_many( + Bounds::add_many( &row.iter() .flat_map(|(value, col_i)| { let start = @@ -842,18 +935,18 @@ impl MatrixGadget> .sum() }) .collect(); - Ok(IntVarInner::new(limbs, bounds)) + Ok(LimbedVar::new(limbs, bounds)) }) .collect() } } -pub fn compute_bounds(lb: &BigInt, ub: &BigInt, bits_per_limb: usize) -> Vec { +fn compute_bounds(lb: &BigInt, ub: &BigInt, bits_per_limb: usize) -> Vec { let len = max(lb.bits(), ub.bits()) as usize; let (n_full_limbs, n_remaining_bits) = len.div_rem(&bits_per_limb); let mut bounds = vec![ - Bound( + Bounds( if lb.is_negative() { BigInt::one() - (BigInt::one() << bits_per_limb) } else { @@ -870,21 +963,21 @@ pub fn compute_bounds(lb: &BigInt, ub: &BigInt, bits_per_limb: usize) -> Vec AllocVar<(BigInt, Bound), F> for IntVarInner { - fn new_variable>( +impl AllocVar<(BigInt, Bounds), F> for LimbedVar { + fn new_variable>( cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { let cs = cs.into().cs(); let v = f()?; - let (x, Bound(lb, ub)) = v.borrow(); + let (x, Bounds(lb, ub)) = v.borrow(); if x < lb || x > ub { return Err(SynthesisError::Unsatisfiable); @@ -952,9 +1045,9 @@ impl AllocVar<(BigInt, Bound), F> for IntVarInner>, - t: impl Borrow<(BigInt, Bound)>, + t: impl Borrow<(BigInt, Bounds)>, ) -> Result { - let (x, Bound(lb, ub)) = t.borrow(); + let (x, Bounds(lb, ub)) = t.borrow(); if x < lb || x > ub { return Err(SynthesisError::Unsatisfiable); @@ -979,7 +1072,7 @@ impl AllocVar<(BigInt, Bound), F> for IntVarInner, Vec<_>>(); @@ -987,7 +1080,7 @@ impl AllocVar<(BigInt, Bound), F> for IntVarInner AllocVar for IntVarInner { +impl AllocVar for LimbedVar { fn new_variable>( cs: impl Into>, f: impl FnOnce() -> Result, @@ -999,7 +1092,7 @@ impl AllocVar for IntVarInner AllocVar for IntVarInner IntVarInner { +impl LimbedVar { + /// [`LimbedVar::constant`] allocates a constant [`LimbedVar`] with value + /// `x`. pub fn constant(x: BigInt) -> Self { // `unwrap` below is safe because we are allocating a constant value, // which is guaranteed to succeed. - Self::new_constant(ConstraintSystemRef::None, (x.clone(), Bound(x.clone(), x))).unwrap() + Self::new_constant(ConstraintSystemRef::None, (x.clone(), Bounds(x.clone(), x))).unwrap() } } @@ -1091,7 +1186,7 @@ macro_rules! impl_assignment_op { impl_binary_op!( Add, add, - |a: &IntVarInner, b: &IntVarInner| -> IntVarInner { + |a: &LimbedVar, b: &LimbedVar| -> LimbedVar { a.add_unaligned(b).unwrap() }, (F: SonobeField, Cfg, const LHS_ALIGNED: bool, const RHS_ALIGNED: bool), @@ -1100,7 +1195,7 @@ impl_binary_op!( impl_assignment_op!( AddAssign, add_assign, - |a: &mut IntVarInner, b: &IntVarInner| { + |a: &mut LimbedVar, b: &LimbedVar| { *a = a.add_unaligned(b).unwrap() }, (F: SonobeField, Cfg, const ALIGNED: bool), @@ -1109,7 +1204,7 @@ impl_assignment_op!( impl_binary_op!( Sub, sub, - |a: &IntVarInner, b: &IntVarInner| -> IntVarInner { + |a: &LimbedVar, b: &LimbedVar| -> LimbedVar { a.sub_unaligned(b).unwrap() }, (F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool), @@ -1118,7 +1213,7 @@ impl_binary_op!( impl_assignment_op!( SubAssign, sub_assign, - |a: &mut IntVarInner, b: &IntVarInner| { + |a: &mut LimbedVar, b: &LimbedVar| { *a = a.sub_unaligned(b).unwrap() }, (F: SonobeField, Cfg, const OTHER_ALIGNED: bool), @@ -1127,7 +1222,7 @@ impl_assignment_op!( impl_binary_op!( Mul, mul, - |a: &IntVarInner, b: &IntVarInner| -> IntVarInner { + |a: &LimbedVar, b: &LimbedVar| -> LimbedVar { a.mul_unaligned(b).unwrap() }, (F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool), @@ -1136,7 +1231,7 @@ impl_binary_op!( impl_assignment_op!( MulAssign, mul_assign, - |a: &mut IntVarInner, b: &IntVarInner| { + |a: &mut LimbedVar, b: &LimbedVar| { *a = a.mul_unaligned(b).unwrap() }, (F: SonobeField, Cfg, const OTHER_ALIGNED: bool), @@ -1192,11 +1287,11 @@ mod tests { for a in v { let cs = ConstraintSystem::::new_ref(); - let a_var = BigIntVar::new_witness(cs.clone(), || { - Ok((a.clone(), Bound(lb.clone(), ub.clone()))) + let a_var = EmulatedIntVar::new_witness(cs.clone(), || { + Ok((a.clone(), Bounds(lb.clone(), ub.clone()))) })?; - let a_const = BigIntVar::::constant(a.clone()); + let a_const = EmulatedIntVar::::constant(a.clone()); assert_eq!(a, a_var.value()?); assert_eq!(a, a_const.value()?); @@ -1220,46 +1315,46 @@ mod tests { let aab = &a * &ab; let abb = &ab * &b; - let a_var = BigIntVar::new_witness(cs.clone(), || { + let a_var = EmulatedIntVar::new_witness(cs.clone(), || { Ok(( a, - Bound( + Bounds( BigInt::one() - (BigInt::one() << size), (BigInt::one() << size) - BigInt::one(), ), )) })?; - let b_var = BigIntVar::new_witness(cs.clone(), || { + let b_var = EmulatedIntVar::new_witness(cs.clone(), || { Ok(( b, - Bound( + Bounds( BigInt::one() - (BigInt::one() << size), (BigInt::one() << size) - BigInt::one(), ), )) })?; - let ab_var = BigIntVar::new_witness(cs.clone(), || { + let ab_var = EmulatedIntVar::new_witness(cs.clone(), || { Ok(( ab, - Bound( + Bounds( BigInt::one() - (BigInt::one() << (size * 2)), (BigInt::one() << (size * 2)) - BigInt::one(), ), )) })?; - let aab_var = BigIntVar::new_witness(cs.clone(), || { + let aab_var = EmulatedIntVar::new_witness(cs.clone(), || { Ok(( aab, - Bound( + Bounds( BigInt::one() - (BigInt::one() << (size * 3)), (BigInt::one() << (size * 3)) - BigInt::one(), ), )) })?; - let abb_var = BigIntVar::new_witness(cs.clone(), || { + let abb_var = EmulatedIntVar::new_witness(cs.clone(), || { Ok(( abb, - Bound( + Bounds( BigInt::one() - (BigInt::one() << (size * 3)), (BigInt::one() << (size * 3)) - BigInt::one(), ), @@ -1340,7 +1435,7 @@ mod tests { let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?; - let mut r_var: IntVarInner = + let mut r_var: LimbedVar = EmulatedFieldVar::constant(BigUint::zero().into()).into(); for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { r_var = r_var.add_unaligned(&a.mul_unaligned(&b)?)?; diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 64b1316b9..8538cae77 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -1,3 +1,6 @@ +//! This module defines extension traits for field elements and their in-circuit +//! counterparts, along with some common implementations. + use ark_ff::{BigInteger, Fp, FpConfig, PrimeField}; use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_relations::gr1cs::SynthesisError; @@ -10,12 +13,12 @@ use ark_std::{ use crate::{ algebra::{Val, field::emulated::EmulatedFieldVar}, traits::{Inputize, InputizeEmulated}, - transcripts::{Absorbable, AbsorbableGadget}, + transcripts::{Absorbable, AbsorbableVar}, }; pub mod emulated; -/// `SonobeField` trait is a wrapper around `PrimeField` that also includes the +/// [`SonobeField`] trait is a wrapper around [`PrimeField`] that also includes /// necessary bounds for the field to be used conveniently in folding schemes. pub trait SonobeField: PrimeField @@ -23,33 +26,36 @@ pub trait SonobeField: + Inputize + Val, EmulatedVar = EmulatedFieldVar> { + /// [`SonobeField::BITS_PER_LIMB`] defines the bit length of each limb when + /// representing field elements as limbs in an emulated field variable. const BITS_PER_LIMB: usize; } impl, const N: usize> SonobeField for Fp { // For a `F` with order > 250 bits, 55 is chosen for optimizing the most // expensive part `Az∘Bz` when checking the R1CS relation for CycleFold. - // Consider using `NonNativeUintVar` to represent the base field `Fq`. - // Since 250 / 55 = 4.46, the `NonNativeUintVar` has 5 limbs. - // Now, the multiplication of two `NonNativeUintVar`s has 9 limbs, and + // Consider using `EmulatedFieldVar` to represent the base field `Fq`. + // Since 250 / 55 = 4.46, the `EmulatedFieldVar` has 5 limbs. + // Now, the multiplication of two `EmulatedFieldVar`s has 9 limbs, and // each limb has at most 2^{55 * 2} * 5 = 112.3 bits. // For a 1400x1400 matrix `A`, the multiplication of `A`'s row and `z` - // is the sum of 1400 `NonNativeUintVar`s, each with 9 limbs. + // is the sum of 1400 `EmulatedFieldVar`s, each with 9 limbs. // Thus, the maximum bit length of limbs of each element in `Az` is // 2^{55 * 2} * 5 * 1400 = 122.7 bits. // Finally, in the hadamard product of `Az` and `Bz`, every element has // 17 limbs, whose maximum bit length is (2^{55 * 2} * 5 * 1400)^2 * 9 - // = 248.7 bits and is less than the native field `Fr`. + // = 248.7 bits and is less than the constraint field `Fr`. // Thus, 55 allows us to compute `Az∘Bz` without the expensive alignment // operation. // // TODO: either make it a global const, or compute an optimal value // based on the modulus size. - const BITS_PER_LIMB: usize = 55; // TODO: make this configurable + // TODO: make this configurable + const BITS_PER_LIMB: usize = 55; } impl, const N: usize> Val for Fp { - type ConstraintField = Self; + type PreferredConstraintField = Self; type Var = FpVar; type EmulatedVar = EmulatedFieldVar; @@ -78,7 +84,7 @@ impl, const N: usize> Absorbable for Fp { } } -impl AbsorbableGadget for FpVar { +impl AbsorbableVar for FpVar { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { dest.push(self.clone()); Ok(()) @@ -86,16 +92,12 @@ impl AbsorbableGadget for FpVar { } impl, const N: usize> Inputize for Fp { - /// Returns the internal representation in the same order as how the value - /// is allocated in `FpVar::new_input`. fn inputize(&self) -> Vec { vec![*self] } } impl InputizeEmulated for P { - /// Returns the internal representation in the same order as how the value - /// is allocated in `NonNativeUintVar::new_input`. fn inputize_emulated(&self) -> Vec { self.into_bigint() .to_bits_le() @@ -105,6 +107,17 @@ impl InputizeEmulated for P { } } +/// [`TwoStageFieldVar`] abstracts over field variables that support a +/// two-stage arithmetic model. +/// +/// In this model, we consider two stages of in-circuit variables for field +/// elements when performing arithmetic operations: +/// 1. Before the operations, we have the standard field variable type, i.e., +/// the implementor of this trait. +/// 2. During the operations, we use [`TwoStageFieldVar::Intermediate`] to hold +/// the intermediate results. +/// Therefore, the [`Add`] and [`Mul`] operations between two field variables +/// yield an intermediate variable. pub trait TwoStageFieldVar: Clone + Add @@ -112,6 +125,14 @@ pub trait TwoStageFieldVar: + Mul + for<'a> Mul<&'a Self, Output = Self::Intermediate> { + /// The intermediate variable type used during arithmetic operations. + /// + /// We require this type to support conversions from and to the original + /// field variable type. + /// + /// In addition, to allow chaining operations without excessive conversions, + /// we require this type to support [`Add`] and [`Mul`] operations with both + /// itself and the original field variable type. type Intermediate: Clone + From + TryInto @@ -125,6 +146,7 @@ pub trait TwoStageFieldVar: + for<'a> Mul<&'a Self, Output = Self::Intermediate>; } +// Operations over the canonical variable `FpVar` always yield another `FpVar`. impl TwoStageFieldVar for FpVar { type Intermediate = Self; } diff --git a/crates/primitives/src/algebra/group/emulated.rs b/crates/primitives/src/algebra/group/emulated.rs index 477de2b0e..6e6b6f3bb 100644 --- a/crates/primitives/src/algebra/group/emulated.rs +++ b/crates/primitives/src/algebra/group/emulated.rs @@ -1,3 +1,12 @@ +//! This module provides implementation of in-circuit variables for emulated +//! elliptic curve points. +//! +//! This is useful when we want to express points whose coordinates lie in a +//! different field than the circuit's constraint field. +//! +//! Note that currently this module only provides the representation of such +//! points, without any arithmetic operations. + use ark_ec::{AffineRepr, short_weierstrass::SWFlags}; use ark_ff::Zero; use ark_r1cs_std::{ @@ -15,19 +24,20 @@ use ark_std::borrow::Borrow; use crate::{ algebra::{field::emulated::EmulatedFieldVar, group::SonobeCurve}, traits::SonobeField, - transcripts::AbsorbableGadget, + transcripts::AbsorbableVar, }; -/// `EmulatedAffineVar` defines an in-circuit elliptic curve point in its affine -/// representation, where the coordinates are non-native field variables in the -/// curve's base field `Target::BaseField`, emulated over the constraint field -/// `Base`. -/// -/// It is not intended to perform operations, but just to record the coordinates -/// in order to perform hash operations of the point. +/// [`EmulatedAffineVar`] defines an in-circuit elliptic curve point with its +/// affine representation, where the coordinates are in the curve's base field +/// `Target::BaseField` and are emulated over the constraint field `Base` in the +/// circuit. #[derive(Debug, Clone)] pub struct EmulatedAffineVar { + /// [`EmulatedAffineVar::x`] is the x-coordinate of the point's affine + /// representation. pub x: EmulatedFieldVar, + /// [`EmulatedAffineVar::y`] is the y-coordinate of the point's affine + /// representation. pub y: EmulatedFieldVar, } @@ -103,6 +113,8 @@ impl EqGadget for EmulatedAffineVa } impl EmulatedAffineVar { + /// [`EmulatedAffineVar::zero`] allocates the zero point (point at infinity) + /// of the curve as a constant. pub fn zero() -> Self { // `unwrap` below is safe because we are allocating a constant value, // which is guaranteed to succeed. @@ -110,7 +122,7 @@ impl EmulatedAffineVar { } } -impl AbsorbableGadget +impl AbsorbableVar for EmulatedAffineVar { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { @@ -158,20 +170,24 @@ mod tests { } #[test] - fn test_improved_to_hash_preimage() -> Result<(), Box> { + fn test_to_hash_preimage() -> Result<(), Box> { let cs = ConstraintSystem::::new_ref(); - // check that point_to_nonnative_limbs returns the expected values let mut rng = thread_rng(); let p = Projective::rand(&mut rng); let p_var = EmulatedAffineVar::::new_witness(cs.clone(), || Ok(p))?; - assert_eq!(p_var.to_absorbable()?.value()?, p.to_absorbable()); + + let mut v = vec![]; + let mut v_var = vec![]; + p.absorb_into(&mut v); + p_var.absorb_into(&mut v_var)?; + + assert_eq!(v_var.value()?, v); Ok(()) } #[test] fn test_inputize() -> Result<(), Box> { - // check that point_to_nonnative_limbs returns the expected values let mut rng = thread_rng(); let p = Projective::rand(&mut rng); diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 3abf989aa..da37399c5 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -1,3 +1,6 @@ +//! This module defines extension traits for elliptic curve points and their +//! in-circuit counterparts, along with some common implementations. + use ark_ec::{ AffineRepr, CurveGroup, PrimeGroup, short_weierstrass::{Projective, SWCurveConfig}, @@ -13,15 +16,17 @@ use ark_relations::gr1cs::SynthesisError; use crate::{ algebra::{Val, field::SonobeField, group::emulated::EmulatedAffineVar}, traits::{Dummy, Inputize, InputizeEmulated}, - transcripts::{Absorbable, AbsorbableGadget}, + transcripts::{Absorbable, AbsorbableVar}, }; pub mod emulated; +/// [`CF1`] is a type alias for the scalar field of a curve `C`. pub type CF1 = ::ScalarField; +/// [`CF2`] is a type alias for the base field of a curve `C`. pub type CF2 = <::BaseField as Field>::BasePrimeField; -/// `SonobeCurve` trait is a wrapper around `CurveGroup` that also includes the +/// [`SonobeCurve`] trait is a wrapper around [`CurveGroup`] that also includes /// necessary bounds for the curve to be used conveniently in folding schemes. pub trait SonobeCurve: CurveGroup @@ -29,7 +34,7 @@ pub trait SonobeCurve: + Inputize + InputizeEmulated + Val< - Var: CurveVar + AbsorbableGadget, + Var: CurveVar + AbsorbableVar, EmulatedVar = EmulatedAffineVar, > { @@ -41,7 +46,7 @@ impl> SonobeC } impl> Val for Projective

{ - type ConstraintField = P::BaseField; + type PreferredConstraintField = P::BaseField; type Var = ProjectiveVar>; type EmulatedVar = EmulatedAffineVar; @@ -61,7 +66,7 @@ impl> Absorbable for Projective

{ } } -impl> AbsorbableGadget +impl> AbsorbableVar for ProjectiveVar> { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { @@ -80,8 +85,6 @@ impl> AbsorbableGadget } impl> Inputize for Projective

{ - /// Returns the internal representation in the same order as how the value - /// is allocated in `ProjectiveVar::new_input`. fn inputize(&self) -> Vec { let affine = self.into_affine(); match affine.xy() { @@ -94,8 +97,6 @@ impl> Inputize for Projec impl> InputizeEmulated for Projective

{ - /// Returns the internal representation in the same order as how the value - /// is allocated in `NonNativeAffineVar::new_input`. fn inputize_emulated(&self) -> Vec { let affine = self.into_affine(); let (x, y) = affine.xy().unwrap_or_default(); @@ -103,69 +104,3 @@ impl> [x, y].inputize_emulated() } } - -// fn lattice_reduction_2x2( -// mut b1: (BigInt, BigInt), -// mut b2: (BigInt, BigInt), -// ) -> ((BigInt, BigInt), (BigInt, BigInt)) { -// loop { -// let mut b1_norm_sq = &b1.0 * &b1.0 + &b1.1 * &b1.1; -// let mut b2_norm_sq = &b2.0 * &b2.0 + &b2.1 * &b2.1; - -// if b1_norm_sq > b2_norm_sq { -// swap(&mut b1, &mut b2); -// swap(&mut b1_norm_sq, &mut b2_norm_sq); -// } - -// let (mut m, r) = (&b1.0 * &b2.0 + &b1.1 * &b2.1).div_rem(&b1_norm_sq); -// if &r + &r >= b1_norm_sq { -// m += BigInt::one(); -// } - -// if m.is_zero() { -// break; -// } - -// b2.0 -= &m * &b1.0; -// b2.1 -= &m * &b1.1; -// } - -// (b1, b2) -// } - -// impl PointScalarMulGadget> for C { -// fn mul_scalar(&self, scalar: &impl ToBitsGadget>) -> Result { -// let scalar = scalar.to_bits_le()?; - -// let cs = scalar.cs(); - -// let m = BigInt::from_biguint(Sign::Plus, CF1::::MODULUS.into()); -// let m_sqrt = m.sqrt(); - -// let (a, b) = lattice_reduction_2x2( -// (m, Zero::zero()), -// ( -// CI2::::from_bits_le(&scalar.value().unwrap_or_default()) -// .into() -// .into(), -// One::one(), -// ), -// ) -// .0; -// let (a_sign, a_abs) = a.into_parts(); -// let (b_sign, b_abs) = b.into_parts(); -// let a_is_negative = -// Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(a_sign == Sign::Minus))?; -// let b_is_negative = -// Boolean::new_variable_with_inferred_mode(cs.clone(), || Ok(b_sign == Sign::Minus))?; - -// // let a = NonNativeUintVar::new_variable_with_inferred_mode(cs.clone(), || { -// // Ok((a_abs.into(), Bound::new_ub(m_sqrt.clone()))) -// // })?; -// // let b = NonNativeUintVar::new_variable_with_inferred_mode(cs, || { -// // Ok((b_abs.into(), Bound::new_ub(m_sqrt))) -// // })?; - -// todo!() -// } -// } diff --git a/crates/primitives/src/algebra/mod.rs b/crates/primitives/src/algebra/mod.rs index 438d26fca..f84a20e46 100644 --- a/crates/primitives/src/algebra/mod.rs +++ b/crates/primitives/src/algebra/mod.rs @@ -1,3 +1,7 @@ +//! This module provides algebraic abstractions used across Sonobe, including +//! field and group type enhancements, in-circuit (both canonical and emulated) +//! variables, and common algebraic operations. + use ark_ff::PrimeField; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; @@ -7,9 +11,23 @@ pub mod field; pub mod group; pub mod ops; +/// [`Val`] associates a type with its in-circuit variables. pub trait Val { - type ConstraintField: PrimeField; - type Var: AllocVar + GR1CSVar; + /// [`Val::PreferredConstraintField`] is the preferred constraint field for + /// expressing `Self` in-circuit. + type PreferredConstraintField: PrimeField; + + /// [`Val::Var`] is the *canonical* in-circuit variable. + /// + /// In this case, the circuit is defined over the preferred constraint field + /// and can represent `Self` directly (i.e., without emulation). + type Var: AllocVar + + GR1CSVar; + /// [`Val::EmulatedVar`] is the *emulated* in-circuit variable. + /// + /// In this case, the circuit is defined over an arbitrary field `F` which + /// may differ from the preferred constraint field, and `Self` is + /// represented in-circuit via emulation. type EmulatedVar: AllocVar + GR1CSVar; } diff --git a/crates/primitives/src/algebra/ops/bits.rs b/crates/primitives/src/algebra/ops/bits.rs index e438db2c0..eb997e259 100644 --- a/crates/primitives/src/algebra/ops/bits.rs +++ b/crates/primitives/src/algebra/ops/bits.rs @@ -1,10 +1,15 @@ +//! This module defines traits for conversion between bit representations and +//! algebraic types inside and outside circuits. + use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, boolean::Boolean, eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; -use crate::algebra::field::emulated::Bound; +use crate::algebra::field::emulated::Bounds; +/// [`FromBits`] reconstructs a value from bits. pub trait FromBits { + /// [`FromBits::from_bits_le`] computes a value from its little-endian bits. fn from_bits_le(bits: &[bool]) -> Self; } @@ -14,22 +19,45 @@ impl FromBits for F { } } +/// [`FromBitsGadget`] is the in-circuit counterpart of [`FromBits`], which +/// reconstructs an in-circuit variable from boolean variables. pub trait FromBitsGadget: Sized { - fn from_bits_le(bits: &[Boolean], bound: Bound) -> Result; + /// [`FromBitsGadget::from_bits_le`] computes a variable from its + /// little-endian bits, inferring bounds from the length of `bits`. + fn from_bits_le(bits: &[Boolean]) -> Result; + + /// [`FromBitsGadget::from_bounded_bits_le`] computes a variable from its + /// little-endian bits with explicitly supplied [`Bounds`]. + fn from_bounded_bits_le(bits: &[Boolean], bounds: Bounds) -> Result; } +/// [`ToBitsGadgetExt`] extends the standard [`ark_r1cs_std::convert::ToBitsGadget`] +/// with more functionality. pub trait ToBitsGadgetExt: Sized { + /// [`ToBitsGadgetExt::to_n_bits_le`] decomposes `self` into `n` + /// little-endian bits. + /// + /// An error is returned if `self` cannot be represented in `n` bits. fn to_n_bits_le(&self, n: usize) -> Result>, SynthesisError>; + /// [`ToBitsGadgetExt::enforce_bit_length`] enforces that `self` can be + /// represented in at most `n` bits. + /// + /// This is useful for checking that a field element is within the range of + /// `[0, 2^n - 1]` fn enforce_bit_length(&self, n: usize) -> Result<(), SynthesisError> { self.to_n_bits_le(n)?; Ok(()) } } impl FromBitsGadget for FpVar { - fn from_bits_le(bits: &[Boolean], _bound: Bound) -> Result { + fn from_bits_le(bits: &[Boolean]) -> Result { Boolean::le_bits_to_fp(bits) } + + fn from_bounded_bits_le(bits: &[Boolean], _bounds: Bounds) -> Result { + Self::from_bits_le(bits) + } } impl ToBitsGadgetExt for FpVar { diff --git a/crates/primitives/src/algebra/ops/eq.rs b/crates/primitives/src/algebra/ops/eq.rs index 67be1e997..294ba63aa 100644 --- a/crates/primitives/src/algebra/ops/eq.rs +++ b/crates/primitives/src/algebra/ops/eq.rs @@ -1,24 +1,30 @@ +//! This module defines traits for enforcing custom, user-defined equivalence +//! relation between in-circuit variables, enabling flexible checks for equality +//! and congruence. + use ark_ff::PrimeField; use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; -/// `EquivalenceGadget` enforces that two in-circuit variables are "equivalent". +/// [`EquivalenceGadget`] enforces two in-circuit variables are "equivalent". /// /// This does not only allow us to ensure the equality of two variables of the /// same type, but can also be used for guaranteeing variables of different /// types represent the "same" (depending on the context) value. pub trait EquivalenceGadget { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError>; + /// [`EquivalenceGadget::enforce_equivalent`] enforces that `self` and + /// `other` are equivalent. + fn enforce_equivalent(&self, other: &Other) -> Result<(), SynthesisError>; } impl EquivalenceGadget> for FpVar { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + fn enforce_equivalent(&self, other: &FpVar) -> Result<(), SynthesisError> { self.enforce_equal(other) } } impl> EquivalenceGadget<[T]> for [T] { - fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> { + fn enforce_equivalent(&self, other: &[T]) -> Result<(), SynthesisError> { self.iter() .zip(other) .try_for_each(|(a, b)| a.enforce_equivalent(b)) diff --git a/crates/primitives/src/algebra/ops/matrix.rs b/crates/primitives/src/algebra/ops/matrix.rs index e1347f30e..fef5a7d04 100644 --- a/crates/primitives/src/algebra/ops/matrix.rs +++ b/crates/primitives/src/algebra/ops/matrix.rs @@ -1,3 +1,6 @@ +//! This module defines in-circuit sparse matrix types and implements operations +//! over them. + use ark_ff::PrimeField; use ark_r1cs_std::{ GR1CSVar, @@ -7,11 +10,17 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{Matrix, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, ops::Index}; +/// [`MatrixGadget`] defines operations on in-circuit matrix variables. pub trait MatrixGadget { + /// [`MatrixGadget::mul_vector`] computes the product of `self` and a column + /// vector `v`. fn mul_vector(&self, v: &impl Index) -> Result, SynthesisError>; } -// same format as the native SparseMatrix (which follows ark_relations::gr1cs::Matrix format) +/// [`SparseMatrixVar`] is a sparse matrix represented as a vector of rows, +/// where each row is a vector of `(value, column_index)` pairs. +/// +/// This follows the same format as [`ark_relations::gr1cs::Matrix`]. #[derive(Debug, Clone)] pub struct SparseMatrixVar(pub Vec>); @@ -51,6 +60,15 @@ impl MatrixGadget> for SparseMatrixVar> { .0 .iter() .map(|row| { + // Theoretically we can use `Iterator::sum` directly: + // ```rs + // row + // .iter() + // .map(|(value, col_i)| value * &v[*col_i]) + // .sum() + // ``` + // But it seems that arkworks will throw an error if we do so + // when the products are all constant values... let products = row .iter() .map(|(value, col_i)| value * &v[*col_i]) diff --git a/crates/primitives/src/algebra/ops/mod.rs b/crates/primitives/src/algebra/ops/mod.rs index bbd850498..50c0595c8 100644 --- a/crates/primitives/src/algebra/ops/mod.rs +++ b/crates/primitives/src/algebra/ops/mod.rs @@ -1,3 +1,14 @@ +//! This module collects common algebraic operation traits and their in-circuit +//! gadgets, including: +//! +//! * [`bits`]: conversions between bit representations and algebraic variables. +//! * [`eq`]: generalization of equality checks. +//! * [`matrix`]: sparse matrix representation and operations. +//! * [`poly`]: helpers for polynomial operations. +//! * [`pow`]: computation of powers. +//! * [`rlc`]: random linear combinations. +//! * [`vector`]: vector operations. + pub mod bits; pub mod eq; pub mod matrix; diff --git a/crates/primitives/src/algebra/ops/poly.rs b/crates/primitives/src/algebra/ops/poly.rs index 91696240a..fed1992fd 100644 --- a/crates/primitives/src/algebra/ops/poly.rs +++ b/crates/primitives/src/algebra/ops/poly.rs @@ -1,3 +1,5 @@ +//! This module provides helpers for working with polynomials inside circuits. + use ark_ff::{Field, PrimeField, Zero}; use ark_poly::{DenseMultilinearExtension, EvaluationDomain, GeneralEvaluationDomain}; use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; @@ -6,7 +8,11 @@ use ark_std::log2; use super::pow::Pow; +/// [`MLEHelper`] provides functionality for multilinear extensions. pub trait MLEHelper { + /// [`MLEHelper::from_evaluations`] builds a multilinear extension from a + /// (possibly non-power-of-two) vector of evaluations, padding with zeros + /// up to the next power of two. fn from_evaluations(evaluations: &[F]) -> Self; } @@ -18,14 +24,23 @@ impl MLEHelper for DenseMultilinearExtension { } } +/// [`EvaluationDomainGadget`] provides a subset of evaluation domain operations +/// in [`EvaluationDomain`] for in-circuit field variables. pub trait EvaluationDomainGadget { + /// [`EvaluationDomainGadget::evaluate_all_lagrange_coefficients_var`] + /// computes all Lagrange basis polynomials evaluated at `tau`. + /// + /// It is the in-circuit counterpart of [`EvaluationDomain::evaluate_all_lagrange_coefficients`]. fn evaluate_all_lagrange_coefficients_var( &self, tau: &FpVar, ) -> Result>, SynthesisError>; - fn evaluate_vanishing_polynomial_var(&self, tau: &FpVar) - -> Result, SynthesisError>; + /// [`EvaluationDomainGadget::evaluate_vanishing_polynomial_var`] evaluates + /// the vanishing polynomial of the domain at `tau`. + /// + /// It is the in-circuit counterpart of [`EvaluationDomain::evaluate_vanishing_polynomial`]. + fn evaluate_vanishing_polynomial_var(&self, tau: &FpVar) -> Result, SynthesisError>; } impl EvaluationDomainGadget for GeneralEvaluationDomain { @@ -39,6 +54,8 @@ impl EvaluationDomainGadget for GeneralEvaluationDomain { let offset_inv = self.coset_offset_inv(); let group_gen = self.group_gen(); + // We assume that the evaluation of vanishing polynomial at tau is non-0 + let l_i = (tau.pow_by_constant([size])? * offset_inv.pow([size - 1]) - offset) * size_inv; group_gen diff --git a/crates/primitives/src/algebra/ops/pow.rs b/crates/primitives/src/algebra/ops/pow.rs index ac78ba6a4..3d1808522 100644 --- a/crates/primitives/src/algebra/ops/pow.rs +++ b/crates/primitives/src/algebra/ops/pow.rs @@ -1,14 +1,21 @@ +//! This module defines and implements powering utilities in and out of circuit. + use ark_ff::{Field, PrimeField}; use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; +/// [`Pow`] provides powering operations for field elements. pub trait Pow: Sized { - /// Compute `self^0, self^1, ..., self^{n-1}` + /// [`Pow::powers`] computes: + /// $self^0, self^1, ..., self^{n-1}$. fn powers(&self, n: usize) -> Vec; - /// Compute `self^{2^0}, self^{2^1}, ..., self^{2^{n-1}}` + /// [`Pow::repeated_squares`] computes: + /// $self^{2^0}, self^{2^1}, ..., self^{2^{n-1}}$. fn repeated_squares(&self, n: usize) -> Vec; - /// Compute `self^0, self^1, ..., self^{2^n - 1}` from repeated squares + /// [`Pow::powers_from_repeated_squares`] expands a vector of repeated + /// squares $x^{2^0}, x^{2^1}, ..., x^{2^{n-1}}$ into all powers: + /// $x^0, x^1, ..., x^{2^n - 1}$. fn powers_from_repeated_squares(squares: &[Self]) -> Vec; } @@ -42,11 +49,20 @@ impl Pow for F { } } +/// [`PowGadget`] is the in-circuit counterpart of [`Pow`], providing powering +/// operations for field element variables. pub trait PowGadget: Sized { + /// [`PowGadget::powers`] computes: + /// $self^0, self^1, ..., self^{n-1}$. fn powers(&self, n: usize) -> Vec; + /// [`PowGadget::repeated_squares`] computes: + /// $self^{2^0}, self^{2^1}, ..., self^{2^{n-1}}$. fn repeated_squares(&self, n: usize) -> Vec; + /// [`PowGadget::powers_from_repeated_squares`] expands a vector of repeated + /// squares $x^{2^0}, x^{2^1}, ..., x^{2^{n-1}}$ into all powers: + /// $x^0, x^1, ..., x^{2^n - 1}$. fn powers_from_repeated_squares(squares: &[Self]) -> Vec; } diff --git a/crates/primitives/src/algebra/ops/rlc.rs b/crates/primitives/src/algebra/ops/rlc.rs index 3e43ade12..b25a23d70 100644 --- a/crates/primitives/src/algebra/ops/rlc.rs +++ b/crates/primitives/src/algebra/ops/rlc.rs @@ -1,11 +1,23 @@ +//! This module defines and implements the computation of random linear +//! combination (RLC). +//! +//! An RLC computes $\sum v_i \cdot c_i$ where $v_i$ are values (scalars or +//! vectors) and $c_i$ are the randomness (challenge coefficients), which is +//! used extensively in folding schemes. + use ark_std::{ iter::Sum, ops::{Add, Mul}, }; +/// [`ScalarRLC`] computes the random linear combination for a sequence of +/// scalars (i.e., each $v_i$ is a scalar). pub trait ScalarRLC { + /// [`ScalarRLC::Value`] is the result type of the RLC computation. type Value; + /// [`ScalarRLC::scalar_rlc`] evaluates the RLC with the given coefficients + /// `coeffs`. fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value; } @@ -20,9 +32,15 @@ where } } +/// [`SliceRLC`] computes the random linear combination for a sequence of +/// vectors (i.e., each $v_i$ is a vector), by computing the RLC element-wise. +// TODO (@winderica): can we unify `ScalarRLC` and `SliceRLC` into one trait? pub trait SliceRLC { + /// [`SliceRLC::Value`] is the result type of the RLC computation. type Value; + /// [`SliceRLC::slice_rlc`] evaluates the RLC with the given coefficients + /// `coeffs`. fn slice_rlc(self, coeffs: &[Coeff]) -> Vec; } diff --git a/crates/primitives/src/algebra/ops/vector.rs b/crates/primitives/src/algebra/ops/vector.rs index 8a7019a6d..cfcdc6fb5 100644 --- a/crates/primitives/src/algebra/ops/vector.rs +++ b/crates/primitives/src/algebra/ops/vector.rs @@ -1,28 +1,54 @@ -use ark_ff::PrimeField; -use ark_r1cs_std::fields::fp::FpVar; +//! This module provides definitions and implementations of in-circuit vector +//! operations. + use ark_relations::gr1cs::SynthesisError; +use ark_std::ops::{Add, Mul, Sub}; +/// [`VectorGadget`] defines operations on in-circuit vector variables. pub trait VectorGadget { + /// [`VectorGadget::add`] computes the element-wise sum of two vectors. fn add(&self, other: &Self) -> Result, SynthesisError>; - fn scale(&self, scalar: &FV) -> Result, SynthesisError>; + /// [`VectorGadget::sub`] computes the element-wise difference of two + /// vectors. + fn sub(&self, other: &Self) -> Result, SynthesisError>; + + /// [`VectorGadget::scale`] multiplies every element by a scalar. + fn scale(&self, scalar: &Scalar) -> Result, SynthesisError> + where + for<'a> &'a Scalar: Mul<&'a FV, Output = Output>; + /// [`VectorGadget::hadamard`] computes the element-wise (Hadamard) product + /// of two vectors. fn hadamard(&self, other: &Self) -> Result, SynthesisError>; } -impl VectorGadget> for [FpVar] { - fn add(&self, other: &Self) -> Result>, SynthesisError> { +impl VectorGadget for [FV] +where + for<'a> &'a FV: Add<&'a FV, Output = FV> + Sub<&'a FV, Output = FV> + Mul<&'a FV, Output = FV>, +{ + fn add(&self, other: &Self) -> Result, SynthesisError> { if self.len() != other.len() { return Err(SynthesisError::Unsatisfiable); } Ok(self.iter().zip(other.iter()).map(|(a, b)| a + b).collect()) } - fn scale(&self, scalar: &FpVar) -> Result>, SynthesisError> { - Ok(self.iter().map(|a| a * scalar).collect()) + fn sub(&self, other: &Self) -> Result, SynthesisError> { + if self.len() != other.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(self.iter().zip(other.iter()).map(|(a, b)| a - b).collect()) + } + + fn scale(&self, scalar: &Scalar) -> Result, SynthesisError> + where + for<'a> &'a Scalar: Mul<&'a FV, Output = Output>, + { + Ok(self.iter().map(|a| scalar * a).collect()) } - fn hadamard(&self, other: &Self) -> Result>, SynthesisError> { + fn hadamard(&self, other: &Self) -> Result, SynthesisError> { if self.len() != other.len() { return Err(SynthesisError::Unsatisfiable); } diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs index f9917cb35..fe9011ae7 100644 --- a/crates/primitives/src/arithmetizations/ccs/circuits.rs +++ b/crates/primitives/src/arithmetizations/ccs/circuits.rs @@ -1,3 +1,5 @@ +//! This module implements in-circuit CCS variables. + use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, @@ -9,12 +11,15 @@ use ark_std::borrow::Borrow; use super::{CCS, CCSVariant}; use crate::algebra::ops::matrix::SparseMatrixVar; -/// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. +/// [`CCSMatricesVar`] is an in-circuit variable of a given CCS structure. +/// +/// Only the matrices are represented, while the remaining CCS parameters are +/// constants to the circuit. #[allow(non_snake_case)] #[derive(Debug, Clone)] pub struct CCSMatricesVar { - // we only need native representation, so the constraint field==F - pub M: Vec>>, + #[allow(dead_code)] + M: Vec>>, } impl AllocVar, F> for CCSMatricesVar { @@ -36,3 +41,5 @@ impl AllocVar, F> for CCSMatricesVar }) } } + +// TODO: add relation check gadgets when needed. diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 722581134..9ef789298 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -1,3 +1,24 @@ +//! This module implements the Customizable Constraint System (CCS) and its +//! relation checks against plain witnesses and instances. +//! +//! Proposed in the CCS [paper], it is a generalization of R1CS as well as many +//! other constraint systems. +//! A CCS structure is defined by the following components: +//! - The number of constraints `m`, the number of variables `n`, and the number +//! of public inputs `l`. +//! - The degree `d`. +//! - A sequence of `t` matrices `M`. +//! - A sequence of `q` multisets `S`, where each multiset `S_i` has at most `d` +//! elements and each element is an index in `[0, t - 1]` pointing to a matrix +//! `M_j`. +//! - A sequence of `q` coefficients `c`. +//! +//! A vector of assignments `z` satisfies the CCS if its evaluation +//! `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j · z))` is zero, where `〇` denotes +//! the Hadamard product among all `M_j · z`. +//! +//! [paper]: https://eprint.iacr.org/2023/552.pdf + use ark_ff::Field; use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; @@ -14,16 +35,26 @@ use crate::{ pub mod circuits; +/// [`CCSVariant`] defines the methods that a CCS variant (e.g., R1CS) should +/// implement. pub trait CCSVariant: Clone + Debug + PartialEq + Default + Sync { + /// [`CCSVariant::n_matrices`] returns the number of matrices in the CCS + /// variant. fn n_matrices() -> usize; + /// [`CCSVariant::degree`] returns the degree of the CCS variant. fn degree() -> usize; + /// [`CCSVariant::multisets_vec`] returns the vector of multisets in the CCS + /// variant. fn multisets_vec() -> Vec>; + /// [`CCSVariant::coefficients_vec`] returns the vector of coefficients in + /// the CCS variant. fn coefficients_vec() -> Vec; } +/// [`CCSConfig`] stores the shape parameters of a CCS structure. #[allow(non_snake_case)] #[derive(Clone, Debug, Default, PartialEq)] pub struct CCSConfig { @@ -61,11 +92,6 @@ impl ArithConfig for CCSConfig { fn n_witnesses(&self) -> usize { self.n_variables() - self.n_public_inputs() - 1 } - - #[inline] - fn set_n_public_inputs(&mut self, l: usize) { - self.l = l; - } } impl, V: CCSVariant> From for CCSConfig { @@ -86,42 +112,40 @@ impl From<&ConstraintSystem> for CCSConfig { } } -/// CCS represents the Customizable Constraint Systems structure defined in -/// the [CCS paper](https://eprint.iacr.org/2023/552) +/// [`CCS`] holds the CCS matrices `M` together with the configuration. #[allow(non_snake_case)] #[derive(Clone)] pub struct CCS { cfg: CCSConfig, - /// vector of matrices - pub M: Vec>, + pub(super) M: Vec>, } impl CCS { - /// Evaluates the CCS relation at a given vector of assignments `z` - pub fn eval_assignments( - &self, - z: Assignments + Sync>, - ) -> Result, Error> { + /// [`CCS::evaluate_at`] evaluates the CCS relation at a given vector of + /// assignments `z`. + pub fn evaluate_at(&self, z: Assignments + Sync>) -> Result, Error> { + let cfg = &self.cfg; + let public_len = z.public.as_ref().len(); let private_len = z.private.as_ref().len(); - if public_len != self.n_public_inputs() { + if public_len != cfg.n_public_inputs() { return Err(Error::MalformedAssignments(format!( "The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", - self.n_public_inputs(), + cfg.n_public_inputs(), public_len ))); } - if private_len != self.n_witnesses() { + if private_len != cfg.n_witnesses() { return Err(Error::MalformedAssignments(format!( "The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", - self.n_witnesses(), + cfg.n_witnesses(), private_len ))); } // Recall that the evaluation of CCS at z is defined as: - // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i * z))$, + // `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j · z))`, // where $\prod$ denotes the Hadamard product. // // Below, we manually expand the vector and matrix operations for less @@ -131,21 +155,21 @@ impl CCS { // We parallelize the outer loop over rows (when the `parallel` feature // is enabled), since the number of constraints in the CCS is typically // large in practice. - Ok(cfg_into_iter!(0..self.n_constraints()) + Ok(cfg_into_iter!(0..cfg.n_constraints()) .map(|row| { - // The row-th entry of the resulting vector is: - // $\sum_{j=0}^{q - 1} (c_j * \prod_{i \in S_j} (M_i[row] * z))$ + // The `row`-th entry of the resulting vector is: + // `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j[row] · z))` V::multisets_vec() .into_iter() .zip(V::coefficients_vec::()) .map(|(s, c)| { // Each term in the sum is: - // $c_j * \prod_{i \in S_j} (M_i[row] * z)$ + // `c_i · 〇_{j ∈ S_i} (M_j[row] · z)` c * s .iter() .map(|&i| { - // Each factor in the product is $M_i[row] * z$, - // i.e., the dot product of $M_i[row]$ and $z$. + // Each factor in the product is `M_j[row] · z`, + // i.e., the dot product of `M_j[row]` and `z`. self.M[i][row] .iter() .map(|(val, col)| z[*col] * val) @@ -158,6 +182,8 @@ impl CCS { .collect()) } + /// [`CCS::mles`] returns the multilinear extensions of all CCS matrices + /// `M_i` evaluated over the assignments `z`. pub fn mles( &self, z: Assignments + Sync>, @@ -202,7 +228,7 @@ impl, U: AsRef<[F]>, V: CCSVariant> ArithRelation type Evaluation = Vec; fn eval_relation(&self, w: &W, u: &U) -> Result { - self.eval_assignments((F::one(), u.as_ref(), w.as_ref()).into()) + self.evaluate_at((F::one(), u.as_ref(), w.as_ref()).into()) } fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index a9dd944ae..6fc4170e8 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -1,3 +1,11 @@ +//! This module defines and implements traits for arithmetizations, also known +//! as constraint systems. +//! +//! In Sonobe, we currently support two constraint systems: the Rank-1 +//! Constraint System (R1CS) and the Customizable Constraint System (CCS). +//! However, user circuits are always synthesized into R1CS currently, since +//! R1CS is the only supported constraint system by ark-relations. + use ark_relations::gr1cs::SynthesisError; use ark_std::{fmt::Debug, log2}; use thiserror::Error; @@ -7,190 +15,173 @@ use crate::relations::{Relation, RelationGadget}; pub mod ccs; pub mod r1cs; -#[derive(Debug, Error)] +/// [`Error`] enumerates possible errors during arithmetization operations. +#[derive(Error, Debug)] pub enum Error { + /// [`Error::MalformedAssignments`] indicates that the provided assignments + /// have incorrect shape. #[error("The provided assignments have incorrect shape: {0}")] MalformedAssignments(String), + /// [`Error::UnsatisfiedAssignments`] indicates that the provided + /// assignments do not satisfy the constraint system. #[error("The provided assignments do not satisfy the constraint system: {0}")] UnsatisfiedAssignments(String), - #[error("Failed to extract constraints from the constraint system: {0}")] - ConstraintExtractionFailure(String), + /// [`Error::SynthesisError`] indicates an error during constraint + /// synthesis. #[error(transparent)] SynthesisError(#[from] SynthesisError), } +/// [`ArithConfig`] describes the configuration of a constraint system. pub trait ArithConfig: Clone + Debug + Default + PartialEq { - /// Returns the degree of the constraint system + /// [`ArithConfig::degree`] returns the degree of the constraint system. fn degree(&self) -> usize; - /// Returns the number of constraints in the constraint system + /// [`ArithConfig::n_constraints`] returns the number of constraints in the + /// constraint system. fn n_constraints(&self) -> usize; + /// [`ArithConfig::log_constraints`] returns the base-2 logarithm of the + /// number of constraints in the constraint system. fn log_constraints(&self) -> usize { log2(self.n_constraints()) as usize } - /// Returns the number of variables in the constraint system + /// [`ArithConfig::n_variables`] returns the number of variables in the + /// constraint system. fn n_variables(&self) -> usize; - /// Returns the number of public inputs / public IO / instances / statements - /// in the constraint system + /// [`ArithConfig::n_public_inputs`] returns the number of public inputs in + /// the constraint system. fn n_public_inputs(&self) -> usize; - /// Returns the number of witnesses / secret inputs in the constraint system + /// [`ArithConfig::n_witnesses`] returns the number of witnesses in the + /// constraint system. fn n_witnesses(&self) -> usize; - - fn set_n_public_inputs(&mut self, l: usize); } -/// [`Arith`] is a trait about constraint systems (R1CS, CCS, etc.), where we -/// define methods for getting information about the constraint system. +/// [`Arith`] is a trait for constraint systems (R1CS, CCS, etc.), where we +/// define methods to get and set configuration about the constraint system. +/// In addition to the configuration, the implementor of this trait may also +/// store the actual constraints and other information. pub trait Arith: Clone + Default { + /// [`Arith::Config`] specifies the arithmetization's configuration. type Config: ArithConfig; + /// [`Arith::config`] returns a reference to the configuration of the + /// constraint system. fn config(&self) -> &Self::Config; + /// [`Arith::config_mut`] returns a mutable reference to the configuration + /// of the constraint system. fn config_mut(&mut self) -> &mut Self::Config; - - /// Returns the degree of the constraint system - #[inline] - fn degree(&self) -> usize { - self.config().degree() - } - - /// Returns the number of constraints in the constraint system - #[inline] - fn n_constraints(&self) -> usize { - self.config().n_constraints() - } - - #[inline] - fn log_constraints(&self) -> usize { - self.config().log_constraints() - } - - /// Returns the number of variables in the constraint system - #[inline] - fn n_variables(&self) -> usize { - self.config().n_variables() - } - - /// Returns the number of public inputs / public IO / instances / statements - /// in the constraint system - #[inline] - fn n_public_inputs(&self) -> usize { - self.config().n_public_inputs() - } - - /// Returns the number of witnesses / secret inputs in the constraint system - #[inline] - fn n_witnesses(&self) -> usize { - self.config().n_witnesses() - } } -/// `ArithRelation` *treats a constraint system as a relation* between a witness -/// of type `W` and a statement / public input / public IO / instance of type -/// `U`, and in this trait, we define the necessary operations on the relation. +/// [`ArithRelation`] treats a constraint system as a relation between a witness +/// of type `W` and an instance of type `U`, and in this trait, we separate the +/// relation check into two steps: evaluating the constraint system and checking +/// the evaluation result. /// -/// Note that the same constraint system may support different types of `W` and -/// `U`, and the satisfiability check may vary. +/// Note that `W` and `U` are part of the trait parameters instead of associated +/// types, because the same constraint system may support different types of `W` +/// and `U`, and the satisfiability check may vary. +/// This "same constraint system, different witness-instance pair" abstraction +/// turns out to be very flexible, as one constraint system struct now can have +/// many different relation checks depending on the context. /// -/// For example, both plain R1CS and relaxed R1CS are represented by 3 matrices, -/// but the types of `W` and `U` are different: -/// - The plain R1CS has `W` and `U` as vectors of field elements. -/// -/// `W = w` and `U = x` satisfy R1CS if `Az ∘ Bz = Cz`, where `z = [1, x, w]`. +/// For example, some folding schemes consider a variant of R1CS known as +/// relaxed R1CS, which is also represented by the `A`, `B`, and `C` matrices +/// but has a different relation check compared to plain R1CS. +/// We handle their similarities and differences in the following way: +/// - Since the structure of relaxed R1CS is exactly the same as plain R1CS, we +/// use a single R1CS struct to represent both of them. +/// - To distinguish their relation checks, we instead use distinct types of `W` +/// and `U`. +/// - For plain R1CS, we use plain witness `W = w` and instance `U = x` that +/// are simply vectors of field elements. +/// The implementation of `ArithRelation` for such `W` and `U` then checks +/// if `Az ∘ Bz = Cz`, where `z = [1, x, w]`. +/// - For relaxed R1CS, we use relaxed witness `W` and relaxed instance `U` +/// that contain extra data such as the error or slack terms, e.g., +/// - In Nova, `W = (w, e, ...)`, `U = (u, x, ...)`. +/// The implementation of `ArithRelation` for such `W` and `U` checks +/// if `Az ∘ Bz = uCz + e`, where `z = [u, x, w]`. +/// - In ProtoGalaxy, `W = (w, ...)`, `U = (x, e, β, ...)`. +/// The implementation of `ArithRelation` for such `W` and `U` checks +/// if `e = Σ pow_i(β) v_i`, where `v = Az ∘ Bz - Cz`,`z = [1, x, w]`. /// -/// - In Nova, Relaxed R1CS has `W` as [`crate::folding::nova::Witness`], -/// and `U` as [`crate::folding::nova::CommittedInstance`]. -/// -/// `W = (w, e, ...)` and `U = (u, x, ...)` satisfy Relaxed R1CS if -/// `Az ∘ Bz = uCz + e`, where `z = [u, x, w]`. -/// (commitments in `U` are not checked here) -/// -/// Also, `W` and `U` have non-native field elements as their components when -/// used as CycleFold witness and instance. -/// -/// - In ProtoGalaxy, Relaxed R1CS has `W` as [`crate::folding::protogalaxy::Witness`], -/// and `U` as [`crate::folding::protogalaxy::CommittedInstance`]. -/// -/// `W = (w, ...)` and `U = (x, e, β, ...)` satisfy Relaxed R1CS if -/// `e = Σ pow_i(β) v_i`, where `v = Az ∘ Bz - Cz`, `z = [1, x, w]`. -/// (commitments in `U` are not checked here) -/// -/// This is also the case of CCS, where `W` and `U` may be vectors of field -/// elements, [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::lcccs::LCCCS`], -/// or [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::cccs::CCCS`]. +/// This is also the case for CCS, where `W` and `U` may be vectors of field +/// elements or running / incoming witness-instance pairs of different folding +/// schemes such as HyperNova. pub trait ArithRelation: Arith { + /// [`ArithRelation::Evaluation`] defines the type of the evaluation result + /// returned by [`ArithRelation::eval_relation`], and consumed by + /// [`ArithRelation::check_evaluation`]. + /// + /// The evaluation result is usually a vector of field elements. + /// However, we use an associated type to represent the evaluation result + /// for future extensions. type Evaluation; - /// Evaluates the constraint system `self` at witness `w` and instance `u`. - /// Returns the evaluation result. + /// [`ArithRelation::eval_relation`] evaluates the constraint system at + /// witness `w` and instance `u`. It returns the evaluation result. /// - /// The evaluation result is usually a vector of field elements. /// For instance: /// - Evaluating the plain R1CS at `W = w` and `U = x` returns /// `Az ∘ Bz - Cz`, where `z = [1, x, w]`. - /// /// - Evaluating the relaxed R1CS in Nova at `W = (w, e, ...)` and /// `U = (u, x, ...)` returns `Az ∘ Bz - uCz`, where `z = [u, x, w]`. - /// /// - Evaluating the relaxed R1CS in ProtoGalaxy at `W = (w, ...)` and /// `U = (x, e, β, ...)` returns `Az ∘ Bz - Cz`, where `z = [1, x, w]`. - /// - /// However, we use `Self::Evaluation` to represent the evaluation result - /// for future extensibility. fn eval_relation(&self, w: &W, u: &U) -> Result; - /// Checks if the evaluation result is valid. The witness `w` and instance - /// `u` are also parameters, because the validity check may need information - /// contained in `w` and/or `u`. + /// [`ArithRelation::check_evaluation`] checks if the evaluation result is + /// valid. The witness `w` and instance `u` are also parameters, because the + /// validity check may need information contained in `w` and/or `u`. /// /// For instance: /// - The evaluation `v` of plain R1CS at satisfying `W` and `U` should be /// an all-zero vector. - /// /// - The evaluation `v` of relaxed R1CS in Nova at satisfying `W` and `U` - /// should be equal to the error term `e` in the witness. - /// + /// should be equal to the error term `e` in `W`. /// - The evaluation `v` of relaxed R1CS in ProtoGalaxy at satisfying `W` /// and `U` should satisfy `e = Σ pow_i(β) v_i`, where `e` is the error - /// term in the committed instance. + /// term in `U`. fn check_evaluation(w: &W, u: &U, v: Self::Evaluation) -> Result<(), Error>; } impl> Relation for A { type Error = Error; - /// Checks if witness `w` and instance `u` satisfy the constraint system - /// `self` by first computing the evaluation result and then checking the - /// validity of the evaluation result. - /// - /// Used only for testing. fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error> { + // `check_relation` is implemented by combining `eval_relation` and + // `check_evaluation`. let e = self.eval_relation(w, u)?; Self::check_evaluation(w, u, e) } } -/// `ArithRelationGadget` defines the in-circuit counterparts of operations -/// specified in `ArithRelation` on constraint systems. +/// [`ArithRelationGadget`] defines the in-circuit gadget for constraint system +/// operations in the same way as [`ArithRelation`]. pub trait ArithRelationGadget { + /// [`ArithRelationGadget::Evaluation`] defines the type of the evaluation + /// result returned by [`ArithRelationGadget::eval_relation`], and consumed + /// by [`ArithRelationGadget::check_evaluation`]. type Evaluation; - /// Evaluates the constraint system `self` at witness `w` and instance `u`. - /// Returns the evaluation result. + /// [`ArithRelationGadget::eval_relation`] evaluates the constraint system + /// at witness `w` and instance `u`. It returns the evaluation result. fn eval_relation(&self, w: &WVar, u: &UVar) -> Result; - /// Generates constraints for enforcing that the evaluation result is valid. - /// The witness `w` and instance `u` are also parameters, because the - /// validity check may need information contained in `w` and/or `u`. + /// [`ArithRelationGadget::check_evaluation`] checks if the evaluation + /// result is valid under the help of the witness `w` and instance `u`. fn check_evaluation(w: &WVar, u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError>; } impl> RelationGadget for A { fn check_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError> { + // `check_relation` is implemented by combining `eval_relation` and + // `check_evaluation`. let e = self.eval_relation(w, u)?; Self::check_evaluation(w, u, e) } diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index 54b1e0563..b6d908f3e 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -1,7 +1,9 @@ -use ark_ff::PrimeField; +//! This module implements in-circuit R1CS variables and relation check gadgets. + +use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{One, borrow::Borrow}; +use ark_std::{One, borrow::Borrow, ops::Mul}; use super::R1CS; use crate::{ @@ -14,13 +16,18 @@ use crate::{ circuits::Assignments, }; -/// An in-circuit representation of the `R1CS` struct. +/// [`R1CSMatricesVar`] is the in-circuit variable of a given R1CS structure. +/// +/// Only the matrices are represented, while the remaining R1CS parameters are +/// constants to the circuit. +/// +/// The naming is chosen to distinguish from arkworks' `(G)R1CSVar`. #[allow(non_snake_case)] #[derive(Debug, Clone)] pub struct R1CSMatricesVar { - pub A: SparseMatrixVar, - pub B: SparseMatrixVar, - pub C: SparseMatrixVar, + A: SparseMatrixVar, + B: SparseMatrixVar, + C: SparseMatrixVar, } impl> @@ -49,20 +56,24 @@ impl R1CSMatricesVar where SparseMatrixVar: MatrixGadget, [FVar]: VectorGadget, + for<'a> &'a FVar: Mul<&'a FVar, Output = FVar>, { + /// [`R1CSMatricesVar::evaluate_at`] is the in-circuit version of + /// [`R1CS::evaluate_at`] that evaluates the R1CS variable at a given vector + /// of assignments `z`. #[allow(non_snake_case)] - pub fn eval_assignments( + pub fn evaluate_at( &self, z: Assignments>, - ) -> Result<(Vec, Vec), SynthesisError> { + ) -> Result, SynthesisError> { // Multiply Cz by z[0] (u) here, allowing this method to be reused for - // both relaxed and unrelaxed R1CS. + // both relaxed and plain R1CS. let Az = self.A.mul_vector(&z)?; let Bz = self.B.mul_vector(&z)?; let Cz = self.C.mul_vector(&z)?; let uCz = Cz.scale(&z[0])?; let AzBz = Az.hadamard(&Bz)?; - Ok((AzBz, uCz)) + AzBz.sub(&uCz) } } @@ -70,24 +81,19 @@ impl, UVar: AsRef<[FVar]>> ArithRelationGadget where SparseMatrixVar: MatrixGadget, - [FVar]: VectorGadget + EquivalenceGadget, - FVar: Clone + One, + [FVar]: VectorGadget + EquivalenceGadget<[FVar]>, + // TODO (@winderica): this will not work for our incoming decider + FVar: Clone + Zero + One, + for<'a> &'a FVar: Mul<&'a FVar, Output = FVar>, { - /// Evaluation is a tuple of two vectors (`AzBz` and `uCz`) instead of a - /// single vector `AzBz - uCz`, because subtraction is not supported for - /// `FVar = NonNativeUintVar`. - type Evaluation = (Vec, Vec); + type Evaluation = Vec; fn eval_relation(&self, w: &WVar, u: &UVar) -> Result { - self.eval_assignments((FVar::one(), u.as_ref(), w.as_ref()).into()) + self.evaluate_at((FVar::one(), u.as_ref(), w.as_ref()).into()) } - fn check_evaluation( - _w: &WVar, - _u: &UVar, - (lhs, rhs): Self::Evaluation, - ) -> Result<(), SynthesisError> { - lhs.enforce_equivalent(&rhs) + fn check_evaluation(_w: &WVar, _u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError> { + e.enforce_equivalent(&vec![FVar::zero(); e.len()]) } } diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 84c1c6278..5a65f25ec 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -1,3 +1,6 @@ +//! This module implements the Rank-1 Constraint System (R1CS) and its relation +//! checks against plain and relaxed witnesses and instances. + use ark_ff::Field; use ark_relations::gr1cs::{ConstraintSystem, Matrix, R1CS_PREDICATE_LABEL}; use ark_std::{cfg_into_iter, cfg_iter, iterable::Iterable}; @@ -12,6 +15,7 @@ use crate::{ pub mod circuits; +/// [`R1CSConfig`] stores the shape parameters of an R1CS structure. #[derive(Debug, Clone, Default, PartialEq)] pub struct R1CSConfig { m: usize, // number of constraints @@ -20,6 +24,7 @@ pub struct R1CSConfig { } impl R1CSConfig { + /// [`R1CSConfig::new`] creates a new R1CS configuration. pub fn new(n_constraints: usize, n_variables: usize, n_public_inputs: usize) -> Self { Self { m: n_constraints, @@ -54,11 +59,6 @@ impl ArithConfig for R1CSConfig { fn n_witnesses(&self) -> usize { self.n_variables() - self.n_public_inputs() - 1 } - - #[inline] - fn set_n_public_inputs(&mut self, l: usize) { - self.l = l; - } } impl From<&ConstraintSystem> for R1CSConfig { @@ -93,48 +93,62 @@ impl CCSVariant for R1CSConfig { } } +/// [`R1CS`] holds the three sparse matrices `A`, `B`, `C` together with the +/// configuration. #[allow(non_snake_case)] #[derive(Debug, Clone, Default, PartialEq)] pub struct R1CS { cfg: R1CSConfig, - pub A: Matrix, - pub B: Matrix, - pub C: Matrix, + pub(super) A: Matrix, + pub(super) B: Matrix, + pub(super) C: Matrix, } +type Row = Vec<(F, usize)>; + impl R1CS { - /// Evaluates the R1CS relation at a given vector of assignments `z` - pub fn eval_assignments( + /// [`R1CS::evaluate_rows`] evaluates the R1CS relation by applying the + /// provided function `f` to each triplet of rows `(A[i], B[i], C[i])`. + pub fn evaluate_rows( + &self, + f: impl FnMut(((&Row, &Row), &Row)) -> Result, + ) -> Result, Error> { + cfg_iter!(self.A).zip(&self.B).zip(&self.C).map(f).collect() + } + + /// [`R1CS::evaluate_at`] evaluates the R1CS relation at a given vector of + /// assignments `z`. + pub fn evaluate_at( &self, z: Assignments + Sync>, ) -> Result, Error> { + let cfg = &self.cfg; + let public_len = z.public.as_ref().len(); let private_len = z.private.as_ref().len(); - if public_len != self.n_public_inputs() { + if public_len != cfg.n_public_inputs() { return Err(Error::MalformedAssignments(format!( "The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", - self.n_public_inputs(), + cfg.n_public_inputs(), public_len ))); } - if private_len != self.n_witnesses() { + if private_len != cfg.n_witnesses() { return Err(Error::MalformedAssignments(format!( "The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", - self.n_witnesses(), + cfg.n_witnesses(), private_len ))); } - Ok(cfg_iter!(self.A) - .zip(&self.B) - .zip(&self.C) - .map(|((a, b), c)| { - let az = a.iter().map(|(val, col)| z[*col] * val).sum::(); - let bz = b.iter().map(|(val, col)| z[*col] * val).sum::(); - let cz = c.iter().map(|(val, col)| z[*col] * val).sum::(); - az * bz - z[0] * cz - }) - .collect()) + self.evaluate_rows(|((a, b), c)| { + let az = a.iter().map(|(val, col)| z[*col] * val).sum::(); + let bz = b.iter().map(|(val, col)| z[*col] * val).sum::(); + let cz = c.iter().map(|(val, col)| z[*col] * val).sum::(); + // use `z[0]` here since the constant term at index 0 may not be 1 + // for relaxed instances + Ok(az * bz - z[0] * cz) + }) } } @@ -153,6 +167,8 @@ impl Arith for R1CS { } impl R1CS { + /// [`R1CS::new`] creates a new R1CS structure from the given configuration + /// and matrices. #[allow(non_snake_case)] pub fn new(cfg: R1CSConfig, [A, B, C]: [Matrix; 3]) -> Self { Self { cfg, A, B, C } @@ -163,11 +179,12 @@ impl TryFrom> for R1CS { type Error = Error; fn try_from(ccs: CCS) -> Result { + let cfg = ccs.config(); Ok(Self::new( R1CSConfig::new( - ccs.n_constraints(), - ccs.n_variables(), - ccs.n_public_inputs(), + cfg.n_constraints(), + cfg.n_variables(), + cfg.n_public_inputs(), ), // `unwrap` is safe here because the type parameter T = 3 ccs.M.try_into().unwrap(), @@ -195,7 +212,7 @@ impl, U: AsRef<[F]>> ArithRelation for R1CS { type Evaluation = Vec; fn eval_relation(&self, w: &W, x: &U) -> Result { - self.eval_assignments((F::one(), x.as_ref(), w.as_ref()).into()) + self.evaluate_at((F::one(), x.as_ref(), w.as_ref()).into()) } fn check_evaluation(_w: &W, _x: &U, e: Self::Evaluation) -> Result<(), Error> { @@ -208,13 +225,23 @@ impl, U: AsRef<[F]>> ArithRelation for R1CS { } } +/// [`RelaxedWitness`] defines a relaxed version of R1CS witness. +/// +/// It is the basis of witnesses in many folding schemes that support R1CS. pub struct RelaxedWitness { + /// [`RelaxedWitness::w`] is the witness vector pub w: V, + /// [`RelaxedWitness::e`] is the error term pub e: V, } +/// [`RelaxedInstance`] defines a relaxed version of R1CS instance. +/// +/// It is the basis of instances in many folding schemes that support R1CS. pub struct RelaxedInstance { + /// [`RelaxedInstance::x`] is the public input vector pub x: V, + /// [`RelaxedInstance::u`] is the constant term pub u: V::Item, } @@ -226,7 +253,7 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 w: &RelaxedWitness<&[F]>, u: &RelaxedInstance<&[F]>, ) -> Result { - self.eval_assignments((*u.u, u.x, w.w).into()) + self.evaluate_at((*u.u, u.x, w.w).into()) } fn check_evaluation( @@ -245,7 +272,7 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 } #[cfg(test)] -pub mod tests { +mod tests { use ark_bn254::Fr; use ark_ff::UniformRand; use ark_relations::gr1cs::ConstraintSynthesizer; diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 78e5b060d..ef7ce03cd 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -1,3 +1,5 @@ +//! This module defines circuits and helpers used by Sonobe. + use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, fields::fp::FpVar}; use ark_relations::gr1cs::{ @@ -8,50 +10,120 @@ use ark_std::{ ops::{Deref, Index, IndexMut}, }; -use crate::transcripts::{Absorbable, AbsorbableGadget}; +use crate::transcripts::{Absorbable, AbsorbableVar}; pub mod utils; -/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. -/// inside the agmented F' function). -/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying -/// the step. -/// Note that the external inputs for the specific circuit are defined at the implementation of -/// both `FCircuit::ExternalInputs` and `FCircuit::ExternalInputsVar`, where the `Default` trait -/// implementation for the `ExternalInputs` returns the initialized data structure (ie. if the type -/// contains a vector, it is initialized at the expected length). +/// [`FCircuit`] defines the trait of step circuits being proven by IVC schemes. +/// +/// In IVC, a step circuit is repeatedly invoked to update some state persisted +/// throughout the execution. +/// For flexibility, we further allow each step to take some external inputs +/// and produce some external outputs that are not part of the state, which may +/// or may not be constrained inside the step circuit. +/// +/// Such a design has several advantages: +/// 1. It allows the implementation to keep the state minimal, only including +/// the parts that need to be preserved and constrained across steps, while +/// step-specific inputs that might be large are not part of the state. +/// +/// For example, in a Merkle tree update circuit, the state may only contain +/// the root of the tree, while the leaf value and authentication path which +/// are large can be provided as external inputs at each step. +/// +/// 2. The caller of the step circuit can peek into the circuit execution at +/// each step via the external outputs by having the circuit return +/// `var.value()` for desired variables. +/// +/// For example, in a Merkle tree update circuit, the circuit can return the +/// intermediate hashes computed at each step as external outputs, allowing +/// the caller to test if the hash computation is correct. +/// +/// 3. The implementation can mix out-of-circuit and in-circuit logic in this +/// structure, where the out-of-circuit logic may consume external inputs and +/// produce external outputs for the next step. +/// This is why the implementation may choose to constrain or not constrain +/// the external inputs/outputs inside the step circuit. +/// Such a mixed design can be helpful if the out-of-circuit logic and the +/// in-circuit logic are highly interdependent. +/// +/// For example, in a Merkle tree update circuit, one may write both the +/// Merkle proof generation (out-of-circuit) and verification (in-circuit) +/// logic in a single [`FCircuit::generate_step_constraints`]. +/// In this case, the external inputs contain the leaf value to be added, as +/// well as all the existing tree nodes. +/// The latter will be used by the out-of-circuit logic to compute the path, +/// but will not be constrained inside the circuit. +/// The external outputs contain the new tree nodes after the update, which +/// will be used as the inputs to the next step. +/// +/// To summarize, the step circuit takes as input the current state and some +/// external inputs, and returns the next state and some external outputs. pub trait FCircuit { + /// [`FCircuit::Field`] is the field over which the circuit is defined. type Field: PrimeField; + /// [`FCircuit::State`] is the type of the state. + /// + /// It is usually an array of field elements, but we make our design quite + /// flexible so that the implementation is free to choose any structure for + /// it. type State: Clone + PartialEq + Absorbable; + /// [`FCircuit::StateVar`] is the in-circuit variable type for the state. + /// + /// If the implementation chooses custom structures for the state, it should + /// implement the required traits for the corresponding variable type. type StateVar: GR1CSVar + AllocVar - + AbsorbableGadget; + + AbsorbableVar; + /// [`FCircuit::ExternalInputs`] is the type of external inputs provided to + /// each step of the circuit. type ExternalInputs; + /// [`FCircuit::ExternalOutputs`] is the type of external outputs produced + /// by each step of the circuit. type ExternalOutputs; + /// [`FCircuit::dummy_state`] returns a dummy state for the circuit. fn dummy_state(&self) -> Self::State; + /// [`FCircuit::dummy_external_inputs`] returns dummy external inputs for + /// the circuit. fn dummy_external_inputs(&self) -> Self::ExternalInputs; - /// generates the constraints for the step of F for the given z_i + /// [`FCircuit::generate_step_constraints`] generates the constraints for + /// the `i`-th step of invocation of the step circuit with the current state + /// `state` and external inputs `external_inputs`, producing the next state + /// and external outputs. + /// + /// ### Tips + /// + /// - Since this method uses `self`, the implementation store some (fixed) + /// info that is shared across all steps inside `self`. + /// - Variables in the implementation should be allocated as witnesses (not + /// public inputs) in the implementation. + /// - If needed, the constraint system `cs` can be accessed via `i.cs()` or + /// `state.cs()` using arkworks' [`GR1CSVar::cs`] method. fn generate_step_constraints( - // this method uses self, so that each FCircuit implementation (and different frontends) - // can hold a state if needed to store data to generate the constraints. &self, - cs: ConstraintSystemRef, i: FpVar, - z_i: Self::StateVar, - external_inputs: Self::ExternalInputs, // inputs that are not part of the state + state: Self::StateVar, + external_inputs: Self::ExternalInputs, ) -> Result<(Self::StateVar, Self::ExternalOutputs), SynthesisError>; } +/// [`Assignments`] represents a full assignment vector `z = (u, x, w)` for a +/// constraint system. #[derive(Clone, Debug, PartialEq)] pub struct Assignments { + /// [`Assignments::constant`] is the "constant" part (leading scalar) of the + /// assignment, which is usually 1 but might be relaxed in some cases. pub constant: F, + /// [`Assignments::public`] contains the public inputs. pub public: V, + /// [`Assignments::private`] contains the witnesses. pub private: V, } +/// [`AssignmentsOwned`] is a convenience alias for owned assignment vectors. pub type AssignmentsOwned = Assignments>; impl From<(F, V, V)> for Assignments { @@ -94,10 +166,14 @@ impl + AsMut<[F]>> IndexMut for Assignments { } } +/// [`ConstraintSystemExt`] wraps a `ConstraintSystemRef` with compile-time +/// flags that control whether constraint matrices (`ARITH_ENABLED`) and / or +/// assignment vectors (`ASSIGNMENTS_ENABLED`) are collected during synthesis. pub struct ConstraintSystemExt { cs: ConstraintSystemRef, } + impl Deref for ConstraintSystemExt { @@ -111,6 +187,8 @@ impl Deref impl ConstraintSystemExt { + /// [`ConstraintSystemExt::new`] creates a new constraint system wrapper + /// with the specified flags. pub fn new() -> Self { let cs = ConstraintSystem::::new_ref(); let mode = if ASSIGNMENTS_ENABLED { @@ -125,6 +203,9 @@ impl Self { cs } } + /// [`ConstraintSystemExt::execute_synthesizer`] executes a circuit inside + /// the constraint system, where the circuit should implement the + /// [`ConstraintSynthesizer`] trait. pub fn execute_synthesizer( &self, circuit: impl ConstraintSynthesizer, @@ -132,6 +213,10 @@ impl self.execute_fn(|cs| circuit.generate_constraints(cs)) } + /// [`ConstraintSystemExt::execute_fn`] executes a circuit inside the + /// constraint system, where the circuit should be defined as a closure that + /// takes as input a `ConstraintSystemRef` and returns a result of type `R`. + /// The return value of the closure will be returned by this method. pub fn execute_fn( &self, circuit: impl FnOnce(ConstraintSystemRef) -> Result, @@ -152,16 +237,25 @@ impl Defau } } +/// [`ArithExtractor`] collects only the constraint matrices (no assignments) +/// from a synthesized circuit. pub type ArithExtractor = ConstraintSystemExt; +/// [`AssignmentsExtractor`] collects only the assignments (no constraint +/// matrices) from a synthesized circuit. pub type AssignmentsExtractor = ConstraintSystemExt; impl ArithExtractor { + /// [`ArithExtractor::arith`] extracts the constraint matrices from the + /// circuit and returns them as an arithmetization / constraint system + /// structure of type `A`. pub fn arith>>(self) -> Result { Ok(self.cs.into_inner().unwrap().into()) } } impl AssignmentsExtractor { + /// [`AssignmentsExtractor::assignments`] extracts the assignments from the + /// circuit and returns them as `Assignments`. pub fn assignments(self) -> Result>, SynthesisError> { let witness = self.cs.witness_assignment()?.to_vec(); // skip the first element which is '1' diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index cd345b9d2..0c8936161 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -1,7 +1,8 @@ +//! This module provides utility circuits. + use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ - alloc::AllocVar, - fields::fp::{AllocatedFp, FpVar}, + GR1CSVar, alloc::AllocVar, fields::fp::{AllocatedFp, FpVar} }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable}; @@ -12,7 +13,13 @@ use crate::{ traits::SonobeField, }; +/// [`CircuitForTest`] implements a simple test circuit computing +/// `y = x^3 + x + 5` with 4 R1CS constraints. +/// +/// It is used in unit tests to verify constraint extraction and witness +/// generation. pub struct CircuitForTest { + /// [`CircuitForTest::x`] is the input variable `x` of the circuit. pub x: F, } @@ -64,11 +71,12 @@ impl FCircuit for CircuitForTest { fn generate_step_constraints( &self, - cs: ConstraintSystemRef, _i: FpVar, z_i: Self::StateVar, _external_inputs: Self::ExternalInputs, ) -> Result<(Self::StateVar, Self::ExternalOutputs), SynthesisError> { + let cs = z_i.cs(); + // Variable 0 (implicitly added by arkworks as 1) // Variable 1 let x = if let FpVar::Var(x) = z_i[0].clone() { @@ -105,6 +113,7 @@ impl FCircuit for CircuitForTest { } } +/// [`constraints_for_test`] returns the R1CS constraints for the test circuit. #[allow(non_snake_case)] pub fn constraints_for_test() -> R1CS { // R1CS for: x^3 + x + 5 = y (example from article @@ -131,6 +140,8 @@ pub fn constraints_for_test() -> R1CS { R1CS::::new(R1CSConfig::new(4, 6, 1), [A, B, C]) } +/// [`satisfying_assignments_for_test`] returns a satisfying assignment for the +/// test circuit given an input `x`. pub fn satisfying_assignments_for_test(x: F) -> Assignments> { Assignments::from(( F::one(), diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 5e1f2f256..c2d676230 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,3 +1,5 @@ +//! Abstract traits and implementations for commitment schemes. + use ark_ff::UniformRand; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, fields::fp::FpVar, select::CondSelectGadget}; use ark_relations::gr1cs::SynthesisError; @@ -17,37 +19,65 @@ use crate::{ ops::bits::FromBitsGadget, }, traits::{CF1, CF2, SonobeCurve, SonobeField}, - transcripts::{Absorbable, AbsorbableGadget}, + transcripts::{Absorbable, AbsorbableVar}, }; pub mod pedersen; // TODO: add back other commitment schemes +/// [`Error`] enumerates possible errors during commitment operations. #[derive(Debug, Error)] pub enum Error { - // Commitment errors + /// [`Error::MessageTooLong`] indicates that the message being committed to + /// is longer than the maximum supported length. #[error( "The message being committed to has length {1}, exceeding the maximum supported length ({0})" )] MessageTooLong(usize, usize), - #[error("Blinding factor not 0 for Commitment without hiding")] - BlindingNotZero, - #[error("Blinding factors incorrect, blinding is set to {0} but blinding values are {1}")] - IncorrectBlinding(bool, String), + /// [`Error::CommitmentVerificationFail`] indicates that the provided + /// opening does not verify against the commitment. #[error("Commitment verification failed")] CommitmentVerificationFail, } +/// [`CommitmentKey`] represents a commitment key (e.g., a vector of group +/// generators for many group-based commitment schemes). pub trait CommitmentKey: Clone { + /// [`CommitmentKey::max_scalars_len`] returns the maximum number of scalars + /// that can be committed to with this key. fn max_scalars_len(&self) -> usize; } +/// [`CommitmentDef`] provides the core type definitions of a commitment scheme, +/// defining the types of relevant cryptographic objects such as the commitment +/// key, scalars, commitments, and randomness. pub trait CommitmentDef: 'static + Clone + Debug + PartialEq + Eq { + /// [`CommitmentDef::IS_HIDING`] indicates whether the commitment scheme has + /// the hiding property. const IS_HIDING: bool; + /// [`CommitmentDef::Key`] is the type of the commitment key. type Key: CommitmentKey; + /// [`CommitmentDef::Scalar`] is the type of the scalars being committed to. + /// + /// For generality, we do not restrict this to field elements and instead + /// only bound it by necessary traits. type Scalar: Clone + Copy + Default + Debug + PartialEq + Eq + Sync + Absorbable + UniformRand; + /// [`CommitmentDef::Commitment`] is the type of the commitment. + /// + /// In the future we may introduce other commitment schemes such as those + /// based on hash functions or lattices, so we do not restrict this to be + /// group elements. type Commitment: Clone + Default + Debug + PartialEq + Eq + Sync + Absorbable; + /// [`CommitmentDef::Randomness`] is the type of the randomness used in + /// the commitment. + /// + /// Hiding commitment schemes and non-hiding schemes may have different + /// randomness types, e.g., the former holds real data, while the latter + /// is just a placeholder type. + /// + /// In this way, we can leverage the compiler to reject misuse, e.g., using + /// randomness where it is not needed, or vice versa, with a unified API. type Randomness: Clone + Copy + Default @@ -64,15 +94,28 @@ pub trait CommitmentDef: 'static + Clone + Debug + PartialEq + Eq { + Sum; } +/// [`CommitmentOps`] defines algorithms for commitment schemes. pub trait CommitmentOps: CommitmentDef { + /// [`CommitmentOps::generate_key`] defines the key generation algorithm, + /// which is a randomized algorithm that takes as input the maximum length + /// `len` of supported messages, and a randomness source `rng`, and outputs + /// the commitment key. fn generate_key(len: usize, rng: impl RngCore) -> Result; + /// [`CommitmentOps::commit`] defines the commitment generation algorithm, + /// which is a (probably) randomized algorithm that takes as input + /// commitment key `ck`, a vector of scalars `v` to be committed to, and a + /// randomness source `rng`, and outputs the commitment and the randomness. fn commit( ck: &Self::Key, v: &[Self::Scalar], rng: impl RngCore, ) -> Result<(Self::Commitment, Self::Randomness), Error>; + /// [`CommitmentOps::open`] defines the commitment opening algorithm, which + /// is a deterministic algorithm that takes as input commitment key `ck`, + /// a vector of scalars `v`, the randomness `r`, and a commitment `cm`, and + /// outputs `Ok(())` if the opening verifies, or an error otherwise. fn open( ck: &Self::Key, v: &[Self::Scalar], @@ -81,28 +124,46 @@ pub trait CommitmentOps: CommitmentDef { ) -> Result<(), Error>; } +/// [`CommitmentDefGadget`] specifies the in-circuit associated types for a +/// commitment scheme gadget. pub trait CommitmentDefGadget: Clone { + /// [`CommitmentDefGadget::ConstraintField`] is the field over which the + /// circuit running the commitment scheme is defined. type ConstraintField: SonobeField; + /// [`CommitmentDefGadget::KeyVar`] is the in-circuit variable type for the + /// commitment key. type KeyVar; - type ScalarVar: AbsorbableGadget + /// [`CommitmentDefGadget::ScalarVar`] is the in-circuit variable type for + /// the scalars being committed to. + type ScalarVar: AbsorbableVar + CondSelectGadget + FromBitsGadget - + AllocVar<::Scalar, Self::ConstraintField> - + GR1CSVar::Scalar> + + AllocVar<::Scalar, Self::ConstraintField> + + GR1CSVar::Scalar> + TwoStageFieldVar; + /// [`CommitmentDefGadget::CommitmentVar`] is the in-circuit variable type + /// for the commitment. type CommitmentVar: Clone - + AbsorbableGadget + + AbsorbableVar + CondSelectGadget - + AllocVar<::Commitment, Self::ConstraintField> - + GR1CSVar::Commitment>; - type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> - + GR1CSVar::Randomness>; - - type Native: CommitmentDef; + + AllocVar<::Commitment, Self::ConstraintField> + + GR1CSVar::Commitment>; + /// [`CommitmentDefGadget::RandomnessVar`] is the in-circuit variable type + /// for the randomness used in the commitment. + type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> + + GR1CSVar::Randomness>; + + /// [`CommitmentDefGadget::Widget`] points to the out-of-circuit commitment + /// scheme widget. + type Widget: CommitmentDef; } +/// [`CommitmentOpsGadget`] defines algorithms (majorly the opening algorithm) +/// for commitment schemes in-circuit. pub trait CommitmentOpsGadget: CommitmentDefGadget { + /// [`CommitmentOpsGadget::open`] defines the commitment opening gadget + /// that matches its out-of-circuit widget [`CommitmentOps::open`]. fn open( ck: &Self::KeyVar, v: &[Self::ScalarVar], @@ -111,22 +172,28 @@ pub trait CommitmentOpsGadget: CommitmentDefGadget { ) -> Result<(), SynthesisError>; } +/// [`GroupBasedCommitment`] is a variant of commitment schemes built on groups +/// (elliptic curves). pub trait GroupBasedCommitment: CommitmentDef::Commitment>> + CommitmentOps { + /// [`GroupBasedCommitment::Gadget1`] points to the in-circuit gadget for + /// the group-based commitment scheme over the curve's base field. type Gadget1: CommitmentOpsGadget + CommitmentDefGadget< ConstraintField = CF2, ScalarVar = EmulatedFieldVar, Self::Scalar>, CommitmentVar = ::Var, - Native = Self, + Widget = Self, >; + /// [`GroupBasedCommitment::Gadget2`] points to the in-circuit gadget for + /// the group-based commitment scheme over the curve's scalar field. type Gadget2: CommitmentDefGadget< ConstraintField = Self::Scalar, ScalarVar = FpVar, CommitmentVar = EmulatedAffineVar, - Native = Self, + Widget = Self, >; } diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index e51ef8edf..7d5c6c295 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -1,3 +1,10 @@ +//! Implementation of the Pedersen commitment scheme, including out-of-circuit +//! widgets and in-circuit gadgets. +//! +//! The Pedersen commitment to a vector `v` is computed as ` + h · r`, +//! where `g` and `h` are generators, `r` is a random scalar, and `` is +//! the multi-scalar multiplication of `g` and `v`. + use ark_r1cs_std::{ boolean::Boolean, convert::ToBitsGadget, eq::EqGadget, fields::fp::FpVar, groups::CurveVar, }; @@ -12,10 +19,12 @@ use crate::{ utils::null::Null, }; +/// [`PedersenKey`] stores the public parameters for the Pedersen commitment +/// scheme, where `H` controls whether the scheme is hiding or not. #[derive(Clone)] pub struct PedersenKey { - pub g: Vec, - pub h: C, + g: Vec, + h: C, } impl CommitmentKey for PedersenKey { @@ -58,16 +67,13 @@ impl PedersenKey { } } +/// [`Pedersen`] defines the out-of-circuit Pedersen widget, where `H` controls +/// whether the scheme is hiding or not. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Pedersen { _c: PhantomData, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PedersenEmulatedGadget { - _c: PhantomData, -} - impl CommitmentDef for Pedersen { const IS_HIDING: bool = false; @@ -86,65 +92,6 @@ impl CommitmentDef for Pedersen { type Randomness = C::ScalarField; } -impl CommitmentDefGadget for PedersenGadget { - type ConstraintField = CF2; - - type KeyVar = Vec; - - type ScalarVar = EmulatedFieldVar, CF1>; - - type CommitmentVar = C::Var; - - type RandomnessVar = Null; - - type Native = Pedersen; -} - -impl CommitmentDefGadget for PedersenGadget { - type ConstraintField = CF2; - - type KeyVar = (Vec, C::Var); - - type ScalarVar = EmulatedFieldVar, CF1>; - - type CommitmentVar = C::Var; - - type RandomnessVar = EmulatedFieldVar, CF1>; - - type Native = Pedersen; -} - -impl CommitmentDefGadget for PedersenEmulatedGadget { - type ConstraintField = CF1; - - type KeyVar = Vec, C>>; - - type ScalarVar = FpVar>; - - type CommitmentVar = EmulatedAffineVar, C>; - - type RandomnessVar = Null; - - type Native = Pedersen; -} - -impl CommitmentDefGadget for PedersenEmulatedGadget { - type ConstraintField = CF1; - - type KeyVar = ( - Vec, C>>, - EmulatedAffineVar, C>, - ); - - type ScalarVar = FpVar>; - - type CommitmentVar = EmulatedAffineVar, C>; - - type RandomnessVar = FpVar>; - - type Native = Pedersen; -} - impl GroupBasedCommitment for Pedersen { type Gadget1 = PedersenGadget; type Gadget2 = PedersenEmulatedGadget; @@ -196,98 +143,18 @@ impl CommitmentOps for Pedersen { } } +/// [`PedersenGadget`] defines the in-circuit Pedersen gadget that operates over +/// the base field of the curve and supports canonical elliptic curve point +/// variables as commitments, where `H` controls whether the scheme is hiding or +/// not. #[derive(Clone)] pub struct PedersenGadget { _c: PhantomData, } -// fn joint_scalar_mul_be>( -// p: &Projective

, -// q: &Projective

, -// bits1: impl Iterator>, -// bits2: impl Iterator>, -// ) -> Result>, SynthesisError> { -// // prepare bits decomposition -// let mut bits1 = bits1.collect::>(); -// if bits1.len() == 0 { -// return Ok(ProjectiveVar::zero()); -// } -// // Remove unnecessary constant zeros in the most-significant positions. -// bits1 = bits1 -// .into_iter() -// // We iterate from the MSB down. -// .rev() -// // Skip leading zeros, if they are constants. -// .skip_while(|b| b.is_constant() && (b.value().unwrap() == false)) -// .collect(); - -// let mut bits2 = bits2.collect::>(); -// if bits2.len() == 0 { -// return Ok(ProjectiveVar::zero()); -// } -// // Remove unnecessary constant zeros in the most-significant positions. -// bits2 = bits2 -// .into_iter() -// // We iterate from the MSB down. -// .rev() -// // Skip leading zeros, if they are constants. -// .skip_while(|b| b.is_constant() && (b.value().unwrap() == false)) -// .collect(); - -// let acc = p.double().into_affine(); -// let sum = (p + q).into_affine(); -// let diff = (p - q).into_affine(); - -// let (sum_x, sum_y) = (FpVar::Constant(sum.x), FpVar::Constant(sum.y)); -// let (diff_x, diff_y) = (FpVar::Constant(diff.x), FpVar::Constant(diff.y)); -// let (mut x, mut y) = (FpVar::Constant(acc.x), FpVar::Constant(acc.y)); - -// // double-and-add loop -// for (bit1, bit2) in (bits1.iter().rev().skip(1).rev()).zip(bits2.iter().rev().skip(1).rev()) { -// let xor = *bit1 ^ *bit2; -// let xx = xor.select(&diff_x, &sum_x)?; -// let yy = xor.select(&diff_y, &sum_y)?; -// let yy = bit1.select(&yy, &yy.negate()?)?; - -// if [&x, &y].is_constant() || ([&xx, &yy].is_constant()) { -// let p = NonZeroAffineVar::new(x.clone(), y.clone()) -// .double()? -// .add_unchecked(&NonZeroAffineVar::new(xx, yy))?; -// x = p.x; -// y = p.y; -// } else { -// let lambda_1 = (&yy - &y).mul_by_inverse_unchecked(&(&xx - &x))?; -// let lambda_1_square = lambda_1.square()?; - -// let lambda_2 = y -// .mul_by_inverse_unchecked(&(&x.double()? + &xx - &lambda_1_square))? -// .double()? -// - lambda_1; - -// let x4 = lambda_2.square()? - lambda_1_square + &xx; -// let y4 = lambda_2 * &(&x - &x4) - &y; -// x = x4; -// y = y4; -// }; -// } - -// let mut acc = NonZeroAffineVar::new(x, y); -// // last bit -// aff1_neg = aff1_neg.add_unchecked(&acc)?; -// acc = bits1[bits1.len() - 1].select(&acc, &aff1_neg)?; -// aff2_neg = aff2_neg.add_unchecked(&acc)?; -// acc = bits2[bits1.len() - 1].select(&acc, &aff2_neg)?; - -// acc.into_projective().add_mixed(&{ -// let mut p = diff; -// for _ in 0..bits1.len() - 1 { -// p = p.double()?; -// } -// NonZeroAffineVar::new(p.x, p.y.negate()?) -// }) -// } - impl PedersenGadget { + /// [`PedersenGadget::msm`] performs multi-scalar multiplication in-circuit + /// with the given generators `g` and scalar bits `v`. fn msm(g: &[C::Var], v: &[Vec>>]) -> Result { let mut res = C::Var::zero(); let n = v.len(); @@ -346,6 +213,74 @@ impl CommitmentOpsGadget for PedersenGadget { } } +/// [`PedersenEmulatedGadget`] defines the in-circuit Pedersen gadget that +/// operates over the scalar field of the curve and supports emulated elliptic +/// curve point variables as commitments, where `H` controls whether the scheme +/// is hiding or not. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PedersenEmulatedGadget { + _c: PhantomData, +} + +impl CommitmentDefGadget for PedersenGadget { + type ConstraintField = CF2; + + type KeyVar = Vec; + + type ScalarVar = EmulatedFieldVar, CF1>; + + type CommitmentVar = C::Var; + + type RandomnessVar = Null; + + type Widget = Pedersen; +} + +impl CommitmentDefGadget for PedersenGadget { + type ConstraintField = CF2; + + type KeyVar = (Vec, C::Var); + + type ScalarVar = EmulatedFieldVar, CF1>; + + type CommitmentVar = C::Var; + + type RandomnessVar = EmulatedFieldVar, CF1>; + + type Widget = Pedersen; +} + +impl CommitmentDefGadget for PedersenEmulatedGadget { + type ConstraintField = CF1; + + type KeyVar = Vec, C>>; + + type ScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = Null; + + type Widget = Pedersen; +} + +impl CommitmentDefGadget for PedersenEmulatedGadget { + type ConstraintField = CF1; + + type KeyVar = ( + Vec, C>>, + EmulatedAffineVar, C>, + ); + + type ScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = FpVar>; + + type Widget = Pedersen; +} + #[cfg(test)] mod tests { use ark_bn254::G1Projective; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 8e76b75ab..ddbe91c8a 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,3 +1,13 @@ +#![warn(missing_docs)] + +//! This crate provides the foundational primitives used throughout Sonobe's +//! folding scheme and IVC implementations. +//! +//! It includes algebraic abstractions (fields, groups, and their in-circuit +//! emulated counterparts), constraint system arithmetizations (R1CS, CCS), +//! commitment schemes, transcript/sponge constructions, sum-check protocols, +//! and various utility types. + pub mod algebra; pub mod arithmetizations; pub mod circuits; diff --git a/crates/primitives/src/relations/mod.rs b/crates/primitives/src/relations/mod.rs index 529af6565..7c055bdbf 100644 --- a/crates/primitives/src/relations/mod.rs +++ b/crates/primitives/src/relations/mod.rs @@ -1,23 +1,42 @@ +//! This module defines the core relation traits for generic witness-instance +//! satisfaction checks and satisfying pair generation. +//! +//! These traits are intentionally generic so that different arithmetizations +//! (R1CS, CCS) and different forms (plain, relaxed) can all implement them. + use ark_relations::gr1cs::SynthesisError; use ark_std::{error::Error, rand::RngCore}; +/// [`Relation`] checks whether a witness `W` and an instance `U` satisfy the +/// specified relation. pub trait Relation { + /// [`Relation::Error`] defines the error type that may occur when checking + /// the relation. type Error: Error; - /// Checks if witness `w` and instance `u` satisfy the relation `self` + /// [`Relation::check_relation`] returns `Ok(())` when `w` and `u` satisfy + /// `self`, or an error otherwise. fn check_relation(&self, w: &W, u: &U) -> Result<(), Self::Error>; } +/// [`RelationGadget`] is the in-circuit counterpart of [`Relation`]. pub trait RelationGadget { - /// Checks if witness `w` and instance `u` satisfy the relation `self` + /// [`RelationGadget::check_relation`] generates constraints enforcing that + /// `w` and `u` satisfy the relation. fn check_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError>; } -/// `WitnessInstanceSampler` allows sampling a random witness-instance pair that -/// satisfies the relation `self`. +/// [`WitnessInstanceSampler`] allows sampling a random witness-instance pair +/// that satisfies the relation. pub trait WitnessInstanceSampler { + /// [`WitnessInstanceSampler::Source`] defines the type of the source from + /// which a satisfying pair is sampled. type Source; + + /// [`WitnessInstanceSampler::Error`] defines the error type that may occur + /// when sampling a satisfying pair. type Error: Error; + /// [`WitnessInstanceSampler::sample`] draws a random satisfying pair. fn sample(&self, source: Self::Source, rng: impl RngCore) -> Result<(W, U), Self::Error>; } diff --git a/crates/primitives/src/sumcheck/circuits.rs b/crates/primitives/src/sumcheck/circuits.rs index b79967e9e..bb91b3ee8 100644 --- a/crates/primitives/src/sumcheck/circuits.rs +++ b/crates/primitives/src/sumcheck/circuits.rs @@ -1,8 +1,37 @@ -/// Heavily inspired from testudo: https://github.com/cryptonetlab/testudo/tree/master -/// Some changes: -/// - Typings to better stick to ark_poly's API -/// - Uses `folding-schemes`' own `TranscriptVar` trait and `PoseidonTranscriptVar` struct -/// - API made closer to gadgets found in `folding-schemes` +//! In-circuit verifier gadget for the sumcheck protocol. +//! +//! The code is forked from Testudo's sumcheck circuit [implementation] and +//! modified to fit Sonobe's design & use case. +//! +//! [implementation]: https://github.com/cryptonetlab/testudo/blob/7db2d30972ce72ee7622070a1debc3b72580f4c7/src/constraints.rs#L116-L143 + +// Below we attach Testudo's original license notice. +// (Note: since the Testudo repo was forked from Microsoft's Spartan repo but no +// modifications were made to the license in Testudo, their copyright notice +// still credits Microsoft.) +// +// MIT License +// +// Copyright (c) Microsoft Corporation. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + use ark_ff::PrimeField; use ark_r1cs_std::{ eq::EqGadget, @@ -11,16 +40,28 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::SynthesisError; -use crate::{sumcheck::utils::VPAuxInfo, transcripts::TranscriptVar}; - -pub struct IOPSumCheckGadget; - -impl IOPSumCheckGadget { +use crate::{sumcheck::utils::VPAuxInfo, transcripts::TranscriptGadget}; + +/// [`SumCheckGadget`] is the in-circuit sumcheck verifier gadget. +pub struct SumCheckGadget; + +impl SumCheckGadget { + /// [`SumCheckGadget::verify`] provides an implementation of the sumcheck + /// verification algorithm in circuit. + /// + /// Given the claimed sum `claimed_sum = z`, the proof `proofs` (i.e., round + /// polynomials `g_1, ..., g_n`), the auxiliary info `aux_info`, and the + /// transcript `transcript`. + /// It returns the final evaluation `z_{n+1} = f(r_1, ..., r_n)` and the + /// Fiat-Shamir challenges `r_1, ..., r_n`. + /// + /// It mirrors the verifier widget [`super::SumCheck::verify`] with exactly + /// the same logic. pub fn verify( - claimed_sum: FpVar, + mut claimed_sum: FpVar, proofs: &Vec>>, aux_info: &VPAuxInfo, - transcript: &mut impl TranscriptVar, + transcript: &mut impl TranscriptGadget, ) -> Result<(FpVar, Vec>), SynthesisError> { transcript.add(&FpVar::constant(F::from(aux_info.num_variables as u64)))?; transcript.add(&FpVar::constant(F::from(aux_info.max_degree as u64)))?; @@ -29,7 +70,6 @@ impl IOPSumCheckGadget { } let mut challenges = Vec::with_capacity(aux_info.num_variables); - let mut expected = claimed_sum; for coeffs in proofs { if coeffs.len() - 1 != aux_info.max_degree { @@ -39,16 +79,17 @@ impl IOPSumCheckGadget { let eval_at_zero = &coeffs[0]; let eval_at_one = coeffs.iter().sum::>(); - (eval_at_zero + eval_at_one).enforce_equal(&expected)?; + (eval_at_zero + eval_at_one).enforce_equal(&claimed_sum)?; transcript.add(&coeffs)?; let challenge = transcript.challenge_field_element()?; - expected = DensePolynomialVar::from_coefficients_slice(coeffs).evaluate(&challenge)?; + claimed_sum = + DensePolynomialVar::from_coefficients_slice(coeffs).evaluate(&challenge)?; challenges.push(challenge); } - Ok((expected, challenges)) + Ok((claimed_sum, challenges)) } } @@ -72,7 +113,7 @@ mod tests { use super::*; use crate::{ - sumcheck::{IOPSumCheck, utils::VirtualPolynomial}, + sumcheck::{SumCheck, utils::VirtualPolynomial}, transcripts::poseidon::poseidon_canonical_config, }; @@ -88,18 +129,18 @@ mod tests { let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, One::one()); let aux_info = virtual_poly.aux_info.clone(); - let (proofs, challenges, _) = IOPSumCheck::prove(virtual_poly, &mut transcript_p)?; + let (proofs, challenges, _) = SumCheck::prove(virtual_poly, &mut transcript_p)?; let poly = DensePolynomial::from_coefficients_slice(&proofs[0]); let claimed_sum = poly.evaluate(&One::one()) + poly.evaluate(&Zero::zero()); let (expected, _) = - IOPSumCheck::verify(claimed_sum, &proofs, &aux_info, &mut transcript_v)?; + SumCheck::verify(claimed_sum, &proofs, &aux_info, &mut transcript_v)?; let cs = ConstraintSystem::new_ref(); let mut transcript_var = PoseidonSpongeVar::new(&poseidon_config); - let (expected_var, challenges_var) = IOPSumCheckGadget::verify( + let (expected_var, challenges_var) = SumCheckGadget::verify( FpVar::new_witness(cs.clone(), || Ok(claimed_sum))?, &proofs .into_iter() diff --git a/crates/primitives/src/sumcheck/mod.rs b/crates/primitives/src/sumcheck/mod.rs index d42d78a1c..7d131c708 100644 --- a/crates/primitives/src/sumcheck/mod.rs +++ b/crates/primitives/src/sumcheck/mod.rs @@ -1,13 +1,34 @@ -// code forked from: -// https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check -// -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . +//! This module implements the sumcheck protocol and its in-circuit gadgets for +//! verification. +//! +//! The code is forked from HyperPlonk's sumcheck [implementation] and modified +//! to fit Sonobe's design & use case. +//! +//! [implementation]: https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check -//! This module implements the sum check protocol. +// Below we attach HyperPlonk's original license notice. +// +// The MIT License (MIT) +// +// Copyright (c) 2022 Espresso Systems (espressosys.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. use ark_ff::PrimeField; use ark_poly::{ @@ -27,20 +48,57 @@ use crate::transcripts::{Absorbable, Transcript}; pub mod circuits; pub mod utils; +/// [`Error`] enumerates possible errors during the sumcheck protocol. #[derive(Debug, Error)] pub enum Error { + /// [`Error::IncorrectEvaluation`] indicates that the evaluation does not + /// match the claimed value. #[error("Incorrect evaluation: claimed {0}, got {1}")] IncorrectEvaluation(String, String), + /// [`Error::UnexpectedProofLength`] indicates that the proof length does + /// not match the expected length. #[error("Incorrect proof length: expected {0}, got {1}")] UnexpectedProofLength(usize, usize), + /// [`Error::UnexpectedPolynomialDegree`] indicates that the polynomial + /// degree exceeds the expected degree. #[error("Unexpected polynomial degree: expected at most {0}, got {1}")] UnexpectedPolynomialDegree(usize, usize), } +/// [`SumCheck`] implements the sumcheck protocol. +/// +/// In the sumcheck protocol, a prover wants to convince a verifier that the sum +/// of a multilinear polynomial `f` over the Boolean hypercube equals a claimed +/// value `z`, i.e., `∑_{x_1, ..., x_n ∈ {0,1}} f(x_1, ..., x_n) = z`, without +/// having the verifier evaluate the sum themselves. +/// +/// To this end, the prover and verifier engage in `n` rounds of interaction. +/// In each round `i`, we consider a variant of the original problem: given +/// polynomial `f_i` of `n - i + 1` variables `x_i, ..., x_n` and a claim `z_i`, +/// check if `∑_{x_i, ..., x_n ∈ {0,1}} f_i(x_i, ..., x_n) = z_i`. +/// The prover and the verifier's goal is to reduce this problem to the next +/// round's problem, where the new polynomial and claim are defined as: +/// - `f_{i+1}(x_{i+1}, ..., x_n) = f_i(r_i, x_{i+1}, ..., x_n)` for a random +/// `r_i` +/// - `z_{i+1} = ∑_{x_{i+1}, ..., x_n ∈ {0,1}} f_i(r_i, x_{i+1}, ..., x_n)` +/// +/// Such a reduction is achieved by the following steps: +/// 1. The prover sends to the verifier the univariate polynomial +/// `g_i(x_i) = ∑_{x_{i+1}, ..., x_n ∈ {0,1}} f_i(x_i, x_{i+1}, ..., x_n)`. +/// 2. The verifier checks if the current claim `z_i = g_i(0) + g_i(1)`. +/// 3. The verifier sends to the prover a random challenge `r_i`. +/// 4. Both parties prepares for the next round's polynomial +/// `f_{i+1}(x_{i+1}, ..., x_n) = f_i(r_i, x_{i+1}, ..., x_n)` and claim +/// `z_{i+1} = g_i(r_i)`, until the last round where all variables are fixed. #[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] -pub struct IOPSumCheck; +pub struct SumCheck; -impl IOPSumCheck { +impl SumCheck { + /// [`SumCheck::prove`] runs the prover of the sumcheck protocol over a + /// [`VirtualPolynomial`] `poly = f` with the given `transcript`. + /// It returns the proof (i.e., round polynomials `g_1, ..., g_n`), + /// Fiat-Shamir challenges `r_1, ..., r_n`, and the final "polynomial" with + /// all variables fixed (i.e., the evaluation `f_{n+1} = f(r_1, ..., r_n)`). #[allow(clippy::type_complexity)] pub fn prove( mut poly: VirtualPolynomial, @@ -61,7 +119,7 @@ impl IOPSumCheck { let mut products_sum = vec![F::ZERO; poly.aux_info.max_degree + 1]; // Step 2: generate sum for the partial evaluated polynomial: - // f(r_1, ... r_m,, x_{m+1}... x_n) + // `f_i(x_i, ..., x_n) = f(r_1, ... r_{i-1}, x_i, ..., x_n)` poly.products.iter().for_each(|(coefficient, products)| { #[cfg(feature = "parallel")] @@ -155,6 +213,12 @@ impl IOPSumCheck { Ok((prover_msgs, challenges, poly.flattened_ml_extensions)) } + /// [`SumCheck::verify`] runs the verifier of the sumcheck protocol given + /// the claimed sum `claimed_sum = z`, the proof `proofs` (i.e., round + /// polynomials `g_1, ..., g_n`), the auxiliary info `aux_info`, and the + /// transcript `transcript`. + /// It returns the final evaluation `z_{n+1} = f(r_1, ..., r_n)` and the + /// Fiat-Shamir challenges `r_1, ..., r_n`. pub fn verify( mut claimed_sum: F, proofs: &[Vec], @@ -186,7 +250,7 @@ impl IOPSumCheck { let eval_at_one = coeffs.iter().sum::(); // the deferred check during the interactive phase: - // 1. check if the received 'P(0) + P(1) = claimed_sum`. + // 1. check if the received 'g_i(0) + g_i(1) = z_i`. if eval_at_zero + eval_at_one != claimed_sum { return Err(Error::IncorrectEvaluation( claimed_sum.to_string(), @@ -197,7 +261,7 @@ impl IOPSumCheck { transcript.add(coeffs); let challenge = transcript.challenge_field_element(); - // 2. set `expected` to `P(r)` + // 2. set next `z_{i+1}` to `g_i(r_i)` claimed_sum = DensePolynomial::from_coefficients_slice(coeffs).evaluate(&challenge); challenges.push(challenge); } @@ -207,7 +271,7 @@ impl IOPSumCheck { } #[cfg(test)] -pub mod tests { +mod tests { use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_ff::Field; use ark_pallas::Fr; @@ -220,40 +284,42 @@ pub mod tests { use crate::transcripts::poseidon::poseidon_canonical_config; #[test] - pub fn sumcheck_poseidon() -> Result<(), Error> { + fn test_sumcheck() -> Result<(), Error> { let n_vars = 10; let mut rng = thread_rng(); let poly_mle = DenseMultilinearExtension::rand(n_vars, &mut rng); - let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, Fr::ONE); - sumcheck_poseidon_opt(virtual_poly)?; + test_sumcheck_opt(poly_mle)?; // test with zero poly let poly_mle = DenseMultilinearExtension::from_evaluations_vec( n_vars, vec![Fr::zero(); 2usize.pow(n_vars as u32)], ); - let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, Fr::ONE); - sumcheck_poseidon_opt(virtual_poly)?; + test_sumcheck_opt(poly_mle)?; Ok(()) } - fn sumcheck_poseidon_opt(virtual_poly: VirtualPolynomial) -> Result<(), Error> { + fn test_sumcheck_opt(poly_mle: DenseMultilinearExtension) -> Result<(), Error> { + let virtual_poly = VirtualPolynomial::new_from_mle(poly_mle, Fr::ONE); + let aux_info = virtual_poly.aux_info.clone(); let poseidon_config = poseidon_canonical_config::(); // sum-check prove let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let (proofs, _, _) = IOPSumCheck::prove(virtual_poly, &mut transcript_p)?; + let (proofs, challenges_p, eval_p) = SumCheck::prove(virtual_poly, &mut transcript_p)?; // sum-check verify let poly = DensePolynomial::from_coefficients_slice(&proofs[0]); let claimed_sum = poly.evaluate(&Fr::one()) + poly.evaluate(&Fr::zero()); let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let res_verify = IOPSumCheck::verify(claimed_sum, &proofs, &aux_info, &mut transcript_v); + let (eval_v, challenges_v) = + SumCheck::verify(claimed_sum, &proofs, &aux_info, &mut transcript_v)?; - assert!(res_verify.is_ok()); + assert_eq!(eval_p[0].evaluate(&vec![]), eval_v); + assert_eq!(challenges_p, challenges_v); Ok(()) } } diff --git a/crates/primitives/src/sumcheck/utils.rs b/crates/primitives/src/sumcheck/utils.rs index 917ba6d2e..184e0f7e0 100644 --- a/crates/primitives/src/sumcheck/utils.rs +++ b/crates/primitives/src/sumcheck/utils.rs @@ -1,14 +1,34 @@ -// code forked from -// https://github.com/privacy-scaling-explorations/multifolding-poc/blob/main/src/espresso/virtual_polynomial.rs +//! Virtual polynomial implementation and polynomial utilities. +//! +//! The code is forked from our previous [multifolding PoC implementation], +//! which is itself forked from HyperPlonk's [virtual polynomial code]. +//! +//! [multifolding PoC implementation]: https://github.com/privacy-scaling-explorations/multifolding-poc/blob/main/src/espresso/virtual_polynomial.rs, +//! [virtual polynomial code]: https://github.com/EspressoSystems/hyperplonk/blob/main/arithmetic/src/virtual_polynomial.rs + +// Below we attach HyperPlonk's original license notice. // -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the HyperPlonk library. - -// You should have received a copy of the MIT License -// along with the HyperPlonk library. If not, see . - -//! This module defines our main mathematical object `VirtualPolynomial`; and -//! various functions associated with it. +// The MIT License (MIT) +// +// Copyright (c) 2022 Espresso Systems (espressosys.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. use ark_ff::{Field, PrimeField, batch_inversion}; use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, univariate::DensePolynomial}; @@ -18,7 +38,7 @@ use ark_std::cfg_into_iter; #[cfg(feature = "parallel")] use rayon::prelude::*; -/// A virtual polynomial is a sum of products of multilinear polynomials; +/// [`VirtualPolynomial`] is a sum of products of multilinear polynomials; /// where the multilinear polynomials are stored via their multilinear /// extensions: `(coefficient, DenseMultilinearExtension)` /// @@ -42,19 +62,25 @@ use rayon::prelude::*; /// #[derive(Clone, Debug, Default, PartialEq)] pub struct VirtualPolynomial { - /// Aux information about the multilinear polynomial + /// [`VirtualPolynomial::aux_info`] is the aux information about the + /// multilinear polynomial. pub aux_info: VPAuxInfo, + /// [`VirtualPolynomial::flattened_ml_extensions`] stores multilinear + /// extensions in which product multiplicand can refer to. pub flattened_ml_extensions: Vec>, - /// list of reference to products (as usize) of multilinear extension + /// [`VirtualPolynomial::products`] is a list of reference to products + /// (as usize) of multilinear extension pub products: Vec<(F, Vec)>, } #[derive(Clone, Debug, Default, PartialEq, Eq, CanonicalSerialize)] -/// Auxiliary information about the multilinear polynomial +/// [`VPAuxInfo`] is auxiliary information about the multilinear polynomial. pub struct VPAuxInfo { - /// max number of multiplicands in each product + /// [`VPAuxInfo::max_degree`] is the max number of multiplicands in each + /// product. pub max_degree: usize, - /// number of variables of the polynomial + /// [`VPAuxInfo::num_variables`] is the number of variables of the + /// polynomial. pub num_variables: usize, } @@ -74,14 +100,13 @@ impl VirtualPolynomial { } } -/// `EqPoly` represents the following polynomial: -/// -/// `eq(x, y) = \prod_{i=1}^n (x_i * y_i + (1 - x_i) * (1 - y_i))` +/// [`EqPoly`] represents the multilinear equality polynomial +/// `eq(x, y) = Π_{i ∈ {0,1}} (x_i y_i + (1 - x_i)(1 - y_i))`. pub struct EqPoly; impl EqPoly { - /// This function builds `eq(x, y)` by fixing `y = r` and outputting the - /// evaluations over all `x` in `[0, 2^n)`. + /// [`EqPoly::fix_y_evals`] function evaluates `eq(x, y)` by fixing `y = r` + /// and outputting the evaluations over all `x` in `[0, 2^n)`. pub fn fix_y_evals(r: &[F]) -> Vec { // we build eq(x,r) from its evaluations // we want to evaluate eq(x,r) over all binary strings `x` of length `n` @@ -113,7 +138,7 @@ impl EqPoly { buf } - /// Evaluate eq polynomial. + /// [`EqPoly::fix_xy_eval`] evaluates `eq(x, y)` by fixing both `x` and `y`. pub fn fix_xy_eval(x: &[F], y: &[F]) -> F { debug_assert_eq!(x.len(), y.len()); x.iter() @@ -123,10 +148,12 @@ impl EqPoly { } } -pub struct EqPolyVar; +/// [`EqPolyGadget`] is the in-circuit gadget of [`EqPoly`]. +pub struct EqPolyGadget; -impl EqPolyVar { - /// Evaluate eq polynomial in circuit. +impl EqPolyGadget { + /// [`EqPolyGadget::fix_xy_eval`] evaluates `eq(x, y)` in-circuit by fixing + /// both `x` and `y`. pub fn fix_xy_eval(x: &[FpVar], y: &[FpVar]) -> FpVar { debug_assert_eq!(x.len(), y.len()); let mut eval = FpVar::::one(); @@ -137,6 +164,10 @@ impl EqPolyVar { } } +/// [`barycentric_weights`] computes the barycentric weights for a given set of +/// evaluation `points`. +/// +/// Used to extrapolate polynomial evaluations via the barycentric formula. #[allow(clippy::filter_map_bool_then)] pub fn barycentric_weights(points: &[F]) -> Vec { let mut weights = points @@ -155,6 +186,8 @@ pub fn barycentric_weights(points: &[F]) -> Vec { weights } +/// [`extrapolate`] extrapolates the polynomial defined by `(points, evals)` to +/// a new point `at`, using the precomputed barycentric `weights`. pub fn extrapolate(points: &[F], weights: &[F], evals: &[F], at: &F) -> F { let (coeffs, sum_inv) = { let mut coeffs = points.iter().map(|point| *at - point).collect::>(); @@ -173,7 +206,8 @@ pub fn extrapolate(points: &[F], weights: &[F], evals: &[F], at: * sum_inv } -/// Computes the lagrange interpolated polynomial from the given points `p_i` +/// [`compute_lagrange_interpolated_poly`] computes the lagrange interpolated +/// polynomial from the given points `p_i`. pub fn compute_lagrange_interpolated_poly(p_i: &[F]) -> DensePolynomial { let v = (0..p_i.len()) .map(|i| F::from(i as u64)) diff --git a/crates/primitives/src/traits.rs b/crates/primitives/src/traits.rs index 6162c5c4c..6ee687a85 100644 --- a/crates/primitives/src/traits.rs +++ b/crates/primitives/src/traits.rs @@ -1,9 +1,19 @@ +//! This module defines helper traits used across Sonobe's crates. + pub use crate::algebra::{ field::SonobeField, group::{CF1, CF2, SonobeCurve}, }; +/// [`Dummy`] provides a way to construct a placeholder ("dummy") value of a +/// given type, parameterized by some configuration `Cfg`. +/// +/// This is useful when initializing data structures that require a value of a +/// certain shape before the real data is available, e.g., when setting up the +/// initial state of a folding scheme. pub trait Dummy { + /// [`Dummy::dummy`] constructs a dummy value of `Self` based on the given + /// configuration `cfg`. fn dummy(cfg: Cfg) -> Self; } @@ -25,12 +35,15 @@ impl, B: Dummy> Dummy for (A, B) { } } -/// Converts a value `self` into a vector of field elements, ordered in the same -/// way as how a variable of type `Var` would be represented *natively* in the -/// circuit. +/// [`Inputize`] converts a value into a vector of field elements, ordered in +/// the same way as how the value's corresponding in-circuit variable would be +/// represented in the canonical way in the circuit when allocated as public +/// input. /// /// This is useful for the verifier to compute the public inputs. pub trait Inputize { + /// [`Inputize::inputize`] outputs the underlying field elements of `self` + /// as if it is allocated in the canonical way in-circuit. fn inputize(&self) -> Vec; } @@ -40,16 +53,19 @@ impl> Inputize for [T] { } } -/// Converts a value `self` into a vector of field elements, ordered in the same -/// way as how a variable of type `Var` would be represented *non-natively* in -/// the circuit. +/// [`InputizeEmulated`] converts a value into a vector of field elements, +/// ordered in the same way as how the value's corresponding in-circuit variable +/// would be represented in the emulated way in the circuit when allocated as +/// public input. /// /// This is useful for the verifier to compute the public inputs. /// /// Note that we require this trait because we need to distinguish between some -/// data types that are represented both natively and non-natively in-circuit -/// (e.g., field elements can have type `FpVar` and `NonNativeUintVar`). +/// data types that can be represented in both the canonical and emulated ways +/// in-circuit (e.g., field elements or elliptic curve points). pub trait InputizeEmulated { + /// [`InputizeEmulated::inputize_emulated`] outputs the underlying field + /// elements of `self` as if it is allocated in the emulated way in-circuit. fn inputize_emulated(&self) -> Vec; } diff --git a/crates/primitives/src/transcripts/absorbable.rs b/crates/primitives/src/transcripts/absorbable.rs index ba7de434c..0c2f2f7de 100644 --- a/crates/primitives/src/transcripts/absorbable.rs +++ b/crates/primitives/src/transcripts/absorbable.rs @@ -1,3 +1,9 @@ +//! This module defines traits for converting values into a form absorbable by a +//! sponge or transcript. +//! +//! Implementations are provided for some primitive types as well as composite +//! types (references, tuples, slices, etc.). + use ark_ff::PrimeField; use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::SynthesisError; @@ -35,14 +41,15 @@ use ark_relations::gr1cs::SynthesisError; // so I can define `SonobeField: for Absorbable`. // Personally I think the best option is 3. File an issue or submit a PR if you // have better solution :) +/// [`Absorbable`] is a trait for objects that can be absorbed into a sponge or +/// transcript. pub trait Absorbable { + /// [`Absorbable::absorb_into`] absorbs `self` into the given destination + /// vector of field elements. + /// + /// The implementation should append the field elements representing `self` + /// to `dest`. fn absorb_into(&self, dest: &mut Vec); - - fn to_absorbable(&self) -> Vec { - let mut result = Vec::new(); - self.absorb_into(&mut result); - result - } } impl Absorbable for usize { @@ -84,46 +91,45 @@ impl Absorbable for Vec { } } -/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field -/// is `F`. +/// [`AbsorbableVar`] is a trait for in-circuit variables that can be absorbed +/// into a sponge or transcript defined over constraint field `F`. /// -/// Matches `AbsorbGadget` in `ark-crypto-primitives`. -pub trait AbsorbableGadget { +/// Matches [`Absorbable`]. +pub trait AbsorbableVar { + /// [`AbsorbableVar::absorb_into`] absorbs `self` into the given + /// destination vector of field element variables. + /// + /// The implementation should append the field element variables + /// representing `self` to `dest`. fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError>; - - fn to_absorbable(&self) -> Result>, SynthesisError> { - let mut result = Vec::new(); - self.absorb_into(&mut result)?; - Ok(result) - } } -impl> AbsorbableGadget for &T { +impl> AbsorbableVar for &T { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { (*self).absorb_into(dest) } } -impl> AbsorbableGadget for (T, T) { +impl> AbsorbableVar for (T, T) { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.0.absorb_into(dest)?; self.1.absorb_into(dest) } } -impl> AbsorbableGadget for [T] { +impl> AbsorbableVar for [T] { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.iter().try_for_each(|t| t.absorb_into(dest)) } } -impl, const N: usize> AbsorbableGadget for [T; N] { +impl, const N: usize> AbsorbableVar for [T; N] { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.as_ref().absorb_into(dest) } } -impl> AbsorbableGadget for Vec { +impl> AbsorbableVar for Vec { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.as_slice().absorb_into(dest) } diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index 518d69547..5660015d2 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -1,3 +1,43 @@ +//! Implementation of the Griffin circuit-friendly hash function and its +//! parameter generation, as well as out-of-circuit widgets and in-circuit +//! gadgets for permutation, hashing, sponges, and transcripts. +//! +//! According to the Griffin [paper], it is very efficient in terms of the +//! number of constraints, but later an [attack] on Griffin and similar hash +//! functions was discovered. +//! Therefore, it is recommended to avoid using Griffin in production. +//! +//! The code is forked from the [implementation] in the Hash Functions for +//! Zero-Knowledge Applications Zoo but uses arkworks instead of bellman as the +//! underlying cryptographic library. +//! +//! [paper]: https://eprint.iacr.org/2022/403.pdf +//! [attack]: https://eprint.iacr.org/2024/347.pdf +//! [implementation]: https://extgit.isec.tugraz.at/krypto/zkfriendlyhashzoo + +// Below we attach Hash functions for Zero-Knowledge applications Zoo's original +// license notice. +// +// Copyright (c) 2021 Graz University of Technology +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + use ark_ff::{LegendreSymbol, PrimeField}; use ark_r1cs_std::{ GR1CSVar, @@ -13,42 +53,28 @@ use sha3::{ pub mod sponge; -pub fn field_element_from_shake(reader: &mut impl XofReader) -> F { - let mut buf = vec![0u8; F::MODULUS_BIT_SIZE.div_ceil(8) as usize]; - - loop { - reader.read(&mut buf); - if let Some(el) = F::from_random_bytes(&buf) { - return el; - } - } -} - -pub fn field_element_from_shake_without_0(reader: &mut impl XofReader) -> F { - loop { - let element = field_element_from_shake::(reader); - if !element.is_zero() { - return element; - } - } -} - +/// [`GriffinParams`] stores the full parameterisation of the Griffin +/// permutation for a given prime field: state width `t`, S-box degree `d`, +/// number of rounds, round constants, alpha/beta constants, and the MDS-like +/// matrix. #[derive(Clone, Debug)] pub struct GriffinParams { - pub(crate) round_constants: Vec>, - pub(crate) t: usize, - pub(crate) d: usize, - pub(crate) d_inv: Vec, - pub(crate) rounds: usize, - pub(crate) alpha_beta: Vec<[F; 2]>, - pub(crate) mat: Vec>, - pub rate: usize, - pub capacity: usize, + round_constants: Vec>, + t: usize, + d: usize, + d_inv: Vec, + rounds: usize, + alpha_beta: Vec<[F; 2]>, + mat: Vec>, + rate: usize, + capacity: usize, } impl GriffinParams { - pub const INIT_SHAKE: &'static str = "Griffin"; + const INIT_SHAKE: &'static str = "Griffin"; + /// [`GriffinParams::new`] constructs new Griffin parameters with the given + /// state width `t`, S-box degree `d`, and number of rounds `rounds`. pub fn new(t: usize, d: usize, rounds: usize) -> Self { // Equivalent to `assert!(t == 3 || t % 4 == 0);`, but bypass clippy's // warning about `is_multiple_of`. @@ -58,7 +84,9 @@ impl GriffinParams { let mut shake = Self::init_shake(); - let d_inv = Self::calculate_d_inv(d as u64) + let d_inv = BigUint::from(d) + .modinv(&(-F::one()).into()) + .unwrap() .to_radix_be(2) .into_iter() .map(|i| i != 0) @@ -82,11 +110,6 @@ impl GriffinParams { } } - fn calculate_d_inv(d: u64) -> BigUint { - let p_1 = -F::one(); - BigUint::from(d).modinv(&p_1.into()).unwrap() - } - fn init_shake() -> Shake128Reader { let mut shake = Shake128::default(); shake.update(Self::INIT_SHAKE.as_bytes()); @@ -97,12 +120,36 @@ impl GriffinParams { } fn instantiate_rc(t: usize, rounds: usize, shake: &mut Shake128Reader) -> Vec> { + fn field_element_from_shake(reader: &mut impl XofReader) -> F { + let mut buf = vec![0u8; F::MODULUS_BIT_SIZE.div_ceil(8) as usize]; + + loop { + reader.read(&mut buf); + if let Some(element) = F::from_random_bytes(&buf) { + return element; + } + } + } + (0..rounds - 1) .map(|_| (0..t).map(|_| field_element_from_shake(shake)).collect()) .collect() } fn instantiate_alpha_beta(t: usize, shake: &mut Shake128Reader) -> Vec<[F; 2]> { + fn field_element_from_shake_without_0(reader: &mut impl XofReader) -> F { + let mut buf = vec![0u8; F::MODULUS_BIT_SIZE.div_ceil(8) as usize]; + + loop { + reader.read(&mut buf); + if let Some(element) = F::from_random_bytes(&buf) + && !element.is_zero() + { + return element; + } + } + } + let mut alpha_beta = Vec::with_capacity(t - 2); // random alpha/beta @@ -154,22 +201,18 @@ impl GriffinParams { alpha_beta } - fn circ_mat(row: &[F]) -> Vec> { - let t = row.len(); - let mut mat: Vec> = Vec::with_capacity(t); - let mut rot = row.to_owned(); - mat.push(rot.clone()); - for _ in 1..t { - rot.rotate_right(1); - mat.push(rot.clone()); - } - mat - } - fn instantiate_matrix(t: usize) -> Vec> { if t == 3 { let row = vec![F::from(2), F::from(1), F::from(1)]; - Self::circ_mat(&row) + let t = row.len(); + let mut mat: Vec> = Vec::with_capacity(t); + let mut rot = row.to_owned(); + mat.push(rot.clone()); + for _ in 1..t { + rot.rotate_right(1); + mat.push(rot.clone()); + } + mat } else { let row1 = vec![F::from(5), F::from(7), F::from(1), F::from(3)]; let row2 = vec![F::from(4), F::from(6), F::from(1), F::from(1)]; @@ -197,14 +240,17 @@ impl GriffinParams { } } -impl GriffinParams { - fn affine_3(&self, input: &mut [S], round: usize) { +/// [`Griffin`] implements the Griffin permutation and Griffin hash. +pub struct Griffin; + +impl Griffin { + fn affine_3(params: &GriffinParams, input: &mut [F], round: usize) { // multiplication by circ(2 1 1) is equal to state + sum(state) let mut sum = input[0]; input.iter().skip(1).for_each(|el| sum.add_assign(el)); - if round < self.rounds - 1 { - for (el, rc) in input.iter_mut().zip(self.round_constants[round].iter()) { + if round < params.rounds - 1 { + for (el, rc) in input.iter_mut().zip(params.round_constants[round].iter()) { el.add_assign(&sum); el.add_assign(rc); // add round constant } @@ -216,7 +262,7 @@ impl GriffinParams { } } - fn affine_4(&self, input: &mut [S], round: usize) { + fn affine_4(params: &GriffinParams, input: &mut [F], round: usize) { let mut t_0 = input[0]; t_0.add_assign(&input[1]); let mut t_1 = input[2]; @@ -244,25 +290,25 @@ impl GriffinParams { input[2] = t_7; input[3] = t_4; - if round < self.rounds - 1 { - for (i, rc) in input.iter_mut().zip(self.round_constants[round].iter()) { + if round < params.rounds - 1 { + for (i, rc) in input.iter_mut().zip(params.round_constants[round].iter()) { i.add_assign(rc); } } } - fn affine(&self, input: &mut [S], round: usize) { - if self.t == 3 { - self.affine_3(input, round); + fn affine(params: &GriffinParams, input: &mut [F], round: usize) { + if params.t == 3 { + Griffin::affine_3(params, input, round); return; } - if self.t == 4 { - self.affine_4(input, round); + if params.t == 4 { + Griffin::affine_4(params, input, round); return; } // first matrix - let t4 = self.t / 4; + let t4 = params.t / 4; for i in 0..t4 { let start_index = i * 4; let mut t_0 = input[start_index]; @@ -275,7 +321,7 @@ impl GriffinParams { let mut t_3 = input[start_index + 3]; t_3.double_in_place(); t_3.add_assign(&t_0); - let mut t_4: S = t_1; + let mut t_4: F = t_1; t_4.double_in_place(); t_4.double_in_place(); t_4.add_assign(&t_3); @@ -290,7 +336,7 @@ impl GriffinParams { } // second matrix - let mut stored = [S::zero(); 4]; + let mut stored = [F::zero(); 4]; for l in 0..4 { stored[l] = input[l]; for j in 1..t4 { @@ -300,17 +346,17 @@ impl GriffinParams { for i in 0..input.len() { input[i].add_assign(&stored[i % 4]); - if round < self.rounds - 1 { - input[i].add_assign(&self.round_constants[round][i]); // add round constant + if round < params.rounds - 1 { + input[i].add_assign(¶ms.round_constants[round][i]); // add round constant } } } - fn non_linear(&self, input: &mut [S]) { + fn non_linear(params: &GriffinParams, input: &mut [F]) { // first two state words input[0] = { - let mut res = S::one(); - for &i in &self.d_inv { + let mut res = F::one(); + for &i in ¶ms.d_inv { res.square_in_place(); if i { res *= input[0]; @@ -322,7 +368,7 @@ impl GriffinParams { let mut state = input[1]; input[1].square_in_place(); - match self.d { + match params.d { 3 => {} 5 => { input[1].square_in_place(); @@ -336,35 +382,47 @@ impl GriffinParams { for i in 2..input.len() { y01_i += input[0]; let l = if i == 2 { y01_i } else { y01_i + state }; - let ab = &self.alpha_beta[i - 2]; + let ab = ¶ms.alpha_beta[i - 2]; state = input[i]; input[i] *= l.square() + l * ab[0] + ab[1]; } } - pub fn permute(&self, input: &mut [S]) { - self.affine(input, self.rounds); // no RC + /// [`Griffin::permute`] applies the Griffin permutation to the given input + /// state `input` in place under parameters `params`. + pub fn permute(params: &GriffinParams, input: &mut [F]) { + Griffin::affine(params, input, params.rounds); // no RC - for r in 0..self.rounds { - self.non_linear(input); - self.affine(input, r); + for r in 0..params.rounds { + Griffin::non_linear(params, input); + Griffin::affine(params, input, r); } } - pub fn hash(&self, message: &[S]) -> S { - let mut state = vec![S::zero(); self.t]; - for chunk in message.chunks(self.rate) { + /// [`Griffin::hash`] implements the Griffin hash function based on the + /// sponge construction, which produces a single field element as the digest + /// of the given message `message` under parameters `params`. + pub fn hash(params: &GriffinParams, message: &[F]) -> F { + let mut state = vec![F::zero(); params.t]; + for chunk in message.chunks(params.rate) { for i in 0..chunk.len() { state[i] += &chunk[i]; } - self.permute(&mut state) + Griffin::permute(params, &mut state) } state[0] } } -impl GriffinParams { - fn non_linear_gadget(&self, state: &[FpVar]) -> Result>, SynthesisError> { +/// [`GriffinGadget`] implements the gadgets for Griffin permutation and Griffin +/// hash. +pub struct GriffinGadget; + +impl GriffinGadget { + fn non_linear( + params: &GriffinParams, + state: &[FpVar], + ) -> Result>, SynthesisError> { let cs = state.cs(); let mut result = state.to_owned(); // x0 @@ -373,7 +431,7 @@ impl GriffinParams { { let v = result[0].value().unwrap_or_default(); let mut res = F::one(); - for &i in &self.d_inv { + for &i in ¶ms.d_inv { res.square_in_place(); if i { res *= v; @@ -385,14 +443,14 @@ impl GriffinParams { })?; let mut sq = result[0].square()?; - if self.d == 5 { + if params.d == 5 { sq = sq.square()?; } result[0].mul_equals(&sq, &state[0])?; // x1 let mut sq = result[1].square()?; - if self.d == 5 { + if params.d == 5 { sq = sq.square()?; } result[1] *= sq; @@ -407,32 +465,37 @@ impl GriffinParams { } else { &y01_i + &state[i - 1] }; - let ab = &self.alpha_beta[i - 2]; + let ab = ¶ms.alpha_beta[i - 2]; result[i] *= l.square()? + l * ab[0] + ab[1]; } Ok(result) } - pub fn permute_gadget(&self, state: &[FpVar]) -> Result>, SynthesisError> { + /// [`GriffinGadget::permute`] applies the Griffin permutation to the given + /// input state variables `input` in place under parameters `params`. + pub fn permute( + params: &GriffinParams, + state: &[FpVar], + ) -> Result>, SynthesisError> { let mut current_state = state.to_owned(); - current_state = self + current_state = params .mat .iter() .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) .collect(); - for r in 0..self.rounds { - current_state = self.non_linear_gadget(¤t_state)?; - current_state = self + for r in 0..params.rounds { + current_state = GriffinGadget::non_linear(params, ¤t_state)?; + current_state = params .mat .iter() .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) .collect(); - if r < self.rounds - 1 { + if r < params.rounds - 1 { current_state = current_state .iter() - .zip(&self.round_constants[r]) + .zip(¶ms.round_constants[r]) .map(|(c, rc)| c + *rc) .collect(); } @@ -440,13 +503,19 @@ impl GriffinParams { Ok(current_state) } - pub fn hash_gadget(&self, message: &[FpVar]) -> Result, SynthesisError> { - let mut state = vec![FpVar::zero(); self.t]; - for chunk in message.chunks(self.rate) { + /// [`GriffinGadget::hash`] implements the gadget for Griffin hash based on + /// the sponge construction, which produces a single field element variable + /// as the digest of the given message `message` under parameters `params`. + pub fn hash( + params: &GriffinParams, + message: &[FpVar], + ) -> Result, SynthesisError> { + let mut state = vec![FpVar::zero(); params.t]; + for chunk in message.chunks(params.rate) { for i in 0..chunk.len() { state[i] += &chunk[i]; } - state = self.permute_gadget(&state)?; + state = GriffinGadget::permute(params, &state)?; } Ok(state[0].clone()) } @@ -466,39 +535,28 @@ mod tests { #[test] fn test() -> Result<(), Box> { let rng = &mut thread_rng(); - let griffin = GriffinParams::new(24, 5, 9); - let t = griffin.t; + let params = GriffinParams::new(24, 5, 9); + let t = params.t; let x: Vec = (0..t).map(|_| Fr::rand(rng)).collect(); - let y = griffin.hash(&x); + let y = Griffin::hash(¶ms, &x); let cs = ConstraintSystem::new_ref(); let x_var = Vec::new_witness(cs.clone(), || Ok(x.clone()))?; - let y_var = griffin.hash_gadget(&x_var)?; + let y_var = GriffinGadget::hash(¶ms, &x_var)?; assert_eq!(y, y_var.value()?); println!("{}", cs.num_constraints()); assert!(cs.is_satisfied()?); Ok(()) } -} - -#[cfg(test)] -mod griffin_tests_bn256 { - use ark_bn254::Fr; - use ark_ff::UniformRand; - use ark_std::rand::thread_rng; - - use super::*; - - static TESTRUNS: usize = 5; #[test] - fn consistent_perm() { + fn test_consistent_perm() { let rng = &mut thread_rng(); - let griffin = GriffinParams::new(3, 5, 12); - let t = griffin.t; - for _ in 0..TESTRUNS { + let params = GriffinParams::new(3, 5, 12); + let t = params.t; + for _ in 0..5 { let input1: Vec<_> = (0..t).map(|_| Fr::rand(rng)).collect(); let mut input2: Vec<_>; @@ -512,9 +570,9 @@ mod griffin_tests_bn256 { let mut perm1 = input1.clone(); let mut perm2 = input1.clone(); let mut perm3 = input2.clone(); - griffin.permute(&mut perm1); - griffin.permute(&mut perm2); - griffin.permute(&mut perm3); + Griffin::permute(¶ms, &mut perm1); + Griffin::permute(¶ms, &mut perm2); + Griffin::permute(¶ms, &mut perm3); assert_eq!(perm1, perm2); assert_ne!(perm1, perm3); } @@ -534,40 +592,40 @@ mod griffin_tests_bn256 { out } - fn affine_test(t: usize) { + fn test_affine_opt(t: usize) { let rng = &mut thread_rng(); - let griffin = GriffinParams::::new(t, 5, 1); + let params = GriffinParams::::new(t, 5, 1); - let mat = &griffin.mat; + let mat = ¶ms.mat; - for _ in 0..TESTRUNS { + for _ in 0..5 { let input: Vec = (0..t).map(|_| F::rand(rng)).collect(); // affine 1 let output1 = matmul(&input, mat); let mut output2 = input.to_owned(); - griffin.affine(&mut output2, 1); + Griffin::affine(¶ms, &mut output2, 1); assert_eq!(output1, output2); } } #[test] - fn affine_3() { - affine_test::(3); + fn test_affine_3() { + test_affine_opt::(3); } #[test] - fn affine_4() { - affine_test::(4); + fn test_affine_4() { + test_affine_opt::(4); } #[test] - fn affine_8() { - affine_test::(8); + fn test_affine_8() { + test_affine_opt::(8); } #[test] - fn affine_60() { - affine_test::(60); + fn test_affine_60() { + test_affine_opt::(60); } } diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index ecf2e75e5..70cfa71ee 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -1,3 +1,5 @@ +//! Implementation of transcript traits for Griffin sponge. + use ark_crypto_primitives::sponge::DuplexSpongeMode; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ @@ -7,23 +9,24 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::SynthesisError; use ark_std::sync::Arc; -use crate::transcripts::{AbsorbableGadget, Transcript, TranscriptVar, griffin::GriffinParams}; +use crate::transcripts::{ + AbsorbableVar, Transcript, TranscriptGadget, + griffin::{Griffin, GriffinGadget, GriffinParams}, +}; +/// [`GriffinSponge`] is a duplex sponge built on the Griffin permutation. +/// +/// The implementation mirrors arkworks' [`ark_crypto_primitives::sponge::poseidon::PoseidonSponge`]. #[derive(Clone)] pub struct GriffinSponge { - /// Sponge Config - pub griffin: Arc>, - - // Sponge State - /// Current sponge's state (current elements in the permutation block) - pub state: Vec, - /// Current mode (whether its absorbing or squeezing) - pub mode: DuplexSpongeMode, + params: Arc>, + state: Vec, + mode: DuplexSpongeMode, } impl GriffinSponge { fn permute(&mut self) { - self.griffin.permute(&mut self.state); + Griffin::permute(&self.params, &mut self.state); } // Absorbs everything in elements, this does not end in an absorption. @@ -32,9 +35,9 @@ impl GriffinSponge { loop { // if we can finish in this call - if rate_start_index + remaining_elements.len() <= self.griffin.rate { + if rate_start_index + remaining_elements.len() <= self.params.rate { for (i, element) in remaining_elements.iter().enumerate() { - self.state[self.griffin.capacity + i + rate_start_index] += element; + self.state[self.params.capacity + i + rate_start_index] += element; } self.mode = DuplexSpongeMode::Absorbing { next_absorb_index: rate_start_index + remaining_elements.len(), @@ -43,13 +46,13 @@ impl GriffinSponge { return; } // otherwise absorb (rate - rate_start_index) elements - let num_elements_absorbed = self.griffin.rate - rate_start_index; + let num_elements_absorbed = self.params.rate - rate_start_index; for (i, element) in remaining_elements .iter() .enumerate() .take(num_elements_absorbed) { - self.state[self.griffin.capacity + i + rate_start_index] += element; + self.state[self.params.capacity + i + rate_start_index] += element; } self.permute(); // the input elements got truncated by num elements absorbed @@ -63,10 +66,10 @@ impl GriffinSponge { let mut output_remaining = output; loop { // if we can finish in this call - if rate_start_index + output_remaining.len() <= self.griffin.rate { + if rate_start_index + output_remaining.len() <= self.params.rate { output_remaining.clone_from_slice( - &self.state[self.griffin.capacity + rate_start_index - ..(self.griffin.capacity + output_remaining.len() + rate_start_index)], + &self.state[self.params.capacity + rate_start_index + ..(self.params.capacity + output_remaining.len() + rate_start_index)], ); self.mode = DuplexSpongeMode::Squeezing { next_squeeze_index: rate_start_index + output_remaining.len(), @@ -74,10 +77,10 @@ impl GriffinSponge { return; } // otherwise squeeze (rate - rate_start_index) elements - let num_elements_squeezed = self.griffin.rate - rate_start_index; + let num_elements_squeezed = self.params.rate - rate_start_index; output_remaining[..num_elements_squeezed].clone_from_slice( - &self.state[self.griffin.capacity + rate_start_index - ..(self.griffin.capacity + num_elements_squeezed + rate_start_index)], + &self.state[self.params.capacity + rate_start_index + ..(self.params.capacity + num_elements_squeezed + rate_start_index)], ); // Repeat with updated output slices @@ -92,21 +95,19 @@ impl GriffinSponge { } } +/// [`GriffinSpongeVar`] is the in-circuit variable of [`GriffinSponge`]. +/// +/// The implementation mirrors arkworks' [`ark_crypto_primitives::sponge::poseidon::constraints::PoseidonSpongeVar`]. #[derive(Clone)] pub struct GriffinSpongeVar { - /// Sponge Parameters - pub griffin: Arc>, - - // Sponge State - /// The sponge's state - pub state: Vec>, - /// The mode - pub mode: DuplexSpongeMode, + params: Arc>, + state: Vec>, + mode: DuplexSpongeMode, } impl GriffinSpongeVar { fn permute(&mut self) -> Result<(), SynthesisError> { - self.state = self.griffin.permute_gadget(&self.state)?; + self.state = GriffinGadget::permute(&self.params, &self.state)?; Ok(()) } @@ -118,9 +119,9 @@ impl GriffinSpongeVar { let mut remaining_elements = elements; loop { // if we can finish in this call - if rate_start_index + remaining_elements.len() <= self.griffin.rate { + if rate_start_index + remaining_elements.len() <= self.params.rate { for (i, element) in remaining_elements.iter().enumerate() { - self.state[self.griffin.capacity + i + rate_start_index] += element; + self.state[self.params.capacity + i + rate_start_index] += element; } self.mode = DuplexSpongeMode::Absorbing { next_absorb_index: rate_start_index + remaining_elements.len(), @@ -129,13 +130,13 @@ impl GriffinSpongeVar { return Ok(()); } // otherwise absorb (rate - rate_start_index) elements - let num_elements_absorbed = self.griffin.rate - rate_start_index; + let num_elements_absorbed = self.params.rate - rate_start_index; for (i, element) in remaining_elements .iter() .enumerate() .take(num_elements_absorbed) { - self.state[self.griffin.capacity + i + rate_start_index] += element; + self.state[self.params.capacity + i + rate_start_index] += element; } self.permute()?; // the input elements got truncated by num elements absorbed @@ -153,10 +154,10 @@ impl GriffinSpongeVar { let mut remaining_output = output; loop { // if we can finish in this call - if rate_start_index + remaining_output.len() <= self.griffin.rate { + if rate_start_index + remaining_output.len() <= self.params.rate { remaining_output.clone_from_slice( - &self.state[self.griffin.capacity + rate_start_index - ..(self.griffin.capacity + remaining_output.len() + rate_start_index)], + &self.state[self.params.capacity + rate_start_index + ..(self.params.capacity + remaining_output.len() + rate_start_index)], ); self.mode = DuplexSpongeMode::Squeezing { next_squeeze_index: rate_start_index + remaining_output.len(), @@ -164,10 +165,10 @@ impl GriffinSpongeVar { return Ok(()); } // otherwise squeeze (rate - rate_start_index) elements - let num_elements_squeezed = self.griffin.rate - rate_start_index; + let num_elements_squeezed = self.params.rate - rate_start_index; remaining_output[..num_elements_squeezed].clone_from_slice( - &self.state[self.griffin.capacity + rate_start_index - ..(self.griffin.capacity + num_elements_squeezed + rate_start_index)], + &self.state[self.params.capacity + rate_start_index + ..(self.params.capacity + num_elements_squeezed + rate_start_index)], ); // Repeat with updated output slices and rate start index @@ -184,7 +185,7 @@ impl GriffinSpongeVar { impl Transcript for GriffinSponge { type Config = Arc>; - type Var = GriffinSpongeVar; + type Gadget = GriffinSpongeVar; fn new(parameters: &Arc>) -> Self { let state = vec![F::zero(); parameters.rate + parameters.capacity]; @@ -193,7 +194,7 @@ impl Transcript for GriffinSponge { }; Self { - griffin: parameters.clone(), + params: parameters.clone(), state, mode, } @@ -207,7 +208,7 @@ impl Transcript for GriffinSponge { match self.mode { DuplexSpongeMode::Absorbing { next_absorb_index } => { let mut absorb_index = next_absorb_index; - if absorb_index == self.griffin.rate { + if absorb_index == self.params.rate { self.permute(); absorb_index = 0; } @@ -249,7 +250,7 @@ impl Transcript for GriffinSponge { } DuplexSpongeMode::Squeezing { next_squeeze_index } => { let mut squeeze_index = next_squeeze_index; - if squeeze_index == self.griffin.rate { + if squeeze_index == self.params.rate { self.permute(); squeeze_index = 0; } @@ -261,8 +262,8 @@ impl Transcript for GriffinSponge { } } -impl TranscriptVar for GriffinSpongeVar { - type Native = GriffinSponge; +impl TranscriptGadget for GriffinSpongeVar { + type Widget = GriffinSponge; fn new(parameters: &Arc>) -> Self where @@ -275,17 +276,22 @@ impl TranscriptVar for GriffinSpongeVar { }; Self { - griffin: parameters.clone(), + params: parameters.clone(), state, mode, } } - fn add + ?Sized>( + fn add + ?Sized>( &mut self, input: &A, ) -> Result<&mut Self, SynthesisError> { - let input = input.to_absorbable()?; + let input = { + let mut result = Vec::new(); + input.absorb_into(&mut result)?; + result + }; + if input.is_empty() { return Ok(self); } @@ -293,7 +299,7 @@ impl TranscriptVar for GriffinSpongeVar { match self.mode { DuplexSpongeMode::Absorbing { next_absorb_index } => { let mut absorb_index = next_absorb_index; - if absorb_index == self.griffin.rate { + if absorb_index == self.params.rate { self.permute()?; absorb_index = 0; } @@ -336,7 +342,7 @@ impl TranscriptVar for GriffinSpongeVar { } DuplexSpongeMode::Squeezing { next_squeeze_index } => { let mut squeeze_index = next_squeeze_index; - if squeeze_index == self.griffin.rate { + if squeeze_index == self.params.rate { self.permute()?; squeeze_index = 0; } @@ -349,15 +355,12 @@ impl TranscriptVar for GriffinSpongeVar { } #[cfg(test)] -pub mod tests { - use ark_bn254::{Fq, Fr, G1Projective as G1, constraints::GVar, g1::Config}; - use ark_ec::PrimeGroup; +mod tests { + use ark_bn254::{Fq, Fr, G1Projective as G1, g1::Config}; use ark_ff::UniformRand; use ark_r1cs_std::{ - GR1CSVar, - alloc::AllocVar, - fields::fp::FpVar, - groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, + GR1CSVar, alloc::AllocVar, fields::fp::FpVar, + groups::curves::short_weierstrass::ProjectiveVar, }; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, rand::thread_rng}; @@ -365,110 +368,97 @@ pub mod tests { use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; - use crate::algebra::{group::emulated::EmulatedAffineVar, ops::bits::FromBits}; + use crate::algebra::group::emulated::EmulatedAffineVar; + + #[test] + fn test_challenge_field_element() -> Result<(), Box> { + // Create a transcript outside of the circuit + let config = Arc::new(GriffinParams::::new(3, 5, 12)); + let mut tr = GriffinSponge::::new(&config); + tr.add(&Fr::from(42_u32)); + let c = tr.challenge_field_element(); + + // Create a transcript inside of the circuit + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = GriffinSpongeVar::::new(&config); + let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; + tr_var.add(&v)?; + let c_var = tr_var.challenge_field_element()?; + + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } #[test] - fn test_transcript_and_transcriptvar_absorb_native_point() -> Result<(), Box> { - // use 'native' transcript + fn test_challenge_bits() -> Result<(), Box> { + let nbits = 128; + + // Create a transcript outside of the circuit let config = Arc::new(GriffinParams::::new(3, 5, 12)); let mut tr = GriffinSponge::::new(&config); - let rng = &mut thread_rng(); - - let p = G1::rand(rng); - tr.add(&p); - let c = tr.challenge_field_element(); + tr.add(&Fq::from(42_u32)); + let c = tr.challenge_bits(nbits); - // use 'gadget' transcript + // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); let mut tr_var = GriffinSpongeVar::::new(&config); - let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; - tr_var.add(&p_var)?; - let c_var = tr_var.challenge_field_element()?; + let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; + tr_var.add(&v)?; + let c_var = tr_var.challenge_bits(nbits)?; - // assert that native & gadget transcripts return the same challenge + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge assert_eq!(c, c_var.value()?); Ok(()) } #[test] - fn test_transcript_and_transcriptvar_absorb_nonnative_point() -> Result<(), Box> { - // use 'native' transcript - let config = Arc::new(GriffinParams::::new(3, 5, 12)); - let mut tr = GriffinSponge::::new(&config); + fn test_absorb_canonical_point() -> Result<(), Box> { + // Create a transcript outside of the circuit + let config = Arc::new(GriffinParams::::new(3, 5, 12)); + let mut tr = GriffinSponge::::new(&config); let rng = &mut thread_rng(); let p = G1::rand(rng); tr.add(&p); let c = tr.challenge_field_element(); - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); - let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; + // Create a transcript inside of the circuit + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = GriffinSpongeVar::::new(&config); + let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; - // assert that native & gadget transcripts return the same challenge + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge assert_eq!(c, c_var.value()?); Ok(()) } #[test] - fn test_transcript_and_transcriptvar_get_challenge() -> Result<(), Box> { - // use 'native' transcript + fn test_absorb_emulated_point() -> Result<(), Box> { + // Create a transcript outside of the circuit let config = Arc::new(GriffinParams::::new(3, 5, 12)); let mut tr = GriffinSponge::::new(&config); - tr.add(&Fr::from(42_u32)); + let rng = &mut thread_rng(); + + let p = G1::rand(rng); + tr.add(&p); let c = tr.challenge_field_element(); - // use 'gadget' transcript + // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); let mut tr_var = GriffinSpongeVar::::new(&config); - let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; - tr_var.add(&v)?; + let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; + tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; - // assert that native & gadget transcripts return the same challenge + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge assert_eq!(c, c_var.value()?); Ok(()) } - - #[test] - fn test_transcript_and_transcriptvar_nbits() -> Result<(), Box> { - let nbits = 128; - - // use 'native' transcript - let config = Arc::new(GriffinParams::::new(3, 5, 12)); - let mut tr = GriffinSponge::::new(&config); - tr.add(&Fq::from(42_u32)); - - // get challenge from native transcript - let c_bits = tr.challenge_bits(nbits); - - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); - let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; - tr_var.add(&v)?; - - // get challenge from circuit transcript - let c_var = tr_var.challenge_bits(nbits)?; - - let p = G1::generator(); - let p_var = GVar::new_witness(cs.clone(), || Ok(p))?; - - // multiply point P by the challenge in different formats, to ensure that we get the same - // result natively and in-circuit - let c = Fr::from_bits_le(&c_bits); - - // check that native c*P and in-circuit c*P using scalar_mul_le are equal - assert_eq!(p * c, p_var.scalar_mul_le(c_var.iter())?.value()?); - // check that native c*P using mul_bits_be and in-circuit c*P using scalar_mul_le are equal - // (notice the .rev to convert the LE to BE) - assert_eq!( - p.mul_bits_be(c_bits.into_iter().rev()), - p_var.scalar_mul_le(c_var.iter())?.value()? - ); - Ok(()) - } } diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 4fdaffa78..e4ca576bc 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -1,45 +1,77 @@ +//! Abstractions of sponges and Fiat-Shamir transcripts. +//! +//! This module defines the traits that unify hash functions (Poseidon, Griffin, +//! etc.) behind a common absorb / squeeze interface suitable for building +//! non-interactive proofs. +//! +//! Concrete implementations live in the [`poseidon`] and [`griffin`] +//! sub-modules. + use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; -pub use self::absorbable::{Absorbable, AbsorbableGadget}; +pub use self::absorbable::{Absorbable, AbsorbableVar}; pub mod absorbable; pub mod griffin; pub mod poseidon; +/// [`Transcript`] is the out-of-circuit widget for transcripts and sponges. +/// +/// Provers and verifiers can use this trait to absorb messages and squeeze +/// challenges in a way that is agnostic to the underlying hash function. pub trait Transcript: Clone { + /// [`Transcript::Config`] is the configuration for the underlying hash + /// function of the transcript. type Config: Clone; - type Var: TranscriptVar; + /// [`Transcript::Gadget`] is the in-circuit gadget corresponding to this + /// widget. + type Gadget: TranscriptGadget; + + /// [`Transcript::new`] creates a new transcript / sponge under the given + /// configuration `config`. fn new(config: &Self::Config) -> Self; - /// `new_with_pp_hash` creates a new transcript / sponge with the given - /// hash of the public parameters. + /// [`Transcript::new_with_pp_hash`] is a convenience method for creating a + /// new transcript / sponge under the given configuration `config` and + /// additionally absorbing a hash of the public parameters `pp_hash`. fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self { let mut sponge = Self::new(config); sponge.add_field_elements(&[pp_hash]); sponge } + /// [`Transcript::add`] absorbs a message `input` that can be any type + /// implementing the [`Absorbable`] trait into the transcript / sponge. fn add(&mut self, input: &A) -> &mut Self { - let elems = input.to_absorbable(); + let mut elems = Vec::new(); + input.absorb_into(&mut elems); self.add_field_elements(&elems) } + /// [`Transcript::add_field_elements`] absorbs a message `input` that is + /// represented as field elements into the transcript / sponge. fn add_field_elements(&mut self, input: &[F]) -> &mut Self; - /// Squeeze `num_bits` bits from the sponge. + /// [`Transcript::get_bits`] squeezes `num_bits` bits from the transcript / + /// sponge. fn get_bits(&mut self, num_bits: usize) -> Vec; + /// [`Transcript::get_field_element`] squeezes a single field element from + /// the transcript / sponge. fn get_field_element(&mut self) -> F { self.get_field_elements(1)[0] } + /// [`Transcript::get_field_elements`] squeezes `num_elements` field + /// elements from the transcript / sponge. fn get_field_elements(&mut self, num_elements: usize) -> Vec; - /// Creates a new sponge with applied domain separation. + /// [`Transcript::separate_domain`] creates a new transcript / sponge by + /// applying domain separation using the provided `domain` byte sequence. fn separate_domain(&self, domain: &[u8]) -> Self { let mut new_sponge = self.clone(); @@ -56,12 +88,23 @@ pub trait Transcript: Clone { new_sponge } + /// [`Transcript::challenge_field_element`] squeezes a challenge from the + /// transcript / sponge as a field element. + /// + /// Internally, it first squeezes a field element and then absorbs it back + /// into the transcript / sponge to ensure security. fn challenge_field_element(&mut self) -> F { let c = self.get_field_elements(1); self.add_field_elements(&c); c[0] } + /// [`Transcript::challenge_bits`] squeezes a challenge from the transcript + /// / sponge as a bit vector. + /// + /// Internally, it first squeezes the bits and then absorbs packed field + /// elements formed by the bits back into the transcript / sponge to ensure + /// security. fn challenge_bits(&mut self, nbits: usize) -> Vec { let bits = self.get_bits(nbits); self.add_field_elements( @@ -74,6 +117,11 @@ pub trait Transcript: Clone { bits } + /// [`Transcript::challenge_field_elements`] squeezes `n` challenges from + /// the transcript / sponge as field elements. + /// + /// Internally, it first squeezes the field elements and then absorbs them + /// back into the transcript / sponge to ensure security. fn challenge_field_elements(&mut self, n: usize) -> Vec { let c = self.get_field_elements(n); self.add_field_elements(&c); @@ -81,15 +129,22 @@ pub trait Transcript: Clone { } } -pub trait TranscriptVar: Clone { - type Native: Transcript; +/// [`TranscriptGadget`] is the in-circuit gadget for transcripts and sponges. +pub trait TranscriptGadget: Clone { + /// [`TranscriptGadget::Widget`] points to the out-of-circuit widget for + /// this transcript gadget. + type Widget: Transcript; - fn new(config: &>::Config) -> Self; + /// [`TranscriptGadget::new`] creates a new transcript / sponge variable + /// under the given configuration `config`. + fn new(config: &>::Config) -> Self; - /// `new_with_pp_hash` creates a new transcript / sponge with the given - /// hash of the public parameters. + /// [`TranscriptGadget::new_with_pp_hash`] is a convenience method for + /// creating a new transcript / sponge variable under the given + /// configuration `config` and additionally absorbing a hash of the public + /// parameters `pp_hash`. fn new_with_pp_hash( - config: &>::Config, + config: &>::Config, pp_hash: &FpVar, ) -> Result { let mut sponge = Self::new(config); @@ -97,21 +152,31 @@ pub trait TranscriptVar: Clone { Ok(sponge) } - fn add + ?Sized>( + /// [`TranscriptGadget::add`] absorbs a message `input` that can be any type + /// implementing the [`AbsorbableGadget`] trait into the transcript / sponge + /// variable. + fn add + ?Sized>( &mut self, input: &A, ) -> Result<&mut Self, SynthesisError>; - /// Squeeze `num_bits` bits from the sponge. + /// [`TranscriptGadget::get_bits`] squeezes `num_bits` bit variables from + /// the transcript / sponge variable. fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError>; + /// [`TranscriptGadget::get_field_element`] squeezes a single field element + /// variable from the transcript / sponge variable. fn get_field_element(&mut self) -> Result, SynthesisError> { Ok(self.get_field_elements(1)?.swap_remove(0)) } + /// [`TranscriptGadget::get_field_elements`] squeezes `num_elements` field + /// element variables from the transcript / sponge variable. fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError>; - /// Creates a new sponge with applied domain separation. + /// [`TranscriptGadget::separate_domain`] creates a new transcript / sponge + /// variable by applying domain separation using the provided `domain` byte + /// sequence. fn separate_domain(&self, domain: &[u8]) -> Result { let mut new_sponge = self.clone(); @@ -128,12 +193,23 @@ pub trait TranscriptVar: Clone { Ok(new_sponge) } + /// [`TranscriptGadget::challenge_field_element`] squeezes a challenge from + /// the transcript / sponge variable as a field element variable. + /// + /// Internally, it first squeezes a field element variable and then absorbs + /// it back into the transcript / sponge variable to ensure security. fn challenge_field_element(&mut self) -> Result, SynthesisError> { let mut c = self.get_field_elements(1)?; self.add(&c[0])?; Ok(c.swap_remove(0)) } + /// [`TranscriptGadget::challenge_bits`] squeezes a challenge from the + /// transcript / sponge variable as a vector of bit variables. + /// + /// Internally, it first squeezes the bit variables and then absorbs packed + /// field element variables formed by the bit variables back into the + /// transcript / sponge variable to ensure security. fn challenge_bits(&mut self, nbits: usize) -> Result>, SynthesisError> { let bits = self.get_bits(nbits)?; self.add( @@ -145,6 +221,12 @@ pub trait TranscriptVar: Clone { Ok(bits) } + /// [`TranscriptGadget::challenge_field_elements`] squeezes `n` challenges + /// from the transcript / sponge variable as field element variables. + /// + /// Internally, it first squeezes the field element variables and then + /// absorbs them back into the transcript / sponge variable to ensure + /// security. fn challenge_field_elements(&mut self, n: usize) -> Result>, SynthesisError> { let c = self.get_field_elements(n)?; self.add(&c)?; diff --git a/crates/primitives/src/transcripts/poseidon/mod.rs b/crates/primitives/src/transcripts/poseidon/mod.rs index 5693fb77c..54bdbaff8 100644 --- a/crates/primitives/src/transcripts/poseidon/mod.rs +++ b/crates/primitives/src/transcripts/poseidon/mod.rs @@ -1,9 +1,12 @@ +//! Poseidon-based transcript configurations and implementations. + use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, find_poseidon_ark_and_mds}; use ark_ff::PrimeField; pub mod sponge; -/// This Poseidon configuration generator produces a Poseidon configuration with custom parameters +/// [`poseidon_custom_config`] produces a Poseidon configuration with custom +/// parameters. pub fn poseidon_custom_config( full_rounds: usize, partial_rounds: usize, @@ -22,7 +25,9 @@ pub fn poseidon_custom_config( PoseidonConfig::new(full_rounds, partial_rounds, alpha, mds, ark, rate, capacity) } -/// This Poseidon configuration generator agrees with Circom's Poseidon(4) in the case of BN254's scalar field +/// [`poseidon_canonical_config`] produces a Poseidon configuration with default +/// parameters, which agrees with Circom's Poseidon(4) when `F` is the scalar +/// field of BN254. pub fn poseidon_canonical_config() -> PoseidonConfig { // 120 bit security target as in // https://eprint.iacr.org/2019/458.pdf diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index e595247c8..89068655b 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -1,3 +1,5 @@ +//! Implementation of transcript traits for arkworks' Poseidon sponge. + use ark_crypto_primitives::sponge::{ Absorb, CryptographicSponge, FieldBasedCryptographicSponge, constraints::CryptographicSpongeVar, @@ -8,11 +10,11 @@ use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; use ark_std::mem::transmute_copy; -use crate::transcripts::{AbsorbableGadget, Transcript, TranscriptVar}; +use crate::transcripts::{AbsorbableVar, Transcript, TranscriptGadget}; impl Transcript for PoseidonSponge { type Config = PoseidonConfig; - type Var = PoseidonSpongeVar; + type Gadget = PoseidonSpongeVar; fn new(config: &Self::Config) -> Self { CryptographicSponge::new(config) @@ -47,8 +49,8 @@ impl Transcript for PoseidonSponge { } } -impl TranscriptVar for PoseidonSpongeVar { - type Native = PoseidonSponge; +impl TranscriptGadget for PoseidonSpongeVar { + type Widget = PoseidonSponge; fn new(config: &PoseidonConfig) -> Self where @@ -57,11 +59,14 @@ impl TranscriptVar for PoseidonSpongeVar { CryptographicSpongeVar::new(ConstraintSystemRef::None, config) } - fn add + ?Sized>( + fn add + ?Sized>( &mut self, input: &A, ) -> Result<&mut Self, SynthesisError> { - self.absorb(&input.to_absorbable()?)?; + let mut result = Vec::new(); + input.absorb_into(&mut result)?; + + self.absorb(&result)?; Ok(self) } @@ -75,16 +80,13 @@ impl TranscriptVar for PoseidonSpongeVar { } #[cfg(test)] -pub mod tests { - use ark_bn254::{Fq, Fr, G1Projective as G1, constraints::GVar, g1::Config}; +mod tests { + use ark_bn254::{Fq, Fr, G1Projective as G1, g1::Config}; use ark_crypto_primitives::sponge::poseidon::{PoseidonSponge, constraints::PoseidonSpongeVar}; - use ark_ec::PrimeGroup; use ark_ff::UniformRand; use ark_r1cs_std::{ - GR1CSVar, - alloc::AllocVar, - fields::fp::FpVar, - groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, + GR1CSVar, alloc::AllocVar, fields::fp::FpVar, + groups::curves::short_weierstrass::ProjectiveVar, }; use ark_relations::gr1cs::ConstraintSystem; use ark_std::{error::Error, rand::thread_rng, str::FromStr}; @@ -92,8 +94,8 @@ pub mod tests { use wasm_bindgen_test::wasm_bindgen_test as test; use crate::{ - algebra::{group::emulated::EmulatedAffineVar, ops::bits::FromBits}, - transcripts::{Transcript, TranscriptVar, poseidon::poseidon_canonical_config}, + algebra::group::emulated::EmulatedAffineVar, + transcripts::{Transcript, TranscriptGadget, poseidon::poseidon_canonical_config}, }; // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 @@ -118,107 +120,94 @@ pub mod tests { } #[test] - fn test_transcript_and_transcriptvar_absorb_native_point() -> Result<(), Box> { - // use 'native' transcript + fn test_challenge_field_element() -> Result<(), Box> { + // Create a transcript outside of the circuit + let config = poseidon_canonical_config::(); + let mut tr = PoseidonSponge::::new(&config); + tr.add(&Fr::from(42_u32)); + let c = tr.challenge_field_element(); + + // Create a transcript inside of the circuit + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = PoseidonSpongeVar::::new(&config); + let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; + tr_var.add(&v)?; + let c_var = tr_var.challenge_field_element()?; + + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge + assert_eq!(c, c_var.value()?); + Ok(()) + } + + #[test] + fn test_challenge_bits() -> Result<(), Box> { + let nbits = 128; + + // Create a transcript outside of the circuit let config = poseidon_canonical_config::(); let mut tr = PoseidonSponge::::new(&config); - let rng = &mut thread_rng(); - - let p = G1::rand(rng); - tr.add(&p); - let c = tr.challenge_field_element(); + tr.add(&Fq::from(42_u32)); + let c = tr.challenge_bits(nbits); - // use 'gadget' transcript + // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); let mut tr_var = PoseidonSpongeVar::::new(&config); - let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; - tr_var.add(&p_var)?; - let c_var = tr_var.challenge_field_element()?; + let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; + tr_var.add(&v)?; + let c_var = tr_var.challenge_bits(nbits)?; - // assert that native & gadget transcripts return the same challenge + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge assert_eq!(c, c_var.value()?); Ok(()) } #[test] - fn test_transcript_and_transcriptvar_absorb_nonnative_point() -> Result<(), Box> { - // use 'native' transcript - let config = poseidon_canonical_config::(); - let mut tr = PoseidonSponge::::new(&config); + fn test_absorb_canonical_point() -> Result<(), Box> { + // Create a transcript outside of the circuit + let config = poseidon_canonical_config::(); + let mut tr = PoseidonSponge::::new(&config); let rng = &mut thread_rng(); let p = G1::rand(rng); tr.add(&p); let c = tr.challenge_field_element(); - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); - let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; + // Create a transcript inside of the circuit + let cs = ConstraintSystem::::new_ref(); + let mut tr_var = PoseidonSpongeVar::::new(&config); + let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; - // assert that native & gadget transcripts return the same challenge + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge assert_eq!(c, c_var.value()?); Ok(()) } #[test] - fn test_transcript_and_transcriptvar_get_challenge() -> Result<(), Box> { - // use 'native' transcript + fn test_absorb_emulated_point() -> Result<(), Box> { + // Create a transcript outside of the circuit let config = poseidon_canonical_config::(); let mut tr = PoseidonSponge::::new(&config); - tr.add(&Fr::from(42_u32)); + let rng = &mut thread_rng(); + + let p = G1::rand(rng); + tr.add(&p); let c = tr.challenge_field_element(); - // use 'gadget' transcript + // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); let mut tr_var = PoseidonSpongeVar::::new(&config); - let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; - tr_var.add(&v)?; + let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; + tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; - // assert that native & gadget transcripts return the same challenge + // Assert that in-circuit and out-of-circuit transcripts return the same + // challenge assert_eq!(c, c_var.value()?); Ok(()) } - - #[test] - fn test_transcript_and_transcriptvar_nbits() -> Result<(), Box> { - let nbits = 128; - - // use 'native' transcript - let config = poseidon_canonical_config::(); - let mut tr = PoseidonSponge::::new(&config); - tr.add(&Fq::from(42_u32)); - - // get challenge from native transcript - let c_bits = tr.challenge_bits(nbits); - - // use 'gadget' transcript - let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); - let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; - tr_var.add(&v)?; - - // get challenge from circuit transcript - let c_var = tr_var.challenge_bits(nbits)?; - - let p = G1::generator(); - let p_var = GVar::new_witness(cs.clone(), || Ok(p))?; - - // multiply point P by the challenge in different formats, to ensure that we get the same - // result natively and in-circuit - let c = Fr::from_bits_le(&c_bits); - - // check that native c*P and in-circuit c*P using scalar_mul_le are equal - assert_eq!(p * c, p_var.scalar_mul_le(c_var.iter())?.value()?); - // check that native c*P using mul_bits_be and in-circuit c*P using scalar_mul_le are equal - // (notice the .rev to convert the LE to BE) - assert_eq!( - p.mul_bits_be(c_bits.into_iter().rev()), - p_var.scalar_mul_le(c_var.iter())?.value()? - ); - Ok(()) - } } diff --git a/crates/primitives/src/utils/mod.rs b/crates/primitives/src/utils/mod.rs index 2cf111496..7f61a9721 100644 --- a/crates/primitives/src/utils/mod.rs +++ b/crates/primitives/src/utils/mod.rs @@ -1 +1,3 @@ +//! Miscellaneous utilities shared across the primitives crate. + pub mod null; diff --git a/crates/primitives/src/utils/null.rs b/crates/primitives/src/utils/null.rs index bc6a9bb09..bce182be9 100644 --- a/crates/primitives/src/utils/null.rs +++ b/crates/primitives/src/utils/null.rs @@ -1,3 +1,6 @@ +//! This module defines a zero-cost placeholder type that have well-defined +//! arithmetic operations. + use ark_ff::Field; use ark_r1cs_std::{ GR1CSVar, @@ -11,6 +14,11 @@ use ark_std::{ ops::{Add, Mul}, }; +/// [`Null`] is a zero-sized type that absorbs any arithmetic and always returns +/// itself. +/// +/// It also has itself as its in-circuit representation, which does not allocate +/// any variables or require any constraints. #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] pub struct Null; From 79e578bfcad3fea6cfe80043e1a697a2f4c8daf0 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:10:50 +0800 Subject: [PATCH 28/99] Bring back R1CS and CCS tests --- .../src/arithmetizations/ccs/mod.rs | 68 +++++++++++++++++- .../src/arithmetizations/r1cs/circuits.rs | 69 ++++++++++++++++++- 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 9ef789298..cebed30fe 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -262,4 +262,70 @@ impl From> for CCS { } } -// TODO: add back tests +#[cfg(test)] +mod tests { + use ark_bn254::Fr; + use ark_ff::{One, UniformRand, Zero}; + use ark_std::{error::Error, rand::thread_rng}; + + use super::*; + use crate::{ + circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, + relations::Relation, + }; + + #[test] + fn test_eval() -> Result<(), Box> { + let mut rng = thread_rng(); + let ccs: CCS = constraints_for_test::().into(); + + assert!( + ccs.evaluate_at(satisfying_assignments_for_test(Fr::rand(&mut rng)))? + .into_iter() + .all(|e| e.is_zero()) + ); + assert!( + !ccs.evaluate_at(Assignments::from(( + Fr::one(), + vec![Fr::rand(&mut rng)], + vec![ + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + ], + )))? + .into_iter() + .all(|e| e.is_zero()) + ); + + Ok(()) + } + + #[test] + fn test_check() -> Result<(), Box> { + let mut rng = thread_rng(); + let ccs: CCS = constraints_for_test::().into(); + + let assignments = satisfying_assignments_for_test(Fr::rand(&mut rng)); + + assert!( + ccs.check_relation(&assignments.private, &assignments.public) + .is_ok() + ); + assert!( + ccs.check_relation( + &[ + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + ], + &[Fr::rand(&mut rng)] + ) + .is_err() + ); + + Ok(()) + } +} diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index b6d908f3e..0d983a4a8 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -97,4 +97,71 @@ where } } -// TODO: add back tests +#[cfg(test)] +mod tests { + use ark_bn254::Fr; + use ark_ff::{One, UniformRand, Zero}; + use ark_std::{error::Error, rand::thread_rng}; + + use super::*; + use crate::{ + circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, + relations::Relation, + }; + + #[test] + fn test_eval() -> Result<(), Box> { + let mut rng = thread_rng(); + let r1cs = constraints_for_test::(); + + assert!( + r1cs.evaluate_at(satisfying_assignments_for_test(Fr::rand(&mut rng)))? + .into_iter() + .all(|e| e.is_zero()) + ); + assert!( + !r1cs + .evaluate_at(Assignments::from(( + Fr::one(), + vec![Fr::rand(&mut rng)], + vec![ + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + ], + )))? + .into_iter() + .all(|e| e.is_zero()) + ); + + Ok(()) + } + + #[test] + fn test_check() -> Result<(), Box> { + let mut rng = thread_rng(); + let r1cs = constraints_for_test::(); + + let assignments = satisfying_assignments_for_test(Fr::rand(&mut rng)); + + assert!( + r1cs.check_relation(&assignments.private, &assignments.public) + .is_ok() + ); + assert!( + r1cs.check_relation( + &[ + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + ], + &[Fr::rand(&mut rng)] + ) + .is_err() + ); + + Ok(()) + } +} From 6f918ba53d48208afc5d834be4587a893f71be0b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:48:16 +0800 Subject: [PATCH 29/99] fmt --- crates/primitives/src/algebra/ops/bits.rs | 4 ++-- crates/primitives/src/algebra/ops/poly.rs | 5 +++-- crates/primitives/src/arithmetizations/r1cs/mod.rs | 5 +---- crates/primitives/src/circuits/utils.rs | 4 +++- crates/primitives/src/sumcheck/circuits.rs | 2 +- crates/primitives/src/sumcheck/utils.rs | 2 +- crates/primitives/src/transcripts/griffin/mod.rs | 2 +- crates/primitives/src/transcripts/mod.rs | 10 ++++------ crates/primitives/src/utils/null.rs | 2 +- 9 files changed, 17 insertions(+), 19 deletions(-) diff --git a/crates/primitives/src/algebra/ops/bits.rs b/crates/primitives/src/algebra/ops/bits.rs index eb997e259..7616d08fc 100644 --- a/crates/primitives/src/algebra/ops/bits.rs +++ b/crates/primitives/src/algebra/ops/bits.rs @@ -36,13 +36,13 @@ pub trait FromBitsGadget: Sized { pub trait ToBitsGadgetExt: Sized { /// [`ToBitsGadgetExt::to_n_bits_le`] decomposes `self` into `n` /// little-endian bits. - /// + /// /// An error is returned if `self` cannot be represented in `n` bits. fn to_n_bits_le(&self, n: usize) -> Result>, SynthesisError>; /// [`ToBitsGadgetExt::enforce_bit_length`] enforces that `self` can be /// represented in at most `n` bits. - /// + /// /// This is useful for checking that a field element is within the range of /// `[0, 2^n - 1]` fn enforce_bit_length(&self, n: usize) -> Result<(), SynthesisError> { diff --git a/crates/primitives/src/algebra/ops/poly.rs b/crates/primitives/src/algebra/ops/poly.rs index fed1992fd..10a5c9cea 100644 --- a/crates/primitives/src/algebra/ops/poly.rs +++ b/crates/primitives/src/algebra/ops/poly.rs @@ -38,9 +38,10 @@ pub trait EvaluationDomainGadget { /// [`EvaluationDomainGadget::evaluate_vanishing_polynomial_var`] evaluates /// the vanishing polynomial of the domain at `tau`. - /// + /// /// It is the in-circuit counterpart of [`EvaluationDomain::evaluate_vanishing_polynomial`]. - fn evaluate_vanishing_polynomial_var(&self, tau: &FpVar) -> Result, SynthesisError>; + fn evaluate_vanishing_polynomial_var(&self, tau: &FpVar) + -> Result, SynthesisError>; } impl EvaluationDomainGadget for GeneralEvaluationDomain { diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 5a65f25ec..c38b8c547 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -118,10 +118,7 @@ impl R1CS { /// [`R1CS::evaluate_at`] evaluates the R1CS relation at a given vector of /// assignments `z`. - pub fn evaluate_at( - &self, - z: Assignments + Sync>, - ) -> Result, Error> { + pub fn evaluate_at(&self, z: Assignments + Sync>) -> Result, Error> { let cfg = &self.cfg; let public_len = z.public.as_ref().len(); diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 0c8936161..464d248d3 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -2,7 +2,9 @@ use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ - GR1CSVar, alloc::AllocVar, fields::fp::{AllocatedFp, FpVar} + GR1CSVar, + alloc::AllocVar, + fields::fp::{AllocatedFp, FpVar}, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable}; diff --git a/crates/primitives/src/sumcheck/circuits.rs b/crates/primitives/src/sumcheck/circuits.rs index bb91b3ee8..60075d9a3 100644 --- a/crates/primitives/src/sumcheck/circuits.rs +++ b/crates/primitives/src/sumcheck/circuits.rs @@ -48,7 +48,7 @@ pub struct SumCheckGadget; impl SumCheckGadget { /// [`SumCheckGadget::verify`] provides an implementation of the sumcheck /// verification algorithm in circuit. - /// + /// /// Given the claimed sum `claimed_sum = z`, the proof `proofs` (i.e., round /// polynomials `g_1, ..., g_n`), the auxiliary info `aux_info`, and the /// transcript `transcript`. diff --git a/crates/primitives/src/sumcheck/utils.rs b/crates/primitives/src/sumcheck/utils.rs index 184e0f7e0..cccac6eb7 100644 --- a/crates/primitives/src/sumcheck/utils.rs +++ b/crates/primitives/src/sumcheck/utils.rs @@ -166,7 +166,7 @@ impl EqPolyGadget { /// [`barycentric_weights`] computes the barycentric weights for a given set of /// evaluation `points`. -/// +/// /// Used to extrapolate polynomial evaluations via the barycentric formula. #[allow(clippy::filter_map_bool_then)] pub fn barycentric_weights(points: &[F]) -> Vec { diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index 5660015d2..dbf1c7b64 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -6,7 +6,7 @@ //! number of constraints, but later an [attack] on Griffin and similar hash //! functions was discovered. //! Therefore, it is recommended to avoid using Griffin in production. -//! +//! //! The code is forked from the [implementation] in the Hash Functions for //! Zero-Knowledge Applications Zoo but uses arkworks instead of bellman as the //! underlying cryptographic library. diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index e4ca576bc..1ddf1c29a 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -155,10 +155,8 @@ pub trait TranscriptGadget: Clone { /// [`TranscriptGadget::add`] absorbs a message `input` that can be any type /// implementing the [`AbsorbableGadget`] trait into the transcript / sponge /// variable. - fn add + ?Sized>( - &mut self, - input: &A, - ) -> Result<&mut Self, SynthesisError>; + fn add + ?Sized>(&mut self, input: &A) + -> Result<&mut Self, SynthesisError>; /// [`TranscriptGadget::get_bits`] squeezes `num_bits` bit variables from /// the transcript / sponge variable. @@ -206,7 +204,7 @@ pub trait TranscriptGadget: Clone { /// [`TranscriptGadget::challenge_bits`] squeezes a challenge from the /// transcript / sponge variable as a vector of bit variables. - /// + /// /// Internally, it first squeezes the bit variables and then absorbs packed /// field element variables formed by the bit variables back into the /// transcript / sponge variable to ensure security. @@ -223,7 +221,7 @@ pub trait TranscriptGadget: Clone { /// [`TranscriptGadget::challenge_field_elements`] squeezes `n` challenges /// from the transcript / sponge variable as field element variables. - /// + /// /// Internally, it first squeezes the field element variables and then /// absorbs them back into the transcript / sponge variable to ensure /// security. diff --git a/crates/primitives/src/utils/null.rs b/crates/primitives/src/utils/null.rs index bce182be9..da179385b 100644 --- a/crates/primitives/src/utils/null.rs +++ b/crates/primitives/src/utils/null.rs @@ -16,7 +16,7 @@ use ark_std::{ /// [`Null`] is a zero-sized type that absorbs any arithmetic and always returns /// itself. -/// +/// /// It also has itself as its in-circuit representation, which does not allocate /// any variables or require any constraints. #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] From 1229c5cedab510cadb64347bcd459e550879b965 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:50:27 +0800 Subject: [PATCH 30/99] Fix missing trait bounds when enabling parallel feature --- crates/primitives/src/arithmetizations/r1cs/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index c38b8c547..54be091c0 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -111,7 +111,7 @@ impl R1CS { /// provided function `f` to each triplet of rows `(A[i], B[i], C[i])`. pub fn evaluate_rows( &self, - f: impl FnMut(((&Row, &Row), &Row)) -> Result, + f: impl Fn(((&Row, &Row), &Row)) -> Result + Send + Sync, ) -> Result, Error> { cfg_iter!(self.A).zip(&self.B).zip(&self.C).map(f).collect() } From df327df60e8550f67506841c247bc69c910fe984 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 17:21:44 +0800 Subject: [PATCH 31/99] Reorganize cargo.toml --- Cargo.toml | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d1e89a20..916915046 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,28 +4,6 @@ members = [ ] resolver = "2" -[patch.crates-io] -# We depend on git versions of arkworks crates, but some of our dependencies -# depend on crates.io versions, so we need to override them here to avoid -# version conflicts. -ark-ff = { git = "https://github.com/arkworks-rs/algebra" } -ark-ec = { git = "https://github.com/arkworks-rs/algebra" } -ark-serialize = { git = "https://github.com/arkworks-rs/algebra" } -ark-poly = { git = "https://github.com/arkworks-rs/algebra" } -ark-std = { git = "https://github.com/arkworks-rs/std" } -ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives", rev = "af003fc" } -ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" } # "sw-fix-updated" branch - -# Curve crates also need git versions -ark-bn254 = { git = "https://github.com/arkworks-rs/algebra" } -ark-grumpkin = { git = "https://github.com/arkworks-rs/algebra" } - -[patch."https://github.com/arkworks-rs/crypto-primitives"] -ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives", rev = "af003fc" } - -[patch."https://github.com/arkworks-rs/r1cs-std"] -ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" } # "sw-fix-updated" branch - [workspace.package] edition = "2024" license = "MIT" @@ -37,7 +15,6 @@ num-bigint = { version = "0.4.3" } num-integer = { version = "0.1" } num-traits = { version = "0.2" } sha3 = { version = "0.10" } -rand = { version = "0.8.5" } rayon = { version = "1" } thiserror = { version = "2.0.16" } wasm-bindgen-test = { version = "0.3" } @@ -63,3 +40,24 @@ ark-vesta = { git = "https://github.com/arkworks-rs/algebra", default-features = # Local crates sonobe-primitives = { path = "crates/primitives", default-features = false } + +[patch.crates-io] +# We depend on git versions of arkworks crates, but some of our dependencies +# depend on crates.io versions, so we need to override them here to avoid +# version conflicts. +ark-ff = { git = "https://github.com/arkworks-rs/algebra" } +ark-ec = { git = "https://github.com/arkworks-rs/algebra" } +ark-serialize = { git = "https://github.com/arkworks-rs/algebra" } +ark-poly = { git = "https://github.com/arkworks-rs/algebra" } +ark-std = { git = "https://github.com/arkworks-rs/std" } +ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives", rev = "af003fc" } +ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" } # "sw-fix-updated" branch + +# Curve crates also need git versions +ark-bn254 = { git = "https://github.com/arkworks-rs/algebra" } + +[patch."https://github.com/arkworks-rs/crypto-primitives"] +ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives", rev = "af003fc" } + +[patch."https://github.com/arkworks-rs/r1cs-std"] +ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" } # "sw-fix-updated" branch \ No newline at end of file From 5e186c7d60b8f3c8155f08cf6cc18440879c60af Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 17 Feb 2026 23:05:06 +0800 Subject: [PATCH 32/99] Fix and test emulated integers with negative limbs --- .../primitives/src/algebra/field/emulated.rs | 259 +++++++++++++----- 1 file changed, 186 insertions(+), 73 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index d75d61923..096a0d1ea 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -312,7 +312,7 @@ impl LimbedVar { // `d = 0` in this range (after we meet the first positive limb) // This guarantees that for every bit after the true bit in `helper`, // the corresponding limb in `delta` is zero. - (&r * &d).enforce_equal(&FpVar::zero())?; + r.mul_equals(&d, &FpVar::zero())?; // Add the current bit to `r`. r += FpVar::from(b); } @@ -494,8 +494,21 @@ impl LimbedVar Result<(), SynthesisError> { let len = min(self.limbs.len(), other.limbs.len()); - // Group the limbs of `self` and `other` so that each group nearly - // reaches the capacity `F::MODULUS_MINUS_ONE_DIV_TWO`. + let mut i = 0; + let mut carry = FpVar::zero(); + let mut x_bound = Bounds::zero(); + let mut y_bound = Bounds::zero(); + let mut step = 0; + // `unwrap` is safe as long as `F` is a prime field with `|F| > 2`. + let inv = F::from(BigUint::one() << F::BITS_PER_LIMB) + .inverse() + .unwrap(); + + // For each limb pair `(x_i, y_i)` in `self` and `other`, we first try + // to group their _bounds_ into `x_bound` and `y_bound`. + // If both new bounds do not overflow / underflow, we can safely group + // the _limbs_. + // // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W` // is `F::BITS_PER_LIMB`, the initial number of bits in a limb. // This is just as what we do in grade school arithmetic, e.g., @@ -509,51 +522,58 @@ impl LimbedVar 2`. - let inv = F::from(BigUint::one() << F::BITS_PER_LIMB) - .inverse() - .unwrap(); - + // + // Assume a pair of grouped limb `(x', y')` consists of `k` original + // limbs. + // Then the lower `k * W` bits of `x'` and `y'` must be equal. + // To check that, we need to enforce that `2^{k * W}` divides `x' - y'`, + // which is done by computing the quotient `q = (x' - y') / 2^{k * W}` + // and enforcing `q` is small that doesn't cause the multiplication + // `q * 2^{k * W}` to overflow. + // + // Moreover, we need to take into account the carry from the previous + // grouped limb, i.e., we actually enforce `x' - y' + carry` is a + // multiple of `2^{k * W}`, and derive the next carry by computing the + // quotient `q`. + // + // We can further avoid storing `x'` and `y'` by updating the carry on + // the fly for each limb, i.e., `carry = (carry + x_i - y_i) / 2^W`. while i < len { if let (Some(new_x_bound), Some(new_y_bound)) = ( self.bounds[i].shl(step).add(&x_bound).filter_safe::(), other.bounds[i].shl(step).add(&y_bound).filter_safe::(), ) { - diff = (diff + &self.limbs[i] - &other.limbs[i]) * inv; - x_bound = new_x_bound; - y_bound = new_y_bound; + carry = (carry + &self.limbs[i] - &other.limbs[i]) * inv; + // The current limb pair is successfully grouped, so we move on + // to the next limb pair. i += 1; + + // Update the bounds and step for the current group. + x_bound = new_x_bound; + y_bound = new_y_bound; step += F::BITS_PER_LIMB; - continue; + } else { + // New bounds overflow / underflow, meaning the current group is + // finalized. + + // `bits` is the maximum possible bit-length of the carry's + // absolute value. + let bits = (max( + min(&x_bound.0, &y_bound.0).bits(), + max(&x_bound.1, &y_bound.1).bits(), + ) as usize) + .saturating_sub(step); + + // We ensure `carry` is small, i.e., `|carry| < 2^bits`, which + // guarantees that `carry * 2^{step}` does not overflow. + (&carry + F::from(BigUint::one() << bits)).enforce_bit_length(bits + 1)?; + + // Reset the bounds and step for the next group. + x_bound = Bounds::zero(); + y_bound = Bounds::zero(); + step = 0; } - // For each group, check the last `step_i` bits of `x_i` and `y_i` are - // equal. - // The intuition is to check `diff = x_i - y_i = 0 (mod 2^step_i)`. - // However, this is only true for `i = 0`, and we need to consider carry - // values `diff >> step_i` for `i > 0`. - // Therefore, we actually check `diff = x_i - y_i + c = 0 (mod 2^step_i)` - // and derive the next `c` by computing `diff >> step_i`. - // To enforce `diff = 0 (mod 2^step_i)`, we compute `diff / 2^step_i` - // and enforce it to be small (soundness holds because for `a` that does - // not divide `b`, `b / a` in the field will be very large). - let bits = (max( - min(&x_bound.0, &y_bound.0).bits(), - max(&x_bound.1, &y_bound.1).bits(), - ) as usize) - .saturating_sub(step); - - (&diff + F::from(BigUint::one() << bits)).enforce_bit_length(bits + 1)?; - - x_bound = Bounds::zero(); - y_bound = Bounds::zero(); - step = 0; } let remaining_limbs = if i < self.limbs.len() { @@ -567,29 +587,26 @@ impl LimbedVar>() - .enforce_equal(&FpVar::zero())?; - Bounds::add_many(remaining_bounds) + // Instead of doing that one by one, we check if their sum is zero + // using a single constraint. + // This is sound, as we first check that the bounds of their sum + // fit within the field capacity, which guarantees that the sum does + // not overflow or underflow, meaning that the sum is zero if and + // only if each limb is zero. + Bounds::add_many(&remaining_bounds[1..]) .filter_safe::() .ok_or(SynthesisError::Unsatisfiable)?; - // For the final carry, we need to ensure that it equals the - // remaining limb `rest`. - diff.enforce_equal(&remaining_limbs[0])?; + FpVar::zero().enforce_equal(&remaining_limbs[1..].iter().sum())?; } Ok(()) @@ -610,19 +627,23 @@ impl let cs = self.cs(); let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into()); // Provide the quotient and remainder as hints - let q = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { - let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); - Ok(( - compose(self.limbs.value().unwrap_or_default()).div_floor(&m), - Bounds(lb, ub), - )) - })?; - let r = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { - Ok(( - compose(self.limbs.value().unwrap_or_default()).abs() % &m, - Bounds(Zero::zero(), m.clone()), - )) - })?; + let (q, r) = { + let v = compose(self.limbs.value().unwrap_or_default()); + let q = v.div_floor(&m); + let r = v - &q * &m; + + ( + LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { + Ok(( + q, + Bounds(self.lbound().div_floor(&m), self.ubound().div_floor(&m)), + )) + })?, + LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { + Ok((r, Bounds(Zero::zero(), m.clone()))) + })?, + ) + }; let m = LimbedVar::constant(m); @@ -646,10 +667,11 @@ impl let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into()); // Provide the quotient as hint let q = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || { - let (lb, ub) = (self.lbound().div_floor(&m), self.ubound().div_floor(&m)); + let x = compose(self.limbs.value().unwrap_or_default()); + let y = compose(other.limbs.value().unwrap_or_default()); Ok(( - compose(self.limbs.value().unwrap_or_default()).div_floor(&m), - Bounds(lb, ub), + (x - y).div_floor(&m), + Bounds(self.lbound().div_floor(&m), self.ubound().div_floor(&m)), )) })?; @@ -1361,15 +1383,50 @@ mod tests { )) })?; + let neg_a_var = EmulatedFieldVar::constant(BigInt::zero()) - &a_var; + let neg_b_var = EmulatedFieldVar::constant(BigInt::zero()) - &b_var; + let neg_ab_var = EmulatedFieldVar::constant(BigInt::zero()) - &ab_var; + let neg_aab_var = EmulatedFieldVar::constant(BigInt::zero()) - &aab_var; + let neg_abb_var = EmulatedFieldVar::constant(BigInt::zero()) - &abb_var; + a_var .mul_unaligned(&b_var)? .enforce_equal_unaligned(&ab_var)?; + neg_a_var + .mul_unaligned(&neg_b_var)? + .enforce_equal_unaligned(&ab_var)?; + a_var + .mul_unaligned(&neg_b_var)? + .enforce_equal_unaligned(&neg_ab_var)?; + neg_a_var + .mul_unaligned(&b_var)? + .enforce_equal_unaligned(&neg_ab_var)?; + a_var .mul_unaligned(&ab_var)? .enforce_equal_unaligned(&aab_var)?; + neg_a_var + .mul_unaligned(&neg_ab_var)? + .enforce_equal_unaligned(&aab_var)?; + a_var + .mul_unaligned(&neg_ab_var)? + .enforce_equal_unaligned(&neg_aab_var)?; + neg_a_var + .mul_unaligned(&ab_var)? + .enforce_equal_unaligned(&neg_aab_var)?; + ab_var .mul_unaligned(&b_var)? .enforce_equal_unaligned(&abb_var)?; + neg_ab_var + .mul_unaligned(&neg_b_var)? + .enforce_equal_unaligned(&abb_var)?; + ab_var + .mul_unaligned(&neg_b_var)? + .enforce_equal_unaligned(&neg_abb_var)?; + neg_ab_var + .mul_unaligned(&b_var)? + .enforce_equal_unaligned(&neg_abb_var)?; assert!(cs.is_satisfied()?); Ok(()) @@ -1392,9 +1449,65 @@ mod tests { let aab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(aab))?; let abb_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(abb))?; + let neg_a_var = EmulatedFieldVar::constant(BigInt::zero()) - &a_var; + let neg_b_var = EmulatedFieldVar::constant(BigInt::zero()) - &b_var; + let neg_ab_var = EmulatedFieldVar::constant(BigInt::zero()) - &ab_var; + let neg_aab_var = EmulatedFieldVar::constant(BigInt::zero()) - &aab_var; + let neg_abb_var = EmulatedFieldVar::constant(BigInt::zero()) - &abb_var; + a_var.mul_unaligned(&b_var)?.enforce_congruent(&ab_var)?; + neg_a_var + .mul_unaligned(&neg_b_var)? + .enforce_congruent(&ab_var)?; + a_var + .mul_unaligned(&neg_b_var)? + .enforce_congruent(&neg_ab_var)?; + neg_a_var + .mul_unaligned(&b_var)? + .enforce_congruent(&neg_ab_var)?; + a_var.mul_unaligned(&ab_var)?.enforce_congruent(&aab_var)?; + neg_a_var + .mul_unaligned(&neg_ab_var)? + .enforce_congruent(&aab_var)?; + a_var + .mul_unaligned(&neg_ab_var)? + .enforce_congruent(&neg_aab_var)?; + neg_a_var + .mul_unaligned(&ab_var)? + .enforce_congruent(&neg_aab_var)?; + ab_var.mul_unaligned(&b_var)?.enforce_congruent(&abb_var)?; + neg_ab_var + .mul_unaligned(&neg_b_var)? + .enforce_congruent(&abb_var)?; + ab_var + .mul_unaligned(&neg_b_var)? + .enforce_congruent(&neg_abb_var)?; + neg_ab_var + .mul_unaligned(&b_var)? + .enforce_congruent(&neg_abb_var)?; + + assert_eq!(a_var.mul_unaligned(&b_var)?.modulo()?.value()?, ab); + assert_eq!(neg_a_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, ab); + assert_eq!(a_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, -ab); + assert_eq!(neg_a_var.mul_unaligned(&b_var)?.modulo()?.value()?, -ab); + + assert_eq!(a_var.mul_unaligned(&ab_var)?.modulo()?.value()?, aab); + assert_eq!( + neg_a_var.mul_unaligned(&neg_ab_var)?.modulo()?.value()?, + aab + ); + assert_eq!(a_var.mul_unaligned(&neg_ab_var)?.modulo()?.value()?, -aab); + assert_eq!(neg_a_var.mul_unaligned(&ab_var)?.modulo()?.value()?, -aab); + + assert_eq!(ab_var.mul_unaligned(&b_var)?.modulo()?.value()?, abb); + assert_eq!( + neg_ab_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, + abb + ); + assert_eq!(ab_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, -abb); + assert_eq!(neg_ab_var.mul_unaligned(&b_var)?.modulo()?.value()?, -abb); assert!(cs.is_satisfied()?); Ok(()) From bd33ed00c28482b27b6a0206d3b4907e50a24ce0 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 33/99] Fix broken images in readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bb9ee6f10..19ca77b10 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Experimental folding schemes library implemented jointly by [0xPARC](https://0xparc.org/) and [PSE](https://pse.dev). - + Sonobe is a modular library to fold arithmetic circuit instances in an Incremental Verifiable computation (IVC) style. It features multiple folding schemes and decider setups, allowing users to pick the scheme which best fits their needs.

@@ -67,7 +67,7 @@ Once the IVC iterations are completed, the IVC proof is compressed into the Deci

- +

Where $w_i$ are the external witnesses used at each iterative step. @@ -87,14 +87,14 @@ The development flow using Sonobe looks like: 4. Generate the decider verifier

- +

The folding scheme and decider used can be swapped with a few lines of code (eg. switching from a Decider that uses two Spartan proofs over a cycle of curves, to a Decider that uses a single Groth16 proof over the BN254 to be verified in an Ethereum smart contract). The [Sonobe docs](https://privacy-scaling-explorations.github.io/sonobe-docs/) contain more details about the usage and design of the library. -Complete examples can be found at [folding-schemes/examples](https://github.com/privacy-scaling-explorations/sonobe/tree/main/examples) +Complete examples can be found at [folding-schemes/examples](https://github.com/privacy-scaling-explorations/sonobeAcknowledgments/tree/main/examples) ## License From 226f96157095d7c3e2bbdd7045cb7a1624575fa1 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 15:51:57 +0800 Subject: [PATCH 34/99] New CI --- .github/scripts/wasm-target-test-build.sh | 30 ---- .github/workflows/ci.yml | 161 ++++++---------------- 2 files changed, 39 insertions(+), 152 deletions(-) delete mode 100644 .github/scripts/wasm-target-test-build.sh diff --git a/.github/scripts/wasm-target-test-build.sh b/.github/scripts/wasm-target-test-build.sh deleted file mode 100644 index 3c42427cd..000000000 --- a/.github/scripts/wasm-target-test-build.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -GIT_ROOT=$(pwd) - -cd /tmp - -# create test project -cargo new foobar -cd foobar - -# set rust-toolchain same as "sonobe" -cp "${GIT_ROOT}/rust-toolchain" . - -# add wasm32-* targets -rustup target add wasm32-unknown-unknown wasm32-wasip1 - -# add dependencies -cargo add --path "${GIT_ROOT}/frontends" --features wasm, parallel -cargo add --path "${GIT_ROOT}/folding-schemes" --features parallel -cargo add getrandom --features wasm_js --target wasm32-unknown-unknown - -# test build for wasm32-* targets -cargo build --release --target wasm32-unknown-unknown -cargo build --release --target wasm32-wasip1 -# Emscripten would require to fetch the `emcc` tooling. Hence we don't build the lib as a dep for it. -# cargo build --release --target wasm32-unknown-emscripten - -# delete test project -cd ../ -rm -rf foobar diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4b70d0cc..7989e8315 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,6 @@ name: CI Check on: + workflow_dispatch: merge_group: pull_request: push: @@ -36,44 +37,26 @@ concurrency: jobs: test: if: github.event.pull_request.draft == false - name: Test + name: Test (${{ matrix.features }}) runs-on: ubuntu-latest strategy: matrix: - feature_set: [basic] + features: + - default + - no-default include: - - feature_set: basic - features: --features default,light-test + - features: default + args: "" + - features: no-default + args: "--no-default-features" steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - - uses: noir-lang/noirup@v0.1.3 - with: - toolchain: 0.36.0 - - name: Download Circom - run: | - mkdir -p $HOME/bin - curl -sSfL https://github.com/iden3/circom/releases/download/v2.1.6/circom-linux-amd64 -o $HOME/bin/circom - chmod +x $HOME/bin/circom - echo "$HOME/bin" >> $GITHUB_PATH - - name: Download solc - run: | - curl -sSfL https://github.com/ethereum/solidity/releases/download/v0.8.4/solc-static-linux -o /usr/local/bin/solc - chmod +x /usr/local/bin/solc - - name: Execute compile.sh to generate .r1cs and .wasm from .circom - run: ./experimental-frontends/src/circom/test_folder/compile.sh - - name: Execute compile.sh to generate .json from noir - run: ./experimental-frontends/src/noir/test_folder/compile.sh + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --workspace --no-default-features ${{ matrix.features }} + run: cargo test --release --workspace ${{ matrix.args }} - name: Run Doc-tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --doc + run: cargo test --doc ${{ matrix.args }} build: if: github.event.pull_request.draft == false @@ -82,79 +65,21 @@ jobs: strategy: matrix: target: + - x86_64-unknown-linux-gnu - wasm32-unknown-unknown - wasm32-wasip1 - # Ignoring until clear usage is required - # - wasm32-unknown-emscripten - steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - override: false - default: true - - name: Add target - run: rustup target add ${{ matrix.target }} - - name: Wasm-compat experimental-frontends build - uses: actions-rs/cargo@v1 - with: - command: build - args: -p experimental-frontends --no-default-features --target ${{ matrix.target }} --features "wasm, parallel" - - name: Wasm-compat folding-schemes build - uses: actions-rs/cargo@v1 - with: - command: build - args: -p folding-schemes --no-default-features --target ${{ matrix.target }} --features "default,light-test" - - name: Run wasm-compat script - run: | - chmod +x .github/scripts/wasm-target-test-build.sh - .github/scripts/wasm-target-test-build.sh - shell: bash - - examples: - if: github.event.pull_request.draft == false - name: Run examples & examples tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - - uses: noir-lang/noirup@v0.1.3 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: 0.36.0 - - name: Download Circom - run: | - mkdir -p $HOME/bin - curl -sSfL https://github.com/iden3/circom/releases/download/v2.1.6/circom-linux-amd64 -o $HOME/bin/circom - chmod +x $HOME/bin/circom - echo "$HOME/bin" >> $GITHUB_PATH - - name: Download solc - run: | - curl -sSfL https://github.com/ethereum/solidity/releases/download/v0.8.4/solc-static-linux -o /usr/local/bin/solc - chmod +x /usr/local/bin/solc - - name: Execute compile.sh to generate .r1cs and .wasm from .circom - run: ./experimental-frontends/src/circom/test_folder/compile.sh - - name: Execute compile.sh to generate .json from noir - run: ./experimental-frontends/src/noir/test_folder/compile.sh - - name: Run examples tests - run: cargo test --examples - - name: Run examples - run: cargo run --release --example 2>&1 | grep -E '^ ' | xargs -n1 cargo run --release --example - - # run the benchmarks with the flag `--no-run` to ensure that they compile, - # but without executing them. - bench: - if: github.event.pull_request.draft == false - name: Bench compile - timeout-minutes: 30 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - uses: actions-rs/cargo@v1 - with: - command: bench - args: -p folding-schemes --no-run + - name: Build sonobe-primitives for ${{ matrix.target }} + run: cargo build -p sonobe-primitives --no-default-features --target ${{ matrix.target }} --features parallel + - name: Build sonobe-fs for ${{ matrix.target }} + run: cargo build -p sonobe-fs --no-default-features --target ${{ matrix.target }} --features parallel + - name: Build sonobe-ivc for ${{ matrix.target }} + run: cargo build -p sonobe-ivc --no-default-features --target ${{ matrix.target }} --features parallel fmt: if: github.event.pull_request.draft == false @@ -162,41 +87,33 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - - uses: Swatinem/rust-cache@v2 - - run: rustup component add rustfmt - - uses: actions-rs/cargo@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - command: fmt - args: --all --check + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Run rustfmt + run: cargo fmt --all --check clippy: if: github.event.pull_request.draft == false - name: Clippy lint checks + name: Clippy (${{ matrix.target }}) runs-on: ubuntu-latest strategy: matrix: - feature_set: [basic, wasm] - include: - - feature_set: basic - features: --features default - # We only want to test `experimental-frontends` package with `wasm` feature. - - feature_set: wasm - features: -p experimental-frontends --features wasm,parallel --target wasm32-unknown-unknown + target: + - x86_64-unknown-linux-gnu + - wasm32-unknown-unknown + - wasm32-wasip1 steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: components: clippy + targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - name: Add target - run: rustup target add wasm32-unknown-unknown - name: Run clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --no-default-features ${{ matrix.features }} -- -D warnings + run: cargo clippy --workspace --all-targets --target ${{ matrix.target }} -- -D warnings typos: if: github.event.pull_request.draft == false From 9b7f4d66420f0975a75da76e8684ff4edb2ec61b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 35/99] Actually test wasm targets --- .cargo/config.toml | 5 ++++ .github/workflows/ci.yml | 57 ++++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..983e89ba0 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' + +[target.wasm32-wasip2] +runner = 'wasmtime' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7989e8315..9ca52f736 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,26 +37,45 @@ concurrency: jobs: test: if: github.event.pull_request.draft == false - name: Test (${{ matrix.features }}) + name: Test ${{ matrix.target }} (${{ matrix.features }}) runs-on: ubuntu-latest strategy: matrix: - features: - - default - - no-default include: - - features: default + # x64: both parallel and no-parallel + - target: x86_64-unknown-linux-gnu + features: parallel + args: "--features parallel" + - target: x86_64-unknown-linux-gnu + features: no-parallel + args: "" + # wasm: no-parallel only + - target: wasm32-unknown-unknown + features: no-parallel + args: "" + - target: wasm32-wasip2 + features: no-parallel args: "" - - features: no-default - args: "--no-default-features" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - name: Run tests - run: cargo test --release --workspace ${{ matrix.args }} - - name: Run Doc-tests - run: cargo test --doc ${{ matrix.args }} + - name: Install wasm-bindgen-cli + if: matrix.target == 'wasm32-unknown-unknown' + run: cargo install wasm-bindgen-cli + - name: Install wasmtime-cli + if: matrix.target == 'wasm32-wasip2' + run: cargo install wasmtime-cli + - name: Test sonobe-primitives + run: cargo test --release -p sonobe-primitives --target ${{ matrix.target }} ${{ matrix.args }} + - name: Test sonobe-fs + run: cargo test --release -p sonobe-fs --target ${{ matrix.target }} ${{ matrix.args }} + - name: Test sonobe-ivc + run: cargo test --release -p sonobe-ivc --target ${{ matrix.target }} ${{ matrix.args }} + - name: Test documentation examples + run: cargo test --doc --target ${{ matrix.target }} ${{ matrix.args }} build: if: github.event.pull_request.draft == false @@ -67,19 +86,19 @@ jobs: target: - x86_64-unknown-linux-gnu - wasm32-unknown-unknown - - wasm32-wasip1 + - wasm32-wasip2 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - - name: Build sonobe-primitives for ${{ matrix.target }} - run: cargo build -p sonobe-primitives --no-default-features --target ${{ matrix.target }} --features parallel - - name: Build sonobe-fs for ${{ matrix.target }} - run: cargo build -p sonobe-fs --no-default-features --target ${{ matrix.target }} --features parallel - - name: Build sonobe-ivc for ${{ matrix.target }} - run: cargo build -p sonobe-ivc --no-default-features --target ${{ matrix.target }} --features parallel + - name: Build sonobe-primitives + run: cargo build -p sonobe-primitives --target ${{ matrix.target }} + - name: Build sonobe-fs + run: cargo build -p sonobe-fs --target ${{ matrix.target }} + - name: Build sonobe-ivc + run: cargo build -p sonobe-ivc --target ${{ matrix.target }} fmt: if: github.event.pull_request.draft == false @@ -104,7 +123,7 @@ jobs: target: - x86_64-unknown-linux-gnu - wasm32-unknown-unknown - - wasm32-wasip1 + - wasm32-wasip2 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable From 44fa229ca691169864c097779a7c07bd72171c6d Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:11:22 +0800 Subject: [PATCH 36/99] Notes on terminology --- docs/Terminology.md | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/Terminology.md diff --git a/docs/Terminology.md b/docs/Terminology.md new file mode 100644 index 000000000..b6e736017 --- /dev/null +++ b/docs/Terminology.md @@ -0,0 +1,50 @@ +## Disambiguation of "Native" + +In cryptographic proof systems, the term "native" can have multiple interpretations depending on the specific context of discussion. + +### Context 1: Native vs Emulated + +When referring to "native field / curve" and "emulated (non-native) field / curve," "native" denotes that the field or curve can be directly represented within the arithmetic circuit of the proof system. +More specifically, "native" in native field means that the field is the same as the circuit's constraint field (i.e., the field over which the circuit is defined). +Similarly, a "native" curve is one whose base field (i.e., the field over which the curve is defined, which is also the field that a point's coordinates belong to) matches the circuit's constraint field. + +In contrast, "emulated" or "non-native" fields and curves are those that cannot be directly represented in the circuit's constraint field and thus require special handling (a.k.a. emulation) within the circuit. + +> [!TIP] +> A side note irrelevant to the main discussion is that the boundary between "native" and "emulated" is not very clear-cut. +> +> For instance, as long as the foundamental element of the circuit is not a curve point (which is the case for all current constraint systems), even a native curve is "emulated" in some sense because curve points need to be encoded as multiple elements in the constraint field and curve operations need to be broken down into field operations. +> One can further argue that, if we regard such an "emulation" as a native representation of the curve, then why not also consider emulated fields as native as well, since they are also encoded as multiple elements in the constraint field. +> +> In Sonobe, we distinguish "native" and "emulated" based on whether the in-circuit representation is the preferred or most efficient form for the given field or curve. For a field element, the preferred representation is a single element in the constraint field, while for a curve point, it is a tuple of elements in the constraint field representing the coordinates, but each coordinate itself is not further decomposed. Consequently, if the circuit is able to achieve these preferred representations, we classify the field or curve as "native"; otherwise, it is deemed "emulated." + +### Context 2: Native vs In-Circuit + +Another common usage of "native" is to distinguish between values and operations built in the host programming language (e.g., Rust) and those defined in the arithmetic circuit of the proof system. In the former case, we refer to them as "native"/"out-of-circuit", while in the latter case, we call them "in-circuit". + +### Proposed New Terminology + +It is unlikely for experienced practitioners to confuse the two contexts above when "native" is used, as the context usually makes it clear which meaning is intended. +However, to ensure everyone is on the same page and to avoid any potential mental overhead in interpreting "native" correctly, we propose adopting more specific terminology for each context. + +- For Context 1, prefer _Canonical_ vs _Emulated_. + + **Justification**: _Canonical_ is not a standard term in the literature and is coined for use in Sonobe. However, it intuitively conveys the idea of being the standard or preferred representation within the circuit. +- For Context 2: + - When referring to something that holds data: + - Prefer _Value_ vs _Variable_. Further qualify them as _Out-of-Circuit Value_ and _In-Circuit Variable_ if necessary. + - Neutral terms such as _Data_, _Element_, _Key_, _Instance_, _Witness_, etc., are also acceptable when solely focusing on the in-circuit or out-of-circuit context. + + **Justification**: The use of _Value_ and _Variable_ aligns with existing conventions, as these terms are widely used in the arkworks codebase. + - When referring to something that performs computation: + - Prefer _Widget_ vs _Gadget_. Further qualify them as _Out-of-Circuit Widget_ and _In-Circuit Gadget_ if necessary. + - Neutral terms such as _Algorithm_, _Procedure_, _Function_, _Method_, etc., are also acceptable when solely focusing on the in-circuit or out-of-circuit context. + + **Justification**: _Gadget_ is already a standard term for in-circuit computation modules or utilities. + + _Widget_ is invented by us to suggest a computational component that operates outside the circuit while maintaining a consistent and visually / phonetically appealing naming scheme. + + Furthermore, searching for "widget vs gadget" yields results that align with our intended meanings. For instance, [this article](https://www.thoughtco.com/widget-vs-gadget-3486689) suggests that in web development, "widgets work on multiple platforms, but gadgets are usually limited to specific devices or systems." This distinction resonates with our usage, where widgets operate in the general-purpose host environment, while gadgets are specialized for the circuit environment. + +The proposed terminology is used throughout the Sonobe documentation and codebase. +For contributions, we recommend doing so as well to enhance clarity and reduce ambiguity. However, in casual discussions / issue reports, it is fine to use "native" for both contexts. \ No newline at end of file From 7fdcd8617eb75462e1912ce311c17072aa2bac6f Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 37/99] Refactor: initialize revamped folding scheme impl --- Cargo.toml | 2 + crates/fs/Cargo.toml | 29 +++++ crates/fs/src/lib.rs | 281 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 crates/fs/Cargo.toml create mode 100644 crates/fs/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 916915046..94f8fa090 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/primitives", + "crates/fs", ] resolver = "2" @@ -40,6 +41,7 @@ ark-vesta = { git = "https://github.com/arkworks-rs/algebra", default-features = # Local crates sonobe-primitives = { path = "crates/primitives", default-features = false } +sonobe-fs = { path = "crates/fs", default-features = false } [patch.crates-io] # We depend on git versions of arkworks crates, but some of our dependencies diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml new file mode 100644 index 000000000..093ca3777 --- /dev/null +++ b/crates/fs/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "sonobe-fs-wip" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } +ark-ec = { workspace = true } +ark-ff = { workspace = true, features = ["asm"] } +ark-poly = { workspace = true } +ark-relations = { workspace = true } +ark-std = { workspace = true, features = ["getrandom"] } +ark-serialize = { workspace = true } +thiserror = { workspace = true } +rayon = { workspace = true } + +sonobe-primitives = { workspace = true } + +[dev-dependencies] +ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } +ark-pallas = { workspace = true, features = ["curve", "r1cs"] } + +[features] +default = ["parallel"] +parallel = [ + "sonobe-primitives/parallel", +] diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs new file mode 100644 index 000000000..af008aedc --- /dev/null +++ b/crates/fs/src/lib.rs @@ -0,0 +1,281 @@ +use ark_relations::gr1cs::SynthesisError; +use ark_std::{fmt::Debug, rand::RngCore}; +use sonobe_primitives::{ + arithmetizations::Arith, + circuits::AssignmentsOwned, + commitments::CommitmentDef, + relations::{Relation, WitnessInstanceSampler}, + sumcheck::Error as SumCheckError, + traits::SonobeField, + transcripts::Transcript, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Arithmetization error: {0}")] + ArithError(#[from] sonobe_primitives::arithmetizations::Error), + #[error("Commitment error: {0}")] + CommitmentError(#[from] sonobe_primitives::commitments::Error), + #[error("Synthesis error: {0}")] + SynthesisError(#[from] SynthesisError), + #[error("Sumcheck error: {0}")] + SumCheckError(#[from] SumCheckError), + #[error("Unsupported use case: {0}")] + Unsupported(String), + #[error("Failed to create domain")] + DomainCreationFailure, +} + +pub trait FoldingWitness: Sync { + /// Returns the reference to all openings contained in the witness, each + /// being a tuple of the values being committed to and the randomness. + fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; +} + +impl FoldingWitness for Vec { + fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { + vec![] + } +} + +pub trait FoldingInstance: Debug + PartialEq + Sync { + /// Returns the commitments contained in the committed instance. + fn commitments(&self) -> Vec<&VC::Commitment>; +} + +impl FoldingInstance for Vec { + fn commitments(&self) -> Vec<&VC::Commitment> { + vec![] + } +} + +// pub trait WitnessOps: PartialEq + Clone + Debug { +// /// The in-circuit representation of the witness. +// type Var: AllocVar + WitnessVarOps; + +// /// Returns the openings (i.e., the values being committed to and the +// /// randomness) contained in the witness. +// fn get_openings(&self) -> Vec<(&[F], F)>; +// } + +// pub trait WitnessVarOps { +// /// Returns the openings (i.e., the values being committed to and the +// /// randomness) contained in the witness. +// fn get_openings(&self) -> Vec<(&[FpVar], FpVar)>; +// } + +// pub trait CommittedInstanceOps: Inputize + PartialEq + Clone + Debug { +// type C: Curve; + +// /// The in-circuit representation of the committed instance. +// type Var: AllocVar + CommittedInstanceVarOps; +// /// `hash` implements the committed instance hash compatible with the +// /// in-circuit implementation from `CommittedInstanceVarOps::hash`. +// /// +// /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and +// /// `U_i` is the committed instance `self`. +// fn hash>(&self, sponge: &T, i: F, z_0: &[F], z_i: &[F]) -> F +// where +// Self: Sized + Absorb, +// F: Absorb, +// { +// let mut sponge = sponge.clone(); +// sponge.absorb(&i); +// sponge.absorb(&z_0); +// sponge.absorb(&z_i); +// sponge.absorb(&self); +// sponge.squeeze_field_elements(1)[0] +// } + +// /// Returns the commitments contained in the committed instance. +// fn get_commitments(&self) -> Vec; + +// /// Returns `true` if the committed instance is an incoming instance, and +// /// `false` if it is a running instance. +// fn is_incoming(&self) -> bool; + +// /// Checks if the committed instance is an incoming instance. +// fn check_incoming(&self) -> Result<(), Error> { +// self.is_incoming() +// .then_some(()) +// .ok_or(Error::NotIncomingCommittedInstance) +// } +// } + +// pub trait CommittedInstanceVarOps { +// type PointVar; +// /// `hash` implements the in-circuit committed instance hash compatible with +// /// the native implementation from `CommittedInstanceOps::hash`. +// /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and +// /// `U_i` is the committed instance `self`. +// /// +// /// Additionally it returns the in-circuit representation of the committed +// /// instance `self` as a vector of field elements, so they can be reused in +// /// other gadgets avoiding recalculating (reconstraining) them. +// #[allow(clippy::type_complexity)] +// fn hash>( +// &self, +// sponge: &impl TranscriptVar, +// i: &FpVar, +// z_0: &[FpVar], +// z_i: &[FpVar], +// ) -> Result<(FpVar, Vec>), SynthesisError> +// where +// Self: AbsorbGadget, +// { +// let mut sponge = sponge.clone(); +// let vec = self.to_sponge_field_elements()?; +// sponge.absorb(&i)?; +// sponge.absorb(&z_0)?; +// sponge.absorb(&z_i)?; +// sponge.absorb(&vec)?; +// Ok(( +// // `unwrap` is safe because the sponge is guaranteed to return a single element +// sponge.squeeze_field_elements(1)?.pop().unwrap(), +// vec, +// )) +// } + +// /// Returns the commitments contained in the committed instance. +// fn get_commitments(&self) -> Vec; + +// /// Returns the public inputs contained in the committed instance. +// fn get_public_inputs(&self) -> &[FpVar]; + +// /// Generates constraints to enforce that the committed instance is an +// /// incoming instance. +// fn enforce_incoming(&self) -> Result<(), SynthesisError>; + +// /// Generates constraints to enforce that the committed instance `self` is +// /// partially equal to another committed instance `other`. +// /// Here, only field elements are compared, while commitments (points) are +// /// not. +// fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError>; +// } + +pub trait FoldingScheme { + type VC: CommitmentDef; + type RW: FoldingWitness; + type RU: FoldingInstance; + type IW: FoldingWitness; + type IU: FoldingInstance; + type TranscriptField: SonobeField; + type Arith: Arith; + type Config; + type PublicParam; + type ProverKey; + type VerifierKey; + type DeciderKey: Relation + + Relation + + WitnessInstanceSampler + + WitnessInstanceSampler< + Self::IW, + Self::IU, + Source = AssignmentsOwned<::Scalar>, + Error = Error, + >; + type Proof; + + /// The preprocessing method is a randomized algorithm that takes as input + /// the size bounds of the folding scheme, which are contained in the + /// `config` parameter, and outputs the public parameters. + /// + /// Here, the randomness source is controlled by `rng`. + /// + /// The security parameter is implicitly specified by the size of underlying + /// fields and groups. + fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; + + /// The key generation method is a deterministic algorithm that takes as + /// input the public parameters `pp` and the constraint system `arith`, and + /// outputs a prover key and a verifier key. + fn generate_keys( + pp: Self::PublicParam, + arith: Self::Arith, + ) -> Result<(Self::ProverKey, Self::VerifierKey, Self::DeciderKey), Error>; + + /// The proof generation method is a deterministic algorithm that takes as + /// input the prover key `pk`, the transcript `transcript` between the + /// prover and the verifier, the first witness-instance pair `W`, `U`, the + /// second witness-instance pair `w`, `u`, and outputs the folded witness + /// and instance, the proof, and the (intermediate) randomness. + /// + /// Here, the randomness source is controlled by `transcript`. The returned + /// intermediate randomness is useful for the construction of CycleFold + /// circuits in our CycleFold-based folding-to-IVC compiler. + fn prove( + pk: &Self::ProverKey, + transcript: &mut impl Transcript, + Ws: &[&Self::RW; M], + Us: &[&Self::RU; M], + ws: &[&Self::IW; N], + us: &[&Self::IU; N], + rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof), Error>; + + fn verify( + vk: &Self::VerifierKey, + transcript: &mut impl Transcript, + Us: &[&Self::RU; M], + us: &[&Self::IU; N], + proof: &Self::Proof, + ) -> Result; + + fn decide_running(dk: &Self::DeciderKey, W: &Self::RW, U: &Self::RU) -> Result<(), Error> { + Relation::::check_relation(dk, W, U) + } + + fn decide_incoming(dk: &Self::DeciderKey, w: &Self::IW, u: &Self::IU) -> Result<(), Error> { + Relation::::check_relation(dk, w, u) + } +} + +#[cfg(test)] +mod tests { + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; + use ark_std::{error::Error, rand::Rng}; + use sonobe_primitives::{ + circuits::{ArithExtractor, AssignmentsOwned}, + transcripts::poseidon::poseidon_canonical_config, + }; + + use super::*; + + pub fn test_folding_scheme_1_1( + config: FS::Config, + circuit: impl ConstraintSynthesizer<::Scalar>, + assignments_vec: Vec::Scalar>>, + mut rng: impl Rng, + ) -> Result<(), Box> + where + FS: FoldingScheme<1, 1, Arith: From::Scalar>>>, + { + let pp = FS::preprocess(config, &mut rng)?; + + let cs = ArithExtractor::new(); + cs.execute_synthesizer(circuit)?; + let arith = cs.arith()?; + let (pk, vk, dk) = FS::generate_keys(pp, arith)?; + + let (mut W, mut U) = WitnessInstanceSampler::::sample(&dk, (), &mut rng)?; + FS::decide_running(&dk, &W, &U)?; + let mut transcript_p = PoseidonSponge::new(&poseidon_canonical_config()); + let mut transcript_v = PoseidonSponge::new(&poseidon_canonical_config()); + + for assignments in assignments_vec { + let (w, u) = + WitnessInstanceSampler::::sample(&dk, assignments, &mut rng)?; + FS::decide_incoming(&dk, &w, &u)?; + let (WW, UU, pi) = + FS::prove(&pk, &mut transcript_p, &[&W], &[&U], &[&w], &[&u], &mut rng)?; + FS::decide_running(&dk, &WW, &UU)?; + assert_eq!(FS::verify(&vk, &mut transcript_v, &[&U], &[&u], &pi)?, UU); + + (W, U) = (WW, UU); + } + + Ok(()) + } +} From 9982261d2aad61e5788dad4ab045c3ad9df1b362 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 15:04:52 +0800 Subject: [PATCH 38/99] Refactor: improve test template --- crates/fs/src/lib.rs | 68 +++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index af008aedc..625bfeda4 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -27,7 +27,7 @@ pub enum Error { DomainCreationFailure, } -pub trait FoldingWitness: Sync { +pub trait FoldingWitness: Debug + Sync { /// Returns the reference to all openings contained in the witness, each /// being a tuple of the values being committed to and the randomness. fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; @@ -207,18 +207,18 @@ pub trait FoldingScheme { fn prove( pk: &Self::ProverKey, transcript: &mut impl Transcript, - Ws: &[&Self::RW; M], - Us: &[&Self::RU; M], - ws: &[&Self::IW; N], - us: &[&Self::IU; N], + Ws: &[Self::RW; M], + Us: &[Self::RU; M], + ws: &[Self::IW; N], + us: &[Self::IU; N], rng: impl RngCore, ) -> Result<(Self::RW, Self::RU, Self::Proof), Error>; fn verify( vk: &Self::VerifierKey, transcript: &mut impl Transcript, - Us: &[&Self::RU; M], - us: &[&Self::IU; N], + Us: &[Self::RU; M], + us: &[Self::IU; N], proof: &Self::Proof, ) -> Result; @@ -243,14 +243,14 @@ mod tests { use super::*; - pub fn test_folding_scheme_1_1( + pub fn test_folding_scheme( config: FS::Config, circuit: impl ConstraintSynthesizer<::Scalar>, assignments_vec: Vec::Scalar>>, mut rng: impl Rng, ) -> Result<(), Box> where - FS: FoldingScheme<1, 1, Arith: From::Scalar>>>, + FS: FoldingScheme::Scalar>>>, { let pp = FS::preprocess(config, &mut rng)?; @@ -259,21 +259,51 @@ mod tests { let arith = cs.arith()?; let (pk, vk, dk) = FS::generate_keys(pp, arith)?; - let (mut W, mut U) = WitnessInstanceSampler::::sample(&dk, (), &mut rng)?; - FS::decide_running(&dk, &W, &U)?; + let mut Ws = vec![]; + let mut Us = vec![]; + for _ in 0..M { + let (W, U) = WitnessInstanceSampler::::sample(&dk, (), &mut rng)?; + FS::decide_running(&dk, &W, &U)?; + Ws.push(W); + Us.push(U); + } + let mut Ws = Ws.try_into().unwrap(); + let mut Us = Us.try_into().unwrap(); + let mut transcript_p = PoseidonSponge::new(&poseidon_canonical_config()); let mut transcript_v = PoseidonSponge::new(&poseidon_canonical_config()); for assignments in assignments_vec { - let (w, u) = - WitnessInstanceSampler::::sample(&dk, assignments, &mut rng)?; - FS::decide_incoming(&dk, &w, &u)?; - let (WW, UU, pi) = - FS::prove(&pk, &mut transcript_p, &[&W], &[&U], &[&w], &[&u], &mut rng)?; + let mut ws = vec![]; + let mut us = vec![]; + for _ in 0..N { + let (w, u) = WitnessInstanceSampler::::sample( + &dk, + assignments.clone(), + &mut rng, + )?; + FS::decide_incoming(&dk, &w, &u)?; + ws.push(w); + us.push(u); + } + let ws = ws.try_into().unwrap(); + let us = us.try_into().unwrap(); + + let (WW, UU, pi) = FS::prove(&pk, &mut transcript_p, &Ws, &Us, &ws, &us, &mut rng)?; FS::decide_running(&dk, &WW, &UU)?; - assert_eq!(FS::verify(&vk, &mut transcript_v, &[&U], &[&u], &pi)?, UU); - - (W, U) = (WW, UU); + assert_eq!(FS::verify(&vk, &mut transcript_v, &Us, &us, &pi)?, UU); + + for i in 0..M { + let (W, U) = WitnessInstanceSampler::::sample(&dk, (), &mut rng)?; + FS::decide_running(&dk, &W, &U)?; + Ws[i] = W; + Us[i] = U; + } + if M != 0 { + let idx = rng.gen_range(0..M); + Ws[idx] = WW; + Us[idx] = UU; + } } Ok(()) From c608afe9f2fd6343bd084c1d99213454cdc1ffe8 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:52 +0800 Subject: [PATCH 39/99] Refactor: initialize revamped IVC --- Cargo.toml | 2 + crates/fs/Cargo.toml | 2 +- crates/ivc/Cargo.toml | 22 +++++++++ crates/ivc/src/compilers/mod.rs | 0 crates/ivc/src/lib.rs | 84 +++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 crates/ivc/Cargo.toml create mode 100644 crates/ivc/src/compilers/mod.rs create mode 100644 crates/ivc/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 94f8fa090..d1b8c62ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/primitives", "crates/fs", + "crates/ivc", ] resolver = "2" @@ -42,6 +43,7 @@ ark-vesta = { git = "https://github.com/arkworks-rs/algebra", default-features = # Local crates sonobe-primitives = { path = "crates/primitives", default-features = false } sonobe-fs = { path = "crates/fs", default-features = false } +sonobe-ivc = { path = "crates/ivc", default-features = false } [patch.crates-io] # We depend on git versions of arkworks crates, but some of our dependencies diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 093ca3777..1ae15744f 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sonobe-fs-wip" +name = "sonobe-fs" version = "0.1.0" edition.workspace = true license.workspace = true diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml new file mode 100644 index 000000000..8d4d67c76 --- /dev/null +++ b/crates/ivc/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "sonobe-ivc" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +ark-ff = { workspace = true, features = ["asm"] } +ark-std = { workspace = true, features = ["getrandom"] } + +sonobe-primitives = { workspace = true } +sonobe-fs = { workspace = true } + +[dev-dependencies] + + +[features] +default = ["parallel"] +parallel = [ + "sonobe-fs/parallel", +] \ No newline at end of file diff --git a/crates/ivc/src/compilers/mod.rs b/crates/ivc/src/compilers/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs new file mode 100644 index 000000000..6db1c46c3 --- /dev/null +++ b/crates/ivc/src/lib.rs @@ -0,0 +1,84 @@ +use ark_ff::PrimeField; +use ark_std::rand::RngCore; +use sonobe_primitives::circuits::FCircuit; + +pub mod compilers; + +pub enum Error {} + +pub trait IVC { + type Field: PrimeField; + + type Config; + type PublicParam; + type ProverKey; + type VerifierKey; + type Proof: Default; + + fn preprocess(rng: impl RngCore, config: &Self::Config) -> Result; + + fn generate_keys>( + pp: &Self::PublicParam, + step_circuit: &FC, + ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; + + fn prove>( + pk: &Self::ProverKey, + step_circuit: &FC, + i: usize, + initial_state: &[Self::Field], + current_state: &[Self::Field], + external_inputs: FC::ExternalInputs, + current_proof: &Self::Proof, + ) -> Result<(Vec, Self::Proof), Error>; + + fn verify>( + vk: &Self::VerifierKey, + i: usize, + initial_state: &[Self::Field], + current_state: &[Self::Field], + proof: &Self::Proof, + ) -> Result<(), Error>; +} + +pub struct IVCStatefulProver { + pub pk: I::ProverKey, + pub step_circuit: FC, + pub i: usize, + pub initial_state: Vec, + pub current_state: Vec, + pub current_proof: I::Proof, +} + +impl, I: IVC> IVCStatefulProver { + pub fn new( + pk: I::ProverKey, + step_circuit: FC, + initial_state: Vec, + ) -> Result { + Ok(Self { + pk, + step_circuit, + i: 0, + current_state: initial_state.clone(), + initial_state, + current_proof: I::Proof::default(), + }) + } + + pub fn prove_step(&mut self, external_inputs: FC::ExternalInputs) -> Result<(), Error> { + let (next_state, next_proof) = I::prove( + &self.pk, + &self.step_circuit, + self.i, + &self.initial_state, + &self.current_state, + external_inputs, + &self.current_proof, + )?; + self.i += 1; + self.current_state = next_state; + self.current_proof = next_proof; + Ok(()) + } +} From b4f50d99a91f0f2007671ad50a3393aa2780c702 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 20:49:56 +0800 Subject: [PATCH 40/99] Refactor: skeleton for cyclefold compiler --- crates/fs/Cargo.toml | 1 + crates/fs/src/lib.rs | 190 +++++++++------------- crates/ivc/Cargo.toml | 3 + crates/ivc/src/compilers/cyclefold/mod.rs | 105 ++++++++++++ crates/ivc/src/compilers/mod.rs | 1 + crates/ivc/src/lib.rs | 29 +++- 6 files changed, 206 insertions(+), 123 deletions(-) create mode 100644 crates/ivc/src/compilers/cyclefold/mod.rs diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 1ae15744f..6f0510f4b 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -10,6 +10,7 @@ ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", ark-ec = { workspace = true } ark-ff = { workspace = true, features = ["asm"] } ark-poly = { workspace = true } +ark-r1cs-std = { workspace = true } ark-relations = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } ark-serialize = { workspace = true } diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 625bfeda4..7bcd2ad49 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,13 +1,14 @@ +use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; -use ark_std::{fmt::Debug, rand::RngCore}; +use ark_std::{borrow::Borrow, fmt::Debug, rand::RngCore}; use sonobe_primitives::{ arithmetizations::Arith, circuits::AssignmentsOwned, - commitments::CommitmentDef, + commitments::{CommitmentDef, CommitmentDefGadget}, relations::{Relation, WitnessInstanceSampler}, sumcheck::Error as SumCheckError, traits::SonobeField, - transcripts::Transcript, + transcripts::{Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -50,110 +51,6 @@ impl FoldingInstance for Vec { } } -// pub trait WitnessOps: PartialEq + Clone + Debug { -// /// The in-circuit representation of the witness. -// type Var: AllocVar + WitnessVarOps; - -// /// Returns the openings (i.e., the values being committed to and the -// /// randomness) contained in the witness. -// fn get_openings(&self) -> Vec<(&[F], F)>; -// } - -// pub trait WitnessVarOps { -// /// Returns the openings (i.e., the values being committed to and the -// /// randomness) contained in the witness. -// fn get_openings(&self) -> Vec<(&[FpVar], FpVar)>; -// } - -// pub trait CommittedInstanceOps: Inputize + PartialEq + Clone + Debug { -// type C: Curve; - -// /// The in-circuit representation of the committed instance. -// type Var: AllocVar + CommittedInstanceVarOps; -// /// `hash` implements the committed instance hash compatible with the -// /// in-circuit implementation from `CommittedInstanceVarOps::hash`. -// /// -// /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and -// /// `U_i` is the committed instance `self`. -// fn hash>(&self, sponge: &T, i: F, z_0: &[F], z_i: &[F]) -> F -// where -// Self: Sized + Absorb, -// F: Absorb, -// { -// let mut sponge = sponge.clone(); -// sponge.absorb(&i); -// sponge.absorb(&z_0); -// sponge.absorb(&z_i); -// sponge.absorb(&self); -// sponge.squeeze_field_elements(1)[0] -// } - -// /// Returns the commitments contained in the committed instance. -// fn get_commitments(&self) -> Vec; - -// /// Returns `true` if the committed instance is an incoming instance, and -// /// `false` if it is a running instance. -// fn is_incoming(&self) -> bool; - -// /// Checks if the committed instance is an incoming instance. -// fn check_incoming(&self) -> Result<(), Error> { -// self.is_incoming() -// .then_some(()) -// .ok_or(Error::NotIncomingCommittedInstance) -// } -// } - -// pub trait CommittedInstanceVarOps { -// type PointVar; -// /// `hash` implements the in-circuit committed instance hash compatible with -// /// the native implementation from `CommittedInstanceOps::hash`. -// /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and -// /// `U_i` is the committed instance `self`. -// /// -// /// Additionally it returns the in-circuit representation of the committed -// /// instance `self` as a vector of field elements, so they can be reused in -// /// other gadgets avoiding recalculating (reconstraining) them. -// #[allow(clippy::type_complexity)] -// fn hash>( -// &self, -// sponge: &impl TranscriptVar, -// i: &FpVar, -// z_0: &[FpVar], -// z_i: &[FpVar], -// ) -> Result<(FpVar, Vec>), SynthesisError> -// where -// Self: AbsorbGadget, -// { -// let mut sponge = sponge.clone(); -// let vec = self.to_sponge_field_elements()?; -// sponge.absorb(&i)?; -// sponge.absorb(&z_0)?; -// sponge.absorb(&z_i)?; -// sponge.absorb(&vec)?; -// Ok(( -// // `unwrap` is safe because the sponge is guaranteed to return a single element -// sponge.squeeze_field_elements(1)?.pop().unwrap(), -// vec, -// )) -// } - -// /// Returns the commitments contained in the committed instance. -// fn get_commitments(&self) -> Vec; - -// /// Returns the public inputs contained in the committed instance. -// fn get_public_inputs(&self) -> &[FpVar]; - -// /// Generates constraints to enforce that the committed instance is an -// /// incoming instance. -// fn enforce_incoming(&self) -> Result<(), SynthesisError>; - -// /// Generates constraints to enforce that the committed instance `self` is -// /// partially equal to another committed instance `other`. -// /// Here, only field elements are compared, while commitments (points) are -// /// not. -// fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError>; -// } - pub trait FoldingScheme { type VC: CommitmentDef; type RW: FoldingWitness; @@ -175,6 +72,7 @@ pub trait FoldingScheme { Source = AssignmentsOwned<::Scalar>, Error = Error, >; + type Challenge; type Proof; /// The preprocessing method is a randomized algorithm that takes as input @@ -207,18 +105,18 @@ pub trait FoldingScheme { fn prove( pk: &Self::ProverKey, transcript: &mut impl Transcript, - Ws: &[Self::RW; M], - Us: &[Self::RU; M], - ws: &[Self::IW; N], - us: &[Self::IU; N], + Ws: &[impl Borrow; M], + Us: &[impl Borrow; M], + ws: &[impl Borrow; N], + us: &[impl Borrow; N], rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof), Error>; + ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; fn verify( vk: &Self::VerifierKey, transcript: &mut impl Transcript, - Us: &[Self::RU; M], - us: &[Self::IU; N], + Us: &[impl Borrow; M], + us: &[impl Borrow; N], proof: &Self::Proof, ) -> Result; @@ -231,6 +129,65 @@ pub trait FoldingScheme { } } +pub trait FoldingWitnessVar { + type Native: FoldingWitness; +} + +pub trait FoldingInstanceVar { + type Native: FoldingInstance; +} + +impl FoldingWitnessVar for Vec { + type Native = Vec<::Scalar>; +} + +impl FoldingInstanceVar for Vec { + type Native = Vec<::Scalar>; +} + +pub trait FoldingSchemePartialGadget { + type Native: FoldingScheme; + + type VC: CommitmentDefGadget; + type RW: FoldingWitnessVar>::RW>; + type RU: FoldingInstanceVar>::RU>; + // + AllocVar<>::RU, Self::TranscriptField>; + type IW: FoldingWitnessVar>::IW>; + type IU: FoldingInstanceVar>::IU>; + // + AllocVar<>::RU, Self::TranscriptField>; + + type TranscriptField: SonobeField; + + type VerifierKey; + + type Challenge; + + type Proof; + + type Hint; + + fn verify_hinted( + vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + Us: &[Self::RU; M], + us: &[Self::IU; N], + proof: &Self::Proof, + hint: Self::Hint, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; +} + +pub trait FoldingSchemeFullGadget: + FoldingSchemePartialGadget +{ + fn verify( + vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + Us: &[Self::RU; M], + us: &[Self::IU; N], + proof: &Self::Proof, + ) -> Result; +} + #[cfg(test)] mod tests { use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; @@ -250,7 +207,8 @@ mod tests { mut rng: impl Rng, ) -> Result<(), Box> where - FS: FoldingScheme::Scalar>>>, + FS: FoldingScheme, + FS::Arith: From::Scalar>>, { let pp = FS::preprocess(config, &mut rng)?; @@ -289,7 +247,7 @@ mod tests { let ws = ws.try_into().unwrap(); let us = us.try_into().unwrap(); - let (WW, UU, pi) = FS::prove(&pk, &mut transcript_p, &Ws, &Us, &ws, &us, &mut rng)?; + let (WW, UU, pi, _) = FS::prove(&pk, &mut transcript_p, &Ws, &Us, &ws, &us, &mut rng)?; FS::decide_running(&dk, &WW, &UU)?; assert_eq!(FS::verify(&vk, &mut transcript_v, &Us, &us, &pi)?, UU); diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index 8d4d67c76..96666532b 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -6,8 +6,11 @@ license.workspace = true repository.workspace = true [dependencies] +ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } ark-ff = { workspace = true, features = ["asm"] } +ark-relations = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } +thiserror = { workspace = true } sonobe-primitives = { workspace = true } sonobe-fs = { workspace = true } diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs new file mode 100644 index 000000000..6fbc89e6e --- /dev/null +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -0,0 +1,105 @@ +use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; +use ark_relations::gr1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, +}; +use ark_std::{marker::PhantomData, rand::RngCore}; + +use sonobe_fs::FoldingScheme; +use sonobe_primitives::circuits::ConstraintSystemExt; +use sonobe_primitives::relations::WitnessInstanceSampler; +use sonobe_primitives::traits::SonobeField; +use sonobe_primitives::transcripts::Transcript; +use sonobe_primitives::{circuits::FCircuit, commitments::CommitmentDef}; + +use crate::IVC; + +pub struct CycleFoldBasedIVC { + _fs1: PhantomData, + _fs2: PhantomData, +} + +pub struct ProverKey { + poseidon_config: PoseidonConfig, + pp_hash: FC::Field, +} + +impl IVC for CycleFoldBasedIVC +where + FS1: FoldingScheme< + 1, + 1, + VC: CommitmentDef>::TranscriptField>, + >, + FS2: FoldingScheme<1, 1> +{ + type Field = ::Scalar; + + type Config = (FS1::Config, FS2::Config); + + type PublicParam = (FS1::PublicParam, FS2::PublicParam); + + type ProverKey = ( + FS1::ProverKey, + FS1::DeciderKey, + FS2::ProverKey, + FS2::DeciderKey, + ProverKey, + ); + + type VerifierKey = (); + + type Proof = (FS1::RW, FS1::RU, FS1::IW, FS1::IU, FS2::RW, FS2::RU); + + fn preprocess( + config: Self::Config, + mut rng: impl RngCore, + ) -> Result { + Ok(( + FS1::preprocess(config.0, &mut rng)?, + FS2::preprocess(config.1, &mut rng)?, + )) + } + + fn generate_keys>( + pp: &Self::PublicParam, + step_circuit: &FC, + ) -> Result<(Self::ProverKey, Self::VerifierKey), crate::Error> { + todo!() + } + + fn prove>( + (pk_fs1, dk_fs1, pk_fs2, dk_fs2, pk): &Self::ProverKey, + step_circuit: &FC, + i: usize, + initial_state: &[FC::Field], + current_state: &[FC::Field], + external_inputs: FC::ExternalInputs, + (W, U, w, u, cfW, cfU): &Self::Proof, + mut rng: impl RngCore, + ) -> Result<(Vec, Self::Proof), crate::Error> { + let poseidon = PoseidonSponge::new_with_pp_hash(&pk.poseidon_config, pk.pp_hash); + let sponge = poseidon.separate_domain("sponge".as_ref()); + let mut transcript = poseidon.separate_domain("transcript".as_ref()); + + let (WW, UU, proof, challenge) = + FS1::prove(pk_fs1, &mut transcript, &[W], &[U], &[w], &[u], &mut rng)?; + + if i == 0 { + } else { + } + + let cs = ConstraintSystem::::new_ref(); + + todo!() + } + + fn verify>( + vk: &Self::VerifierKey, + i: usize, + initial_state: &[FC::Field], + current_state: &[FC::Field], + proof: &Self::Proof, + ) -> Result<(), crate::Error> { + todo!() + } +} diff --git a/crates/ivc/src/compilers/mod.rs b/crates/ivc/src/compilers/mod.rs index e69de29bb..41e86f249 100644 --- a/crates/ivc/src/compilers/mod.rs +++ b/crates/ivc/src/compilers/mod.rs @@ -0,0 +1 @@ +pub mod cyclefold; diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 6db1c46c3..51f424172 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -1,21 +1,29 @@ use ark_ff::PrimeField; use ark_std::rand::RngCore; +use thiserror::Error; + use sonobe_primitives::circuits::FCircuit; pub mod compilers; -pub enum Error {} +#[derive(Debug, Error)] +pub enum Error { + #[error("Arithmetization error: {0}")] + ArithError(#[from] sonobe_primitives::arithmetizations::Error), + #[error("Folding error: {0}")] + FoldingError(#[from] sonobe_fs::Error), +} pub trait IVC { type Field: PrimeField; type Config; type PublicParam; - type ProverKey; - type VerifierKey; - type Proof: Default; + type ProverKey; + type VerifierKey; + type Proof; - fn preprocess(rng: impl RngCore, config: &Self::Config) -> Result; + fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; fn generate_keys>( pp: &Self::PublicParam, @@ -30,6 +38,7 @@ pub trait IVC { current_state: &[Self::Field], external_inputs: FC::ExternalInputs, current_proof: &Self::Proof, + rng: impl RngCore, ) -> Result<(Vec, Self::Proof), Error>; fn verify>( @@ -55,6 +64,7 @@ impl, I: IVC> IVCStatefulProver { pk: I::ProverKey, step_circuit: FC, initial_state: Vec, + initial_proof: I::Proof, ) -> Result { Ok(Self { pk, @@ -62,11 +72,15 @@ impl, I: IVC> IVCStatefulProver { i: 0, current_state: initial_state.clone(), initial_state, - current_proof: I::Proof::default(), + current_proof: initial_proof, }) } - pub fn prove_step(&mut self, external_inputs: FC::ExternalInputs) -> Result<(), Error> { + pub fn prove_step( + &mut self, + external_inputs: FC::ExternalInputs, + rng: impl RngCore, + ) -> Result<(), Error> { let (next_state, next_proof) = I::prove( &self.pk, &self.step_circuit, @@ -75,6 +89,7 @@ impl, I: IVC> IVCStatefulProver { &self.current_state, external_inputs, &self.current_proof, + rng, )?; self.i += 1; self.current_state = next_state; From 35b0459e678f22a89bcc88d3efb33be370bd3c1c Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 22:38:47 +0800 Subject: [PATCH 41/99] Refactor: dedicated types for plain instance & witness --- crates/fs/src/lib.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 7bcd2ad49..97df6f0ae 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -34,18 +34,21 @@ pub trait FoldingWitness: Debug + Sync { fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; } -impl FoldingWitness for Vec { - fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { - vec![] - } -} - pub trait FoldingInstance: Debug + PartialEq + Sync { /// Returns the commitments contained in the committed instance. fn commitments(&self) -> Vec<&VC::Commitment>; } -impl FoldingInstance for Vec { +pub type PlainWitness = Vec<::Scalar>; +pub type PlainInstance = Vec<::Scalar>; + +impl FoldingWitness for PlainWitness { + fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { + vec![] + } +} + +impl FoldingInstance for PlainInstance { fn commitments(&self) -> Vec<&VC::Commitment> { vec![] } From 9383495b7f3b75373cb9dc67445897c4fb3150c9 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 25 Oct 2025 23:27:13 +0800 Subject: [PATCH 42/99] Initialize augmented circuit --- crates/fs/src/lib.rs | 83 ++++++++++++++++--- .../ivc/src/compilers/cyclefold/circuits.rs | 66 +++++++++++++++ crates/ivc/src/compilers/cyclefold/mod.rs | 14 ++-- crates/ivc/src/lib.rs | 25 +++--- 4 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 crates/ivc/src/compilers/cyclefold/circuits.rs diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 97df6f0ae..107abcb20 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,5 +1,11 @@ -use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar}; -use ark_relations::gr1cs::SynthesisError; +use std::ops::{Deref, DerefMut}; + +use ark_ff::{Field, PrimeField}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + fields::fp::FpVar, +}; +use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, fmt::Debug, rand::RngCore}; use sonobe_primitives::{ arithmetizations::Arith, @@ -8,7 +14,7 @@ use sonobe_primitives::{ relations::{Relation, WitnessInstanceSampler}, sumcheck::Error as SumCheckError, traits::SonobeField, - transcripts::{Transcript, TranscriptGadget}, + transcripts::{Absorbable, AbsorbableVar, Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -39,8 +45,44 @@ pub trait FoldingInstance: Debug + PartialEq + Sync { fn commitments(&self) -> Vec<&VC::Commitment>; } -pub type PlainWitness = Vec<::Scalar>; -pub type PlainInstance = Vec<::Scalar>; +#[derive(Debug, PartialEq)] +pub struct WrappedVec(Vec); + +impl Deref for WrappedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for WrappedVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for WrappedVec { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl Absorbable for WrappedVec { + fn absorb_into(&self, dest: &mut Vec) { + self.0.absorb_into(dest) + } +} + +impl> AbsorbableVar for WrappedVec { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + self.0.absorb_into(dest) + } +} + +pub type PlainWitness = WrappedVec<::Scalar>; + +pub type PlainInstance = WrappedVec<::Scalar>; impl FoldingWitness for PlainWitness { fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { @@ -132,20 +174,39 @@ pub trait FoldingScheme { } } -pub trait FoldingWitnessVar { +pub trait FoldingWitnessVar: + AllocVar +{ type Native: FoldingWitness; } -pub trait FoldingInstanceVar { +pub trait FoldingInstanceVar: + AllocVar +{ type Native: FoldingInstance; } -impl FoldingWitnessVar for Vec { - type Native = Vec<::Scalar>; +pub type PlainWitnessVar = WrappedVec<::ScalarVar>; +pub type PlainInstanceVar = WrappedVec<::ScalarVar>; + +impl FoldingWitnessVar for PlainWitnessVar { + type Native = PlainWitness; +} + +impl FoldingInstanceVar for PlainInstanceVar { + type Native = PlainInstance; } -impl FoldingInstanceVar for Vec { - type Native = Vec<::Scalar>; +impl, Y, F: Field> AllocVar, F> for WrappedVec { + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let v = f()?; + let v = v.borrow(); + Vec::new_variable(cs, || Ok(&v[..]), mode).map(|v| Self(v)) + } } pub trait FoldingSchemePartialGadget { diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs new file mode 100644 index 000000000..d0f589382 --- /dev/null +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -0,0 +1,66 @@ +use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; +use ark_ff::Zero; +use ark_relations::gr1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, +}; +use ark_std::{marker::PhantomData, rand::RngCore}; + +use sonobe_fs::{FoldingScheme, FoldingSchemeFullGadget, FoldingSchemePartialGadget}; +use sonobe_primitives::circuits::ConstraintSystemExt; +use sonobe_primitives::relations::WitnessInstanceSampler; +use sonobe_primitives::traits::SonobeField; +use sonobe_primitives::transcripts::Transcript; +use sonobe_primitives::{circuits::FCircuit, commitments::CommitmentDef}; + +pub struct AugmentedCircuit<'a, FC: FCircuit, FS1, FS2> { + poseidon_config: PoseidonConfig, + step_circuit: &'a FC, + _fs1: PhantomData, + _fs2: PhantomData, +} + +impl<'a, FC: FCircuit, FS1, FS2> AugmentedCircuit<'a, FC, FS1, FS2> +where + FS1: FoldingSchemePartialGadget<1, 1>, + FS2: FoldingSchemeFullGadget<1, 1>, +{ + fn compute_next_state( + &self, + cs: ConstraintSystemRef, + pp_hash: FC::Field, + i: usize, + initial_state: &FC::State, + current_state: &FC::State, + external_inputs: FC::ExternalInputs, + U: FS1::RU, + u: FS1::IU, + hint: FS1::Hint, + ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { + todo!() + } +} + +impl<'a, FC: FCircuit, FS1, FS2> ConstraintSynthesizer + for AugmentedCircuit<'a, FC, FS1, FS2> +where + FS1: FoldingSchemePartialGadget<1, 1>, + FS2: FoldingSchemeFullGadget<1, 1>, +{ + fn generate_constraints( + self, + cs: ConstraintSystemRef, + ) -> Result<(), SynthesisError> { + self.compute_next_state( + cs, + Default::default(), + 0, + &self.step_circuit.dummy_state(), + &self.step_circuit.dummy_state(), + todo!(), + todo!(), + todo!(), + todo!(), + ) + .map(|_| ()) + } +} diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 6fbc89e6e..c6ae6f108 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -13,6 +13,8 @@ use sonobe_primitives::{circuits::FCircuit, commitments::CommitmentDef}; use crate::IVC; +mod circuits; + pub struct CycleFoldBasedIVC { _fs1: PhantomData, _fs2: PhantomData, @@ -30,7 +32,7 @@ where 1, VC: CommitmentDef>::TranscriptField>, >, - FS2: FoldingScheme<1, 1> + FS2: FoldingScheme<1, 1>, { type Field = ::Scalar; @@ -71,12 +73,12 @@ where (pk_fs1, dk_fs1, pk_fs2, dk_fs2, pk): &Self::ProverKey, step_circuit: &FC, i: usize, - initial_state: &[FC::Field], - current_state: &[FC::Field], + initial_state: &FC::State, + current_state: &FC::State, external_inputs: FC::ExternalInputs, (W, U, w, u, cfW, cfU): &Self::Proof, mut rng: impl RngCore, - ) -> Result<(Vec, Self::Proof), crate::Error> { + ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), crate::Error> { let poseidon = PoseidonSponge::new_with_pp_hash(&pk.poseidon_config, pk.pp_hash); let sponge = poseidon.separate_domain("sponge".as_ref()); let mut transcript = poseidon.separate_domain("transcript".as_ref()); @@ -96,8 +98,8 @@ where fn verify>( vk: &Self::VerifierKey, i: usize, - initial_state: &[FC::Field], - current_state: &[FC::Field], + initial_state: &FC::State, + current_state: &FC::State, proof: &Self::Proof, ) -> Result<(), crate::Error> { todo!() diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 51f424172..2f04c7d07 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -1,8 +1,7 @@ use ark_ff::PrimeField; use ark_std::rand::RngCore; -use thiserror::Error; - use sonobe_primitives::circuits::FCircuit; +use thiserror::Error; pub mod compilers; @@ -34,18 +33,18 @@ pub trait IVC { pk: &Self::ProverKey, step_circuit: &FC, i: usize, - initial_state: &[Self::Field], - current_state: &[Self::Field], + initial_state: &FC::State, + current_state: &FC::State, external_inputs: FC::ExternalInputs, current_proof: &Self::Proof, rng: impl RngCore, - ) -> Result<(Vec, Self::Proof), Error>; + ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error>; fn verify>( vk: &Self::VerifierKey, i: usize, - initial_state: &[Self::Field], - current_state: &[Self::Field], + initial_state: &FC::State, + current_state: &FC::State, proof: &Self::Proof, ) -> Result<(), Error>; } @@ -54,8 +53,8 @@ pub struct IVCStatefulProver { pub pk: I::ProverKey, pub step_circuit: FC, pub i: usize, - pub initial_state: Vec, - pub current_state: Vec, + pub initial_state: FC::State, + pub current_state: FC::State, pub current_proof: I::Proof, } @@ -63,7 +62,7 @@ impl, I: IVC> IVCStatefulProver { pub fn new( pk: I::ProverKey, step_circuit: FC, - initial_state: Vec, + initial_state: FC::State, initial_proof: I::Proof, ) -> Result { Ok(Self { @@ -80,8 +79,8 @@ impl, I: IVC> IVCStatefulProver { &mut self, external_inputs: FC::ExternalInputs, rng: impl RngCore, - ) -> Result<(), Error> { - let (next_state, next_proof) = I::prove( + ) -> Result { + let (next_state, external_outputs, next_proof) = I::prove( &self.pk, &self.step_circuit, self.i, @@ -94,6 +93,6 @@ impl, I: IVC> IVCStatefulProver { self.i += 1; self.current_state = next_state; self.current_proof = next_proof; - Ok(()) + Ok(external_outputs) } } From 2e50af4fabc9a27cdc8989185023148e42f9a466 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 27 Oct 2025 17:19:35 +0800 Subject: [PATCH 43/99] Fully implement augmented circuit for CycleFold --- crates/fs/src/lib.rs | 166 ++++++++++------ crates/ivc/Cargo.toml | 1 + .../ivc/src/compilers/cyclefold/circuits.rs | 182 ++++++++++++++++-- crates/ivc/src/compilers/cyclefold/mod.rs | 37 ++-- 4 files changed, 300 insertions(+), 86 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 107abcb20..7f538bfa4 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -2,10 +2,13 @@ use std::ops::{Deref, DerefMut}; use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ + GR1CSVar, alloc::{AllocVar, AllocationMode}, fields::fp::FpVar, + prelude::Boolean, + select::CondSelectGadget, }; -use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, fmt::Debug, rand::RngCore}; use sonobe_primitives::{ arithmetizations::Arith, @@ -13,7 +16,7 @@ use sonobe_primitives::{ commitments::{CommitmentDef, CommitmentDefGadget}, relations::{Relation, WitnessInstanceSampler}, sumcheck::Error as SumCheckError, - traits::SonobeField, + traits::{Dummy, SonobeField}, transcripts::{Absorbable, AbsorbableVar, Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -34,18 +37,40 @@ pub enum Error { DomainCreationFailure, } -pub trait FoldingWitness: Debug + Sync { +pub trait FoldingWitness: Debug { /// Returns the reference to all openings contained in the witness, each /// being a tuple of the values being committed to and the randomness. fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; } -pub trait FoldingInstance: Debug + PartialEq + Sync { +pub trait FoldingInstance: Clone + Debug + PartialEq { /// Returns the commitments contained in the committed instance. fn commitments(&self) -> Vec<&VC::Commitment>; + + fn public_inputs(&self) -> &[VC::Scalar]; +} + +pub type PlainWitness = WrappedVec<::Scalar>; + +pub type PlainInstance = WrappedVec<::Scalar>; + +impl FoldingWitness for PlainWitness { + fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { + vec![] + } +} + +impl FoldingInstance for PlainInstance { + fn commitments(&self) -> Vec<&VC::Commitment> { + vec![] + } + + fn public_inputs(&self) -> &[::Scalar] { + self + } } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct WrappedVec(Vec); impl Deref for WrappedVec { @@ -80,28 +105,54 @@ impl> AbsorbableVar for WrappedVec { } } -pub type PlainWitness = WrappedVec<::Scalar>; - -pub type PlainInstance = WrappedVec<::Scalar>; - -impl FoldingWitness for PlainWitness { - fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { - vec![] +impl, Y, F: Field> AllocVar, F> for WrappedVec { + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let v = f()?; + Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(|v| Self(v)) } } -impl FoldingInstance for PlainInstance { - fn commitments(&self) -> Vec<&VC::Commitment> { - vec![] +impl> CondSelectGadget for WrappedVec { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.len() != false_value.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(WrappedVec( + true_value + .0 + .iter() + .zip(false_value.0.iter()) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + )) } } +impl> GR1CSVar for WrappedVec { + type Value = WrappedVec; + + fn cs(&self) -> ConstraintSystemRef { + self.0.cs() + } + + fn value(&self) -> Result { + self.0.value().map(WrappedVec) + } +} pub trait FoldingScheme { type VC: CommitmentDef; type RW: FoldingWitness; - type RU: FoldingInstance; + type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; type IW: FoldingWitness; - type IU: FoldingInstance; + type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; type TranscriptField: SonobeField; type Arith: Arith; type Config; @@ -118,7 +169,7 @@ pub trait FoldingScheme { Error = Error, >; type Challenge; - type Proof; + type Proof: Clone + for<'a> Dummy<&'a ::Config>; /// The preprocessing method is a randomized algorithm that takes as input /// the size bounds of the folding scheme, which are contained in the @@ -175,66 +226,73 @@ pub trait FoldingScheme { } pub trait FoldingWitnessVar: - AllocVar + AllocVar + + GR1CSVar> +{ +} + +impl FoldingWitnessVar for T where + T: AllocVar + + GR1CSVar> { - type Native: FoldingWitness; } pub trait FoldingInstanceVar: - AllocVar + AllocVar + + GR1CSVar> + + AbsorbableVar + + CondSelectGadget { - type Native: FoldingInstance; + /// Returns the commitments contained in the committed instance. + fn commitments(&self) -> Vec<&VC::CommitmentVar>; + + fn public_inputs(&self) -> &Vec; } pub type PlainWitnessVar = WrappedVec<::ScalarVar>; pub type PlainInstanceVar = WrappedVec<::ScalarVar>; -impl FoldingWitnessVar for PlainWitnessVar { - type Native = PlainWitness; -} - impl FoldingInstanceVar for PlainInstanceVar { - type Native = PlainInstance; -} + fn commitments(&self) -> Vec<&VC::CommitmentVar> { + vec![] + } -impl, Y, F: Field> AllocVar, F> for WrappedVec { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let v = f()?; - let v = v.borrow(); - Vec::new_variable(cs, || Ok(&v[..]), mode).map(|v| Self(v)) + fn public_inputs(&self) -> &Vec { + self } } pub trait FoldingSchemePartialGadget { - type Native: FoldingScheme; + type Native: FoldingScheme::Widget>; type VC: CommitmentDefGadget; - type RW: FoldingWitnessVar>::RW>; - type RU: FoldingInstanceVar>::RU>; - // + AllocVar<>::RU, Self::TranscriptField>; - type IW: FoldingWitnessVar>::IW>; - type IU: FoldingInstanceVar>::IU>; - // + AllocVar<>::RU, Self::TranscriptField>; - - type TranscriptField: SonobeField; + type RW: FoldingWitnessVar>::RW>; + type RU: FoldingInstanceVar>::RU>; + type IW: FoldingWitnessVar>::IW>; + type IU: FoldingInstanceVar>::IU>; type VerifierKey; type Challenge; - type Proof; + type Proof: AllocVar< + >::Proof, + ::ConstraintField, + > + GR1CSVar< + ::ConstraintField, + Value = >::Proof, + >; - type Hint; + type Hint: AllocVar< + ::ConstraintField>>::Value, + ::ConstraintField, + > + GR1CSVar<::ConstraintField, Value: Default>; fn verify_hinted( vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - Us: &[Self::RU; M], - us: &[Self::IU; N], + transcript: &mut impl TranscriptGadget<::ConstraintField>, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], proof: &Self::Proof, hint: Self::Hint, ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; @@ -245,9 +303,9 @@ pub trait FoldingSchemeFullGadget: { fn verify( vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - Us: &[Self::RU; M], - us: &[Self::IU; N], + transcript: &mut impl TranscriptGadget<::ConstraintField>, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], proof: &Self::Proof, ) -> Result; } diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index 96666532b..5c27a0280 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true [dependencies] ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } ark-ff = { workspace = true, features = ["asm"] } +ark-r1cs-std = { workspace = true } ark-relations = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } thiserror = { workspace = true } diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index d0f589382..997029228 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -1,19 +1,43 @@ -use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; +use ark_crypto_primitives::sponge::poseidon::{ + PoseidonConfig, PoseidonSponge, constraints::PoseidonSpongeVar, +}; use ark_ff::Zero; +use ark_r1cs_std::{ + GR1CSVar, + alloc::AllocVar, + eq::EqGadget, + fields::{FieldVar, fp::FpVar}, +}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, }; use ark_std::{marker::PhantomData, rand::RngCore}; +use sonobe_fs::{ + FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemeFullGadget, + FoldingSchemePartialGadget, +}; +use sonobe_primitives::{ + arithmetizations::Arith, + circuits::{ConstraintSystemExt, FCircuit}, + commitments::{CommitmentDef, CommitmentDefGadget}, + relations::WitnessInstanceSampler, + traits::{Dummy, SonobeField}, + transcripts::{Transcript, TranscriptGadget}, +}; -use sonobe_fs::{FoldingScheme, FoldingSchemeFullGadget, FoldingSchemePartialGadget}; -use sonobe_primitives::circuits::ConstraintSystemExt; -use sonobe_primitives::relations::WitnessInstanceSampler; -use sonobe_primitives::traits::SonobeField; -use sonobe_primitives::transcripts::Transcript; -use sonobe_primitives::{circuits::FCircuit, commitments::CommitmentDef}; +use crate::compilers::cyclefold::FoldingSchemeCycleFoldGadget; -pub struct AugmentedCircuit<'a, FC: FCircuit, FS1, FS2> { +pub struct AugmentedCircuit< + 'a, + FC: FCircuit, + FS1: FoldingSchemePartialGadget<1, 1>, + FS2: FoldingSchemeFullGadget<1, 1>, +> { poseidon_config: PoseidonConfig, + arith1_config: <>::Arith as Arith>::Config, + arith2_config: <>::Arith as Arith>::Config, + vk1: FS1::VerifierKey, + vk2: FS2::VerifierKey, step_circuit: &'a FC, _fs1: PhantomData, _fs2: PhantomData, @@ -21,10 +45,19 @@ pub struct AugmentedCircuit<'a, FC: FCircuit, FS1, FS2> { impl<'a, FC: FCircuit, FS1, FS2> AugmentedCircuit<'a, FC, FS1, FS2> where - FS1: FoldingSchemePartialGadget<1, 1>, - FS2: FoldingSchemeFullGadget<1, 1>, + FS1: FoldingSchemeCycleFoldGadget< + 1, + 1, + VC: CommitmentDefGadget>, + CFScalarVar = ::ScalarVar, + >, + FS2: FoldingSchemeFullGadget< + 1, + 1, + VC: CommitmentDefGadget>, + >, { - fn compute_next_state( + pub fn compute_next_state( &self, cs: ConstraintSystemRef, pp_hash: FC::Field, @@ -32,34 +65,141 @@ where initial_state: &FC::State, current_state: &FC::State, external_inputs: FC::ExternalInputs, - U: FS1::RU, - u: FS1::IU, - hint: FS1::Hint, + U: >::Value, + u: >::Value, + proof: >::Value, + hint: >::Value, + cf_U: >::Value, + cf_us: Vec<>::Value>, + cf_proofs: Vec<>::Value>, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { - todo!() + let poseidon = PoseidonSpongeVar::new_with_pp_hash( + &self.poseidon_config, + &FpVar::new_witness(cs.clone(), || Ok(pp_hash))?, + )?; + let sponge = poseidon.separate_domain("sponge".as_ref())?; + let mut transcript = poseidon.separate_domain("transcript".as_ref())?; + + let i = FpVar::new_witness(cs.clone(), || Ok(FC::Field::from(i as u64)))?; + let ii = &i + FpVar::one(); + + let is_basecase = i.is_zero()?; + + let initial_state = FC::StateVar::new_witness(cs.clone(), || Ok(initial_state))?; + let current_state = FC::StateVar::new_witness(cs.clone(), || Ok(current_state))?; + + let U_dummy = FS1::RU::new_witness(cs.clone(), || { + Ok(>::Value::dummy(&self.arith1_config)) + })?; + let U = FS1::RU::new_witness(cs.clone(), || Ok(U))?; + let u = FS1::IU::new_witness(cs.clone(), || Ok(u))?; + let proof = FS1::Proof::new_witness(cs.clone(), || Ok(proof))?; + let hint = FS1::Hint::new_witness(cs.clone(), || Ok(hint))?; + + let cf_U_dummy = FS2::RU::new_witness(cs.clone(), || { + Ok(>::Value::dummy(&self.arith2_config)) + })?; + let cf_U = FS2::RU::new_witness(cs.clone(), || Ok(cf_U))?; + let cf_us = Vec::new_witness(cs.clone(), || Ok(cf_us))?; + let cf_proofs = Vec::new_witness(cs.clone(), || Ok(cf_proofs))?; + + let u_x = { + let mut sponge = sponge.clone(); + sponge.add(&i)?; + sponge.add(&initial_state)?; + sponge.add(¤t_state)?; + sponge.add(&U)?; + sponge.add(&cf_U)?; + sponge.get_field_elements(2)? + }; + + let (next_state, external_outputs) = + self.step_circuit + .generate_step_constraints(i, current_state, external_inputs)?; + + let (UU, rho) = FS1::verify_hinted(&self.vk1, &mut transcript, &[&U], &[&u], &proof, hint)?; + + let mut cf_UU = cf_U; + for (cf_u, cf_proof) in cf_us.iter().zip(&cf_proofs) { + cf_UU = FS2::verify(&self.vk2, &mut transcript, &[cf_UU], &[cf_u], cf_proof)?; + } + + let uu_x = { + let mut sponge = sponge.clone(); + sponge.add(&ii)?; + sponge.add(&initial_state)?; + sponge.add(&next_state)?; + sponge.add(&is_basecase.select(&U_dummy, &UU)?)?; + sponge.add(&is_basecase.select(&cf_U_dummy, &cf_UU)?)?; + sponge.get_field_elements(2)? + }; + // This line "converts" `uu_x` from witnesses to public inputs. + // Instead of directly modifying the constraint system, we explicitly + // allocate a public input and enforce that its value is indeed `uu_x`. + // While comparing `uu_x` with itself seems redundant, this is necessary + // because: + // - `.value()` allows an honest prover to extract public inputs without + // computing them outside the circuit. + // - `.enforce_equal()` prevents a malicious prover from claiming wrong + // public inputs that are not the honest `uu_x` computed in-circuit. + uu_x.enforce_equal(&Vec::new_input(cs.clone(), || uu_x.value())?)?; + + u.public_inputs().enforce_equal(&u_x)?; + + cf_us + .iter() + .zip(FS1::to_cyclefold_inputs(U, u, UU, rho)?) + .try_for_each(|(cf_u, cf_u_x)| cf_u.public_inputs().enforce_equal(&cf_u_x))?; + + if cs.is_in_setup_mode() { + Ok((self.step_circuit.dummy_state(), external_outputs)) + } else { + Ok((next_state.value()?, external_outputs)) + } } } impl<'a, FC: FCircuit, FS1, FS2> ConstraintSynthesizer for AugmentedCircuit<'a, FC, FS1, FS2> where - FS1: FoldingSchemePartialGadget<1, 1>, - FS2: FoldingSchemeFullGadget<1, 1>, + FS1: FoldingSchemeCycleFoldGadget< + 1, + 1, + VC: CommitmentDefGadget>, + CFScalarVar = ::ScalarVar, + >, + FS2: FoldingSchemeFullGadget< + 1, + 1, + VC: CommitmentDefGadget>, + >, { fn generate_constraints( self, cs: ConstraintSystemRef, ) -> Result<(), SynthesisError> { + let external_inputs = self.step_circuit.dummy_external_inputs(); + let U = >::Value::dummy(&self.arith1_config); + let u = Dummy::dummy(&self.arith1_config); + let proof = Dummy::dummy(&self.arith1_config); + let hint = Default::default(); + let cf_U = Dummy::dummy(&self.arith2_config); + let cf_us = vec![Dummy::dummy(&self.arith2_config); U.commitments().len()]; + let cf_proofs = vec![Dummy::dummy(&self.arith2_config); U.commitments().len()]; self.compute_next_state( cs, Default::default(), 0, &self.step_circuit.dummy_state(), &self.step_circuit.dummy_state(), - todo!(), - todo!(), - todo!(), - todo!(), + external_inputs, + U, + u, + proof, + hint, + cf_U, + cf_us, + cf_proofs, ) .map(|_| ()) } diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index c6ae6f108..1a538b2b0 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,20 +1,35 @@ use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; +use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, }; use ark_std::{marker::PhantomData, rand::RngCore}; - -use sonobe_fs::FoldingScheme; -use sonobe_primitives::circuits::ConstraintSystemExt; -use sonobe_primitives::relations::WitnessInstanceSampler; -use sonobe_primitives::traits::SonobeField; -use sonobe_primitives::transcripts::Transcript; -use sonobe_primitives::{circuits::FCircuit, commitments::CommitmentDef}; +use sonobe_fs::{FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemePartialGadget}; +use sonobe_primitives::{ + circuits::{ConstraintSystemExt, FCircuit}, + commitments::{CommitmentDef}, + relations::WitnessInstanceSampler, + traits::SonobeField, + transcripts::Transcript, +}; use crate::IVC; mod circuits; +pub trait FoldingSchemeCycleFoldGadget: + FoldingSchemePartialGadget +{ + type CFScalarVar; + + fn to_cyclefold_inputs( + U: Self::RU, + u: Self::IU, + UU: Self::RU, + rho: Self::Challenge, + ) -> Result>, SynthesisError>; +} + pub struct CycleFoldBasedIVC { _fs1: PhantomData, _fs2: PhantomData, @@ -28,10 +43,10 @@ pub struct ProverKey { impl IVC for CycleFoldBasedIVC where FS1: FoldingScheme< - 1, - 1, - VC: CommitmentDef>::TranscriptField>, - >, + 1, + 1, + VC: CommitmentDef>::TranscriptField>, + >, FS2: FoldingScheme<1, 1>, { type Field = ::Scalar; From 18b24b0bf34880c3ed8bd392acba042dba2c90ac Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 8 Nov 2025 08:47:34 +0800 Subject: [PATCH 44/99] Finish unified CycleFold compiler --- crates/fs/Cargo.toml | 1 + crates/fs/src/lib.rs | 230 ++++++++--- crates/ivc/Cargo.toml | 2 + .../ivc/src/compilers/cyclefold/circuits.rs | 381 ++++++++++++++---- crates/ivc/src/compilers/cyclefold/mod.rs | 335 ++++++++++++--- crates/ivc/src/lib.rs | 37 +- 6 files changed, 782 insertions(+), 204 deletions(-) diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 6f0510f4b..9d1860885 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -14,6 +14,7 @@ ark-r1cs-std = { workspace = true } ark-relations = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } ark-serialize = { workspace = true } +num-bigint = { workspace = true, features = ["rand"] } thiserror = { workspace = true } rayon = { workspace = true } diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 7f538bfa4..c8b728d8e 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -11,7 +11,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, fmt::Debug, rand::RngCore}; use sonobe_primitives::{ - arithmetizations::Arith, + arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget}, relations::{Relation, WitnessInstanceSampler}, @@ -23,13 +23,13 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum Error { - #[error("Arithmetization error: {0}")] + #[error(transparent)] ArithError(#[from] sonobe_primitives::arithmetizations::Error), - #[error("Commitment error: {0}")] + #[error(transparent)] CommitmentError(#[from] sonobe_primitives::commitments::Error), - #[error("Synthesis error: {0}")] + #[error(transparent)] SynthesisError(#[from] SynthesisError), - #[error("Sumcheck error: {0}")] + #[error(transparent)] SumCheckError(#[from] SumCheckError), #[error("Unsupported use case: {0}")] Unsupported(String), @@ -38,42 +38,120 @@ pub enum Error { } pub trait FoldingWitness: Debug { + const N_OPENINGS: usize; + /// Returns the reference to all openings contained in the witness, each /// being a tuple of the values being committed to and the randomness. - fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; + fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; } -pub trait FoldingInstance: Clone + Debug + PartialEq { +pub trait FoldingInstance: Clone + Debug + PartialEq + Absorbable { + const N_COMMITMENTS: usize; + /// Returns the commitments contained in the committed instance. fn commitments(&self) -> Vec<&VC::Commitment>; fn public_inputs(&self) -> &[VC::Scalar]; + + fn public_inputs_mut(&mut self) -> &mut [VC::Scalar]; } -pub type PlainWitness = WrappedVec<::Scalar>; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PlainWitness(pub Vec); -pub type PlainInstance = WrappedVec<::Scalar>; +impl Deref for PlainWitness { + type Target = Vec; -impl FoldingWitness for PlainWitness { - fn openings_ref(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { - vec![] + fn deref(&self) -> &Self::Target { + &self.0 } } -impl FoldingInstance for PlainInstance { - fn commitments(&self) -> Vec<&VC::Commitment> { - vec![] +impl DerefMut for PlainWitness { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } +} - fn public_inputs(&self) -> &[::Scalar] { - self +impl From> for PlainWitness { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl Absorbable for PlainWitness { + fn absorb_into(&self, dest: &mut Vec) { + self.0.absorb_into(dest) + } +} + +impl> AbsorbableVar for PlainWitness { + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + self.0.absorb_into(dest) + } +} + +impl, Y, F: Field> AllocVar, F> for PlainWitness { + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let v = f()?; + Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(|v| Self(v)) + } +} + +impl> CondSelectGadget for PlainWitness { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.len() != false_value.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self( + true_value + .0 + .iter() + .zip(false_value.0.iter()) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + )) } } -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct WrappedVec(Vec); +impl> GR1CSVar for PlainWitness { + type Value = PlainWitness; + + fn cs(&self) -> ConstraintSystemRef { + self.0.cs() + } -impl Deref for WrappedVec { + fn value(&self) -> Result { + self.0.value().map(PlainWitness) + } +} + +impl Dummy<&A> for PlainWitness { + fn dummy(cfg: &A) -> Self { + vec![V::default(); cfg.n_witnesses()].into() + } +} + +impl FoldingWitness for PlainWitness { + const N_OPENINGS: usize = 0; + + fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { + vec![] + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PlainInstance(pub Vec); + +impl Deref for PlainInstance { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -81,32 +159,32 @@ impl Deref for WrappedVec { } } -impl DerefMut for WrappedVec { +impl DerefMut for PlainInstance { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl From> for WrappedVec { +impl From> for PlainInstance { fn from(v: Vec) -> Self { Self(v) } } -impl Absorbable for WrappedVec { +impl Absorbable for PlainInstance { fn absorb_into(&self, dest: &mut Vec) { self.0.absorb_into(dest) } } -impl> AbsorbableVar for WrappedVec { +impl> AbsorbableVar for PlainInstance { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.0.absorb_into(dest) } } -impl, Y, F: Field> AllocVar, F> for WrappedVec { - fn new_variable>>( +impl, Y, F: Field> AllocVar, F> for PlainInstance { + fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, @@ -116,7 +194,7 @@ impl, Y, F: Field> AllocVar, F> for WrappedVec> CondSelectGadget for WrappedVec { +impl> CondSelectGadget for PlainInstance { fn conditionally_select( cond: &Boolean, true_value: &Self, @@ -125,7 +203,7 @@ impl> CondSelectGadget for WrappedVec> CondSelectGadget for WrappedVec> GR1CSVar for WrappedVec { - type Value = WrappedVec; +impl> GR1CSVar for PlainInstance { + type Value = PlainInstance; fn cs(&self) -> ConstraintSystemRef { self.0.cs() } fn value(&self) -> Result { - self.0.value().map(WrappedVec) + self.0.value().map(PlainInstance) + } +} + +impl Dummy<&A> for PlainInstance { + fn dummy(cfg: &A) -> Self { + vec![V::default(); cfg.n_public_inputs()].into() } } + +impl FoldingWitness for PlainInstance { + const N_OPENINGS: usize = 0; + + fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { + vec![] + } +} + +impl FoldingInstance for PlainInstance { + const N_COMMITMENTS: usize = 0; + + fn commitments(&self) -> Vec<&VC::Commitment> { + vec![] + } + + fn public_inputs(&self) -> &[VC::Scalar] { + self + } + + fn public_inputs_mut(&mut self) -> &mut [VC::Scalar] { + self + } +} + +pub trait DeciderKey {} + pub trait FoldingScheme { type VC: CommitmentDef; - type RW: FoldingWitness; + type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; - type IW: FoldingWitness; + type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; type TranscriptField: SonobeField; type Arith: Arith; @@ -159,7 +270,8 @@ pub trait FoldingScheme { type PublicParam; type ProverKey; type VerifierKey; - type DeciderKey: Relation + type DeciderKey: Clone + + Relation + Relation + WitnessInstanceSampler + WitnessInstanceSampler< @@ -198,6 +310,7 @@ pub trait FoldingScheme { /// Here, the randomness source is controlled by `transcript`. The returned /// intermediate randomness is useful for the construction of CycleFold /// circuits in our CycleFold-based folding-to-IVC compiler. + #[allow(non_snake_case)] fn prove( pk: &Self::ProverKey, transcript: &mut impl Transcript, @@ -208,6 +321,7 @@ pub trait FoldingScheme { rng: impl RngCore, ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; + #[allow(non_snake_case)] fn verify( vk: &Self::VerifierKey, transcript: &mut impl Transcript, @@ -216,6 +330,7 @@ pub trait FoldingScheme { proof: &Self::Proof, ) -> Result; + #[allow(non_snake_case)] fn decide_running(dk: &Self::DeciderKey, W: &Self::RW, U: &Self::RU) -> Result<(), Error> { Relation::::check_relation(dk, W, U) } @@ -247,10 +362,16 @@ pub trait FoldingInstanceVar: fn commitments(&self) -> Vec<&VC::CommitmentVar>; fn public_inputs(&self) -> &Vec; + + fn new_witness_with_public_inputs( + cs: impl Into>, + u: &Self::Value, + x: Vec, + ) -> Result; } -pub type PlainWitnessVar = WrappedVec<::ScalarVar>; -pub type PlainInstanceVar = WrappedVec<::ScalarVar>; +pub type PlainWitnessVar = PlainWitness<::ScalarVar>; +pub type PlainInstanceVar = PlainInstance<::ScalarVar>; impl FoldingInstanceVar for PlainInstanceVar { fn commitments(&self) -> Vec<&VC::CommitmentVar> { @@ -260,12 +381,20 @@ impl FoldingInstanceVar for PlainInstanceVar { fn public_inputs(&self) -> &Vec { self } + + fn new_witness_with_public_inputs( + _cs: impl Into>, + _u: &Self::Value, + x: Vec, + ) -> Result { + Ok(Self(x)) + } } pub trait FoldingSchemePartialGadget { - type Native: FoldingScheme::Widget>; + type Native: FoldingScheme; - type VC: CommitmentDefGadget; + type VC: CommitmentDefGadget>::VC>; type RW: FoldingWitnessVar>::RW>; type RU: FoldingInstanceVar>::RU>; type IW: FoldingWitnessVar>::IW>; @@ -283,24 +412,20 @@ pub trait FoldingSchemePartialGadget { Value = >::Proof, >; - type Hint: AllocVar< - ::ConstraintField>>::Value, - ::ConstraintField, - > + GR1CSVar<::ConstraintField, Value: Default>; - + #[allow(non_snake_case)] fn verify_hinted( vk: &Self::VerifierKey, transcript: &mut impl TranscriptGadget<::ConstraintField>, Us: &[impl Borrow; M], us: &[impl Borrow; N], proof: &Self::Proof, - hint: Self::Hint, ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; } pub trait FoldingSchemeFullGadget: FoldingSchemePartialGadget { + #[allow(non_snake_case)] fn verify( vk: &Self::VerifierKey, transcript: &mut impl TranscriptGadget<::ConstraintField>, @@ -314,22 +439,25 @@ pub trait FoldingSchemeFullGadget: mod tests { use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; - use ark_std::{error::Error, rand::Rng}; + use ark_std::{error::Error, rand::Rng, sync::Arc}; use sonobe_primitives::{ circuits::{ArithExtractor, AssignmentsOwned}, - transcripts::poseidon::poseidon_canonical_config, + transcripts::{ + griffin::{GriffinParams, sponge::GriffinSponge}, + poseidon::poseidon_canonical_config, + }, }; use super::*; - pub fn test_folding_scheme( + #[allow(non_snake_case)] + pub fn test_folding_scheme, const M: usize, const N: usize>( config: FS::Config, circuit: impl ConstraintSynthesizer<::Scalar>, assignments_vec: Vec::Scalar>>, mut rng: impl Rng, ) -> Result<(), Box> where - FS: FoldingScheme, FS::Arith: From::Scalar>>, { let pp = FS::preprocess(config, &mut rng)?; @@ -350,8 +478,10 @@ mod tests { let mut Ws = Ws.try_into().unwrap(); let mut Us = Us.try_into().unwrap(); - let mut transcript_p = PoseidonSponge::new(&poseidon_canonical_config()); - let mut transcript_v = PoseidonSponge::new(&poseidon_canonical_config()); + let config = Arc::new(GriffinParams::new(16, 5, 9)); + + let mut transcript_p = GriffinSponge::new(&config); + let mut transcript_v = GriffinSponge::new(&config); for assignments in assignments_vec { let mut ws = vec![]; diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index 5c27a0280..be76625ee 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -17,6 +17,8 @@ sonobe-primitives = { workspace = true } sonobe-fs = { workspace = true } [dev-dependencies] +ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } +ark-grumpkin = { workspace = true, features = ["r1cs"] } [features] diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 997029228..017036671 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -1,60 +1,62 @@ -use ark_crypto_primitives::sponge::poseidon::{ - PoseidonConfig, PoseidonSponge, constraints::PoseidonSpongeVar, -}; -use ark_ff::Zero; +use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, + boolean::Boolean, + convert::ToConstraintFieldGadget, eq::EqGadget, fields::{FieldVar, fp::FpVar}, + groups::CurveVar, }; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, }; -use ark_std::{marker::PhantomData, rand::RngCore}; +use ark_std::{marker::PhantomData, rand::RngCore, sync::Arc}; use sonobe_fs::{ FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemeFullGadget, FoldingSchemePartialGadget, }; use sonobe_primitives::{ + algebra::Val, arithmetizations::Arith, circuits::{ConstraintSystemExt, FCircuit}, commitments::{CommitmentDef, CommitmentDefGadget}, relations::WitnessInstanceSampler, - traits::{Dummy, SonobeField}, - transcripts::{Transcript, TranscriptGadget}, + traits::{CF1, CF2, Dummy, SonobeCurve, SonobeField}, + transcripts::{ + Transcript, TranscriptGadget, + griffin::{GriffinParams, sponge::GriffinSpongeVar}, + }, }; use crate::compilers::cyclefold::FoldingSchemeCycleFoldGadget; pub struct AugmentedCircuit< 'a, + FS1: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + FS2: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, FC: FCircuit, - FS1: FoldingSchemePartialGadget<1, 1>, - FS2: FoldingSchemeFullGadget<1, 1>, > { - poseidon_config: PoseidonConfig, - arith1_config: <>::Arith as Arith>::Config, - arith2_config: <>::Arith as Arith>::Config, - vk1: FS1::VerifierKey, - vk2: FS2::VerifierKey, - step_circuit: &'a FC, - _fs1: PhantomData, - _fs2: PhantomData, + pub griffin_config: Arc>, + pub arith1_config: &'a <>::Arith as Arith>::Config, + pub arith2_config: &'a <>::Arith as Arith>::Config, + pub step_circuit: &'a FC, } -impl<'a, FC: FCircuit, FS1, FS2> AugmentedCircuit<'a, FC, FS1, FS2> +impl<'a, FS1, FS2, FC: FCircuit> AugmentedCircuit<'a, FS1, FS2, FC> where FS1: FoldingSchemeCycleFoldGadget< 1, 1, + VerifierKey = (), VC: CommitmentDefGadget>, CFScalarVar = ::ScalarVar, >, FS2: FoldingSchemeFullGadget< 1, 1, - VC: CommitmentDefGadget>, + VerifierKey = (), + VC: CommitmentDefGadget, >, { pub fn compute_next_state( @@ -65,20 +67,19 @@ where initial_state: &FC::State, current_state: &FC::State, external_inputs: FC::ExternalInputs, - U: >::Value, - u: >::Value, + U: &>::Value, + u: &>::Value, proof: >::Value, - hint: >::Value, - cf_U: >::Value, + cf_U: &>::Value, cf_us: Vec<>::Value>, cf_proofs: Vec<>::Value>, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { - let poseidon = PoseidonSpongeVar::new_with_pp_hash( - &self.poseidon_config, + let hash = GriffinSpongeVar::new_with_pp_hash( + &self.griffin_config, &FpVar::new_witness(cs.clone(), || Ok(pp_hash))?, )?; - let sponge = poseidon.separate_domain("sponge".as_ref())?; - let mut transcript = poseidon.separate_domain("transcript".as_ref())?; + let sponge = hash.separate_domain("sponge".as_ref())?; + let mut transcript = hash.separate_domain("transcript".as_ref())?; let i = FpVar::new_witness(cs.clone(), || Ok(FC::Field::from(i as u64)))?; let ii = &i + FpVar::one(); @@ -88,51 +89,63 @@ where let initial_state = FC::StateVar::new_witness(cs.clone(), || Ok(initial_state))?; let current_state = FC::StateVar::new_witness(cs.clone(), || Ok(current_state))?; - let U_dummy = FS1::RU::new_witness(cs.clone(), || { - Ok(>::Value::dummy(&self.arith1_config)) - })?; + let U_dummy = FS1::RU::new_constant( + cs.clone(), + >::Value::dummy(self.arith1_config), + )?; let U = FS1::RU::new_witness(cs.clone(), || Ok(U))?; - let u = FS1::IU::new_witness(cs.clone(), || Ok(u))?; let proof = FS1::Proof::new_witness(cs.clone(), || Ok(proof))?; - let hint = FS1::Hint::new_witness(cs.clone(), || Ok(hint))?; - let cf_U_dummy = FS2::RU::new_witness(cs.clone(), || { - Ok(>::Value::dummy(&self.arith2_config)) - })?; + let cf_U_dummy = FS2::RU::new_constant( + cs.clone(), + >::Value::dummy(self.arith2_config), + )?; let cf_U = FS2::RU::new_witness(cs.clone(), || Ok(cf_U))?; - let cf_us = Vec::new_witness(cs.clone(), || Ok(cf_us))?; let cf_proofs = Vec::new_witness(cs.clone(), || Ok(cf_proofs))?; - let u_x = { - let mut sponge = sponge.clone(); - sponge.add(&i)?; - sponge.add(&initial_state)?; - sponge.add(¤t_state)?; - sponge.add(&U)?; - sponge.add(&cf_U)?; - sponge.get_field_elements(2)? - }; + println!("{}", cs.num_constraints()); - let (next_state, external_outputs) = - self.step_circuit - .generate_step_constraints(i, current_state, external_inputs)?; + let u_x = sponge + .clone() + .add(&i)? + .add(&initial_state)? + .add(¤t_state)? + .add(&U)? + .add(&cf_U)? + .get_field_elements(2)?; + let u = FS1::IU::new_witness_with_public_inputs(cs.clone(), u, u_x)?; + let (UU, rho) = FS1::verify_hinted(&(), &mut transcript, &[&U], &[&u], &proof)?; + let actual_UU = is_basecase.select(&U_dummy, &UU)?; - let (UU, rho) = FS1::verify_hinted(&self.vk1, &mut transcript, &[&U], &[&u], &proof, hint)?; + println!("{}", cs.num_constraints()); let mut cf_UU = cf_U; - for (cf_u, cf_proof) in cf_us.iter().zip(&cf_proofs) { - cf_UU = FS2::verify(&self.vk2, &mut transcript, &[cf_UU], &[cf_u], cf_proof)?; + for ((cf_u, cf_u_x), cf_proof) in cf_us + .iter() + .zip(FS1::to_cyclefold_inputs(U, u, UU, proof, rho)?) + .zip(&cf_proofs) + { + let cf_u = FS2::IU::new_witness_with_public_inputs(cs.clone(), cf_u, cf_u_x)?; + cf_UU = FS2::verify(&(), &mut transcript, &[cf_UU], &[cf_u], cf_proof)?; } + let actual_cf_UU = is_basecase.select(&cf_U_dummy, &cf_UU)?; + + println!("{}", cs.num_constraints()); + + let (next_state, external_outputs) = + self.step_circuit + .generate_step_constraints(i, current_state, external_inputs)?; + + println!("{}", cs.num_constraints()); - let uu_x = { - let mut sponge = sponge.clone(); - sponge.add(&ii)?; - sponge.add(&initial_state)?; - sponge.add(&next_state)?; - sponge.add(&is_basecase.select(&U_dummy, &UU)?)?; - sponge.add(&is_basecase.select(&cf_U_dummy, &cf_UU)?)?; - sponge.get_field_elements(2)? - }; + let uu_x = sponge + .clone() + .add(&ii)? + .add(&initial_state)? + .add(&next_state)? + .add(&actual_UU)? + .add(&actual_cf_UU)? + .get_field_elements(2)?; // This line "converts" `uu_x` from witnesses to public inputs. // Instead of directly modifying the constraint system, we explicitly // allocate a public input and enforce that its value is indeed `uu_x`. @@ -142,14 +155,11 @@ where // computing them outside the circuit. // - `.enforce_equal()` prevents a malicious prover from claiming wrong // public inputs that are not the honest `uu_x` computed in-circuit. - uu_x.enforce_equal(&Vec::new_input(cs.clone(), || uu_x.value())?)?; + uu_x.enforce_equal(&Vec::new_input(cs.clone(), || { + Ok(uu_x.value().unwrap_or(vec![Default::default(); uu_x.len()])) + })?)?; - u.public_inputs().enforce_equal(&u_x)?; - - cf_us - .iter() - .zip(FS1::to_cyclefold_inputs(U, u, UU, rho)?) - .try_for_each(|(cf_u, cf_u_x)| cf_u.public_inputs().enforce_equal(&cf_u_x))?; + println!("{}", cs.num_constraints()); if cs.is_in_setup_mode() { Ok((self.step_circuit.dummy_state(), external_outputs)) @@ -159,48 +169,247 @@ where } } -impl<'a, FC: FCircuit, FS1, FS2> ConstraintSynthesizer - for AugmentedCircuit<'a, FC, FS1, FS2> +impl<'a, FS1, FS2, FC: FCircuit> ConstraintSynthesizer + for AugmentedCircuit<'a, FS1, FS2, FC> where FS1: FoldingSchemeCycleFoldGadget< 1, 1, + VerifierKey = (), VC: CommitmentDefGadget>, CFScalarVar = ::ScalarVar, >, FS2: FoldingSchemeFullGadget< 1, 1, - VC: CommitmentDefGadget>, + VerifierKey = (), + VC: CommitmentDefGadget, >, { fn generate_constraints( self, cs: ConstraintSystemRef, ) -> Result<(), SynthesisError> { - let external_inputs = self.step_circuit.dummy_external_inputs(); - let U = >::Value::dummy(&self.arith1_config); - let u = Dummy::dummy(&self.arith1_config); - let proof = Dummy::dummy(&self.arith1_config); - let hint = Default::default(); - let cf_U = Dummy::dummy(&self.arith2_config); - let cf_us = vec![Dummy::dummy(&self.arith2_config); U.commitments().len()]; - let cf_proofs = vec![Dummy::dummy(&self.arith2_config); U.commitments().len()]; self.compute_next_state( cs, Default::default(), 0, &self.step_circuit.dummy_state(), &self.step_circuit.dummy_state(), - external_inputs, - U, - u, - proof, - hint, - cf_U, - cf_us, - cf_proofs, + self.step_circuit.dummy_external_inputs(), + &Dummy::dummy(self.arith1_config), + &Dummy::dummy(self.arith1_config), + Dummy::dummy(self.arith1_config), + &Dummy::dummy(self.arith2_config), + vec![Dummy::dummy(self.arith2_config); >::Value::N_COMMITMENTS], + vec![Dummy::dummy(self.arith2_config); >::Value::N_COMMITMENTS], ) .map(|_| ()) } } + +/// [`CycleFoldConfig`] controls the behavior of [`CycleFoldCircuit`]. +/// +/// Looking ahead, the circuit computes the random linear combination of points, +/// which is essentially done by iteratively computing `P = (P + p_i) * r_i`, +/// where `P` is the folded point, `p_i` is the input point, and `r_i` is the +/// randomness. +pub trait CycleFoldConfig: Sized + Default { + type C: SonobeCurve; + + /// `N_INPUT_POINTS` specifies the number of input points that are folded in + /// [`CycleFoldCircuit`] via random linear combinations. + const N_INPUT_POINTS: usize; + /// `N_UNIQUE_RANDOMNESSES` specifies the number of *unique* randomnesses + /// allocated in [`CycleFoldCircuit`]. Although the linear combination in + /// general consists of multiple randomnesses, some folding schemes (such as + /// Nova and HyperNova) only need a single one. Thus, by setting this value, + /// the circuit can learn how many randomnesses are used and how long the + /// public inputs vector should be. + const N_UNIQUE_RANDOMNESSES: usize; + /// `RANDOMNESS_BIT_LENGTH` is the maximum bit length of a randomness `r_i`. + const RANDOMNESS_BIT_LENGTH: usize; + /// `FIELD_CAPACITY` is the maximum number of bits that can be stored in a + /// field element. + /// + /// By default, `FIELD_CAPACITY` is set to `MODULUS_BIT_SIZE - 1`. + /// + /// Given a randomness `r_i` with `RANDOMNESS_BIT_LENGTH` bits, we need + /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY` field elements to represent it + /// *compactly* in-circuit. + const FIELD_CAPACITY: usize = CF2::::MODULUS_BIT_SIZE as usize - 1; + + /// Public inputs length for the [`CycleFoldCircuit`], which depends on the + /// above constants defined by the concrete folding scheme. For example: + /// * In Nova, this is `|r| + |p_1| + |p_2| + |P|` + /// * In HyperNova, this is `|r| + |p_i| * n_points + |P|`. + /// * In ProtoGalaxy, this is `|[..., r_i, ...]| + |p_i| * n_points + |P|`. + /// + /// As explained above, `|r|` (i.e., the length of a single randomness) is + /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY`. + /// When there are multiple randomnesses, the length of `|[..., r_i, ...]|` + /// is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY`, as + /// the bits of all randomnesses are concatenated before being packed into + /// field elements. + /// The length of a point `p_i` when treated as public inputs is 2, as we + /// only need the `x` and `y` coordinates of the point. + /// + /// Thus, `IO_LEN` is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY + 2 * (N_INPUT_POINTS + 1)`. + const IO_LEN: usize = { + (Self::RANDOMNESS_BIT_LENGTH * Self::N_UNIQUE_RANDOMNESSES).div_ceil(Self::FIELD_CAPACITY) + + 2 * (Self::N_INPUT_POINTS + 1) + }; + + /// `alloc_points` allocates the points that are going to be folded in the + /// [`CycleFoldCircuit`] via random linear combinations. + /// + /// The implementation must allocate the points as *witness* variables (i.e. + /// by calling [`AllocVar::new_witness`]) first, then mark them as public + /// inputs by calling [`CycleFoldConfig::mark_point_as_public`], and finally + /// return the allocated witness variables. + /// + /// While it is possible to allocate the points as public inputs directly, + /// we do not use this approach because this will create a longer vector of + /// public inputs, which is not ideal for the augmented step circuit on the + /// primary curve. + fn alloc_points( + &self, + cs: ConstraintSystemRef>, + ) -> Result::Var>, SynthesisError>; + + /// `alloc_randomnesses` allocates the randomnesses used as coefficients of + /// the random linear combinations in the `CycleFoldCircuit`. + /// + /// The implementation must allocate the randomnesses as *witness* variables + /// (i.e. by calling [`AllocVar::new_witness`]) first, then mark them as + /// public inputs by calling [`CycleFoldConfig::mark_point_as_public`], and + /// finally return the allocated witness variables. + /// + /// See [`CycleFoldConfig::alloc_points`] for the reason why they need to be + /// allocated as witness variables first and converted to public later. + /// + /// In addition, because the circuit computes `P = (P + p_i) * r_i` for each + /// `i` from `N_INPUT_POINTS - 1` down to `0`, the actual linear combination + /// is `P = r_0 * p_0 + (r_0 r_1) * p_1 + (r_0 r_1 r_2) * p_2 + ...`. Thus, + /// to compute `P = R_0 p_0 + R_1 p_1 + R_2 p_2 + ...`, the implementation + /// should return `r_0 = R_0, r_1 = R_1 / R_0, ..., r_i = R_i / R_{i - 1}`. + /// A special case is `R_i = R^i`, where the allocated randomnesses become + /// `r_0 = 1, r_1 = r_2 = ... = R`. + fn alloc_randomnesses( + &self, + cs: ConstraintSystemRef>, + ) -> Result>>>, SynthesisError>; + + /// `mark_point_as_public` marks a point as public. + /// + /// The final vector of public inputs is shorter than the result of calling + /// [`AllocVar::new_input`], because we only need the x and y coordinates of + /// the point, but the `infinity` flag is not necessary. + fn mark_point_as_public(point: &::Var) -> Result<(), SynthesisError> { + for x in &point.to_constraint_field()?[..2] { + // This line "converts" `x` from a witness to a public input. + // Instead of directly modifying the constraint system, we explicitly + // allocate a public input and enforce that its value is indeed `x`. + // While comparing `x` with itself seems redundant, this is necessary + // because: + // - `.value()` allows an honest prover to extract public inputs without + // computing them outside the circuit. + // - `.enforce_equal()` prevents a malicious prover from claiming wrong + // public inputs that are not the honest `x` computed in-circuit. + FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(x)?; + } + Ok(()) + } + + /// `mark_randomness_as_public` marks randomness as public. + /// + /// The final vector of public inputs is shorter than the result of calling + /// [`AllocVar::new_input`], because we pack the bits of randomness into + /// a compact field elements. + fn mark_randomness_as_public(r: &[Boolean>]) -> Result<(), SynthesisError> { + for bits in r.chunks(Self::FIELD_CAPACITY) { + let x = Boolean::le_bits_to_fp(bits)?; + FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(&x)?; + } + Ok(()) + } +} + +#[derive(Debug, Clone, Default)] +pub struct CycleFoldCircuit { + _cfg: PhantomData, +} + +impl CycleFoldCircuit { + pub fn fold_points( + &self, + cs: ConstraintSystemRef>, + cfg: Cfg, + ) -> Result<(), SynthesisError> { + let rs = cfg.alloc_randomnesses(cs.clone())?; + let points = cfg.alloc_points(cs.clone())?; + + #[cfg(test)] + { + assert_eq!(Cfg::N_INPUT_POINTS, points.len()); + assert_eq!(Cfg::N_INPUT_POINTS, rs.len()); + for r in &rs { + assert_eq!(Cfg::RANDOMNESS_BIT_LENGTH, r.len()); + } + } + + // A slightly optimized version of `scalar_mul_le`. + fn point_mul( + point: &C::Var, + r: &[Boolean>], + ) -> Result { + if r.is_constant() { + let r = CF1::::from( as PrimeField>::BigInt::from_bits_le(&r.value()?)); + if r.is_one() { + return Ok(point.clone()); + } + } + point.scalar_mul_le(r.iter()) + } + + // Given a vector of points (over the primary curve) that are obtained + // from the instances of the folding scheme, we fold them *natively* in + // the CycleFold circuit (over the secondary curve). + // * In Nova, we need to compute P = p_0 + R * p_1. + // - for the cmW we're computing: U_i1.cmW = U_i.cmW + R * u_i.cmW + // - for the cmE we're computing: U_i1.cmE = U_i.cmE + R * cmT + R^2 * u_i.cmE, where u_i.cmE + // is assumed to be 0, so, U_i1.cmE = U_i.cmE + R * cmT + // * In HyperNova, we need to compute P = p_0 + R * p_1 + R^2 * p_2 + ... + R^{n-1} * p_{n-1}. + // * In ProtoGalaxy, we need to compute P = R_0 * p_0 + R_1 * p_1 + R_2 * p_2 + ... + R_{n-1} * p_{n-1}. + // + // To handle HyperNova more efficiently (with less constraints), we do + // P = ((((p_{n-1} * R) + p_{n-2}) * R + p_{n-3}) * R + ...) * R + p_0. + // This can be done iteratively by computing P = (P + p_i) * R. + // + // We further generalize this to support ProtoGalaxy, which now becomes + // P = (((((p_{n-1} * r_{n-1}) + p_{n-2}) * r_{n-2} + p_{n-3}) * r_{n-3} + ...) * r_1 + p_0) * r_0 + // + // Here, r_0 = 1, r_1 = r_2 = ... = r_{n-1} = R for Nova and HyperNova, + // and r_i = R_i / R_{i - 1} for ProtoGalaxy. + let mut p_folded = point_mul::( + &points[Cfg::N_INPUT_POINTS - 1], + &rs[Cfg::N_INPUT_POINTS - 1], + )?; + for i in (0..Cfg::N_INPUT_POINTS - 1).rev() { + p_folded = point_mul::(&(p_folded + &points[i]), &rs[i])?; + } + + Cfg::mark_point_as_public(&p_folded)?; + + Ok(()) + } +} + +impl ConstraintSynthesizer> for CycleFoldCircuit { + fn generate_constraints( + self, + cs: ConstraintSystemRef>, + ) -> Result<(), SynthesisError> { + self.fold_points(cs, Cfg::default()) + } +} diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 1a538b2b0..3a36f4993 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,122 +1,349 @@ use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; +use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, +}; +use ark_std::{marker::PhantomData, rand::RngCore, sync::Arc}; +use sonobe_fs::{ + FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemeFullGadget, + FoldingSchemePartialGadget, }; -use ark_std::{marker::PhantomData, rand::RngCore}; -use sonobe_fs::{FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemePartialGadget}; use sonobe_primitives::{ - circuits::{ConstraintSystemExt, FCircuit}, - commitments::{CommitmentDef}, + arithmetizations::{Arith, ArithConfig}, + circuits::{ArithExtractor, AssignmentsExtractor, ConstraintSystemExt, FCircuit}, + commitments::{CommitmentDef, CommitmentDefGadget}, relations::WitnessInstanceSampler, - traits::SonobeField, - transcripts::Transcript, + traits::{CF1, CF2, Dummy, Inputize, InputizeEmulated, SonobeCurve, SonobeField}, + transcripts::{ + Absorbable, Transcript, + griffin::{GriffinParams, sponge::GriffinSponge}, + }, }; -use crate::IVC; +use crate::{ + Error, IVC, + compilers::cyclefold::circuits::{AugmentedCircuit, CycleFoldCircuit, CycleFoldConfig}, +}; -mod circuits; +pub mod circuits; pub trait FoldingSchemeCycleFoldGadget: FoldingSchemePartialGadget { + type CFConfig: CycleFoldConfig< + C = <>::VC as CommitmentDef>::Commitment, + >; + type CFScalarVar; + const N_CYCLEFOLDS: usize; + + fn to_cyclefold_configs( + U: &>::RU, + u: &>::IU, + proof: &>::Proof, + rho: >::Challenge, + ) -> Vec; + fn to_cyclefold_inputs( U: Self::RU, u: Self::IU, UU: Self::RU, + proof: Self::Proof, rho: Self::Challenge, ) -> Result>, SynthesisError>; } -pub struct CycleFoldBasedIVC { - _fs1: PhantomData, - _fs2: PhantomData, +pub struct ProverKey, FS2: FoldingScheme<1, 1>>( + FS1::ProverKey, + FS1::DeciderKey, + FS2::ProverKey, + FS2::DeciderKey, + Arc>, + F, + ::Config, + ::Config, +); + +pub struct Proof, FS2: FoldingScheme<1, 1>>( + FS1::RW, + FS1::RU, + FS1::IW, + FS1::IU, + FS2::RW, + FS2::RU, +); + +impl, FS2: FoldingScheme<1, 1>> + Dummy<&ProverKey> for Proof +{ + fn dummy(pk: &ProverKey) -> Self { + let cfg1 = &pk.6; + let cfg2 = &pk.7; + + let W = FS1::RW::dummy(cfg1); + let U = FS1::RU::dummy(cfg1); + let w = FS1::IW::dummy(cfg1); + let u = FS1::IU::dummy(cfg1); + let cf_W = FS2::RW::dummy(cfg2); + let cf_U = FS2::RU::dummy(cfg2); + + Self(W, U, w, u, cf_W, cf_U) + } } -pub struct ProverKey { - poseidon_config: PoseidonConfig, - pp_hash: FC::Field, +pub struct CycleFoldBasedIVC { + _d: PhantomData<(C1, C2, FS1, FS2)>, } -impl IVC for CycleFoldBasedIVC +impl IVC for CycleFoldBasedIVC where - FS1: FoldingScheme< + C1: SonobeCurve, + FS1: FoldingSchemeCycleFoldGadget< + 1, + 1, + VC: CommitmentDefGadget< + ConstraintField = C1::ScalarField, + ScalarVar = FpVar, + >, + CFScalarVar = ::ScalarVar, + Native: FoldingScheme< + 1, + 1, + Arith: Arith + From>, + TranscriptField = C1::ScalarField, + VC: CommitmentDef, + >, + VerifierKey = (), + >, + FS2: FoldingSchemeFullGadget< 1, 1, - VC: CommitmentDef>::TranscriptField>, + Native: FoldingScheme< + 1, + 1, + Arith: Arith + From>, + TranscriptField = C1::ScalarField, + VC: CommitmentDef, + >, + VerifierKey = (), + VC: CommitmentDefGadget, >, - FS2: FoldingScheme<1, 1>, { - type Field = ::Scalar; - - type Config = (FS1::Config, FS2::Config); + type Field = C1::ScalarField; - type PublicParam = (FS1::PublicParam, FS2::PublicParam); + type Config = ( + >::Config, + >::Config, + Arc>, + ); - type ProverKey = ( - FS1::ProverKey, - FS1::DeciderKey, - FS2::ProverKey, - FS2::DeciderKey, - ProverKey, + type PublicParam = ( + >::PublicParam, + >::PublicParam, + Arc>, ); - type VerifierKey = (); + type ProverKey = ProverKey; + + type VerifierKey = ( + >::DeciderKey, + >::DeciderKey, + Arc>, + Self::Field, + ); - type Proof = (FS1::RW, FS1::RU, FS1::IW, FS1::IU, FS2::RW, FS2::RU); + type Proof = Proof; fn preprocess( - config: Self::Config, + (cfg1, cfg2, griffin_config): Self::Config, mut rng: impl RngCore, - ) -> Result { + ) -> Result { Ok(( - FS1::preprocess(config.0, &mut rng)?, - FS2::preprocess(config.1, &mut rng)?, + FS1::Native::preprocess(cfg1, &mut rng)?, + FS2::Native::preprocess(cfg2, &mut rng)?, + griffin_config, )) } fn generate_keys>( - pp: &Self::PublicParam, + (pp1, pp2, griffin_config): Self::PublicParam, step_circuit: &FC, - ) -> Result<(Self::ProverKey, Self::VerifierKey), crate::Error> { - todo!() + ) -> Result<(Self::ProverKey, Self::VerifierKey), Error> { + let mut arith2 = >::Arith::default(); + + loop { + let cyclefold_circuit = CycleFoldCircuit::::default(); + + let cs = ArithExtractor::new(); + cs.execute_synthesizer(cyclefold_circuit)?; + let new_arith2 = cs.arith::<>::Arith>()?; + if new_arith2.config() == arith2.config() { + break; + } + arith2 = new_arith2; + } + + let mut arith1 = >::Arith::default(); + + loop { + let augmented_circuit = AugmentedCircuit:: { + griffin_config: griffin_config.clone(), + arith1_config: arith1.config(), + arith2_config: arith2.config(), + step_circuit, + }; + + let cs = ArithExtractor::new(); + cs.execute_synthesizer(augmented_circuit)?; + let new_arith1 = cs.arith::<>::Arith>()?; + if new_arith1.config() == arith1.config() { + break; + } + arith1 = new_arith1; + } + + let arith1_config = arith1.config().clone(); + let arith2_config = arith2.config().clone(); + + let (pk1, _, dk1) = FS1::Native::generate_keys(pp1, arith1)?; + let (pk2, _, dk2) = FS2::Native::generate_keys(pp2, arith2)?; + + let pp_hash = Zero::zero(); // TODO + + Ok(( + ProverKey( + pk1, + dk1.clone(), + pk2, + dk2.clone(), + griffin_config.clone(), + pp_hash, + arith1_config, + arith2_config, + ), + (dk1, dk2, griffin_config, pp_hash), + )) } fn prove>( - (pk_fs1, dk_fs1, pk_fs2, dk_fs2, pk): &Self::ProverKey, + ProverKey(pk1, dk1, pk2, dk2, griffin_config, pp_hash, arith1_config, arith2_config): &Self::ProverKey, step_circuit: &FC, i: usize, initial_state: &FC::State, current_state: &FC::State, external_inputs: FC::ExternalInputs, - (W, U, w, u, cfW, cfU): &Self::Proof, + Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, mut rng: impl RngCore, - ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), crate::Error> { - let poseidon = PoseidonSponge::new_with_pp_hash(&pk.poseidon_config, pk.pp_hash); - let sponge = poseidon.separate_domain("sponge".as_ref()); - let mut transcript = poseidon.separate_domain("transcript".as_ref()); + ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error> { + let hash = GriffinSponge::new_with_pp_hash(&griffin_config, *pp_hash); + let mut transcript = hash.separate_domain("transcript".as_ref()); - let (WW, UU, proof, challenge) = - FS1::prove(pk_fs1, &mut transcript, &[W], &[U], &[w], &[u], &mut rng)?; + let augmented_circuit = AugmentedCircuit:: { + griffin_config: griffin_config.clone(), + arith1_config, + arith2_config, + step_circuit, + }; - if i == 0 { - } else { + let mut WW = Dummy::dummy(arith1_config); + let mut UU = Dummy::dummy(arith1_config); + let mut proof = Dummy::dummy(arith1_config); + let mut cf_us = vec![Dummy::dummy(arith2_config); FS1::N_CYCLEFOLDS]; + let mut cf_proofs = vec![Dummy::dummy(arith2_config); FS1::N_CYCLEFOLDS]; + let mut cf_UU = Dummy::dummy(arith2_config); + let mut cf_WW = Dummy::dummy(arith2_config); + + if i != 0 { + cf_us.clear(); + cf_proofs.clear(); + + let challenge; + (WW, UU, proof, challenge) = + FS1::Native::prove(pk1, &mut transcript, &[W], &[U], &[w], &[u], &mut rng)?; + + let cf_configs = FS1::to_cyclefold_configs(&U, &u, &proof, challenge); + for cfg in cf_configs { + let cs = AssignmentsExtractor::new(); + cs.execute_fn(|cs| CycleFoldCircuit::default().fold_points(cs, cfg))?; + + let (cf_w, cf_u) = dk2.sample(cs.assignments()?, &mut rng)?; + + let cf_proof; + (cf_WW, cf_UU, cf_proof, _) = FS2::Native::prove( + pk2, + &mut transcript, + &[cf_W], + &[cf_U], + &[&cf_w], + &[&cf_u], + &mut rng, + )?; + cf_us.push(cf_u); + cf_proofs.push(cf_proof); + } } - let cs = ConstraintSystem::::new_ref(); + let cs = AssignmentsExtractor::new(); + let (next_state, external_outputs) = cs.execute_fn(|cs| { + augmented_circuit.compute_next_state( + cs, + *pp_hash, + i, + initial_state, + current_state, + external_inputs, + U, + u, + proof, + cf_U, + cf_us, + cf_proofs, + ) + })?; + + let (ww, uu) = dk1.sample(cs.assignments()?, &mut rng)?; - todo!() + Ok(( + next_state, + external_outputs, + Proof(WW, UU, ww, uu, cf_WW, cf_UU), + )) } fn verify>( - vk: &Self::VerifierKey, + (dk1, dk2, griffin_config, pp_hash): &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, - proof: &Self::Proof, - ) -> Result<(), crate::Error> { - todo!() + Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, + ) -> Result<(), Error> { + if i == 0 { + return (initial_state == current_state) + .then_some(()) + .ok_or(Error::IVCVerificationFail); + } + + let griffin = GriffinSponge::new_with_pp_hash(griffin_config, *pp_hash); + let mut sponge = griffin.separate_domain("sponge".as_ref()); + + let u_x = sponge + .add(&i) + .add(initial_state) + .add(current_state) + .add(U) + .add(cf_U) + .get_field_elements(2); + + if u.public_inputs() != &u_x[..] { + return Err(Error::IVCVerificationFail); + } + + FS1::Native::decide_running(&dk1, &W, &U)?; + FS1::Native::decide_incoming(&dk1, &w, &u)?; + FS2::Native::decide_running(&dk2, &cf_W, &cf_U)?; + + Ok(()) } } diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 2f04c7d07..183910db0 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -1,16 +1,21 @@ use ark_ff::PrimeField; +use ark_relations::gr1cs::SynthesisError; use ark_std::rand::RngCore; -use sonobe_primitives::circuits::FCircuit; +use sonobe_primitives::{circuits::FCircuit, traits::Dummy}; use thiserror::Error; pub mod compilers; #[derive(Debug, Error)] pub enum Error { - #[error("Arithmetization error: {0}")] + #[error(transparent)] ArithError(#[from] sonobe_primitives::arithmetizations::Error), - #[error("Folding error: {0}")] + #[error(transparent)] FoldingError(#[from] sonobe_fs::Error), + #[error(transparent)] + SynthesisError(#[from] SynthesisError), + #[error("IVC verification failed")] + IVCVerificationFail, } pub trait IVC { @@ -18,19 +23,19 @@ pub trait IVC { type Config; type PublicParam; - type ProverKey; - type VerifierKey; + type ProverKey; + type VerifierKey; type Proof; fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; fn generate_keys>( - pp: &Self::PublicParam, + pp: Self::PublicParam, step_circuit: &FC, - ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; + ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; fn prove>( - pk: &Self::ProverKey, + pk: &Self::ProverKey, step_circuit: &FC, i: usize, initial_state: &FC::State, @@ -41,7 +46,7 @@ pub trait IVC { ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error>; fn verify>( - vk: &Self::VerifierKey, + vk: &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, @@ -50,7 +55,7 @@ pub trait IVC { } pub struct IVCStatefulProver { - pub pk: I::ProverKey, + pub pk: I::ProverKey, pub step_circuit: FC, pub i: usize, pub initial_state: FC::State, @@ -60,18 +65,22 @@ pub struct IVCStatefulProver { impl, I: IVC> IVCStatefulProver { pub fn new( - pk: I::ProverKey, + pk: I::ProverKey, step_circuit: FC, initial_state: FC::State, - initial_proof: I::Proof, - ) -> Result { + ) -> Result + where + I::Proof: for<'a> Dummy<&'a I::ProverKey>, + { + let current_proof = I::Proof::dummy(&pk); + Ok(Self { pk, step_circuit, i: 0, current_state: initial_state.clone(), initial_state, - current_proof: initial_proof, + current_proof, }) } From ec8e739a51efe17fb4649fbaf14e13c8f3801e85 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 17 Nov 2025 06:09:37 +0800 Subject: [PATCH 45/99] Convenience trait for group based folding schemes --- crates/fs/src/lib.rs | 45 ++- crates/ivc/Cargo.toml | 2 + .../ivc/src/compilers/cyclefold/circuits.rs | 276 +++++------------- crates/ivc/src/compilers/cyclefold/mod.rs | 131 ++++----- crates/ivc/src/compilers/mod.rs | 1 + crates/ivc/src/lib.rs | 64 +++- 6 files changed, 230 insertions(+), 289 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index c8b728d8e..68248f408 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -11,12 +11,13 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, fmt::Debug, rand::RngCore}; use sonobe_primitives::{ + algebra::group::emulated::EmulatedAffineVar, arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, - commitments::{CommitmentDef, CommitmentDefGadget}, + commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, sumcheck::Error as SumCheckError, - traits::{Dummy, SonobeField}, + traits::{CF2, Dummy, SonobeField}, transcripts::{Absorbable, AbsorbableVar, Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -391,6 +392,38 @@ impl FoldingInstanceVar for PlainInstanceVar { } } +pub trait GroupBasedFoldingSchemePrimary: + FoldingScheme< + M, + N, + VC: GroupBasedCommitment, + TranscriptField = <>::VC as CommitmentDef>::Scalar, +> +{ + type Gadget: FoldingSchemePartialGadget< + M, + N, + Native = Self, + VC = ::Gadget2, + >; +} + +pub trait GroupBasedFoldingSchemeSecondary: + FoldingScheme< + M, + N, + VC: GroupBasedCommitment, + TranscriptField = CF2<<>::VC as CommitmentDef>::Commitment>, +> +{ + type Gadget: FoldingSchemeFullGadget< + M, + N, + Native = Self, + VC = ::Gadget1, + >; +} + pub trait FoldingSchemePartialGadget { type Native: FoldingScheme; @@ -416,8 +449,8 @@ pub trait FoldingSchemePartialGadget { fn verify_hinted( vk: &Self::VerifierKey, transcript: &mut impl TranscriptGadget<::ConstraintField>, - Us: &[impl Borrow; M], - us: &[impl Borrow; N], + Us: [&Self::RU; M], + us: [&Self::IU; N], proof: &Self::Proof, ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; } @@ -429,8 +462,8 @@ pub trait FoldingSchemeFullGadget: fn verify( vk: &Self::VerifierKey, transcript: &mut impl TranscriptGadget<::ConstraintField>, - Us: &[impl Borrow; M], - us: &[impl Borrow; N], + Us: [&Self::RU; M], + us: [&Self::IU; N], proof: &Self::Proof, ) -> Result; } diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index be76625ee..5924a988b 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -7,10 +7,12 @@ repository.workspace = true [dependencies] ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } +ark-ec = { workspace = true } ark-ff = { workspace = true, features = ["asm"] } ark-r1cs-std = { workspace = true } ark-relations = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } +num-bigint = { workspace = true, features = ["rand"] } thiserror = { workspace = true } sonobe-primitives = { workspace = true } diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 017036671..3cc9d088a 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -14,7 +14,7 @@ use ark_relations::gr1cs::{ use ark_std::{marker::PhantomData, rand::RngCore, sync::Arc}; use sonobe_fs::{ FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemeFullGadget, - FoldingSchemePartialGadget, + FoldingSchemePartialGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ algebra::Val, @@ -33,31 +33,35 @@ use crate::compilers::cyclefold::FoldingSchemeCycleFoldGadget; pub struct AugmentedCircuit< 'a, - FS1: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, - FS2: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + FS1: GroupBasedFoldingSchemePrimary<1, 1>, + FS2: GroupBasedFoldingSchemeSecondary<1, 1>, FC: FCircuit, > { pub griffin_config: Arc>, - pub arith1_config: &'a <>::Arith as Arith>::Config, - pub arith2_config: &'a <>::Arith as Arith>::Config, + pub arith1_config: &'a ::Config, + pub arith2_config: &'a ::Config, pub step_circuit: &'a FC, } -impl<'a, FS1, FS2, FC: FCircuit> AugmentedCircuit<'a, FS1, FS2, FC> +impl<'a, FS1, FS2, FC> AugmentedCircuit<'a, FS1, FS2, FC> where FS1: FoldingSchemeCycleFoldGadget< - 1, - 1, - VerifierKey = (), - VC: CommitmentDefGadget>, - CFScalarVar = ::ScalarVar, + 1, + 1, + Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, - FS2: FoldingSchemeFullGadget< - 1, - 1, - VerifierKey = (), - VC: CommitmentDefGadget, + >, + FS2: GroupBasedFoldingSchemeSecondary< + 1, + 1, + Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, + >, + FC: FCircuit::Scalar>, { pub fn compute_next_state( &self, @@ -67,12 +71,12 @@ where initial_state: &FC::State, current_state: &FC::State, external_inputs: FC::ExternalInputs, - U: &>::Value, - u: &>::Value, - proof: >::Value, - cf_U: &>::Value, - cf_us: Vec<>::Value>, - cf_proofs: Vec<>::Value>, + U: &FS1::RU, + u: &FS1::IU, + proof: FS1::Proof, + cf_U: &FS2::RU, + cf_us: Vec, + cf_proofs: Vec, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { let hash = GriffinSpongeVar::new_with_pp_hash( &self.griffin_config, @@ -89,22 +93,14 @@ where let initial_state = FC::StateVar::new_witness(cs.clone(), || Ok(initial_state))?; let current_state = FC::StateVar::new_witness(cs.clone(), || Ok(current_state))?; - let U_dummy = FS1::RU::new_constant( - cs.clone(), - >::Value::dummy(self.arith1_config), - )?; - let U = FS1::RU::new_witness(cs.clone(), || Ok(U))?; - let proof = FS1::Proof::new_witness(cs.clone(), || Ok(proof))?; + let U_dummy = AllocVar::new_constant(cs.clone(), FS1::RU::dummy(self.arith1_config))?; + let U = AllocVar::new_witness(cs.clone(), || Ok(U))?; + let proof = AllocVar::new_witness(cs.clone(), || Ok(proof))?; - let cf_U_dummy = FS2::RU::new_constant( - cs.clone(), - >::Value::dummy(self.arith2_config), - )?; - let cf_U = FS2::RU::new_witness(cs.clone(), || Ok(cf_U))?; + let cf_U_dummy = AllocVar::new_constant(cs.clone(), FS2::RU::dummy(self.arith2_config))?; + let cf_U = AllocVar::new_witness(cs.clone(), || Ok(cf_U))?; let cf_proofs = Vec::new_witness(cs.clone(), || Ok(cf_proofs))?; - println!("{}", cs.num_constraints()); - let u_x = sponge .clone() .add(&i)? @@ -113,30 +109,27 @@ where .add(&U)? .add(&cf_U)? .get_field_elements(2)?; - let u = FS1::IU::new_witness_with_public_inputs(cs.clone(), u, u_x)?; - let (UU, rho) = FS1::verify_hinted(&(), &mut transcript, &[&U], &[&u], &proof)?; + let u = FoldingInstanceVar::new_witness_with_public_inputs(cs.clone(), u, u_x)?; + let (UU, rho) = FS1::Gadget::verify_hinted(&(), &mut transcript, [&U], [&u], &proof)?; let actual_UU = is_basecase.select(&U_dummy, &UU)?; - println!("{}", cs.num_constraints()); - let mut cf_UU = cf_U; for ((cf_u, cf_u_x), cf_proof) in cf_us .iter() - .zip(FS1::to_cyclefold_inputs(U, u, UU, proof, rho)?) + .zip(FS1::to_cyclefold_inputs([U], [u], UU, proof, rho)?) .zip(&cf_proofs) { - let cf_u = FS2::IU::new_witness_with_public_inputs(cs.clone(), cf_u, cf_u_x)?; - cf_UU = FS2::verify(&(), &mut transcript, &[cf_UU], &[cf_u], cf_proof)?; + let cf_u = + FoldingInstanceVar::new_witness_with_public_inputs(cs.clone(), cf_u, cf_u_x)?; + cf_UU = FS2::Gadget::verify(&(), &mut transcript, [&cf_UU], [&cf_u], cf_proof)?; } let actual_cf_UU = is_basecase.select(&cf_U_dummy, &cf_UU)?; - println!("{}", cs.num_constraints()); - - let (next_state, external_outputs) = - self.step_circuit - .generate_step_constraints(i, current_state, external_inputs)?; - - println!("{}", cs.num_constraints()); + let (next_state, external_outputs) = self.step_circuit.generate_step_constraints( + i, + current_state, + external_inputs, + )?; let uu_x = sponge .clone() @@ -159,8 +152,6 @@ where Ok(uu_x.value().unwrap_or(vec![Default::default(); uu_x.len()])) })?)?; - println!("{}", cs.num_constraints()); - if cs.is_in_setup_mode() { Ok((self.step_circuit.dummy_state(), external_outputs)) } else { @@ -169,22 +160,23 @@ where } } -impl<'a, FS1, FS2, FC: FCircuit> ConstraintSynthesizer - for AugmentedCircuit<'a, FS1, FS2, FC> +impl<'a, FS1, FS2, FC> ConstraintSynthesizer for AugmentedCircuit<'a, FS1, FS2, FC> where FS1: FoldingSchemeCycleFoldGadget< - 1, - 1, - VerifierKey = (), - VC: CommitmentDefGadget>, - CFScalarVar = ::ScalarVar, - >, - FS2: FoldingSchemeFullGadget< - 1, - 1, - VerifierKey = (), - VC: CommitmentDefGadget, + 1, + 1, + Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, + >, + FS2: GroupBasedFoldingSchemeSecondary< + 1, + 1, + Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef>, + >, + FC: FCircuit::Scalar>, { fn generate_constraints( self, @@ -201,8 +193,8 @@ where &Dummy::dummy(self.arith1_config), Dummy::dummy(self.arith1_config), &Dummy::dummy(self.arith2_config), - vec![Dummy::dummy(self.arith2_config); >::Value::N_COMMITMENTS], - vec![Dummy::dummy(self.arith2_config); >::Value::N_COMMITMENTS], + vec![Dummy::dummy(self.arith2_config); FS1::N_CYCLEFOLDS], + vec![Dummy::dummy(self.arith2_config); FS1::N_CYCLEFOLDS], ) .map(|_| ()) } @@ -220,22 +212,14 @@ pub trait CycleFoldConfig: Sized + Default { /// `N_INPUT_POINTS` specifies the number of input points that are folded in /// [`CycleFoldCircuit`] via random linear combinations. const N_INPUT_POINTS: usize; - /// `N_UNIQUE_RANDOMNESSES` specifies the number of *unique* randomnesses - /// allocated in [`CycleFoldCircuit`]. Although the linear combination in - /// general consists of multiple randomnesses, some folding schemes (such as - /// Nova and HyperNova) only need a single one. Thus, by setting this value, - /// the circuit can learn how many randomnesses are used and how long the - /// public inputs vector should be. - const N_UNIQUE_RANDOMNESSES: usize; - /// `RANDOMNESS_BIT_LENGTH` is the maximum bit length of a randomness `r_i`. - const RANDOMNESS_BIT_LENGTH: usize; + const N_INPUT_RANDOMNESS_BITS: usize; /// `FIELD_CAPACITY` is the maximum number of bits that can be stored in a /// field element. /// /// By default, `FIELD_CAPACITY` is set to `MODULUS_BIT_SIZE - 1`. /// - /// Given a randomness `r_i` with `RANDOMNESS_BIT_LENGTH` bits, we need - /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY` field elements to represent it + /// Given a randomness with `N_INPUT_RANDOMNESS_BITS` bits, we need + /// `N_INPUT_RANDOMNESS_BITS / FIELD_CAPACITY` field elements to pack it /// *compactly* in-circuit. const FIELD_CAPACITY: usize = CF2::::MODULUS_BIT_SIZE as usize - 1; @@ -246,66 +230,25 @@ pub trait CycleFoldConfig: Sized + Default { /// * In ProtoGalaxy, this is `|[..., r_i, ...]| + |p_i| * n_points + |P|`. /// /// As explained above, `|r|` (i.e., the length of a single randomness) is - /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY`. - /// When there are multiple randomnesses, the length of `|[..., r_i, ...]|` - /// is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY`, as - /// the bits of all randomnesses are concatenated before being packed into - /// field elements. + /// `N_INPUT_RANDOMNESS_BITS / FIELD_CAPACITY`. /// The length of a point `p_i` when treated as public inputs is 2, as we /// only need the `x` and `y` coordinates of the point. /// - /// Thus, `IO_LEN` is `RANDOMNESS_BIT_LENGTH * N_UNIQUE_RANDOMNESSES / FIELD_CAPACITY + 2 * (N_INPUT_POINTS + 1)`. + /// Thus, `IO_LEN` is: + /// `N_INPUT_RANDOMNESS_BITS / FIELD_CAPACITY + 2 * (N_INPUT_POINTS + 1)`. const IO_LEN: usize = { - (Self::RANDOMNESS_BIT_LENGTH * Self::N_UNIQUE_RANDOMNESSES).div_ceil(Self::FIELD_CAPACITY) + Self::N_INPUT_RANDOMNESS_BITS.div_ceil(Self::FIELD_CAPACITY) + 2 * (Self::N_INPUT_POINTS + 1) }; - /// `alloc_points` allocates the points that are going to be folded in the - /// [`CycleFoldCircuit`] via random linear combinations. - /// - /// The implementation must allocate the points as *witness* variables (i.e. - /// by calling [`AllocVar::new_witness`]) first, then mark them as public - /// inputs by calling [`CycleFoldConfig::mark_point_as_public`], and finally - /// return the allocated witness variables. - /// - /// While it is possible to allocate the points as public inputs directly, - /// we do not use this approach because this will create a longer vector of - /// public inputs, which is not ideal for the augmented step circuit on the - /// primary curve. - fn alloc_points( - &self, - cs: ConstraintSystemRef>, - ) -> Result::Var>, SynthesisError>; - - /// `alloc_randomnesses` allocates the randomnesses used as coefficients of - /// the random linear combinations in the `CycleFoldCircuit`. - /// - /// The implementation must allocate the randomnesses as *witness* variables - /// (i.e. by calling [`AllocVar::new_witness`]) first, then mark them as - /// public inputs by calling [`CycleFoldConfig::mark_point_as_public`], and - /// finally return the allocated witness variables. - /// - /// See [`CycleFoldConfig::alloc_points`] for the reason why they need to be - /// allocated as witness variables first and converted to public later. - /// - /// In addition, because the circuit computes `P = (P + p_i) * r_i` for each - /// `i` from `N_INPUT_POINTS - 1` down to `0`, the actual linear combination - /// is `P = r_0 * p_0 + (r_0 r_1) * p_1 + (r_0 r_1 r_2) * p_2 + ...`. Thus, - /// to compute `P = R_0 p_0 + R_1 p_1 + R_2 p_2 + ...`, the implementation - /// should return `r_0 = R_0, r_1 = R_1 / R_0, ..., r_i = R_i / R_{i - 1}`. - /// A special case is `R_i = R^i`, where the allocated randomnesses become - /// `r_0 = 1, r_1 = r_2 = ... = R`. - fn alloc_randomnesses( - &self, - cs: ConstraintSystemRef>, - ) -> Result>>>, SynthesisError>; - /// `mark_point_as_public` marks a point as public. /// /// The final vector of public inputs is shorter than the result of calling /// [`AllocVar::new_input`], because we only need the x and y coordinates of /// the point, but the `infinity` flag is not necessary. - fn mark_point_as_public(point: &::Var) -> Result<(), SynthesisError> { + fn mark_point_as_public( + point: &impl CurveVar>, + ) -> Result<(), SynthesisError> { for x in &point.to_constraint_field()?[..2] { // This line "converts" `x` from a witness to a public input. // Instead of directly modifying the constraint system, we explicitly @@ -316,23 +259,13 @@ pub trait CycleFoldConfig: Sized + Default { // computing them outside the circuit. // - `.enforce_equal()` prevents a malicious prover from claiming wrong // public inputs that are not the honest `x` computed in-circuit. - FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(x)?; + FpVar::new_input(x.cs(), || x.value())?.enforce_equal(x)?; } Ok(()) } - /// `mark_randomness_as_public` marks randomness as public. - /// - /// The final vector of public inputs is shorter than the result of calling - /// [`AllocVar::new_input`], because we pack the bits of randomness into - /// a compact field elements. - fn mark_randomness_as_public(r: &[Boolean>]) -> Result<(), SynthesisError> { - for bits in r.chunks(Self::FIELD_CAPACITY) { - let x = Boolean::le_bits_to_fp(bits)?; - FpVar::new_input(x.cs().clone(), || x.value())?.enforce_equal(&x)?; - } - Ok(()) - } + fn verify_point_rlc(&self, cs: ConstraintSystemRef>) + -> Result<(), SynthesisError>; } #[derive(Debug, Clone, Default)] @@ -340,76 +273,11 @@ pub struct CycleFoldCircuit { _cfg: PhantomData, } -impl CycleFoldCircuit { - pub fn fold_points( - &self, - cs: ConstraintSystemRef>, - cfg: Cfg, - ) -> Result<(), SynthesisError> { - let rs = cfg.alloc_randomnesses(cs.clone())?; - let points = cfg.alloc_points(cs.clone())?; - - #[cfg(test)] - { - assert_eq!(Cfg::N_INPUT_POINTS, points.len()); - assert_eq!(Cfg::N_INPUT_POINTS, rs.len()); - for r in &rs { - assert_eq!(Cfg::RANDOMNESS_BIT_LENGTH, r.len()); - } - } - - // A slightly optimized version of `scalar_mul_le`. - fn point_mul( - point: &C::Var, - r: &[Boolean>], - ) -> Result { - if r.is_constant() { - let r = CF1::::from( as PrimeField>::BigInt::from_bits_le(&r.value()?)); - if r.is_one() { - return Ok(point.clone()); - } - } - point.scalar_mul_le(r.iter()) - } - - // Given a vector of points (over the primary curve) that are obtained - // from the instances of the folding scheme, we fold them *natively* in - // the CycleFold circuit (over the secondary curve). - // * In Nova, we need to compute P = p_0 + R * p_1. - // - for the cmW we're computing: U_i1.cmW = U_i.cmW + R * u_i.cmW - // - for the cmE we're computing: U_i1.cmE = U_i.cmE + R * cmT + R^2 * u_i.cmE, where u_i.cmE - // is assumed to be 0, so, U_i1.cmE = U_i.cmE + R * cmT - // * In HyperNova, we need to compute P = p_0 + R * p_1 + R^2 * p_2 + ... + R^{n-1} * p_{n-1}. - // * In ProtoGalaxy, we need to compute P = R_0 * p_0 + R_1 * p_1 + R_2 * p_2 + ... + R_{n-1} * p_{n-1}. - // - // To handle HyperNova more efficiently (with less constraints), we do - // P = ((((p_{n-1} * R) + p_{n-2}) * R + p_{n-3}) * R + ...) * R + p_0. - // This can be done iteratively by computing P = (P + p_i) * R. - // - // We further generalize this to support ProtoGalaxy, which now becomes - // P = (((((p_{n-1} * r_{n-1}) + p_{n-2}) * r_{n-2} + p_{n-3}) * r_{n-3} + ...) * r_1 + p_0) * r_0 - // - // Here, r_0 = 1, r_1 = r_2 = ... = r_{n-1} = R for Nova and HyperNova, - // and r_i = R_i / R_{i - 1} for ProtoGalaxy. - let mut p_folded = point_mul::( - &points[Cfg::N_INPUT_POINTS - 1], - &rs[Cfg::N_INPUT_POINTS - 1], - )?; - for i in (0..Cfg::N_INPUT_POINTS - 1).rev() { - p_folded = point_mul::(&(p_folded + &points[i]), &rs[i])?; - } - - Cfg::mark_point_as_public(&p_folded)?; - - Ok(()) - } -} - impl ConstraintSynthesizer> for CycleFoldCircuit { fn generate_constraints( self, cs: ConstraintSystemRef>, ) -> Result<(), SynthesisError> { - self.fold_points(cs, Cfg::default()) + Cfg::default().verify_point_rlc(cs) } } diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 3a36f4993..36cdf38ad 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,15 +1,17 @@ use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; +use ark_ec::{CurveGroup, PrimeGroup}; use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; -use ark_std::{marker::PhantomData, rand::RngCore, sync::Arc}; +use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore, sync::Arc}; use sonobe_fs::{ FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemeFullGadget, - FoldingSchemePartialGadget, + FoldingSchemePartialGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ + algebra::field::emulated::EmulatedFieldVar, arithmetizations::{Arith, ArithConfig}, circuits::{ArithExtractor, AssignmentsExtractor, ConstraintSystemExt, FCircuit}, commitments::{CommitmentDef, CommitmentDefGadget}, @@ -29,30 +31,36 @@ use crate::{ pub mod circuits; pub trait FoldingSchemeCycleFoldGadget: - FoldingSchemePartialGadget + GroupBasedFoldingSchemePrimary { - type CFConfig: CycleFoldConfig< - C = <>::VC as CommitmentDef>::Commitment, - >; - - type CFScalarVar; + type CFConfig: CycleFoldConfig::Commitment>; const N_CYCLEFOLDS: usize; fn to_cyclefold_configs( - U: &>::RU, - u: &>::IU, - proof: &>::Proof, - rho: >::Challenge, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + proof: &Self::Proof, + rho: Self::Challenge, ) -> Vec; fn to_cyclefold_inputs( - U: Self::RU, - u: Self::IU, - UU: Self::RU, - proof: Self::Proof, - rho: Self::Challenge, - ) -> Result>, SynthesisError>; + Us: [>::RU; M], + us: [>::IU; N], + UU: >::RU, + proof: >::Proof, + rho: >::Challenge, + ) -> Result< + Vec< + Vec< + EmulatedFieldVar< + ::Scalar, + CF2<::Commitment>, + >, + >, + >, + SynthesisError, + >; } pub struct ProverKey, FS2: FoldingScheme<1, 1>>( @@ -93,76 +101,59 @@ impl, FS2: FoldingScheme<1, 1>> } } -pub struct CycleFoldBasedIVC { - _d: PhantomData<(C1, C2, FS1, FS2)>, +pub struct CycleFoldBasedIVC { + _d: PhantomData<(FS1, FS2)>, } -impl IVC for CycleFoldBasedIVC +impl IVC for CycleFoldBasedIVC where - C1: SonobeCurve, FS1: FoldingSchemeCycleFoldGadget< 1, 1, - VC: CommitmentDefGadget< - ConstraintField = C1::ScalarField, - ScalarVar = FpVar, - >, - CFScalarVar = ::ScalarVar, - Native: FoldingScheme< - 1, - 1, - Arith: Arith + From>, - TranscriptField = C1::ScalarField, - VC: CommitmentDef, + Arith: From::Commitment>>>, + Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, - VerifierKey = (), >, - FS2: FoldingSchemeFullGadget< + FS2: GroupBasedFoldingSchemeSecondary< 1, 1, - Native: FoldingScheme< - 1, - 1, - Arith: Arith + From>, - TranscriptField = C1::ScalarField, - VC: CommitmentDef, + Arith: From::Commitment>>>, + Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, - VerifierKey = (), - VC: CommitmentDefGadget, >, { - type Field = C1::ScalarField; + type Field = ::Scalar; - type Config = ( - >::Config, - >::Config, - Arc>, - ); + type Config = (FS1::Config, FS2::Config, Arc>); type PublicParam = ( - >::PublicParam, - >::PublicParam, + FS1::PublicParam, + FS2::PublicParam, Arc>, ); - type ProverKey = ProverKey; + type ProverKey = ProverKey; type VerifierKey = ( - >::DeciderKey, - >::DeciderKey, + FS1::DeciderKey, + FS2::DeciderKey, Arc>, Self::Field, ); - type Proof = Proof; + type Proof = Proof; fn preprocess( (cfg1, cfg2, griffin_config): Self::Config, mut rng: impl RngCore, ) -> Result { Ok(( - FS1::Native::preprocess(cfg1, &mut rng)?, - FS2::Native::preprocess(cfg2, &mut rng)?, + FS1::preprocess(cfg1, &mut rng)?, + FS2::preprocess(cfg2, &mut rng)?, griffin_config, )) } @@ -171,21 +162,21 @@ where (pp1, pp2, griffin_config): Self::PublicParam, step_circuit: &FC, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error> { - let mut arith2 = >::Arith::default(); + let mut arith2 = FS2::Arith::default(); loop { let cyclefold_circuit = CycleFoldCircuit::::default(); let cs = ArithExtractor::new(); cs.execute_synthesizer(cyclefold_circuit)?; - let new_arith2 = cs.arith::<>::Arith>()?; + let new_arith2 = cs.arith::()?; if new_arith2.config() == arith2.config() { break; } arith2 = new_arith2; } - let mut arith1 = >::Arith::default(); + let mut arith1 = FS1::Arith::default(); loop { let augmented_circuit = AugmentedCircuit:: { @@ -197,7 +188,7 @@ where let cs = ArithExtractor::new(); cs.execute_synthesizer(augmented_circuit)?; - let new_arith1 = cs.arith::<>::Arith>()?; + let new_arith1 = cs.arith::()?; if new_arith1.config() == arith1.config() { break; } @@ -207,8 +198,8 @@ where let arith1_config = arith1.config().clone(); let arith2_config = arith2.config().clone(); - let (pk1, _, dk1) = FS1::Native::generate_keys(pp1, arith1)?; - let (pk2, _, dk2) = FS2::Native::generate_keys(pp2, arith2)?; + let (pk1, _, dk1) = FS1::generate_keys(pp1, arith1)?; + let (pk2, _, dk2) = FS2::generate_keys(pp2, arith2)?; let pp_hash = Zero::zero(); // TODO @@ -261,17 +252,17 @@ where let challenge; (WW, UU, proof, challenge) = - FS1::Native::prove(pk1, &mut transcript, &[W], &[U], &[w], &[u], &mut rng)?; + FS1::prove(pk1, &mut transcript, &[W], &[U], &[w], &[u], &mut rng)?; - let cf_configs = FS1::to_cyclefold_configs(&U, &u, &proof, challenge); + let cf_configs = FS1::to_cyclefold_configs(&[U], &[u], &proof, challenge); for cfg in cf_configs { let cs = AssignmentsExtractor::new(); - cs.execute_fn(|cs| CycleFoldCircuit::default().fold_points(cs, cfg))?; + cs.execute_fn(|cs| cfg.verify_point_rlc(cs))?; let (cf_w, cf_u) = dk2.sample(cs.assignments()?, &mut rng)?; let cf_proof; - (cf_WW, cf_UU, cf_proof, _) = FS2::Native::prove( + (cf_WW, cf_UU, cf_proof, _) = FS2::prove( pk2, &mut transcript, &[cf_W], @@ -340,9 +331,9 @@ where return Err(Error::IVCVerificationFail); } - FS1::Native::decide_running(&dk1, &W, &U)?; - FS1::Native::decide_incoming(&dk1, &w, &u)?; - FS2::Native::decide_running(&dk2, &cf_W, &cf_U)?; + FS1::decide_running(&dk1, &W, &U)?; + FS1::decide_incoming(&dk1, &w, &u)?; + FS2::decide_running(&dk2, &cf_W, &cf_U)?; Ok(()) } diff --git a/crates/ivc/src/compilers/mod.rs b/crates/ivc/src/compilers/mod.rs index 41e86f249..764a51d10 100644 --- a/crates/ivc/src/compilers/mod.rs +++ b/crates/ivc/src/compilers/mod.rs @@ -1 +1,2 @@ pub mod cyclefold; + diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 183910db0..88bd3b592 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -25,7 +25,7 @@ pub trait IVC { type PublicParam; type ProverKey; type VerifierKey; - type Proof; + type Proof: for<'a> Dummy<&'a Self::ProverKey>; fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; @@ -68,19 +68,14 @@ impl, I: IVC> IVCStatefulProver { pk: I::ProverKey, step_circuit: FC, initial_state: FC::State, - ) -> Result - where - I::Proof: for<'a> Dummy<&'a I::ProverKey>, - { - let current_proof = I::Proof::dummy(&pk); - + ) -> Result { Ok(Self { - pk, step_circuit, i: 0, current_state: initial_state.clone(), initial_state, - current_proof, + current_proof: I::Proof::dummy(&pk), + pk, }) } @@ -105,3 +100,54 @@ impl, I: IVC> IVCStatefulProver { Ok(external_outputs) } } + +#[cfg(test)] +mod tests { + use ark_bn254::{Fr, G1Projective as C1}; + use ark_crypto_primitives::sponge::{CryptographicSponge, poseidon::PoseidonSponge}; + use ark_ff::UniformRand; + use ark_grumpkin::Projective as C2; + use ark_std::{error::Error, rand::Rng, sync::Arc, test_rng}; + use sonobe_fs::{ + FoldingScheme, FoldingSchemeFullGadget, FoldingSchemePartialGadget, PlainInstance as IU, + PlainInstanceVar as IUVar, PlainWitness as IW, PlainWitnessVar as IWVar, + }; + use sonobe_primitives::{ + arithmetizations::Arith, + circuits::utils::CircuitForTest, + commitments::pedersen::{Pedersen, PedersenEmulatedGadget, PedersenGadget}, + traits::Dummy, + transcripts::{Transcript, griffin::GriffinParams, poseidon::poseidon_canonical_config}, + }; + + use super::*; + + pub fn test_ivc>( + config: I::Config, + step_circuit: F, + external_inputs_vec: Vec, + mut rng: impl Rng, + ) -> Result<(), Box> { + let pp = I::preprocess(config, &mut rng)?; + + let (pk, vk) = I::generate_keys(pp, &step_circuit)?; + + let initial_state = step_circuit.dummy_state(); + + let mut prover = IVCStatefulProver::<_, I>::new(pk, step_circuit, initial_state)?; + + for external_inputs in external_inputs_vec { + prover.prove_step(external_inputs, &mut rng)?; + + I::verify::( + &vk, + prover.i, + &prover.initial_state, + &prover.current_state, + &prover.current_proof, + )?; + } + + Ok(()) + } +} From a57c9b6a015b8d3d7ef90de09a03dc14e7efd4dc Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 17 Nov 2025 07:48:25 +0800 Subject: [PATCH 46/99] Correctly update CF running instance & witness --- crates/fs/src/lib.rs | 1 - .../ivc/src/compilers/cyclefold/circuits.rs | 73 +++++++++---------- crates/ivc/src/compilers/cyclefold/mod.rs | 21 ++---- 3 files changed, 42 insertions(+), 53 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 68248f408..e1bc7e0fb 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -11,7 +11,6 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, fmt::Debug, rand::RngCore}; use sonobe_primitives::{ - algebra::group::emulated::EmulatedAffineVar, arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 3cc9d088a..291f3c532 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -3,18 +3,15 @@ use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, boolean::Boolean, - convert::ToConstraintFieldGadget, eq::EqGadget, fields::{FieldVar, fp::FpVar}, groups::CurveVar, }; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, -}; -use ark_std::{marker::PhantomData, rand::RngCore, sync::Arc}; +use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; +use ark_std::{marker::PhantomData, sync::Arc}; use sonobe_fs::{ - FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemeFullGadget, - FoldingSchemePartialGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, + FoldingInstanceVar, FoldingSchemeFullGadget, FoldingSchemePartialGadget, + GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ algebra::Val, @@ -29,7 +26,7 @@ use sonobe_primitives::{ }, }; -use crate::compilers::cyclefold::FoldingSchemeCycleFoldGadget; +use crate::compilers::cyclefold::FoldingSchemeCycleFoldExt; pub struct AugmentedCircuit< 'a, @@ -45,22 +42,22 @@ pub struct AugmentedCircuit< impl<'a, FS1, FS2, FC> AugmentedCircuit<'a, FS1, FS2, FC> where - FS1: FoldingSchemeCycleFoldGadget< - 1, - 1, - Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + FS1: FoldingSchemeCycleFoldExt< + 1, + 1, + Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FS2: GroupBasedFoldingSchemeSecondary< - 1, - 1, - Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + 1, + 1, + Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FC: FCircuit::Scalar>, { pub fn compute_next_state( @@ -125,11 +122,9 @@ where } let actual_cf_UU = is_basecase.select(&cf_U_dummy, &cf_UU)?; - let (next_state, external_outputs) = self.step_circuit.generate_step_constraints( - i, - current_state, - external_inputs, - )?; + let (next_state, external_outputs) = + self.step_circuit + .generate_step_constraints(i, current_state, external_inputs)?; let uu_x = sponge .clone() @@ -162,20 +157,20 @@ where impl<'a, FS1, FS2, FC> ConstraintSynthesizer for AugmentedCircuit<'a, FS1, FS2, FC> where - FS1: FoldingSchemeCycleFoldGadget< - 1, - 1, - Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + FS1: FoldingSchemeCycleFoldExt< + 1, + 1, + Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FS2: GroupBasedFoldingSchemeSecondary< - 1, - 1, - Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef>, - >, + 1, + 1, + Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef>, + >, FC: FCircuit::Scalar>, { fn generate_constraints( @@ -265,7 +260,7 @@ pub trait CycleFoldConfig: Sized + Default { } fn verify_point_rlc(&self, cs: ConstraintSystemRef>) - -> Result<(), SynthesisError>; + -> Result<(), SynthesisError>; } #[derive(Debug, Clone, Default)] diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 36cdf38ad..5a91923d4 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,14 +1,9 @@ -use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; -use ark_ec::{CurveGroup, PrimeGroup}; use ark_ff::{PrimeField, Zero}; -use ark_r1cs_std::{eq::EqGadget, fields::fp::FpVar}; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, -}; +use ark_relations::gr1cs::{ConstraintSystem, SynthesisError, SynthesisMode}; use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore, sync::Arc}; use sonobe_fs::{ - FoldingInstance, FoldingInstanceVar, FoldingScheme, FoldingSchemeFullGadget, - FoldingSchemePartialGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, + FoldingInstance, FoldingScheme, FoldingSchemeFullGadget, FoldingSchemePartialGadget, + GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, @@ -30,7 +25,7 @@ use crate::{ pub mod circuits; -pub trait FoldingSchemeCycleFoldGadget: +pub trait FoldingSchemeCycleFoldExt: GroupBasedFoldingSchemePrimary { type CFConfig: CycleFoldConfig::Commitment>; @@ -107,7 +102,7 @@ pub struct CycleFoldBasedIVC { impl IVC for CycleFoldBasedIVC where - FS1: FoldingSchemeCycleFoldGadget< + FS1: FoldingSchemeCycleFoldExt< 1, 1, Arith: From::Commitment>>>, @@ -255,7 +250,7 @@ where FS1::prove(pk1, &mut transcript, &[W], &[U], &[w], &[u], &mut rng)?; let cf_configs = FS1::to_cyclefold_configs(&[U], &[u], &proof, challenge); - for cfg in cf_configs { + for (i, cfg) in cf_configs.iter().enumerate() { let cs = AssignmentsExtractor::new(); cs.execute_fn(|cs| cfg.verify_point_rlc(cs))?; @@ -265,8 +260,8 @@ where (cf_WW, cf_UU, cf_proof, _) = FS2::prove( pk2, &mut transcript, - &[cf_W], - &[cf_U], + &[if i == 0 { cf_W } else { &cf_WW }], + &[if i == 0 { cf_U } else { &cf_UU }], &[&cf_w], &[&cf_u], &mut rng, From 2cb766f29c4740615a18473dd1d0a2be4399ea7e Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 03:04:37 +0800 Subject: [PATCH 47/99] Prefer ark_std over std --- crates/fs/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index e1bc7e0fb..b561fcead 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,4 +1,3 @@ -use std::ops::{Deref, DerefMut}; use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ @@ -9,7 +8,12 @@ use ark_r1cs_std::{ select::CondSelectGadget, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, fmt::Debug, rand::RngCore}; +use ark_std::{ + borrow::Borrow, + fmt::Debug, + ops::{Deref, DerefMut}, + rand::RngCore, +}; use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, @@ -475,7 +479,7 @@ mod tests { use sonobe_primitives::{ circuits::{ArithExtractor, AssignmentsOwned}, transcripts::{ - griffin::{GriffinParams, sponge::GriffinSponge}, + griffin::{sponge::GriffinSponge, GriffinParams}, poseidon::poseidon_canonical_config, }, }; From 86a9415f424b767e945e9818578d7a14fb210e9a Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 08:30:31 +0800 Subject: [PATCH 48/99] Decider key now contains pk and vk --- crates/fs/src/lib.rs | 73 +++++++++--------- .../ivc/src/compilers/cyclefold/circuits.rs | 5 +- crates/ivc/src/compilers/cyclefold/mod.rs | 74 ++++++++----------- 3 files changed, 72 insertions(+), 80 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index b561fcead..25be38ef8 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,4 +1,3 @@ - use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ GR1CSVar, @@ -260,7 +259,15 @@ impl FoldingInstance for PlainInstance { } } -pub trait DeciderKey {} +pub trait DeciderKey { + type ProverKey; + type VerifierKey; + type ArithConfig: ArithConfig; + + fn to_pk(&self) -> &Self::ProverKey; + fn to_vk(&self) -> &Self::VerifierKey; + fn to_arith_config(&self) -> &Self::ArithConfig; +} pub trait FoldingScheme { type VC: CommitmentDef; @@ -269,12 +276,11 @@ pub trait FoldingScheme { type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; type TranscriptField: SonobeField; - type Arith: Arith; + type Arith: Arith::ArithConfig>; type Config; type PublicParam; - type ProverKey; - type VerifierKey; - type DeciderKey: Clone + type DeciderKey: DeciderKey + + Clone + Relation + Relation + WitnessInstanceSampler @@ -300,10 +306,7 @@ pub trait FoldingScheme { /// The key generation method is a deterministic algorithm that takes as /// input the public parameters `pp` and the constraint system `arith`, and /// outputs a prover key and a verifier key. - fn generate_keys( - pp: Self::PublicParam, - arith: Self::Arith, - ) -> Result<(Self::ProverKey, Self::VerifierKey, Self::DeciderKey), Error>; + fn generate_keys(pp: Self::PublicParam, arith: Self::Arith) -> Result; /// The proof generation method is a deterministic algorithm that takes as /// input the prover key `pk`, the transcript `transcript` between the @@ -316,7 +319,7 @@ pub trait FoldingScheme { /// circuits in our CycleFold-based folding-to-IVC compiler. #[allow(non_snake_case)] fn prove( - pk: &Self::ProverKey, + pk: &::ProverKey, transcript: &mut impl Transcript, Ws: &[impl Borrow; M], Us: &[impl Borrow; M], @@ -327,7 +330,7 @@ pub trait FoldingScheme { #[allow(non_snake_case)] fn verify( - vk: &Self::VerifierKey, + vk: &::VerifierKey, transcript: &mut impl Transcript, Us: &[impl Borrow; M], us: &[impl Borrow; N], @@ -397,34 +400,34 @@ impl FoldingInstanceVar for PlainInstanceVar { pub trait GroupBasedFoldingSchemePrimary: FoldingScheme< - M, - N, - VC: GroupBasedCommitment, - TranscriptField = <>::VC as CommitmentDef>::Scalar, -> -{ - type Gadget: FoldingSchemePartialGadget< M, N, - Native = Self, - VC = ::Gadget2, - >; + VC: GroupBasedCommitment, + TranscriptField = <>::VC as CommitmentDef>::Scalar, + > +{ + type Gadget: FoldingSchemePartialGadget< + M, + N, + Native = Self, + VC = ::Gadget2, + >; } pub trait GroupBasedFoldingSchemeSecondary: FoldingScheme< - M, - N, - VC: GroupBasedCommitment, - TranscriptField = CF2<<>::VC as CommitmentDef>::Commitment>, -> -{ - type Gadget: FoldingSchemeFullGadget< M, N, - Native = Self, - VC = ::Gadget1, - >; + VC: GroupBasedCommitment, + TranscriptField = CF2<<>::VC as CommitmentDef>::Commitment>, + > +{ + type Gadget: FoldingSchemeFullGadget< + M, + N, + Native = Self, + VC = ::Gadget1, + >; } pub trait FoldingSchemePartialGadget { @@ -479,7 +482,7 @@ mod tests { use sonobe_primitives::{ circuits::{ArithExtractor, AssignmentsOwned}, transcripts::{ - griffin::{sponge::GriffinSponge, GriffinParams}, + griffin::{GriffinParams, sponge::GriffinSponge}, poseidon::poseidon_canonical_config, }, }; @@ -501,7 +504,9 @@ mod tests { let cs = ArithExtractor::new(); cs.execute_synthesizer(circuit)?; let arith = cs.arith()?; - let (pk, vk, dk) = FS::generate_keys(pp, arith)?; + let dk = FS::generate_keys(pp, arith)?; + let pk = dk.to_pk(); + let vk = dk.to_vk(); let mut Ws = vec![]; let mut Us = vec![]; diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 291f3c532..ab6fda74c 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -3,6 +3,7 @@ use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, boolean::Boolean, + convert::ToConstraintFieldGadget, eq::EqGadget, fields::{FieldVar, fp::FpVar}, groups::CurveVar, @@ -241,9 +242,7 @@ pub trait CycleFoldConfig: Sized + Default { /// The final vector of public inputs is shorter than the result of calling /// [`AllocVar::new_input`], because we only need the x and y coordinates of /// the point, but the `infinity` flag is not necessary. - fn mark_point_as_public( - point: &impl CurveVar>, - ) -> Result<(), SynthesisError> { + fn mark_point_as_public(point: &::Var) -> Result<(), SynthesisError> { for x in &point.to_constraint_field()?[..2] { // This line "converts" `x` from a witness to a public input. // Instead of directly modifying the constraint system, we explicitly diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 5a91923d4..e8d51a55d 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -2,8 +2,8 @@ use ark_ff::{PrimeField, Zero}; use ark_relations::gr1cs::{ConstraintSystem, SynthesisError, SynthesisMode}; use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore, sync::Arc}; use sonobe_fs::{ - FoldingInstance, FoldingScheme, FoldingSchemeFullGadget, FoldingSchemePartialGadget, - GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, + DeciderKey, FoldingInstance, FoldingScheme, FoldingSchemeFullGadget, + FoldingSchemePartialGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, @@ -58,15 +58,11 @@ pub trait FoldingSchemeCycleFoldExt: >; } -pub struct ProverKey, FS2: FoldingScheme<1, 1>>( - FS1::ProverKey, +pub struct Key, FS2: FoldingScheme<1, 1>>( FS1::DeciderKey, - FS2::ProverKey, FS2::DeciderKey, Arc>, F, - ::Config, - ::Config, ); pub struct Proof, FS2: FoldingScheme<1, 1>>( @@ -78,12 +74,12 @@ pub struct Proof, FS2: FoldingScheme<1, 1>>( FS2::RU, ); -impl, FS2: FoldingScheme<1, 1>> - Dummy<&ProverKey> for Proof +impl, FS2: FoldingScheme<1, 1>> Dummy<&Key> + for Proof { - fn dummy(pk: &ProverKey) -> Self { - let cfg1 = &pk.6; - let cfg2 = &pk.7; + fn dummy(pk: &Key) -> Self { + let cfg1 = pk.0.to_arith_config(); + let cfg2 = pk.1.to_arith_config(); let W = FS1::RW::dummy(cfg1); let U = FS1::RU::dummy(cfg1); @@ -131,14 +127,9 @@ where Arc>, ); - type ProverKey = ProverKey; + type ProverKey = Key; - type VerifierKey = ( - FS1::DeciderKey, - FS2::DeciderKey, - Arc>, - Self::Field, - ); + type VerifierKey = Key; type Proof = Proof; @@ -190,31 +181,19 @@ where arith1 = new_arith1; } - let arith1_config = arith1.config().clone(); - let arith2_config = arith2.config().clone(); - - let (pk1, _, dk1) = FS1::generate_keys(pp1, arith1)?; - let (pk2, _, dk2) = FS2::generate_keys(pp2, arith2)?; + let dk1 = FS1::generate_keys(pp1, arith1)?; + let dk2 = FS2::generate_keys(pp2, arith2)?; let pp_hash = Zero::zero(); // TODO Ok(( - ProverKey( - pk1, - dk1.clone(), - pk2, - dk2.clone(), - griffin_config.clone(), - pp_hash, - arith1_config, - arith2_config, - ), - (dk1, dk2, griffin_config, pp_hash), + Key(dk1.clone(), dk2.clone(), griffin_config.clone(), pp_hash), + Key(dk1, dk2, griffin_config, pp_hash), )) } fn prove>( - ProverKey(pk1, dk1, pk2, dk2, griffin_config, pp_hash, arith1_config, arith2_config): &Self::ProverKey, + Key(dk1, dk2, griffin_config, pp_hash): &Self::ProverKey, step_circuit: &FC, i: usize, initial_state: &FC::State, @@ -226,6 +205,8 @@ where let hash = GriffinSponge::new_with_pp_hash(&griffin_config, *pp_hash); let mut transcript = hash.separate_domain("transcript".as_ref()); + let arith1_config = dk1.to_arith_config(); + let arith2_config = dk2.to_arith_config(); let augmented_circuit = AugmentedCircuit:: { griffin_config: griffin_config.clone(), arith1_config, @@ -246,8 +227,15 @@ where cf_proofs.clear(); let challenge; - (WW, UU, proof, challenge) = - FS1::prove(pk1, &mut transcript, &[W], &[U], &[w], &[u], &mut rng)?; + (WW, UU, proof, challenge) = FS1::prove( + dk1.to_pk(), + &mut transcript, + &[W], + &[U], + &[w], + &[u], + &mut rng, + )?; let cf_configs = FS1::to_cyclefold_configs(&[U], &[u], &proof, challenge); for (i, cfg) in cf_configs.iter().enumerate() { @@ -258,7 +246,7 @@ where let cf_proof; (cf_WW, cf_UU, cf_proof, _) = FS2::prove( - pk2, + dk2.to_pk(), &mut transcript, &[if i == 0 { cf_W } else { &cf_WW }], &[if i == 0 { cf_U } else { &cf_UU }], @@ -299,7 +287,7 @@ where } fn verify>( - (dk1, dk2, griffin_config, pp_hash): &Self::VerifierKey, + Key(dk1, dk2, griffin_config, pp_hash): &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, @@ -326,9 +314,9 @@ where return Err(Error::IVCVerificationFail); } - FS1::decide_running(&dk1, &W, &U)?; - FS1::decide_incoming(&dk1, &w, &u)?; - FS2::decide_running(&dk2, &cf_W, &cf_U)?; + FS1::decide_running(dk1, W, U)?; + FS1::decide_incoming(dk1, w, u)?; + FS2::decide_running(dk2, cf_W, cf_U)?; Ok(()) } From 867220a6c2fca93f33b8c9471880d57f3eecf1fc Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 04:50:00 +0800 Subject: [PATCH 49/99] Use generic hash function in CF --- crates/fs/src/lib.rs | 6 ++ .../ivc/src/compilers/cyclefold/circuits.rs | 47 ++------- crates/ivc/src/compilers/cyclefold/mod.rs | 98 ++++++++----------- 3 files changed, 55 insertions(+), 96 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 25be38ef8..0d812dc39 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -38,6 +38,12 @@ pub enum Error { Unsupported(String), #[error("Failed to create domain")] DomainCreationFailure, + #[error("Indivisible by vanishing polynomial")] + IndivisibleByVanishingPoly, + #[error("Unsatisfied relation: {0}")] + UnsatisfiedRelation(String), + #[error("Invalid public parameters: {0}")] + InvalidPublicParameters(String), } pub trait FoldingWitness: Debug { diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index ab6fda74c..c93c4ff0e 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -9,7 +9,7 @@ use ark_r1cs_std::{ groups::CurveVar, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::{marker::PhantomData, sync::Arc}; +use ark_std::marker::PhantomData; use sonobe_fs::{ FoldingInstanceVar, FoldingSchemeFullGadget, FoldingSchemePartialGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, @@ -34,14 +34,15 @@ pub struct AugmentedCircuit< FS1: GroupBasedFoldingSchemePrimary<1, 1>, FS2: GroupBasedFoldingSchemeSecondary<1, 1>, FC: FCircuit, + T: Transcript, > { - pub griffin_config: Arc>, + pub hash_config: T::Config, pub arith1_config: &'a ::Config, pub arith2_config: &'a ::Config, pub step_circuit: &'a FC, } -impl<'a, FS1, FS2, FC> AugmentedCircuit<'a, FS1, FS2, FC> +impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> where FS1: FoldingSchemeCycleFoldExt< 1, @@ -60,6 +61,7 @@ where >, >, FC: FCircuit::Scalar>, + T: Transcript, { pub fn compute_next_state( &self, @@ -76,8 +78,8 @@ where cf_us: Vec, cf_proofs: Vec, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { - let hash = GriffinSpongeVar::new_with_pp_hash( - &self.griffin_config, + let hash = T::Gadget::new_with_pp_hash( + &self.hash_config, &FpVar::new_witness(cs.clone(), || Ok(pp_hash))?, )?; let sponge = hash.separate_domain("sponge".as_ref())?; @@ -156,7 +158,7 @@ where } } -impl<'a, FS1, FS2, FC> ConstraintSynthesizer for AugmentedCircuit<'a, FS1, FS2, FC> +impl<'a, FS1, FS2, FC, T> ConstraintSynthesizer for AugmentedCircuit<'a, FS1, FS2, FC, T> where FS1: FoldingSchemeCycleFoldExt< 1, @@ -173,6 +175,7 @@ where VC: CommitmentDef>, >, FC: FCircuit::Scalar>, + T: Transcript, { fn generate_constraints( self, @@ -205,38 +208,6 @@ where pub trait CycleFoldConfig: Sized + Default { type C: SonobeCurve; - /// `N_INPUT_POINTS` specifies the number of input points that are folded in - /// [`CycleFoldCircuit`] via random linear combinations. - const N_INPUT_POINTS: usize; - const N_INPUT_RANDOMNESS_BITS: usize; - /// `FIELD_CAPACITY` is the maximum number of bits that can be stored in a - /// field element. - /// - /// By default, `FIELD_CAPACITY` is set to `MODULUS_BIT_SIZE - 1`. - /// - /// Given a randomness with `N_INPUT_RANDOMNESS_BITS` bits, we need - /// `N_INPUT_RANDOMNESS_BITS / FIELD_CAPACITY` field elements to pack it - /// *compactly* in-circuit. - const FIELD_CAPACITY: usize = CF2::::MODULUS_BIT_SIZE as usize - 1; - - /// Public inputs length for the [`CycleFoldCircuit`], which depends on the - /// above constants defined by the concrete folding scheme. For example: - /// * In Nova, this is `|r| + |p_1| + |p_2| + |P|` - /// * In HyperNova, this is `|r| + |p_i| * n_points + |P|`. - /// * In ProtoGalaxy, this is `|[..., r_i, ...]| + |p_i| * n_points + |P|`. - /// - /// As explained above, `|r|` (i.e., the length of a single randomness) is - /// `N_INPUT_RANDOMNESS_BITS / FIELD_CAPACITY`. - /// The length of a point `p_i` when treated as public inputs is 2, as we - /// only need the `x` and `y` coordinates of the point. - /// - /// Thus, `IO_LEN` is: - /// `N_INPUT_RANDOMNESS_BITS / FIELD_CAPACITY + 2 * (N_INPUT_POINTS + 1)`. - const IO_LEN: usize = { - Self::N_INPUT_RANDOMNESS_BITS.div_ceil(Self::FIELD_CAPACITY) - + 2 * (Self::N_INPUT_POINTS + 1) - }; - /// `mark_point_as_public` marks a point as public. /// /// The final vector of public inputs is shorter than the result of calling diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index e8d51a55d..aebe497d0 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -58,11 +58,10 @@ pub trait FoldingSchemeCycleFoldExt: >; } -pub struct Key, FS2: FoldingScheme<1, 1>>( +pub struct Key, FS2: FoldingScheme<1, 1>, T>( FS1::DeciderKey, FS2::DeciderKey, - Arc>, - F, + T, ); pub struct Proof, FS2: FoldingScheme<1, 1>>( @@ -74,29 +73,28 @@ pub struct Proof, FS2: FoldingScheme<1, 1>>( FS2::RU, ); -impl, FS2: FoldingScheme<1, 1>> Dummy<&Key> +impl, FS2: FoldingScheme<1, 1>, T> Dummy<&Key> for Proof { - fn dummy(pk: &Key) -> Self { + fn dummy(pk: &Key) -> Self { let cfg1 = pk.0.to_arith_config(); let cfg2 = pk.1.to_arith_config(); - - let W = FS1::RW::dummy(cfg1); - let U = FS1::RU::dummy(cfg1); - let w = FS1::IW::dummy(cfg1); - let u = FS1::IU::dummy(cfg1); - let cf_W = FS2::RW::dummy(cfg2); - let cf_U = FS2::RU::dummy(cfg2); - - Self(W, U, w, u, cf_W, cf_U) + Self( + FS1::RW::dummy(cfg1), + FS1::RU::dummy(cfg1), + FS1::IW::dummy(cfg1), + FS1::IU::dummy(cfg1), + FS2::RW::dummy(cfg2), + FS2::RU::dummy(cfg2), + ) } } -pub struct CycleFoldBasedIVC { - _d: PhantomData<(FS1, FS2)>, +pub struct CycleFoldBasedIVC { + _d: PhantomData<(FS1, FS2, T)>, } -impl IVC for CycleFoldBasedIVC +impl IVC for CycleFoldBasedIVC where FS1: FoldingSchemeCycleFoldExt< 1, @@ -116,57 +114,46 @@ where Commitment: SonobeCurve::Scalar>, >, >, + T: Transcript::Commitment>>, { type Field = ::Scalar; - type Config = (FS1::Config, FS2::Config, Arc>); + type Config = (FS1::Config, FS2::Config, T::Config); - type PublicParam = ( - FS1::PublicParam, - FS2::PublicParam, - Arc>, - ); + type PublicParam = (FS1::PublicParam, FS2::PublicParam, T::Config); - type ProverKey = Key; + type ProverKey = Key; - type VerifierKey = Key; + type VerifierKey = Key; type Proof = Proof; fn preprocess( - (cfg1, cfg2, griffin_config): Self::Config, + (cfg1, cfg2, hash_config): Self::Config, mut rng: impl RngCore, ) -> Result { Ok(( FS1::preprocess(cfg1, &mut rng)?, FS2::preprocess(cfg2, &mut rng)?, - griffin_config, + hash_config, )) } fn generate_keys>( - (pp1, pp2, griffin_config): Self::PublicParam, + (pp1, pp2, hash_config): Self::PublicParam, step_circuit: &FC, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error> { - let mut arith2 = FS2::Arith::default(); + let cyclefold_circuit = CycleFoldCircuit::::default(); - loop { - let cyclefold_circuit = CycleFoldCircuit::::default(); - - let cs = ArithExtractor::new(); - cs.execute_synthesizer(cyclefold_circuit)?; - let new_arith2 = cs.arith::()?; - if new_arith2.config() == arith2.config() { - break; - } - arith2 = new_arith2; - } + let cs = ArithExtractor::new(); + cs.execute_synthesizer(cyclefold_circuit)?; + let arith2 = cs.arith::()?; let mut arith1 = FS1::Arith::default(); loop { - let augmented_circuit = AugmentedCircuit:: { - griffin_config: griffin_config.clone(), + let augmented_circuit = AugmentedCircuit:: { + hash_config: hash_config.clone(), arith1_config: arith1.config(), arith2_config: arith2.config(), step_circuit, @@ -187,13 +174,13 @@ where let pp_hash = Zero::zero(); // TODO Ok(( - Key(dk1.clone(), dk2.clone(), griffin_config.clone(), pp_hash), - Key(dk1, dk2, griffin_config, pp_hash), + Key(dk1.clone(), dk2.clone(), (hash_config.clone(), pp_hash)), + Key(dk1, dk2, (hash_config, pp_hash)), )) } fn prove>( - Key(dk1, dk2, griffin_config, pp_hash): &Self::ProverKey, + Key(dk1, dk2, (hash_config, pp_hash)): &Self::ProverKey, step_circuit: &FC, i: usize, initial_state: &FC::State, @@ -202,13 +189,13 @@ where Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, mut rng: impl RngCore, ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error> { - let hash = GriffinSponge::new_with_pp_hash(&griffin_config, *pp_hash); + let hash = T::new_with_pp_hash(&hash_config, *pp_hash); let mut transcript = hash.separate_domain("transcript".as_ref()); let arith1_config = dk1.to_arith_config(); let arith2_config = dk2.to_arith_config(); - let augmented_circuit = AugmentedCircuit:: { - griffin_config: griffin_config.clone(), + let augmented_circuit = AugmentedCircuit:: { + hash_config: hash_config.clone(), arith1_config, arith2_config, step_circuit, @@ -223,9 +210,6 @@ where let mut cf_WW = Dummy::dummy(arith2_config); if i != 0 { - cf_us.clear(); - cf_proofs.clear(); - let challenge; (WW, UU, proof, challenge) = FS1::prove( dk1.to_pk(), @@ -244,8 +228,7 @@ where let (cf_w, cf_u) = dk2.sample(cs.assignments()?, &mut rng)?; - let cf_proof; - (cf_WW, cf_UU, cf_proof, _) = FS2::prove( + (cf_WW, cf_UU, cf_proofs[i], _) = FS2::prove( dk2.to_pk(), &mut transcript, &[if i == 0 { cf_W } else { &cf_WW }], @@ -254,8 +237,7 @@ where &[&cf_u], &mut rng, )?; - cf_us.push(cf_u); - cf_proofs.push(cf_proof); + cf_us[i] = cf_u; } } @@ -287,7 +269,7 @@ where } fn verify>( - Key(dk1, dk2, griffin_config, pp_hash): &Self::VerifierKey, + Key(dk1, dk2, (hash_config, pp_hash)): &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, @@ -299,8 +281,8 @@ where .ok_or(Error::IVCVerificationFail); } - let griffin = GriffinSponge::new_with_pp_hash(griffin_config, *pp_hash); - let mut sponge = griffin.separate_domain("sponge".as_ref()); + let hash = T::new_with_pp_hash(hash_config, *pp_hash); + let mut sponge = hash.separate_domain("sponge".as_ref()); let u_x = sponge .add(&i) From ea6cb018ddd90aa14e0c56797c8f41ff615e4ee7 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 04:50:26 +0800 Subject: [PATCH 50/99] Initial redesign of decider trait --- crates/ivc/src/lib.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 88bd3b592..cd1e51a87 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -101,6 +101,31 @@ impl, I: IVC> IVCStatefulProver { } } +pub trait Decider { + type IVC: IVC; + + type ProverKey; + type VerifierKey; + type Instance; + type Witness; + type Proof; + + fn preprocess_and_generate_keys( + ivc_pk: &::ProverKey, + rng: impl RngCore, + ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; + + fn prove( + pk: &Self::ProverKey, + w: &Self::Witness, + x: &Self::Instance, + rng: impl RngCore, + ) -> Result; + + fn verify(vk: &Self::VerifierKey, x: &Self::Instance, proof: &Self::Proof) + -> Result<(), Error>; +} + #[cfg(test)] mod tests { use ark_bn254::{Fr, G1Projective as C1}; From 09b8075718b5d1939d63fe54b26e456cee461797 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 20:14:48 +0800 Subject: [PATCH 51/99] Introduce tagged vector for plain instance & witness --- crates/fs/src/lib.rs | 136 +++++++++---------------------------------- 1 file changed, 28 insertions(+), 108 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 0d812dc39..2386e6456 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -66,9 +66,9 @@ pub trait FoldingInstance: Clone + Debug + PartialEq + Absorb } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct PlainWitness(pub Vec); +pub struct TaggedVec(pub Vec); -impl Deref for PlainWitness { +impl Deref for TaggedVec { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -76,42 +76,48 @@ impl Deref for PlainWitness { } } -impl DerefMut for PlainWitness { +impl DerefMut for TaggedVec { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl From> for PlainWitness { +impl From> for TaggedVec { fn from(v: Vec) -> Self { Self(v) } } -impl Absorbable for PlainWitness { +impl Absorbable for TaggedVec { fn absorb_into(&self, dest: &mut Vec) { self.0.absorb_into(dest) } } -impl> AbsorbableVar for PlainWitness { +impl, const TAG: char> AbsorbableVar + for TaggedVec +{ fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.0.absorb_into(dest) } } -impl, Y, F: Field> AllocVar, F> for PlainWitness { - fn new_variable>>( +impl, Y, const TAG: char> AllocVar, F> + for TaggedVec +{ + fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { let v = f()?; - Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(|v| Self(v)) + Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(Self) } } -impl> CondSelectGadget for PlainWitness { +impl, const TAG: char> CondSelectGadget + for TaggedVec +{ fn conditionally_select( cond: &Boolean, true_value: &Self, @@ -120,29 +126,29 @@ impl> CondSelectGadget for PlainWitness if true_value.len() != false_value.len() { return Err(SynthesisError::Unsatisfiable); } - Ok(Self( - true_value - .0 - .iter() - .zip(false_value.0.iter()) - .map(|(t, f)| cond.select(t, f)) - .collect::>()?, - )) + true_value + .iter() + .zip(false_value.iter()) + .map(|(t, f)| cond.select(t, f)) + .collect::>() + .map(Self) } } -impl> GR1CSVar for PlainWitness { - type Value = PlainWitness; +impl, const TAG: char> GR1CSVar for TaggedVec { + type Value = TaggedVec; fn cs(&self) -> ConstraintSystemRef { self.0.cs() } fn value(&self) -> Result { - self.0.value().map(PlainWitness) + self.0.value().map(TaggedVec) } } +pub type PlainWitness = TaggedVec; + impl Dummy<&A> for PlainWitness { fn dummy(cfg: &A) -> Self { vec![V::default(); cfg.n_witnesses()].into() @@ -157,83 +163,7 @@ impl FoldingWitness for PlainWitness { } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PlainInstance(pub Vec); - -impl Deref for PlainInstance { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for PlainInstance { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From> for PlainInstance { - fn from(v: Vec) -> Self { - Self(v) - } -} - -impl Absorbable for PlainInstance { - fn absorb_into(&self, dest: &mut Vec) { - self.0.absorb_into(dest) - } -} - -impl> AbsorbableVar for PlainInstance { - fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { - self.0.absorb_into(dest) - } -} - -impl, Y, F: Field> AllocVar, F> for PlainInstance { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let v = f()?; - Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(|v| Self(v)) - } -} - -impl> CondSelectGadget for PlainInstance { - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - if true_value.len() != false_value.len() { - return Err(SynthesisError::Unsatisfiable); - } - Ok(Self( - true_value - .0 - .iter() - .zip(false_value.0.iter()) - .map(|(t, f)| cond.select(t, f)) - .collect::>()?, - )) - } -} - -impl> GR1CSVar for PlainInstance { - type Value = PlainInstance; - - fn cs(&self) -> ConstraintSystemRef { - self.0.cs() - } - - fn value(&self) -> Result { - self.0.value().map(PlainInstance) - } -} +pub type PlainInstance = TaggedVec; impl Dummy<&A> for PlainInstance { fn dummy(cfg: &A) -> Self { @@ -241,14 +171,6 @@ impl Dummy<&A> for PlainInstance { } } -impl FoldingWitness for PlainInstance { - const N_OPENINGS: usize = 0; - - fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { - vec![] - } -} - impl FoldingInstance for PlainInstance { const N_COMMITMENTS: usize = 0; @@ -440,9 +362,7 @@ pub trait FoldingSchemePartialGadget { type Native: FoldingScheme; type VC: CommitmentDefGadget>::VC>; - type RW: FoldingWitnessVar>::RW>; type RU: FoldingInstanceVar>::RU>; - type IW: FoldingWitnessVar>::IW>; type IU: FoldingInstanceVar>::IU>; type VerifierKey; From 0d6f6a13185eb20adfcda96ca4d6362e23f09dd8 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 20:15:37 +0800 Subject: [PATCH 52/99] Make fields of CF key and proof visible --- crates/ivc/src/compilers/cyclefold/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index aebe497d0..4accd00f1 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -59,18 +59,18 @@ pub trait FoldingSchemeCycleFoldExt: } pub struct Key, FS2: FoldingScheme<1, 1>, T>( - FS1::DeciderKey, - FS2::DeciderKey, - T, + pub FS1::DeciderKey, + pub FS2::DeciderKey, + pub T, ); pub struct Proof, FS2: FoldingScheme<1, 1>>( - FS1::RW, - FS1::RU, - FS1::IW, - FS1::IU, - FS2::RW, - FS2::RU, + pub FS1::RW, + pub FS1::RU, + pub FS1::IW, + pub FS1::IU, + pub FS2::RW, + pub FS2::RU, ); impl, FS2: FoldingScheme<1, 1>, T> Dummy<&Key> From 5b33e690841584dbc7eae0048eb2a081d5be3acb Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 22:33:18 +0800 Subject: [PATCH 53/99] Allow step circuit to have states of any shape --- .../ivc/src/compilers/cyclefold/circuits.rs | 48 +++++++++---------- crates/ivc/src/compilers/cyclefold/mod.rs | 20 ++++---- crates/ivc/src/lib.rs | 33 +++++++------ 3 files changed, 50 insertions(+), 51 deletions(-) diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index c93c4ff0e..cbe95b319 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -45,21 +45,21 @@ pub struct AugmentedCircuit< impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> where FS1: FoldingSchemeCycleFoldExt< - 1, - 1, - Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, - >, + 1, + 1, + Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, + >, FS2: GroupBasedFoldingSchemeSecondary< - 1, - 1, - Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, - >, + 1, + 1, + Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, + >, FC: FCircuit::Scalar>, T: Transcript, { @@ -161,19 +161,19 @@ where impl<'a, FS1, FS2, FC, T> ConstraintSynthesizer for AugmentedCircuit<'a, FS1, FS2, FC, T> where FS1: FoldingSchemeCycleFoldExt< - 1, - 1, - Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, - >, + 1, + 1, + Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, + >, FS2: GroupBasedFoldingSchemeSecondary< - 1, - 1, - Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef>, - >, + 1, + 1, + Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + VC: CommitmentDef>, + >, FC: FCircuit::Scalar>, T: Transcript, { @@ -230,7 +230,7 @@ pub trait CycleFoldConfig: Sized + Default { } fn verify_point_rlc(&self, cs: ConstraintSystemRef>) - -> Result<(), SynthesisError>; + -> Result<(), SynthesisError>; } #[derive(Debug, Clone, Default)] diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 4accd00f1..8793b979c 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -122,11 +122,11 @@ where type PublicParam = (FS1::PublicParam, FS2::PublicParam, T::Config); - type ProverKey = Key; + type ProverKey = Key; - type VerifierKey = Key; + type VerifierKey = Key; - type Proof = Proof; + type Proof = Proof; fn preprocess( (cfg1, cfg2, hash_config): Self::Config, @@ -142,7 +142,7 @@ where fn generate_keys>( (pp1, pp2, hash_config): Self::PublicParam, step_circuit: &FC, - ) -> Result<(Self::ProverKey, Self::VerifierKey), Error> { + ) -> Result<(Self::ProverKey, Self::VerifierKey), Error> { let cyclefold_circuit = CycleFoldCircuit::::default(); let cs = ArithExtractor::new(); @@ -180,16 +180,16 @@ where } fn prove>( - Key(dk1, dk2, (hash_config, pp_hash)): &Self::ProverKey, + Key(dk1, dk2, (hash_config, pp_hash)): &Self::ProverKey, step_circuit: &FC, i: usize, initial_state: &FC::State, current_state: &FC::State, external_inputs: FC::ExternalInputs, - Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, + Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, mut rng: impl RngCore, - ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error> { - let hash = T::new_with_pp_hash(&hash_config, *pp_hash); + ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error> { + let hash = T::new_with_pp_hash(hash_config, *pp_hash); let mut transcript = hash.separate_domain("transcript".as_ref()); let arith1_config = dk1.to_arith_config(); @@ -269,11 +269,11 @@ where } fn verify>( - Key(dk1, dk2, (hash_config, pp_hash)): &Self::VerifierKey, + Key(dk1, dk2, (hash_config, pp_hash)): &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, - Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, + Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, ) -> Result<(), Error> { if i == 0 { return (initial_state == current_state) diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index cd1e51a87..7a660a54e 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -23,49 +23,49 @@ pub trait IVC { type Config; type PublicParam; - type ProverKey; - type VerifierKey; - type Proof: for<'a> Dummy<&'a Self::ProverKey>; + type ProverKey; + type VerifierKey; + type Proof: for<'a> Dummy<&'a Self::ProverKey>; fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; fn generate_keys>( pp: Self::PublicParam, step_circuit: &FC, - ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; + ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; fn prove>( - pk: &Self::ProverKey, + pk: &Self::ProverKey, step_circuit: &FC, i: usize, initial_state: &FC::State, current_state: &FC::State, external_inputs: FC::ExternalInputs, - current_proof: &Self::Proof, + current_proof: &Self::Proof, rng: impl RngCore, - ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error>; + ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error>; fn verify>( - vk: &Self::VerifierKey, + vk: &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, - proof: &Self::Proof, + proof: &Self::Proof, ) -> Result<(), Error>; } pub struct IVCStatefulProver { - pub pk: I::ProverKey, + pub pk: I::ProverKey, pub step_circuit: FC, pub i: usize, pub initial_state: FC::State, pub current_state: FC::State, - pub current_proof: I::Proof, + pub current_proof: I::Proof, } impl, I: IVC> IVCStatefulProver { pub fn new( - pk: I::ProverKey, + pk: I::ProverKey, step_circuit: FC, initial_state: FC::State, ) -> Result { @@ -110,8 +110,8 @@ pub trait Decider { type Witness; type Proof; - fn preprocess_and_generate_keys( - ivc_pk: &::ProverKey, + fn preprocess_and_generate_keys( + ivc_pk: &::ProverKey, rng: impl RngCore, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; @@ -123,14 +123,13 @@ pub trait Decider { ) -> Result; fn verify(vk: &Self::VerifierKey, x: &Self::Instance, proof: &Self::Proof) - -> Result<(), Error>; + -> Result<(), Error>; } #[cfg(test)] mod tests { use ark_bn254::{Fr, G1Projective as C1}; use ark_crypto_primitives::sponge::{CryptographicSponge, poseidon::PoseidonSponge}; - use ark_ff::UniformRand; use ark_grumpkin::Projective as C2; use ark_std::{error::Error, rand::Rng, sync::Arc, test_rng}; use sonobe_fs::{ @@ -164,7 +163,7 @@ mod tests { for external_inputs in external_inputs_vec { prover.prove_step(external_inputs, &mut rng)?; - I::verify::( + I::verify( &vk, prover.i, &prover.initial_state, From d63173f556d44df24e9f5e549ff5e732a6c53685 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 23:02:25 +0800 Subject: [PATCH 54/99] We only need 1 public input for primary instances in CF --- crates/ivc/src/compilers/cyclefold/circuits.rs | 10 +++++----- crates/ivc/src/compilers/cyclefold/mod.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index cbe95b319..616e9bd8e 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -108,8 +108,8 @@ where .add(¤t_state)? .add(&U)? .add(&cf_U)? - .get_field_elements(2)?; - let u = FoldingInstanceVar::new_witness_with_public_inputs(cs.clone(), u, u_x)?; + .get_field_element()?; + let u = FoldingInstanceVar::new_witness_with_public_inputs(cs.clone(), u, vec![u_x])?; let (UU, rho) = FS1::Gadget::verify_hinted(&(), &mut transcript, [&U], [&u], &proof)?; let actual_UU = is_basecase.select(&U_dummy, &UU)?; @@ -136,7 +136,7 @@ where .add(&next_state)? .add(&actual_UU)? .add(&actual_cf_UU)? - .get_field_elements(2)?; + .get_field_element()?; // This line "converts" `uu_x` from witnesses to public inputs. // Instead of directly modifying the constraint system, we explicitly // allocate a public input and enforce that its value is indeed `uu_x`. @@ -146,8 +146,8 @@ where // computing them outside the circuit. // - `.enforce_equal()` prevents a malicious prover from claiming wrong // public inputs that are not the honest `uu_x` computed in-circuit. - uu_x.enforce_equal(&Vec::new_input(cs.clone(), || { - Ok(uu_x.value().unwrap_or(vec![Default::default(); uu_x.len()])) + uu_x.enforce_equal(&FpVar::new_input(cs.clone(), || { + Ok(uu_x.value().unwrap_or_default()) })?)?; if cs.is_in_setup_mode() { diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 8793b979c..baaa963ae 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -290,9 +290,9 @@ where .add(current_state) .add(U) .add(cf_U) - .get_field_elements(2); + .get_field_element(); - if u.public_inputs() != &u_x[..] { + if u.public_inputs() != [u_x] { return Err(Error::IVCVerificationFail); } From 85b50232ebc09808dacfc251e1dd21e0276e7e46 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 04:41:39 +0800 Subject: [PATCH 55/99] Separate VC and FS into Def and Ops --- crates/fs/src/lib.rs | 116 +++++++++--------- .../ivc/src/compilers/cyclefold/circuits.rs | 18 +-- crates/ivc/src/compilers/cyclefold/mod.rs | 35 +++--- crates/ivc/src/lib.rs | 4 - 4 files changed, 85 insertions(+), 88 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 2386e6456..0038faf4a 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -94,9 +94,7 @@ impl Absorbable for TaggedVec { } } -impl, const TAG: char> AbsorbableVar - for TaggedVec -{ +impl, const TAG: char> AbsorbableVar for TaggedVec { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.0.absorb_into(dest) } @@ -197,7 +195,7 @@ pub trait DeciderKey { fn to_arith_config(&self) -> &Self::ArithConfig; } -pub trait FoldingScheme { +pub trait FoldingSchemeDef { type VC: CommitmentDef; type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; @@ -219,8 +217,11 @@ pub trait FoldingScheme { Error = Error, >; type Challenge; - type Proof: Clone + for<'a> Dummy<&'a ::Config>; + type Proof: Clone + + for<'a> Dummy<&'a ::Config>; +} +pub trait FoldingSchemeOps: FoldingSchemeDef { /// The preprocessing method is a randomized algorithm that takes as input /// the size bounds of the folding scheme, which are contained in the /// `config` parameter, and outputs the public parameters. @@ -254,7 +255,7 @@ pub trait FoldingScheme { ws: &[impl Borrow; N], us: &[impl Borrow; N], rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; + ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; #[allow(non_snake_case)] fn verify( @@ -262,7 +263,7 @@ pub trait FoldingScheme { transcript: &mut impl Transcript, Us: &[impl Borrow; M], us: &[impl Borrow; N], - proof: &Self::Proof, + proof: &Self::Proof, ) -> Result; #[allow(non_snake_case)] @@ -305,10 +306,10 @@ pub trait FoldingInstanceVar: ) -> Result; } -pub type PlainWitnessVar = PlainWitness<::ScalarVar>; -pub type PlainInstanceVar = PlainInstance<::ScalarVar>; +pub type PlainWitnessVar = PlainWitness; +pub type PlainInstanceVar = PlainInstance; -impl FoldingInstanceVar for PlainInstanceVar { +impl FoldingInstanceVar for PlainInstanceVar { fn commitments(&self) -> Vec<&VC::CommitmentVar> { vec![] } @@ -326,69 +327,40 @@ impl FoldingInstanceVar for PlainInstanceVar { } } -pub trait GroupBasedFoldingSchemePrimary: - FoldingScheme< - M, - N, - VC: GroupBasedCommitment, - TranscriptField = <>::VC as CommitmentDef>::Scalar, - > -{ - type Gadget: FoldingSchemePartialGadget< - M, - N, - Native = Self, - VC = ::Gadget2, - >; -} +pub trait FoldingSchemeGadgetDef { + type Native: FoldingSchemeDef; -pub trait GroupBasedFoldingSchemeSecondary: - FoldingScheme< - M, - N, - VC: GroupBasedCommitment, - TranscriptField = CF2<<>::VC as CommitmentDef>::Commitment>, - > -{ - type Gadget: FoldingSchemeFullGadget< - M, - N, - Native = Self, - VC = ::Gadget1, - >; -} - -pub trait FoldingSchemePartialGadget { - type Native: FoldingScheme; - - type VC: CommitmentDefGadget>::VC>; - type RU: FoldingInstanceVar>::RU>; - type IU: FoldingInstanceVar>::IU>; + type VC: CommitmentDefGadget::VC>; + type RU: FoldingInstanceVar::RU>; + type IU: FoldingInstanceVar::IU>; type VerifierKey; type Challenge; - - type Proof: AllocVar< - >::Proof, + type Proof: AllocVar< + ::Proof, ::ConstraintField, > + GR1CSVar< ::ConstraintField, - Value = >::Proof, + Value = ::Proof, >; +} +pub trait FoldingSchemeGadgetOpsPartial: + FoldingSchemeGadgetDef> +{ #[allow(non_snake_case)] fn verify_hinted( vk: &Self::VerifierKey, transcript: &mut impl TranscriptGadget<::ConstraintField>, Us: [&Self::RU; M], us: [&Self::IU; N], - proof: &Self::Proof, + proof: &Self::Proof, ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; } -pub trait FoldingSchemeFullGadget: - FoldingSchemePartialGadget +pub trait FoldingSchemeGadgetOpsFull: + FoldingSchemeGadgetOpsPartial { #[allow(non_snake_case)] fn verify( @@ -396,10 +368,38 @@ pub trait FoldingSchemeFullGadget: transcript: &mut impl TranscriptGadget<::ConstraintField>, Us: [&Self::RU; M], us: [&Self::IU; N], - proof: &Self::Proof, + proof: &Self::Proof, ) -> Result; } +pub trait GroupBasedFoldingSchemePrimary: + FoldingSchemeDef< + VC: GroupBasedCommitment, + TranscriptField = <::VC as CommitmentDef>::Scalar, + > + FoldingSchemeOps +{ + type Gadget: FoldingSchemeGadgetOpsPartial< + M, + N, + Native = Self, + VC = ::Gadget2, + >; +} + +pub trait GroupBasedFoldingSchemeSecondary: + FoldingSchemeDef< + VC: GroupBasedCommitment, + TranscriptField = CF2<<::VC as CommitmentDef>::Commitment>, + > + FoldingSchemeOps +{ + type Gadget: FoldingSchemeGadgetOpsFull< + M, + N, + Native = Self, + VC = ::Gadget1, + >; +} + #[cfg(test)] mod tests { use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; @@ -416,7 +416,7 @@ mod tests { use super::*; #[allow(non_snake_case)] - pub fn test_folding_scheme, const M: usize, const N: usize>( + pub fn test_folding_scheme, const M: usize, const N: usize>( config: FS::Config, circuit: impl ConstraintSynthesizer<::Scalar>, assignments_vec: Vec::Scalar>>, @@ -466,9 +466,9 @@ mod tests { let ws = ws.try_into().unwrap(); let us = us.try_into().unwrap(); - let (WW, UU, pi, _) = FS::prove(&pk, &mut transcript_p, &Ws, &Us, &ws, &us, &mut rng)?; + let (WW, UU, pi, _) = FS::prove(pk, &mut transcript_p, &Ws, &Us, &ws, &us, &mut rng)?; FS::decide_running(&dk, &WW, &UU)?; - assert_eq!(FS::verify(&vk, &mut transcript_v, &Us, &us, &pi)?, UU); + assert_eq!(FS::verify(vk, &mut transcript_v, &Us, &us, &pi)?, UU); for i in 0..M { let (W, U) = WitnessInstanceSampler::::sample(&dk, (), &mut rng)?; diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 616e9bd8e..a5a5e051d 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -11,7 +11,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_std::marker::PhantomData; use sonobe_fs::{ - FoldingInstanceVar, FoldingSchemeFullGadget, FoldingSchemePartialGadget, + FoldingInstanceVar, FoldingSchemeGadgetOpsFull, FoldingSchemeGadgetOpsPartial, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ @@ -47,7 +47,7 @@ where FS1: FoldingSchemeCycleFoldExt< 1, 1, - Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeGadgetOpsPartial<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -55,7 +55,7 @@ where FS2: GroupBasedFoldingSchemeSecondary< 1, 1, - Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeGadgetOpsFull<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -73,10 +73,10 @@ where external_inputs: FC::ExternalInputs, U: &FS1::RU, u: &FS1::IU, - proof: FS1::Proof, + proof: FS1::Proof<1, 1>, cf_U: &FS2::RU, cf_us: Vec, - cf_proofs: Vec, + cf_proofs: Vec>, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { let hash = T::Gadget::new_with_pp_hash( &self.hash_config, @@ -163,7 +163,7 @@ where FS1: FoldingSchemeCycleFoldExt< 1, 1, - Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeGadgetOpsPartial<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -171,8 +171,10 @@ where FS2: GroupBasedFoldingSchemeSecondary< 1, 1, - Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef>, + Gadget: FoldingSchemeGadgetOpsFull<1, 1, VerifierKey = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, FC: FCircuit::Scalar>, T: Transcript, diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index baaa963ae..197e687cd 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,13 +1,14 @@ -use ark_ff::{PrimeField, Zero}; +use ark_ff::Zero; use ark_relations::gr1cs::{ConstraintSystem, SynthesisError, SynthesisMode}; -use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore, sync::Arc}; +use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore}; use sonobe_fs::{ - DeciderKey, FoldingInstance, FoldingScheme, FoldingSchemeFullGadget, - FoldingSchemePartialGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, + DeciderKey, FoldingInstance, FoldingSchemeDef, FoldingSchemeGadgetDef, + FoldingSchemeGadgetOpsFull, FoldingSchemeGadgetOpsPartial, GroupBasedFoldingSchemePrimary, + GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, - arithmetizations::{Arith, ArithConfig}, + arithmetizations::Arith, circuits::{ArithExtractor, AssignmentsExtractor, ConstraintSystemExt, FCircuit}, commitments::{CommitmentDef, CommitmentDefGadget}, relations::WitnessInstanceSampler, @@ -35,16 +36,16 @@ pub trait FoldingSchemeCycleFoldExt: fn to_cyclefold_configs( Us: &[impl Borrow; M], us: &[impl Borrow; N], - proof: &Self::Proof, + proof: &Self::Proof, rho: Self::Challenge, ) -> Vec; fn to_cyclefold_inputs( - Us: [>::RU; M], - us: [>::IU; N], - UU: >::RU, - proof: >::Proof, - rho: >::Challenge, + Us: [::RU; M], + us: [::IU; N], + UU: ::RU, + proof: ::Proof, + rho: ::Challenge, ) -> Result< Vec< Vec< @@ -58,13 +59,13 @@ pub trait FoldingSchemeCycleFoldExt: >; } -pub struct Key, FS2: FoldingScheme<1, 1>, T>( +pub struct Key( pub FS1::DeciderKey, pub FS2::DeciderKey, pub T, ); -pub struct Proof, FS2: FoldingScheme<1, 1>>( +pub struct Proof( pub FS1::RW, pub FS1::RU, pub FS1::IW, @@ -73,9 +74,7 @@ pub struct Proof, FS2: FoldingScheme<1, 1>>( pub FS2::RU, ); -impl, FS2: FoldingScheme<1, 1>, T> Dummy<&Key> - for Proof -{ +impl Dummy<&Key> for Proof { fn dummy(pk: &Key) -> Self { let cfg1 = pk.0.to_arith_config(); let cfg2 = pk.1.to_arith_config(); @@ -100,7 +99,7 @@ where 1, 1, Arith: From::Commitment>>>, - Gadget: FoldingSchemePartialGadget<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeGadgetOpsPartial<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -109,7 +108,7 @@ where 1, 1, Arith: From::Commitment>>>, - Gadget: FoldingSchemeFullGadget<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeGadgetOpsFull<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 7a660a54e..efe68a390 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -132,10 +132,6 @@ mod tests { use ark_crypto_primitives::sponge::{CryptographicSponge, poseidon::PoseidonSponge}; use ark_grumpkin::Projective as C2; use ark_std::{error::Error, rand::Rng, sync::Arc, test_rng}; - use sonobe_fs::{ - FoldingScheme, FoldingSchemeFullGadget, FoldingSchemePartialGadget, PlainInstance as IU, - PlainInstanceVar as IUVar, PlainWitness as IW, PlainWitnessVar as IWVar, - }; use sonobe_primitives::{ arithmetizations::Arith, circuits::utils::CircuitForTest, From c4ab18e061bf9a949e3eb9e457f5a2f2788a4d96 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 05:30:03 +0800 Subject: [PATCH 56/99] Improve trait design for group based FS --- crates/fs/src/lib.rs | 60 +++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 0038faf4a..15602b944 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -54,7 +54,9 @@ pub trait FoldingWitness: Debug { fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; } -pub trait FoldingInstance: Clone + Debug + PartialEq + Absorbable { +pub trait FoldingInstance: + Clone + Debug + PartialEq + Eq + Absorbable +{ const N_COMMITMENTS: usize; /// Returns the commitments contained in the committed instance. @@ -372,32 +374,50 @@ pub trait FoldingSchemeGadgetOpsFull: ) -> Result; } +pub trait GroupBasedFoldingSchemePrimaryDef: + FoldingSchemeDef< + VC: GroupBasedCommitment, + TranscriptField = <::VC as CommitmentDef>::Scalar, +> +{ + type Gadget: FoldingSchemeGadgetDef< + Native = Self, + VC = ::Gadget2, + >; +} + pub trait GroupBasedFoldingSchemePrimary: + GroupBasedFoldingSchemePrimaryDef> + + FoldingSchemeOps +{ +} + +impl GroupBasedFoldingSchemePrimary for FS where + FS: GroupBasedFoldingSchemePrimaryDef> +{ +} + +pub trait GroupBasedFoldingSchemeSecondaryDef: FoldingSchemeDef< - VC: GroupBasedCommitment, - TranscriptField = <::VC as CommitmentDef>::Scalar, - > + FoldingSchemeOps + VC: GroupBasedCommitment, + TranscriptField = CF2<<::VC as CommitmentDef>::Commitment>, +> { - type Gadget: FoldingSchemeGadgetOpsPartial< - M, - N, - Native = Self, - VC = ::Gadget2, - >; + type Gadget: FoldingSchemeGadgetDef< + Native = Self, + VC = ::Gadget1, + >; } pub trait GroupBasedFoldingSchemeSecondary: - FoldingSchemeDef< - VC: GroupBasedCommitment, - TranscriptField = CF2<<::VC as CommitmentDef>::Commitment>, - > + FoldingSchemeOps + GroupBasedFoldingSchemeSecondaryDef> + + FoldingSchemeOps +{ +} + +impl GroupBasedFoldingSchemeSecondary for FS where + FS: GroupBasedFoldingSchemeSecondaryDef> { - type Gadget: FoldingSchemeGadgetOpsFull< - M, - N, - Native = Self, - VC = ::Gadget1, - >; } #[cfg(test)] From dbc9fe609fd01a8db4bae3b638e709aa997f4ecc Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 09:34:45 +0800 Subject: [PATCH 57/99] Add trait bounds for in-circuit challenges --- crates/fs/src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 15602b944..4c2de6590 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -67,7 +67,7 @@ pub trait FoldingInstance: fn public_inputs_mut(&mut self) -> &mut [VC::Scalar]; } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct TaggedVec(pub Vec); impl Deref for TaggedVec { @@ -90,6 +90,12 @@ impl From> for TaggedVec { } } +impl From> for Vec { + fn from(val: TaggedVec) -> Self { + val.0 + } +} + impl Absorbable for TaggedVec { fn absorb_into(&self, dest: &mut Vec) { self.0.absorb_into(dest) @@ -338,7 +344,13 @@ pub trait FoldingSchemeGadgetDef { type VerifierKey; - type Challenge; + type Challenge: AllocVar< + ::Challenge, + ::ConstraintField, + > + GR1CSVar< + ::ConstraintField, + Value = ::Challenge, + >; type Proof: AllocVar< ::Proof, ::ConstraintField, From 0fca7de14a3a3a54e9925d3c2eec1afed2783e0b Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 25 Nov 2025 00:52:37 +0800 Subject: [PATCH 58/99] Further split FS Ops --- crates/fs/src/lib.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 4c2de6590..30c298426 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -229,7 +229,7 @@ pub trait FoldingSchemeDef { + for<'a> Dummy<&'a ::Config>; } -pub trait FoldingSchemeOps: FoldingSchemeDef { +pub trait FoldingSchemePreprocessor: FoldingSchemeDef { /// The preprocessing method is a randomized algorithm that takes as input /// the size bounds of the folding scheme, which are contained in the /// `config` parameter, and outputs the public parameters. @@ -239,12 +239,16 @@ pub trait FoldingSchemeOps: FoldingSchemeDef { /// The security parameter is implicitly specified by the size of underlying /// fields and groups. fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; +} +pub trait FoldingSchemeKeyGenerator: FoldingSchemeDef { /// The key generation method is a deterministic algorithm that takes as /// input the public parameters `pp` and the constraint system `arith`, and /// outputs a prover key and a verifier key. fn generate_keys(pp: Self::PublicParam, arith: Self::Arith) -> Result; +} +pub trait FoldingSchemeProver: FoldingSchemeDef { /// The proof generation method is a deterministic algorithm that takes as /// input the prover key `pk`, the transcript `transcript` between the /// prover and the verifier, the first witness-instance pair `W`, `U`, the @@ -264,7 +268,9 @@ pub trait FoldingSchemeOps: FoldingSchemeDef { us: &[impl Borrow; N], rng: impl RngCore, ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; +} +pub trait FoldingSchemeVerifier: FoldingSchemeDef { #[allow(non_snake_case)] fn verify( vk: &::VerifierKey, @@ -273,7 +279,9 @@ pub trait FoldingSchemeOps: FoldingSchemeDef { us: &[impl Borrow; N], proof: &Self::Proof, ) -> Result; +} +pub trait FoldingSchemeDecider: FoldingSchemeDef { #[allow(non_snake_case)] fn decide_running(dk: &Self::DeciderKey, W: &Self::RW, U: &Self::RU) -> Result<(), Error> { Relation::::check_relation(dk, W, U) @@ -284,6 +292,26 @@ pub trait FoldingSchemeOps: FoldingSchemeDef { } } +impl FoldingSchemeDecider for FS {} + +pub trait FoldingSchemeOps: + FoldingSchemePreprocessor + + FoldingSchemeKeyGenerator + + FoldingSchemeProver + + FoldingSchemeVerifier + + FoldingSchemeDecider +{ +} + +impl FoldingSchemeOps for FS where + FS: FoldingSchemePreprocessor + + FoldingSchemeKeyGenerator + + FoldingSchemeProver + + FoldingSchemeVerifier + + FoldingSchemeDecider +{ +} + pub trait FoldingWitnessVar: AllocVar + GR1CSVar> From c7239421e103357de3e2c15bd10cb93af0ff337c Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 26 Dec 2025 22:08:26 +0800 Subject: [PATCH 59/99] Stateful prover no longer owns pk and step circuit --- crates/ivc/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index efe68a390..befd8bf17 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -54,19 +54,19 @@ pub trait IVC { ) -> Result<(), Error>; } -pub struct IVCStatefulProver { - pub pk: I::ProverKey, - pub step_circuit: FC, +pub struct IVCStatefulProver<'a, FC: FCircuit, I: IVC> { + pub pk: &'a I::ProverKey, + pub step_circuit: &'a FC, pub i: usize, pub initial_state: FC::State, pub current_state: FC::State, pub current_proof: I::Proof, } -impl, I: IVC> IVCStatefulProver { +impl<'a, FC: FCircuit, I: IVC> IVCStatefulProver<'a, FC, I> { pub fn new( - pk: I::ProverKey, - step_circuit: FC, + pk: &'a I::ProverKey, + step_circuit: &'a FC, initial_state: FC::State, ) -> Result { Ok(Self { @@ -154,7 +154,7 @@ mod tests { let initial_state = step_circuit.dummy_state(); - let mut prover = IVCStatefulProver::<_, I>::new(pk, step_circuit, initial_state)?; + let mut prover = IVCStatefulProver::<_, I>::new(&pk, &step_circuit, initial_state)?; for external_inputs in external_inputs_vec { prover.prove_step(external_inputs, &mut rng)?; From d0ec53120527d26bfd853fdd711d7d7e3da74f5a Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 60/99] Adjust the naming of traits for gadgets --- crates/fs/src/lib.rs | 22 +++++++++---------- .../ivc/src/compilers/cyclefold/circuits.rs | 10 ++++----- crates/ivc/src/compilers/cyclefold/mod.rs | 18 +++++++-------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 30c298426..9c7cd725d 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -363,7 +363,7 @@ impl FoldingInstanceVar for PlainInstanceVar::VC>; @@ -388,8 +388,8 @@ pub trait FoldingSchemeGadgetDef { >; } -pub trait FoldingSchemeGadgetOpsPartial: - FoldingSchemeGadgetDef> +pub trait FoldingSchemePartialVerifierGadget: + FoldingSchemeDefGadget> { #[allow(non_snake_case)] fn verify_hinted( @@ -401,8 +401,8 @@ pub trait FoldingSchemeGadgetOpsPartial: ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; } -pub trait FoldingSchemeGadgetOpsFull: - FoldingSchemeGadgetOpsPartial +pub trait FoldingSchemeFullVerifierGadget: + FoldingSchemePartialVerifierGadget { #[allow(non_snake_case)] fn verify( @@ -420,20 +420,20 @@ pub trait GroupBasedFoldingSchemePrimaryDef: TranscriptField = <::VC as CommitmentDef>::Scalar, > { - type Gadget: FoldingSchemeGadgetDef< + type Gadget: FoldingSchemeDefGadget< Native = Self, VC = ::Gadget2, >; } pub trait GroupBasedFoldingSchemePrimary: - GroupBasedFoldingSchemePrimaryDef> + GroupBasedFoldingSchemePrimaryDef> + FoldingSchemeOps { } impl GroupBasedFoldingSchemePrimary for FS where - FS: GroupBasedFoldingSchemePrimaryDef> + FS: GroupBasedFoldingSchemePrimaryDef> { } @@ -443,20 +443,20 @@ pub trait GroupBasedFoldingSchemeSecondaryDef: TranscriptField = CF2<<::VC as CommitmentDef>::Commitment>, > { - type Gadget: FoldingSchemeGadgetDef< + type Gadget: FoldingSchemeDefGadget< Native = Self, VC = ::Gadget1, >; } pub trait GroupBasedFoldingSchemeSecondary: - GroupBasedFoldingSchemeSecondaryDef> + GroupBasedFoldingSchemeSecondaryDef> + FoldingSchemeOps { } impl GroupBasedFoldingSchemeSecondary for FS where - FS: GroupBasedFoldingSchemeSecondaryDef> + FS: GroupBasedFoldingSchemeSecondaryDef> { } diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index a5a5e051d..734943c16 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -11,7 +11,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_std::marker::PhantomData; use sonobe_fs::{ - FoldingInstanceVar, FoldingSchemeGadgetOpsFull, FoldingSchemeGadgetOpsPartial, + FoldingInstanceVar, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ @@ -47,7 +47,7 @@ where FS1: FoldingSchemeCycleFoldExt< 1, 1, - Gadget: FoldingSchemeGadgetOpsPartial<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -55,7 +55,7 @@ where FS2: GroupBasedFoldingSchemeSecondary< 1, 1, - Gadget: FoldingSchemeGadgetOpsFull<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -163,7 +163,7 @@ where FS1: FoldingSchemeCycleFoldExt< 1, 1, - Gadget: FoldingSchemeGadgetOpsPartial<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -171,7 +171,7 @@ where FS2: GroupBasedFoldingSchemeSecondary< 1, 1, - Gadget: FoldingSchemeGadgetOpsFull<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 197e687cd..4af38ead6 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -2,8 +2,8 @@ use ark_ff::Zero; use ark_relations::gr1cs::{ConstraintSystem, SynthesisError, SynthesisMode}; use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore}; use sonobe_fs::{ - DeciderKey, FoldingInstance, FoldingSchemeDef, FoldingSchemeGadgetDef, - FoldingSchemeGadgetOpsFull, FoldingSchemeGadgetOpsPartial, GroupBasedFoldingSchemePrimary, + DeciderKey, FoldingInstance, FoldingSchemeDef, FoldingSchemeDefGadget, + FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ @@ -41,11 +41,11 @@ pub trait FoldingSchemeCycleFoldExt: ) -> Vec; fn to_cyclefold_inputs( - Us: [::RU; M], - us: [::IU; N], - UU: ::RU, - proof: ::Proof, - rho: ::Challenge, + Us: [::RU; M], + us: [::IU; N], + UU: ::RU, + proof: ::Proof, + rho: ::Challenge, ) -> Result< Vec< Vec< @@ -99,7 +99,7 @@ where 1, 1, Arith: From::Commitment>>>, - Gadget: FoldingSchemeGadgetOpsPartial<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -108,7 +108,7 @@ where 1, 1, Arith: From::Commitment>>>, - Gadget: FoldingSchemeGadgetOpsFull<1, 1, VerifierKey = ()>, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, VC: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, From d715587f7b028d1ba3a4eb81ea2e3c163cd985c2 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 61/99] Move trait definitions to their dedicated files --- crates/fs/src/definitions/algorithms.rs | 87 ++++ crates/fs/src/definitions/circuits.rs | 32 ++ crates/fs/src/definitions/errors.rs | 28 ++ crates/fs/src/definitions/instances.rs | 86 ++++ crates/fs/src/definitions/keys.rs | 11 + crates/fs/src/definitions/mod.rs | 75 ++++ crates/fs/src/definitions/utils.rs | 103 +++++ crates/fs/src/definitions/variants.rs | 55 +++ crates/fs/src/definitions/witnesses.rs | 47 +++ crates/fs/src/lib.rs | 483 +--------------------- crates/ivc/src/compilers/cyclefold/mod.rs | 4 +- 11 files changed, 547 insertions(+), 464 deletions(-) create mode 100644 crates/fs/src/definitions/algorithms.rs create mode 100644 crates/fs/src/definitions/circuits.rs create mode 100644 crates/fs/src/definitions/errors.rs create mode 100644 crates/fs/src/definitions/instances.rs create mode 100644 crates/fs/src/definitions/keys.rs create mode 100644 crates/fs/src/definitions/mod.rs create mode 100644 crates/fs/src/definitions/utils.rs create mode 100644 crates/fs/src/definitions/variants.rs create mode 100644 crates/fs/src/definitions/witnesses.rs diff --git a/crates/fs/src/definitions/algorithms.rs b/crates/fs/src/definitions/algorithms.rs new file mode 100644 index 000000000..23642a6bb --- /dev/null +++ b/crates/fs/src/definitions/algorithms.rs @@ -0,0 +1,87 @@ +use ark_std::{borrow::Borrow, rand::RngCore}; +use sonobe_primitives::{relations::Relation, transcripts::Transcript}; + +use super::{errors::Error, keys::DeciderKey, FoldingSchemeDef}; + +pub trait FoldingSchemePreprocessor: FoldingSchemeDef { + /// The preprocessing method is a randomized algorithm that takes as input + /// the size bounds of the folding scheme, which are contained in the + /// `config` parameter, and outputs the public parameters. + /// + /// Here, the randomness source is controlled by `rng`. + /// + /// The security parameter is implicitly specified by the size of underlying + /// fields and groups. + fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; +} + +pub trait FoldingSchemeKeyGenerator: FoldingSchemeDef { + /// The key generation method is a deterministic algorithm that takes as + /// input the public parameters `pp` and the constraint system `arith`, and + /// outputs a prover key and a verifier key. + fn generate_keys(pp: Self::PublicParam, arith: Self::Arith) -> Result; +} + +pub trait FoldingSchemeProver: FoldingSchemeDef { + /// The proof generation method is a deterministic algorithm that takes as + /// input the prover key `pk`, the transcript `transcript` between the + /// prover and the verifier, the first witness-instance pair `W`, `U`, the + /// second witness-instance pair `w`, `u`, and outputs the folded witness + /// and instance, the proof, and the (intermediate) randomness. + /// + /// Here, the randomness source is controlled by `transcript`. The returned + /// intermediate randomness is useful for the construction of CycleFold + /// circuits in our CycleFold-based folding-to-IVC compiler. + #[allow(non_snake_case)] + fn prove( + pk: &::ProverKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; M], + Us: &[impl Borrow; M], + ws: &[impl Borrow; N], + us: &[impl Borrow; N], + rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; +} + +pub trait FoldingSchemeVerifier: FoldingSchemeDef { + #[allow(non_snake_case)] + fn verify( + vk: &::VerifierKey, + transcript: &mut impl Transcript, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + proof: &Self::Proof, + ) -> Result; +} + +pub trait FoldingSchemeDecider: FoldingSchemeDef { + #[allow(non_snake_case)] + fn decide_running(dk: &Self::DeciderKey, W: &Self::RW, U: &Self::RU) -> Result<(), Error> { + Relation::::check_relation(dk, W, U) + } + + fn decide_incoming(dk: &Self::DeciderKey, w: &Self::IW, u: &Self::IU) -> Result<(), Error> { + Relation::::check_relation(dk, w, u) + } +} + +impl FoldingSchemeDecider for FS {} + +pub trait FoldingSchemeOps: + FoldingSchemePreprocessor + + FoldingSchemeKeyGenerator + + FoldingSchemeProver + + FoldingSchemeVerifier + + FoldingSchemeDecider +{ +} + +impl FoldingSchemeOps for FS where + FS: FoldingSchemePreprocessor + + FoldingSchemeKeyGenerator + + FoldingSchemeProver + + FoldingSchemeVerifier + + FoldingSchemeDecider +{ +} diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs new file mode 100644 index 000000000..1e17fed77 --- /dev/null +++ b/crates/fs/src/definitions/circuits.rs @@ -0,0 +1,32 @@ +use ark_relations::gr1cs::SynthesisError; +use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::TranscriptGadget}; + +use super::{ + algorithms::FoldingSchemeOps, FoldingSchemeDefGadget, +}; + +pub trait FoldingSchemePartialVerifierGadget: + FoldingSchemeDefGadget> +{ + #[allow(non_snake_case)] + fn verify_hinted( + vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget<::ConstraintField>, + Us: [&Self::RU; M], + us: [&Self::IU; N], + proof: &Self::Proof, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; +} + +pub trait FoldingSchemeFullVerifierGadget: + FoldingSchemePartialVerifierGadget +{ + #[allow(non_snake_case)] + fn verify( + vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget<::ConstraintField>, + Us: [&Self::RU; M], + us: [&Self::IU; N], + proof: &Self::Proof, + ) -> Result; +} diff --git a/crates/fs/src/definitions/errors.rs b/crates/fs/src/definitions/errors.rs new file mode 100644 index 000000000..de9edfd35 --- /dev/null +++ b/crates/fs/src/definitions/errors.rs @@ -0,0 +1,28 @@ +use ark_relations::gr1cs::SynthesisError; +use sonobe_primitives::{ + arithmetizations::Error as ArithError, commitments::Error as CommitmentError, + sumcheck::Error as SumCheckError, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + ArithError(#[from] ArithError), + #[error(transparent)] + CommitmentError(#[from] CommitmentError), + #[error(transparent)] + SynthesisError(#[from] SynthesisError), + #[error(transparent)] + SumCheckError(#[from] SumCheckError), + #[error("Unsupported use case: {0}")] + Unsupported(String), + #[error("Failed to create domain")] + DomainCreationFailure, + #[error("Indivisible by vanishing polynomial")] + IndivisibleByVanishingPoly, + #[error("Unsatisfied relation: {0}")] + UnsatisfiedRelation(String), + #[error("Invalid public parameters: {0}")] + InvalidPublicParameters(String), +} diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs new file mode 100644 index 000000000..e54dc487b --- /dev/null +++ b/crates/fs/src/definitions/instances.rs @@ -0,0 +1,86 @@ +use ark_r1cs_std::{alloc::AllocVar, select::CondSelectGadget, GR1CSVar}; +use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_std::fmt::Debug; +use sonobe_primitives::{ + arithmetizations::ArithConfig, + commitments::{CommitmentDef, CommitmentDefGadget}, + traits::Dummy, + transcripts::{Absorbable, AbsorbableVar}, +}; + +use super::utils::TaggedVec; + +pub trait FoldingInstance: + Clone + Debug + PartialEq + Eq + Absorbable +{ + const N_COMMITMENTS: usize; + + /// Returns the commitments contained in the committed instance. + fn commitments(&self) -> Vec<&VC::Commitment>; + + fn public_inputs(&self) -> &[VC::Scalar]; + + fn public_inputs_mut(&mut self) -> &mut [VC::Scalar]; +} + +pub type PlainInstance = TaggedVec; + +impl Dummy<&A> for PlainInstance { + fn dummy(cfg: &A) -> Self { + vec![V::default(); cfg.n_public_inputs()].into() + } +} + +impl FoldingInstance for PlainInstance { + const N_COMMITMENTS: usize = 0; + + fn commitments(&self) -> Vec<&VC::Commitment> { + vec![] + } + + fn public_inputs(&self) -> &[VC::Scalar] { + self + } + + fn public_inputs_mut(&mut self) -> &mut [VC::Scalar] { + self + } +} + +pub trait FoldingInstanceVar: + AllocVar + + GR1CSVar> + + AbsorbableVar + + CondSelectGadget +{ + /// Returns the commitments contained in the committed instance. + fn commitments(&self) -> Vec<&VC::CommitmentVar>; + + fn public_inputs(&self) -> &Vec; + + fn new_witness_with_public_inputs( + cs: impl Into>, + u: &Self::Value, + x: Vec, + ) -> Result; +} + +impl FoldingInstanceVar for PlainInstanceVar { + fn commitments(&self) -> Vec<&VC::CommitmentVar> { + vec![] + } + + fn public_inputs(&self) -> &Vec { + self + } + + fn new_witness_with_public_inputs( + _cs: impl Into>, + _u: &Self::Value, + x: Vec, + ) -> Result { + Ok(Self(x)) + } +} + +pub type PlainInstanceVar = PlainInstance; diff --git a/crates/fs/src/definitions/keys.rs b/crates/fs/src/definitions/keys.rs new file mode 100644 index 000000000..230395e18 --- /dev/null +++ b/crates/fs/src/definitions/keys.rs @@ -0,0 +1,11 @@ +use sonobe_primitives::arithmetizations::ArithConfig; + +pub trait DeciderKey { + type ProverKey; + type VerifierKey; + type ArithConfig: ArithConfig; + + fn to_pk(&self) -> &Self::ProverKey; + fn to_vk(&self) -> &Self::VerifierKey; + fn to_arith_config(&self) -> &Self::ArithConfig; +} diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs new file mode 100644 index 000000000..d1c13a2f3 --- /dev/null +++ b/crates/fs/src/definitions/mod.rs @@ -0,0 +1,75 @@ +pub mod algorithms; +pub mod circuits; +pub mod errors; +pub mod instances; +pub mod keys; +pub mod utils; +pub mod variants; +pub mod witnesses; + +use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; +use sonobe_primitives::{ + arithmetizations::Arith, + circuits::AssignmentsOwned, + commitments::{CommitmentDef, CommitmentDefGadget}, + relations::{Relation, WitnessInstanceSampler}, + traits::{Dummy, SonobeField}, +}; + +use self::{ + errors::Error, + instances::{FoldingInstance, FoldingInstanceVar}, + keys::DeciderKey, + witnesses::FoldingWitness, +}; + +pub trait FoldingSchemeDef { + type VC: CommitmentDef; + type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type TranscriptField: SonobeField; + type Arith: Arith::ArithConfig>; + type Config; + type PublicParam; + type DeciderKey: DeciderKey + + Clone + + Relation + + Relation + + WitnessInstanceSampler + + WitnessInstanceSampler< + Self::IW, + Self::IU, + Source = AssignmentsOwned<::Scalar>, + Error = Error, + >; + type Challenge; + type Proof: Clone + + for<'a> Dummy<&'a ::Config>; +} + +pub trait FoldingSchemeDefGadget { + type Native: FoldingSchemeDef; + + type VC: CommitmentDefGadget::VC>; + type RU: FoldingInstanceVar::RU>; + type IU: FoldingInstanceVar::IU>; + + type VerifierKey; + + type Challenge: AllocVar< + ::Challenge, + ::ConstraintField, + > + GR1CSVar< + ::ConstraintField, + Value = ::Challenge, + >; + type Proof: AllocVar< + ::Proof, + ::ConstraintField, + > + GR1CSVar< + ::ConstraintField, + Value = ::Proof, + >; +} diff --git a/crates/fs/src/definitions/utils.rs b/crates/fs/src/definitions/utils.rs new file mode 100644 index 000000000..8d96e02d9 --- /dev/null +++ b/crates/fs/src/definitions/utils.rs @@ -0,0 +1,103 @@ + +use ark_ff::{Field, PrimeField}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + fields::fp::FpVar, + prelude::Boolean, + select::CondSelectGadget, + GR1CSVar, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{ + borrow::Borrow, + ops::{Deref, DerefMut}, +}; +use sonobe_primitives::transcripts::{Absorbable, AbsorbableVar}; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TaggedVec(pub Vec); + +impl Deref for TaggedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for TaggedVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for TaggedVec { + fn from(v: Vec) -> Self { + Self(v) + } +} + +impl From> for Vec { + fn from(val: TaggedVec) -> Self { + val.0 + } +} + +impl Absorbable for TaggedVec { + fn absorb_into(&self, dest: &mut Vec) { + self.0.absorb_into(dest) + } +} + +impl, const TAG: char> AbsorbableVar + for TaggedVec +{ + fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { + self.0.absorb_into(dest) + } +} + +impl, Y, const TAG: char> AllocVar, F> + for TaggedVec +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let v = f()?; + Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(Self) + } +} + +impl, const TAG: char> CondSelectGadget + for TaggedVec +{ + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.len() != false_value.len() { + return Err(SynthesisError::Unsatisfiable); + } + true_value + .iter() + .zip(false_value.iter()) + .map(|(t, f)| cond.select(t, f)) + .collect::>() + .map(Self) + } +} + +impl, const TAG: char> GR1CSVar for TaggedVec { + type Value = TaggedVec; + + fn cs(&self) -> ConstraintSystemRef { + self.0.cs() + } + + fn value(&self) -> Result { + self.0.value().map(TaggedVec) + } +} diff --git a/crates/fs/src/definitions/variants.rs b/crates/fs/src/definitions/variants.rs new file mode 100644 index 000000000..86ab90581 --- /dev/null +++ b/crates/fs/src/definitions/variants.rs @@ -0,0 +1,55 @@ +use sonobe_primitives::{ + commitments::{GroupBasedCommitment, CommitmentDef}, + traits::CF2, +}; + +use crate::{ + FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, FoldingSchemeOps, + FoldingSchemePartialVerifierGadget, +}; + +pub trait GroupBasedFoldingSchemePrimaryDef: + FoldingSchemeDef< + VC: GroupBasedCommitment, + TranscriptField = <::VC as CommitmentDef>::Scalar, +> +{ + type Gadget: FoldingSchemeDefGadget< + Native = Self, + VC = ::Gadget2, + >; +} + +pub trait GroupBasedFoldingSchemePrimary: + GroupBasedFoldingSchemePrimaryDef> + + FoldingSchemeOps +{ +} + +impl GroupBasedFoldingSchemePrimary for FS where + FS: GroupBasedFoldingSchemePrimaryDef> +{ +} + +pub trait GroupBasedFoldingSchemeSecondaryDef: + FoldingSchemeDef< + VC: GroupBasedCommitment, + TranscriptField = CF2<<::VC as CommitmentDef>::Commitment>, +> +{ + type Gadget: FoldingSchemeDefGadget< + Native = Self, + VC = ::Gadget1, + >; +} + +pub trait GroupBasedFoldingSchemeSecondary: + GroupBasedFoldingSchemeSecondaryDef> + + FoldingSchemeOps +{ +} + +impl GroupBasedFoldingSchemeSecondary for FS where + FS: GroupBasedFoldingSchemeSecondaryDef> +{ +} diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs new file mode 100644 index 000000000..c20833e5c --- /dev/null +++ b/crates/fs/src/definitions/witnesses.rs @@ -0,0 +1,47 @@ +use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; +use ark_std::fmt::Debug; +use sonobe_primitives::{ + arithmetizations::ArithConfig, + commitments::{CommitmentDef, CommitmentDefGadget}, + traits::Dummy, +}; + +use super::utils::TaggedVec; + +pub trait FoldingWitness: Debug { + const N_OPENINGS: usize; + + /// Returns the reference to all openings contained in the witness, each + /// being a tuple of the values being committed to and the randomness. + fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; +} + +pub type PlainWitness = TaggedVec; + +impl Dummy<&A> for PlainWitness { + fn dummy(cfg: &A) -> Self { + vec![V::default(); cfg.n_witnesses()].into() + } +} + +impl FoldingWitness for PlainWitness { + const N_OPENINGS: usize = 0; + + fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { + vec![] + } +} + +pub trait FoldingWitnessVar: + AllocVar + + GR1CSVar> +{ +} + +impl FoldingWitnessVar for T where + T: AllocVar + + GR1CSVar> +{ +} + +pub type PlainWitnessVar = PlainWitness; diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 9c7cd725d..c4716e408 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,475 +1,34 @@ -use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::{ - GR1CSVar, - alloc::{AllocVar, AllocationMode}, - fields::fp::FpVar, - prelude::Boolean, - select::CondSelectGadget, +pub mod definitions; + +pub use self::definitions::{ + FoldingSchemeDef, FoldingSchemeDefGadget, + algorithms::{ + FoldingSchemeDecider, FoldingSchemeKeyGenerator, FoldingSchemeOps, + FoldingSchemePreprocessor, FoldingSchemeProver, FoldingSchemeVerifier, + }, + circuits::{FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget}, + errors::Error, + instances::{FoldingInstance, FoldingInstanceVar, PlainInstance, PlainInstanceVar}, + keys::DeciderKey, + utils::TaggedVec, + variants::{ + GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemePrimaryDef, + GroupBasedFoldingSchemeSecondary, GroupBasedFoldingSchemeSecondaryDef, + }, + witnesses::{FoldingWitness, FoldingWitnessVar, PlainWitness, PlainWitnessVar}, }; -use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{ - borrow::Borrow, - fmt::Debug, - ops::{Deref, DerefMut}, - rand::RngCore, -}; -use sonobe_primitives::{ - arithmetizations::{Arith, ArithConfig}, - circuits::AssignmentsOwned, - commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, - relations::{Relation, WitnessInstanceSampler}, - sumcheck::Error as SumCheckError, - traits::{CF2, Dummy, SonobeField}, - transcripts::{Absorbable, AbsorbableVar, Transcript, TranscriptGadget}, -}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum Error { - #[error(transparent)] - ArithError(#[from] sonobe_primitives::arithmetizations::Error), - #[error(transparent)] - CommitmentError(#[from] sonobe_primitives::commitments::Error), - #[error(transparent)] - SynthesisError(#[from] SynthesisError), - #[error(transparent)] - SumCheckError(#[from] SumCheckError), - #[error("Unsupported use case: {0}")] - Unsupported(String), - #[error("Failed to create domain")] - DomainCreationFailure, - #[error("Indivisible by vanishing polynomial")] - IndivisibleByVanishingPoly, - #[error("Unsatisfied relation: {0}")] - UnsatisfiedRelation(String), - #[error("Invalid public parameters: {0}")] - InvalidPublicParameters(String), -} - -pub trait FoldingWitness: Debug { - const N_OPENINGS: usize; - - /// Returns the reference to all openings contained in the witness, each - /// being a tuple of the values being committed to and the randomness. - fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; -} - -pub trait FoldingInstance: - Clone + Debug + PartialEq + Eq + Absorbable -{ - const N_COMMITMENTS: usize; - - /// Returns the commitments contained in the committed instance. - fn commitments(&self) -> Vec<&VC::Commitment>; - - fn public_inputs(&self) -> &[VC::Scalar]; - - fn public_inputs_mut(&mut self) -> &mut [VC::Scalar]; -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct TaggedVec(pub Vec); - -impl Deref for TaggedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for TaggedVec { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From> for TaggedVec { - fn from(v: Vec) -> Self { - Self(v) - } -} - -impl From> for Vec { - fn from(val: TaggedVec) -> Self { - val.0 - } -} - -impl Absorbable for TaggedVec { - fn absorb_into(&self, dest: &mut Vec) { - self.0.absorb_into(dest) - } -} - -impl, const TAG: char> AbsorbableVar for TaggedVec { - fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { - self.0.absorb_into(dest) - } -} - -impl, Y, const TAG: char> AllocVar, F> - for TaggedVec -{ - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - let v = f()?; - Vec::new_variable(cs, || Ok(&v.borrow()[..]), mode).map(Self) - } -} - -impl, const TAG: char> CondSelectGadget - for TaggedVec -{ - fn conditionally_select( - cond: &Boolean, - true_value: &Self, - false_value: &Self, - ) -> Result { - if true_value.len() != false_value.len() { - return Err(SynthesisError::Unsatisfiable); - } - true_value - .iter() - .zip(false_value.iter()) - .map(|(t, f)| cond.select(t, f)) - .collect::>() - .map(Self) - } -} - -impl, const TAG: char> GR1CSVar for TaggedVec { - type Value = TaggedVec; - - fn cs(&self) -> ConstraintSystemRef { - self.0.cs() - } - - fn value(&self) -> Result { - self.0.value().map(TaggedVec) - } -} - -pub type PlainWitness = TaggedVec; - -impl Dummy<&A> for PlainWitness { - fn dummy(cfg: &A) -> Self { - vec![V::default(); cfg.n_witnesses()].into() - } -} - -impl FoldingWitness for PlainWitness { - const N_OPENINGS: usize = 0; - - fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { - vec![] - } -} - -pub type PlainInstance = TaggedVec; - -impl Dummy<&A> for PlainInstance { - fn dummy(cfg: &A) -> Self { - vec![V::default(); cfg.n_public_inputs()].into() - } -} - -impl FoldingInstance for PlainInstance { - const N_COMMITMENTS: usize = 0; - - fn commitments(&self) -> Vec<&VC::Commitment> { - vec![] - } - - fn public_inputs(&self) -> &[VC::Scalar] { - self - } - - fn public_inputs_mut(&mut self) -> &mut [VC::Scalar] { - self - } -} - -pub trait DeciderKey { - type ProverKey; - type VerifierKey; - type ArithConfig: ArithConfig; - - fn to_pk(&self) -> &Self::ProverKey; - fn to_vk(&self) -> &Self::VerifierKey; - fn to_arith_config(&self) -> &Self::ArithConfig; -} - -pub trait FoldingSchemeDef { - type VC: CommitmentDef; - type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; - type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; - type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; - type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; - type TranscriptField: SonobeField; - type Arith: Arith::ArithConfig>; - type Config; - type PublicParam; - type DeciderKey: DeciderKey - + Clone - + Relation - + Relation - + WitnessInstanceSampler - + WitnessInstanceSampler< - Self::IW, - Self::IU, - Source = AssignmentsOwned<::Scalar>, - Error = Error, - >; - type Challenge; - type Proof: Clone - + for<'a> Dummy<&'a ::Config>; -} - -pub trait FoldingSchemePreprocessor: FoldingSchemeDef { - /// The preprocessing method is a randomized algorithm that takes as input - /// the size bounds of the folding scheme, which are contained in the - /// `config` parameter, and outputs the public parameters. - /// - /// Here, the randomness source is controlled by `rng`. - /// - /// The security parameter is implicitly specified by the size of underlying - /// fields and groups. - fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; -} - -pub trait FoldingSchemeKeyGenerator: FoldingSchemeDef { - /// The key generation method is a deterministic algorithm that takes as - /// input the public parameters `pp` and the constraint system `arith`, and - /// outputs a prover key and a verifier key. - fn generate_keys(pp: Self::PublicParam, arith: Self::Arith) -> Result; -} - -pub trait FoldingSchemeProver: FoldingSchemeDef { - /// The proof generation method is a deterministic algorithm that takes as - /// input the prover key `pk`, the transcript `transcript` between the - /// prover and the verifier, the first witness-instance pair `W`, `U`, the - /// second witness-instance pair `w`, `u`, and outputs the folded witness - /// and instance, the proof, and the (intermediate) randomness. - /// - /// Here, the randomness source is controlled by `transcript`. The returned - /// intermediate randomness is useful for the construction of CycleFold - /// circuits in our CycleFold-based folding-to-IVC compiler. - #[allow(non_snake_case)] - fn prove( - pk: &::ProverKey, - transcript: &mut impl Transcript, - Ws: &[impl Borrow; M], - Us: &[impl Borrow; M], - ws: &[impl Borrow; N], - us: &[impl Borrow; N], - rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; -} - -pub trait FoldingSchemeVerifier: FoldingSchemeDef { - #[allow(non_snake_case)] - fn verify( - vk: &::VerifierKey, - transcript: &mut impl Transcript, - Us: &[impl Borrow; M], - us: &[impl Borrow; N], - proof: &Self::Proof, - ) -> Result; -} - -pub trait FoldingSchemeDecider: FoldingSchemeDef { - #[allow(non_snake_case)] - fn decide_running(dk: &Self::DeciderKey, W: &Self::RW, U: &Self::RU) -> Result<(), Error> { - Relation::::check_relation(dk, W, U) - } - - fn decide_incoming(dk: &Self::DeciderKey, w: &Self::IW, u: &Self::IU) -> Result<(), Error> { - Relation::::check_relation(dk, w, u) - } -} - -impl FoldingSchemeDecider for FS {} - -pub trait FoldingSchemeOps: - FoldingSchemePreprocessor - + FoldingSchemeKeyGenerator - + FoldingSchemeProver - + FoldingSchemeVerifier - + FoldingSchemeDecider -{ -} - -impl FoldingSchemeOps for FS where - FS: FoldingSchemePreprocessor - + FoldingSchemeKeyGenerator - + FoldingSchemeProver - + FoldingSchemeVerifier - + FoldingSchemeDecider -{ -} - -pub trait FoldingWitnessVar: - AllocVar - + GR1CSVar> -{ -} - -impl FoldingWitnessVar for T where - T: AllocVar - + GR1CSVar> -{ -} - -pub trait FoldingInstanceVar: - AllocVar - + GR1CSVar> - + AbsorbableVar - + CondSelectGadget -{ - /// Returns the commitments contained in the committed instance. - fn commitments(&self) -> Vec<&VC::CommitmentVar>; - - fn public_inputs(&self) -> &Vec; - - fn new_witness_with_public_inputs( - cs: impl Into>, - u: &Self::Value, - x: Vec, - ) -> Result; -} - -pub type PlainWitnessVar = PlainWitness; -pub type PlainInstanceVar = PlainInstance; - -impl FoldingInstanceVar for PlainInstanceVar { - fn commitments(&self) -> Vec<&VC::CommitmentVar> { - vec![] - } - - fn public_inputs(&self) -> &Vec { - self - } - - fn new_witness_with_public_inputs( - _cs: impl Into>, - _u: &Self::Value, - x: Vec, - ) -> Result { - Ok(Self(x)) - } -} - -pub trait FoldingSchemeDefGadget { - type Native: FoldingSchemeDef; - - type VC: CommitmentDefGadget::VC>; - type RU: FoldingInstanceVar::RU>; - type IU: FoldingInstanceVar::IU>; - - type VerifierKey; - - type Challenge: AllocVar< - ::Challenge, - ::ConstraintField, - > + GR1CSVar< - ::ConstraintField, - Value = ::Challenge, - >; - type Proof: AllocVar< - ::Proof, - ::ConstraintField, - > + GR1CSVar< - ::ConstraintField, - Value = ::Proof, - >; -} - -pub trait FoldingSchemePartialVerifierGadget: - FoldingSchemeDefGadget> -{ - #[allow(non_snake_case)] - fn verify_hinted( - vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget<::ConstraintField>, - Us: [&Self::RU; M], - us: [&Self::IU; N], - proof: &Self::Proof, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; -} - -pub trait FoldingSchemeFullVerifierGadget: - FoldingSchemePartialVerifierGadget -{ - #[allow(non_snake_case)] - fn verify( - vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget<::ConstraintField>, - Us: [&Self::RU; M], - us: [&Self::IU; N], - proof: &Self::Proof, - ) -> Result; -} - -pub trait GroupBasedFoldingSchemePrimaryDef: - FoldingSchemeDef< - VC: GroupBasedCommitment, - TranscriptField = <::VC as CommitmentDef>::Scalar, -> -{ - type Gadget: FoldingSchemeDefGadget< - Native = Self, - VC = ::Gadget2, - >; -} - -pub trait GroupBasedFoldingSchemePrimary: - GroupBasedFoldingSchemePrimaryDef> - + FoldingSchemeOps -{ -} - -impl GroupBasedFoldingSchemePrimary for FS where - FS: GroupBasedFoldingSchemePrimaryDef> -{ -} - -pub trait GroupBasedFoldingSchemeSecondaryDef: - FoldingSchemeDef< - VC: GroupBasedCommitment, - TranscriptField = CF2<<::VC as CommitmentDef>::Commitment>, -> -{ - type Gadget: FoldingSchemeDefGadget< - Native = Self, - VC = ::Gadget1, - >; -} - -pub trait GroupBasedFoldingSchemeSecondary: - GroupBasedFoldingSchemeSecondaryDef> - + FoldingSchemeOps -{ -} - -impl GroupBasedFoldingSchemeSecondary for FS where - FS: GroupBasedFoldingSchemeSecondaryDef> -{ -} #[cfg(test)] mod tests { - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; use ark_std::{error::Error, rand::Rng, sync::Arc}; use sonobe_primitives::{ circuits::{ArithExtractor, AssignmentsOwned}, + commitments::CommitmentDef, + relations::WitnessInstanceSampler, transcripts::{ + Transcript, griffin::{GriffinParams, sponge::GriffinSponge}, - poseidon::poseidon_canonical_config, }, }; diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 4af38ead6..aff040c1f 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -3,8 +3,8 @@ use ark_relations::gr1cs::{ConstraintSystem, SynthesisError, SynthesisMode}; use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore}; use sonobe_fs::{ DeciderKey, FoldingInstance, FoldingSchemeDef, FoldingSchemeDefGadget, - FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, - GroupBasedFoldingSchemeSecondary, + FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, + GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, From 727ba2d7ecf521e46cfe027e80df131be14f4eea Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 62/99] Clean up --- crates/ivc/src/compilers/cyclefold/circuits.rs | 1 + crates/ivc/src/compilers/cyclefold/mod.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 734943c16..a9f9ca9b3 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -63,6 +63,7 @@ where FC: FCircuit::Scalar>, T: Transcript, { + #[allow(non_snake_case)] pub fn compute_next_state( &self, cs: ConstraintSystemRef, diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index aff040c1f..4494ed24e 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -33,6 +33,7 @@ pub trait FoldingSchemeCycleFoldExt: const N_CYCLEFOLDS: usize; + #[allow(non_snake_case)] fn to_cyclefold_configs( Us: &[impl Borrow; M], us: &[impl Borrow; N], @@ -40,6 +41,7 @@ pub trait FoldingSchemeCycleFoldExt: rho: Self::Challenge, ) -> Vec; + #[allow(non_snake_case)] fn to_cyclefold_inputs( Us: [::RU; M], us: [::IU; N], @@ -178,6 +180,7 @@ where )) } + #[allow(non_snake_case)] fn prove>( Key(dk1, dk2, (hash_config, pp_hash)): &Self::ProverKey, step_circuit: &FC, @@ -267,6 +270,7 @@ where )) } + #[allow(non_snake_case)] fn verify>( Key(dk1, dk2, (hash_config, pp_hash)): &Self::VerifierKey, i: usize, From 461c0ef66411e9eab79185db9be92e90b232a9c2 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 15:50:26 +0800 Subject: [PATCH 63/99] Improve naming and API design --- crates/fs/src/definitions/algorithms.rs | 2 +- crates/fs/src/definitions/circuits.rs | 8 +-- crates/fs/src/definitions/instances.rs | 48 +++++++------ crates/fs/src/definitions/mod.rs | 28 ++++---- crates/fs/src/definitions/utils.rs | 3 +- crates/fs/src/definitions/variants.rs | 24 +++---- crates/fs/src/definitions/witnesses.rs | 22 +++--- crates/fs/src/lib.rs | 6 +- .../ivc/src/compilers/cyclefold/circuits.rs | 68 +++++++++---------- crates/ivc/src/compilers/cyclefold/mod.rs | 44 ++++++------ crates/ivc/src/compilers/mod.rs | 1 - 11 files changed, 116 insertions(+), 138 deletions(-) diff --git a/crates/fs/src/definitions/algorithms.rs b/crates/fs/src/definitions/algorithms.rs index 23642a6bb..607c83d64 100644 --- a/crates/fs/src/definitions/algorithms.rs +++ b/crates/fs/src/definitions/algorithms.rs @@ -1,7 +1,7 @@ use ark_std::{borrow::Borrow, rand::RngCore}; use sonobe_primitives::{relations::Relation, transcripts::Transcript}; -use super::{errors::Error, keys::DeciderKey, FoldingSchemeDef}; +use super::{FoldingSchemeDef, errors::Error, keys::DeciderKey}; pub trait FoldingSchemePreprocessor: FoldingSchemeDef { /// The preprocessing method is a randomized algorithm that takes as input diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs index 1e17fed77..5c127dcd6 100644 --- a/crates/fs/src/definitions/circuits.rs +++ b/crates/fs/src/definitions/circuits.rs @@ -1,9 +1,7 @@ use ark_relations::gr1cs::SynthesisError; use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::TranscriptGadget}; -use super::{ - algorithms::FoldingSchemeOps, FoldingSchemeDefGadget, -}; +use super::{FoldingSchemeDefGadget, algorithms::FoldingSchemeOps}; pub trait FoldingSchemePartialVerifierGadget: FoldingSchemeDefGadget> @@ -11,7 +9,7 @@ pub trait FoldingSchemePartialVerifierGadget: #[allow(non_snake_case)] fn verify_hinted( vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget<::ConstraintField>, + transcript: &mut impl TranscriptGadget<::ConstraintField>, Us: [&Self::RU; M], us: [&Self::IU; N], proof: &Self::Proof, @@ -24,7 +22,7 @@ pub trait FoldingSchemeFullVerifierGadget: #[allow(non_snake_case)] fn verify( vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget<::ConstraintField>, + transcript: &mut impl TranscriptGadget<::ConstraintField>, Us: [&Self::RU; M], us: [&Self::IU; N], proof: &Self::Proof, diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index e54dc487b..f24a44c60 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -1,4 +1,4 @@ -use ark_r1cs_std::{alloc::AllocVar, select::CondSelectGadget, GR1CSVar}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, select::CondSelectGadget}; use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::fmt::Debug; use sonobe_primitives::{ @@ -10,17 +10,15 @@ use sonobe_primitives::{ use super::utils::TaggedVec; -pub trait FoldingInstance: - Clone + Debug + PartialEq + Eq + Absorbable -{ +pub trait FoldingInstance: Clone + Debug + PartialEq + Eq + Absorbable { const N_COMMITMENTS: usize; /// Returns the commitments contained in the committed instance. - fn commitments(&self) -> Vec<&VC::Commitment>; + fn commitments(&self) -> Vec<&CM::Commitment>; - fn public_inputs(&self) -> &[VC::Scalar]; + fn public_inputs(&self) -> &[CM::Scalar]; - fn public_inputs_mut(&mut self) -> &mut [VC::Scalar]; + fn public_inputs_mut(&mut self) -> &mut [CM::Scalar]; } pub type PlainInstance = TaggedVec; @@ -31,53 +29,53 @@ impl Dummy<&A> for PlainInstance { } } -impl FoldingInstance for PlainInstance { +impl FoldingInstance for PlainInstance { const N_COMMITMENTS: usize = 0; - fn commitments(&self) -> Vec<&VC::Commitment> { + fn commitments(&self) -> Vec<&CM::Commitment> { vec![] } - fn public_inputs(&self) -> &[VC::Scalar] { + fn public_inputs(&self) -> &[CM::Scalar] { self } - fn public_inputs_mut(&mut self) -> &mut [VC::Scalar] { + fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { self } } -pub trait FoldingInstanceVar: - AllocVar - + GR1CSVar> - + AbsorbableVar - + CondSelectGadget +pub trait FoldingInstanceVar: + AllocVar + + GR1CSVar> + + AbsorbableVar + + CondSelectGadget { /// Returns the commitments contained in the committed instance. - fn commitments(&self) -> Vec<&VC::CommitmentVar>; + fn commitments(&self) -> Vec<&CM::CommitmentVar>; - fn public_inputs(&self) -> &Vec; + fn public_inputs(&self) -> &Vec; fn new_witness_with_public_inputs( - cs: impl Into>, + cs: impl Into>, u: &Self::Value, - x: Vec, + x: Vec, ) -> Result; } -impl FoldingInstanceVar for PlainInstanceVar { - fn commitments(&self) -> Vec<&VC::CommitmentVar> { +impl FoldingInstanceVar for PlainInstanceVar { + fn commitments(&self) -> Vec<&CM::CommitmentVar> { vec![] } - fn public_inputs(&self) -> &Vec { + fn public_inputs(&self) -> &Vec { self } fn new_witness_with_public_inputs( - _cs: impl Into>, + _cs: impl Into>, _u: &Self::Value, - x: Vec, + x: Vec, ) -> Result { Ok(Self(x)) } diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index d1c13a2f3..77d45952e 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -7,7 +7,7 @@ pub mod utils; pub mod variants; pub mod witnesses; -use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use sonobe_primitives::{ arithmetizations::Arith, circuits::AssignmentsOwned, @@ -24,11 +24,11 @@ use self::{ }; pub trait FoldingSchemeDef { - type VC: CommitmentDef; - type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; - type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; - type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; - type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type CM: CommitmentDef; + type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; type TranscriptField: SonobeField; type Arith: Arith::ArithConfig>; type Config; @@ -41,7 +41,7 @@ pub trait FoldingSchemeDef { + WitnessInstanceSampler< Self::IW, Self::IU, - Source = AssignmentsOwned<::Scalar>, + Source = AssignmentsOwned<::Scalar>, Error = Error, >; type Challenge; @@ -52,24 +52,24 @@ pub trait FoldingSchemeDef { pub trait FoldingSchemeDefGadget { type Native: FoldingSchemeDef; - type VC: CommitmentDefGadget::VC>; - type RU: FoldingInstanceVar::RU>; - type IU: FoldingInstanceVar::IU>; + type CM: CommitmentDefGadget::CM>; + type RU: FoldingInstanceVar::RU>; + type IU: FoldingInstanceVar::IU>; type VerifierKey; type Challenge: AllocVar< ::Challenge, - ::ConstraintField, + ::ConstraintField, > + GR1CSVar< - ::ConstraintField, + ::ConstraintField, Value = ::Challenge, >; type Proof: AllocVar< ::Proof, - ::ConstraintField, + ::ConstraintField, > + GR1CSVar< - ::ConstraintField, + ::ConstraintField, Value = ::Proof, >; } diff --git a/crates/fs/src/definitions/utils.rs b/crates/fs/src/definitions/utils.rs index 8d96e02d9..51e2dd49b 100644 --- a/crates/fs/src/definitions/utils.rs +++ b/crates/fs/src/definitions/utils.rs @@ -1,11 +1,10 @@ - use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ + GR1CSVar, alloc::{AllocVar, AllocationMode}, fields::fp::FpVar, prelude::Boolean, select::CondSelectGadget, - GR1CSVar, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{ diff --git a/crates/fs/src/definitions/variants.rs b/crates/fs/src/definitions/variants.rs index 86ab90581..d8bcae599 100644 --- a/crates/fs/src/definitions/variants.rs +++ b/crates/fs/src/definitions/variants.rs @@ -1,5 +1,5 @@ use sonobe_primitives::{ - commitments::{GroupBasedCommitment, CommitmentDef}, + commitments::{CommitmentDef, GroupBasedCommitment}, traits::CF2, }; @@ -10,14 +10,11 @@ use crate::{ pub trait GroupBasedFoldingSchemePrimaryDef: FoldingSchemeDef< - VC: GroupBasedCommitment, - TranscriptField = <::VC as CommitmentDef>::Scalar, -> + CM: GroupBasedCommitment, + TranscriptField = <::CM as CommitmentDef>::Scalar, + > { - type Gadget: FoldingSchemeDefGadget< - Native = Self, - VC = ::Gadget2, - >; + type Gadget: FoldingSchemeDefGadget::Gadget2>; } pub trait GroupBasedFoldingSchemePrimary: @@ -33,14 +30,11 @@ impl GroupBasedFoldingSchemePrimary fo pub trait GroupBasedFoldingSchemeSecondaryDef: FoldingSchemeDef< - VC: GroupBasedCommitment, - TranscriptField = CF2<<::VC as CommitmentDef>::Commitment>, -> + CM: GroupBasedCommitment, + TranscriptField = CF2<<::CM as CommitmentDef>::Commitment>, + > { - type Gadget: FoldingSchemeDefGadget< - Native = Self, - VC = ::Gadget1, - >; + type Gadget: FoldingSchemeDefGadget::Gadget1>; } pub trait GroupBasedFoldingSchemeSecondary: diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs index c20833e5c..783bd3f1f 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -1,4 +1,4 @@ -use ark_r1cs_std::{alloc::AllocVar, GR1CSVar}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use ark_std::fmt::Debug; use sonobe_primitives::{ arithmetizations::ArithConfig, @@ -8,12 +8,12 @@ use sonobe_primitives::{ use super::utils::TaggedVec; -pub trait FoldingWitness: Debug { +pub trait FoldingWitness: Debug { const N_OPENINGS: usize; /// Returns the reference to all openings contained in the witness, each /// being a tuple of the values being committed to and the randomness. - fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)>; + fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)>; } pub type PlainWitness = TaggedVec; @@ -24,23 +24,23 @@ impl Dummy<&A> for PlainWitness { } } -impl FoldingWitness for PlainWitness { +impl FoldingWitness for PlainWitness { const N_OPENINGS: usize = 0; - fn openings(&self) -> Vec<(&[VC::Scalar], &VC::Randomness)> { + fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)> { vec![] } } -pub trait FoldingWitnessVar: - AllocVar - + GR1CSVar> +pub trait FoldingWitnessVar: + AllocVar + + GR1CSVar> { } -impl FoldingWitnessVar for T where - T: AllocVar - + GR1CSVar> +impl FoldingWitnessVar for T where + T: AllocVar + + GR1CSVar> { } diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index c4716e408..0d92bed27 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -37,12 +37,12 @@ mod tests { #[allow(non_snake_case)] pub fn test_folding_scheme, const M: usize, const N: usize>( config: FS::Config, - circuit: impl ConstraintSynthesizer<::Scalar>, - assignments_vec: Vec::Scalar>>, + circuit: impl ConstraintSynthesizer<::Scalar>, + assignments_vec: Vec::Scalar>>, mut rng: impl Rng, ) -> Result<(), Box> where - FS::Arith: From::Scalar>>, + FS::Arith: From::Scalar>>, { let pp = FS::preprocess(config, &mut rng)?; diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index a9f9ca9b3..b0bdbcb79 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -2,11 +2,9 @@ use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, - boolean::Boolean, convert::ToConstraintFieldGadget, eq::EqGadget, fields::{FieldVar, fp::FpVar}, - groups::CurveVar, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_std::marker::PhantomData; @@ -17,14 +15,10 @@ use sonobe_fs::{ use sonobe_primitives::{ algebra::Val, arithmetizations::Arith, - circuits::{ConstraintSystemExt, FCircuit}, - commitments::{CommitmentDef, CommitmentDefGadget}, - relations::WitnessInstanceSampler, - traits::{CF1, CF2, Dummy, SonobeCurve, SonobeField}, - transcripts::{ - Transcript, TranscriptGadget, - griffin::{GriffinParams, sponge::GriffinSpongeVar}, - }, + circuits::FCircuit, + commitments::CommitmentDef, + traits::{CF2, Dummy, SonobeCurve}, + transcripts::{Transcript, TranscriptGadget}, }; use crate::compilers::cyclefold::FoldingSchemeCycleFoldExt; @@ -45,22 +39,22 @@ pub struct AugmentedCircuit< impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> where FS1: FoldingSchemeCycleFoldExt< - 1, - 1, - Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + 1, + 1, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FS2: GroupBasedFoldingSchemeSecondary< - 1, - 1, - Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + 1, + 1, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, - FC: FCircuit::Scalar>, + FC: FCircuit::Scalar>, T: Transcript, { #[allow(non_snake_case)] @@ -162,22 +156,22 @@ where impl<'a, FS1, FS2, FC, T> ConstraintSynthesizer for AugmentedCircuit<'a, FS1, FS2, FC, T> where FS1: FoldingSchemeCycleFoldExt< - 1, - 1, - Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + 1, + 1, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FS2: GroupBasedFoldingSchemeSecondary< - 1, - 1, - Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + 1, + 1, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, - FC: FCircuit::Scalar>, + FC: FCircuit::Scalar>, T: Transcript, { fn generate_constraints( @@ -233,7 +227,7 @@ pub trait CycleFoldConfig: Sized + Default { } fn verify_point_rlc(&self, cs: ConstraintSystemRef>) - -> Result<(), SynthesisError>; + -> Result<(), SynthesisError>; } #[derive(Debug, Clone, Default)] diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 4494ed24e..fcfe007a8 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,5 +1,5 @@ use ark_ff::Zero; -use ark_relations::gr1cs::{ConstraintSystem, SynthesisError, SynthesisMode}; +use ark_relations::gr1cs::{ConstraintSystem, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore}; use sonobe_fs::{ DeciderKey, FoldingInstance, FoldingSchemeDef, FoldingSchemeDefGadget, @@ -9,14 +9,11 @@ use sonobe_fs::{ use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, arithmetizations::Arith, - circuits::{ArithExtractor, AssignmentsExtractor, ConstraintSystemExt, FCircuit}, - commitments::{CommitmentDef, CommitmentDefGadget}, + circuits::{ArithExtractor, AssignmentsExtractor, FCircuit}, + commitments::CommitmentDef, relations::WitnessInstanceSampler, - traits::{CF1, CF2, Dummy, Inputize, InputizeEmulated, SonobeCurve, SonobeField}, - transcripts::{ - Absorbable, Transcript, - griffin::{GriffinParams, sponge::GriffinSponge}, - }, + traits::{CF1, CF2, Dummy, SonobeCurve}, + transcripts::Transcript, }; use crate::{ @@ -29,7 +26,7 @@ pub mod circuits; pub trait FoldingSchemeCycleFoldExt: GroupBasedFoldingSchemePrimary { - type CFConfig: CycleFoldConfig::Commitment>; + type CFConfig: CycleFoldConfig::Commitment>; const N_CYCLEFOLDS: usize; @@ -52,8 +49,8 @@ pub trait FoldingSchemeCycleFoldExt: Vec< Vec< EmulatedFieldVar< - ::Scalar, - CF2<::Commitment>, + ::Scalar, + CF2<::Commitment>, >, >, >, @@ -98,26 +95,26 @@ pub struct CycleFoldBasedIVC { impl IVC for CycleFoldBasedIVC where FS1: FoldingSchemeCycleFoldExt< - 1, - 1, - Arith: From::Commitment>>>, - Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + 1, + 1, + Arith: From::Commitment>>>, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FS2: GroupBasedFoldingSchemeSecondary< 1, 1, - Arith: From::Commitment>>>, + Arith: From::Commitment>>>, Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, >, - T: Transcript::Commitment>>, + T: Transcript::Commitment>>, { - type Field = ::Scalar; + type Field = ::Scalar; type Config = (FS1::Config, FS2::Config, T::Config); @@ -159,7 +156,6 @@ where arith2_config: arith2.config(), step_circuit, }; - let cs = ArithExtractor::new(); cs.execute_synthesizer(augmented_circuit)?; let new_arith1 = cs.arith::()?; diff --git a/crates/ivc/src/compilers/mod.rs b/crates/ivc/src/compilers/mod.rs index 764a51d10..41e86f249 100644 --- a/crates/ivc/src/compilers/mod.rs +++ b/crates/ivc/src/compilers/mod.rs @@ -1,2 +1 @@ pub mod cyclefold; - From 81a47c669e8c863c741ac8900f283a2c9a6981ce Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:19:13 +0800 Subject: [PATCH 64/99] Fix CI --- crates/fs/Cargo.toml | 3 +++ crates/ivc/Cargo.toml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 9d1860885..0a86a8ee4 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -24,6 +24,9 @@ sonobe-primitives = { workspace = true } ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } ark-pallas = { workspace = true, features = ["curve", "r1cs"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom = { version = "0.2", features = ["js"] } + [features] default = ["parallel"] parallel = [ diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index 5924a988b..74d249ea0 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -22,6 +22,8 @@ sonobe-fs = { workspace = true } ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } ark-grumpkin = { workspace = true, features = ["r1cs"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom = { version = "0.2", features = ["js"] } [features] default = ["parallel"] From 4792dc4efc6aa5ee7721e13bf361cf7a115b8f76 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:44:30 +0800 Subject: [PATCH 65/99] Fix clippy --- crates/fs/src/definitions/algorithms.rs | 2 +- crates/fs/src/definitions/utils.rs | 4 +--- .../ivc/src/compilers/cyclefold/circuits.rs | 3 +-- crates/ivc/src/compilers/cyclefold/mod.rs | 16 +++++++-------- crates/ivc/src/lib.rs | 20 ++++++------------- 5 files changed, 17 insertions(+), 28 deletions(-) diff --git a/crates/fs/src/definitions/algorithms.rs b/crates/fs/src/definitions/algorithms.rs index 607c83d64..8310ffa4a 100644 --- a/crates/fs/src/definitions/algorithms.rs +++ b/crates/fs/src/definitions/algorithms.rs @@ -32,7 +32,7 @@ pub trait FoldingSchemeProver: FoldingSchemeDef /// Here, the randomness source is controlled by `transcript`. The returned /// intermediate randomness is useful for the construction of CycleFold /// circuits in our CycleFold-based folding-to-IVC compiler. - #[allow(non_snake_case)] + #[allow(non_snake_case, clippy::type_complexity)] fn prove( pk: &::ProverKey, transcript: &mut impl Transcript, diff --git a/crates/fs/src/definitions/utils.rs b/crates/fs/src/definitions/utils.rs index 51e2dd49b..6c2017157 100644 --- a/crates/fs/src/definitions/utils.rs +++ b/crates/fs/src/definitions/utils.rs @@ -48,9 +48,7 @@ impl Absorbable for TaggedVec { } } -impl, const TAG: char> AbsorbableVar - for TaggedVec -{ +impl, const TAG: char> AbsorbableVar for TaggedVec { fn absorb_into(&self, dest: &mut Vec>) -> Result<(), SynthesisError> { self.0.absorb_into(dest) } diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index b0bdbcb79..8c44b290e 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -1,4 +1,3 @@ -use ark_ff::{BigInteger, One, PrimeField, Zero}; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, @@ -57,7 +56,7 @@ where FC: FCircuit::Scalar>, T: Transcript, { - #[allow(non_snake_case)] + #[allow(non_snake_case, clippy::too_many_arguments)] pub fn compute_next_state( &self, cs: ConstraintSystemRef, diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index fcfe007a8..7df70056d 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -38,7 +38,7 @@ pub trait FoldingSchemeCycleFoldExt: rho: Self::Challenge, ) -> Vec; - #[allow(non_snake_case)] + #[allow(non_snake_case, clippy::type_complexity)] fn to_cyclefold_inputs( Us: [::RU; M], us: [::IU; N], @@ -95,14 +95,14 @@ pub struct CycleFoldBasedIVC { impl IVC for CycleFoldBasedIVC where FS1: FoldingSchemeCycleFoldExt< - 1, - 1, - Arith: From::Commitment>>>, - Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, - CM: CommitmentDef< - Commitment: SonobeCurve::Scalar>, - >, + 1, + 1, + Arith: From::Commitment>>>, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, + >, FS2: GroupBasedFoldingSchemeSecondary< 1, 1, diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index befd8bf17..6a13c38b3 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -29,11 +29,13 @@ pub trait IVC { fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; + #[allow(clippy::type_complexity)] fn generate_keys>( pp: Self::PublicParam, step_circuit: &FC, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; + #[allow(clippy::type_complexity, clippy::too_many_arguments)] fn prove>( pk: &Self::ProverKey, step_circuit: &FC, @@ -74,7 +76,7 @@ impl<'a, FC: FCircuit, I: IVC> IVCStatefulProver<'a, FC, I> { i: 0, current_state: initial_state.clone(), initial_state, - current_proof: I::Proof::dummy(&pk), + current_proof: I::Proof::dummy(pk), pk, }) } @@ -85,8 +87,8 @@ impl<'a, FC: FCircuit, I: IVC> IVCStatefulProver<'a, FC, I> { rng: impl RngCore, ) -> Result { let (next_state, external_outputs, next_proof) = I::prove( - &self.pk, - &self.step_circuit, + self.pk, + self.step_circuit, self.i, &self.initial_state, &self.current_state, @@ -128,17 +130,7 @@ pub trait Decider { #[cfg(test)] mod tests { - use ark_bn254::{Fr, G1Projective as C1}; - use ark_crypto_primitives::sponge::{CryptographicSponge, poseidon::PoseidonSponge}; - use ark_grumpkin::Projective as C2; - use ark_std::{error::Error, rand::Rng, sync::Arc, test_rng}; - use sonobe_primitives::{ - arithmetizations::Arith, - circuits::utils::CircuitForTest, - commitments::pedersen::{Pedersen, PedersenEmulatedGadget, PedersenGadget}, - traits::Dummy, - transcripts::{Transcript, griffin::GriffinParams, poseidon::poseidon_canonical_config}, - }; + use ark_std::{error::Error, rand::Rng}; use super::*; From 1c6a8b2cac0d8ba449443cbd8d2fae00c74305c4 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 66/99] Declare `wasm-bindgen-test` as dev-dependency --- crates/fs/Cargo.toml | 5 ++++- crates/ivc/Cargo.toml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 0a86a8ee4..772f4e47d 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -27,8 +27,11 @@ ark-pallas = { workspace = true, features = ["curve", "r1cs"] } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] getrandom = { version = "0.2", features = ["js"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +wasm-bindgen-test = { workspace = true } + [features] -default = ["parallel"] +default = [] parallel = [ "sonobe-primitives/parallel", ] diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index 74d249ea0..a57dda5d7 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -25,8 +25,11 @@ ark-grumpkin = { workspace = true, features = ["r1cs"] } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] getrandom = { version = "0.2", features = ["js"] } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +wasm-bindgen-test = { workspace = true } + [features] -default = ["parallel"] +default = [] parallel = [ "sonobe-fs/parallel", ] \ No newline at end of file From 0b6490079d24019b7d783082c1a4fef3ece65941 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:10:33 +0800 Subject: [PATCH 67/99] Add FS & IVC docs --- crates/fs/src/definitions/algorithms.rs | 61 ++++++--- crates/fs/src/definitions/circuits.rs | 32 ++++- crates/fs/src/definitions/errors.rs | 21 ++++ crates/fs/src/definitions/instances.rs | 34 ++++- crates/fs/src/definitions/keys.rs | 14 +++ crates/fs/src/definitions/mod.rs | 79 ++++++++++-- crates/fs/src/definitions/utils.rs | 7 ++ crates/fs/src/definitions/variants.rs | 23 +++- crates/fs/src/definitions/witnesses.rs | 22 +++- crates/fs/src/lib.rs | 27 ++++ .../ivc/src/compilers/cyclefold/circuits.rs | 84 ++++++++----- crates/ivc/src/compilers/cyclefold/mod.rs | 85 ++++++++++--- crates/ivc/src/compilers/mod.rs | 6 + crates/ivc/src/lib.rs | 117 ++++++++++++++++-- 14 files changed, 520 insertions(+), 92 deletions(-) diff --git a/crates/fs/src/definitions/algorithms.rs b/crates/fs/src/definitions/algorithms.rs index 8310ffa4a..a4238a038 100644 --- a/crates/fs/src/definitions/algorithms.rs +++ b/crates/fs/src/definitions/algorithms.rs @@ -1,12 +1,18 @@ +//! Traits that define out-of-circuit widgets for folding scheme algorithms +//! (preprocessing, key generation, proof generation, proof verification, and +//! deciding). + use ark_std::{borrow::Borrow, rand::RngCore}; use sonobe_primitives::{relations::Relation, transcripts::Transcript}; use super::{FoldingSchemeDef, errors::Error, keys::DeciderKey}; +/// [`FoldingSchemePreprocessor`] is the trait for folding scheme preprocessor. pub trait FoldingSchemePreprocessor: FoldingSchemeDef { - /// The preprocessing method is a randomized algorithm that takes as input - /// the size bounds of the folding scheme, which are contained in the - /// `config` parameter, and outputs the public parameters. + /// [`FoldingSchemePreprocessor::preprocess`] defines the preprocessing + /// algorithm, which is a randomized algorithm that takes as input the + /// config / parameterization `config` of the folding scheme (e.g., size + /// bounds of the folding scheme) and outputs the public parameters. /// /// Here, the randomness source is controlled by `rng`. /// @@ -15,23 +21,32 @@ pub trait FoldingSchemePreprocessor: FoldingSchemeDef { fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; } +/// [`FoldingSchemeKeyGenerator`] is the trait for folding scheme key generator. pub trait FoldingSchemeKeyGenerator: FoldingSchemeDef { - /// The key generation method is a deterministic algorithm that takes as - /// input the public parameters `pp` and the constraint system `arith`, and - /// outputs a prover key and a verifier key. + /// [`FoldingSchemeKeyGenerator::generate_keys`] defines the key generation + /// algorithm, which is a deterministic algorithm that takes as input the + /// public parameters `pp` and the arithmetization `arith`, and outputs a + /// prover key and a verifier key. fn generate_keys(pp: Self::PublicParam, arith: Self::Arith) -> Result; } +/// [`FoldingSchemeProver`] is the trait for folding scheme prover. pub trait FoldingSchemeProver: FoldingSchemeDef { - /// The proof generation method is a deterministic algorithm that takes as - /// input the prover key `pk`, the transcript `transcript` between the - /// prover and the verifier, the first witness-instance pair `W`, `U`, the - /// second witness-instance pair `w`, `u`, and outputs the folded witness - /// and instance, the proof, and the (intermediate) randomness. + /// [`FoldingSchemeProver::prove`] defines the proof generation algorithm, + /// which is a (probably) randomized algorithm that takes as input the + /// prover key `pk`, the transcript `transcript` between the prover and the + /// verifier, `M` running witnesses `Ws`, `M` running instances `Us`, `N` + /// incoming witnesses `ws`, and `N` incoming instances `us`, and outputs + /// the folded witness and instance, the proof, and the challenges. + /// + /// Here, although the challenges can usually be derived by `transcript` and + /// thus do not necessarily need to be returned for verification, we still + /// have the prover return them explicitly so that they can be used for the + /// construction of CycleFold circuits in our CycleFold-based folding-to-IVC + /// compiler without re-deriving them from the transcript. /// - /// Here, the randomness source is controlled by `transcript`. The returned - /// intermediate randomness is useful for the construction of CycleFold - /// circuits in our CycleFold-based folding-to-IVC compiler. + /// The prover may further use `rng` as the randomness source, e.g., for + /// the hiding/zero-knowledge property. #[allow(non_snake_case, clippy::type_complexity)] fn prove( pk: &::ProverKey, @@ -44,7 +59,13 @@ pub trait FoldingSchemeProver: FoldingSchemeDef ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; } +/// [`FoldingSchemeVerifier`] is the trait for folding scheme verifier. pub trait FoldingSchemeVerifier: FoldingSchemeDef { + /// [`FoldingSchemeVerifier::verify`] defines the proof verification + /// algorithm, which is a deterministic algorithm that takes as input the + /// verifier key `vk`, the transcript `transcript` between the prover and + /// the verifier, `M` running instances `Us`, `N` incoming instances `us`, + /// and the proof `proof`, and outputs the folded instance. #[allow(non_snake_case)] fn verify( vk: &::VerifierKey, @@ -55,12 +76,23 @@ pub trait FoldingSchemeVerifier: FoldingSchemeDe ) -> Result; } +/// [`FoldingSchemeDecider`] is the trait for folding scheme decider. pub trait FoldingSchemeDecider: FoldingSchemeDef { + /// [`FoldingSchemeDecider::decide_running`] defines the deciding algorithm + /// for running witness-instance pairs, which is a deterministic algorithm + /// that takes as input the decider key `dk`, a running witness `W` and a + /// running instance `U`, and outputs whether the witness-instance pair + /// satisfies the running relation. #[allow(non_snake_case)] fn decide_running(dk: &Self::DeciderKey, W: &Self::RW, U: &Self::RU) -> Result<(), Error> { Relation::::check_relation(dk, W, U) } + /// [`FoldingSchemeDecider::decide_running`] defines the deciding algorithm + /// for incoming witness-instance pairs, which is a deterministic algorithm + /// that takes as input the decider key `dk`, an incoming witness `W` and an + /// incoming instance `U`, and outputs whether the witness-instance pair + /// satisfies the incoming relation. fn decide_incoming(dk: &Self::DeciderKey, w: &Self::IW, u: &Self::IU) -> Result<(), Error> { Relation::::check_relation(dk, w, u) } @@ -68,6 +100,7 @@ pub trait FoldingSchemeDecider: FoldingSchemeDef { impl FoldingSchemeDecider for FS {} +/// [`FoldingSchemeOps`] is a convenience super-trait bundling all algorithms. pub trait FoldingSchemeOps: FoldingSchemePreprocessor + FoldingSchemeKeyGenerator diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs index 5c127dcd6..4b4402d02 100644 --- a/crates/fs/src/definitions/circuits.rs +++ b/crates/fs/src/definitions/circuits.rs @@ -1,11 +1,30 @@ +//! Traits that define in-circuit gadgets for folding scheme algorithms, mainly +//! for proof verification. + use ark_relations::gr1cs::SynthesisError; use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::TranscriptGadget}; use super::{FoldingSchemeDefGadget, algorithms::FoldingSchemeOps}; +/// [`FoldingSchemePartialVerifierGadget`] is the partial in-circuit verifier. +/// +/// For schemes that have circuit-unfriendly parts in their verification, the +/// implementation can choose to only implement this partial verifier gadget and +/// use some other techniques for the remaining verification work. +/// For example, group-based folding schemes can defer the expensive elliptic +/// curve operations on commitments to an external CycleFold circuit. pub trait FoldingSchemePartialVerifierGadget: - FoldingSchemeDefGadget> + FoldingSchemeDefGadget> { + /// [`FoldingSchemePartialVerifierGadget::verify_hinted`] defines the proof + /// verification gadget that matches its out-of-circuit widget + /// [`crate::FoldingSchemeVerifier::verify`]. + /// + /// The implementation is allowed to create hints for the missing parts of + /// the verification that are not performed inside the constraint system, + /// and it is unnecessary to constrain these hints inside the circuit. + /// However, it is the caller's responsibility to ensure that these hints + /// are later verified using other techniques (e.g., CycleFold helper). #[allow(non_snake_case)] fn verify_hinted( vk: &Self::VerifierKey, @@ -16,9 +35,20 @@ pub trait FoldingSchemePartialVerifierGadget: ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; } +/// [`FoldingSchemeFullVerifierGadget`] is the full in-circuit verifier. +/// +/// Extends [`FoldingSchemePartialVerifierGadget`] by performing everything +/// required for proof verification inside the constraint system. pub trait FoldingSchemeFullVerifierGadget: FoldingSchemePartialVerifierGadget { + /// [`FoldingSchemeFullVerifierGadget::verify`] defines the proof + /// verification gadget that matches its out-of-circuit widget + /// [`crate::FoldingSchemeVerifier::verify`]. + /// + /// Unlike [`FoldingSchemePartialVerifierGadget::verify_hinted`], the + /// implementation is expected to perform all necessary verification steps + /// and constrain all required variables inside the circuit. #[allow(non_snake_case)] fn verify( vk: &Self::VerifierKey, diff --git a/crates/fs/src/definitions/errors.rs b/crates/fs/src/definitions/errors.rs index de9edfd35..d721a88dc 100644 --- a/crates/fs/src/definitions/errors.rs +++ b/crates/fs/src/definitions/errors.rs @@ -1,3 +1,5 @@ +//! Error definitions for folding schemes. + use ark_relations::gr1cs::SynthesisError; use sonobe_primitives::{ arithmetizations::Error as ArithError, commitments::Error as CommitmentError, @@ -5,24 +7,43 @@ use sonobe_primitives::{ }; use thiserror::Error; +/// [`Error`] enumerates possible errors during folding scheme operations. #[derive(Debug, Error)] pub enum Error { + /// [`Error::ArithError`] indicates an error from the underlying constraint + /// system. #[error(transparent)] ArithError(#[from] ArithError), + /// [`Error::CommitmentError`] indicates an error from the underlying + /// commitment scheme. #[error(transparent)] CommitmentError(#[from] CommitmentError), + /// [`Error::SynthesisError`] indicates an error during constraint + /// synthesis. #[error(transparent)] SynthesisError(#[from] SynthesisError), + /// [`Error::SumCheckError`] indicates an error from the underlying sumcheck + /// protocol. #[error(transparent)] SumCheckError(#[from] SumCheckError), + /// [`Error::Unsupported`] indicates that a certain use case is not + /// supported. #[error("Unsupported use case: {0}")] Unsupported(String), + /// [`Error::DomainCreationFailure`] indicates a failure in creating + /// evaluation domains. #[error("Failed to create domain")] DomainCreationFailure, + /// [`Error::IndivisibleByVanishingPoly`] indicates that a polynomial is + /// not divisible by the vanishing polynomial of a certain domain. #[error("Indivisible by vanishing polynomial")] IndivisibleByVanishingPoly, + /// [`Error::UnsatisfiedRelation`] indicates that a certain relation is not + /// satisfied. #[error("Unsatisfied relation: {0}")] UnsatisfiedRelation(String), + /// [`Error::InvalidPublicParameters`] indicates that the provided public + /// parameters are invalid. #[error("Invalid public parameters: {0}")] InvalidPublicParameters(String), } diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index f24a44c60..cd431c448 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -1,3 +1,5 @@ +//! Traits and abstractions for folding scheme instances. + use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, select::CondSelectGadget}; use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_std::fmt::Debug; @@ -10,17 +12,36 @@ use sonobe_primitives::{ use super::utils::TaggedVec; +/// [`FoldingInstance`] defines the operations that a folding scheme's instance +/// should support. pub trait FoldingInstance: Clone + Debug + PartialEq + Eq + Absorbable { + /// [`FoldingInstance::N_COMMITMENTS`] defines the number of commitments + /// contained in the instance. const N_COMMITMENTS: usize; - /// Returns the commitments contained in the committed instance. + /// [`FoldingInstance::commitments`] returns the commitments contained in + /// the instance. + // TODO (@winderica): consider the scenario where the instance has multiple + // commitments of different types. fn commitments(&self) -> Vec<&CM::Commitment>; + /// [`FoldingInstance::public_inputs`] returns the reference to the public + /// inputs contained in the instance. fn public_inputs(&self) -> &[CM::Scalar]; + /// [`FoldingInstance::public_inputs_mut`] returns the mutable reference to + /// the public inputs contained in the instance. fn public_inputs_mut(&mut self) -> &mut [CM::Scalar]; } +/// [`PlainInstance`] is a vector of field elements that are the statements / +/// public inputs to a constraint system. +/// We provide this type for folding schemes that support such simple instances, +/// enabling compatibility with the definition of accumulation schemes (i.e., +/// running x plain -> running). +/// +/// To distinguish it from the witness vector, we use a tagged vector with tag +/// `'u'` for it. pub type PlainInstance = TaggedVec; impl Dummy<&A> for PlainInstance { @@ -45,17 +66,24 @@ impl FoldingInstance for PlainInstance { } } +/// [`FoldingInstanceVar`] is the in-circuit variable of [`FoldingInstance`]. pub trait FoldingInstanceVar: AllocVar + GR1CSVar> + AbsorbableVar + CondSelectGadget { - /// Returns the commitments contained in the committed instance. + /// [`FoldingInstanceVar::commitments`] returns the commitments contained in + /// the instance variable. fn commitments(&self) -> Vec<&CM::CommitmentVar>; + /// [`FoldingInstanceVar::public_inputs`] returns the reference to the + /// public inputs contained in the instance variable. fn public_inputs(&self) -> &Vec; + /// [`FoldingInstanceVar::new_witness_with_public_inputs`] allocates a + /// folding instance in the circuit as a witness variable, with the given + /// pre-allocated public inputs. fn new_witness_with_public_inputs( cs: impl Into>, u: &Self::Value, @@ -81,4 +109,6 @@ impl FoldingInstanceVar for PlainInstanceVar = PlainInstance; diff --git a/crates/fs/src/definitions/keys.rs b/crates/fs/src/definitions/keys.rs index 230395e18..a71d7f0b7 100644 --- a/crates/fs/src/definitions/keys.rs +++ b/crates/fs/src/definitions/keys.rs @@ -1,11 +1,25 @@ +//! Traits and abstractions for folding scheme keys. + use sonobe_primitives::arithmetizations::ArithConfig; +/// [`DeciderKey`] defines the information that a folding scheme's decider key +/// should include or provide access to. pub trait DeciderKey { + /// [`DeciderKey::ProverKey`] is the type of the prover key contained in the + /// decider key. type ProverKey; + /// [`DeciderKey::VerifierKey`] is the type of the verifier key contained in + /// the decider key. type VerifierKey; + /// [`DeciderKey::ArithConfig`] is the constraint system configuration + /// associated with the folding scheme. type ArithConfig: ArithConfig; + /// [`DeciderKey::to_pk`] returns the reference to the prover key. fn to_pk(&self) -> &Self::ProverKey; + /// [`DeciderKey::to_vk`] returns the reference to the verifier key. fn to_vk(&self) -> &Self::VerifierKey; + /// [`DeciderKey::to_arith_config`] returns the reference to the constraint + /// system configuration. fn to_arith_config(&self) -> &Self::ArithConfig; } diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index 77d45952e..fe1225413 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -1,3 +1,6 @@ +//! Shared traits for folding schemes, including definitions of related +//! cryptographic objects and algorithms in and out of circuit. + pub mod algorithms; pub mod circuits; pub mod errors; @@ -23,16 +26,56 @@ use self::{ witnesses::FoldingWitness, }; +/// [`FoldingSchemeDef`] provides the core type definitions of a folding scheme. +/// +/// A folding scheme is a cryptographic primitive that folds multiple instances +/// of computations into a single instance while preserving the validity of the +/// computations. +/// More specifically, a folding scheme in general considers two relations `R1` +/// and `R2`. +/// The folding prover folds `M` witness-instance pairs satisfying `R1` and `N` +/// witness-instance pairs satisfying `R2` into a single witness-instance pair +/// satisfying `R1`, along with a proof that the folding was done correctly. +/// The folding verifier folds `M` instances of `R1` and `N` instances of `R2` +/// into a single instance of `R1` under the help of the proof. +/// +/// While folding schemes can be applied in various contexts, we primarily focus +/// on their use in constructing recursive proof systems, and thus we refer to +/// `R1` as the "running relation" and `R2` as the "incoming relation" in the +/// codebase. +/// A witness-instance pair `(W, U)` of type `(RW, RU)` for `R1` is called a +/// "running" witness-instance pair, while a witness-instance pair `(w, u)` of +/// type `(IW, IU)` for `R2` is called an "incoming" witness-instance pair. +/// +/// Different folding schemes support different running and incoming relations, +/// as well as the number of witness-instance pairs that can be folded at once. pub trait FoldingSchemeDef { + /// [`FoldingSchemeDef::CM`] is the commitment scheme used by the folding + /// scheme. type CM: CommitmentDef; + /// [`FoldingSchemeDef::RW`] is the type of running witness. type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + /// [`FoldingSchemeDef::RU`] is the type of running instance. type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + /// [`FoldingSchemeDef::IW`] is the type of incoming witness. type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + /// [`FoldingSchemeDef::IU`] is the type of incoming instance. type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + /// [`FoldingSchemeDef::TranscriptField`] is the field type used in the + /// transcript of the folding scheme. type TranscriptField: SonobeField; + /// [`FoldingSchemeDef::Arith`] is the constraint system supported by the + /// folding scheme. type Arith: Arith::ArithConfig>; + /// [`FoldingSchemeDef::Config`] is the type of configuration required to + /// generate the public parameters of the folding scheme. type Config; + /// [`FoldingSchemeDef::PublicParam`] is the type of public parameters of + /// the folding scheme. type PublicParam; + /// [`FoldingSchemeDef::DeciderKey`] is the type of decider key of the + /// folding scheme, which is used to determine the satisfiability of a + /// witness-instance pair. type DeciderKey: DeciderKey + Clone + Relation @@ -44,32 +87,52 @@ pub trait FoldingSchemeDef { Source = AssignmentsOwned<::Scalar>, Error = Error, >; + /// [`FoldingSchemeDef::Challenge`] is the type of challenge generated + /// during the folding process. type Challenge; + /// [`FoldingSchemeDef::Proof`] is the type of proof generated by the + /// folding prover. type Proof: Clone + for<'a> Dummy<&'a ::Config>; } + +/// [`FoldingSchemeDefGadget`] specifies the in-circuit associated types for a +/// folding scheme gadget. pub trait FoldingSchemeDefGadget { - type Native: FoldingSchemeDef; + /// [`FoldingSchemeDefGadget::Widget`] points to the out-of-circuit folding + /// scheme widget. + type Widget: FoldingSchemeDef; - type CM: CommitmentDefGadget::CM>; - type RU: FoldingInstanceVar::RU>; - type IU: FoldingInstanceVar::IU>; + /// [`FoldingSchemeDefGadget::CM`] is the commitment scheme gadget. + type CM: CommitmentDefGadget::CM>; + /// [`FoldingSchemeDefGadget::RU`] is the type of in-circuit running + /// instance variable. + type RU: FoldingInstanceVar::RU>; + /// [`FoldingSchemeDefGadget::IU`] is the type of in-circuit incoming + /// instance variable. + type IU: FoldingInstanceVar::IU>; + /// [`FoldingSchemeDefGadget::VerifierKey`] is the type of in-circuit + /// verifier key variable. type VerifierKey; + /// [`FoldingSchemeDefGadget::Challenge`] is the type of in-circuit + /// challenge variable. type Challenge: AllocVar< - ::Challenge, + ::Challenge, ::ConstraintField, > + GR1CSVar< ::ConstraintField, - Value = ::Challenge, + Value = ::Challenge, >; + /// [`FoldingSchemeDefGadget::Proof`] is the type of in-circuit proof + /// variable. type Proof: AllocVar< - ::Proof, + ::Proof, ::ConstraintField, > + GR1CSVar< ::ConstraintField, - Value = ::Proof, + Value = ::Proof, >; } diff --git a/crates/fs/src/definitions/utils.rs b/crates/fs/src/definitions/utils.rs index 6c2017157..f4d27acb8 100644 --- a/crates/fs/src/definitions/utils.rs +++ b/crates/fs/src/definitions/utils.rs @@ -1,3 +1,5 @@ +//! Utility types shared across folding scheme definitions. + use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ GR1CSVar, @@ -13,6 +15,11 @@ use ark_std::{ }; use sonobe_primitives::transcripts::{Absorbable, AbsorbableVar}; +/// [`TaggedVec`] is a wrapper around a vector that additionally carries a +/// compile-time `char` tag. +/// +/// This is used to create nominally distinct vector types that are structurally +/// identical. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct TaggedVec(pub Vec); diff --git a/crates/fs/src/definitions/variants.rs b/crates/fs/src/definitions/variants.rs index d8bcae599..b3655f37d 100644 --- a/crates/fs/src/definitions/variants.rs +++ b/crates/fs/src/definitions/variants.rs @@ -1,3 +1,6 @@ +//! Traits that define variants of folding schemes based on different underlying +//! mathematical structures. + use sonobe_primitives::{ commitments::{CommitmentDef, GroupBasedCommitment}, traits::CF2, @@ -8,15 +11,23 @@ use crate::{ FoldingSchemePartialVerifierGadget, }; +/// [`GroupBasedFoldingSchemePrimaryDef`] defines a folding scheme based on +/// groups (elliptic curves), whose transcript field is the scalar field of its +/// group-based commitment scheme. pub trait GroupBasedFoldingSchemePrimaryDef: FoldingSchemeDef< CM: GroupBasedCommitment, TranscriptField = <::CM as CommitmentDef>::Scalar, > { - type Gadget: FoldingSchemeDefGadget::Gadget2>; + /// [`GroupBasedFoldingSchemePrimaryDef::Gadget`] is the in-circuit gadget + /// that defines the folding scheme. + type Gadget: FoldingSchemeDefGadget::Gadget2>; } +/// [`GroupBasedFoldingSchemePrimary`] is a convenience trait that combines the +/// definition [`GroupBasedFoldingSchemePrimaryDef`] and operations +/// [`FoldingSchemeOps`]. pub trait GroupBasedFoldingSchemePrimary: GroupBasedFoldingSchemePrimaryDef> + FoldingSchemeOps @@ -28,15 +39,23 @@ impl GroupBasedFoldingSchemePrimary fo { } +/// [`GroupBasedFoldingSchemeSecondaryDef`] defines a folding scheme based on +/// groups (elliptic curves), whose transcript field is the base field of its +/// group-based commitment scheme. pub trait GroupBasedFoldingSchemeSecondaryDef: FoldingSchemeDef< CM: GroupBasedCommitment, TranscriptField = CF2<<::CM as CommitmentDef>::Commitment>, > { - type Gadget: FoldingSchemeDefGadget::Gadget1>; + /// [`GroupBasedFoldingSchemeSecondaryDef::Gadget`] is the in-circuit gadget + /// that defines the folding scheme. + type Gadget: FoldingSchemeDefGadget::Gadget1>; } +/// [`GroupBasedFoldingSchemeSecondary`] is a convenience trait that combines +/// the definition [`GroupBasedFoldingSchemeSecondaryDef`] and operations +/// [`FoldingSchemeOps`]. pub trait GroupBasedFoldingSchemeSecondary: GroupBasedFoldingSchemeSecondaryDef> + FoldingSchemeOps diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs index 783bd3f1f..95da4b78b 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -1,3 +1,5 @@ +//! Traits and abstractions for folding scheme witnesses. + use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use ark_std::fmt::Debug; use sonobe_primitives::{ @@ -8,14 +10,27 @@ use sonobe_primitives::{ use super::utils::TaggedVec; +/// [`FoldingWitness`] defines the operations that a folding scheme's witness +/// should support. pub trait FoldingWitness: Debug { + /// [`FoldingWitness::N_OPENINGS`] defines the number of openings contained + /// in the witness. const N_OPENINGS: usize; - /// Returns the reference to all openings contained in the witness, each - /// being a tuple of the values being committed to and the randomness. + /// [`FoldingWitness::openings`] returns the reference to all openings + /// contained in the witness, where each opening a tuple of the values being + /// committed to and the randomness used in the commitment. fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)>; } +/// [`PlainWitness`] is a vector of field elements that are the witnesses to a +/// constraint system. +/// We provide this type for folding schemes that support such simple witnesses, +/// enabling compatibility with the definition of accumulation schemes (i.e., +/// running x plain -> running). +/// +/// To distinguish it from the instance vector, we use a tagged vector with tag +/// `'w'` for it. pub type PlainWitness = TaggedVec; impl Dummy<&A> for PlainWitness { @@ -32,6 +47,7 @@ impl FoldingWitness for PlainWitness { } } +/// [`FoldingWitnessVar`] is the in-circuit variable of [`FoldingWitness`]. pub trait FoldingWitnessVar: AllocVar + GR1CSVar> @@ -44,4 +60,6 @@ impl FoldingWitnessVar for T where { } +/// [`PlainWitnessVar`] is the in-circuit variable of [`PlainWitness`]. +// TODO (@winderica): use a different tag? pub type PlainWitnessVar = PlainWitness; diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 0d92bed27..7ee5bf18c 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,3 +1,30 @@ +#![warn(missing_docs)] + +//! Folding scheme definition and implementations. +//! +//! This crate provides the traits for folding schemes, the out-of-circuit +//! widgets and the in-circuit gadgets of their algorithms, and their associated +//! structures (such as keys, instances, and witnesses) in [`definitions`]. +//! +//! Concrete constructions of the following folding schemes are then implemented +//! as submodules: +//! - [`Nova`](nova) +//! - [`HyperNova`](hypernova) +//! - [`Mova`](mova) +//! - [`Ova`](ova) +//! - [`ProtoGalaxy`](protogalaxy) +//! +//! Each scheme module mirrors the same directory layout: +//! - `algorithms/`: Implementations for the following algorithms: +//! - Preprocessing/Setup: [`FoldingSchemePreprocessor`] +//! - Key generation: [`FoldingSchemeKeyGenerator`] +//! - Proof generation: [`FoldingSchemeProver`] +//! - Proof verification: [`FoldingSchemeVerifier`] +//! - `circuits/`: In-circuit (partial / full) gadgets, mainly for verification. +//! - `instances/`: Instance types. +//! - `witnesses/`: Witness types. + + pub mod definitions; pub use self::definitions::{ diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 8c44b290e..18ef8cd04 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -1,3 +1,6 @@ +//! Augmented and CycleFold circuits for the CycleFold-based IVC compiler. + +use ark_ff::PrimeField; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, @@ -6,22 +9,22 @@ use ark_r1cs_std::{ fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_std::marker::PhantomData; use sonobe_fs::{ FoldingInstanceVar, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ - algebra::Val, arithmetizations::Arith, circuits::FCircuit, commitments::CommitmentDef, - traits::{CF2, Dummy, SonobeCurve}, + traits::{Dummy, SonobeCurve}, transcripts::{Transcript, TranscriptGadget}, }; use crate::compilers::cyclefold::FoldingSchemeCycleFoldExt; +/// [`AugmentedCircuit`] defines an augmented version of the user's step circuit +/// which additionally verifies the folding proofs in-circuit. pub struct AugmentedCircuit< 'a, FS1: GroupBasedFoldingSchemePrimary<1, 1>, @@ -29,10 +32,10 @@ pub struct AugmentedCircuit< FC: FCircuit, T: Transcript, > { - pub hash_config: T::Config, - pub arith1_config: &'a ::Config, - pub arith2_config: &'a ::Config, - pub step_circuit: &'a FC, + pub(super) hash_config: T::Config, + pub(super) arith1_config: &'a ::Config, + pub(super) arith2_config: &'a ::Config, + pub(super) step_circuit: &'a FC, } impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> @@ -56,6 +59,9 @@ where FC: FCircuit::Scalar>, T: Transcript, { + /// [`AugmentedCircuit::compute_next_state`] invokes the step circuit on the + /// current state and external inputs to compute the next state and external + /// outputs, and it additionally verifies the folding proofs in-circuit. #[allow(non_snake_case, clippy::too_many_arguments)] pub fn compute_next_state( &self, @@ -95,6 +101,9 @@ where let cf_U = AllocVar::new_witness(cs.clone(), || Ok(cf_U))?; let cf_proofs = Vec::new_witness(cs.clone(), || Ok(cf_proofs))?; + // 1. Fold primary instances. + // 1.a. Derive the public input to the primary (augmented) circuit in + // the `i-1`-th step, which is `u.x = H(i, z_0, z_i, U, cf_U)`. let u_x = sponge .clone() .add(&i)? @@ -103,26 +112,47 @@ where .add(&U)? .add(&cf_U)? .get_field_element()?; + // 1.b. Construct the incoming instance `u` representing the `i-1`-th + // execution of primary (augmented) circuit with the derived public + // input. let u = FoldingInstanceVar::new_witness_with_public_inputs(cs.clone(), u, vec![u_x])?; + // 1.c. Fold the primary running instance `U` and incoming instance `u` + // using the provided proof to obtain the next running instance + // `UU`. let (UU, rho) = FS1::Gadget::verify_hinted(&(), &mut transcript, [&U], [&u], &proof)?; + // 1.d. If this is the base case (`i = 0`), then we should instead use + // the dummy running instance as the next running instance. let actual_UU = is_basecase.select(&U_dummy, &UU)?; + // 2. Fold secondary instances. let mut cf_UU = cf_U; for ((cf_u, cf_u_x), cf_proof) in cf_us .iter() + // 2.a. Derive the public inputs to the secondary (CycleFold) + // circuits in the `i`-th step, which are obtained by calling + // the implementation of `FoldingSchemeCycleFoldExt`. .zip(FS1::to_cyclefold_inputs([U], [u], UU, proof, rho)?) .zip(&cf_proofs) { + // 2.b. Construct the incoming instance `cf_u` representing the + // corresponding execution of secondary (CycleFold) circuit + // with the derived public inputs. let cf_u = FoldingInstanceVar::new_witness_with_public_inputs(cs.clone(), cf_u, cf_u_x)?; + // 2.c. Fold the secondary incoming instance `cf_u` into the running + // instance `cf_UU` using the provided proof. cf_UU = FS2::Gadget::verify(&(), &mut transcript, [&cf_UU], [&cf_u], cf_proof)?; } + // 2.d. If this is the base case (`i = 0`), then we should instead use + // the dummy running instance as the next running instance. let actual_cf_UU = is_basecase.select(&cf_U_dummy, &cf_UU)?; + // 3. Update state by invoking the step circuit. let (next_state, external_outputs) = self.step_circuit .generate_step_constraints(i, current_state, external_inputs)?; + // 4. Compute public input `uu.x = H(i+1, z_0, z_{i+1}, UU, cf_UU)`. let uu_x = sponge .clone() .add(&ii)? @@ -195,21 +225,18 @@ where } } -/// [`CycleFoldConfig`] controls the behavior of [`CycleFoldCircuit`]. -/// -/// Looking ahead, the circuit computes the random linear combination of points, -/// which is essentially done by iteratively computing `P = (P + p_i) * r_i`, -/// where `P` is the folded point, `p_i` is the input point, and `r_i` is the -/// randomness. -pub trait CycleFoldConfig: Sized + Default { - type C: SonobeCurve; - - /// `mark_point_as_public` marks a point as public. +/// [`CycleFoldCircuit`] is the trait describing the deferred verification of +/// the folding proofs which is now expressed as a circuit on the secondary +/// curve. +pub trait CycleFoldCircuit: Sized + Default { + /// [`CycleFoldCircuit::mark_point_as_public`] marks a point as public. /// /// The final vector of public inputs is shorter than the result of calling /// [`AllocVar::new_input`], because we only need the x and y coordinates of /// the point, but the `infinity` flag is not necessary. - fn mark_point_as_public(point: &::Var) -> Result<(), SynthesisError> { + fn mark_point_as_public>( + point: &V, + ) -> Result<(), SynthesisError> { for x in &point.to_constraint_field()?[..2] { // This line "converts" `x` from a witness to a public input. // Instead of directly modifying the constraint system, we explicitly @@ -225,20 +252,9 @@ pub trait CycleFoldConfig: Sized + Default { Ok(()) } - fn verify_point_rlc(&self, cs: ConstraintSystemRef>) - -> Result<(), SynthesisError>; -} - -#[derive(Debug, Clone, Default)] -pub struct CycleFoldCircuit { - _cfg: PhantomData, -} - -impl ConstraintSynthesizer> for CycleFoldCircuit { - fn generate_constraints( - self, - cs: ConstraintSystemRef>, - ) -> Result<(), SynthesisError> { - Cfg::default().verify_point_rlc(cs) - } + /// [`CycleFoldCircuit::verify_point_rlc`] verifies the deferred folding + /// proof in-circuit on the secondary curve, which is done by checking the + /// random linear combination of the commitments contained in the folding + /// instances. + fn verify_point_rlc(&self, cs: ConstraintSystemRef) -> Result<(), SynthesisError>; } diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 7df70056d..6dea6c308 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,3 +1,9 @@ +//! Implementation of the CycleFold-based IVC compiler. +//! +//! It turns any compatible folding scheme into a full IVC scheme by running the +//! primary circuit on one curve and a "CycleFold" circuit on the secondary +//! curve to handle emulated elliptic curve operations. + use ark_ff::Zero; use ark_relations::gr1cs::{ConstraintSystem, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore}; @@ -18,26 +24,38 @@ use sonobe_primitives::{ use crate::{ Error, IVC, - compilers::cyclefold::circuits::{AugmentedCircuit, CycleFoldCircuit, CycleFoldConfig}, + compilers::cyclefold::circuits::{AugmentedCircuit, CycleFoldCircuit}, }; pub mod circuits; +/// [`FoldingSchemeCycleFoldExt`] is the extension trait that a folding scheme +/// must implement to be used with the CycleFold compiler. pub trait FoldingSchemeCycleFoldExt: GroupBasedFoldingSchemePrimary { - type CFConfig: CycleFoldConfig::Commitment>; + /// [`FoldingSchemeCycleFoldExt::CFCircuit`] is the CycleFold circuit type + /// associated with the folding scheme. + type CFCircuit: CycleFoldCircuit::Commitment>>; + /// [`FoldingSchemeCycleFoldExt::N_CYCLEFOLDS`] specifies how many CycleFold + /// operations are needed to verify the primary folding scheme's proof. const N_CYCLEFOLDS: usize; + /// [`FoldingSchemeCycleFoldExt::to_cyclefold_circuits`] creates CycleFold + /// circuits for verifying the point RLCs needed by the folding scheme. #[allow(non_snake_case)] - fn to_cyclefold_configs( + fn to_cyclefold_circuits( Us: &[impl Borrow; M], us: &[impl Borrow; N], proof: &Self::Proof, rho: Self::Challenge, - ) -> Vec; + ) -> Vec; + /// [`FoldingSchemeCycleFoldExt::to_cyclefold_inputs`] computes the inputs + /// to CycleFold circuits. + /// + /// This will be called by the augmented circuit on the primary curve. #[allow(non_snake_case, clippy::type_complexity)] fn to_cyclefold_inputs( Us: [::RU; M], @@ -58,12 +76,14 @@ pub trait FoldingSchemeCycleFoldExt: >; } +/// [`Key`] is the prover / verifier key for the CycleFold-based IVC scheme. pub struct Key( pub FS1::DeciderKey, pub FS2::DeciderKey, pub T, ); +/// [`Proof`] is the proof produced by the CycleFold compiler. pub struct Proof( pub FS1::RW, pub FS1::RU, @@ -88,6 +108,16 @@ impl Dummy<&Key> f } } +/// [`CycleFoldBasedIVC`] is the main implementation of the IVC compiler based +/// on CycleFold. +/// +/// We consider two folding schemes `FS1` and `FS2`, where `FS1` is the folding +/// scheme on the primary curve and `FS2` is the folding scheme on the secondary +/// curve. +/// The user's step circuit is proven using `FS1`, and part of the verification +/// of `FS1`'s proof is offloaded to `FS2` using CycleFold. +/// +/// `T` is the transcript type used by the IVC prover and verifier. pub struct CycleFoldBasedIVC { _d: PhantomData<(FS1, FS2, T)>, } @@ -98,6 +128,10 @@ where 1, 1, Arith: From::Commitment>>>, + // TODO (@winderica): + // All folding schemes we currently support have an empty verifier + // key, so I used `()` here, but this should be generalized in the + // future. Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, CM: CommitmentDef< Commitment: SonobeCurve::Scalar>, @@ -141,24 +175,35 @@ where (pp1, pp2, hash_config): Self::PublicParam, step_circuit: &FC, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error> { - let cyclefold_circuit = CycleFoldCircuit::::default(); - - let cs = ArithExtractor::new(); - cs.execute_synthesizer(cyclefold_circuit)?; - let arith2 = cs.arith::()?; + // Run the CycleFold circuit to extract the arithmetization on the + // secondary curve. + let arith2 = { + let cs = ArithExtractor::new(); + cs.execute_fn(|cs| FS1::CFCircuit::default().verify_point_rlc(cs))?; + cs.arith::()? + }; + // The augmented circuit depends on the configuration of itself. + // For instance, we are not aware of the number of constraints in the + // augmented circuit until we fix `arith1_config`, which requires us to + // provide the number of constraints in the augmented circuit. + // + // To break this circular dependency, we use a fixed-point iteration + // where we start from a default arithmetization and repeatedly update + // it until its configuration stabilizes. let mut arith1 = FS1::Arith::default(); loop { - let augmented_circuit = AugmentedCircuit:: { - hash_config: hash_config.clone(), - arith1_config: arith1.config(), - arith2_config: arith2.config(), - step_circuit, + let new_arith1 = { + let cs = ArithExtractor::new(); + cs.execute_synthesizer(AugmentedCircuit:: { + hash_config: hash_config.clone(), + arith1_config: arith1.config(), + arith2_config: arith2.config(), + step_circuit, + })?; + cs.arith::()? }; - let cs = ArithExtractor::new(); - cs.execute_synthesizer(augmented_circuit)?; - let new_arith1 = cs.arith::()?; if new_arith1.config() == arith1.config() { break; } @@ -219,10 +264,10 @@ where &mut rng, )?; - let cf_configs = FS1::to_cyclefold_configs(&[U], &[u], &proof, challenge); - for (i, cfg) in cf_configs.iter().enumerate() { + let cf_circuits = FS1::to_cyclefold_circuits(&[U], &[u], &proof, challenge); + for (i, cf_circuit) in cf_circuits.into_iter().enumerate() { let cs = AssignmentsExtractor::new(); - cs.execute_fn(|cs| cfg.verify_point_rlc(cs))?; + cs.execute_fn(|cs| cf_circuit.verify_point_rlc(cs))?; let (cf_w, cf_u) = dk2.sample(cs.assignments()?, &mut rng)?; diff --git a/crates/ivc/src/compilers/mod.rs b/crates/ivc/src/compilers/mod.rs index 41e86f249..80e579802 100644 --- a/crates/ivc/src/compilers/mod.rs +++ b/crates/ivc/src/compilers/mod.rs @@ -1 +1,7 @@ +//! Compilers that transform a folding scheme into a full IVC scheme. +//! +//! We currently provide a compiler based on CycleFold, and in the future there +//! may be other compilers such as the naive one (on a single curve) which fits +//! well with hash-based folding schemes and the two curves one. + pub mod cyclefold; diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 6a13c38b3..790b4f534 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -1,40 +1,106 @@ +#![warn(missing_docs)] + +//! Incremental Verifiable Computation (IVC) abstractions. +//! +//! This crate provides the [`IVC`] trait, which describes the common +//! interface for all IVC constructions, and [compilers] that turn a folding +//! scheme into a full IVC scheme. + use ark_ff::PrimeField; use ark_relations::gr1cs::SynthesisError; use ark_std::rand::RngCore; -use sonobe_primitives::{circuits::FCircuit, traits::Dummy}; +use sonobe_fs::Error as FoldingError; +use sonobe_primitives::{arithmetizations::Error as ArithError, circuits::FCircuit, traits::Dummy}; use thiserror::Error; pub mod compilers; +/// [`Error`] enumerates possible errors during the IVC operations. #[derive(Debug, Error)] pub enum Error { + /// [`Error::ArithError`] indicates an error from the underlying constraint + /// system. #[error(transparent)] - ArithError(#[from] sonobe_primitives::arithmetizations::Error), + ArithError(#[from] ArithError), + /// [`Error::FoldingError`] indicates an error from the underlying folding + /// scheme. #[error(transparent)] - FoldingError(#[from] sonobe_fs::Error), + FoldingError(#[from] FoldingError), + /// [`Error::SynthesisError`] indicates an error during constraint + /// synthesis. #[error(transparent)] SynthesisError(#[from] SynthesisError), + /// [`Error::IVCVerificationFail`] indicates that the IVC verification has + /// failed. #[error("IVC verification failed")] IVCVerificationFail, } +/// [`IVC`] defines the interface of Incremental Verifiable Computation schemes. +/// It follows the general definition of proof/argument systems, with +/// preprocessing, key generation, proving, and verification algorithms. pub trait IVC { + /// [`IVC::Field`] defines the field over which the IVC scheme operates. type Field: PrimeField; + /// [`IVC::Config`] defines the configuration (e.g., the size of public + /// parameters) for the IVC scheme. type Config; + /// [`IVC::PublicParam`] defines the public parameters produced by + /// preprocessing. type PublicParam; + /// [`IVC::ProverKey`] defines the prover key type for the IVC scheme. + /// We parameterize it by the step circuit type `FC`, so that a prover key + /// for one step circuit cannot be used for another step circuit. type ProverKey; + /// [`IVC::VerifierKey`] defines the verifier key type for the IVC scheme. + /// We parameterize it by the step circuit type `FC`, so that a verifier key + /// for one step circuit cannot be used for another step circuit. type VerifierKey; + /// [`IVC::Proof`] defines the proof type for the IVC scheme. + /// We parameterize it by the step circuit type `FC`, so that a proof for + /// one step circuit cannot be used for another step circuit. type Proof: for<'a> Dummy<&'a Self::ProverKey>; + /// [`IVC::preprocess`] defines the preprocessing algorithm, which is a + /// randomized algorithm that takes as input the config / parameterization + /// `config` of the IVC scheme and outputs the public parameters. + /// + /// Here, the randomness source is controlled by `rng`. + /// + /// The security parameter is implicitly specified by the size of underlying + /// fields and groups. + /// + /// This is usually called once for the given configuration and can be + /// reused for generating multiple keys for different step circuits, as long + /// as the step circuits conform to the configuration. fn preprocess(config: Self::Config, rng: impl RngCore) -> Result; + /// [`IVC::generate_keys`] defines the key generation algorithm, which is a + /// deterministic algorithm that takes as input the public parameters `pp` + /// and the step circuit `step_circuit`, and outputs a prover key and a + /// verifier key. #[allow(clippy::type_complexity)] fn generate_keys>( pp: Self::PublicParam, step_circuit: &FC, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; + /// [`IVC::prove`] defines the proof updating algorithm, which is a + /// (probably) randomized algorithm that takes as input the prover key `pk`, + /// the step circuit `step_circuit`, the current step `i`, the initial state + /// `initial_state`, the current state `current_state`, the external inputs + /// `external_inputs`, and the current proof `current_proof`. + /// It executes the step circuit on the current state and external inputs, + /// and outputs its returned next state and external outputs, along with the + /// new proof. + /// + /// Here, `current_proof` attests that `current_state` is correctly derived + /// from `initial_state` after `i` steps of executing `step_circuit`, and + /// the returned next proof attests that the next state is correctly derived + /// from `initial_state` after `i+1` steps with the given `external_inputs`. + /// + /// The prover may further use `rng` as the randomness source. #[allow(clippy::type_complexity, clippy::too_many_arguments)] fn prove>( pk: &Self::ProverKey, @@ -47,6 +113,11 @@ pub trait IVC { rng: impl RngCore, ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error>; + /// [`IVC::verify`] defines the proof verification algorithm, which is a + /// deterministic algorithm that takes as input the verifier key `vk`, the + /// current step `i`, the initial state `initial_state`, the current state + /// `current_state`, and the proof `proof`, and outputs `Ok(())` if the + /// proof is valid, or an error otherwise. fn verify>( vk: &Self::VerifierKey, i: usize, @@ -56,16 +127,23 @@ pub trait IVC { ) -> Result<(), Error>; } +/// [`IVCStatefulProver`] is a convenience struct that implements a stateful IVC +/// prover who maintains running state across iterations, so that the user does +/// not need to manually track and pass in the current state and proof at each +/// step. pub struct IVCStatefulProver<'a, FC: FCircuit, I: IVC> { - pub pk: &'a I::ProverKey, - pub step_circuit: &'a FC, - pub i: usize, - pub initial_state: FC::State, - pub current_state: FC::State, - pub current_proof: I::Proof, + pk: &'a I::ProverKey, + step_circuit: &'a FC, + i: usize, + initial_state: FC::State, + current_state: FC::State, + current_proof: I::Proof, } impl<'a, FC: FCircuit, I: IVC> IVCStatefulProver<'a, FC, I> { + /// [`IVCStatefulProver::new`] creates a new stateful IVC prover with the + /// given prover key `pk`, step circuit `step_circuit`, and initial state + /// `initial_state`. pub fn new( pk: &'a I::ProverKey, step_circuit: &'a FC, @@ -81,6 +159,8 @@ impl<'a, FC: FCircuit, I: IVC> IVCStatefulProver<'a, FC, I> { }) } + /// [`IVCStatefulProver::prove_step`] performs one step of proving, updating + /// the internal state and proof, and returning the external outputs. pub fn prove_step( &mut self, external_inputs: FC::ExternalInputs, @@ -103,20 +183,37 @@ impl<'a, FC: FCircuit, I: IVC> IVCStatefulProver<'a, FC, I> { } } +/// [`Decider`] defines a decider / proof-compression SNARK, which produces a +/// final succinct zero-knowledge proof from an IVC proof. +// TODO (@winderica): Still WIP pub trait Decider { + /// [`Decider::IVC`] defines the underlying IVC scheme that the decider + /// compiles. type IVC: IVC; + /// [`Decider::ProverKey`] defines the prover key type for the decider. type ProverKey; + /// [`Decider::VerifierKey`] defines the verifier key type for the decider. type VerifierKey; + /// [`Decider::Instance`] defines the instance type for the decider. type Instance; + /// [`Decider::Witness`] defines the witness type for the decider. type Witness; + /// [`Decider::Proof`] defines the proof type for the decider. type Proof; + /// [`Decider::preprocess_and_generate_keys`] preprocesses the IVC prover + /// key `ivc_pk` and generates the decider's prover key and verifier key. + /// + /// This can be seen as a SNARK with circuit-specific setup. + // TODO (@winderica): consider universal/transparent setup fn preprocess_and_generate_keys( ivc_pk: &::ProverKey, rng: impl RngCore, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; + /// [`Decider::prove`] generates a decider proof from the given IVC proof + /// and instance/witness. fn prove( pk: &Self::ProverKey, w: &Self::Witness, @@ -124,6 +221,8 @@ pub trait Decider { rng: impl RngCore, ) -> Result; + /// [`Decider::verify`] verifies the decider proof against the given + /// instance. fn verify(vk: &Self::VerifierKey, x: &Self::Instance, proof: &Self::Proof) -> Result<(), Error>; } From 3fb63b5b3d42575709ebe65046fea2767290110c Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:48:16 +0800 Subject: [PATCH 68/99] Fmt --- crates/fs/src/definitions/circuits.rs | 2 +- crates/fs/src/definitions/mod.rs | 1 - crates/fs/src/lib.rs | 1 - crates/ivc/src/compilers/cyclefold/mod.rs | 2 +- crates/ivc/src/compilers/mod.rs | 2 +- 5 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs index 4b4402d02..68d1be5f5 100644 --- a/crates/fs/src/definitions/circuits.rs +++ b/crates/fs/src/definitions/circuits.rs @@ -45,7 +45,7 @@ pub trait FoldingSchemeFullVerifierGadget: /// [`FoldingSchemeFullVerifierGadget::verify`] defines the proof /// verification gadget that matches its out-of-circuit widget /// [`crate::FoldingSchemeVerifier::verify`]. - /// + /// /// Unlike [`FoldingSchemePartialVerifierGadget::verify_hinted`], the /// implementation is expected to perform all necessary verification steps /// and constrain all required variables inside the circuit. diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index fe1225413..38a737027 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -96,7 +96,6 @@ pub trait FoldingSchemeDef { + for<'a> Dummy<&'a ::Config>; } - /// [`FoldingSchemeDefGadget`] specifies the in-circuit associated types for a /// folding scheme gadget. pub trait FoldingSchemeDefGadget { diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 7ee5bf18c..abfe146bc 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -24,7 +24,6 @@ //! - `instances/`: Instance types. //! - `witnesses/`: Witness types. - pub mod definitions; pub use self::definitions::{ diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 6dea6c308..b48b66976 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -54,7 +54,7 @@ pub trait FoldingSchemeCycleFoldExt: /// [`FoldingSchemeCycleFoldExt::to_cyclefold_inputs`] computes the inputs /// to CycleFold circuits. - /// + /// /// This will be called by the augmented circuit on the primary curve. #[allow(non_snake_case, clippy::type_complexity)] fn to_cyclefold_inputs( diff --git a/crates/ivc/src/compilers/mod.rs b/crates/ivc/src/compilers/mod.rs index 80e579802..95113ac8c 100644 --- a/crates/ivc/src/compilers/mod.rs +++ b/crates/ivc/src/compilers/mod.rs @@ -1,5 +1,5 @@ //! Compilers that transform a folding scheme into a full IVC scheme. -//! +//! //! We currently provide a compiler based on CycleFold, and in the future there //! may be other compilers such as the naive one (on a single curve) which fits //! well with hash-based folding schemes and the two curves one. From a692460cd201832494c866179f11e332033ca91d Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 69/99] Refactor: start porting Nova --- crates/fs/src/lib.rs | 1 + crates/fs/src/nova/instance.rs | 89 +++++++++ crates/fs/src/nova/mod.rs | 318 +++++++++++++++++++++++++++++++++ crates/fs/src/nova/witness.rs | 53 ++++++ 4 files changed, 461 insertions(+) create mode 100644 crates/fs/src/nova/instance.rs create mode 100644 crates/fs/src/nova/mod.rs create mode 100644 crates/fs/src/nova/witness.rs diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index abfe146bc..3c5b7624a 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -24,6 +24,7 @@ //! - `instances/`: Instance types. //! - `witnesses/`: Witness types. +pub mod nova; pub mod definitions; pub use self::definitions::{ diff --git a/crates/fs/src/nova/instance.rs b/crates/fs/src/nova/instance.rs new file mode 100644 index 000000000..ec832215a --- /dev/null +++ b/crates/fs/src/nova/instance.rs @@ -0,0 +1,89 @@ +use ark_ff::PrimeField; +use sonobe_primitives::{ + arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy, + transcripts::Absorbable, +}; + +use crate::FoldingInstance; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RunningInstance { + pub cm_e: CM::Commitment, + pub u: CM::Scalar, + pub cm_w: CM::Commitment, + pub x: Vec, +} + +impl FoldingInstance for RunningInstance { + const N_COMMITMENTS: usize = 2; + + fn commitments(&self) -> Vec<&CM::Commitment> { + vec![&self.cm_e, &self.cm_w] + } + + fn public_inputs(&self) -> &[CM::Scalar] { + &self.x + } + + fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { + &mut self.x + } +} + +impl Dummy<&Cfg> for RunningInstance { + fn dummy(cfg: &Cfg) -> Self { + Self { + cm_e: Default::default(), + u: Default::default(), + cm_w: Default::default(), + x: vec![Default::default(); cfg.n_public_inputs()], + } + } +} + +impl Absorbable for RunningInstance { + fn absorb_into(&self, dest: &mut Vec) { + self.u.absorb_into(dest); + self.x.absorb_into(dest); + self.cm_e.absorb_into(dest); + self.cm_w.absorb_into(dest); + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IncomingInstance { + pub cm_w: CM::Commitment, + pub x: Vec, +} + +impl FoldingInstance for IncomingInstance { + const N_COMMITMENTS: usize = 1; + + fn commitments(&self) -> Vec<&CM::Commitment> { + vec![&self.cm_w] + } + + fn public_inputs(&self) -> &[CM::Scalar] { + &self.x + } + + fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { + &mut self.x + } +} + +impl Dummy<&Cfg> for IncomingInstance { + fn dummy(cfg: &Cfg) -> Self { + Self { + cm_w: Default::default(), + x: vec![Default::default(); cfg.n_public_inputs()], + } + } +} + +impl Absorbable for IncomingInstance { + fn absorb_into(&self, dest: &mut Vec) { + self.x.absorb_into(dest); + self.cm_w.absorb_into(dest); + } +} diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs new file mode 100644 index 000000000..f47e182cb --- /dev/null +++ b/crates/fs/src/nova/mod.rs @@ -0,0 +1,318 @@ +use ark_ff::{BigInteger, Field, One, PrimeField}; +use ark_std::{ + UniformRand, + borrow::Borrow, + cfg_into_iter, cfg_iter, + marker::PhantomData, + ops::Mul, + rand::{RngCore, rngs::mock::StepRng}, + sync::Arc, +}; +use instance::{IncomingInstance as IU, RunningInstance as RU}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use sonobe_primitives::{ + arithmetizations::{ + Arith, ArithConfig, ArithRelation, + r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, + }, + circuits::AssignmentsOwned, + commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, + relations::{Relation, WitnessInstanceSampler}, + traits::{CF1, SonobeCurve, SonobeField}, + transcripts::Transcript, +}; +use witness::{IncomingWitness as IW, RunningWitness as RW}; + +use crate::{ + DeciderKey, Error, FoldingSchemeDecider, FoldingSchemeDef, FoldingSchemeKeyGenerator, + FoldingSchemePreprocessor, FoldingSchemeProver, FoldingSchemeVerifier, +}; + +pub mod instance; +pub mod witness; + +#[derive(Clone)] +pub struct NovaKey { + arith: Arc, + ck: Arc, +} + +impl DeciderKey for NovaKey { + type ProverKey = Self; + type VerifierKey = (); + type ArithConfig = A::Config; + + fn to_pk(&self) -> &Self::ProverKey { + self + } + + fn to_vk(&self) -> &Self::VerifierKey { + &() + } + + fn to_arith_config(&self) -> &Self::ArithConfig { + self.arith.config() + } +} + +impl Relation, RU> for NovaKey +where + A: for<'a> ArithRelation, RelaxedInstance<&'a [CM::Scalar]>>, + CM: CommitmentOps, +{ + type Error = Error; + + fn check_relation(&self, w: &RW, u: &RU) -> Result<(), Self::Error> { + self.arith.check_relation( + &RelaxedWitness { w: &w.w, e: &w.e }, + &RelaxedInstance { x: &u.x, u: &u.u }, + )?; + CM::open(&self.ck, &w.w, &w.r_w, &u.cm_w)?; + CM::open(&self.ck, &w.e, &w.r_e, &u.cm_e)?; + Ok(()) + } +} + +impl Relation, IU> for NovaKey +where + A: ArithRelation, Vec>, + CM: CommitmentOps, +{ + type Error = Error; + + fn check_relation(&self, w: &IW, u: &IU) -> Result<(), Self::Error> { + self.arith.check_relation(&w.w, &u.x)?; + CM::open(&self.ck, &w.w, &w.r_w, &u.cm_w)?; + Ok(()) + } +} + +impl WitnessInstanceSampler, IU> for NovaKey { + type Source = AssignmentsOwned; + type Error = Error; + + fn sample(&self, z: Self::Source, rng: impl RngCore) -> Result<(IW, IU), Error> { + let (w, x) = (z.private, z.public); + let (cm_w, r_w) = CM::commit(&self.ck, &w, rng)?; + Ok((IW { w, r_w }, IU { cm_w, x })) + } +} + +impl WitnessInstanceSampler, RU> for NovaKey +where + A: for<'a> ArithRelation< + RelaxedWitness<&'a [CM::Scalar]>, + RelaxedInstance<&'a [CM::Scalar]>, + Evaluation = Vec, + >, + CM: CommitmentOps, +{ + type Source = (); + type Error = Error; + + fn sample(&self, _: Self::Source, mut rng: impl RngCore) -> Result<(RW, RU), Error> { + let cfg = self.arith.config(); + + let u = CM::Scalar::rand(&mut rng); + let x = (0..cfg.n_public_inputs()) + .map(|_| CM::Scalar::rand(&mut rng)) + .collect::>(); + let w = (0..cfg.n_witnesses()) + .map(|_| CM::Scalar::rand(&mut rng)) + .collect::>(); + let e = self.arith.eval_relation( + &RelaxedWitness { w: &w, e: &[] }, + &RelaxedInstance { x: &x, u: &u }, + )?; + + let (cm_w, r_w) = CM::commit(&self.ck, &w, &mut rng)?; + let (cm_e, r_e) = CM::commit(&self.ck, &e, &mut rng)?; + Ok((RW { w, r_w, e, r_e }, RU { cm_w, x, cm_e, u })) + } +} + +// used for the RO challenges. +// From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness +// error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this +// case, we keep the size of S close to 2^128. +pub struct Nova { + _CM: PhantomData, +} + +impl FoldingSchemeDef + for Nova +{ + type CM = CM; + type RW = RW; + type RU = RU; + type IW = IW; + type IU = IU; + + type TranscriptField = CM::Scalar; + type Arith = R1CS; + + type Config = usize; + type PublicParam = CM::Key; + type DeciderKey = NovaKey; + type Challenge = [bool; CHALLENGE_BITS]; + type Proof = CM::Commitment; +} + +impl FoldingSchemePreprocessor + for Nova +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} + +impl FoldingSchemeKeyGenerator + for Nova +{ + fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let r1cs = Arc::new(r1cs); + let cfg = r1cs.config(); + if ck.max_scalars_len() < cfg.n_constraints().max(cfg.n_witnesses()) { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the R1CS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: r1cs, ck }) + } +} + +impl FoldingSchemeProver<1, 1> + for Nova +{ + fn prove( + pk: &NovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; 1], + Us: &[impl Borrow; 1], + ws: &[impl Borrow; 1], + us: &[impl Borrow; 1], + _rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + let (W, U) = (Ws[0].borrow(), Us[0].borrow()); + let (w, u) = (ws[0].borrow(), us[0].borrow()); + + // Compute the cross term `T` by following the optimized approach in + // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. + let v = pk.arith.evaluate_at(AssignmentsOwned::from(( + U.u + CM::Scalar::one(), + cfg_iter!(U.x).zip(&u.x).map(|(a, b)| *a + b).collect(), + cfg_iter!(W.w).zip(&w.w).map(|(a, b)| *a + b).collect(), + )))?; + let t = cfg_into_iter!(v) + .zip(&W.e) + .map(|(a, b)| a - b) + .collect::>(); + + // Use `StepRng::new(0, 0)`, which is a dummy RNG that always generates + // 0 for the randomness (i.e., `r_T = 0`), no matter whether `CM` itself + // is hiding or not. + // + // This is because in Nova, we don't need hiding property for commitment + // to `T`. + let (cm_t, r_t) = CM::commit(&pk.ck, &t, StepRng::new(0, 0))?; + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(&cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + + Ok(( + RW { + e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), + r_e: W.r_e + r_t * rho, + w: cfg_iter!(W.w).zip(&w.w).map(|(a, b)| rho * b + a).collect(), + r_w: W.r_w + w.r_w * rho, + }, + RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + u.cm_w.mul(rho), + x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), + }, + cm_t, + rho_bits.try_into().unwrap(), + )) + } +} + +impl FoldingSchemeVerifier<1, 1> + for Nova +{ + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; 1], + us: &[impl Borrow; 1], + cm_t: &Self::Proof<1, 1>, + ) -> Result { + let (U, u) = (Us[0].borrow(), us[0].borrow()); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(&cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + + Ok(RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + u.cm_w.mul(rho), + x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), + }) + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::{Fr, G1Projective}; + use ark_ff::UniformRand; + use ark_std::{error::Error, test_rng}; + use sonobe_primitives::{ + circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, + commitments::pedersen::Pedersen, + }; + + use super::*; + use crate::tests::test_folding_scheme; + + #[test] + fn test_nova() -> Result<(), Box> { + let mut rng = test_rng(); + + test_folding_scheme::>, 1, 1>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..10) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + + test_folding_scheme::>, 1, 1>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..10) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + Ok(()) + } +} diff --git a/crates/fs/src/nova/witness.rs b/crates/fs/src/nova/witness.rs new file mode 100644 index 000000000..57dd4b176 --- /dev/null +++ b/crates/fs/src/nova/witness.rs @@ -0,0 +1,53 @@ +use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy}; + +use crate::FoldingWitness; + +#[derive(Debug, PartialEq)] +pub struct RunningWitness { + pub e: Vec, + pub r_e: CM::Randomness, + pub w: Vec, + pub r_w: CM::Randomness, +} + +impl FoldingWitness for RunningWitness { + const N_OPENINGS: usize = 2; + + fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)> { + vec![(&self.e, &self.r_e), (&self.w, &self.r_w)] + } +} + +impl Dummy<&Cfg> for RunningWitness { + fn dummy(cfg: &Cfg) -> Self { + Self { + e: vec![Default::default(); cfg.n_constraints()], + r_e: Default::default(), + w: vec![Default::default(); cfg.n_witnesses()], + r_w: Default::default(), + } + } +} + +#[derive(Debug, PartialEq)] +pub struct IncomingWitness { + pub w: Vec, + pub r_w: CM::Randomness, +} + +impl FoldingWitness for IncomingWitness { + const N_OPENINGS: usize = 1; + + fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)> { + vec![(&self.w, &self.r_w)] + } +} + +impl Dummy<&Cfg> for IncomingWitness { + fn dummy(cfg: &Cfg) -> Self { + Self { + w: vec![Default::default(); cfg.n_witnesses()], + r_w: Default::default(), + } + } +} From c9e7dce73f5a575591afd39beae7db16ed41ae45 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:27 +0800 Subject: [PATCH 70/99] Refactor: Allow Nova to have any transcript field --- crates/fs/src/nova/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index f47e182cb..ee3c59597 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -136,12 +136,15 @@ where // From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this // case, we keep the size of S close to 2^128. -pub struct Nova { - _CM: PhantomData, +pub struct AbstractNova { + _t: PhantomData<(CM, TF)>, } -impl FoldingSchemeDef - for Nova +pub type Nova = + AbstractNova::Scalar, CHALLENGE_BITS>; + +impl FoldingSchemeDef + for AbstractNova { type CM = CM; type RW = RW; From e294f003c1715bd80523457e890bd8bbf4cf9fc0 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 19:04:22 +0800 Subject: [PATCH 71/99] Prepare for cyclefold --- crates/fs/src/nova/mod.rs | 55 ++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index ee3c59597..34745c357 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -1,3 +1,4 @@ +use ark_ec::CurveGroup; use ark_ff::{BigInteger, Field, One, PrimeField}; use ark_std::{ UniformRand, @@ -19,7 +20,7 @@ use sonobe_primitives::{ circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, - traits::{CF1, SonobeCurve, SonobeField}, + traits::{CF1, CF2, SonobeCurve, SonobeField}, transcripts::Transcript, }; use witness::{IncomingWitness as IW, RunningWitness as RW}; @@ -143,6 +144,9 @@ pub struct AbstractNova { pub type Nova = AbstractNova::Scalar, CHALLENGE_BITS>; +pub type CycleFoldNova = + AbstractNova::Commitment>, CHALLENGE_BITS>; + impl FoldingSchemeDef for AbstractNova { @@ -152,7 +156,7 @@ impl Fol type IW = IW; type IU = IU; - type TranscriptField = CM::Scalar; + type TranscriptField = TF; type Arith = R1CS; type Config = usize; @@ -162,8 +166,8 @@ impl Fol type Proof = CM::Commitment; } -impl FoldingSchemePreprocessor - for Nova +impl + FoldingSchemePreprocessor for AbstractNova { fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { let ck = CM::generate_key(ck_len, &mut rng)?; @@ -171,8 +175,8 @@ impl FoldingSchemePreproc } } -impl FoldingSchemeKeyGenerator - for Nova +impl + FoldingSchemeKeyGenerator for AbstractNova { fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { let ck = Arc::new(ck); @@ -187,12 +191,12 @@ impl FoldingSchemeKeyGene } } -impl FoldingSchemeProver<1, 1> - for Nova +impl + FoldingSchemeProver<1, 1> for AbstractNova { fn prove( pk: &NovaKey, - transcript: &mut impl Transcript, + transcript: &mut impl Transcript, Ws: &[impl Borrow; 1], Us: &[impl Borrow; 1], ws: &[impl Borrow; 1], @@ -249,12 +253,12 @@ impl FoldingSchemeProver< } } -impl FoldingSchemeVerifier<1, 1> - for Nova +impl FoldingSchemeVerifier<1, 1> + for AbstractNova { fn verify( _vk: &(), - transcript: &mut impl Transcript, + transcript: &mut impl Transcript, Us: &[impl Borrow; 1], us: &[impl Borrow; 1], cm_t: &Self::Proof<1, 1>, @@ -280,7 +284,7 @@ impl FoldingSchemeVerifie #[cfg(test)] mod tests { - use ark_bn254::{Fr, G1Projective}; + use ark_bn254::{Fq, Fr, G1Projective}; use ark_ff::UniformRand; use ark_std::{error::Error, test_rng}; use sonobe_primitives::{ @@ -291,31 +295,40 @@ mod tests { use super::*; use crate::tests::test_folding_scheme; - #[test] - fn test_nova() -> Result<(), Box> { - let mut rng = test_rng(); - - test_folding_scheme::>, 1, 1>( + fn test_nova_opt( + rounds: usize, + mut rng: impl RngCore, + ) -> Result<(), Box> { + test_folding_scheme::, TF>, 1, 1>( 8, CircuitForTest { x: Fr::rand(&mut rng), }, - (0..10) + (0..rounds) .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) .collect(), &mut rng, )?; - test_folding_scheme::>, 1, 1>( + test_folding_scheme::, TF>, 1, 1>( 8, CircuitForTest { x: Fr::rand(&mut rng), }, - (0..10) + (0..rounds) .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) .collect(), &mut rng, )?; Ok(()) } + + #[test] + fn test_nova() -> Result<(), Box> { + let mut rng = test_rng(); + + test_nova_opt::(10, &mut rng)?; + test_nova_opt::(10, &mut rng)?; + Ok(()) + } } From 086df2e05683fc57e2beef908c8891ec70c72a35 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 22:38:47 +0800 Subject: [PATCH 72/99] Accumulation scheme compatible interface --- crates/fs/src/nova/mod.rs | 221 +++++++++++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 3 deletions(-) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 34745c357..992d4e7c3 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -27,7 +27,8 @@ use witness::{IncomingWitness as IW, RunningWitness as RW}; use crate::{ DeciderKey, Error, FoldingSchemeDecider, FoldingSchemeDef, FoldingSchemeKeyGenerator, - FoldingSchemePreprocessor, FoldingSchemeProver, FoldingSchemeVerifier, + FoldingSchemePreprocessor, FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as PU, + PlainWitness as PW, }; pub mod instance; @@ -89,6 +90,19 @@ where } } +impl Relation, PU> for NovaKey +where + A: ArithRelation, Vec>, + CM: CommitmentDef, +{ + type Error = Error; + + fn check_relation(&self, w: &PW, u: &PU) -> Result<(), Self::Error> { + self.arith.check_relation(w, u)?; + Ok(()) + } +} + impl WitnessInstanceSampler, IU> for NovaKey { type Source = AssignmentsOwned; type Error = Error; @@ -100,6 +114,21 @@ impl WitnessInstanceSampler, IU> for NovaKey WitnessInstanceSampler, PU> + for NovaKey +{ + type Source = AssignmentsOwned; + type Error = Error; + + fn sample( + &self, + z: Self::Source, + _rng: impl RngCore, + ) -> Result<(PW, PU), Error> { + Ok((z.private.into(), z.public.into())) + } +} + impl WitnessInstanceSampler, RU> for NovaKey where A: for<'a> ArithRelation< @@ -253,8 +282,8 @@ impl } } -impl FoldingSchemeVerifier<1, 1> - for AbstractNova +impl + FoldingSchemeVerifier<1, 1> for AbstractNova { fn verify( _vk: &(), @@ -282,6 +311,170 @@ impl Fol } } +// used for the RO challenges. +// From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness +// error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this +// case, we keep the size of S close to 2^128. +pub struct AbstractNova2 { + _t: PhantomData<(CM, TF)>, +} + +pub type Nova2 = + AbstractNova2::Scalar, CHALLENGE_BITS>; + +pub type CycleFoldNova2 = + AbstractNova2::Commitment>, CHALLENGE_BITS>; + +impl FoldingSchemeDef + for AbstractNova2 +{ + type CM = CM; + type RW = RW; + type RU = RU; + type IW = PW; + type IU = PU; + + type TranscriptField = TF; + type Arith = R1CS; + + type Config = usize; + type PublicParam = CM::Key; + type DeciderKey = NovaKey; + type Challenge = [bool; CHALLENGE_BITS]; + type Proof = (CM::Commitment, CM::Commitment); +} + +impl + FoldingSchemePreprocessor for AbstractNova2 +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} + +impl + FoldingSchemeKeyGenerator for AbstractNova2 +{ + fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let r1cs = Arc::new(r1cs); + let cfg = r1cs.config(); + if ck.max_scalars_len() < cfg.n_constraints().max(cfg.n_witnesses()) { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the R1CS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: r1cs, ck }) + } +} + +impl + FoldingSchemeProver<1, 1> for AbstractNova2 +{ + fn prove( + pk: &NovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; 1], + Us: &[impl Borrow; 1], + ws: &[impl Borrow; 1], + us: &[impl Borrow; 1], + mut rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + let (W, U) = (Ws[0].borrow(), Us[0].borrow()); + let (w, u) = (ws[0].borrow(), us[0].borrow()); + + // Compute the cross term `T` by following the optimized approach in + // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. + let v = pk.arith.evaluate_at(AssignmentsOwned::from(( + U.u + CM::Scalar::one(), + cfg_iter!(U.x).zip(&u[..]).map(|(a, b)| *a + b).collect(), + cfg_iter!(W.w).zip(&w[..]).map(|(a, b)| *a + b).collect(), + )))?; + let t = cfg_into_iter!(v) + .zip(&W.e) + .map(|(a, b)| a - b) + .collect::>(); + + let (cm_w, r_w) = CM::commit(&pk.ck, w, rng)?; + + // Use `StepRng::new(0, 0)`, which is a dummy RNG that always generates + // 0 for the randomness (i.e., `r_T = 0`), no matter whether `CM` itself + // is hiding or not. + // + // This is because in Nova, we don't need hiding property for commitment + // to `T`. + let (cm_t, r_t) = CM::commit(&pk.ck, &t, StepRng::new(0, 0))?; + + let pi = (cm_w, cm_t); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(&pi); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + + Ok(( + RW { + e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), + r_e: W.r_e + r_t * rho, + w: cfg_iter!(W.w) + .zip(&w[..]) + .map(|(a, b)| rho * b + a) + .collect(), + r_w: W.r_w + r_w * rho, + }, + RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + cm_w.mul(rho), + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }, + pi, + rho_bits.try_into().unwrap(), + )) + } +} + +impl + FoldingSchemeVerifier<1, 1> for AbstractNova2 +{ + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; 1], + us: &[impl Borrow; 1], + pi: &Self::Proof<1, 1>, + ) -> Result { + let (U, u) = (Us[0].borrow(), us[0].borrow()); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(pi); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + + let (cm_w, cm_t) = pi; + + Ok(RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + cm_w.mul(rho), + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }) + } +} + #[cfg(test)] mod tests { use ark_bn254::{Fq, Fr, G1Projective}; @@ -320,6 +513,28 @@ mod tests { .collect(), &mut rng, )?; + + test_folding_scheme::, TF>, 1, 1>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..rounds) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + + test_folding_scheme::, TF>, 1, 1>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..rounds) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; Ok(()) } From 0fb6edea554a7ecb37800b8b29f7ae81a75c7179 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 25 Oct 2025 23:27:13 +0800 Subject: [PATCH 73/99] In circuit variables for Nova instances and witnesses --- crates/fs/src/nova/instance/circuits.rs | 216 ++++++++++++++++++ .../src/nova/{instance.rs => instance/mod.rs} | 2 + crates/fs/src/nova/witness/circuits.rs | 59 +++++ .../src/nova/{witness.rs => witness/mod.rs} | 2 + 4 files changed, 279 insertions(+) create mode 100644 crates/fs/src/nova/instance/circuits.rs rename crates/fs/src/nova/{instance.rs => instance/mod.rs} (99%) create mode 100644 crates/fs/src/nova/witness/circuits.rs rename crates/fs/src/nova/{witness.rs => witness/mod.rs} (98%) diff --git a/crates/fs/src/nova/instance/circuits.rs b/crates/fs/src/nova/instance/circuits.rs new file mode 100644 index 000000000..559e0d5dd --- /dev/null +++ b/crates/fs/src/nova/instance/circuits.rs @@ -0,0 +1,216 @@ +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, + fields::fp::FpVar, + prelude::Boolean, + select::CondSelectGadget, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::borrow::Borrow; +use sonobe_primitives::{ + commitments::{CommitmentDef, CommitmentDefGadget}, + transcripts::{Absorbable, AbsorbableVar}, +}; + +use super::{IncomingInstance, RunningInstance}; +use crate::{FoldingInstance, FoldingInstanceVar}; + +#[derive(Clone, Debug, PartialEq)] +pub struct RunningInstanceVar { + pub cm_e: CM::CommitmentVar, + pub u: CM::ScalarVar, + pub cm_w: CM::CommitmentVar, + pub x: Vec, +} + +impl AllocVar, CM::ConstraintField> + for RunningInstanceVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let RunningInstance { cm_e, u, cm_w, x } = v.borrow(); + Ok(Self { + cm_e: AllocVar::new_variable(cs.clone(), || Ok(cm_e), mode)?, + u: AllocVar::new_variable(cs.clone(), || Ok(u), mode)?, + cm_w: AllocVar::new_variable(cs.clone(), || Ok(cm_w), mode)?, + x: AllocVar::new_variable(cs.clone(), || Ok(&x[..]), mode)?, + }) + } +} + +impl GR1CSVar for RunningInstanceVar { + type Value = RunningInstance; + + fn cs(&self) -> ConstraintSystemRef { + self.cm_e + .cs() + .or(self.u.cs()) + .or(self.cm_w.cs()) + .or(self.x.cs()) + } + + fn value(&self) -> Result { + Ok(RunningInstance { + cm_e: self.cm_e.value()?, + u: self.u.value()?, + cm_w: self.cm_w.value()?, + x: self.x.value()?, + }) + } +} + +impl AbsorbableVar for RunningInstanceVar { + fn absorb_into( + &self, + dest: &mut Vec>, + ) -> Result<(), SynthesisError> { + self.u.absorb_into(dest)?; + self.x.absorb_into(dest)?; + self.cm_e.absorb_into(dest)?; + self.cm_w.absorb_into(dest) + } +} + +impl CondSelectGadget for RunningInstanceVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.x.len() != false_value.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self { + cm_e: cond.select(&true_value.cm_e, &false_value.cm_e)?, + u: cond.select(&true_value.u, &false_value.u)?, + cm_w: cond.select(&true_value.cm_w, &false_value.cm_w)?, + x: true_value + .x + .iter() + .zip(&false_value.x) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + }) + } +} + +impl FoldingInstanceVar for RunningInstanceVar { + fn commitments(&self) -> Vec<&CM::CommitmentVar> { + vec![&self.cm_w, &self.cm_e] + } + + fn public_inputs(&self) -> &Vec { + &self.x + } + + fn new_witness_with_public_inputs( + cs: impl Into>, + u: &Self::Value, + x: Vec, + ) -> Result { + let cs = cs.into().cs(); + Ok(Self { + cm_e: AllocVar::new_witness(cs.clone(), || Ok(&u.cm_e))?, + u: AllocVar::new_witness(cs.clone(), || Ok(&u.u))?, + cm_w: AllocVar::new_witness(cs.clone(), || Ok(&u.cm_w))?, + x, + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct IncomingInstanceVar { + pub cm_w: CM::CommitmentVar, + pub x: Vec, +} + +impl AllocVar, CM::ConstraintField> + for IncomingInstanceVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let IncomingInstance { cm_w, x } = v.borrow(); + Ok(Self { + cm_w: AllocVar::new_variable(cs.clone(), || Ok(cm_w), mode)?, + x: AllocVar::new_variable(cs.clone(), || Ok(&x[..]), mode)?, + }) + } +} + +impl GR1CSVar for IncomingInstanceVar { + type Value = IncomingInstance; + + fn cs(&self) -> ConstraintSystemRef { + self.cm_w.cs().or(self.x.cs()) + } + + fn value(&self) -> Result { + Ok(IncomingInstance { + cm_w: self.cm_w.value()?, + x: self.x.value()?, + }) + } +} + +impl AbsorbableVar for IncomingInstanceVar { + fn absorb_into( + &self, + dest: &mut Vec>, + ) -> Result<(), SynthesisError> { + self.x.absorb_into(dest)?; + self.cm_w.absorb_into(dest) + } +} + +impl CondSelectGadget for IncomingInstanceVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.x.len() != false_value.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self { + cm_w: cond.select(&true_value.cm_w, &false_value.cm_w)?, + x: true_value + .x + .iter() + .zip(&false_value.x) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + }) + } +} + +impl FoldingInstanceVar for IncomingInstanceVar { + fn commitments(&self) -> Vec<&CM::CommitmentVar> { + vec![&self.cm_w] + } + + fn public_inputs(&self) -> &Vec { + &self.x + } + + fn new_witness_with_public_inputs( + cs: impl Into>, + u: &Self::Value, + x: Vec, + ) -> Result { + let cs = cs.into().cs(); + Ok(Self { + cm_w: AllocVar::new_witness(cs.clone(), || Ok(&u.cm_w))?, + x, + }) + } +} diff --git a/crates/fs/src/nova/instance.rs b/crates/fs/src/nova/instance/mod.rs similarity index 99% rename from crates/fs/src/nova/instance.rs rename to crates/fs/src/nova/instance/mod.rs index ec832215a..ba81b5838 100644 --- a/crates/fs/src/nova/instance.rs +++ b/crates/fs/src/nova/instance/mod.rs @@ -6,6 +6,8 @@ use sonobe_primitives::{ use crate::FoldingInstance; +pub mod circuits; + #[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningInstance { pub cm_e: CM::Commitment, diff --git a/crates/fs/src/nova/witness/circuits.rs b/crates/fs/src/nova/witness/circuits.rs new file mode 100644 index 000000000..7cbe61d98 --- /dev/null +++ b/crates/fs/src/nova/witness/circuits.rs @@ -0,0 +1,59 @@ +use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; +use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_std::borrow::Borrow; +use sonobe_primitives::commitments::{CommitmentDef, CommitmentDefGadget}; + +use super::{IncomingWitness, RunningWitness}; +use crate::FoldingWitnessVar; + +#[derive(Debug, PartialEq)] +pub struct RunningWitnessVar { + pub e: Vec, + pub r_e: CM::RandomnessVar, + pub w: Vec, + pub r_w: CM::RandomnessVar, +} + +impl AllocVar, CM::ConstraintField> + for RunningWitnessVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let RunningWitness { e, r_e, w, r_w } = v.borrow(); + Ok(Self { + e: AllocVar::new_variable(cs.clone(), || Ok(&e[..]), mode)?, + r_e: AllocVar::new_variable(cs.clone(), || Ok(r_e), mode)?, + w: AllocVar::new_variable(cs.clone(), || Ok(&w[..]), mode)?, + r_w: AllocVar::new_variable(cs.clone(), || Ok(r_w), mode)?, + }) + } +} + +#[derive(Debug, PartialEq)] +pub struct IncomingWitnessVar { + pub w: Vec, + pub r_w: CM::RandomnessVar, +} + +impl AllocVar, CM::ConstraintField> + for IncomingWitnessVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let IncomingWitness { w, r_w } = v.borrow(); + Ok(Self { + w: AllocVar::new_variable(cs.clone(), || Ok(&w[..]), mode)?, + r_w: AllocVar::new_variable(cs.clone(), || Ok(r_w), mode)?, + }) + } +} diff --git a/crates/fs/src/nova/witness.rs b/crates/fs/src/nova/witness/mod.rs similarity index 98% rename from crates/fs/src/nova/witness.rs rename to crates/fs/src/nova/witness/mod.rs index 57dd4b176..f98f49bf8 100644 --- a/crates/fs/src/nova/witness.rs +++ b/crates/fs/src/nova/witness/mod.rs @@ -2,6 +2,8 @@ use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDe use crate::FoldingWitness; +pub mod circuits; + #[derive(Debug, PartialEq)] pub struct RunningWitness { pub e: Vec, From d9214eea6eebdd72a15f59584b77cd62451f0cba Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 8 Nov 2025 08:47:34 +0800 Subject: [PATCH 74/99] Implement more traits for Nova instances & witnesses --- crates/fs/src/lib.rs | 2 +- crates/fs/src/nova/instance/circuits.rs | 7 ++-- crates/fs/src/nova/mod.rs | 20 ++++++----- crates/fs/src/nova/witness/circuits.rs | 46 ++++++++++++++++++++++--- crates/fs/src/nova/witness/mod.rs | 4 +-- 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 3c5b7624a..a347a133a 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -24,8 +24,8 @@ //! - `instances/`: Instance types. //! - `witnesses/`: Witness types. -pub mod nova; pub mod definitions; +pub mod nova; pub use self::definitions::{ FoldingSchemeDef, FoldingSchemeDefGadget, diff --git a/crates/fs/src/nova/instance/circuits.rs b/crates/fs/src/nova/instance/circuits.rs index 559e0d5dd..9a850957c 100644 --- a/crates/fs/src/nova/instance/circuits.rs +++ b/crates/fs/src/nova/instance/circuits.rs @@ -7,13 +7,10 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::borrow::Borrow; -use sonobe_primitives::{ - commitments::{CommitmentDef, CommitmentDefGadget}, - transcripts::{Absorbable, AbsorbableVar}, -}; +use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::AbsorbableVar}; use super::{IncomingInstance, RunningInstance}; -use crate::{FoldingInstance, FoldingInstanceVar}; +use crate::FoldingInstanceVar; #[derive(Clone, Debug, PartialEq)] pub struct RunningInstanceVar { diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 992d4e7c3..0c28a61e1 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -1,5 +1,4 @@ -use ark_ec::CurveGroup; -use ark_ff::{BigInteger, Field, One, PrimeField}; +use ark_ff::{BigInteger, One, PrimeField}; use ark_std::{ UniformRand, borrow::Borrow, @@ -9,7 +8,6 @@ use ark_std::{ rand::{RngCore, rngs::mock::StepRng}, sync::Arc, }; -use instance::{IncomingInstance as IU, RunningInstance as RU}; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ @@ -20,15 +18,17 @@ use sonobe_primitives::{ circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, - traits::{CF1, CF2, SonobeCurve, SonobeField}, + traits::{CF2, SonobeField}, transcripts::Transcript, }; -use witness::{IncomingWitness as IW, RunningWitness as RW}; +use self::{ + instance::{IncomingInstance as IU, RunningInstance as RU}, + witness::{IncomingWitness as IW, RunningWitness as RW}, +}; use crate::{ - DeciderKey, Error, FoldingSchemeDecider, FoldingSchemeDef, FoldingSchemeKeyGenerator, - FoldingSchemePreprocessor, FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as PU, - PlainWitness as PW, + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeKeyGenerator, FoldingSchemePreprocessor, + FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as PU, PlainWitness as PW, }; pub mod instance; @@ -223,6 +223,7 @@ impl impl FoldingSchemeProver<1, 1> for AbstractNova { + #[allow(non_snake_case)] fn prove( pk: &NovaKey, transcript: &mut impl Transcript, @@ -285,6 +286,7 @@ impl impl FoldingSchemeVerifier<1, 1> for AbstractNova { + #[allow(non_snake_case)] fn verify( _vk: &(), transcript: &mut impl Transcript, @@ -372,6 +374,7 @@ impl impl FoldingSchemeProver<1, 1> for AbstractNova2 { + #[allow(non_snake_case)] fn prove( pk: &NovaKey, transcript: &mut impl Transcript, @@ -444,6 +447,7 @@ impl impl FoldingSchemeVerifier<1, 1> for AbstractNova2 { + #[allow(non_snake_case)] fn verify( _vk: &(), transcript: &mut impl Transcript, diff --git a/crates/fs/src/nova/witness/circuits.rs b/crates/fs/src/nova/witness/circuits.rs index 7cbe61d98..ee8554fd0 100644 --- a/crates/fs/src/nova/witness/circuits.rs +++ b/crates/fs/src/nova/witness/circuits.rs @@ -1,10 +1,12 @@ -use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::borrow::Borrow; -use sonobe_primitives::commitments::{CommitmentDef, CommitmentDefGadget}; +use sonobe_primitives::commitments::CommitmentDefGadget; use super::{IncomingWitness, RunningWitness}; -use crate::FoldingWitnessVar; #[derive(Debug, PartialEq)] pub struct RunningWitnessVar { @@ -34,6 +36,27 @@ impl AllocVar, CM::Constrain } } +impl GR1CSVar for RunningWitnessVar { + type Value = RunningWitness; + + fn cs(&self) -> ConstraintSystemRef { + self.e + .cs() + .or(self.r_e.cs()) + .or(self.w.cs()) + .or(self.r_w.cs()) + } + + fn value(&self) -> Result { + Ok(RunningWitness { + e: self.e.value()?, + r_e: self.r_e.value()?, + w: self.w.value()?, + r_w: self.r_w.value()?, + }) + } +} + #[derive(Debug, PartialEq)] pub struct IncomingWitnessVar { pub w: Vec, @@ -57,3 +80,18 @@ impl AllocVar, CM::Constrai }) } } + +impl GR1CSVar for IncomingWitnessVar { + type Value = IncomingWitness; + + fn cs(&self) -> ConstraintSystemRef { + self.w.cs().or(self.r_w.cs()) + } + + fn value(&self) -> Result { + Ok(IncomingWitness { + w: self.w.value()?, + r_w: self.r_w.value()?, + }) + } +} diff --git a/crates/fs/src/nova/witness/mod.rs b/crates/fs/src/nova/witness/mod.rs index f98f49bf8..583abfebd 100644 --- a/crates/fs/src/nova/witness/mod.rs +++ b/crates/fs/src/nova/witness/mod.rs @@ -4,7 +4,7 @@ use crate::FoldingWitness; pub mod circuits; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningWitness { pub e: Vec, pub r_e: CM::Randomness, @@ -31,7 +31,7 @@ impl Dummy<&Cfg> for RunningWitness { } } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct IncomingWitness { pub w: Vec, pub r_w: CM::Randomness, From b4de64fcf27becb08cb2ad7e6478b097ee1bc098 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 17 Nov 2025 07:48:25 +0800 Subject: [PATCH 75/99] Nova CycleFold adapter --- crates/fs/src/nova/mod.rs | 150 +++++++++++++++-- .../src/compilers/cyclefold/adapters/mod.rs | 4 + .../src/compilers/cyclefold/adapters/nova.rs | 152 ++++++++++++++++++ crates/ivc/src/compilers/cyclefold/mod.rs | 1 + 4 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 crates/ivc/src/compilers/cyclefold/adapters/mod.rs create mode 100644 crates/ivc/src/compilers/cyclefold/adapters/nova.rs diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 0c28a61e1..1d674cb29 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -1,4 +1,6 @@ use ark_ff::{BigInteger, One, PrimeField}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, boolean::Boolean, groups::CurveVar}; +use ark_relations::gr1cs::SynthesisError; use ark_std::{ UniformRand, borrow::Borrow, @@ -11,24 +13,32 @@ use ark_std::{ #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ + algebra::ops::bits::FromBitsGadget, arithmetizations::{ Arith, ArithConfig, ArithRelation, r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, }, circuits::AssignmentsOwned, - commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, + commitments::{ + CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, GroupBasedCommitment, + }, relations::{Relation, WitnessInstanceSampler}, traits::{CF2, SonobeField}, - transcripts::Transcript, + transcripts::{Transcript, TranscriptGadget}, }; use self::{ - instance::{IncomingInstance as IU, RunningInstance as RU}, + instance::{ + IncomingInstance as IU, RunningInstance as RU, + circuits::{IncomingInstanceVar as IUVar, RunningInstanceVar as RUVar}, + }, witness::{IncomingWitness as IW, RunningWitness as RW}, }; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeKeyGenerator, FoldingSchemePreprocessor, - FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as PU, PlainWitness as PW, + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, + FoldingSchemeKeyGenerator, FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, + FoldingSchemeProver, FoldingSchemeVerifier, GroupBasedFoldingSchemePrimaryDef, + GroupBasedFoldingSchemeSecondaryDef, PlainInstance as PU, PlainWitness as PW, }; pub mod instance; @@ -317,14 +327,15 @@ impl // From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this // case, we keep the size of S close to 2^128. -pub struct AbstractNova2 { +// TODO: experimental design +struct AbstractNova2 { _t: PhantomData<(CM, TF)>, } -pub type Nova2 = +type Nova2 = AbstractNova2::Scalar, CHALLENGE_BITS>; -pub type CycleFoldNova2 = +type CycleFoldNova2 = AbstractNova2::Commitment>, CHALLENGE_BITS>; impl FoldingSchemeDef @@ -382,7 +393,7 @@ impl Us: &[impl Borrow; 1], ws: &[impl Borrow; 1], us: &[impl Borrow; 1], - mut rng: impl RngCore, + rng: impl RngCore, ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { let (W, U) = (Ws[0].borrow(), Us[0].borrow()); let (w, u) = (ws[0].borrow(), us[0].borrow()); @@ -479,6 +490,127 @@ impl } } +pub struct AbstractNovaGadget { + _vc: PhantomData, +} + +impl FoldingSchemeDefGadget + for AbstractNovaGadget +where + CM: CommitmentDefGadget, +{ + type Widget = AbstractNova; + + type CM = CM; + type RU = RUVar; + type IU = IUVar; + type VerifierKey = (); + type Challenge = [Boolean; CHALLENGE_BITS]; + type Proof = CM::CommitmentVar; +} + +impl FoldingSchemePartialVerifierGadget<1, 1> + for AbstractNovaGadget +where + CM: CommitmentDefGadget, +{ + #[allow(non_snake_case)] + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(( + Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: CM::CommitmentVar::new_witness( + U.cm_e.cs().or(proof.cs()).or(rho.cs()), + || { + Ok(U.cm_e.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) + }, + )?, + cm_w: CM::CommitmentVar::new_witness( + U.cm_w.cs().or(u.cm_w.cs()).or(rho.cs()), + || { + Ok(U.cm_w.value().unwrap_or_default() + + u.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) + }, + )?, + x: U.x + .iter() + .zip(&u.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl FoldingSchemeFullVerifierGadget<1, 1> + for AbstractNovaGadget +where + CM: CommitmentDefGadget, + CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, +{ + #[allow(non_snake_case)] + fn verify( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: proof.scalar_mul_le(rho_bits.iter())? + &U.cm_e, + cm_w: u.cm_w.scalar_mul_le(rho_bits.iter())? + &U.cm_w, + x: U.x + .iter() + .zip(&u.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }) + } +} + +impl GroupBasedFoldingSchemePrimaryDef + for AbstractNova +{ + type Gadget = AbstractNovaGadget; +} + +impl GroupBasedFoldingSchemeSecondaryDef + for AbstractNova, CHALLENGE_BITS> +{ + type Gadget = AbstractNovaGadget; +} + #[cfg(test)] mod tests { use ark_bn254::{Fq, Fr, G1Projective}; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/mod.rs b/crates/ivc/src/compilers/cyclefold/adapters/mod.rs new file mode 100644 index 000000000..cfbca4153 --- /dev/null +++ b/crates/ivc/src/compilers/cyclefold/adapters/mod.rs @@ -0,0 +1,4 @@ +//! Per-scheme adapters that implement [`super::CycleFoldCircuit`] for supported +//! folding schemes. + +pub mod nova; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs new file mode 100644 index 000000000..1a8d271de --- /dev/null +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -0,0 +1,152 @@ +use ark_ff::{BigInteger, PrimeField, Zero}; +use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean}; +use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; +use ark_std::{borrow::Borrow, iter::once}; +use sonobe_fs::{ + FoldingSchemeDefGadget, + nova::{CycleFoldNova, Nova}, +}; +use sonobe_primitives::{ + algebra::{ + field::emulated::{Bounds, EmulatedFieldVar}, + ops::bits::{FromBitsGadget, ToBitsGadgetExt}, + }, + commitments::GroupBasedCommitment, + traits::{CF2, SonobeCurve}, +}; + +use crate::compilers::cyclefold::{ + CycleFoldBasedIVC, FoldingSchemeCycleFoldExt, circuits::CycleFoldCircuit, +}; + +/// Configuration for Nova's CycleFold circuit +pub struct NovaCycleFoldCircuit { + r: Vec, + points: Vec, +} + +impl Default + for NovaCycleFoldCircuit +{ + fn default() -> Self { + Self { + r: vec![false; CHALLENGE_BITS], + points: vec![C::zero(); 2], + } + } +} + +impl CycleFoldCircuit> + for NovaCycleFoldCircuit +{ + fn verify_point_rlc(&self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + let rho = FpVar::new_input(cs.clone(), || { + Ok(CF2::::from( + as PrimeField>::BigInt::from_bits_le(&self.r[..]), + )) + })?; + let rho_bits = rho.to_n_bits_le(CHALLENGE_BITS)?; + + let points = Vec::::new_witness(cs.clone(), || Ok(&self.points[..]))?; + for point in &points { + Self::mark_point_as_public(point)?; + } + + Self::mark_point_as_public(&(points[1].scalar_mul_le(rho_bits.iter())? + &points[0])) + } +} + +impl FoldingSchemeCycleFoldExt<1, 1> + for Nova +{ + const N_CYCLEFOLDS: usize = 2; + + type CFCircuit = NovaCycleFoldCircuit; + + fn to_cyclefold_circuits( + [U]: &[impl Borrow; 1], + [u]: &[impl Borrow; 1], + proof: &Self::Proof<1, 1>, + rho: Self::Challenge, + ) -> Vec { + vec![ + NovaCycleFoldCircuit { + r: rho.into(), + points: vec![U.borrow().cm_e, *proof], + }, + NovaCycleFoldCircuit { + r: rho.into(), + points: vec![U.borrow().cm_w, u.borrow().cm_w], + }, + ] + } + + fn to_cyclefold_inputs( + [U]: [::RU; 1], + [u]: [::IU; 1], + UU: ::RU, + proof: ::Proof<1, 1>, + rho: ::Challenge, + ) -> Result>>>, SynthesisError> { + let mut rho = rho.to_vec(); + rho.resize( + CF2::::MODULUS_BIT_SIZE as usize, + Boolean::FALSE, + ); + let rho = EmulatedFieldVar::from_bounded_bits_le( + &rho, + Bounds(Zero::zero(), CF2::::MODULUS.into().into()), + )?; + Ok(vec![ + once(rho.clone()) + .chain( + [U.cm_e, proof, UU.cm_e] + .into_iter() + .flat_map(|p| [p.x, p.y]), + ) + .collect(), + once(rho) + .chain( + [U.cm_w, u.cm_w, UU.cm_w] + .into_iter() + .flat_map(|p| [p.x, p.y]), + ) + .collect(), + ]) + } +} + +pub type NovaNovaIVC = + CycleFoldBasedIVC, CycleFoldNova, T>; + +#[cfg(test)] +mod tests { + use ark_bn254::{Fr, G1Projective as C1}; + use ark_ff::UniformRand; + use ark_grumpkin::Projective as C2; + use ark_std::{error::Error, rand::thread_rng, sync::Arc}; + use sonobe_primitives::{ + circuits::utils::CircuitForTest, + commitments::pedersen::Pedersen, + transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, + }; + + use super::*; + use crate::tests::test_ivc; + + #[test] + fn test_nova_nova() -> Result<(), Box> { + let mut rng = thread_rng(); + + test_ivc::, Pedersen, GriffinSponge<_>>, _>( + (65536, 2048, Arc::new(GriffinParams::new(16, 5, 9))), + CircuitForTest { + x: Fr::rand(&mut rng), + }, + vec![(); 20], + &mut rng, + )?; + + Ok(()) + } +} diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index b48b66976..2a42bf0ba 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -27,6 +27,7 @@ use crate::{ compilers::cyclefold::circuits::{AugmentedCircuit, CycleFoldCircuit}, }; +pub mod adapters; pub mod circuits; /// [`FoldingSchemeCycleFoldExt`] is the extension trait that a folding scheme From f2be215b9a08a9c50456c779cf1f169410516fe9 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 23:32:34 +0800 Subject: [PATCH 76/99] Hide cross term commitment in Nova --- crates/fs/src/nova/mod.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 1d674cb29..e11b72497 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -241,7 +241,7 @@ impl Us: &[impl Borrow; 1], ws: &[impl Borrow; 1], us: &[impl Borrow; 1], - _rng: impl RngCore, + rng: impl RngCore, ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { let (W, U) = (Ws[0].borrow(), Us[0].borrow()); let (w, u) = (ws[0].borrow(), us[0].borrow()); @@ -258,13 +258,7 @@ impl .map(|(a, b)| a - b) .collect::>(); - // Use `StepRng::new(0, 0)`, which is a dummy RNG that always generates - // 0 for the randomness (i.e., `r_T = 0`), no matter whether `CM` itself - // is hiding or not. - // - // This is because in Nova, we don't need hiding property for commitment - // to `T`. - let (cm_t, r_t) = CM::commit(&pk.ck, &t, StepRng::new(0, 0))?; + let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; let rho_bits = { transcript.add(&U); @@ -393,7 +387,7 @@ impl Us: &[impl Borrow; 1], ws: &[impl Borrow; 1], us: &[impl Borrow; 1], - rng: impl RngCore, + mut rng: impl RngCore, ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { let (W, U) = (Ws[0].borrow(), Us[0].borrow()); let (w, u) = (ws[0].borrow(), us[0].borrow()); @@ -410,15 +404,9 @@ impl .map(|(a, b)| a - b) .collect::>(); - let (cm_w, r_w) = CM::commit(&pk.ck, w, rng)?; + let (cm_w, r_w) = CM::commit(&pk.ck, w, &mut rng)?; - // Use `StepRng::new(0, 0)`, which is a dummy RNG that always generates - // 0 for the randomness (i.e., `r_T = 0`), no matter whether `CM` itself - // is hiding or not. - // - // This is because in Nova, we don't need hiding property for commitment - // to `T`. - let (cm_t, r_t) = CM::commit(&pk.ck, &t, StepRng::new(0, 0))?; + let (cm_t, r_t) = CM::commit(&pk.ck, &t, &mut rng)?; let pi = (cm_w, cm_t); From c1fc8ff8cb362c802419ca8a5b8517c29cdf687b Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 04:41:39 +0800 Subject: [PATCH 77/99] FS<2, 0> for Nova --- crates/fs/src/nova/mod.rs | 125 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index e11b72497..7234c6d82 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -317,6 +317,109 @@ impl } } +impl + FoldingSchemeProver<2, 0> for AbstractNova +{ + #[allow(non_snake_case)] + fn prove( + pk: &NovaKey, + transcript: &mut impl Transcript, + [W1, W2]: &[impl Borrow; 2], + [U1, U2]: &[impl Borrow; 2], + _: &[impl Borrow; 0], + _: &[impl Borrow; 0], + rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<2, 0>, Self::Challenge), Error> { + let (W1, U1) = (W1.borrow(), U1.borrow()); + let (W2, U2) = (W2.borrow(), U2.borrow()); + + // Compute the cross term `T` by following the optimized approach in + // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. + let v = pk.arith.evaluate_at(AssignmentsOwned::from(( + U1.u + U2.u, + cfg_iter!(U1.x).zip(&U2.x).map(|(a, b)| *a + b).collect(), + cfg_iter!(W1.w).zip(&W2.w).map(|(a, b)| *a + b).collect(), + )))?; + let t = cfg_into_iter!(v) + .zip(&W1.e) + .zip(&W2.e) + .map(|((a, b), c)| a - b - c) + .collect::>(); + + let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; + + let rho_bits = { + transcript.add(&U1); + transcript.add(&U2); + transcript.add(&cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho_squared = rho * rho; + + Ok(( + RW { + e: cfg_iter!(W1.e) + .zip(&t) + .zip(&W2.e) + .map(|((a, b), c)| rho_squared * c + rho * b + a) + .collect(), + r_e: W1.r_e + r_t * rho + W2.r_e * rho_squared, + w: cfg_iter!(W1.w) + .zip(&W2.w) + .map(|(a, b)| rho * b + a) + .collect(), + r_w: W1.r_w + W2.r_w * rho, + }, + RU { + cm_e: U1.cm_e + cm_t.mul(rho) + U2.cm_e.mul(rho_squared), + u: U1.u + rho * U2.u, + cm_w: U1.cm_w + U2.cm_w.mul(rho), + x: cfg_iter!(U1.x) + .zip(&U2.x) + .map(|(a, b)| rho * b + a) + .collect(), + }, + cm_t, + rho_bits.try_into().unwrap(), + )) + } +} + +impl + FoldingSchemeVerifier<2, 0> for AbstractNova +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + [U1, U2]: &[impl Borrow; 2], + _: &[impl Borrow; 0], + cm_t: &Self::Proof<2, 0>, + ) -> Result { + let (U1, U2) = (U1.borrow(), U2.borrow()); + + let rho_bits = { + transcript.add(&U1); + transcript.add(&U2); + transcript.add(cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho_squared = rho * rho; + + Ok(RU { + cm_e: U1.cm_e + cm_t.mul(rho) + U2.cm_e.mul(rho_squared), + u: U1.u + rho * U2.u, + cm_w: U1.cm_w + U2.cm_w.mul(rho), + x: cfg_iter!(U1.x) + .zip(&U2.x) + .map(|(a, b)| rho * b + a) + .collect(), + }) + } +} + // used for the RO challenges. // From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this @@ -638,6 +741,28 @@ mod tests { &mut rng, )?; + test_folding_scheme::, TF>, 2, 0>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..rounds) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + + test_folding_scheme::, TF>, 2, 0>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..rounds) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + test_folding_scheme::, TF>, 1, 1>( 8, CircuitForTest { From d9f2cb445ce5b2fb421facd8b3588de426453993 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 05:40:41 +0800 Subject: [PATCH 78/99] Circuits for 2+0 Nova --- crates/fs/src/nova/mod.rs | 55 +++++++++++++ .../src/compilers/cyclefold/adapters/nova.rs | 80 ++++++++++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 7234c6d82..acad1cfa8 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -652,6 +652,61 @@ where } } +impl FoldingSchemePartialVerifierGadget<2, 0> + for AbstractNovaGadget +where + CM: CommitmentDefGadget, +{ + #[allow(non_snake_case)] + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U1, U2]: [&Self::RU; 2], + _: [&Self::IU; 0], + proof: &Self::Proof<2, 0>, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let rho_bits = { + transcript.add(&U1)?; + transcript.add(&U2)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(( + Self::RU { + u: (U2.u.clone() * &rho + &U1.u) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: CM::CommitmentVar::new_witness( + U1.cm_e.cs().or(U2.cm_e.cs()).or(proof.cs()).or(rho.cs()), + || { + let rho = rho.value().unwrap_or_default(); + Ok(U1.cm_e.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho + + U2.cm_e.value().unwrap_or_default() * rho * rho) + }, + )?, + cm_w: CM::CommitmentVar::new_witness( + U1.cm_w.cs().or(U2.cm_w.cs()).or(rho.cs()), + || { + Ok(U1.cm_w.value().unwrap_or_default() + + U2.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) + }, + )?, + x: U1 + .x + .iter() + .zip(&U2.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }, + rho_bits.try_into().unwrap(), + )) + } +} + impl FoldingSchemeFullVerifierGadget<1, 1> for AbstractNovaGadget where diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 1a8d271de..93613cc6e 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -1,5 +1,7 @@ use ark_ff::{BigInteger, PrimeField, Zero}; -use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean}; +use ark_r1cs_std::{ + GR1CSVar, alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean, +}; use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; use ark_std::{borrow::Borrow, iter::once}; use sonobe_fs::{ @@ -9,6 +11,7 @@ use sonobe_fs::{ use sonobe_primitives::{ algebra::{ field::emulated::{Bounds, EmulatedFieldVar}, + group::emulated::EmulatedAffineVar, ops::bits::{FromBitsGadget, ToBitsGadgetExt}, }, commitments::GroupBasedCommitment, @@ -116,6 +119,81 @@ impl FoldingSchemeCycleFo } } +impl FoldingSchemeCycleFoldExt<2, 0> + for Nova +{ + const N_CYCLEFOLDS: usize = 3; + + type CFCircuit = NovaCycleFoldCircuit; + + fn to_cyclefold_circuits( + [U1, U2]: &[impl Borrow; 2], + _: &[impl Borrow; 0], + proof: &Self::Proof<2, 0>, + rho_bits: Self::Challenge, + ) -> Vec { + let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + vec![ + NovaCycleFoldCircuit { + r: rho_bits.into(), + points: vec![*proof, U2.borrow().cm_e], + }, + NovaCycleFoldCircuit { + r: rho_bits.into(), + points: vec![U1.borrow().cm_e, U2.borrow().cm_e * rho + proof], + }, + NovaCycleFoldCircuit { + r: rho_bits.into(), + points: vec![U1.borrow().cm_w, U2.borrow().cm_w], + }, + ] + } + + fn to_cyclefold_inputs( + [U1, U2]: [::RU; 2], + _: [::IU; 0], + UU: ::RU, + proof: ::Proof<2, 0>, + rho_bits: ::Challenge, + ) -> Result>>>, SynthesisError> { + let mut rho_bits = rho_bits.to_vec(); + rho_bits.resize( + CF2::::MODULUS_BIT_SIZE as usize, + Boolean::FALSE, + ); + let rho = EmulatedFieldVar::from_bounded_bits_le( + &rho_bits, + Bounds(Zero::zero(), CF2::::MODULUS.into().into()), + )?; + let x = + EmulatedAffineVar::new_witness(U2.cm_e.cs().or(proof.cs()).or(rho_bits.cs()), || { + let rho_bits = rho_bits.value().unwrap_or_default(); + let rho = + CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + Ok(proof.value().unwrap_or_default() + U2.cm_e.value().unwrap_or_default() * rho) + })?; + Ok(vec![ + once(rho.clone()) + .chain( + [proof, U2.cm_e, x.clone()] + .into_iter() + .flat_map(|p| [p.x, p.y]), + ) + .collect(), + once(rho.clone()) + .chain([U1.cm_e, x, UU.cm_e].into_iter().flat_map(|p| [p.x, p.y])) + .collect(), + once(rho) + .chain( + [U1.cm_w, U2.cm_w, UU.cm_w] + .into_iter() + .flat_map(|p| [p.x, p.y]), + ) + .collect(), + ]) + } +} + pub type NovaNovaIVC = CycleFoldBasedIVC, CycleFoldNova, T>; From 9ea5b521df8fa3c77fb7d2f0f780f53e1a1fa2a4 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 03:11:21 +0800 Subject: [PATCH 79/99] Cleanup --- crates/fs/src/nova/mod.rs | 25 ++++++++----------- .../src/compilers/cyclefold/adapters/nova.rs | 15 ++++------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index acad1cfa8..4dd3dd09f 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -1,19 +1,14 @@ -use ark_ff::{BigInteger, One, PrimeField}; +use ark_ff::One; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, boolean::Boolean, groups::CurveVar}; use ark_relations::gr1cs::SynthesisError; use ark_std::{ - UniformRand, - borrow::Borrow, - cfg_into_iter, cfg_iter, - marker::PhantomData, - ops::Mul, - rand::{RngCore, rngs::mock::StepRng}, - sync::Arc, + UniformRand, borrow::Borrow, cfg_into_iter, cfg_iter, marker::PhantomData, ops::Mul, + rand::RngCore, sync::Arc, }; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ - algebra::ops::bits::FromBitsGadget, + algebra::ops::bits::{FromBits, FromBitsGadget}, arithmetizations::{ Arith, ArithConfig, ArithRelation, r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, @@ -266,7 +261,7 @@ impl transcript.add(&cm_t); transcript.challenge_bits(CHALLENGE_BITS) }; - let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); Ok(( RW { @@ -306,7 +301,7 @@ impl transcript.add(&cm_t); transcript.challenge_bits(CHALLENGE_BITS) }; - let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); Ok(RU { cm_e: U.cm_e + cm_t.mul(rho), @@ -354,7 +349,7 @@ impl transcript.add(&cm_t); transcript.challenge_bits(CHALLENGE_BITS) }; - let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); let rho_squared = rho * rho; Ok(( @@ -405,7 +400,7 @@ impl transcript.add(cm_t); transcript.challenge_bits(CHALLENGE_BITS) }; - let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); let rho_squared = rho * rho; Ok(RU { @@ -519,7 +514,7 @@ impl transcript.add(&pi); transcript.challenge_bits(CHALLENGE_BITS) }; - let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); Ok(( RW { @@ -565,7 +560,7 @@ impl transcript.add(pi); transcript.challenge_bits(CHALLENGE_BITS) }; - let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); let (cm_w, cm_t) = pi; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 93613cc6e..c3cd4ae35 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -1,4 +1,4 @@ -use ark_ff::{BigInteger, PrimeField, Zero}; +use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean, }; @@ -12,7 +12,7 @@ use sonobe_primitives::{ algebra::{ field::emulated::{Bounds, EmulatedFieldVar}, group::emulated::EmulatedAffineVar, - ops::bits::{FromBitsGadget, ToBitsGadgetExt}, + ops::bits::{FromBits, FromBitsGadget, ToBitsGadgetExt}, }, commitments::GroupBasedCommitment, traits::{CF2, SonobeCurve}, @@ -43,11 +43,7 @@ impl CycleFoldCircuit> for NovaCycleFoldCircuit { fn verify_point_rlc(&self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - let rho = FpVar::new_input(cs.clone(), || { - Ok(CF2::::from( - as PrimeField>::BigInt::from_bits_le(&self.r[..]), - )) - })?; + let rho = FpVar::new_input(cs.clone(), || Ok(CF2::::from_bits_le(&self.r[..])))?; let rho_bits = rho.to_n_bits_le(CHALLENGE_BITS)?; let points = Vec::::new_witness(cs.clone(), || Ok(&self.points[..]))?; @@ -132,7 +128,7 @@ impl FoldingSchemeCycleFo proof: &Self::Proof<2, 0>, rho_bits: Self::Challenge, ) -> Vec { - let rho = CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); vec![ NovaCycleFoldCircuit { r: rho_bits.into(), @@ -168,8 +164,7 @@ impl FoldingSchemeCycleFo let x = EmulatedAffineVar::new_witness(U2.cm_e.cs().or(proof.cs()).or(rho_bits.cs()), || { let rho_bits = rho_bits.value().unwrap_or_default(); - let rho = - CM::Scalar::from(::BigInt::from_bits_le(&rho_bits)); + let rho = CM::Scalar::from_bits_le(&rho_bits); Ok(proof.value().unwrap_or_default() + U2.cm_e.value().unwrap_or_default() * rho) })?; Ok(vec![ From 461720b8297b30dffc9146e8905ff3f8c4df1cdd Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 25 Nov 2025 00:52:37 +0800 Subject: [PATCH 80/99] Split impls into separate submodules --- .../fs/src/nova/algorithms/key_generator.rs | 43 ++ crates/fs/src/nova/algorithms/mod.rs | 4 + crates/fs/src/nova/algorithms/preprocessor.rs | 25 + crates/fs/src/nova/algorithms/prover.rs | 209 +++++++ crates/fs/src/nova/algorithms/verifier.rs | 111 ++++ crates/fs/src/nova/circuits/mod.rs | 1 + crates/fs/src/nova/circuits/verifier.rs | 158 ++++++ .../nova/{instance => instances}/circuits.rs | 0 .../src/nova/{instance => instances}/mod.rs | 0 crates/fs/src/nova/mod.rs | 518 +----------------- .../nova/{witness => witnesses}/circuits.rs | 0 .../fs/src/nova/{witness => witnesses}/mod.rs | 0 12 files changed, 561 insertions(+), 508 deletions(-) create mode 100644 crates/fs/src/nova/algorithms/key_generator.rs create mode 100644 crates/fs/src/nova/algorithms/mod.rs create mode 100644 crates/fs/src/nova/algorithms/preprocessor.rs create mode 100644 crates/fs/src/nova/algorithms/prover.rs create mode 100644 crates/fs/src/nova/algorithms/verifier.rs create mode 100644 crates/fs/src/nova/circuits/mod.rs create mode 100644 crates/fs/src/nova/circuits/verifier.rs rename crates/fs/src/nova/{instance => instances}/circuits.rs (100%) rename crates/fs/src/nova/{instance => instances}/mod.rs (100%) rename crates/fs/src/nova/{witness => witnesses}/circuits.rs (100%) rename crates/fs/src/nova/{witness => witnesses}/mod.rs (100%) diff --git a/crates/fs/src/nova/algorithms/key_generator.rs b/crates/fs/src/nova/algorithms/key_generator.rs new file mode 100644 index 000000000..7e49489ba --- /dev/null +++ b/crates/fs/src/nova/algorithms/key_generator.rs @@ -0,0 +1,43 @@ +use ark_std::sync::Arc; +use sonobe_primitives::{ + arithmetizations::{Arith, ArithConfig}, + commitments::{CommitmentKey, GroupBasedCommitment}, + traits::SonobeField, +}; + +use crate::{ + Error, FoldingSchemeKeyGenerator, + nova::{AbstractNova, AbstractNova2}, +}; + +impl + FoldingSchemeKeyGenerator for AbstractNova +{ + fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let r1cs = Arc::new(r1cs); + let cfg = r1cs.config(); + if ck.max_scalars_len() < cfg.n_constraints().max(cfg.n_witnesses()) { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the R1CS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: r1cs, ck }) + } +} + +impl + FoldingSchemeKeyGenerator for AbstractNova2 +{ + fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let r1cs = Arc::new(r1cs); + let cfg = r1cs.config(); + if ck.max_scalars_len() < cfg.n_constraints().max(cfg.n_witnesses()) { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the R1CS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: r1cs, ck }) + } +} diff --git a/crates/fs/src/nova/algorithms/mod.rs b/crates/fs/src/nova/algorithms/mod.rs new file mode 100644 index 000000000..b0960535b --- /dev/null +++ b/crates/fs/src/nova/algorithms/mod.rs @@ -0,0 +1,4 @@ +pub mod preprocessor; +pub mod key_generator; +pub mod prover; +pub mod verifier; diff --git a/crates/fs/src/nova/algorithms/preprocessor.rs b/crates/fs/src/nova/algorithms/preprocessor.rs new file mode 100644 index 000000000..916aa08bd --- /dev/null +++ b/crates/fs/src/nova/algorithms/preprocessor.rs @@ -0,0 +1,25 @@ +use ark_std::rand::RngCore; +use sonobe_primitives::{commitments::GroupBasedCommitment, traits::SonobeField}; + +use crate::{ + nova::{AbstractNova, AbstractNova2}, + Error, FoldingSchemePreprocessor, +}; + +impl + FoldingSchemePreprocessor for AbstractNova +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} + +impl + FoldingSchemePreprocessor for AbstractNova2 +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs new file mode 100644 index 000000000..3e163b878 --- /dev/null +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -0,0 +1,209 @@ +use ark_ff::One; +use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, ops::Mul, rand::RngCore}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use sonobe_primitives::{ + algebra::ops::bits::FromBits, + circuits::AssignmentsOwned, + commitments::GroupBasedCommitment, + traits::SonobeField, + transcripts::Transcript, +}; + +use crate::{ + nova::{AbstractNova, AbstractNova2, NovaKey}, + Error, FoldingSchemeProver, +}; + +impl + FoldingSchemeProver<1, 1> for AbstractNova +{ + #[allow(non_snake_case)] + fn prove( + pk: &NovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; 1], + Us: &[impl Borrow; 1], + ws: &[impl Borrow; 1], + us: &[impl Borrow; 1], + rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + let (W, U) = (Ws[0].borrow(), Us[0].borrow()); + let (w, u) = (ws[0].borrow(), us[0].borrow()); + + // Compute the cross term `T` by following the optimized approach in + // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. + let v = pk.arith.evaluate_at(AssignmentsOwned::from(( + U.u + CM::Scalar::one(), + cfg_iter!(U.x).zip(&u.x).map(|(a, b)| *a + b).collect(), + cfg_iter!(W.w).zip(&w.w).map(|(a, b)| *a + b).collect(), + )))?; + let t = cfg_into_iter!(v) + .zip(&W.e) + .map(|(a, b)| a - b) + .collect::>(); + + let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(&cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + Ok(( + Self::RW { + e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), + r_e: W.r_e + r_t * rho, + w: cfg_iter!(W.w).zip(&w.w).map(|(a, b)| rho * b + a).collect(), + r_w: W.r_w + w.r_w * rho, + }, + Self::RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + u.cm_w.mul(rho), + x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), + }, + cm_t, + rho_bits.try_into().unwrap(), + )) + } +} + +impl + FoldingSchemeProver<2, 0> for AbstractNova +{ + #[allow(non_snake_case)] + fn prove( + pk: &NovaKey, + transcript: &mut impl Transcript, + [W1, W2]: &[impl Borrow; 2], + [U1, U2]: &[impl Borrow; 2], + _: &[impl Borrow; 0], + _: &[impl Borrow; 0], + rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<2, 0>, Self::Challenge), Error> { + let (W1, U1) = (W1.borrow(), U1.borrow()); + let (W2, U2) = (W2.borrow(), U2.borrow()); + + // Compute the cross term `T` by following the optimized approach in + // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. + let v = pk.arith.evaluate_at(AssignmentsOwned::from(( + U1.u + U2.u, + cfg_iter!(U1.x).zip(&U2.x).map(|(a, b)| *a + b).collect(), + cfg_iter!(W1.w).zip(&W2.w).map(|(a, b)| *a + b).collect(), + )))?; + let t = cfg_into_iter!(v) + .zip(&W1.e) + .zip(&W2.e) + .map(|((a, b), c)| a - b - c) + .collect::>(); + + let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; + + let rho_bits = { + transcript.add(&U1); + transcript.add(&U2); + transcript.add(&cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + let rho_squared = rho * rho; + + Ok(( + Self::RW { + e: cfg_iter!(W1.e) + .zip(&t) + .zip(&W2.e) + .map(|((a, b), c)| rho_squared * c + rho * b + a) + .collect(), + r_e: W1.r_e + r_t * rho + W2.r_e * rho_squared, + w: cfg_iter!(W1.w) + .zip(&W2.w) + .map(|(a, b)| rho * b + a) + .collect(), + r_w: W1.r_w + W2.r_w * rho, + }, + Self::RU { + cm_e: U1.cm_e + cm_t.mul(rho) + U2.cm_e.mul(rho_squared), + u: U1.u + rho * U2.u, + cm_w: U1.cm_w + U2.cm_w.mul(rho), + x: cfg_iter!(U1.x) + .zip(&U2.x) + .map(|(a, b)| rho * b + a) + .collect(), + }, + cm_t, + rho_bits.try_into().unwrap(), + )) + } +} + +impl + FoldingSchemeProver<1, 1> for AbstractNova2 +{ + #[allow(non_snake_case)] + fn prove( + pk: &NovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; 1], + Us: &[impl Borrow; 1], + ws: &[impl Borrow; 1], + us: &[impl Borrow; 1], + mut rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + let (W, U) = (Ws[0].borrow(), Us[0].borrow()); + let (w, u) = (ws[0].borrow(), us[0].borrow()); + + // Compute the cross term `T` by following the optimized approach in + // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. + let v = pk.arith.evaluate_at(AssignmentsOwned::from(( + U.u + CM::Scalar::one(), + cfg_iter!(U.x).zip(&u[..]).map(|(a, b)| *a + b).collect(), + cfg_iter!(W.w).zip(&w[..]).map(|(a, b)| *a + b).collect(), + )))?; + let t = cfg_into_iter!(v) + .zip(&W.e) + .map(|(a, b)| a - b) + .collect::>(); + + let (cm_w, r_w) = CM::commit(&pk.ck, w, &mut rng)?; + + let (cm_t, r_t) = CM::commit(&pk.ck, &t, &mut rng)?; + + let pi = (cm_w, cm_t); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(&pi); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + Ok(( + Self::RW { + e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), + r_e: W.r_e + r_t * rho, + w: cfg_iter!(W.w) + .zip(&w[..]) + .map(|(a, b)| rho * b + a) + .collect(), + r_w: W.r_w + r_w * rho, + }, + Self::RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + cm_w.mul(rho), + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }, + pi, + rho_bits.try_into().unwrap(), + )) + } +} diff --git a/crates/fs/src/nova/algorithms/verifier.rs b/crates/fs/src/nova/algorithms/verifier.rs new file mode 100644 index 000000000..30dc056fe --- /dev/null +++ b/crates/fs/src/nova/algorithms/verifier.rs @@ -0,0 +1,111 @@ +use ark_std::{borrow::Borrow, cfg_iter, ops::Mul}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use sonobe_primitives::{ + algebra::ops::bits::FromBits, commitments::GroupBasedCommitment, traits::SonobeField, + transcripts::Transcript, +}; + +use crate::{ + nova::{AbstractNova, AbstractNova2}, + Error, FoldingSchemeVerifier, +}; + +impl + FoldingSchemeVerifier<1, 1> for AbstractNova +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; 1], + us: &[impl Borrow; 1], + cm_t: &Self::Proof<1, 1>, + ) -> Result { + let (U, u) = (Us[0].borrow(), us[0].borrow()); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + Ok(Self::RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + u.cm_w.mul(rho), + x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), + }) + } +} + +impl + FoldingSchemeVerifier<2, 0> for AbstractNova +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + [U1, U2]: &[impl Borrow; 2], + _: &[impl Borrow; 0], + cm_t: &Self::Proof<2, 0>, + ) -> Result { + let (U1, U2) = (U1.borrow(), U2.borrow()); + + let rho_bits = { + transcript.add(&U1); + transcript.add(&U2); + transcript.add(cm_t); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + let rho_squared = rho * rho; + + Ok(Self::RU { + cm_e: U1.cm_e + cm_t.mul(rho) + U2.cm_e.mul(rho_squared), + u: U1.u + rho * U2.u, + cm_w: U1.cm_w + U2.cm_w.mul(rho), + x: cfg_iter!(U1.x) + .zip(&U2.x) + .map(|(a, b)| rho * b + a) + .collect(), + }) + } +} + +impl + FoldingSchemeVerifier<1, 1> for AbstractNova2 +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; 1], + us: &[impl Borrow; 1], + pi: &Self::Proof<1, 1>, + ) -> Result { + let (U, u) = (Us[0].borrow(), us[0].borrow()); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(pi); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let (cm_w, cm_t) = pi; + + Ok(Self::RU { + cm_e: U.cm_e + cm_t.mul(rho), + u: U.u + rho, + cm_w: U.cm_w + cm_w.mul(rho), + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }) + } +} diff --git a/crates/fs/src/nova/circuits/mod.rs b/crates/fs/src/nova/circuits/mod.rs new file mode 100644 index 000000000..9a0722027 --- /dev/null +++ b/crates/fs/src/nova/circuits/mod.rs @@ -0,0 +1 @@ +pub mod verifier; diff --git a/crates/fs/src/nova/circuits/verifier.rs b/crates/fs/src/nova/circuits/verifier.rs new file mode 100644 index 000000000..b550373be --- /dev/null +++ b/crates/fs/src/nova/circuits/verifier.rs @@ -0,0 +1,158 @@ +use ark_ff::{One, Zero}; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, groups::CurveVar}; +use ark_relations::gr1cs::SynthesisError; +use num_bigint::BigInt; +use sonobe_primitives::{ + algebra::{field::emulated::Bounds, ops::bits::FromBitsGadget}, + commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, + transcripts::TranscriptGadget, +}; + +use crate::{ + FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, nova::AbstractNovaGadget, +}; + +impl FoldingSchemePartialVerifierGadget<1, 1> + for AbstractNovaGadget +where + CM: CommitmentDefGadget, +{ + #[allow(non_snake_case)] + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(( + Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: CM::CommitmentVar::new_witness( + U.cm_e.cs().or(proof.cs()).or(rho.cs()), + || { + Ok(U.cm_e.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) + }, + )?, + cm_w: CM::CommitmentVar::new_witness( + U.cm_w.cs().or(u.cm_w.cs()).or(rho.cs()), + || { + Ok(U.cm_w.value().unwrap_or_default() + + u.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) + }, + )?, + x: U.x + .iter() + .zip(&u.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl FoldingSchemePartialVerifierGadget<2, 0> + for AbstractNovaGadget +where + CM: CommitmentDefGadget, +{ + #[allow(non_snake_case)] + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U1, U2]: [&Self::RU; 2], + _: [&Self::IU; 0], + proof: &Self::Proof<2, 0>, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let rho_bits = { + transcript.add(&U1)?; + transcript.add(&U2)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(( + Self::RU { + u: (U2.u.clone() * &rho + &U1.u) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: CM::CommitmentVar::new_witness( + U1.cm_e.cs().or(U2.cm_e.cs()).or(proof.cs()).or(rho.cs()), + || { + let rho = rho.value().unwrap_or_default(); + Ok(U1.cm_e.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho + + U2.cm_e.value().unwrap_or_default() * rho * rho) + }, + )?, + cm_w: CM::CommitmentVar::new_witness( + U1.cm_w.cs().or(U2.cm_w.cs()).or(rho.cs()), + || { + Ok(U1.cm_w.value().unwrap_or_default() + + U2.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) + }, + )?, + x: U1 + .x + .iter() + .zip(&U2.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl FoldingSchemeFullVerifierGadget<1, 1> + for AbstractNovaGadget +where + CM: CommitmentDefGadget, + CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, +{ + #[allow(non_snake_case)] + fn verify( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: proof.scalar_mul_le(rho_bits.iter())? + &U.cm_e, + cm_w: u.cm_w.scalar_mul_le(rho_bits.iter())? + &U.cm_w, + x: U.x + .iter() + .zip(&u.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }) + } +} diff --git a/crates/fs/src/nova/instance/circuits.rs b/crates/fs/src/nova/instances/circuits.rs similarity index 100% rename from crates/fs/src/nova/instance/circuits.rs rename to crates/fs/src/nova/instances/circuits.rs diff --git a/crates/fs/src/nova/instance/mod.rs b/crates/fs/src/nova/instances/mod.rs similarity index 100% rename from crates/fs/src/nova/instance/mod.rs rename to crates/fs/src/nova/instances/mod.rs diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 4dd3dd09f..22fd55d6a 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -1,43 +1,32 @@ -use ark_ff::One; -use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, boolean::Boolean, groups::CurveVar}; -use ark_relations::gr1cs::SynthesisError; -use ark_std::{ - UniformRand, borrow::Borrow, cfg_into_iter, cfg_iter, marker::PhantomData, ops::Mul, - rand::RngCore, sync::Arc, -}; -#[cfg(feature = "parallel")] -use rayon::prelude::*; +use ark_r1cs_std::boolean::Boolean; +use ark_std::{UniformRand, marker::PhantomData, rand::RngCore, sync::Arc}; use sonobe_primitives::{ - algebra::ops::bits::{FromBits, FromBitsGadget}, arithmetizations::{ Arith, ArithConfig, ArithRelation, r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, }, circuits::AssignmentsOwned, - commitments::{ - CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, GroupBasedCommitment, - }, + commitments::{CommitmentDef, CommitmentDefGadget, CommitmentOps, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, traits::{CF2, SonobeField}, - transcripts::{Transcript, TranscriptGadget}, }; use self::{ - instance::{ + instances::{ IncomingInstance as IU, RunningInstance as RU, circuits::{IncomingInstanceVar as IUVar, RunningInstanceVar as RUVar}, }, - witness::{IncomingWitness as IW, RunningWitness as RW}, + witnesses::{IncomingWitness as IW, RunningWitness as RW}, }; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, - FoldingSchemeKeyGenerator, FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, - FoldingSchemeProver, FoldingSchemeVerifier, GroupBasedFoldingSchemePrimaryDef, + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, GroupBasedFoldingSchemeSecondaryDef, PlainInstance as PU, PlainWitness as PW, }; -pub mod instance; -pub mod witness; +pub mod algorithms; +pub mod circuits; +pub mod instances; +pub mod witnesses; #[derive(Clone)] pub struct NovaKey { @@ -200,221 +189,6 @@ impl Fol type Proof = CM::Commitment; } -impl - FoldingSchemePreprocessor for AbstractNova -{ - fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { - let ck = CM::generate_key(ck_len, &mut rng)?; - Ok(ck) - } -} - -impl - FoldingSchemeKeyGenerator for AbstractNova -{ - fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { - let ck = Arc::new(ck); - let r1cs = Arc::new(r1cs); - let cfg = r1cs.config(); - if ck.max_scalars_len() < cfg.n_constraints().max(cfg.n_witnesses()) { - return Err(Error::InvalidPublicParameters( - "The commitment key is too short for the R1CS instance".into(), - )); - } - Ok(Self::DeciderKey { arith: r1cs, ck }) - } -} - -impl - FoldingSchemeProver<1, 1> for AbstractNova -{ - #[allow(non_snake_case)] - fn prove( - pk: &NovaKey, - transcript: &mut impl Transcript, - Ws: &[impl Borrow; 1], - Us: &[impl Borrow; 1], - ws: &[impl Borrow; 1], - us: &[impl Borrow; 1], - rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { - let (W, U) = (Ws[0].borrow(), Us[0].borrow()); - let (w, u) = (ws[0].borrow(), us[0].borrow()); - - // Compute the cross term `T` by following the optimized approach in - // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. - let v = pk.arith.evaluate_at(AssignmentsOwned::from(( - U.u + CM::Scalar::one(), - cfg_iter!(U.x).zip(&u.x).map(|(a, b)| *a + b).collect(), - cfg_iter!(W.w).zip(&w.w).map(|(a, b)| *a + b).collect(), - )))?; - let t = cfg_into_iter!(v) - .zip(&W.e) - .map(|(a, b)| a - b) - .collect::>(); - - let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; - - let rho_bits = { - transcript.add(&U); - transcript.add(&u); - transcript.add(&cm_t); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - - Ok(( - RW { - e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), - r_e: W.r_e + r_t * rho, - w: cfg_iter!(W.w).zip(&w.w).map(|(a, b)| rho * b + a).collect(), - r_w: W.r_w + w.r_w * rho, - }, - RU { - cm_e: U.cm_e + cm_t.mul(rho), - u: U.u + rho, - cm_w: U.cm_w + u.cm_w.mul(rho), - x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), - }, - cm_t, - rho_bits.try_into().unwrap(), - )) - } -} - -impl - FoldingSchemeVerifier<1, 1> for AbstractNova -{ - #[allow(non_snake_case)] - fn verify( - _vk: &(), - transcript: &mut impl Transcript, - Us: &[impl Borrow; 1], - us: &[impl Borrow; 1], - cm_t: &Self::Proof<1, 1>, - ) -> Result { - let (U, u) = (Us[0].borrow(), us[0].borrow()); - - let rho_bits = { - transcript.add(&U); - transcript.add(&u); - transcript.add(&cm_t); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - - Ok(RU { - cm_e: U.cm_e + cm_t.mul(rho), - u: U.u + rho, - cm_w: U.cm_w + u.cm_w.mul(rho), - x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), - }) - } -} - -impl - FoldingSchemeProver<2, 0> for AbstractNova -{ - #[allow(non_snake_case)] - fn prove( - pk: &NovaKey, - transcript: &mut impl Transcript, - [W1, W2]: &[impl Borrow; 2], - [U1, U2]: &[impl Borrow; 2], - _: &[impl Borrow; 0], - _: &[impl Borrow; 0], - rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<2, 0>, Self::Challenge), Error> { - let (W1, U1) = (W1.borrow(), U1.borrow()); - let (W2, U2) = (W2.borrow(), U2.borrow()); - - // Compute the cross term `T` by following the optimized approach in - // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. - let v = pk.arith.evaluate_at(AssignmentsOwned::from(( - U1.u + U2.u, - cfg_iter!(U1.x).zip(&U2.x).map(|(a, b)| *a + b).collect(), - cfg_iter!(W1.w).zip(&W2.w).map(|(a, b)| *a + b).collect(), - )))?; - let t = cfg_into_iter!(v) - .zip(&W1.e) - .zip(&W2.e) - .map(|((a, b), c)| a - b - c) - .collect::>(); - - let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; - - let rho_bits = { - transcript.add(&U1); - transcript.add(&U2); - transcript.add(&cm_t); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - let rho_squared = rho * rho; - - Ok(( - RW { - e: cfg_iter!(W1.e) - .zip(&t) - .zip(&W2.e) - .map(|((a, b), c)| rho_squared * c + rho * b + a) - .collect(), - r_e: W1.r_e + r_t * rho + W2.r_e * rho_squared, - w: cfg_iter!(W1.w) - .zip(&W2.w) - .map(|(a, b)| rho * b + a) - .collect(), - r_w: W1.r_w + W2.r_w * rho, - }, - RU { - cm_e: U1.cm_e + cm_t.mul(rho) + U2.cm_e.mul(rho_squared), - u: U1.u + rho * U2.u, - cm_w: U1.cm_w + U2.cm_w.mul(rho), - x: cfg_iter!(U1.x) - .zip(&U2.x) - .map(|(a, b)| rho * b + a) - .collect(), - }, - cm_t, - rho_bits.try_into().unwrap(), - )) - } -} - -impl - FoldingSchemeVerifier<2, 0> for AbstractNova -{ - #[allow(non_snake_case)] - fn verify( - _vk: &(), - transcript: &mut impl Transcript, - [U1, U2]: &[impl Borrow; 2], - _: &[impl Borrow; 0], - cm_t: &Self::Proof<2, 0>, - ) -> Result { - let (U1, U2) = (U1.borrow(), U2.borrow()); - - let rho_bits = { - transcript.add(&U1); - transcript.add(&U2); - transcript.add(cm_t); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - let rho_squared = rho * rho; - - Ok(RU { - cm_e: U1.cm_e + cm_t.mul(rho) + U2.cm_e.mul(rho_squared), - u: U1.u + rho * U2.u, - cm_w: U1.cm_w + U2.cm_w.mul(rho), - x: cfg_iter!(U1.x) - .zip(&U2.x) - .map(|(a, b)| rho * b + a) - .collect(), - }) - } -} - // used for the RO challenges. // From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this @@ -449,133 +223,6 @@ impl Fol type Proof = (CM::Commitment, CM::Commitment); } -impl - FoldingSchemePreprocessor for AbstractNova2 -{ - fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { - let ck = CM::generate_key(ck_len, &mut rng)?; - Ok(ck) - } -} - -impl - FoldingSchemeKeyGenerator for AbstractNova2 -{ - fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { - let ck = Arc::new(ck); - let r1cs = Arc::new(r1cs); - let cfg = r1cs.config(); - if ck.max_scalars_len() < cfg.n_constraints().max(cfg.n_witnesses()) { - return Err(Error::InvalidPublicParameters( - "The commitment key is too short for the R1CS instance".into(), - )); - } - Ok(Self::DeciderKey { arith: r1cs, ck }) - } -} - -impl - FoldingSchemeProver<1, 1> for AbstractNova2 -{ - #[allow(non_snake_case)] - fn prove( - pk: &NovaKey, - transcript: &mut impl Transcript, - Ws: &[impl Borrow; 1], - Us: &[impl Borrow; 1], - ws: &[impl Borrow; 1], - us: &[impl Borrow; 1], - mut rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { - let (W, U) = (Ws[0].borrow(), Us[0].borrow()); - let (w, u) = (ws[0].borrow(), us[0].borrow()); - - // Compute the cross term `T` by following the optimized approach in - // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. - let v = pk.arith.evaluate_at(AssignmentsOwned::from(( - U.u + CM::Scalar::one(), - cfg_iter!(U.x).zip(&u[..]).map(|(a, b)| *a + b).collect(), - cfg_iter!(W.w).zip(&w[..]).map(|(a, b)| *a + b).collect(), - )))?; - let t = cfg_into_iter!(v) - .zip(&W.e) - .map(|(a, b)| a - b) - .collect::>(); - - let (cm_w, r_w) = CM::commit(&pk.ck, w, &mut rng)?; - - let (cm_t, r_t) = CM::commit(&pk.ck, &t, &mut rng)?; - - let pi = (cm_w, cm_t); - - let rho_bits = { - transcript.add(&U); - transcript.add(&u); - transcript.add(&pi); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - - Ok(( - RW { - e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), - r_e: W.r_e + r_t * rho, - w: cfg_iter!(W.w) - .zip(&w[..]) - .map(|(a, b)| rho * b + a) - .collect(), - r_w: W.r_w + r_w * rho, - }, - RU { - cm_e: U.cm_e + cm_t.mul(rho), - u: U.u + rho, - cm_w: U.cm_w + cm_w.mul(rho), - x: cfg_iter!(U.x) - .zip(&u[..]) - .map(|(a, b)| rho * b + a) - .collect(), - }, - pi, - rho_bits.try_into().unwrap(), - )) - } -} - -impl - FoldingSchemeVerifier<1, 1> for AbstractNova2 -{ - #[allow(non_snake_case)] - fn verify( - _vk: &(), - transcript: &mut impl Transcript, - Us: &[impl Borrow; 1], - us: &[impl Borrow; 1], - pi: &Self::Proof<1, 1>, - ) -> Result { - let (U, u) = (Us[0].borrow(), us[0].borrow()); - - let rho_bits = { - transcript.add(&U); - transcript.add(&u); - transcript.add(pi); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - - let (cm_w, cm_t) = pi; - - Ok(RU { - cm_e: U.cm_e + cm_t.mul(rho), - u: U.u + rho, - cm_w: U.cm_w + cm_w.mul(rho), - x: cfg_iter!(U.x) - .zip(&u[..]) - .map(|(a, b)| rho * b + a) - .collect(), - }) - } -} - pub struct AbstractNovaGadget { _vc: PhantomData, } @@ -595,151 +242,6 @@ where type Proof = CM::CommitmentVar; } -impl FoldingSchemePartialVerifierGadget<1, 1> - for AbstractNovaGadget -where - CM: CommitmentDefGadget, -{ - #[allow(non_snake_case)] - fn verify_hinted( - _vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - [U]: [&Self::RU; 1], - [u]: [&Self::IU; 1], - proof: &Self::Proof<1, 1>, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { - let rho_bits = { - transcript.add(&U)?; - transcript.add(&u)?; - transcript.add(proof)?; - transcript.challenge_bits(CHALLENGE_BITS)? - }; - let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; - - Ok(( - Self::RU { - u: (U.u.clone() + &rho) - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?, - cm_e: CM::CommitmentVar::new_witness( - U.cm_e.cs().or(proof.cs()).or(rho.cs()), - || { - Ok(U.cm_e.value().unwrap_or_default() - + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) - }, - )?, - cm_w: CM::CommitmentVar::new_witness( - U.cm_w.cs().or(u.cm_w.cs()).or(rho.cs()), - || { - Ok(U.cm_w.value().unwrap_or_default() - + u.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) - }, - )?, - x: U.x - .iter() - .zip(&u.x) - .map(|(a, b)| (b.clone() * &rho + a).try_into()) - .collect::>() - .map_err(|_| SynthesisError::Unsatisfiable)?, - }, - rho_bits.try_into().unwrap(), - )) - } -} - -impl FoldingSchemePartialVerifierGadget<2, 0> - for AbstractNovaGadget -where - CM: CommitmentDefGadget, -{ - #[allow(non_snake_case)] - fn verify_hinted( - _vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - [U1, U2]: [&Self::RU; 2], - _: [&Self::IU; 0], - proof: &Self::Proof<2, 0>, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { - let rho_bits = { - transcript.add(&U1)?; - transcript.add(&U2)?; - transcript.add(proof)?; - transcript.challenge_bits(CHALLENGE_BITS)? - }; - let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; - - Ok(( - Self::RU { - u: (U2.u.clone() * &rho + &U1.u) - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?, - cm_e: CM::CommitmentVar::new_witness( - U1.cm_e.cs().or(U2.cm_e.cs()).or(proof.cs()).or(rho.cs()), - || { - let rho = rho.value().unwrap_or_default(); - Ok(U1.cm_e.value().unwrap_or_default() - + proof.value().unwrap_or_default() * rho - + U2.cm_e.value().unwrap_or_default() * rho * rho) - }, - )?, - cm_w: CM::CommitmentVar::new_witness( - U1.cm_w.cs().or(U2.cm_w.cs()).or(rho.cs()), - || { - Ok(U1.cm_w.value().unwrap_or_default() - + U2.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) - }, - )?, - x: U1 - .x - .iter() - .zip(&U2.x) - .map(|(a, b)| (b.clone() * &rho + a).try_into()) - .collect::>() - .map_err(|_| SynthesisError::Unsatisfiable)?, - }, - rho_bits.try_into().unwrap(), - )) - } -} - -impl FoldingSchemeFullVerifierGadget<1, 1> - for AbstractNovaGadget -where - CM: CommitmentDefGadget, - CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, -{ - #[allow(non_snake_case)] - fn verify( - _vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - [U]: [&Self::RU; 1], - [u]: [&Self::IU; 1], - proof: &Self::Proof<1, 1>, - ) -> Result { - let rho_bits = { - transcript.add(&U)?; - transcript.add(&u)?; - transcript.add(proof)?; - transcript.challenge_bits(CHALLENGE_BITS)? - }; - let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; - - Ok(Self::RU { - u: (U.u.clone() + &rho) - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?, - cm_e: proof.scalar_mul_le(rho_bits.iter())? + &U.cm_e, - cm_w: u.cm_w.scalar_mul_le(rho_bits.iter())? + &U.cm_w, - x: U.x - .iter() - .zip(&u.x) - .map(|(a, b)| (b.clone() * &rho + a).try_into()) - .collect::>() - .map_err(|_| SynthesisError::Unsatisfiable)?, - }) - } -} - impl GroupBasedFoldingSchemePrimaryDef for AbstractNova { diff --git a/crates/fs/src/nova/witness/circuits.rs b/crates/fs/src/nova/witnesses/circuits.rs similarity index 100% rename from crates/fs/src/nova/witness/circuits.rs rename to crates/fs/src/nova/witnesses/circuits.rs diff --git a/crates/fs/src/nova/witness/mod.rs b/crates/fs/src/nova/witnesses/mod.rs similarity index 100% rename from crates/fs/src/nova/witness/mod.rs rename to crates/fs/src/nova/witnesses/mod.rs From 796ed35b8c836273b79385f21c50dd9e6cadf792 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 81/99] Clean up --- crates/fs/src/nova/algorithms/mod.rs | 2 +- crates/fs/src/nova/algorithms/preprocessor.rs | 2 +- crates/fs/src/nova/algorithms/prover.rs | 9 +++------ crates/fs/src/nova/algorithms/verifier.rs | 2 +- crates/fs/src/nova/mod.rs | 6 +++--- crates/ivc/src/compilers/cyclefold/adapters/nova.rs | 4 ++++ 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/fs/src/nova/algorithms/mod.rs b/crates/fs/src/nova/algorithms/mod.rs index b0960535b..11e838298 100644 --- a/crates/fs/src/nova/algorithms/mod.rs +++ b/crates/fs/src/nova/algorithms/mod.rs @@ -1,4 +1,4 @@ -pub mod preprocessor; pub mod key_generator; +pub mod preprocessor; pub mod prover; pub mod verifier; diff --git a/crates/fs/src/nova/algorithms/preprocessor.rs b/crates/fs/src/nova/algorithms/preprocessor.rs index 916aa08bd..c41dbce6b 100644 --- a/crates/fs/src/nova/algorithms/preprocessor.rs +++ b/crates/fs/src/nova/algorithms/preprocessor.rs @@ -2,8 +2,8 @@ use ark_std::rand::RngCore; use sonobe_primitives::{commitments::GroupBasedCommitment, traits::SonobeField}; use crate::{ - nova::{AbstractNova, AbstractNova2}, Error, FoldingSchemePreprocessor, + nova::{AbstractNova, AbstractNova2}, }; impl diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index 3e163b878..49901a0d4 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -3,16 +3,13 @@ use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, ops::Mul, rand::RngCore}; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ - algebra::ops::bits::FromBits, - circuits::AssignmentsOwned, - commitments::GroupBasedCommitment, - traits::SonobeField, - transcripts::Transcript, + algebra::ops::bits::FromBits, circuits::AssignmentsOwned, commitments::GroupBasedCommitment, + traits::SonobeField, transcripts::Transcript, }; use crate::{ - nova::{AbstractNova, AbstractNova2, NovaKey}, Error, FoldingSchemeProver, + nova::{AbstractNova, AbstractNova2, NovaKey}, }; impl diff --git a/crates/fs/src/nova/algorithms/verifier.rs b/crates/fs/src/nova/algorithms/verifier.rs index 30dc056fe..e682e5b36 100644 --- a/crates/fs/src/nova/algorithms/verifier.rs +++ b/crates/fs/src/nova/algorithms/verifier.rs @@ -7,8 +7,8 @@ use sonobe_primitives::{ }; use crate::{ - nova::{AbstractNova, AbstractNova2}, Error, FoldingSchemeVerifier, + nova::{AbstractNova, AbstractNova2}, }; impl diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 22fd55d6a..01e5c7d43 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -194,14 +194,14 @@ impl Fol // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this // case, we keep the size of S close to 2^128. // TODO: experimental design -struct AbstractNova2 { +pub struct AbstractNova2 { _t: PhantomData<(CM, TF)>, } -type Nova2 = +pub type Nova2 = AbstractNova2::Scalar, CHALLENGE_BITS>; -type CycleFoldNova2 = +pub type CycleFoldNova2 = AbstractNova2::Commitment>, CHALLENGE_BITS>; impl FoldingSchemeDef diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index c3cd4ae35..c694b0942 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -62,6 +62,7 @@ impl FoldingSchemeCycleFo type CFCircuit = NovaCycleFoldCircuit; + #[allow(non_snake_case)] fn to_cyclefold_circuits( [U]: &[impl Borrow; 1], [u]: &[impl Borrow; 1], @@ -80,6 +81,7 @@ impl FoldingSchemeCycleFo ] } + #[allow(non_snake_case)] fn to_cyclefold_inputs( [U]: [::RU; 1], [u]: [::IU; 1], @@ -122,6 +124,7 @@ impl FoldingSchemeCycleFo type CFCircuit = NovaCycleFoldCircuit; + #[allow(non_snake_case)] fn to_cyclefold_circuits( [U1, U2]: &[impl Borrow; 2], _: &[impl Borrow; 0], @@ -145,6 +148,7 @@ impl FoldingSchemeCycleFo ] } + #[allow(non_snake_case)] fn to_cyclefold_inputs( [U1, U2]: [::RU; 2], _: [::IU; 0], From ec71a50189db1224e576cd92d9f74d77f8b8a60b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 82/99] Actually test wasm targets --- crates/fs/src/nova/mod.rs | 6 ++++-- crates/ivc/src/compilers/cyclefold/adapters/nova.rs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 01e5c7d43..12e3e2240 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -258,11 +258,13 @@ impl GroupBasedFoldingSch mod tests { use ark_bn254::{Fq, Fr, G1Projective}; use ark_ff::UniformRand; - use ark_std::{error::Error, test_rng}; + use ark_std::{error::Error, rand::thread_rng}; use sonobe_primitives::{ circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, commitments::pedersen::Pedersen, }; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::tests::test_folding_scheme; @@ -341,7 +343,7 @@ mod tests { #[test] fn test_nova() -> Result<(), Box> { - let mut rng = test_rng(); + let mut rng = thread_rng(); test_nova_opt::(10, &mut rng)?; test_nova_opt::(10, &mut rng)?; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index c694b0942..88eab910a 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -207,6 +207,8 @@ mod tests { commitments::pedersen::Pedersen, transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, }; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::tests::test_ivc; From 0e51dcac5ffab9f00e4fb9602feb98d981fa6eb7 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 9 Feb 2026 19:36:40 +0800 Subject: [PATCH 83/99] Add FS & IVC docs --- .../fs/src/nova/algorithms/key_generator.rs | 2 ++ crates/fs/src/nova/algorithms/mod.rs | 2 ++ crates/fs/src/nova/algorithms/preprocessor.rs | 2 ++ crates/fs/src/nova/algorithms/prover.rs | 2 ++ crates/fs/src/nova/algorithms/verifier.rs | 2 ++ crates/fs/src/nova/circuits/mod.rs | 2 ++ crates/fs/src/nova/circuits/verifier.rs | 6 ++--- crates/fs/src/nova/instances/circuits.rs | 12 ++++++++++ crates/fs/src/nova/instances/mod.rs | 11 ++++++++++ crates/fs/src/nova/mod.rs | 22 ++++++++++++++++++- crates/fs/src/nova/witnesses/circuits.rs | 12 ++++++++++ crates/fs/src/nova/witnesses/mod.rs | 11 ++++++++++ .../src/compilers/cyclefold/adapters/nova.rs | 6 ++++- 13 files changed, 87 insertions(+), 5 deletions(-) diff --git a/crates/fs/src/nova/algorithms/key_generator.rs b/crates/fs/src/nova/algorithms/key_generator.rs index 7e49489ba..89104470e 100644 --- a/crates/fs/src/nova/algorithms/key_generator.rs +++ b/crates/fs/src/nova/algorithms/key_generator.rs @@ -1,3 +1,5 @@ +//! Key generation for Nova. + use ark_std::sync::Arc; use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig}, diff --git a/crates/fs/src/nova/algorithms/mod.rs b/crates/fs/src/nova/algorithms/mod.rs index 11e838298..263d3cd42 100644 --- a/crates/fs/src/nova/algorithms/mod.rs +++ b/crates/fs/src/nova/algorithms/mod.rs @@ -1,3 +1,5 @@ +//! Implementations folding scheme algorithms for Nova. + pub mod key_generator; pub mod preprocessor; pub mod prover; diff --git a/crates/fs/src/nova/algorithms/preprocessor.rs b/crates/fs/src/nova/algorithms/preprocessor.rs index c41dbce6b..316fbbec6 100644 --- a/crates/fs/src/nova/algorithms/preprocessor.rs +++ b/crates/fs/src/nova/algorithms/preprocessor.rs @@ -1,3 +1,5 @@ +//! Preprocessing for Nova. + use ark_std::rand::RngCore; use sonobe_primitives::{commitments::GroupBasedCommitment, traits::SonobeField}; diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index 49901a0d4..de6ec8485 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -1,3 +1,5 @@ +//! Proof generation for Nova. + use ark_ff::One; use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, ops::Mul, rand::RngCore}; #[cfg(feature = "parallel")] diff --git a/crates/fs/src/nova/algorithms/verifier.rs b/crates/fs/src/nova/algorithms/verifier.rs index e682e5b36..bfc3f89b9 100644 --- a/crates/fs/src/nova/algorithms/verifier.rs +++ b/crates/fs/src/nova/algorithms/verifier.rs @@ -1,3 +1,5 @@ +//! Proof verification for Nova. + use ark_std::{borrow::Borrow, cfg_iter, ops::Mul}; #[cfg(feature = "parallel")] use rayon::prelude::*; diff --git a/crates/fs/src/nova/circuits/mod.rs b/crates/fs/src/nova/circuits/mod.rs index 9a0722027..8d54a8eb9 100644 --- a/crates/fs/src/nova/circuits/mod.rs +++ b/crates/fs/src/nova/circuits/mod.rs @@ -1 +1,3 @@ +//! In-circuit gadgets for Nova. + pub mod verifier; diff --git a/crates/fs/src/nova/circuits/verifier.rs b/crates/fs/src/nova/circuits/verifier.rs index b550373be..a69bb0835 100644 --- a/crates/fs/src/nova/circuits/verifier.rs +++ b/crates/fs/src/nova/circuits/verifier.rs @@ -1,9 +1,9 @@ -use ark_ff::{One, Zero}; +//! Partial and full in-circuit verifier implementations for Nova. + use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, groups::CurveVar}; use ark_relations::gr1cs::SynthesisError; -use num_bigint::BigInt; use sonobe_primitives::{ - algebra::{field::emulated::Bounds, ops::bits::FromBitsGadget}, + algebra::ops::bits::FromBitsGadget, commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, transcripts::TranscriptGadget, }; diff --git a/crates/fs/src/nova/instances/circuits.rs b/crates/fs/src/nova/instances/circuits.rs index 9a850957c..6d0c81007 100644 --- a/crates/fs/src/nova/instances/circuits.rs +++ b/crates/fs/src/nova/instances/circuits.rs @@ -1,3 +1,5 @@ +//! In-circuit variables for Nova instances. + use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, @@ -12,11 +14,17 @@ use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::Absorbabl use super::{IncomingInstance, RunningInstance}; use crate::FoldingInstanceVar; +/// [`RunningInstanceVar`] defines Nova's running instance variable. #[derive(Clone, Debug, PartialEq)] pub struct RunningInstanceVar { + /// [`RunningInstanceVar::cm_e`] is the error term commitment. pub cm_e: CM::CommitmentVar, + /// [`RunningInstanceVar::u`] is the constant term. pub u: CM::ScalarVar, + /// [`RunningInstanceVar::cm_w`] is the witness commitment. pub cm_w: CM::CommitmentVar, + /// [`RunningInstanceVar::x`] is the vector of public inputs (to the + /// circuit). pub x: Vec, } @@ -120,9 +128,13 @@ impl FoldingInstanceVar for RunningInstanceVar } } +/// [`IncomingInstanceVar`] defines Nova's incoming instance variable. #[derive(Clone, Debug, PartialEq)] pub struct IncomingInstanceVar { + /// [`IncomingInstanceVar::cm_w`] is the witness commitment. pub cm_w: CM::CommitmentVar, + /// [`IncomingInstanceVar::x`] is the vector of public inputs (to the + /// circuit). pub x: Vec, } diff --git a/crates/fs/src/nova/instances/mod.rs b/crates/fs/src/nova/instances/mod.rs index ba81b5838..a2408bafb 100644 --- a/crates/fs/src/nova/instances/mod.rs +++ b/crates/fs/src/nova/instances/mod.rs @@ -1,3 +1,6 @@ +//! Definitions of out-of-circuit values and in-circuit variables for Nova +//! instances. + use ark_ff::PrimeField; use sonobe_primitives::{ arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy, @@ -8,11 +11,16 @@ use crate::FoldingInstance; pub mod circuits; +/// [`RunningInstance`] defines Nova's running instance. #[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningInstance { + /// [`RunningInstance::cm_e`] is the error term commitment. pub cm_e: CM::Commitment, + /// [`RunningInstance::u`] is the constant term. pub u: CM::Scalar, + /// [`RunningInstance::cm_w`] is the witness commitment. pub cm_w: CM::Commitment, + /// [`RunningInstance::x`] is the vector of public inputs (to the circuit). pub x: Vec, } @@ -52,9 +60,12 @@ impl Absorbable for RunningInstance { } } +/// [`IncomingInstance`] defines Nova's incoming instance. #[derive(Clone, Debug, Eq, PartialEq)] pub struct IncomingInstance { + /// [`IncomingInstance::cm_w`] is the witness commitment. pub cm_w: CM::Commitment, + /// [`IncomingInstance::x`] is the vector of public inputs (to the circuit). pub x: Vec, } diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 12e3e2240..e9988cf2d 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -1,3 +1,8 @@ +//! This module implements the Nova folding scheme, which is introduced in this +//! [paper]. +//! +//! [paper]: https://eprint.iacr.org/2021/370.pdf + use ark_r1cs_std::boolean::Boolean; use ark_std::{UniformRand, marker::PhantomData, rand::RngCore, sync::Arc}; use sonobe_primitives::{ @@ -28,6 +33,7 @@ pub mod circuits; pub mod instances; pub mod witnesses; +/// [`NovaKey`] is Nova's decider key. #[derive(Clone)] pub struct NovaKey { arith: Arc, @@ -160,13 +166,18 @@ where // From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this // case, we keep the size of S close to 2^128. +/// [`AbstractNova`] implements the Nova folding scheme which can operate on +/// both the primary and secondary curves. pub struct AbstractNova { _t: PhantomData<(CM, TF)>, } +/// [`Nova`] is the main Nova folding scheme on the primary curve. pub type Nova = AbstractNova::Scalar, CHALLENGE_BITS>; +/// [`CycleFoldNova`] is the Nova folding scheme on the secondary curve which +/// can be used as the folding scheme for folding CycleFold instances. pub type CycleFoldNova = AbstractNova::Commitment>, CHALLENGE_BITS>; @@ -193,14 +204,22 @@ impl Fol // From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this // case, we keep the size of S close to 2^128. -// TODO: experimental design +/// [`AbstractNova2`] implements the Nova folding scheme which can operate on +/// both the primary and secondary curves. +/// +/// This design is experimental, following the definition of accumulation +/// schemes where the incoming witnesses and instances are simply plain vectors +/// in the circuit's assignments. pub struct AbstractNova2 { _t: PhantomData<(CM, TF)>, } +/// [`Nova2`] is the main Nova folding scheme on the primary curve. pub type Nova2 = AbstractNova2::Scalar, CHALLENGE_BITS>; +/// [`CycleFoldNova2`] is the Nova folding scheme on the secondary curve which +/// can be used as the folding scheme for folding CycleFold instances. pub type CycleFoldNova2 = AbstractNova2::Commitment>, CHALLENGE_BITS>; @@ -223,6 +242,7 @@ impl Fol type Proof = (CM::Commitment, CM::Commitment); } +/// [`AbstractNovaGadget`] is the in-circuit gadget for [`AbstractNova`]. pub struct AbstractNovaGadget { _vc: PhantomData, } diff --git a/crates/fs/src/nova/witnesses/circuits.rs b/crates/fs/src/nova/witnesses/circuits.rs index ee8554fd0..b85ea26f5 100644 --- a/crates/fs/src/nova/witnesses/circuits.rs +++ b/crates/fs/src/nova/witnesses/circuits.rs @@ -1,3 +1,5 @@ +//! In-circuit variables for Nova witnesses. + use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, @@ -8,11 +10,17 @@ use sonobe_primitives::commitments::CommitmentDefGadget; use super::{IncomingWitness, RunningWitness}; +/// [`RunningWitnessVar`] defines Nova's running witness variable. #[derive(Debug, PartialEq)] pub struct RunningWitnessVar { + /// [`RunningWitnessVar::e`] is the error term. pub e: Vec, + /// [`RunningWitnessVar::r_e`] is the randomness for the error term + /// commitment. pub r_e: CM::RandomnessVar, + /// [`RunningWitnessVar::w`] is the vector of witnesses (to the circuit). pub w: Vec, + /// [`RunningWitnessVar::r_w`] is the randomness for the witness commitment. pub r_w: CM::RandomnessVar, } @@ -57,9 +65,13 @@ impl GR1CSVar for RunningWitnessVa } } +/// [`IncomingWitnessVar`] defines Nova's incoming witness variable. #[derive(Debug, PartialEq)] pub struct IncomingWitnessVar { + /// [`IncomingWitnessVar::w`] is the vector of witnesses (to the circuit). pub w: Vec, + /// [`IncomingWitnessVar::r_w`] is the randomness for the witness + /// commitment. pub r_w: CM::RandomnessVar, } diff --git a/crates/fs/src/nova/witnesses/mod.rs b/crates/fs/src/nova/witnesses/mod.rs index 583abfebd..5f144df36 100644 --- a/crates/fs/src/nova/witnesses/mod.rs +++ b/crates/fs/src/nova/witnesses/mod.rs @@ -1,14 +1,22 @@ +//! Definitions of out-of-circuit values and in-circuit variables for Nova +//! witnesses. + use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy}; use crate::FoldingWitness; pub mod circuits; +/// [`RunningWitness`] defines Nova's running witness. #[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningWitness { + /// [`RunningWitness::e`] is the error term. pub e: Vec, + /// [`RunningWitness::r_e`] is the randomness for the error term commitment. pub r_e: CM::Randomness, + /// [`RunningWitness::w`] is the vector of witnesses (to the circuit). pub w: Vec, + /// [`RunningWitness::r_w`] is the randomness for the witness commitment. pub r_w: CM::Randomness, } @@ -31,9 +39,12 @@ impl Dummy<&Cfg> for RunningWitness { } } +/// [`IncomingWitness`] defines Nova's incoming witness. #[derive(Clone, Debug, Eq, PartialEq)] pub struct IncomingWitness { + /// [`IncomingWitness::w`] is the witness (to the circuit). pub w: Vec, + /// [`IncomingWitness::r_w`] is the randomness for the witness commitment. pub r_w: CM::Randomness, } diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 88eab910a..dec49d0d6 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -1,3 +1,5 @@ +//! Nova CycleFold adapter that bridges Nova into the CycleFold IVC compiler. + use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean, @@ -22,7 +24,7 @@ use crate::compilers::cyclefold::{ CycleFoldBasedIVC, FoldingSchemeCycleFoldExt, circuits::CycleFoldCircuit, }; -/// Configuration for Nova's CycleFold circuit +/// [`NovaCycleFoldCircuit`] defines CycleFold circuit for Nova. pub struct NovaCycleFoldCircuit { r: Vec, points: Vec, @@ -193,6 +195,8 @@ impl FoldingSchemeCycleFo } } +/// [`NovaNovaIVC`] defines a CycleFold-based IVC using Nova as the primary +/// folding scheme and Nova as the secondary folding scheme. pub type NovaNovaIVC = CycleFoldBasedIVC, CycleFoldNova, T>; From 5f8724e9e67ac3a4774a94ae202f8cfbafad2e5b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 84/99] Refactor: start porting Ova --- crates/fs/src/lib.rs | 1 + crates/fs/src/ova/instance.rs | 48 +++++ crates/fs/src/ova/mod.rs | 318 ++++++++++++++++++++++++++++++++++ crates/fs/src/ova/witness.rs | 26 +++ 4 files changed, 393 insertions(+) create mode 100644 crates/fs/src/ova/instance.rs create mode 100644 crates/fs/src/ova/mod.rs create mode 100644 crates/fs/src/ova/witness.rs diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index a347a133a..1c0f0b8fb 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -26,6 +26,7 @@ pub mod definitions; pub mod nova; +pub mod ova; pub use self::definitions::{ FoldingSchemeDef, FoldingSchemeDefGadget, diff --git a/crates/fs/src/ova/instance.rs b/crates/fs/src/ova/instance.rs new file mode 100644 index 000000000..0f30a6b75 --- /dev/null +++ b/crates/fs/src/ova/instance.rs @@ -0,0 +1,48 @@ +use ark_ff::PrimeField; +use sonobe_primitives::{ + arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy, + transcripts::Absorbable, +}; + +use crate::FoldingInstance; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RunningInstance { + pub u: CM::Scalar, + pub cm: CM::Commitment, + pub x: Vec, +} + +impl FoldingInstance for RunningInstance { + const N_COMMITMENTS: usize = 1; + + fn commitments(&self) -> Vec<&CM::Commitment> { + vec![&self.cm] + } + + fn public_inputs(&self) -> &[CM::Scalar] { + &self.x + } + + fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { + &mut self.x + } +} + +impl Dummy<&Cfg> for RunningInstance { + fn dummy(cfg: &Cfg) -> Self { + Self { + u: Default::default(), + cm: Default::default(), + x: vec![Default::default(); cfg.n_public_inputs()], + } + } +} + +impl Absorbable for RunningInstance { + fn absorb_into(&self, dest: &mut Vec) { + self.u.absorb_into(dest); + self.x.absorb_into(dest); + self.cm.absorb_into(dest); + } +} diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs new file mode 100644 index 000000000..7d33e471d --- /dev/null +++ b/crates/fs/src/ova/mod.rs @@ -0,0 +1,318 @@ +use ark_ff::One; +use ark_std::{ + UniformRand, borrow::Borrow, cfg_iter, marker::PhantomData, ops::Mul, rand::RngCore, sync::Arc, +}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use sonobe_primitives::{ + algebra::ops::bits::FromBits, + arithmetizations::{ + Arith, ArithConfig, ArithRelation, + r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, + }, + circuits::{Assignments, AssignmentsOwned}, + commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, + relations::{Relation, WitnessInstanceSampler}, + transcripts::Transcript, +}; + +use self::{instance::RunningInstance as RU, witness::RunningWitness as RW}; +use crate::{ + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeKeyGenerator, FoldingSchemePreprocessor, + FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as IU, PlainWitness as IW, +}; + +pub mod instance; +pub mod witness; + +#[derive(Clone)] +pub struct OvaKey { + arith: Arc, + ck: Arc, +} + +impl DeciderKey for OvaKey { + type ProverKey = Self; + type VerifierKey = (); + type ArithConfig = A::Config; + + fn to_pk(&self) -> &Self::ProverKey { + self + } + + fn to_vk(&self) -> &Self::VerifierKey { + &() + } + + fn to_arith_config(&self) -> &Self::ArithConfig { + self.arith.config() + } +} + +impl Relation, RU> for OvaKey +where + A: for<'a> ArithRelation< + RelaxedWitness<&'a [CM::Scalar]>, + RelaxedInstance<&'a [CM::Scalar]>, + Evaluation = Vec, + >, + CM: CommitmentOps, +{ + type Error = Error; + + fn check_relation(&self, w: &RW, u: &RU) -> Result<(), Self::Error> { + let e = self.arith.eval_relation( + &RelaxedWitness { w: &w.w, e: &[] }, + &RelaxedInstance { x: &u.x, u: &u.u }, + )?; + CM::open(&self.ck, &[&w.w[..], &e].concat(), &w.r, &u.cm)?; + Ok(()) + } +} + +impl Relation, IU> for OvaKey +where + A: ArithRelation, Vec>, + CM: CommitmentDef, +{ + type Error = Error; + + fn check_relation(&self, w: &IW, u: &IU) -> Result<(), Self::Error> { + self.arith.check_relation(w, u)?; + Ok(()) + } +} + +impl WitnessInstanceSampler, IU> + for OvaKey +{ + type Source = AssignmentsOwned; + type Error = Error; + + fn sample( + &self, + z: Self::Source, + _rng: impl RngCore, + ) -> Result<(IW, IU), Error> { + Ok((z.private.into(), z.public.into())) + } +} + +impl WitnessInstanceSampler, RU> for OvaKey +where + A: for<'a> ArithRelation< + RelaxedWitness<&'a [CM::Scalar]>, + RelaxedInstance<&'a [CM::Scalar]>, + Evaluation = Vec, + >, + CM: CommitmentOps, +{ + type Source = (); + type Error = Error; + + fn sample(&self, _: Self::Source, mut rng: impl RngCore) -> Result<(RW, RU), Error> { + let cfg = self.arith.config(); + + let u = CM::Scalar::rand(&mut rng); + let x = (0..cfg.n_public_inputs()) + .map(|_| CM::Scalar::rand(&mut rng)) + .collect::>(); + let w = (0..cfg.n_witnesses()) + .map(|_| CM::Scalar::rand(&mut rng)) + .collect::>(); + let e = self.arith.eval_relation( + &RelaxedWitness { w: &w, e: &[] }, + &RelaxedInstance { x: &x, u: &u }, + )?; + + let (cm, r) = CM::commit(&self.ck, &[&w[..], &e].concat(), &mut rng)?; + Ok((RW { w, r }, RU { x, cm, u })) + } +} + +pub struct Ova { + _vc: PhantomData, +} + +impl FoldingSchemeDef + for Ova +{ + type CM = CM; + type RW = RW; + type RU = RU; + type IW = IW; + type IU = IU; + + type TranscriptField = CM::Scalar; + type Arith = R1CS; + + type Config = (usize, usize); + type PublicParam = CM::Key; + type DeciderKey = OvaKey; + type Challenge = [bool; CHALLENGE_BITS]; + type Proof = CM::Commitment; +} + +impl FoldingSchemePreprocessor + for Ova +{ + fn preprocess( + (n_constraints, n_witnesses): (usize, usize), + mut rng: impl RngCore, + ) -> Result { + let ck = CM::generate_key(n_constraints + n_witnesses, &mut rng)?; + Ok(ck) + } +} + +impl FoldingSchemeKeyGenerator + for Ova +{ + fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let r1cs = Arc::new(r1cs); + let cfg = r1cs.config(); + if ck.max_scalars_len() < cfg.n_constraints() + cfg.n_witnesses() { + return Err(Error::InvalidPublicParameters( + "The commitment key generated by preprocessing is too short for the R1CS instance" + .into(), + )); + } + Ok(Self::DeciderKey { arith: r1cs, ck }) + } +} + +impl FoldingSchemeProver<1, 1> + for Ova +{ + fn prove( + pk: &OvaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; 1], + Us: &[impl Borrow; 1], + ws: &[impl Borrow; 1], + us: &[impl Borrow; 1], + rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + let (W, U) = (Ws[0].borrow(), Us[0].borrow()); + let (w, u) = (ws[0].borrow(), us[0].borrow()); + + // Compute the cross term `T` by following the original Nova paper. + let z1 = Assignments::from((U.u, &U.x, &W.w)); + let z2 = Assignments::from((CM::Scalar::one(), &u[..], &w[..])); + let t = pk.arith.evaluate_rows(|((a, b), c)| { + let az1: CM::Scalar = a.iter().map(|(val, col)| z1[*col] * val).sum(); + let az2: CM::Scalar = a.iter().map(|(val, col)| z2[*col] * val).sum(); + let bz1: CM::Scalar = b.iter().map(|(val, col)| z1[*col] * val).sum(); + let bz2: CM::Scalar = b.iter().map(|(val, col)| z2[*col] * val).sum(); + let cz1: CM::Scalar = c.iter().map(|(val, col)| z1[*col] * val).sum(); + let cz2: CM::Scalar = c.iter().map(|(val, col)| z2[*col] * val).sum(); + Ok(az1 * bz2 + az2 * bz1 - z2[0] * cz1 - z1[0] * cz2) + })?; + + let (cm, r) = CM::commit(&pk.ck, &[w, &t[..]].concat(), rng)?; + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(&cm); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + Ok(( + RW { + w: cfg_iter!(W.w) + .zip(&w[..]) + .map(|(a, b)| rho * b + a) + .collect(), + r: W.r + r * rho, + }, + RU { + u: U.u + rho, + cm: U.cm + cm.mul(rho), + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }, + cm, + rho_bits.try_into().unwrap(), + )) + } +} + +impl FoldingSchemeVerifier<1, 1> + for Ova +{ + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; 1], + us: &[impl Borrow; 1], + cm: &Self::Proof<1, 1>, + ) -> Result { + let (U, u) = (Us[0].borrow(), us[0].borrow()); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(cm); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + Ok(RU { + u: U.u + rho, + cm: U.cm + cm.mul(rho), + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }) + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::{Fr, G1Projective}; + use ark_ff::UniformRand; + use ark_std::{error::Error, test_rng}; + use sonobe_primitives::{ + circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, + commitments::pedersen::Pedersen, + }; + + use super::*; + use crate::tests::test_folding_scheme; + + #[test] + fn test_ova() -> Result<(), Box> { + let mut rng = test_rng(); + + let config = (4, 4); + + test_folding_scheme::>, 1, 1>( + config, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..10) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + + test_folding_scheme::>, 1, 1>( + config, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..10) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + Ok(()) + } +} diff --git a/crates/fs/src/ova/witness.rs b/crates/fs/src/ova/witness.rs new file mode 100644 index 000000000..624bfad41 --- /dev/null +++ b/crates/fs/src/ova/witness.rs @@ -0,0 +1,26 @@ +use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy}; + +use crate::FoldingWitness; + +#[derive(Debug, PartialEq)] +pub struct RunningWitness { + pub w: Vec, + pub r: CM::Randomness, +} + +impl FoldingWitness for RunningWitness { + const N_OPENINGS: usize = 1; + + fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)> { + vec![(&self.w, &self.r)] + } +} + +impl Dummy<&Cfg> for RunningWitness { + fn dummy(cfg: &Cfg) -> Self { + Self { + w: vec![Default::default(); cfg.n_witnesses()], + r: Default::default(), + } + } +} From 46330a4a81cc337b19e3895266777455429a5158 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:27 +0800 Subject: [PATCH 85/99] Refactor: allow Ova to have any transcript field --- crates/fs/src/ova/mod.rs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs index 7d33e471d..749a4f452 100644 --- a/crates/fs/src/ova/mod.rs +++ b/crates/fs/src/ova/mod.rs @@ -13,6 +13,7 @@ use sonobe_primitives::{ circuits::{Assignments, AssignmentsOwned}, commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, + traits::SonobeField, transcripts::Transcript, }; @@ -130,12 +131,15 @@ where } } -pub struct Ova { - _vc: PhantomData, +pub struct AbstractOva { + _t: PhantomData<(CM, TF)>, } -impl FoldingSchemeDef - for Ova +pub type Ova = + AbstractOva::Scalar, CHALLENGE_BITS>; + +impl FoldingSchemeDef + for AbstractOva { type CM = CM; type RW = RW; @@ -143,7 +147,7 @@ impl FoldingSchemeDef type IW = IW; type IU = IU; - type TranscriptField = CM::Scalar; + type TranscriptField = TF; type Arith = R1CS; type Config = (usize, usize); @@ -153,8 +157,8 @@ impl FoldingSchemeDef type Proof = CM::Commitment; } -impl FoldingSchemePreprocessor - for Ova +impl + FoldingSchemePreprocessor for AbstractOva { fn preprocess( (n_constraints, n_witnesses): (usize, usize), @@ -165,8 +169,8 @@ impl FoldingSchemePreproc } } -impl FoldingSchemeKeyGenerator - for Ova +impl + FoldingSchemeKeyGenerator for AbstractOva { fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { let ck = Arc::new(ck); @@ -182,12 +186,12 @@ impl FoldingSchemeKeyGene } } -impl FoldingSchemeProver<1, 1> - for Ova +impl + FoldingSchemeProver<1, 1> for AbstractOva { fn prove( pk: &OvaKey, - transcript: &mut impl Transcript, + transcript: &mut impl Transcript, Ws: &[impl Borrow; 1], Us: &[impl Borrow; 1], ws: &[impl Borrow; 1], @@ -242,12 +246,12 @@ impl FoldingSchemeProver< } } -impl FoldingSchemeVerifier<1, 1> - for Ova +impl + FoldingSchemeVerifier<1, 1> for AbstractOva { fn verify( _vk: &(), - transcript: &mut impl Transcript, + transcript: &mut impl Transcript, Us: &[impl Borrow; 1], us: &[impl Borrow; 1], cm: &Self::Proof<1, 1>, From 51d30574b1a16d319b4e07dd56e11541c03b2606 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 19:04:22 +0800 Subject: [PATCH 86/99] Prepare for cyclefold --- crates/fs/src/ova/mod.rs | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs index 749a4f452..09ae05694 100644 --- a/crates/fs/src/ova/mod.rs +++ b/crates/fs/src/ova/mod.rs @@ -13,7 +13,7 @@ use sonobe_primitives::{ circuits::{Assignments, AssignmentsOwned}, commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, - traits::SonobeField, + traits::{CF2, SonobeField}, transcripts::Transcript, }; @@ -135,8 +135,11 @@ pub struct AbstractOva { _t: PhantomData<(CM, TF)>, } -pub type Ova = - AbstractOva::Scalar, CHALLENGE_BITS>; +pub type Ova = + AbstractOva::Scalar, CHALLENGE_BITS>; + +pub type CycleFoldOva = + AbstractOva::Commitment>, CHALLENGE_BITS>; impl FoldingSchemeDef for AbstractOva @@ -279,7 +282,7 @@ impl #[cfg(test)] mod tests { - use ark_bn254::{Fr, G1Projective}; + use ark_bn254::{Fq, Fr, G1Projective}; use ark_ff::UniformRand; use ark_std::{error::Error, test_rng}; use sonobe_primitives::{ @@ -290,33 +293,42 @@ mod tests { use super::*; use crate::tests::test_folding_scheme; - #[test] - fn test_ova() -> Result<(), Box> { - let mut rng = test_rng(); - + fn test_ova_opt( + rounds: usize, + mut rng: impl RngCore, + ) -> Result<(), Box> { let config = (4, 4); - test_folding_scheme::>, 1, 1>( + test_folding_scheme::, TF>, 1, 1>( config, CircuitForTest { x: Fr::rand(&mut rng), }, - (0..10) + (0..rounds) .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) .collect(), &mut rng, )?; - test_folding_scheme::>, 1, 1>( + test_folding_scheme::, TF>, 1, 1>( config, CircuitForTest { x: Fr::rand(&mut rng), }, - (0..10) + (0..rounds) .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) .collect(), &mut rng, )?; Ok(()) } + + #[test] + fn test_ova() -> Result<(), Box> { + let mut rng = test_rng(); + + test_ova_opt::(10, &mut rng)?; + test_ova_opt::(10, &mut rng)?; + Ok(()) + } } From a8a459a897cc33876f212046dee59a72331fc99b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 20:49:56 +0800 Subject: [PATCH 87/99] In circuit variables for Ova instances and witnesses --- crates/fs/src/ova/instance/circuits.rs | 111 +++++++++++++++++ .../src/ova/{instance.rs => instance/mod.rs} | 2 + crates/fs/src/ova/mod.rs | 117 +++++++++++++++++- crates/fs/src/ova/witness/circuits.rs | 48 +++++++ .../fs/src/ova/{witness.rs => witness/mod.rs} | 4 +- 5 files changed, 275 insertions(+), 7 deletions(-) create mode 100644 crates/fs/src/ova/instance/circuits.rs rename crates/fs/src/ova/{instance.rs => instance/mod.rs} (98%) create mode 100644 crates/fs/src/ova/witness/circuits.rs rename crates/fs/src/ova/{witness.rs => witness/mod.rs} (92%) diff --git a/crates/fs/src/ova/instance/circuits.rs b/crates/fs/src/ova/instance/circuits.rs new file mode 100644 index 000000000..e0b58af52 --- /dev/null +++ b/crates/fs/src/ova/instance/circuits.rs @@ -0,0 +1,111 @@ +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, + fields::fp::FpVar, + prelude::Boolean, + select::CondSelectGadget, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::borrow::Borrow; +use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::AbsorbableVar}; + +use super::RunningInstance; +use crate::FoldingInstanceVar; + +#[derive(Clone, Debug, PartialEq)] +pub struct RunningInstanceVar { + pub u: CM::ScalarVar, + pub cm: CM::CommitmentVar, + pub x: Vec, +} + +impl AllocVar, CM::ConstraintField> + for RunningInstanceVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let RunningInstance { u, cm, x } = v.borrow(); + Ok(Self { + u: AllocVar::new_variable(cs.clone(), || Ok(u), mode)?, + cm: AllocVar::new_variable(cs.clone(), || Ok(cm), mode)?, + x: AllocVar::new_variable(cs.clone(), || Ok(&x[..]), mode)?, + }) + } +} + +impl GR1CSVar for RunningInstanceVar { + type Value = RunningInstance; + + fn cs(&self) -> ConstraintSystemRef { + self.u.cs().or(self.cm.cs()).or(self.x.cs()) + } + + fn value(&self) -> Result { + Ok(RunningInstance { + u: self.u.value()?, + cm: self.cm.value()?, + x: self.x.value()?, + }) + } +} + +impl AbsorbableVar for RunningInstanceVar { + fn absorb_into( + &self, + dest: &mut Vec>, + ) -> Result<(), SynthesisError> { + self.u.absorb_into(dest)?; + self.x.absorb_into(dest)?; + self.cm.absorb_into(dest) + } +} + +impl CondSelectGadget for RunningInstanceVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.x.len() != false_value.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self { + u: cond.select(&true_value.u, &false_value.u)?, + x: true_value + .x + .iter() + .zip(&false_value.x) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + cm: cond.select(&true_value.cm, &false_value.cm)?, + }) + } +} + +impl FoldingInstanceVar for RunningInstanceVar { + fn commitments(&self) -> Vec<&CM::CommitmentVar> { + vec![&self.cm] + } + + fn public_inputs(&self) -> &Vec { + &self.x + } + + fn new_witness_with_public_inputs( + cs: impl Into>, + u: &Self::Value, + x: Vec, + ) -> Result { + let cs = cs.into().cs(); + Ok(Self { + u: AllocVar::new_witness(cs.clone(), || Ok(&u.u))?, + cm: AllocVar::new_witness(cs.clone(), || Ok(&u.cm))?, + x, + }) + } +} diff --git a/crates/fs/src/ova/instance.rs b/crates/fs/src/ova/instance/mod.rs similarity index 98% rename from crates/fs/src/ova/instance.rs rename to crates/fs/src/ova/instance/mod.rs index 0f30a6b75..283057bf8 100644 --- a/crates/fs/src/ova/instance.rs +++ b/crates/fs/src/ova/instance/mod.rs @@ -6,6 +6,8 @@ use sonobe_primitives::{ use crate::FoldingInstance; +pub mod circuits; + #[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningInstance { pub u: CM::Scalar, diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs index 09ae05694..e32b2b09f 100644 --- a/crates/fs/src/ova/mod.rs +++ b/crates/fs/src/ova/mod.rs @@ -1,26 +1,35 @@ use ark_ff::One; +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, boolean::Boolean, groups::CurveVar}; +use ark_relations::gr1cs::SynthesisError; use ark_std::{ UniformRand, borrow::Borrow, cfg_iter, marker::PhantomData, ops::Mul, rand::RngCore, sync::Arc, }; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ - algebra::ops::bits::FromBits, + algebra::ops::bits::{FromBits, FromBitsGadget}, arithmetizations::{ Arith, ArithConfig, ArithRelation, r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, }, circuits::{Assignments, AssignmentsOwned}, - commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, + commitments::{ + CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, GroupBasedCommitment, + }, relations::{Relation, WitnessInstanceSampler}, traits::{CF2, SonobeField}, - transcripts::Transcript, + transcripts::{Transcript, TranscriptGadget}, }; -use self::{instance::RunningInstance as RU, witness::RunningWitness as RW}; +use self::{ + instance::{RunningInstance as RU, circuits::RunningInstanceVar as RUVar}, + witness::RunningWitness as RW, +}; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeKeyGenerator, FoldingSchemePreprocessor, - FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as IU, PlainWitness as IW, + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, + FoldingSchemeKeyGenerator, FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, + FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as IU, PlainInstanceVar as IUVar, + PlainWitness as IW, }; pub mod instance; @@ -280,6 +289,102 @@ impl } } +pub struct AbstractOvaGadget { + _t: PhantomData, +} + +impl FoldingSchemeDefGadget + for AbstractOvaGadget +where + CM: CommitmentDefGadget, +{ + type Widget = AbstractOva; + + type CM = CM; + type RU = RUVar; + type IU = IUVar; + type VerifierKey = (); + type Challenge = [Boolean; CHALLENGE_BITS]; + type Proof = CM::CommitmentVar; +} + +impl FoldingSchemePartialVerifierGadget<1, 1> + for AbstractOvaGadget +where + CM: CommitmentDefGadget, +{ + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(( + Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm: CM::CommitmentVar::new_witness(U.cm.cs().or(proof.cs()).or(rho.cs()), || { + Ok(U.cm.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) + })?, + x: U.x + .iter() + .zip(&u[..]) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl FoldingSchemeFullVerifierGadget<1, 1> + for AbstractOvaGadget +where + CM: CommitmentDefGadget, + CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, +{ + fn verify( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm: proof.scalar_mul_le(rho_bits.iter())? + &U.cm, + x: U.x + .iter() + .zip(&u[..]) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }) + } +} + #[cfg(test)] mod tests { use ark_bn254::{Fq, Fr, G1Projective}; diff --git a/crates/fs/src/ova/witness/circuits.rs b/crates/fs/src/ova/witness/circuits.rs new file mode 100644 index 000000000..0a7652fc6 --- /dev/null +++ b/crates/fs/src/ova/witness/circuits.rs @@ -0,0 +1,48 @@ +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::borrow::Borrow; +use sonobe_primitives::commitments::CommitmentDefGadget; + +use super::RunningWitness; + +#[derive(Debug, PartialEq)] +pub struct RunningWitnessVar { + pub w: Vec, + pub r: CM::RandomnessVar, +} + +impl AllocVar, CM::ConstraintField> + for RunningWitnessVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let RunningWitness { w, r } = v.borrow(); + Ok(Self { + w: AllocVar::new_variable(cs.clone(), || Ok(&w[..]), mode)?, + r: AllocVar::new_variable(cs.clone(), || Ok(r), mode)?, + }) + } +} + +impl GR1CSVar for RunningWitnessVar { + type Value = RunningWitness; + + fn cs(&self) -> ConstraintSystemRef { + self.w.cs().or(self.r.cs()) + } + + fn value(&self) -> Result { + Ok(RunningWitness { + w: self.w.value()?, + r: self.r.value()?, + }) + } +} diff --git a/crates/fs/src/ova/witness.rs b/crates/fs/src/ova/witness/mod.rs similarity index 92% rename from crates/fs/src/ova/witness.rs rename to crates/fs/src/ova/witness/mod.rs index 624bfad41..a20c1a854 100644 --- a/crates/fs/src/ova/witness.rs +++ b/crates/fs/src/ova/witness/mod.rs @@ -2,7 +2,9 @@ use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDe use crate::FoldingWitness; -#[derive(Debug, PartialEq)] +pub mod circuits; + +#[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningWitness { pub w: Vec, pub r: CM::Randomness, From 2c1602e4abc1fddd44a4b339f49318cf0e499802 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 8 Nov 2025 08:47:34 +0800 Subject: [PATCH 88/99] Ova adapter --- crates/fs/src/ova/mod.rs | 25 ++- .../src/compilers/cyclefold/adapters/mod.rs | 1 + .../src/compilers/cyclefold/adapters/nova.rs | 22 +++ .../src/compilers/cyclefold/adapters/ova.rs | 154 ++++++++++++++++++ 4 files changed, 196 insertions(+), 6 deletions(-) create mode 100644 crates/ivc/src/compilers/cyclefold/adapters/ova.rs diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs index e32b2b09f..54a094717 100644 --- a/crates/fs/src/ova/mod.rs +++ b/crates/fs/src/ova/mod.rs @@ -26,10 +26,7 @@ use self::{ witness::RunningWitness as RW, }; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, - FoldingSchemeKeyGenerator, FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, - FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as IU, PlainInstanceVar as IUVar, - PlainWitness as IW, + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, FoldingSchemeKeyGenerator, FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, FoldingSchemeProver, FoldingSchemeVerifier, GroupBasedFoldingSchemePrimaryDef, GroupBasedFoldingSchemeSecondaryDef, PlainInstance as IU, PlainInstanceVar as IUVar, PlainWitness as IW }; pub mod instance; @@ -201,6 +198,7 @@ impl impl FoldingSchemeProver<1, 1> for AbstractOva { + #[allow(non_snake_case)] fn prove( pk: &OvaKey, transcript: &mut impl Transcript, @@ -246,7 +244,7 @@ impl }, RU { u: U.u + rho, - cm: U.cm + cm.mul(rho), + cm: U.cm + cm * rho, x: cfg_iter!(U.x) .zip(&u[..]) .map(|(a, b)| rho * b + a) @@ -261,6 +259,7 @@ impl impl FoldingSchemeVerifier<1, 1> for AbstractOva { + #[allow(non_snake_case)] fn verify( _vk: &(), transcript: &mut impl Transcript, @@ -280,7 +279,7 @@ impl Ok(RU { u: U.u + rho, - cm: U.cm + cm.mul(rho), + cm: U.cm + *cm * rho, x: cfg_iter!(U.x) .zip(&u[..]) .map(|(a, b)| rho * b + a) @@ -313,6 +312,7 @@ impl FoldingSchemePartialVerifierGadget<1, 1> where CM: CommitmentDefGadget, { + #[allow(non_snake_case)] fn verify_hinted( _vk: &Self::VerifierKey, transcript: &mut impl TranscriptGadget, @@ -355,6 +355,7 @@ where CM: CommitmentDefGadget, CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, { + #[allow(non_snake_case)] fn verify( _vk: &Self::VerifierKey, transcript: &mut impl TranscriptGadget, @@ -385,6 +386,18 @@ where } } +impl GroupBasedFoldingSchemePrimaryDef + for AbstractOva +{ + type Gadget = AbstractOvaGadget; +} + +impl GroupBasedFoldingSchemeSecondaryDef + for AbstractOva, CHALLENGE_BITS> +{ + type Gadget = AbstractOvaGadget; +} + #[cfg(test)] mod tests { use ark_bn254::{Fq, Fr, G1Projective}; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/mod.rs b/crates/ivc/src/compilers/cyclefold/adapters/mod.rs index cfbca4153..8994ba2cd 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/mod.rs @@ -2,3 +2,4 @@ //! folding schemes. pub mod nova; +pub mod ova; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index dec49d0d6..a2d199a42 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -9,6 +9,7 @@ use ark_std::{borrow::Borrow, iter::once}; use sonobe_fs::{ FoldingSchemeDefGadget, nova::{CycleFoldNova, Nova}, + ova::CycleFoldOva, }; use sonobe_primitives::{ algebra::{ @@ -195,6 +196,11 @@ impl FoldingSchemeCycleFo } } +/// [`NovaOvaIVC`] defines a CycleFold-based IVC using Nova as the primary +/// folding scheme and Ova as the secondary folding scheme. +pub type NovaOvaIVC = + CycleFoldBasedIVC, CycleFoldOva, T>; + /// [`NovaNovaIVC`] defines a CycleFold-based IVC using Nova as the primary /// folding scheme and Nova as the secondary folding scheme. pub type NovaNovaIVC = @@ -217,6 +223,22 @@ mod tests { use super::*; use crate::tests::test_ivc; + #[test] + fn test_nova_ova() -> Result<(), Box> { + let mut rng = thread_rng(); + + test_ivc::, Pedersen, GriffinSponge<_>>, _>( + (65536, (2048, 2048), Arc::new(GriffinParams::new(16, 5, 9))), + CircuitForTest { + x: Fr::rand(&mut rng), + }, + vec![(); 20], + &mut rng, + )?; + + Ok(()) + } + #[test] fn test_nova_nova() -> Result<(), Box> { let mut rng = thread_rng(); diff --git a/crates/ivc/src/compilers/cyclefold/adapters/ova.rs b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs new file mode 100644 index 000000000..25f6dacd7 --- /dev/null +++ b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs @@ -0,0 +1,154 @@ +use ark_ff::{PrimeField, Zero}; +use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean}; +use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; +use ark_std::{borrow::Borrow, iter::once}; +use sonobe_fs::{ + FoldingSchemeDefGadget, + nova::CycleFoldNova, + ova::{CycleFoldOva, Ova}, +}; +use sonobe_primitives::{ + algebra::{ + field::emulated::{Bounds, EmulatedFieldVar}, + ops::bits::{FromBits, FromBitsGadget, ToBitsGadgetExt}, + }, + commitments::GroupBasedCommitment, + traits::{CF2, SonobeCurve}, +}; + +use crate::compilers::cyclefold::{ + CycleFoldBasedIVC, FoldingSchemeCycleFoldExt, circuits::CycleFoldCircuit, +}; + +/// Configuration for Ova's CycleFold circuit +pub struct OvaCycleFoldCircuit { + r: Vec, + points: Vec, +} + +impl Default + for OvaCycleFoldCircuit +{ + fn default() -> Self { + Self { + r: vec![false; CHALLENGE_BITS], + points: vec![C::zero(); 2], + } + } +} + +impl CycleFoldCircuit> + for OvaCycleFoldCircuit +{ + fn verify_point_rlc(&self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + let rho = FpVar::new_input(cs.clone(), || Ok(CF2::::from_bits_le(&self.r[..])))?; + let rho_bits = rho.to_n_bits_le(CHALLENGE_BITS)?; + + let points = Vec::::new_witness(cs.clone(), || Ok(&self.points[..]))?; + for point in &points { + Self::mark_point_as_public(point)?; + } + + Self::mark_point_as_public(&(points[1].scalar_mul_le(rho_bits.iter())? + &points[0])) + } +} + +impl FoldingSchemeCycleFoldExt<1, 1> + for Ova +{ + const N_CYCLEFOLDS: usize = 1; + + type CFCircuit = OvaCycleFoldCircuit; + + fn to_cyclefold_circuits( + [U]: &[impl Borrow; 1], + _us: &[impl Borrow; 1], + proof: &Self::Proof<1, 1>, + rho: Self::Challenge, + ) -> Vec { + vec![OvaCycleFoldCircuit { + r: rho.into(), + points: vec![U.borrow().cm, *proof], + }] + } + + fn to_cyclefold_inputs( + [U]: [::RU; 1], + _us: [::IU; 1], + UU: ::RU, + proof: ::Proof<1, 1>, + rho: ::Challenge, + ) -> Result>>>, SynthesisError> { + let mut rho = rho.to_vec(); + rho.resize( + CF2::::MODULUS_BIT_SIZE as usize, + Boolean::FALSE, + ); + Ok(vec![ + once(EmulatedFieldVar::from_bounded_bits_le( + &rho, + Bounds(Zero::zero(), CF2::::MODULUS.into().into()), + )?) + .chain([U.cm, proof, UU.cm].into_iter().flat_map(|p| [p.x, p.y])) + .collect(), + ]) + } +} + +pub type OvaOvaIVC = + CycleFoldBasedIVC, CycleFoldOva, T>; + +pub type OvaNovaIVC = + CycleFoldBasedIVC, CycleFoldNova, T>; + +#[cfg(test)] +mod tests { + use ark_bn254::{Fr, G1Projective as C1}; + use ark_ff::UniformRand; + use ark_grumpkin::Projective as C2; + use ark_std::{error::Error, rand::thread_rng, sync::Arc}; + use sonobe_primitives::{ + circuits::utils::CircuitForTest, + commitments::pedersen::Pedersen, + transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, + }; + + use super::*; + use crate::tests::test_ivc; + + #[test] + fn test_ova_ova() -> Result<(), Box> { + let mut rng = thread_rng(); + + test_ivc::, Pedersen, GriffinSponge<_>>, _>( + ( + (65536, 65536), + (2048, 2048), + Arc::new(GriffinParams::new(16, 5, 9)), + ), + CircuitForTest { + x: Fr::rand(&mut rng), + }, + vec![(); 20], + &mut rng, + )?; + + Ok(()) + } + + #[test] + fn test_ova_nova() -> Result<(), Box> { + let mut rng = thread_rng(); + + test_ivc::, Pedersen, GriffinSponge<_>>, _>( + ((65536, 65536), 2048, Arc::new(GriffinParams::new(16, 5, 9))), + CircuitForTest { + x: Fr::rand(&mut rng), + }, + vec![(); 20], + &mut rng, + )?; + + Ok(()) + } +} From 681faf4a2df81c14be54c9de44cc842b72291607 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 04:50:00 +0800 Subject: [PATCH 89/99] Split impls into separate submodules --- crates/fs/src/ova/algorithms/key_generator.rs | 25 ++ crates/fs/src/ova/algorithms/mod.rs | 4 + crates/fs/src/ova/algorithms/preprocessor.rs | 16 ++ crates/fs/src/ova/algorithms/prover.rs | 74 ++++++ crates/fs/src/ova/algorithms/verifier.rs | 41 ++++ crates/fs/src/ova/circuits/mod.rs | 1 + crates/fs/src/ova/circuits/verifier.rs | 90 +++++++ .../ova/{instance => instances}/circuits.rs | 2 +- .../fs/src/ova/{instance => instances}/mod.rs | 0 crates/fs/src/ova/mod.rs | 231 +----------------- .../ova/{witness => witnesses}/circuits.rs | 0 .../fs/src/ova/{witness => witnesses}/mod.rs | 0 .../src/compilers/cyclefold/adapters/ova.rs | 2 + 13 files changed, 267 insertions(+), 219 deletions(-) create mode 100644 crates/fs/src/ova/algorithms/key_generator.rs create mode 100644 crates/fs/src/ova/algorithms/mod.rs create mode 100644 crates/fs/src/ova/algorithms/preprocessor.rs create mode 100644 crates/fs/src/ova/algorithms/prover.rs create mode 100644 crates/fs/src/ova/algorithms/verifier.rs create mode 100644 crates/fs/src/ova/circuits/mod.rs create mode 100644 crates/fs/src/ova/circuits/verifier.rs rename crates/fs/src/ova/{instance => instances}/circuits.rs (99%) rename crates/fs/src/ova/{instance => instances}/mod.rs (100%) rename crates/fs/src/ova/{witness => witnesses}/circuits.rs (100%) rename crates/fs/src/ova/{witness => witnesses}/mod.rs (100%) diff --git a/crates/fs/src/ova/algorithms/key_generator.rs b/crates/fs/src/ova/algorithms/key_generator.rs new file mode 100644 index 000000000..7cfff3f3d --- /dev/null +++ b/crates/fs/src/ova/algorithms/key_generator.rs @@ -0,0 +1,25 @@ +use ark_std::sync::Arc; +use sonobe_primitives::{ + arithmetizations::{Arith, ArithConfig}, + commitments::{CommitmentKey, GroupBasedCommitment}, + traits::SonobeField, +}; + +use crate::{Error, FoldingSchemeKeyGenerator, ova::AbstractOva}; + +impl + FoldingSchemeKeyGenerator for AbstractOva +{ + fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let r1cs = Arc::new(r1cs); + let cfg = r1cs.config(); + if ck.max_scalars_len() < cfg.n_constraints() + cfg.n_witnesses() { + return Err(Error::InvalidPublicParameters( + "The commitment key generated by preprocessing is too short for the R1CS instance" + .into(), + )); + } + Ok(Self::DeciderKey { arith: r1cs, ck }) + } +} diff --git a/crates/fs/src/ova/algorithms/mod.rs b/crates/fs/src/ova/algorithms/mod.rs new file mode 100644 index 000000000..11e838298 --- /dev/null +++ b/crates/fs/src/ova/algorithms/mod.rs @@ -0,0 +1,4 @@ +pub mod key_generator; +pub mod preprocessor; +pub mod prover; +pub mod verifier; diff --git a/crates/fs/src/ova/algorithms/preprocessor.rs b/crates/fs/src/ova/algorithms/preprocessor.rs new file mode 100644 index 000000000..d5e988c91 --- /dev/null +++ b/crates/fs/src/ova/algorithms/preprocessor.rs @@ -0,0 +1,16 @@ +use ark_std::rand::RngCore; +use sonobe_primitives::{commitments::GroupBasedCommitment, traits::SonobeField}; + +use crate::{Error, FoldingSchemePreprocessor, ova::AbstractOva}; + +impl + FoldingSchemePreprocessor for AbstractOva +{ + fn preprocess( + (n_constraints, n_witnesses): (usize, usize), + mut rng: impl RngCore, + ) -> Result { + let ck = CM::generate_key(n_constraints + n_witnesses, &mut rng)?; + Ok(ck) + } +} diff --git a/crates/fs/src/ova/algorithms/prover.rs b/crates/fs/src/ova/algorithms/prover.rs new file mode 100644 index 000000000..1941951bd --- /dev/null +++ b/crates/fs/src/ova/algorithms/prover.rs @@ -0,0 +1,74 @@ +use ark_ff::One; +use ark_std::{borrow::Borrow, cfg_iter, rand::RngCore}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use sonobe_primitives::{ + algebra::ops::bits::FromBits, circuits::Assignments, commitments::GroupBasedCommitment, + traits::SonobeField, transcripts::Transcript, +}; + +use crate::{ + Error, FoldingSchemeProver, + ova::{AbstractOva, OvaKey}, +}; + +impl + FoldingSchemeProver<1, 1> for AbstractOva +{ + #[allow(non_snake_case)] + fn prove( + pk: &OvaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; 1], + Us: &[impl Borrow; 1], + ws: &[impl Borrow; 1], + us: &[impl Borrow; 1], + rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + let (W, U) = (Ws[0].borrow(), Us[0].borrow()); + let (w, u) = (ws[0].borrow(), us[0].borrow()); + + // Compute the cross term `T` by following the original Nova paper. + let z1 = Assignments::from((U.u, &U.x, &W.w)); + let z2 = Assignments::from((CM::Scalar::one(), &u[..], &w[..])); + let t = pk.arith.evaluate_rows(|((a, b), c)| { + let az1: CM::Scalar = a.iter().map(|(val, col)| z1[*col] * val).sum(); + let az2: CM::Scalar = a.iter().map(|(val, col)| z2[*col] * val).sum(); + let bz1: CM::Scalar = b.iter().map(|(val, col)| z1[*col] * val).sum(); + let bz2: CM::Scalar = b.iter().map(|(val, col)| z2[*col] * val).sum(); + let cz1: CM::Scalar = c.iter().map(|(val, col)| z1[*col] * val).sum(); + let cz2: CM::Scalar = c.iter().map(|(val, col)| z2[*col] * val).sum(); + Ok(az1 * bz2 + az2 * bz1 - z2[0] * cz1 - z1[0] * cz2) + })?; + + let (cm, r) = CM::commit(&pk.ck, &[w, &t[..]].concat(), rng)?; + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(&cm); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + Ok(( + Self::RW { + w: cfg_iter!(W.w) + .zip(&w[..]) + .map(|(a, b)| rho * b + a) + .collect(), + r: W.r + r * rho, + }, + Self::RU { + u: U.u + rho, + cm: U.cm + cm * rho, + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }, + cm, + rho_bits.try_into().unwrap(), + )) + } +} diff --git a/crates/fs/src/ova/algorithms/verifier.rs b/crates/fs/src/ova/algorithms/verifier.rs new file mode 100644 index 000000000..57f771fe3 --- /dev/null +++ b/crates/fs/src/ova/algorithms/verifier.rs @@ -0,0 +1,41 @@ +use ark_std::{borrow::Borrow, cfg_iter}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use sonobe_primitives::{ + algebra::ops::bits::FromBits, commitments::GroupBasedCommitment, traits::SonobeField, + transcripts::Transcript, +}; + +use crate::{Error, FoldingSchemeVerifier, ova::AbstractOva}; + +impl + FoldingSchemeVerifier<1, 1> for AbstractOva +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; 1], + us: &[impl Borrow; 1], + cm: &Self::Proof<1, 1>, + ) -> Result { + let (U, u) = (Us[0].borrow(), us[0].borrow()); + + let rho_bits = { + transcript.add(&U); + transcript.add(&u); + transcript.add(cm); + transcript.challenge_bits(CHALLENGE_BITS) + }; + let rho = CM::Scalar::from_bits_le(&rho_bits); + + Ok(Self::RU { + u: U.u + rho, + cm: U.cm + *cm * rho, + x: cfg_iter!(U.x) + .zip(&u[..]) + .map(|(a, b)| rho * b + a) + .collect(), + }) + } +} diff --git a/crates/fs/src/ova/circuits/mod.rs b/crates/fs/src/ova/circuits/mod.rs new file mode 100644 index 000000000..9a0722027 --- /dev/null +++ b/crates/fs/src/ova/circuits/mod.rs @@ -0,0 +1 @@ +pub mod verifier; diff --git a/crates/fs/src/ova/circuits/verifier.rs b/crates/fs/src/ova/circuits/verifier.rs new file mode 100644 index 000000000..eb397a597 --- /dev/null +++ b/crates/fs/src/ova/circuits/verifier.rs @@ -0,0 +1,90 @@ +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, groups::CurveVar}; +use ark_relations::gr1cs::SynthesisError; +use sonobe_primitives::{ + algebra::ops::bits::FromBitsGadget, + commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, + transcripts::TranscriptGadget, +}; + +use crate::{ + FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, ova::AbstractOvaGadget, +}; + +impl FoldingSchemePartialVerifierGadget<1, 1> + for AbstractOvaGadget +where + CM: CommitmentDefGadget, +{ + #[allow(non_snake_case)] + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(( + Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm: CM::CommitmentVar::new_witness(U.cm.cs().or(proof.cs()).or(rho.cs()), || { + Ok(U.cm.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) + })?, + x: U.x + .iter() + .zip(&u[..]) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl FoldingSchemeFullVerifierGadget<1, 1> + for AbstractOvaGadget +where + CM: CommitmentDefGadget, + CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, +{ + #[allow(non_snake_case)] + fn verify( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + [U]: [&Self::RU; 1], + [u]: [&Self::IU; 1], + proof: &Self::Proof<1, 1>, + ) -> Result { + let rho_bits = { + transcript.add(&U)?; + transcript.add(&u)?; + transcript.add(proof)?; + transcript.challenge_bits(CHALLENGE_BITS)? + }; + let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + + Ok(Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm: proof.scalar_mul_le(rho_bits.iter())? + &U.cm, + x: U.x + .iter() + .zip(&u[..]) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }) + } +} diff --git a/crates/fs/src/ova/instance/circuits.rs b/crates/fs/src/ova/instances/circuits.rs similarity index 99% rename from crates/fs/src/ova/instance/circuits.rs rename to crates/fs/src/ova/instances/circuits.rs index e0b58af52..06af66ff5 100644 --- a/crates/fs/src/ova/instance/circuits.rs +++ b/crates/fs/src/ova/instances/circuits.rs @@ -1,8 +1,8 @@ use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, + boolean::Boolean, fields::fp::FpVar, - prelude::Boolean, select::CondSelectGadget, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; diff --git a/crates/fs/src/ova/instance/mod.rs b/crates/fs/src/ova/instances/mod.rs similarity index 100% rename from crates/fs/src/ova/instance/mod.rs rename to crates/fs/src/ova/instances/mod.rs diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs index 54a094717..1b90d37ea 100644 --- a/crates/fs/src/ova/mod.rs +++ b/crates/fs/src/ova/mod.rs @@ -1,36 +1,32 @@ -use ark_ff::One; -use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, boolean::Boolean, groups::CurveVar}; -use ark_relations::gr1cs::SynthesisError; -use ark_std::{ - UniformRand, borrow::Borrow, cfg_iter, marker::PhantomData, ops::Mul, rand::RngCore, sync::Arc, -}; +use ark_r1cs_std::boolean::Boolean; +use ark_std::{UniformRand, marker::PhantomData, rand::RngCore, sync::Arc}; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ - algebra::ops::bits::{FromBits, FromBitsGadget}, arithmetizations::{ Arith, ArithConfig, ArithRelation, r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, }, - circuits::{Assignments, AssignmentsOwned}, - commitments::{ - CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, GroupBasedCommitment, - }, + circuits::AssignmentsOwned, + commitments::{CommitmentDef, CommitmentDefGadget, CommitmentOps, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, traits::{CF2, SonobeField}, - transcripts::{Transcript, TranscriptGadget}, }; use self::{ - instance::{RunningInstance as RU, circuits::RunningInstanceVar as RUVar}, - witness::RunningWitness as RW, + instances::{RunningInstance as RU, circuits::RunningInstanceVar as RUVar}, + witnesses::RunningWitness as RW, }; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, FoldingSchemeKeyGenerator, FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, FoldingSchemeProver, FoldingSchemeVerifier, GroupBasedFoldingSchemePrimaryDef, GroupBasedFoldingSchemeSecondaryDef, PlainInstance as IU, PlainInstanceVar as IUVar, PlainWitness as IW + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, + GroupBasedFoldingSchemeSecondaryDef, PlainInstance as IU, PlainInstanceVar as IUVar, + PlainWitness as IW, }; -pub mod instance; -pub mod witness; +pub mod algorithms; +pub mod circuits; +pub mod instances; +pub mod witnesses; #[derive(Clone)] pub struct OvaKey { @@ -166,128 +162,6 @@ impl Fol type Proof = CM::Commitment; } -impl - FoldingSchemePreprocessor for AbstractOva -{ - fn preprocess( - (n_constraints, n_witnesses): (usize, usize), - mut rng: impl RngCore, - ) -> Result { - let ck = CM::generate_key(n_constraints + n_witnesses, &mut rng)?; - Ok(ck) - } -} - -impl - FoldingSchemeKeyGenerator for AbstractOva -{ - fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { - let ck = Arc::new(ck); - let r1cs = Arc::new(r1cs); - let cfg = r1cs.config(); - if ck.max_scalars_len() < cfg.n_constraints() + cfg.n_witnesses() { - return Err(Error::InvalidPublicParameters( - "The commitment key generated by preprocessing is too short for the R1CS instance" - .into(), - )); - } - Ok(Self::DeciderKey { arith: r1cs, ck }) - } -} - -impl - FoldingSchemeProver<1, 1> for AbstractOva -{ - #[allow(non_snake_case)] - fn prove( - pk: &OvaKey, - transcript: &mut impl Transcript, - Ws: &[impl Borrow; 1], - Us: &[impl Borrow; 1], - ws: &[impl Borrow; 1], - us: &[impl Borrow; 1], - rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { - let (W, U) = (Ws[0].borrow(), Us[0].borrow()); - let (w, u) = (ws[0].borrow(), us[0].borrow()); - - // Compute the cross term `T` by following the original Nova paper. - let z1 = Assignments::from((U.u, &U.x, &W.w)); - let z2 = Assignments::from((CM::Scalar::one(), &u[..], &w[..])); - let t = pk.arith.evaluate_rows(|((a, b), c)| { - let az1: CM::Scalar = a.iter().map(|(val, col)| z1[*col] * val).sum(); - let az2: CM::Scalar = a.iter().map(|(val, col)| z2[*col] * val).sum(); - let bz1: CM::Scalar = b.iter().map(|(val, col)| z1[*col] * val).sum(); - let bz2: CM::Scalar = b.iter().map(|(val, col)| z2[*col] * val).sum(); - let cz1: CM::Scalar = c.iter().map(|(val, col)| z1[*col] * val).sum(); - let cz2: CM::Scalar = c.iter().map(|(val, col)| z2[*col] * val).sum(); - Ok(az1 * bz2 + az2 * bz1 - z2[0] * cz1 - z1[0] * cz2) - })?; - - let (cm, r) = CM::commit(&pk.ck, &[w, &t[..]].concat(), rng)?; - - let rho_bits = { - transcript.add(&U); - transcript.add(&u); - transcript.add(&cm); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - - Ok(( - RW { - w: cfg_iter!(W.w) - .zip(&w[..]) - .map(|(a, b)| rho * b + a) - .collect(), - r: W.r + r * rho, - }, - RU { - u: U.u + rho, - cm: U.cm + cm * rho, - x: cfg_iter!(U.x) - .zip(&u[..]) - .map(|(a, b)| rho * b + a) - .collect(), - }, - cm, - rho_bits.try_into().unwrap(), - )) - } -} - -impl - FoldingSchemeVerifier<1, 1> for AbstractOva -{ - #[allow(non_snake_case)] - fn verify( - _vk: &(), - transcript: &mut impl Transcript, - Us: &[impl Borrow; 1], - us: &[impl Borrow; 1], - cm: &Self::Proof<1, 1>, - ) -> Result { - let (U, u) = (Us[0].borrow(), us[0].borrow()); - - let rho_bits = { - transcript.add(&U); - transcript.add(&u); - transcript.add(cm); - transcript.challenge_bits(CHALLENGE_BITS) - }; - let rho = CM::Scalar::from_bits_le(&rho_bits); - - Ok(RU { - u: U.u + rho, - cm: U.cm + *cm * rho, - x: cfg_iter!(U.x) - .zip(&u[..]) - .map(|(a, b)| rho * b + a) - .collect(), - }) - } -} - pub struct AbstractOvaGadget { _t: PhantomData, } @@ -307,85 +181,6 @@ where type Proof = CM::CommitmentVar; } -impl FoldingSchemePartialVerifierGadget<1, 1> - for AbstractOvaGadget -where - CM: CommitmentDefGadget, -{ - #[allow(non_snake_case)] - fn verify_hinted( - _vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - [U]: [&Self::RU; 1], - [u]: [&Self::IU; 1], - proof: &Self::Proof<1, 1>, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { - let rho_bits = { - transcript.add(&U)?; - transcript.add(&u)?; - transcript.add(proof)?; - transcript.challenge_bits(CHALLENGE_BITS)? - }; - let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; - - Ok(( - Self::RU { - u: (U.u.clone() + &rho) - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?, - cm: CM::CommitmentVar::new_witness(U.cm.cs().or(proof.cs()).or(rho.cs()), || { - Ok(U.cm.value().unwrap_or_default() - + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) - })?, - x: U.x - .iter() - .zip(&u[..]) - .map(|(a, b)| (b.clone() * &rho + a).try_into()) - .collect::>() - .map_err(|_| SynthesisError::Unsatisfiable)?, - }, - rho_bits.try_into().unwrap(), - )) - } -} - -impl FoldingSchemeFullVerifierGadget<1, 1> - for AbstractOvaGadget -where - CM: CommitmentDefGadget, - CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, -{ - #[allow(non_snake_case)] - fn verify( - _vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - [U]: [&Self::RU; 1], - [u]: [&Self::IU; 1], - proof: &Self::Proof<1, 1>, - ) -> Result { - let rho_bits = { - transcript.add(&U)?; - transcript.add(&u)?; - transcript.add(proof)?; - transcript.challenge_bits(CHALLENGE_BITS)? - }; - let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; - - Ok(Self::RU { - u: (U.u.clone() + &rho) - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?, - cm: proof.scalar_mul_le(rho_bits.iter())? + &U.cm, - x: U.x - .iter() - .zip(&u[..]) - .map(|(a, b)| (b.clone() * &rho + a).try_into()) - .collect::>() - .map_err(|_| SynthesisError::Unsatisfiable)?, - }) - } -} - impl GroupBasedFoldingSchemePrimaryDef for AbstractOva { diff --git a/crates/fs/src/ova/witness/circuits.rs b/crates/fs/src/ova/witnesses/circuits.rs similarity index 100% rename from crates/fs/src/ova/witness/circuits.rs rename to crates/fs/src/ova/witnesses/circuits.rs diff --git a/crates/fs/src/ova/witness/mod.rs b/crates/fs/src/ova/witnesses/mod.rs similarity index 100% rename from crates/fs/src/ova/witness/mod.rs rename to crates/fs/src/ova/witnesses/mod.rs diff --git a/crates/ivc/src/compilers/cyclefold/adapters/ova.rs b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs index 25f6dacd7..d1443099f 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/ova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs @@ -60,6 +60,7 @@ impl FoldingSchemeCycleFo type CFCircuit = OvaCycleFoldCircuit; + #[allow(non_snake_case)] fn to_cyclefold_circuits( [U]: &[impl Borrow; 1], _us: &[impl Borrow; 1], @@ -72,6 +73,7 @@ impl FoldingSchemeCycleFo }] } + #[allow(non_snake_case)] fn to_cyclefold_inputs( [U]: [::RU; 1], _us: [::IU; 1], From 493d8fa0403a3f2736c4c9b9bdb4546ab9485bdf Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 90/99] Actually test wasm targets --- crates/fs/src/ova/mod.rs | 6 ++++-- crates/ivc/src/compilers/cyclefold/adapters/ova.rs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs index 1b90d37ea..5a9b5c44c 100644 --- a/crates/fs/src/ova/mod.rs +++ b/crates/fs/src/ova/mod.rs @@ -197,11 +197,13 @@ impl GroupBasedFoldingSch mod tests { use ark_bn254::{Fq, Fr, G1Projective}; use ark_ff::UniformRand; - use ark_std::{error::Error, test_rng}; + use ark_std::{error::Error, rand::thread_rng}; use sonobe_primitives::{ circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, commitments::pedersen::Pedersen, }; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::tests::test_folding_scheme; @@ -238,7 +240,7 @@ mod tests { #[test] fn test_ova() -> Result<(), Box> { - let mut rng = test_rng(); + let mut rng = thread_rng(); test_ova_opt::(10, &mut rng)?; test_ova_opt::(10, &mut rng)?; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/ova.rs b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs index d1443099f..6c9d495de 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/ova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs @@ -114,6 +114,8 @@ mod tests { commitments::pedersen::Pedersen, transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, }; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::tests::test_ivc; From 749e2a4d4c267a4dd49f16baf78270b430112eb1 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:10:33 +0800 Subject: [PATCH 91/99] Add Ova docs --- crates/fs/src/ova/algorithms/key_generator.rs | 2 ++ crates/fs/src/ova/algorithms/mod.rs | 2 ++ crates/fs/src/ova/algorithms/preprocessor.rs | 2 ++ crates/fs/src/ova/algorithms/prover.rs | 2 ++ crates/fs/src/ova/algorithms/verifier.rs | 2 ++ crates/fs/src/ova/circuits/mod.rs | 2 ++ crates/fs/src/ova/circuits/verifier.rs | 2 ++ crates/fs/src/ova/instances/circuits.rs | 8 ++++++++ crates/fs/src/ova/instances/mod.rs | 8 ++++++++ crates/fs/src/ova/mod.rs | 14 ++++++++++++-- crates/fs/src/ova/witnesses/circuits.rs | 5 +++++ crates/fs/src/ova/witnesses/mod.rs | 6 ++++++ crates/ivc/src/compilers/cyclefold/adapters/ova.rs | 8 +++++++- 13 files changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/fs/src/ova/algorithms/key_generator.rs b/crates/fs/src/ova/algorithms/key_generator.rs index 7cfff3f3d..f897b3466 100644 --- a/crates/fs/src/ova/algorithms/key_generator.rs +++ b/crates/fs/src/ova/algorithms/key_generator.rs @@ -1,3 +1,5 @@ +//! Key generation for Ova. + use ark_std::sync::Arc; use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig}, diff --git a/crates/fs/src/ova/algorithms/mod.rs b/crates/fs/src/ova/algorithms/mod.rs index 11e838298..7c51fe3d8 100644 --- a/crates/fs/src/ova/algorithms/mod.rs +++ b/crates/fs/src/ova/algorithms/mod.rs @@ -1,3 +1,5 @@ +//! Implementations folding scheme algorithms for Ova. + pub mod key_generator; pub mod preprocessor; pub mod prover; diff --git a/crates/fs/src/ova/algorithms/preprocessor.rs b/crates/fs/src/ova/algorithms/preprocessor.rs index d5e988c91..36729fb56 100644 --- a/crates/fs/src/ova/algorithms/preprocessor.rs +++ b/crates/fs/src/ova/algorithms/preprocessor.rs @@ -1,3 +1,5 @@ +//! Preprocessing for Ova. + use ark_std::rand::RngCore; use sonobe_primitives::{commitments::GroupBasedCommitment, traits::SonobeField}; diff --git a/crates/fs/src/ova/algorithms/prover.rs b/crates/fs/src/ova/algorithms/prover.rs index 1941951bd..12fc69f94 100644 --- a/crates/fs/src/ova/algorithms/prover.rs +++ b/crates/fs/src/ova/algorithms/prover.rs @@ -1,3 +1,5 @@ +//! Proof generation for Ova. + use ark_ff::One; use ark_std::{borrow::Borrow, cfg_iter, rand::RngCore}; #[cfg(feature = "parallel")] diff --git a/crates/fs/src/ova/algorithms/verifier.rs b/crates/fs/src/ova/algorithms/verifier.rs index 57f771fe3..72754dd9e 100644 --- a/crates/fs/src/ova/algorithms/verifier.rs +++ b/crates/fs/src/ova/algorithms/verifier.rs @@ -1,3 +1,5 @@ +//! Proof verification for Ova. + use ark_std::{borrow::Borrow, cfg_iter}; #[cfg(feature = "parallel")] use rayon::prelude::*; diff --git a/crates/fs/src/ova/circuits/mod.rs b/crates/fs/src/ova/circuits/mod.rs index 9a0722027..ffed2641a 100644 --- a/crates/fs/src/ova/circuits/mod.rs +++ b/crates/fs/src/ova/circuits/mod.rs @@ -1 +1,3 @@ +//! In-circuit gadgets for Ova. + pub mod verifier; diff --git a/crates/fs/src/ova/circuits/verifier.rs b/crates/fs/src/ova/circuits/verifier.rs index eb397a597..8581e809d 100644 --- a/crates/fs/src/ova/circuits/verifier.rs +++ b/crates/fs/src/ova/circuits/verifier.rs @@ -1,3 +1,5 @@ +//! Partial and full in-circuit verifier implementations for Ova. + use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, groups::CurveVar}; use ark_relations::gr1cs::SynthesisError; use sonobe_primitives::{ diff --git a/crates/fs/src/ova/instances/circuits.rs b/crates/fs/src/ova/instances/circuits.rs index 06af66ff5..3b3ee1a29 100644 --- a/crates/fs/src/ova/instances/circuits.rs +++ b/crates/fs/src/ova/instances/circuits.rs @@ -1,3 +1,5 @@ +//! In-circuit variables for Ova instances. + use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, @@ -12,10 +14,16 @@ use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::Absorbabl use super::RunningInstance; use crate::FoldingInstanceVar; +/// [`RunningInstanceVar`] defines Ova's running instance variable. #[derive(Clone, Debug, PartialEq)] pub struct RunningInstanceVar { + /// [`RunningInstanceVar::u`] is the constant term. pub u: CM::ScalarVar, + /// [`RunningInstanceVar::cm`] is the combined witness and error term + /// commitment. pub cm: CM::CommitmentVar, + /// [`RunningInstanceVar::x`] is the vector of public inputs (to the + /// circuit). pub x: Vec, } diff --git a/crates/fs/src/ova/instances/mod.rs b/crates/fs/src/ova/instances/mod.rs index 283057bf8..498373588 100644 --- a/crates/fs/src/ova/instances/mod.rs +++ b/crates/fs/src/ova/instances/mod.rs @@ -1,3 +1,6 @@ +//! Definitions of out-of-circuit values and in-circuit variables for Ova +//! instances. + use ark_ff::PrimeField; use sonobe_primitives::{ arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy, @@ -8,10 +11,15 @@ use crate::FoldingInstance; pub mod circuits; +/// [`RunningInstance`] defines Ova's running instance. #[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningInstance { + /// [`RunningInstance::u`] is the constant term. pub u: CM::Scalar, + /// [`RunningInstance::cm`] is the combined witness and error term + /// commitment. pub cm: CM::Commitment, + /// [`RunningInstance::x`] is the vector of public inputs (to the circuit). pub x: Vec, } diff --git a/crates/fs/src/ova/mod.rs b/crates/fs/src/ova/mod.rs index 5a9b5c44c..f9299091c 100644 --- a/crates/fs/src/ova/mod.rs +++ b/crates/fs/src/ova/mod.rs @@ -1,7 +1,10 @@ +//! This module implements the Ova folding scheme, which is introduced in this +//! [note]. +//! +//! [note]: https://hackmd.io/V4838nnlRKal9ZiTHiGYzw + use ark_r1cs_std::boolean::Boolean; use ark_std::{UniformRand, marker::PhantomData, rand::RngCore, sync::Arc}; -#[cfg(feature = "parallel")] -use rayon::prelude::*; use sonobe_primitives::{ arithmetizations::{ Arith, ArithConfig, ArithRelation, @@ -28,6 +31,7 @@ pub mod circuits; pub mod instances; pub mod witnesses; +/// [`OvaKey`] is Ova's decider key. #[derive(Clone)] pub struct OvaKey { arith: Arc, @@ -133,13 +137,18 @@ where } } +/// [`AbstractOva`] implements the Ova folding scheme which can operate on +/// both the primary and secondary curves. pub struct AbstractOva { _t: PhantomData<(CM, TF)>, } +/// [`Ova`] is the main Ova folding scheme on the primary curve. pub type Ova = AbstractOva::Scalar, CHALLENGE_BITS>; +/// [`CycleFoldOva`] is the Ova folding scheme on the secondary curve which can +/// be used as the folding scheme for folding CycleFold instances. pub type CycleFoldOva = AbstractOva::Commitment>, CHALLENGE_BITS>; @@ -162,6 +171,7 @@ impl Fol type Proof = CM::Commitment; } +/// [`AbstractOvaGadget`] is the in-circuit gadget for [`AbstractOva`]. pub struct AbstractOvaGadget { _t: PhantomData, } diff --git a/crates/fs/src/ova/witnesses/circuits.rs b/crates/fs/src/ova/witnesses/circuits.rs index 0a7652fc6..515c22a29 100644 --- a/crates/fs/src/ova/witnesses/circuits.rs +++ b/crates/fs/src/ova/witnesses/circuits.rs @@ -1,3 +1,5 @@ +//! In-circuit variables for Ova witnesses. + use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, @@ -8,9 +10,12 @@ use sonobe_primitives::commitments::CommitmentDefGadget; use super::RunningWitness; +/// [`RunningWitnessVar`] defines Ova's running witness variable. #[derive(Debug, PartialEq)] pub struct RunningWitnessVar { + /// [`RunningWitnessVar::w`] is the witness (to the circuit). pub w: Vec, + /// [`RunningWitnessVar::r`] is the randomness for the witness commitment. pub r: CM::RandomnessVar, } diff --git a/crates/fs/src/ova/witnesses/mod.rs b/crates/fs/src/ova/witnesses/mod.rs index a20c1a854..7fefbf852 100644 --- a/crates/fs/src/ova/witnesses/mod.rs +++ b/crates/fs/src/ova/witnesses/mod.rs @@ -1,12 +1,18 @@ +//! Definitions of out-of-circuit values and in-circuit variables for Ova +//! witnesses. + use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy}; use crate::FoldingWitness; pub mod circuits; +/// [`RunningWitness`] defines Ova's running witness. #[derive(Clone, Debug, Eq, PartialEq)] pub struct RunningWitness { + /// [`RunningWitness::w`] is the witness (to the circuit). pub w: Vec, + /// [`RunningWitness::r`] is the randomness for the witness commitment. pub r: CM::Randomness, } diff --git a/crates/ivc/src/compilers/cyclefold/adapters/ova.rs b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs index 6c9d495de..6fb0e5d46 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/ova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/ova.rs @@ -1,3 +1,5 @@ +//! Ova CycleFold adapter that bridges Ova into the CycleFold IVC compiler. + use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean}; use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; @@ -20,7 +22,7 @@ use crate::compilers::cyclefold::{ CycleFoldBasedIVC, FoldingSchemeCycleFoldExt, circuits::CycleFoldCircuit, }; -/// Configuration for Ova's CycleFold circuit +/// [`OvaCycleFoldCircuit`] defines CycleFold circuit for Ova. pub struct OvaCycleFoldCircuit { r: Vec, points: Vec, @@ -97,9 +99,13 @@ impl FoldingSchemeCycleFo } } +/// [`OvaOvaIVC`] defines a CycleFold-based IVC using Ova as the primary folding +/// scheme and Ova as the secondary folding scheme. pub type OvaOvaIVC = CycleFoldBasedIVC, CycleFoldOva, T>; +/// [`OvaNovaIVC`] defines a CycleFold-based IVC using Ova as the primary +/// folding scheme and Nova as the secondary folding scheme. pub type OvaNovaIVC = CycleFoldBasedIVC, CycleFoldNova, T>; From 814a6e038f8ebecd8e4acba53bf00f6831761e9f Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 92/99] Refactor: start porting HyperNova --- crates/fs/src/hypernova/instance.rs | 94 +++++ crates/fs/src/hypernova/mod.rs | 563 ++++++++++++++++++++++++++++ crates/fs/src/hypernova/witness.rs | 49 +++ crates/fs/src/lib.rs | 1 + 4 files changed, 707 insertions(+) create mode 100644 crates/fs/src/hypernova/instance.rs create mode 100644 crates/fs/src/hypernova/mod.rs create mode 100644 crates/fs/src/hypernova/witness.rs diff --git a/crates/fs/src/hypernova/instance.rs b/crates/fs/src/hypernova/instance.rs new file mode 100644 index 000000000..fddc58884 --- /dev/null +++ b/crates/fs/src/hypernova/instance.rs @@ -0,0 +1,94 @@ +use ark_ff::PrimeField; +use sonobe_primitives::{ + arithmetizations::{ArithConfig, ccs::{CCSConfig, CCSVariant}}, + commitments::CommitmentDef, + traits::Dummy, + transcripts::Absorbable, +}; + +use crate::FoldingInstance; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LCCCSInstance { + pub cm: CM::Commitment, + pub u: CM::Scalar, + pub x: Vec, + pub r_x: Vec, + pub v: Vec, +} + +impl FoldingInstance for LCCCSInstance { + const N_COMMITMENTS: usize = 1; + + fn commitments(&self) -> Vec<&CM::Commitment> { + vec![&self.cm] + } + + fn public_inputs(&self) -> &[CM::Scalar] { + &self.x + } + + fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { + &mut self.x + } +} + +impl Dummy<&CCSConfig> for LCCCSInstance { + fn dummy(cfg: &CCSConfig) -> Self { + Self { + cm: Default::default(), + u: Default::default(), + x: vec![Default::default(); cfg.n_public_inputs()], + r_x: vec![Default::default(); cfg.log_constraints()], + v: vec![Default::default(); V::n_matrices()], + } + } +} + +impl Absorbable for LCCCSInstance { + fn absorb_into(&self, dest: &mut Vec) { + self.cm.absorb_into(dest); + self.u.absorb_into(dest); + self.x.absorb_into(dest); + self.r_x.absorb_into(dest); + self.v.absorb_into(dest); + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CCCSInstance { + pub cm: CM::Commitment, + pub x: Vec, +} + +impl FoldingInstance for CCCSInstance { + const N_COMMITMENTS: usize = 1; + + fn commitments(&self) -> Vec<&CM::Commitment> { + vec![&self.cm] + } + + fn public_inputs(&self) -> &[CM::Scalar] { + &self.x + } + + fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { + &mut self.x + } +} + +impl Dummy<&Cfg> for CCCSInstance { + fn dummy(cfg: &Cfg) -> Self { + Self { + cm: Default::default(), + x: vec![Default::default(); cfg.n_public_inputs()], + } + } +} + +impl Absorbable for CCCSInstance { + fn absorb_into(&self, dest: &mut Vec) { + self.cm.absorb_into(dest); + self.x.absorb_into(dest); + } +} diff --git a/crates/fs/src/hypernova/mod.rs b/crates/fs/src/hypernova/mod.rs new file mode 100644 index 000000000..e2c24093e --- /dev/null +++ b/crates/fs/src/hypernova/mod.rs @@ -0,0 +1,563 @@ +use ark_ff::{BigInteger, Field, One, PrimeField, Zero}; +use ark_poly::{DenseMultilinearExtension as MLE, MultilinearExtension}; +use ark_std::{ + UniformRand, borrow::Borrow, cfg_into_iter, cfg_iter, log2, marker::PhantomData, rand::RngCore, + sync::Arc, +}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; +use sonobe_primitives::{ + algebra::ops::{ + bits::FromBits, + pow::Pow, + rlc::{ScalarRLC, SliceRLC}, + }, + arithmetizations::{ + Arith, ArithConfig, ArithRelation, Error as ArithError, + ccs::{CCS, CCSConfig, CCSVariant}, + r1cs::R1CSConfig, + }, + circuits::{Assignments, AssignmentsOwned}, + commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, + relations::{Relation, WitnessInstanceSampler}, + sumcheck::{ + Error as SumCheckError, SumCheck, + utils::{EqPoly, VPAuxInfo, VirtualPolynomial}, + }, + traits::{CF1, Dummy, SonobeCurve}, + transcripts::Transcript, +}; + +use self::{ + instance::{CCCSInstance as IU, LCCCSInstance as RU}, + witness::{CCCSWitness as IW, LCCCSWitness as RW}, +}; +use crate::{ + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeKeyGenerator, FoldingSchemePreprocessor, + FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as PU, PlainWitness as PW, +}; + +pub mod instance; +pub mod witness; + +#[derive(Clone)] +pub struct HyperNovaKey { + arith: Arc, + ck: Arc, +} + +impl DeciderKey for HyperNovaKey { + type ProverKey = Self; + type VerifierKey = (); + type ArithConfig = A::Config; + + fn to_pk(&self) -> &Self::ProverKey { + self + } + + fn to_vk(&self) -> &Self::VerifierKey { + &() + } + + fn to_arith_config(&self) -> &Self::ArithConfig { + self.arith.config() + } +} + +impl, V: CCSVariant> ArithRelation, RU> + for CCS +{ + type Evaluation = Vec; + + fn eval_relation(&self, w: &RW, u: &RU) -> Result { + let z = Assignments::from((u.u, &u.x, &w.w)); + Ok(self + .mles(z) + .iter() + .map(|mle| mle.fix_variables(&u.r_x)[0]) + .collect()) + } + + fn check_evaluation(_w: &RW, u: &RU, e: Self::Evaluation) -> Result<(), ArithError> { + cfg_iter!(e) + .zip(&u.v) + .all(|(e, v)| e == v) + .then_some(()) + .ok_or(ArithError::UnsatisfiedAssignments( + "Evaluation contains non-zero values".into(), + )) + } +} + +impl Relation, RU> for HyperNovaKey +where + A: ArithRelation, RU>, + CM: CommitmentOps, +{ + type Error = Error; + + fn check_relation(&self, w: &RW, u: &RU) -> Result<(), Self::Error> { + self.arith.check_relation(w, u)?; + CM::open(&self.ck, &w.w, &w.r, &u.cm)?; + Ok(()) + } +} + +impl Relation, IU> for HyperNovaKey +where + A: ArithRelation, Vec>, + CM: CommitmentOps, +{ + type Error = Error; + + fn check_relation(&self, w: &IW, u: &IU) -> Result<(), Self::Error> { + self.arith.check_relation(&w.w, &u.x)?; + CM::open(&self.ck, &w.w, &w.r, &u.cm)?; + Ok(()) + } +} + +impl Relation, PU> for HyperNovaKey +where + A: ArithRelation, Vec>, + CM: CommitmentDef, +{ + type Error = Error; + + fn check_relation(&self, w: &PW, u: &PU) -> Result<(), Self::Error> { + self.arith.check_relation(w, u)?; + Ok(()) + } +} + +impl WitnessInstanceSampler, IU> for HyperNovaKey { + type Source = AssignmentsOwned; + type Error = Error; + + fn sample(&self, z: Self::Source, rng: impl RngCore) -> Result<(IW, IU), Error> { + let (w, x) = (z.private, z.public); + let (cm, r) = CM::commit(&self.ck, &w, rng)?; + Ok((IW { w, r }, IU { cm, x })) + } +} + +impl WitnessInstanceSampler, PU> + for HyperNovaKey +{ + type Source = AssignmentsOwned; + type Error = Error; + + fn sample( + &self, + z: Self::Source, + _rng: impl RngCore, + ) -> Result<(PW, PU), Error> { + Ok((z.private.into(), z.public.into())) + } +} + +impl WitnessInstanceSampler, RU> for HyperNovaKey +where + A: ArithRelation, RU, Evaluation = Vec>, + CM: CommitmentOps, +{ + type Source = (); + type Error = Error; + + #[allow(non_snake_case)] + fn sample(&self, _: Self::Source, mut rng: impl RngCore) -> Result<(RW, RU), Error> { + let cfg = self.arith.config(); + + let u = CM::Scalar::rand(&mut rng); + let x = (0..cfg.n_public_inputs()) + .map(|_| CM::Scalar::rand(&mut rng)) + .collect::>(); + let w = (0..cfg.n_witnesses()) + .map(|_| CM::Scalar::rand(&mut rng)) + .collect::>(); + let (cm, r) = CM::commit(&self.ck, &w, &mut rng)?; + + let r_x = (0..cfg.log_constraints()) + .map(|_| CM::Scalar::rand(&mut rng)) + .collect(); + + let W = RW { w, r }; + let mut U = RU { + cm, + x, + u, + r_x, + v: vec![], + }; + U.v = self.arith.eval_relation(&W, &U)?; + + Ok((W, U)) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NIMFSProof { + pub sc_proof: Vec>, + pub sigmas: Vec, + pub thetas: Vec, +} + +impl Dummy<&CCSConfig> + for NIMFSProof +{ + fn dummy(cfg: &CCSConfig) -> Self { + let s = cfg.log_constraints(); + let d = cfg.degree(); + let t = V::n_matrices(); + Self { + sc_proof: vec![vec![F::zero(); d + 2]; s], + sigmas: vec![F::zero(); t * M], + thetas: vec![F::zero(); t * N], + } + } +} + +pub struct HyperNova { + _t: PhantomData<(CM, V)>, +} + +impl FoldingSchemeDef + for HyperNova +{ + type CM = CM; + type RW = RW; + type RU = RU; + type IW = IW; + type IU = IU; + + type TranscriptField = CM::Scalar; + type Arith = CCS; + + type Config = usize; + type PublicParam = CM::Key; + type DeciderKey = HyperNovaKey; + type Challenge = [bool; CHALLENGE_BITS]; + type Proof = NIMFSProof; +} + +impl FoldingSchemePreprocessor + for HyperNova +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} + +impl FoldingSchemeKeyGenerator + for HyperNova +{ + fn generate_keys(ck: Self::PublicParam, ccs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let ccs = Arc::new(ccs); + if ck.max_scalars_len() < ccs.config().n_witnesses() { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the CCS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: ccs, ck }) + } +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeProver for HyperNova +{ + #[allow(non_snake_case)] + fn prove( + pk: &HyperNovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; M], + Us: &[impl Borrow; M], + ws: &[impl Borrow; N], + us: &[impl Borrow; N], + _rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error> { + let Ws = &Ws.iter().map(|i| i.borrow()).collect::>(); + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let ws = &ws.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let ccs = &pk.arith; + let d = V::degree(); + let s = ccs.config().log_constraints(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + let (running_gammas, incoming_gammas) = gamma_powers.split_at(M * t); + + // Compute g(x) + let running_mles = Ws + .iter() + .zip(Us) + .flat_map(|(W, U)| ccs.mles((U.u, &U.x, &W.w).into())); + let incoming_mles = ws + .iter() + .zip(us) + .flat_map(|(w, u)| ccs.mles((One::one(), &u.x, &w.w).into())); + let eq_mles = Us + .iter() + .map(|U| &U.r_x) + .chain([&beta]) + .map(|r| MLE::from_evaluations_vec(s, EqPoly::fix_y_evals(r))); + + let running_products = running_gammas + .iter() + .enumerate() + .map(|(i, &gamma)| (gamma, vec![i, (M + N) * t + i / t])); + let incoming_products = incoming_gammas.iter().enumerate().flat_map(|(k, gamma)| { + S.iter().zip(c).map(move |(S_i, &c_i)| { + ( + c_i * gamma, + S_i.iter() + .map(|j| (M + k) * t + j) + .chain([(M + N) * t + M]) + .collect(), + ) + }) + }); + + let g = VirtualPolynomial { + aux_info: VPAuxInfo { + num_variables: s, + max_degree: d + 1, + }, + flattened_ml_extensions: running_mles.chain(incoming_mles).chain(eq_mles).collect(), + products: running_products.chain(incoming_products).collect(), + }; + + // Step 3: Run the sumcheck prover + // Step 2: dig into the sumcheck and extract r_x_prime + let (sumcheck_proof, r_x_prime, mles) = SumCheck::prove(g, transcript)?; + + // Step 4: compute sigmas and thetas + let sigmas = mles[0..t * M] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + let thetas = mles[t * M..t * (M + N)] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(( + Self::RW { + w: Ws + .iter() + .map(|w| &w.w[..]) + .chain(ws.iter().map(|w| &w.w[..])) + .slice_rlc(&rho_powers), + r: Ws + .iter() + .map(|w| w.r) + .chain(ws.iter().map(|w| w.r)) + .scalar_rlc(&rho_powers), + }, + Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(us.iter().map(|u| u.cm)) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u.x[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: sigmas + .chunks(t) + .chain(thetas.chunks(t)) + .slice_rlc(&rho_powers), + }, + NIMFSProof { + sc_proof: sumcheck_proof, + sigmas, + thetas, + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeVerifier for HyperNova +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + proof: &Self::Proof, + ) -> Result { + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let d = V::degree(); + let s = proof.sc_proof.len(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + + let vp_aux_info = VPAuxInfo { + max_degree: d + 1, + num_variables: s, + }; + + // Step 3: Start verifying the sumcheck + // First, compute the expected sumcheck sum: \sum gamma^j v_j + let sum_v_j_gamma = Us + .iter() + .zip(gamma_powers.chunks(t)) + .flat_map(|(U, gammas)| U.v.iter().zip(gammas).map(|(&v, &g)| v * g)) + .sum(); + + // Verify the interactive part of the sumcheck + // Step 2: Dig into the sumcheck claim and extract the randomness used + let (claimed_eval, r_x_prime) = + SumCheck::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; + + // Step 5: Finish verifying sumcheck (verify the claim c) + let e_beta = EqPoly::fix_xy_eval(&beta, &r_x_prime); + let c = proof + .sigmas + .chunks(t) + .zip(Us) + .flat_map(|(sigmas, u)| { + let e_lcccs = EqPoly::fix_xy_eval(&u.r_x, &r_x_prime); + sigmas.iter().map(move |sigma_j| e_lcccs * sigma_j) + }) + .chain(proof.thetas.chunks(t).map(|thetas| { + S.iter() + .zip(c) + .map(|(S_i, &c_i)| c_i * S_i.iter().map(|&j| thetas[j]).product::()) + .sum::() + * e_beta + })) + .zip(gamma_powers) + .map(|(val, gamma_i)| val * gamma_i) + .sum::(); + // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas + (c == claimed_eval).then_some(()).ok_or_else(|| { + SumCheckError::IncorrectEvaluation(claimed_eval.to_string(), c.to_string()) + })?; + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(us.iter().map(|u| u.cm)) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u.x[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: proof + .sigmas + .chunks(t) + .chain(proof.thetas.chunks(t)) + .slice_rlc(&rho_powers), + }) + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::{Fr, G1Projective}; + use ark_ff::UniformRand; + use ark_std::{error::Error, test_rng}; + use sonobe_primitives::{ + circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, + commitments::pedersen::Pedersen, + }; + + use super::*; + use crate::tests::test_folding_scheme; + + #[test] + fn test_hypernova() -> Result<(), Box> { + let mut rng = test_rng(); + + test_folding_scheme::>, 1, 1>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..10) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + + test_folding_scheme::>, 1, 1>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..10) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + Ok(()) + } +} diff --git a/crates/fs/src/hypernova/witness.rs b/crates/fs/src/hypernova/witness.rs new file mode 100644 index 000000000..8745a2158 --- /dev/null +++ b/crates/fs/src/hypernova/witness.rs @@ -0,0 +1,49 @@ +use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy}; + +use crate::FoldingWitness; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LCCCSWitness { + pub w: Vec, + pub r: CM::Randomness, +} + +impl FoldingWitness for LCCCSWitness { + const N_OPENINGS: usize = 1; + + fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)> { + vec![(&self.w, &self.r)] + } +} + +impl Dummy<&Cfg> for LCCCSWitness { + fn dummy(cfg: &Cfg) -> Self { + Self { + w: vec![Default::default(); cfg.n_witnesses()], + r: Default::default(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CCCSWitness { + pub w: Vec, + pub r: CM::Randomness, +} + +impl FoldingWitness for CCCSWitness { + const N_OPENINGS: usize = 1; + + fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)> { + vec![(&self.w, &self.r)] + } +} + +impl Dummy<&Cfg> for CCCSWitness { + fn dummy(cfg: &Cfg) -> Self { + Self { + w: vec![Default::default(); cfg.n_witnesses()], + r: Default::default(), + } + } +} diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 1c0f0b8fb..b1d5db2f2 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -25,6 +25,7 @@ //! - `witnesses/`: Witness types. pub mod definitions; +pub mod hypernova; pub mod nova; pub mod ova; From a3a90a321144c0484e52d33577f1493f622ffe2a Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 15:04:52 +0800 Subject: [PATCH 93/99] Test HyperNova with different `M` and `N` --- crates/fs/src/hypernova/mod.rs | 40 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/crates/fs/src/hypernova/mod.rs b/crates/fs/src/hypernova/mod.rs index e2c24093e..a1d33fe19 100644 --- a/crates/fs/src/hypernova/mod.rs +++ b/crates/fs/src/hypernova/mod.rs @@ -1,8 +1,7 @@ -use ark_ff::{BigInteger, Field, One, PrimeField, Zero}; +use ark_ff::{Field, One}; use ark_poly::{DenseMultilinearExtension as MLE, MultilinearExtension}; use ark_std::{ - UniformRand, borrow::Borrow, cfg_into_iter, cfg_iter, log2, marker::PhantomData, rand::RngCore, - sync::Arc, + UniformRand, borrow::Borrow, cfg_iter, marker::PhantomData, rand::RngCore, sync::Arc, }; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -24,7 +23,7 @@ use sonobe_primitives::{ Error as SumCheckError, SumCheck, utils::{EqPoly, VPAuxInfo, VirtualPolynomial}, }, - traits::{CF1, Dummy, SonobeCurve}, + traits::Dummy, transcripts::Transcript, }; @@ -524,7 +523,10 @@ impl< mod tests { use ark_bn254::{Fr, G1Projective}; use ark_ff::UniformRand; - use ark_std::{error::Error, test_rng}; + use ark_std::{ + error::Error, + rand::{Rng, thread_rng}, + }; use sonobe_primitives::{ circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, commitments::pedersen::Pedersen, @@ -533,31 +535,43 @@ mod tests { use super::*; use crate::tests::test_folding_scheme; - #[test] - fn test_hypernova() -> Result<(), Box> { - let mut rng = test_rng(); - - test_folding_scheme::>, 1, 1>( + fn test_hypernova_opt( + rounds: usize, + mut rng: impl Rng, + ) -> Result<(), Box> { + test_folding_scheme::>, M, N>( 8, CircuitForTest { x: Fr::rand(&mut rng), }, - (0..10) + (0..rounds) .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) .collect(), &mut rng, )?; - test_folding_scheme::>, 1, 1>( + test_folding_scheme::>, M, N>( 8, CircuitForTest { x: Fr::rand(&mut rng), }, - (0..10) + (0..rounds) .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) .collect(), &mut rng, )?; Ok(()) } + + #[test] + fn test_hypernova() -> Result<(), Box> { + let mut rng = thread_rng(); + test_hypernova_opt::<1, 1>(10, &mut rng)?; + test_hypernova_opt::<1, 3>(10, &mut rng)?; + test_hypernova_opt::<3, 1>(10, &mut rng)?; + test_hypernova_opt::<3, 3>(10, &mut rng)?; + test_hypernova_opt::<0, 5>(10, &mut rng)?; + test_hypernova_opt::<5, 0>(10, &mut rng)?; + Ok(()) + } } From ba59a9ebb96048cbf843d0924b931c4eae5f3556 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 22:38:47 +0800 Subject: [PATCH 94/99] Accumulation scheme compatible interface for HyperNova --- crates/fs/src/hypernova/mod.rs | 335 +++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) diff --git a/crates/fs/src/hypernova/mod.rs b/crates/fs/src/hypernova/mod.rs index a1d33fe19..8fca81e8a 100644 --- a/crates/fs/src/hypernova/mod.rs +++ b/crates/fs/src/hypernova/mod.rs @@ -519,6 +519,319 @@ impl< } } +pub struct HyperNova2 { + _t: PhantomData<(CM, V)>, +} + +impl FoldingSchemeDef + for HyperNova2 +{ + type CM = CM; + type RW = RW; + type RU = RU; + type IW = PW; + type IU = PU; + + type TranscriptField = CM::Scalar; + type Arith = CCS; + + type Config = usize; + type PublicParam = CM::Key; + type DeciderKey = HyperNovaKey; + type Challenge = [bool; CHALLENGE_BITS]; + type Proof = + ([CM::Commitment; N], NIMFSProof); +} + +impl FoldingSchemePreprocessor + for HyperNova2 +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} + +impl FoldingSchemeKeyGenerator + for HyperNova2 +{ + fn generate_keys(ck: Self::PublicParam, ccs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let ccs = Arc::new(ccs); + if ck.max_scalars_len() < ccs.config().n_witnesses() { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the CCS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: ccs, ck }) + } +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeProver for HyperNova2 +{ + #[allow(non_snake_case)] + fn prove( + pk: &HyperNovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; M], + Us: &[impl Borrow; M], + ws: &[impl Borrow; N], + us: &[impl Borrow; N], + mut rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error> { + let Ws = &Ws.iter().map(|i| i.borrow()).collect::>(); + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let ws = &ws.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let ccs = &pk.arith; + let d = V::degree(); + let s = ccs.config().log_constraints(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + let mut cms = [CM::Commitment::default(); N]; + let mut rs = [CM::Randomness::default(); N]; + for i in 0..N { + let (cm, r) = CM::commit(&pk.ck, ws[i], &mut rng)?; + cms[i] = cm; + rs[i] = r; + } + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + transcript.add(&cms[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + let (running_gammas, incoming_gammas) = gamma_powers.split_at(M * t); + + // Compute g(x) + let running_mles = Ws + .iter() + .zip(Us) + .flat_map(|(W, U)| ccs.mles((U.u, &U.x, &W.w).into())); + let incoming_mles = ws + .iter() + .zip(us) + .flat_map(|(w, u)| ccs.mles((One::one(), &u[..], &w[..]).into())); + let eq_mles = Us + .iter() + .map(|U| &U.r_x) + .chain([&beta]) + .map(|r| MLE::from_evaluations_vec(s, EqPoly::fix_y_evals(r))); + + let running_products = running_gammas + .iter() + .enumerate() + .map(|(i, &gamma)| (gamma, vec![i, (M + N) * t + i / t])); + let incoming_products = incoming_gammas.iter().enumerate().flat_map(|(k, gamma)| { + S.iter().zip(c).map(move |(S_i, &c_i)| { + ( + c_i * gamma, + S_i.iter() + .map(|j| (M + k) * t + j) + .chain([(M + N) * t + M]) + .collect(), + ) + }) + }); + + let g = VirtualPolynomial { + aux_info: VPAuxInfo { + num_variables: s, + max_degree: d + 1, + }, + flattened_ml_extensions: running_mles.chain(incoming_mles).chain(eq_mles).collect(), + products: running_products.chain(incoming_products).collect(), + }; + + // Step 3: Run the sumcheck prover + // Step 2: dig into the sumcheck and extract r_x_prime + let (sumcheck_proof, r_x_prime, mles) = SumCheck::prove(g, transcript)?; + + // Step 4: compute sigmas and thetas + let sigmas = mles[0..t * M] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + let thetas = mles[t * M..t * (M + N)] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(( + Self::RW { + w: Ws + .iter() + .map(|w| &w.w[..]) + .chain(ws.iter().map(|w| &w[..])) + .slice_rlc(&rho_powers), + r: Ws.iter().map(|w| w.r).chain(rs).scalar_rlc(&rho_powers), + }, + Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(cms.iter().copied()) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: sigmas + .chunks(t) + .chain(thetas.chunks(t)) + .slice_rlc(&rho_powers), + }, + ( + cms, + NIMFSProof { + sc_proof: sumcheck_proof, + sigmas, + thetas, + }, + ), + rho_bits.try_into().unwrap(), + )) + } +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeVerifier for HyperNova2 +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + (cms, proof): &Self::Proof, + ) -> Result { + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let d = V::degree(); + let s = proof.sc_proof.len(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + transcript.add(&cms[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + + let vp_aux_info = VPAuxInfo { + max_degree: d + 1, + num_variables: s, + }; + + // Step 3: Start verifying the sumcheck + // First, compute the expected sumcheck sum: \sum gamma^j v_j + let sum_v_j_gamma = Us + .iter() + .zip(gamma_powers.chunks(t)) + .flat_map(|(U, gammas)| U.v.iter().zip(gammas).map(|(&v, &g)| v * g)) + .sum(); + + // Verify the interactive part of the sumcheck + // Step 2: Dig into the sumcheck claim and extract the randomness used + let (claimed_eval, r_x_prime) = + SumCheck::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; + + // Step 5: Finish verifying sumcheck (verify the claim c) + let e_beta = EqPoly::fix_xy_eval(&beta, &r_x_prime); + let c = proof + .sigmas + .chunks(t) + .zip(Us) + .flat_map(|(sigmas, u)| { + let e_lcccs = EqPoly::fix_xy_eval(&u.r_x, &r_x_prime); + sigmas.iter().map(move |sigma_j| e_lcccs * sigma_j) + }) + .chain(proof.thetas.chunks(t).map(|thetas| { + S.iter() + .zip(c) + .map(|(S_i, &c_i)| c_i * S_i.iter().map(|&j| thetas[j]).product::()) + .sum::() + * e_beta + })) + .zip(gamma_powers) + .map(|(val, gamma_i)| val * gamma_i) + .sum::(); + // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas + (c == claimed_eval).then_some(()).ok_or_else(|| { + SumCheckError::IncorrectEvaluation(claimed_eval.to_string(), c.to_string()) + })?; + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(cms.iter().copied()) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: proof + .sigmas + .chunks(t) + .chain(proof.thetas.chunks(t)) + .slice_rlc(&rho_powers), + }) + } +} + #[cfg(test)] mod tests { use ark_bn254::{Fr, G1Projective}; @@ -560,6 +873,28 @@ mod tests { .collect(), &mut rng, )?; + + test_folding_scheme::>, M, N>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..rounds) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; + + test_folding_scheme::>, M, N>( + 8, + CircuitForTest { + x: Fr::rand(&mut rng), + }, + (0..rounds) + .map(|_| satisfying_assignments_for_test(Fr::rand(&mut rng))) + .collect(), + &mut rng, + )?; Ok(()) } From 0625721f68a16519818e14e78c5a62feffa28571 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 25 Oct 2025 23:27:13 +0800 Subject: [PATCH 95/99] In circuit variables for HyperNova instances and witnesses --- crates/fs/src/hypernova/instance/circuits.rs | 236 ++++++++++++++++++ .../{instance.rs => instance/mod.rs} | 7 +- crates/fs/src/hypernova/witness/circuits.rs | 87 +++++++ .../hypernova/{witness.rs => witness/mod.rs} | 2 + 4 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 crates/fs/src/hypernova/instance/circuits.rs rename crates/fs/src/hypernova/{instance.rs => instance/mod.rs} (95%) create mode 100644 crates/fs/src/hypernova/witness/circuits.rs rename crates/fs/src/hypernova/{witness.rs => witness/mod.rs} (98%) diff --git a/crates/fs/src/hypernova/instance/circuits.rs b/crates/fs/src/hypernova/instance/circuits.rs new file mode 100644 index 000000000..9a295fec4 --- /dev/null +++ b/crates/fs/src/hypernova/instance/circuits.rs @@ -0,0 +1,236 @@ +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, + fields::fp::FpVar, + select::CondSelectGadget, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::borrow::Borrow; +use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::AbsorbableVar}; + +use super::{CCCSInstance, LCCCSInstance}; +use crate::FoldingInstanceVar; + +#[derive(Clone, Debug, PartialEq)] +pub struct LCCCSInstanceVar { + pub cm: CM::CommitmentVar, + pub u: CM::ScalarVar, + pub x: Vec, + pub r_x: Vec, + pub v: Vec, +} + +impl AllocVar, CM::ConstraintField> + for LCCCSInstanceVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let LCCCSInstance { cm, u, x, r_x, v } = v.borrow(); + Ok(Self { + cm: AllocVar::new_variable(cs.clone(), || Ok(cm), mode)?, + u: AllocVar::new_variable(cs.clone(), || Ok(u), mode)?, + x: AllocVar::new_variable(cs.clone(), || Ok(&x[..]), mode)?, + r_x: AllocVar::new_variable(cs.clone(), || Ok(&r_x[..]), mode)?, + v: AllocVar::new_variable(cs.clone(), || Ok(&v[..]), mode)?, + }) + } +} + +impl GR1CSVar for LCCCSInstanceVar { + type Value = LCCCSInstance; + + fn cs(&self) -> ConstraintSystemRef { + self.cm + .cs() + .or(self.u.cs()) + .or(self.x.cs()) + .or(self.r_x.cs()) + .or(self.v.cs()) + } + + fn value(&self) -> Result { + Ok(LCCCSInstance { + cm: self.cm.value()?, + u: self.u.value()?, + x: self.x.value()?, + r_x: self.r_x.value()?, + v: self.v.value()?, + }) + } +} + +impl AbsorbableVar for LCCCSInstanceVar { + fn absorb_into( + &self, + dest: &mut Vec>, + ) -> Result<(), SynthesisError> { + self.cm.absorb_into(dest)?; + self.u.absorb_into(dest)?; + self.x.absorb_into(dest)?; + self.r_x.absorb_into(dest)?; + self.v.absorb_into(dest) + } +} + +impl CondSelectGadget for LCCCSInstanceVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.x.len() != false_value.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + if true_value.r_x.len() != false_value.r_x.len() { + return Err(SynthesisError::Unsatisfiable); + } + if true_value.v.len() != false_value.v.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self { + cm: cond.select(&true_value.cm, &false_value.cm)?, + u: cond.select(&true_value.u, &false_value.u)?, + x: true_value + .x + .iter() + .zip(&false_value.x) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + r_x: true_value + .r_x + .iter() + .zip(&false_value.r_x) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + v: true_value + .v + .iter() + .zip(&false_value.v) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + }) + } +} + +impl FoldingInstanceVar for LCCCSInstanceVar { + fn commitments(&self) -> Vec<&CM::CommitmentVar> { + vec![&self.cm] + } + + fn public_inputs(&self) -> &Vec { + &self.x + } + + fn new_witness_with_public_inputs( + cs: impl Into>, + u: &Self::Value, + x: Vec, + ) -> Result { + let cs = cs.into().cs(); + Ok(Self { + cm: AllocVar::new_witness(cs.clone(), || Ok(&u.cm))?, + u: AllocVar::new_witness(cs.clone(), || Ok(&u.u))?, + x, + r_x: AllocVar::new_witness(cs.clone(), || Ok(&u.r_x[..]))?, + v: AllocVar::new_witness(cs.clone(), || Ok(&u.v[..]))?, + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CCCSInstanceVar { + pub cm: CM::CommitmentVar, + pub x: Vec, +} + +impl AllocVar, CM::ConstraintField> + for CCCSInstanceVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let CCCSInstance { cm, x } = v.borrow(); + Ok(Self { + cm: AllocVar::new_variable(cs.clone(), || Ok(cm), mode)?, + x: AllocVar::new_variable(cs.clone(), || Ok(&x[..]), mode)?, + }) + } +} + +impl GR1CSVar for CCCSInstanceVar { + type Value = CCCSInstance; + + fn cs(&self) -> ConstraintSystemRef { + self.cm.cs().or(self.x.cs()) + } + + fn value(&self) -> Result { + Ok(CCCSInstance { + cm: self.cm.value()?, + x: self.x.value()?, + }) + } +} + +impl AbsorbableVar for CCCSInstanceVar { + fn absorb_into( + &self, + dest: &mut Vec>, + ) -> Result<(), SynthesisError> { + self.cm.absorb_into(dest)?; + self.x.absorb_into(dest) + } +} + +impl CondSelectGadget for CCCSInstanceVar { + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + if true_value.x.len() != false_value.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self { + cm: cond.select(&true_value.cm, &false_value.cm)?, + x: true_value + .x + .iter() + .zip(&false_value.x) + .map(|(t, f)| cond.select(t, f)) + .collect::>()?, + }) + } +} + +impl FoldingInstanceVar for CCCSInstanceVar { + fn commitments(&self) -> Vec<&CM::CommitmentVar> { + vec![&self.cm] + } + + fn public_inputs(&self) -> &Vec { + &self.x + } + + fn new_witness_with_public_inputs( + cs: impl Into>, + u: &Self::Value, + x: Vec, + ) -> Result { + let cs = cs.into().cs(); + Ok(Self { + cm: AllocVar::new_witness(cs.clone(), || Ok(&u.cm))?, + x, + }) + } +} diff --git a/crates/fs/src/hypernova/instance.rs b/crates/fs/src/hypernova/instance/mod.rs similarity index 95% rename from crates/fs/src/hypernova/instance.rs rename to crates/fs/src/hypernova/instance/mod.rs index fddc58884..a73e50a84 100644 --- a/crates/fs/src/hypernova/instance.rs +++ b/crates/fs/src/hypernova/instance/mod.rs @@ -1,6 +1,9 @@ use ark_ff::PrimeField; use sonobe_primitives::{ - arithmetizations::{ArithConfig, ccs::{CCSConfig, CCSVariant}}, + arithmetizations::{ + ArithConfig, + ccs::{CCSConfig, CCSVariant}, + }, commitments::CommitmentDef, traits::Dummy, transcripts::Absorbable, @@ -8,6 +11,8 @@ use sonobe_primitives::{ use crate::FoldingInstance; +pub mod circuits; + #[derive(Clone, Debug, Eq, PartialEq)] pub struct LCCCSInstance { pub cm: CM::Commitment, diff --git a/crates/fs/src/hypernova/witness/circuits.rs b/crates/fs/src/hypernova/witness/circuits.rs new file mode 100644 index 000000000..4f2cbc973 --- /dev/null +++ b/crates/fs/src/hypernova/witness/circuits.rs @@ -0,0 +1,87 @@ +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::borrow::Borrow; +use sonobe_primitives::commitments::CommitmentDefGadget; + +use super::{CCCSWitness, LCCCSWitness}; + +#[derive(Debug, PartialEq)] +pub struct LCCCSWitnessVar { + pub w: Vec, + pub r: CM::RandomnessVar, +} + +impl AllocVar, CM::ConstraintField> + for LCCCSWitnessVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let LCCCSWitness { w, r } = v.borrow(); + Ok(Self { + w: AllocVar::new_variable(cs.clone(), || Ok(&w[..]), mode)?, + r: AllocVar::new_variable(cs.clone(), || Ok(r), mode)?, + }) + } +} + +impl GR1CSVar for LCCCSWitnessVar { + type Value = LCCCSWitness; + + fn cs(&self) -> ConstraintSystemRef { + self.w.cs().or(self.r.cs()) + } + + fn value(&self) -> Result { + Ok(LCCCSWitness { + w: self.w.value()?, + r: self.r.value()?, + }) + } +} + +#[derive(Debug, PartialEq)] +pub struct CCCSWitnessVar { + pub w: Vec, + pub r: CM::RandomnessVar, +} + +impl AllocVar, CM::ConstraintField> + for CCCSWitnessVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let CCCSWitness { w, r } = v.borrow(); + Ok(Self { + w: AllocVar::new_variable(cs.clone(), || Ok(&w[..]), mode)?, + r: AllocVar::new_variable(cs.clone(), || Ok(r), mode)?, + }) + } +} + +impl GR1CSVar for CCCSWitnessVar { + type Value = CCCSWitness; + + fn cs(&self) -> ConstraintSystemRef { + self.w.cs().or(self.r.cs()) + } + + fn value(&self) -> Result { + Ok(CCCSWitness { + w: self.w.value()?, + r: self.r.value()?, + }) + } +} diff --git a/crates/fs/src/hypernova/witness.rs b/crates/fs/src/hypernova/witness/mod.rs similarity index 98% rename from crates/fs/src/hypernova/witness.rs rename to crates/fs/src/hypernova/witness/mod.rs index 8745a2158..ca5247e70 100644 --- a/crates/fs/src/hypernova/witness.rs +++ b/crates/fs/src/hypernova/witness/mod.rs @@ -2,6 +2,8 @@ use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDe use crate::FoldingWitness; +pub mod circuits; + #[derive(Clone, Debug, Eq, PartialEq)] pub struct LCCCSWitness { pub w: Vec, From 55ff315548b8c17c8206f2804622830ae880b967 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 17 Nov 2025 06:09:37 +0800 Subject: [PATCH 96/99] HyperNova CycleFold adapter --- crates/fs/src/hypernova/mod.rs | 235 +++++++++++++++++- .../compilers/cyclefold/adapters/hypernova.rs | 176 +++++++++++++ .../src/compilers/cyclefold/adapters/mod.rs | 1 + 3 files changed, 405 insertions(+), 7 deletions(-) create mode 100644 crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs diff --git a/crates/fs/src/hypernova/mod.rs b/crates/fs/src/hypernova/mod.rs index 8fca81e8a..fa2f5cdb7 100644 --- a/crates/fs/src/hypernova/mod.rs +++ b/crates/fs/src/hypernova/mod.rs @@ -1,5 +1,13 @@ -use ark_ff::{Field, One}; +use ark_ff::{Field, One, PrimeField}; use ark_poly::{DenseMultilinearExtension as MLE, MultilinearExtension}; +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, + eq::EqGadget, + fields::{FieldVar, fp::FpVar}, + prelude::Boolean, +}; +use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{ UniformRand, borrow::Borrow, cfg_iter, marker::PhantomData, rand::RngCore, sync::Arc, }; @@ -8,7 +16,7 @@ use rayon::prelude::*; use sonobe_primitives::{ algebra::ops::{ bits::FromBits, - pow::Pow, + pow::{Pow, PowGadget}, rlc::{ScalarRLC, SliceRLC}, }, arithmetizations::{ @@ -21,19 +29,25 @@ use sonobe_primitives::{ relations::{Relation, WitnessInstanceSampler}, sumcheck::{ Error as SumCheckError, SumCheck, - utils::{EqPoly, VPAuxInfo, VirtualPolynomial}, + circuits::SumCheckGadget, + utils::{EqPoly, EqPolyGadget, VPAuxInfo, VirtualPolynomial}, }, traits::Dummy, - transcripts::Transcript, + transcripts::{Transcript, TranscriptGadget}, }; use self::{ - instance::{CCCSInstance as IU, LCCCSInstance as RU}, + instance::{ + CCCSInstance as IU, LCCCSInstance as RU, + circuits::{CCCSInstanceVar as IUVar, LCCCSInstanceVar as RUVar}, + }, witness::{CCCSWitness as IW, LCCCSWitness as RW}, }; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeKeyGenerator, FoldingSchemePreprocessor, - FoldingSchemeProver, FoldingSchemeVerifier, PlainInstance as PU, PlainWitness as PW, + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeKeyGenerator, + FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, FoldingSchemeProver, + FoldingSchemeVerifier, GroupBasedFoldingSchemePrimaryDef, PlainInstance as PU, + PlainWitness as PW, }; pub mod instance; @@ -832,6 +846,213 @@ impl< } } +#[derive(Clone)] +pub struct NIMFSProofVar { + pub sc_proof: Vec>>, + pub sigmas: Vec>, + pub thetas: Vec>, +} + +impl AllocVar, F> + for NIMFSProofVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + let cs = ns.cs(); + + let proof = f()?.borrow().clone(); + + Ok(NIMFSProofVar { + sc_proof: proof + .sc_proof + .iter() + .map(|v| Vec::new_variable(cs.clone(), || Ok(&v[..]), mode)) + .collect::>, SynthesisError>>()?, + sigmas: Vec::new_variable(cs.clone(), || Ok(&proof.sigmas[..]), mode)?, + thetas: Vec::new_variable(cs.clone(), || Ok(&proof.thetas[..]), mode)?, + }) + } +} + +impl GR1CSVar for NIMFSProofVar { + type Value = NIMFSProof; + + fn cs(&self) -> ConstraintSystemRef { + self.sc_proof + .iter() + .fold(ConstraintSystemRef::None, |cs, v| cs.or(v.cs())) + .or(self.sigmas.cs()) + .or(self.thetas.cs()) + } + + fn value(&self) -> Result { + Ok(NIMFSProof { + sc_proof: self + .sc_proof + .iter() + .map(|v| v.value()) + .collect::>, SynthesisError>>()?, + sigmas: self.sigmas.value()?, + thetas: self.thetas.value()?, + }) + } +} + +pub struct HyperNovaGadget { + _t: PhantomData<(CM, V)>, +} + +impl FoldingSchemeDefGadget + for HyperNovaGadget +{ + type Widget = HyperNova; + + type CM = CM::Gadget2; + type RU = RUVar; + type IU = IUVar; + type VerifierKey = (); + type Challenge = [Boolean; CHALLENGE_BITS]; + type Proof = NIMFSProofVar; +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemePartialVerifierGadget for HyperNovaGadget +{ + #[allow(non_snake_case)] + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + Us: [&Self::RU; M], + us: [&Self::IU; N], + proof: &Self::Proof, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let d = V::degree(); + let s = proof.sc_proof.len(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..])?; + transcript.add(&us[..])?; + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element()?; + let beta = transcript.challenge_field_elements(s)?; + + let gamma_powers = gamma.powers(M * t + N); + + let vp_aux_info = VPAuxInfo { + max_degree: d + 1, + num_variables: s, + }; + + // Step 3: Start verifying the sumcheck + // First, compute the expected sumcheck sum: \sum gamma^j v_j + let mut sum_v_j_gamma = FpVar::zero(); + for (i, U) in Us.iter().enumerate() { + for j in 0..U.v.len() { + sum_v_j_gamma += &U.v[j] * &gamma_powers[i * t + j]; + } + } + + // Verify the interactive part of the sumcheck + // Step 2: Dig into the sumcheck claim and extract the randomness used + let (expected_eval, r_x_prime) = + SumCheckGadget::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; + + // Step 5: Finish verifying sumcheck (verify the claim c) + let c = { + let e2 = EqPolyGadget::fix_xy_eval(&beta, &r_x_prime); + proof + .sigmas + .chunks(t) + .zip(Us) + .flat_map(|(sigmas, u)| { + let e_lcccs = EqPolyGadget::fix_xy_eval(&u.r_x, &r_x_prime); + sigmas.iter().map(move |sigma_j| &e_lcccs * sigma_j) + }) + .chain(proof.thetas.chunks(t).map(|thetas| { + &e2 * S + .iter() + .zip(c) + .map(|(S_i, &c_i)| { + let mut prod = FpVar::one(); + for &j in S_i { + prod *= &thetas[j]; + } + prod * c_i + }) + .sum::>() + })) + .zip(gamma_powers.iter()) + .map(|(val, gamma_i)| val * gamma_i) + .sum::>() + }; + + // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas + c.enforce_equal(&expected_eval)?; + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS)?; + let rho = Boolean::le_bits_to_fp(&rho_bits)?; + + let rho_powers = rho.powers(M + N); + + Ok(( + Self::RU { + cm: { + let cms = Us + .iter() + .map(|u| &u.cm) + .chain(us.iter().map(|u| &u.cm)) + .collect::>(); + + AllocVar::new_witness(cms.cs().or(rho_powers.cs()), || { + let cms = cms.value().unwrap_or(vec![Default::default(); M + N]); + let rho_powers = rho_powers + .value() + .unwrap_or(vec![Default::default(); M + N]); + Ok(cms.into_iter().scalar_rlc(&rho_powers)) + })? + }, + u: Us + .iter() + .map(|u| u.u.clone()) + .chain(vec![FpVar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u.x[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: proof + .sigmas + .chunks(t) + .chain(proof.thetas.chunks(t)) + .slice_rlc(&rho_powers), + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl + GroupBasedFoldingSchemePrimaryDef for HyperNova +{ + type Gadget = HyperNovaGadget; +} + #[cfg(test)] mod tests { use ark_bn254::{Fr, G1Projective}; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs b/crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs new file mode 100644 index 000000000..6ee21bdb1 --- /dev/null +++ b/crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs @@ -0,0 +1,176 @@ +use ark_ff::{PrimeField, Zero}; +use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean}; +use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; +use ark_std::{borrow::Borrow, iter::once}; +use sonobe_fs::{ + FoldingSchemeDefGadget, hypernova::HyperNova, nova::CycleFoldNova, ova::CycleFoldOva, +}; +use sonobe_primitives::{ + algebra::{ + field::emulated::{Bounds, EmulatedFieldVar}, + ops::bits::{FromBits, FromBitsGadget, ToBitsGadgetExt}, + }, + arithmetizations::{ccs::CCSVariant, r1cs::R1CSConfig}, + commitments::GroupBasedCommitment, + traits::{CF2, SonobeCurve}, +}; + +use crate::compilers::cyclefold::{ + CycleFoldBasedIVC, FoldingSchemeCycleFoldExt, circuits::CycleFoldCircuit, +}; + +/// Configuration for HyperNova's CycleFold circuit +pub struct HyperNovaCycleFoldCircuit +{ + r: Vec, + points: Vec, +} + +impl Default + for HyperNovaCycleFoldCircuit +{ + fn default() -> Self { + Self { + r: vec![false; CHALLENGE_BITS], + points: vec![C::zero(); M + N], + } + } +} + +impl + CycleFoldCircuit> for HyperNovaCycleFoldCircuit +{ + fn verify_point_rlc(&self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { + let rho = FpVar::new_input(cs.clone(), || Ok(CF2::::from_bits_le(&self.r)))?; + let rho_bits = rho.to_n_bits_le(CHALLENGE_BITS)?; + + let points = Vec::new_witness(cs.clone(), || Ok(&self.points[..]))?; + for point in &points { + Self::mark_point_as_public(point)?; + } + + let mut p_folded = C::Var::zero(); + for i in (1..M + N).rev() { + p_folded += &points[i]; + p_folded = p_folded.scalar_mul_le(rho_bits.iter())?; + } + p_folded += &points[0]; + + Self::mark_point_as_public(&p_folded) + } +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeCycleFoldExt for HyperNova +{ + const N_CYCLEFOLDS: usize = 1; + + type CFCircuit = HyperNovaCycleFoldCircuit; + + #[allow(non_snake_case)] + fn to_cyclefold_circuits( + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + _proof: &Self::Proof, + rho: Self::Challenge, + ) -> Vec { + vec![HyperNovaCycleFoldCircuit { + r: rho.into(), + points: Us + .iter() + .map(|U| U.borrow().cm) + .chain(us.iter().map(|u| u.borrow().cm)) + .collect(), + }] + } + + #[allow(non_snake_case)] + fn to_cyclefold_inputs( + Us: [::RU; M], + us: [::IU; N], + UU: ::RU, + _proof: ::Proof, + rho: ::Challenge, + ) -> Result>>>, SynthesisError> { + let mut rho = rho.to_vec(); + rho.resize( + CF2::::MODULUS_BIT_SIZE as usize, + Boolean::FALSE, + ); + Ok(vec![ + once(EmulatedFieldVar::from_bounded_bits_le( + &rho, + Bounds(Zero::zero(), CF2::::MODULUS.into().into()), + )?) + .chain( + Us.into_iter() + .map(|U| U.cm) + .chain(us.into_iter().map(|u| u.cm)) + .chain(once(UU.cm)) + .flat_map(|p| [p.x, p.y]), + ) + .collect(), + ]) + } +} + +pub type HyperNovaOvaIVC = + CycleFoldBasedIVC, CycleFoldOva, T>; + +pub type HyperNovaNovaIVC = + CycleFoldBasedIVC, CycleFoldNova, T>; + +#[cfg(test)] +mod tests { + use ark_bn254::{Fr, G1Projective as C1}; + use ark_ff::UniformRand; + use ark_grumpkin::Projective as C2; + use ark_std::{error::Error, rand::thread_rng, sync::Arc}; + use sonobe_primitives::{ + circuits::utils::CircuitForTest, + commitments::pedersen::Pedersen, + transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, + }; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::*; + use crate::tests::test_ivc; + + #[test] + fn test_hypernova_ova() -> Result<(), Box> { + let mut rng = thread_rng(); + + test_ivc::, Pedersen, GriffinSponge<_>>, _>( + (65536, (2048, 2048), Arc::new(GriffinParams::new(16, 5, 9))), + CircuitForTest { + x: Fr::rand(&mut rng), + }, + vec![(); 20], + &mut rng, + )?; + + Ok(()) + } + + #[test] + fn test_hypernova_nova() -> Result<(), Box> { + let mut rng = thread_rng(); + + test_ivc::, Pedersen, GriffinSponge<_>>, _>( + (65536, 2048, Arc::new(GriffinParams::new(16, 5, 9))), + CircuitForTest { + x: Fr::rand(&mut rng), + }, + vec![(); 20], + &mut rng, + )?; + + Ok(()) + } +} diff --git a/crates/ivc/src/compilers/cyclefold/adapters/mod.rs b/crates/ivc/src/compilers/cyclefold/adapters/mod.rs index 8994ba2cd..e8a843ed1 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/mod.rs @@ -1,5 +1,6 @@ //! Per-scheme adapters that implement [`super::CycleFoldCircuit`] for supported //! folding schemes. +pub mod hypernova; pub mod nova; pub mod ova; From b3df52d19858a4bd7d18aca53dde80709ec1276b Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 25 Nov 2025 00:52:37 +0800 Subject: [PATCH 97/99] Split impls into separate submodules --- .../src/hypernova/algorithms/key_generator.rs | 40 + crates/fs/src/hypernova/algorithms/mod.rs | 4 + .../src/hypernova/algorithms/preprocessor.rs | 25 + crates/fs/src/hypernova/algorithms/prover.rs | 322 ++++++++ .../fs/src/hypernova/algorithms/verifier.rs | 242 ++++++ crates/fs/src/hypernova/circuits/mod.rs | 1 + crates/fs/src/hypernova/circuits/verifier.rs | 151 ++++ .../{instance => instances}/circuits.rs | 0 .../hypernova/{instance => instances}/mod.rs | 0 crates/fs/src/hypernova/mod.rs | 733 +----------------- .../{witness => witnesses}/circuits.rs | 0 .../hypernova/{witness => witnesses}/mod.rs | 0 12 files changed, 797 insertions(+), 721 deletions(-) create mode 100644 crates/fs/src/hypernova/algorithms/key_generator.rs create mode 100644 crates/fs/src/hypernova/algorithms/mod.rs create mode 100644 crates/fs/src/hypernova/algorithms/preprocessor.rs create mode 100644 crates/fs/src/hypernova/algorithms/prover.rs create mode 100644 crates/fs/src/hypernova/algorithms/verifier.rs create mode 100644 crates/fs/src/hypernova/circuits/mod.rs create mode 100644 crates/fs/src/hypernova/circuits/verifier.rs rename crates/fs/src/hypernova/{instance => instances}/circuits.rs (100%) rename crates/fs/src/hypernova/{instance => instances}/mod.rs (100%) rename crates/fs/src/hypernova/{witness => witnesses}/circuits.rs (100%) rename crates/fs/src/hypernova/{witness => witnesses}/mod.rs (100%) diff --git a/crates/fs/src/hypernova/algorithms/key_generator.rs b/crates/fs/src/hypernova/algorithms/key_generator.rs new file mode 100644 index 000000000..d903800f7 --- /dev/null +++ b/crates/fs/src/hypernova/algorithms/key_generator.rs @@ -0,0 +1,40 @@ +use ark_std::sync::Arc; +use sonobe_primitives::{ + arithmetizations::{Arith, ArithConfig, ccs::CCSVariant}, + commitments::{CommitmentKey, GroupBasedCommitment}, +}; + +use crate::{ + Error, FoldingSchemeKeyGenerator, + hypernova::{HyperNova, HyperNova2}, +}; + +impl FoldingSchemeKeyGenerator + for HyperNova +{ + fn generate_keys(ck: Self::PublicParam, ccs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let ccs = Arc::new(ccs); + if ck.max_scalars_len() < ccs.config().n_witnesses() { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the CCS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: ccs, ck }) + } +} + +impl FoldingSchemeKeyGenerator + for HyperNova2 +{ + fn generate_keys(ck: Self::PublicParam, ccs: Self::Arith) -> Result { + let ck = Arc::new(ck); + let ccs = Arc::new(ccs); + if ck.max_scalars_len() < ccs.config().n_witnesses() { + return Err(Error::InvalidPublicParameters( + "The commitment key is too short for the CCS instance".into(), + )); + } + Ok(Self::DeciderKey { arith: ccs, ck }) + } +} diff --git a/crates/fs/src/hypernova/algorithms/mod.rs b/crates/fs/src/hypernova/algorithms/mod.rs new file mode 100644 index 000000000..11e838298 --- /dev/null +++ b/crates/fs/src/hypernova/algorithms/mod.rs @@ -0,0 +1,4 @@ +pub mod key_generator; +pub mod preprocessor; +pub mod prover; +pub mod verifier; diff --git a/crates/fs/src/hypernova/algorithms/preprocessor.rs b/crates/fs/src/hypernova/algorithms/preprocessor.rs new file mode 100644 index 000000000..3ea2735ac --- /dev/null +++ b/crates/fs/src/hypernova/algorithms/preprocessor.rs @@ -0,0 +1,25 @@ +use ark_std::rand::RngCore; +use sonobe_primitives::{arithmetizations::ccs::CCSVariant, commitments::GroupBasedCommitment}; + +use crate::{ + Error, FoldingSchemePreprocessor, + hypernova::{HyperNova, HyperNova2}, +}; + +impl FoldingSchemePreprocessor + for HyperNova +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} + +impl FoldingSchemePreprocessor + for HyperNova2 +{ + fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { + let ck = CM::generate_key(ck_len, &mut rng)?; + Ok(ck) + } +} \ No newline at end of file diff --git a/crates/fs/src/hypernova/algorithms/prover.rs b/crates/fs/src/hypernova/algorithms/prover.rs new file mode 100644 index 000000000..afa4010b9 --- /dev/null +++ b/crates/fs/src/hypernova/algorithms/prover.rs @@ -0,0 +1,322 @@ +use ark_ff::One; +use ark_poly::{DenseMultilinearExtension as MLE, MultilinearExtension}; +use ark_std::{borrow::Borrow, rand::RngCore}; +use sonobe_primitives::{ + algebra::ops::{ + bits::FromBits, + pow::Pow, + rlc::{ScalarRLC, SliceRLC}, + }, + arithmetizations::{Arith, ArithConfig, ccs::CCSVariant}, + commitments::GroupBasedCommitment, + sumcheck::{ + SumCheck, + utils::{EqPoly, VPAuxInfo, VirtualPolynomial}, + }, + transcripts::Transcript, +}; + +use crate::{ + Error, FoldingSchemeProver, + hypernova::{HyperNova, HyperNova2, HyperNovaKey, NIMFSProof}, +}; + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeProver for HyperNova +{ + #[allow(non_snake_case)] + fn prove( + pk: &HyperNovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; M], + Us: &[impl Borrow; M], + ws: &[impl Borrow; N], + us: &[impl Borrow; N], + _rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error> { + let Ws = &Ws.iter().map(|i| i.borrow()).collect::>(); + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let ws = &ws.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let ccs = &pk.arith; + let d = V::degree(); + let s = ccs.config().log_constraints(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + let (running_gammas, incoming_gammas) = gamma_powers.split_at(M * t); + + // Compute g(x) + let running_mles = Ws + .iter() + .zip(Us) + .flat_map(|(W, U)| ccs.mles((U.u, &U.x, &W.w).into())); + let incoming_mles = ws + .iter() + .zip(us) + .flat_map(|(w, u)| ccs.mles((One::one(), &u.x, &w.w).into())); + let eq_mles = Us + .iter() + .map(|U| &U.r_x) + .chain([&beta]) + .map(|r| MLE::from_evaluations_vec(s, EqPoly::fix_y_evals(r))); + + let running_products = running_gammas + .iter() + .enumerate() + .map(|(i, &gamma)| (gamma, vec![i, (M + N) * t + i / t])); + let incoming_products = incoming_gammas.iter().enumerate().flat_map(|(k, gamma)| { + S.iter().zip(c).map(move |(S_i, &c_i)| { + ( + c_i * gamma, + S_i.iter() + .map(|j| (M + k) * t + j) + .chain([(M + N) * t + M]) + .collect(), + ) + }) + }); + + let g = VirtualPolynomial { + aux_info: VPAuxInfo { + num_variables: s, + max_degree: d + 1, + }, + flattened_ml_extensions: running_mles.chain(incoming_mles).chain(eq_mles).collect(), + products: running_products.chain(incoming_products).collect(), + }; + + // Step 3: Run the sumcheck prover + // Step 2: dig into the sumcheck and extract r_x_prime + let (sumcheck_proof, r_x_prime, mles) = SumCheck::prove(g, transcript)?; + + // Step 4: compute sigmas and thetas + let sigmas = mles[0..t * M] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + let thetas = mles[t * M..t * (M + N)] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(( + Self::RW { + w: Ws + .iter() + .map(|w| &w.w[..]) + .chain(ws.iter().map(|w| &w.w[..])) + .slice_rlc(&rho_powers), + r: Ws + .iter() + .map(|w| w.r) + .chain(ws.iter().map(|w| w.r)) + .scalar_rlc(&rho_powers), + }, + Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(us.iter().map(|u| u.cm)) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u.x[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: sigmas + .chunks(t) + .chain(thetas.chunks(t)) + .slice_rlc(&rho_powers), + }, + NIMFSProof { + sc_proof: sumcheck_proof, + sigmas, + thetas, + }, + rho_bits.try_into().unwrap(), + )) + } +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeProver for HyperNova2 +{ + #[allow(non_snake_case)] + fn prove( + pk: &HyperNovaKey, + transcript: &mut impl Transcript, + Ws: &[impl Borrow; M], + Us: &[impl Borrow; M], + ws: &[impl Borrow; N], + us: &[impl Borrow; N], + mut rng: impl RngCore, + ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error> { + let Ws = &Ws.iter().map(|i| i.borrow()).collect::>(); + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let ws = &ws.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let ccs = &pk.arith; + let d = V::degree(); + let s = ccs.config().log_constraints(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + let mut cms = [CM::Commitment::default(); N]; + let mut rs = [CM::Randomness::default(); N]; + for i in 0..N { + let (cm, r) = CM::commit(&pk.ck, ws[i], &mut rng)?; + cms[i] = cm; + rs[i] = r; + } + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + transcript.add(&cms[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + let (running_gammas, incoming_gammas) = gamma_powers.split_at(M * t); + + // Compute g(x) + let running_mles = Ws + .iter() + .zip(Us) + .flat_map(|(W, U)| ccs.mles((U.u, &U.x, &W.w).into())); + let incoming_mles = ws + .iter() + .zip(us) + .flat_map(|(w, u)| ccs.mles((One::one(), &u[..], &w[..]).into())); + let eq_mles = Us + .iter() + .map(|U| &U.r_x) + .chain([&beta]) + .map(|r| MLE::from_evaluations_vec(s, EqPoly::fix_y_evals(r))); + + let running_products = running_gammas + .iter() + .enumerate() + .map(|(i, &gamma)| (gamma, vec![i, (M + N) * t + i / t])); + let incoming_products = incoming_gammas.iter().enumerate().flat_map(|(k, gamma)| { + S.iter().zip(c).map(move |(S_i, &c_i)| { + ( + c_i * gamma, + S_i.iter() + .map(|j| (M + k) * t + j) + .chain([(M + N) * t + M]) + .collect(), + ) + }) + }); + + let g = VirtualPolynomial { + aux_info: VPAuxInfo { + num_variables: s, + max_degree: d + 1, + }, + flattened_ml_extensions: running_mles.chain(incoming_mles).chain(eq_mles).collect(), + products: running_products.chain(incoming_products).collect(), + }; + + // Step 3: Run the sumcheck prover + // Step 2: dig into the sumcheck and extract r_x_prime + let (sumcheck_proof, r_x_prime, mles) = SumCheck::prove(g, transcript)?; + + // Step 4: compute sigmas and thetas + let sigmas = mles[0..t * M] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + let thetas = mles[t * M..t * (M + N)] + .iter() + .map(|mle| mle.fix_variables(&[])[0]) + .collect::>(); + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(( + Self::RW { + w: Ws + .iter() + .map(|w| &w.w[..]) + .chain(ws.iter().map(|w| &w[..])) + .slice_rlc(&rho_powers), + r: Ws.iter().map(|w| w.r).chain(rs).scalar_rlc(&rho_powers), + }, + Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(cms.iter().copied()) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: sigmas + .chunks(t) + .chain(thetas.chunks(t)) + .slice_rlc(&rho_powers), + }, + ( + cms, + NIMFSProof { + sc_proof: sumcheck_proof, + sigmas, + thetas, + }, + ), + rho_bits.try_into().unwrap(), + )) + } +} diff --git a/crates/fs/src/hypernova/algorithms/verifier.rs b/crates/fs/src/hypernova/algorithms/verifier.rs new file mode 100644 index 000000000..89933da59 --- /dev/null +++ b/crates/fs/src/hypernova/algorithms/verifier.rs @@ -0,0 +1,242 @@ +use ark_ff::One; +use ark_std::borrow::Borrow; +use sonobe_primitives::{ + algebra::ops::{ + bits::FromBits, + pow::Pow, + rlc::{ScalarRLC, SliceRLC}, + }, + arithmetizations::ccs::CCSVariant, + commitments::GroupBasedCommitment, + sumcheck::{ + Error as SumCheckError, SumCheck, + utils::{EqPoly, VPAuxInfo}, + }, + transcripts::Transcript, +}; + +use crate::{ + Error, FoldingSchemeVerifier, + hypernova::{HyperNova, HyperNova2}, +}; + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeVerifier for HyperNova +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + proof: &Self::Proof, + ) -> Result { + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let d = V::degree(); + let s = proof.sc_proof.len(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + + let vp_aux_info = VPAuxInfo { + max_degree: d + 1, + num_variables: s, + }; + + // Step 3: Start verifying the sumcheck + // First, compute the expected sumcheck sum: \sum gamma^j v_j + let sum_v_j_gamma = Us + .iter() + .zip(gamma_powers.chunks(t)) + .flat_map(|(U, gammas)| U.v.iter().zip(gammas).map(|(&v, &g)| v * g)) + .sum(); + + // Verify the interactive part of the sumcheck + // Step 2: Dig into the sumcheck claim and extract the randomness used + let (claimed_eval, r_x_prime) = + SumCheck::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; + + // Step 5: Finish verifying sumcheck (verify the claim c) + let e_beta = EqPoly::fix_xy_eval(&beta, &r_x_prime); + let c = proof + .sigmas + .chunks(t) + .zip(Us) + .flat_map(|(sigmas, u)| { + let e_lcccs = EqPoly::fix_xy_eval(&u.r_x, &r_x_prime); + sigmas.iter().map(move |sigma_j| e_lcccs * sigma_j) + }) + .chain(proof.thetas.chunks(t).map(|thetas| { + S.iter() + .zip(c) + .map(|(S_i, &c_i)| c_i * S_i.iter().map(|&j| thetas[j]).product::()) + .sum::() + * e_beta + })) + .zip(gamma_powers) + .map(|(val, gamma_i)| val * gamma_i) + .sum::(); + // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas + (c == claimed_eval).then_some(()).ok_or_else(|| { + SumCheckError::IncorrectEvaluation(claimed_eval.to_string(), c.to_string()) + })?; + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(us.iter().map(|u| u.cm)) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u.x[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: proof + .sigmas + .chunks(t) + .chain(proof.thetas.chunks(t)) + .slice_rlc(&rho_powers), + }) + } +} + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemeVerifier for HyperNova2 +{ + #[allow(non_snake_case)] + fn verify( + _vk: &(), + transcript: &mut impl Transcript, + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + (cms, proof): &Self::Proof, + ) -> Result { + let Us = &Us.iter().map(|i| i.borrow()).collect::>(); + let us = &us.iter().map(|i| i.borrow()).collect::>(); + + let d = V::degree(); + let s = proof.sc_proof.len(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..]); + transcript.add(&us[..]); + transcript.add(&cms[..]); + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element(); + let beta = transcript.challenge_field_elements(s); + + let gamma_powers = gamma.powers(M * t + N); + + let vp_aux_info = VPAuxInfo { + max_degree: d + 1, + num_variables: s, + }; + + // Step 3: Start verifying the sumcheck + // First, compute the expected sumcheck sum: \sum gamma^j v_j + let sum_v_j_gamma = Us + .iter() + .zip(gamma_powers.chunks(t)) + .flat_map(|(U, gammas)| U.v.iter().zip(gammas).map(|(&v, &g)| v * g)) + .sum(); + + // Verify the interactive part of the sumcheck + // Step 2: Dig into the sumcheck claim and extract the randomness used + let (claimed_eval, r_x_prime) = + SumCheck::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; + + // Step 5: Finish verifying sumcheck (verify the claim c) + let e_beta = EqPoly::fix_xy_eval(&beta, &r_x_prime); + let c = proof + .sigmas + .chunks(t) + .zip(Us) + .flat_map(|(sigmas, u)| { + let e_lcccs = EqPoly::fix_xy_eval(&u.r_x, &r_x_prime); + sigmas.iter().map(move |sigma_j| e_lcccs * sigma_j) + }) + .chain(proof.thetas.chunks(t).map(|thetas| { + S.iter() + .zip(c) + .map(|(S_i, &c_i)| c_i * S_i.iter().map(|&j| thetas[j]).product::()) + .sum::() + * e_beta + })) + .zip(gamma_powers) + .map(|(val, gamma_i)| val * gamma_i) + .sum::(); + // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas + (c == claimed_eval).then_some(()).ok_or_else(|| { + SumCheckError::IncorrectEvaluation(claimed_eval.to_string(), c.to_string()) + })?; + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); + let rho = CM::Scalar::from_bits_le(&rho_bits); + + let rho_powers = rho.powers(M + N); + + Ok(Self::RU { + cm: Us + .iter() + .map(|u| u.cm) + .chain(cms.iter().copied()) + .scalar_rlc(&rho_powers), + u: Us + .iter() + .map(|u| u.u) + .chain([CM::Scalar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: proof + .sigmas + .chunks(t) + .chain(proof.thetas.chunks(t)) + .slice_rlc(&rho_powers), + }) + } +} diff --git a/crates/fs/src/hypernova/circuits/mod.rs b/crates/fs/src/hypernova/circuits/mod.rs new file mode 100644 index 000000000..9a0722027 --- /dev/null +++ b/crates/fs/src/hypernova/circuits/mod.rs @@ -0,0 +1 @@ +pub mod verifier; diff --git a/crates/fs/src/hypernova/circuits/verifier.rs b/crates/fs/src/hypernova/circuits/verifier.rs new file mode 100644 index 000000000..fa2d7b299 --- /dev/null +++ b/crates/fs/src/hypernova/circuits/verifier.rs @@ -0,0 +1,151 @@ +use ark_r1cs_std::{ + GR1CSVar, + alloc::AllocVar, + eq::EqGadget, + fields::{FieldVar, fp::FpVar}, + prelude::Boolean, +}; +use ark_relations::gr1cs::SynthesisError; +use sonobe_primitives::{ + algebra::ops::{ + pow::PowGadget, + rlc::{ScalarRLC, SliceRLC}, + }, + arithmetizations::ccs::CCSVariant, + commitments::GroupBasedCommitment, + sumcheck::{ + circuits::SumCheckGadget, + utils::{EqPolyGadget, VPAuxInfo}, + }, + transcripts::TranscriptGadget, +}; + +use crate::{FoldingSchemePartialVerifierGadget, hypernova::HyperNovaGadget}; + +impl< + CM: GroupBasedCommitment, + V: CCSVariant, + const M: usize, + const N: usize, + const CHALLENGE_BITS: usize, +> FoldingSchemePartialVerifierGadget for HyperNovaGadget +{ + #[allow(non_snake_case)] + fn verify_hinted( + _vk: &Self::VerifierKey, + transcript: &mut impl TranscriptGadget, + Us: [&Self::RU; M], + us: [&Self::IU; N], + proof: &Self::Proof, + ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + let d = V::degree(); + let s = proof.sc_proof.len(); + let t = V::n_matrices(); + let S = &V::multisets_vec(); + let c = &V::coefficients_vec::(); + + // absorb instances to transcript + transcript.add(&Us[..])?; + transcript.add(&us[..])?; + + // Step 1: Get some challenges + let gamma = transcript.challenge_field_element()?; + let beta = transcript.challenge_field_elements(s)?; + + let gamma_powers = gamma.powers(M * t + N); + + let vp_aux_info = VPAuxInfo { + max_degree: d + 1, + num_variables: s, + }; + + // Step 3: Start verifying the sumcheck + // First, compute the expected sumcheck sum: \sum gamma^j v_j + let mut sum_v_j_gamma = FpVar::zero(); + for (i, U) in Us.iter().enumerate() { + for j in 0..U.v.len() { + sum_v_j_gamma += &U.v[j] * &gamma_powers[i * t + j]; + } + } + + // Verify the interactive part of the sumcheck + // Step 2: Dig into the sumcheck claim and extract the randomness used + let (expected_eval, r_x_prime) = + SumCheckGadget::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; + + // Step 5: Finish verifying sumcheck (verify the claim c) + let c = { + let e2 = EqPolyGadget::fix_xy_eval(&beta, &r_x_prime); + proof + .sigmas + .chunks(t) + .zip(Us) + .flat_map(|(sigmas, u)| { + let e_lcccs = EqPolyGadget::fix_xy_eval(&u.r_x, &r_x_prime); + sigmas.iter().map(move |sigma_j| &e_lcccs * sigma_j) + }) + .chain(proof.thetas.chunks(t).map(|thetas| { + &e2 * S + .iter() + .zip(c) + .map(|(S_i, &c_i)| { + let mut prod = FpVar::one(); + for &j in S_i { + prod *= &thetas[j]; + } + prod * c_i + }) + .sum::>() + })) + .zip(gamma_powers.iter()) + .map(|(val, gamma_i)| val * gamma_i) + .sum::>() + }; + + // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas + c.enforce_equal(&expected_eval)?; + + // Step 6: Get the folding challenge + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS)?; + let rho = Boolean::le_bits_to_fp(&rho_bits)?; + + let rho_powers = rho.powers(M + N); + + Ok(( + Self::RU { + cm: { + let cms = Us + .iter() + .map(|u| &u.cm) + .chain(us.iter().map(|u| &u.cm)) + .collect::>(); + + AllocVar::new_witness(cms.cs().or(rho_powers.cs()), || { + let cms = cms.value().unwrap_or(vec![Default::default(); M + N]); + let rho_powers = rho_powers + .value() + .unwrap_or(vec![Default::default(); M + N]); + Ok(cms.into_iter().scalar_rlc(&rho_powers)) + })? + }, + u: Us + .iter() + .map(|u| u.u.clone()) + .chain(vec![FpVar::one(); N]) + .scalar_rlc(&rho_powers), + x: Us + .iter() + .map(|u| &u.x[..]) + .chain(us.iter().map(|u| &u.x[..])) + .slice_rlc(&rho_powers), + r_x: r_x_prime, + v: proof + .sigmas + .chunks(t) + .chain(proof.thetas.chunks(t)) + .slice_rlc(&rho_powers), + }, + rho_bits.try_into().unwrap(), + )) + } +} diff --git a/crates/fs/src/hypernova/instance/circuits.rs b/crates/fs/src/hypernova/instances/circuits.rs similarity index 100% rename from crates/fs/src/hypernova/instance/circuits.rs rename to crates/fs/src/hypernova/instances/circuits.rs diff --git a/crates/fs/src/hypernova/instance/mod.rs b/crates/fs/src/hypernova/instances/mod.rs similarity index 100% rename from crates/fs/src/hypernova/instance/mod.rs rename to crates/fs/src/hypernova/instances/mod.rs diff --git a/crates/fs/src/hypernova/mod.rs b/crates/fs/src/hypernova/mod.rs index fa2f5cdb7..2692ab03c 100644 --- a/crates/fs/src/hypernova/mod.rs +++ b/crates/fs/src/hypernova/mod.rs @@ -1,10 +1,9 @@ -use ark_ff::{Field, One, PrimeField}; -use ark_poly::{DenseMultilinearExtension as MLE, MultilinearExtension}; +use ark_ff::{Field, PrimeField}; +use ark_poly::MultilinearExtension; use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, - eq::EqGadget, - fields::{FieldVar, fp::FpVar}, + fields::fp::FpVar, prelude::Boolean, }; use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; @@ -14,44 +13,33 @@ use ark_std::{ #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ - algebra::ops::{ - bits::FromBits, - pow::{Pow, PowGadget}, - rlc::{ScalarRLC, SliceRLC}, - }, arithmetizations::{ Arith, ArithConfig, ArithRelation, Error as ArithError, ccs::{CCS, CCSConfig, CCSVariant}, r1cs::R1CSConfig, }, circuits::{Assignments, AssignmentsOwned}, - commitments::{CommitmentDef, CommitmentKey, CommitmentOps, GroupBasedCommitment}, + commitments::{CommitmentDef, CommitmentOps, GroupBasedCommitment}, relations::{Relation, WitnessInstanceSampler}, - sumcheck::{ - Error as SumCheckError, SumCheck, - circuits::SumCheckGadget, - utils::{EqPoly, EqPolyGadget, VPAuxInfo, VirtualPolynomial}, - }, traits::Dummy, - transcripts::{Transcript, TranscriptGadget}, }; use self::{ - instance::{ + instances::{ CCCSInstance as IU, LCCCSInstance as RU, circuits::{CCCSInstanceVar as IUVar, LCCCSInstanceVar as RUVar}, }, - witness::{CCCSWitness as IW, LCCCSWitness as RW}, + witnesses::{CCCSWitness as IW, LCCCSWitness as RW}, }; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeKeyGenerator, - FoldingSchemePartialVerifierGadget, FoldingSchemePreprocessor, FoldingSchemeProver, - FoldingSchemeVerifier, GroupBasedFoldingSchemePrimaryDef, PlainInstance as PU, - PlainWitness as PW, + DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, + PlainInstance as PU, PlainWitness as PW, }; -pub mod instance; -pub mod witness; +pub mod algorithms; +pub mod circuits; +pub mod instances; +pub mod witnesses; #[derive(Clone)] pub struct HyperNovaKey { @@ -253,286 +241,6 @@ impl Foldi type Proof = NIMFSProof; } -impl FoldingSchemePreprocessor - for HyperNova -{ - fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { - let ck = CM::generate_key(ck_len, &mut rng)?; - Ok(ck) - } -} - -impl FoldingSchemeKeyGenerator - for HyperNova -{ - fn generate_keys(ck: Self::PublicParam, ccs: Self::Arith) -> Result { - let ck = Arc::new(ck); - let ccs = Arc::new(ccs); - if ck.max_scalars_len() < ccs.config().n_witnesses() { - return Err(Error::InvalidPublicParameters( - "The commitment key is too short for the CCS instance".into(), - )); - } - Ok(Self::DeciderKey { arith: ccs, ck }) - } -} - -impl< - CM: GroupBasedCommitment, - V: CCSVariant, - const M: usize, - const N: usize, - const CHALLENGE_BITS: usize, -> FoldingSchemeProver for HyperNova -{ - #[allow(non_snake_case)] - fn prove( - pk: &HyperNovaKey, - transcript: &mut impl Transcript, - Ws: &[impl Borrow; M], - Us: &[impl Borrow; M], - ws: &[impl Borrow; N], - us: &[impl Borrow; N], - _rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error> { - let Ws = &Ws.iter().map(|i| i.borrow()).collect::>(); - let Us = &Us.iter().map(|i| i.borrow()).collect::>(); - let ws = &ws.iter().map(|i| i.borrow()).collect::>(); - let us = &us.iter().map(|i| i.borrow()).collect::>(); - - let ccs = &pk.arith; - let d = V::degree(); - let s = ccs.config().log_constraints(); - let t = V::n_matrices(); - let S = &V::multisets_vec(); - let c = &V::coefficients_vec::(); - - // absorb instances to transcript - transcript.add(&Us[..]); - transcript.add(&us[..]); - - // Step 1: Get some challenges - let gamma = transcript.challenge_field_element(); - let beta = transcript.challenge_field_elements(s); - - let gamma_powers = gamma.powers(M * t + N); - let (running_gammas, incoming_gammas) = gamma_powers.split_at(M * t); - - // Compute g(x) - let running_mles = Ws - .iter() - .zip(Us) - .flat_map(|(W, U)| ccs.mles((U.u, &U.x, &W.w).into())); - let incoming_mles = ws - .iter() - .zip(us) - .flat_map(|(w, u)| ccs.mles((One::one(), &u.x, &w.w).into())); - let eq_mles = Us - .iter() - .map(|U| &U.r_x) - .chain([&beta]) - .map(|r| MLE::from_evaluations_vec(s, EqPoly::fix_y_evals(r))); - - let running_products = running_gammas - .iter() - .enumerate() - .map(|(i, &gamma)| (gamma, vec![i, (M + N) * t + i / t])); - let incoming_products = incoming_gammas.iter().enumerate().flat_map(|(k, gamma)| { - S.iter().zip(c).map(move |(S_i, &c_i)| { - ( - c_i * gamma, - S_i.iter() - .map(|j| (M + k) * t + j) - .chain([(M + N) * t + M]) - .collect(), - ) - }) - }); - - let g = VirtualPolynomial { - aux_info: VPAuxInfo { - num_variables: s, - max_degree: d + 1, - }, - flattened_ml_extensions: running_mles.chain(incoming_mles).chain(eq_mles).collect(), - products: running_products.chain(incoming_products).collect(), - }; - - // Step 3: Run the sumcheck prover - // Step 2: dig into the sumcheck and extract r_x_prime - let (sumcheck_proof, r_x_prime, mles) = SumCheck::prove(g, transcript)?; - - // Step 4: compute sigmas and thetas - let sigmas = mles[0..t * M] - .iter() - .map(|mle| mle.fix_variables(&[])[0]) - .collect::>(); - let thetas = mles[t * M..t * (M + N)] - .iter() - .map(|mle| mle.fix_variables(&[])[0]) - .collect::>(); - - // Step 6: Get the folding challenge - let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); - let rho = CM::Scalar::from_bits_le(&rho_bits); - - let rho_powers = rho.powers(M + N); - - Ok(( - Self::RW { - w: Ws - .iter() - .map(|w| &w.w[..]) - .chain(ws.iter().map(|w| &w.w[..])) - .slice_rlc(&rho_powers), - r: Ws - .iter() - .map(|w| w.r) - .chain(ws.iter().map(|w| w.r)) - .scalar_rlc(&rho_powers), - }, - Self::RU { - cm: Us - .iter() - .map(|u| u.cm) - .chain(us.iter().map(|u| u.cm)) - .scalar_rlc(&rho_powers), - u: Us - .iter() - .map(|u| u.u) - .chain([CM::Scalar::one(); N]) - .scalar_rlc(&rho_powers), - x: Us - .iter() - .map(|u| &u.x[..]) - .chain(us.iter().map(|u| &u.x[..])) - .slice_rlc(&rho_powers), - r_x: r_x_prime, - v: sigmas - .chunks(t) - .chain(thetas.chunks(t)) - .slice_rlc(&rho_powers), - }, - NIMFSProof { - sc_proof: sumcheck_proof, - sigmas, - thetas, - }, - rho_bits.try_into().unwrap(), - )) - } -} - -impl< - CM: GroupBasedCommitment, - V: CCSVariant, - const M: usize, - const N: usize, - const CHALLENGE_BITS: usize, -> FoldingSchemeVerifier for HyperNova -{ - #[allow(non_snake_case)] - fn verify( - _vk: &(), - transcript: &mut impl Transcript, - Us: &[impl Borrow; M], - us: &[impl Borrow; N], - proof: &Self::Proof, - ) -> Result { - let Us = &Us.iter().map(|i| i.borrow()).collect::>(); - let us = &us.iter().map(|i| i.borrow()).collect::>(); - - let d = V::degree(); - let s = proof.sc_proof.len(); - let t = V::n_matrices(); - let S = &V::multisets_vec(); - let c = &V::coefficients_vec::(); - - // absorb instances to transcript - transcript.add(&Us[..]); - transcript.add(&us[..]); - - // Step 1: Get some challenges - let gamma = transcript.challenge_field_element(); - let beta = transcript.challenge_field_elements(s); - - let gamma_powers = gamma.powers(M * t + N); - - let vp_aux_info = VPAuxInfo { - max_degree: d + 1, - num_variables: s, - }; - - // Step 3: Start verifying the sumcheck - // First, compute the expected sumcheck sum: \sum gamma^j v_j - let sum_v_j_gamma = Us - .iter() - .zip(gamma_powers.chunks(t)) - .flat_map(|(U, gammas)| U.v.iter().zip(gammas).map(|(&v, &g)| v * g)) - .sum(); - - // Verify the interactive part of the sumcheck - // Step 2: Dig into the sumcheck claim and extract the randomness used - let (claimed_eval, r_x_prime) = - SumCheck::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; - - // Step 5: Finish verifying sumcheck (verify the claim c) - let e_beta = EqPoly::fix_xy_eval(&beta, &r_x_prime); - let c = proof - .sigmas - .chunks(t) - .zip(Us) - .flat_map(|(sigmas, u)| { - let e_lcccs = EqPoly::fix_xy_eval(&u.r_x, &r_x_prime); - sigmas.iter().map(move |sigma_j| e_lcccs * sigma_j) - }) - .chain(proof.thetas.chunks(t).map(|thetas| { - S.iter() - .zip(c) - .map(|(S_i, &c_i)| c_i * S_i.iter().map(|&j| thetas[j]).product::()) - .sum::() - * e_beta - })) - .zip(gamma_powers) - .map(|(val, gamma_i)| val * gamma_i) - .sum::(); - // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas - (c == claimed_eval).then_some(()).ok_or_else(|| { - SumCheckError::IncorrectEvaluation(claimed_eval.to_string(), c.to_string()) - })?; - - // Step 6: Get the folding challenge - let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); - let rho = CM::Scalar::from_bits_le(&rho_bits); - - let rho_powers = rho.powers(M + N); - - Ok(Self::RU { - cm: Us - .iter() - .map(|u| u.cm) - .chain(us.iter().map(|u| u.cm)) - .scalar_rlc(&rho_powers), - u: Us - .iter() - .map(|u| u.u) - .chain([CM::Scalar::one(); N]) - .scalar_rlc(&rho_powers), - x: Us - .iter() - .map(|u| &u.x[..]) - .chain(us.iter().map(|u| &u.x[..])) - .slice_rlc(&rho_powers), - r_x: r_x_prime, - v: proof - .sigmas - .chunks(t) - .chain(proof.thetas.chunks(t)) - .slice_rlc(&rho_powers), - }) - } -} - pub struct HyperNova2 { _t: PhantomData<(CM, V)>, } @@ -557,295 +265,6 @@ impl Foldi ([CM::Commitment; N], NIMFSProof); } -impl FoldingSchemePreprocessor - for HyperNova2 -{ - fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { - let ck = CM::generate_key(ck_len, &mut rng)?; - Ok(ck) - } -} - -impl FoldingSchemeKeyGenerator - for HyperNova2 -{ - fn generate_keys(ck: Self::PublicParam, ccs: Self::Arith) -> Result { - let ck = Arc::new(ck); - let ccs = Arc::new(ccs); - if ck.max_scalars_len() < ccs.config().n_witnesses() { - return Err(Error::InvalidPublicParameters( - "The commitment key is too short for the CCS instance".into(), - )); - } - Ok(Self::DeciderKey { arith: ccs, ck }) - } -} - -impl< - CM: GroupBasedCommitment, - V: CCSVariant, - const M: usize, - const N: usize, - const CHALLENGE_BITS: usize, -> FoldingSchemeProver for HyperNova2 -{ - #[allow(non_snake_case)] - fn prove( - pk: &HyperNovaKey, - transcript: &mut impl Transcript, - Ws: &[impl Borrow; M], - Us: &[impl Borrow; M], - ws: &[impl Borrow; N], - us: &[impl Borrow; N], - mut rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error> { - let Ws = &Ws.iter().map(|i| i.borrow()).collect::>(); - let Us = &Us.iter().map(|i| i.borrow()).collect::>(); - let ws = &ws.iter().map(|i| i.borrow()).collect::>(); - let us = &us.iter().map(|i| i.borrow()).collect::>(); - - let ccs = &pk.arith; - let d = V::degree(); - let s = ccs.config().log_constraints(); - let t = V::n_matrices(); - let S = &V::multisets_vec(); - let c = &V::coefficients_vec::(); - - let mut cms = [CM::Commitment::default(); N]; - let mut rs = [CM::Randomness::default(); N]; - for i in 0..N { - let (cm, r) = CM::commit(&pk.ck, ws[i], &mut rng)?; - cms[i] = cm; - rs[i] = r; - } - - // absorb instances to transcript - transcript.add(&Us[..]); - transcript.add(&us[..]); - transcript.add(&cms[..]); - - // Step 1: Get some challenges - let gamma = transcript.challenge_field_element(); - let beta = transcript.challenge_field_elements(s); - - let gamma_powers = gamma.powers(M * t + N); - let (running_gammas, incoming_gammas) = gamma_powers.split_at(M * t); - - // Compute g(x) - let running_mles = Ws - .iter() - .zip(Us) - .flat_map(|(W, U)| ccs.mles((U.u, &U.x, &W.w).into())); - let incoming_mles = ws - .iter() - .zip(us) - .flat_map(|(w, u)| ccs.mles((One::one(), &u[..], &w[..]).into())); - let eq_mles = Us - .iter() - .map(|U| &U.r_x) - .chain([&beta]) - .map(|r| MLE::from_evaluations_vec(s, EqPoly::fix_y_evals(r))); - - let running_products = running_gammas - .iter() - .enumerate() - .map(|(i, &gamma)| (gamma, vec![i, (M + N) * t + i / t])); - let incoming_products = incoming_gammas.iter().enumerate().flat_map(|(k, gamma)| { - S.iter().zip(c).map(move |(S_i, &c_i)| { - ( - c_i * gamma, - S_i.iter() - .map(|j| (M + k) * t + j) - .chain([(M + N) * t + M]) - .collect(), - ) - }) - }); - - let g = VirtualPolynomial { - aux_info: VPAuxInfo { - num_variables: s, - max_degree: d + 1, - }, - flattened_ml_extensions: running_mles.chain(incoming_mles).chain(eq_mles).collect(), - products: running_products.chain(incoming_products).collect(), - }; - - // Step 3: Run the sumcheck prover - // Step 2: dig into the sumcheck and extract r_x_prime - let (sumcheck_proof, r_x_prime, mles) = SumCheck::prove(g, transcript)?; - - // Step 4: compute sigmas and thetas - let sigmas = mles[0..t * M] - .iter() - .map(|mle| mle.fix_variables(&[])[0]) - .collect::>(); - let thetas = mles[t * M..t * (M + N)] - .iter() - .map(|mle| mle.fix_variables(&[])[0]) - .collect::>(); - - // Step 6: Get the folding challenge - let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); - let rho = CM::Scalar::from_bits_le(&rho_bits); - - let rho_powers = rho.powers(M + N); - - Ok(( - Self::RW { - w: Ws - .iter() - .map(|w| &w.w[..]) - .chain(ws.iter().map(|w| &w[..])) - .slice_rlc(&rho_powers), - r: Ws.iter().map(|w| w.r).chain(rs).scalar_rlc(&rho_powers), - }, - Self::RU { - cm: Us - .iter() - .map(|u| u.cm) - .chain(cms.iter().copied()) - .scalar_rlc(&rho_powers), - u: Us - .iter() - .map(|u| u.u) - .chain([CM::Scalar::one(); N]) - .scalar_rlc(&rho_powers), - x: Us - .iter() - .map(|u| &u.x[..]) - .chain(us.iter().map(|u| &u[..])) - .slice_rlc(&rho_powers), - r_x: r_x_prime, - v: sigmas - .chunks(t) - .chain(thetas.chunks(t)) - .slice_rlc(&rho_powers), - }, - ( - cms, - NIMFSProof { - sc_proof: sumcheck_proof, - sigmas, - thetas, - }, - ), - rho_bits.try_into().unwrap(), - )) - } -} - -impl< - CM: GroupBasedCommitment, - V: CCSVariant, - const M: usize, - const N: usize, - const CHALLENGE_BITS: usize, -> FoldingSchemeVerifier for HyperNova2 -{ - #[allow(non_snake_case)] - fn verify( - _vk: &(), - transcript: &mut impl Transcript, - Us: &[impl Borrow; M], - us: &[impl Borrow; N], - (cms, proof): &Self::Proof, - ) -> Result { - let Us = &Us.iter().map(|i| i.borrow()).collect::>(); - let us = &us.iter().map(|i| i.borrow()).collect::>(); - - let d = V::degree(); - let s = proof.sc_proof.len(); - let t = V::n_matrices(); - let S = &V::multisets_vec(); - let c = &V::coefficients_vec::(); - - // absorb instances to transcript - transcript.add(&Us[..]); - transcript.add(&us[..]); - transcript.add(&cms[..]); - - // Step 1: Get some challenges - let gamma = transcript.challenge_field_element(); - let beta = transcript.challenge_field_elements(s); - - let gamma_powers = gamma.powers(M * t + N); - - let vp_aux_info = VPAuxInfo { - max_degree: d + 1, - num_variables: s, - }; - - // Step 3: Start verifying the sumcheck - // First, compute the expected sumcheck sum: \sum gamma^j v_j - let sum_v_j_gamma = Us - .iter() - .zip(gamma_powers.chunks(t)) - .flat_map(|(U, gammas)| U.v.iter().zip(gammas).map(|(&v, &g)| v * g)) - .sum(); - - // Verify the interactive part of the sumcheck - // Step 2: Dig into the sumcheck claim and extract the randomness used - let (claimed_eval, r_x_prime) = - SumCheck::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; - - // Step 5: Finish verifying sumcheck (verify the claim c) - let e_beta = EqPoly::fix_xy_eval(&beta, &r_x_prime); - let c = proof - .sigmas - .chunks(t) - .zip(Us) - .flat_map(|(sigmas, u)| { - let e_lcccs = EqPoly::fix_xy_eval(&u.r_x, &r_x_prime); - sigmas.iter().map(move |sigma_j| e_lcccs * sigma_j) - }) - .chain(proof.thetas.chunks(t).map(|thetas| { - S.iter() - .zip(c) - .map(|(S_i, &c_i)| c_i * S_i.iter().map(|&j| thetas[j]).product::()) - .sum::() - * e_beta - })) - .zip(gamma_powers) - .map(|(val, gamma_i)| val * gamma_i) - .sum::(); - // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas - (c == claimed_eval).then_some(()).ok_or_else(|| { - SumCheckError::IncorrectEvaluation(claimed_eval.to_string(), c.to_string()) - })?; - - // Step 6: Get the folding challenge - let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); - let rho = CM::Scalar::from_bits_le(&rho_bits); - - let rho_powers = rho.powers(M + N); - - Ok(Self::RU { - cm: Us - .iter() - .map(|u| u.cm) - .chain(cms.iter().copied()) - .scalar_rlc(&rho_powers), - u: Us - .iter() - .map(|u| u.u) - .chain([CM::Scalar::one(); N]) - .scalar_rlc(&rho_powers), - x: Us - .iter() - .map(|u| &u.x[..]) - .chain(us.iter().map(|u| &u[..])) - .slice_rlc(&rho_powers), - r_x: r_x_prime, - v: proof - .sigmas - .chunks(t) - .chain(proof.thetas.chunks(t)) - .slice_rlc(&rho_powers), - }) - } -} - #[derive(Clone)] pub struct NIMFSProofVar { pub sc_proof: Vec>>, @@ -919,134 +338,6 @@ impl Foldi type Proof = NIMFSProofVar; } -impl< - CM: GroupBasedCommitment, - V: CCSVariant, - const M: usize, - const N: usize, - const CHALLENGE_BITS: usize, -> FoldingSchemePartialVerifierGadget for HyperNovaGadget -{ - #[allow(non_snake_case)] - fn verify_hinted( - _vk: &Self::VerifierKey, - transcript: &mut impl TranscriptGadget, - Us: [&Self::RU; M], - us: [&Self::IU; N], - proof: &Self::Proof, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { - let d = V::degree(); - let s = proof.sc_proof.len(); - let t = V::n_matrices(); - let S = &V::multisets_vec(); - let c = &V::coefficients_vec::(); - - // absorb instances to transcript - transcript.add(&Us[..])?; - transcript.add(&us[..])?; - - // Step 1: Get some challenges - let gamma = transcript.challenge_field_element()?; - let beta = transcript.challenge_field_elements(s)?; - - let gamma_powers = gamma.powers(M * t + N); - - let vp_aux_info = VPAuxInfo { - max_degree: d + 1, - num_variables: s, - }; - - // Step 3: Start verifying the sumcheck - // First, compute the expected sumcheck sum: \sum gamma^j v_j - let mut sum_v_j_gamma = FpVar::zero(); - for (i, U) in Us.iter().enumerate() { - for j in 0..U.v.len() { - sum_v_j_gamma += &U.v[j] * &gamma_powers[i * t + j]; - } - } - - // Verify the interactive part of the sumcheck - // Step 2: Dig into the sumcheck claim and extract the randomness used - let (expected_eval, r_x_prime) = - SumCheckGadget::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript)?; - - // Step 5: Finish verifying sumcheck (verify the claim c) - let c = { - let e2 = EqPolyGadget::fix_xy_eval(&beta, &r_x_prime); - proof - .sigmas - .chunks(t) - .zip(Us) - .flat_map(|(sigmas, u)| { - let e_lcccs = EqPolyGadget::fix_xy_eval(&u.r_x, &r_x_prime); - sigmas.iter().map(move |sigma_j| &e_lcccs * sigma_j) - }) - .chain(proof.thetas.chunks(t).map(|thetas| { - &e2 * S - .iter() - .zip(c) - .map(|(S_i, &c_i)| { - let mut prod = FpVar::one(); - for &j in S_i { - prod *= &thetas[j]; - } - prod * c_i - }) - .sum::>() - })) - .zip(gamma_powers.iter()) - .map(|(val, gamma_i)| val * gamma_i) - .sum::>() - }; - - // check that the g(r_x') from the sumcheck proof is equal to the computed c from sigmas&thetas - c.enforce_equal(&expected_eval)?; - - // Step 6: Get the folding challenge - let rho_bits = transcript.challenge_bits(CHALLENGE_BITS)?; - let rho = Boolean::le_bits_to_fp(&rho_bits)?; - - let rho_powers = rho.powers(M + N); - - Ok(( - Self::RU { - cm: { - let cms = Us - .iter() - .map(|u| &u.cm) - .chain(us.iter().map(|u| &u.cm)) - .collect::>(); - - AllocVar::new_witness(cms.cs().or(rho_powers.cs()), || { - let cms = cms.value().unwrap_or(vec![Default::default(); M + N]); - let rho_powers = rho_powers - .value() - .unwrap_or(vec![Default::default(); M + N]); - Ok(cms.into_iter().scalar_rlc(&rho_powers)) - })? - }, - u: Us - .iter() - .map(|u| u.u.clone()) - .chain(vec![FpVar::one(); N]) - .scalar_rlc(&rho_powers), - x: Us - .iter() - .map(|u| &u.x[..]) - .chain(us.iter().map(|u| &u.x[..])) - .slice_rlc(&rho_powers), - r_x: r_x_prime, - v: proof - .sigmas - .chunks(t) - .chain(proof.thetas.chunks(t)) - .slice_rlc(&rho_powers), - }, - rho_bits.try_into().unwrap(), - )) - } -} - impl GroupBasedFoldingSchemePrimaryDef for HyperNova { diff --git a/crates/fs/src/hypernova/witness/circuits.rs b/crates/fs/src/hypernova/witnesses/circuits.rs similarity index 100% rename from crates/fs/src/hypernova/witness/circuits.rs rename to crates/fs/src/hypernova/witnesses/circuits.rs diff --git a/crates/fs/src/hypernova/witness/mod.rs b/crates/fs/src/hypernova/witnesses/mod.rs similarity index 100% rename from crates/fs/src/hypernova/witness/mod.rs rename to crates/fs/src/hypernova/witnesses/mod.rs From 72f3be4e77d005d0cd70d658c1f8ebe9fa32c386 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 98/99] Actually test wasm targets --- crates/fs/src/hypernova/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/fs/src/hypernova/mod.rs b/crates/fs/src/hypernova/mod.rs index 2692ab03c..aaa158722 100644 --- a/crates/fs/src/hypernova/mod.rs +++ b/crates/fs/src/hypernova/mod.rs @@ -356,6 +356,8 @@ mod tests { circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, commitments::pedersen::Pedersen, }; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::tests::test_folding_scheme; From 9c4e61f8d1a5be87c00f28838a0ef657a3cf479f Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:10:33 +0800 Subject: [PATCH 99/99] Add HyperNova docs --- .../src/hypernova/algorithms/key_generator.rs | 2 ++ crates/fs/src/hypernova/algorithms/mod.rs | 2 ++ .../src/hypernova/algorithms/preprocessor.rs | 4 ++- crates/fs/src/hypernova/algorithms/prover.rs | 2 ++ .../fs/src/hypernova/algorithms/verifier.rs | 2 ++ crates/fs/src/hypernova/circuits/mod.rs | 2 ++ crates/fs/src/hypernova/circuits/verifier.rs | 2 ++ crates/fs/src/hypernova/instances/circuits.rs | 11 ++++++++ crates/fs/src/hypernova/instances/mod.rs | 13 ++++++++++ crates/fs/src/hypernova/mod.rs | 26 +++++++++++++++++++ crates/fs/src/hypernova/witnesses/circuits.rs | 8 ++++++ crates/fs/src/hypernova/witnesses/mod.rs | 9 +++++++ .../compilers/cyclefold/adapters/hypernova.rs | 9 ++++++- 13 files changed, 90 insertions(+), 2 deletions(-) diff --git a/crates/fs/src/hypernova/algorithms/key_generator.rs b/crates/fs/src/hypernova/algorithms/key_generator.rs index d903800f7..7c5c709e9 100644 --- a/crates/fs/src/hypernova/algorithms/key_generator.rs +++ b/crates/fs/src/hypernova/algorithms/key_generator.rs @@ -1,3 +1,5 @@ +//! Key generation for HyperNova. + use ark_std::sync::Arc; use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig, ccs::CCSVariant}, diff --git a/crates/fs/src/hypernova/algorithms/mod.rs b/crates/fs/src/hypernova/algorithms/mod.rs index 11e838298..2b6a65a6e 100644 --- a/crates/fs/src/hypernova/algorithms/mod.rs +++ b/crates/fs/src/hypernova/algorithms/mod.rs @@ -1,3 +1,5 @@ +//! Implementations folding scheme algorithms for HyperNova. + pub mod key_generator; pub mod preprocessor; pub mod prover; diff --git a/crates/fs/src/hypernova/algorithms/preprocessor.rs b/crates/fs/src/hypernova/algorithms/preprocessor.rs index 3ea2735ac..f4624318d 100644 --- a/crates/fs/src/hypernova/algorithms/preprocessor.rs +++ b/crates/fs/src/hypernova/algorithms/preprocessor.rs @@ -1,3 +1,5 @@ +//! Preprocessing for HyperNova. + use ark_std::rand::RngCore; use sonobe_primitives::{arithmetizations::ccs::CCSVariant, commitments::GroupBasedCommitment}; @@ -22,4 +24,4 @@ impl Foldi let ck = CM::generate_key(ck_len, &mut rng)?; Ok(ck) } -} \ No newline at end of file +} diff --git a/crates/fs/src/hypernova/algorithms/prover.rs b/crates/fs/src/hypernova/algorithms/prover.rs index afa4010b9..4ee5ba037 100644 --- a/crates/fs/src/hypernova/algorithms/prover.rs +++ b/crates/fs/src/hypernova/algorithms/prover.rs @@ -1,3 +1,5 @@ +//! Proof generation for HyperNova. + use ark_ff::One; use ark_poly::{DenseMultilinearExtension as MLE, MultilinearExtension}; use ark_std::{borrow::Borrow, rand::RngCore}; diff --git a/crates/fs/src/hypernova/algorithms/verifier.rs b/crates/fs/src/hypernova/algorithms/verifier.rs index 89933da59..324e8e4a6 100644 --- a/crates/fs/src/hypernova/algorithms/verifier.rs +++ b/crates/fs/src/hypernova/algorithms/verifier.rs @@ -1,3 +1,5 @@ +//! Proof verification for HyperNova. + use ark_ff::One; use ark_std::borrow::Borrow; use sonobe_primitives::{ diff --git a/crates/fs/src/hypernova/circuits/mod.rs b/crates/fs/src/hypernova/circuits/mod.rs index 9a0722027..9367afad7 100644 --- a/crates/fs/src/hypernova/circuits/mod.rs +++ b/crates/fs/src/hypernova/circuits/mod.rs @@ -1 +1,3 @@ +//! In-circuit gadgets for HyperNova. + pub mod verifier; diff --git a/crates/fs/src/hypernova/circuits/verifier.rs b/crates/fs/src/hypernova/circuits/verifier.rs index fa2d7b299..38d35c2aa 100644 --- a/crates/fs/src/hypernova/circuits/verifier.rs +++ b/crates/fs/src/hypernova/circuits/verifier.rs @@ -1,3 +1,5 @@ +//! Partial in-circuit verifier implementation for HyperNova. + use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, diff --git a/crates/fs/src/hypernova/instances/circuits.rs b/crates/fs/src/hypernova/instances/circuits.rs index 9a295fec4..384f1b7e7 100644 --- a/crates/fs/src/hypernova/instances/circuits.rs +++ b/crates/fs/src/hypernova/instances/circuits.rs @@ -1,3 +1,4 @@ +//! In-circuit variables for HyperNova instances. use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, @@ -12,12 +13,19 @@ use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::Absorbabl use super::{CCCSInstance, LCCCSInstance}; use crate::FoldingInstanceVar; +/// [`LCCCSInstanceVar`] defines HyperNova's running instance variable. #[derive(Clone, Debug, PartialEq)] pub struct LCCCSInstanceVar { + /// [`LCCCSInstanceVar::cm`] is the witness commitment. pub cm: CM::CommitmentVar, + /// [`LCCCSInstanceVar::u`] is the constant term. pub u: CM::ScalarVar, + /// [`LCCCSInstanceVar::x`] is the vector of public inputs (to the circuit). pub x: Vec, + /// [`LCCCSInstanceVar::r_x`] is the random evaluation point. pub r_x: Vec, + /// [`LCCCSInstanceVar::v`] is the vector of sums of MLE evaluations defined + /// in Definition 2. pub v: Vec, } @@ -143,9 +151,12 @@ impl FoldingInstanceVar for LCCCSInstanceVar { } } +/// [`CCCSInstanceVar`] defines HyperNova's incoming instance variable. #[derive(Clone, Debug, PartialEq)] pub struct CCCSInstanceVar { + /// [`CCCSInstanceVar::cm`] is the witness commitment. pub cm: CM::CommitmentVar, + /// [`CCCSInstanceVar::x`] is the vector of public inputs (to the circuit). pub x: Vec, } diff --git a/crates/fs/src/hypernova/instances/mod.rs b/crates/fs/src/hypernova/instances/mod.rs index a73e50a84..5399c6c07 100644 --- a/crates/fs/src/hypernova/instances/mod.rs +++ b/crates/fs/src/hypernova/instances/mod.rs @@ -1,3 +1,6 @@ +//! Definitions of out-of-circuit values and in-circuit variables for HyperNova +//! instances. + use ark_ff::PrimeField; use sonobe_primitives::{ arithmetizations::{ @@ -13,12 +16,19 @@ use crate::FoldingInstance; pub mod circuits; +/// [`LCCCSInstance`] defines HyperNova's running instance. #[derive(Clone, Debug, Eq, PartialEq)] pub struct LCCCSInstance { + /// [`LCCCSInstance::cm`] is the witness commitment. pub cm: CM::Commitment, + /// [`LCCCSInstance::u`] is the constant term. pub u: CM::Scalar, + /// [`LCCCSInstance::x`] is the vector of public inputs (to the circuit). pub x: Vec, + /// [`LCCCSInstance::r_x`] is the random evaluation point. pub r_x: Vec, + /// [`LCCCSInstance::v`] is the vector of sums of MLE evaluations defined in + /// Definition 2. pub v: Vec, } @@ -60,9 +70,12 @@ impl Absorbable for LCCCSInstance { } } +/// [`CCCSInstance`] defines HyperNova's incoming instance. #[derive(Clone, Debug, Eq, PartialEq)] pub struct CCCSInstance { + /// [`CCCSInstance::cm`] is the witness commitment. pub cm: CM::Commitment, + /// [`CCCSInstance::x`] is the vector of public inputs (to the circuit). pub x: Vec, } diff --git a/crates/fs/src/hypernova/mod.rs b/crates/fs/src/hypernova/mod.rs index aaa158722..1e5ea89d9 100644 --- a/crates/fs/src/hypernova/mod.rs +++ b/crates/fs/src/hypernova/mod.rs @@ -1,3 +1,8 @@ +//! This module implements the HyperNova folding scheme, which is introduced in +//! this [paper]. +//! +//! [paper]: https://eprint.iacr.org/2023/573.pdf + use ark_ff::{Field, PrimeField}; use ark_poly::MultilinearExtension; use ark_r1cs_std::{ @@ -41,6 +46,7 @@ pub mod circuits; pub mod instances; pub mod witnesses; +/// [`HyperNovaKey`] is HyperNova's decider key. #[derive(Clone)] pub struct HyperNovaKey { arith: Arc, @@ -196,10 +202,16 @@ where } } +/// [`NIMFSProof`] is HyperNova's proof. #[derive(Clone, Debug, PartialEq, Eq)] pub struct NIMFSProof { + /// [`NIMFSProof::sc_proof`] is the sum-check proof. pub sc_proof: Vec>, + /// [`NIMFSProof::sigmas`] is a vector of claimed internal sums defined + /// in Equation 9 pub sigmas: Vec, + /// [`NIMFSProof::thetas`] is a vector of claimed internal sums defined + /// in Equation 10 pub thetas: Vec, } @@ -218,6 +230,7 @@ impl Dummy<&CCSConfig { _t: PhantomData<(CM, V)>, } @@ -241,6 +254,12 @@ impl Foldi type Proof = NIMFSProof; } +/// [`HyperNova2`] implements the HyperNova folding scheme for a CCS variant +/// `V`. +/// +/// This design is experimental, following the definition of accumulation +/// schemes where the incoming witnesses and instances are simply plain vectors +/// in the circuit's assignments. pub struct HyperNova2 { _t: PhantomData<(CM, V)>, } @@ -265,10 +284,16 @@ impl Foldi ([CM::Commitment; N], NIMFSProof); } +/// [`NIMFSProofVar`] is the in-circuit variable for [`NIMFSProof`]. #[derive(Clone)] pub struct NIMFSProofVar { + /// [`NIMFSProofVar::sc_proof`] is the sum-check proof. pub sc_proof: Vec>>, + /// [`NIMFSProofVar::sigmas`] is a vector of claimed internal sums defined + /// in Equation 9 pub sigmas: Vec>, + /// [`NIMFSProofVar::thetas`] is a vector of claimed internal sums defined + /// in Equation 10 pub thetas: Vec>, } @@ -321,6 +346,7 @@ impl GR1CSVar for NIMFSProofVa } } +/// [`HyperNovaGadget`] is the in-circuit gadget for [`HyperNova`]. pub struct HyperNovaGadget { _t: PhantomData<(CM, V)>, } diff --git a/crates/fs/src/hypernova/witnesses/circuits.rs b/crates/fs/src/hypernova/witnesses/circuits.rs index 4f2cbc973..a129b7eea 100644 --- a/crates/fs/src/hypernova/witnesses/circuits.rs +++ b/crates/fs/src/hypernova/witnesses/circuits.rs @@ -1,3 +1,5 @@ +//! In-circuit variables for HyperNova witnesses. + use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, @@ -8,9 +10,12 @@ use sonobe_primitives::commitments::CommitmentDefGadget; use super::{CCCSWitness, LCCCSWitness}; +/// [`LCCCSWitnessVar`] defines HyperNova's running witness variable. #[derive(Debug, PartialEq)] pub struct LCCCSWitnessVar { + /// [`LCCCSWitnessVar::w`] is the witness (to the circuit). pub w: Vec, + /// [`LCCCSWitnessVar::r`] is the randomness for the witness commitment. pub r: CM::RandomnessVar, } @@ -47,9 +52,12 @@ impl GR1CSVar for LCCCSWitnessVar< } } +/// [`CCCSWitnessVar`] defines HyperNova's incoming witness variable. #[derive(Debug, PartialEq)] pub struct CCCSWitnessVar { + /// [`CCCSWitnessVar::w`] is the witness (to the circuit). pub w: Vec, + /// [`CCCSWitnessVar::r`] is the randomness for the witness commitment. pub r: CM::RandomnessVar, } diff --git a/crates/fs/src/hypernova/witnesses/mod.rs b/crates/fs/src/hypernova/witnesses/mod.rs index ca5247e70..fbe217614 100644 --- a/crates/fs/src/hypernova/witnesses/mod.rs +++ b/crates/fs/src/hypernova/witnesses/mod.rs @@ -1,12 +1,18 @@ +//! Definitions of out-of-circuit values and in-circuit variables for HyperNova +//! witnesses. + use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy}; use crate::FoldingWitness; pub mod circuits; +/// [`LCCCSWitness`] defines HyperNova's running witness. #[derive(Clone, Debug, Eq, PartialEq)] pub struct LCCCSWitness { + /// [`LCCCSWitness::w`] is the witness (to the circuit). pub w: Vec, + /// [`LCCCSWitness::r`] is the randomness for the witness commitment. pub r: CM::Randomness, } @@ -27,9 +33,12 @@ impl Dummy<&Cfg> for LCCCSWitness { } } +/// [`CCCSWitness`] defines HyperNova's incoming witness. #[derive(Clone, Debug, Eq, PartialEq)] pub struct CCCSWitness { + /// [`CCCSWitness::w`] is the witness (to the circuit). pub w: Vec, + /// [`CCCSWitness::r`] is the randomness for the witness commitment. pub r: CM::Randomness, } diff --git a/crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs b/crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs index 6ee21bdb1..a552e22da 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/hypernova.rs @@ -1,3 +1,6 @@ +//! HyperNova CycleFold adapter that bridges HyperNova into the CycleFold IVC +//! compiler. + use ark_ff::{PrimeField, Zero}; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, prelude::Boolean}; use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; @@ -19,7 +22,7 @@ use crate::compilers::cyclefold::{ CycleFoldBasedIVC, FoldingSchemeCycleFoldExt, circuits::CycleFoldCircuit, }; -/// Configuration for HyperNova's CycleFold circuit +/// [`HyperNovaCycleFoldCircuit`] defines CycleFold circuit for HyperNova. pub struct HyperNovaCycleFoldCircuit { r: Vec, @@ -119,9 +122,13 @@ impl< } } +/// [`HyperNovaOvaIVC`] defines a CycleFold-based IVC using HyperNova as the +/// primary folding scheme and Ova as the secondary folding scheme. pub type HyperNovaOvaIVC = CycleFoldBasedIVC, CycleFoldOva, T>; +/// [`HyperNovaNovaIVC`] defines a CycleFold-based IVC using HyperNova as the +/// primary folding scheme and Nova as the secondary folding scheme. pub type HyperNovaNovaIVC = CycleFoldBasedIVC, CycleFoldNova, T>;