From c79bc82227ed846e2da346fd87c28624ce9928c2 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 001/131] 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 002/131] 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 003/131] 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 59d3abe6f658b94898f7562bdf1335e7e110ab99 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 004/131] Refactor: port circuit traits, and more --- Cargo.toml | 4 +- 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 | 5 +- crates/primitives/src/relations/mod.rs | 52 ++++ .../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 -- 19 files changed, 748 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 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..1677e4faa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "crates/primitives", - "crates/traits", ] resolver = "2" @@ -20,6 +19,8 @@ ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", rev = "ae8283a" # 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" } [patch."https://github.com/arkworks-rs/crypto-primitives"] ark-crypto-primitives = { git = "https://github.com/winderica/crypto-primitives", rev = "af003fc" } @@ -79,4 +80,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..ea5e81c82 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,4 +1,7 @@ -pub mod commitments; pub mod arithmetizations; +pub mod circuits; +pub mod commitments; pub mod gadgets; +pub mod relations; +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/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 af476ef42e535cc86778b6c3d79e0e9a23d716dd Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:27 +0800 Subject: [PATCH 005/131] 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/traits/mod.rs | 139 +++++++++++------- crates/primitives/src/transcripts/mod.rs | 103 +++++++++---- crates/primitives/src/transcripts/poseidon.rs | 87 ++++++----- 5 files changed, 209 insertions(+), 127 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/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 f22a1c24ec7094dbb454e8eb71cc23ac4e811b71 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:52 +0800 Subject: [PATCH 006/131] 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 19e36f2e5c9feb697fb763effa17ef79fb218e26 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 20:49:56 +0800 Subject: [PATCH 007/131] 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/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 +- 27 files changed, 3255 insertions(+), 1533 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 ea5e81c82..3a551eccf 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 traits; pub mod transcripts; 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 e684054973c8ef7b8f644c0e1b3fe994964fd1d3 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 22:09:08 +0800 Subject: [PATCH 008/131] 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 f4291aa36297a287652710f6c78eafda8fa0b99e Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 25 Oct 2025 23:27:13 +0800 Subject: [PATCH 009/131] 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 615b223b6768cb6a11ee3b699d5cdb0832f3107b Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 27 Oct 2025 17:19:35 +0800 Subject: [PATCH 010/131] 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/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 ++ 31 files changed, 2301 insertions(+), 1842 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/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 3a551eccf..55d3d4f55 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -5,3 +5,4 @@ pub mod commitments; pub mod relations; pub mod traits; pub mod transcripts; +pub mod utils; 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 de0599f3326bfe9768dceb7f83b3bc845e101581 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 03:02:00 +0800 Subject: [PATCH 011/131] 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 c95a11777aa6ea999c9904a35b5424c26f4e4cdb Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 03:04:37 +0800 Subject: [PATCH 012/131] 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 08b5188a1340cee975812f8ef313c5b141b1f1ba Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 04:30:45 +0800 Subject: [PATCH 013/131] 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 ---- .../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 + 16 files changed, 191 insertions(+), 195 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/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 e63dda9f0d086f1d55b3301459faf8964a47c525 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 22:33:18 +0800 Subject: [PATCH 014/131] 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 11400e607e35fc5779849399cc94cb9a3f61f302 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 05:40:41 +0800 Subject: [PATCH 015/131] 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 1886efa2bbba8d246ee8eb22901bccf38e9f4643 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 03:11:21 +0800 Subject: [PATCH 016/131] 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 2576f14308c08f26b0227f9af8c2cef7ac4e221d Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 05:30:24 +0800 Subject: [PATCH 017/131] 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 1afbefcd05c4ce61244580ff4d63b4bd86101808 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 25 Nov 2025 00:52:37 +0800 Subject: [PATCH 018/131] 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 21306991875af231ff203cf128e8fcba3895ff3b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 019/131] 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 ec407731fe7d39dffddb6adc10df1d98bb64e885 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 020/131] 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 8a066c73a4ed7100d89c661001de08db2bd70aec Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 021/131] 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 8c2b6b88cacfc132d2ca556f2224f9cd28442980 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 022/131] 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 1677e4faa..0e2ec2403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,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 2e9ff828bf9e80d8717cb18f6cb5932a32ecf6c8 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 15:50:26 +0800 Subject: [PATCH 023/131] 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/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 +- 22 files changed, 202 insertions(+), 189 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/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 6ec10e4a72b73beb52f6059e45262dd4169a3f31 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:19:13 +0800 Subject: [PATCH 024/131] 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 f30e971be6f72bdbe0de17b4535e538680cc2fac Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:44:30 +0800 Subject: [PATCH 025/131] Fix clippy --- crates/primitives/src/algebra/field/emulated.rs | 4 ++-- crates/primitives/src/transcripts/griffin/mod.rs | 4 +++- 2 files changed, 5 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/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 423a038e3825c20a36b2d438e0c8c682cfa46d54 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 026/131] 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 +++- .../primitives/src/transcripts/griffin/mod.rs | 2 + .../src/transcripts/griffin/sponge.rs | 8 +-- .../src/transcripts/poseidon/sponge.rs | 8 +-- 9 files changed, 59 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e2ec2403..50a2befbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,49 +35,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/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 f9f26c04fa793968e9ac9ef9bfd02e812fc81bf4 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 9 Feb 2026 19:36:40 +0800 Subject: [PATCH 027/131] 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/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 + 33 files changed, 1627 insertions(+), 1052 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 55d3d4f55..f595546f7 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/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 c1f820c972c577ff5568a423c25ad129f85140dc Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:10:50 +0800 Subject: [PATCH 028/131] 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 defb4e98528b256492a0478f2db8501c198a4f99 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:48:16 +0800 Subject: [PATCH 029/131] 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/transcripts/griffin/mod.rs | 2 +- crates/primitives/src/transcripts/mod.rs | 10 ++++------ crates/primitives/src/utils/null.rs | 2 +- 7 files changed, 15 insertions(+), 17 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/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 bb0359682283cf9e688f8a9359e1c9b56ffea0cc Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:50:27 +0800 Subject: [PATCH 030/131] 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 ecbfdbdfc604eecaae463cbe0c5596bb486da16f Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 17:21:44 +0800 Subject: [PATCH 031/131] Reorganize cargo.toml --- Cargo.toml | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 50a2befbb..916915046 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,30 +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" } -ark-pallas = { git = "https://github.com/arkworks-rs/algebra" } -ark-vesta = { 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" @@ -39,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" } @@ -65,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 4daef2a55fd8a55ba980ce7e5bf0660c0944560a Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 17 Feb 2026 23:05:06 +0800 Subject: [PATCH 032/131] 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 6cabcaf37192506ba1bae7a49d1bcfbc28f2ad32 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 033/131] 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 dbab02b4d7c22c47340e38c36c5452c08d3a623d Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 15:51:57 +0800 Subject: [PATCH 034/131] 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 5ccccc703b74756bf3fea60e21f56bf5c024e3ee Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 035/131] 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 e6b78289044a66e04b43db7bdd05bf3e926b502e Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:11:22 +0800 Subject: [PATCH 036/131] 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 fd2a2539cd123804d5b28c9c5896943a76f7b6ad Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 037/131] Refactor: initialize revamped folding scheme impl --- Cargo.toml | 2 + crates/fs/Cargo.toml | 29 +++++ crates/fs/src/lib.rs | 278 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 309 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..a1e37162a --- /dev/null +++ b/crates/fs/src/lib.rs @@ -0,0 +1,278 @@ +use ark_relations::gr1cs::SynthesisError; +use ark_std::{fmt::Debug, rand::RngCore}; +use sonobe_primitives::{ + arithmetizations::Arith, + circuits::AssignmentsOwned, + commitments::CommitmentDef, + relations::{Relation, WitnessInstanceSampler}, + 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("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 2f81747f4b66c30cedad1adeaf099f74131400dc Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 15:04:52 +0800 Subject: [PATCH 038/131] 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 a1e37162a..611c67597 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -24,7 +24,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)>; @@ -204,18 +204,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; @@ -240,14 +240,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)?; @@ -256,21 +256,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 f7f46a5764ecaf3926180f5556fd003d3bef9280 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:52 +0800 Subject: [PATCH 039/131] 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 c275554a0d6ecd49bdfb6bc2f6d4c63cc8574d12 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 20:49:56 +0800 Subject: [PATCH 040/131] 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 611c67597..7ed5d1cef 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,12 +1,13 @@ +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}, traits::SonobeField, - transcripts::Transcript, + transcripts::{Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -47,110 +48,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; @@ -172,6 +69,7 @@ pub trait FoldingScheme { Source = AssignmentsOwned<::Scalar>, Error = Error, >; + type Challenge; type Proof; /// The preprocessing method is a randomized algorithm that takes as input @@ -204,18 +102,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; @@ -228,6 +126,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; @@ -247,7 +204,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)?; @@ -286,7 +244,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 cfacb05cd23292b753aa47109f1745c900f9a2a9 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 22:38:47 +0800 Subject: [PATCH 041/131] 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 7ed5d1cef..ec4647b61 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -31,18 +31,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 70edc08bc8fe322871eb68e45c75ac401e472f07 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 25 Oct 2025 23:27:13 +0800 Subject: [PATCH 042/131] 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 ec4647b61..2bba3e833 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, @@ -7,7 +13,7 @@ use sonobe_primitives::{ commitments::{CommitmentDef, CommitmentDefGadget}, relations::{Relation, WitnessInstanceSampler}, traits::SonobeField, - transcripts::{Transcript, TranscriptGadget}, + transcripts::{Absorbable, AbsorbableVar, Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -36,8 +42,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)> { @@ -129,20 +171,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 75175113d7245c800e8c3cb4944b61d033af42c1 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 27 Oct 2025 17:19:35 +0800 Subject: [PATCH 043/131] 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 2bba3e833..69b80f533 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -2,17 +2,20 @@ 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, circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget}, relations::{Relation, WitnessInstanceSampler}, - traits::SonobeField, + traits::{Dummy, SonobeField}, transcripts::{Absorbable, AbsorbableVar, Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -31,18 +34,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 { @@ -77,28 +102,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; @@ -115,7 +166,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 @@ -172,66 +223,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>; @@ -242,9 +300,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 c96086de27a676ddb78c804fdada8b1f9443e2bd Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 8 Nov 2025 08:47:34 +0800 Subject: [PATCH 044/131] Finish unified CycleFold compiler --- crates/fs/Cargo.toml | 1 + crates/fs/src/lib.rs | 228 ++++++++--- 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, 781 insertions(+), 203 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 69b80f533..e5167ecb8 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}, @@ -22,11 +22,11 @@ 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("Unsupported use case: {0}")] Unsupported(String), @@ -35,42 +35,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 { @@ -78,32 +156,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, @@ -113,7 +191,7 @@ impl, Y, F: Field> AllocVar, F> for WrappedVec> CondSelectGadget for WrappedVec { +impl> CondSelectGadget for PlainInstance { fn conditionally_select( cond: &Boolean, true_value: &Self, @@ -122,7 +200,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; @@ -156,7 +267,8 @@ pub trait FoldingScheme { type PublicParam; type ProverKey; type VerifierKey; - type DeciderKey: Relation + type DeciderKey: Clone + + Relation + Relation + WitnessInstanceSampler + WitnessInstanceSampler< @@ -195,6 +307,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, @@ -205,6 +318,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, @@ -213,6 +327,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) } @@ -244,10 +359,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> { @@ -257,12 +378,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>; @@ -280,24 +409,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>, @@ -311,22 +436,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)?; @@ -347,8 +475,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 c0d44114c0f94dd8f6368488682e7d3d8e1c4ca2 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 17 Nov 2025 06:09:37 +0800 Subject: [PATCH 045/131] 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 e5167ecb8..5ec165373 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -11,11 +11,12 @@ 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}, - traits::{Dummy, SonobeField}, + traits::{CF2, Dummy, SonobeField}, transcripts::{Absorbable, AbsorbableVar, Transcript, TranscriptGadget}, }; use thiserror::Error; @@ -388,6 +389,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; @@ -413,8 +446,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>; } @@ -426,8 +459,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 23fe288f4082270a476d11b280f1818c1427719d Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 17 Nov 2025 07:48:25 +0800 Subject: [PATCH 046/131] 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 5ec165373..52bd212db 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 ccf63b60671e79789d13fc86593598621c8fe64a Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 03:04:37 +0800 Subject: [PATCH 047/131] 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 52bd212db..a567ba48a 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, @@ -472,7 +476,7 @@ mod tests { use sonobe_primitives::{ circuits::{ArithExtractor, AssignmentsOwned}, transcripts::{ - griffin::{GriffinParams, sponge::GriffinSponge}, + griffin::{sponge::GriffinSponge, GriffinParams}, poseidon::poseidon_canonical_config, }, }; From cdf86e09603b0e69e85423c6ff1a4868c28e9af0 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 18 Nov 2025 08:30:31 +0800 Subject: [PATCH 048/131] 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 a567ba48a..85e434491 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, @@ -257,7 +256,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; @@ -266,12 +273,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 @@ -297,10 +303,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 @@ -313,7 +316,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], @@ -324,7 +327,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], @@ -394,34 +397,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 { @@ -476,7 +479,7 @@ mod tests { use sonobe_primitives::{ circuits::{ArithExtractor, AssignmentsOwned}, transcripts::{ - griffin::{sponge::GriffinSponge, GriffinParams}, + griffin::{GriffinParams, sponge::GriffinSponge}, poseidon::poseidon_canonical_config, }, }; @@ -498,7 +501,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 4a4ae7381e8c58a018d649bd991106048107712d Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 04:50:00 +0800 Subject: [PATCH 049/131] 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 85e434491..1e78ff38c 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -35,6 +35,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 2c2750cf777b35eb12a7a3c5f4eaa592c9de0108 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 04:50:26 +0800 Subject: [PATCH 050/131] 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 31b2c1484ccf67c5f2bc3d65632e1b091c63c651 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 20:14:48 +0800 Subject: [PATCH 051/131] 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 1e78ff38c..6361ff4a6 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -63,9 +63,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 { @@ -73,42 +73,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, @@ -117,29 +123,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() @@ -154,83 +160,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 { @@ -238,14 +168,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; @@ -437,9 +359,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 6b0e71fce507b20bb1684ba1fd944d1e7bfa8a9c Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 20:15:37 +0800 Subject: [PATCH 052/131] 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 1aecf36c96dca776554a06afb7f1baefeeaf620c Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 22:33:18 +0800 Subject: [PATCH 053/131] 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 0acc7522b091898a880e53cbf3774181cc76de73 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 23:02:25 +0800 Subject: [PATCH 054/131] 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 494e2a7f8d794d735d6b096e79c7b00157f0af1a Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 04:41:39 +0800 Subject: [PATCH 055/131] 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 6361ff4a6..402556008 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -91,9 +91,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) } @@ -194,7 +192,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>; @@ -216,8 +214,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. @@ -251,7 +252,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( @@ -259,7 +260,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)] @@ -302,10 +303,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![] } @@ -323,69 +324,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( @@ -393,10 +365,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; @@ -413,7 +413,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>>, @@ -463,9 +463,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 9265cd9fe4fe49733f873fa58cc5252bd9c91bf4 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 05:30:03 +0800 Subject: [PATCH 056/131] 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 402556008..e46bc0c6c 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -51,7 +51,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. @@ -369,32 +371,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 8ef8788f39801476e55e314d609e879cb67e5d27 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 09:34:45 +0800 Subject: [PATCH 057/131] 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 e46bc0c6c..bf2b9f3f9 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -64,7 +64,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 { @@ -87,6 +87,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) @@ -335,7 +341,13 @@ pub trait FoldingSchemeGadgetDef { type VerifierKey; - type Challenge; + type Challenge: AllocVar< + ::Challenge, + ::ConstraintField, + > + GR1CSVar< + ::ConstraintField, + Value = ::Challenge, + >; type Proof: AllocVar< ::Proof, ::ConstraintField, From 855712299f24675c77eeb1d8e0298762f3778344 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 25 Nov 2025 00:52:37 +0800 Subject: [PATCH 058/131] 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 bf2b9f3f9..b4368c53e 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -226,7 +226,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. @@ -236,12 +236,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 @@ -261,7 +265,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, @@ -270,7 +276,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) @@ -281,6 +289,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 c322d53c5e7a202bab39ab78ae75488acbd665e0 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 26 Dec 2025 22:08:26 +0800 Subject: [PATCH 059/131] 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 05e8c38e36ca9f7b565f88dfed482cab8603d2d9 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 060/131] 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 b4368c53e..f67cdd8d2 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -360,7 +360,7 @@ impl FoldingInstanceVar for PlainInstanceVar::VC>; @@ -385,8 +385,8 @@ pub trait FoldingSchemeGadgetDef { >; } -pub trait FoldingSchemeGadgetOpsPartial: - FoldingSchemeGadgetDef> +pub trait FoldingSchemePartialVerifierGadget: + FoldingSchemeDefGadget> { #[allow(non_snake_case)] fn verify_hinted( @@ -398,8 +398,8 @@ pub trait FoldingSchemeGadgetOpsPartial: ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; } -pub trait FoldingSchemeGadgetOpsFull: - FoldingSchemeGadgetOpsPartial +pub trait FoldingSchemeFullVerifierGadget: + FoldingSchemePartialVerifierGadget { #[allow(non_snake_case)] fn verify( @@ -417,20 +417,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> { } @@ -440,20 +440,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 2f2170a2a2308686e8c004f893eaf12b806dedb8 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 21 Feb 2026 22:19:52 +0800 Subject: [PATCH 061/131] Move trait definitions to their dedicated files # Conflicts: # crates/fs/src/lib.rs --- crates/fs/src/definitions/algorithms.rs | 87 ++++ crates/fs/src/definitions/circuits.rs | 30 ++ crates/fs/src/definitions/errors.rs | 25 + crates/fs/src/definitions/instances.rs | 84 +++ crates/fs/src/definitions/keys.rs | 11 + crates/fs/src/definitions/mod.rs | 75 +++ crates/fs/src/definitions/utils.rs | 100 ++++ crates/fs/src/definitions/variants.rs | 49 ++ crates/fs/src/definitions/witnesses.rs | 47 ++ crates/fs/src/lib.rs | 480 +----------------- .../ivc/src/compilers/cyclefold/circuits.rs | 50 +- crates/ivc/src/compilers/cyclefold/mod.rs | 4 +- crates/ivc/src/compilers/mod.rs | 1 - 13 files changed, 556 insertions(+), 487 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..607c83d64 --- /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::{FoldingSchemeDef, errors::Error, keys::DeciderKey}; + +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..bb1dae623 --- /dev/null +++ b/crates/fs/src/definitions/circuits.rs @@ -0,0 +1,30 @@ +use ark_relations::gr1cs::SynthesisError; +use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::TranscriptGadget}; + +use super::{FoldingSchemeDefGadget, algorithms::FoldingSchemeOps}; + +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..6d3dfd2a6 --- /dev/null +++ b/crates/fs/src/definitions/errors.rs @@ -0,0 +1,25 @@ +use ark_relations::gr1cs::SynthesisError; +use sonobe_primitives::{ + arithmetizations::Error as ArithError, commitments::Error as CommitmentError, +}; +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("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..a03227cbb --- /dev/null +++ b/crates/fs/src/definitions/instances.rs @@ -0,0 +1,84 @@ +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, select::CondSelectGadget}; +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..8c2b407b2 --- /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::{GR1CSVar, alloc::AllocVar}; +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..6c2017157 --- /dev/null +++ b/crates/fs/src/definitions/utils.rs @@ -0,0 +1,100 @@ +use ark_ff::{Field, PrimeField}; +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, + 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..29cc37c49 --- /dev/null +++ b/crates/fs/src/definitions/variants.rs @@ -0,0 +1,49 @@ +use sonobe_primitives::{ + commitments::{CommitmentDef, GroupBasedCommitment}, + traits::CF2, +}; + +use crate::{ + FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, FoldingSchemeOps, + FoldingSchemePartialVerifierGadget, +}; + +pub trait GroupBasedFoldingSchemePrimaryDef: + FoldingSchemeDef< + VC: GroupBasedCommitment, + TranscriptField = <::VC as CommitmentDef>::Scalar, + > +{ + type Gadget: FoldingSchemeDefGadget::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::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..edfacb43f --- /dev/null +++ b/crates/fs/src/definitions/witnesses.rs @@ -0,0 +1,47 @@ +use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; +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 f67cdd8d2..c4716e408 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -1,472 +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}, - 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("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/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 734943c16..dcf885728 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: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + 1, + 1, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, + VC: 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 = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FC: FCircuit::Scalar>, T: Transcript, { @@ -161,21 +161,21 @@ 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 = ()>, + VC: 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 = ()>, + VC: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, >, - >, FC: FCircuit::Scalar>, T: Transcript, { @@ -232,7 +232,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 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, 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 6c3ec508948d4ba76e3ea09b91489fb5db8f2475 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 062/131] Clean up --- crates/fs/src/definitions/circuits.rs | 4 +- crates/fs/src/definitions/instances.rs | 44 ++++++++--------- crates/fs/src/definitions/mod.rs | 26 +++++----- crates/fs/src/definitions/variants.rs | 12 ++--- crates/fs/src/definitions/witnesses.rs | 20 ++++---- crates/fs/src/lib.rs | 6 +-- .../ivc/src/compilers/cyclefold/circuits.rs | 35 ++++++-------- crates/ivc/src/compilers/cyclefold/mod.rs | 48 +++++++++---------- 8 files changed, 95 insertions(+), 100 deletions(-) diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs index bb1dae623..5c127dcd6 100644 --- a/crates/fs/src/definitions/circuits.rs +++ b/crates/fs/src/definitions/circuits.rs @@ -9,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, @@ -22,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 a03227cbb..f24a44c60 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -10,15 +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; @@ -29,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 8c2b407b2..77d45952e 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -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/variants.rs b/crates/fs/src/definitions/variants.rs index 29cc37c49..d8bcae599 100644 --- a/crates/fs/src/definitions/variants.rs +++ b/crates/fs/src/definitions/variants.rs @@ -10,11 +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::Gadget2>; + type Gadget: FoldingSchemeDefGadget::Gadget2>; } pub trait GroupBasedFoldingSchemePrimary: @@ -30,11 +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::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 edfacb43f..783bd3f1f 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -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 dcf885728..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; @@ -48,21 +42,22 @@ where 1, 1, Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, >, FS2: GroupBasedFoldingSchemeSecondary< 1, 1, Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, >, - FC: FCircuit::Scalar>, + FC: FCircuit::Scalar>, T: Transcript, { + #[allow(non_snake_case)] pub fn compute_next_state( &self, cs: ConstraintSystemRef, @@ -164,19 +159,19 @@ where 1, 1, Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, >, FS2: GroupBasedFoldingSchemeSecondary< 1, 1, Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, - VC: CommitmentDef< - Commitment: SonobeCurve::Scalar>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, >, >, - FC: FCircuit::Scalar>, + FC: FCircuit::Scalar>, T: Transcript, { fn generate_constraints( diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index aff040c1f..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,10 +26,11 @@ pub mod circuits; pub trait FoldingSchemeCycleFoldExt: GroupBasedFoldingSchemePrimary { - type CFConfig: CycleFoldConfig::Commitment>; + type CFConfig: CycleFoldConfig::Commitment>; const N_CYCLEFOLDS: usize; + #[allow(non_snake_case)] fn to_cyclefold_configs( Us: &[impl Borrow; M], us: &[impl Borrow; N], @@ -40,6 +38,7 @@ pub trait FoldingSchemeCycleFoldExt: rho: Self::Challenge, ) -> Vec; + #[allow(non_snake_case)] fn to_cyclefold_inputs( Us: [::RU; M], us: [::IU; N], @@ -50,8 +49,8 @@ pub trait FoldingSchemeCycleFoldExt: Vec< Vec< EmulatedFieldVar< - ::Scalar, - CF2<::Commitment>, + ::Scalar, + CF2<::Commitment>, >, >, >, @@ -96,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); @@ -157,7 +156,6 @@ where arith2_config: arith2.config(), step_circuit, }; - let cs = ArithExtractor::new(); cs.execute_synthesizer(augmented_circuit)?; let new_arith1 = cs.arith::()?; @@ -178,6 +176,7 @@ where )) } + #[allow(non_snake_case)] fn prove>( Key(dk1, dk2, (hash_config, pp_hash)): &Self::ProverKey, step_circuit: &FC, @@ -267,6 +266,7 @@ where )) } + #[allow(non_snake_case)] fn verify>( Key(dk1, dk2, (hash_config, pp_hash)): &Self::VerifierKey, i: usize, From 5f58bcba45e45d425c8ce6bfeafdfaadfab52963 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:19:13 +0800 Subject: [PATCH 063/131] 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 daf3bdff0c2b5acd261637a3c6bcd28408547006 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 16:44:30 +0800 Subject: [PATCH 064/131] Fix clippy --- crates/fs/src/definitions/algorithms.rs | 2 +- .../ivc/src/compilers/cyclefold/circuits.rs | 3 +-- crates/ivc/src/compilers/cyclefold/mod.rs | 16 +++++++-------- crates/ivc/src/lib.rs | 20 ++++++------------- 4 files changed, 16 insertions(+), 25 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/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 91ddd27f6011efe5721614b5f2fe5f8a52df1578 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 065/131] 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 c2db87f95f8215d60ef0e892aa46fc013270bc1b Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:10:33 +0800 Subject: [PATCH 066/131] Add FS & IVC docs --- crates/fs/src/definitions/algorithms.rs | 61 ++++++--- crates/fs/src/definitions/circuits.rs | 32 ++++- crates/fs/src/definitions/errors.rs | 17 +++ 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, 516 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 6d3dfd2a6..7e904db83 100644 --- a/crates/fs/src/definitions/errors.rs +++ b/crates/fs/src/definitions/errors.rs @@ -1,25 +1,42 @@ +//! Error definitions for folding schemes. + use ark_relations::gr1cs::SynthesisError; use sonobe_primitives::{ arithmetizations::Error as ArithError, commitments::Error as CommitmentError, }; 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("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 2afff6ec72dced09f2c5bd7cfd4cad38bd1be34d Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 13 Feb 2026 14:48:16 +0800 Subject: [PATCH 067/131] 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 b80f07e0f98496e1081dec578768ca385e01f825 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 04:39:50 +0800 Subject: [PATCH 068/131] 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 db2c7cc5594723e60a92f1779891351d726750fb Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 18:43:27 +0800 Subject: [PATCH 069/131] 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 806b000a724caea7d5bc105f78532d497b024da6 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 10 Oct 2025 19:04:22 +0800 Subject: [PATCH 070/131] 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 92ba93828f9ee8dc209e09d9fd905e1e6999988d Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 24 Oct 2025 22:38:47 +0800 Subject: [PATCH 071/131] 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 110602023dfda7aa8bc877ccd76d57554dd6d7ac Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 25 Oct 2025 23:27:13 +0800 Subject: [PATCH 072/131] 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 b020550c6e17f005ed2e5266c0c894b32cc7b022 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 8 Nov 2025 08:47:34 +0800 Subject: [PATCH 073/131] 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 876cfd79e1e28a712f543d139d6794aa20d3269f Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 17 Nov 2025 07:48:25 +0800 Subject: [PATCH 074/131] 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 27c36e0b23cdb4e959db353b117e36aaf336f802 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 20 Nov 2025 23:32:34 +0800 Subject: [PATCH 075/131] 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 6c82d65ec1bbe62a98295dc1265a493d230b67a2 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 04:41:39 +0800 Subject: [PATCH 076/131] 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 745013cdcb36340c90c4cb929604651d095dacac Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 22 Nov 2025 05:40:41 +0800 Subject: [PATCH 077/131] 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 69d3ad6a981dc7d725146899e6273bb536af93ae Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 24 Nov 2025 03:11:21 +0800 Subject: [PATCH 078/131] 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 46f750e753e6a63e6d1382dbfb9ae86e5dcd00cc Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 25 Nov 2025 00:52:37 +0800 Subject: [PATCH 079/131] 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 2d96c1a23c02c99a3ae9b872223cc80708fd9d89 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 01:57:52 +0800 Subject: [PATCH 080/131] 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 bb6f4fdc3a4f6f04efeb4a0bab20b2e670120aa6 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 6 Feb 2026 18:35:53 +0800 Subject: [PATCH 081/131] 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 f418991ba627045b700b1169655c24ec8567a9a9 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 9 Feb 2026 19:36:40 +0800 Subject: [PATCH 082/131] Add FS & IVC docs --- crates/fs/src/definitions/errors.rs | 2 ++ .../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 ++++- 14 files changed, 89 insertions(+), 5 deletions(-) diff --git a/crates/fs/src/definitions/errors.rs b/crates/fs/src/definitions/errors.rs index 7e904db83..3879aa9b4 100644 --- a/crates/fs/src/definitions/errors.rs +++ b/crates/fs/src/definitions/errors.rs @@ -21,6 +21,8 @@ pub enum Error { /// synthesis. #[error(transparent)] SynthesisError(#[from] SynthesisError), + /// [`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 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 7af1d5b2809ae7b2ea473195983c3eeb25529073 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 19 Feb 2026 13:06:03 +0800 Subject: [PATCH 083/131] Less buggy emulated field equality checks --- .../primitives/src/algebra/field/emulated.rs | 271 ++++++++++++------ crates/primitives/src/algebra/field/mod.rs | 23 +- 2 files changed, 179 insertions(+), 115 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 096a0d1ea..6959c1f26 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -110,13 +110,30 @@ impl Bounds { ) } - /// [`Bounds::shl`] computes the bounds after left-shifting by `shift` bits. + /// [`Bounds::shl`] shifts the bounds left by `shift` bits, i.e., multiplies + /// the bounds by `2^shift`. 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::shr_narrower`] shifts the bounds right by `shift` bits, i.e., + /// divides the bounds by `2^shift` and rounds the lower bound up and the + /// upper bound down, which gives a narrower range. + pub fn shr_narrower(&self, shift: usize) -> Self { + let d = BigInt::from(1u64) << shift; + Self(self.0.div_ceil(&d), self.1.div_floor(&d)) + } + + /// [`Bounds::shr_wider`] shifts the bounds right by `shift` bits, i.e., + /// divides the bounds by `2^shift` and rounds the lower bound down and the + /// upper bound up, which gives a wider range. + pub fn shr_wider(&self, shift: usize) -> Self { + let d = BigInt::from(1u64) << shift; + Self(self.0.div_floor(&d), self.1.div_ceil(&d)) + } + /// [`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 { @@ -200,14 +217,18 @@ impl GR1CSVar fn value(&self) -> Result { 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)), - } + bigint_to_field_element(v).ok_or(SynthesisError::Unsatisfiable) + } +} + +fn bigint_to_field_element(v: BigInt) -> Option { + let (sign, abs) = v.into_parts(); + if abs >= F::MODULUS.into() { + return None; + } + match sign { + Sign::Plus | Sign::NoSign => Some(F::from(abs)), + Sign::Minus => Some(-F::from(abs)), } } @@ -492,22 +513,23 @@ impl LimbedVar, ) -> Result<(), SynthesisError> { - let len = min(self.limbs.len(), other.limbs.len()); + // Equality between `self` and `other` can be reduced to the equality + // between `diff = self - other` and 0. + let diff = self.sub_unaligned(other)?; - 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; + let mut carry_bounds = Bounds::zero(); + let mut group_bounds = Bounds::zero(); + let mut offset = 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_. + // For each limb in `diff`, we first try to group its _bounds_ into + // `group_bounds`. + // If the new bounds do not overflow / underflow, we can safely group + // the _limb_. // // 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. @@ -523,91 +545,52 @@ impl LimbedVar(), - other.bounds[i].shl(step).add(&y_bound).filter_safe::(), - ) { - 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; + // We can further avoid storing `v` by updating the carry on the fly for + // each limb, i.e., `carry = (carry + limb) / 2^W`, until the virtual + // grouped limb `v` is finalized. + for (limb, bounds) in diff.limbs.iter().zip(&diff.bounds) { + if let Some(new_group_bounds) = group_bounds.add(&bounds.shl(offset)).filter_safe::() + { + carry = (carry + limb) * inv; + carry_bounds = carry_bounds.add(bounds).shr_narrower(F::BITS_PER_LIMB); + group_bounds = new_group_bounds; + offset += F::BITS_PER_LIMB; } else { - // New bounds overflow / underflow, meaning the current group is + // New bounds overflow / underflow, i.e., 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; + debug_assert!(carry_bounds.shl(offset).0 >= group_bounds.0); + debug_assert!(carry_bounds.shl(offset).1 <= group_bounds.1); + + // We ensure `carry` is small, i.e., `lb <= carry <= ub`, or + // equivalently, `0 <= carry - lb <= ub - lb`, which can be done + // by ensuring `carry - lb` is a `log2(ub - lb + 1)`-bit number. + (&carry + - bigint_to_field_element::(carry_bounds.0.clone()) + .ok_or(SynthesisError::Unsatisfiable)?) + .enforce_bit_length( + (&carry_bounds.1 - &carry_bounds.0 + BigInt::one()).bits() as usize + )?; + + carry = (carry + limb) * inv; + carry_bounds = carry_bounds.add(bounds).shr_narrower(F::BITS_PER_LIMB); + group_bounds = carry_bounds.clone(); + offset = 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() { - carry.enforce_equal(&FpVar::zero())?; - } else { - // If there is any remaining limb, the first one must be the final - // carry (which will be checked later), and the following ones must - // be zero. - - // Ensure that the final carry equals the remaining limb. - carry.enforce_equal(&remaining_limbs[0])?; - - // 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 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)?; - FpVar::zero().enforce_equal(&remaining_limbs[1..].iter().sum())?; - } + carry.enforce_equal(&FpVar::zero())?; Ok(()) } @@ -1264,13 +1247,111 @@ mod tests { use ark_ff::Field; use ark_pallas::{Fq, Fr}; use ark_relations::gr1cs::ConstraintSystem; - use ark_std::{UniformRand, error::Error, rand::thread_rng}; + use ark_std::{ + UniformRand, + error::Error, + rand::{Rng, 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_eq() -> Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + let zero = LimbedVar::::new(vec![], vec![]); + let zero2 = LimbedVar::::new( + vec![ + FpVar::new_witness(cs.clone(), || { + Ok(Fr::from(BigUint::one() << Fr::BITS_PER_LIMB)) + })?, + FpVar::new_witness(cs.clone(), || Ok(-Fr::one()))?, + ], + vec![ + Bounds( + -(BigInt::one() << (Fr::BITS_PER_LIMB * 2)), + BigInt::one() << (Fr::BITS_PER_LIMB * 2), + ), + Bounds( + -(BigInt::one() << (Fr::BITS_PER_LIMB * 2)), + BigInt::one() << (Fr::BITS_PER_LIMB * 2), + ), + ], + ); + let zero3 = LimbedVar::::new( + vec![ + FpVar::new_witness(cs.clone(), || { + Ok(Fr::from(BigUint::one() << Fr::BITS_PER_LIMB)) + })?, + FpVar::new_witness(cs.clone(), || Ok(-Fr::one()))?, + ], + vec![ + Bounds( + -BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), + BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), + ), + Bounds( + -BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), + BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), + ), + ], + ); + + zero.enforce_equal_unaligned(&zero2)?; + zero.enforce_equal_unaligned(&zero3)?; + + let rng = &mut thread_rng(); + + let n_limbs = 100; + + let coeffs = (0..n_limbs) + .map(|_| if rng.gen_bool(0.5) { + -Fr::one() + } else { + Fr::one() + } * Fr::from(rng.gen_biguint(Fr::BITS_PER_LIMB as u64 * 2 - 1))) + .collect::>(); + let unaligned = LimbedVar::::new( + Vec::new_witness(cs.clone(), || Ok(&coeffs[..]))?, + vec![ + Bounds( + -(BigInt::one() << (Fr::BITS_PER_LIMB * 2)), + BigInt::one() << (Fr::BITS_PER_LIMB * 2), + ); + n_limbs + ], + ); + + let aligned = EmulatedIntVar::new_witness(cs.clone(), || { + let v = compose(&coeffs[..]); + Ok(( + v, + Bounds( + BigInt::one() - (BigInt::one() << (Fr::BITS_PER_LIMB * 2 * n_limbs)), + (BigInt::one() << (Fr::BITS_PER_LIMB * 2 * n_limbs)) - BigInt::one(), + ), + )) + })?; + aligned.enforce_equal_unaligned(&unaligned)?; + + assert!(cs.is_satisfied()?); + + let mut unaligned_incorrect = unaligned.clone(); + unaligned_incorrect.limbs[0] = if coeffs[0].is_zero() { + FpVar::new_witness(cs.clone(), || Ok(Fr::one()))? + } else { + FpVar::new_witness(cs.clone(), || Ok(-coeffs[0]))? + }; + aligned.enforce_equal_unaligned(&unaligned_incorrect)?; + + assert!(!cs.is_satisfied()?); + + Ok(()) + } + #[test] fn test_alloc() -> Result<(), Box> { let rng = &mut thread_rng(); diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 8538cae77..51bcd4404 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -28,30 +28,13 @@ pub trait SonobeField: { /// [`SonobeField::BITS_PER_LIMB`] defines the bit length of each limb when /// representing field elements as limbs in an emulated field variable. + // TODO: either make it configurable, or compute an optimal value based on + // the modulus size. 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 `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 `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 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. - // TODO: make this configurable - const BITS_PER_LIMB: usize = 55; + const BITS_PER_LIMB: usize = 32; } impl, const N: usize> Val for Fp { From ef739d8e911083afe2bf4f0b3a055ae7cb7a7c96 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 19 Feb 2026 16:50:29 +0800 Subject: [PATCH 084/131] Fix bound limits --- .../primitives/src/algebra/field/emulated.rs | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 6959c1f26..aecbb5493 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -137,10 +137,10 @@ impl Bounds { /// [`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`. + // We restrict variables to be within a window of size `(|F| + 1) / 2`, + // and the window to be within `[-(|F| - 1) / 2, (|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) + (self.0 >= -&limit && self.1 <= limit && &self.1 - &self.0 <= limit).then_some(self) } } @@ -278,21 +278,19 @@ impl LimbedVar { /// [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(); - // 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::>(); + let delta = other.sub_unaligned(self)?; + let len = delta.limbs.len(); + + // If `delta` has no limb, the difference between `self` and `other` is + // zero, and thus `self < other` does not hold. + if len == 0 { + return Err(SynthesisError::Unsatisfiable); + } // `helper` is a vector of booleans that indicates if the corresponding // limb of `delta` is the first (searching from MSB) positive limb. @@ -302,11 +300,11 @@ impl LimbedVar { // 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 cs = delta.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 { + let limb = delta.limbs[i].value().unwrap_or_default().into_bigint(); + if !limb.is_zero() && limb <= F::MODULUS_MINUS_ONE_DIV_TWO { helper[i] = true; break; } @@ -320,7 +318,7 @@ impl LimbedVar { // 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) { + for (b, d) in helper.into_iter().zip(delta.limbs) { // Choose the limb `d` only if `b` is true. p += b.select(&d, &FpVar::zero())?; // Either `r` or `d` should be zero. @@ -341,11 +339,31 @@ impl LimbedVar { // 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`. + + // Ensure that `p` is positive, i.e., `1 <= p <= (|F| - 1) / 2`. // 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)?; + // To this end, we check `0 <= p - 1 <= 2^x - 1`, where `2^x` should + // satisfy `max_ub <= 2^x <= (|F| - 1) / 2`. + // Hence, we compute `x` as the ceiling of `log2(max_ub)`, so the left + // inequality holds, and the right inequality also holds because: + // - `max_ub` is the upper bound of a limb in `delta` + // - `delta` is the difference between two aligned `LimbedVar`s, whose + // limbs have at most `F::BITS_PER_LIMB` bits, which is much smaller + // than the field capacity + // Thus, `log2(max_ub)` is at most `F::BITS_PER_LIMB + 1`, from which we + // can conclude `2^x << (|F| - 1) / 2`. + + // `unwrap` is safe here because `None` can only happen when `delta` has + // no limbs, which is already handled at the beginning of the function. + let max_ub = delta.bounds.iter().map(|b| &b.1).max().unwrap(); + if !max_ub.is_positive() { + // If the maximum upper bound of `delta`'s limbs is non-positive, + // then all limbs in `delta` are non-positive, violating the + // requirement of `self < other`. + return Err(SynthesisError::Unsatisfiable); + } + (p - FpVar::one()).enforce_bit_length(max_ub.bits() as usize)?; Ok(()) } @@ -1290,12 +1308,12 @@ mod tests { ], vec![ Bounds( - -BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), + BigInt::zero(), BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), ), Bounds( -BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), - BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()), + BigInt::zero(), ), ], ); From e1f0c67ea2394c481868444ceb942f3012bbfc98 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 19 Feb 2026 19:37:30 +0800 Subject: [PATCH 085/131] Further clean up Nova FS and IVC --- crates/fs/src/definitions/utils.rs | 45 ++++ .../fs/src/nova/algorithms/key_generator.rs | 25 +- crates/fs/src/nova/algorithms/preprocessor.rs | 18 +- crates/fs/src/nova/algorithms/prover.rs | 234 +++++++----------- crates/fs/src/nova/algorithms/verifier.rs | 62 +---- crates/fs/src/nova/circuits/verifier.rs | 30 +-- crates/fs/src/nova/keys/mod.rs | 148 +++++++++++ crates/fs/src/nova/mod.rs | 214 +--------------- .../src/compilers/cyclefold/adapters/nova.rs | 17 +- .../ivc/src/compilers/cyclefold/circuits.rs | 40 +-- crates/ivc/src/compilers/cyclefold/mod.rs | 18 +- crates/primitives/src/algebra/field/mod.rs | 29 ++- crates/primitives/src/algebra/group/mod.rs | 13 +- crates/primitives/src/circuits/mod.rs | 13 + 14 files changed, 377 insertions(+), 529 deletions(-) create mode 100644 crates/fs/src/nova/keys/mod.rs diff --git a/crates/fs/src/definitions/utils.rs b/crates/fs/src/definitions/utils.rs index f4d27acb8..d7a69ec43 100644 --- a/crates/fs/src/definitions/utils.rs +++ b/crates/fs/src/definitions/utils.rs @@ -12,6 +12,13 @@ use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{ borrow::Borrow, ops::{Deref, DerefMut}, + slice::Iter, + vec::IntoIter, +}; +use rayon::{ + iter::{IntoParallelIterator, IntoParallelRefIterator}, + slice::Iter as RayonIter, + vec::IntoIter as RayonIntoIter, }; use sonobe_primitives::transcripts::{Absorbable, AbsorbableVar}; @@ -43,6 +50,44 @@ impl From> for TaggedVec { } } +impl IntoIterator for TaggedVec { + type Item = V; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, V, const TAG: char> IntoIterator for &'a TaggedVec { + type Item = &'a V; + type IntoIter = Iter<'a, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl IntoParallelIterator for TaggedVec { + type Item = V; + + type Iter = RayonIntoIter; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +impl<'a, V: Sync, const TAG: char> IntoParallelIterator for &'a TaggedVec { + type Iter = RayonIter<'a, V>; + + type Item = &'a V; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter() + } +} + impl From> for Vec { fn from(val: TaggedVec) -> Self { val.0 diff --git a/crates/fs/src/nova/algorithms/key_generator.rs b/crates/fs/src/nova/algorithms/key_generator.rs index 89104470e..38e41ba82 100644 --- a/crates/fs/src/nova/algorithms/key_generator.rs +++ b/crates/fs/src/nova/algorithms/key_generator.rs @@ -7,29 +7,10 @@ use sonobe_primitives::{ 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 }) - } -} +use crate::{Error, FoldingSchemeKeyGenerator, nova::AbstractNova}; -impl - FoldingSchemeKeyGenerator for AbstractNova2 +impl FoldingSchemeKeyGenerator + for AbstractNova { fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result { let ck = Arc::new(ck); diff --git a/crates/fs/src/nova/algorithms/preprocessor.rs b/crates/fs/src/nova/algorithms/preprocessor.rs index 316fbbec6..3d71ca64b 100644 --- a/crates/fs/src/nova/algorithms/preprocessor.rs +++ b/crates/fs/src/nova/algorithms/preprocessor.rs @@ -3,22 +3,10 @@ use ark_std::rand::RngCore; use sonobe_primitives::{commitments::GroupBasedCommitment, traits::SonobeField}; -use crate::{ - Error, FoldingSchemePreprocessor, - nova::{AbstractNova, AbstractNova2}, -}; +use crate::{Error, FoldingSchemePreprocessor, nova::AbstractNova}; -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 +impl FoldingSchemePreprocessor + for AbstractNova { fn preprocess(ck_len: usize, mut rng: impl RngCore) -> Result { let ck = CM::generate_key(ck_len, &mut rng)?; diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index de6ec8485..48b8657cc 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -1,21 +1,54 @@ //! Proof generation for Nova. -use ark_ff::One; +use ark_ff::{Field, 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, + algebra::ops::bits::FromBits, + arithmetizations::r1cs::R1CS, + circuits::{Assignments, AssignmentsOwned}, + commitments::GroupBasedCommitment, + traits::SonobeField, + transcripts::Transcript, }; use crate::{ Error, FoldingSchemeProver, - nova::{AbstractNova, AbstractNova2, NovaKey}, + nova::{AbstractNova, NovaKey}, }; -impl - FoldingSchemeProver<1, 1> for AbstractNova +fn cross_term<'a, F: Field>( + arith: &R1CS, + z1: impl Into>, + z2: impl Into>, + #[cfg(feature = "parallel")] e: impl IndexedParallelIterator>, + #[cfg(not(feature = "parallel"))] e: impl Iterator>, +) -> Result, Error> { + let z1 = z1.into(); + let z2 = z2.into(); + + // 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 = arith.evaluate_at(AssignmentsOwned::from(( + z1.constant + z2.constant, + cfg_iter!(z1.public) + .zip(z2.public) + .map(|(a, b)| *a + b) + .collect(), + cfg_iter!(z1.private) + .zip(z2.private) + .map(|(a, b)| *a + b) + .collect(), + )))?; + Ok(cfg_into_iter!(v) + .zip(e) + .map(|(a, b)| a - b.borrow()) + .collect()) +} + +impl FoldingSchemeProver<1, 1> + for AbstractNova { #[allow(non_snake_case)] fn prove( @@ -30,49 +63,32 @@ impl 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 (z1, z2) = ((U.u, &U.x[..], &W.w[..]), (One::one(), &u.x[..], &w.w[..])); + let t = cross_term(&pk.arith, z1, z2, cfg_iter!(W.e))?; 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_bits = transcript.add(&U).add(&u).add(&cm_t).challenge_bits(B); 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(), - )) + let WW = 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, + }; + let UU = 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(), + }; + Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) } } -impl - FoldingSchemeProver<2, 0> for AbstractNova +impl FoldingSchemeProver<2, 0> + for AbstractNova { #[allow(non_snake_case)] fn prove( @@ -87,122 +103,38 @@ impl 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 (z1, z2) = ((U1.u, &U1.x[..], &W1.w[..]), (U2.u, &U2.x[..], &W2.w[..])); + let e = cfg_iter!(W1.e).zip(&W2.e).map(|(a, b)| *a + b); + let t = cross_term(&pk.arith, z1, z2, e)?; 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_bits = transcript.add(&(U1, U2)).add(&cm_t).challenge_bits(B); 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 WW = 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, }; - 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(), - )) + let UU = 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(), + }; + Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) } } diff --git a/crates/fs/src/nova/algorithms/verifier.rs b/crates/fs/src/nova/algorithms/verifier.rs index bfc3f89b9..5892d01d2 100644 --- a/crates/fs/src/nova/algorithms/verifier.rs +++ b/crates/fs/src/nova/algorithms/verifier.rs @@ -8,13 +8,10 @@ use sonobe_primitives::{ transcripts::Transcript, }; -use crate::{ - Error, FoldingSchemeVerifier, - nova::{AbstractNova, AbstractNova2}, -}; +use crate::{Error, FoldingSchemeVerifier, nova::AbstractNova}; -impl - FoldingSchemeVerifier<1, 1> for AbstractNova +impl FoldingSchemeVerifier<1, 1> + for AbstractNova { #[allow(non_snake_case)] fn verify( @@ -26,12 +23,7 @@ impl ) -> 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_bits = transcript.add(&U).add(&u).add(cm_t).challenge_bits(B); let rho = CM::Scalar::from_bits_le(&rho_bits); Ok(Self::RU { @@ -43,8 +35,8 @@ impl } } -impl - FoldingSchemeVerifier<2, 0> for AbstractNova +impl FoldingSchemeVerifier<2, 0> + for AbstractNova { #[allow(non_snake_case)] fn verify( @@ -56,12 +48,7 @@ impl ) -> 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_bits = transcript.add(&(U1, U2)).add(cm_t).challenge_bits(B); let rho = CM::Scalar::from_bits_le(&rho_bits); let rho_squared = rho * rho; @@ -76,38 +63,3 @@ impl }) } } - -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/verifier.rs b/crates/fs/src/nova/circuits/verifier.rs index a69bb0835..000af39a8 100644 --- a/crates/fs/src/nova/circuits/verifier.rs +++ b/crates/fs/src/nova/circuits/verifier.rs @@ -12,8 +12,7 @@ use crate::{ FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, nova::AbstractNovaGadget, }; -impl FoldingSchemePartialVerifierGadget<1, 1> - for AbstractNovaGadget +impl FoldingSchemePartialVerifierGadget<1, 1> for AbstractNovaGadget where CM: CommitmentDefGadget, { @@ -25,12 +24,7 @@ where [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_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; Ok(( @@ -64,8 +58,7 @@ where } } -impl FoldingSchemePartialVerifierGadget<2, 0> - for AbstractNovaGadget +impl FoldingSchemePartialVerifierGadget<2, 0> for AbstractNovaGadget where CM: CommitmentDefGadget, { @@ -77,12 +70,7 @@ where _: [&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_bits = transcript.add(&(U1, U2))?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; Ok(( @@ -119,8 +107,7 @@ where } } -impl FoldingSchemeFullVerifierGadget<1, 1> - for AbstractNovaGadget +impl FoldingSchemeFullVerifierGadget<1, 1> for AbstractNovaGadget where CM: CommitmentDefGadget, CM::CommitmentVar: CurveVar<::Commitment, CM::ConstraintField>, @@ -133,12 +120,7 @@ where [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_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; Ok(Self::RU { diff --git a/crates/fs/src/nova/keys/mod.rs b/crates/fs/src/nova/keys/mod.rs new file mode 100644 index 000000000..c1dbae4cf --- /dev/null +++ b/crates/fs/src/nova/keys/mod.rs @@ -0,0 +1,148 @@ +//! Definitions of Nova keys and trait implementations for relation checks and +//! witness-instance sampling using Nova keys. + +use ark_std::{UniformRand, rand::RngCore, sync::Arc}; +use sonobe_primitives::{ + arithmetizations::{ + Arith, ArithConfig, ArithRelation, + r1cs::{RelaxedInstance, RelaxedWitness}, + }, + circuits::AssignmentsOwned, + commitments::{CommitmentDef, CommitmentOps}, + relations::{Relation, WitnessInstanceSampler}, +}; + +use super::{ + instances::{IncomingInstance as IU, RunningInstance as RU}, + witnesses::{IncomingWitness as IW, RunningWitness as RW}, +}; +use crate::{DeciderKey, Error, PlainInstance as PU, PlainWitness as PW}; + +/// [`NovaKey`] is Nova's decider key. +#[derive(Clone)] +pub struct NovaKey { + pub(super) arith: Arc, + pub(super) 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 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; + + 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, 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< + 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 })) + } +} diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index e9988cf2d..31af9b856 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -4,15 +4,10 @@ //! [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 ark_std::marker::PhantomData; use sonobe_primitives::{ - arithmetizations::{ - Arith, ArithConfig, ArithRelation, - r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, - }, - circuits::AssignmentsOwned, - commitments::{CommitmentDef, CommitmentDefGadget, CommitmentOps, GroupBasedCommitment}, - relations::{Relation, WitnessInstanceSampler}, + arithmetizations::r1cs::R1CS, + commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, traits::{CF2, SonobeField}, }; @@ -24,144 +19,16 @@ use self::{ witnesses::{IncomingWitness as IW, RunningWitness as RW}, }; use crate::{ - DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, - GroupBasedFoldingSchemeSecondaryDef, PlainInstance as PU, PlainWitness as PW, + FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, + GroupBasedFoldingSchemeSecondaryDef, nova::keys::NovaKey, }; pub mod algorithms; pub mod circuits; pub mod instances; +pub mod keys; pub mod witnesses; -/// [`NovaKey`] is Nova's decider key. -#[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 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; - - 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, 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< - 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 @@ -200,48 +67,6 @@ impl Fol type Proof = CM::Commitment; } -// 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. -/// [`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>; - -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); -} - /// [`AbstractNovaGadget`] is the in-circuit gadget for [`AbstractNova`]. pub struct AbstractNovaGadget { _vc: PhantomData, @@ -278,7 +103,10 @@ impl GroupBasedFoldingSch mod tests { use ark_bn254::{Fq, Fr, G1Projective}; use ark_ff::UniformRand; - use ark_std::{error::Error, rand::thread_rng}; + use ark_std::{ + error::Error, + rand::{RngCore, thread_rng}, + }; use sonobe_primitives::{ circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, commitments::pedersen::Pedersen, @@ -336,28 +164,6 @@ 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(()) } diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index dec49d0d6..83b42f932 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -16,6 +16,7 @@ use sonobe_primitives::{ group::emulated::EmulatedAffineVar, ops::bits::{FromBits, FromBitsGadget, ToBitsGadgetExt}, }, + circuits::WitnessToPublic, commitments::GroupBasedCommitment, traits::{CF2, SonobeCurve}, }; @@ -49,11 +50,9 @@ impl CycleFoldCircuit> 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)?; - } + points.mark_as_public()?; - Self::mark_point_as_public(&(points[1].scalar_mul_le(rho_bits.iter())? + &points[0])) + (points[1].scalar_mul_le(rho_bits.iter())? + &points[0]).mark_as_public() } } @@ -167,7 +166,7 @@ impl FoldingSchemeCycleFo &rho_bits, Bounds(Zero::zero(), CF2::::MODULUS.into().into()), )?; - let x = + let cm_tmp = 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_bits_le(&rho_bits); @@ -176,13 +175,17 @@ impl FoldingSchemeCycleFo Ok(vec![ once(rho.clone()) .chain( - [proof, U2.cm_e, x.clone()] + [proof, U2.cm_e, cm_tmp.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])) + .chain( + [U1.cm_e, cm_tmp, UU.cm_e] + .into_iter() + .flat_map(|p| [p.x, p.y]), + ) .collect(), once(rho) .chain( diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 18ef8cd04..7593b72ec 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -4,8 +4,6 @@ use ark_ff::PrimeField; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, - convert::ToConstraintFieldGadget, - eq::EqGadget, fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; @@ -15,7 +13,7 @@ use sonobe_fs::{ }; use sonobe_primitives::{ arithmetizations::Arith, - circuits::FCircuit, + circuits::{FCircuit, WitnessToPublic}, commitments::CommitmentDef, traits::{Dummy, SonobeCurve}, transcripts::{Transcript, TranscriptGadget}, @@ -161,18 +159,7 @@ where .add(&actual_UU)? .add(&actual_cf_UU)? .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`. - // 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(&FpVar::new_input(cs.clone(), || { - Ok(uu_x.value().unwrap_or_default()) - })?)?; + uu_x.mark_as_public()?; if cs.is_in_setup_mode() { Ok((self.step_circuit.dummy_state(), external_outputs)) @@ -229,29 +216,6 @@ where /// 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: &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 - // 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(), || x.value())?.enforce_equal(x)?; - } - Ok(()) - } - /// [`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 diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 2a42bf0ba..cc71373fe 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -238,20 +238,12 @@ where let arith1_config = dk1.to_arith_config(); let arith2_config = dk2.to_arith_config(); - let augmented_circuit = AugmentedCircuit:: { - hash_config: hash_config.clone(), - arith1_config, - arith2_config, - step_circuit, - }; - let mut WW = Dummy::dummy(arith1_config); - let mut UU = Dummy::dummy(arith1_config); + let (mut WW, mut UU) = (Dummy::dummy(arith1_config), 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); + let (mut cf_UU, mut cf_WW) = (Dummy::dummy(arith2_config), Dummy::dummy(arith2_config)); if i != 0 { let challenge; @@ -287,6 +279,12 @@ where let cs = AssignmentsExtractor::new(); let (next_state, external_outputs) = cs.execute_fn(|cs| { + let augmented_circuit = AugmentedCircuit:: { + hash_config: hash_config.clone(), + arith1_config, + arith2_config, + step_circuit, + }; augmented_circuit.compute_next_state( cs, *pp_hash, diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 51bcd4404..0ef0532c4 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -2,7 +2,12 @@ //! counterparts, along with some common implementations. use ark_ff::{BigInteger, Fp, FpConfig, PrimeField}; -use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; +use ark_r1cs_std::{ + GR1CSVar, + alloc::AllocVar, + eq::EqGadget, + fields::{FieldVar, fp::FpVar}, +}; use ark_relations::gr1cs::SynthesisError; use ark_std::{ any::TypeId, @@ -12,6 +17,7 @@ use ark_std::{ use crate::{ algebra::{Val, field::emulated::EmulatedFieldVar}, + circuits::WitnessToPublic, traits::{Inputize, InputizeEmulated}, transcripts::{Absorbable, AbsorbableVar}, }; @@ -24,7 +30,10 @@ pub trait SonobeField: PrimeField + Absorbable + Inputize - + Val, EmulatedVar = EmulatedFieldVar> + + Val< + Var: FieldVar + WitnessToPublic, + EmulatedVar = EmulatedFieldVar, + > { /// [`SonobeField::BITS_PER_LIMB`] defines the bit length of each limb when /// representing field elements as limbs in an emulated field variable. @@ -90,6 +99,22 @@ impl InputizeEmulated for P { } } +impl WitnessToPublic for FpVar { + fn mark_as_public(&self) -> Result<(), SynthesisError> { + // This line "converts" `x` from a witness to a public input. + // Instead of directly modifying the constraint system, we allocate a + // public input variable explicitly and enforce that its value is indeed + // `x`. + // While seemingly redundant, comparing `x` with itself 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 public + // inputs that are not the honest `x` computed in-circuit. + self.enforce_equal(&FpVar::new_input(self.cs(), || self.value())?) + } +} + /// [`TwoStageFieldVar`] abstracts over field variables that support a /// two-stage arithmetic model. /// diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index da37399c5..e116799e3 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -15,6 +15,7 @@ use ark_relations::gr1cs::SynthesisError; use crate::{ algebra::{Val, field::SonobeField, group::emulated::EmulatedAffineVar}, + circuits::WitnessToPublic, traits::{Dummy, Inputize, InputizeEmulated}, transcripts::{Absorbable, AbsorbableVar}, }; @@ -34,7 +35,7 @@ pub trait SonobeCurve: + Inputize + InputizeEmulated + Val< - Var: CurveVar + AbsorbableVar, + Var: CurveVar + AbsorbableVar + WitnessToPublic, EmulatedVar = EmulatedAffineVar, > { @@ -104,3 +105,13 @@ impl> [x, y].inputize_emulated() } } + +impl> WitnessToPublic + for ProjectiveVar> +{ + fn mark_as_public(&self) -> Result<(), SynthesisError> { + // We only need the x and y coordinates of the point, but the `infinity` + // flag is not necessary. + self.to_constraint_field()?[..2].mark_as_public() + } +} diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index ef7ce03cd..7732a0456 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -264,3 +264,16 @@ impl AssignmentsExtractor { Ok((F::one(), instance, witness).into()) } } + +/// [`WitnessToPublic`] defines a helper trait for marking witness variables as +/// public inputs in the constraint system. +pub trait WitnessToPublic { + /// [`WitnessToPublic::mark_as_public`] marks a witness variable as public. + fn mark_as_public(&self) -> Result<(), SynthesisError>; +} + +impl WitnessToPublic for [T] { + fn mark_as_public(&self) -> Result<(), SynthesisError> { + self.iter().try_for_each(|x| x.mark_as_public()) + } +} From 19c220fea449e361173c67e24f8b694a314ac170 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 19 Feb 2026 23:44:53 +0800 Subject: [PATCH 086/131] Compute digest of public parameters for strong Fiat-Shamir --- crates/fs/src/definitions/keys.rs | 3 +- crates/fs/src/nova/keys/mod.rs | 9 ++-- crates/ivc/Cargo.toml | 2 + crates/ivc/src/compilers/cyclefold/mod.rs | 42 +++++++++++++++++-- crates/ivc/src/lib.rs | 4 ++ .../src/arithmetizations/ccs/mod.rs | 7 ++-- crates/primitives/src/arithmetizations/mod.rs | 3 +- .../src/arithmetizations/r1cs/mod.rs | 5 ++- crates/primitives/src/commitments/mod.rs | 3 +- crates/primitives/src/commitments/pedersen.rs | 3 +- .../primitives/src/transcripts/griffin/mod.rs | 3 +- crates/primitives/src/transcripts/mod.rs | 3 +- 12 files changed, 68 insertions(+), 19 deletions(-) diff --git a/crates/fs/src/definitions/keys.rs b/crates/fs/src/definitions/keys.rs index a71d7f0b7..fc843568e 100644 --- a/crates/fs/src/definitions/keys.rs +++ b/crates/fs/src/definitions/keys.rs @@ -1,10 +1,11 @@ //! Traits and abstractions for folding scheme keys. +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; 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 { +pub trait DeciderKey: CanonicalSerialize + CanonicalDeserialize { /// [`DeciderKey::ProverKey`] is the type of the prover key contained in the /// decider key. type ProverKey; diff --git a/crates/fs/src/nova/keys/mod.rs b/crates/fs/src/nova/keys/mod.rs index c1dbae4cf..ca70224d6 100644 --- a/crates/fs/src/nova/keys/mod.rs +++ b/crates/fs/src/nova/keys/mod.rs @@ -1,6 +1,7 @@ //! Definitions of Nova keys and trait implementations for relation checks and //! witness-instance sampling using Nova keys. +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{UniformRand, rand::RngCore, sync::Arc}; use sonobe_primitives::{ arithmetizations::{ @@ -19,8 +20,8 @@ use super::{ use crate::{DeciderKey, Error, PlainInstance as PU, PlainWitness as PW}; /// [`NovaKey`] is Nova's decider key. -#[derive(Clone)] -pub struct NovaKey { +#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct NovaKey { pub(super) arith: Arc, pub(super) ck: Arc, } @@ -88,7 +89,7 @@ where } } -impl WitnessInstanceSampler, IU> for NovaKey { +impl WitnessInstanceSampler, IU> for NovaKey { type Source = AssignmentsOwned; type Error = Error; @@ -99,7 +100,7 @@ impl WitnessInstanceSampler, IU> for NovaKey WitnessInstanceSampler, PU> +impl WitnessInstanceSampler, PU> for NovaKey { type Source = AssignmentsOwned; diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index a57dda5d7..6a88b7f0e 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -11,8 +11,10 @@ ark-ec = { workspace = true } ark-ff = { workspace = true, features = ["asm"] } ark-r1cs-std = { workspace = true } ark-relations = { workspace = true } +ark-serialize = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } num-bigint = { workspace = true, features = ["rand"] } +sha3 = { workspace = true } thiserror = { workspace = true } sonobe-primitives = { workspace = true } diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index cc71373fe..bc1093295 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -1,12 +1,25 @@ -//! Implementation of the CycleFold-based IVC compiler. +//! Implementation of the CycleFold-based IVC compiler as described in this +//! [paper]. //! //! 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. +//! +//! [paper]: https://eprint.iacr.org/2023/1192.pdf -use ark_ff::Zero; +use ark_ff::field_hashers::hash_to_field; use ark_relations::gr1cs::{ConstraintSystem, SynthesisError}; -use ark_std::{borrow::Borrow, marker::PhantomData, rand::RngCore}; +use ark_serialize::CanonicalSerialize; +use ark_std::{ + borrow::Borrow, + io::{Error as IoError, Write}, + marker::PhantomData, + rand::RngCore, +}; +use sha3::{ + Shake128, + digest::{ExtendableOutput, Update}, +}; use sonobe_fs::{ DeciderKey, FoldingInstance, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, @@ -214,7 +227,28 @@ where let dk1 = FS1::generate_keys(pp1, arith1)?; let dk2 = FS2::generate_keys(pp2, arith2)?; - let pp_hash = Zero::zero(); // TODO + struct HashMarshaller<'a>(&'a mut Shake128); + + impl Write for HashMarshaller<'_> { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.0.update(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> Result<(), IoError> { + Ok(()) + } + } + + let pp_hash = { + let mut shake = Shake128::default(); + dk1.serialize_compressed(HashMarshaller(&mut shake))?; + dk2.serialize_compressed(HashMarshaller(&mut shake))?; + hash_config.serialize_compressed(HashMarshaller(&mut shake))?; + hash_to_field::<_, _, 128>(&mut shake.finalize_xof()) + }; Ok(( Key(dk1.clone(), dk2.clone(), (hash_config.clone(), pp_hash)), diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 790b4f534..de8a4dba1 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -8,6 +8,7 @@ use ark_ff::PrimeField; use ark_relations::gr1cs::SynthesisError; +use ark_serialize::SerializationError; use ark_std::rand::RngCore; use sonobe_fs::Error as FoldingError; use sonobe_primitives::{arithmetizations::Error as ArithError, circuits::FCircuit, traits::Dummy}; @@ -22,6 +23,9 @@ pub enum Error { /// system. #[error(transparent)] ArithError(#[from] ArithError), + /// [`Error::SerializationError`] indicates an error during serialization. + #[error(transparent)] + SerializationError(#[from] SerializationError), /// [`Error::FoldingError`] indicates an error from the underlying folding /// scheme. #[error(transparent)] diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index cebed30fe..c9f9a0d12 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -22,6 +22,7 @@ use ark_ff::Field; use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, fmt::Debug, marker::PhantomData}; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -37,7 +38,7 @@ pub mod circuits; /// [`CCSVariant`] defines the methods that a CCS variant (e.g., R1CS) should /// implement. -pub trait CCSVariant: Clone + Debug + PartialEq + Default + Sync { +pub trait CCSVariant: Clone + Debug + PartialEq + Default + Sync + Send { /// [`CCSVariant::n_matrices`] returns the number of matrices in the CCS /// variant. fn n_matrices() -> usize; @@ -56,7 +57,7 @@ pub trait CCSVariant: Clone + Debug + PartialEq + Default + Sync { /// [`CCSConfig`] stores the shape parameters of a CCS structure. #[allow(non_snake_case)] -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CCSConfig { _v: PhantomData, /// m: number of rows in M_i (such that M_i \in F^{m, n}) @@ -114,7 +115,7 @@ impl From<&ConstraintSystem> for CCSConfig { /// [`CCS`] holds the CCS matrices `M` together with the configuration. #[allow(non_snake_case)] -#[derive(Clone)] +#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct CCS { cfg: CCSConfig, diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 6fc4170e8..c0e2457e1 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -7,6 +7,7 @@ //! R1CS is the only supported constraint system by ark-relations. use ark_relations::gr1cs::SynthesisError; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{fmt::Debug, log2}; use thiserror::Error; @@ -64,7 +65,7 @@ pub trait ArithConfig: Clone + Debug + Default + PartialEq { /// 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 { +pub trait Arith: Clone + Default + Send + Sync + CanonicalSerialize + CanonicalDeserialize { /// [`Arith::Config`] specifies the arithmetization's configuration. type Config: ArithConfig; diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 54be091c0..9e818e405 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -3,6 +3,7 @@ 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::*; @@ -16,7 +17,7 @@ use crate::{ pub mod circuits; /// [`R1CSConfig`] stores the shape parameters of an R1CS structure. -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct R1CSConfig { m: usize, // number of constraints n: usize, // number of variables @@ -96,7 +97,7 @@ 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)] +#[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct R1CS { cfg: R1CSConfig, pub(super) A: Matrix, diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index c2d676230..92dbd33bc 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -3,6 +3,7 @@ use ark_ff::UniformRand; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, fields::fp::FpVar, select::CondSelectGadget}; use ark_relations::gr1cs::SynthesisError; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{ fmt::Debug, iter::Sum, @@ -42,7 +43,7 @@ pub enum Error { /// [`CommitmentKey`] represents a commitment key (e.g., a vector of group /// generators for many group-based commitment schemes). -pub trait CommitmentKey: Clone { +pub trait CommitmentKey: Clone + Send + Sync + CanonicalSerialize + CanonicalDeserialize { /// [`CommitmentKey::max_scalars_len`] returns the maximum number of scalars /// that can be committed to with this key. fn max_scalars_len(&self) -> usize; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 7d5c6c295..018777e94 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -9,6 +9,7 @@ use ark_r1cs_std::{ boolean::Boolean, convert::ToBitsGadget, eq::EqGadget, fields::fp::FpVar, groups::CurveVar, }; use ark_relations::gr1cs::SynthesisError; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{UniformRand, iter::repeat_with, marker::PhantomData, rand::RngCore}; use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error}; @@ -21,7 +22,7 @@ use crate::{ /// [`PedersenKey`] stores the public parameters for the Pedersen commitment /// scheme, where `H` controls whether the scheme is hiding or not. -#[derive(Clone)] +#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct PedersenKey { g: Vec, h: C, diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index dbf1c7b64..7f429d6ed 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -45,6 +45,7 @@ use ark_r1cs_std::{ fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::SynthesisError; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use num_bigint::BigUint; use sha3::{ Shake128, Shake128Reader, @@ -57,7 +58,7 @@ pub mod sponge; /// 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)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct GriffinParams { round_constants: Vec>, t: usize, diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 1ddf1c29a..7b00c8855 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -10,6 +10,7 @@ use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; pub use self::absorbable::{Absorbable, AbsorbableVar}; @@ -24,7 +25,7 @@ pub mod poseidon; pub trait Transcript: Clone { /// [`Transcript::Config`] is the configuration for the underlying hash /// function of the transcript. - type Config: Clone; + type Config: Clone + CanonicalSerialize + CanonicalDeserialize; /// [`Transcript::Gadget`] is the in-circuit gadget corresponding to this /// widget. From 6795b899f063cafe5363034e96b98c47b6863a3c Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 19 Feb 2026 23:54:12 +0800 Subject: [PATCH 087/131] Use `hash_to_field` for griffin param generation as well --- .../primitives/src/transcripts/griffin/mod.rs | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index 7f429d6ed..d3f3dad9b 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -38,7 +38,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -use ark_ff::{LegendreSymbol, PrimeField}; +use ark_ff::{LegendreSymbol, PrimeField, field_hashers::hash_to_field}; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, @@ -121,31 +121,16 @@ 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()) + .map(|_| (0..t).map(|_| hash_to_field::<_, _, 128>(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]; - + fn hash_to_non_zero_field(reader: &mut impl XofReader) -> F { loop { - reader.read(&mut buf); - if let Some(element) = F::from_random_bytes(&buf) - && !element.is_zero() - { + let element = hash_to_field::(reader); + if !element.is_zero() { return element; } } @@ -155,11 +140,11 @@ impl GriffinParams { // random alpha/beta loop { - let alpha = field_element_from_shake_without_0::(shake); - let mut beta = field_element_from_shake_without_0::(shake); + let alpha = hash_to_non_zero_field::(shake); + let mut beta = hash_to_non_zero_field::(shake); // distinct while alpha == beta { - beta = field_element_from_shake_without_0::(shake); + beta = hash_to_non_zero_field::(shake); } let mut symbol = alpha; symbol.square_in_place(); @@ -181,7 +166,7 @@ impl GriffinParams { beta.mul_assign(&F::from((i * i) as u64)); // distinct while alpha == beta { - beta = field_element_from_shake_without_0::(shake); + beta = hash_to_non_zero_field::(shake); } #[cfg(debug_assertions)] From 842b45a8630668afc49054071a4cf3e42fb75f5d Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 21 Feb 2026 15:52:49 +0800 Subject: [PATCH 088/131] Minor improvements to docs --- crates/fs/src/lib.rs | 1 + crates/primitives/src/circuits/mod.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index a347a133a..cf528aa75 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -22,6 +22,7 @@ //! - Proof verification: [`FoldingSchemeVerifier`] //! - `circuits/`: In-circuit (partial / full) gadgets, mainly for verification. //! - `instances/`: Instance types. +//! - `keys/`: Key types. //! - `witnesses/`: Witness types. pub mod definitions; diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 7732a0456..a466876f6 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -96,7 +96,7 @@ pub trait FCircuit { /// /// ### Tips /// - /// - Since this method uses `self`, the implementation store some (fixed) + /// - Since this method uses `self`, the implementation can 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. From 3e061747113a0d59e53f3d47188321d2b181beff Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 17 May 2026 21:36:29 +0800 Subject: [PATCH 089/131] Include `joint_scalar_mul` so we no longer need a fork --- crates/primitives/src/algebra/group/mod.rs | 154 +++++++++++++++++- crates/primitives/src/commitments/mod.rs | 30 +++- crates/primitives/src/commitments/pedersen.rs | 115 +++++++++++-- 3 files changed, 275 insertions(+), 24 deletions(-) diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index e116799e3..c37b3799b 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -5,11 +5,16 @@ use ark_ec::{ AffineRepr, CurveGroup, PrimeGroup, short_weierstrass::{Projective, SWCurveConfig}, }; -use ark_ff::{Field, One, PrimeField, Zero}; +use ark_ff::{AdditiveGroup, Field, One, PrimeField, Zero}; use ark_r1cs_std::{ + GR1CSVar, + boolean::Boolean, convert::ToConstraintFieldGadget, - fields::fp::FpVar, - groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, + fields::{FieldVar, fp::FpVar}, + groups::{ + CurveVar, + curves::short_weierstrass::{ProjectiveVar, non_zero_affine::NonZeroAffineVar}, + }, }; use ark_relations::gr1cs::SynthesisError; @@ -35,7 +40,10 @@ pub trait SonobeCurve: + Inputize + InputizeEmulated + Val< - Var: CurveVar + AbsorbableVar + WitnessToPublic, + Var: CurveVar + + AbsorbableVar + + WitnessToPublic + + JointScalarMul, EmulatedVar = EmulatedAffineVar, > { @@ -115,3 +123,141 @@ impl> WitnessToPublic self.to_constraint_field()?[..2].mark_as_public() } } + +/// [`JointScalarMul`] extends arkworks' [`CurveVar`] for more efficient MSM +/// operations in-circuit, without relying on a fork. +pub trait JointScalarMul: Sized { + /// [`JointScalarMul::joint_scalar_mul_be`] computes `s1 * self + s2 * p`, + /// where `s1` and `s2` are the scalars represented in big-endian `Boolean`. + /// + /// `self` and `p` are non-zero and `self` ≠ `-p`. + fn joint_scalar_mul_be<'a>( + &self, + p: &Self, + s1: impl Iterator>, + s2: impl Iterator>, + ) -> Result; +} + +impl> JointScalarMul + for ProjectiveVar> +{ + fn joint_scalar_mul_be<'a>( + &self, + p: &Self, + bits1: impl Iterator::BasePrimeField>>, + bits2: impl Iterator::BasePrimeField>>, + ) -> Result { + // prepare bits decomposition + let mut bits1 = bits1.collect::>(); + if bits1.is_empty() { + return Ok(Self::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()) + .collect(); + + let mut bits2 = bits2.collect::>(); + if bits2.is_empty() { + return Ok(Self::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()) + .collect(); + + // precompute points + let aff1 = self.to_affine()?; + let nz_aff1 = NonZeroAffineVar::::new(aff1.x, aff1.y); + + let aff2 = p.to_affine()?; + let nz_aff2 = NonZeroAffineVar::new(aff2.x, aff2.y); + + let mut aff1_neg = NonZeroAffineVar::new(nz_aff1.x.clone(), nz_aff1.y.negate()?); + let mut aff2_neg = NonZeroAffineVar::new(nz_aff2.x.clone(), nz_aff2.y.negate()?); + let acc = nz_aff1.double()?; + + let sum = nz_aff1.add_unchecked(&nz_aff2)?; + let diff = nz_aff1.add_unchecked(&aff2_neg)?; + let NonZeroAffineVar { mut x, mut y, .. } = acc; + + // 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)?; + + let acc = acc.into_projective(); + let mut p = diff; + for _ in 0..bits1.len() - 1 { + p = p.double()?; + } + + // [`ProjectiveVar::add_mixed`] + let (x1, y1, z1) = (&acc.x, &acc.y, &acc.z); + let (x2, y2) = (&p.x, &p.y.negate()?); + let three_b = P::COEFF_B.double() + P::COEFF_B; + + let xx = x1 * x2; // 1 + let yy = y1 * y2; // 2 + let xy_pairs = (x1 + y1) * (x2 + y2) - (&xx + &yy); // 4, 5, 6, 7, 8 + let xz_pairs = x2 * z1 + x1; // 8, 9 + let yz_pairs = y2 * z1 + y1; // 10, 11 + + let bz3_part = &xz_pairs * P::COEFF_A + z1 * three_b; // 12, 13, 14 + + let yy_m_bz3 = &yy - &bz3_part; // 15 + let yy_p_bz3 = &yy + &bz3_part; // 16 + + let azz = z1 * P::COEFF_A; // 20 + let xx3_p_azz = xx.double()? + &xx + &azz; // 18, 19, 22 + + let b3_xz_pairs = (&xx - &azz) * P::COEFF_A + &xz_pairs * three_b; // 21, 23, 24, 25 + + Ok(ProjectiveVar::new( + &yy_m_bz3 * &xy_pairs - &yz_pairs * &b3_xz_pairs, // 28, 29, 30 + &yy_p_bz3 * &yy_m_bz3 + &xx3_p_azz * b3_xz_pairs, // 17, 26, 27 + &yy_p_bz3 * &yz_pairs + xy_pairs * xx3_p_azz, // 31, 32, 33 + )) + } +} diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 92dbd33bc..3d96e5ca5 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -134,7 +134,7 @@ pub trait CommitmentDefGadget: Clone { /// [`CommitmentDefGadget::KeyVar`] is the in-circuit variable type for the /// commitment key. - type KeyVar; + type KeyVar: AllocVar<::Key, Self::ConstraintField>; /// [`CommitmentDefGadget::ScalarVar`] is the in-circuit variable type for /// the scalars being committed to. type ScalarVar: AbsorbableVar @@ -162,7 +162,7 @@ pub trait CommitmentDefGadget: Clone { /// [`CommitmentOpsGadget`] defines algorithms (majorly the opening algorithm) /// for commitment schemes in-circuit. -pub trait CommitmentOpsGadget: CommitmentDefGadget { +pub trait CommitmentOpsGadget: CommitmentDefGadget { /// [`CommitmentOpsGadget::open`] defines the commitment opening gadget /// that matches its out-of-circuit widget [`CommitmentOps::open`]. fn open( @@ -201,6 +201,7 @@ pub trait GroupBasedCommitment: #[cfg(test)] mod tests { use ark_ff::UniformRand; + use ark_relations::gr1cs::ConstraintSystem; use ark_std::error::Error; use super::*; @@ -218,4 +219,29 @@ mod tests { CM::open(&ck, &v, &r, &cm)?; Ok(()) } + + pub fn test_commitment_gadget_correctness( + mut rng: impl RngCore, + len: usize, + ) -> Result<(), Box> { + let v = (0..len) + .map(|_| UniformRand::rand(&mut rng)) + .collect::>(); + + let ck = CM::Widget::generate_key(len, &mut rng)?; + let (cm, r) = CM::Widget::commit(&ck, &v, &mut rng)?; + + let cs = ConstraintSystem::new_ref(); + + let v_var = Vec::new_witness(cs.clone(), || Ok(v))?; + let r_var = AllocVar::new_witness(cs.clone(), || Ok(r))?; + let ck_var = AllocVar::new_constant(cs.clone(), ck)?; + let cm_var = AllocVar::new_witness(cs.clone(), || Ok(cm))?; + + CM::open(&ck_var, &v_var, &r_var, &cm_var)?; + + assert!(cs.is_satisfied()?); + + Ok(()) + } } diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 018777e94..80ce66d59 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -5,16 +5,25 @@ //! where `g` and `h` are generators, `r` is a random scalar, and `` is //! the multi-scalar multiplication of `g` and `v`. +use ark_ec::AffineRepr; use ark_r1cs_std::{ - boolean::Boolean, convert::ToBitsGadget, eq::EqGadget, fields::fp::FpVar, groups::CurveVar, + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, + convert::ToBitsGadget, + eq::EqGadget, + fields::fp::FpVar, + groups::CurveVar, }; -use ark_relations::gr1cs::SynthesisError; +use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{UniformRand, iter::repeat_with, marker::PhantomData, rand::RngCore}; +use ark_std::{UniformRand, borrow::Borrow, iter::repeat_with, marker::PhantomData, rand::RngCore}; use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error}; use crate::{ - algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, + algebra::{ + field::emulated::EmulatedFieldVar, + group::{JointScalarMul, emulated::EmulatedAffineVar}, + }, commitments::{CommitmentOpsGadget, GroupBasedCommitment}, traits::{CF1, CF2, SonobeCurve}, utils::null::Null, @@ -68,6 +77,64 @@ impl PedersenKey { } } +/// [`PedersenKeyVar`] is the in-circuit variable for [`PedersenKey`], whose +/// generators are encoded in the canonical form. +pub struct PedersenKeyVar { + g: Vec, + h: C::Var, +} + +/// [`PedersenEmulatedKeyVar`] is the in-circuit variable for [`PedersenKey`], +/// whose generators are encoded in the emulated form. +pub struct PedersenEmulatedKeyVar { + #[allow(dead_code)] + g: Vec, C>>, + #[allow(dead_code)] + h: EmulatedAffineVar, C>, +} + +impl AllocVar, C::BaseField> + for PedersenKeyVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let PedersenKey { g, h } = v.borrow(); + + Ok(Self { + g: AllocVar::new_variable(cs.clone(), || Ok(&g[..]), mode)?, + h: AllocVar::new_variable(cs.clone(), || Ok(*h), mode)?, + }) + } +} + +impl AllocVar, CF1> + for PedersenEmulatedKeyVar +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let PedersenKey { g, h } = v.borrow(); + + Ok(Self { + g: AllocVar::new_variable( + cs.clone(), + || Ok(g.iter().map(|i| i.into_group()).collect::>()), + mode, + )?, + h: AllocVar::new_variable(cs.clone(), || Ok(*h), mode)?, + }) + } +} + /// [`Pedersen`] defines the out-of-circuit Pedersen widget, where `H` controls /// whether the scheme is hiding or not. #[derive(Clone, Debug, PartialEq, Eq)] @@ -181,13 +248,13 @@ impl PedersenGadget { impl CommitmentOpsGadget for PedersenGadget { fn open( - ck: &Vec, + ck: &PedersenKeyVar, v: &[EmulatedFieldVar, CF1>], _r: &Null, cm: &C::Var, ) -> Result<(), SynthesisError> { Self::msm( - ck, + &ck.g, &v.iter() .map(|i| i.to_bits_le()) .collect::, _>>()?, @@ -198,18 +265,18 @@ impl CommitmentOpsGadget for PedersenGadget { impl CommitmentOpsGadget for PedersenGadget { fn open( - (g, h): &(Vec, C::Var), + ck: &PedersenKeyVar, v: &[EmulatedFieldVar, CF1>], r: &EmulatedFieldVar, CF1>, cm: &C::Var, ) -> Result<(), SynthesisError> { let gv = Self::msm( - g, + &ck.g, &v.iter() .map(|i| i.to_bits_le()) .collect::, _>>()?, )?; - let hr = h.scalar_mul_le(r.to_bits_le()?.iter())?; + let hr = ck.h.scalar_mul_le(r.to_bits_le()?.iter())?; (gv + hr).enforce_equal(cm) } } @@ -226,7 +293,7 @@ pub struct PedersenEmulatedGadget { impl CommitmentDefGadget for PedersenGadget { type ConstraintField = CF2; - type KeyVar = Vec; + type KeyVar = PedersenKeyVar; type ScalarVar = EmulatedFieldVar, CF1>; @@ -240,7 +307,7 @@ impl CommitmentDefGadget for PedersenGadget { impl CommitmentDefGadget for PedersenGadget { type ConstraintField = CF2; - type KeyVar = (Vec, C::Var); + type KeyVar = PedersenKeyVar; type ScalarVar = EmulatedFieldVar, CF1>; @@ -254,7 +321,7 @@ impl CommitmentDefGadget for PedersenGadget { impl CommitmentDefGadget for PedersenEmulatedGadget { type ConstraintField = CF1; - type KeyVar = Vec, C>>; + type KeyVar = PedersenEmulatedKeyVar; type ScalarVar = FpVar>; @@ -268,10 +335,7 @@ impl CommitmentDefGadget for PedersenEmulatedGadget { impl CommitmentDefGadget for PedersenEmulatedGadget { type ConstraintField = CF1; - type KeyVar = ( - Vec, C>>, - EmulatedAffineVar, C>, - ); + type KeyVar = PedersenEmulatedKeyVar; type ScalarVar = FpVar>; @@ -293,7 +357,9 @@ mod tests { use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; - use crate::commitments::tests::test_commitment_correctness; + use crate::commitments::tests::{ + test_commitment_correctness, test_commitment_gadget_correctness, + }; #[test] fn test_pedersen_commitment() -> Result<(), Box> { @@ -306,5 +372,18 @@ mod tests { Ok(()) } - // TODO: add back gadget tests + #[test] + fn test_pedersen_commitment_circuit() -> Result<(), Box> { + let mut rng = thread_rng(); + for i in 0..5 { + let len = rng.gen_range((1 << i)..(1 << (i + 1))); + test_commitment_gadget_correctness::>( + &mut rng, len, + )?; + test_commitment_gadget_correctness::>( + &mut rng, len, + )?; + } + Ok(()) + } } From c36dee76b9ee0d77e2b7bc3cdd812698c7a1efea Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 17 May 2026 21:36:46 +0800 Subject: [PATCH 090/131] Arkworks 0.6 --- Cargo.toml | 50 ++++++++++-------------------------- crates/primitives/Cargo.toml | 10 +++++--- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d1b8c62ac..3cd726c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,46 +22,24 @@ thiserror = { version = "2.0.16" } wasm-bindgen-test = { version = "0.3" } # Arkworks family -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 = { git = "https://github.com/arkworks-rs/algebra", default-features = false } -ark-snark = { git = "https://github.com/arkworks-rs/snark", default-features = false } -ark-std = { git = "https://github.com/arkworks-rs/std", default-features = false } +ark-crypto-primitives = { version = "0.6.0", default-features = false } +ark-ec = { version = "0.6.0", default-features = false } +ark-ff = { version = "0.6.0", default-features = false } +ark-groth16 = { version = "0.6.0", default-features = false } +ark-poly = { version = "0.6.0", default-features = false } +ark-r1cs-std = { version = "0.6.0", default-features = false } +ark-relations = { version = "0.6.0", default-features = false } +ark-serialize = { version = "0.6.0", default-features = false } +ark-snark = { version = "0.6.0", default-features = false } +ark-std = { version = "0.6.0", 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 } +ark-bn254 = { version = "0.6.0", default-features = false } +ark-grumpkin = { version = "0.6.0", default-features = false } +ark-pallas = { version = "0.6.0", default-features = false } +ark-vesta = { version = "0.6.0", default-features = false } # 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 -# 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 diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 15b3e513f..68458cbc0 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -9,12 +9,11 @@ repository.workspace = true 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 } +ark-std = { workspace = true, features = ["getrandom"] } num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } num-traits = { workspace = true } @@ -35,7 +34,12 @@ wasm-bindgen-test = { workspace = true } [features] default = [] parallel = [ - "ark-poly-commit/parallel", + "ark-crypto-primitives/parallel", + "ark-ec/parallel", + "ark-ff/parallel", + "ark-poly/parallel", "ark-relations/parallel", "ark-r1cs-std/parallel", + "ark-serialize/parallel", + "ark-std/parallel", ] \ No newline at end of file From 43305408a288f33c343ed604369141880b963f2d Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 17 May 2026 21:38:09 +0800 Subject: [PATCH 091/131] Don't ignore lock file --- .gitignore | 1 - Cargo.lock | 1557 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1557 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index df0c112d5..71f19e54b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /target -Cargo.lock *.r1cs *.sym diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..89ee22be3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1557 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ark-bn254" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc66f96ebe2a17a499475b4f94791d379817592ef494171586967ffdc6f95db" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-std", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b3409b1846fe459d19c95df039481575ac6d5842ae63858ad75cc31219bfc1" +dependencies = [ + "ahash", + "ark-crypto-primitives-macros", + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-relations", + "ark-serialize", + "ark-snark", + "ark-std", + "blake2", + "blake3", + "derivative", + "digest", + "fnv", + "merlin", + "num-bigint", + "rayon", + "sha2", + "tracing", +] + +[[package]] +name = "ark-crypto-primitives-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8352a2b2aedf6ba2cc38f7520fc51191d518dde96175c729af19f2d059f191c4" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.17.1", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "digest", + "educe", + "num-bigint", + "num-traits", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-grumpkin" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c969a12ceed8881e5c66277dabd3e7a5aa2c72c9370f3f42eca3f2dd33a21a" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-std", +] + +[[package]] +name = "ark-pallas" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26dbfc18163d3313389ce5d515aa675bb694b8ef64744006c5a2c88439c7133" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-std", +] + +[[package]] +name = "ark-poly" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f55af10b672002b8d953e230282c51206842e20e5791a94432219b4201de5c" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.17.1", + "rayon", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291f1c6628bfcac79b0dc2adbe401aa9100e2e96daa971645e0b18fc94de9a98" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-relations", + "ark-std", + "educe", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4c11c797a64b8a23e22bf4e77bf582ac27bb21395e3183a9a506ba2561e9f9" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "foldhash", + "indexmap 2.14.0", + "rayon", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ark-serialize" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest", + "num-bigint", + "rayon", + "serde_with", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-snark" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bdb461d2be9b2bd6f303c79fffc89f5858790a7b4d33257bca3178e2c071fb9" +dependencies = [ + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" +dependencies = [ + "num-traits", + "rand", + "rayon", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "rand", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "time", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "sonobe-fs" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-pallas", + "ark-poly", + "ark-r1cs-std", + "ark-relations", + "ark-serialize", + "ark-std", + "getrandom", + "num-bigint", + "rayon", + "sonobe-primitives", + "thiserror", + "wasm-bindgen-test", +] + +[[package]] +name = "sonobe-ivc" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-grumpkin", + "ark-r1cs-std", + "ark-relations", + "ark-serialize", + "ark-std", + "getrandom", + "num-bigint", + "sha3", + "sonobe-fs", + "sonobe-primitives", + "thiserror", + "wasm-bindgen-test", +] + +[[package]] +name = "sonobe-primitives" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-pallas", + "ark-poly", + "ark-r1cs-std", + "ark-relations", + "ark-serialize", + "ark-std", + "getrandom", + "num-bigint", + "num-integer", + "num-traits", + "rayon", + "sha3", + "thiserror", + "wasm-bindgen-test", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af5ec93229ad9ccd0a545a516dec76dc276613f278f6a91aa6b463d5b33d42d0" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c81b9fef827e575e0e54431736d1baa0d700315d8c62cfef1f61fa3aad0cbeb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" From 3a86594ec6081bdc8050cbaa1fb7389fb8d37c46 Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 17 May 2026 21:41:57 +0800 Subject: [PATCH 092/131] Fix clippy --- crates/primitives/src/algebra/field/emulated.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index aecbb5493..48e73da93 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -1392,7 +1392,7 @@ mod tests { 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()) { + for (lb, ub) in lbs.into_iter().zip(ubs) { let mut v = vec![ lb.clone(), ub.clone(), @@ -1649,7 +1649,7 @@ mod tests { let mut r_var: LimbedVar = EmulatedFieldVar::constant(BigUint::zero().into()).into(); - for (a, b) in a_var.into_iter().zip(b_var.into_iter()) { + for (a, b) in a_var.into_iter().zip(b_var) { r_var = r_var.add_unaligned(&a.mul_unaligned(&b)?)?; } r_var.enforce_congruent(&c_var)?; From 4403e208264f8ce618de31ff18951ced33497028 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:01:19 +0800 Subject: [PATCH 093/131] Include state size in augmented circuit and verifier --- crates/ivc/src/compilers/cyclefold/circuits.rs | 5 +++++ crates/ivc/src/compilers/cyclefold/mod.rs | 5 +++++ crates/primitives/src/circuits/mod.rs | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 7593b72ec..40e39a025 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -7,6 +7,7 @@ use ark_r1cs_std::{ fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; +use ark_serialize::CanonicalSerialize; use sonobe_fs::{ FoldingInstanceVar, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, @@ -88,6 +89,8 @@ where let is_basecase = i.is_zero()?; + let state_size = FpVar::Constant((initial_state.uncompressed_size() as u64).into()); + let initial_state = FC::StateVar::new_witness(cs.clone(), || Ok(initial_state))?; let current_state = FC::StateVar::new_witness(cs.clone(), || Ok(current_state))?; @@ -104,6 +107,7 @@ where // the `i-1`-th step, which is `u.x = H(i, z_0, z_i, U, cf_U)`. let u_x = sponge .clone() + .add(&state_size)? .add(&i)? .add(&initial_state)? .add(¤t_state)? @@ -153,6 +157,7 @@ where // 4. Compute public input `uu.x = H(i+1, z_0, z_{i+1}, UU, cf_UU)`. let uu_x = sponge .clone() + .add(&state_size)? .add(&ii)? .add(&initial_state)? .add(&next_state)? diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index bc1093295..53284c453 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -358,10 +358,15 @@ where .ok_or(Error::IVCVerificationFail); } + if initial_state.uncompressed_size() != current_state.uncompressed_size() { + return Err(Error::IVCVerificationFail); + } + 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(&initial_state.uncompressed_size()) .add(&i) .add(initial_state) .add(current_state) diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index a466876f6..bb0866b64 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -5,6 +5,7 @@ use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, fields::fp::FpVar}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; +use ark_serialize::CanonicalSerialize; use ark_std::{ fmt::Debug, ops::{Deref, Index, IndexMut}, @@ -67,7 +68,7 @@ pub trait FCircuit { /// 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; + type State: Clone + PartialEq + Absorbable + CanonicalSerialize; /// [`FCircuit::StateVar`] is the in-circuit variable type for the state. /// /// If the implementation chooses custom structures for the state, it should From cab289ea14849a9e02c6a400d8655657815a2537 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:01:02 +0800 Subject: [PATCH 094/131] Ensure Poseidon's alpha and `F::MODULUS - 1` are coprime --- crates/primitives/src/transcripts/poseidon/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/primitives/src/transcripts/poseidon/mod.rs b/crates/primitives/src/transcripts/poseidon/mod.rs index 54bdbaff8..3ff563958 100644 --- a/crates/primitives/src/transcripts/poseidon/mod.rs +++ b/crates/primitives/src/transcripts/poseidon/mod.rs @@ -1,7 +1,9 @@ //! Poseidon-based transcript configurations and implementations. use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, find_poseidon_ark_and_mds}; -use ark_ff::PrimeField; +use ark_ff::{One, PrimeField}; +use num_bigint::BigUint; +use num_integer::Integer; pub mod sponge; @@ -14,6 +16,11 @@ pub fn poseidon_custom_config( rate: usize, capacity: usize, ) -> PoseidonConfig { + assert_eq!( + BigUint::from(alpha).gcd(&(-F::one()).into()), + BigUint::one() + ); + let (ark, mds) = find_poseidon_ark_and_mds::( F::MODULUS_BIT_SIZE as u64, rate, From 0e7dae0ea5d2f4085cf0cfbf32318c93060721c0 Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 17 May 2026 22:42:33 +0800 Subject: [PATCH 095/131] Generate Poseidon round numbers in the same way as paper's --- .../src/transcripts/poseidon/mod.rs | 136 ++++++++++++++++-- .../src/transcripts/poseidon/sponge.rs | 12 +- 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/crates/primitives/src/transcripts/poseidon/mod.rs b/crates/primitives/src/transcripts/poseidon/mod.rs index 3ff563958..799915f0e 100644 --- a/crates/primitives/src/transcripts/poseidon/mod.rs +++ b/crates/primitives/src/transcripts/poseidon/mod.rs @@ -7,35 +7,133 @@ use num_integer::Integer; pub mod sponge; -/// [`poseidon_custom_config`] produces a Poseidon configuration with custom -/// parameters. -pub fn poseidon_custom_config( - full_rounds: usize, - partial_rounds: usize, +fn log2_order() -> f64 { + let x = F::MODULUS.into(); + let bits = x.bits(); // bit length + if bits <= 53 { + // Fits in f64 mantissa exactly + let val: u64 = x.try_into().unwrap(); + return (val as f64).log2(); + } + // Shift right so only top ~53 bits remain + let shift = bits - 53; + let top = x >> shift; + let top_u64: u64 = top.try_into().unwrap(); + (top_u64 as f64).log2() + shift as f64 +} + +fn sat_inequiv_alpha(t: usize, r_f: u64, r_p: u64, alpha: u64, m: usize) -> bool { + let log2_p = log2_order::(); + let n = log2_p.ceil() as usize; + let m_f = m as f64; + let n_f = n as f64; + let t_f = t as f64; + let r_p_f = r_p as f64; + let r_f_f = r_f as f64; + let alpha_f = alpha as f64; + let log2_alpha = 2.0f64.ln() / alpha_f.ln(); + + let r_f_1: f64 = if m_f <= (log2_p - (alpha_f - 1.0) / 2.0).floor() * (t_f + 1.0) { + 6.0 + } else { + 10.0 + }; + + let r_f_2 = 1.0 + log2_alpha * m_f.min(n_f) + (t_f.ln() / alpha_f.ln()).ceil() - r_p_f; + + let r_f_3 = 1.0 + log2_alpha * (m_f / 3.0).min(log2_p / 2.0) - r_p_f; + + let r_f_4 = t_f - 1.0 + (log2_alpha * m_f / (t_f + 1.0)).min(log2_alpha * log2_p / 2.0) - r_p_f; + + let r_f_max = r_f_1 + .ceil() + .max(r_f_2.ceil()) + .max(r_f_3.ceil()) + .max(r_f_4.ceil()); + + r_f_f >= r_f_max +} + +fn get_sbox_cost(r_f: u64, r_p: u64, _n: usize, t: usize) -> usize { + t * r_f as usize + r_p as usize +} + +fn find_fd_round_numbers( + t: usize, + alpha: u64, + m: usize, + cost_function: fn(u64, u64, usize, usize) -> usize, + security_margin: bool, +) -> (u64, u64) { + let n = log2_order::().ceil() as usize; + let n_total = n * t; + + let mut r_p: u64 = 0; + let mut r_f: u64 = 0; + let mut min_cost = usize::MAX; + let mut max_cost_rf: u64 = 0; + + for r_p_t in 1u64..500 { + for r_f_t in (4u64..100).step_by(2) { + if !sat_inequiv_alpha::(t, r_f_t, r_p_t, alpha, m) { + continue; + } + + let (r_f_eff, r_p_eff) = if security_margin { + (r_f_t + 2, (r_p_t as f64 * 1.075).ceil() as u64) + } else { + (r_f_t, r_p_t) + }; + + let cost = cost_function(r_f_eff, r_p_eff, n_total, t); + if cost < min_cost || (cost == min_cost && r_f_eff < max_cost_rf) { + r_p = r_p_eff; + r_f = r_f_eff; + min_cost = cost; + max_cost_rf = r_f; + } + } + } + + (r_f, r_p) +} + +/// [`poseidon_paper_config`] produces a Poseidon configuration which agrees +/// with the paper's reference implementation. +/// +/// TODO(@winderica): alpha = -1 is not supported yet. +pub fn poseidon_paper_config( alpha: u64, rate: usize, - capacity: usize, ) -> PoseidonConfig { assert_eq!( BigUint::from(alpha).gcd(&(-F::one()).into()), BigUint::one() ); - + let (full_rounds, partial_rounds) = + find_fd_round_numbers::(rate, alpha, SECURITY_BITS, get_sbox_cost, true); let (ark, mds) = find_poseidon_ark_and_mds::( F::MODULUS_BIT_SIZE as u64, rate, - full_rounds as u64, - partial_rounds as u64, + full_rounds, + partial_rounds, 0, ); - PoseidonConfig::new(full_rounds, partial_rounds, alpha, mds, ark, rate, capacity) + PoseidonConfig::new( + full_rounds as usize, + partial_rounds as usize, + alpha, + mds, + ark, + rate, + 1, + ) } -/// [`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 { +/// [`poseidon_circom_config`] produces a Poseidon configuration which agrees +/// with Circom's Poseidon(4) when `F` is the scalar field of BN254. +pub fn poseidon_circom_config() -> PoseidonConfig { // 120 bit security target as in // https://eprint.iacr.org/2019/458.pdf // t = rate + 1 @@ -45,5 +143,13 @@ pub fn poseidon_canonical_config() -> PoseidonConfig { let alpha = 5; let rate = 4; - poseidon_custom_config(full_rounds, partial_rounds, alpha, rate, 1) + 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, 1) } diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index 89068655b..49cd82250 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -95,13 +95,13 @@ mod tests { use crate::{ algebra::group::emulated::EmulatedAffineVar, - transcripts::{Transcript, TranscriptGadget, poseidon::poseidon_canonical_config}, + transcripts::{Transcript, TranscriptGadget, poseidon::poseidon_circom_config}, }; // 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 config = poseidon_circom_config::(); let mut poseidon_sponge = PoseidonSponge::new(&config); let v = vec![1, 2, 3, 4] .into_iter() @@ -122,7 +122,7 @@ mod tests { #[test] fn test_challenge_field_element() -> Result<(), Box> { // Create a transcript outside of the circuit - let config = poseidon_canonical_config::(); + let config = poseidon_circom_config::(); let mut tr = PoseidonSponge::::new(&config); tr.add(&Fr::from(42_u32)); let c = tr.challenge_field_element(); @@ -145,7 +145,7 @@ mod tests { let nbits = 128; // Create a transcript outside of the circuit - let config = poseidon_canonical_config::(); + let config = poseidon_circom_config::(); let mut tr = PoseidonSponge::::new(&config); tr.add(&Fq::from(42_u32)); let c = tr.challenge_bits(nbits); @@ -166,7 +166,7 @@ mod tests { #[test] fn test_absorb_canonical_point() -> Result<(), Box> { // Create a transcript outside of the circuit - let config = poseidon_canonical_config::(); + let config = poseidon_circom_config::(); let mut tr = PoseidonSponge::::new(&config); let rng = &mut thread_rng(); @@ -190,7 +190,7 @@ mod tests { #[test] fn test_absorb_emulated_point() -> Result<(), Box> { // Create a transcript outside of the circuit - let config = poseidon_canonical_config::(); + let config = poseidon_circom_config::(); let mut tr = PoseidonSponge::::new(&config); let rng = &mut thread_rng(); From f9f64924624bee9dfb9f0a336915322769c5877f Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 21:59:00 +0800 Subject: [PATCH 096/131] Add the missing check of in-circuit state against initial state for i=0 --- crates/ivc/src/compilers/cyclefold/circuits.rs | 7 ++++--- crates/primitives/src/circuits/mod.rs | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 40e39a025..e7098060e 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -2,9 +2,7 @@ use ark_ff::PrimeField; use ark_r1cs_std::{ - GR1CSVar, - alloc::AllocVar, - fields::{FieldVar, fp::FpVar}, + GR1CSVar, alloc::AllocVar, eq::EqGadget, fields::{FieldVar, fp::FpVar} }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_serialize::CanonicalSerialize; @@ -102,6 +100,9 @@ where let cf_U = AllocVar::new_witness(cs.clone(), || Ok(cf_U))?; let cf_proofs = Vec::new_witness(cs.clone(), || Ok(cf_proofs))?; + // 0. Check initial state consistency + initial_state.conditional_enforce_equal(¤t_state, &is_basecase)?; + // 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)`. diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index bb0866b64..d42e0a521 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -1,7 +1,7 @@ //! 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_r1cs_std::{GR1CSVar, alloc::AllocVar, eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; @@ -75,7 +75,8 @@ pub trait FCircuit { /// implement the required traits for the corresponding variable type. type StateVar: GR1CSVar + AllocVar - + AbsorbableVar; + + AbsorbableVar + + EqGadget; /// [`FCircuit::ExternalInputs`] is the type of external inputs provided to /// each step of the circuit. type ExternalInputs; From aee2abc872041a4224d01861f09b70561d923a34 Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 17 May 2026 21:57:55 +0800 Subject: [PATCH 097/131] Format --- crates/ivc/src/compilers/cyclefold/circuits.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index e7098060e..5d6b8f700 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -2,7 +2,10 @@ use ark_ff::PrimeField; use ark_r1cs_std::{ - GR1CSVar, alloc::AllocVar, eq::EqGadget, fields::{FieldVar, fp::FpVar} + GR1CSVar, + alloc::AllocVar, + eq::EqGadget, + fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_serialize::CanonicalSerialize; From ba7683ddcdf0e231042e5799887aaae02c6cf114 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:00:21 +0800 Subject: [PATCH 098/131] Don't enforce equality if `should_enforce` is a constant false --- crates/primitives/src/algebra/field/emulated.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 48e73da93..a07d8e3f7 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -825,7 +825,7 @@ impl EqGadget for LimbedVar { if should_enforce.value()? { return self.enforce_equal(other); } else { - return self.enforce_not_equal(other); + return Ok(()); // No constraint when should_enforce is false } } self.is_eq(other)? From 2c6c4d77a9047e0be9effadfe20cfa9cbca690cf Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:00:21 +0800 Subject: [PATCH 099/131] Fix incorrect bounds of quotient `q` in `enforce_congruent` --- crates/primitives/src/algebra/field/emulated.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index a07d8e3f7..af1b7744b 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -672,7 +672,10 @@ impl let y = compose(other.limbs.value().unwrap_or_default()); Ok(( (x - y).div_floor(&m), - Bounds(self.lbound().div_floor(&m), self.ubound().div_floor(&m)), + Bounds( + (self.lbound() - other.ubound()).div_floor(&m), + (self.ubound() - other.lbound()).div_floor(&m), + ), )) })?; From ccc7c9008a3d406ef1409f37c3414d680cf056b9 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:00:21 +0800 Subject: [PATCH 100/131] Don't override the default impl of `enforce_not_equal` --- crates/primitives/src/algebra/field/emulated.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index af1b7744b..3d49c61c0 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -803,22 +803,6 @@ impl EqGadget for LimbedVar { 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, From 5cac2756d75acb5fccef6b26097cfe78c14c481a Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:00:21 +0800 Subject: [PATCH 101/131] Use `kary_and` in `is_eq` for better efficiency --- crates/primitives/src/algebra/field/emulated.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 3d49c61c0..759d9a480 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -771,20 +771,24 @@ impl TwoStageFieldVar for LimbedVar EqGadget for LimbedVar { 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); } + let mut bits = vec![]; 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])?; + bits.push(self.limbs[i].is_eq(&other.limbs[i])?); + } + if bits.len() == 0 { + Ok(Boolean::TRUE) + } else { + Boolean::kary_and(&bits) } - Ok(result) } fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { From 5049592010f8872ed5ba1da7b7bd9355be04a6b5 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:00:21 +0800 Subject: [PATCH 102/131] Use `G::MODULUS - 1` as the upper bound when allocating a `G` element --- crates/primitives/src/algebra/field/emulated.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 759d9a480..b7f997caa 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -1105,8 +1105,8 @@ impl AllocVar for LimbedVar Date: Sun, 17 May 2026 21:59:03 +0800 Subject: [PATCH 103/131] Fix clippy --- crates/primitives/src/algebra/field/emulated.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index b7f997caa..7c945a1d8 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -784,7 +784,7 @@ impl EqGadget for LimbedVar { } bits.push(self.limbs[i].is_eq(&other.limbs[i])?); } - if bits.len() == 0 { + if bits.is_empty() { Ok(Boolean::TRUE) } else { Boolean::kary_and(&bits) From f9b8232d89d457364295d2a6d60e4425aaac6e3c Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 22:00:40 +0800 Subject: [PATCH 104/131] Fix the issue where chunked values are larger than the field capacity --- crates/primitives/src/transcripts/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 7b00c8855..10e94db7a 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -80,7 +80,7 @@ pub trait Transcript: Clone { input.extend_from_slice(domain); let limbs = input - .chunks(F::MODULUS_BIT_SIZE.div_ceil(8) as usize) + .chunks(F::MODULUS_BIT_SIZE as usize / 8) .map(|chunk| F::from_le_bytes_mod_order(chunk)) .collect::>(); @@ -183,7 +183,7 @@ pub trait TranscriptGadget: Clone { input.extend_from_slice(domain); let limbs = input - .chunks(F::MODULUS_BIT_SIZE.div_ceil(8) as usize) + .chunks(F::MODULUS_BIT_SIZE as usize / 8) .map(|chunk| FpVar::Constant(F::from_le_bytes_mod_order(chunk))) .collect::>(); From 13ead2495ce7e004f293157aafc18c0238a9c07c Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 21:59:30 +0800 Subject: [PATCH 105/131] Fix unsafe `zip`s Strategy: 1. Remove `zip`. 2. If `zip` is inevitable, add explicit length checks and return `Err` early to avoid panics. 3. If cannot return `Err`, replace `zip` with `zip_eq`. --- Cargo.lock | 2 + Cargo.toml | 1 + crates/fs/Cargo.toml | 1 + crates/fs/src/nova/algorithms/prover.rs | 33 ++++++++---- crates/fs/src/nova/algorithms/verifier.rs | 9 +++- crates/fs/src/nova/circuits/verifier.rs | 12 +++++ .../ivc/src/compilers/cyclefold/circuits.rs | 12 ++--- crates/primitives/Cargo.toml | 1 + .../primitives/src/algebra/field/emulated.rs | 54 ++++++++++--------- crates/primitives/src/algebra/ops/eq.rs | 4 ++ crates/primitives/src/algebra/ops/rlc.rs | 7 +-- .../src/arithmetizations/r1cs/mod.rs | 8 +++ .../primitives/src/transcripts/griffin/mod.rs | 17 ++++-- 13 files changed, 110 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89ee22be3..62f596db3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1086,6 +1086,7 @@ dependencies = [ "ark-serialize", "ark-std", "getrandom", + "itertools", "num-bigint", "rayon", "sonobe-primitives", @@ -1130,6 +1131,7 @@ dependencies = [ "ark-serialize", "ark-std", "getrandom", + "itertools", "num-bigint", "num-integer", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 3cd726c60..e47297e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/privacy-scaling-explorations/sonobe/" rust-version = "1.85.1" [workspace.dependencies] +itertools = { version = "0.14.0" } num-bigint = { version = "0.4.3" } num-integer = { version = "0.1" } num-traits = { version = "0.2" } diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 772f4e47d..b955cc775 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 } +itertools = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } thiserror = { workspace = true } rayon = { workspace = true } diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index 48b8657cc..30ccb1513 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -2,6 +2,8 @@ use ark_ff::{Field, One}; use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, ops::Mul, rand::RngCore}; +#[cfg(not(feature = "parallel"))] +use itertools::Itertools; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ @@ -33,16 +35,16 @@ fn cross_term<'a, F: Field>( let v = arith.evaluate_at(AssignmentsOwned::from(( z1.constant + z2.constant, cfg_iter!(z1.public) - .zip(z2.public) + .zip_eq(z2.public) .map(|(a, b)| *a + b) .collect(), cfg_iter!(z1.private) - .zip(z2.private) + .zip_eq(z2.private) .map(|(a, b)| *a + b) .collect(), )))?; Ok(cfg_into_iter!(v) - .zip(e) + .zip_eq(e) .map(|(a, b)| a - b.borrow()) .collect()) } @@ -72,16 +74,25 @@ impl FoldingSchemePro let rho = CM::Scalar::from_bits_le(&rho_bits); let WW = Self::RW { - e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), + e: cfg_iter!(W.e) + .zip_eq(&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(), + w: cfg_iter!(W.w) + .zip_eq(&w.w) + .map(|(a, b)| rho * b + a) + .collect(), r_w: W.r_w + w.r_w * rho, }; let UU = 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(), + x: cfg_iter!(U.x) + .zip_eq(&u.x) + .map(|(a, b)| rho * b + a) + .collect(), }; Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) } @@ -104,7 +115,7 @@ impl FoldingSchemePro let (W2, U2) = (W2.borrow(), U2.borrow()); let (z1, z2) = ((U1.u, &U1.x[..], &W1.w[..]), (U2.u, &U2.x[..], &W2.w[..])); - let e = cfg_iter!(W1.e).zip(&W2.e).map(|(a, b)| *a + b); + let e = cfg_iter!(W1.e).zip_eq(&W2.e).map(|(a, b)| *a + b); let t = cross_term(&pk.arith, z1, z2, e)?; let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; @@ -115,13 +126,13 @@ impl FoldingSchemePro let WW = Self::RW { e: cfg_iter!(W1.e) - .zip(&t) - .zip(&W2.e) + .zip_eq(&t) + .zip_eq(&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) + .zip_eq(&W2.w) .map(|(a, b)| rho * b + a) .collect(), r_w: W1.r_w + W2.r_w * rho, @@ -131,7 +142,7 @@ impl FoldingSchemePro u: U1.u + rho * U2.u, cm_w: U1.cm_w + U2.cm_w.mul(rho), x: cfg_iter!(U1.x) - .zip(&U2.x) + .zip_eq(&U2.x) .map(|(a, b)| rho * b + a) .collect(), }; diff --git a/crates/fs/src/nova/algorithms/verifier.rs b/crates/fs/src/nova/algorithms/verifier.rs index 5892d01d2..8bbda14e4 100644 --- a/crates/fs/src/nova/algorithms/verifier.rs +++ b/crates/fs/src/nova/algorithms/verifier.rs @@ -1,6 +1,8 @@ //! Proof verification for Nova. use ark_std::{borrow::Borrow, cfg_iter, ops::Mul}; +#[cfg(not(feature = "parallel"))] +use itertools::Itertools; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ @@ -30,7 +32,10 @@ impl FoldingSchemeVer 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(), + x: cfg_iter!(U.x) + .zip_eq(&u.x) + .map(|(a, b)| rho * b + a) + .collect(), }) } } @@ -57,7 +62,7 @@ impl FoldingSchemeVer u: U1.u + rho * U2.u, cm_w: U1.cm_w + U2.cm_w.mul(rho), x: cfg_iter!(U1.x) - .zip(&U2.x) + .zip_eq(&U2.x) .map(|(a, b)| rho * b + a) .collect(), }) diff --git a/crates/fs/src/nova/circuits/verifier.rs b/crates/fs/src/nova/circuits/verifier.rs index 000af39a8..54127a51c 100644 --- a/crates/fs/src/nova/circuits/verifier.rs +++ b/crates/fs/src/nova/circuits/verifier.rs @@ -27,6 +27,10 @@ where let rho_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + if U.x.len() != u.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(( Self::RU { u: (U.u.clone() + &rho) @@ -73,6 +77,10 @@ where let rho_bits = transcript.add(&(U1, U2))?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + if U1.x.len() != U2.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(( Self::RU { u: (U2.u.clone() * &rho + &U1.u) @@ -123,6 +131,10 @@ where let rho_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + if U.x.len() != u.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self::RU { u: (U.u.clone() + &rho) .try_into() diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 5d6b8f700..5bbbd649c 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -131,15 +131,15 @@ where 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) - { + let cf_u_xs = FS1::to_cyclefold_inputs([U], [u], UU, proof, rho)?; + if [cf_us.len(), cf_u_xs.len(), cf_proofs.len()] != [FS1::N_CYCLEFOLDS; 3] { + return Err(SynthesisError::Unsatisfiable); + } + let mut cf_UU = cf_U; + for ((cf_u, cf_u_x), cf_proof) in cf_us.iter().zip(cf_u_xs).zip(&cf_proofs) { // 2.b. Construct the incoming instance `cf_u` representing the // corresponding execution of secondary (CycleFold) circuit // with the derived public inputs. diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 68458cbc0..711de1524 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -14,6 +14,7 @@ ark-relations = { workspace = true } ark-r1cs-std = { workspace = true } ark-serialize = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } +itertools = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } num-traits = { workspace = true } diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 7c945a1d8..1e919f2c4 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -1366,24 +1366,29 @@ mod tests { let rng = &mut thread_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) { + let zero = BigInt::zero(); + let max: BigInt = (BigInt::one() << size) - BigInt::one(); + + let mut bounds = vec![(zero.clone(), max.clone())]; + + bounds.push((-&max, zero.clone())); + bounds.push((-&max, max.clone())); + bounds.push((rng.gen_bigint_range(&-&max, &zero), zero.clone())); + bounds.push((zero.clone(), rng.gen_bigint_range(&zero, &max))); + bounds.push(( + rng.gen_bigint_range(&-&max, &zero), + rng.gen_bigint_range(&zero, &max), + )); + bounds.push({ + let lb = rng.gen_bigint_range(&-&max, &zero); + (lb.clone(), rng.gen_bigint_range(&lb, &zero)) + }); + bounds.push({ + let lb = rng.gen_bigint_range(&zero, &max); + (lb.clone(), rng.gen_bigint_range(&lb, &max)) + }); + + for (lb, ub) in bounds { let mut v = vec![ lb.clone(), ub.clone(), @@ -1632,17 +1637,18 @@ mod tests { 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::(); - 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 a_var = Vec::>::new_witness(cs.clone(), || Ok(&a[..]))?; + let b_var = Vec::>::new_witness(cs.clone(), || Ok(&b[..]))?; + let mut c = Fq::zero(); let mut r_var: LimbedVar = EmulatedFieldVar::constant(BigUint::zero().into()).into(); - for (a, b) in a_var.into_iter().zip(b_var) { - r_var = r_var.add_unaligned(&a.mul_unaligned(&b)?)?; + for i in 0..len { + c += a[i] * b[i]; + r_var = r_var.add_unaligned(&a_var[i].mul_unaligned(&b_var[i])?)?; } + let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?; r_var.enforce_congruent(&c_var)?; assert!(cs.is_satisfied()?); diff --git a/crates/primitives/src/algebra/ops/eq.rs b/crates/primitives/src/algebra/ops/eq.rs index 294ba63aa..d6d0da9c3 100644 --- a/crates/primitives/src/algebra/ops/eq.rs +++ b/crates/primitives/src/algebra/ops/eq.rs @@ -25,6 +25,10 @@ impl EquivalenceGadget> for FpVar { impl> EquivalenceGadget<[T]> for [T] { fn enforce_equivalent(&self, other: &[T]) -> Result<(), SynthesisError> { + if self.len() != other.len() { + return Err(SynthesisError::Unsatisfiable); + } + self.iter() .zip(other) .try_for_each(|(a, b)| a.enforce_equivalent(b)) diff --git a/crates/primitives/src/algebra/ops/rlc.rs b/crates/primitives/src/algebra/ops/rlc.rs index b25a23d70..301aa2696 100644 --- a/crates/primitives/src/algebra/ops/rlc.rs +++ b/crates/primitives/src/algebra/ops/rlc.rs @@ -9,6 +9,7 @@ use ark_std::{ iter::Sum, ops::{Add, Mul}, }; +use itertools::Itertools; /// [`ScalarRLC`] computes the random linear combination for a sequence of /// scalars (i.e., each $v_i$ is a scalar). @@ -28,7 +29,7 @@ where type Value = I::Item; fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value { - self.zip(coeffs).map(|(v, c)| v * c).sum::() + self.zip_eq(coeffs).map(|(v, c)| v * c).sum::() } } @@ -53,12 +54,12 @@ where fn slice_rlc(self, coeffs: &[Coeff]) -> Vec { let mut iter = self - .zip(coeffs) + .zip_eq(coeffs) .map(|(v, c)| v.iter().map(|x| x.clone() * c)); let first = iter.next().unwrap(); iter.fold(first.collect(), |acc, v| { - acc.into_iter().zip(v).map(|(a, b)| a + b).collect() + acc.into_iter().zip_eq(v).map(|(a, b)| a + b).collect() }) } } diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 9e818e405..a6e3318de 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -259,6 +259,14 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 _u: &RelaxedInstance<&[F]>, v: Self::Evaluation, ) -> Result<(), Error> { + if w.e.len() != v.len() { + return Err(Error::MalformedAssignments(format!( + "The number of constraints in R1CS ({}) does not match the length of the provided relaxed witness's error term ({}).", + v.len(), + w.e.len() + ))); + } + cfg_iter!(w.e) .zip(&v) .all(|(e, v)| e == v) diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index d3f3dad9b..a97fd7f77 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -46,6 +46,7 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::SynthesisError; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use itertools::Itertools; use num_bigint::BigUint; use sha3::{ Shake128, Shake128Reader, @@ -236,7 +237,10 @@ impl Griffin { input.iter().skip(1).for_each(|el| sum.add_assign(el)); if round < params.rounds - 1 { - for (el, rc) in input.iter_mut().zip(params.round_constants[round].iter()) { + for (el, rc) in input + .iter_mut() + .zip_eq(params.round_constants[round].iter()) + { el.add_assign(&sum); el.add_assign(rc); // add round constant } @@ -277,7 +281,10 @@ impl Griffin { input[3] = t_4; if round < params.rounds - 1 { - for (i, rc) in input.iter_mut().zip(params.round_constants[round].iter()) { + for (i, rc) in input + .iter_mut() + .zip_eq(params.round_constants[round].iter()) + { i.add_assign(rc); } } @@ -468,7 +475,7 @@ impl GriffinGadget { current_state = params .mat .iter() - .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) + .map(|row| current_state.iter().zip_eq(row).map(|(a, b)| a * *b).sum()) .collect(); for r in 0..params.rounds { @@ -476,12 +483,12 @@ impl GriffinGadget { current_state = params .mat .iter() - .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) + .map(|row| current_state.iter().zip_eq(row).map(|(a, b)| a * *b).sum()) .collect(); if r < params.rounds - 1 { current_state = current_state .iter() - .zip(¶ms.round_constants[r]) + .zip_eq(¶ms.round_constants[r]) .map(|(c, rc)| c + *rc) .collect(); } From 9ffbde333876b52e707790aaa036103daf5cd8a7 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 28 Apr 2026 21:59:30 +0800 Subject: [PATCH 106/131] Refactor `ArithConfig` to allow modifying the number of public inputs This addresses the issue of inconsistent public inputs lengths when guessing augmented circuit configuration. --- crates/fs/src/definitions/instances.rs | 10 +- crates/fs/src/definitions/keys.rs | 9 +- crates/fs/src/definitions/mod.rs | 15 +- crates/fs/src/definitions/witnesses.rs | 8 +- .../fs/src/nova/algorithms/key_generator.rs | 4 +- crates/fs/src/nova/algorithms/prover.rs | 2 +- crates/fs/src/nova/instances/mod.rs | 12 +- crates/fs/src/nova/keys/mod.rs | 7 +- crates/fs/src/nova/witnesses/mod.rs | 14 +- .../ivc/src/compilers/cyclefold/circuits.rs | 45 ++- crates/ivc/src/compilers/cyclefold/mod.rs | 39 ++- .../src/arithmetizations/ccs/circuits.rs | 45 --- .../src/arithmetizations/ccs/mod.rs | 289 +++--------------- crates/primitives/src/arithmetizations/mod.rs | 53 ++-- .../src/arithmetizations/r1cs/circuits.rs | 22 +- .../src/arithmetizations/r1cs/mod.rs | 272 +++++------------ crates/primitives/src/circuits/mod.rs | 53 ++++ crates/primitives/src/circuits/utils.rs | 8 +- 18 files changed, 309 insertions(+), 598 deletions(-) delete mode 100644 crates/primitives/src/arithmetizations/ccs/circuits.rs diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index cd431c448..9c2edcfc1 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -14,7 +14,9 @@ use super::utils::TaggedVec; /// [`FoldingInstance`] defines the operations that a folding scheme's instance /// should support. -pub trait FoldingInstance: Clone + Debug + PartialEq + Eq + Absorbable { +pub trait FoldingInstance: + Clone + Debug + PartialEq + Eq + Absorbable + for<'a> Dummy<&'a ArithConfig> +{ /// [`FoldingInstance::N_COMMITMENTS`] defines the number of commitments /// contained in the instance. const N_COMMITMENTS: usize; @@ -44,9 +46,9 @@ pub trait FoldingInstance: Clone + Debug + PartialEq + Eq + A /// `'u'` for it. pub type PlainInstance = TaggedVec; -impl Dummy<&A> for PlainInstance { - fn dummy(cfg: &A) -> Self { - vec![V::default(); cfg.n_public_inputs()].into() +impl Dummy<&ArithConfig> for PlainInstance { + fn dummy(cfg: &ArithConfig) -> Self { + vec![V::default(); cfg.n_public_inputs].into() } } diff --git a/crates/fs/src/definitions/keys.rs b/crates/fs/src/definitions/keys.rs index fc843568e..796d574d8 100644 --- a/crates/fs/src/definitions/keys.rs +++ b/crates/fs/src/definitions/keys.rs @@ -12,15 +12,12 @@ pub trait DeciderKey: CanonicalSerialize + CanonicalDeserialize { /// [`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; + /// [`DeciderKey::to_arith_config`] returns the constraint system + /// configuration. + fn to_arith_config(&self) -> ArithConfig; } diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index 38a737027..547a326c6 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -12,7 +12,7 @@ pub mod witnesses; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use sonobe_primitives::{ - arithmetizations::Arith, + arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget}, relations::{Relation, WitnessInstanceSampler}, @@ -54,19 +54,19 @@ pub trait FoldingSchemeDef { /// scheme. type CM: CommitmentDef; /// [`FoldingSchemeDef::RW`] is the type of running witness. - type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type RW: FoldingWitness; /// [`FoldingSchemeDef::RU`] is the type of running instance. - type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type RU: FoldingInstance; /// [`FoldingSchemeDef::IW`] is the type of incoming witness. - type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type IW: FoldingWitness; /// [`FoldingSchemeDef::IU`] is the type of incoming instance. - type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type IU: FoldingInstance; /// [`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>; + type Arith: Arith; /// [`FoldingSchemeDef::Config`] is the type of configuration required to /// generate the public parameters of the folding scheme. type Config; @@ -92,8 +92,7 @@ pub trait FoldingSchemeDef { type Challenge; /// [`FoldingSchemeDef::Proof`] is the type of proof generated by the /// folding prover. - type Proof: Clone - + for<'a> Dummy<&'a ::Config>; + type Proof: Clone + for<'a> Dummy<&'a ArithConfig>; } /// [`FoldingSchemeDefGadget`] specifies the in-circuit associated types for a diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs index 95da4b78b..913c2c492 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -12,7 +12,7 @@ use super::utils::TaggedVec; /// [`FoldingWitness`] defines the operations that a folding scheme's witness /// should support. -pub trait FoldingWitness: Debug { +pub trait FoldingWitness: Debug + for<'a> Dummy<&'a ArithConfig> { /// [`FoldingWitness::N_OPENINGS`] defines the number of openings contained /// in the witness. const N_OPENINGS: usize; @@ -33,9 +33,9 @@ pub trait FoldingWitness: Debug { /// `'w'` for it. pub type PlainWitness = TaggedVec; -impl Dummy<&A> for PlainWitness { - fn dummy(cfg: &A) -> Self { - vec![V::default(); cfg.n_witnesses()].into() +impl Dummy<&ArithConfig> for PlainWitness { + fn dummy(cfg: &ArithConfig) -> Self { + vec![V::default(); cfg.n_witnesses].into() } } diff --git a/crates/fs/src/nova/algorithms/key_generator.rs b/crates/fs/src/nova/algorithms/key_generator.rs index 38e41ba82..c107cc75c 100644 --- a/crates/fs/src/nova/algorithms/key_generator.rs +++ b/crates/fs/src/nova/algorithms/key_generator.rs @@ -2,7 +2,7 @@ use ark_std::sync::Arc; use sonobe_primitives::{ - arithmetizations::{Arith, ArithConfig}, + arithmetizations::Arith, commitments::{CommitmentKey, GroupBasedCommitment}, traits::SonobeField, }; @@ -16,7 +16,7 @@ impl FoldingSchemeKey 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()) { + 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(), )); diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index 30ccb1513..eda3416ca 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -32,7 +32,7 @@ fn cross_term<'a, F: Field>( // 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 = arith.evaluate_at(AssignmentsOwned::from(( + let v = arith.evaluate_r1cs(AssignmentsOwned::from(( z1.constant + z2.constant, cfg_iter!(z1.public) .zip_eq(z2.public) diff --git a/crates/fs/src/nova/instances/mod.rs b/crates/fs/src/nova/instances/mod.rs index a2408bafb..d4958997c 100644 --- a/crates/fs/src/nova/instances/mod.rs +++ b/crates/fs/src/nova/instances/mod.rs @@ -40,13 +40,13 @@ impl FoldingInstance for RunningInstance { } } -impl Dummy<&Cfg> for RunningInstance { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for RunningInstance { + fn dummy(cfg: &ArithConfig) -> Self { Self { cm_e: Default::default(), u: Default::default(), cm_w: Default::default(), - x: vec![Default::default(); cfg.n_public_inputs()], + x: vec![Default::default(); cfg.n_public_inputs], } } } @@ -85,11 +85,11 @@ impl FoldingInstance for IncomingInstance { } } -impl Dummy<&Cfg> for IncomingInstance { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for IncomingInstance { + fn dummy(cfg: &ArithConfig) -> Self { Self { cm_w: Default::default(), - x: vec![Default::default(); cfg.n_public_inputs()], + x: vec![Default::default(); cfg.n_public_inputs], } } } diff --git a/crates/fs/src/nova/keys/mod.rs b/crates/fs/src/nova/keys/mod.rs index ca70224d6..479539779 100644 --- a/crates/fs/src/nova/keys/mod.rs +++ b/crates/fs/src/nova/keys/mod.rs @@ -29,7 +29,6 @@ pub struct NovaKey { impl DeciderKey for NovaKey { type ProverKey = Self; type VerifierKey = (); - type ArithConfig = A::Config; fn to_pk(&self) -> &Self::ProverKey { self @@ -39,7 +38,7 @@ impl DeciderKey for NovaKey { &() } - fn to_arith_config(&self) -> &Self::ArithConfig { + fn to_arith_config(&self) -> ArithConfig { self.arith.config() } } @@ -131,10 +130,10 @@ where let cfg = self.arith.config(); let u = CM::Scalar::rand(&mut rng); - let x = (0..cfg.n_public_inputs()) + let x = (0..cfg.n_public_inputs) .map(|_| CM::Scalar::rand(&mut rng)) .collect::>(); - let w = (0..cfg.n_witnesses()) + let w = (0..cfg.n_witnesses) .map(|_| CM::Scalar::rand(&mut rng)) .collect::>(); let e = self.arith.eval_relation( diff --git a/crates/fs/src/nova/witnesses/mod.rs b/crates/fs/src/nova/witnesses/mod.rs index 5f144df36..dbb673c41 100644 --- a/crates/fs/src/nova/witnesses/mod.rs +++ b/crates/fs/src/nova/witnesses/mod.rs @@ -28,12 +28,12 @@ impl FoldingWitness for RunningWitness { } } -impl Dummy<&Cfg> for RunningWitness { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for RunningWitness { + fn dummy(cfg: &ArithConfig) -> Self { Self { - e: vec![Default::default(); cfg.n_constraints()], + e: vec![Default::default(); cfg.n_constraints], r_e: Default::default(), - w: vec![Default::default(); cfg.n_witnesses()], + w: vec![Default::default(); cfg.n_witnesses], r_w: Default::default(), } } @@ -56,10 +56,10 @@ impl FoldingWitness for IncomingWitness { } } -impl Dummy<&Cfg> for IncomingWitness { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for IncomingWitness { + fn dummy(cfg: &ArithConfig) -> Self { Self { - w: vec![Default::default(); cfg.n_witnesses()], + w: vec![Default::default(); cfg.n_witnesses], r_w: Default::default(), } } diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 5bbbd649c..6667639aa 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -9,12 +9,13 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_serialize::CanonicalSerialize; +use ark_std::marker::PhantomData; use sonobe_fs::{ FoldingInstanceVar, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ - arithmetizations::Arith, + arithmetizations::ArithConfig, circuits::{FCircuit, WitnessToPublic}, commitments::CommitmentDef, traits::{Dummy, SonobeCurve}, @@ -32,10 +33,36 @@ pub struct AugmentedCircuit< FC: FCircuit, T: Transcript, > { - pub(super) hash_config: T::Config, - pub(super) arith1_config: &'a ::Config, - pub(super) arith2_config: &'a ::Config, - pub(super) step_circuit: &'a FC, + _fs: PhantomData<(FS1, FS2)>, + hash_config: &'a T::Config, + arith1_config: &'a ArithConfig, + arith2_config: &'a ArithConfig, + step_circuit: &'a FC, +} + +impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> +where + FS1: GroupBasedFoldingSchemePrimary<1, 1>, + FS2: GroupBasedFoldingSchemeSecondary<1, 1>, + FC: FCircuit, + T: Transcript, +{ + /// [`AugmentedCircuit::new`] creates an instance of the augmented circuit + /// for the given step circuit. + pub fn new( + hash_config: &'a T::Config, + arith1_config: &'a ArithConfig, + arith2_config: &'a ArithConfig, + step_circuit: &'a FC, + ) -> Self { + Self { + _fs: PhantomData, + hash_config, + arith1_config, + arith2_config, + step_circuit, + } + } } impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> @@ -79,7 +106,7 @@ where cf_proofs: Vec>, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { let hash = T::Gadget::new_with_pp_hash( - &self.hash_config, + self.hash_config, &FpVar::new_witness(cs.clone(), || Ok(pp_hash))?, )?; let sponge = hash.separate_domain("sponge".as_ref())?; @@ -131,9 +158,9 @@ where let actual_UU = is_basecase.select(&U_dummy, &UU)?; // 2. Fold secondary instances. - // 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`. + // 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`. let cf_u_xs = FS1::to_cyclefold_inputs([U], [u], UU, proof, rho)?; if [cf_us.len(), cf_u_xs.len(), cf_proofs.len()] != [FS1::N_CYCLEFOLDS; 3] { return Err(SynthesisError::Unsatisfiable); diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 53284c453..184153926 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -27,7 +27,7 @@ use sonobe_fs::{ }; use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, - arithmetizations::Arith, + arithmetizations::{Arith, ArithConfig}, circuits::{ArithExtractor, AssignmentsExtractor, FCircuit}, commitments::CommitmentDef, relations::WitnessInstanceSampler, @@ -109,8 +109,8 @@ pub struct 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(); + let cfg1 = &pk.0.to_arith_config(); + let cfg2 = &pk.1.to_arith_config(); Self( FS1::RW::dummy(cfg1), FS1::RU::dummy(cfg1), @@ -205,23 +205,30 @@ where // 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(); + let mut arith1_config = ArithConfig { + n_public_inputs: 1, + ..Default::default() + }; + let arith2_config = &arith2.config(); + let arith1; loop { let new_arith1 = { let cs = ArithExtractor::new(); - cs.execute_synthesizer(AugmentedCircuit:: { - hash_config: hash_config.clone(), - arith1_config: arith1.config(), - arith2_config: arith2.config(), + cs.execute_synthesizer(AugmentedCircuit::::new( + &hash_config, + &arith1_config, + arith2_config, step_circuit, - })?; + ))?; cs.arith::()? }; - if new_arith1.config() == arith1.config() { + let new_arith1_config = new_arith1.config(); + if new_arith1_config == arith1_config { + arith1 = new_arith1; break; } - arith1 = new_arith1; + arith1_config = new_arith1_config; } let dk1 = FS1::generate_keys(pp1, arith1)?; @@ -270,8 +277,8 @@ where 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 arith1_config = &dk1.to_arith_config(); + let arith2_config = &dk2.to_arith_config(); let (mut WW, mut UU) = (Dummy::dummy(arith1_config), Dummy::dummy(arith1_config)); let mut proof = Dummy::dummy(arith1_config); @@ -313,12 +320,12 @@ where let cs = AssignmentsExtractor::new(); let (next_state, external_outputs) = cs.execute_fn(|cs| { - let augmented_circuit = AugmentedCircuit:: { - hash_config: hash_config.clone(), + let augmented_circuit = AugmentedCircuit::::new( + hash_config, arith1_config, arith2_config, step_circuit, - }; + ); augmented_circuit.compute_next_state( cs, *pp_hash, diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs deleted file mode 100644 index fe9011ae7..000000000 --- a/crates/primitives/src/arithmetizations/ccs/circuits.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! This module implements in-circuit CCS variables. - -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, CCSVariant}; -use crate::algebra::ops::matrix::SparseMatrixVar; - -/// [`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 { - #[allow(dead_code)] - 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(); - Ok(Self { - M: val - .borrow() - .M - .iter() - .map(|m| SparseMatrixVar::new_constant(cs.clone(), m)) - .collect::>()?, - }) - }) - } -} - -// 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 c9f9a0d12..c71465f7c 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -22,126 +22,48 @@ use ark_ff::Field; use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, fmt::Debug, marker::PhantomData}; +use ark_std::{cfg_into_iter, cfg_iter}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use super::{Arith, ArithRelation, Error, r1cs::R1CS}; -use crate::{ - algebra::ops::poly::MLEHelper, - arithmetizations::{ArithConfig, r1cs::R1CSConfig}, - circuits::Assignments, -}; +use super::{Arith, Error}; +use crate::{algebra::ops::poly::MLEHelper, circuits::Assignments}; -pub mod circuits; +/// [`CCS`] is an abstract trait that defines the behavior of all CCS variants, +/// including but not limited to R1CS. +pub trait CCS: + Arith + for<'a> From<&'a ConstraintSystem> + From> +{ + /// [`CCS::Field`] specifies the underlying field of a CCS instance + type Field: Field; -/// [`CCSVariant`] defines the methods that a CCS variant (e.g., R1CS) should -/// implement. -pub trait CCSVariant: Clone + Debug + PartialEq + Default + Sync + Send { - /// [`CCSVariant::n_matrices`] returns the number of matrices in the CCS - /// variant. - fn n_matrices() -> usize; + /// [`CCS::matrices`] returns the matrices contained in a concrete CCS + /// instance `self`. + fn matrices(&self) -> &[Matrix]; - /// [`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, CanonicalSerialize, CanonicalDeserialize)] -pub struct CCSConfig { - _v: PhantomData, - /// 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, -} - -impl ArithConfig for CCSConfig { - #[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 - } -} - -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`] holds the CCS matrices `M` together with the configuration. -#[allow(non_snake_case)] -#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] -pub struct CCS { - cfg: CCSConfig, - - pub(super) M: Vec>, -} - -impl CCS { - /// [`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; + /// [`CCS::evaluate_ccs`] evaluates the CCS relation at a given vector of + /// assignments, multisets, and coefficients. + fn evaluate_ccs( + &self, + z: Assignments + Sync>, + multisets: [Vec; Q], + coefficients: [Self::Field; Q], + ) -> Result, Error> { + let cfg = self.config(); + let matrices = self.matrices(); let public_len = z.public.as_ref().len(); let private_len = z.private.as_ref().len(); - if public_len != cfg.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 ({}).", - cfg.n_public_inputs(), - public_len + cfg.n_public_inputs, public_len ))); } - if private_len != cfg.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 ({}).", - cfg.n_witnesses(), - private_len + cfg.n_witnesses, private_len ))); } @@ -156,13 +78,13 @@ 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..cfg.n_constraints()) + Ok(cfg_into_iter!(0..cfg.n_constraints) .map(|row| { // 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::()) + multisets + .iter() + .zip(coefficients) .map(|(s, c)| { // Each term in the sum is: // `c_i · 〇_{j ∈ S_i} (M_j[row] · z)` @@ -171,12 +93,12 @@ impl CCS { .map(|&i| { // 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] + matrices[i][row] .iter() .map(|(val, col)| z[*col] * val) - .sum::() + .sum::() }) - .product::() + .product::() }) .sum() }) @@ -185,14 +107,15 @@ impl CCS { /// [`CCS::mles`] returns the multilinear extensions of all CCS matrices /// `M_i` evaluated over the assignments `z`. - pub fn mles( + fn mles( &self, - z: Assignments + Sync>, - ) -> Vec> { - (0..V::n_matrices()) - .map(|i| { + z: Assignments + Sync>, + ) -> Vec> { + self.matrices() + .iter() + .map(|matrix| { DenseMultilinearExtension::from_evaluations( - &cfg_iter!(self.M[i]) + &cfg_iter!(matrix) .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) .collect::>(), ) @@ -200,133 +123,3 @@ impl CCS { .collect() } } - -impl Default for CCS { - #[inline] - fn default() -> Self { - Self { - cfg: CCSConfig::default(), - M: vec![vec![]; V::n_matrices()], - } - } -} - -impl Arith for CCS { - type Config = CCSConfig; - - #[inline] - fn config(&self) -> &Self::Config { - &self.cfg - } - - #[inline] - fn config_mut(&mut self) -> &mut Self::Config { - &mut self.cfg - } -} - -impl, U: AsRef<[F]>, V: CCSVariant> ArithRelation for CCS { - type Evaluation = Vec; - - fn eval_relation(&self, w: &W, u: &U) -> Result { - self.evaluate_at((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 From> for CCS { - fn from(r1cs: R1CS) -> Self { - 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) - } -} - -#[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/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index c0e2457e1..f592561ac 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -34,31 +34,34 @@ pub enum Error { } /// [`ArithConfig`] describes the configuration of a constraint system. -pub trait ArithConfig: Clone + Debug + Default + PartialEq { - /// [`ArithConfig::degree`] returns the degree of the constraint system. - fn degree(&self) -> usize; +#[derive(Clone, Debug, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct ArithConfig { + /// [`ArithConfig::degree`] specifies the degree of the constraint system. + pub degree: usize, - /// [`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 - } + /// [`ArithConfig::n_constraints`] specifies the number of constraints in + /// the constraint system. + pub n_constraints: usize, - /// [`ArithConfig::n_variables`] returns the number of variables in the + /// [`ArithConfig::n_variables`] specifies the number of variables in the /// constraint system. - fn n_variables(&self) -> usize; + pub n_variables: usize, - /// [`ArithConfig::n_public_inputs`] returns the number of public inputs in - /// the constraint system. - fn n_public_inputs(&self) -> usize; + /// [`ArithConfig::n_public_inputs`] specifies the number of public inputs + /// in the constraint system. + pub n_public_inputs: usize, - /// [`ArithConfig::n_witnesses`] returns the number of witnesses in the + /// [`ArithConfig::n_witnesses`] specifies the number of witnesses in the /// constraint system. - fn n_witnesses(&self) -> usize; + pub n_witnesses: usize, +} + +impl ArithConfig { + /// [`ArithConfig::log_constraints`] returns the base-2 logarithm of the + /// number of constraints in the constraint system. + pub fn log_constraints(&self) -> usize { + log2(self.n_constraints) as usize + } } /// [`Arith`] is a trait for constraint systems (R1CS, CCS, etc.), where we @@ -66,16 +69,8 @@ pub trait ArithConfig: Clone + Debug + Default + PartialEq { /// In addition to the configuration, the implementor of this trait may also /// store the actual constraints and other information. pub trait Arith: Clone + Default + Send + Sync + CanonicalSerialize + CanonicalDeserialize { - /// [`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; + /// [`Arith::config`] returns the configuration of the constraint system. + fn config(&self) -> ArithConfig; } /// [`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 0d983a4a8..594c30be3 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -44,9 +44,21 @@ impl> let val = val.borrow(); Ok(Self { - 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)?, + A: SparseMatrixVar::::new_variable( + cs.clone(), + || Ok(&val.matrices[0]), + mode, + )?, + B: SparseMatrixVar::::new_variable( + cs.clone(), + || Ok(&val.matrices[1]), + mode, + )?, + C: SparseMatrixVar::::new_variable( + cs.clone(), + || Ok(&val.matrices[2]), + mode, + )?, }) }) } @@ -115,13 +127,13 @@ mod tests { let r1cs = constraints_for_test::(); assert!( - r1cs.evaluate_at(satisfying_assignments_for_test(Fr::rand(&mut rng)))? + r1cs.evaluate_r1cs(satisfying_assignments_for_test(Fr::rand(&mut rng)))? .into_iter() .all(|e| e.is_zero()) ); assert!( !r1cs - .evaluate_at(Assignments::from(( + .evaluate_r1cs(Assignments::from(( Fr::one(), vec![Fr::rand(&mut rng)], vec![ diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index a6e3318de..3cfb2106e 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -4,189 +4,74 @@ 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}; +use ark_std::{cfg_into_iter, cfg_iter}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use super::{Arith, ArithRelation, Error, ccs::CCS}; -use crate::{ - arithmetizations::{ArithConfig, ccs::CCSVariant}, - circuits::Assignments, -}; +use super::{Arith, ArithConfig, ArithRelation, Error, ccs::CCS}; +use crate::circuits::Assignments; pub mod circuits; -/// [`R1CSConfig`] stores the shape parameters of an R1CS structure. +/// [`R1CS`] holds the three sparse matrices `A`, `B`, `C` together with the +/// configuration. #[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct R1CSConfig { +pub struct R1CS { m: usize, // number of constraints n: usize, // number of variables l: usize, // io len + matrices: [Matrix; 3], } -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, - n: n_variables, - l: n_public_inputs, - } - } -} - -impl ArithConfig for R1CSConfig { - #[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 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 { - #[inline] - fn n_matrices() -> usize { - 3 - } - - #[inline] - fn degree() -> usize { - 2 - } - - #[inline] - fn multisets_vec() -> Vec> { - vec![vec![0, 1], vec![2]] - } - +impl Arith for R1CS { #[inline] - fn coefficients_vec() -> Vec { - vec![F::one(), -F::one()] - } -} - -/// [`R1CS`] holds the three sparse matrices `A`, `B`, `C` together with the -/// configuration. -#[allow(non_snake_case)] -#[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct R1CS { - cfg: R1CSConfig, - pub(super) A: Matrix, - pub(super) B: Matrix, - pub(super) C: Matrix, -} - -type Row = Vec<(F, usize)>; - -impl R1CS { - /// [`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 Fn(((&Row, &Row), &Row)) -> Result + Send + Sync, - ) -> 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 != 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 ({}).", - cfg.n_public_inputs(), - public_len - ))); - } - 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 ({}).", - cfg.n_witnesses(), - private_len - ))); + fn config(&self) -> ArithConfig { + ArithConfig { + degree: 2, + n_constraints: self.m, + n_variables: self.n, + n_public_inputs: self.l, + n_witnesses: self.n - self.l - 1, } - - 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) - }) } } -impl Arith for R1CS { - type Config = R1CSConfig; - - #[inline] - fn config(&self) -> &Self::Config { - &self.cfg - } +impl CCS for R1CS { + type Field = F; - #[inline] - fn config_mut(&mut self) -> &mut Self::Config { - &mut self.cfg + fn matrices(&self) -> &[Matrix] { + &self.matrices[..] } } 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 } + pub fn new( + n_constraints: usize, + n_variables: usize, + n_public_inputs: usize, + matrices: [Matrix; 3], + ) -> Self { + Self { + m: n_constraints, + l: n_public_inputs, + n: n_variables, + matrices, + } } -} - -impl TryFrom> for R1CS { - type Error = Error; - fn try_from(ccs: CCS) -> Result { - let cfg = ccs.config(); - Ok(Self::new( - R1CSConfig::new( - 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(), - )) + /// [`R1CS::evaluate_r1cs`] evaluates the R1CS relation at a given vector of + /// assignments `z`. + /// + /// This method is simply a wrapper of [`CCS::evaluate_ccs`] with fixed + /// coefficients and multisets. + pub fn evaluate_r1cs( + &self, + z: Assignments + Sync>, + ) -> Result, Error> { + let u = z[0]; + self.evaluate_ccs(z, [vec![0, 1], vec![2]], [F::one(), -u]) } } @@ -196,7 +81,12 @@ impl From<&ConstraintSystem> for R1CS { 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()) + R1CS::new( + cs.num_constraints(), + cs.num_instance_variables + cs.num_witness_variables, + cs.num_instance_variables - 1, // -1 to subtract the first '1' + matrices.try_into().unwrap(), + ) } } @@ -210,7 +100,7 @@ impl, U: AsRef<[F]>> ArithRelation for R1CS { type Evaluation = Vec; fn eval_relation(&self, w: &W, x: &U) -> Result { - self.evaluate_at((F::one(), x.as_ref(), w.as_ref()).into()) + self.evaluate_r1cs((F::one(), x.as_ref(), w.as_ref()).into()) } fn check_evaluation(_w: &W, _x: &U, e: Self::Evaluation) -> Result<(), Error> { @@ -251,7 +141,7 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 w: &RelaxedWitness<&[F]>, u: &RelaxedInstance<&[F]>, ) -> Result { - self.evaluate_at((*u.u, u.x, w.w).into()) + self.evaluate_r1cs((*u.u, u.x, w.w).into()) } fn check_evaluation( @@ -281,51 +171,37 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 mod tests { use ark_bn254::Fr; use ark_ff::UniformRand; - use ark_relations::gr1cs::ConstraintSynthesizer; 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::{ - ArithExtractor, AssignmentsExtractor, - utils::{CircuitForTest, constraints_for_test, satisfying_assignments_for_test}, + use crate::{ + circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, + relations::Relation, }; #[test] - fn test_satisfiability() -> Result<(), Box> { - let mut rng = thread_rng(); - let circuit = CircuitForTest:: { - x: Fr::rand(&mut rng), - }; - let cs = ConstraintSystem::new_ref(); - circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - - Ok(()) - } - - #[test] - fn test_constraint_extraction() -> Result<(), Box> { - let mut rng = thread_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(()) - } - - #[test] - fn test_witness_extraction() -> Result<(), Box> { + fn test_check() -> Result<(), Box> { let mut rng = thread_rng(); - let x = Fr::rand(&mut rng); - let circuit = CircuitForTest:: { x }; + 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() + ); - 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 d42e0a521..4b729004f 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -279,3 +279,56 @@ impl WitnessToPublic for [T] { self.iter().try_for_each(|x| x.mark_as_public()) } } + +#[cfg(test)] +mod tests { + use ark_bn254::Fr; + use ark_ff::UniformRand; + use ark_relations::gr1cs::ConstraintSynthesizer; + 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::{ + utils::{CircuitForTest, constraints_for_test, satisfying_assignments_for_test}, + *, + }; + use crate::arithmetizations::r1cs::R1CS; + + #[test] + fn test_satisfiability() -> Result<(), Box> { + let mut rng = thread_rng(); + let circuit = CircuitForTest:: { + x: Fr::rand(&mut rng), + }; + let cs = ConstraintSystem::new_ref(); + circuit.generate_constraints(cs.clone())?; + assert!(cs.is_satisfied()?); + + Ok(()) + } + + #[test] + fn test_constraint_extraction() -> Result<(), Box> { + let mut rng = thread_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(()) + } + + #[test] + fn test_witness_extraction() -> Result<(), Box> { + let mut rng = thread_rng(); + let x = Fr::rand(&mut rng); + let circuit = CircuitForTest:: { x }; + + 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/utils.rs b/crates/primitives/src/circuits/utils.rs index 464d248d3..0917544f9 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -9,11 +9,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable}; use super::Assignments; -use crate::{ - arithmetizations::r1cs::{R1CS, R1CSConfig}, - circuits::FCircuit, - traits::SonobeField, -}; +use crate::{arithmetizations::r1cs::R1CS, circuits::FCircuit, traits::SonobeField}; /// [`CircuitForTest`] implements a simple test circuit computing /// `y = x^3 + x + 5` with 4 R1CS constraints. @@ -139,7 +135,7 @@ pub fn constraints_for_test() -> R1CS { vec![(F::one(), 2)], ]; - R1CS::::new(R1CSConfig::new(4, 6, 1), [A, B, C]) + R1CS::::new(4, 6, 1, [A, B, C]) } /// [`satisfying_assignments_for_test`] returns a satisfying assignment for the From 157495aabc6e9134b6c7552a10576af8609e35ca Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 23 May 2026 14:10:35 +0800 Subject: [PATCH 107/131] Bump dependencies --- Cargo.lock | 56 +++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62f596db3..1dd1e8801 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -505,9 +505,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "enum-ordinalize" @@ -685,9 +685,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -778,9 +778,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -993,9 +993,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1343,9 +1343,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -1356,9 +1356,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1366,9 +1366,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1376,9 +1376,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -1389,18 +1389,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5ec93229ad9ccd0a545a516dec76dc276613f278f6a91aa6b463d5b33d42d0" +checksum = "74fde991ccdc895cb7fbaa14b137d62af74d9011be67b71c694bfc40edd3119c" dependencies = [ "async-trait", "cast", @@ -1420,9 +1420,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c81b9fef827e575e0e54431736d1baa0d700315d8c62cfef1f61fa3aad0cbeb" +checksum = "e925354648d2a4d1bf205412e36d520a800280622eef4719678d268e5d40e978" dependencies = [ "proc-macro2", "quote", @@ -1431,9 +1431,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527" +checksum = "684365b586a9a6256c1cc3544eee8680de48d6041142f581776ec7b139622ae9" [[package]] name = "winapi-util" From 29b3ce46eb05f24adb148f3e33a88f230f1c7b7f Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 26 May 2026 14:18:38 +0800 Subject: [PATCH 108/131] Check R1CS validity by default --- crates/primitives/src/arithmetizations/mod.rs | 12 ++++++ .../src/arithmetizations/r1cs/mod.rs | 38 +++++++++++++++++-- crates/primitives/src/circuits/utils.rs | 2 +- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index f592561ac..5e76a9ade 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -27,6 +27,18 @@ pub enum Error { /// assignments do not satisfy the constraint system. #[error("The provided assignments do not satisfy the constraint system: {0}")] UnsatisfiedAssignments(String), + /// [`Error::InvalidNumberOfConstraints`] indicates that the constraint + /// system's number of constraints does not match the provided config. + #[error( + "The number of constraints in the constraint system configuration is invalid. Provided: {0}, expected: {1}" + )] + InvalidNumberOfConstraints(usize, usize), + /// [`Error::InvalidNumberOfVariables`] indicates that the constraint + /// system's number of variables does not match the provided config. + #[error( + "The number of variables in the constraint system configuration is invalid. Provided: {0}, expected: {1}" + )] + InvalidNumberOfVariables(usize, usize), /// [`Error::SynthesisError`] indicates an error during constraint /// synthesis. #[error(transparent)] diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 3cfb2106e..0439e7793 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -52,6 +52,37 @@ impl R1CS { n_variables: usize, n_public_inputs: usize, matrices: [Matrix; 3], + ) -> Result { + for matrix in &matrices { + if matrix.len() != n_constraints { + return Err(Error::InvalidNumberOfConstraints( + n_constraints, + matrix.len(), + )); + } + for row in matrix { + if let Some(max) = row.iter().map(|(_, i)| *i).max() + && max >= n_variables + { + return Err(Error::InvalidNumberOfVariables(n_variables, max + 1)); + } + } + } + Ok(Self::new_without_validity_check( + n_constraints, + n_variables, + n_public_inputs, + matrices, + )) + } + + /// [`R1CS::new_without_validity_check`] creates a new R1CS structure from + /// the given configuration and matrices without checking their validity. + pub fn new_without_validity_check( + n_constraints: usize, + n_variables: usize, + n_public_inputs: usize, + matrices: [Matrix; 3], ) -> Self { Self { m: n_constraints, @@ -80,12 +111,13 @@ impl From<&ConstraintSystem> for R1CS { // 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( + + // matrices are extracted from a circuit, which we assume is trusted + R1CS::new_without_validity_check( cs.num_constraints(), cs.num_instance_variables + cs.num_witness_variables, cs.num_instance_variables - 1, // -1 to subtract the first '1' - matrices.try_into().unwrap(), + matrices.try_into().unwrap(), // safe as R1CS always has 3 matrices ) } } diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 0917544f9..16b291895 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -135,7 +135,7 @@ pub fn constraints_for_test() -> R1CS { vec![(F::one(), 2)], ]; - R1CS::::new(4, 6, 1, [A, B, C]) + R1CS::::new_without_validity_check(4, 6, 1, [A, B, C]) } /// [`satisfying_assignments_for_test`] returns a satisfying assignment for the From 3e766a10c5c15f5d8881b9f97c31ea7fa7ea9bc1 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 26 May 2026 16:45:20 +0800 Subject: [PATCH 109/131] Implement recording and replaying transcripts so we don't need to return challenges explicitly --- crates/fs/src/definitions/algorithms.rs | 2 +- crates/fs/src/definitions/circuits.rs | 2 +- crates/fs/src/lib.rs | 6 +- crates/fs/src/nova/algorithms/prover.rs | 8 +- crates/fs/src/nova/circuits/verifier.rs | 110 ++++++++---------- .../src/compilers/cyclefold/adapters/nova.rs | 30 +++-- .../ivc/src/compilers/cyclefold/circuits.rs | 21 ++-- crates/ivc/src/compilers/cyclefold/mod.rs | 31 ++--- .../src/transcripts/griffin/sponge.rs | 64 +++------- crates/primitives/src/transcripts/mod.rs | 56 ++++++--- .../src/transcripts/poseidon/sponge.rs | 57 ++++----- .../src/transcripts/recording/mod.rs | 69 +++++++++++ .../primitives/src/transcripts/replay/mod.rs | 90 ++++++++++++++ 13 files changed, 350 insertions(+), 196 deletions(-) create mode 100644 crates/primitives/src/transcripts/recording/mod.rs create mode 100644 crates/primitives/src/transcripts/replay/mod.rs diff --git a/crates/fs/src/definitions/algorithms.rs b/crates/fs/src/definitions/algorithms.rs index a4238a038..d5f9c37a4 100644 --- a/crates/fs/src/definitions/algorithms.rs +++ b/crates/fs/src/definitions/algorithms.rs @@ -56,7 +56,7 @@ pub trait FoldingSchemeProver: FoldingSchemeDef 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), Error>; } /// [`FoldingSchemeVerifier`] is the trait for folding scheme verifier. diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs index 68d1be5f5..63aac1e13 100644 --- a/crates/fs/src/definitions/circuits.rs +++ b/crates/fs/src/definitions/circuits.rs @@ -32,7 +32,7 @@ pub trait FoldingSchemePartialVerifierGadget: Us: [&Self::RU; M], us: [&Self::IU; N], proof: &Self::Proof, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; + ) -> Result; } /// [`FoldingSchemeFullVerifierGadget`] is the full in-circuit verifier. diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index cf528aa75..2ee180acd 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -94,8 +94,8 @@ mod tests { let config = Arc::new(GriffinParams::new(16, 5, 9)); - let mut transcript_p = GriffinSponge::new(&config); - let mut transcript_v = GriffinSponge::new(&config); + let mut transcript_p = GriffinSponge::new(config.clone()); + let mut transcript_v = GriffinSponge::new(config); for assignments in assignments_vec { let mut ws = vec![]; @@ -113,7 +113,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/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index eda3416ca..c5e4daca9 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -61,7 +61,7 @@ impl FoldingSchemePro ws: &[impl Borrow; 1], us: &[impl Borrow; 1], rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>), Error> { let (W, U) = (Ws[0].borrow(), Us[0].borrow()); let (w, u) = (ws[0].borrow(), us[0].borrow()); @@ -94,7 +94,7 @@ impl FoldingSchemePro .map(|(a, b)| rho * b + a) .collect(), }; - Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) + Ok((WW, UU, cm_t)) } } @@ -110,7 +110,7 @@ impl FoldingSchemePro _: &[impl Borrow; 0], _: &[impl Borrow; 0], rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<2, 0>, Self::Challenge), Error> { + ) -> Result<(Self::RW, Self::RU, Self::Proof<2, 0>), Error> { let (W1, U1) = (W1.borrow(), U1.borrow()); let (W2, U2) = (W2.borrow(), U2.borrow()); @@ -146,6 +146,6 @@ impl FoldingSchemePro .map(|(a, b)| rho * b + a) .collect(), }; - Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) + Ok((WW, UU, cm_t)) } } diff --git a/crates/fs/src/nova/circuits/verifier.rs b/crates/fs/src/nova/circuits/verifier.rs index 54127a51c..d60dc6a81 100644 --- a/crates/fs/src/nova/circuits/verifier.rs +++ b/crates/fs/src/nova/circuits/verifier.rs @@ -23,7 +23,7 @@ where [U]: [&Self::RU; 1], [u]: [&Self::IU; 1], proof: &Self::Proof<1, 1>, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + ) -> Result { let rho_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; @@ -31,34 +31,25 @@ where return Err(SynthesisError::Unsatisfiable); } - 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(), - )) + 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)?, + }) } } @@ -73,7 +64,7 @@ where [U1, U2]: [&Self::RU; 2], _: [&Self::IU; 0], proof: &Self::Proof<2, 0>, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + ) -> Result { let rho_bits = transcript.add(&(U1, U2))?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; @@ -81,37 +72,34 @@ where return Err(SynthesisError::Unsatisfiable); } - 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(), - )) + 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)?, + }) } } diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 83b42f932..75525325f 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -18,7 +18,11 @@ use sonobe_primitives::{ }, circuits::WitnessToPublic, commitments::GroupBasedCommitment, - traits::{CF2, SonobeCurve}, + traits::{CF1, CF2, SonobeCurve}, + transcripts::{ + Transcript, TranscriptGadget, + replay::{ReplayTranscript, ReplayTranscriptVar}, + }, }; use crate::compilers::cyclefold::{ @@ -68,15 +72,16 @@ impl FoldingSchemeCycleFo [U]: &[impl Borrow; 1], [u]: &[impl Borrow; 1], proof: &Self::Proof<1, 1>, - rho: Self::Challenge, + mut transcript: ReplayTranscript>, ) -> Vec { + let rho = transcript.challenge_bits(CHALLENGE_BITS); vec![ NovaCycleFoldCircuit { - r: rho.into(), + r: rho.clone(), points: vec![U.borrow().cm_e, *proof], }, NovaCycleFoldCircuit { - r: rho.into(), + r: rho, points: vec![U.borrow().cm_w, u.borrow().cm_w], }, ] @@ -88,9 +93,9 @@ impl FoldingSchemeCycleFo [u]: [::IU; 1], UU: ::RU, proof: ::Proof<1, 1>, - rho: ::Challenge, + mut transcript: ReplayTranscriptVar>, ) -> Result>>>, SynthesisError> { - let mut rho = rho.to_vec(); + let mut rho = transcript.challenge_bits(CHALLENGE_BITS)?; rho.resize( CF2::::MODULUS_BIT_SIZE as usize, Boolean::FALSE, @@ -130,20 +135,21 @@ impl FoldingSchemeCycleFo [U1, U2]: &[impl Borrow; 2], _: &[impl Borrow; 0], proof: &Self::Proof<2, 0>, - rho_bits: Self::Challenge, + mut transcript: ReplayTranscript>, ) -> Vec { + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); let rho = CM::Scalar::from_bits_le(&rho_bits); vec![ NovaCycleFoldCircuit { - r: rho_bits.into(), + r: rho_bits.clone(), points: vec![*proof, U2.borrow().cm_e], }, NovaCycleFoldCircuit { - r: rho_bits.into(), + r: rho_bits.clone(), points: vec![U1.borrow().cm_e, U2.borrow().cm_e * rho + proof], }, NovaCycleFoldCircuit { - r: rho_bits.into(), + r: rho_bits, points: vec![U1.borrow().cm_w, U2.borrow().cm_w], }, ] @@ -155,9 +161,9 @@ impl FoldingSchemeCycleFo _: [::IU; 0], UU: ::RU, proof: ::Proof<2, 0>, - rho_bits: ::Challenge, + mut transcript: ReplayTranscriptVar>, ) -> Result>>>, SynthesisError> { - let mut rho_bits = rho_bits.to_vec(); + let mut rho_bits = transcript.challenge_bits(CHALLENGE_BITS)?; rho_bits.resize( CF2::::MODULUS_BIT_SIZE as usize, Boolean::FALSE, diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 6667639aa..7f989fe0a 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -19,7 +19,7 @@ use sonobe_primitives::{ circuits::{FCircuit, WitnessToPublic}, commitments::CommitmentDef, traits::{Dummy, SonobeCurve}, - transcripts::{Transcript, TranscriptGadget}, + transcripts::{TranscriptGadget, recording::RecordingTranscriptVar}, }; use crate::compilers::cyclefold::FoldingSchemeCycleFoldExt; @@ -31,7 +31,7 @@ pub struct AugmentedCircuit< FS1: GroupBasedFoldingSchemePrimary<1, 1>, FS2: GroupBasedFoldingSchemeSecondary<1, 1>, FC: FCircuit, - T: Transcript, + T: TranscriptGadget, > { _fs: PhantomData<(FS1, FS2)>, hash_config: &'a T::Config, @@ -45,7 +45,7 @@ where FS1: GroupBasedFoldingSchemePrimary<1, 1>, FS2: GroupBasedFoldingSchemeSecondary<1, 1>, FC: FCircuit, - T: Transcript, + T: TranscriptGadget, { /// [`AugmentedCircuit::new`] creates an instance of the augmented circuit /// for the given step circuit. @@ -84,7 +84,7 @@ where >, >, FC: FCircuit::Scalar>, - T: Transcript, + T: TranscriptGadget, { /// [`AugmentedCircuit::compute_next_state`] invokes the step circuit on the /// current state and external inputs to compute the next state and external @@ -105,12 +105,13 @@ where cf_us: Vec, cf_proofs: Vec>, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { - let hash = T::Gadget::new_with_pp_hash( - self.hash_config, + let hash = T::new_with_pp_hash( + self.hash_config.clone(), &FpVar::new_witness(cs.clone(), || Ok(pp_hash))?, )?; let sponge = hash.separate_domain("sponge".as_ref())?; - let mut transcript = hash.separate_domain("transcript".as_ref())?; + let mut transcript = + RecordingTranscriptVar::new(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(); @@ -152,7 +153,7 @@ where // 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)?; + let UU = 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)?; @@ -161,7 +162,7 @@ where // 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`. - let cf_u_xs = FS1::to_cyclefold_inputs([U], [u], UU, proof, rho)?; + let cf_u_xs = FS1::to_cyclefold_inputs([U], [u], UU, proof, transcript.clone().into())?; if [cf_us.len(), cf_u_xs.len(), cf_proofs.len()] != [FS1::N_CYCLEFOLDS; 3] { return Err(SynthesisError::Unsatisfiable); } @@ -224,7 +225,7 @@ where >, >, FC: FCircuit::Scalar>, - T: Transcript, + T: TranscriptGadget, { fn generate_constraints( self, diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 184153926..2cb4b12e6 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -32,7 +32,11 @@ use sonobe_primitives::{ commitments::CommitmentDef, relations::WitnessInstanceSampler, traits::{CF1, CF2, Dummy, SonobeCurve}, - transcripts::Transcript, + transcripts::{ + Transcript, TranscriptGadget, + recording::RecordingTranscript, + replay::{ReplayTranscript, ReplayTranscriptVar}, + }, }; use crate::{ @@ -63,7 +67,7 @@ pub trait FoldingSchemeCycleFoldExt: Us: &[impl Borrow; M], us: &[impl Borrow; N], proof: &Self::Proof, - rho: Self::Challenge, + transcript: ReplayTranscript::Commitment>>, ) -> Vec; /// [`FoldingSchemeCycleFoldExt::to_cyclefold_inputs`] computes the inputs @@ -76,7 +80,7 @@ pub trait FoldingSchemeCycleFoldExt: us: [::IU; N], UU: ::RU, proof: ::Proof, - rho: ::Challenge, + transcript: ReplayTranscriptVar::Commitment>>, ) -> Result< Vec< Vec< @@ -160,7 +164,8 @@ where Commitment: SonobeCurve::Scalar>, >, >, - T: Transcript::Commitment>>, + T: Transcript::Commitment>, Config: CanonicalSerialize>, + T::Gadget: TranscriptGadget::Commitment>, Config = T::Config>, { type Field = ::Scalar; @@ -215,7 +220,7 @@ where loop { let new_arith1 = { let cs = ArithExtractor::new(); - cs.execute_synthesizer(AugmentedCircuit::::new( + cs.execute_synthesizer(AugmentedCircuit::::new( &hash_config, &arith1_config, arith2_config, @@ -274,8 +279,8 @@ 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 = T::new_with_pp_hash(hash_config, *pp_hash); - let mut transcript = hash.separate_domain("transcript".as_ref()); + let hash = T::new_with_pp_hash(hash_config.clone(), *pp_hash); + let mut transcript = RecordingTranscript::new(hash.separate_domain("transcript".as_ref())); let arith1_config = &dk1.to_arith_config(); let arith2_config = &dk2.to_arith_config(); @@ -287,8 +292,7 @@ where let (mut cf_UU, mut cf_WW) = (Dummy::dummy(arith2_config), Dummy::dummy(arith2_config)); if i != 0 { - let challenge; - (WW, UU, proof, challenge) = FS1::prove( + (WW, UU, proof) = FS1::prove( dk1.to_pk(), &mut transcript, &[W], @@ -298,14 +302,15 @@ where &mut rng, )?; - let cf_circuits = FS1::to_cyclefold_circuits(&[U], &[u], &proof, challenge); + let cf_circuits = + FS1::to_cyclefold_circuits(&[U], &[u], &proof, transcript.clone().into()); for (i, cf_circuit) in cf_circuits.into_iter().enumerate() { let cs = AssignmentsExtractor::new(); cs.execute_fn(|cs| cf_circuit.verify_point_rlc(cs))?; let (cf_w, cf_u) = dk2.sample(cs.assignments()?, &mut rng)?; - (cf_WW, cf_UU, cf_proofs[i], _) = FS2::prove( + (cf_WW, cf_UU, cf_proofs[i]) = FS2::prove( dk2.to_pk(), &mut transcript, &[if i == 0 { cf_W } else { &cf_WW }], @@ -320,7 +325,7 @@ where let cs = AssignmentsExtractor::new(); let (next_state, external_outputs) = cs.execute_fn(|cs| { - let augmented_circuit = AugmentedCircuit::::new( + let augmented_circuit = AugmentedCircuit::::new( hash_config, arith1_config, arith2_config, @@ -369,7 +374,7 @@ where return Err(Error::IVCVerificationFail); } - let hash = T::new_with_pp_hash(hash_config, *pp_hash); + let hash = T::new_with_pp_hash(hash_config.clone(), *pp_hash); let mut sponge = hash.separate_domain("sponge".as_ref()); let u_x = sponge diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index 70cfa71ee..e644f6cd2 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -1,11 +1,8 @@ //! Implementation of transcript traits for Griffin sponge. use ark_crypto_primitives::sponge::DuplexSpongeMode; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{ - fields::{FieldVar, fp::FpVar}, - prelude::{Boolean, ToBitsGadget}, -}; +use ark_ff::PrimeField; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_relations::gr1cs::SynthesisError; use ark_std::sync::Arc; @@ -187,7 +184,7 @@ impl Transcript for GriffinSponge { type Config = Arc>; type Gadget = GriffinSpongeVar; - fn new(parameters: &Arc>) -> Self { + fn new(parameters: Arc>) -> Self { let state = vec![F::zero(); parameters.rate + parameters.capacity]; let mode = DuplexSpongeMode::Absorbing { next_absorb_index: 0, @@ -223,22 +220,6 @@ impl Transcript for GriffinSponge { 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 { @@ -263,9 +244,10 @@ impl Transcript for GriffinSponge { } impl TranscriptGadget for GriffinSpongeVar { + type Config = Arc>; type Widget = GriffinSponge; - fn new(parameters: &Arc>) -> Self + fn new(parameters: Arc>) -> Self where Self: Sized, { @@ -282,10 +264,7 @@ impl TranscriptGadget for GriffinSpongeVar { } } - fn add + ?Sized>( - &mut self, - input: &A, - ) -> Result<&mut Self, SynthesisError> { + fn add>(&mut self, input: &A) -> Result<&mut Self, SynthesisError> { let input = { let mut result = Vec::new(); input.absorb_into(&mut result)?; @@ -315,21 +294,6 @@ impl TranscriptGadget for GriffinSpongeVar { 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]; @@ -374,13 +338,13 @@ mod tests { 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); + let mut tr = GriffinSponge::::new(config.clone()); 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 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()?; @@ -397,13 +361,13 @@ mod tests { // Create a transcript outside of the circuit let config = Arc::new(GriffinParams::::new(3, 5, 12)); - let mut tr = GriffinSponge::::new(&config); + let mut tr = GriffinSponge::::new(config.clone()); tr.add(&Fq::from(42_u32)); let c = tr.challenge_bits(nbits); // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); + let mut tr_var = GriffinSpongeVar::::new(config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; tr_var.add(&v)?; let c_var = tr_var.challenge_bits(nbits)?; @@ -418,7 +382,7 @@ mod tests { 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 mut tr = GriffinSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -427,7 +391,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); + 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()?; @@ -442,7 +406,7 @@ mod tests { 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); + let mut tr = GriffinSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -451,7 +415,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); + 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()?; diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 10e94db7a..e2e55b24d 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -8,15 +8,16 @@ //! sub-modules. use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; +use ark_r1cs_std::{boolean::Boolean, convert::ToBitsGadget, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; pub use self::absorbable::{Absorbable, AbsorbableVar}; pub mod absorbable; pub mod griffin; pub mod poseidon; +pub mod recording; +pub mod replay; /// [`Transcript`] is the out-of-circuit widget for transcripts and sponges. /// @@ -25,7 +26,7 @@ pub mod poseidon; pub trait Transcript: Clone { /// [`Transcript::Config`] is the configuration for the underlying hash /// function of the transcript. - type Config: Clone + CanonicalSerialize + CanonicalDeserialize; + type Config: Clone; /// [`Transcript::Gadget`] is the in-circuit gadget corresponding to this /// widget. @@ -33,12 +34,12 @@ pub trait Transcript: Clone { /// [`Transcript::new`] creates a new transcript / sponge under the given /// configuration `config`. - fn new(config: &Self::Config) -> Self; + fn new(config: Self::Config) -> Self; /// [`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 { + 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 @@ -59,7 +60,21 @@ pub trait Transcript: Clone { /// [`Transcript::get_bits`] squeezes `num_bits` bits from the transcript / /// sponge. - fn get_bits(&mut self, num_bits: usize) -> Vec; + 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 + } /// [`Transcript::get_field_element`] squeezes a single field element from /// the transcript / sponge. @@ -132,22 +147,23 @@ pub trait Transcript: Clone { /// [`TranscriptGadget`] is the in-circuit gadget for transcripts and sponges. pub trait TranscriptGadget: Clone { + /// [`TranscriptGadget::Config`] is the configuration for the underlying + /// hash function of the transcript gadget. + type Config: Clone; + /// [`TranscriptGadget::Widget`] points to the out-of-circuit widget for /// this transcript gadget. type Widget: Transcript; /// [`TranscriptGadget::new`] creates a new transcript / sponge variable /// under the given configuration `config`. - fn new(config: &>::Config) -> Self; + fn new(config: Self::Config) -> Self; /// [`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, - pp_hash: &FpVar, - ) -> Result { + fn new_with_pp_hash(config: Self::Config, pp_hash: &FpVar) -> Result { let mut sponge = Self::new(config); sponge.add(&pp_hash)?; Ok(sponge) @@ -156,12 +172,24 @@ 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>(&mut self, input: &A) -> Result<&mut Self, SynthesisError>; /// [`TranscriptGadget::get_bits`] squeezes `num_bits` bit variables from /// the transcript / sponge variable. - fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError>; + 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) + } /// [`TranscriptGadget::get_field_element`] squeezes a single field element /// variable from the transcript / sponge variable. diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index 49cd82250..eb827a19f 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -1,12 +1,12 @@ //! Implementation of transcript traits for arkworks' Poseidon sponge. use ark_crypto_primitives::sponge::{ - Absorb, CryptographicSponge, FieldBasedCryptographicSponge, + Absorb, CryptographicSponge, DuplexSpongeMode, FieldBasedCryptographicSponge, constraints::CryptographicSpongeVar, poseidon::{PoseidonConfig, PoseidonSponge, constraints::PoseidonSpongeVar}, }; use ark_ff::PrimeField; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; use ark_std::mem::transmute_copy; @@ -16,8 +16,14 @@ impl Transcript for PoseidonSponge { type Config = PoseidonConfig; type Gadget = PoseidonSpongeVar; - fn new(config: &Self::Config) -> Self { - CryptographicSponge::new(config) + fn new(config: Self::Config) -> Self { + Self { + state: vec![F::zero(); config.rate + config.capacity], + parameters: config, + mode: DuplexSpongeMode::Absorbing { + next_absorb_index: 0, + }, + } } fn add_field_elements(&mut self, input: &[F]) -> &mut Self { @@ -40,29 +46,30 @@ impl Transcript for PoseidonSponge { self } - fn get_bits(&mut self, num_bits: usize) -> Vec { - CryptographicSponge::squeeze_bits(self, num_bits) - } - fn get_field_elements(&mut self, num_elements: usize) -> Vec { self.squeeze_native_field_elements(num_elements) } } impl TranscriptGadget for PoseidonSpongeVar { + type Config = PoseidonConfig; type Widget = PoseidonSponge; - fn new(config: &PoseidonConfig) -> Self + fn new(config: PoseidonConfig) -> Self where Self: Sized, { - CryptographicSpongeVar::new(ConstraintSystemRef::None, config) + Self { + cs: ConstraintSystemRef::None, + state: vec![FpVar::::zero(); config.rate + config.capacity], + parameters: config, + mode: DuplexSpongeMode::Absorbing { + next_absorb_index: 0, + }, + } } - fn add + ?Sized>( - &mut self, - input: &A, - ) -> Result<&mut Self, SynthesisError> { + fn add>(&mut self, input: &A) -> Result<&mut Self, SynthesisError> { let mut result = Vec::new(); input.absorb_into(&mut result)?; @@ -70,10 +77,6 @@ impl TranscriptGadget for PoseidonSpongeVar { Ok(self) } - fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { - self.squeeze_bits(num_bits) - } - fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { self.squeeze_field_elements(num_elements) } @@ -102,7 +105,7 @@ mod tests { #[test] fn check_against_circom_poseidon() -> Result<(), Box> { let config = poseidon_circom_config::(); - let mut poseidon_sponge = PoseidonSponge::new(&config); + let mut poseidon_sponge = PoseidonSponge::new(config); let v = vec![1, 2, 3, 4] .into_iter() .map(Fr::from) @@ -123,13 +126,13 @@ mod tests { fn test_challenge_field_element() -> Result<(), Box> { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); 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 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()?; @@ -146,13 +149,13 @@ mod tests { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); tr.add(&Fq::from(42_u32)); let c = tr.challenge_bits(nbits); // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); + let mut tr_var = PoseidonSpongeVar::::new(config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; tr_var.add(&v)?; let c_var = tr_var.challenge_bits(nbits)?; @@ -167,7 +170,7 @@ mod tests { fn test_absorb_canonical_point() -> Result<(), Box> { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -176,7 +179,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); + 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()?; @@ -191,7 +194,7 @@ mod tests { fn test_absorb_emulated_point() -> Result<(), Box> { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -200,7 +203,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); + 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()?; diff --git a/crates/primitives/src/transcripts/recording/mod.rs b/crates/primitives/src/transcripts/recording/mod.rs new file mode 100644 index 000000000..d91e5d9a1 --- /dev/null +++ b/crates/primitives/src/transcripts/recording/mod.rs @@ -0,0 +1,69 @@ +//! Implementation of transcripts that can automatically record generated +//! challenges, eliminating the need to pass challenges thoughout protocols. + +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::gr1cs::SynthesisError; + +use super::{AbsorbableVar, Transcript, TranscriptGadget}; + +/// [`RecordingTranscript`] wraps a regular transcript to record all challenges +/// it produces. +#[derive(Clone)] +pub struct RecordingTranscript> { + inner: T, + pub(super) cached_challenges: Vec, +} + +impl> Transcript for RecordingTranscript { + type Config = T; + type Gadget = RecordingTranscriptVar; + + fn new(inner: Self::Config) -> Self { + Self { + inner, + cached_challenges: vec![], + } + } + + fn add_field_elements(&mut self, input: &[F]) -> &mut Self { + self.inner.add_field_elements(input); + self + } + + fn get_field_elements(&mut self, num_elements: usize) -> Vec { + let v = self.inner.get_field_elements(num_elements); + self.cached_challenges.extend_from_slice(&v); + v + } +} + +/// [`RecordingTranscriptVar`] is the in-circuit variable of [`RecordingTranscript`]. +#[derive(Clone)] +pub struct RecordingTranscriptVar> { + inner: T, + pub(super) cached_challenges: Vec>, +} + +impl> TranscriptGadget for RecordingTranscriptVar { + type Config = T; + type Widget = RecordingTranscript; + + fn new(inner: Self::Config) -> Self { + Self { + inner, + cached_challenges: vec![], + } + } + + fn add>(&mut self, input: &A) -> Result<&mut Self, SynthesisError> { + self.inner.add(input)?; + Ok(self) + } + + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { + let v = self.inner.get_field_elements(num_elements)?; + self.cached_challenges.extend_from_slice(&v); + Ok(v) + } +} diff --git a/crates/primitives/src/transcripts/replay/mod.rs b/crates/primitives/src/transcripts/replay/mod.rs new file mode 100644 index 000000000..9c4e8197e --- /dev/null +++ b/crates/primitives/src/transcripts/replay/mod.rs @@ -0,0 +1,90 @@ +//! Implementation of transcripts that always produces designated challenge +//! values. + +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::gr1cs::SynthesisError; + +use super::{AbsorbableVar, Transcript, TranscriptGadget}; +use crate::transcripts::recording::{RecordingTranscript, RecordingTranscriptVar}; + +/// [`ReplayTranscript`] is a convenience struct that generates specific values +/// as challenges without running the actual hash function. +/// +/// WARNING: This struct itself is insecure. The caller is responsible for +/// checking the validity of the designated challenge values. +#[derive(Clone)] +pub struct ReplayTranscript { + cached_challenges: Vec, +} + +impl> From> for ReplayTranscript { + fn from(value: RecordingTranscript) -> Self { + Self { + cached_challenges: value.cached_challenges, + } + } +} + +impl Transcript for ReplayTranscript { + type Config = Vec; + type Gadget = ReplayTranscriptVar; + + fn new(mut cached_challenges: Self::Config) -> Self { + cached_challenges.reverse(); + Self { cached_challenges } + } + + fn add_field_elements(&mut self, _: &[F]) -> &mut Self { + self + } + + fn get_field_elements(&mut self, num_elements: usize) -> Vec { + let mut result = vec![]; + for _ in 0..num_elements { + result.push(self.cached_challenges.pop().unwrap()) + } + result + } +} + +/// [`ReplayTranscriptVar`] is the in-circuit variable of [`ReplayTranscript`]. +#[derive(Clone)] +pub struct ReplayTranscriptVar { + cached_challenges: Vec>, +} + +impl> From> + for ReplayTranscriptVar +{ + fn from(value: RecordingTranscriptVar) -> Self { + Self { + cached_challenges: value.cached_challenges, + } + } +} + +impl TranscriptGadget for ReplayTranscriptVar { + type Config = Vec>; + type Widget = ReplayTranscript; + + fn new(mut cached_challenges: Vec>) -> Self + where + Self: Sized, + { + cached_challenges.reverse(); + Self { cached_challenges } + } + + fn add + ?Sized>(&mut self, _: &A) -> Result<&mut Self, SynthesisError> { + Ok(self) + } + + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { + let mut result = vec![]; + for _ in 0..num_elements { + result.push(self.cached_challenges.pop().unwrap()) + } + Ok(result) + } +} From dec2ccc9e3870276475eba94084af6da3221e905 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 26 May 2026 16:48:43 +0800 Subject: [PATCH 110/131] Fix typos --- crates/primitives/src/transcripts/recording/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/primitives/src/transcripts/recording/mod.rs b/crates/primitives/src/transcripts/recording/mod.rs index d91e5d9a1..bf2de8899 100644 --- a/crates/primitives/src/transcripts/recording/mod.rs +++ b/crates/primitives/src/transcripts/recording/mod.rs @@ -1,5 +1,5 @@ //! Implementation of transcripts that can automatically record generated -//! challenges, eliminating the need to pass challenges thoughout protocols. +//! challenges, eliminating the need to pass challenges throughout protocols. use ark_ff::PrimeField; use ark_r1cs_std::fields::fp::FpVar; From 568dd0478b61bbdbe8d79499ec52600035d0204d Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 1 Jun 2026 12:19:14 +0800 Subject: [PATCH 111/131] Absorb full field elements rather than repacked field elements --- crates/primitives/src/transcripts/mod.rs | 73 +++++++++++++----------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index e2e55b24d..57227b544 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -105,10 +105,10 @@ pub trait Transcript: Clone { } /// [`Transcript::challenge_field_element`] squeezes a challenge from the - /// transcript / sponge as a field element. + /// transcript as a field element. /// /// Internally, it first squeezes a field element and then absorbs it back - /// into the transcript / sponge to ensure security. + /// into the transcript to ensure security. fn challenge_field_element(&mut self) -> F { let c = self.get_field_elements(1); self.add_field_elements(&c); @@ -116,28 +116,31 @@ pub trait Transcript: Clone { } /// [`Transcript::challenge_bits`] squeezes a challenge from the transcript - /// / sponge as a bit vector. + /// 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( - &bits - .chunks(F::MODULUS_BIT_SIZE as usize - 1) - .map(F::BigInt::from_bits_le) - .map(F::from) - .collect::>(), - ); + /// Internally, it squeezes several field elements, absorbs them back to the + /// transcript (for strong Fiat-Shamir), and decompose them into bits. + fn challenge_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.challenge_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 } /// [`Transcript::challenge_field_elements`] squeezes `n` challenges from - /// the transcript / sponge as field elements. + /// the transcript as field elements. /// /// Internally, it first squeezes the field elements and then absorbs them - /// back into the transcript / sponge to ensure security. + /// back into the transcript to ensure security. fn challenge_field_elements(&mut self, n: usize) -> Vec { let c = self.get_field_elements(n); self.add_field_elements(&c); @@ -221,10 +224,10 @@ pub trait TranscriptGadget: Clone { } /// [`TranscriptGadget::challenge_field_element`] squeezes a challenge from - /// the transcript / sponge variable as a field element variable. + /// the transcript 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. + /// it back into the transcript variable to ensure security. fn challenge_field_element(&mut self) -> Result, SynthesisError> { let mut c = self.get_field_elements(1)?; self.add(&c[0])?; @@ -232,27 +235,31 @@ pub trait TranscriptGadget: Clone { } /// [`TranscriptGadget::challenge_bits`] squeezes a challenge from the - /// transcript / sponge variable as a vector of bit variables. + /// transcript 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( - &bits - .chunks(F::MODULUS_BIT_SIZE as usize - 1) - .map(Boolean::le_bits_to_fp) - .collect::, _>>()?, - )?; + /// Internally, it squeezes several field element variables, absorbs them + /// back to the transcript variable (for strong Fiat-Shamir), and decompose + /// them into bit variables. + fn challenge_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.challenge_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) } /// [`TranscriptGadget::challenge_field_elements`] squeezes `n` challenges - /// from the transcript / sponge variable as field element variables. + /// from the transcript 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 + /// absorbs them back into the transcript variable to ensure /// security. fn challenge_field_elements(&mut self, n: usize) -> Result>, SynthesisError> { let c = self.get_field_elements(n)?; From a038427930a7fef97802850e62236271ff9b304d Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 1 Jun 2026 12:21:02 +0800 Subject: [PATCH 112/131] Fix grammar --- crates/primitives/src/transcripts/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 57227b544..5dcc088cc 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -119,7 +119,7 @@ pub trait Transcript: Clone { /// as a bit vector. /// /// Internally, it squeezes several field elements, absorbs them back to the - /// transcript (for strong Fiat-Shamir), and decompose them into bits. + /// transcript (for strong Fiat-Shamir), and decomposes them into bits. fn challenge_bits(&mut self, num_bits: usize) -> Vec { let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; @@ -238,7 +238,7 @@ pub trait TranscriptGadget: Clone { /// transcript variable as a vector of bit variables. /// /// Internally, it squeezes several field element variables, absorbs them - /// back to the transcript variable (for strong Fiat-Shamir), and decompose + /// back to the transcript variable (for strong Fiat-Shamir), and decomposes /// them into bit variables. fn challenge_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; From 0f35c77700ef200c4536737bad7fe9461497cefa Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 25 Jun 2026 21:13:28 +0800 Subject: [PATCH 113/131] Remove the unused `FpVar::from_bounded_bits_le` implementation --- .../src/compilers/cyclefold/adapters/nova.rs | 2 +- .../primitives/src/algebra/field/emulated.rs | 23 +++++++++++-------- crates/primitives/src/algebra/ops/bits.rs | 10 -------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 75525325f..db6d3216c 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -14,7 +14,7 @@ use sonobe_primitives::{ algebra::{ field::emulated::{Bounds, EmulatedFieldVar}, group::emulated::EmulatedAffineVar, - ops::bits::{FromBits, FromBitsGadget, ToBitsGadgetExt}, + ops::bits::{FromBits, ToBitsGadgetExt}, }, circuits::WitnessToPublic, commitments::GroupBasedCommitment, diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 1e919f2c4..622c9e966 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -271,6 +271,20 @@ impl LimbedVar { } impl LimbedVar { + /// [`LimbedVar::from_bounded_bits_le`] computes a `LimbedVar` from its + /// little-endian bits with explicitly supplied [`Bounds`]. + pub 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(&bounds.0, &bounds.1, F::BITS_PER_LIMB), + )) + } + /// [`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]. @@ -834,15 +848,6 @@ impl FromBitsGadget for LimbedVar { ), ) } - - 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(&bounds.0, &bounds.1, F::BITS_PER_LIMB), - )) - } } impl CondSelectGadget for LimbedVar { diff --git a/crates/primitives/src/algebra/ops/bits.rs b/crates/primitives/src/algebra/ops/bits.rs index 7616d08fc..0ecf35bee 100644 --- a/crates/primitives/src/algebra/ops/bits.rs +++ b/crates/primitives/src/algebra/ops/bits.rs @@ -5,8 +5,6 @@ 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::Bounds; - /// [`FromBits`] reconstructs a value from bits. pub trait FromBits { /// [`FromBits::from_bits_le`] computes a value from its little-endian bits. @@ -25,10 +23,6 @@ pub trait FromBitsGadget: Sized { /// [`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`] @@ -54,10 +48,6 @@ impl FromBitsGadget for FpVar { 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 { From de250962f5fa61076640245fae0bf1e76ead8496 Mon Sep 17 00:00:00 2001 From: winderica Date: Wed, 24 Jun 2026 15:41:32 +0800 Subject: [PATCH 114/131] Fix the off by one bug when enforcing equality of unaligned emulated integers --- .../primitives/src/algebra/field/emulated.rs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 622c9e966..4ea52628f 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -617,8 +617,13 @@ impl LimbedVar Result<(), Box> { + let cs = ConstraintSystem::::new_ref(); + + // Base-`2^BITS_PER_LIMB` digits of `p = Fr::MODULUS`. + let mask = (BigUint::one() << Fr::BITS_PER_LIMB) - BigUint::one(); + let mut vals = vec![]; + + let mut t: BigUint = Fr::MODULUS.into(); + t <<= Fr::BITS_PER_LIMB; + while !t.is_zero() { + vals.push(Fr::from(&t & &mask)); + t >>= Fr::BITS_PER_LIMB; + } + assert_eq!(compose(&vals[..]) >> Fr::BITS_PER_LIMB, Fr::MODULUS.into()); + + let mut bounds = vec![Bounds(BigInt::zero(), BigInt::zero())]; + // The huge bound on the first digit forces the first group to finalize + // immediately + bounds.push(Bounds( + BigInt::zero(), + BigInt::one() << (Fr::MODULUS_BIT_SIZE - 2), + )); + bounds.resize( + vals.len(), + Bounds(BigInt::zero(), BigInt::one() << (Fr::BITS_PER_LIMB + 1)), + ); + + let v = EmulatedIntVar::new(Vec::new_witness(cs.clone(), || Ok(vals))?, bounds); + + assert_eq!(v.value()? >> Fr::BITS_PER_LIMB, Fr::MODULUS.into()); + assert!(cs.is_satisfied()?); + + v.enforce_equal_unaligned(&EmulatedIntVar::constant(Zero::zero()))?; + + // A non-zero multiple of `p` must NOT be accepted as equal to zero. + assert!(!cs.is_satisfied()?); + + Ok(()) + } + #[test] fn test_alloc() -> Result<(), Box> { let rng = &mut thread_rng(); From e6fb5decb17290e5a3d046d07fc323490378e0a5 Mon Sep 17 00:00:00 2001 From: winderica Date: Wed, 24 Jun 2026 16:45:14 +0800 Subject: [PATCH 115/131] Revert to arkworks' unoptimized but sound scalar multiplication gadget for the stable release --- crates/primitives/src/algebra/group/mod.rs | 152 +----------------- crates/primitives/src/commitments/pedersen.rs | 20 +-- 2 files changed, 7 insertions(+), 165 deletions(-) diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index c37b3799b..d366126c1 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -5,16 +5,11 @@ use ark_ec::{ AffineRepr, CurveGroup, PrimeGroup, short_weierstrass::{Projective, SWCurveConfig}, }; -use ark_ff::{AdditiveGroup, Field, One, PrimeField, Zero}; +use ark_ff::{Field, One, PrimeField, Zero}; use ark_r1cs_std::{ - GR1CSVar, - boolean::Boolean, convert::ToConstraintFieldGadget, - fields::{FieldVar, fp::FpVar}, - groups::{ - CurveVar, - curves::short_weierstrass::{ProjectiveVar, non_zero_affine::NonZeroAffineVar}, - }, + fields::fp::FpVar, + groups::{CurveVar, curves::short_weierstrass::ProjectiveVar}, }; use ark_relations::gr1cs::SynthesisError; @@ -42,8 +37,7 @@ pub trait SonobeCurve: + Val< Var: CurveVar + AbsorbableVar - + WitnessToPublic - + JointScalarMul, + + WitnessToPublic, EmulatedVar = EmulatedAffineVar, > { @@ -123,141 +117,3 @@ impl> WitnessToPublic self.to_constraint_field()?[..2].mark_as_public() } } - -/// [`JointScalarMul`] extends arkworks' [`CurveVar`] for more efficient MSM -/// operations in-circuit, without relying on a fork. -pub trait JointScalarMul: Sized { - /// [`JointScalarMul::joint_scalar_mul_be`] computes `s1 * self + s2 * p`, - /// where `s1` and `s2` are the scalars represented in big-endian `Boolean`. - /// - /// `self` and `p` are non-zero and `self` ≠ `-p`. - fn joint_scalar_mul_be<'a>( - &self, - p: &Self, - s1: impl Iterator>, - s2: impl Iterator>, - ) -> Result; -} - -impl> JointScalarMul - for ProjectiveVar> -{ - fn joint_scalar_mul_be<'a>( - &self, - p: &Self, - bits1: impl Iterator::BasePrimeField>>, - bits2: impl Iterator::BasePrimeField>>, - ) -> Result { - // prepare bits decomposition - let mut bits1 = bits1.collect::>(); - if bits1.is_empty() { - return Ok(Self::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()) - .collect(); - - let mut bits2 = bits2.collect::>(); - if bits2.is_empty() { - return Ok(Self::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()) - .collect(); - - // precompute points - let aff1 = self.to_affine()?; - let nz_aff1 = NonZeroAffineVar::::new(aff1.x, aff1.y); - - let aff2 = p.to_affine()?; - let nz_aff2 = NonZeroAffineVar::new(aff2.x, aff2.y); - - let mut aff1_neg = NonZeroAffineVar::new(nz_aff1.x.clone(), nz_aff1.y.negate()?); - let mut aff2_neg = NonZeroAffineVar::new(nz_aff2.x.clone(), nz_aff2.y.negate()?); - let acc = nz_aff1.double()?; - - let sum = nz_aff1.add_unchecked(&nz_aff2)?; - let diff = nz_aff1.add_unchecked(&aff2_neg)?; - let NonZeroAffineVar { mut x, mut y, .. } = acc; - - // 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)?; - - let acc = acc.into_projective(); - let mut p = diff; - for _ in 0..bits1.len() - 1 { - p = p.double()?; - } - - // [`ProjectiveVar::add_mixed`] - let (x1, y1, z1) = (&acc.x, &acc.y, &acc.z); - let (x2, y2) = (&p.x, &p.y.negate()?); - let three_b = P::COEFF_B.double() + P::COEFF_B; - - let xx = x1 * x2; // 1 - let yy = y1 * y2; // 2 - let xy_pairs = (x1 + y1) * (x2 + y2) - (&xx + &yy); // 4, 5, 6, 7, 8 - let xz_pairs = x2 * z1 + x1; // 8, 9 - let yz_pairs = y2 * z1 + y1; // 10, 11 - - let bz3_part = &xz_pairs * P::COEFF_A + z1 * three_b; // 12, 13, 14 - - let yy_m_bz3 = &yy - &bz3_part; // 15 - let yy_p_bz3 = &yy + &bz3_part; // 16 - - let azz = z1 * P::COEFF_A; // 20 - let xx3_p_azz = xx.double()? + &xx + &azz; // 18, 19, 22 - - let b3_xz_pairs = (&xx - &azz) * P::COEFF_A + &xz_pairs * three_b; // 21, 23, 24, 25 - - Ok(ProjectiveVar::new( - &yy_m_bz3 * &xy_pairs - &yz_pairs * &b3_xz_pairs, // 28, 29, 30 - &yy_p_bz3 * &yy_m_bz3 + &xx3_p_azz * b3_xz_pairs, // 17, 26, 27 - &yy_p_bz3 * &yz_pairs + xy_pairs * xx3_p_azz, // 31, 32, 33 - )) - } -} diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 80ce66d59..f83192391 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -22,7 +22,7 @@ use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Er use crate::{ algebra::{ field::emulated::EmulatedFieldVar, - group::{JointScalarMul, emulated::EmulatedAffineVar}, + group::emulated::EmulatedAffineVar, }, commitments::{CommitmentOpsGadget, GroupBasedCommitment}, traits::{CF1, CF2, SonobeCurve}, @@ -225,22 +225,8 @@ impl PedersenGadget { /// 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(); - 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(), - )?; + for (g_i, v_i) in g.iter().zip(v) { + res += g_i.scalar_mul_le(v_i.to_bits_le()?.iter())?; } Ok(res) } From 590d780b35bc2e811c15ae60e12952258ae15b6f Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 26 Jun 2026 00:41:25 +0800 Subject: [PATCH 116/131] Fmt --- crates/primitives/src/algebra/group/mod.rs | 4 +--- crates/primitives/src/commitments/pedersen.rs | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index d366126c1..e116799e3 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -35,9 +35,7 @@ pub trait SonobeCurve: + Inputize + InputizeEmulated + Val< - Var: CurveVar - + AbsorbableVar - + WitnessToPublic, + Var: CurveVar + AbsorbableVar + WitnessToPublic, EmulatedVar = EmulatedAffineVar, > { diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index f83192391..6048eda67 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -20,10 +20,7 @@ use ark_std::{UniformRand, borrow::Borrow, iter::repeat_with, marker::PhantomDat use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error}; use crate::{ - algebra::{ - field::emulated::EmulatedFieldVar, - group::emulated::EmulatedAffineVar, - }, + algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, commitments::{CommitmentOpsGadget, GroupBasedCommitment}, traits::{CF1, CF2, SonobeCurve}, utils::null::Null, From 417a21f45f14666c5ee2fa04b7cc63bfb79a3f91 Mon Sep 17 00:00:00 2001 From: winderica Date: Wed, 24 Jun 2026 21:49:03 +0800 Subject: [PATCH 117/131] Fix state inconsistency again --- .../ivc/src/compilers/cyclefold/circuits.rs | 5 --- crates/ivc/src/compilers/cyclefold/mod.rs | 44 +++++++++++-------- crates/ivc/src/lib.rs | 10 ++--- crates/primitives/src/circuits/mod.rs | 21 ++++++++- crates/primitives/src/circuits/utils.rs | 5 +++ 5 files changed, 55 insertions(+), 30 deletions(-) diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 7f989fe0a..cce372512 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -8,7 +8,6 @@ use ark_r1cs_std::{ fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_serialize::CanonicalSerialize; use ark_std::marker::PhantomData; use sonobe_fs::{ FoldingInstanceVar, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, @@ -118,8 +117,6 @@ where let is_basecase = i.is_zero()?; - let state_size = FpVar::Constant((initial_state.uncompressed_size() as u64).into()); - let initial_state = FC::StateVar::new_witness(cs.clone(), || Ok(initial_state))?; let current_state = FC::StateVar::new_witness(cs.clone(), || Ok(current_state))?; @@ -139,7 +136,6 @@ where // the `i-1`-th step, which is `u.x = H(i, z_0, z_i, U, cf_U)`. let u_x = sponge .clone() - .add(&state_size)? .add(&i)? .add(&initial_state)? .add(¤t_state)? @@ -189,7 +185,6 @@ where // 4. Compute public input `uu.x = H(i+1, z_0, z_{i+1}, UU, cf_UU)`. let uu_x = sponge .clone() - .add(&state_size)? .add(&ii)? .add(&initial_state)? .add(&next_state)? diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 2cb4b12e6..aca3bddd6 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -95,11 +95,8 @@ 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, -); +#[derive(Clone)] +pub struct Key(pub DK1, pub DK2, pub T); /// [`Proof`] is the proof produced by the CycleFold compiler. pub struct Proof( @@ -111,8 +108,10 @@ pub struct Proof( pub FS2::RU, ); -impl Dummy<&Key> for Proof { - fn dummy(pk: &Key) -> Self { +impl + Dummy<&Key> for Proof +{ + fn dummy(pk: &Key) -> Self { let cfg1 = &pk.0.to_arith_config(); let cfg2 = &pk.1.to_arith_config(); Self( @@ -173,11 +172,13 @@ 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, @@ -261,16 +262,15 @@ where hash_config.serialize_compressed(HashMarshaller(&mut shake))?; hash_to_field::<_, _, 128>(&mut shake.finalize_xof()) }; + let reference_state = step_circuit.dummy_state(); + let key = Key(dk1, dk2, (hash_config, pp_hash, reference_state)); - Ok(( - Key(dk1.clone(), dk2.clone(), (hash_config.clone(), pp_hash)), - Key(dk1, dk2, (hash_config, pp_hash)), - )) + Ok((key.clone(), key)) } #[allow(non_snake_case)] 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, @@ -358,7 +358,7 @@ where #[allow(non_snake_case)] fn verify>( - Key(dk1, dk2, (hash_config, pp_hash)): &Self::VerifierKey, + Key(dk1, dk2, (hash_config, pp_hash, reference_state)): &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, @@ -370,7 +370,16 @@ where .ok_or(Error::IVCVerificationFail); } - if initial_state.uncompressed_size() != current_state.uncompressed_size() { + // Ensure the prover supplied `initial_state` and `current_state` have + // the same shape as `reference_state`'s, which is exactly what the + // augmented circuit was synthesized for. + // + // A state that merely re-groups the same flattened field elements (e.g. + // `[[x, y], []]` vs `[[x], [y]]`) has a different shape and is rejected + // here. + if !FC::same_state_shape(reference_state, initial_state) + || !FC::same_state_shape(reference_state, current_state) + { return Err(Error::IVCVerificationFail); } @@ -378,7 +387,6 @@ where let mut sponge = hash.separate_domain("sponge".as_ref()); let u_x = sponge - .add(&initial_state.uncompressed_size()) .add(&i) .add(initial_state) .add(current_state) diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index de8a4dba1..9ea2ac6ce 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -56,15 +56,15 @@ pub trait IVC { /// [`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; + 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; + 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>; + 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 @@ -135,7 +135,7 @@ pub trait 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 struct IVCStatefulProver<'a, FC: FCircuit, I: IVC> { pk: &'a I::ProverKey, step_circuit: &'a FC, i: usize, @@ -211,7 +211,7 @@ pub trait Decider { /// /// This can be seen as a SNARK with circuit-specific setup. // TODO (@winderica): consider universal/transparent setup - fn preprocess_and_generate_keys( + fn preprocess_and_generate_keys::Field>>( ivc_pk: &::ProverKey, rng: impl RngCore, ) -> Result<(Self::ProverKey, Self::VerifierKey), Error>; diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 4b729004f..20a6eded9 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -5,7 +5,6 @@ use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, }; -use ark_serialize::CanonicalSerialize; use ark_std::{ fmt::Debug, ops::{Deref, Index, IndexMut}, @@ -68,7 +67,7 @@ pub trait FCircuit { /// 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 + CanonicalSerialize; + 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 @@ -84,7 +83,25 @@ pub trait FCircuit { /// by each step of the circuit. type ExternalOutputs; + /// [`FCircuit::same_state_shape`] returns whether two states `a` and `b` + /// have the same shape/structure. + /// + /// This allows the verifier to check whether the prover's claimed states + /// have the desired shape. + /// + /// The implementation should perform the checks carefully to ensure that + /// all fields/members of the provided states are consistent. Specifically, + /// if all fields/members of [`FCircuit::State`] have fixed size, then the + /// shape consistency is trivially `true`. However, if [`FCircuit::State`] + /// contains variable-length fields/members (e.g., `Vec`, `Vec>`), + /// then the implementation must examine all of such fields/members and + /// return `false` when any of them are differently sized in `a` and `b`. + fn same_state_shape(a: &Self::State, b: &Self::State) -> bool; + /// [`FCircuit::dummy_state`] returns a dummy state for the circuit. + /// + /// The dummy state should have the same shape as states in real IVC + /// executions. fn dummy_state(&self) -> Self::State; /// [`FCircuit::dummy_external_inputs`] returns dummy external inputs for diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 16b291895..210ae345f 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -65,6 +65,11 @@ impl FCircuit for CircuitForTest { [F::zero(); 1] } + fn same_state_shape(_a: &Self::State, _b: &Self::State) -> bool { + // `[F; 1]` is fixed-size, so all states share the same shape. + true + } + fn dummy_external_inputs(&self) -> Self::ExternalInputs {} fn generate_step_constraints( From 6364d04ab6141844c1a4b49971e802ca1cefaf9a Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 25 Jun 2026 00:07:07 +0800 Subject: [PATCH 118/131] Absorb domain separator's length consistently across different platforms --- crates/primitives/src/transcripts/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 5dcc088cc..393d19c95 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -91,7 +91,9 @@ pub trait Transcript: Clone { fn separate_domain(&self, domain: &[u8]) -> Self { let mut new_sponge = self.clone(); - let mut input = domain.len().to_le_bytes().to_vec(); + // Encode the domain length with a fixed-width `u64` so the derived + // challenges are identical across targets. + let mut input = (domain.len() as u64).to_le_bytes().to_vec(); input.extend_from_slice(domain); let limbs = input @@ -210,7 +212,9 @@ pub trait TranscriptGadget: Clone { fn separate_domain(&self, domain: &[u8]) -> Result { let mut new_sponge = self.clone(); - let mut input = domain.len().to_le_bytes().to_vec(); + // Encode the domain length with a fixed-width `u64` so the derived + // challenges are identical across targets. + let mut input = (domain.len() as u64).to_le_bytes().to_vec(); input.extend_from_slice(domain); let limbs = input From cf62d653cf6a7a6383cccc272c27a9da25c58eb7 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 25 Jun 2026 00:40:30 +0800 Subject: [PATCH 119/131] Ensure challenges order consistency when creating replay transcript via `from` and `new` --- crates/primitives/src/transcripts/replay/mod.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/primitives/src/transcripts/replay/mod.rs b/crates/primitives/src/transcripts/replay/mod.rs index 9c4e8197e..5ba177913 100644 --- a/crates/primitives/src/transcripts/replay/mod.rs +++ b/crates/primitives/src/transcripts/replay/mod.rs @@ -20,9 +20,7 @@ pub struct ReplayTranscript { impl> From> for ReplayTranscript { fn from(value: RecordingTranscript) -> Self { - Self { - cached_challenges: value.cached_challenges, - } + Self::new(value.cached_challenges) } } @@ -58,9 +56,7 @@ impl> From> for ReplayTranscriptVar { fn from(value: RecordingTranscriptVar) -> Self { - Self { - cached_challenges: value.cached_challenges, - } + Self::new(value.cached_challenges) } } @@ -68,10 +64,7 @@ impl TranscriptGadget for ReplayTranscriptVar { type Config = Vec>; type Widget = ReplayTranscript; - fn new(mut cached_challenges: Vec>) -> Self - where - Self: Sized, - { + fn new(mut cached_challenges: Vec>) -> Self { cached_challenges.reverse(); Self { cached_challenges } } From 7f5a6c7a06235d066a9bf4da05e35b5b927f996a Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 25 Jun 2026 13:56:14 +0800 Subject: [PATCH 120/131] Improve the robustness of poseidon config generation --- Cargo.lock | 1 + crates/primitives/Cargo.toml | 3 +- .../src/transcripts/poseidon/mod.rs | 17 ++++--- .../src/transcripts/poseidon/sponge.rs | 47 ++++++++++--------- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1dd1e8801..f83268b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1124,6 +1124,7 @@ dependencies = [ "ark-crypto-primitives", "ark-ec", "ark-ff", + "ark-grumpkin", "ark-pallas", "ark-poly", "ark-r1cs-std", diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 711de1524..951d24a9b 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -6,6 +6,7 @@ license.workspace = true repository.workspace = true [dependencies] +ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } ark-ec = { workspace = true } ark-ff = { workspace = true, features = ["asm"] } @@ -23,7 +24,7 @@ sha3 = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } +ark-grumpkin = { workspace = true, features = ["r1cs"] } ark-pallas = { workspace = true, features = ["curve", "r1cs"] } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] diff --git a/crates/primitives/src/transcripts/poseidon/mod.rs b/crates/primitives/src/transcripts/poseidon/mod.rs index 799915f0e..ca9b881aa 100644 --- a/crates/primitives/src/transcripts/poseidon/mod.rs +++ b/crates/primitives/src/transcripts/poseidon/mod.rs @@ -95,6 +95,8 @@ fn find_fd_round_numbers( } } + assert_ne!(min_cost, usize::MAX); + (r_f, r_p) } @@ -106,13 +108,14 @@ pub fn poseidon_paper_config( alpha: u64, rate: usize, ) -> PoseidonConfig { + assert_ne!(alpha, 1); assert_eq!( BigUint::from(alpha).gcd(&(-F::one()).into()), BigUint::one() ); let (full_rounds, partial_rounds) = - find_fd_round_numbers::(rate, alpha, SECURITY_BITS, get_sbox_cost, true); - let (ark, mds) = find_poseidon_ark_and_mds::( + find_fd_round_numbers::(rate + 1, alpha, SECURITY_BITS, get_sbox_cost, true); + let (ark, mds) = find_poseidon_ark_and_mds( F::MODULUS_BIT_SIZE as u64, rate, full_rounds, @@ -131,9 +134,9 @@ pub fn poseidon_paper_config( ) } -/// [`poseidon_circom_config`] produces a Poseidon configuration which agrees -/// with Circom's Poseidon(4) when `F` is the scalar field of BN254. -pub fn poseidon_circom_config() -> PoseidonConfig { +/// [`poseidon_circom_config`] produces a Poseidon configuration for BN254's +/// scalar field that agrees with Circom's Poseidon(4). +pub fn poseidon_circom_config() -> PoseidonConfig { // 120 bit security target as in // https://eprint.iacr.org/2019/458.pdf // t = rate + 1 @@ -143,8 +146,8 @@ pub fn poseidon_circom_config() -> PoseidonConfig { let alpha = 5; let rate = 4; - let (ark, mds) = find_poseidon_ark_and_mds::( - F::MODULUS_BIT_SIZE as u64, + let (ark, mds) = find_poseidon_ark_and_mds( + ark_bn254::Fr::MODULUS_BIT_SIZE as u64, rate, full_rounds as u64, partial_rounds as u64, diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index eb827a19f..daf8bc537 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -84,9 +84,10 @@ impl TranscriptGadget for PoseidonSpongeVar { #[cfg(test)] mod tests { - use ark_bn254::{Fq, Fr, G1Projective as G1, g1::Config}; + use ark_bn254::{Fr, G1Projective as G1}; use ark_crypto_primitives::sponge::poseidon::{PoseidonSponge, constraints::PoseidonSpongeVar}; use ark_ff::UniformRand; + use ark_grumpkin::Projective as G2; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, fields::fp::FpVar, groups::curves::short_weierstrass::ProjectiveVar, @@ -104,7 +105,7 @@ 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> { - let config = poseidon_circom_config::(); + let config = poseidon_circom_config(); let mut poseidon_sponge = PoseidonSponge::new(config); let v = vec![1, 2, 3, 4] .into_iter() @@ -125,15 +126,15 @@ mod tests { #[test] fn test_challenge_field_element() -> Result<(), Box> { // Create a transcript outside of the circuit - let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(config.clone()); + let config = poseidon_circom_config(); + let mut tr = PoseidonSponge::new(config.clone()); 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)))?; + 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()?; @@ -148,15 +149,15 @@ mod tests { let nbits = 128; // Create a transcript outside of the circuit - let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(config.clone()); - tr.add(&Fq::from(42_u32)); + let config = poseidon_circom_config(); + let mut tr = PoseidonSponge::new(config.clone()); + tr.add(&Fr::from(42_u32)); let c = tr.challenge_bits(nbits); // 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(Fq::from(42_u32)))?; + 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_bits(nbits)?; @@ -169,18 +170,18 @@ mod tests { #[test] fn test_absorb_canonical_point() -> Result<(), Box> { // Create a transcript outside of the circuit - let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(config.clone()); + let config = poseidon_circom_config(); + let mut tr = PoseidonSponge::new(config.clone()); let rng = &mut thread_rng(); - let p = G1::rand(rng); + let p = G2::rand(rng); tr.add(&p); 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 p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; + 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()?; @@ -193,8 +194,8 @@ mod tests { #[test] fn test_absorb_emulated_point() -> Result<(), Box> { // Create a transcript outside of the circuit - let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(config.clone()); + let config = poseidon_circom_config(); + let mut tr = PoseidonSponge::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -202,8 +203,8 @@ mod tests { 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 cs = ConstraintSystem::new_ref(); + 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()?; From f89b262e4bcef939f93c070a835184ffac549fe2 Mon Sep 17 00:00:00 2001 From: winderica Date: Thu, 25 Jun 2026 21:53:52 +0800 Subject: [PATCH 121/131] Fix off by one when chunking the domain separator --- crates/primitives/src/transcripts/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 393d19c95..a20426248 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -96,8 +96,10 @@ pub trait Transcript: Clone { let mut input = (domain.len() as u64).to_le_bytes().to_vec(); input.extend_from_slice(domain); + // Chunk into `(MODULUS_BIT_SIZE - 1) / 8` bytes so a full chunk is + // always `< 2^(MODULUS_BIT_SIZE - 1) <= MODULUS` let limbs = input - .chunks(F::MODULUS_BIT_SIZE as usize / 8) + .chunks((F::MODULUS_BIT_SIZE as usize - 1) / 8) .map(|chunk| F::from_le_bytes_mod_order(chunk)) .collect::>(); @@ -217,8 +219,10 @@ pub trait TranscriptGadget: Clone { let mut input = (domain.len() as u64).to_le_bytes().to_vec(); input.extend_from_slice(domain); + // Chunk into `(MODULUS_BIT_SIZE - 1) / 8` bytes so a full chunk is + // always `< 2^(MODULUS_BIT_SIZE - 1) <= MODULUS` let limbs = input - .chunks(F::MODULUS_BIT_SIZE as usize / 8) + .chunks((F::MODULUS_BIT_SIZE as usize - 1) / 8) .map(|chunk| FpVar::Constant(F::from_le_bytes_mod_order(chunk))) .collect::>(); From 6cb77661f9ccb3f77940c0e6b35036675f6251ce Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 26 Jun 2026 00:29:42 +0800 Subject: [PATCH 122/131] Manually implement R1CS deserialization for custom validity check --- .../src/arithmetizations/r1cs/mod.rs | 98 ++++++++++++++++--- 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 0439e7793..97aa20cdd 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -3,7 +3,9 @@ use ark_ff::Field; use ark_relations::gr1cs::{ConstraintSystem, Matrix, R1CS_PREDICATE_LABEL}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_serialize::{ + CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, +}; use ark_std::{cfg_into_iter, cfg_iter}; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -15,7 +17,7 @@ pub mod circuits; /// [`R1CS`] holds the three sparse matrices `A`, `B`, `C` together with the /// configuration. -#[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize)] pub struct R1CS { m: usize, // number of constraints n: usize, // number of variables @@ -53,27 +55,29 @@ impl R1CS { n_public_inputs: usize, matrices: [Matrix; 3], ) -> Result { - for matrix in &matrices { - if matrix.len() != n_constraints { - return Err(Error::InvalidNumberOfConstraints( - n_constraints, - matrix.len(), - )); + let r1cs = + Self::new_without_validity_check(n_constraints, n_variables, n_public_inputs, matrices); + r1cs.validate()?; + Ok(r1cs) + } + + /// [`R1CS::validate`] checks that the structural invariant of the R1CS + /// holds, i.w., every matrix has exactly `m` rows (one per constraint), and + /// no column index reaches beyond the `n` variables. + pub fn validate(&self) -> Result<(), Error> { + for matrix in &self.matrices { + if matrix.len() != self.m { + return Err(Error::InvalidNumberOfConstraints(self.m, matrix.len())); } for row in matrix { if let Some(max) = row.iter().map(|(_, i)| *i).max() - && max >= n_variables + && max >= self.n { - return Err(Error::InvalidNumberOfVariables(n_variables, max + 1)); + return Err(Error::InvalidNumberOfVariables(self.n, max + 1)); } } } - Ok(Self::new_without_validity_check( - n_constraints, - n_variables, - n_public_inputs, - matrices, - )) + Ok(()) } /// [`R1CS::new_without_validity_check`] creates a new R1CS structure from @@ -106,6 +110,33 @@ impl R1CS { } } +impl Valid for R1CS { + fn check(&self) -> Result<(), SerializationError> { + self.matrices.check()?; + self.validate().map_err(|_| SerializationError::InvalidData) + } +} + +impl CanonicalDeserialize for R1CS { + fn deserialize_with_mode( + mut reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + let m = usize::deserialize_with_mode(&mut reader, compress, Validate::No)?; + let n = usize::deserialize_with_mode(&mut reader, compress, Validate::No)?; + let l = usize::deserialize_with_mode(&mut reader, compress, Validate::No)?; + let matrices = + <[Matrix; 3]>::deserialize_with_mode(&mut reader, compress, Validate::No)?; + + let r1cs = Self::new_without_validity_check(m, n, l, matrices); + if validate == Validate::Yes { + r1cs.check()?; + } + Ok(r1cs) + } +} + impl From<&ConstraintSystem> for R1CS { fn from(cs: &ConstraintSystem) -> Self { // Get the R1CS predicate matrices @@ -205,6 +236,7 @@ mod tests { use ark_ff::UniformRand; use ark_std::{error::Error, rand::thread_rng}; + use super::*; use crate::{ circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, relations::Relation, @@ -236,4 +268,38 @@ mod tests { Ok(()) } + + #[test] + fn test_deserialize_rejects_malformed() -> Result<(), Box> { + let valid = R1CS::::new(1, 1, 0, [vec![vec![]], vec![vec![]], vec![vec![]]]).unwrap(); + let mut bytes = vec![]; + valid.serialize_compressed(&mut bytes)?; + assert_eq!(valid, R1CS::::deserialize_compressed(&bytes[..])?); + + let mismatched_constraints = R1CS::::new_without_validity_check( + 2, + 1, + 0, + [vec![vec![]], vec![vec![]], vec![vec![]]], + ); + let mut bytes = vec![]; + mismatched_constraints + .serialize_compressed(&mut bytes) + .unwrap(); + assert!(R1CS::::deserialize_compressed_unchecked(&bytes[..]).is_ok()); + assert!(R1CS::::deserialize_compressed(&bytes[..]).is_err()); + + let out_of_range_variable = R1CS::::new_without_validity_check( + 1, + 1, + 0, + [vec![vec![(Fr::from(1u64), 5)]], vec![vec![]], vec![vec![]]], + ); + let mut bytes = vec![]; + out_of_range_variable.serialize_compressed(&mut bytes)?; + assert!(R1CS::::deserialize_compressed_unchecked(&bytes[..]).is_ok()); + assert!(R1CS::::deserialize_compressed(&bytes[..]).is_err()); + + Ok(()) + } } From 7383e0061befc40d2690ac1261e55bef42c53dad Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 27 Jun 2026 13:57:24 +0800 Subject: [PATCH 123/131] Check state shape for i = 0 as well --- crates/ivc/src/compilers/cyclefold/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index aca3bddd6..efb14d566 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -364,12 +364,6 @@ where current_state: &FC::State, 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); - } - // Ensure the prover supplied `initial_state` and `current_state` have // the same shape as `reference_state`'s, which is exactly what the // augmented circuit was synthesized for. @@ -383,6 +377,12 @@ where return Err(Error::IVCVerificationFail); } + if i == 0 { + return (initial_state == current_state) + .then_some(()) + .ok_or(Error::IVCVerificationFail); + } + let hash = T::new_with_pp_hash(hash_config.clone(), *pp_hash); let mut sponge = hash.separate_domain("sponge".as_ref()); From c562bd16353b14e055355e37618a66501d8793c6 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 23 May 2026 14:55:47 +0800 Subject: [PATCH 124/131] Preliminary decider abstraction & implementaiton --- Cargo.lock | 140 +++- Cargo.toml | 3 + README.md | 54 +- crates/fs/src/definitions/circuits.rs | 35 +- crates/fs/src/definitions/mod.rs | 16 +- crates/fs/src/lib.rs | 2 +- crates/fs/src/nova/keys/circuits.rs | 90 ++ crates/fs/src/nova/keys/mod.rs | 2 + crates/fs/src/nova/mod.rs | 21 +- crates/ivc/Cargo.toml | 4 +- .../src/compilers/cyclefold/adapters/nova.rs | 53 +- .../ivc/src/compilers/cyclefold/circuits.rs | 2 +- crates/ivc/src/compilers/cyclefold/mod.rs | 409 ++++++++- crates/ivc/src/lib.rs | 547 +++++++++--- crates/primitives/Cargo.toml | 2 + .../primitives/src/algebra/field/emulated.rs | 110 ++- crates/primitives/src/algebra/field/mod.rs | 78 +- .../primitives/src/algebra/group/emulated.rs | 9 +- crates/primitives/src/algebra/group/mod.rs | 42 +- crates/primitives/src/algebra/ops/eq.rs | 2 +- crates/primitives/src/algebra/ops/matrix.rs | 29 +- crates/primitives/src/algebra/ops/vector.rs | 67 +- .../src/arithmetizations/ccs/mod.rs | 63 +- crates/primitives/src/arithmetizations/mod.rs | 10 +- .../src/arithmetizations/r1cs/circuits.rs | 157 ++-- .../src/arithmetizations/r1cs/mod.rs | 2 +- crates/primitives/src/circuits/alloc.rs | 35 + crates/primitives/src/circuits/mod.rs | 72 +- crates/primitives/src/circuits/utils.rs | 2 +- crates/primitives/src/commitments/mod.rs | 22 +- crates/primitives/src/commitments/pedersen.rs | 148 +++- crates/primitives/src/traits.rs | 40 +- crates/snarks/Cargo.toml | 39 + crates/snarks/src/cp/groth16.rs | 779 ++++++++++++++++++ crates/snarks/src/cp/mod.rs | 43 + crates/snarks/src/lib.rs | 2 + crates/snarks/src/linear_subspace/mod.rs | 204 +++++ 37 files changed, 2883 insertions(+), 452 deletions(-) create mode 100644 crates/fs/src/nova/keys/circuits.rs create mode 100644 crates/primitives/src/circuits/alloc.rs create mode 100644 crates/snarks/Cargo.toml create mode 100644 crates/snarks/src/cp/groth16.rs create mode 100644 crates/snarks/src/cp/mod.rs create mode 100644 crates/snarks/src/lib.rs create mode 100644 crates/snarks/src/linear_subspace/mod.rs diff --git a/Cargo.lock b/Cargo.lock index f83268b07..f6398bbfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,23 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ark-groth16" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a293328aa422e65527e285614ce5d1dceb0bd7b8b18d18b1b63191ee1f74cb41" +dependencies = [ + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-relations", + "ark-serialize", + "ark-snark", + "ark-std", + "rayon", +] + [[package]] name = "ark-grumpkin" version = "0.6.0" @@ -211,7 +228,7 @@ dependencies = [ "ark-poly", "ark-serialize", "ark-std", - "foldhash", + "foldhash 0.1.5", "indexmap 2.14.0", "rayon", "tracing", @@ -261,6 +278,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" dependencies = [ + "colored", "num-traits", "rand", "rayon", @@ -388,6 +406,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -553,6 +581,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "futures-core" version = "0.3.32" @@ -613,6 +647,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", + "equivalent", + "foldhash 0.2.0", ] [[package]] @@ -762,7 +798,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1112,6 +1148,7 @@ dependencies = [ "sha3", "sonobe-fs", "sonobe-primitives", + "sonobe-snarks", "thiserror", "wasm-bindgen-test", ] @@ -1132,6 +1169,7 @@ dependencies = [ "ark-serialize", "ark-std", "getrandom", + "hashbrown 0.17.1", "itertools", "num-bigint", "num-integer", @@ -1142,6 +1180,29 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "sonobe-snarks" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-groth16", + "ark-grumpkin", + "ark-poly", + "ark-relations", + "ark-serialize", + "ark-snark", + "ark-std", + "getrandom", + "hashbrown 0.17.1", + "itertools", + "rayon", + "sonobe-primitives", + "thiserror", + "wasm-bindgen-test", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1442,7 +1503,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1504,6 +1565,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1513,6 +1583,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "zerocopy" version = "0.8.48" diff --git a/Cargo.toml b/Cargo.toml index e47297e84..2af26bb08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "crates/primitives", "crates/fs", "crates/ivc", + "crates/snarks", ] resolver = "2" @@ -13,6 +14,7 @@ repository = "https://github.com/privacy-scaling-explorations/sonobe/" rust-version = "1.85.1" [workspace.dependencies] +hashbrown = { version = "0.17" } itertools = { version = "0.14.0" } num-bigint = { version = "0.4.3" } num-integer = { version = "0.1" } @@ -44,3 +46,4 @@ ark-vesta = { version = "0.6.0", default-features = false } sonobe-primitives = { path = "crates/primitives", default-features = false } sonobe-fs = { path = "crates/fs", default-features = false } sonobe-ivc = { path = "crates/ivc", default-features = false } +sonobe-snarks = { path = "crates/snarks", default-features = false } diff --git a/README.md b/README.md index 19ca77b10..b7d7564e3 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,40 @@ -# sonobe +# Sonobe -Experimental folding schemes library implemented jointly by [0xPARC](https://0xparc.org/) and [PSE](https://pse.dev). +Experimental folding/accumulation 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. -

-Sonobe is conceived as an exploratory effort with the aim to push forward the practical side of folding schemes and advancing towards onchain (EVM) verification. -

-"The
Sonobe module is one of the many units used to build modular origami. The popularity of Sonobe modular origami models derives from the simplicity of folding the modules, the sturdy and easy assembly, and the flexibility of the system." +## What is folding? -
+Folding/accumulation schemes are a cryptographic primitive that empowers recursive zero-knowledge proofs by merging multiple instances of a computation into a single instance of the same computation. -> **Warning**: experimental code, do not use in production.
-> The code has not been audited. Several optimizations are also pending. Our focus so far has been on implementing the Nova, HyperNova and ProtoGalaxy schemes, all with the CycleFold approach; and achieving the onchain (in EVM) verification of the folding proofs. +For more details about folding schemes, please refer to our [documentation](./docs/Folding.md). +## Features -## Schemes implemented +In Sonobe, we aim to provide _modular_, _secure_, _performant_, and _easy to use_ implementations of folding schemes. + +- **Modularity**: As our main priority, Sonobe features multiple folding schemes, commitment schemes, deciders (a.k.a. proof compression SNARKs), and frontends/DSLs, allowing users to pick the combination which best fits their needs. It provides compilers that are able to build higher level primitives such as Incremental Verifiable Computation (IVC) and Proof-Carrying Data (PCD) from arbitrary folding schemes. +- **Security**: + +In addition, Sonobe is conceived as an exploratory effort with the aim to push forward the practical side of folding schemes and advancing towards onchain (EVM) verification. + +_"The [Sonobe module](https://en.wikipedia.org/wiki/Sonobe) is one of the many units used to build modular origami. The popularity of Sonobe modular origami models derives from the simplicity of folding the modules, the sturdy and easy assembly, and the flexibility of the system."_ + +> **Warning**: experimental code, do not use in production. +> +> The code has not been audited. Several optimizations are also pending. Expect breaking changes. + + +## Supported schemes + +Below is the support matrix of different schemes implemented in Sonobe: + +| Folding Schemes | Folding-to-IVC compilers | Folding-to-PCD compilers | Deciders | Commitment Schemes | Frontends | +|---|---|---|---|---|---| +| Nova[^nova] | | | | | | +| | | | | | | +| | | | | | | Folding schemes implemented: @@ -24,8 +42,6 @@ Folding schemes implemented: - [CycleFold: Folding-scheme-based recursive arguments over a cycle of elliptic curves](https://eprint.iacr.org/2023/1192.pdf), Abhiram Kothapalli, Srinath Setty. 2023 - [HyperNova: Recursive arguments for customizable constraint systems](https://eprint.iacr.org/2023/573.pdf), Abhiram Kothapalli, Srinath Setty. 2023 - [ProtoGalaxy: Efficient ProtoStar-style folding of multiple instances](https://eprint.iacr.org/2023/1106.pdf), Liam Eagen, Ariel Gabizon. 2023 - - ## Frontends Frontends allow to define the circuit to be folded (ie. `FCircuit`). @@ -37,10 +53,12 @@ More details about the frontend interface and the experimental frontends can be ## Usage -Import the library: +Declare the libraries as dependencies in your `Cargo.toml`: ```toml [dependencies] -folding-schemes = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "folding-schemes"} +sonobe-fs = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "sonobe-fs" } +sonobe-ivc = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "sonobe-ivc" } +sonobe-primitives = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "sonobe-primitives" } ``` Available packages: @@ -50,7 +68,6 @@ Available packages: Available features: - `parallel` enables some parallelization optimizations available in the crate. It is enabled by default. -- `light-test` disables part of the DeciderEthCircuit various circuits (which accounts for ~9M constraints) so that the tests involving those circuits can run faster. Do not use it outside tests. This feature is disabled by default. Examples of usage can be found at the [examples](https://github.com/privacy-scaling-explorations/sonobe/tree/main/examples) directory. @@ -107,3 +124,8 @@ This project builds on top of multiple [arkworks](https://github.com/arkworks-rs The Solidity templates used in `nova_cyclefold_verifier.sol`, use [iden3](https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs)'s Groth16 implementation and a KZG10 Solidity template adapted from [weijiekoh/libkzg](https://github.com/weijiekoh/libkzg). In addition to the direct code contributors who make this repository possible, this project has been made possible by many conversations with [Srinath Setty](https://github.com/srinathsetty), [Lev Soukhanov](https://github.com/levs57), [Matej Penciak](https://github.com/mpenciak), [Adrian Hamelink](https://github.com/adr1anh), [François Garillot](https://github.com/huitseeker), [Daniel Marin](https://github.com/danielmarinq), [Han Jian](https://github.com/han0110), [Wyatt Benno](https://github.com/wyattbenno777), [Niсolas Gailly](https://github.com/nikkolasg) and [Nalin Bhardwaj](https://github.com/nalinbhardwaj), to whom we are grateful. + + +## Citations + +[^nova]: \ No newline at end of file diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs index 63aac1e13..9dc4452df 100644 --- a/crates/fs/src/definitions/circuits.rs +++ b/crates/fs/src/definitions/circuits.rs @@ -2,7 +2,9 @@ //! for proof verification. use ark_relations::gr1cs::SynthesisError; -use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::TranscriptGadget}; +use sonobe_primitives::{ + commitments::CommitmentDefGadget, relations::RelationGadget, transcripts::TranscriptGadget, +}; use super::{FoldingSchemeDefGadget, algorithms::FoldingSchemeOps}; @@ -58,3 +60,34 @@ pub trait FoldingSchemeFullVerifierGadget: proof: &Self::Proof, ) -> Result; } + +pub trait FoldingSchemeDeciderGadget: + FoldingSchemeDefGadget< + DeciderKey: RelationGadget + RelationGadget, +> +{ + #[allow(non_snake_case)] + fn decide_running( + dk: &Self::DeciderKey, + W: &Self::RW, + U: &Self::RU, + ) -> Result<(), SynthesisError> { + RelationGadget::::check_relation(dk, W, U) + } + + fn decide_incoming( + dk: &Self::DeciderKey, + w: &Self::IW, + u: &Self::IU, + ) -> Result<(), SynthesisError> { + RelationGadget::::check_relation(dk, w, u) + } +} + +impl< + FS: FoldingSchemeDefGadget< + DeciderKey: RelationGadget + RelationGadget, + >, +> FoldingSchemeDeciderGadget for FS +{ +} diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index 547a326c6..ea7666468 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -13,9 +13,9 @@ pub mod witnesses; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig}, - circuits::AssignmentsOwned, + circuits::{AssignmentsOwned, WitnessToCommitted}, commitments::{CommitmentDef, CommitmentDefGadget}, - relations::{Relation, WitnessInstanceSampler}, + relations::{Relation, RelationGadget, WitnessInstanceSampler}, traits::{Dummy, SonobeField}, }; @@ -25,6 +25,7 @@ use self::{ keys::DeciderKey, witnesses::FoldingWitness, }; +use crate::FoldingWitnessVar; /// [`FoldingSchemeDef`] provides the core type definitions of a folding scheme. /// @@ -104,16 +105,27 @@ pub trait FoldingSchemeDefGadget { /// [`FoldingSchemeDefGadget::CM`] is the commitment scheme gadget. type CM: CommitmentDefGadget::CM>; + type RW: FoldingWitnessVar::RW>; /// [`FoldingSchemeDefGadget::RU`] is the type of in-circuit running /// instance variable. type RU: FoldingInstanceVar::RU>; + type IW: FoldingWitnessVar::IW>; /// [`FoldingSchemeDefGadget::IU`] is the type of in-circuit incoming /// instance variable. type IU: FoldingInstanceVar::IU>; + type Arith: AllocVar< + ::Arith, + ::ConstraintField, + >; + /// [`FoldingSchemeDefGadget::VerifierKey`] is the type of in-circuit /// verifier key variable. type VerifierKey; + type DeciderKey: AllocVar< + ::DeciderKey, + ::ConstraintField, + >; /// [`FoldingSchemeDefGadget::Challenge`] is the type of in-circuit /// challenge variable. diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index 2ee180acd..85159d33b 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -74,7 +74,7 @@ mod tests { { let pp = FS::preprocess(config, &mut rng)?; - let cs = ArithExtractor::new(); + let mut cs = ArithExtractor::new(); cs.execute_synthesizer(circuit)?; let arith = cs.arith()?; let dk = FS::generate_keys(pp, arith)?; diff --git a/crates/fs/src/nova/keys/circuits.rs b/crates/fs/src/nova/keys/circuits.rs new file mode 100644 index 000000000..23947ba4a --- /dev/null +++ b/crates/fs/src/nova/keys/circuits.rs @@ -0,0 +1,90 @@ +use ark_r1cs_std::{ + GR1CSVar, + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, +}; +use ark_relations::gr1cs::{Namespace, SynthesisError}; +use ark_std::{borrow::Borrow, marker::PhantomData}; +use sonobe_primitives::{ + arithmetizations::{ + Arith, ArithConfig, ArithGadget, ArithRelation, ArithRelationGadget, + r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, + }, + circuits::{AssignmentsOwned, WitnessToCommitted}, + commitments::{ + CommitmentDef, CommitmentDefGadget, CommitmentOps, CommitmentOpsGadget, + GroupBasedCommitment, + }, + relations::{Relation, RelationGadget, WitnessInstanceSampler}, + traits::{CF2, SonobeField}, +}; + +use super::super::{ + instances::{ + IncomingInstance as IU, RunningInstance as RU, + circuits::{IncomingInstanceVar as IUVar, RunningInstanceVar as RUVar}, + }, + witnesses::{ + IncomingWitness as IW, RunningWitness as RW, + circuits::{IncomingWitnessVar as IWVar, RunningWitnessVar as RWVar}, + }, +}; +use crate::{ + FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, + GroupBasedFoldingSchemeSecondaryDef, nova::keys::NovaKey, +}; + +#[derive(Clone)] +pub struct NovaKeyVar { + arith: A, + ck: CM::KeyVar, +} + +impl, CM: CommitmentDefGadget> + AllocVar, CM::ConstraintField> for NovaKeyVar +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let NovaKey { arith, ck } = v.borrow(); + Ok(Self { + arith: AllocVar::new_variable(cs.clone(), || Ok(arith.borrow()), mode)?, + ck: AllocVar::new_variable(cs.clone(), || Ok(ck.borrow()), mode)?, + }) + } +} + +impl RelationGadget, RUVar> for NovaKeyVar +where + A: for<'a> ArithRelationGadget< + RelaxedWitness<&'a [CM::ScalarVar]>, + RelaxedInstance<&'a [CM::ScalarVar]>, + >, + CM: CommitmentOpsGadget, +{ + fn check_relation(&self, w: &RWVar, u: &RUVar) -> Result<(), SynthesisError> { + self.arith.check_relation( + &RelaxedWitness { w: &w.w, e: &w.e }, + &RelaxedInstance { x: &u.x, u: &u.u }, + )?; + CM::open(&self.ck, &w.e, &w.r_e, &u.cm_e)?; + CM::open(&self.ck, &w.w, &w.r_w, &u.cm_w)?; + Ok(()) + } +} + +impl RelationGadget, IUVar> for NovaKeyVar +where + A: ArithRelationGadget, Vec>, + CM: CommitmentOpsGadget, +{ + fn check_relation(&self, w: &IWVar, u: &IUVar) -> Result<(), SynthesisError> { + self.arith.check_relation(&w.w, &u.x)?; + CM::open(&self.ck, &w.w, &w.r_w, &u.cm_w)?; + Ok(()) + } +} diff --git a/crates/fs/src/nova/keys/mod.rs b/crates/fs/src/nova/keys/mod.rs index 479539779..2a08a1452 100644 --- a/crates/fs/src/nova/keys/mod.rs +++ b/crates/fs/src/nova/keys/mod.rs @@ -19,6 +19,8 @@ use super::{ }; use crate::{DeciderKey, Error, PlainInstance as PU, PlainWitness as PW}; +pub mod circuits; + /// [`NovaKey`] is Nova's decider key. #[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct NovaKey { diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 31af9b856..962544861 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -6,8 +6,13 @@ use ark_r1cs_std::boolean::Boolean; use ark_std::marker::PhantomData; use sonobe_primitives::{ - arithmetizations::r1cs::R1CS, - commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, + arithmetizations::{ + Arith, ArithConfig, ArithRelation, ArithRelationGadget, + r1cs::{R1CS, RelaxedInstance, RelaxedWitness, circuits::R1CSVar}, + }, + circuits::{AssignmentsOwned, WitnessToCommitted}, + commitments::{CommitmentDef, CommitmentDefGadget, CommitmentOps, GroupBasedCommitment}, + relations::{Relation, WitnessInstanceSampler}, traits::{CF2, SonobeField}, }; @@ -16,11 +21,15 @@ use self::{ IncomingInstance as IU, RunningInstance as RU, circuits::{IncomingInstanceVar as IUVar, RunningInstanceVar as RUVar}, }, - witnesses::{IncomingWitness as IW, RunningWitness as RW}, + witnesses::{ + IncomingWitness as IW, RunningWitness as RW, + circuits::{IncomingWitnessVar as IWVar, RunningWitnessVar as RWVar}, + }, }; use crate::{ FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, - GroupBasedFoldingSchemeSecondaryDef, nova::keys::NovaKey, + GroupBasedFoldingSchemeSecondaryDef, + nova::keys::{NovaKey, circuits::NovaKeyVar}, }; pub mod algorithms; @@ -79,10 +88,14 @@ where { type Widget = AbstractNova; + type Arith = R1CSVar; type CM = CM; + type RW = RWVar; type RU = RUVar; + type IW = IWVar; type IU = IUVar; type VerifierKey = (); + type DeciderKey = NovaKeyVar; type Challenge = [Boolean; CHALLENGE_BITS]; type Proof = CM::CommitmentVar; } diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index 6a88b7f0e..c735c6207 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -19,6 +19,7 @@ thiserror = { workspace = true } sonobe-primitives = { workspace = true } sonobe-fs = { workspace = true } +sonobe-snarks = { workspace = true } [dev-dependencies] ark-bn254 = { workspace = true, features = ["curve", "r1cs"] } @@ -31,7 +32,8 @@ getrandom = { version = "0.2", features = ["js"] } wasm-bindgen-test = { workspace = true } [features] -default = [] +default = ["ark-std/print-trace"] parallel = [ "sonobe-fs/parallel", + "sonobe-snarks/parallel", ] \ No newline at end of file diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index db6d3216c..01c410803 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -211,20 +211,38 @@ pub type NovaNovaIVC = #[cfg(test)] mod tests { - use ark_bn254::{Fr, G1Projective as C1}; + use ark_bn254::{Bn254, Fq, 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_fs::{ + GroupBasedFoldingSchemePrimaryDef, GroupBasedFoldingSchemeSecondaryDef, + definitions::circuits::FoldingSchemeDeciderGadget, + nova::{ + AbstractNovaGadget, instances::circuits::RunningInstanceVar, + keys::circuits::NovaKeyVar, witnesses::circuits::RunningWitnessVar, + }, + }; use sonobe_primitives::{ - circuits::utils::CircuitForTest, - commitments::pedersen::Pedersen, + algebra::{field::TwoStageFieldVar, ops::{eq::EquivalenceGadget, vector::VectorMulGadget}}, + arithmetizations::{ + ArithRelationGadget, + r1cs::{RelaxedInstance, RelaxedWitness, circuits::R1CSVar}, + }, + circuits::{Assignments, utils::CircuitForTest}, + commitments::pedersen::{Pedersen, PedersenCommitAndProveGadget}, + relations::RelationGadget, transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, }; + use sonobe_snarks::cp::groth16::LegoGroth16; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; - use crate::tests::test_ivc; + use crate::{ + compilers::cyclefold::CycleFoldBasedIVCDecider, + tests::{test_ivc, test_ivc_decider}, + }; #[test] fn test_nova_nova() -> Result<(), Box> { @@ -241,4 +259,31 @@ mod tests { Ok(()) } + + #[test] + fn test_nova_nova_decider() -> Result<(), Box> { + let mut rng = thread_rng(); + + // f::<<> as GroupBasedFoldingSchemeSecondaryDef>::Gadget as FoldingSchemeDefGadget>::DeciderKey>(); + // g::>(); + + test_ivc_decider::< + CycleFoldBasedIVCDecider< + Nova>, + CycleFoldNova>, + GriffinSponge<_>, + LegoGroth16, + >, + _, + >( + (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/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index cce372512..a0752768a 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -180,7 +180,7 @@ where // 3. Update state by invoking the step circuit. let (next_state, external_outputs) = self.step_circuit - .generate_step_constraints(i, current_state, external_inputs)?; + .synthesize_step(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 diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index efb14d566..f8250b54f 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -7,10 +7,19 @@ //! //! [paper]: https://eprint.iacr.org/2023/1192.pdf +use ark_ec::CurveGroup; use ark_ff::field_hashers::hash_to_field; -use ark_relations::gr1cs::{ConstraintSystem, SynthesisError}; +use ark_r1cs_std::{ + alloc::AllocVar, + eq::EqGadget, + fields::{FieldVar, fp::FpVar}, +}; +use ark_relations::gr1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, +}; use ark_serialize::CanonicalSerialize; use ark_std::{ + any::TypeId, borrow::Borrow, io::{Error as IoError, Write}, marker::PhantomData, @@ -21,26 +30,31 @@ use sha3::{ digest::{ExtendableOutput, Update}, }; use sonobe_fs::{ - DeciderKey, FoldingInstance, FoldingSchemeDef, FoldingSchemeDefGadget, + DeciderKey, FoldingInstance, FoldingInstanceVar, FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, + definitions::circuits::FoldingSchemeDeciderGadget, }; use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, arithmetizations::{Arith, ArithConfig}, - circuits::{ArithExtractor, AssignmentsExtractor, FCircuit}, - commitments::CommitmentDef, + circuits::{ + ArithExtractor, AssignmentsExtractor, FCircuit, + alloc::{CommitmentCache, CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, + }, + commitments::{CommitmentDef, CommitmentDefGadget}, relations::WitnessInstanceSampler, - traits::{CF1, CF2, Dummy, SonobeCurve}, + traits::{CF1, CF2, Dummy, Inputize, SonobeCurve}, transcripts::{ Transcript, TranscriptGadget, recording::RecordingTranscript, replay::{ReplayTranscript, ReplayTranscriptVar}, }, }; +use sonobe_snarks::cp::CPSNARK; use crate::{ - Error, IVC, + Error, IVCKeyGenerator, IVCPreprocessor, IVCProofCompressor, IVCProver, IVCTypes, IVCVerifier, compilers::cyclefold::circuits::{AugmentedCircuit, CycleFoldCircuit}, }; @@ -139,32 +153,11 @@ pub struct CycleFoldBasedIVC { _d: PhantomData<(FS1, FS2, T)>, } -impl IVC for CycleFoldBasedIVC +impl IVCTypes for CycleFoldBasedIVC where - FS1: FoldingSchemeCycleFoldExt< - 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>, - >, - >, - FS2: GroupBasedFoldingSchemeSecondary< - 1, - 1, - Arith: From::Commitment>>>, - Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, - CM: CommitmentDef< - Commitment: SonobeCurve::Scalar>, - >, - >, - T: Transcript::Commitment>, Config: CanonicalSerialize>, - T::Gadget: TranscriptGadget::Commitment>, Config = T::Config>, + FS1: FoldingSchemeCycleFoldExt<1, 1>, + FS2: GroupBasedFoldingSchemeSecondary<1, 1>, + T: Transcript::Commitment>>, { type Field = ::Scalar; @@ -179,7 +172,14 @@ where Key; type Proof = Proof; +} +impl IVCPreprocessor for CycleFoldBasedIVC +where + FS1: FoldingSchemeCycleFoldExt<1, 1>, + FS2: GroupBasedFoldingSchemeSecondary<1, 1>, + T: Transcript::Commitment>>, +{ fn preprocess( (cfg1, cfg2, hash_config): Self::Config, mut rng: impl RngCore, @@ -190,7 +190,35 @@ where hash_config, )) } +} +impl IVCKeyGenerator for CycleFoldBasedIVC +where + FS1: FoldingSchemeCycleFoldExt< + 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>, + >, + >, + FS2: GroupBasedFoldingSchemeSecondary< + 1, + 1, + Arith: From::Commitment>>>, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, + >, + T: Transcript::Commitment>, Config: CanonicalSerialize>, + T::Gadget: TranscriptGadget::Commitment>, Config = T::Config>, +{ fn generate_keys>( (pp1, pp2, hash_config): Self::PublicParam, step_circuit: &FC, @@ -198,7 +226,7 @@ where // Run the CycleFold circuit to extract the arithmetization on the // secondary curve. let arith2 = { - let cs = ArithExtractor::new(); + let mut cs = ArithExtractor::new(); cs.execute_fn(|cs| FS1::CFCircuit::default().verify_point_rlc(cs))?; cs.arith::()? }; @@ -220,7 +248,7 @@ where let arith1; loop { let new_arith1 = { - let cs = ArithExtractor::new(); + let mut cs = ArithExtractor::new(); cs.execute_synthesizer(AugmentedCircuit::::new( &hash_config, &arith1_config, @@ -267,7 +295,33 @@ where Ok((key.clone(), key)) } +} +impl IVCProver for CycleFoldBasedIVC +where + FS1: FoldingSchemeCycleFoldExt< + 1, + 1, + // 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>, + >, + >, + FS2: GroupBasedFoldingSchemeSecondary< + 1, + 1, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()>, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, + >, + T: Transcript::Commitment>>, + T::Gadget: TranscriptGadget::Commitment>, Config = T::Config>, +{ #[allow(non_snake_case)] fn prove>( Key(dk1, dk2, (hash_config, pp_hash, _)): &Self::ProverKey, @@ -305,7 +359,7 @@ where let cf_circuits = FS1::to_cyclefold_circuits(&[U], &[u], &proof, transcript.clone().into()); for (i, cf_circuit) in cf_circuits.into_iter().enumerate() { - let cs = AssignmentsExtractor::new(); + let mut cs = AssignmentsExtractor::new(); cs.execute_fn(|cs| cf_circuit.verify_point_rlc(cs))?; let (cf_w, cf_u) = dk2.sample(cs.assignments()?, &mut rng)?; @@ -323,7 +377,7 @@ where } } - let cs = AssignmentsExtractor::new(); + let mut cs = AssignmentsExtractor::new(); let (next_state, external_outputs) = cs.execute_fn(|cs| { let augmented_circuit = AugmentedCircuit::::new( hash_config, @@ -355,7 +409,14 @@ where Proof(WW, UU, ww, uu, cf_WW, cf_UU), )) } +} +impl IVCVerifier for CycleFoldBasedIVC +where + FS1: FoldingSchemeCycleFoldExt<1, 1>, + FS2: GroupBasedFoldingSchemeSecondary<1, 1>, + T: Transcript::Commitment>>, +{ #[allow(non_snake_case)] fn verify>( Key(dk1, dk2, (hash_config, pp_hash, reference_state)): &Self::VerifierKey, @@ -405,3 +466,281 @@ where Ok(()) } } + +pub struct CycleFoldBasedIVCDecider { + _p: PhantomData<(FS1, FS2, T, S)>, +} + +impl< + FS1: FoldingSchemeCycleFoldExt< + 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 = ()> + + FoldingSchemeDeciderGadget, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, + >, + FS2: GroupBasedFoldingSchemeSecondary< + 1, + 1, + Arith: From::Commitment>>>, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()> + + FoldingSchemeDeciderGadget, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, + >, + T: Transcript< + CF1<::Commitment>, + Config: CanonicalSerialize, + Gadget: TranscriptGadget< + CF1<::Commitment>, + Config = T::Config, + >, + >, + S: CPSNARK< + Field = ::Scalar, + Relation = (FS1::Arith, UsizeSet), + CommitmentKey = ::Key, + Commitment = <::Commitment as CurveGroup>::Affine, + CommitmentOpening = ::Scalar, + Error = SynthesisError, + >, +> IVCProofCompressor for CycleFoldBasedIVCDecider +{ + type IVC = CycleFoldBasedIVC; + + type ProverKey = ( + S::ProverKey, + Key, + ); + + type VerifierKey = (S::VerifierKey, FC::State); + + type CompressedProof = (S::Proof, Vec<::Commitment>); + + type Error = Error; + + fn preprocess_and_generate_keys::Field>>( + circuit: &FC, + Key(dk1, dk2, (hash_config, pp_hash, reference_state)): ::VerifierKey, + rng: impl RngCore, + ) -> Result<(Self::ProverKey, Self::VerifierKey), Self::Error> { + let mut cs = ArithExtractor::new(); + { + let mut cache = cs.cache_map.borrow_mut(); + cache.insert( + TypeId::of::(), + Box::new(UsizeSet::default()), + ); + cache.insert( + TypeId::of::(), + Box::new(Vec::<::Key>::new()), + ); + } + + let ivc_vk = Key(dk1, dk2, (hash_config, pp_hash)); + + cs.execute_synthesizer(CycleFoldBasedIVCDeciderCircuit:: { + vk: &ivc_vk, + i: 0, + initial_state: &circuit.dummy_state(), + current_state: &circuit.dummy_state(), + proof: &Proof::dummy(&ivc_vk), + })?; + let (committed_variable_indices, ck) = { + let mut cache = cs.cache_map.borrow_mut(); + let committed_variable_indices = *cache + .remove(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast::() + .map_err(|_| SynthesisError::AssignmentMissing)?; + let ck = *cache + .remove(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast::::Key>>() + .map_err(|_| SynthesisError::AssignmentMissing)?; + + (committed_variable_indices, ck) + }; + + let (pk, vk) = S::generate_keys((cs.arith()?, committed_variable_indices), &ck, rng)?; + + Ok(((pk, ivc_vk), (vk, reference_state))) + } + + fn prove::Field>>( + (pk, ivc_vk): &Self::ProverKey, + i: usize, + initial_state: &FC::State, + current_state: &FC::State, + proof: &::Proof, + rng: impl RngCore, + ) -> Result, Self::Error> { + let mut cs = AssignmentsExtractor::new(); + { + let mut cache = cs.cache_map.borrow_mut(); + cache.insert( + TypeId::of::(), + Box::new(UsizeSet::default()), + ); + cache.insert( + TypeId::of::(), + Box::new(Vec::<::Field>::new()), + ); + cache.insert( + TypeId::of::(), + Box::new(Vec::<::Commitment>::new()), + ); + } + cs.execute_synthesizer(CycleFoldBasedIVCDeciderCircuit:: { + vk: ivc_vk, + i, + initial_state, + current_state, + proof, + })?; + let w = &cs.assignments.witness_assignment; + let x = &cs.assignments.instance_assignment; + let mut cache = cs.cache_map.borrow_mut(); + let o = *cache + .remove(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast::::Field>>() + .map_err(|_| SynthesisError::AssignmentMissing)?; + let c = *cache + .remove(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast::::Commitment>>() + .map_err(|_| SynthesisError::AssignmentMissing)?; + + let proof = S::prove(pk, &x[1..], &w, &o, rng)?; + + Ok((proof, c)) + } + + fn verify::Field>>( + (vk, reference_state): &Self::VerifierKey, + i: usize, + initial_state: &FC::State, + current_state: &FC::State, + (proof, commitments): &Self::CompressedProof, + ) -> Result<(), Self::Error> { + if !FC::same_state_shape(reference_state, initial_state) + || !FC::same_state_shape(reference_state, current_state) + { + return Err(Error::IVCVerificationFail); + } + + if i == 0 { + return (initial_state == current_state) + .then_some(()) + .ok_or(Error::IVCVerificationFail); + } + + let x = &[ + vec![::Field::from(i as u64)], + FC::StateVar::inputize(initial_state), + FC::StateVar::inputize(current_state), + commitments.iter().flat_map(<::CM as CommitmentDefGadget>::CommitmentVar::inputize).collect::>() + ] + .concat(); + let c = CurveGroup::normalize_batch(commitments); + + S::verify(vk, x, &c, proof)?; + + Ok(()) + } +} + +pub struct CycleFoldBasedIVCDeciderCircuit< + 'a, + FS1: FoldingSchemeDef, + FS2: FoldingSchemeDef, + T: Transcript, + FC: FCircuit, +> { + vk: &'a Key, + i: usize, + initial_state: &'a FC::State, + current_state: &'a FC::State, + proof: &'a Proof, +} + +impl< + 'a, + FS1: FoldingSchemeCycleFoldExt< + 1, + 1, + Gadget: FoldingSchemeDeciderGadget, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, + >, + FS2: GroupBasedFoldingSchemeSecondary< + 1, + 1, + Gadget: FoldingSchemeDeciderGadget, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, + >, + T: Transcript< + CF1<::Commitment>, + Gadget: TranscriptGadget< + CF1<::Commitment>, + Config = T::Config, + >, + >, + FC: FCircuit::Commitment>>, +> ConstraintSynthesizer for CycleFoldBasedIVCDeciderCircuit<'a, FS1, FS2, T, FC> +{ + fn generate_constraints( + self, + cs: ConstraintSystemRef, + ) -> Result<(), SynthesisError> { + let i = FpVar::new_input(cs.clone(), || Ok(FC::Field::from(self.i as u64)))?; + let initial_state = FC::StateVar::new_input(cs.clone(), || Ok(self.initial_state))?; + let current_state = FC::StateVar::new_input(cs.clone(), || Ok(self.current_state))?; + + let Key(dk1, dk2, (hash_config, pp_hash)) = &self.vk; + let dk1 = AllocVar::new_constant(cs.clone(), dk1)?; + let dk2 = AllocVar::new_constant(cs.clone(), dk2)?; + let pp_hash = FpVar::new_constant(cs.clone(), pp_hash)?; + + let Proof(W, U, w, u, cf_W, cf_U) = &self.proof; + let W = AllocVar::new_witness(cs.clone(), || Ok(W))?; + let U = AllocVar::new_witness(cs.clone(), || Ok(U))?; + let w = AllocVar::new_witness(cs.clone(), || Ok(w))?; + let u = AllocVar::new_witness(cs.clone(), || Ok(u))?; + let cf_W = AllocVar::new_witness(cs.clone(), || Ok(cf_W))?; + let cf_U = AllocVar::new_witness(cs.clone(), || Ok(cf_U))?; + + i.enforce_not_equal(&FpVar::zero())?; + + FS1::Gadget::decide_running(&dk1, &W, &U)?; + FS1::Gadget::decide_incoming(&dk1, &w, &u)?; + FS2::Gadget::decide_running(&dk2, &cf_W, &cf_U)?; + + let hash = T::Gadget::new_with_pp_hash(hash_config.clone(), &pp_hash)?; + let mut sponge = hash.separate_domain("sponge".as_ref())?; + + let u_x = sponge + .add(&i)? + .add(&initial_state)? + .add(¤t_state)? + .add(&U)? + .add(&cf_U)? + .get_field_element()?; + + u.public_inputs().enforce_equal(&vec![u_x])?; + + Ok(()) + } +} diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 9ea2ac6ce..e29148dbc 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -7,9 +7,10 @@ //! scheme into a full IVC scheme. use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::SynthesisError; use ark_serialize::SerializationError; -use ark_std::rand::RngCore; +use ark_std::{error::Error as ErrorTrait, rand::RngCore}; use sonobe_fs::Error as FoldingError; use sonobe_primitives::{arithmetizations::Error as ArithError, circuits::FCircuit, traits::Dummy}; use thiserror::Error; @@ -40,71 +41,167 @@ pub enum Error { 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. +/// [`IVCTypes`] defines the associated types for an IVC scheme. +pub trait IVCTypes { + /// [`IVCTypes::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. + /// [`IVCTypes::Config`] defines the configuration of IVC. + /// + /// ### Examples + /// + /// In folding-based IVC schemes, this is usually the configuration of the + /// underlying folding scheme. type Config; - /// [`IVC::PublicParam`] defines the public parameters produced by - /// preprocessing. + + /// [`IVCTypes::PublicParam`] defines the public parameters of IVC. 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. + + /// [`IVCTypes::ProverKey`] defines the prover key of IVC. + /// + /// ### Design Rationale + /// + /// It is parameterized by the step circuit type `FC`, because the one + /// prover key can only be used for one step circuit, and different step + /// circuits require different prover keys. + /// With `FC`, we can prevent the misuse of keys on the type level. 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. + + /// [`IVCTypes::VerifierKey`] defines the verifier key of IVC. + /// + /// ### Design Rationale + /// + /// It is parameterized by the step circuit type `FC`, because the one + /// verifier key can only be used for one step circuit, and different step + /// circuits require different verifier keys. + /// With `FC`, we can prevent the misuse of keys on the type level. 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. + + /// [`IVCTypes::Proof`] defines the proof of IVC. + /// + /// ### Design Rationale + /// + /// It is parameterized by the step circuit type `FC`, because it might be + /// problematic if the prover generates a proof for one step circuit but the + /// verifier expects a proof for another step circuit. + /// With `FC`, we can prevent such inconsistencies on the type level. + /// + /// It should also implement `Dummy`, so that we can generate an initial + /// proof from the prover key in a uniform way across different IVC schemes. 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. +/// [`IVCPreprocessor`] defines the preprocessing algorithm of IVC. +pub trait IVCPreprocessor: IVCTypes { + /// [`IVCPreprocessor::preprocess`] is a randomized algorithm that generates + /// public parameters for the IVC scheme under a given configuration. + /// + /// ### Function Signature /// - /// Here, the randomness source is controlled by `rng`. + /// [`IVCPreprocessor::preprocess`] takes as input + /// - `config`: the configuration for the IVC scheme, and + /// - `rng`: the randomness source. /// - /// The security parameter is implicitly specified by the size of underlying - /// fields and groups. + /// It returns + /// - an error if the preprocessing fails, or + /// - `Ok(pp)` otherwise, where + /// - `pp`: the public parameters. /// - /// 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. + /// ### Usage + /// + /// See [`IVC`] for an example of how to use this method in the context of a + /// full IVC workflow. + /// + /// ### Notes + /// + /// - The security parameter is implicitly specified by the underlying + /// mathematical structures (e.g., field/group orders) and the algorithms + /// involved in the implementation. + /// - This is usually called once for one configuration + /// - The same public parameters can be reused for different step circuits, + /// as long as they 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. +/// [`IVCKeyGenerator`] defines the key generation algorithm of IVC. +pub trait IVCKeyGenerator: IVCTypes { + /// [`IVCKeyGenerator::generate_keys`] is a deterministic algorithm that + /// generates a pair of prover and verifier keys for a given step circuit. + /// + /// ### Function Signature + /// + /// [`IVCKeyGenerator::generate_keys`] takes as input + /// - `pp`: the public parameters, and + /// - `step_circuit`: the step circuit. + /// + /// It outputs + /// - an error if the key generation fails, or + /// - `Ok((pk, vk))` otherwise, where + /// - `pk`: the prover key, and + /// - `vk`: the verifier key. + /// + /// ### Usage + /// + /// See [`IVC`] for an example of how to use this method in the context of a + /// full IVC workflow. + /// + /// ### Notes + /// + /// - This is usually called once for one step circuit. + /// - The same prover and verifier keys can be reused for different initial + /// states and external inputs, as long as the step circuit is the same. #[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. +/// [`IVCProver`] defines the proof generation algorithm of IVC. +pub trait IVCProver: IVCTypes { + /// [`IVCProver::prove`] is a (probably) randomized algorithm that proves + /// the (next) state is correctly derived from the initial state after + /// invoking the step circuit for the claimed number of steps. + /// Proof generation is done by executing the step circuit on the current + /// state to obtain the next state, and then updating an existing proof for + /// the current state to a new proof for the next state. + /// + /// ### Function Signature + /// + /// [`IVCProver::prove`] takes as input + /// - `pk`: the prover key, + /// - `step_circuit`: the step circuit, + /// - `i`: the current step, + /// - `initial_state`: the initial state, + /// - `current_state`: the current state, + /// - `external_inputs`: the external inputs, + /// - `current_proof`: the current proof attesting that `current_state` is + /// correctly derived from `initial_state` after `i` executions of + /// `step_circuit`, and + /// - `rng`: the randomness source. + /// + /// It outputs + /// - an error if the proof generation fails, or + /// - `Ok((next_state, external_outputs, next_proof))` otherwise, where + /// - `next_state`: the next state, + /// - `external_outputs`: the external outputs, and + /// - `next_proof`: the next proof attesting that `next_state` is + /// correctly derived from `initial_state` after `i+1` executions of + /// `step_circuit`. + /// + /// ### Usage + /// + /// See [`IVC`] for an example of how to use this method in the context of a + /// full IVC workflow. + /// + /// ### Design Rationale + /// + /// `external_inputs` is needed by [`IVCProver::prove`] since it is one of + /// the inputs to [`FCircuit::synthesize_step`]. + /// + /// Similarly, [`FCircuit::synthesize_step`] returns `external_outputs`, and + /// it needs to be returned by [`IVCProver::prove`] so that the caller can + /// use it. #[allow(clippy::type_complexity, clippy::too_many_arguments)] fn prove>( pk: &Self::ProverKey, @@ -116,12 +213,32 @@ pub trait IVC { current_proof: &Self::Proof, 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. +/// [`IVCVerifier`] defines the proof verification algorithm of IVC. +pub trait IVCVerifier: IVCTypes { + /// [`IVCVerifier::verify`] is a deterministic algorithm that checks the + /// current state is correctly derived from the initial state after invoking + /// the step circuit for the claimed number of steps, by verifying the IVC + /// proof. + /// + /// ### Function Signature + /// + /// [`IVCVerifier::verify`] takes as input + /// - `vk`: the verifier key, + /// - `i`: the current step, + /// - `initial_state`: the initial state, + /// - `current_state`: the current state, and + /// - `proof`: the proof. + /// + /// It outputs + /// - an error if the proof is invalid, or + /// - `Ok(())` otherwise. + /// + /// ### Usage + /// + /// See [`IVC`] for an example of how to use this method in the context of a + /// full IVC workflow. fn verify>( vk: &Self::VerifierKey, i: usize, @@ -131,10 +248,137 @@ pub trait IVC { ) -> Result<(), Error>; } +/// [`IVCOps`] is a convenience super-trait bundling all algorithms. +pub trait IVCOps: IVCPreprocessor + IVCKeyGenerator + IVCProver + IVCVerifier {} + +impl IVCOps for I {} + +/// [`IVC`] is the main trait for an Incrementally Verifiable Computation (IVC) +/// scheme, which includes the type definitions and all the algorithms. +/// +/// ### Usage +/// +/// A concrete IVC scheme `I` that implements [`IVC`] can usually be used in the +/// following way: +/// +/// ```rust +/// use ark_std::rand::Rng; +/// use sonobe_ivc::{Error, IVC}; +/// use sonobe_primitives::{circuits::FCircuit, traits::Dummy}; +/// +/// fn ivc_usage>( +/// config: I::Config, +/// step_circuit: F, +/// initial_state: F::State, +/// external_inputs_vec: Vec, +/// mut rng: impl Rng, +/// ) -> Result<(), Error> { +/// let n_steps = external_inputs_vec.len(); +/// +/// // 1. Generate public parameters. +/// let pp = I::preprocess(config, &mut rng)?; +/// +/// // 2. Generate prover key and verifier key for the step circuit. +/// let (pk, vk) = I::generate_keys(pp, &step_circuit)?; +/// +/// let mut current_state = initial_state.clone(); +/// let mut current_proof = I::Proof::dummy(&pk); +/// +/// for (i, external_inputs) in external_inputs_vec.into_iter().enumerate() { +/// // 3. Generate the new state and proof from the current state and +/// // proof. +/// let (next_state, external_outputs, next_proof) = I::prove( +/// &pk, +/// &step_circuit, +/// i, +/// &initial_state, +/// ¤t_state, +/// external_inputs, +/// ¤t_proof, +/// &mut rng, +/// )?; +/// current_state = next_state; +/// current_proof = next_proof; +/// } +/// +/// // 4. Verify the final state and proof. +/// I::verify(&vk, n_steps, &initial_state, ¤t_state, ¤t_proof)?; +/// +/// Ok(()) +/// } +/// ``` +pub trait IVC: IVCTypes + IVCOps {} + +impl IVC for I {} + +pub trait IVCTypesGadget { + type Widget: IVCTypes; + + type VerifierKey; + + type ProofVar; +} + +/// [`IVCVerifier`] defines the proof verification algorithm of IVC. +pub trait IVCVerifierGadget: IVCTypesGadget { + fn verify( + vk: &Self::VerifierKey, + i: FpVar, + initial_state: &FC::StateVar, + current_state: &FC::StateVar, + proof: &Self::ProofVar, + ) -> 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. +/// +/// ### Usage +/// +/// With [`IVCStatefulProver`], the IVC workflow can be simplified as follows: +/// +/// ```rust +/// use ark_std::rand::Rng; +/// use sonobe_ivc::{Error, IVC, IVCStatefulProver}; +/// use sonobe_primitives::{circuits::FCircuit, traits::Dummy}; +/// +/// fn ivc_usage>( +/// config: I::Config, +/// step_circuit: F, +/// initial_state: F::State, +/// external_inputs_vec: Vec, +/// mut rng: impl Rng, +/// ) -> Result<(), Error> { +/// let n_steps = external_inputs_vec.len(); +/// +/// // 1. Generate public parameters. +/// let pp = I::preprocess(config, &mut rng)?; +/// +/// // 2. Generate prover key and verifier key for the step circuit. +/// let (pk, vk) = I::generate_keys(pp, &step_circuit)?; +/// +/// let mut prover = IVCStatefulProver::<_, I>::new(pk, step_circuit, initial_state)?; +/// +/// for external_inputs in external_inputs_vec { +/// // 3. Generate the new state and proof from the current state and +/// // proof. +/// prover.prove_step(external_inputs, &mut rng)?; +/// } +/// +/// // 4. Verify the final state and proof. +/// I::verify( +/// &vk, +/// prover.i, +/// &prover.initial_state, +/// &prover.current_state, +/// &prover.current_proof, +/// )?; +/// +/// Ok(()) +/// } +/// ``` pub struct IVCStatefulProver<'a, FC: FCircuit, I: IVC> { pk: &'a I::ProverKey, step_circuit: &'a FC, @@ -187,48 +431,41 @@ 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 +pub trait IVCProofCompressor { + /// [`IVCProofCompressor::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::Field>>( - ivc_pk: &::ProverKey, + /// [`IVCProofCompressor::ProverKey`] defines the prover key type for the decider. + type ProverKey; + /// [`IVCProofCompressor::VerifierKey`] defines the verifier key type for the decider. + type VerifierKey; + type CompressedProof; + + type Error: ErrorTrait + 'static; + + fn preprocess_and_generate_keys::Field>>( + circuit: &FC, + ivc_vk: ::VerifierKey, 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, - x: &Self::Instance, + ) -> Result<(Self::ProverKey, Self::VerifierKey), Self::Error>; + + fn prove::Field>>( + pk: &Self::ProverKey, + i: usize, + initial_state: &FC::State, + current_state: &FC::State, + proof: &::Proof, rng: impl RngCore, - ) -> Result; + ) -> Result, Self::Error>; - /// [`Decider::verify`] verifies the decider proof against the given - /// instance. - fn verify(vk: &Self::VerifierKey, x: &Self::Instance, proof: &Self::Proof) - -> Result<(), Error>; + fn verify::Field>>( + vk: &Self::VerifierKey, + i: usize, + initial_state: &FC::State, + current_state: &FC::State, + compressed_proof: &Self::CompressedProof, + ) -> Result<(), Self::Error>; } #[cfg(test)] @@ -237,25 +474,62 @@ mod tests { use super::*; - pub fn test_ivc>( - config: I::Config, - step_circuit: F, + fn test_manual_state_management>( + pk: &I::ProverKey, + vk: &I::VerifierKey, + step_circuit: &F, + initial_state: F::State, external_inputs_vec: Vec, mut rng: impl Rng, - ) -> Result<(), Box> { - let pp = I::preprocess(config, &mut rng)?; + ) -> Result<(F::State, I::Proof), Box> { + let mut current_state = initial_state.clone(); + let mut current_proof = I::Proof::dummy(pk); - let (pk, vk) = I::generate_keys(pp, &step_circuit)?; + I::verify(vk, 0, &initial_state, ¤t_state, ¤t_proof)?; - let initial_state = step_circuit.dummy_state(); + for (i, external_inputs) in external_inputs_vec.into_iter().enumerate() { + let (next_state, _, next_proof) = I::prove( + pk, + step_circuit, + i, + &initial_state, + ¤t_state, + external_inputs, + ¤t_proof, + &mut rng, + )?; + current_state = next_state; + current_proof = next_proof; - let mut prover = IVCStatefulProver::<_, I>::new(&pk, &step_circuit, initial_state)?; + I::verify(vk, i + 1, &initial_state, ¤t_state, ¤t_proof)?; + } + + Ok((current_state, current_proof)) + } + + fn test_auto_state_management>( + pk: &I::ProverKey, + vk: &I::VerifierKey, + step_circuit: &F, + initial_state: F::State, + external_inputs_vec: Vec, + mut rng: impl Rng, + ) -> Result<(F::State, I::Proof), Box> { + let mut prover = IVCStatefulProver::<_, I>::new(pk, step_circuit, initial_state)?; + + I::verify( + vk, + prover.i, + &prover.initial_state, + &prover.current_state, + &prover.current_proof, + )?; for external_inputs in external_inputs_vec { prover.prove_step(external_inputs, &mut rng)?; I::verify( - &vk, + vk, prover.i, &prover.initial_state, &prover.current_state, @@ -263,6 +537,85 @@ mod tests { )?; } + Ok((prover.current_state, prover.current_proof)) + } + + 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(); + + test_auto_state_management::( + &pk, + &vk, + &step_circuit, + initial_state.clone(), + external_inputs_vec.clone(), + &mut rng, + )?; + + test_manual_state_management::( + &pk, + &vk, + &step_circuit, + initial_state, + external_inputs_vec, + &mut rng, + )?; + + Ok(()) + } + + pub fn test_ivc_decider< + D: IVCProofCompressor, + F: FCircuit::Field, ExternalInputs: Clone>, + >( + config: ::Config, + step_circuit: F, + external_inputs_vec: Vec, + mut rng: impl Rng, + ) -> Result<(), Box> { + let pp = D::IVC::preprocess(config, &mut rng)?; + + let (pk, vk) = D::IVC::generate_keys(pp, &step_circuit)?; + + let initial_state = step_circuit.dummy_state(); + + let (current_state, current_proof) = test_auto_state_management::( + &pk, + &vk, + &step_circuit, + initial_state.clone(), + external_inputs_vec.clone(), + &mut rng, + )?; + + let (pk, vk) = D::preprocess_and_generate_keys(&step_circuit, vk, &mut rng)?; + + let proof = D::prove( + &pk, + external_inputs_vec.len(), + &initial_state, + ¤t_state, + ¤t_proof, + &mut rng, + )?; + + D::verify( + &vk, + external_inputs_vec.len(), + &initial_state, + ¤t_state, + &proof, + )?; + Ok(()) } } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 951d24a9b..357922139 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -15,6 +15,7 @@ ark-relations = { workspace = true } ark-r1cs-std = { workspace = true } ark-serialize = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } +hashbrown = { workspace = true } itertools = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } @@ -41,6 +42,7 @@ parallel = [ "ark-ff/parallel", "ark-poly/parallel", "ark-relations/parallel", + "ark-crypto-primitives/parallel", "ark-r1cs-std/parallel", "ark-serialize/parallel", "ark-std/parallel", diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 4ea52628f..45fd1ff55 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -38,6 +38,7 @@ use crate::{ bits::{FromBitsGadget, ToBitsGadgetExt}, eq::EquivalenceGadget, matrix::{MatrixGadget, SparseMatrixVar}, + vector::VectorMulGadget, }, }, transcripts::AbsorbableVar, @@ -784,7 +785,17 @@ impl TryFrom TwoStageFieldVar for LimbedVar { + type ValueField = Target; + type ConstraintField = Base; type Intermediate = LimbedVar; + + fn additive_identity() -> Self { + Self::constant(BigInt::zero()) + } + + fn multiplicative_identity() -> Self { + Self::constant(BigInt::one()) + } } // Only implement `EqGadget` for aligned variables. @@ -911,6 +922,59 @@ impl AbsorbableVar for LimbedVar { } } +impl< + CF: SonobeField, + Cfg, + Other: Index>, + const LHS_ALIGNED: bool, + const RHS_ALIGNED: bool, +> VectorMulGadget for [(LimbedVar, usize)] +{ + type Output = LimbedVar; + + fn mul(&self, other: &Other) -> Result { + let len = self + .iter() + .map(|(value, index)| value.limbs.len() + other[*index].limbs.len() - 1) + .max() + .unwrap_or(0); + // 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| { + Bounds::add_many( + &self + .iter() + .flat_map(|(value, index)| { + let start = + max(i + 1, other[*index].bounds.len()) - other[*index].bounds.len(); + let end = min(i + 1, value.bounds.len()); + (start..end).map(|j| value.bounds[j].mul(&other[*index].bounds[i - j])) + }) + .collect::>(), + ) + .filter_safe::() + }) + .collect::>>() + .ok_or(SynthesisError::Unsatisfiable)?; + let limbs = (0..len) + .map(|i| { + self.iter() + .flat_map(|(value, index)| { + let start = + max(i + 1, other[*index].limbs.len()) - other[*index].limbs.len(); + let end = min(i + 1, value.limbs.len()); + (start..end).map(|j| &value.limbs[j] * &other[*index].limbs[i - j]) + }) + .sum() + }) + .collect(); + Ok(LimbedVar::new(limbs, bounds)) + } +} + impl MatrixGadget> for SparseMatrixVar> { @@ -918,50 +982,7 @@ impl MatrixGadget> &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_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| { - Bounds::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(LimbedVar::new(limbs, bounds)) - }) - .collect() + self.0.iter().map(|row| row.mul(v)).collect() } } @@ -1060,6 +1081,7 @@ impl AllocVar<(BigInt, Bounds), F> for LimbedVar + Absorbable - + Inputize + Val< - Var: FieldVar + WitnessToPublic, + Var: FieldVar + WitnessToPublic + Inputize, EmulatedVar = EmulatedFieldVar, > { @@ -83,18 +82,21 @@ impl AbsorbableVar for FpVar { } } -impl, const N: usize> Inputize for Fp { - fn inputize(&self) -> Vec { - vec![*self] +impl Inputize for FpVar { + fn inputize(value: &Self::Value) -> Vec { + vec![*value] } } -impl InputizeEmulated for P { - fn inputize_emulated(&self) -> Vec { - self.into_bigint() +impl Inputize for EmulatedFieldVar { + fn inputize(value: &Self::Value) -> Vec { + // TODO: pack bits + value + .into_bigint() .to_bits_le() - .chunks(F::BITS_PER_LIMB) - .map(|chunk| F::from(F::BigInt::from_bits_le(chunk))) + .into_iter() + .take(Target::MODULUS_BIT_SIZE as usize) + .map(Base::from) .collect() } } @@ -111,7 +113,17 @@ impl WitnessToPublic for FpVar { // computing them outside the circuit. // - `.enforce_equal()` prevents a malicious prover from claiming public // inputs that are not the honest `x` computed in-circuit. - self.enforce_equal(&FpVar::new_input(self.cs(), || self.value())?) + self.enforce_equal(&Self::new_input(self.cs(), || { + Ok(self.value().unwrap_or_default()) + })?) + } +} + +impl WitnessToPublic for EmulatedFieldVar { + fn mark_as_public(&self) -> Result<(), SynthesisError> { + self.enforce_equal(&Self::new_input(self.cs(), || { + Ok(self.value().unwrap_or_default()) + })?) } } @@ -119,42 +131,66 @@ impl WitnessToPublic for FpVar { /// two-stage arithmetic model. /// /// In this model, we consider two stages of in-circuit variables for field -/// elements when performing arithmetic operations: +/// elements when performing field 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. +/// Therefore, the field operations on two field variables yield a new +/// intermediate variable. pub trait TwoStageFieldVar: Clone + Add + for<'a> Add<&'a Self, Output = Self::Intermediate> + + Sub + + for<'a> Sub<&'a Self, Output = Self::Intermediate> + Mul + for<'a> Mul<&'a Self, Output = Self::Intermediate> + + GR1CSVar + + AllocVar { - /// The intermediate variable type used during arithmetic operations. + type ValueField: Field; + type ConstraintField: Field; + + /// The intermediate variable type used during field 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. + /// we require this type to support field operations with both itself and + /// the original field variable type. type Intermediate: Clone + From + TryInto + Add + for<'a> Add<&'a Self::Intermediate, Output = Self::Intermediate> + + Sub + + for<'a> Sub<&'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> + + Sub + + for<'a> Sub<&'a Self, Output = Self::Intermediate> + Mul + for<'a> Mul<&'a Self, Output = Self::Intermediate>; + + fn additive_identity() -> Self; + fn multiplicative_identity() -> Self; } // Operations over the canonical variable `FpVar` always yield another `FpVar`. impl TwoStageFieldVar for FpVar { + type ValueField = F; + type ConstraintField = F; type Intermediate = Self; + + fn additive_identity() -> Self { + Self::zero() + } + + fn multiplicative_identity() -> Self { + Self::one() + } } diff --git a/crates/primitives/src/algebra/group/emulated.rs b/crates/primitives/src/algebra/group/emulated.rs index 6e6b6f3bb..53054b0ba 100644 --- a/crates/primitives/src/algebra/group/emulated.rs +++ b/crates/primitives/src/algebra/group/emulated.rs @@ -155,10 +155,7 @@ mod tests { use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; - use crate::{ - traits::{Inputize, InputizeEmulated}, - transcripts::Absorbable, - }; + use crate::{traits::Inputize, transcripts::Absorbable}; #[test] fn test_alloc_zero() { @@ -195,14 +192,14 @@ mod tests { 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_emulated() + EmulatedAffineVar::inputize(&p) ); 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() + ProjectiveVar::inputize(&p) ); Ok(()) } diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index e116799e3..212a662a1 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -14,9 +14,13 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::SynthesisError; use crate::{ - algebra::{Val, field::SonobeField, group::emulated::EmulatedAffineVar}, + algebra::{ + Val, + field::{SonobeField, emulated::EmulatedFieldVar}, + group::emulated::EmulatedAffineVar, + }, circuits::WitnessToPublic, - traits::{Dummy, Inputize, InputizeEmulated}, + traits::{Dummy, Inputize}, transcripts::{Absorbable, AbsorbableVar}, }; @@ -32,10 +36,9 @@ pub type CF2 = <::BaseField as Field>::BasePrimeField; pub trait SonobeCurve: CurveGroup + Absorbable - + Inputize - + InputizeEmulated + Val< - Var: CurveVar + AbsorbableVar + WitnessToPublic, + Var: CurveVar + AbsorbableVar + WitnessToPublic + + Inputize, EmulatedVar = EmulatedAffineVar, > { @@ -85,9 +88,11 @@ impl> AbsorbableVar } } -impl> Inputize for Projective

{ - fn inputize(&self) -> Vec { - let affine = self.into_affine(); +impl> Inputize + for ProjectiveVar> +{ + fn inputize(value: &Self::Value) -> Vec { + let affine = value.into_affine(); match affine.xy() { Some((x, y)) => vec![x, y, One::one()], None => vec![Zero::zero(), One::one(), Zero::zero()], @@ -95,14 +100,11 @@ impl> Inputize for Projec } } -impl> - InputizeEmulated for Projective

-{ - fn inputize_emulated(&self) -> Vec { - let affine = self.into_affine(); +impl Inputize for EmulatedAffineVar { + fn inputize(value: &Self::Value) -> Vec { + let affine = value.into_affine(); let (x, y) = affine.xy().unwrap_or_default(); - - [x, y].inputize_emulated() + <[EmulatedFieldVar]>::inputize(&vec![x, y]) } } @@ -115,3 +117,13 @@ impl> WitnessToPublic self.to_constraint_field()?[..2].mark_as_public() } } + +impl WitnessToPublic + for EmulatedAffineVar +{ + fn mark_as_public(&self) -> Result<(), SynthesisError> { + self.x.mark_as_public()?; + self.y.mark_as_public()?; + Ok(()) + } +} diff --git a/crates/primitives/src/algebra/ops/eq.rs b/crates/primitives/src/algebra/ops/eq.rs index d6d0da9c3..364a3b425 100644 --- a/crates/primitives/src/algebra/ops/eq.rs +++ b/crates/primitives/src/algebra/ops/eq.rs @@ -23,7 +23,7 @@ impl EquivalenceGadget> for FpVar { } } -impl> EquivalenceGadget<[T]> for [T] { +impl, T> EquivalenceGadget<[T]> for [S] { fn enforce_equivalent(&self, other: &[T]) -> Result<(), SynthesisError> { if self.len() != other.len() { return Err(SynthesisError::Unsatisfiable); diff --git a/crates/primitives/src/algebra/ops/matrix.rs b/crates/primitives/src/algebra/ops/matrix.rs index fef5a7d04..89a696c9c 100644 --- a/crates/primitives/src/algebra/ops/matrix.rs +++ b/crates/primitives/src/algebra/ops/matrix.rs @@ -1,7 +1,7 @@ //! This module defines in-circuit sparse matrix types and implements operations //! over them. -use ark_ff::PrimeField; +use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ GR1CSVar, alloc::{AllocVar, AllocationMode}, @@ -10,6 +10,8 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{Matrix, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, ops::Index}; +use crate::algebra::ops::vector::VectorMulGadget; + /// [`MatrixGadget`] defines operations on in-circuit matrix variables. pub trait MatrixGadget { /// [`MatrixGadget::mul_vector`] computes the product of `self` and a column @@ -24,7 +26,7 @@ pub trait MatrixGadget { #[derive(Debug, Clone)] pub struct SparseMatrixVar(pub Vec>); -impl> AllocVar, CF> +impl> AllocVar, CF> for SparseMatrixVar { fn new_variable>>( @@ -56,29 +58,12 @@ impl MatrixGadget> for SparseMatrixVar> { &self, v: &impl Index>, ) -> Result>, SynthesisError> { - Ok(self + self .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]) - .collect::>(); - if products.is_constant() { - FpVar::constant(products.value().unwrap_or_default().into_iter().sum()) - } else { - products.iter().sum() - } + row.mul(v) }) - .collect()) + .collect() } } diff --git a/crates/primitives/src/algebra/ops/vector.rs b/crates/primitives/src/algebra/ops/vector.rs index cfcdc6fb5..6d6d05922 100644 --- a/crates/primitives/src/algebra/ops/vector.rs +++ b/crates/primitives/src/algebra/ops/vector.rs @@ -1,57 +1,44 @@ //! 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>; +use std::ops::Index; - /// [`VectorGadget::sub`] computes the element-wise difference of two - /// vectors. - fn sub(&self, other: &Self) -> Result, SynthesisError>; +use ark_ff::PrimeField; +use ark_r1cs_std::{ + GR1CSVar, + fields::{FieldVar, fp::FpVar}, +}; +use ark_relations::gr1cs::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>; +/// [`VectorMulGadget`] defines the multiplication (dot product) operation on +/// in-circuit vector variables. +pub trait VectorMulGadget { + type Output; - /// [`VectorGadget::hadamard`] computes the element-wise (Hadamard) product - /// of two vectors. - fn hadamard(&self, other: &Self) -> Result, SynthesisError>; + fn mul(&self, other: &Other) -> Result; } -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()) - } +impl VectorMulGadget<[FpVar]> for [FpVar] { + type Output = FpVar; - fn sub(&self, other: &Self) -> Result, SynthesisError> { + fn mul(&self, other: &[FpVar]) -> Result { 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()) + Ok(self.iter().zip(other).map(|(a, b)| a * b).sum()) } +} - 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()) +impl>> VectorMulGadget + for [(FpVar, usize)] +{ + type Output = FpVar; + + fn mul(&self, other: &Other) -> Result { + Ok(self + .iter() + .map(|(value, index)| value * &other[*index]) + .sum()) } } diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index c71465f7c..b796a4c84 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -21,13 +21,19 @@ use ark_ff::Field; use ark_poly::DenseMultilinearExtension; -use ark_relations::gr1cs::{ConstraintSystem, Matrix}; -use ark_std::{cfg_into_iter, cfg_iter}; +use ark_relations::gr1cs::{ConstraintSystem, Matrix, SynthesisError}; +use ark_std::{cfg_into_iter, cfg_iter, ops::Index}; #[cfg(feature = "parallel")] use rayon::prelude::*; use super::{Arith, Error}; -use crate::{algebra::ops::poly::MLEHelper, circuits::Assignments}; +use crate::{ + algebra::{ + field::TwoStageFieldVar, + ops::{matrix::SparseMatrixVar, poly::MLEHelper, vector::VectorMulGadget}, + }, + circuits::Assignments, +}; /// [`CCS`] is an abstract trait that defines the behavior of all CCS variants, /// including but not limited to R1CS. @@ -123,3 +129,54 @@ pub trait CCS: .collect() } } + +pub trait CCSGadget { + type FieldVar: TwoStageFieldVar; + + /// [`CCS::matrices`] returns the matrices contained in a concrete CCS + /// instance `self`. + fn matrices(&self) -> &[SparseMatrixVar]; + + /// [`CCS::evaluate_ccs`] evaluates the CCS relation at a given vector of + /// assignments, multisets, and coefficients. + fn evaluate_ccs, const Q: usize>( + &self, + z: A, + multisets: [Vec; Q], + coefficients: [impl Clone + Into<::Intermediate>; Q], + ) -> Result::Intermediate>, SynthesisError> + where + [(Self::FieldVar, usize)]: + VectorMulGadget::Intermediate>, + { + let matrices = self.matrices(); + + // Recall that the evaluation of CCS at z is defined as: + // `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j · z))`, + // where $\prod$ denotes the Hadamard product. + (0..matrices[0].0.len()) + .map(|row| { + // The `row`-th entry of the resulting vector is: + // `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j[row] · z))` + let mut sum = None; + + for (s, c) in multisets.iter().zip(&coefficients) { + // Each term in the sum is: + // `c_i · 〇_{j ∈ S_i} (M_j[row] · z)` + let mut prod = c.clone().into(); + for i in s { + // TODO: intermediate + // let v = matrices[*i].0[row].mul(&z)?; + let v = matrices[*i].0[row].mul(&z)?.try_into().map_err(|_| SynthesisError::Unsatisfiable)?; + prod = prod * v; + } + sum = match sum { + Some(sum) => Some(sum + prod), + None => Some(prod), + }; + } + Ok(sum.unwrap()) + }) + .collect() + } +} diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index 5e76a9ade..257b3661f 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -6,6 +6,8 @@ //! However, user circuits are always synthesized into R1CS currently, since //! R1CS is the only supported constraint system by ark-relations. +use ark_ff::Field; +use ark_r1cs_std::alloc::AllocVar; use ark_relations::gr1cs::SynthesisError; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{fmt::Debug, log2}; @@ -85,6 +87,12 @@ pub trait Arith: Clone + Default + Send + Sync + CanonicalSerialize + CanonicalD fn config(&self) -> ArithConfig; } +pub trait ArithGadget: AllocVar { + type ConstraintField: Field; + + type Widget: Arith; +} + /// [`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 @@ -171,7 +179,7 @@ impl> Relation for A { /// [`ArithRelationGadget`] defines the in-circuit gadget for constraint system /// operations in the same way as [`ArithRelation`]. -pub trait ArithRelationGadget { +pub trait ArithRelationGadget: ArithGadget { /// [`ArithRelationGadget::Evaluation`] defines the type of the evaluation /// result returned by [`ArithRelationGadget::eval_relation`], and consumed /// by [`ArithRelationGadget::check_evaluation`]. diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index 594c30be3..6932c5fd3 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -1,40 +1,57 @@ //! This module implements in-circuit R1CS variables and relation check gadgets. -use ark_ff::{PrimeField, Zero}; +use ark_ff::{Field, PrimeField, Zero}; use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{One, borrow::Borrow, ops::Mul}; +use ark_std::{ + One, + borrow::Borrow, + ops::{Index, Mul, Sub}, +}; -use super::R1CS; +use super::{R1CS, RelaxedInstance, RelaxedWitness}; use crate::{ - algebra::ops::{ - eq::EquivalenceGadget, - matrix::{MatrixGadget, SparseMatrixVar}, - vector::VectorGadget, + algebra::{ + field::TwoStageFieldVar, + ops::{ + eq::EquivalenceGadget, + matrix::{MatrixGadget, SparseMatrixVar}, + vector::VectorMulGadget, + }, }, - arithmetizations::ArithRelationGadget, + arithmetizations::{ArithGadget, ArithRelationGadget, ccs::CCSGadget}, circuits::Assignments, }; -/// [`R1CSMatricesVar`] is the in-circuit variable of a given R1CS structure. +/// [`R1CSVar`] 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 { - A: SparseMatrixVar, - B: SparseMatrixVar, - C: SparseMatrixVar, +pub struct R1CSVar { + matrices: [SparseMatrixVar; 3], +} + +impl ArithGadget for R1CSVar { + type ConstraintField = FVar::ConstraintField; + + type Widget = R1CS; } -impl> - AllocVar, ConstraintF> for R1CSMatricesVar +impl CCSGadget for R1CSVar { + type FieldVar = FVar; + + fn matrices(&self) -> &[SparseMatrixVar] { + &self.matrices[..] + } +} + +impl + AllocVar, FVar::ConstraintField> for R1CSVar { - fn new_variable>>( - cs: impl Into>, + fn new_variable>>( + cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { @@ -44,68 +61,78 @@ impl> let val = val.borrow(); Ok(Self { - A: SparseMatrixVar::::new_variable( - cs.clone(), - || Ok(&val.matrices[0]), - mode, - )?, - B: SparseMatrixVar::::new_variable( - cs.clone(), - || Ok(&val.matrices[1]), - mode, - )?, - C: SparseMatrixVar::::new_variable( - cs.clone(), - || Ok(&val.matrices[2]), - mode, - )?, + matrices: [ + AllocVar::new_variable(cs.clone(), || Ok(&val.matrices[0]), mode)?, + AllocVar::new_variable(cs.clone(), || Ok(&val.matrices[1]), mode)?, + AllocVar::new_variable(cs.clone(), || Ok(&val.matrices[2]), mode)?, + ], }) }) } } -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 evaluate_at( +impl R1CSVar { + pub fn evaluate_r1cs>( &self, - z: Assignments>, - ) -> Result, SynthesisError> { - // Multiply Cz by z[0] (u) here, allowing this method to be reused for - // 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)?; - AzBz.sub(&uCz) + z: A, + ) -> Result, SynthesisError> + where + [(FVar, usize)]: VectorMulGadget, + { + let neg_u = FVar::additive_identity() - &z[0]; + self.evaluate_ccs( + z, + [vec![0, 1], vec![2]], + [FVar::multiplicative_identity().into(), neg_u], + ) } } -impl, UVar: AsRef<[FVar]>> ArithRelationGadget - for R1CSMatricesVar +impl, UVar: AsRef<[FVar]>> + ArithRelationGadget for R1CSVar where - SparseMatrixVar: MatrixGadget, - [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>, + [FVar::Intermediate]: EquivalenceGadget<[FVar]>, + [(FVar, usize)]: + for<'a> VectorMulGadget, Output = FVar::Intermediate>, { - type Evaluation = Vec; + type Evaluation = Vec; fn eval_relation(&self, w: &WVar, u: &UVar) -> Result { - self.evaluate_at((FVar::one(), u.as_ref(), w.as_ref()).into()) + self.evaluate_r1cs(Assignments::from(( + FVar::multiplicative_identity(), + u.as_ref(), + w.as_ref(), + ))) } fn check_evaluation(_w: &WVar, _u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError> { - e.enforce_equivalent(&vec![FVar::zero(); e.len()]) + e.enforce_equivalent(&vec![FVar::additive_identity(); e.len()]) + } +} + +impl ArithRelationGadget, RelaxedInstance<&[FVar]>> + for R1CSVar +where + [FVar::Intermediate]: EquivalenceGadget<[FVar]>, + [(FVar, usize)]: + for<'a> VectorMulGadget, Output = FVar::Intermediate>, +{ + type Evaluation = Vec; + + fn eval_relation( + &self, + w: &RelaxedWitness<&[FVar]>, + u: &RelaxedInstance<&[FVar]>, + ) -> Result { + self.evaluate_r1cs(Assignments::from((u.u.clone(), u.x, w.w))) + } + + fn check_evaluation( + w: &RelaxedWitness<&[FVar]>, + _u: &RelaxedInstance<&[FVar]>, + e: Self::Evaluation, + ) -> Result<(), SynthesisError> { + e.enforce_equivalent(&w.e) } } diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 97aa20cdd..96eebc997 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -11,7 +11,7 @@ use ark_std::{cfg_into_iter, cfg_iter}; use rayon::prelude::*; use super::{Arith, ArithConfig, ArithRelation, Error, ccs::CCS}; -use crate::circuits::Assignments; +use crate::circuits::{Assignments}; pub mod circuits; diff --git a/crates/primitives/src/circuits/alloc.rs b/crates/primitives/src/circuits/alloc.rs new file mode 100644 index 000000000..e26b6281c --- /dev/null +++ b/crates/primitives/src/circuits/alloc.rs @@ -0,0 +1,35 @@ +use ark_ff::{Field, PrimeField}; +use ark_r1cs_std::fields::fp::{AllocatedFp, FpVar}; +use ark_relations::gr1cs::{ConstraintSystem, ConstraintSystemRef, SynthesisError, Variable}; +use ark_std::{ + any::{Any, TypeId}, + collections::BTreeMap, + hash::{BuildHasherDefault, Hasher}, +}; +use hashbrown::HashSet; + +#[derive(Default)] +pub struct IdentityHasher { + value: u64, +} + +impl Hasher for IdentityHasher { + fn finish(&self) -> u64 { + self.value + } + + fn write(&mut self, _: &[u8]) { + panic!("IdentityHasher only supports usize"); + } + + fn write_usize(&mut self, value: usize) { + self.value = value as u64; + } +} + +pub type UsizeSet = HashSet>; + +pub struct CommittedCache; +pub struct CommitmentCache; +pub struct CommitmentKeyCache; +pub struct RandomnessCache; diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 20a6eded9..1032227be 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -3,15 +3,20 @@ use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar, eq::EqGadget, fields::fp::FpVar}; use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, SynthesisMode, + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, OptimizationGoal, SynthesisError, + SynthesisMode, }; use ark_std::{ fmt::Debug, ops::{Deref, Index, IndexMut}, }; -use crate::transcripts::{Absorbable, AbsorbableVar}; +use crate::{ + traits::Inputize, + transcripts::{Absorbable, AbsorbableVar}, +}; +pub mod alloc; pub mod utils; /// [`FCircuit`] defines the trait of step circuits being proven by IVC schemes. @@ -49,7 +54,7 @@ pub mod utils; /// /// 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`]. +/// logic in a single [`FCircuit::synthesize_step`]. /// 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, @@ -75,7 +80,8 @@ pub trait FCircuit { type StateVar: GR1CSVar + AllocVar + AbsorbableVar - + EqGadget; + + EqGadget + + Inputize; /// [`FCircuit::ExternalInputs`] is the type of external inputs provided to /// each step of the circuit. type ExternalInputs; @@ -108,7 +114,7 @@ pub trait FCircuit { /// the circuit. fn dummy_external_inputs(&self) -> Self::ExternalInputs; - /// [`FCircuit::generate_step_constraints`] generates the constraints for + /// [`FCircuit::synthesize_step`] 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. @@ -121,7 +127,7 @@ pub trait FCircuit { /// 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( + fn synthesize_step( &self, i: FpVar, state: Self::StateVar, @@ -188,18 +194,17 @@ 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, -} +pub struct ConstraintSystemExt( + ConstraintSystem, +); impl Deref for ConstraintSystemExt { - type Target = ConstraintSystemRef; + type Target = ConstraintSystem; fn deref(&self) -> &Self::Target { - &self.cs + &self.0 } } @@ -209,7 +214,8 @@ impl /// [`ConstraintSystemExt::new`] creates a new constraint system wrapper /// with the specified flags. pub fn new() -> Self { - let cs = ConstraintSystem::::new_ref(); + let mut cs = ConstraintSystem::::new(); + cs.set_optimization_goal(OptimizationGoal::Constraints); let mode = if ASSIGNMENTS_ENABLED { SynthesisMode::Prove { construct_matrices: ARITH_ENABLED, @@ -219,14 +225,14 @@ impl SynthesisMode::Setup }; cs.set_mode(mode); - Self { cs } + 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, + &mut self, circuit: impl ConstraintSynthesizer, ) -> Result<(), SynthesisError> { self.execute_fn(|cs| circuit.generate_constraints(cs)) @@ -237,12 +243,15 @@ impl /// 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, + &mut self, circuit: impl FnOnce(ConstraintSystemRef) -> Result, ) -> Result { - let result = circuit(self.cs.clone())?; + let cs = std::mem::take(&mut self.0); + let cs_ref = ConstraintSystemRef::new(cs); + let result = circuit(cs_ref.clone())?; + self.0 = cs_ref.into_inner().unwrap(); if ARITH_ENABLED { - self.cs.finalize(); + self.0.finalize(); } Ok(result) } @@ -268,7 +277,7 @@ impl ArithExtractor { /// 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()) + Ok(self.0.into()) } } @@ -276,9 +285,12 @@ 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(); + let cs = self.0; + + let witness = cs.assignments.witness_assignment; + let mut instance = cs.assignments.instance_assignment; // skip the first element which is '1' - let instance = self.cs.instance_assignment()?[1..].to_vec(); + instance.remove(0); Ok((F::one(), instance, witness).into()) } @@ -291,12 +303,26 @@ pub trait WitnessToPublic { fn mark_as_public(&self) -> Result<(), SynthesisError>; } +impl WitnessToPublic for &T { + fn mark_as_public(&self) -> Result<(), SynthesisError> { + (*self).mark_as_public() + } +} + impl WitnessToPublic for [T] { fn mark_as_public(&self) -> Result<(), SynthesisError> { self.iter().try_for_each(|x| x.mark_as_public()) } } +/// [`WitnessToCommitted`] defines a helper trait for marking witness variables +/// as committed witnesses in the constraint system. +pub trait WitnessToCommitted { + /// [`WitnessToCommitted::mark_as_committed`] marks a witness variable as a + /// committed witness. + fn mark_as_committed(&self) -> Result<(), SynthesisError>; +} + #[cfg(test)] mod tests { use ark_bn254::Fr; @@ -331,7 +357,7 @@ mod tests { let circuit = CircuitForTest:: { x: Fr::rand(&mut rng), }; - let cs = ArithExtractor::new(); + let mut cs = ArithExtractor::new(); cs.execute_synthesizer(circuit)?; assert_eq!(cs.arith::>()?, constraints_for_test()); Ok(()) @@ -343,7 +369,7 @@ mod tests { let x = Fr::rand(&mut rng); let circuit = CircuitForTest:: { x }; - let cs = AssignmentsExtractor::new(); + let mut cs = AssignmentsExtractor::new(); cs.execute_synthesizer(circuit)?; assert_eq!(cs.assignments()?, satisfying_assignments_for_test(x)); Ok(()) diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 210ae345f..a5753eab0 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -72,7 +72,7 @@ impl FCircuit for CircuitForTest { fn dummy_external_inputs(&self) -> Self::ExternalInputs {} - fn generate_step_constraints( + fn synthesize_step( &self, _i: FpVar, z_i: Self::StateVar, diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 3d96e5ca5..13e8ee6b1 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -1,7 +1,9 @@ //! 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_r1cs_std::{ + GR1CSVar, alloc::AllocVar, eq::EqGadget, fields::fp::FpVar, select::CondSelectGadget, +}; use ark_relations::gr1cs::SynthesisError; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{ @@ -19,7 +21,7 @@ use crate::{ group::emulated::EmulatedAffineVar, ops::bits::FromBitsGadget, }, - traits::{CF1, CF2, SonobeCurve, SonobeField}, + traits::{CF1, CF2, Inputize, SonobeCurve, SonobeField}, transcripts::{Absorbable, AbsorbableVar}, }; @@ -139,17 +141,20 @@ pub trait CommitmentDefGadget: Clone { /// the scalars being committed to. type ScalarVar: AbsorbableVar + CondSelectGadget + + EqGadget + FromBitsGadget - + AllocVar<::Scalar, Self::ConstraintField> - + GR1CSVar::Scalar> - + TwoStageFieldVar; + + TwoStageFieldVar< + ConstraintField = Self::ConstraintField, + ValueField = ::Scalar, + >; /// [`CommitmentDefGadget::CommitmentVar`] is the in-circuit variable type /// for the commitment. type CommitmentVar: Clone + AbsorbableVar + CondSelectGadget + AllocVar<::Commitment, Self::ConstraintField> - + GR1CSVar::Commitment>; + + GR1CSVar::Commitment> + + Inputize; /// [`CommitmentDefGadget::RandomnessVar`] is the in-circuit variable type /// for the randomness used in the commitment. type RandomnessVar: AllocVar<::Randomness, Self::ConstraintField> @@ -181,8 +186,7 @@ pub trait GroupBasedCommitment: { /// [`GroupBasedCommitment::Gadget1`] points to the in-circuit gadget for /// the group-based commitment scheme over the curve's base field. - type Gadget1: CommitmentOpsGadget - + CommitmentDefGadget< + type Gadget1: CommitmentOpsGadget< ConstraintField = CF2, ScalarVar = EmulatedFieldVar, Self::Scalar>, CommitmentVar = ::Var, @@ -190,7 +194,7 @@ pub trait GroupBasedCommitment: >; /// [`GroupBasedCommitment::Gadget2`] points to the in-circuit gadget for /// the group-based commitment scheme over the curve's scalar field. - type Gadget2: CommitmentDefGadget< + type Gadget2: CommitmentOpsGadget< ConstraintField = Self::Scalar, ScalarVar = FpVar, CommitmentVar = EmulatedAffineVar, diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 6048eda67..acb345e4a 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -5,8 +5,11 @@ //! where `g` and `h` are generators, `r` is a random scalar, and `` is //! the multi-scalar multiplication of `g` and `v`. +use std::any::TypeId; + use ark_ec::AffineRepr; use ark_r1cs_std::{ + GR1CSVar, alloc::{AllocVar, AllocationMode}, boolean::Boolean, convert::ToBitsGadget, @@ -21,6 +24,12 @@ use ark_std::{UniformRand, borrow::Borrow, iter::repeat_with, marker::PhantomDat use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error}; use crate::{ algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, + circuits::{ + WitnessToCommitted, WitnessToPublic, alloc::{ + CommitmentCache, CommitmentKeyCache, CommittedCache, RandomnessCache, + UsizeSet, + } + }, commitments::{CommitmentOpsGadget, GroupBasedCommitment}, traits::{CF1, CF2, SonobeCurve}, utils::null::Null, @@ -30,8 +39,8 @@ use crate::{ /// scheme, where `H` controls whether the scheme is hiding or not. #[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct PedersenKey { - g: Vec, - h: C, + pub g: Vec, + pub h: C::Affine, } impl CommitmentKey for PedersenKey { @@ -47,7 +56,11 @@ impl PedersenKey { .collect::>(); Self { g: C::normalize_batch(&generators), - h: if H { C::rand(&mut rng) } else { C::zero() }, + h: if H { + C::Affine::rand(&mut rng) + } else { + C::Affine::zero() + }, } } } @@ -59,7 +72,7 @@ impl PedersenKey { } // + 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)) + Ok(C::msm_unchecked(&self.g, v) + self.h * r) } } @@ -90,6 +103,8 @@ pub struct PedersenEmulatedKeyVar { h: EmulatedAffineVar, C>, } +pub struct PedersenFakeKeyVar(PedersenKey); + impl AllocVar, C::BaseField> for PedersenKeyVar { @@ -127,11 +142,24 @@ impl AllocVar, CF1> || Ok(g.iter().map(|i| i.into_group()).collect::>()), mode, )?, - h: AllocVar::new_variable(cs.clone(), || Ok(*h), mode)?, + h: AllocVar::new_variable(cs.clone(), || Ok(h.into_group()), mode)?, }) } } +impl AllocVar, CF1> + for PedersenFakeKeyVar +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let v = f()?; + Ok(PedersenFakeKeyVar(v.borrow().clone())) + } +} + /// [`Pedersen`] defines the out-of-circuit Pedersen widget, where `H` controls /// whether the scheme is hiding or not. #[derive(Clone, Debug, PartialEq, Eq)] @@ -159,12 +187,12 @@ impl CommitmentDef for Pedersen { impl GroupBasedCommitment for Pedersen { type Gadget1 = PedersenGadget; - type Gadget2 = PedersenEmulatedGadget; + type Gadget2 = PedersenCommitAndProveGadget; } impl GroupBasedCommitment for Pedersen { type Gadget1 = PedersenGadget; - type Gadget2 = PedersenEmulatedGadget; + type Gadget2 = PedersenCommitAndProveGadget; } impl CommitmentOps for Pedersen { @@ -329,6 +357,112 @@ impl CommitmentDefGadget for PedersenEmulatedGadget { type Widget = Pedersen; } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PedersenCommitAndProveGadget { + _c: PhantomData, +} + +impl CommitmentDefGadget for PedersenCommitAndProveGadget { + type ConstraintField = CF1; + + type KeyVar = PedersenFakeKeyVar; + + type ScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = Null; + + type Widget = Pedersen; +} + +impl CommitmentDefGadget for PedersenCommitAndProveGadget { + type ConstraintField = CF1; + + type KeyVar = PedersenFakeKeyVar; + + type ScalarVar = FpVar>; + + type CommitmentVar = EmulatedAffineVar, C>; + + type RandomnessVar = FpVar>; + + type Widget = Pedersen; +} + +impl CommitmentOpsGadget for PedersenCommitAndProveGadget { + fn open( + ck: &PedersenFakeKeyVar, + v: &[FpVar>], + _r: &Null, + cm: &EmulatedAffineVar, C>, + ) -> Result<(), SynthesisError> { + todo!() + } +} + +impl CommitmentOpsGadget for PedersenCommitAndProveGadget { + fn open( + ck: &PedersenFakeKeyVar, + v: &[FpVar>], + r: &FpVar>, + cm: &EmulatedAffineVar, C>, + ) -> Result<(), SynthesisError> { + cm.mark_as_public()?; + + let cs = v.cs().or(r.cs()).or(cm.cs()); + + let cs = cs.borrow_mut().ok_or(SynthesisError::MissingCS)?; + let mut cache = cs.cache_map.borrow_mut(); + if !cs.is_in_setup_mode() { + cache + .get_mut(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast_mut::>>() + .ok_or(SynthesisError::AssignmentMissing)? + .push(r.value()?); + cache + .get_mut(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast_mut::>() + .ok_or(SynthesisError::AssignmentMissing)? + .push(cm.value()?); + } else { + cache + .get_mut(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast_mut::>>() + .ok_or(SynthesisError::AssignmentMissing)? + .push(PedersenKey { + g: ck.0.g[..v.len()].to_vec(), + h: ck.0.h, + }); + } + // TODO: independent on alloc order + let indices = v + .iter() + .map(|i| match i { + FpVar::Constant(_) => Err(SynthesisError::AssignmentMissing), + FpVar::Var(fp) => { + if !fp.variable.is_witness() { + Err(SynthesisError::AssignmentMissing) + } else { + Ok(fp.variable.index().unwrap()) + } + } + }) + .collect::, _>>()?; + cache + .get_mut(&TypeId::of::()) + .ok_or(SynthesisError::AssignmentMissing)? + .downcast_mut::() + .ok_or(SynthesisError::AssignmentMissing)? + .extend(indices); + + Ok(()) + } +} + #[cfg(test)] mod tests { use ark_bn254::G1Projective; diff --git a/crates/primitives/src/traits.rs b/crates/primitives/src/traits.rs index 6ee687a85..f38346917 100644 --- a/crates/primitives/src/traits.rs +++ b/crates/primitives/src/traits.rs @@ -1,5 +1,8 @@ //! This module defines helper traits used across Sonobe's crates. +use ark_ff::Field; +use ark_r1cs_std::GR1CSVar; + pub use crate::algebra::{ field::SonobeField, group::{CF1, CF2, SonobeCurve}, @@ -37,42 +40,23 @@ impl, B: Dummy> Dummy for (A, B) { /// [`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. +/// represented in the circuit when allocated as public input. /// /// This is useful for the verifier to compute the public inputs. -pub trait Inputize { +pub trait Inputize: GR1CSVar { /// [`Inputize::inputize`] outputs the underlying field elements of `self` /// as if it is allocated in the canonical way in-circuit. - fn inputize(&self) -> Vec; + fn inputize(value: &Self::Value) -> Vec; } -impl> Inputize for [T] { - fn inputize(&self) -> Vec { - self.iter().flat_map(Inputize::::inputize).collect() +impl> Inputize for [T] { + fn inputize(value: &Self::Value) -> Vec { + value.iter().flat_map(T::inputize).collect() } } -/// [`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 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; -} - -impl> InputizeEmulated for [T] { - fn inputize_emulated(&self) -> Vec { - self.iter() - .flat_map(InputizeEmulated::::inputize_emulated) - .collect() +impl, const N: usize> Inputize for [T; N] { + fn inputize(value: &Self::Value) -> Vec { + value.iter().flat_map(T::inputize).collect() } } diff --git a/crates/snarks/Cargo.toml b/crates/snarks/Cargo.toml new file mode 100644 index 000000000..502fd8484 --- /dev/null +++ b/crates/snarks/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "sonobe-snarks" +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-groth16 = { workspace = true } +ark-poly = { workspace = true } +ark-relations = { workspace = true } +ark-serialize = { workspace = true } +ark-snark = { workspace = true } +ark-std = { workspace = true, features = ["getrandom"] } +hashbrown = { workspace = true } +itertools = { workspace = true } +thiserror = { workspace = true } +rayon = { workspace = true } + +sonobe-primitives = { workspace = true } + +[dev-dependencies] +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"] } + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +wasm-bindgen-test = { workspace = true } + +[features] +default = [] +parallel = [ + "sonobe-primitives/parallel", + "ark-groth16/parallel", +] diff --git a/crates/snarks/src/cp/groth16.rs b/crates/snarks/src/cp/groth16.rs new file mode 100644 index 000000000..a46c9865c --- /dev/null +++ b/crates/snarks/src/cp/groth16.rs @@ -0,0 +1,779 @@ +use ark_ec::{ + AffineRepr, CurveGroup, VariableBaseMSM, + pairing::{Pairing, PairingOutput}, + scalar_mul::BatchMulPreprocessing, +}; +use ark_ff::{Field, One, PrimeField, UniformRand, Zero}; +use ark_groth16::{ + Proof as Groth16Proof, + r1cs_to_qap::{LibsnarkReduction, R1CSToQAP}, +}; +use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; +use ark_relations::gr1cs::{ + ConstraintSynthesizer, ConstraintSystem, Matrix, OptimizationGoal, R1CS_PREDICATE_LABEL, + SynthesisError, SynthesisMode, +}; +use ark_std::{ + any::TypeId, borrow::Borrow, cfg_into_iter, cfg_iter, cfg_iter_mut, end_timer, + hash::BuildHasherDefault, marker::PhantomData, rand::RngCore, rc::Rc, start_timer, +}; +use hashbrown::HashSet; +#[cfg(not(feature = "parallel"))] +use itertools::{Either, Itertools}; +#[cfg(feature = "parallel")] +use rayon::{iter::Either, prelude::*}; +use sonobe_primitives::{ + arithmetizations::{Arith, ccs::CCS, r1cs::R1CS}, + circuits::alloc::{CommittedCache, IdentityHasher, UsizeSet}, + commitments::pedersen::PedersenKey, + traits::{SonobeCurve, SonobeField}, +}; +use thiserror::Error; + +use crate::{ + cp::CPSNARK, + linear_subspace::{ + LinearSubspaceSNARK, ProverKey as LinearSubspacePK, VerifierKey as LinearSubspaceVK, + }, +}; + +pub struct CCGroth16ProverKey { + /// The element `beta * G` in `E::G1`. + pub alpha_g1: E::G1Affine, + pub beta_g1: E::G1Affine, + pub beta_g2: E::G2Affine, + /// The element `delta * G` in `E::G1`. + pub delta_g1: E::G1Affine, + pub delta_g2: E::G2Affine, + /// The element `eta*delta^-1 * G` in `E::G1`. + pub eta_delta_inv_g1: E::G1Affine, + /// The element `eta*gamma^-1 * G` in `E::G1`. + pub eta_gamma_inv_g1: E::G1Affine, + /// The elements `a_i * G` in `E::G1`. + pub a_query: Vec, + /// The elements `b_i * G` in `E::G1`. + pub b_g1_query: Vec, + /// The elements `b_i * H` in `E::G2`. + pub b_g2_query: Vec, + /// The elements `h_i * G` in `E::G1`. + pub h_query: Vec, + /// The elements `l_i * G` in `E::G1`. + pub l_query: Vec, + /// The `gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H`, where `H` is + /// the generator of `E::G1`. + pub gamma_abc_g1_cm: Vec, +} + +pub struct CCGroth16VerifierKey { + /// The element `e(alpha * G, beta * H)` in `E::GT`. + pub alpha_g1_beta_g2: PairingOutput, + /// The element `- gamma * H` in `E::G2`, prepared for use in pairings. + pub gamma_g2_neg_pc: E::G2Prepared, + /// The element `- delta * H` in `E::G2`, prepared for use in pairings. + pub delta_g2_neg_pc: E::G2Prepared, + /// The `gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H`, where `H` is + /// the generator of `E::G1`. + pub gamma_abc_g1_pub: Vec, +} + +pub struct ProverKey { + pub r1cs: R1CS, + pub committed_variable_indices: HashSet>, + pub cc_pk: CCGroth16ProverKey, + pub link_ek: LinearSubspacePK, +} + +pub struct VerifierKey { + pub cc_vk: CCGroth16VerifierKey, + pub link_vk: LinearSubspaceVK, +} + +pub struct Proof { + pub groth16_proof: Groth16Proof, + /// The `D` element in `G1`. Commits to a subset of private inputs of the + /// circuit + pub d: E::G1Affine, + /// proof of commitment opening equality between `cp_{link}` and `d` + pub link_pi: E::G1Affine, +} + +/// [`Error`] enumerates possible errors during LegoGroth16 operations. +#[derive(Debug, Error)] +pub enum Error { + /// [`Error::SynthesisError`] indicates an error during constraint + /// synthesis. + #[error(transparent)] + SynthesisError(#[from] SynthesisError), + /// [`Error::VerificationFail`] indicates that the verification has + /// failed. + #[error("Verification failed")] + VerificationFail, +} + +pub struct LegoGroth16< + E: Pairing, + QAP: R1CSToQAP = LibsnarkReduction, +> { + _p: PhantomData<(E, QAP)>, +} + +impl, QAP: R1CSToQAP> + CPSNARK for LegoGroth16 +{ + type Field = E::ScalarField; + + type Relation = (R1CS, UsizeSet); + + type Commitment = E::G1Affine; + type CommitmentKey = PedersenKey; + type CommitmentOpening = E::ScalarField; + + type ProverKey = ProverKey; + type VerifierKey = VerifierKey; + type Error = SynthesisError; + type Proof = Proof; + + fn generate_keys( + (r1cs, committed_variable_indices): Self::Relation, + commitment_key: &[impl Borrow + Sync], + mut rng: impl RngCore, + ) -> Result<(Self::ProverKey, Self::VerifierKey), Self::Error> { + let alpha = E::ScalarField::rand(&mut rng); + let beta = E::ScalarField::rand(&mut rng); + let gamma = E::ScalarField::rand(&mut rng); + let delta = E::ScalarField::rand(&mut rng); + let eta = E::ScalarField::rand(&mut rng); + + let gamma_inverse = gamma.inverse().ok_or(SynthesisError::DivisionByZero)?; + let delta_inverse = delta.inverse().ok_or(SynthesisError::DivisionByZero)?; + + let g1_generator = E::G1::rand(&mut rng); + let g2_generator = E::G2::rand(&mut rng); + + let setup_time = start_timer!(|| "Groth16::Generator"); + + let r1cs_config = r1cs.config(); + let n_instance_variables = r1cs_config.n_public_inputs + 1; + let n_constraints = r1cs_config.n_constraints; + let n_variables = r1cs_config.n_variables; + let matrices = r1cs.matrices(); + + let n_committed_variables = committed_variable_indices.len(); + + // Following is the mapping of symbols from the Groth16 paper to this + // implementation l -> num_instance_variables + // m -> qap_num_variables + // x -> t + // t(x) - zt + // u_i(x) -> a + // v_i(x) -> b + // w_i(x) -> c + + /////////////////////////////////////////////////////////////////////////// + let domain_time = start_timer!(|| "Constructing evaluation domain"); + + let domain = GeneralEvaluationDomain::new(n_constraints + n_instance_variables) + .ok_or(SynthesisError::PolynomialDegreeTooLarge)?; + let domain_size = domain.size(); + + let t = domain.sample_element_outside_domain(&mut rng); + let zt_over_delta = domain.evaluate_vanishing_polynomial(t) * delta_inverse; + + end_timer!(domain_time); + /////////////////////////////////////////////////////////////////////////// + + let reduction_time = start_timer!(|| "R1CS to QAP Instance Map with Evaluation"); + + // Evaluate all Lagrange polynomials + let coefficients_time = start_timer!(|| "Evaluate Lagrange coefficients"); + let u = domain.evaluate_all_lagrange_coefficients(t); + end_timer!(coefficients_time); + + let mut a = vec![E::ScalarField::zero(); n_variables]; + let mut b = vec![E::ScalarField::zero(); n_variables]; + let mut c = vec![E::ScalarField::zero(); n_variables]; + + a[0..n_instance_variables] + .copy_from_slice(&u[n_constraints..(n_instance_variables + n_constraints)]); + + for (i, u_i) in u.into_iter().enumerate().take(n_constraints) { + for &(ref coeff, index) in &matrices[0][i] { + a[index] += &(u_i * coeff); + } + for &(ref coeff, index) in &matrices[1][i] { + b[index] += &(u_i * coeff); + } + for &(ref coeff, index) in &matrices[2][i] { + c[index] += &(u_i * coeff); + } + } + + end_timer!(reduction_time); + + // Compute query densities + let non_zero_a = cfg_into_iter!(0..n_variables) + .map(|i| usize::from(!a[i].is_zero())) + .sum::(); + + let non_zero_b = cfg_into_iter!(0..n_variables) + .map(|i| usize::from(!b[i].is_zero())) + .sum::(); + + let (gamma_abc, l): (Vec<_>, Vec<_>) = cfg_iter!(a) + .zip(&b) + .zip(&c) + .enumerate() + .partition_map(|(i, ((a, b), c))| { + if i < n_instance_variables + || committed_variable_indices.contains(&(i - n_instance_variables)) + { + Either::Left((beta * a + alpha * b + c) * gamma_inverse) + } else { + Either::Right((beta * a + alpha * b + c) * delta_inverse) + } + }); + + drop(c); + + // Compute B window table + let g2_time = start_timer!(|| "Compute G2 table"); + let g2_table = BatchMulPreprocessing::new(g2_generator, non_zero_b); + end_timer!(g2_time); + + // Compute the B-query in G2 + let b_g2_time = start_timer!(|| format!("Calculate B G2 of size {}", b.len())); + let b_g2_query = g2_table.batch_mul(&b); + drop(g2_table); + end_timer!(b_g2_time); + + // Compute G window table + let g1_window_time = start_timer!(|| "Compute G1 window table"); + let num_scalars = non_zero_a + non_zero_b + n_variables + domain_size - 1; + let g1_table = BatchMulPreprocessing::new(g1_generator, num_scalars); + end_timer!(g1_window_time); + + // Generate the R1CS proving key + let proving_key_time = start_timer!(|| "Generate the R1CS proving key"); + + // Compute the A-query + let a_time = start_timer!(|| "Calculate A"); + let a_query = g1_table.batch_mul(&a); + drop(a); + end_timer!(a_time); + + // Compute the B-query in G1 + let b_g1_time = start_timer!(|| "Calculate B G1"); + let b_g1_query = g1_table.batch_mul(&b); + drop(b); + end_timer!(b_g1_time); + + // Compute the H-query + let h_time = start_timer!(|| "Calculate H"); + let h_scalars = cfg_into_iter!(0..domain_size - 1) + .map(|i| zt_over_delta * t.pow([i as u64])) + .collect::>(); + let h_query = g1_table.batch_mul(&h_scalars); + end_timer!(h_time); + + // Compute the L-query + let l_time = start_timer!(|| "Calculate L"); + let l_query = g1_table.batch_mul(&l); + drop(l); + end_timer!(l_time); + + end_timer!(proving_key_time); + + // Generate R1CS verification key + let verifying_key_time = start_timer!(|| "Generate the R1CS verification key"); + let mut gamma_abc_g1 = g1_table.batch_mul(&gamma_abc); + let gamma_abc_g1_part_2 = gamma_abc_g1.split_off(n_instance_variables); + drop(g1_table); + + end_timer!(verifying_key_time); + + let eta_gamma_inv_g1 = (g1_generator * (eta * gamma_inverse)).into_affine(); + + let eta_delta_inv_g1 = (g1_generator * (eta * delta_inverse)).into_affine(); + + // Setup public params for the Subspace Snark + let (link_ek, link_vk) = LinearSubspaceSNARK::generate_keys( + commitment_key.len() + 1, + |k| { + let mut p = cfg_iter!(commitment_key) + .zip(&k) + .flat_map(|(ck, u)| cfg_iter!(ck.borrow().g).map(move |i| *i * u)) + .chain( + cfg_iter!(commitment_key) + .zip(&k) + .map(|(ck, u)| ck.borrow().h * u), + ) + .collect::>(); + assert_eq!(p.len(), n_committed_variables + commitment_key.len()); + + cfg_iter_mut!(p) + .zip(&gamma_abc_g1_part_2) + .for_each(|(v, i)| { + *v += *i * k[commitment_key.len()]; + }); + p.push(eta_gamma_inv_g1 * k[commitment_key.len()]); + + p + }, + &mut rng, + ); + + end_timer!(setup_time); + + let alpha_g1 = (g1_generator * alpha).into_affine(); + let beta_g1 = (g1_generator * beta).into_affine(); + let beta_g2 = (g2_generator * beta).into_affine(); + let delta_g1 = (g1_generator * delta).into_affine(); + let delta_g2 = (g2_generator * delta).into_affine(); + + Ok(( + ProverKey { + r1cs, + committed_variable_indices, + cc_pk: CCGroth16ProverKey { + alpha_g1, + beta_g1, + beta_g2, + delta_g1, + delta_g2, + eta_delta_inv_g1, + eta_gamma_inv_g1, + a_query, + b_g1_query, + b_g2_query, + h_query, + l_query, + gamma_abc_g1_cm: gamma_abc_g1_part_2, + }, + link_ek, + }, + VerifierKey { + cc_vk: CCGroth16VerifierKey { + alpha_g1_beta_g2: E::pairing(alpha_g1, beta_g2), + gamma_g2_neg_pc: (g2_generator * -gamma).into_affine().into(), + delta_g2_neg_pc: (-delta_g2).into(), + gamma_abc_g1_pub: gamma_abc_g1, + }, + link_vk, + }, + )) + } + + fn prove( + pk: &Self::ProverKey, + x: &[Self::Field], + w: &[Self::Field], + o: &[Self::CommitmentOpening], + mut rng: impl RngCore, + ) -> Result { + let r = E::ScalarField::rand(&mut rng); + let s = E::ScalarField::rand(&mut rng); + let v = E::ScalarField::rand(&mut rng); + + let r1cs_config = pk.r1cs.config(); + let num_inputs = r1cs_config.n_public_inputs + 1; + let num_constraints = r1cs_config.n_constraints; + + let prover_time = start_timer!(|| "Groth16::Prover"); + + let assignment = [&[E::ScalarField::one()][..], x, w].concat(); + + let witness_map_time = start_timer!(|| "R1CS to QAP witness map"); + let h = { + QAP::witness_map_from_matrices::<_, GeneralEvaluationDomain<_>>( + &pk.r1cs.matrices(), + num_inputs, + num_constraints, + &assignment, + )? + }; + end_timer!(witness_map_time); + + let assignment_bigint = cfg_into_iter!(assignment) + .map(|s| s.into_bigint()) + .collect::>(); + + // Compute A + let a_acc_time = start_timer!(|| "Compute A"); + let g_a = pk.cc_pk.delta_g1 * r + + E::G1::msm_bigint(&pk.cc_pk.a_query, &assignment_bigint) + + pk.cc_pk.alpha_g1; + end_timer!(a_acc_time); + + // Compute B in G1 if needed + let b_g1_acc_time = start_timer!(|| "Compute B in G1"); + let g1_b = if !r.is_zero() { + pk.cc_pk.delta_g1 * s + + E::G1::msm_bigint(&pk.cc_pk.b_g1_query, &assignment_bigint) + + pk.cc_pk.beta_g1 + } else { + E::G1::zero() + }; + end_timer!(b_g1_acc_time); + + // Compute B in G2 + let b_g2_acc_time = start_timer!(|| "Compute B in G2"); + let g2_b = pk.cc_pk.delta_g2 * s + + E::G2::msm_bigint(&pk.cc_pk.b_g2_query, &assignment_bigint) + + pk.cc_pk.beta_g2; + + end_timer!(b_g2_acc_time); + + // Compute C + let c_time = start_timer!(|| "Compute C"); + let mut g_c = g_a * &s; + g_c += g1_b * &r; + g_c -= pk.cc_pk.delta_g1 * (r * s); + + let (witness_bigint, committed_bigint) = cfg_into_iter!(assignment_bigint) + .skip(num_inputs) + .enumerate() + .partition_map::, Vec<_>, _, _, _>(|(i, v)| { + if pk.committed_variable_indices.contains(&i) { + Either::Right(v) + } else { + Either::Left(v) + } + }); + g_c += E::G1::msm_bigint(&pk.cc_pk.l_query, &witness_bigint); + drop(witness_bigint); + + let h_bigint = cfg_into_iter!(h) + .map(|s| s.into_bigint()) + .collect::>(); + g_c += E::G1::msm_bigint(&pk.cc_pk.h_query, &h_bigint); + drop(h_bigint); + + g_c -= pk.cc_pk.eta_delta_inv_g1 * v; + end_timer!(c_time); + + // Compute D + let d_acc_time = start_timer!(|| "Compute D"); + let g_d = E::G1::msm_bigint(&pk.cc_pk.gamma_abc_g1_cm, &committed_bigint) + + pk.cc_pk.eta_gamma_inv_g1 * v; + end_timer!(d_acc_time); + + let link_time = start_timer!(|| "Compute CP_{link}"); + let mut ss_snark_witness = committed_bigint; + ss_snark_witness.extend(o.iter().chain([&v]).map(|i| i.into_bigint())); + let link_pi = LinearSubspaceSNARK::prove(&pk.link_ek, &ss_snark_witness); + end_timer!(link_time); + + end_timer!(prover_time); + + Ok(Proof { + groth16_proof: Groth16Proof { + a: g_a.into_affine(), + b: g2_b.into_affine(), + c: g_c.into_affine(), + }, + + d: g_d.into_affine(), + + link_pi, + }) + } + + fn verify( + vk: &Self::VerifierKey, + x: &[Self::Field], + c: &[Self::Commitment], + proof: &Self::Proof, + ) -> Result<(), Self::Error> { + let mut g_ic = vk.cc_vk.gamma_abc_g1_pub[0].into_group(); + for (i, b) in x.iter().zip(vk.cc_vk.gamma_abc_g1_pub.iter().skip(1)) { + g_ic += *b * i; + } + + if E::multi_pairing( + [ + E::G1Prepared::from(proof.groth16_proof.a), + (proof.d + g_ic).into_affine().into(), + proof.groth16_proof.c.into(), + ], + [ + proof.groth16_proof.b.into(), + vk.cc_vk.gamma_g2_neg_pc.clone(), + vk.cc_vk.delta_g2_neg_pc.clone(), + ], + ) != vk.cc_vk.alpha_g1_beta_g2 + { + println!("xxx"); + return Err(SynthesisError::Unsatisfiable); + } + + if !LinearSubspaceSNARK::verify( + &vk.link_vk, + &[&c[..], &[proof.d][..]].concat(), + &proof.link_pi, + ) { + println!("yyy"); + return Err(SynthesisError::Unsatisfiable); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::Bn254; + use ark_relations::{gr1cs::ConstraintSystemRef, lc}; + use ark_std::rand::thread_rng; + use sonobe_primitives::circuits::{ArithExtractor, AssignmentsExtractor}; + + use super::*; + + struct MySillyCircuit { + a: F, + b: F, + c: F, + d: F, + e: F, + f: F, + } + + impl ConstraintSynthesizer for MySillyCircuit { + fn generate_constraints( + self, + cs: ConstraintSystemRef, + ) -> Result<(), SynthesisError> { + let d = cs.new_witness_variable(|| Ok(self.d))?; + let a = cs.new_witness_variable(|| Ok(self.a))?; + let b = cs.new_witness_variable(|| Ok(self.b))?; + let e = cs.new_witness_variable(|| Ok(self.e))?; + let c = cs.new_witness_variable(|| Ok(self.c))?; + let g = cs.new_input_variable(|| { + Ok((self.a + self.b + self.c) * (self.d + self.e + self.f)) + })?; + let f = cs.new_witness_variable(|| Ok(self.f))?; + + cs.enforce_r1cs_constraint(|| lc![a, b, c], || lc![d, e, f], || lc![g])?; + + Ok(()) + } + } + + fn test_prove_and_verify< + E: Pairing, + >( + n_iters: usize, + ) { + let mut rng = thread_rng(); + + let g = vec![E::G1Affine::rand(&mut rng)]; + let h = E::G1Affine::rand(&mut rng); + let generators = [&g[..], &[h]].concat(); + let ck = PedersenKey { g, h }; + + let mut cs = ArithExtractor::new(); + cs.execute_synthesizer(MySillyCircuit { + a: Default::default(), + b: Default::default(), + c: Default::default(), + d: Default::default(), + e: Default::default(), + f: Default::default(), + }) + .unwrap(); + + let (pk, vk) = LegoGroth16::::generate_keys( + (cs.arith().unwrap(), UsizeSet::from_iter(vec![0, 3, 5])), + &[&ck, &ck, &ck], + &mut rng, + ) + .unwrap(); + + for _ in 0..n_iters { + let a = E::ScalarField::rand(&mut rng); + let b = E::ScalarField::rand(&mut rng); + let c = E::ScalarField::rand(&mut rng); + let d = E::ScalarField::rand(&mut rng); + let e = E::ScalarField::rand(&mut rng); + let f = E::ScalarField::rand(&mut rng); + + let mut cs = AssignmentsExtractor::new(); + cs.execute_synthesizer(MySillyCircuit { a, b, c, d, e, f }) + .unwrap(); + let assignments = cs.assignments().unwrap(); + + let o = [ + E::ScalarField::rand(&mut rng), + E::ScalarField::rand(&mut rng), + E::ScalarField::rand(&mut rng), + ]; + + let cm = vec![ + E::G1::msm_unchecked(&generators, &[d, o[0]]).into_affine(), + E::G1::msm_unchecked(&generators, &[e, o[1]]).into_affine(), + E::G1::msm_unchecked(&generators, &[f, o[2]]).into_affine(), + ]; + + let proof = LegoGroth16::::prove( + &pk, + &assignments.public, + &assignments.private, + &o, + &mut rng, + ) + .unwrap(); + + assert!( + LegoGroth16::::verify(&vk, &[(a + b + c) * (d + e + f)], &cm, &proof).is_ok() + ); + assert!(LegoGroth16::::verify(&vk, &[a], &cm, &proof).is_err()); + } + } + + fn test_prove_and_verify2< + E: Pairing, + >( + n_iters: usize, + ) { + let mut rng = thread_rng(); + + let g = vec![E::G1Affine::rand(&mut rng), E::G1Affine::rand(&mut rng)]; + let h = E::G1Affine::rand(&mut rng); + let generators1 = [&g[..], &[h]].concat(); + let ck1 = PedersenKey { g, h }; + + let g = vec![E::G1Affine::rand(&mut rng)]; + let h = E::G1Affine::rand(&mut rng); + let generators2 = [&g[..], &[h]].concat(); + let ck2 = PedersenKey { g, h }; + + let mut cs = ArithExtractor::new(); + cs.execute_synthesizer(MySillyCircuit { + a: Default::default(), + b: Default::default(), + c: Default::default(), + d: Default::default(), + e: Default::default(), + f: Default::default(), + }) + .unwrap(); + + let (pk, vk) = LegoGroth16::::generate_keys( + (cs.arith().unwrap(), UsizeSet::from_iter(vec![0, 3, 5])), + &[&ck1, &ck2], + &mut rng, + ) + .unwrap(); + + for _ in 0..n_iters { + let a = E::ScalarField::rand(&mut rng); + let b = E::ScalarField::rand(&mut rng); + let c = E::ScalarField::rand(&mut rng); + let d = E::ScalarField::rand(&mut rng); + let e = E::ScalarField::rand(&mut rng); + let f = E::ScalarField::rand(&mut rng); + + let mut cs = AssignmentsExtractor::new(); + cs.execute_synthesizer(MySillyCircuit { a, b, c, d, e, f }) + .unwrap(); + let assignments = cs.assignments().unwrap(); + + let o = [ + E::ScalarField::rand(&mut rng), + E::ScalarField::rand(&mut rng), + ]; + + let cm = vec![ + E::G1::msm_unchecked(&generators1, &[d, e, o[0]]).into_affine(), + E::G1::msm_unchecked(&generators2, &[f, o[1]]).into_affine(), + ]; + + let proof = LegoGroth16::::prove( + &pk, + &assignments.public, + &assignments.private, + &o, + &mut rng, + ) + .unwrap(); + + assert!( + LegoGroth16::::verify(&vk, &[(a + b + c) * (d + e + f)], &cm, &proof).is_ok() + ); + assert!(LegoGroth16::::verify(&vk, &[a], &cm, &proof).is_err()); + } + } + + fn test_prove_and_verify3< + E: Pairing, + >( + n_iters: usize, + ) { + let mut rng = thread_rng(); + + let g = vec![ + E::G1Affine::rand(&mut rng), + E::G1Affine::rand(&mut rng), + E::G1Affine::rand(&mut rng), + ]; + let h = E::G1Affine::rand(&mut rng); + let generators = [&g[..], &[h]].concat(); + let ck = PedersenKey { g, h }; + + let mut cs = ArithExtractor::new(); + cs.execute_synthesizer(MySillyCircuit { + a: Default::default(), + b: Default::default(), + c: Default::default(), + d: Default::default(), + e: Default::default(), + f: Default::default(), + }) + .unwrap(); + + let (pk, vk) = LegoGroth16::::generate_keys( + (cs.arith().unwrap(), UsizeSet::from_iter(vec![0, 3, 5])), + &[&ck], + &mut rng, + ) + .unwrap(); + + for _ in 0..n_iters { + let a = E::ScalarField::rand(&mut rng); + let b = E::ScalarField::rand(&mut rng); + let c = E::ScalarField::rand(&mut rng); + let d = E::ScalarField::rand(&mut rng); + let e = E::ScalarField::rand(&mut rng); + let f = E::ScalarField::rand(&mut rng); + + let mut cs = AssignmentsExtractor::new(); + cs.execute_synthesizer(MySillyCircuit { a, b, c, d, e, f }) + .unwrap(); + let assignments = cs.assignments().unwrap(); + + let o = [E::ScalarField::rand(&mut rng)]; + + let cm = vec![E::G1::msm_unchecked(&generators, &[d, e, f, o[0]]).into_affine()]; + + let proof = LegoGroth16::::prove( + &pk, + &assignments.public, + &assignments.private, + &o, + &mut rng, + ) + .unwrap(); + + assert!( + LegoGroth16::::verify(&vk, &[(a + b + c) * (d + e + f)], &cm, &proof).is_ok() + ); + assert!(LegoGroth16::::verify(&vk, &[a], &cm, &proof).is_err()); + } + } + + #[test] + fn prove_and_verify() { + test_prove_and_verify::(5); + test_prove_and_verify2::(5); + test_prove_and_verify3::(5); + } +} diff --git a/crates/snarks/src/cp/mod.rs b/crates/snarks/src/cp/mod.rs new file mode 100644 index 000000000..1646dc75d --- /dev/null +++ b/crates/snarks/src/cp/mod.rs @@ -0,0 +1,43 @@ +use ark_ff::Field; +use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystem}; +use ark_std::{borrow::Borrow, rand::RngCore}; + +pub mod groth16; + +pub trait CPSNARK { + type Field: Field; + + type Relation; + + type CommitmentKey; + type CommitmentOpening; + type Commitment; + + type ProverKey; + type VerifierKey; + + type Proof; + + type Error; + + fn generate_keys( + relation: Self::Relation, + commitment_key: &[impl Borrow + Sync], + rng: impl RngCore, + ) -> Result<(Self::ProverKey, Self::VerifierKey), Self::Error>; + + fn prove( + pk: &Self::ProverKey, + x: &[Self::Field], + w: &[Self::Field], + o: &[Self::CommitmentOpening], + rng: impl RngCore, + ) -> Result; + + fn verify( + vk: &Self::VerifierKey, + x: &[Self::Field], + c: &[Self::Commitment], + proof: &Self::Proof, + ) -> Result<(), Self::Error>; +} diff --git a/crates/snarks/src/lib.rs b/crates/snarks/src/lib.rs new file mode 100644 index 000000000..3da6b5238 --- /dev/null +++ b/crates/snarks/src/lib.rs @@ -0,0 +1,2 @@ +pub mod linear_subspace; +pub mod cp; \ No newline at end of file diff --git a/crates/snarks/src/linear_subspace/mod.rs b/crates/snarks/src/linear_subspace/mod.rs new file mode 100644 index 000000000..7822139b9 --- /dev/null +++ b/crates/snarks/src/linear_subspace/mod.rs @@ -0,0 +1,204 @@ +use ark_ec::{CurveGroup, ScalarMul, VariableBaseMSM, pairing::Pairing}; +use ark_ff::{PrimeField, UniformRand, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::rand::Rng; + +#[derive(Clone, Default, PartialEq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct ProverKey { + pub p: Vec, +} + +#[derive(Clone, Default, PartialEq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct VerifierKey { + pub c: Vec, + pub a_neg: E::G2Affine, +} + +pub struct LinearSubspaceSNARK {} + +impl LinearSubspaceSNARK { + pub fn generate_keys( + n_rows: usize, + mvm: impl Fn(Vec) -> Vec, + rng: &mut impl Rng, + ) -> (ProverKey, VerifierKey) { + let k = (0..n_rows) + .map(|_| E::ScalarField::rand(rng)) + .collect::>(); + let a = E::G2::rand(rng); + let c = a.batch_mul(&k); + + ( + ProverKey { + p: E::G1::normalize_batch(&mvm(k)), + }, + VerifierKey { + c, + a_neg: -a.into_affine(), + }, + ) + } + + pub fn prove( + ek: &ProverKey, + w: &[::BigInt], + ) -> E::G1Affine { + assert_eq!(ek.p.len(), w.len()); + E::G1::msm_bigint(&ek.p, &w).into_affine() + } + + pub fn verify(vk: &VerifierKey, x: &[E::G1Affine], pi: &E::G1Affine) -> bool { + // `PE::multi_pairin` internally uses `zip_eq` + E::multi_pairing([x, &[*pi][..]].concat(), [&vk.c, &[vk.a_neg][..]].concat()).is_zero() + } +} + +#[cfg(test)] +mod tests { + use ark_bn254::{Bn254, Fr, G1Affine}; + use ark_ec::AffineRepr; + use ark_ff::One; + use ark_std::rand::thread_rng; + + use super::*; + + fn test_mvm(m: &[Vec]) -> impl Fn(Vec) -> Vec { + move |k| { + (0..m[0].len()) + .map(|i| k.iter().zip(m).map(|(u, v)| v[i] * u).sum::()) + .collect::>() + } + } + + #[test] + fn test_basic() { + // Prove knowledge of all `x_i` in `y = \sum_i g_i * x_i` + let mut rng = thread_rng(); + let g1 = G1Affine::rand(&mut rng); + + let m = vec![vec![g1, g1]]; + + let x = vec![Fr::one().into_bigint(), Fr::zero().into_bigint()]; + + let x_bad = vec![Fr::one().into_bigint(), Fr::one().into_bigint()]; + + let y: Vec = vec![g1]; + + let (ek, vk) = LinearSubspaceSNARK::generate_keys::(m.len(), test_mvm(&m), &mut rng); + + let pi = LinearSubspaceSNARK::prove(&ek, &x); + let pi_bad = LinearSubspaceSNARK::prove(&ek, &x_bad); + + assert!(LinearSubspaceSNARK::verify(&vk, &y, &pi)); + assert!(!LinearSubspaceSNARK::verify(&vk, &y, &pi_bad)); + } + + #[test] + fn test_basic_1() { + // Prove knowledge of all `w_i` in `y = \sum_i h_i * w_i` + let mut rng = thread_rng(); + + let h1 = G1Affine::rand(&mut rng); + let h2 = G1Affine::rand(&mut rng); + let m = vec![vec![h1, h2]]; + + let two = Fr::one() + Fr::one(); + let three = Fr::one() + two; + + // Correct witness + let w = vec![two.into_bigint(), three.into_bigint()]; + // Incorrect witness + let w_bad = vec![Fr::one().into_bigint(), Fr::one().into_bigint()]; + + // y is a Pedersen-like commitment to `two` and `three` and bases `h1` and `h2`, + // i.e `y = h1 * two + h2 * three` + let y: Vec = vec![ + (h1.mul_bigint(two.into_bigint()) + h2.mul_bigint(three.into_bigint())).into_affine(), + ]; + + let (ek, vk) = LinearSubspaceSNARK::generate_keys::(m.len(), test_mvm(&m), &mut rng); + + let pi = LinearSubspaceSNARK::prove(&ek, &w); + let pi_bad = LinearSubspaceSNARK::prove(&ek, &w_bad); + + assert!(LinearSubspaceSNARK::verify(&vk, &y, &pi)); + assert!(!LinearSubspaceSNARK::verify(&vk, &y, &pi_bad)); + } + + #[test] + fn test_same_value_different_bases() { + // Given `bases1 = [h1, h2]` and `bases2 = [h3, h4]`, prove knowledge of `x1, x2 + // x3` in `y0 = h1 * x0 + h2 * x2` and `y1 = h3 * x1 + h4 * x2` + + let mut rng = thread_rng(); + + let bases1 = [G1Affine::rand(&mut rng), G1Affine::rand(&mut rng)]; + let bases2 = [G1Affine::rand(&mut rng), G1Affine::rand(&mut rng)]; + let m = vec![ + vec![bases1[0], G1Affine::zero(), bases1[1]], + vec![G1Affine::zero(), bases2[0], bases2[1]], + ]; + + let w = vec![ + Fr::rand(&mut rng).into_bigint(), + Fr::rand(&mut rng).into_bigint(), + Fr::rand(&mut rng).into_bigint(), + ]; + + let x: Vec = vec![ + (bases1[0].mul_bigint(w[0]) + bases1[1].mul_bigint(w[2])).into_affine(), + (bases2[0].mul_bigint(w[1]) + bases2[1].mul_bigint(w[2])).into_affine(), + ]; + + let (ek, vk) = LinearSubspaceSNARK::generate_keys::(m.len(), test_mvm(&m), &mut rng); + + let pi = LinearSubspaceSNARK::prove(&ek, &w); + + assert!(LinearSubspaceSNARK::verify(&vk, &x, &pi)); + } + + #[test] + fn test_some_vals_equal() { + // Given `bases1 = [h1, h2, h3]` and `bases2 = [h4, h5, h6]`, prove knowledge of + // `x1, x2 x3, x4` in `y0 = h1 * x0 + h2 * x2 + h3 * x3` and `y1 = h4 * x1 + h5 + // * x2 + h6 * x4` + + let mut rng = thread_rng(); + + let bases1 = [ + G1Affine::rand(&mut rng), + G1Affine::rand(&mut rng), + G1Affine::rand(&mut rng), + ]; + let bases2 = [ + G1Affine::rand(&mut rng), + G1Affine::rand(&mut rng), + G1Affine::rand(&mut rng), + ]; + + let m = vec![ + vec![bases1[0], bases1[1], bases1[2], G1Affine::zero()], + vec![bases2[0], bases2[1], G1Affine::zero(), bases2[2]], + ]; + + let w = vec![ + Fr::rand(&mut rng).into_bigint(), + Fr::rand(&mut rng).into_bigint(), + Fr::rand(&mut rng).into_bigint(), + Fr::rand(&mut rng).into_bigint(), + ]; + + let x: Vec = vec![ + (bases1[0].mul_bigint(w[0]) + bases1[1].mul_bigint(w[1]) + bases1[2].mul_bigint(w[2])) + .into_affine(), + (bases2[0].mul_bigint(w[0]) + bases2[1].mul_bigint(w[1]) + bases2[2].mul_bigint(w[3])) + .into_affine(), + ]; + + let (ek, vk) = LinearSubspaceSNARK::generate_keys::(m.len(), test_mvm(&m), &mut rng); + + let pi = LinearSubspaceSNARK::prove(&ek, &w); + + assert!(LinearSubspaceSNARK::verify(&vk, &x, &pi)); + } +} From 1caf183771263ac04d040e1f6f00cc65678479f8 Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 24 May 2026 15:19:28 +0800 Subject: [PATCH 125/131] Fold the final running & incoming pairs before compression --- crates/fs/src/definitions/instances.rs | 4 +- crates/fs/src/definitions/keys.rs | 2 +- crates/fs/src/nova/instances/circuits.rs | 2 +- crates/fs/src/nova/instances/mod.rs | 8 +- crates/ivc/src/compilers/cyclefold/mod.rs | 121 ++++++++++++++++------ crates/snarks/src/cp/groth16.rs | 2 - 6 files changed, 96 insertions(+), 43 deletions(-) diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index 9c2edcfc1..d9f8130ef 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -25,7 +25,7 @@ pub trait FoldingInstance: /// the instance. // TODO (@winderica): consider the scenario where the instance has multiple // commitments of different types. - fn commitments(&self) -> Vec<&CM::Commitment>; + fn commitments(&self) -> Vec; /// [`FoldingInstance::public_inputs`] returns the reference to the public /// inputs contained in the instance. @@ -55,7 +55,7 @@ impl Dummy<&ArithConfig> for PlainInstance { impl FoldingInstance for PlainInstance { const N_COMMITMENTS: usize = 0; - fn commitments(&self) -> Vec<&CM::Commitment> { + fn commitments(&self) -> Vec { vec![] } diff --git a/crates/fs/src/definitions/keys.rs b/crates/fs/src/definitions/keys.rs index 796d574d8..d430c4efd 100644 --- a/crates/fs/src/definitions/keys.rs +++ b/crates/fs/src/definitions/keys.rs @@ -11,7 +11,7 @@ pub trait DeciderKey: CanonicalSerialize + CanonicalDeserialize { type ProverKey; /// [`DeciderKey::VerifierKey`] is the type of the verifier key contained in /// the decider key. - type VerifierKey; + type VerifierKey: Clone; /// [`DeciderKey::to_pk`] returns the reference to the prover key. fn to_pk(&self) -> &Self::ProverKey; diff --git a/crates/fs/src/nova/instances/circuits.rs b/crates/fs/src/nova/instances/circuits.rs index 6d0c81007..4a3b61f37 100644 --- a/crates/fs/src/nova/instances/circuits.rs +++ b/crates/fs/src/nova/instances/circuits.rs @@ -106,7 +106,7 @@ impl CondSelectGadget for RunningI impl FoldingInstanceVar for RunningInstanceVar { fn commitments(&self) -> Vec<&CM::CommitmentVar> { - vec![&self.cm_w, &self.cm_e] + vec![&self.cm_e, &self.cm_w] } fn public_inputs(&self) -> &Vec { diff --git a/crates/fs/src/nova/instances/mod.rs b/crates/fs/src/nova/instances/mod.rs index d4958997c..cf445d37e 100644 --- a/crates/fs/src/nova/instances/mod.rs +++ b/crates/fs/src/nova/instances/mod.rs @@ -27,8 +27,8 @@ pub struct RunningInstance { impl FoldingInstance for RunningInstance { const N_COMMITMENTS: usize = 2; - fn commitments(&self) -> Vec<&CM::Commitment> { - vec![&self.cm_e, &self.cm_w] + fn commitments(&self) -> Vec { + vec![self.cm_e.clone(), self.cm_w.clone()] } fn public_inputs(&self) -> &[CM::Scalar] { @@ -72,8 +72,8 @@ pub struct IncomingInstance { impl FoldingInstance for IncomingInstance { const N_COMMITMENTS: usize = 1; - fn commitments(&self) -> Vec<&CM::Commitment> { - vec![&self.cm_w] + fn commitments(&self) -> Vec { + vec![self.cm_w.clone()] } fn public_inputs(&self) -> &[CM::Scalar] { diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index f8250b54f..1d91c0c20 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -518,20 +518,29 @@ impl< type ProverKey = ( S::ProverKey, - Key, + ::VerifierKey, ); - type VerifierKey = (S::VerifierKey, FC::State); + type VerifierKey = ( + S::VerifierKey, + ::VerifierKey, + T::Config, + ::Field, + FC::State, + ); - type CompressedProof = (S::Proof, Vec<::Commitment>); + type CompressedProof = (S::Proof, FS1::RU, FS1::IU, FS1::Proof<1, 1>); type Error = Error; fn preprocess_and_generate_keys::Field>>( circuit: &FC, - Key(dk1, dk2, (hash_config, pp_hash, reference_state)): ::VerifierKey, + ivc_vk: ::VerifierKey, rng: impl RngCore, ) -> Result<(Self::ProverKey, Self::VerifierKey), Self::Error> { + let cfg1 = &ivc_vk.0.to_arith_config(); + let cfg2 = &ivc_vk.1.to_arith_config(); + let mut cs = ArithExtractor::new(); { let mut cache = cs.cache_map.borrow_mut(); @@ -545,14 +554,18 @@ impl< ); } - let ivc_vk = Key(dk1, dk2, (hash_config, pp_hash)); - cs.execute_synthesizer(CycleFoldBasedIVCDeciderCircuit:: { vk: &ivc_vk, i: 0, initial_state: &circuit.dummy_state(), current_state: &circuit.dummy_state(), - proof: &Proof::dummy(&ivc_vk), + proof: &Dummy::dummy(cfg1), + WW: &Dummy::dummy(cfg1), + UU: &Dummy::dummy(cfg1), + U: &Dummy::dummy(cfg1), + u: &Dummy::dummy(cfg1), + cf_W: &Dummy::dummy(cfg2), + cf_U: &Dummy::dummy(cfg2), })?; let (committed_variable_indices, ck) = { let mut cache = cs.cache_map.borrow_mut(); @@ -572,7 +585,15 @@ impl< let (pk, vk) = S::generate_keys((cs.arith()?, committed_variable_indices), &ck, rng)?; - Ok(((pk, ivc_vk), (vk, reference_state))) + let vk = ( + vk, + ivc_vk.0.to_vk().clone(), + ivc_vk.2.0.clone(), + ivc_vk.2.1, + ivc_vk.2.2.clone(), + ); + + Ok(((pk, ivc_vk), vk)) } fn prove::Field>>( @@ -580,9 +601,22 @@ impl< i: usize, initial_state: &FC::State, current_state: &FC::State, - proof: &::Proof, - rng: impl RngCore, + Proof(W, U, w, u, cf_W, cf_U): &::Proof, + mut rng: impl RngCore, ) -> Result, Self::Error> { + let hash = T::new_with_pp_hash(ivc_vk.2.0.clone(), ivc_vk.2.1); + let mut transcript = hash.separate_domain("transcript".as_ref()); + + let (WW, UU, folding_proof) = FS1::prove( + &ivc_vk.0.to_pk(), + &mut transcript, + &[W], + &[U], + &[w], + &[u], + &mut rng, + )?; + let mut cs = AssignmentsExtractor::new(); { let mut cache = cs.cache_map.borrow_mut(); @@ -599,12 +633,19 @@ impl< Box::new(Vec::<::Commitment>::new()), ); } + cs.execute_synthesizer(CycleFoldBasedIVCDeciderCircuit:: { vk: ivc_vk, i, initial_state, current_state, - proof, + proof: &folding_proof, + WW: &WW, + UU: &UU, + U, + u, + cf_W, + cf_U, })?; let w = &cs.assignments.witness_assignment; let x = &cs.assignments.instance_assignment; @@ -620,17 +661,17 @@ impl< .downcast::::Commitment>>() .map_err(|_| SynthesisError::AssignmentMissing)?; - let proof = S::prove(pk, &x[1..], &w, &o, rng)?; + let compressed_proof = S::prove(pk, &x[1..], &w, &o, &mut rng)?; - Ok((proof, c)) + Ok((compressed_proof, U.clone(), u.clone(), folding_proof)) } fn verify::Field>>( - (vk, reference_state): &Self::VerifierKey, + (vk, folding_vk, hash_config, pp_hash, reference_state): &Self::VerifierKey, i: usize, initial_state: &FC::State, current_state: &FC::State, - (proof, commitments): &Self::CompressedProof, + (compressed_proof, U, u, folding_proof): &Self::CompressedProof, ) -> Result<(), Self::Error> { if !FC::same_state_shape(reference_state, initial_state) || !FC::same_state_shape(reference_state, current_state) @@ -644,16 +685,22 @@ impl< .ok_or(Error::IVCVerificationFail); } + let hash = T::new_with_pp_hash(hash_config.clone(), *pp_hash); + let mut transcript = hash.separate_domain("transcript".as_ref()); + + let UU = FS1::verify(folding_vk, &mut transcript, &[U], &[u], folding_proof)?; + let commitments = UU.commitments(); + let x = &[ vec![::Field::from(i as u64)], FC::StateVar::inputize(initial_state), FC::StateVar::inputize(current_state), - commitments.iter().flat_map(<::CM as CommitmentDefGadget>::CommitmentVar::inputize).collect::>() + commitments.iter().flat_map(|c| <::CM as CommitmentDefGadget>::CommitmentVar::inputize(c)).collect::>() ] .concat(); - let c = CurveGroup::normalize_batch(commitments); + let c = CurveGroup::normalize_batch(&commitments); - S::verify(vk, x, &c, proof)?; + S::verify(vk, x, &c, compressed_proof)?; Ok(()) } @@ -666,11 +713,17 @@ pub struct CycleFoldBasedIVCDeciderCircuit< T: Transcript, FC: FCircuit, > { - vk: &'a Key, + vk: &'a Key, i: usize, initial_state: &'a FC::State, current_state: &'a FC::State, - proof: &'a Proof, + proof: &'a FS1::Proof<1, 1>, + WW: &'a FS1::RW, + UU: &'a FS1::RU, + U: &'a FS1::RU, + u: &'a FS1::IU, + cf_W: &'a FS2::RW, + cf_U: &'a FS2::RU, } impl< @@ -678,7 +731,8 @@ impl< FS1: FoldingSchemeCycleFoldExt< 1, 1, - Gadget: FoldingSchemeDeciderGadget, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()> + + FoldingSchemeDeciderGadget, CM: CommitmentDef< Commitment: SonobeCurve::Scalar>, >, @@ -709,27 +763,28 @@ impl< let initial_state = FC::StateVar::new_input(cs.clone(), || Ok(self.initial_state))?; let current_state = FC::StateVar::new_input(cs.clone(), || Ok(self.current_state))?; - let Key(dk1, dk2, (hash_config, pp_hash)) = &self.vk; + let Key(dk1, dk2, (hash_config, pp_hash, _)) = &self.vk; let dk1 = AllocVar::new_constant(cs.clone(), dk1)?; let dk2 = AllocVar::new_constant(cs.clone(), dk2)?; let pp_hash = FpVar::new_constant(cs.clone(), pp_hash)?; - let Proof(W, U, w, u, cf_W, cf_U) = &self.proof; - let W = AllocVar::new_witness(cs.clone(), || Ok(W))?; - let U = AllocVar::new_witness(cs.clone(), || Ok(U))?; - let w = AllocVar::new_witness(cs.clone(), || Ok(w))?; - let u = AllocVar::new_witness(cs.clone(), || Ok(u))?; - let cf_W = AllocVar::new_witness(cs.clone(), || Ok(cf_W))?; - let cf_U = AllocVar::new_witness(cs.clone(), || Ok(cf_U))?; + let WW = AllocVar::new_witness(cs.clone(), || Ok(self.WW))?; + let U = AllocVar::new_witness(cs.clone(), || Ok(self.U))?; + let u = AllocVar::new_witness(cs.clone(), || Ok(self.u))?; + let cf_W = AllocVar::new_witness(cs.clone(), || Ok(self.cf_W))?; + let cf_U = AllocVar::new_witness(cs.clone(), || Ok(self.cf_U))?; + let proof = AllocVar::new_witness(cs.clone(), || Ok(self.proof))?; i.enforce_not_equal(&FpVar::zero())?; - FS1::Gadget::decide_running(&dk1, &W, &U)?; - FS1::Gadget::decide_incoming(&dk1, &w, &u)?; - FS2::Gadget::decide_running(&dk2, &cf_W, &cf_U)?; - let hash = T::Gadget::new_with_pp_hash(hash_config.clone(), &pp_hash)?; let mut sponge = hash.separate_domain("sponge".as_ref())?; + let mut transcript = hash.separate_domain("transcript".as_ref())?; + + let UU = FS1::Gadget::verify_hinted(&(), &mut transcript, [&U], [&u], &proof)?; + + FS1::Gadget::decide_running(&dk1, &WW, &UU)?; + FS2::Gadget::decide_running(&dk2, &cf_W, &cf_U)?; let u_x = sponge .add(&i)? diff --git a/crates/snarks/src/cp/groth16.rs b/crates/snarks/src/cp/groth16.rs index a46c9865c..0acf8bb41 100644 --- a/crates/snarks/src/cp/groth16.rs +++ b/crates/snarks/src/cp/groth16.rs @@ -502,7 +502,6 @@ impl Date: Sun, 24 May 2026 15:33:02 +0800 Subject: [PATCH 126/131] Cleanup --- crates/fs/src/nova/keys/circuits.rs | 36 +++++-------------- crates/fs/src/nova/mod.rs | 9 ++--- .../src/compilers/cyclefold/adapters/nova.rs | 23 ++---------- crates/ivc/src/compilers/cyclefold/mod.rs | 22 +++--------- crates/primitives/src/algebra/field/mod.rs | 3 +- crates/primitives/src/algebra/group/mod.rs | 4 +-- crates/primitives/src/algebra/ops/matrix.rs | 15 ++------ crates/primitives/src/algebra/ops/vector.rs | 8 ++--- .../src/arithmetizations/ccs/mod.rs | 5 ++- .../src/arithmetizations/r1cs/circuits.rs | 17 ++------- .../src/arithmetizations/r1cs/mod.rs | 2 +- crates/primitives/src/circuits/alloc.rs | 10 +----- crates/primitives/src/commitments/pedersen.rs | 18 +++------- crates/snarks/src/cp/groth16.rs | 29 +++++++-------- crates/snarks/src/cp/mod.rs | 1 - crates/snarks/src/lib.rs | 4 ++- crates/snarks/src/linear_subspace/mod.rs | 2 +- 17 files changed, 58 insertions(+), 150 deletions(-) diff --git a/crates/fs/src/nova/keys/circuits.rs b/crates/fs/src/nova/keys/circuits.rs index 23947ba4a..c3c5c16d3 100644 --- a/crates/fs/src/nova/keys/circuits.rs +++ b/crates/fs/src/nova/keys/circuits.rs @@ -1,38 +1,20 @@ -use ark_r1cs_std::{ - GR1CSVar, - alloc::{AllocVar, AllocationMode}, - boolean::Boolean, -}; +use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{borrow::Borrow, marker::PhantomData}; +use ark_std::borrow::Borrow; use sonobe_primitives::{ arithmetizations::{ - Arith, ArithConfig, ArithGadget, ArithRelation, ArithRelationGadget, - r1cs::{R1CS, RelaxedInstance, RelaxedWitness}, - }, - circuits::{AssignmentsOwned, WitnessToCommitted}, - commitments::{ - CommitmentDef, CommitmentDefGadget, CommitmentOps, CommitmentOpsGadget, - GroupBasedCommitment, + ArithGadget, ArithRelationGadget, + r1cs::{RelaxedInstance, RelaxedWitness}, }, - relations::{Relation, RelationGadget, WitnessInstanceSampler}, - traits::{CF2, SonobeField}, + commitments::{CommitmentDefGadget, CommitmentOpsGadget}, + relations::RelationGadget, }; use super::super::{ - instances::{ - IncomingInstance as IU, RunningInstance as RU, - circuits::{IncomingInstanceVar as IUVar, RunningInstanceVar as RUVar}, - }, - witnesses::{ - IncomingWitness as IW, RunningWitness as RW, - circuits::{IncomingWitnessVar as IWVar, RunningWitnessVar as RWVar}, - }, -}; -use crate::{ - FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef, - GroupBasedFoldingSchemeSecondaryDef, nova::keys::NovaKey, + instances::circuits::{IncomingInstanceVar as IUVar, RunningInstanceVar as RUVar}, + witnesses::circuits::{IncomingWitnessVar as IWVar, RunningWitnessVar as RWVar}, }; +use crate::nova::keys::NovaKey; #[derive(Clone)] pub struct NovaKeyVar { diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 962544861..37ee0efc2 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -6,13 +6,8 @@ use ark_r1cs_std::boolean::Boolean; use ark_std::marker::PhantomData; use sonobe_primitives::{ - arithmetizations::{ - Arith, ArithConfig, ArithRelation, ArithRelationGadget, - r1cs::{R1CS, RelaxedInstance, RelaxedWitness, circuits::R1CSVar}, - }, - circuits::{AssignmentsOwned, WitnessToCommitted}, - commitments::{CommitmentDef, CommitmentDefGadget, CommitmentOps, GroupBasedCommitment}, - relations::{Relation, WitnessInstanceSampler}, + arithmetizations::r1cs::{R1CS, circuits::R1CSVar}, + commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, traits::{CF2, SonobeField}, }; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 01c410803..d18949bf3 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -211,27 +211,13 @@ pub type NovaNovaIVC = #[cfg(test)] mod tests { - use ark_bn254::{Bn254, Fq, Fr, G1Projective as C1}; + use ark_bn254::{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_fs::{ - GroupBasedFoldingSchemePrimaryDef, GroupBasedFoldingSchemeSecondaryDef, - definitions::circuits::FoldingSchemeDeciderGadget, - nova::{ - AbstractNovaGadget, instances::circuits::RunningInstanceVar, - keys::circuits::NovaKeyVar, witnesses::circuits::RunningWitnessVar, - }, - }; use sonobe_primitives::{ - algebra::{field::TwoStageFieldVar, ops::{eq::EquivalenceGadget, vector::VectorMulGadget}}, - arithmetizations::{ - ArithRelationGadget, - r1cs::{RelaxedInstance, RelaxedWitness, circuits::R1CSVar}, - }, - circuits::{Assignments, utils::CircuitForTest}, - commitments::pedersen::{Pedersen, PedersenCommitAndProveGadget}, - relations::RelationGadget, + circuits::utils::CircuitForTest, + commitments::pedersen::Pedersen, transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, }; use sonobe_snarks::cp::groth16::LegoGroth16; @@ -264,9 +250,6 @@ mod tests { fn test_nova_nova_decider() -> Result<(), Box> { let mut rng = thread_rng(); - // f::<<> as GroupBasedFoldingSchemeSecondaryDef>::Gadget as FoldingSchemeDefGadget>::DeciderKey>(); - // g::>(); - test_ivc_decider::< CycleFoldBasedIVCDecider< Nova>, diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 1d91c0c20..872f4a672 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -40,7 +40,7 @@ use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig}, circuits::{ ArithExtractor, AssignmentsExtractor, FCircuit, - alloc::{CommitmentCache, CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, + alloc::{CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, }, commitments::{CommitmentDef, CommitmentDefGadget}, relations::WitnessInstanceSampler, @@ -561,7 +561,6 @@ impl< current_state: &circuit.dummy_state(), proof: &Dummy::dummy(cfg1), WW: &Dummy::dummy(cfg1), - UU: &Dummy::dummy(cfg1), U: &Dummy::dummy(cfg1), u: &Dummy::dummy(cfg1), cf_W: &Dummy::dummy(cfg2), @@ -607,8 +606,8 @@ impl< let hash = T::new_with_pp_hash(ivc_vk.2.0.clone(), ivc_vk.2.1); let mut transcript = hash.separate_domain("transcript".as_ref()); - let (WW, UU, folding_proof) = FS1::prove( - &ivc_vk.0.to_pk(), + let (WW, _, folding_proof) = FS1::prove( + ivc_vk.0.to_pk(), &mut transcript, &[W], &[U], @@ -628,10 +627,6 @@ impl< TypeId::of::(), Box::new(Vec::<::Field>::new()), ); - cache.insert( - TypeId::of::(), - Box::new(Vec::<::Commitment>::new()), - ); } cs.execute_synthesizer(CycleFoldBasedIVCDeciderCircuit:: { @@ -641,7 +636,6 @@ impl< current_state, proof: &folding_proof, WW: &WW, - UU: &UU, U, u, cf_W, @@ -655,13 +649,8 @@ impl< .ok_or(SynthesisError::AssignmentMissing)? .downcast::::Field>>() .map_err(|_| SynthesisError::AssignmentMissing)?; - let c = *cache - .remove(&TypeId::of::()) - .ok_or(SynthesisError::AssignmentMissing)? - .downcast::::Commitment>>() - .map_err(|_| SynthesisError::AssignmentMissing)?; - let compressed_proof = S::prove(pk, &x[1..], &w, &o, &mut rng)?; + let compressed_proof = S::prove(pk, &x[1..], w, &o, &mut rng)?; Ok((compressed_proof, U.clone(), u.clone(), folding_proof)) } @@ -695,7 +684,7 @@ impl< vec![::Field::from(i as u64)], FC::StateVar::inputize(initial_state), FC::StateVar::inputize(current_state), - commitments.iter().flat_map(|c| <::CM as CommitmentDefGadget>::CommitmentVar::inputize(c)).collect::>() + commitments.iter().flat_map(<::CM as CommitmentDefGadget>::CommitmentVar::inputize).collect::>() ] .concat(); let c = CurveGroup::normalize_batch(&commitments); @@ -719,7 +708,6 @@ pub struct CycleFoldBasedIVCDeciderCircuit< current_state: &'a FC::State, proof: &'a FS1::Proof<1, 1>, WW: &'a FS1::RW, - UU: &'a FS1::RU, U: &'a FS1::RU, u: &'a FS1::IU, cf_W: &'a FS2::RW, diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index d8febabc5..387bf16d4 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -17,7 +17,7 @@ use ark_std::{ use crate::{ algebra::{Val, field::emulated::EmulatedFieldVar}, - circuits::{WitnessToCommitted, WitnessToPublic}, + circuits::WitnessToPublic, traits::Inputize, transcripts::{Absorbable, AbsorbableVar}, }; @@ -149,6 +149,7 @@ pub trait TwoStageFieldVar: + GR1CSVar + AllocVar { + // TODO: seems that using GR1CSVar's Value breaks the compiler... type ValueField: Field; type ConstraintField: Field; diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 212a662a1..8e85303da 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -118,9 +118,7 @@ impl> WitnessToPublic } } -impl WitnessToPublic - for EmulatedAffineVar -{ +impl WitnessToPublic for EmulatedAffineVar { fn mark_as_public(&self) -> Result<(), SynthesisError> { self.x.mark_as_public()?; self.y.mark_as_public()?; diff --git a/crates/primitives/src/algebra/ops/matrix.rs b/crates/primitives/src/algebra/ops/matrix.rs index 89a696c9c..ed84edf6e 100644 --- a/crates/primitives/src/algebra/ops/matrix.rs +++ b/crates/primitives/src/algebra/ops/matrix.rs @@ -3,9 +3,8 @@ use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ - GR1CSVar, alloc::{AllocVar, AllocationMode}, - fields::{FieldVar, fp::FpVar}, + fields::fp::FpVar, }; use ark_relations::gr1cs::{Matrix, Namespace, SynthesisError}; use ark_std::{borrow::Borrow, ops::Index}; @@ -26,9 +25,7 @@ pub trait MatrixGadget { #[derive(Debug, Clone)] pub struct SparseMatrixVar(pub Vec>); -impl> AllocVar, CF> - for SparseMatrixVar -{ +impl> AllocVar, CF> for SparseMatrixVar { fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, @@ -58,12 +55,6 @@ impl MatrixGadget> for SparseMatrixVar> { &self, v: &impl Index>, ) -> Result>, SynthesisError> { - self - .0 - .iter() - .map(|row| { - row.mul(v) - }) - .collect() + self.0.iter().map(|row| row.mul(v)).collect() } } diff --git a/crates/primitives/src/algebra/ops/vector.rs b/crates/primitives/src/algebra/ops/vector.rs index 6d6d05922..e6bfa9fd5 100644 --- a/crates/primitives/src/algebra/ops/vector.rs +++ b/crates/primitives/src/algebra/ops/vector.rs @@ -1,14 +1,10 @@ //! This module provides definitions and implementations of in-circuit vector //! operations. -use std::ops::Index; - use ark_ff::PrimeField; -use ark_r1cs_std::{ - GR1CSVar, - fields::{FieldVar, fp::FpVar}, -}; +use ark_r1cs_std::fields::fp::FpVar; use ark_relations::gr1cs::SynthesisError; +use ark_std::ops::Index; /// [`VectorMulGadget`] defines the multiplication (dot product) operation on /// in-circuit vector variables. diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index b796a4c84..4da595661 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -167,7 +167,10 @@ pub trait CCSGadget { for i in s { // TODO: intermediate // let v = matrices[*i].0[row].mul(&z)?; - let v = matrices[*i].0[row].mul(&z)?.try_into().map_err(|_| SynthesisError::Unsatisfiable)?; + let v = matrices[*i].0[row] + .mul(&z)? + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?; prod = prod * v; } sum = match sum { diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index 6932c5fd3..b4470640a 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -1,23 +1,14 @@ //! This module implements in-circuit R1CS variables and relation check gadgets. -use ark_ff::{Field, PrimeField, Zero}; use ark_r1cs_std::alloc::{AllocVar, AllocationMode}; use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::{ - One, - borrow::Borrow, - ops::{Index, Mul, Sub}, -}; +use ark_std::{borrow::Borrow, ops::Index}; use super::{R1CS, RelaxedInstance, RelaxedWitness}; use crate::{ algebra::{ field::TwoStageFieldVar, - ops::{ - eq::EquivalenceGadget, - matrix::{MatrixGadget, SparseMatrixVar}, - vector::VectorMulGadget, - }, + ops::{eq::EquivalenceGadget, matrix::SparseMatrixVar, vector::VectorMulGadget}, }, arithmetizations::{ArithGadget, ArithRelationGadget, ccs::CCSGadget}, circuits::Assignments, @@ -47,9 +38,7 @@ impl CCSGadget for R1CSVar { } } -impl - AllocVar, FVar::ConstraintField> for R1CSVar -{ +impl AllocVar, FVar::ConstraintField> for R1CSVar { fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 96eebc997..97aa20cdd 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -11,7 +11,7 @@ use ark_std::{cfg_into_iter, cfg_iter}; use rayon::prelude::*; use super::{Arith, ArithConfig, ArithRelation, Error, ccs::CCS}; -use crate::circuits::{Assignments}; +use crate::circuits::Assignments; pub mod circuits; diff --git a/crates/primitives/src/circuits/alloc.rs b/crates/primitives/src/circuits/alloc.rs index e26b6281c..d5986e064 100644 --- a/crates/primitives/src/circuits/alloc.rs +++ b/crates/primitives/src/circuits/alloc.rs @@ -1,11 +1,4 @@ -use ark_ff::{Field, PrimeField}; -use ark_r1cs_std::fields::fp::{AllocatedFp, FpVar}; -use ark_relations::gr1cs::{ConstraintSystem, ConstraintSystemRef, SynthesisError, Variable}; -use ark_std::{ - any::{Any, TypeId}, - collections::BTreeMap, - hash::{BuildHasherDefault, Hasher}, -}; +use ark_std::hash::{BuildHasherDefault, Hasher}; use hashbrown::HashSet; #[derive(Default)] @@ -30,6 +23,5 @@ impl Hasher for IdentityHasher { pub type UsizeSet = HashSet>; pub struct CommittedCache; -pub struct CommitmentCache; pub struct CommitmentKeyCache; pub struct RandomnessCache; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index acb345e4a..4a9a040dd 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -5,8 +5,6 @@ //! where `g` and `h` are generators, `r` is a random scalar, and `` is //! the multi-scalar multiplication of `g` and `v`. -use std::any::TypeId; - use ark_ec::AffineRepr; use ark_r1cs_std::{ GR1CSVar, @@ -19,16 +17,16 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::{Namespace, SynthesisError}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{UniformRand, borrow::Borrow, iter::repeat_with, marker::PhantomData, rand::RngCore}; +use ark_std::{ + UniformRand, any::TypeId, borrow::Borrow, iter::repeat_with, marker::PhantomData, rand::RngCore, +}; use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error}; use crate::{ algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, circuits::{ - WitnessToCommitted, WitnessToPublic, alloc::{ - CommitmentCache, CommitmentKeyCache, CommittedCache, RandomnessCache, - UsizeSet, - } + WitnessToPublic, + alloc::{CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, }, commitments::{CommitmentOpsGadget, GroupBasedCommitment}, traits::{CF1, CF2, SonobeCurve}, @@ -421,12 +419,6 @@ impl CommitmentOpsGadget for PedersenCommitAndProveGadget>>() .ok_or(SynthesisError::AssignmentMissing)? .push(r.value()?); - cache - .get_mut(&TypeId::of::()) - .ok_or(SynthesisError::AssignmentMissing)? - .downcast_mut::>() - .ok_or(SynthesisError::AssignmentMissing)? - .push(cm.value()?); } else { cache .get_mut(&TypeId::of::()) diff --git a/crates/snarks/src/cp/groth16.rs b/crates/snarks/src/cp/groth16.rs index 0acf8bb41..a53134aca 100644 --- a/crates/snarks/src/cp/groth16.rs +++ b/crates/snarks/src/cp/groth16.rs @@ -9,13 +9,10 @@ use ark_groth16::{ r1cs_to_qap::{LibsnarkReduction, R1CSToQAP}, }; use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; -use ark_relations::gr1cs::{ - ConstraintSynthesizer, ConstraintSystem, Matrix, OptimizationGoal, R1CS_PREDICATE_LABEL, - SynthesisError, SynthesisMode, -}; +use ark_relations::gr1cs::SynthesisError; use ark_std::{ - any::TypeId, borrow::Borrow, cfg_into_iter, cfg_iter, cfg_iter_mut, end_timer, - hash::BuildHasherDefault, marker::PhantomData, rand::RngCore, rc::Rc, start_timer, + borrow::Borrow, cfg_into_iter, cfg_iter, cfg_iter_mut, end_timer, hash::BuildHasherDefault, + marker::PhantomData, rand::RngCore, start_timer, }; use hashbrown::HashSet; #[cfg(not(feature = "parallel"))] @@ -24,7 +21,7 @@ use itertools::{Either, Itertools}; use rayon::{iter::Either, prelude::*}; use sonobe_primitives::{ arithmetizations::{Arith, ccs::CCS, r1cs::R1CS}, - circuits::alloc::{CommittedCache, IdentityHasher, UsizeSet}, + circuits::alloc::{IdentityHasher, UsizeSet}, commitments::pedersen::PedersenKey, traits::{SonobeCurve, SonobeField}, }; @@ -385,7 +382,7 @@ impl>( - &pk.r1cs.matrices(), + pk.r1cs.matrices(), num_inputs, num_constraints, &assignment, @@ -425,8 +422,8 @@ impl::BigInt], ) -> E::G1Affine { assert_eq!(ek.p.len(), w.len()); - E::G1::msm_bigint(&ek.p, &w).into_affine() + E::G1::msm_bigint(&ek.p, w).into_affine() } pub fn verify(vk: &VerifierKey, x: &[E::G1Affine], pi: &E::G1Affine) -> bool { From 43a587bc3aeb2eb41b89d5f6ce132e9c9127dd3b Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 26 May 2026 13:48:15 +0800 Subject: [PATCH 127/131] Reduce numbers of lc and constraints --- .../primitives/src/algebra/field/emulated.rs | 225 +++++++++++------- crates/primitives/src/algebra/field/mod.rs | 4 +- .../src/arithmetizations/ccs/mod.rs | 8 +- crates/primitives/src/circuits/mod.rs | 10 +- crates/snarks/src/cp/groth16.rs | 15 +- 5 files changed, 153 insertions(+), 109 deletions(-) diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 45fd1ff55..75a5db986 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -271,6 +271,97 @@ impl LimbedVar { } } +#[derive(PartialEq)] +pub enum RangeCheckMode { + Loose, + Tight, +} + +impl LimbedVar { + pub fn alloc>( + cs: impl Into>, + f: impl FnOnce() -> Result, + alloc_mode: AllocationMode, + range_check_mode: RangeCheckMode, + ) -> Result { + let cs = cs.into().cs(); + let v = f()?; + let (x, Bounds(lb, ub)) = v.borrow(); + + if x < lb || x > ub { + return Err(SynthesisError::Unsatisfiable); + } + + let len = max(lb.bits(), ub.bits()) as usize; + let zero = BigInt::zero(); + let one = BigInt::one(); + let min = &one - (&one << len); + let max = (&one << len) - &one; + + let (lb, ub) = if range_check_mode == RangeCheckMode::Loose { + ( + if lb.is_negative() { &min } else { &zero }, + if ub.is_positive() { &max } else { &zero }, + ) + } else { + (lb, ub) + }; + + let x_is_neg = x.is_negative(); + let mut x_abs = x.magnitude().clone(); + let mask = (BigUint::one() << F::BITS_PER_LIMB) - BigUint::one(); + let mut limbs_abs = vec![]; + for _ in 0..len.div_ceil(F::BITS_PER_LIMB) { + limbs_abs.push(F::from(&x_abs & &mask)); + x_abs >>= F::BITS_PER_LIMB; + } + + 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), alloc_mode)? + }; + let limbs_abs = Vec::>::new_variable(cs, || Ok(limbs_abs), alloc_mode)?; + + let limbs = limbs_abs + .into_iter() + .map(|limb_abs| { + limb_abs.enforce_bit_length(F::BITS_PER_LIMB)?; + x_is_neg.select(&limb_abs.negate()?, &limb_abs) + }) + .collect::>()?; + + let bounds = compute_bounds(lb, ub, F::BITS_PER_LIMB); + + 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() && lb != &min { + Self::constant(lb - &one).enforce_lt(&var)?; + } + if !ub.is_zero() && ub != &max { + var.enforce_lt(&Self::constant(ub + &one))?; + } + + Ok(var) + } +} + impl LimbedVar { /// [`LimbedVar::from_bounded_bits_le`] computes a `LimbedVar` from its /// little-endian bits with explicitly supplied [`Bounds`]. @@ -648,21 +739,33 @@ impl let cs = self.cs(); let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into()); // Provide the quotient and remainder as hints - let (q, r) = { + let (q, mut r) = { let v = compose(self.limbs.value().unwrap_or_default()); let q = v.div_floor(&m); let r = v - &q * &m; + let mode = if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }; ( - 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()))) - })?, + LimbedVar::alloc( + cs.clone(), + || { + let lb = self.lbound().div_floor(&m); + let ub = self.ubound().div_floor(&m); + Ok((q, Bounds(lb, ub))) + }, + mode, + RangeCheckMode::Loose, + )?, + LimbedVar::alloc( + cs.clone(), + || Ok((r, Bounds(Zero::zero(), m.clone()))), + mode, + RangeCheckMode::Loose, + )?, ) }; @@ -674,6 +777,11 @@ impl .enforce_equal_unaligned(self)?; // Enforce `r < m` (and `r >= 0` already holds) r.enforce_lt(&m)?; + r.bounds = compute_bounds( + &BigInt::zero(), + &(-Target::one()).into_bigint().into().into(), + Base::BITS_PER_LIMB, + ); Ok(r) } @@ -684,20 +792,25 @@ impl &self, other: &LimbedVar, ) -> Result<(), SynthesisError> { - let cs = self.cs(); + let cs = self.cs().or(other.cs()); 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 x = compose(self.limbs.value().unwrap_or_default()); - let y = compose(other.limbs.value().unwrap_or_default()); - Ok(( - (x - y).div_floor(&m), - Bounds( - (self.lbound() - other.ubound()).div_floor(&m), - (self.ubound() - other.lbound()).div_floor(&m), - ), - )) - })?; + let q = LimbedVar::alloc( + cs.clone(), + || { + let x = compose(self.limbs.value().unwrap_or_default()); + let y = compose(other.limbs.value().unwrap_or_default()); + let lb = (self.lbound() - other.ubound()).div_floor(&m); + let ub = (self.ubound() - other.lbound()).div_floor(&m); + Ok(((x - y).div_floor(&m), Bounds(lb, ub))) + }, + if cs.is_none() { + AllocationMode::Constant + } else { + AllocationMode::Witness + }, + RangeCheckMode::Loose, + )?; let m = LimbedVar::constant(m); @@ -1020,73 +1133,7 @@ impl AllocVar<(BigInt, Bounds), F> for LimbedVar Result, mode: AllocationMode, ) -> Result { - let cs = cs.into().cs(); - let v = f()?; - let (x, Bounds(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 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 bounds = compute_bounds(lb, ub, F::BITS_PER_LIMB); - - 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` - #[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 - && ub + BigInt::one() == BigInt::one() << len - { - } else { - // TODO: strict range checks are not always necessary - var.enforce_lt(&Self::constant(ub + BigInt::one()))?; - Self::constant(lb - BigInt::one()).enforce_lt(&var)?; - } - - Ok(var) + Self::alloc(cs, f, mode, RangeCheckMode::Tight) } fn new_constant( diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 387bf16d4..83dd98240 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -94,8 +94,8 @@ impl Inputize for EmulatedFieldVar value .into_bigint() .to_bits_le() - .into_iter() - .take(Target::MODULUS_BIT_SIZE as usize) + .chunks(Base::BITS_PER_LIMB) + .map(Base::BigInt::from_bits_le) .map(Base::from) .collect() } diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index 4da595661..183a50263 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -165,13 +165,7 @@ pub trait CCSGadget { // `c_i · 〇_{j ∈ S_i} (M_j[row] · z)` let mut prod = c.clone().into(); for i in s { - // TODO: intermediate - // let v = matrices[*i].0[row].mul(&z)?; - let v = matrices[*i].0[row] - .mul(&z)? - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?; - prod = prod * v; + prod = prod * matrices[*i].0[row].mul(&z)?; } sum = match sum { Some(sum) => Some(sum + prod), diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 1032227be..0eb928f3c 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -247,10 +247,14 @@ impl circuit: impl FnOnce(ConstraintSystemRef) -> Result, ) -> Result { let cs = std::mem::take(&mut self.0); - let cs_ref = ConstraintSystemRef::new(cs); - let result = circuit(cs_ref.clone())?; - self.0 = cs_ref.into_inner().unwrap(); + let result = { + let cs_ref = ConstraintSystemRef::new(cs); + let result = circuit(cs_ref.clone())?; + self.0 = cs_ref.into_inner().unwrap(); + result + }; if ARITH_ENABLED { + println!("{}", self.0.num_constraints()); self.0.finalize(); } Ok(result) diff --git a/crates/snarks/src/cp/groth16.rs b/crates/snarks/src/cp/groth16.rs index a53134aca..5f4d41137 100644 --- a/crates/snarks/src/cp/groth16.rs +++ b/crates/snarks/src/cp/groth16.rs @@ -380,14 +380,13 @@ impl>( - pk.r1cs.matrices(), - num_inputs, - num_constraints, - &assignment, - )? - }; + let h = QAP::witness_map_from_matrices::<_, GeneralEvaluationDomain<_>>( + pk.r1cs.matrices(), + num_inputs, + num_constraints, + &assignment, + )?; + end_timer!(witness_map_time); let assignment_bigint = cfg_into_iter!(assignment) From 7b1587781c685d4b890f341d9b583867193c2689 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 26 May 2026 17:38:06 +0800 Subject: [PATCH 128/131] Remove unused methods --- crates/fs/src/definitions/instances.rs | 14 -------------- crates/fs/src/definitions/mod.rs | 4 ++-- crates/fs/src/definitions/witnesses.rs | 19 ++----------------- crates/fs/src/nova/instances/mod.rs | 12 ------------ crates/fs/src/nova/witnesses/mod.rs | 16 ++-------------- 5 files changed, 6 insertions(+), 59 deletions(-) diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index d9f8130ef..40db779a6 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -17,10 +17,6 @@ use super::utils::TaggedVec; pub trait FoldingInstance: Clone + Debug + PartialEq + Eq + Absorbable + for<'a> Dummy<&'a ArithConfig> { - /// [`FoldingInstance::N_COMMITMENTS`] defines the number of commitments - /// contained in the instance. - const N_COMMITMENTS: usize; - /// [`FoldingInstance::commitments`] returns the commitments contained in /// the instance. // TODO (@winderica): consider the scenario where the instance has multiple @@ -30,10 +26,6 @@ pub trait FoldingInstance: /// [`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 / @@ -53,8 +45,6 @@ impl Dummy<&ArithConfig> for PlainInstance { } impl FoldingInstance for PlainInstance { - const N_COMMITMENTS: usize = 0; - fn commitments(&self) -> Vec { vec![] } @@ -62,10 +52,6 @@ impl FoldingInstance for PlainInstance { fn public_inputs(&self) -> &[CM::Scalar] { self } - - fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { - self - } } /// [`FoldingInstanceVar`] is the in-circuit variable of [`FoldingInstance`]. diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index ea7666468..4f38013bc 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -13,9 +13,9 @@ pub mod witnesses; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig}, - circuits::{AssignmentsOwned, WitnessToCommitted}, + circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget}, - relations::{Relation, RelationGadget, WitnessInstanceSampler}, + relations::{Relation, WitnessInstanceSampler}, traits::{Dummy, SonobeField}, }; diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs index 913c2c492..f849e25aa 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -12,16 +12,7 @@ use super::utils::TaggedVec; /// [`FoldingWitness`] defines the operations that a folding scheme's witness /// should support. -pub trait FoldingWitness: Debug + for<'a> Dummy<&'a ArithConfig> { - /// [`FoldingWitness::N_OPENINGS`] defines the number of openings contained - /// in the witness. - const N_OPENINGS: usize; - - /// [`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)>; -} +pub trait FoldingWitness: Debug + for<'a> Dummy<&'a ArithConfig> {} /// [`PlainWitness`] is a vector of field elements that are the witnesses to a /// constraint system. @@ -39,13 +30,7 @@ impl Dummy<&ArithConfig> for PlainWitness { } } -impl FoldingWitness for PlainWitness { - const N_OPENINGS: usize = 0; - - fn openings(&self) -> Vec<(&[CM::Scalar], &CM::Randomness)> { - vec![] - } -} +impl FoldingWitness for PlainWitness {} /// [`FoldingWitnessVar`] is the in-circuit variable of [`FoldingWitness`]. pub trait FoldingWitnessVar: diff --git a/crates/fs/src/nova/instances/mod.rs b/crates/fs/src/nova/instances/mod.rs index cf445d37e..c538ad66c 100644 --- a/crates/fs/src/nova/instances/mod.rs +++ b/crates/fs/src/nova/instances/mod.rs @@ -25,8 +25,6 @@ pub struct RunningInstance { } impl FoldingInstance for RunningInstance { - const N_COMMITMENTS: usize = 2; - fn commitments(&self) -> Vec { vec![self.cm_e.clone(), self.cm_w.clone()] } @@ -34,10 +32,6 @@ impl FoldingInstance for RunningInstance { fn public_inputs(&self) -> &[CM::Scalar] { &self.x } - - fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { - &mut self.x - } } impl Dummy<&ArithConfig> for RunningInstance { @@ -70,8 +64,6 @@ pub struct IncomingInstance { } impl FoldingInstance for IncomingInstance { - const N_COMMITMENTS: usize = 1; - fn commitments(&self) -> Vec { vec![self.cm_w.clone()] } @@ -79,10 +71,6 @@ impl FoldingInstance for IncomingInstance { fn public_inputs(&self) -> &[CM::Scalar] { &self.x } - - fn public_inputs_mut(&mut self) -> &mut [CM::Scalar] { - &mut self.x - } } impl Dummy<&ArithConfig> for IncomingInstance { diff --git a/crates/fs/src/nova/witnesses/mod.rs b/crates/fs/src/nova/witnesses/mod.rs index dbb673c41..d5cfb8679 100644 --- a/crates/fs/src/nova/witnesses/mod.rs +++ b/crates/fs/src/nova/witnesses/mod.rs @@ -20,13 +20,7 @@ pub struct RunningWitness { 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 FoldingWitness for RunningWitness {} impl Dummy<&ArithConfig> for RunningWitness { fn dummy(cfg: &ArithConfig) -> Self { @@ -48,13 +42,7 @@ pub struct IncomingWitness { 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 FoldingWitness for IncomingWitness {} impl Dummy<&ArithConfig> for IncomingWitness { fn dummy(cfg: &ArithConfig) -> Self { From dc211e15cf73b2496bec39c35ba59dad84deaeb4 Mon Sep 17 00:00:00 2001 From: winderica Date: Sat, 30 May 2026 13:33:01 +0800 Subject: [PATCH 129/131] Fix doc tests --- crates/ivc/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index e29148dbc..c13070ae4 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -359,7 +359,7 @@ pub trait IVCVerifierGadget: IVCTypesGadget { /// // 2. Generate prover key and verifier key for the step circuit. /// let (pk, vk) = I::generate_keys(pp, &step_circuit)?; /// -/// 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 { /// // 3. Generate the new state and proof from the current state and @@ -382,10 +382,10 @@ pub trait IVCVerifierGadget: IVCTypesGadget { pub struct IVCStatefulProver<'a, FC: FCircuit, I: IVC> { pk: &'a I::ProverKey, step_circuit: &'a FC, - i: usize, - initial_state: FC::State, - current_state: FC::State, - current_proof: I::Proof, + pub i: usize, + pub initial_state: FC::State, + pub current_state: FC::State, + pub current_proof: I::Proof, } impl<'a, FC: FCircuit, I: IVC> IVCStatefulProver<'a, FC, I> { From 68681a21022e9894f1fa9669089daa4ce5119716 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 8 Jun 2026 01:05:17 +0800 Subject: [PATCH 130/131] Reorganize traits --- crates/fs/src/definitions/instances.rs | 3 +- crates/fs/src/definitions/mod.rs | 4 +- crates/fs/src/definitions/variants.rs | 2 +- crates/fs/src/definitions/witnesses.rs | 3 +- .../fs/src/nova/algorithms/key_generator.rs | 2 +- crates/fs/src/nova/algorithms/preprocessor.rs | 2 +- crates/fs/src/nova/algorithms/prover.rs | 3 +- crates/fs/src/nova/algorithms/verifier.rs | 3 +- crates/fs/src/nova/instances/mod.rs | 4 +- crates/fs/src/nova/mod.rs | 6 +- crates/fs/src/nova/witnesses/mod.rs | 2 +- .../src/compilers/cyclefold/adapters/nova.rs | 5 +- .../ivc/src/compilers/cyclefold/circuits.rs | 6 +- crates/ivc/src/compilers/cyclefold/mod.rs | 11 ++-- crates/ivc/src/lib.rs | 6 +- crates/primitives/src/algebra/field/mod.rs | 18 +++++- .../primitives/src/algebra/group/emulated.rs | 8 ++- crates/primitives/src/algebra/group/mod.rs | 21 ++++++- crates/primitives/src/algebra/mod.rs | 2 +- .../src/arithmetizations/r1cs/circuits.rs | 2 +- .../src/arithmetizations/r1cs/mod.rs | 2 +- .../src/circuits/{alloc.rs => cache.rs} | 0 crates/primitives/src/circuits/inputize.rs | 25 ++++++++ crates/primitives/src/circuits/mod.rs | 17 ++--- .../src/circuits/{utils.rs => test_utils.rs} | 2 +- crates/primitives/src/commitments/mod.rs | 8 +-- crates/primitives/src/commitments/pedersen.rs | 8 ++- crates/primitives/src/lib.rs | 1 - crates/primitives/src/traits.rs | 62 ------------------- crates/primitives/src/utils/dummy.rs | 29 +++++++++ crates/primitives/src/utils/evm.rs | 15 +++++ crates/primitives/src/utils/mod.rs | 2 + crates/snarks/src/cp/groth16.rs | 23 ++++++- 33 files changed, 174 insertions(+), 133 deletions(-) rename crates/primitives/src/circuits/{alloc.rs => cache.rs} (100%) create mode 100644 crates/primitives/src/circuits/inputize.rs rename crates/primitives/src/circuits/{utils.rs => test_utils.rs} (98%) delete mode 100644 crates/primitives/src/traits.rs create mode 100644 crates/primitives/src/utils/dummy.rs create mode 100644 crates/primitives/src/utils/evm.rs diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index 40db779a6..934488baf 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -6,8 +6,7 @@ use ark_std::fmt::Debug; use sonobe_primitives::{ arithmetizations::ArithConfig, commitments::{CommitmentDef, CommitmentDefGadget}, - traits::Dummy, - transcripts::{Absorbable, AbsorbableVar}, + transcripts::{Absorbable, AbsorbableVar}, utils::dummy::Dummy, }; use super::utils::TaggedVec; diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index 4f38013bc..6ea5d6e34 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -12,11 +12,11 @@ pub mod witnesses; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use sonobe_primitives::{ + algebra::field::SonobeField, arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget}, - relations::{Relation, WitnessInstanceSampler}, - traits::{Dummy, SonobeField}, + relations::{Relation, WitnessInstanceSampler}, utils::dummy::Dummy, }; use self::{ diff --git a/crates/fs/src/definitions/variants.rs b/crates/fs/src/definitions/variants.rs index b3655f37d..5d4ba2f2c 100644 --- a/crates/fs/src/definitions/variants.rs +++ b/crates/fs/src/definitions/variants.rs @@ -2,8 +2,8 @@ //! mathematical structures. use sonobe_primitives::{ + algebra::group::CF2, commitments::{CommitmentDef, GroupBasedCommitment}, - traits::CF2, }; use crate::{ diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs index f849e25aa..19c59b491 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -4,8 +4,7 @@ use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use ark_std::fmt::Debug; use sonobe_primitives::{ arithmetizations::ArithConfig, - commitments::{CommitmentDef, CommitmentDefGadget}, - traits::Dummy, + commitments::{CommitmentDef, CommitmentDefGadget}, utils::dummy::Dummy, }; use super::utils::TaggedVec; diff --git a/crates/fs/src/nova/algorithms/key_generator.rs b/crates/fs/src/nova/algorithms/key_generator.rs index c107cc75c..f9d1420d6 100644 --- a/crates/fs/src/nova/algorithms/key_generator.rs +++ b/crates/fs/src/nova/algorithms/key_generator.rs @@ -2,9 +2,9 @@ use ark_std::sync::Arc; use sonobe_primitives::{ + algebra::field::SonobeField, arithmetizations::Arith, commitments::{CommitmentKey, GroupBasedCommitment}, - traits::SonobeField, }; use crate::{Error, FoldingSchemeKeyGenerator, nova::AbstractNova}; diff --git a/crates/fs/src/nova/algorithms/preprocessor.rs b/crates/fs/src/nova/algorithms/preprocessor.rs index 3d71ca64b..70d143f58 100644 --- a/crates/fs/src/nova/algorithms/preprocessor.rs +++ b/crates/fs/src/nova/algorithms/preprocessor.rs @@ -1,7 +1,7 @@ //! Preprocessing for Nova. use ark_std::rand::RngCore; -use sonobe_primitives::{commitments::GroupBasedCommitment, traits::SonobeField}; +use sonobe_primitives::{algebra::field::SonobeField, commitments::GroupBasedCommitment}; use crate::{Error, FoldingSchemePreprocessor, nova::AbstractNova}; diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index c5e4daca9..3201d0c71 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -7,11 +7,10 @@ use itertools::Itertools; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ - algebra::ops::bits::FromBits, + algebra::{field::SonobeField, ops::bits::FromBits}, arithmetizations::r1cs::R1CS, circuits::{Assignments, AssignmentsOwned}, commitments::GroupBasedCommitment, - traits::SonobeField, transcripts::Transcript, }; diff --git a/crates/fs/src/nova/algorithms/verifier.rs b/crates/fs/src/nova/algorithms/verifier.rs index 8bbda14e4..9596c031f 100644 --- a/crates/fs/src/nova/algorithms/verifier.rs +++ b/crates/fs/src/nova/algorithms/verifier.rs @@ -6,7 +6,8 @@ use itertools::Itertools; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ - algebra::ops::bits::FromBits, commitments::GroupBasedCommitment, traits::SonobeField, + algebra::{field::SonobeField, ops::bits::FromBits}, + commitments::GroupBasedCommitment, transcripts::Transcript, }; diff --git a/crates/fs/src/nova/instances/mod.rs b/crates/fs/src/nova/instances/mod.rs index c538ad66c..45dcc4c6f 100644 --- a/crates/fs/src/nova/instances/mod.rs +++ b/crates/fs/src/nova/instances/mod.rs @@ -3,8 +3,8 @@ use ark_ff::PrimeField; use sonobe_primitives::{ - arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy, - transcripts::Absorbable, + arithmetizations::ArithConfig, commitments::CommitmentDef, + transcripts::Absorbable, utils::dummy::Dummy, }; use crate::FoldingInstance; diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 37ee0efc2..687f191a5 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -6,9 +6,7 @@ use ark_r1cs_std::boolean::Boolean; use ark_std::marker::PhantomData; use sonobe_primitives::{ - arithmetizations::r1cs::{R1CS, circuits::R1CSVar}, - commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, - traits::{CF2, SonobeField}, + algebra::{field::SonobeField, group::CF2}, arithmetizations::r1cs::{R1CS, circuits::R1CSVar}, commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment} }; use self::{ @@ -116,7 +114,7 @@ mod tests { rand::{RngCore, thread_rng}, }; use sonobe_primitives::{ - circuits::utils::{CircuitForTest, satisfying_assignments_for_test}, + circuits::test_utils::{CircuitForTest, satisfying_assignments_for_test}, commitments::pedersen::Pedersen, }; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] diff --git a/crates/fs/src/nova/witnesses/mod.rs b/crates/fs/src/nova/witnesses/mod.rs index d5cfb8679..b521a44ed 100644 --- a/crates/fs/src/nova/witnesses/mod.rs +++ b/crates/fs/src/nova/witnesses/mod.rs @@ -1,7 +1,7 @@ //! Definitions of out-of-circuit values and in-circuit variables for Nova //! witnesses. -use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy}; +use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, utils::dummy::Dummy}; use crate::FoldingWitness; diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index d18949bf3..2399e5e23 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -13,12 +13,11 @@ use sonobe_fs::{ use sonobe_primitives::{ algebra::{ field::emulated::{Bounds, EmulatedFieldVar}, - group::emulated::EmulatedAffineVar, + group::{CF1, CF2, SonobeCurve, emulated::EmulatedAffineVar}, ops::bits::{FromBits, ToBitsGadgetExt}, }, circuits::WitnessToPublic, commitments::GroupBasedCommitment, - traits::{CF1, CF2, SonobeCurve}, transcripts::{ Transcript, TranscriptGadget, replay::{ReplayTranscript, ReplayTranscriptVar}, @@ -216,7 +215,7 @@ mod tests { use ark_grumpkin::Projective as C2; use ark_std::{error::Error, rand::thread_rng, sync::Arc}; use sonobe_primitives::{ - circuits::utils::CircuitForTest, + circuits::test_utils::CircuitForTest, commitments::pedersen::Pedersen, transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, }; diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index a0752768a..cc7525d60 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -14,11 +14,7 @@ use sonobe_fs::{ GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ - arithmetizations::ArithConfig, - circuits::{FCircuit, WitnessToPublic}, - commitments::CommitmentDef, - traits::{Dummy, SonobeCurve}, - transcripts::{TranscriptGadget, recording::RecordingTranscriptVar}, + algebra::group::SonobeCurve, arithmetizations::ArithConfig, circuits::{FCircuit, WitnessToPublic}, commitments::CommitmentDef, transcripts::{TranscriptGadget, recording::RecordingTranscriptVar}, utils::dummy::Dummy }; use crate::compilers::cyclefold::FoldingSchemeCycleFoldExt; diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 872f4a672..1e6826f26 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -36,20 +36,23 @@ use sonobe_fs::{ definitions::circuits::FoldingSchemeDeciderGadget, }; use sonobe_primitives::{ - algebra::field::emulated::EmulatedFieldVar, + algebra::{ + field::emulated::EmulatedFieldVar, + group::{CF1, CF2, SonobeCurve}, + }, arithmetizations::{Arith, ArithConfig}, circuits::{ ArithExtractor, AssignmentsExtractor, FCircuit, - alloc::{CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, + cache::{CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, + inputize::Inputize, }, commitments::{CommitmentDef, CommitmentDefGadget}, relations::WitnessInstanceSampler, - traits::{CF1, CF2, Dummy, Inputize, SonobeCurve}, transcripts::{ Transcript, TranscriptGadget, recording::RecordingTranscript, replay::{ReplayTranscript, ReplayTranscriptVar}, - }, + }, utils::dummy::Dummy, }; use sonobe_snarks::cp::CPSNARK; diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index c13070ae4..0f98be97d 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -12,7 +12,7 @@ use ark_relations::gr1cs::SynthesisError; use ark_serialize::SerializationError; use ark_std::{error::Error as ErrorTrait, rand::RngCore}; use sonobe_fs::Error as FoldingError; -use sonobe_primitives::{arithmetizations::Error as ArithError, circuits::FCircuit, traits::Dummy}; +use sonobe_primitives::{arithmetizations::Error as ArithError, circuits::FCircuit, utils::dummy::Dummy}; use thiserror::Error; pub mod compilers; @@ -264,7 +264,7 @@ impl IVCOps for /// ```rust /// use ark_std::rand::Rng; /// use sonobe_ivc::{Error, IVC}; -/// use sonobe_primitives::{circuits::FCircuit, traits::Dummy}; +/// use sonobe_primitives::{circuits::FCircuit, utils::dummy::Dummy}; /// /// fn ivc_usage>( /// config: I::Config, @@ -342,7 +342,7 @@ pub trait IVCVerifierGadget: IVCTypesGadget { /// ```rust /// use ark_std::rand::Rng; /// use sonobe_ivc::{Error, IVC, IVCStatefulProver}; -/// use sonobe_primitives::{circuits::FCircuit, traits::Dummy}; +/// use sonobe_primitives::{circuits::FCircuit, utils::dummy::Dummy}; /// /// fn ivc_usage>( /// config: I::Config, diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 83dd98240..392059d3b 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -1,7 +1,7 @@ //! This module defines extension traits for field elements and their in-circuit //! counterparts, along with some common implementations. -use ark_ff::{BigInteger, Field, Fp, FpConfig, PrimeField}; +use ark_ff::{BigInteger, Field, Fp, Fp2, Fp2Config, FpConfig, PrimeField}; use ark_r1cs_std::{ GR1CSVar, alloc::AllocVar, @@ -17,9 +17,9 @@ use ark_std::{ use crate::{ algebra::{Val, field::emulated::EmulatedFieldVar}, - circuits::WitnessToPublic, - traits::Inputize, + circuits::{WitnessToPublic, inputize::Inputize}, transcripts::{Absorbable, AbsorbableVar}, + utils::evm::EVMSerialize, }; pub mod emulated; @@ -127,6 +127,18 @@ impl WitnessToPublic for EmulatedFieldVa } } +impl, const N: usize> EVMSerialize for Fp { + fn to_calldata(&self) -> Vec { + self.into_bigint().to_bytes_be() + } +} + +impl> EVMSerialize for Fp2

{ + fn to_calldata(&self) -> Vec { + [self.c1.to_calldata(), self.c0.to_calldata()].concat() + } +} + /// [`TwoStageFieldVar`] abstracts over field variables that support a /// two-stage arithmetic model. /// diff --git a/crates/primitives/src/algebra/group/emulated.rs b/crates/primitives/src/algebra/group/emulated.rs index 53054b0ba..0b18cccd4 100644 --- a/crates/primitives/src/algebra/group/emulated.rs +++ b/crates/primitives/src/algebra/group/emulated.rs @@ -22,8 +22,10 @@ use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags}; use ark_std::borrow::Borrow; use crate::{ - algebra::{field::emulated::EmulatedFieldVar, group::SonobeCurve}, - traits::SonobeField, + algebra::{ + field::{SonobeField, emulated::EmulatedFieldVar}, + group::SonobeCurve, + }, transcripts::AbsorbableVar, }; @@ -155,7 +157,7 @@ mod tests { use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; - use crate::{traits::Inputize, transcripts::Absorbable}; + use crate::{circuits::inputize::Inputize, transcripts::Absorbable}; #[test] fn test_alloc_zero() { diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 8e85303da..3a991ebd4 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -3,7 +3,7 @@ use ark_ec::{ AffineRepr, CurveGroup, PrimeGroup, - short_weierstrass::{Projective, SWCurveConfig}, + short_weierstrass::{Affine, Projective, SWCurveConfig}, }; use ark_ff::{Field, One, PrimeField, Zero}; use ark_r1cs_std::{ @@ -19,9 +19,9 @@ use crate::{ field::{SonobeField, emulated::EmulatedFieldVar}, group::emulated::EmulatedAffineVar, }, - circuits::WitnessToPublic, - traits::{Dummy, Inputize}, + circuits::{WitnessToPublic, inputize::Inputize}, transcripts::{Absorbable, AbsorbableVar}, + utils::{dummy::Dummy, evm::EVMSerialize}, }; pub mod emulated; @@ -125,3 +125,18 @@ impl WitnessToPublic for EmulatedAffineV Ok(()) } } + +impl> EVMSerialize for Affine

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

{ + fn to_calldata(&self) -> Vec { + self.into_affine().to_calldata() + } +} diff --git a/crates/primitives/src/algebra/mod.rs b/crates/primitives/src/algebra/mod.rs index f84a20e46..975c3f0c7 100644 --- a/crates/primitives/src/algebra/mod.rs +++ b/crates/primitives/src/algebra/mod.rs @@ -5,7 +5,7 @@ use ark_ff::PrimeField; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; -use crate::traits::SonobeField; +use crate::algebra::field::SonobeField; pub mod field; pub mod group; diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index b4470640a..869fbf9f8 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -133,7 +133,7 @@ mod tests { use super::*; use crate::{ - circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, + circuits::test_utils::{constraints_for_test, satisfying_assignments_for_test}, relations::Relation, }; diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 97aa20cdd..0c87aba7c 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -238,7 +238,7 @@ mod tests { use super::*; use crate::{ - circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, + circuits::test_utils::{constraints_for_test, satisfying_assignments_for_test}, relations::Relation, }; diff --git a/crates/primitives/src/circuits/alloc.rs b/crates/primitives/src/circuits/cache.rs similarity index 100% rename from crates/primitives/src/circuits/alloc.rs rename to crates/primitives/src/circuits/cache.rs diff --git a/crates/primitives/src/circuits/inputize.rs b/crates/primitives/src/circuits/inputize.rs new file mode 100644 index 000000000..1ec1a0455 --- /dev/null +++ b/crates/primitives/src/circuits/inputize.rs @@ -0,0 +1,25 @@ +use ark_ff::Field; +use ark_r1cs_std::GR1CSVar; + +/// [`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 circuit when allocated as public input. +/// +/// This is useful for the verifier to compute the public inputs. +pub trait Inputize: GR1CSVar { + /// [`Inputize::inputize`] outputs the underlying field elements of `self` + /// as if it is allocated in the canonical way in-circuit. + fn inputize(value: &Self::Value) -> Vec; +} + +impl> Inputize for [T] { + fn inputize(value: &Self::Value) -> Vec { + value.iter().flat_map(T::inputize).collect() + } +} + +impl, const N: usize> Inputize for [T; N] { + fn inputize(value: &Self::Value) -> Vec { + value.iter().flat_map(T::inputize).collect() + } +} diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index 0eb928f3c..f8401a9bc 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -12,12 +12,13 @@ use ark_std::{ }; use crate::{ - traits::Inputize, + circuits::inputize::Inputize, transcripts::{Absorbable, AbsorbableVar}, }; -pub mod alloc; -pub mod utils; +pub mod cache; +pub mod inputize; +pub mod test_utils; /// [`FCircuit`] defines the trait of step circuits being proven by IVC schemes. /// @@ -319,14 +320,6 @@ impl WitnessToPublic for [T] { } } -/// [`WitnessToCommitted`] defines a helper trait for marking witness variables -/// as committed witnesses in the constraint system. -pub trait WitnessToCommitted { - /// [`WitnessToCommitted::mark_as_committed`] marks a witness variable as a - /// committed witness. - fn mark_as_committed(&self) -> Result<(), SynthesisError>; -} - #[cfg(test)] mod tests { use ark_bn254::Fr; @@ -337,7 +330,7 @@ mod tests { use wasm_bindgen_test::wasm_bindgen_test as test; use super::{ - utils::{CircuitForTest, constraints_for_test, satisfying_assignments_for_test}, + test_utils::{CircuitForTest, constraints_for_test, satisfying_assignments_for_test}, *, }; use crate::arithmetizations::r1cs::R1CS; diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/test_utils.rs similarity index 98% rename from crates/primitives/src/circuits/utils.rs rename to crates/primitives/src/circuits/test_utils.rs index a5753eab0..137d465d3 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/test_utils.rs @@ -9,7 +9,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable}; use super::Assignments; -use crate::{arithmetizations::r1cs::R1CS, circuits::FCircuit, traits::SonobeField}; +use crate::{algebra::field::SonobeField, arithmetizations::r1cs::R1CS, circuits::FCircuit}; /// [`CircuitForTest`] implements a simple test circuit computing /// `y = x^3 + x + 5` with 4 R1CS constraints. diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index 13e8ee6b1..a3f45368e 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -17,12 +17,10 @@ use thiserror::Error; use crate::{ algebra::{ Val, - field::{TwoStageFieldVar, emulated::EmulatedFieldVar}, - group::emulated::EmulatedAffineVar, + field::{SonobeField, TwoStageFieldVar, emulated::EmulatedFieldVar}, + group::{CF1, CF2, SonobeCurve, emulated::EmulatedAffineVar}, ops::bits::FromBitsGadget, - }, - traits::{CF1, CF2, Inputize, SonobeCurve, SonobeField}, - transcripts::{Absorbable, AbsorbableVar}, + }, circuits::inputize::Inputize, transcripts::{Absorbable, AbsorbableVar} }; pub mod pedersen; diff --git a/crates/primitives/src/commitments/pedersen.rs b/crates/primitives/src/commitments/pedersen.rs index 4a9a040dd..67a240bb9 100644 --- a/crates/primitives/src/commitments/pedersen.rs +++ b/crates/primitives/src/commitments/pedersen.rs @@ -23,13 +23,15 @@ use ark_std::{ use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error}; use crate::{ - algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar}, + algebra::{ + field::emulated::EmulatedFieldVar, + group::{CF1, CF2, SonobeCurve, emulated::EmulatedAffineVar}, + }, circuits::{ WitnessToPublic, - alloc::{CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, + cache::{CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, }, commitments::{CommitmentOpsGadget, GroupBasedCommitment}, - traits::{CF1, CF2, SonobeCurve}, utils::null::Null, }; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index f595546f7..1529d3a6e 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -13,6 +13,5 @@ pub mod arithmetizations; pub mod circuits; pub mod commitments; pub mod relations; -pub mod traits; pub mod transcripts; pub mod utils; diff --git a/crates/primitives/src/traits.rs b/crates/primitives/src/traits.rs deleted file mode 100644 index f38346917..000000000 --- a/crates/primitives/src/traits.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! This module defines helper traits used across Sonobe's crates. - -use ark_ff::Field; -use ark_r1cs_std::GR1CSVar; - -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; -} - -impl Dummy for Vec { - fn dummy(cfg: usize) -> Self { - vec![Default::default(); cfg] - } -} - -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)) - } -} - -/// [`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 circuit when allocated as public input. -/// -/// This is useful for the verifier to compute the public inputs. -pub trait Inputize: GR1CSVar { - /// [`Inputize::inputize`] outputs the underlying field elements of `self` - /// as if it is allocated in the canonical way in-circuit. - fn inputize(value: &Self::Value) -> Vec; -} - -impl> Inputize for [T] { - fn inputize(value: &Self::Value) -> Vec { - value.iter().flat_map(T::inputize).collect() - } -} - -impl, const N: usize> Inputize for [T; N] { - fn inputize(value: &Self::Value) -> Vec { - value.iter().flat_map(T::inputize).collect() - } -} diff --git a/crates/primitives/src/utils/dummy.rs b/crates/primitives/src/utils/dummy.rs new file mode 100644 index 000000000..408cd4f95 --- /dev/null +++ b/crates/primitives/src/utils/dummy.rs @@ -0,0 +1,29 @@ +/// [`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; +} + +impl Dummy for Vec { + fn dummy(cfg: usize) -> Self { + vec![Default::default(); cfg] + } +} + +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)) + } +} diff --git a/crates/primitives/src/utils/evm.rs b/crates/primitives/src/utils/evm.rs new file mode 100644 index 000000000..981579a57 --- /dev/null +++ b/crates/primitives/src/utils/evm.rs @@ -0,0 +1,15 @@ +pub trait EVMSerialize { + fn to_calldata(&self) -> Vec; +} + +impl EVMSerialize for [T] { + fn to_calldata(&self) -> Vec { + self.iter().flat_map(EVMSerialize::to_calldata).collect() + } +} + +impl EVMSerialize for u8 { + fn to_calldata(&self) -> Vec { + vec![*self] + } +} diff --git a/crates/primitives/src/utils/mod.rs b/crates/primitives/src/utils/mod.rs index 7f61a9721..f926713da 100644 --- a/crates/primitives/src/utils/mod.rs +++ b/crates/primitives/src/utils/mod.rs @@ -1,3 +1,5 @@ //! Miscellaneous utilities shared across the primitives crate. pub mod null; +pub mod dummy; +pub mod evm; \ No newline at end of file diff --git a/crates/snarks/src/cp/groth16.rs b/crates/snarks/src/cp/groth16.rs index 5f4d41137..deec64d11 100644 --- a/crates/snarks/src/cp/groth16.rs +++ b/crates/snarks/src/cp/groth16.rs @@ -20,10 +20,11 @@ use itertools::{Either, Itertools}; #[cfg(feature = "parallel")] use rayon::{iter::Either, prelude::*}; use sonobe_primitives::{ + algebra::{field::SonobeField, group::SonobeCurve}, arithmetizations::{Arith, ccs::CCS, r1cs::R1CS}, - circuits::alloc::{IdentityHasher, UsizeSet}, + circuits::cache::{IdentityHasher, UsizeSet}, commitments::pedersen::PedersenKey, - traits::{SonobeCurve, SonobeField}, + utils::evm::EVMSerialize, }; use thiserror::Error; @@ -94,6 +95,19 @@ pub struct Proof { pub link_pi: E::G1Affine, } +impl> EVMSerialize for Proof { + fn to_calldata(&self) -> Vec { + [ + self.groth16_proof.a.to_calldata(), + self.groth16_proof.b.to_calldata(), + self.groth16_proof.c.to_calldata(), + self.d.to_calldata(), + self.link_pi.to_calldata(), + ] + .concat() + } +} + /// [`Error`] enumerates possible errors during LegoGroth16 operations. #[derive(Debug, Error)] pub enum Error { @@ -518,7 +532,10 @@ mod tests { lc, }; use ark_std::rand::thread_rng; - use sonobe_primitives::circuits::{ArithExtractor, AssignmentsExtractor}; + use sonobe_primitives::{ + algebra::group::SonobeCurve, + circuits::{ArithExtractor, AssignmentsExtractor}, + }; use super::*; From 243391ebc14ad993f425802eb9dbaf44fdd54436 Mon Sep 17 00:00:00 2001 From: winderica Date: Wed, 1 Jul 2026 12:02:19 +0800 Subject: [PATCH 131/131] EVM verifier --- Cargo.lock | 1354 +++++++++++++++-- Cargo.toml | 4 + crates/fs/src/definitions/instances.rs | 3 +- crates/fs/src/definitions/mod.rs | 3 +- crates/fs/src/definitions/witnesses.rs | 3 +- crates/fs/src/nova/instances/mod.rs | 4 +- crates/fs/src/nova/mod.rs | 4 +- crates/fs/src/nova/witnesses/mod.rs | 4 +- crates/ivc/Cargo.toml | 10 +- .../src/compilers/cyclefold/adapters/nova.rs | 94 +- .../ivc/src/compilers/cyclefold/circuits.rs | 7 +- .../src/compilers/cyclefold/evm_verifier.rs | 63 + crates/ivc/src/compilers/cyclefold/mod.rs | 107 +- crates/ivc/src/lib.rs | 110 +- .../cyclefold_based_ivc_decider.sol.askama | 107 ++ crates/primitives/Cargo.toml | 4 + crates/primitives/src/algebra/field/mod.rs | 5 +- crates/primitives/src/algebra/group/mod.rs | 10 +- crates/primitives/src/circuits/mod.rs | 4 +- crates/primitives/src/commitments/mod.rs | 4 +- .../src/transcripts/recording/mod.rs | 8 +- crates/primitives/src/utils/evm.rs | 15 - crates/primitives/src/utils/evm/compiler.rs | 167 ++ crates/primitives/src/utils/evm/harness.rs | 86 ++ crates/primitives/src/utils/evm/mod.rs | 5 + crates/primitives/src/utils/evm/serialize.rs | 68 + crates/primitives/src/utils/mod.rs | 5 +- crates/snarks/Cargo.toml | 3 + .../snarks/src/cp/legogroth16/evm_verifier.rs | 49 + .../src/cp/{groth16.rs => legogroth16/mod.rs} | 294 ++-- crates/snarks/src/cp/mod.rs | 2 +- crates/snarks/src/linear_subspace/mod.rs | 2 +- .../snarks/templates/legogroth16.sol.askama | 139 ++ 33 files changed, 2320 insertions(+), 427 deletions(-) create mode 100644 crates/ivc/src/compilers/cyclefold/evm_verifier.rs create mode 100644 crates/ivc/templates/cyclefold_based_ivc_decider.sol.askama delete mode 100644 crates/primitives/src/utils/evm.rs create mode 100644 crates/primitives/src/utils/evm/compiler.rs create mode 100644 crates/primitives/src/utils/evm/harness.rs create mode 100644 crates/primitives/src/utils/evm/mod.rs create mode 100644 crates/primitives/src/utils/evm/serialize.rs create mode 100644 crates/snarks/src/cp/legogroth16/evm_verifier.rs rename crates/snarks/src/cp/{groth16.rs => legogroth16/mod.rs} (75%) create mode 100644 crates/snarks/templates/legogroth16.sol.askama diff --git a/Cargo.lock b/Cargo.lock index f6398bbfc..da61a4ad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,82 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-eip7928" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b12337f74cbfa451cb04dac173974814a6ff463079e1793aa09600ba8813ab" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "once_cell", + "thiserror", +] + +[[package]] +name = "alloy-primitives" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hashbrown 0.17.1", + "indexmap 2.14.0", + "itoa", + "k256", + "paste", + "ruint", + "rustc-hash", + "sha3 0.11.0", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +dependencies = [ + "alloy-rlp-derive", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -29,16 +105,39 @@ dependencies = [ "libc", ] +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + [[package]] name = "ark-bn254" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bc66f96ebe2a17a499475b4f94791d379817592ef494171586967ffdc6f95db" dependencies = [ - "ark-ec", - "ark-ff", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-r1cs-std", - "ark-std", + "ark-std 0.6.0", ] [[package]] @@ -49,17 +148,17 @@ checksum = "31b3409b1846fe459d19c95df039481575ac6d5842ae63858ad75cc31219bfc1" dependencies = [ "ahash", "ark-crypto-primitives-macros", - "ark-ec", - "ark-ff", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-r1cs-std", "ark-relations", - "ark-serialize", + "ark-serialize 0.6.0", "ark-snark", - "ark-std", + "ark-std 0.6.0", "blake2", "blake3", "derivative", - "digest", + "digest 0.10.7", "fnv", "merlin", "num-bigint", @@ -76,7 +175,28 @@ checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", ] [[package]] @@ -86,14 +206,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8352a2b2aedf6ba2cc38f7520fc51191d518dde96175c729af19f2d059f191c4" dependencies = [ "ahash", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ff 0.6.0", + "ark-poly 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", "educe", "fnv", "hashbrown 0.17.1", - "itertools", + "itertools 0.14.0", "num-bigint", "num-integer", "num-traits", @@ -101,17 +221,37 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", - "digest", + "ark-ff-asm 0.6.0", + "ark-ff-macros 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", "educe", "num-bigint", "num-traits", @@ -119,6 +259,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.118", +] + [[package]] name = "ark-ff-asm" version = "0.6.0" @@ -126,7 +276,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -139,7 +302,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -149,13 +312,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a293328aa422e65527e285614ce5d1dceb0bd7b8b18d18b1b63191ee1f74cb41" dependencies = [ "ark-crypto-primitives", - "ark-ec", - "ark-ff", - "ark-poly", + "ark-ec 0.6.0", + "ark-ff 0.6.0", + "ark-poly 0.6.0", "ark-relations", - "ark-serialize", + "ark-serialize 0.6.0", "ark-snark", - "ark-std", + "ark-std 0.6.0", "rayon", ] @@ -165,11 +328,11 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2c969a12ceed8881e5c66277dabd3e7a5aa2c72c9370f3f42eca3f2dd33a21a" dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff", + "ark-bn254 0.6.0", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-r1cs-std", - "ark-std", + "ark-std 0.6.0", ] [[package]] @@ -178,10 +341,25 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26dbfc18163d3313389ce5d515aa675bb694b8ef64744006c5a2c88439c7133" dependencies = [ - "ark-ec", - "ark-ff", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-r1cs-std", - "ark-std", + "ark-std 0.6.0", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", ] [[package]] @@ -191,9 +369,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f55af10b672002b8d953e230282c51206842e20e5791a94432219b4201de5c" dependencies = [ "ahash", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", "educe", "fnv", "hashbrown 0.17.1", @@ -206,12 +384,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291f1c6628bfcac79b0dc2adbe401aa9100e2e96daa971645e0b18fc94de9a98" dependencies = [ - "ark-ec", - "ark-ff", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-relations", - "ark-std", + "ark-std 0.6.0", "educe", - "itertools", + "itertools 0.14.0", "num-bigint", "num-integer", "num-traits", @@ -224,10 +402,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4c11c797a64b8a23e22bf4e77bf582ac27bb21395e3183a9a506ba2561e9f9" dependencies = [ - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", + "ark-ff 0.6.0", + "ark-poly 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", "foldhash 0.1.5", "indexmap 2.14.0", "rayon", @@ -235,20 +413,44 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + [[package]] name = "ark-serialize" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" dependencies = [ - "ark-serialize-derive", - "ark-std", - "digest", + "ark-serialize-derive 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", "num-bigint", "rayon", "serde_with", ] +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "ark-serialize-derive" version = "0.6.0" @@ -257,7 +459,7 @@ checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -266,10 +468,20 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bdb461d2be9b2bd6f303c79fffc89f5858790a7b4d33257bca3178e2c071fb9" dependencies = [ - "ark-ff", + "ark-ff 0.6.0", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.6.0", + "ark-std 0.6.0", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.6", ] [[package]] @@ -280,7 +492,7 @@ checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" dependencies = [ "colored", "num-traits", - "rand", + "rand 0.8.6", "rayon", ] @@ -292,9 +504,62 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "askama" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc" +dependencies = [ + "askama_macros", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738" +dependencies = [ + "askama_parser", + "basic-toml", + "glob", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn 2.0.118", +] + +[[package]] +name = "askama_macros" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a" +dependencies = [ + "askama_derive", +] + +[[package]] +name = "askama_parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" +dependencies = [ + "rustc-hash", + "serde", + "serde_derive", + "unicode-ident", + "winnow", +] [[package]] name = "async-trait" @@ -304,7 +569,28 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -313,19 +599,52 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -351,6 +670,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bs58" version = "0.5.1" @@ -372,6 +700,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + [[package]] name = "cast" version = "0.3.0" @@ -380,9 +714,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.62" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "shlex", @@ -396,9 +730,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "num-traits", @@ -416,12 +750,39 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -471,23 +832,53 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -502,23 +893,81 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.118", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.6", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "educe" version = "0.6.0" @@ -528,7 +977,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -537,6 +986,24 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enum-ordinalize" version = "4.3.2" @@ -554,7 +1021,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -563,6 +1030,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -587,6 +1064,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-core" version = "0.3.32" @@ -613,12 +1096,13 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -634,12 +1118,38 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -649,6 +1159,8 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] @@ -657,6 +1169,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -704,6 +1234,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -720,15 +1259,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "js-sys" -version = "0.3.99" +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", + "ecdsa", + "elliptic-curve", + "sha2", ] [[package]] @@ -740,6 +1290,16 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -760,15 +1320,15 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "merlin" @@ -777,8 +1337,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", - "keccak", - "rand_core", + "keccak 0.1.6", + "rand_core 0.6.4", "zeroize", ] @@ -792,6 +1352,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -801,6 +1367,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -809,7 +1389,16 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "rand", + "rand 0.8.6", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", ] [[package]] @@ -827,6 +1416,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -849,6 +1460,30 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -870,6 +1505,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -879,15 +1523,35 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "unarray", +] + [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.6" @@ -895,8 +1559,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_core 0.9.5", ] [[package]] @@ -906,7 +1579,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -918,6 +1601,21 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "rayon" version = "1.12.0" @@ -955,7 +1653,240 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", +] + +[[package]] +name = "revm" +version = "40.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823da6e5509bb8e5dcd91295870e494917a030ad506fc83301f3f08ad8b15b17" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b378c2653331fe60969d9745e802cd773d82a20d8aaced914dfcf26ab8f0d9" +dependencies = [ + "bitvec", + "revm-primitives", +] + +[[package]] +name = "revm-context" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafa298114f3cab706945de14c04e73e6e6d7896302e4183dae273f968e52f80" +dependencies = [ + "bitvec", + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-context-interface" +version = "19.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9c13f1dfc79425931fd184b6bd373dfac7baba50859b01107d5c0e20549cbb" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database" +version = "15.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69c3ce73454a09ef89a66177239d7c4f5f697227ae27254c99451866603b19d" +dependencies = [ + "derive_more", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database-interface" +version = "12.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2656187f9f9c22ef9dd9300ed71aeaeca3506a6a0a229a07f264649b960d68" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "thiserror", +] + +[[package]] +name = "revm-handler" +version = "20.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ce1d66037ca1394128313bb995fa9f50d834927a389386bb34f8f0ef914648f" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-inspector" +version = "21.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fe3635d3411e8318849546570ca0220e783443319e28f5397c9f80b05bf4344" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-interpreter" +version = "37.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae56c57ddca1f5c4abd443f826f1b3e49a86a528e7e1ea0fc207cdc4671a37e" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-precompile" +version = "36.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191db865091e07ecb80b12ce3048192c76071ca3d2b0a315b111b271cd4ced37" +dependencies = [ + "ark-bls12-381", + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "cfg-if", + "k256", + "p256", + "revm-context-interface", + "revm-primitives", + "ripemd", + "sha2", +] + +[[package]] +name = "revm-primitives" +version = "24.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5102d804892908d4ebf68da29b8562895922dffa26c230ff2c4dadcf93916f" +dependencies = [ + "alloy-primitives", + "once_cell", +] + +[[package]] +name = "revm-state" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eff6067185cf80932e06f6a9c8045b012ecb6f99a8d6edc618ec2792373e14" +dependencies = [ + "alloy-eip7928", + "bitflags", + "nonmax", + "revm-bytecode", + "revm-primitives", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "ruint" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" +dependencies = [ + "alloy-rlp", + "proptest", + "rand 0.8.6", + "rand 0.9.4", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", ] [[package]] @@ -997,6 +1928,25 @@ dependencies = [ "serde_json", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -1024,7 +1974,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1042,9 +1992,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64", "bs58", @@ -1067,7 +2017,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] @@ -1076,8 +2026,18 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ - "digest", - "keccak", + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] @@ -1091,9 +2051,19 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] [[package]] name = "slab" @@ -1103,26 +2073,26 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "sonobe-fs" version = "0.1.0" dependencies = [ - "ark-bn254", + "ark-bn254 0.6.0", "ark-crypto-primitives", - "ark-ec", - "ark-ff", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-pallas", - "ark-poly", + "ark-poly 0.6.0", "ark-r1cs-std", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.6.0", + "ark-std 0.6.0", "getrandom", - "itertools", + "itertools 0.14.0", "num-bigint", "rayon", "sonobe-primitives", @@ -1134,18 +2104,19 @@ dependencies = [ name = "sonobe-ivc" version = "0.1.0" dependencies = [ - "ark-bn254", + "ark-bn254 0.6.0", "ark-crypto-primitives", - "ark-ec", - "ark-ff", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-grumpkin", "ark-r1cs-std", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "askama", "getrandom", "num-bigint", - "sha3", + "sha3 0.10.9", "sonobe-fs", "sonobe-primitives", "sonobe-snarks", @@ -1157,25 +2128,28 @@ dependencies = [ name = "sonobe-primitives" version = "0.1.0" dependencies = [ - "ark-bn254", + "ark-bn254 0.6.0", "ark-crypto-primitives", - "ark-ec", - "ark-ff", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-grumpkin", "ark-pallas", - "ark-poly", + "ark-poly 0.6.0", "ark-r1cs-std", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.6.0", + "ark-std 0.6.0", "getrandom", "hashbrown 0.17.1", - "itertools", + "itertools 0.14.0", "num-bigint", "num-integer", "num-traits", "rayon", - "sha3", + "revm", + "serde", + "serde_json", + "sha3 0.10.9", "thiserror", "wasm-bindgen-test", ] @@ -1184,19 +2158,20 @@ dependencies = [ name = "sonobe-snarks" version = "0.1.0" dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff", + "ark-bn254 0.6.0", + "ark-ec 0.6.0", + "ark-ff 0.6.0", "ark-groth16", "ark-grumpkin", - "ark-poly", + "ark-poly 0.6.0", "ark-relations", - "ark-serialize", + "ark-serialize 0.6.0", "ark-snark", - "ark-std", + "ark-std 0.6.0", + "askama", "getrandom", "hashbrown 0.17.1", - "itertools", + "itertools 0.14.0", "rayon", "sonobe-primitives", "thiserror", @@ -1222,15 +2197,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "2.0.18" @@ -1248,7 +2229,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1262,12 +2243,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -1277,15 +2257,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" dependencies = [ "num-conv", "time-core", @@ -1325,7 +2305,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1365,9 +2345,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" @@ -1375,6 +2361,18 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "valuable" version = "0.1.1" @@ -1405,9 +2403,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -1418,9 +2416,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -1428,9 +2426,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1438,31 +2436,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fde991ccdc895cb7fbaa14b137d62af74d9011be67b71c694bfc40edd3119c" +checksum = "2a0d555ca874445df8d314f94f5c948a4e74e5418f332c89f660a3d8310a96f4" dependencies = [ "async-trait", "cast", @@ -1482,20 +2480,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e925354648d2a4d1bf205412e36d520a800280622eef4719678d268e5d40e978" +checksum = "94eb68555b95bcea5e8cf4abe280b529049479fa995bfc23734af96a6aedc120" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684365b586a9a6256c1cc3544eee8680de48d6041142f581776ec7b139622ae9" +checksum = "c31d56021e873866c968588ed85ccdf56db5c426e44afdb4618c39895104b920" [[package]] name = "winapi-util" @@ -1527,7 +2525,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1538,7 +2536,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1647,44 +2645,62 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2af26bb08..abfdacf5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ repository = "https://github.com/privacy-scaling-explorations/sonobe/" rust-version = "1.85.1" [workspace.dependencies] +askama = { version = "0.16.0" } hashbrown = { version = "0.17" } itertools = { version = "0.14.0" } num-bigint = { version = "0.4.3" } @@ -21,6 +22,9 @@ num-integer = { version = "0.1" } num-traits = { version = "0.2" } sha3 = { version = "0.10" } rayon = { version = "1" } +revm = { version = "40.0.3", default-features = false } +serde = { version = "1.0" } +serde_json = { version = "1.0" } thiserror = { version = "2.0.16" } wasm-bindgen-test = { version = "0.3" } diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index 934488baf..191774b66 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -6,7 +6,8 @@ use ark_std::fmt::Debug; use sonobe_primitives::{ arithmetizations::ArithConfig, commitments::{CommitmentDef, CommitmentDefGadget}, - transcripts::{Absorbable, AbsorbableVar}, utils::dummy::Dummy, + transcripts::{Absorbable, AbsorbableVar}, + utils::dummy::Dummy, }; use super::utils::TaggedVec; diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index 6ea5d6e34..9c0c56213 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -16,7 +16,8 @@ use sonobe_primitives::{ arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget}, - relations::{Relation, WitnessInstanceSampler}, utils::dummy::Dummy, + relations::{Relation, WitnessInstanceSampler}, + utils::dummy::Dummy, }; use self::{ diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs index 19c59b491..aecc50934 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -4,7 +4,8 @@ use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use ark_std::fmt::Debug; use sonobe_primitives::{ arithmetizations::ArithConfig, - commitments::{CommitmentDef, CommitmentDefGadget}, utils::dummy::Dummy, + commitments::{CommitmentDef, CommitmentDefGadget}, + utils::dummy::Dummy, }; use super::utils::TaggedVec; diff --git a/crates/fs/src/nova/instances/mod.rs b/crates/fs/src/nova/instances/mod.rs index 45dcc4c6f..e47264ab9 100644 --- a/crates/fs/src/nova/instances/mod.rs +++ b/crates/fs/src/nova/instances/mod.rs @@ -3,8 +3,8 @@ use ark_ff::PrimeField; use sonobe_primitives::{ - arithmetizations::ArithConfig, commitments::CommitmentDef, - transcripts::Absorbable, utils::dummy::Dummy, + arithmetizations::ArithConfig, commitments::CommitmentDef, transcripts::Absorbable, + utils::dummy::Dummy, }; use crate::FoldingInstance; diff --git a/crates/fs/src/nova/mod.rs b/crates/fs/src/nova/mod.rs index 687f191a5..11136fefa 100644 --- a/crates/fs/src/nova/mod.rs +++ b/crates/fs/src/nova/mod.rs @@ -6,7 +6,9 @@ use ark_r1cs_std::boolean::Boolean; use ark_std::marker::PhantomData; use sonobe_primitives::{ - algebra::{field::SonobeField, group::CF2}, arithmetizations::r1cs::{R1CS, circuits::R1CSVar}, commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment} + algebra::{field::SonobeField, group::CF2}, + arithmetizations::r1cs::{R1CS, circuits::R1CSVar}, + commitments::{CommitmentDef, CommitmentDefGadget, GroupBasedCommitment}, }; use self::{ diff --git a/crates/fs/src/nova/witnesses/mod.rs b/crates/fs/src/nova/witnesses/mod.rs index b521a44ed..2df4c86f2 100644 --- a/crates/fs/src/nova/witnesses/mod.rs +++ b/crates/fs/src/nova/witnesses/mod.rs @@ -1,7 +1,9 @@ //! Definitions of out-of-circuit values and in-circuit variables for Nova //! witnesses. -use sonobe_primitives::{arithmetizations::ArithConfig, commitments::CommitmentDef, utils::dummy::Dummy}; +use sonobe_primitives::{ + arithmetizations::ArithConfig, commitments::CommitmentDef, utils::dummy::Dummy, +}; use crate::FoldingWitness; diff --git a/crates/ivc/Cargo.toml b/crates/ivc/Cargo.toml index c735c6207..0b5e8c191 100644 --- a/crates/ivc/Cargo.toml +++ b/crates/ivc/Cargo.toml @@ -6,6 +6,7 @@ license.workspace = true repository.workspace = true [dependencies] +ark-bn254 = { workspace = true, features = ["curve"], optional = true } ark-crypto-primitives = { workspace = true, features = ["constraints", "sponge", "crh"] } ark-ec = { workspace = true } ark-ff = { workspace = true, features = ["asm"] } @@ -13,6 +14,7 @@ ark-r1cs-std = { workspace = true } ark-relations = { workspace = true } ark-serialize = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } +askama = { workspace = true, optional = true } num-bigint = { workspace = true, features = ["rand"] } sha3 = { workspace = true } thiserror = { workspace = true } @@ -32,7 +34,13 @@ getrandom = { version = "0.2", features = ["js"] } wasm-bindgen-test = { workspace = true } [features] -default = ["ark-std/print-trace"] +default = ["ark-std/print-trace", "evm"] +evm = [ + "dep:ark-bn254", + "dep:askama", + "sonobe-primitives/evm", + "sonobe-snarks/evm", +] parallel = [ "sonobe-fs/parallel", "sonobe-snarks/parallel", diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 2399e5e23..ce100bd60 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -10,6 +10,8 @@ use sonobe_fs::{ FoldingSchemeDefGadget, nova::{CycleFoldNova, Nova}, }; +#[cfg(feature = "evm")] +use sonobe_primitives::utils::evm::serialize::EVMSerialize; use sonobe_primitives::{ algebra::{ field::emulated::{Bounds, EmulatedFieldVar}, @@ -24,6 +26,8 @@ use sonobe_primitives::{ }, }; +#[cfg(feature = "evm")] +use crate::compilers::cyclefold::evm_verifier::{DeciderFoldFragment, FoldingSchemeEVMExt}; use crate::compilers::cyclefold::{ CycleFoldBasedIVC, FoldingSchemeCycleFoldExt, circuits::CycleFoldCircuit, }; @@ -208,25 +212,68 @@ impl FoldingSchemeCycleFo pub type NovaNovaIVC = CycleFoldBasedIVC, CycleFoldNova, T>; +#[cfg(feature = "evm")] +impl< + CM: GroupBasedCommitment, + const CHALLENGE_BITS: usize, +> FoldingSchemeEVMExt<1, 1> for Nova +{ + fn decider_fold_fragment() -> DeciderFoldFragment { + let challenge = "challenge"; + DeciderFoldFragment { + challenge: challenge.to_string(), + params: ["U_cm_e", "cm_t", "U_cm_w", "u_cm_w"] + .map(|p| format!("uint256[2] calldata {p}")) + .to_vec(), + body: [ + format!("uint256 rho = {challenge} & ((1 << {CHALLENGE_BITS}) - 1);"), + "uint256[2] memory cm_e = _ecAdd(U_cm_e, _ecMul(cm_t, rho));".to_string(), + "uint256[2] memory cm_w = _ecAdd(U_cm_w, _ecMul(u_cm_w, rho));".to_string(), + ] + .join("\n"), + commitments: ["cm_e", "cm_w"].map(ToString::to_string).to_vec(), + } + } + + #[allow(non_snake_case)] + fn verify_calldata( + [U]: &[impl Borrow; 1], + [u]: &[impl Borrow; 1], + proof: &Self::Proof<1, 1>, + ) -> Result, sonobe_fs::Error> { + let (U, u) = (U.borrow(), u.borrow()); + Ok((&U.cm_e, proof, &U.cm_w, &u.cm_w).to_calldata()) + } +} + #[cfg(test)] mod tests { use ark_bn254::{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}; + #[cfg(feature = "evm")] + use askama::Template; use sonobe_primitives::{ circuits::test_utils::CircuitForTest, commitments::pedersen::Pedersen, transcripts::griffin::{GriffinParams, sponge::GriffinSponge}, }; - use sonobe_snarks::cp::groth16::LegoGroth16; + use sonobe_snarks::cp::legogroth16::LegoGroth16; + #[cfg(feature = "evm")] + use sonobe_snarks::cp::legogroth16::evm_verifier::LegoGroth16VerifierTemplate; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] use wasm_bindgen_test::wasm_bindgen_test as test; use super::*; use crate::{ compilers::cyclefold::CycleFoldBasedIVCDecider, - tests::{test_ivc, test_ivc_decider}, + tests::{test_decider, test_ivc}, + }; + #[cfg(feature = "evm")] + use crate::{ + compilers::cyclefold::evm_verifier::CycleFoldBasedIVCDeciderVerifierTemplate, + tests::test_decider_evm, }; #[test] @@ -240,16 +287,14 @@ mod tests { }, vec![(); 20], &mut rng, - )?; - - Ok(()) + ) } #[test] fn test_nova_nova_decider() -> Result<(), Box> { let mut rng = thread_rng(); - test_ivc_decider::< + test_decider::< CycleFoldBasedIVCDecider< Nova>, CycleFoldNova>, @@ -264,8 +309,41 @@ mod tests { }, vec![(); 20], &mut rng, - )?; + ) + } - Ok(()) + #[cfg(feature = "evm")] + #[test] + fn test_nova_nova_decider_evm() -> Result<(), Box> { + let mut rng = thread_rng(); + + test_decider_evm::< + CycleFoldBasedIVCDecider< + Nova>, + CycleFoldNova>, + GriffinSponge, + LegoGroth16, + >, + _, + >( + (65536, 2048, Arc::new(GriffinParams::new(16, 5, 9))), + CircuitForTest { + x: Fr::rand(&mut rng), + }, + vec![(); 20], + |(lego_vk, _, _, _, reference_state)| { + let lego_src = LegoGroth16VerifierTemplate { vk: lego_vk }.render()?; + let decider_src = CycleFoldBasedIVCDeciderVerifierTemplate::< + Nova>, + CircuitForTest, + >::new(lego_vk, reference_state) + .render()?; + Ok(vec![ + ("DeciderVerifier.sol".to_string(), decider_src), + ("LegoGroth16Verifier.sol".to_string(), lego_src), + ]) + }, + &mut rng, + ) } } diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index cc7525d60..37f4eed2c 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -14,7 +14,12 @@ use sonobe_fs::{ GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ - algebra::group::SonobeCurve, arithmetizations::ArithConfig, circuits::{FCircuit, WitnessToPublic}, commitments::CommitmentDef, transcripts::{TranscriptGadget, recording::RecordingTranscriptVar}, utils::dummy::Dummy + algebra::group::SonobeCurve, + arithmetizations::ArithConfig, + circuits::{FCircuit, WitnessToPublic}, + commitments::CommitmentDef, + transcripts::{TranscriptGadget, recording::RecordingTranscriptVar}, + utils::dummy::Dummy, }; use crate::compilers::cyclefold::FoldingSchemeCycleFoldExt; diff --git a/crates/ivc/src/compilers/cyclefold/evm_verifier.rs b/crates/ivc/src/compilers/cyclefold/evm_verifier.rs new file mode 100644 index 000000000..a67f5ffcf --- /dev/null +++ b/crates/ivc/src/compilers/cyclefold/evm_verifier.rs @@ -0,0 +1,63 @@ +//! Solidity codegen traits and templates for the on-chain decider verifier. + +use ark_bn254::Bn254; +use ark_std::{borrow::Borrow, marker::PhantomData}; +use askama::Template; +use sonobe_fs::{Error, FoldingSchemeVerifier}; +use sonobe_primitives::circuits::FCircuit; +use sonobe_snarks::cp::legogroth16::VerifierKey; + +pub struct DeciderFoldFragment { + pub challenge: String, + pub params: Vec, + pub body: String, + pub commitments: Vec, +} + +pub trait FoldingSchemeEVMExt: FoldingSchemeVerifier { + fn decider_fold_fragment() -> DeciderFoldFragment; + + #[allow(non_snake_case)] + fn verify_calldata( + Us: &[impl Borrow; M], + us: &[impl Borrow; N], + proof: &Self::Proof, + ) -> Result, Error>; +} + +pub struct DeciderStateFragment { + pub type_name: String, + pub type_def: String, + pub shape_check_body: String, + pub flatten_body: String, +} + +pub trait FCircuitEVMExt: FCircuit { + fn decider_state_fragment(reference: &Self::State) -> DeciderStateFragment; +} + +/// [`CycleFoldBasedIVCDeciderVerifierTemplate`] is rendered as the contract +/// `DeciderVerifier` via [`askama`]. +#[derive(Template)] +#[template(path = "cyclefold_based_ivc_decider.sol.askama")] +pub struct CycleFoldBasedIVCDeciderVerifierTemplate< + 'a, + FS: FoldingSchemeEVMExt<1, 1>, + FC: FCircuitEVMExt, +> { + _t: PhantomData, + vk: &'a VerifierKey, + reference_state: &'a FC::State, +} + +impl<'a, FS: FoldingSchemeEVMExt<1, 1>, FC: FCircuitEVMExt> + CycleFoldBasedIVCDeciderVerifierTemplate<'a, FS, FC> +{ + pub fn new(vk: &'a VerifierKey, reference_state: &'a FC::State) -> Self { + Self { + _t: PhantomData, + vk, + reference_state, + } + } +} diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 1e6826f26..8a28b9ff6 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -35,6 +35,8 @@ use sonobe_fs::{ GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, definitions::circuits::FoldingSchemeDeciderGadget, }; +#[cfg(feature = "evm")] +use sonobe_primitives::utils::evm::serialize::EVMSerialize; use sonobe_primitives::{ algebra::{ field::emulated::EmulatedFieldVar, @@ -42,7 +44,7 @@ use sonobe_primitives::{ }, arithmetizations::{Arith, ArithConfig}, circuits::{ - ArithExtractor, AssignmentsExtractor, FCircuit, + ArithExtractor, AssignmentsExtractor, FCircuit, WitnessToPublic, cache::{CommitmentKeyCache, CommittedCache, RandomnessCache, UsizeSet}, inputize::Inputize, }, @@ -50,9 +52,10 @@ use sonobe_primitives::{ relations::WitnessInstanceSampler, transcripts::{ Transcript, TranscriptGadget, - recording::RecordingTranscript, + recording::{RecordingTranscript, RecordingTranscriptVar}, replay::{ReplayTranscript, ReplayTranscriptVar}, - }, utils::dummy::Dummy, + }, + utils::dummy::Dummy, }; use sonobe_snarks::cp::CPSNARK; @@ -60,9 +63,13 @@ use crate::{ Error, IVCKeyGenerator, IVCPreprocessor, IVCProofCompressor, IVCProver, IVCTypes, IVCVerifier, compilers::cyclefold::circuits::{AugmentedCircuit, CycleFoldCircuit}, }; +#[cfg(feature = "evm")] +use crate::{IVCProofCompressorEVMExt, compilers::cyclefold::evm_verifier::FoldingSchemeEVMExt}; pub mod adapters; pub mod circuits; +#[cfg(feature = "evm")] +pub mod evm_verifier; /// [`FoldingSchemeCycleFoldExt`] is the extension trait that a folding scheme /// must implement to be used with the CycleFold compiler. @@ -519,10 +526,7 @@ impl< { type IVC = CycleFoldBasedIVC; - type ProverKey = ( - S::ProverKey, - ::VerifierKey, - ); + type ProverKey = (S::ProverKey, ::VerifierKey); type VerifierKey = ( S::VerifierKey, @@ -532,7 +536,13 @@ impl< FC::State, ); - type CompressedProof = (S::Proof, FS1::RU, FS1::IU, FS1::Proof<1, 1>); + type CompressedProof = ( + S::Proof, + FS1::RU, + FS1::IU, + FS1::Proof<1, 1>, + Vec<::Field>, + ); type Error = Error; @@ -607,7 +617,9 @@ impl< mut rng: impl RngCore, ) -> Result, Self::Error> { let hash = T::new_with_pp_hash(ivc_vk.2.0.clone(), ivc_vk.2.1); - let mut transcript = hash.separate_domain("transcript".as_ref()); + // Record the transcript so the cached challenges can be returned in the + // compressed proof. + let mut transcript = RecordingTranscript::new(hash.separate_domain("transcript".as_ref())); let (WW, _, folding_proof) = FS1::prove( ivc_vk.0.to_pk(), @@ -655,7 +667,13 @@ impl< let compressed_proof = S::prove(pk, &x[1..], w, &o, &mut rng)?; - Ok((compressed_proof, U.clone(), u.clone(), folding_proof)) + Ok(( + compressed_proof, + U.clone(), + u.clone(), + folding_proof, + transcript.cached_challenges, + )) } fn verify::Field>>( @@ -663,7 +681,7 @@ impl< i: usize, initial_state: &FC::State, current_state: &FC::State, - (compressed_proof, U, u, folding_proof): &Self::CompressedProof, + (compressed_proof, U, u, folding_proof, challenges): &Self::CompressedProof, ) -> Result<(), Self::Error> { if !FC::same_state_shape(reference_state, initial_state) || !FC::same_state_shape(reference_state, current_state) @@ -687,6 +705,7 @@ impl< vec![::Field::from(i as u64)], FC::StateVar::inputize(initial_state), FC::StateVar::inputize(current_state), + challenges.clone(), commitments.iter().flat_map(<::CM as CommitmentDefGadget>::CommitmentVar::inputize).collect::>() ] .concat(); @@ -698,6 +717,67 @@ impl< } } +#[cfg(feature = "evm")] +impl< + FS1: FoldingSchemeCycleFoldExt< + 1, + 1, + Arith: From::Commitment>>>, + Gadget: FoldingSchemePartialVerifierGadget<1, 1, VerifierKey = ()> + + FoldingSchemeDeciderGadget, + CM: CommitmentDef< + Scalar: EVMSerialize, + Commitment: SonobeCurve::Scalar>, + >, + > + FoldingSchemeEVMExt<1, 1>, + FS2: GroupBasedFoldingSchemeSecondary< + 1, + 1, + Arith: From::Commitment>>>, + Gadget: FoldingSchemeFullVerifierGadget<1, 1, VerifierKey = ()> + + FoldingSchemeDeciderGadget, + CM: CommitmentDef< + Commitment: SonobeCurve::Scalar>, + >, + >, + T: Transcript< + CF1<::Commitment>, + Config: CanonicalSerialize, + Gadget: TranscriptGadget< + CF1<::Commitment>, + Config = T::Config, + >, + >, + S: CPSNARK< + Field = ::Scalar, + Relation = (FS1::Arith, UsizeSet), + CommitmentKey = ::Key, + Commitment = <::Commitment as CurveGroup>::Affine, + CommitmentOpening = ::Scalar, + Proof: EVMSerialize, + Error = SynthesisError, + >, +> IVCProofCompressorEVMExt for CycleFoldBasedIVCDecider +{ + fn verify_calldata::Field>>( + (_vk, _folding_vk, _hash_config, _pp_hash, _reference_state): &Self::VerifierKey, + i: usize, + initial_state: &FC::State, + current_state: &FC::State, + (compressed_proof, U, u, folding_proof, challenges): &Self::CompressedProof, + ) -> Result, Self::Error> { + Ok(( + vec![::Field::from(i as u64)], + FC::StateVar::inputize(initial_state), + FC::StateVar::inputize(current_state), + &challenges, + FS1::verify_calldata(&[U], &[u], folding_proof)?, + compressed_proof, + ) + .to_calldata()) + } +} + pub struct CycleFoldBasedIVCDeciderCircuit< 'a, FS1: FoldingSchemeDef, @@ -770,10 +850,13 @@ impl< let hash = T::Gadget::new_with_pp_hash(hash_config.clone(), &pp_hash)?; let mut sponge = hash.separate_domain("sponge".as_ref())?; - let mut transcript = hash.separate_domain("transcript".as_ref())?; + let mut transcript = + RecordingTranscriptVar::new(hash.separate_domain("transcript".as_ref())?); let UU = FS1::Gadget::verify_hinted(&(), &mut transcript, [&U], [&u], &proof)?; + transcript.cached_challenges.mark_as_public()?; + FS1::Gadget::decide_running(&dk1, &WW, &UU)?; FS2::Gadget::decide_running(&dk2, &cf_W, &cf_U)?; diff --git a/crates/ivc/src/lib.rs b/crates/ivc/src/lib.rs index 0f98be97d..fd144be52 100644 --- a/crates/ivc/src/lib.rs +++ b/crates/ivc/src/lib.rs @@ -12,7 +12,9 @@ use ark_relations::gr1cs::SynthesisError; use ark_serialize::SerializationError; use ark_std::{error::Error as ErrorTrait, rand::RngCore}; use sonobe_fs::Error as FoldingError; -use sonobe_primitives::{arithmetizations::Error as ArithError, circuits::FCircuit, utils::dummy::Dummy}; +use sonobe_primitives::{ + arithmetizations::Error as ArithError, circuits::FCircuit, utils::dummy::Dummy, +}; use thiserror::Error; pub mod compilers; @@ -468,11 +470,50 @@ pub trait IVCProofCompressor { ) -> Result<(), Self::Error>; } +/// [`IVCProofCompressorEVMExt`] extends [`IVCProofCompressor`] with the ability +/// to produce the calldata for the decider's EVM verifier contract. +#[cfg(feature = "evm")] +pub trait IVCProofCompressorEVMExt: IVCProofCompressor { + /// [`IVCProofCompressorEVMExt::verify_calldata`] builds the calldata for + /// the EVM verifier from the same inputs as [`IVCProofCompressor::verify`]. + fn verify_calldata::Field>>( + vk: &Self::VerifierKey, + i: usize, + initial_state: &FC::State, + current_state: &FC::State, + compressed_proof: &Self::CompressedProof, + ) -> Result, Self::Error>; +} + #[cfg(test)] mod tests { use ark_std::{error::Error, rand::Rng}; + #[cfg(feature = "evm")] + use sonobe_primitives::{ + algebra::field::SonobeField, + circuits::test_utils::CircuitForTest, + utils::evm::{compiler::SolidityCompiler, harness::TestEVM}, + }; use super::*; + #[cfg(feature = "evm")] + use crate::compilers::cyclefold::evm_verifier::{DeciderStateFragment, FCircuitEVMExt}; + + #[cfg(feature = "evm")] + impl FCircuitEVMExt for CircuitForTest { + fn decider_state_fragment(_: &Self::State) -> DeciderStateFragment { + DeciderStateFragment { + // `[F; 1]` is represented onchain as a fixed-size `uint256[1]` + type_name: "uint256[1]".to_string(), + // No custom struct + type_def: String::new(), + // The array length already pins the shape + shape_check_body: String::new(), + // Already flat + flatten_body: "return z;".to_string(), + } + } + } fn test_manual_state_management>( pk: &I::ProverKey, @@ -573,7 +614,7 @@ mod tests { Ok(()) } - pub fn test_ivc_decider< + pub fn test_decider< D: IVCProofCompressor, F: FCircuit::Field, ExternalInputs: Clone>, >( @@ -582,6 +623,8 @@ mod tests { external_inputs_vec: Vec, mut rng: impl Rng, ) -> Result<(), Box> { + let n = external_inputs_vec.len(); + let pp = D::IVC::preprocess(config, &mut rng)?; let (pk, vk) = D::IVC::generate_keys(pp, &step_circuit)?; @@ -593,7 +636,7 @@ mod tests { &vk, &step_circuit, initial_state.clone(), - external_inputs_vec.clone(), + external_inputs_vec, &mut rng, )?; @@ -601,21 +644,74 @@ mod tests { let proof = D::prove( &pk, - external_inputs_vec.len(), + n, &initial_state, ¤t_state, ¤t_proof, &mut rng, )?; - D::verify( + D::verify(&vk, n, &initial_state, ¤t_state, &proof)?; + + Ok(()) + } + + #[cfg(feature = "evm")] + pub fn test_decider_evm< + D: IVCProofCompressorEVMExt, + F: FCircuit::Field, ExternalInputs: Clone>, + >( + config: ::Config, + step_circuit: F, + external_inputs_vec: Vec, + sources_generator: impl Fn(&D::VerifierKey) -> Result, Box>, + mut rng: impl Rng, + ) -> Result<(), Box> { + let n = external_inputs_vec.len(); + + let pp = D::IVC::preprocess(config, &mut rng)?; + + let (pk, vk) = D::IVC::generate_keys(pp, &step_circuit)?; + + let initial_state = step_circuit.dummy_state(); + + let (current_state, current_proof) = test_auto_state_management::( + &pk, &vk, - external_inputs_vec.len(), + &step_circuit, + initial_state.clone(), + external_inputs_vec, + &mut rng, + )?; + + let (pk, vk) = D::preprocess_and_generate_keys(&step_circuit, vk, &mut rng)?; + + let proof = D::prove( + &pk, + n, &initial_state, ¤t_state, - &proof, + ¤t_proof, + &mut rng, )?; + let solc = SolidityCompiler::default(); + assert!(solc.available()); + + let (bytecode, selectors) = solc.compile(sources_generator(&vk)?, "DeciderVerifier")?; + let mut evm = TestEVM::default(); + let addr = evm.deploy(&bytecode, ())?.unwrap(); + let sig = *selectors.get("verifyDeciderProof").unwrap(); + + assert!( + evm.view( + addr, + sig, + &D::verify_calldata::(&vk, n, &initial_state, ¤t_state, &proof)? + )? + .is_success() + ); + Ok(()) } } diff --git a/crates/ivc/templates/cyclefold_based_ivc_decider.sol.askama b/crates/ivc/templates/cyclefold_based_ivc_decider.sol.askama new file mode 100644 index 000000000..8736e7d8d --- /dev/null +++ b/crates/ivc/templates/cyclefold_based_ivc_decider.sol.askama @@ -0,0 +1,107 @@ +{%- let numCommitments = vk.link_vk.c.len() - 1 -%} +{%- let numPublicInputs = vk.cc_vk.gamma_abc_g1_pub.len() - 1 -%} +{%- let stateLen = (numPublicInputs - 1 - numCommitments * 16 - 1) / 2 %} +{%- let fold = FS::decider_fold_fragment() %} +{%- let state = FC::decider_state_fragment(&reference_state) %} +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.35; + +import "LegoGroth16Verifier.sol"; + +{{ state.type_def|safe }} + +contract DeciderVerifier is LegoGroth16Verifier { + error PointRLCFailed(); + error BaseCaseMismatch(); + error StateShapeMismatch(); + + function verifyDeciderProof( + uint256 i, + {{ state.type_name }} calldata z_0, + {{ state.type_name }} calldata z_i, + uint256 {{ fold.challenge }}, + {%- for p in fold.params %} + {{ p }}, + {%- endfor %} + uint256[12] calldata proof + ) public view { + _checkStateShape(z_0); + _checkStateShape(z_i); + uint256[{{ stateLen }}] memory z_0_flattened = _flattenState(z_0); + uint256[{{ stateLen }}] memory z_i_flattened = _flattenState(z_i); + + if (i == 0) { + for (uint256 k = 0; k < {{ stateLen }}; k++) { + if (z_0_flattened[k] != z_i_flattened[k]) { + revert BaseCaseMismatch(); + } + } + return; + } + + // Scheme-emitted point RLC, which computes the folded commitments from + // the unfolded ones and the challenge. + {{ fold.body|indent(8)|safe }} + + uint256[{{ numCommitments * 2 }}] memory c = [ + {%- for cm in fold.commitments %} + {{ cm }}[0], {{ cm }}[1]{% if !loop.last %},{% endif %} + {%- endfor %} + ]; + + // x = [i, z_0.., z_i.., challenge, inputize(cm)]. + uint256[{{ numPublicInputs }}] memory x; + x[0] = i; + for (uint256 k = 0; k < {{ stateLen }}; k++) { + x[1 + k] = z_0_flattened[k]; + x[1 + {{ stateLen }} + k] = z_i_flattened[k]; + } + x[{{ 1 + 2 * stateLen }}] = {{ fold.challenge }}; + for (uint256 i = 0; i < {{ numCommitments * 2 }}; i++) { + for (uint256 k = 0; k < 8; k++) { + x[{{ 1 + 2 * stateLen + 1 }} + i * 8 + k] = (c[i] >> (32 * k)) & 0xFFFFFFFF; + } + } + + this.verifyProof(x, c, proof); + } + + function _checkStateShape({{ state.type_name }} calldata z) internal pure { + {{ state.shape_check_body|indent(8)|safe }} + } + + function _flattenState({{ state.type_name }} calldata z) internal pure returns (uint256[{{ stateLen }}] memory) { + {{ state.flatten_body|indent(8)|safe }} + } + + function _ecMul(uint256[2] calldata p, uint256 s) + internal + view + returns (uint256[2] memory r) + { + uint256[3] memory input = [p[0], p[1], s]; + bool ok; + assembly ("memory-safe") { + ok := staticcall(gas(), 0x07, input, 0x60, r, 0x40) + } + if (!ok) { + revert PointRLCFailed(); + } + } + + function _ecAdd(uint256[2] calldata a, uint256[2] memory b) + internal + view + returns (uint256[2] memory r) + { + uint256[4] memory input = [a[0], a[1], b[0], b[1]]; + bool ok; + assembly ("memory-safe") { + ok := staticcall(gas(), 0x06, input, 0x80, r, 0x40) + } + if (!ok) { + revert PointRLCFailed(); + } + } +} diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 357922139..f4c6e8dd1 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -21,6 +21,9 @@ num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } num-traits = { workspace = true } rayon = { workspace = true } +revm = { workspace = true, optional = true } +serde = { workspace = true, optional = true, features = ["derive"] } +serde_json = { workspace = true, optional = true } sha3 = { workspace = true } thiserror = { workspace = true } @@ -36,6 +39,7 @@ wasm-bindgen-test = { workspace = true } [features] default = [] +evm = ["dep:revm", "dep:serde", "dep:serde_json", "ark-std/std", "hashbrown/serde"] parallel = [ "ark-crypto-primitives/parallel", "ark-ec/parallel", diff --git a/crates/primitives/src/algebra/field/mod.rs b/crates/primitives/src/algebra/field/mod.rs index 392059d3b..cb148e60a 100644 --- a/crates/primitives/src/algebra/field/mod.rs +++ b/crates/primitives/src/algebra/field/mod.rs @@ -15,11 +15,12 @@ use ark_std::{ ops::{Add, Mul, Sub}, }; +#[cfg(feature = "evm")] +use crate::utils::evm::serialize::EVMSerialize; use crate::{ algebra::{Val, field::emulated::EmulatedFieldVar}, circuits::{WitnessToPublic, inputize::Inputize}, transcripts::{Absorbable, AbsorbableVar}, - utils::evm::EVMSerialize, }; pub mod emulated; @@ -127,12 +128,14 @@ impl WitnessToPublic for EmulatedFieldVa } } +#[cfg(feature = "evm")] impl, const N: usize> EVMSerialize for Fp { fn to_calldata(&self) -> Vec { self.into_bigint().to_bytes_be() } } +#[cfg(feature = "evm")] impl> EVMSerialize for Fp2

{ fn to_calldata(&self) -> Vec { [self.c1.to_calldata(), self.c0.to_calldata()].concat() diff --git a/crates/primitives/src/algebra/group/mod.rs b/crates/primitives/src/algebra/group/mod.rs index 3a991ebd4..2f17e8ee2 100644 --- a/crates/primitives/src/algebra/group/mod.rs +++ b/crates/primitives/src/algebra/group/mod.rs @@ -13,6 +13,8 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::SynthesisError; +#[cfg(feature = "evm")] +use crate::utils::evm::serialize::EVMSerialize; use crate::{ algebra::{ Val, @@ -21,7 +23,7 @@ use crate::{ }, circuits::{WitnessToPublic, inputize::Inputize}, transcripts::{Absorbable, AbsorbableVar}, - utils::{dummy::Dummy, evm::EVMSerialize}, + utils::dummy::Dummy, }; pub mod emulated; @@ -37,7 +39,9 @@ pub trait SonobeCurve: CurveGroup + Absorbable + Val< - Var: CurveVar + AbsorbableVar + WitnessToPublic + Var: CurveVar + + AbsorbableVar + + WitnessToPublic + Inputize, EmulatedVar = EmulatedAffineVar, > @@ -126,6 +130,7 @@ impl WitnessToPublic for EmulatedAffineV } } +#[cfg(feature = "evm")] impl> EVMSerialize for Affine

{ fn to_calldata(&self) -> Vec { // the encoding of the additive identity is [0, 0] on the EVM @@ -135,6 +140,7 @@ impl> EVMSerialize for Affine

{ } } +#[cfg(feature = "evm")] impl> EVMSerialize for Projective

{ fn to_calldata(&self) -> Vec { self.into_affine().to_calldata() diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index f8401a9bc..ad94466fd 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -8,6 +8,7 @@ use ark_relations::gr1cs::{ }; use ark_std::{ fmt::Debug, + mem::take, ops::{Deref, Index, IndexMut}, }; @@ -247,7 +248,7 @@ impl &mut self, circuit: impl FnOnce(ConstraintSystemRef) -> Result, ) -> Result { - let cs = std::mem::take(&mut self.0); + let cs = take(&mut self.0); let result = { let cs_ref = ConstraintSystemRef::new(cs); let result = circuit(cs_ref.clone())?; @@ -255,7 +256,6 @@ impl result }; if ARITH_ENABLED { - println!("{}", self.0.num_constraints()); self.0.finalize(); } Ok(result) diff --git a/crates/primitives/src/commitments/mod.rs b/crates/primitives/src/commitments/mod.rs index a3f45368e..79709017b 100644 --- a/crates/primitives/src/commitments/mod.rs +++ b/crates/primitives/src/commitments/mod.rs @@ -20,7 +20,9 @@ use crate::{ field::{SonobeField, TwoStageFieldVar, emulated::EmulatedFieldVar}, group::{CF1, CF2, SonobeCurve, emulated::EmulatedAffineVar}, ops::bits::FromBitsGadget, - }, circuits::inputize::Inputize, transcripts::{Absorbable, AbsorbableVar} + }, + circuits::inputize::Inputize, + transcripts::{Absorbable, AbsorbableVar}, }; pub mod pedersen; diff --git a/crates/primitives/src/transcripts/recording/mod.rs b/crates/primitives/src/transcripts/recording/mod.rs index bf2de8899..57266ffe9 100644 --- a/crates/primitives/src/transcripts/recording/mod.rs +++ b/crates/primitives/src/transcripts/recording/mod.rs @@ -12,7 +12,9 @@ use super::{AbsorbableVar, Transcript, TranscriptGadget}; #[derive(Clone)] pub struct RecordingTranscript> { inner: T, - pub(super) cached_challenges: Vec, + /// [`RecordingTranscript::cached_challenges`] contains the challenge field + /// elements recorded so far. + pub cached_challenges: Vec, } impl> Transcript for RecordingTranscript { @@ -42,7 +44,9 @@ impl> Transcript for RecordingTranscript> { inner: T, - pub(super) cached_challenges: Vec>, + /// [`RecordingTranscriptVar::cached_challenges`] contains the challenge + /// field element variables recorded so far. + pub cached_challenges: Vec>, } impl> TranscriptGadget for RecordingTranscriptVar { diff --git a/crates/primitives/src/utils/evm.rs b/crates/primitives/src/utils/evm.rs deleted file mode 100644 index 981579a57..000000000 --- a/crates/primitives/src/utils/evm.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub trait EVMSerialize { - fn to_calldata(&self) -> Vec; -} - -impl EVMSerialize for [T] { - fn to_calldata(&self) -> Vec { - self.iter().flat_map(EVMSerialize::to_calldata).collect() - } -} - -impl EVMSerialize for u8 { - fn to_calldata(&self) -> Vec { - vec![*self] - } -} diff --git a/crates/primitives/src/utils/evm/compiler.rs b/crates/primitives/src/utils/evm/compiler.rs new file mode 100644 index 000000000..cff618394 --- /dev/null +++ b/crates/primitives/src/utils/evm/compiler.rs @@ -0,0 +1,167 @@ +use ark_std::{ + io::{Error as IOError, Write}, + process::{Command, Stdio}, +}; +use hashbrown::HashMap; +use revm::primitives::{ + Bytes, + hex::{FromHexError, decode}, +}; +use serde::Deserialize; +use serde_json::{Error as JSONError, from_slice, json, to_vec}; +use thiserror::Error; + +/// [`Error`] enumerates possible errors during solidity compilation. +#[derive(Debug, Error)] +pub enum Error { + /// Failed to run the `solc` process or read/write its pipes. + #[error("solc i/o error: {0}")] + Io(#[from] IOError), + /// Failed to (de)serialize the `solc` Standard JSON. + #[error("solc json error: {0}")] + Json(#[from] JSONError), + /// Failed to decode the `solc` bytecode hex. + #[error("invalid bytecode hex: {0}")] + Hex(#[from] FromHexError), + /// `solc` reported a compilation failure, or produced no bytecode. + #[error("solc compilation failed: {0}")] + Solc(String), + /// The compiled contract has no function with the requested name. + #[error("no function named `{0}` in the compiled contract")] + UnknownFunction(String), +} + +#[derive(Deserialize)] +struct Output { + #[serde(default)] + errors: Vec, + #[serde(default)] + contracts: HashMap>, +} + +#[derive(Deserialize)] +struct OutputError { + severity: String, + #[serde(rename = "formattedMessage")] + formatted_message: String, +} + +#[derive(Deserialize)] +struct OutputContract { + evm: OutputEvm, +} + +#[derive(Deserialize)] +struct OutputEvm { + bytecode: OutputBytecode, + #[serde(rename = "methodIdentifiers")] + method_identifiers: HashMap, +} + +#[derive(Deserialize)] +struct OutputBytecode { + object: String, +} + +/// [`SolidityCompiler`] contains a handle to a Solidity compiler for compiling +/// rendered verifier contracts in tests. +pub struct SolidityCompiler { + solc: String, +} + +impl Default for SolidityCompiler { + fn default() -> Self { + Self::new("solc") + } +} + +impl SolidityCompiler { + /// [`SolidityCompiler::new`] creates a compiler that uses a specific `solc` + /// binary (a path, or a command on `PATH`). + pub fn new(solc: impl Into) -> Self { + Self { solc: solc.into() } + } + + /// [`SolidityCompiler::available`] tries to invoke the compiler and returns + /// whether the invocation is successful. + pub fn available(&self) -> bool { + Command::new(&self.solc) + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) + } + + /// [`SolidityCompiler::compile`] compiles `sources`, which is a list of + /// file name and solidity code pairs, retrieves the compilation results for + /// `contract`, and returns its bytecode and function selectors. + pub fn compile( + &self, + sources: Vec<(impl Into, impl Into)>, + contract: impl Into, + ) -> Result<(Bytes, HashMap), Error> { + let contract = &contract.into(); + + let input = json!({ + "language": "Solidity", + "sources": sources + .into_iter() + .map(|(name, src)| (name.into(), json!({ "content": src.into() }))) + .collect::>(), + "settings": { + "optimizer": { "enabled": true, "runs": 200 }, + "outputSelection": { + "*": { "*": ["evm.bytecode.object", "evm.methodIdentifiers"] }, + }, + }, + }); + + let mut child = Command::new(&self.solc) + .arg("--standard-json") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + child + .stdin + .take() + .ok_or_else(|| Error::Solc("failed to open solc stdin".into()))? + .write_all(&to_vec(&input)?)?; + let out = child.wait_with_output()?; + if !out.status.success() { + return Err(Error::Solc( + String::from_utf8_lossy(&out.stderr).into_owned(), + )); + } + + let output: Output = from_slice(&out.stdout)?; + if let Some(e) = output.errors.iter().find(|e| e.severity == "error") { + return Err(Error::Solc(e.formatted_message.clone())); + } + + let evm = output + .contracts + .into_values() + .find_map(|mut cs| cs.remove(contract)) + .map(|c| c.evm) + .ok_or_else(|| Error::Solc(format!("no output for `{contract}`")))?; + + Ok(( + decode(evm.bytecode.object)?.into(), + evm.method_identifiers + .into_iter() + .map(|(sig, sel)| { + // `sig` ("name(types)") -> name + let name = sig.split('(').next().unwrap_or(&sig).to_string(); + // `sel` (hex string) -> bytes + let bytes: [u8; 4] = decode(&sel)? + .try_into() + .map_err(|_| Error::Solc(format!("bad selector for `{sig}`")))?; + Ok((name, bytes)) + }) + .collect::>()?, + )) + } +} diff --git a/crates/primitives/src/utils/evm/harness.rs b/crates/primitives/src/utils/evm/harness.rs new file mode 100644 index 000000000..a393a0ad7 --- /dev/null +++ b/crates/primitives/src/utils/evm/harness.rs @@ -0,0 +1,86 @@ +use ark_std::convert::Infallible; +use revm::{ + ExecuteCommitEvm, ExecuteEvm, MainBuilder, MainContext, MainnetEvm, + context::{Context, TxEnv, result::EVMError}, + context_interface::result::ExecutionResult, + database::{CacheDB, EmptyDB}, + handler::MainnetContext, + primitives::{Address, Bytes}, +}; + +use super::serialize::EVMSerialize; + +/// [`TestEVM`] is a minimal in-memory `revm` harness for deploying and invoking +/// rendered verifier contracts in tests. +pub struct TestEVM { + evm: MainnetEvm>>, +} + +impl Default for TestEVM { + fn default() -> Self { + Self { + evm: Context::mainnet() + .with_db(CacheDB::default()) + .modify_cfg_chained(|cfg| cfg.disable_nonce_check = true) + .build_mainnet(), + } + } +} + +impl TestEVM { + /// [`TestEVM::deploy`] deploys a smart contract's `bytecode`, calls its + /// constructor with `args`, and returns the deployed contract's address. + pub fn deploy( + &mut self, + bytecode: &Bytes, + args: impl EVMSerialize, + ) -> Result, EVMError> { + self.evm + .transact_commit( + TxEnv::builder() + .create() + .data([&bytecode[..], &args.to_calldata()].concat().into()) + .build_fill(), + ) + .map(|i| i.created_address()) + } + + /// [`TestEVM::send`] sends to the contract deployed at `to` a transaction, + /// which calls the function specified by `selector` with `args`. + /// + /// This method mutates the onchain state and returns the execution result. + pub fn send( + &mut self, + to: Address, + selector: [u8; 4], + args: impl EVMSerialize, + ) -> Result> { + self.evm.transact_commit( + TxEnv::builder() + .call(to) + .data([&selector[..], &args.to_calldata()].concat().into()) + .build_fill(), + ) + } + + /// [`TestEVM::view`] simulates the execution of the contract deployed at + /// `to` on a call to the function specified by `selector` with `args`. + /// + /// This method keeps the onchain state unchanged and returns the execution + /// result. + pub fn view( + &mut self, + to: Address, + selector: [u8; 4], + args: impl EVMSerialize, + ) -> Result> { + self.evm + .transact( + TxEnv::builder() + .call(to) + .data([&selector[..], &args.to_calldata()].concat().into()) + .build_fill(), + ) + .map(|i| i.result) + } +} diff --git a/crates/primitives/src/utils/evm/mod.rs b/crates/primitives/src/utils/evm/mod.rs new file mode 100644 index 000000000..76c1a63e5 --- /dev/null +++ b/crates/primitives/src/utils/evm/mod.rs @@ -0,0 +1,5 @@ +//! EVM serialization and harness utilities. + +pub mod compiler; +pub mod serialize; +pub mod harness; diff --git a/crates/primitives/src/utils/evm/serialize.rs b/crates/primitives/src/utils/evm/serialize.rs new file mode 100644 index 000000000..1df129e2a --- /dev/null +++ b/crates/primitives/src/utils/evm/serialize.rs @@ -0,0 +1,68 @@ +/// [`EVMSerialize`] encodes a value as EVM calldata (the ABI byte encoding the +/// on-chain verifier expects). +pub trait EVMSerialize { + fn to_calldata(&self) -> Vec; +} + +impl EVMSerialize for &T { + fn to_calldata(&self) -> Vec { + T::to_calldata(self) + } +} + +impl EVMSerialize for [T] { + fn to_calldata(&self) -> Vec { + self.iter().flat_map(EVMSerialize::to_calldata).collect() + } +} + +impl EVMSerialize for [T; N] { + fn to_calldata(&self) -> Vec { + self[..].to_calldata() + } +} + +impl EVMSerialize for Vec { + fn to_calldata(&self) -> Vec { + self[..].to_calldata() + } +} + +impl EVMSerialize for u8 { + fn to_calldata(&self) -> Vec { + vec![*self] + } +} + +impl EVMSerialize for () { + fn to_calldata(&self) -> Vec { + Vec::new() + } +} + +macro_rules! impl_evm_serialize_tuple { + ($($T:ident),+) => { + impl<$($T: EVMSerialize),+> EVMSerialize for ($($T,)+) { + fn to_calldata(&self) -> Vec { + #[allow(non_snake_case)] + let ($($T,)+) = self; + let mut out = Vec::new(); + $(out.extend($T.to_calldata());)+ + out + } + } + }; +} + +impl_evm_serialize_tuple!(A); +impl_evm_serialize_tuple!(A, B); +impl_evm_serialize_tuple!(A, B, C); +impl_evm_serialize_tuple!(A, B, C, D); +impl_evm_serialize_tuple!(A, B, C, D, E); +impl_evm_serialize_tuple!(A, B, C, D, E, F); +impl_evm_serialize_tuple!(A, B, C, D, E, F, G); +impl_evm_serialize_tuple!(A, B, C, D, E, F, G, H); +impl_evm_serialize_tuple!(A, B, C, D, E, F, G, H, I); +impl_evm_serialize_tuple!(A, B, C, D, E, F, G, H, I, J); +impl_evm_serialize_tuple!(A, B, C, D, E, F, G, H, I, J, K); +impl_evm_serialize_tuple!(A, B, C, D, E, F, G, H, I, J, K, L); diff --git a/crates/primitives/src/utils/mod.rs b/crates/primitives/src/utils/mod.rs index f926713da..9c393840e 100644 --- a/crates/primitives/src/utils/mod.rs +++ b/crates/primitives/src/utils/mod.rs @@ -1,5 +1,6 @@ //! Miscellaneous utilities shared across the primitives crate. -pub mod null; pub mod dummy; -pub mod evm; \ No newline at end of file +#[cfg(feature = "evm")] +pub mod evm; +pub mod null; diff --git a/crates/snarks/Cargo.toml b/crates/snarks/Cargo.toml index 502fd8484..f84e13746 100644 --- a/crates/snarks/Cargo.toml +++ b/crates/snarks/Cargo.toml @@ -6,6 +6,7 @@ license.workspace = true repository.workspace = true [dependencies] +ark-bn254 = { workspace = true, features = ["curve", "r1cs"], optional = true } ark-ec = { workspace = true } ark-ff = { workspace = true, features = ["asm"] } ark-groth16 = { workspace = true } @@ -14,6 +15,7 @@ ark-relations = { workspace = true } ark-serialize = { workspace = true } ark-snark = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } +askama = { workspace = true, optional = true } hashbrown = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } @@ -33,6 +35,7 @@ wasm-bindgen-test = { workspace = true } [features] default = [] +evm = ["dep:askama", "sonobe-primitives/evm", "dep:ark-bn254"] parallel = [ "sonobe-primitives/parallel", "ark-groth16/parallel", diff --git a/crates/snarks/src/cp/legogroth16/evm_verifier.rs b/crates/snarks/src/cp/legogroth16/evm_verifier.rs new file mode 100644 index 000000000..c46d29783 --- /dev/null +++ b/crates/snarks/src/cp/legogroth16/evm_verifier.rs @@ -0,0 +1,49 @@ +use ark_bn254::Bn254; +use ark_ec::AffineRepr; +use askama::Template; + +use crate::cp::legogroth16::VerifierKey; + +#[derive(Template)] +#[template(path = "legogroth16.sol.askama")] +pub struct LegoGroth16VerifierTemplate<'a> { + pub vk: &'a VerifierKey, +} + +#[cfg(test)] +mod tests { + use ark_std::{error::Error, rand::thread_rng}; + use sonobe_primitives::utils::evm::{compiler::SolidityCompiler, harness::TestEVM}; + + use super::*; + use crate::cp::legogroth16::tests::{toy_keygen, toy_prove}; + + #[test] + fn test_evm_verifier() -> Result<(), Box> { + let solc = SolidityCompiler::default(); + assert!(solc.available()); + + let mut rng = thread_rng(); + let ck_sizes = [1, 1, 1]; + let (pk, vk, generators) = toy_keygen::(&ck_sizes, &mut rng); + let (g, cm, proof) = toy_prove(&pk, &generators, &ck_sizes, &mut rng); + + let source = LegoGroth16VerifierTemplate { vk: &vk }.render()?; + let (bytecode, selectors) = solc.compile( + vec![("LegoGroth16Verifier.sol", source)], + "LegoGroth16Verifier", + )?; + let mut evm = TestEVM::default(); + let addr = evm.deploy(&bytecode, ())?.unwrap(); + + assert!( + evm.view( + addr, + *selectors.get("verifyProof").unwrap(), + ([g], cm, proof) + )? + .is_success() + ); + Ok(()) + } +} diff --git a/crates/snarks/src/cp/groth16.rs b/crates/snarks/src/cp/legogroth16/mod.rs similarity index 75% rename from crates/snarks/src/cp/groth16.rs rename to crates/snarks/src/cp/legogroth16/mod.rs index deec64d11..698bf627e 100644 --- a/crates/snarks/src/cp/groth16.rs +++ b/crates/snarks/src/cp/legogroth16/mod.rs @@ -19,12 +19,13 @@ use hashbrown::HashSet; use itertools::{Either, Itertools}; #[cfg(feature = "parallel")] use rayon::{iter::Either, prelude::*}; +#[cfg(feature = "evm")] +use sonobe_primitives::utils::evm::serialize::EVMSerialize; use sonobe_primitives::{ algebra::{field::SonobeField, group::SonobeCurve}, arithmetizations::{Arith, ccs::CCS, r1cs::R1CS}, circuits::cache::{IdentityHasher, UsizeSet}, commitments::pedersen::PedersenKey, - utils::evm::EVMSerialize, }; use thiserror::Error; @@ -35,6 +36,9 @@ use crate::{ }, }; +#[cfg(feature = "evm")] +pub mod evm_verifier; + pub struct CCGroth16ProverKey { /// The element `beta * G` in `E::G1`. pub alpha_g1: E::G1Affine, @@ -63,6 +67,15 @@ pub struct CCGroth16ProverKey { } pub struct CCGroth16VerifierKey { + #[cfg(feature = "evm")] + pub alpha_g1: E::G1Affine, + #[cfg(feature = "evm")] + pub beta_g2_neg: E::G2Affine, + #[cfg(feature = "evm")] + pub gamma_g2_neg: E::G2Affine, + #[cfg(feature = "evm")] + pub delta_g2_neg: E::G2Affine, + /// The element `e(alpha * G, beta * H)` in `E::GT`. pub alpha_g1_beta_g2: PairingOutput, /// The element `- gamma * H` in `E::G2`, prepared for use in pairings. @@ -95,6 +108,7 @@ pub struct Proof { pub link_pi: E::G1Affine, } +#[cfg(feature = "evm")] impl> EVMSerialize for Proof { fn to_calldata(&self) -> Vec { [ @@ -338,6 +352,7 @@ impl { - a: F, - b: F, - c: F, - d: F, - e: F, - f: F, + /// A toy circuit enforcing `(a + b + c) * (d + e + f) = g`. + pub(crate) struct ToyCircuit { + pub a: F, + pub b: F, + pub c: F, + pub d: F, + pub e: F, + pub f: F, } - impl ConstraintSynthesizer for MySillyCircuit { + impl ConstraintSynthesizer for ToyCircuit { fn generate_constraints( self, cs: ConstraintSystemRef, @@ -569,20 +593,26 @@ mod tests { } } - fn test_prove_and_verify< + pub(crate) fn toy_keygen< E: Pairing, >( - n_iters: usize, - ) { - let mut rng = thread_rng(); - - let g = vec![E::G1Affine::rand(&mut rng)]; - let h = E::G1Affine::rand(&mut rng); - let generators = [&g[..], &[h]].concat(); - let ck = PedersenKey { g, h }; + ck_sizes: &[usize], + mut rng: impl RngCore, + ) -> (ProverKey, VerifierKey, Vec>) { + let cks: Vec<_> = ck_sizes + .iter() + .map(|&n| { + let g: Vec<_> = (0..n).map(|_| E::G1Affine::rand(&mut rng)).collect(); + PedersenKey { + g, + h: E::G1Affine::rand(&mut rng), + } + }) + .collect(); + let generators = cks.iter().map(|ck| [&ck.g[..], &[ck.h]].concat()).collect(); let mut cs = ArithExtractor::new(); - cs.execute_synthesizer(MySillyCircuit { + cs.execute_synthesizer(ToyCircuit:: { a: Default::default(), b: Default::default(), c: Default::default(), @@ -591,200 +621,74 @@ mod tests { f: Default::default(), }) .unwrap(); - let (pk, vk) = LegoGroth16::::generate_keys( (cs.arith().unwrap(), UsizeSet::from_iter(vec![0, 3, 5])), - &[&ck, &ck, &ck], - &mut rng, + &cks, + rng, ) .unwrap(); - - for _ in 0..n_iters { - let a = E::ScalarField::rand(&mut rng); - let b = E::ScalarField::rand(&mut rng); - let c = E::ScalarField::rand(&mut rng); - let d = E::ScalarField::rand(&mut rng); - let e = E::ScalarField::rand(&mut rng); - let f = E::ScalarField::rand(&mut rng); - - let mut cs = AssignmentsExtractor::new(); - cs.execute_synthesizer(MySillyCircuit { a, b, c, d, e, f }) - .unwrap(); - let assignments = cs.assignments().unwrap(); - - let o = [ - E::ScalarField::rand(&mut rng), - E::ScalarField::rand(&mut rng), - E::ScalarField::rand(&mut rng), - ]; - - let cm = vec![ - E::G1::msm_unchecked(&generators, &[d, o[0]]).into_affine(), - E::G1::msm_unchecked(&generators, &[e, o[1]]).into_affine(), - E::G1::msm_unchecked(&generators, &[f, o[2]]).into_affine(), - ]; - - let proof = LegoGroth16::::prove( - &pk, - &assignments.public, - &assignments.private, - &o, - &mut rng, - ) - .unwrap(); - - assert!( - LegoGroth16::::verify(&vk, &[(a + b + c) * (d + e + f)], &cm, &proof).is_ok() - ); - assert!(LegoGroth16::::verify(&vk, &[a], &cm, &proof).is_err()); - } + (pk, vk, generators) } - fn test_prove_and_verify2< + pub(crate) fn toy_prove< E: Pairing, >( - n_iters: usize, - ) { - let mut rng = thread_rng(); - - let g = vec![E::G1Affine::rand(&mut rng), E::G1Affine::rand(&mut rng)]; - let h = E::G1Affine::rand(&mut rng); - let generators1 = [&g[..], &[h]].concat(); - let ck1 = PedersenKey { g, h }; - - let g = vec![E::G1Affine::rand(&mut rng)]; - let h = E::G1Affine::rand(&mut rng); - let generators2 = [&g[..], &[h]].concat(); - let ck2 = PedersenKey { g, h }; - - let mut cs = ArithExtractor::new(); - cs.execute_synthesizer(MySillyCircuit { - a: Default::default(), - b: Default::default(), - c: Default::default(), - d: Default::default(), - e: Default::default(), - f: Default::default(), - }) - .unwrap(); - - let (pk, vk) = LegoGroth16::::generate_keys( - (cs.arith().unwrap(), UsizeSet::from_iter(vec![0, 3, 5])), - &[&ck1, &ck2], - &mut rng, - ) - .unwrap(); + pk: &ProverKey, + generators: &[Vec], + ck_sizes: &[usize], + mut rng: impl RngCore, + ) -> (E::ScalarField, Vec, Proof) { + let [a, b, c, d, e, f] = [(); 6].map(|_| E::ScalarField::rand(&mut rng)); - for _ in 0..n_iters { - let a = E::ScalarField::rand(&mut rng); - let b = E::ScalarField::rand(&mut rng); - let c = E::ScalarField::rand(&mut rng); - let d = E::ScalarField::rand(&mut rng); - let e = E::ScalarField::rand(&mut rng); - let f = E::ScalarField::rand(&mut rng); - - let mut cs = AssignmentsExtractor::new(); - cs.execute_synthesizer(MySillyCircuit { a, b, c, d, e, f }) - .unwrap(); - let assignments = cs.assignments().unwrap(); - - let o = [ - E::ScalarField::rand(&mut rng), - E::ScalarField::rand(&mut rng), - ]; - - let cm = vec![ - E::G1::msm_unchecked(&generators1, &[d, e, o[0]]).into_affine(), - E::G1::msm_unchecked(&generators2, &[f, o[1]]).into_affine(), - ]; - - let proof = LegoGroth16::::prove( - &pk, - &assignments.public, - &assignments.private, - &o, - &mut rng, - ) + let mut cs = AssignmentsExtractor::new(); + cs.execute_synthesizer(ToyCircuit { a, b, c, d, e, f }) + .unwrap(); + let assignments = cs.assignments().unwrap(); + + let committed = [d, e, f]; + let mut next = 0; + let mut o = vec![]; + let commitments = ck_sizes + .iter() + .zip(generators) + .map(|(&n, gens)| { + let opening = E::ScalarField::rand(&mut rng); + o.push(opening); + let scalars: Vec<_> = committed[next..next + n] + .iter() + .copied() + .chain([opening]) + .collect(); + next += n; + E::G1::msm_unchecked(gens, &scalars).into_affine() + }) + .collect(); + + let proof = LegoGroth16::::prove(pk, &assignments.public, &assignments.private, &o, rng) .unwrap(); - assert!( - LegoGroth16::::verify(&vk, &[(a + b + c) * (d + e + f)], &cm, &proof).is_ok() - ); - assert!(LegoGroth16::::verify(&vk, &[a], &cm, &proof).is_err()); - } + ((a + b + c) * (d + e + f), commitments, proof) } - fn test_prove_and_verify3< + fn test_legogroth16_opt< E: Pairing, >( - n_iters: usize, + ck_sizes: &[usize], + mut rng: impl RngCore, ) { - let mut rng = thread_rng(); - - let g = vec![ - E::G1Affine::rand(&mut rng), - E::G1Affine::rand(&mut rng), - E::G1Affine::rand(&mut rng), - ]; - let h = E::G1Affine::rand(&mut rng); - let generators = [&g[..], &[h]].concat(); - let ck = PedersenKey { g, h }; - - let mut cs = ArithExtractor::new(); - cs.execute_synthesizer(MySillyCircuit { - a: Default::default(), - b: Default::default(), - c: Default::default(), - d: Default::default(), - e: Default::default(), - f: Default::default(), - }) - .unwrap(); + let (pk, vk, generators) = toy_keygen::(ck_sizes, &mut rng); - let (pk, vk) = LegoGroth16::::generate_keys( - (cs.arith().unwrap(), UsizeSet::from_iter(vec![0, 3, 5])), - &[&ck], - &mut rng, - ) - .unwrap(); - - for _ in 0..n_iters { - let a = E::ScalarField::rand(&mut rng); - let b = E::ScalarField::rand(&mut rng); - let c = E::ScalarField::rand(&mut rng); - let d = E::ScalarField::rand(&mut rng); - let e = E::ScalarField::rand(&mut rng); - let f = E::ScalarField::rand(&mut rng); - - let mut cs = AssignmentsExtractor::new(); - cs.execute_synthesizer(MySillyCircuit { a, b, c, d, e, f }) - .unwrap(); - let assignments = cs.assignments().unwrap(); - - let o = [E::ScalarField::rand(&mut rng)]; - - let cm = vec![E::G1::msm_unchecked(&generators, &[d, e, f, o[0]]).into_affine()]; - - let proof = LegoGroth16::::prove( - &pk, - &assignments.public, - &assignments.private, - &o, - &mut rng, - ) - .unwrap(); - - assert!( - LegoGroth16::::verify(&vk, &[(a + b + c) * (d + e + f)], &cm, &proof).is_ok() - ); - assert!(LegoGroth16::::verify(&vk, &[a], &cm, &proof).is_err()); - } + let (g, cm, proof) = toy_prove::(&pk, &generators, ck_sizes, &mut rng); + assert!(LegoGroth16::::verify(&vk, &[g], &cm, &proof).is_ok()); + assert!(LegoGroth16::::verify(&vk, &[g + E::ScalarField::ONE], &cm, &proof).is_err()); } #[test] - fn prove_and_verify() { - test_prove_and_verify::(5); - test_prove_and_verify2::(5); - test_prove_and_verify3::(5); + fn test_legogroth16() { + let mut rng = thread_rng(); + // Three commitment layouts for the committed witnesses `d, e, f`. + test_legogroth16_opt::(&[1, 1, 1], &mut rng); + test_legogroth16_opt::(&[2, 1], &mut rng); + test_legogroth16_opt::(&[3], &mut rng); } } diff --git a/crates/snarks/src/cp/mod.rs b/crates/snarks/src/cp/mod.rs index 9e24e432a..1cd406cef 100644 --- a/crates/snarks/src/cp/mod.rs +++ b/crates/snarks/src/cp/mod.rs @@ -1,7 +1,7 @@ use ark_ff::Field; use ark_std::{borrow::Borrow, rand::RngCore}; -pub mod groth16; +pub mod legogroth16; pub trait CPSNARK { type Field: Field; diff --git a/crates/snarks/src/linear_subspace/mod.rs b/crates/snarks/src/linear_subspace/mod.rs index a6adedb73..c5e223896 100644 --- a/crates/snarks/src/linear_subspace/mod.rs +++ b/crates/snarks/src/linear_subspace/mod.rs @@ -48,7 +48,7 @@ impl LinearSubspaceSNARK { } pub fn verify(vk: &VerifierKey, x: &[E::G1Affine], pi: &E::G1Affine) -> bool { - // `PE::multi_pairin` internally uses `zip_eq` + // `E::multi_pairing` internally uses `zip_eq` E::multi_pairing([x, &[*pi][..]].concat(), [&vk.c, &[vk.a_neg][..]].concat()).is_zero() } } diff --git a/crates/snarks/templates/legogroth16.sol.askama b/crates/snarks/templates/legogroth16.sol.askama new file mode 100644 index 000000000..33be31f23 --- /dev/null +++ b/crates/snarks/templates/legogroth16.sol.askama @@ -0,0 +1,139 @@ +{%- let numCommitments = vk.link_vk.c.len() - 1 -%} +{%- let numPublicInputs = vk.cc_vk.gamma_abc_g1_pub.len() - 1 %} +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.35; + +contract LegoGroth16Verifier { + error ProofInvalid(); + error NonCanonicalInput(); + + // BN254 scalar field order + uint256 constant R = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + uint256 constant ALPHA_X = {{ vk.cc_vk.alpha_g1.x().unwrap() }}; + uint256 constant ALPHA_Y = {{ vk.cc_vk.alpha_g1.y().unwrap() }}; + + uint256 constant BETA_NEG_X_0 = {{ vk.cc_vk.beta_g2_neg.x().unwrap().c0 }}; + uint256 constant BETA_NEG_X_1 = {{ vk.cc_vk.beta_g2_neg.x().unwrap().c1 }}; + uint256 constant BETA_NEG_Y_0 = {{ vk.cc_vk.beta_g2_neg.y().unwrap().c0 }}; + uint256 constant BETA_NEG_Y_1 = {{ vk.cc_vk.beta_g2_neg.y().unwrap().c1 }}; + + uint256 constant GAMMA_NEG_X_0 = {{ vk.cc_vk.gamma_g2_neg.x().unwrap().c0 }}; + uint256 constant GAMMA_NEG_X_1 = {{ vk.cc_vk.gamma_g2_neg.x().unwrap().c1 }}; + uint256 constant GAMMA_NEG_Y_0 = {{ vk.cc_vk.gamma_g2_neg.y().unwrap().c0 }}; + uint256 constant GAMMA_NEG_Y_1 = {{ vk.cc_vk.gamma_g2_neg.y().unwrap().c1 }}; + + uint256 constant DELTA_NEG_X_0 = {{ vk.cc_vk.delta_g2_neg.x().unwrap().c0 }}; + uint256 constant DELTA_NEG_X_1 = {{ vk.cc_vk.delta_g2_neg.x().unwrap().c1 }}; + uint256 constant DELTA_NEG_Y_0 = {{ vk.cc_vk.delta_g2_neg.y().unwrap().c0 }}; + uint256 constant DELTA_NEG_Y_1 = {{ vk.cc_vk.delta_g2_neg.y().unwrap().c1 }}; + + {% for i in 0..vk.cc_vk.gamma_abc_g1_pub.len() %} + uint256 constant GAMMA_ABC_{{ i }}_X = {{ vk.cc_vk.gamma_abc_g1_pub[i].x().unwrap() }}; + uint256 constant GAMMA_ABC_{{ i }}_Y = {{ vk.cc_vk.gamma_abc_g1_pub[i].y().unwrap() }}; + {%- endfor %} + + {% for i in 0..vk.link_vk.c.len() %} + uint256 constant LINK_C_{{ i }}_X_0 = {{ vk.link_vk.c[i].x().unwrap().c0 }}; + uint256 constant LINK_C_{{ i }}_X_1 = {{ vk.link_vk.c[i].x().unwrap().c1 }}; + uint256 constant LINK_C_{{ i }}_Y_0 = {{ vk.link_vk.c[i].y().unwrap().c0 }}; + uint256 constant LINK_C_{{ i }}_Y_1 = {{ vk.link_vk.c[i].y().unwrap().c1 }}; + {%- endfor %} + + uint256 constant LINK_A_NEG_X_0 = {{ vk.link_vk.a_neg.x().unwrap().c0 }}; + uint256 constant LINK_A_NEG_X_1 = {{ vk.link_vk.a_neg.x().unwrap().c1 }}; + uint256 constant LINK_A_NEG_Y_0 = {{ vk.link_vk.a_neg.y().unwrap().c0 }}; + uint256 constant LINK_A_NEG_Y_1 = {{ vk.link_vk.a_neg.y().unwrap().c1 }}; + + function verifyProof( + uint256[{{ numPublicInputs }}] calldata x, + uint256[{{ numCommitments * 2 }}] calldata c, + uint256[12] calldata proof + ) public view { + for (uint256 k = 0; k < {{ numPublicInputs }}; k++) { + if (x[k] >= R) { + revert NonCanonicalInput(); + } + } + + bool ok = true; + uint256 pub_x; + uint256 pub_y; + + assembly ("memory-safe") { + let f := mload(0x40) + + // Public input MSM + mstore(f, GAMMA_ABC_0_X) + mstore(add(f, 0x20), GAMMA_ABC_0_Y) + {% for i in 1..vk.cc_vk.gamma_abc_g1_pub.len() %} + mstore(add(f, 0x40), GAMMA_ABC_{{ i }}_X) + mstore(add(f, 0x60), GAMMA_ABC_{{ i }}_Y) + mstore(add(f, 0x80), calldataload(add(x, {{ (i - 1) * 0x20 }}))) + ok := and(ok, staticcall(gas(), 0x07, add(f, 0x40), 0x60, add(f, 0x40), 0x40)) + ok := and(ok, staticcall(gas(), 0x06, f, 0x80, f, 0x40)) + {%- endfor %} + + pub_x := mload(f) + pub_y := mload(add(f, 0x20)) + + // A, B + calldatacopy(f, proof, 0xc0) + + // C, -δ + calldatacopy(add(f, 0xc0), add(proof, 0xc0), 0x40) + mstore(add(f, 0x100), DELTA_NEG_X_1) + mstore(add(f, 0x120), DELTA_NEG_X_0) + mstore(add(f, 0x140), DELTA_NEG_Y_1) + mstore(add(f, 0x160), DELTA_NEG_Y_0) + + // α, -β + mstore(add(f, 0x180), ALPHA_X) + mstore(add(f, 0x1a0), ALPHA_Y) + mstore(add(f, 0x1c0), BETA_NEG_X_1) + mstore(add(f, 0x1e0), BETA_NEG_X_0) + mstore(add(f, 0x200), BETA_NEG_Y_1) + mstore(add(f, 0x220), BETA_NEG_Y_0) + + // pub + D, -γ + mstore(add(f, 0x240), pub_x) + mstore(add(f, 0x260), pub_y) + calldatacopy(add(f, 0x280), add(proof, 0x100), 0x40) + ok := and(ok, staticcall(gas(), 0x06, add(f, 0x240), 0x80, add(f, 0x240), 0x40)) + mstore(add(f, 0x280), GAMMA_NEG_X_1) + mstore(add(f, 0x2a0), GAMMA_NEG_X_0) + mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) + mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) + + ok := and(ok, staticcall(gas(), 0x08, f, 0x300, f, 0x20)) + ok := and(ok, mload(f)) + + // c || D, Link_C + {% for i in 0..numCommitments %} + calldatacopy(add(f, {{ i * 6 * 0x20 }}), add(c, {{ i * 2 * 0x20 }}), 0x40) + mstore(add(f, {{ i * 6 * 0x20 + 0x40 }}), LINK_C_{{ i }}_X_1) + mstore(add(f, {{ i * 6 * 0x20 + 0x60 }}), LINK_C_{{ i }}_X_0) + mstore(add(f, {{ i * 6 * 0x20 + 0x80 }}), LINK_C_{{ i }}_Y_1) + mstore(add(f, {{ i * 6 * 0x20 + 0xa0 }}), LINK_C_{{ i }}_Y_0) + {%- endfor %} + calldatacopy(add(f, {{ numCommitments * 6 * 0x20 }}), add(proof, 0x100), 0x40) + mstore(add(f, {{ numCommitments * 6 * 0x20 + 0x40 }}), LINK_C_{{ numCommitments }}_X_1) + mstore(add(f, {{ numCommitments * 6 * 0x20 + 0x60 }}), LINK_C_{{ numCommitments }}_X_0) + mstore(add(f, {{ numCommitments * 6 * 0x20 + 0x80 }}), LINK_C_{{ numCommitments }}_Y_1) + mstore(add(f, {{ numCommitments * 6 * 0x20 + 0xa0 }}), LINK_C_{{ numCommitments }}_Y_0) + // Link_pi, -Link_A + calldatacopy(add(f, {{ (numCommitments + 1) * 6 * 0x20 }}), add(proof, 0x140), 0x40) + mstore(add(f, {{ (numCommitments + 1) * 6 * 0x20 + 0x40 }}), LINK_A_NEG_X_1) + mstore(add(f, {{ (numCommitments + 1) * 6 * 0x20 + 0x60 }}), LINK_A_NEG_X_0) + mstore(add(f, {{ (numCommitments + 1) * 6 * 0x20 + 0x80 }}), LINK_A_NEG_Y_1) + mstore(add(f, {{ (numCommitments + 1) * 6 * 0x20 + 0xa0 }}), LINK_A_NEG_Y_0) + + ok := and(ok, staticcall(gas(), 0x08, f, {{ (numCommitments + 2) * 6 * 0x20 }}, f, 0x20)) + ok := and(ok, mload(f)) + } + if (!ok) { + revert ProofInvalid(); + } + } +} \ No newline at end of file