diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 00000000..983e89ba
--- /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/scripts/wasm-target-test-build.sh b/.github/scripts/wasm-target-test-build.sh
deleted file mode 100644
index 3c42427c..00000000
--- 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 a4b70d0c..9ca52f73 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,45 @@ concurrency:
jobs:
test:
if: github.event.pull_request.draft == false
- name: Test
+ name: Test ${{ matrix.target }} (${{ matrix.features }})
runs-on: ubuntu-latest
strategy:
matrix:
- feature_set: [basic]
include:
- - feature_set: basic
- features: --features default,light-test
+ # 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: ""
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
- - name: Run tests
- uses: actions-rs/cargo@v1
- with:
- command: test
- args: --release --workspace --no-default-features ${{ matrix.features }}
- - name: Run Doc-tests
- uses: actions-rs/cargo@v1
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
with:
- command: test
- args: --doc
+ targets: ${{ matrix.target }}
+ - uses: Swatinem/rust-cache@v2
+ - 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
@@ -82,79 +84,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
+ - wasm32-wasip2
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
+ 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
@@ -162,41 +106,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-wasip2
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
diff --git a/Cargo.toml b/Cargo.toml
index e8fe65f3..d1b8c62a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,50 @@
[workspace]
members = [
"crates/primitives",
- "crates/traits",
+ "crates/fs",
+ "crates/ivc",
]
resolver = "2"
+[workspace.package]
+edition = "2024"
+license = "MIT"
+repository = "https://github.com/privacy-scaling-explorations/sonobe/"
+rust-version = "1.85.1"
+
+[workspace.dependencies]
+num-bigint = { version = "0.4.3" }
+num-integer = { version = "0.1" }
+num-traits = { version = "0.2" }
+sha3 = { version = "0.10" }
+rayon = { version = "1" }
+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 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 }
+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
@@ -19,64 +59,9 @@ 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" }
[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"
-license = "MIT"
-repository = "https://github.com/privacy-scaling-explorations/sonobe/"
-
-[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 }
-
-# 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-relations = { git = "https://github.com/arkworks-rs/snark", default-features = false }
-ark-serialize = { version = "^0.5.0" }
-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
-sonobe-primitives = { path = "crates/primitives", default-features = false }
-sonobe-traits = { path = "crates/traits" }
\ No newline at end of file
+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/README.md b/README.md
index bb9ee6f1..19ca77b1 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
diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml
new file mode 100644
index 00000000..772f4e47
--- /dev/null
+++ b/crates/fs/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "sonobe-fs"
+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-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 }
+
+sonobe-primitives = { workspace = true }
+
+[dev-dependencies]
+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"] }
+
+[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies]
+wasm-bindgen-test = { workspace = true }
+
+[features]
+default = []
+parallel = [
+ "sonobe-primitives/parallel",
+]
diff --git a/crates/fs/src/definitions/algorithms.rs b/crates/fs/src/definitions/algorithms.rs
new file mode 100644
index 00000000..a4238a03
--- /dev/null
+++ b/crates/fs/src/definitions/algorithms.rs
@@ -0,0 +1,120 @@
+//! 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 {
+ /// [`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`.
+ ///
+ /// The security parameter is implicitly specified by the size of underlying
+ /// fields and groups.
+ fn preprocess(config: Self::Config, rng: impl RngCore) -> Result;
+}
+
+/// [`FoldingSchemeKeyGenerator`] is the trait for folding scheme key generator.
+pub trait FoldingSchemeKeyGenerator: FoldingSchemeDef {
+ /// [`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 {
+ /// [`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.
+ ///
+ /// 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,
+ 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>;
+}
+
+/// [`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,
+ transcript: &mut impl Transcript,
+ Us: &[impl Borrow; M],
+ us: &[impl Borrow; N],
+ proof: &Self::Proof,
+ ) -> 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)
+ }
+}
+
+impl FoldingSchemeDecider for FS {}
+
+/// [`FoldingSchemeOps`] is a convenience super-trait bundling all algorithms.
+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 00000000..68d1be5f
--- /dev/null
+++ b/crates/fs/src/definitions/circuits.rs
@@ -0,0 +1,60 @@
+//! 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>
+{
+ /// [`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,
+ transcript: &mut impl TranscriptGadget<::ConstraintField>,
+ Us: [&Self::RU; M],
+ us: [&Self::IU; N],
+ proof: &Self::Proof,
+ ) -> 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,
+ 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 00000000..d721a88d
--- /dev/null
+++ b/crates/fs/src/definitions/errors.rs
@@ -0,0 +1,49 @@
+//! Error definitions for folding schemes.
+
+use ark_relations::gr1cs::SynthesisError;
+use sonobe_primitives::{
+ arithmetizations::Error as ArithError, commitments::Error as CommitmentError,
+ sumcheck::Error as SumCheckError,
+};
+use thiserror::Error;
+
+/// [`Error`] enumerates possible errors during folding scheme operations.
+#[derive(Debug, Error)]
+pub enum Error {
+ /// [`Error::ArithError`] indicates an error from the underlying constraint
+ /// system.
+ #[error(transparent)]
+ ArithError(#[from] ArithError),
+ /// [`Error::CommitmentError`] indicates an error from the underlying
+ /// commitment scheme.
+ #[error(transparent)]
+ CommitmentError(#[from] CommitmentError),
+ /// [`Error::SynthesisError`] indicates an error during constraint
+ /// synthesis.
+ #[error(transparent)]
+ SynthesisError(#[from] SynthesisError),
+ /// [`Error::SumCheckError`] indicates an error from the underlying sumcheck
+ /// protocol.
+ #[error(transparent)]
+ SumCheckError(#[from] SumCheckError),
+ /// [`Error::Unsupported`] indicates that a certain use case is not
+ /// supported.
+ #[error("Unsupported use case: {0}")]
+ Unsupported(String),
+ /// [`Error::DomainCreationFailure`] indicates a failure in creating
+ /// evaluation domains.
+ #[error("Failed to create domain")]
+ DomainCreationFailure,
+ /// [`Error::IndivisibleByVanishingPoly`] indicates that a polynomial is
+ /// not divisible by the vanishing polynomial of a certain domain.
+ #[error("Indivisible by vanishing polynomial")]
+ IndivisibleByVanishingPoly,
+ /// [`Error::UnsatisfiedRelation`] indicates that a certain relation is not
+ /// satisfied.
+ #[error("Unsatisfied relation: {0}")]
+ UnsatisfiedRelation(String),
+ /// [`Error::InvalidPublicParameters`] indicates that the provided public
+ /// parameters are invalid.
+ #[error("Invalid public parameters: {0}")]
+ InvalidPublicParameters(String),
+}
diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs
new file mode 100644
index 00000000..cd431c44
--- /dev/null
+++ b/crates/fs/src/definitions/instances.rs
@@ -0,0 +1,114 @@
+//! 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;
+use sonobe_primitives::{
+ arithmetizations::ArithConfig,
+ commitments::{CommitmentDef, CommitmentDefGadget},
+ traits::Dummy,
+ transcripts::{Absorbable, AbsorbableVar},
+};
+
+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;
+
+ /// [`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 {
+ 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<&CM::Commitment> {
+ vec![]
+ }
+
+ 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`].
+pub trait FoldingInstanceVar:
+ AllocVar
+ + GR1CSVar>
+ + AbsorbableVar
+ + CondSelectGadget
+{
+ /// [`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,
+ x: Vec,
+ ) -> Result;
+}
+
+impl FoldingInstanceVar for PlainInstanceVar {
+ fn commitments(&self) -> Vec<&CM::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))
+ }
+}
+
+/// [`PlainInstanceVar`] is the in-circuit variable of [`PlainInstance`].
+// TODO (@winderica): use a different tag?
+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 00000000..a71d7f0b
--- /dev/null
+++ b/crates/fs/src/definitions/keys.rs
@@ -0,0 +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
new file mode 100644
index 00000000..38a73702
--- /dev/null
+++ b/crates/fs/src/definitions/mod.rs
@@ -0,0 +1,137 @@
+//! 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;
+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,
+};
+
+/// [`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
+ + Relation
+ + WitnessInstanceSampler
+ + WitnessInstanceSampler<
+ Self::IW,
+ Self::IU,
+ 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 {
+ /// [`FoldingSchemeDefGadget::Widget`] points to the out-of-circuit folding
+ /// scheme widget.
+ type Widget: FoldingSchemeDef;
+
+ /// [`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,
+ ::ConstraintField,
+ > + GR1CSVar<
+ ::ConstraintField,
+ Value = ::Challenge,
+ >;
+ /// [`FoldingSchemeDefGadget::Proof`] is the type of in-circuit proof
+ /// variable.
+ 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 00000000..f4d27acb
--- /dev/null
+++ b/crates/fs/src/definitions/utils.rs
@@ -0,0 +1,107 @@
+//! Utility types shared across folding scheme definitions.
+
+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};
+
+/// [`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);
+
+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 00000000..b3655f37
--- /dev/null
+++ b/crates/fs/src/definitions/variants.rs
@@ -0,0 +1,68 @@
+//! Traits that define variants of folding schemes based on different underlying
+//! mathematical structures.
+
+use sonobe_primitives::{
+ commitments::{CommitmentDef, GroupBasedCommitment},
+ traits::CF2,
+};
+
+use crate::{
+ FoldingSchemeDef, FoldingSchemeDefGadget, FoldingSchemeFullVerifierGadget, FoldingSchemeOps,
+ 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,
+ >
+{
+ /// [`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
+{
+}
+
+impl GroupBasedFoldingSchemePrimary for FS where
+ FS: GroupBasedFoldingSchemePrimaryDef>
+{
+}
+
+/// [`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>,
+ >
+{
+ /// [`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
+{
+}
+
+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 00000000..95da4b78
--- /dev/null
+++ b/crates/fs/src/definitions/witnesses.rs
@@ -0,0 +1,65 @@
+//! Traits and abstractions for folding scheme witnesses.
+
+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;
+
+/// [`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;
+
+ /// [`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 {
+ 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<(&[CM::Scalar], &CM::Randomness)> {
+ vec![]
+ }
+}
+
+/// [`FoldingWitnessVar`] is the in-circuit variable of [`FoldingWitness`].
+pub trait FoldingWitnessVar:
+ AllocVar
+ + GR1CSVar>
+{
+}
+
+impl FoldingWitnessVar for T where
+ T: AllocVar
+ + GR1CSVar>
+{
+}
+
+/// [`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
new file mode 100644
index 00000000..582dd98d
--- /dev/null
+++ b/crates/fs/src/lib.rs
@@ -0,0 +1,136 @@
+#![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 mod mova;
+pub mod nova;
+pub mod ova;
+
+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},
+};
+
+#[cfg(test)]
+mod tests {
+ 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},
+ },
+ };
+
+ use super::*;
+
+ #[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::Arith: From::Scalar>>,
+ {
+ let pp = FS::preprocess(config, &mut rng)?;
+
+ let cs = ArithExtractor::new();
+ cs.execute_synthesizer(circuit)?;
+ let arith = cs.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![];
+ 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 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![];
+ 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, &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(())
+ }
+}
diff --git a/crates/fs/src/mova/algorithms/key_generator.rs b/crates/fs/src/mova/algorithms/key_generator.rs
new file mode 100644
index 00000000..29c3f2d2
--- /dev/null
+++ b/crates/fs/src/mova/algorithms/key_generator.rs
@@ -0,0 +1,27 @@
+//! Key generation for Mova.
+
+use ark_std::sync::Arc;
+use sonobe_primitives::{
+ arithmetizations::{Arith, ArithConfig},
+ commitments::{CommitmentKey, GroupBasedCommitment},
+};
+
+use crate::{
+ Error, FoldingSchemeKeyGenerator,
+ mova::{Mova, MovaKey},
+};
+
+impl FoldingSchemeKeyGenerator
+ for Mova
+{
+ fn generate_keys(ck: Self::PublicParam, r1cs: Self::Arith) -> Result {
+ let ck = Arc::new(ck);
+ let r1cs = Arc::new(r1cs);
+ if ck.max_scalars_len() < r1cs.config().n_witnesses() {
+ return Err(Error::InvalidPublicParameters(
+ "The commitment key is too short for the R1CS instance".into(),
+ ));
+ }
+ Ok(MovaKey { arith: r1cs, ck })
+ }
+}
diff --git a/crates/fs/src/mova/algorithms/mod.rs b/crates/fs/src/mova/algorithms/mod.rs
new file mode 100644
index 00000000..d7d329ec
--- /dev/null
+++ b/crates/fs/src/mova/algorithms/mod.rs
@@ -0,0 +1,6 @@
+//! Implementations folding scheme algorithms for Mova.
+
+pub mod key_generator;
+pub mod preprocessor;
+pub mod prover;
+pub mod verifier;
diff --git a/crates/fs/src/mova/algorithms/preprocessor.rs b/crates/fs/src/mova/algorithms/preprocessor.rs
new file mode 100644
index 00000000..fc8c56e7
--- /dev/null
+++ b/crates/fs/src/mova/algorithms/preprocessor.rs
@@ -0,0 +1,15 @@
+//! Preprocessing for Mova.
+
+use ark_std::rand::RngCore;
+use sonobe_primitives::commitments::GroupBasedCommitment;
+
+use crate::{Error, FoldingSchemePreprocessor, mova::Mova};
+
+impl FoldingSchemePreprocessor
+ for Mova
+{
+ fn preprocess(n_witnesses: usize, mut rng: impl RngCore) -> Result {
+ let ck = CM::generate_key(n_witnesses, &mut rng)?;
+ Ok(ck)
+ }
+}
diff --git a/crates/fs/src/mova/algorithms/prover.rs b/crates/fs/src/mova/algorithms/prover.rs
new file mode 100644
index 00000000..be6632db
--- /dev/null
+++ b/crates/fs/src/mova/algorithms/prover.rs
@@ -0,0 +1,137 @@
+//! Proof generation for Mova.
+
+use ark_ff::{One, Zero};
+use ark_poly::{
+ DenseMultilinearExtension as MLE, DenseUVPolynomial, Polynomial, univariate::DensePolynomial,
+};
+use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, rand::RngCore};
+#[cfg(feature = "parallel")]
+use rayon::prelude::*;
+use sonobe_primitives::{
+ algebra::ops::{bits::FromBits, poly::MLEHelper},
+ arithmetizations::{Arith, ArithConfig},
+ circuits::AssignmentsOwned,
+ commitments::GroupBasedCommitment,
+ transcripts::Transcript,
+};
+
+use crate::{
+ Error, FoldingSchemeProver,
+ mova::{Mova, MovaKey, MovaProof},
+};
+
+impl FoldingSchemeProver<1, 1>
+ for Mova
+{
+ #[allow(non_snake_case)]
+ fn prove(
+ pk: &MovaKey,
+ 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());
+
+ // Protocol 5
+
+ // Step 5.1: Commit to w & send commitment
+ let (cm_w, r_w) = CM::commit(&pk.ck, w, rng)?;
+
+ transcript.add(U);
+ transcript.add(u);
+ transcript.add(&cm_w);
+
+ // Step 5.2: Get challenge r_E
+ let r_e = transcript.challenge_field_elements(U.r_e.len());
+
+ // Protocol 6
+
+ // Step 6.1: Compute l(X) such that l(0) = r1, l(1) = r2
+ let l = U
+ .r_e
+ .iter()
+ .zip(&r_e)
+ .map(|(&r1, &r2)| DensePolynomial::from_coefficients_vec(vec![r1, r2 - r1]))
+ .collect::>();
+ // Step 6.1: Compute h1(X) and h2(X), where h2(X) is empty in our case
+ let h1 = {
+ // Initialize the polynomial vector from the evaluations in the MLE.
+ // Each evaluation is turned into a constant polynomial.
+ let mut poly =
+ W.e.iter()
+ .chain(vec![Zero::zero(); 1 << U.r_e.len()].iter())
+ .map(|&x| DensePolynomial::from_coefficients_slice(&[x]))
+ .collect::>();
+
+ for i in &l {
+ poly = poly
+ .chunks_exact(2)
+ .map(|w| &w[0] + (&w[1] - &w[0]).naive_mul(i))
+ .collect();
+ }
+
+ poly.swap_remove(0)
+ };
+ // Step 6.1: Send h1(X) and h2(X), where the constant term is omitted
+ // because it always equals v
+ let mut h1_coeffs = h1.coeffs.clone();
+ h1_coeffs.resize(pk.arith.config().log_constraints() + 1, Zero::zero());
+ h1_coeffs.remove(0);
+ transcript.add(&h1_coeffs);
+
+ // Step 6.2: Get challenge beta
+ let beta = transcript.challenge_field_element();
+
+ // Step 6.3: Compute r_E'
+ let r_e_prime = l.iter().map(|i| i.evaluate(&beta)).collect();
+
+ // Protocol 7
+
+ // Step 7.1: Compute cross term `T`. We follow 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::>();
+ // Step 7.1: Evaluate & send T's MLE at r_E'
+ let t = MLE::from_evaluations(&T).evaluate(&r_e_prime);
+ transcript.add(&t);
+
+ // Step 7.2: Get challenge rho
+ let rho_bits = transcript.challenge_bits(CHALLENGE_BITS);
+ let rho = CM::Scalar::from_bits_le(&rho_bits);
+
+ // Step 7.3: Compute new W and U
+ Ok((
+ Self::RW {
+ e: cfg_iter!(W.e).zip(&T).map(|(a, b)| rho * b + a).collect(),
+ w: cfg_iter!(W.w)
+ .zip(&w[..])
+ .map(|(a, b)| rho * b + a)
+ .collect(),
+ r_w: W.r_w + r_w * rho,
+ },
+ Self::RU {
+ r_e: r_e_prime,
+ v: h1.evaluate(&beta) + rho * t,
+ u: U.u + rho,
+ cm_w: U.cm_w + cm_w * rho,
+ x: cfg_iter!(U.x)
+ .zip(&u[..])
+ .map(|(a, b)| rho * b + a)
+ .collect(),
+ },
+ MovaProof { h1_coeffs, t, cm_w },
+ rho_bits.try_into().unwrap(),
+ ))
+ }
+}
diff --git a/crates/fs/src/mova/algorithms/verifier.rs b/crates/fs/src/mova/algorithms/verifier.rs
new file mode 100644
index 00000000..131014bb
--- /dev/null
+++ b/crates/fs/src/mova/algorithms/verifier.rs
@@ -0,0 +1,59 @@
+//! Proof verification for Mova.
+
+use ark_poly::{DenseUVPolynomial, Polynomial, univariate::DensePolynomial};
+use ark_std::{borrow::Borrow, cfg_iter};
+#[cfg(feature = "parallel")]
+use rayon::prelude::*;
+use sonobe_primitives::{
+ algebra::ops::bits::FromBits, commitments::GroupBasedCommitment, transcripts::Transcript,
+};
+
+use crate::{Error, FoldingSchemeVerifier, mova::Mova};
+
+impl FoldingSchemeVerifier<1, 1>
+ for Mova
+{
+ #[allow(non_snake_case)]
+ fn verify(
+ _vk: &(),
+ transcript: &mut impl Transcript,
+ Us: &[impl Borrow; 1],
+ us: &[impl Borrow; 1],
+ proof: &Self::Proof<1, 1>,
+ ) -> Result {
+ let (U, u) = (Us[0].borrow(), us[0].borrow());
+
+ let h1 = DensePolynomial::from_coefficients_vec([&[U.v][..], &proof.h1_coeffs].concat());
+
+ transcript.add(U);
+ transcript.add(u);
+ transcript.add(&proof.cm_w);
+
+ let r_e = transcript.challenge_field_elements(U.r_e.len());
+
+ transcript.add(&proof.h1_coeffs);
+
+ let beta = transcript.challenge_field_element();
+
+ transcript.add(&proof.t);
+
+ let rho_bits = transcript.challenge_bits(CHALLENGE_BITS);
+ let rho = CM::Scalar::from_bits_le(&rho_bits);
+
+ Ok(Self::RU {
+ r_e: U
+ .r_e
+ .iter()
+ .zip(r_e)
+ .map(|(&r1, r2)| r1 + beta * (r2 - r1))
+ .collect(),
+ v: h1.evaluate(&beta) + rho * proof.t,
+ u: U.u + rho,
+ cm_w: U.cm_w + proof.cm_w * rho,
+ x: cfg_iter!(U.x)
+ .zip(&u[..])
+ .map(|(a, b)| rho * b + a)
+ .collect(),
+ })
+ }
+}
diff --git a/crates/fs/src/mova/circuits/mod.rs b/crates/fs/src/mova/circuits/mod.rs
new file mode 100644
index 00000000..9ed16d86
--- /dev/null
+++ b/crates/fs/src/mova/circuits/mod.rs
@@ -0,0 +1,3 @@
+//! In-circuit gadgets for Mova.
+
+pub mod verifier;
diff --git a/crates/fs/src/mova/circuits/verifier.rs b/crates/fs/src/mova/circuits/verifier.rs
new file mode 100644
index 00000000..777b6090
--- /dev/null
+++ b/crates/fs/src/mova/circuits/verifier.rs
@@ -0,0 +1,64 @@
+//! Partial in-circuit verifier implementation for Mova.
+
+use ark_r1cs_std::{
+ GR1CSVar, alloc::AllocVar, fields::fp::FpVar,
+ poly::polynomial::univariate::dense::DensePolynomialVar,
+};
+use ark_relations::gr1cs::SynthesisError;
+use sonobe_primitives::{
+ algebra::ops::bits::FromBitsGadget, commitments::GroupBasedCommitment,
+ transcripts::TranscriptGadget,
+};
+
+use crate::{FoldingSchemePartialVerifierGadget, mova::MovaGadget};
+
+impl FoldingSchemePartialVerifierGadget<1, 1>
+ for MovaGadget
+{
+ #[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 h1 = DensePolynomialVar::from_coefficients_vec(
+ [&[U.v.clone()][..], &proof.h1_coeffs].concat(),
+ );
+
+ transcript.add(U)?;
+ transcript.add(u)?;
+ transcript.add(&proof.cm_w)?;
+
+ let r_e = transcript.challenge_field_elements(U.r_e.len())?;
+
+ transcript.add(&proof.h1_coeffs)?;
+
+ let beta = transcript.challenge_field_element()?;
+
+ transcript.add(&proof.t)?;
+
+ let rho_bits = transcript.challenge_bits(CHALLENGE_BITS)?;
+ let rho = FpVar::from_bits_le(&rho_bits)?;
+
+ Ok((
+ Self::RU {
+ r_e: U
+ .r_e
+ .iter()
+ .zip(r_e)
+ .map(|(r1, r2)| r1 + &beta * (r2 - r1))
+ .collect(),
+ v: h1.evaluate(&beta)? + &rho * &proof.t,
+ u: &U.u + &rho,
+ cm_w: AllocVar::new_witness(U.cm_w.cs().or(proof.cm_w.cs()).or(rho.cs()), || {
+ Ok(U.cm_w.value().unwrap_or_default()
+ + proof.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default())
+ })?,
+ x: U.x.iter().zip(&u[..]).map(|(a, b)| &rho * b + a).collect(),
+ },
+ rho_bits.try_into().unwrap(),
+ ))
+ }
+}
diff --git a/crates/fs/src/mova/instances/circuits.rs b/crates/fs/src/mova/instances/circuits.rs
new file mode 100644
index 00000000..2c3ff022
--- /dev/null
+++ b/crates/fs/src/mova/instances/circuits.rs
@@ -0,0 +1,147 @@
+//! In-circuit variables for Mova instances.
+
+use ark_r1cs_std::{
+ GR1CSVar,
+ alloc::{AllocVar, AllocationMode},
+ boolean::Boolean,
+ fields::fp::FpVar,
+ select::CondSelectGadget,
+};
+use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError};
+use ark_std::borrow::Borrow;
+use sonobe_primitives::{commitments::CommitmentDefGadget, transcripts::AbsorbableVar};
+
+use super::RunningInstance;
+use crate::FoldingInstanceVar;
+
+/// [`RunningInstanceVar`] defines Mova's running instance variable.
+#[derive(Clone, Debug, PartialEq)]
+pub struct RunningInstanceVar {
+ /// [`RunningInstanceVar::r_e`] is the random evaluation point for the error
+ /// term.
+ pub r_e: Vec,
+ /// [`RunningInstanceVar::v`] is the evaluation of the MLE of the error term
+ /// at `r_e`.
+ pub v: CM::ScalarVar,
+ /// [`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,
+}
+
+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 { r_e, v, u, cm_w, x } = v.borrow();
+ Ok(Self {
+ r_e: AllocVar::new_variable(cs.clone(), || Ok(&r_e[..]), mode)?,
+ v: AllocVar::new_variable(cs.clone(), || Ok(v), 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.r_e
+ .cs()
+ .or(self.v.cs())
+ .or(self.u.cs())
+ .or(self.cm_w.cs())
+ .or(self.x.cs())
+ }
+
+ fn value(&self) -> Result {
+ Ok(RunningInstance {
+ r_e: self.r_e.value()?,
+ v: self.v.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.v.absorb_into(dest)?;
+ self.r_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.r_e.len() != false_value.r_e.len() {
+ return Err(SynthesisError::Unsatisfiable);
+ }
+ if true_value.x.len() != false_value.x.len() {
+ return Err(SynthesisError::Unsatisfiable);
+ }
+ Ok(Self {
+ r_e: true_value
+ .r_e
+ .iter()
+ .zip(&false_value.r_e)
+ .map(|(t, f)| cond.select(t, f))
+ .collect::>()?,
+ v: cond.select(&true_value.v, &false_value.v)?,
+ u: cond.select(&true_value.u, &false_value.u)?,
+ x: true_value
+ .x
+ .iter()
+ .zip(&false_value.x)
+ .map(|(t, f)| cond.select(t, f))
+ .collect::>()?,
+ cm_w: cond.select(&true_value.cm_w, &false_value.cm_w)?,
+ })
+ }
+}
+
+impl FoldingInstanceVar for RunningInstanceVar {
+ 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 {
+ r_e: AllocVar::new_witness(cs.clone(), || Ok(&u.r_e[..]))?,
+ v: AllocVar::new_witness(cs.clone(), || Ok(u.v))?,
+ u: AllocVar::new_witness(cs.clone(), || Ok(u.u))?,
+ cm_w: AllocVar::new_witness(cs.clone(), || Ok(&u.cm_w))?,
+ x,
+ })
+ }
+}
diff --git a/crates/fs/src/mova/instances/mod.rs b/crates/fs/src/mova/instances/mod.rs
new file mode 100644
index 00000000..c09db828
--- /dev/null
+++ b/crates/fs/src/mova/instances/mod.rs
@@ -0,0 +1,67 @@
+//! Definitions of out-of-circuit values and in-circuit variables for Mova
+//! instances.
+
+use ark_ff::PrimeField;
+use sonobe_primitives::{
+ arithmetizations::ArithConfig, commitments::CommitmentDef, traits::Dummy,
+ transcripts::Absorbable,
+};
+
+use crate::FoldingInstance;
+
+pub mod circuits;
+
+/// [`RunningInstance`] defines Mova's running instance.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct RunningInstance {
+ /// [`RunningInstance::r_e`] is the random evaluation point for the error
+ /// term.
+ pub r_e: Vec,
+ /// [`RunningInstance::v`] is the evaluation of the MLE of the error term
+ /// at `r_e`.
+ pub v: CM::Scalar,
+ /// [`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,
+}
+
+impl FoldingInstance for RunningInstance {
+ 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 RunningInstance {
+ fn dummy(cfg: &Cfg) -> Self {
+ Self {
+ r_e: vec![Default::default(); cfg.log_constraints()],
+ v: 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.v.absorb_into(dest);
+ self.r_e.absorb_into(dest);
+ self.cm_w.absorb_into(dest);
+ }
+}
diff --git a/crates/fs/src/mova/mod.rs b/crates/fs/src/mova/mod.rs
new file mode 100644
index 00000000..db1965c7
--- /dev/null
+++ b/crates/fs/src/mova/mod.rs
@@ -0,0 +1,325 @@
+//! This module implements the Mova folding scheme, which is introduced in this
+//! [paper].
+//!
+//! [paper]: https://eprint.iacr.org/2024/1220.pdf
+
+use ark_ff::{Field, Zero};
+use ark_poly::{DenseMultilinearExtension as MLE, Polynomial};
+use ark_r1cs_std::{
+ GR1CSVar,
+ alloc::{AllocVar, AllocationMode},
+ boolean::Boolean,
+ fields::fp::FpVar,
+};
+use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError};
+use ark_std::{UniformRand, borrow::Borrow, marker::PhantomData, rand::RngCore, sync::Arc};
+use sonobe_primitives::{
+ algebra::ops::poly::MLEHelper,
+ arithmetizations::{
+ Arith, ArithConfig, ArithRelation,
+ r1cs::{R1CS, RelaxedInstance, RelaxedWitness},
+ },
+ circuits::AssignmentsOwned,
+ commitments::{CommitmentDef, CommitmentDefGadget, CommitmentOps, GroupBasedCommitment},
+ relations::{Relation, WitnessInstanceSampler},
+ traits::{CF1, Dummy, SonobeCurve},
+};
+
+use self::{
+ instances::{RunningInstance as RU, circuits::RunningInstanceVar as RUVar},
+ witnesses::RunningWitness as RW,
+};
+use crate::{
+ DeciderKey, Error, FoldingSchemeDef, FoldingSchemeDefGadget, GroupBasedFoldingSchemePrimaryDef,
+ PlainInstance as IU, PlainInstanceVar as IUVar, PlainWitness as IW,
+};
+
+pub mod algorithms;
+pub mod circuits;
+pub mod instances;
+pub mod witnesses;
+
+/// [`MovaKey`] is Mova's decider key.
+#[derive(Clone)]
+pub struct MovaKey {
+ arith: Arc,
+ ck: Arc,
+}
+
+impl DeciderKey for MovaKey {
+ 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 MovaKey