Repository: worldcoin/semaphore-rs Branch: main Commit: 00df132aaa76 Files: 93 Total size: 403.0 KB Directory structure: gitextract_5m5mxrb4/ ├── .github/ │ └── workflows/ │ ├── build-and-test.yml │ ├── release-crates.yml │ └── relyance-sci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── crates/ │ ├── ark-circom/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── circom.rs │ │ │ ├── ethereum.rs │ │ │ ├── lib.rs │ │ │ └── zkey.rs │ │ └── test-vectors/ │ │ ├── test.zkey │ │ └── verification_key.json │ ├── ark-zkey/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── lib.rs │ │ └── semaphore.16.arkzkey │ ├── circom-witness-rs/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── build.rs │ │ ├── include/ │ │ │ └── witness.h │ │ ├── script/ │ │ │ └── replace.sh │ │ └── src/ │ │ ├── field.rs │ │ ├── generate.rs │ │ ├── graph.rs │ │ └── lib.rs │ ├── hasher/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── js/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.mjs │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── index.mjs │ │ │ ├── index.ts │ │ │ └── package.json │ │ └── src/ │ │ └── lib.rs │ ├── keccak/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── keccak.rs │ │ ├── lib.rs │ │ └── sha3.rs │ ├── poseidon/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── constants.rs │ │ ├── lib.rs │ │ └── poseidon.rs │ ├── proof/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── ark.rs │ │ ├── compression.rs │ │ ├── lib.rs │ │ └── packing.rs │ ├── semaphore/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches/ │ │ │ ├── cascading_merkle_tree.rs │ │ │ └── lazy_merkle_tree.rs │ │ ├── build.rs │ │ ├── examples/ │ │ │ └── abort/ │ │ │ └── main.rs │ │ └── src/ │ │ ├── circuit.rs │ │ ├── field.rs │ │ ├── hash.rs │ │ ├── identity.rs │ │ ├── lib.rs │ │ ├── packed_proof.rs │ │ ├── poseidon_tree.rs │ │ ├── protocol/ │ │ │ ├── authentication.rs │ │ │ └── mod.rs │ │ └── util.rs │ ├── semaphore-depth-config/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── semaphore-depth-macros/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── storage/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── native/ │ │ ├── mmap_vec.rs │ │ └── mod.rs │ ├── trees/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── cascading/ │ │ │ │ ├── mod.rs │ │ │ │ └── storage_ops.rs │ │ │ ├── imt/ │ │ │ │ └── mod.rs │ │ │ ├── lazy/ │ │ │ │ └── mod.rs │ │ │ ├── lib.rs │ │ │ └── proof.rs │ │ └── tests/ │ │ └── equivalent.rs │ └── utils/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── cspell.json ├── mit-license.md ├── publish_all.sh ├── release-plz.toml └── supply-chain/ ├── audits.toml └── config.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build-and-test.yml ================================================ name: Build and Test on: push env: RUST_VERSION: 1.94.0 jobs: dependabot-dependency-review: runs-on: ubuntu-latest steps: - name: "Checkout Repository" uses: actions/checkout@v4 - name: "Dependency Review" uses: actions/dependency-review-action@v4 with: base-ref: ${{ inputs.base-ref || github.event.pull_request.base.sha || 'main' }} head-ref: ${{ inputs.head-ref || github.event.pull_request.head.sha || github.ref }} test: name: Test runs-on: ubuntu-latest env: RUSTFLAGS: "-D warnings" steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Set up Rust run: | rustup update ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} && rustup component add rustfmt --toolchain ${{ env.RUST_VERSION }} && rustup component add clippy --toolchain ${{ env.RUST_VERSION }} - name: Cache Cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-cargo-registry- - name: Cache Cargo index uses: actions/cache@v4 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-cargo-index- - name: Cache Cargo build uses: actions/cache@v4 with: path: target key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-cargo-build- - name: Check run: cargo check --workspace --tests --benches --features depth_16,depth_20,depth_30 - name: Check Formatting run: cargo fmt --all -- --check - name: Run Clippy run: cargo clippy --all-targets --features depth_16,depth_20,depth_30 - name: Run test run: cargo test --workspace --features depth_16,depth_20,depth_30 wasm: name: WASM Check runs-on: ubuntu-latest env: RUSTFLAGS: "-D warnings" steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Set up Rust run: | rustup update ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} rustup target add wasm32-unknown-unknown - name: Cache Cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry key: ${{ runner.os }}-wasm-cargo-registry-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-wasm-cargo-registry- - name: Cache Cargo index uses: actions/cache@v4 with: path: ~/.cargo/git key: ${{ runner.os }}-wasm-cargo-index-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-wasm-cargo-index- - name: Cache Cargo build uses: actions/cache@v4 with: path: target key: ${{ runner.os }}-wasm-cargo-build-${{ hashFiles('**/Cargo.toml') }} restore-keys: | ${{ runner.os }}-wasm-cargo-build- - name: Check (storage) run: cargo check --target wasm32-unknown-unknown -p semaphore-rs-storage - name: Check (trees) run: cargo check --target wasm32-unknown-unknown -p semaphore-rs-trees - name: Check (semaphore-rs) run: cargo check --target wasm32-unknown-unknown -p semaphore-rs --features depth_16 # vet: # name: Vet Dependencies # runs-on: ubuntu-latest # steps: # - uses: actions/checkout@master # - name: Install Rust # uses: actions-rs/toolchain@v1 # with: # profile: minimal # toolchain: ${{ env.RUST_VERSION }} # override: true # - uses: actions-rs/cargo@v1 # with: # command: build # - uses: actions/cache@v3 # with: # path: | # ~/.cargo/registry/index/ # ~/.cargo/registry/cache/ # ~/.cargo/git/db/ # target/ # key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} # - name: Install cargo-vet # run: cargo install cargo-vet --version ${{ env.CARGO_VET_VERSION }} --git ${{ env.CARGO_VET_REPO }} # - name: Prune (If some import got updated) # run: cargo vet prune # - name: Invoke cargo-vet # run: cargo vet ================================================ FILE: .github/workflows/release-crates.yml ================================================ name: Release Crates on: push: branches: - main workflow_dispatch: # Prevent concurrent release runs — release-plz is not safe to run in parallel # against the same repo state. Keep in-progress runs; don't cancel them. concurrency: group: release-plz cancel-in-progress: false jobs: release-crates: name: Release Crates runs-on: ubuntu-latest permissions: contents: write pull-requests: write id-token: write steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Run release-plz uses: release-plz/action@1efcf74dfcd6e500990dad806e286899ae384064 # v0.5.119 (https://github.com/release-plz/action/releases/tag/v0.5.119) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/relyance-sci.yml ================================================ name: Relyance SCI Scan on: schedule: - cron: "30 0 * * *" workflow_dispatch: permissions: contents: read jobs: execute-relyance-sci: name: Relyance SCI Job runs-on: group: arc-public-large-amd64-runner permissions: contents: read steps: - name: Run Relyance SCI uses: worldcoin/gh-actions-public/relyance@main # More information: https://github.com/worldcoin/gh-actions-public/tree/main/relyance with: secrets-dpp-sci-key: ${{ secrets.DPP_SCI_KEY }} ================================================ FILE: .gitignore ================================================ /target *.profraw snarkfiles_tmp semaphore_files .idea lcov.info *.DS_Store Cargo.lock ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.5.3](https://github.com/worldcoin/semaphore-rs/compare/semaphore-rs-v0.5.2...semaphore-rs-v0.5.3) - 2026-03-20 ### Other - Optimize auth proofs and restore depth test macros ([#137](https://github.com/worldcoin/semaphore-rs/pull/137)) ## [0.5.2](https://github.com/worldcoin/semaphore-rs/compare/semaphore-rs-v0.5.1...semaphore-rs-v0.5.2) - 2026-03-20 ### Other - update Cargo.toml dependencies ## [0.5.1](https://github.com/worldcoin/semaphore-rs/compare/semaphore-rs-v0.5.0...semaphore-rs-v0.5.1) - 2026-03-16 ### Added - gate mmap-rs and lazy trees for WASM compatibility ([#131](https://github.com/worldcoin/semaphore-rs/pull/131)) - cascade improvements ([#130](https://github.com/worldcoin/semaphore-rs/pull/130)) - check for pushing past tree depth - check for empty range ### Other - satisfy clippy ================================================ FILE: Cargo.toml ================================================ [workspace] members = ["crates/*"] resolver = "2" [workspace.package] version = "0.5.3" edition = "2021" homepage = "https://github.com/worldcoin/semaphore-rs" license = "MIT" repository = "https://github.com/worldcoin/semaphore-rs" authors = [ "Remco Bloemen ", "Philipp Sippl ", ] description = "Rust support library for Semaphore" keywords = ["worldcoin", "protocol", "signup"] categories = ["cryptography"] [workspace.dependencies] # Internal semaphore-rs-utils = { version = "0.5.3", path = "crates/utils" } semaphore-rs-ark-circom = { version = "0.5.3", path = "crates/ark-circom" } semaphore-rs-ark-zkey = { version = "0.5.3", path = "crates/ark-zkey" } semaphore-rs-proof = { version = "0.5.3", path = "crates/proof", default-features = false } semaphore-rs-poseidon = { version = "0.5.3", path = "crates/poseidon" } semaphore-rs-hasher = { version = "0.5.3", path = "crates/hasher" } semaphore-rs-keccak = { version = "0.5.3", path = "crates/keccak" } semaphore-rs-trees = { version = "0.5.3", path = "crates/trees" } semaphore-rs-storage = { version = "0.5.3", path = "crates/storage" } semaphore-rs-depth-config = { version = "0.5.3", path = "crates/semaphore-depth-config" } semaphore-rs-depth-macros = { version = "0.5.3", path = "crates/semaphore-depth-macros" } semaphore-rs-witness = { version = "0.5.3", path = "crates/circom-witness-rs" } # 3rd Party alloy-core = { version = "1.0", default-features = false, features = [ "sol-types", ] } bincode = "1.3.3" bytemuck = "1.18" byteorder = "1" color-eyre = "0.6" criterion = { version = "0.5", features = ["async_tokio", "html_reports"] } cxx = "1" cxx-build = "1" derive-where = "1.6.0" hex = "0.4.0" hex-literal = "0.4" itertools = "0.13" lazy_static = "1" mmap-rs = "0.6.1" num-bigint = { version = "0.4", default-features = false, features = ["rand"] } num-traits = "0.2.19" once_cell = "1.8" postcard = "1" proptest = "1.0" rand = { version = "0.8.4", features = ["small_rng"] } rand_chacha = "0.3.1" rayon = "1.5.1" reqwest = { version = "0.12", default-features = false, features = [ "blocking", "rustls-tls", ] } ruint = { version = "1.12.3", features = [ "rand", "bytemuck", "serde", "num-bigint", "ark-ff-05", ] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.79" serial_test = "3" sha2 = "0.10.1" test-case = "3.3.1" tempfile = "3.0" thiserror = "1.0.0" tiny-keccak = { version = "2.0.2", features = ["sha3", "keccak"] } tokio = "1" tracing-test = "0.2" zeroize = "1.6.0" memmap2 = "0.9" flame = "0.2" flamer = "0.5" # WASM wasm-bindgen = "0.2" js-sys = "0.3" # Required if we're compiling to WASM getrandom = { version = "0.2.15", features = ["js"] } wasm-bindgen-test = "0.3" # Proc macros syn = { version = "2.0.9", features = ["full", "visit-mut", "extra-traits"] } proc-macro2 = "1.0.53" quote = "1.0.26" # Ark ark-bn254 = { version = "0.5.0" } ark-ec = { version = "0.5.0", default-features = false, features = [ "parallel", ] } ark-ff = { version = "0.5.0", default-features = false, features = [ "parallel", "asm", ] } ark-groth16 = { version = "0.5.0", features = ["parallel"] } ark-relations = { version = "0.5.0", default-features = false } ark-std = { version = "0.5.0", default-features = false, features = [ "parallel", ] } ark-serialize = { version = "0.5.0", features = ["derive"] } ark-poly = { version = "0.5.0" } ark-crypto-primitives = { version = "0.5.0" } [profile.release] codegen-units = 1 lto = true panic = "abort" opt-level = 3 # Compilation profile for any non-workspace member. # Dependencies are optimized, even in a dev build. This improves dev performance # while having neglible impact on incremental build times. [profile.dev.package."*"] opt-level = 3 ================================================ FILE: crates/ark-circom/Cargo.toml ================================================ [package] name = "semaphore-rs-ark-circom" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] ark-ff.workspace = true ark-relations.workspace = true ark-serialize.workspace = true ark-std.workspace = true ark-bn254.workspace = true ark-groth16.workspace = true ark-poly.workspace = true ark-crypto-primitives.workspace = true ruint.workspace = true serde_json.workspace = true byteorder.workspace = true num-traits.workspace = true num-bigint.workspace = true thiserror.workspace = true [features] # This features does nothing but the ark_std::cfg_into_iter expectes it parallel = [] ================================================ FILE: crates/ark-circom/src/circom.rs ================================================ use ark_ff::PrimeField; use ark_groth16::r1cs_to_qap::{evaluate_constraint, LibsnarkReduction, R1CSToQAP}; use ark_poly::EvaluationDomain; use ark_relations::r1cs::{ConstraintMatrices, ConstraintSystemRef, SynthesisError}; use ark_std::{cfg_into_iter, cfg_iter, cfg_iter_mut, vec}; /// Implements the witness map used by snarkjs. The arkworks witness map calculates the /// coefficients of H through computing (AB-C)/Z in the evaluation domain and going back to the /// coefficients domain. snarkjs instead precomputes the Lagrange form of the powers of tau bases /// in a domain twice as large and the witness map is computed as the odd coefficients of (AB-C) /// in that domain. This serves as HZ when computing the C proof element. pub struct CircomReduction; impl R1CSToQAP for CircomReduction { #[allow(clippy::type_complexity)] fn instance_map_with_evaluation>( cs: ConstraintSystemRef, t: &F, ) -> Result<(Vec, Vec, Vec, F, usize, usize), SynthesisError> { LibsnarkReduction::instance_map_with_evaluation::(cs, t) } fn witness_map_from_matrices>( matrices: &ConstraintMatrices, num_inputs: usize, num_constraints: usize, full_assignment: &[F], ) -> Result, SynthesisError> { let zero = F::zero(); let domain = D::new(num_constraints + num_inputs).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; let domain_size = domain.size(); let mut a = vec![zero; domain_size]; let mut b = vec![zero; domain_size]; cfg_iter_mut!(a[..num_constraints]) .zip(cfg_iter_mut!(b[..num_constraints])) .zip(cfg_iter!(&matrices.a)) .zip(cfg_iter!(&matrices.b)) .for_each(|(((a, b), at_i), bt_i)| { *a = evaluate_constraint(at_i, full_assignment); *b = evaluate_constraint(bt_i, full_assignment); }); { let start = num_constraints; let end = start + num_inputs; a[start..end].clone_from_slice(&full_assignment[..num_inputs]); } let mut c = vec![zero; domain_size]; cfg_iter_mut!(c[..num_constraints]) .zip(&a) .zip(&b) .for_each(|((c_i, &a), &b)| { *c_i = a * b; }); domain.ifft_in_place(&mut a); domain.ifft_in_place(&mut b); let root_of_unity = { let domain_size_double = 2 * domain_size; let domain_double = D::new(domain_size_double).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; domain_double.element(1) }; D::distribute_powers_and_mul_by_const(&mut a, root_of_unity, F::one()); D::distribute_powers_and_mul_by_const(&mut b, root_of_unity, F::one()); domain.fft_in_place(&mut a); domain.fft_in_place(&mut b); let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &b); drop(a); drop(b); domain.ifft_in_place(&mut c); D::distribute_powers_and_mul_by_const(&mut c, root_of_unity, F::one()); domain.fft_in_place(&mut c); cfg_iter_mut!(ab) .zip(c) .for_each(|(ab_i, c_i)| *ab_i -= &c_i); Ok(ab) } fn h_query_scalars>( max_power: usize, t: F, _: F, delta_inverse: F, ) -> Result, SynthesisError> { // the usual H query has domain-1 powers. Z has domain powers. So HZ has 2*domain-1 powers. let mut scalars = cfg_into_iter!(0..2 * max_power + 1) .map(|i| delta_inverse * t.pow([i as u64])) .collect::>(); let domain_size = scalars.len(); let domain = D::new(domain_size).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; // generate the lagrange coefficients domain.ifft_in_place(&mut scalars); Ok(cfg_into_iter!(scalars).skip(1).step_by(2).collect()) } } ================================================ FILE: crates/ark-circom/src/ethereum.rs ================================================ //! Helpers for converting Arkworks types to U256-tuples as expected by the //! Solidity Groth16 Verifier smart contracts use ark_ff::{BigInteger, PrimeField}; use num_traits::Zero; use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G2Affine}; use ark_serialize::CanonicalDeserialize; use ruint::aliases::U256; use thiserror::Error; #[derive(Error, Debug)] pub enum AffineError { #[error("point is not on curve")] NotOnCurve, #[error("point is not in correct subgroup")] NotInCorrectSubgroup, } pub struct Inputs(pub Vec); impl From<&[Fr]> for Inputs { fn from(src: &[Fr]) -> Self { let els = src.iter().map(|point| point_to_u256(*point)).collect(); Self(els) } } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct G1 { pub x: U256, pub y: U256, } impl TryFrom for G1Affine { type Error = AffineError; fn try_from(value: G1) -> Result { let x: Fq = u256_to_point(value.x); let y: Fq = u256_to_point(value.y); if x.is_zero() && y.is_zero() { Ok(G1Affine::identity()) } else { let point = G1Affine { x, y, infinity: false, }; if !point.is_on_curve() { return Err(AffineError::NotOnCurve); } if !point.is_in_correct_subgroup_assuming_on_curve() { return Err(AffineError::NotInCorrectSubgroup); } Ok(point) } } } type G1Tup = (U256, U256); impl G1 { pub fn as_tuple(&self) -> (U256, U256) { (self.x, self.y) } } impl From<&G1Affine> for G1 { fn from(p: &G1Affine) -> Self { Self { x: point_to_u256(p.x), y: point_to_u256(p.y), } } } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct G2 { pub x: [U256; 2], pub y: [U256; 2], } impl TryFrom for G2Affine { type Error = AffineError; fn try_from(src: G2) -> Result { let c0 = u256_to_point(src.x[0]); let c1 = u256_to_point(src.x[1]); let x = Fq2::new(c0, c1); let c0 = u256_to_point(src.y[0]); let c1 = u256_to_point(src.y[1]); let y = Fq2::new(c0, c1); if x.is_zero() && y.is_zero() { Ok(G2Affine::identity()) } else { let point = G2Affine { x, y, infinity: false, }; if !point.is_on_curve() { return Err(AffineError::NotOnCurve); } if !point.is_in_correct_subgroup_assuming_on_curve() { return Err(AffineError::NotInCorrectSubgroup); } Ok(point) } } } type G2Tup = ([U256; 2], [U256; 2]); impl G2 { // NB: Serialize the c1 limb first. pub fn as_tuple(&self) -> G2Tup { ([self.x[1], self.x[0]], [self.y[1], self.y[0]]) } } impl From<&G2Affine> for G2 { fn from(p: &G2Affine) -> Self { Self { x: [point_to_u256(p.x.c0), point_to_u256(p.x.c1)], y: [point_to_u256(p.y.c0), point_to_u256(p.y.c1)], } } } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Proof { pub a: G1, pub b: G2, pub c: G1, } impl Proof { pub fn as_tuple(&self) -> (G1Tup, G2Tup, G1Tup) { (self.a.as_tuple(), self.b.as_tuple(), self.c.as_tuple()) } } impl From> for Proof { fn from(proof: ark_groth16::Proof) -> Self { Self { a: G1::from(&proof.a), b: G2::from(&proof.b), c: G1::from(&proof.c), } } } impl TryFrom for ark_groth16::Proof { type Error = AffineError; fn try_from(src: Proof) -> Result, AffineError> { Ok(ark_groth16::Proof { a: src.a.try_into()?, b: src.b.try_into()?, c: src.c.try_into()?, }) } } #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct VerifyingKey { pub alpha1: G1, pub beta2: G2, pub gamma2: G2, pub delta2: G2, pub ic: Vec, } impl VerifyingKey { pub fn as_tuple(&self) -> (G1Tup, G2Tup, G2Tup, G2Tup, Vec) { ( self.alpha1.as_tuple(), self.beta2.as_tuple(), self.gamma2.as_tuple(), self.delta2.as_tuple(), self.ic.iter().map(|i| i.as_tuple()).collect(), ) } } impl From> for VerifyingKey { fn from(vk: ark_groth16::VerifyingKey) -> Self { Self { alpha1: G1::from(&vk.alpha_g1), beta2: G2::from(&vk.beta_g2), gamma2: G2::from(&vk.gamma_g2), delta2: G2::from(&vk.delta_g2), ic: vk.gamma_abc_g1.iter().map(G1::from).collect(), } } } impl TryFrom for ark_groth16::VerifyingKey { type Error = AffineError; fn try_from(src: VerifyingKey) -> Result, AffineError> { Ok(ark_groth16::VerifyingKey { alpha_g1: src.alpha1.try_into()?, beta_g2: src.beta2.try_into()?, gamma_g2: src.gamma2.try_into()?, delta_g2: src.delta2.try_into()?, gamma_abc_g1: src .ic .into_iter() .map(TryInto::try_into) .collect::>()?, }) } } // Helper for converting a PrimeField to its U256 representation for Ethereum compatibility fn u256_to_point(point: U256) -> F { let buf: [u8; 32] = point.to_le_bytes(); let bigint = F::BigInt::deserialize_uncompressed(&buf[..]).expect("always works"); F::from_bigint(bigint).expect("always works") } // Helper for converting a PrimeField to its U256 representation for Ethereum compatibility // (U256 reads data as big endian) fn point_to_u256(point: F) -> U256 { let point = point.into_bigint(); let point_bytes = point.to_bytes_be(); U256::try_from_be_slice(&point_bytes[..]).expect("always works") } #[cfg(test)] mod tests { use super::*; use ark_bn254::Fq; use ark_std::UniformRand; fn fq() -> Fq { Fq::from(2) } fn fr() -> Fr { Fr::from(2) } fn g1() -> G1Affine { let rng = &mut ark_std::test_rng(); G1Affine::rand(rng) } fn g2() -> G2Affine { let rng = &mut ark_std::test_rng(); G2Affine::rand(rng) } #[test] fn convert_fq() { let el = fq(); let el2 = point_to_u256(el); let el3: Fq = u256_to_point(el2); let el4 = point_to_u256(el3); assert_eq!(el, el3); assert_eq!(el2, el4); } #[test] fn convert_fr() { let el = fr(); let el2 = point_to_u256(el); let el3: Fr = u256_to_point(el2); let el4 = point_to_u256(el3); assert_eq!(el, el3); assert_eq!(el2, el4); } #[test] fn convert_g1() { let el = g1(); let el2 = G1::from(&el); let el3: G1Affine = el2.try_into().unwrap(); let el4 = G1::from(&el3); assert_eq!(el, el3); assert_eq!(el2, el4); } #[test] fn convert_g2() { let el = g2(); let el2 = G2::from(&el); let el3: G2Affine = el2.try_into().unwrap(); let el4 = G2::from(&el3); assert_eq!(el, el3); assert_eq!(el2, el4); } #[test] fn convert_vk() { let vk = ark_groth16::VerifyingKey:: { alpha_g1: g1(), beta_g2: g2(), gamma_g2: g2(), delta_g2: g2(), gamma_abc_g1: vec![g1(), g1(), g1()], }; let vk_ethers = VerifyingKey::from(vk.clone()); let ark_vk: ark_groth16::VerifyingKey = vk_ethers.try_into().unwrap(); assert_eq!(ark_vk, vk); } #[test] fn convert_proof() { let p = ark_groth16::Proof:: { a: g1(), b: g2(), c: g1(), }; let p2 = Proof::from(p.clone()); let p3 = ark_groth16::Proof::try_from(p2).unwrap(); assert_eq!(p, p3); } } ================================================ FILE: crates/ark-circom/src/lib.rs ================================================ pub mod circom; pub mod ethereum; pub mod zkey; pub use circom::CircomReduction; pub use zkey::read_zkey; ================================================ FILE: crates/ark-circom/src/zkey.rs ================================================ use ark_ff::{BigInteger256, PrimeField}; use ark_relations::r1cs::ConstraintMatrices; use ark_serialize::{CanonicalDeserialize, SerializationError}; use ark_std::log2; use byteorder::{LittleEndian, ReadBytesExt}; use std::{ collections::HashMap, io::{Read, Seek, SeekFrom}, }; use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G2Affine}; use ark_groth16::{ProvingKey, VerifyingKey}; use num_traits::Zero; type IoResult = Result; #[derive(Clone, Debug)] struct Section { position: u64, #[allow(dead_code)] size: usize, } /// Reads a SnarkJS ZKey file into an Arkworks ProvingKey. pub fn read_zkey( reader: &mut R, ) -> IoResult<(ProvingKey, ConstraintMatrices)> { let mut binfile = BinFile::new(reader)?; let proving_key = binfile.proving_key()?; let matrices = binfile.matrices()?; Ok((proving_key, matrices)) } #[derive(Debug)] struct BinFile<'a, R> { #[allow(dead_code)] ftype: String, #[allow(dead_code)] version: u32, sections: HashMap>, reader: &'a mut R, } impl<'a, R: Read + Seek> BinFile<'a, R> { fn new(reader: &'a mut R) -> IoResult { let mut magic = [0u8; 4]; reader.read_exact(&mut magic)?; let version = reader.read_u32::()?; let num_sections = reader.read_u32::()?; let mut sections = HashMap::new(); for _ in 0..num_sections { let section_id = reader.read_u32::()?; let section_length = reader.read_u64::()?; let section = sections.entry(section_id).or_insert_with(Vec::new); section.push(Section { position: reader.stream_position()?, size: section_length as usize, }); reader.seek(SeekFrom::Current(section_length as i64))?; } Ok(Self { ftype: std::str::from_utf8(&magic[..]).unwrap().to_string(), version, sections, reader, }) } fn proving_key(&mut self) -> IoResult> { let header = self.groth_header()?; let ic = self.ic(header.n_public)?; let a_query = self.a_query(header.n_vars)?; let b_g1_query = self.b_g1_query(header.n_vars)?; let b_g2_query = self.b_g2_query(header.n_vars)?; let l_query = self.l_query(header.n_vars - header.n_public - 1)?; let h_query = self.h_query(header.domain_size as usize)?; let vk = VerifyingKey:: { alpha_g1: header.verifying_key.alpha_g1, beta_g2: header.verifying_key.beta_g2, gamma_g2: header.verifying_key.gamma_g2, delta_g2: header.verifying_key.delta_g2, gamma_abc_g1: ic, }; let pk = ProvingKey:: { vk, beta_g1: header.verifying_key.beta_g1, delta_g1: header.verifying_key.delta_g1, a_query, b_g1_query, b_g2_query, h_query, l_query, }; Ok(pk) } fn get_section(&self, id: u32) -> Section { self.sections.get(&id).unwrap()[0].clone() } fn groth_header(&mut self) -> IoResult { let section = self.get_section(2); let header = HeaderGroth::new(&mut self.reader, §ion)?; Ok(header) } fn ic(&mut self, n_public: usize) -> IoResult> { // the range is non-inclusive so we do +1 to get all inputs self.g1_section(n_public + 1, 3) } /// Returns the [`ConstraintMatrices`] corresponding to the zkey pub fn matrices(&mut self) -> IoResult> { let header = self.groth_header()?; let section = self.get_section(4); self.reader.seek(SeekFrom::Start(section.position))?; let num_coeffs: u32 = self.reader.read_u32::()?; // insantiate AB let mut matrices = vec![vec![vec![]; header.domain_size as usize]; 2]; let mut max_constraint_index = 0; for _ in 0..num_coeffs { let matrix: u32 = self.reader.read_u32::()?; let constraint: u32 = self.reader.read_u32::()?; let signal: u32 = self.reader.read_u32::()?; let value: Fr = deserialize_field_fr(&mut self.reader)?; max_constraint_index = std::cmp::max(max_constraint_index, constraint); matrices[matrix as usize][constraint as usize].push((value, signal as usize)); } let num_constraints = max_constraint_index as usize - header.n_public; // Remove the public input constraints, Arkworks adds them later matrices.iter_mut().for_each(|m| { m.truncate(num_constraints); }); // This is taken from Arkworks' to_matrices() function let a = matrices[0].clone(); let b = matrices[1].clone(); let a_num_non_zero: usize = a.iter().map(|lc| lc.len()).sum(); let b_num_non_zero: usize = b.iter().map(|lc| lc.len()).sum(); let matrices = ConstraintMatrices { num_instance_variables: header.n_public + 1, num_witness_variables: header.n_vars - header.n_public, num_constraints, a_num_non_zero, b_num_non_zero, c_num_non_zero: 0, a, b, c: vec![], }; Ok(matrices) } fn a_query(&mut self, n_vars: usize) -> IoResult> { self.g1_section(n_vars, 5) } fn b_g1_query(&mut self, n_vars: usize) -> IoResult> { self.g1_section(n_vars, 6) } fn b_g2_query(&mut self, n_vars: usize) -> IoResult> { self.g2_section(n_vars, 7) } fn l_query(&mut self, n_vars: usize) -> IoResult> { self.g1_section(n_vars, 8) } fn h_query(&mut self, n_vars: usize) -> IoResult> { self.g1_section(n_vars, 9) } fn g1_section(&mut self, num: usize, section_id: usize) -> IoResult> { let section = self.get_section(section_id as u32); self.reader.seek(SeekFrom::Start(section.position))?; deserialize_g1_vec(self.reader, num as u32) } fn g2_section(&mut self, num: usize, section_id: usize) -> IoResult> { let section = self.get_section(section_id as u32); self.reader.seek(SeekFrom::Start(section.position))?; deserialize_g2_vec(self.reader, num as u32) } } #[derive(Default, Clone, Debug, CanonicalDeserialize)] pub struct ZVerifyingKey { alpha_g1: G1Affine, beta_g1: G1Affine, beta_g2: G2Affine, gamma_g2: G2Affine, delta_g1: G1Affine, delta_g2: G2Affine, } impl ZVerifyingKey { fn new(reader: &mut R) -> IoResult { let alpha_g1 = deserialize_g1(reader)?; let beta_g1 = deserialize_g1(reader)?; let beta_g2 = deserialize_g2(reader)?; let gamma_g2 = deserialize_g2(reader)?; let delta_g1 = deserialize_g1(reader)?; let delta_g2 = deserialize_g2(reader)?; Ok(Self { alpha_g1, beta_g1, beta_g2, gamma_g2, delta_g1, delta_g2, }) } } #[derive(Clone, Debug)] struct HeaderGroth { #[allow(dead_code)] n8q: u32, #[allow(dead_code)] q: BigInteger256, #[allow(dead_code)] n8r: u32, #[allow(dead_code)] r: BigInteger256, n_vars: usize, n_public: usize, domain_size: u32, #[allow(dead_code)] power: u32, verifying_key: ZVerifyingKey, } impl HeaderGroth { fn new(reader: &mut R, section: &Section) -> IoResult { reader.seek(SeekFrom::Start(section.position))?; Self::read(reader) } fn read(mut reader: &mut R) -> IoResult { // TODO: Impl From in Arkworks let n8q: u32 = u32::deserialize_uncompressed(&mut reader)?; // group order r of Bn254 let q = BigInteger256::deserialize_uncompressed(&mut reader)?; let n8r: u32 = u32::deserialize_uncompressed(&mut reader)?; // Prime field modulus let r = BigInteger256::deserialize_uncompressed(&mut reader)?; let n_vars = u32::deserialize_uncompressed(&mut reader)? as usize; let n_public = u32::deserialize_uncompressed(&mut reader)? as usize; let domain_size: u32 = u32::deserialize_uncompressed(&mut reader)?; let power = log2(domain_size as usize); let verifying_key = ZVerifyingKey::new(&mut reader)?; Ok(Self { n8q, q, n8r, r, n_vars, n_public, domain_size, power, verifying_key, }) } } // need to divide by R, since snarkjs outputs the zkey with coefficients // multiplieid by R^2 fn deserialize_field_fr(reader: &mut R) -> IoResult { let bigint = BigInteger256::deserialize_uncompressed(reader)?; Ok(Fr::new_unchecked(Fr::new_unchecked(bigint).into_bigint())) } // skips the multiplication by R because Circom points are already in Montgomery form fn deserialize_field(reader: &mut R) -> IoResult { let bigint = BigInteger256::deserialize_uncompressed(reader)?; // if you use Fq::new it multiplies by R Ok(Fq::new_unchecked(bigint)) } pub fn deserialize_field2(reader: &mut R) -> IoResult { let c0 = deserialize_field(reader)?; let c1 = deserialize_field(reader)?; Ok(Fq2::new(c0, c1)) } fn deserialize_g1(reader: &mut R) -> IoResult { let x = deserialize_field(reader)?; let y = deserialize_field(reader)?; let infinity = x.is_zero() && y.is_zero(); if infinity { Ok(G1Affine::identity()) } else { Ok(G1Affine::new(x, y)) } } fn deserialize_g2(reader: &mut R) -> IoResult { let f1 = deserialize_field2(reader)?; let f2 = deserialize_field2(reader)?; let infinity = f1.is_zero() && f2.is_zero(); if infinity { Ok(G2Affine::identity()) } else { Ok(G2Affine::new(f1, f2)) } } fn deserialize_g1_vec(reader: &mut R, n_vars: u32) -> IoResult> { (0..n_vars).map(|_| deserialize_g1(reader)).collect() } fn deserialize_g2_vec(reader: &mut R, n_vars: u32) -> IoResult> { (0..n_vars).map(|_| deserialize_g2(reader)).collect() } #[cfg(test)] mod tests { use super::*; use ark_bn254::{G1Projective, G2Projective}; use num_bigint::BigUint; use serde_json::Value; use std::fs::File; use num_traits::{One, Zero}; use std::str::FromStr; use std::convert::TryFrom; fn fq_from_str(s: &str) -> Fq { BigInteger256::try_from(BigUint::from_str(s).unwrap()) .unwrap() .into() } // Circom snarkjs code: // console.log(curve.G1.F.one) fn fq_buf() -> Vec { vec![ 157, 13, 143, 197, 141, 67, 93, 211, 61, 11, 199, 245, 40, 235, 120, 10, 44, 70, 121, 120, 111, 163, 110, 102, 47, 223, 7, 154, 193, 119, 10, 14, ] } // Circom snarkjs code: // const buff = new Uint8Array(curve.G1.F.n8*2); // curve.G1.toRprLEM(buff, 0, curve.G1.one); // console.dir( buff, { 'maxArrayLength': null }) fn g1_buf() -> Vec { vec![ 157, 13, 143, 197, 141, 67, 93, 211, 61, 11, 199, 245, 40, 235, 120, 10, 44, 70, 121, 120, 111, 163, 110, 102, 47, 223, 7, 154, 193, 119, 10, 14, 58, 27, 30, 139, 27, 135, 186, 166, 123, 22, 142, 235, 81, 214, 241, 20, 88, 140, 242, 240, 222, 70, 221, 204, 94, 190, 15, 52, 131, 239, 20, 28, ] } // Circom snarkjs code: // const buff = new Uint8Array(curve.G2.F.n8*2); // curve.G2.toRprLEM(buff, 0, curve.G2.one); // console.dir( buff, { 'maxArrayLength': null }) fn g2_buf() -> Vec { vec![ 38, 32, 188, 2, 209, 181, 131, 142, 114, 1, 123, 73, 53, 25, 235, 220, 223, 26, 129, 151, 71, 38, 184, 251, 59, 80, 150, 175, 65, 56, 87, 25, 64, 97, 76, 168, 125, 115, 180, 175, 196, 216, 2, 88, 90, 221, 67, 96, 134, 47, 160, 82, 252, 80, 233, 9, 107, 123, 234, 58, 131, 240, 254, 20, 246, 233, 107, 136, 157, 250, 157, 97, 120, 155, 158, 245, 151, 210, 127, 254, 254, 125, 27, 35, 98, 26, 158, 255, 6, 66, 158, 174, 235, 126, 253, 40, 238, 86, 24, 199, 86, 91, 9, 100, 187, 60, 125, 50, 34, 249, 87, 220, 118, 16, 53, 51, 190, 53, 249, 85, 130, 100, 253, 147, 230, 160, 164, 13, ] } // Circom logs in Projective coordinates: console.log(curve.G1.one) fn g1_one() -> G1Affine { let x = Fq::one(); let y = Fq::one() + Fq::one(); let z = Fq::one(); G1Affine::from(G1Projective::new(x, y, z)) } // Circom logs in Projective coordinates: console.log(curve.G2.one) fn g2_one() -> G2Affine { let x = Fq2::new( fq_from_str( "10857046999023057135944570762232829481370756359578518086990519993285655852781", ), fq_from_str( "11559732032986387107991004021392285783925812861821192530917403151452391805634", ), ); let y = Fq2::new( fq_from_str( "8495653923123431417604973247489272438418190587263600148770280649306958101930", ), fq_from_str( "4082367875863433681332203403145435568316851327593401208105741076214120093531", ), ); let z = Fq2::new(Fq::one(), Fq::zero()); G2Affine::from(G2Projective::new(x, y, z)) } #[test] fn can_deser_fq() { let buf = fq_buf(); let fq = deserialize_field(&mut &buf[..]).unwrap(); assert_eq!(fq, Fq::one()); } #[test] fn can_deser_g1() { let buf = g1_buf(); assert_eq!(buf.len(), 64); let g1 = deserialize_g1(&mut &buf[..]).unwrap(); let expected = g1_one(); assert_eq!(g1, expected); } #[test] fn can_deser_g1_vec() { let n_vars = 10; let buf = vec![g1_buf(); n_vars] .iter() .flatten() .cloned() .collect::>(); let expected = vec![g1_one(); n_vars]; let de = deserialize_g1_vec(&mut &buf[..], n_vars as u32).unwrap(); assert_eq!(expected, de); } #[test] fn can_deser_g2() { let buf = g2_buf(); assert_eq!(buf.len(), 128); let g2 = deserialize_g2(&mut &buf[..]).unwrap(); let expected = g2_one(); assert_eq!(g2, expected); } #[test] fn can_deser_g2_vec() { let n_vars = 10; let buf = vec![g2_buf(); n_vars] .iter() .flatten() .cloned() .collect::>(); let expected = vec![g2_one(); n_vars]; let de = deserialize_g2_vec(&mut &buf[..], n_vars as u32).unwrap(); assert_eq!(expected, de); } #[test] fn header() { // `circom --r1cs` using the below file: // // template Multiplier() { // signal private input a; // signal private input b; // signal output c; // // c <== a*b; // } // // component main = Multiplier(); // // Then: // `snarkjs zkey new circuit.r1cs powersOfTau28_hez_final_10.ptau test.zkey` let path = "./test-vectors/test.zkey"; let mut file = File::open(path).unwrap(); let mut binfile = BinFile::new(&mut file).unwrap(); let header = binfile.groth_header().unwrap(); assert_eq!(header.n_vars, 4); assert_eq!(header.n_public, 1); assert_eq!(header.domain_size, 4); assert_eq!(header.power, 2); } #[test] fn deser_key() { let path = "./test-vectors/test.zkey"; let mut file = File::open(path).unwrap(); let (params, _matrices) = read_zkey(&mut file).unwrap(); // Check IC let expected = vec![ deserialize_g1( &mut &[ 11, 205, 205, 176, 2, 105, 129, 243, 153, 58, 137, 89, 61, 95, 99, 161, 133, 201, 153, 192, 119, 19, 113, 136, 43, 105, 47, 206, 166, 55, 81, 22, 154, 77, 58, 119, 28, 230, 160, 206, 134, 98, 4, 115, 112, 184, 46, 117, 61, 180, 103, 138, 141, 202, 110, 252, 199, 252, 141, 211, 5, 46, 244, 10, ][..], ) .unwrap(), deserialize_g1( &mut &[ 118, 135, 198, 156, 63, 190, 210, 98, 194, 59, 169, 168, 204, 168, 76, 208, 109, 170, 24, 193, 57, 31, 184, 88, 234, 218, 118, 58, 107, 129, 90, 36, 230, 98, 62, 243, 3, 55, 68, 227, 117, 64, 188, 81, 81, 247, 161, 68, 68, 210, 142, 191, 174, 43, 110, 194, 253, 128, 217, 4, 54, 196, 111, 43, ][..], ) .unwrap(), ]; assert_eq!(expected, params.vk.gamma_abc_g1); // Check A Query let expected = vec![ deserialize_g1( &mut &[ 240, 165, 110, 187, 72, 39, 218, 59, 128, 85, 50, 174, 229, 1, 86, 58, 125, 244, 145, 205, 248, 253, 120, 2, 165, 140, 154, 55, 220, 253, 14, 19, 212, 106, 59, 19, 125, 198, 202, 4, 59, 74, 14, 62, 20, 248, 219, 47, 234, 205, 54, 183, 33, 119, 165, 84, 46, 75, 39, 17, 229, 42, 192, 2, ][..], ) .unwrap(), deserialize_g1( &mut &[ 93, 53, 177, 82, 50, 5, 123, 116, 91, 35, 14, 196, 43, 180, 54, 15, 88, 144, 197, 105, 57, 167, 54, 5, 188, 109, 17, 89, 9, 223, 80, 1, 39, 193, 211, 168, 203, 119, 169, 105, 17, 156, 53, 106, 11, 102, 44, 92, 123, 220, 158, 240, 97, 253, 30, 121, 4, 236, 171, 23, 100, 34, 133, 11, ][..], ) .unwrap(), deserialize_g1( &mut &[ 177, 47, 21, 237, 244, 73, 76, 98, 80, 10, 10, 142, 80, 145, 40, 254, 100, 214, 103, 33, 38, 84, 238, 248, 252, 181, 75, 32, 109, 16, 93, 23, 135, 157, 206, 122, 107, 105, 202, 164, 197, 124, 242, 100, 70, 108, 9, 180, 224, 102, 250, 149, 130, 14, 133, 185, 132, 189, 193, 230, 180, 143, 156, 30, ][..], ) .unwrap(), deserialize_g1( &mut &[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ][..], ) .unwrap(), ]; assert_eq!(expected, params.a_query); // B G1 Query let expected = vec![ deserialize_g1( &mut &[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ][..], ) .unwrap(), deserialize_g1( &mut &[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ][..], ) .unwrap(), deserialize_g1( &mut &[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ][..], ) .unwrap(), deserialize_g1( &mut &[ 177, 47, 21, 237, 244, 73, 76, 98, 80, 10, 10, 142, 80, 145, 40, 254, 100, 214, 103, 33, 38, 84, 238, 248, 252, 181, 75, 32, 109, 16, 93, 23, 192, 95, 174, 93, 171, 34, 86, 151, 199, 77, 127, 3, 75, 254, 119, 227, 124, 241, 134, 235, 51, 55, 203, 254, 164, 226, 111, 250, 189, 190, 199, 17, ][..], ) .unwrap(), ]; assert_eq!(expected, params.b_g1_query); // B G2 Query let expected = vec![ deserialize_g2( &mut &[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ][..], ) .unwrap(), deserialize_g2( &mut &[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ][..], ) .unwrap(), deserialize_g2( &mut &[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ][..], ) .unwrap(), deserialize_g2( &mut &[ 240, 25, 157, 232, 164, 49, 152, 204, 244, 190, 178, 178, 29, 133, 205, 175, 172, 28, 12, 123, 139, 202, 196, 13, 67, 165, 204, 42, 74, 40, 6, 36, 112, 104, 61, 67, 107, 112, 72, 41, 213, 210, 249, 75, 89, 144, 144, 34, 177, 228, 18, 70, 80, 195, 124, 82, 40, 122, 91, 21, 198, 100, 154, 1, 16, 235, 41, 4, 176, 106, 9, 113, 141, 251, 100, 233, 188, 128, 194, 173, 0, 100, 206, 110, 53, 223, 163, 47, 166, 235, 25, 12, 151, 238, 45, 0, 78, 210, 56, 53, 57, 212, 67, 189, 253, 132, 62, 62, 116, 20, 235, 15, 245, 113, 30, 182, 33, 127, 203, 231, 124, 149, 74, 223, 39, 190, 217, 41, ][..], ) .unwrap(), ]; assert_eq!(expected, params.b_g2_query); // Check L Query let expected = vec![ deserialize_g1( &mut &[ 146, 142, 29, 235, 9, 162, 84, 255, 6, 119, 86, 214, 154, 18, 12, 190, 202, 19, 168, 45, 29, 76, 174, 130, 6, 59, 146, 15, 229, 82, 81, 40, 50, 25, 124, 247, 129, 12, 147, 35, 108, 119, 178, 116, 238, 145, 33, 184, 74, 201, 128, 41, 151, 6, 60, 84, 156, 225, 200, 14, 240, 171, 128, 20, ][..], ) .unwrap(), deserialize_g1( &mut &[ 26, 32, 112, 226, 161, 84, 188, 236, 141, 226, 119, 169, 235, 218, 253, 176, 157, 184, 108, 243, 73, 122, 239, 217, 39, 190, 239, 105, 147, 190, 80, 47, 211, 68, 155, 212, 139, 173, 229, 160, 123, 117, 243, 110, 162, 188, 217, 206, 102, 19, 36, 189, 87, 183, 113, 8, 164, 133, 43, 142, 138, 109, 66, 33, ][..], ) .unwrap(), ]; assert_eq!(expected, params.l_query); // Check H Query let expected = vec![ deserialize_g1( &mut &[ 21, 76, 104, 34, 28, 236, 135, 204, 218, 16, 160, 115, 185, 44, 19, 62, 43, 24, 57, 99, 207, 105, 10, 139, 195, 60, 17, 57, 85, 244, 167, 10, 166, 166, 165, 55, 38, 75, 116, 116, 182, 87, 217, 112, 28, 237, 239, 123, 231, 180, 122, 109, 77, 116, 88, 67, 102, 48, 80, 214, 137, 47, 94, 30, ][..], ) .unwrap(), deserialize_g1( &mut &[ 144, 175, 205, 119, 119, 192, 11, 10, 148, 224, 87, 161, 157, 231, 101, 208, 55, 15, 13, 16, 24, 59, 9, 22, 63, 215, 255, 30, 77, 188, 71, 37, 84, 227, 59, 29, 159, 116, 101, 93, 212, 220, 159, 141, 204, 107, 131, 87, 174, 149, 175, 72, 199, 109, 64, 109, 180, 150, 160, 249, 246, 33, 212, 29, ][..], ) .unwrap(), deserialize_g1( &mut &[ 129, 169, 52, 179, 66, 88, 123, 199, 222, 69, 24, 17, 219, 235, 118, 195, 156, 210, 14, 21, 76, 155, 178, 210, 223, 4, 233, 5, 8, 18, 156, 24, 82, 68, 183, 186, 7, 126, 2, 201, 207, 207, 74, 45, 44, 199, 16, 165, 25, 65, 157, 199, 90, 159, 12, 150, 250, 17, 177, 193, 244, 93, 230, 41, ][..], ) .unwrap(), deserialize_g1( &mut &[ 207, 61, 229, 214, 21, 61, 103, 165, 93, 145, 54, 138, 143, 214, 5, 83, 183, 22, 174, 87, 108, 59, 99, 96, 19, 20, 25, 139, 114, 238, 198, 40, 182, 88, 1, 255, 206, 132, 156, 165, 178, 171, 0, 226, 179, 30, 192, 4, 79, 198, 69, 43, 145, 133, 116, 86, 36, 144, 190, 119, 79, 241, 76, 16, ][..], ) .unwrap(), ]; assert_eq!(expected, params.h_query); } #[test] fn deser_vk() { let path = "./test-vectors/test.zkey"; let mut file = File::open(path).unwrap(); let (params, _matrices) = read_zkey(&mut file).unwrap(); let json = std::fs::read_to_string("./test-vectors/verification_key.json").unwrap(); let json: Value = serde_json::from_str(&json).unwrap(); assert_eq!(json_to_g1(&json, "vk_alpha_1"), params.vk.alpha_g1); assert_eq!(json_to_g2(&json, "vk_beta_2"), params.vk.beta_g2); assert_eq!(json_to_g2(&json, "vk_gamma_2"), params.vk.gamma_g2); assert_eq!(json_to_g2(&json, "vk_delta_2"), params.vk.delta_g2); assert_eq!(json_to_g1_vec(&json, "IC"), params.vk.gamma_abc_g1); } fn json_to_g1(json: &Value, key: &str) -> G1Affine { let els: Vec = json .get(key) .unwrap() .as_array() .unwrap() .iter() .map(|i| i.as_str().unwrap().to_string()) .collect(); G1Affine::from(G1Projective::new( fq_from_str(&els[0]), fq_from_str(&els[1]), fq_from_str(&els[2]), )) } fn json_to_g1_vec(json: &Value, key: &str) -> Vec { let els: Vec> = json .get(key) .unwrap() .as_array() .unwrap() .iter() .map(|i| { i.as_array() .unwrap() .iter() .map(|x| x.as_str().unwrap().to_string()) .collect::>() }) .collect(); els.iter() .map(|coords| { G1Affine::from(G1Projective::new( fq_from_str(&coords[0]), fq_from_str(&coords[1]), fq_from_str(&coords[2]), )) }) .collect() } fn json_to_g2(json: &Value, key: &str) -> G2Affine { let els: Vec> = json .get(key) .unwrap() .as_array() .unwrap() .iter() .map(|i| { i.as_array() .unwrap() .iter() .map(|x| x.as_str().unwrap().to_string()) .collect::>() }) .collect(); let x = Fq2::new(fq_from_str(&els[0][0]), fq_from_str(&els[0][1])); let y = Fq2::new(fq_from_str(&els[1][0]), fq_from_str(&els[1][1])); let z = Fq2::new(fq_from_str(&els[2][0]), fq_from_str(&els[2][1])); G2Affine::from(G2Projective::new(x, y, z)) } } ================================================ FILE: crates/ark-circom/test-vectors/verification_key.json ================================================ { "protocol": "groth16", "curve": "bn128", "nPublic": 1, "vk_alpha_1": [ "20491192805390485299153009773594534940189261866228447918068658471970481763042", "9383485363053290200918347156157836566562967994039712273449902621266178545958", "1" ], "vk_beta_2": [ [ "6375614351688725206403948262868962793625744043794305715222011528459656738731", "4252822878758300859123897981450591353533073413197771768651442665752259397132" ], [ "10505242626370262277552901082094356697409835680220590971873171140371331206856", "21847035105528745403288232691147584728191162732299865338377159692350059136679" ], [ "1", "0" ] ], "vk_gamma_2": [ [ "10857046999023057135944570762232829481370756359578518086990519993285655852781", "11559732032986387107991004021392285783925812861821192530917403151452391805634" ], [ "8495653923123431417604973247489272438418190587263600148770280649306958101930", "4082367875863433681332203403145435568316851327593401208105741076214120093531" ], [ "1", "0" ] ], "vk_delta_2": [ [ "10857046999023057135944570762232829481370756359578518086990519993285655852781", "11559732032986387107991004021392285783925812861821192530917403151452391805634" ], [ "8495653923123431417604973247489272438418190587263600148770280649306958101930", "4082367875863433681332203403145435568316851327593401208105741076214120093531" ], [ "1", "0" ] ], "vk_alphabeta_12": [ [ [ "2029413683389138792403550203267699914886160938906632433982220835551125967885", "21072700047562757817161031222997517981543347628379360635925549008442030252106" ], [ "5940354580057074848093997050200682056184807770593307860589430076672439820312", "12156638873931618554171829126792193045421052652279363021382169897324752428276" ], [ "7898200236362823042373859371574133993780991612861777490112507062703164551277", "7074218545237549455313236346927434013100842096812539264420499035217050630853" ] ], [ [ "7077479683546002997211712695946002074877511277312570035766170199895071832130", "10093483419865920389913245021038182291233451549023025229112148274109565435465" ], [ "4595479056700221319381530156280926371456704509942304414423590385166031118820", "19831328484489333784475432780421641293929726139240675179672856274388269393268" ], [ "11934129596455521040620786944827826205713621633706285934057045369193958244500", "8037395052364110730298837004334506829870972346962140206007064471173334027475" ] ] ], "IC": [ [ "6819801395408938350212900248749732364821477541620635511814266536599629892365", "9092252330033992554755034971584864587974280972948086568597554018278609861372", "1" ], [ "17882351432929302592725330552407222299541667716607588771282887857165175611387", "18907419617206324833977586007131055763810739835484972981819026406579664278293", "1" ] ] } ================================================ FILE: crates/ark-zkey/.gitignore ================================================ # Generated by Cargo # will have compiled files and executables debug/ target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb ================================================ FILE: crates/ark-zkey/Cargo.toml ================================================ [package] name = "semaphore-rs-ark-zkey" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] semaphore-rs-ark-circom.workspace = true color-eyre.workspace = true memmap2.workspace = true ark-serialize.workspace = true ark-bn254.workspace = true ark-groth16.workspace = true ark-relations.workspace = true ark-ff.workspace = true ark-ec.workspace = true ================================================ FILE: crates/ark-zkey/README.md ================================================ # ark-zkey Library to read `zkey` faster by serializing to `arkworks` friendly format. See https://github.com/oskarth/mopro/issues/25 for context. ## To generate arkzkey Hacky, but the way we generate `arkzkey` now is by running the corresponding test. Note that we also neeed to change the const `ZKEY_BYTES` above. E.g.: ``` cargo test multiplier2 --release -- --nocapture cargo test keccak256 --release -- --nocapture cargo test rsa --release -- --nocapture ``` Will take corresponding `zkey` and put `arkzkey`` in same folder. ## Multiplier NOTE: Need to change const ZKEY here `cargo test multiplier2 --release -- --nocapture` ``` running 1 test [build] Processing zkey data... [build] Time to process zkey data: 3.513041ms [build] Serializing proving key and constraint matrices [build] Time to serialize proving key and constraint matrices: 42ns [build] Writing arkzkey to: ../mopro-core/examples/circom/multiplier2/target/multiplier2_final.arkzkey [build] Time to write arkzkey: 1.884875ms Reading arkzkey from: ../mopro-core/examples/circom/multiplier2/target/multiplier2_final.arkzkey Time to open arkzkey file: 18.084µs Time to mmap arkzkey: 8.542µs Time to deserialize proving key: 305.75µs Time to deserialize matrices: 5µs Time to read arkzkey: 348.083µs test tests::test_multiplier2_serialization_deserialization ... ok ``` Naive test: `cargo test naive --release -- --nocapture` (with right zkey constant). **Result: `350µs` vs naive `3.3ms`** ## Keccak NOTE: Need to change const ZKEY here `cargo test keccak256 --release -- --nocapture` ``` [build] Processing zkey data... test tests::test_keccak256_serialization_deserialization has been running for over 60 seconds [build]Time to process zkey data: 158.753181958s [build] Serializing proving key and constraint matrices [build] Time to serialize proving key and constraint matrices: 42ns [build] Writing arkzkey to: ../mopro-core/examples/circom/keccak256/target/keccak256_256_test_final.arkzkey [build] Time to write arkzkey: 16.204274125s Reading arkzkey from: ../mopro-core/examples/circom/keccak256/target/keccak256_256_test_final.arkzkey Time to open arkzkey file: 51.75µs Time to mmap arkzkey: 17.25µs Time to deserialize proving key: 18.323550083s Time to deserialize matrices: 46.935792ms Time to read arkzkey: 18.3730695s test tests::test_keccak256_serialization_deserialization ... ok ``` Vs naive: `[build] Time to process zkey data: 158.753181958s` **Result: 18s vs 158s** ================================================ FILE: crates/ark-zkey/src/lib.rs ================================================ use std::fs::File; use std::io::BufReader; use std::path::PathBuf; use ark_bn254::{Bn254, Fr}; use ark_ff::Field; use ark_groth16::ProvingKey; use ark_relations::r1cs::ConstraintMatrices; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use color_eyre::eyre::{Result, WrapErr}; use semaphore_rs_ark_circom::read_zkey; #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)] pub struct SerializableProvingKey(pub ProvingKey); #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)] pub struct SerializableMatrix { pub data: Vec>, } #[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)] pub struct SerializableConstraintMatrices { pub num_instance_variables: usize, pub num_witness_variables: usize, pub num_constraints: usize, pub a_num_non_zero: usize, pub b_num_non_zero: usize, pub c_num_non_zero: usize, pub a: SerializableMatrix, pub b: SerializableMatrix, pub c: SerializableMatrix, } // TODO: Return ProvingKey, ConstraintMatrices? pub fn read_arkzkey_from_bytes( arkzkey_bytes: &[u8], ) -> Result<(ProvingKey, ConstraintMatrices)> { let mut cursor = std::io::Cursor::new(arkzkey_bytes); let serialized_proving_key = SerializableProvingKey::deserialize_compressed_unchecked(&mut cursor) .wrap_err("Failed to deserialize proving key")?; let serialized_constraint_matrices = SerializableConstraintMatrices::deserialize_compressed_unchecked(&mut cursor) .wrap_err("Failed to deserialize constraint matrices")?; // Get on right form for API let proving_key: ProvingKey = serialized_proving_key.0; let constraint_matrices: ConstraintMatrices = ConstraintMatrices { num_instance_variables: serialized_constraint_matrices.num_instance_variables, num_witness_variables: serialized_constraint_matrices.num_witness_variables, num_constraints: serialized_constraint_matrices.num_constraints, a_num_non_zero: serialized_constraint_matrices.a_num_non_zero, b_num_non_zero: serialized_constraint_matrices.b_num_non_zero, c_num_non_zero: serialized_constraint_matrices.c_num_non_zero, a: serialized_constraint_matrices.a.data, b: serialized_constraint_matrices.b.data, c: serialized_constraint_matrices.c.data, }; Ok((proving_key, constraint_matrices)) } pub fn read_proving_key_and_matrices_from_zkey( zkey_path: &str, ) -> Result<(SerializableProvingKey, SerializableConstraintMatrices)> { let zkey_file_path = PathBuf::from(zkey_path); let zkey_file = File::open(zkey_file_path).wrap_err("Failed to open zkey file")?; let mut buf_reader = BufReader::new(zkey_file); let (proving_key, matrices) = read_zkey(&mut buf_reader).wrap_err("Failed to read zkey file")?; let serializable_proving_key = SerializableProvingKey(proving_key); let serializable_constrain_matrices = SerializableConstraintMatrices { num_instance_variables: matrices.num_instance_variables, num_witness_variables: matrices.num_witness_variables, num_constraints: matrices.num_constraints, a_num_non_zero: matrices.a_num_non_zero, b_num_non_zero: matrices.b_num_non_zero, c_num_non_zero: matrices.c_num_non_zero, a: SerializableMatrix { data: matrices.a }, b: SerializableMatrix { data: matrices.b }, c: SerializableMatrix { data: matrices.c }, }; Ok((serializable_proving_key, serializable_constrain_matrices)) } pub fn convert_zkey( proving_key: SerializableProvingKey, constraint_matrices: SerializableConstraintMatrices, arkzkey_path: &str, ) -> Result<()> { let arkzkey_file_path = PathBuf::from(arkzkey_path); let mut file = File::create(&arkzkey_file_path) .wrap_err("Failed to create serialized proving key file")?; proving_key .serialize_compressed(&mut file) .wrap_err("Failed to serialize proving key")?; constraint_matrices .serialize_compressed(&mut file) .wrap_err("Failed to serialize constraint matrices")?; Ok(()) } #[cfg(test)] mod tests { use std::time::Instant; use super::*; #[test] fn test_read_arkzkey_from_bytes() -> Result<()> { const ARKZKEY_BYTES: &[u8] = include_bytes!("./semaphore.16.arkzkey"); println!("Reading arkzkey from bytes (keccak)"); let now = Instant::now(); let (_deserialized_proving_key, _deserialized_constraint_matrices) = read_arkzkey_from_bytes(ARKZKEY_BYTES)?; println!("Time to read arkzkey: {:?}", now.elapsed()); Ok(()) } } ================================================ FILE: crates/circom-witness-rs/.gitignore ================================================ /target *_cpp *.new .DS_Store circuit.cc constants.dat .idea ================================================ FILE: crates/circom-witness-rs/Cargo.toml ================================================ [package] name = "semaphore-rs-witness" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] ark-bn254 = { workspace = true, features = ["std"] } ark-ff = { workspace = true, features = ["std"] } ark-serialize.workspace = true byteorder.workspace = true color-eyre.workspace = true hex.workspace = true postcard = { workspace = true, features = ["use-std"] } rand.workspace = true ruint.workspace = true serde.workspace = true serde_json.workspace = true [build-dependencies] cxx-build.workspace = true [features] build-witness = [] ================================================ FILE: crates/circom-witness-rs/LICENSE ================================================ MIT License Copyright (c) 2023 Philipp Sippl 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. ================================================ FILE: crates/circom-witness-rs/README.md ================================================ # 🏎️ circom-witness-rs ## Description This crate provides a fast witness generator for Circom circuits, serving as a drop-in replacement for Circom's witness generator. It was created in response to the slow performance of Circom's WASM generator for larger circuits, which also necessitates a WASM runtime, often a cumbersome requirement. The native C++ generator, though faster, depends on x86 assembly for field operations, rendering it impractical for use on other platforms (e.g., cross-compiling to ARM for mobile devices). `circom-witness-rs` comes with two modes: 1. Generate the static execution graph required for the witness generation at build time (`--features=build-witness`). 2. Generate the witness elements at runtime from serialized graph. In the first mode, it generates the c++ version of the witness generator through circom and links itself against it. The c++ code is made accessible to rust through [`cxx`](https://github.com/dtolnay/cxx). It hooks all field functions (which are x86 assembly in the original generator), such that it can recreate the execution graph through symblic execution. The execution graph is further optimized through constant propagation and dead code elimination. The resulting graph is then serialized to a binary format. At runtime, the graph can be embedded in the binary and interpreted to generate the witness. ## Usage See this [example project](https://github.com/philsippl/semaphore-witness-example) for Semaphore with more details on building. See `semaphore-rs` for an [example at runtime](https://github.com/worldcoin/semaphore-rs/blob/62f556bdc1a2a25021dcccc97af4dfa522ab5789/src/protocol/mod.rs#L161-L163). All of those example were used with `circom compiler 2.1.6` ([dcf7d68](https://github.com/iden3/circom/tree/dcf7d687a81c6d9b3e3840181fd83cdaf5f4ac05)). Using a different version of circom might cause issues due to different c++ code being generated. ## Benchmarks ### [semaphore-rs](https://github.com/worldcoin/semaphore-rs/tree/main) **TLDR: For semaphore circuit (depth 30) `circom-witness-rs` is ~25x faster than wasm and ~10x faster than native c++ version.** ``` cargo bench --bench=criterion --features=bench,depth_30 ``` With `circom-witness-rs`:q ``` witness_30 time: [993.84 µs 996.62 µs 999.42 µs] ``` With wasm witness generator from [`circom-compat`](https://github.com/arkworks-rs/circom-compat/blob/master/src/witness/witness_calculator.rs): ``` witness_30 time: [24.630 ms 24.693 ms 24.759 ms] ``` With native c++ witness generator from circom: `9.640ms` As a nice side effect of the graph optimizations, the binary size is also reduced heavily. In the example of Semaphore the binary size is reduced from `1.3MB` (`semaphore.wasm`) to `350KB` (`graph.bin`). ## Unimplemented features There are still quite a few missing operations that need to be implemented. The list of supported and unsupported operations can be found here. Support for the missing operations is very straighfoward and will be added in the future. https://github.com/philsippl/circom-witness-rs/blob/e889cedde49a8929812b825aede55d9668118302/src/generate.rs#L61-L89 ================================================ FILE: crates/circom-witness-rs/build.rs ================================================ use std::{env, fs, path::Path, process::Command}; fn main() { if cfg!(feature = "build-witness") { let witness_cpp = env::var("WITNESS_CPP").unwrap(); let circuit_file = Path::new(&witness_cpp); let circuit_name = circuit_file.file_stem().unwrap().to_str().unwrap(); let status = Command::new("circom") .args([ fs::canonicalize(circuit_file).unwrap().to_str().unwrap(), "--c", ]) .status() .unwrap(); assert!(status.success()); let cpp = Path::new("./") .join(circuit_name.to_owned() + "_cpp") .join(circuit_name.to_owned() + ".cpp"); println!("cargo:warning=\"{}\"", cpp.to_str().unwrap()); let status = Command::new("./script/replace.sh") .arg(cpp.to_str().unwrap()) .status() .unwrap(); assert!(status.success()); cxx_build::bridge("src/generate.rs") .file("src/circuit.cc") .flag_if_supported("-std=c++14") .flag_if_supported("-w") .flag_if_supported("-d") .flag_if_supported("-g") .compile("witness"); println!("cargo:rerun-if-changed=src/main.rs"); println!("cargo:rerun-if-changed=src/circuit.cc"); println!("cargo:rerun-if-changed=include/circuit.h"); } } ================================================ FILE: crates/circom-witness-rs/include/witness.h ================================================ #pragma once #include "rust/cxx.h" #include typedef unsigned long long u64; typedef uint32_t u32; typedef uint8_t u8; struct Circom_CalcWit; void run(Circom_CalcWit *buf); uint get_size_of_io_map(); uint get_total_signal_no(); uint get_main_input_signal_no(); uint get_main_input_signal_start(); uint get_number_of_components(); uint get_size_of_constants(); uint get_size_of_input_hashmap(); uint get_size_of_witness(); ================================================ FILE: crates/circom-witness-rs/script/replace.sh ================================================ #!/bin/sh # Check for input file if [ "$#" -ne 1 ]; then echo "Usage: $0 " exit 1 fi filename=$(basename "$1" .cpp) # Add header cat < "$filename.new" #include "witness/include/witness.h" #include "witness/src/generate.rs.h" /// We need this accessor since cxx doesn't support hashmaps yet class IOSignalInfoAccessor { private: Circom_CalcWit *calcWitContext; public: explicit IOSignalInfoAccessor(Circom_CalcWit *calcWit) : calcWitContext(calcWit) {} auto operator[](size_t index) const -> decltype(auto) { return (calcWitContext ->templateInsId2IOSignalInfoList)[index % get_size_of_input_hashmap()]; } }; typedef void (*Circom_TemplateFunction)(uint __cIdx, Circom_CalcWit* __ctx); ////////////////////////////////////////////////////////////////// /// Generated code from circom compiler below ////////////////////////////////////////////////////////////////// EOT # Replace a few things we can't do in cxx sed -e 's/FrElement\* signalValues/rust::Vec \&signalValues/g' \ -e 's/std::string/rust::string/g' \ -e 's/ctx->templateInsId2IOSignalInfo/IOSignalInfoAccessor(ctx)/g' \ -e 's/u32\* mySubcomponents/rust::Vec mySubcomponents/g' \ -e 's/FrElement\* circuitConstants/rust::Vec \&circuitConstants/g' \ -e 's/rust::string\* listOfTemplateMessages/rust::Vec \&listOfTemplateMessages/g' \ -e 's/FrElement expaux\[\([0-9]*\)\];/rust::Vec expaux = create_vec(\1);/g' \ -e 's/FrElement lvar\[\([0-9]*\)\];/rust::Vec lvar = create_vec(\1);/g' \ -e 's/FrElement lvarcall\[\([0-9]*\)\];/rust::Vec lvarcall = create_vec(\1);/g' \ -e 's/PFrElement aux_dest/FrElement \*aux_dest/g' \ -e 's/subcomponents = new uint\[\([0-9]*\)\];/subcomponents = create_vec_u32(\1);/g' \ -e '/trace/d' \ -e 's/\(ctx,\)\(lvarcall,\)\(myId,\)/\1\&\2\3/g' \ -e '/^#include/d' \ -e '/assert/d' \ -e '/mySubcomponentsParallel/d' \ -e 's/FrElement lvarcall\[\([0-9]*\)\];/rust::Vec lvarcall = create_vec(\1);/g' \ -e 's/,FrElement\* lvar,/,rust::Vec\& lvar,/g' \ -e 's/ctx,\&lvarcall,myId,/ctx,lvarcall,myId,/g' \ -e '/delete \[\][^;]*;/d' -e 'N;/\ndelete/!P;D' \ -e '/^#include/d' "$1" >> "$filename.new" sed -E -e 's/"([^"]+)"\+ctx->generate_position_array\(([^)]+)\)/generate_position_array("\1", \2)/g' \ -e 's/subcomponents = new uint\[([0-9]+)\]\{0\};/subcomponents = create_vec_u32(\1);/g' \ -e 's/^uint aux_dimensions\[([0-9]+)\] = \{([^}]+)\};$/rust::Vec aux_dimensions = rust::Vec{\2};/' "$filename.new" > "src/circuit.cc" cp "$(echo $filename)_cpp/$filename.dat" src/constants.dat ================================================ FILE: crates/circom-witness-rs/src/field.rs ================================================ #![allow(unused, non_snake_case)] use crate::graph::{Node, Operation}; use ruint::{aliases::U256, uint}; use std::{ptr, sync::Mutex}; pub const M: U256 = uint!(21888242871839275222246405745257275088548364400416034343698204186575808495617_U256); pub const INV: u64 = 14042775128853446655; pub const R: U256 = uint!(0x0e0a77c19a07df2f666ea36f7879462e36fc76959f60cd29ac96341c4ffffffb_U256); static NODES: Mutex> = Mutex::new(Vec::new()); static VALUES: Mutex> = Mutex::new(Vec::new()); static CONSTANT: Mutex> = Mutex::new(Vec::new()); #[derive(Debug, Default, Clone, Copy)] pub struct FrElement(pub usize); pub fn print_eval() { let nodes = NODES.lock().unwrap(); let values = VALUES.lock().unwrap(); let constant = CONSTANT.lock().unwrap(); let mut constants = 0_usize; for (i, node) in nodes.iter().enumerate() { print!("{}: {:?}", i, node); if constant[i] { constants += 1; println!(" = {}", values[i]); } else { println!(); } } eprintln!( "{} nodes of which {} constant and {} dynamic", nodes.len(), constants, nodes.len() - constants ); } pub fn get_graph() -> Vec { NODES.lock().unwrap().clone() } pub fn get_values() -> Vec { VALUES.lock().unwrap().clone() } pub fn undefined() -> FrElement { FrElement(usize::MAX) } pub fn constant(c: U256) -> FrElement { let mut nodes = NODES.lock().unwrap(); let mut values = VALUES.lock().unwrap(); let mut constant = CONSTANT.lock().unwrap(); assert_eq!(nodes.len(), values.len()); assert_eq!(nodes.len(), constant.len()); nodes.push(Node::Constant(c)); values.push(c); constant.push(true); FrElement(nodes.len() - 1) } pub fn input(i: usize, value: U256) -> FrElement { let mut nodes = NODES.lock().unwrap(); let mut values = VALUES.lock().unwrap(); let mut constant = CONSTANT.lock().unwrap(); assert_eq!(nodes.len(), values.len()); assert_eq!(nodes.len(), constant.len()); nodes.push(Node::Input(i)); values.push(value); constant.push(false); FrElement(nodes.len() - 1) } fn binop(op: Operation, to: *mut FrElement, a: *const FrElement, b: *const FrElement) { let mut nodes = NODES.lock().unwrap(); let mut values = VALUES.lock().unwrap(); let mut constant = CONSTANT.lock().unwrap(); assert_eq!(nodes.len(), values.len()); assert_eq!(nodes.len(), constant.len()); let (a, b, to) = unsafe { ((*a).0, (*b).0, &mut (*to).0) }; assert!(a < nodes.len()); assert!(b < nodes.len()); nodes.push(Node::Op(op, a, b)); *to = nodes.len() - 1; let (va, vb) = (values[a], values[b]); values.push(op.eval(va, vb)); let (ca, cb) = (constant[a], constant[b]); constant.push(ca && cb); } pub fn Fr_mul(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Mul, to, a, b); } #[allow(warnings)] pub unsafe fn Fr_add(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Add, to, a, b); } #[allow(warnings)] pub unsafe fn Fr_sub(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Sub, to, a, b); } #[allow(warnings)] pub fn Fr_copy(to: *mut FrElement, a: *const FrElement) { unsafe { *to = *a; } } #[allow(warnings)] pub fn Fr_copyn(to: *mut FrElement, a: *const FrElement, n: usize) { unsafe { ptr::copy_nonoverlapping(a, to, n); } } /// Create a vector of FrElement with length `len`. /// Needed because the default constructor of opaque type is not implemented. pub fn create_vec(len: usize) -> Vec { vec![FrElement(usize::MAX); len] } pub fn create_vec_u32(len: usize) -> Vec { vec![0; len] } pub fn generate_position_array( prefix: String, dimensions: Vec, size_dimensions: u32, index: u32, ) -> String { let mut positions: String = prefix; let mut index = index; for i in 0..size_dimensions { let last_pos = index % dimensions[size_dimensions as usize - 1 - i as usize]; index /= dimensions[size_dimensions as usize - 1 - i as usize]; let new_pos = format!("[{}]", last_pos); positions = new_pos + &positions; } positions } pub unsafe fn Fr_toInt(a: *const FrElement) -> u64 { let nodes = NODES.lock().unwrap(); let values = VALUES.lock().unwrap(); let constant = CONSTANT.lock().unwrap(); assert_eq!(nodes.len(), values.len()); assert_eq!(nodes.len(), constant.len()); let a = unsafe { (*a).0 }; assert!(a < nodes.len()); assert!(constant[a]); values[a].try_into().unwrap() } pub unsafe fn print(a: *const FrElement) { println!("DEBUG>> {:?}", (*a).0); } pub fn Fr_isTrue(a: *mut FrElement) -> bool { let nodes = NODES.lock().unwrap(); let values = VALUES.lock().unwrap(); let constant = CONSTANT.lock().unwrap(); assert_eq!(nodes.len(), values.len()); assert_eq!(nodes.len(), constant.len()); let a = unsafe { (*a).0 }; assert!(a < nodes.len()); assert!(constant[a]); values[a] != U256::ZERO } pub unsafe fn Fr_eq(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Eq, to, a, b); } pub unsafe fn Fr_neq(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Neq, to, a, b); } pub unsafe fn Fr_lt(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Lt, to, a, b); } pub unsafe fn Fr_gt(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Gt, to, a, b); } pub unsafe fn Fr_leq(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Leq, to, a, b); } pub unsafe fn Fr_geq(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Geq, to, a, b); } pub unsafe fn Fr_lor(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Lor, to, a, b); } pub unsafe fn Fr_shl(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Shl, to, a, b); } pub unsafe fn Fr_shr(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Shr, to, a, b); } pub unsafe fn Fr_band(to: *mut FrElement, a: *const FrElement, b: *const FrElement) { binop(Operation::Band, to, a, b); } ================================================ FILE: crates/circom-witness-rs/src/generate.rs ================================================ #![allow(non_snake_case)] use crate::field::{self, *}; use crate::graph::{self, Node}; use crate::HashSignalInfo; use byteorder::{LittleEndian, ReadBytesExt}; use ffi::InputOutputList; use ruint::{aliases::U256, uint}; use serde::{Deserialize, Serialize}; use std::{io::Read, time::Instant}; #[cxx::bridge] mod ffi { #[derive(Debug, Default, Clone)] pub struct InputOutputList { pub defs: Vec, } #[derive(Debug, Clone, Default)] pub struct IODef { pub code: usize, pub offset: usize, pub lengths: Vec, } #[derive(Debug, Default, Clone)] struct Circom_Component { templateId: u64, signalStart: u64, inputCounter: u64, templateName: String, componentName: String, idFather: u64, subcomponents: Vec, outputIsSet: Vec, } #[derive(Debug)] struct Circom_CalcWit { signalValues: Vec, componentMemory: Vec, circuitConstants: Vec, templateInsId2IOSignalInfoList: Vec, listOfTemplateMessages: Vec, } // Rust types and signatures exposed to C++. extern "Rust" { type FrElement; fn create_vec(len: usize) -> Vec; fn create_vec_u32(len: usize) -> Vec; fn generate_position_array( prefix: String, dimensions: Vec, size_dimensions: u32, index: u32, ) -> String; // Field operations unsafe fn Fr_mul(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_add(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_sub(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_copy(to: *mut FrElement, a: *const FrElement); unsafe fn Fr_copyn(to: *mut FrElement, a: *const FrElement, n: usize); // unsafe fn Fr_neg(to: *mut FrElement, a: *const FrElement); // unsafe fn Fr_inv(to: *mut FrElement, a: *const FrElement); // unsafe fn Fr_div(to: *mut FrElement, a: *const FrElement, b: *const FrElement); // unsafe fn Fr_square(to: *mut FrElement, a: *const FrElement); unsafe fn Fr_shl(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_shr(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_band(to: *mut FrElement, a: *const FrElement, b: *const FrElement); // fn Fr_bor(to: &mut FrElement, a: &FrElement, b: &FrElement); // fn Fr_bxor(to: &mut FrElement, a: &FrElement, b: &FrElement); // fn Fr_bnot(to: &mut FrElement, a: &FrElement); unsafe fn Fr_eq(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_neq(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_lt(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_gt(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_leq(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_geq(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn Fr_isTrue(a: *mut FrElement) -> bool; // fn Fr_fromBool(to: &mut FrElement, a: bool); unsafe fn Fr_toInt(a: *mut FrElement) -> u64; unsafe fn Fr_lor(to: *mut FrElement, a: *const FrElement, b: *const FrElement); unsafe fn print(a: *mut FrElement); // fn Fr_pow(to: &mut FrElement, a: &FrElement, b: &FrElement); // fn Fr_idiv(to: &mut FrElement, a: &FrElement, b: &FrElement); } // C++ types and signatures exposed to Rust. unsafe extern "C++" { include!("witness/include/witness.h"); unsafe fn run(ctx: *mut Circom_CalcWit); fn get_size_of_io_map() -> u32; fn get_total_signal_no() -> u32; fn get_main_input_signal_no() -> u32; fn get_main_input_signal_start() -> u32; fn get_number_of_components() -> u32; fn get_size_of_constants() -> u32; fn get_size_of_input_hashmap() -> u32; fn get_size_of_witness() -> u32; } } const DAT_BYTES: &[u8] = include_bytes!("constants.dat"); pub fn get_input_hash_map() -> Vec { let mut bytes = &DAT_BYTES[..(ffi::get_size_of_input_hashmap() as usize) * 24]; let mut input_hash_map = vec![HashSignalInfo::default(); ffi::get_size_of_input_hashmap() as usize]; for i in 0..ffi::get_size_of_input_hashmap() as usize { let hash = bytes.read_u64::().unwrap(); let signalid = bytes.read_u64::().unwrap(); let signalsize = bytes.read_u64::().unwrap(); input_hash_map[i] = HashSignalInfo { hash, signalid, signalsize, }; } input_hash_map } pub fn get_witness_to_signal() -> Vec { let mut bytes = &DAT_BYTES[(ffi::get_size_of_input_hashmap() as usize) * 24 ..(ffi::get_size_of_input_hashmap() as usize) * 24 + (ffi::get_size_of_witness() as usize) * 8]; let mut signal_list = Vec::with_capacity(ffi::get_size_of_witness() as usize); for i in 0..ffi::get_size_of_witness() as usize { signal_list.push(bytes.read_u64::().unwrap() as usize); } signal_list } pub fn get_constants() -> Vec { if ffi::get_size_of_constants() == 0 { return vec![]; } // skip the first part let mut bytes = &DAT_BYTES[(ffi::get_size_of_input_hashmap() as usize) * 24 + (ffi::get_size_of_witness() as usize) * 8..]; let mut constants = vec![field::constant(U256::from(0)); ffi::get_size_of_constants() as usize]; for i in 0..ffi::get_size_of_constants() as usize { let sv = bytes.read_i32::().unwrap() as i32; let typ = bytes.read_u32::().unwrap() as u32; let mut buf = [0; 32]; bytes.read_exact(&mut buf); if typ & 0x80000000 == 0 { constants[i] = field::constant(U256::from(sv)); } else { constants[i] = field::constant(U256::from_le_bytes(buf).mul_redc(uint!(1_U256), M, INV)); } } return constants; } pub fn get_iosignals() -> Vec { if ffi::get_size_of_io_map() == 0 { return vec![]; } // skip the first part let mut bytes = &DAT_BYTES[(ffi::get_size_of_input_hashmap() as usize) * 24 + (ffi::get_size_of_witness() as usize) * 8 + (ffi::get_size_of_constants() as usize * 40)..]; let io_size = ffi::get_size_of_io_map() as usize; let hashmap_size = ffi::get_size_of_input_hashmap() as usize; let mut indices = vec![0usize; io_size]; let mut map: Vec = vec![InputOutputList::default(); hashmap_size]; (0..io_size).for_each(|i| { let t32 = bytes.read_u32::().unwrap() as usize; indices[i] = t32; }); (0..io_size).for_each(|i| { let l32 = bytes.read_u32::().unwrap() as usize; let mut io_list: InputOutputList = InputOutputList { defs: vec![] }; (0..l32).for_each(|_j| { let offset = bytes.read_u32::().unwrap() as usize; let len = bytes.read_u32::().unwrap() as usize + 1; let mut lengths = vec![0usize; len]; (1..len).for_each(|k| { lengths[k] = bytes.read_u32::().unwrap() as usize; }); io_list.defs.push(ffi::IODef { code: 0, offset, lengths, }); }); map[indices[i] % hashmap_size] = io_list; }); map } /// Run cpp witness generator and optimize graph pub fn build_witness() -> color_color_eyre::Result<()> { let mut signal_values = vec![field::undefined(); ffi::get_total_signal_no() as usize]; signal_values[0] = field::constant(uint!(1_U256)); let total_input_len = (ffi::get_main_input_signal_no() + ffi::get_main_input_signal_start()) as usize; for i in 0..total_input_len { signal_values[i + 1] = field::input(i + 1, uint!(0_U256)); } let mut ctx = ffi::Circom_CalcWit { signalValues: signal_values, componentMemory: vec![ ffi::Circom_Component::default(); ffi::get_number_of_components() as usize ], circuitConstants: get_constants(), templateInsId2IOSignalInfoList: get_iosignals(), listOfTemplateMessages: vec![], }; // measure time let now = Instant::now(); unsafe { ffi::run(&mut ctx as *mut _); } eprintln!("Calculation took: {:?}", now.elapsed()); let signal_values = get_witness_to_signal(); let mut signals = signal_values .into_iter() .map(|i| ctx.signalValues[i].0) .collect::>(); let mut nodes = field::get_graph(); eprintln!("Graph with {} nodes", nodes.len()); // Optimize graph graph::optimize(&mut nodes, &mut signals); // Store graph to file. let input_map = get_input_hash_map(); let bytes = postcard::to_stdvec(&(&nodes, &signals, &input_map)).unwrap(); eprintln!("Graph size: {} bytes", bytes.len()); std::fs::write("graph.bin", bytes).unwrap(); // Evaluate the graph. let input_len = (ffi::get_main_input_signal_no() + ffi::get_main_input_signal_start()) as usize; // TODO: fetch from file let mut inputs = vec![U256::from(0); input_len]; inputs[0] = U256::from(1); for i in 1..nodes.len() { if let Node::Input(j) = nodes[i] { inputs[j] = get_values()[i]; } else { break; } } let now = Instant::now(); for _ in 0..10 { _ = graph::evaluate(&nodes, &inputs, &signals); } eprintln!("Calculation took: {:?}", now.elapsed() / 10); // Print graph // for (i, node) in nodes.iter().enumerate() { // println!("node[{}] = {:?}", i, node); // } // for (i, j) in signals.iter().enumerate() { // println!("signal[{}] = node[{}]", i, j); // } Ok(()) } ================================================ FILE: crates/circom-witness-rs/src/graph.rs ================================================ use std::{ collections::HashMap, ops::{Shl, Shr}, }; use crate::field::M; use ark_bn254::Fr; use ark_ff::PrimeField; use rand::Rng; use ruint::aliases::U256; use serde::{Deserialize, Serialize}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate}; fn ark_se(a: &A, s: S) -> Result where S: serde::Serializer, { let mut bytes = vec![]; a.serialize_with_mode(&mut bytes, Compress::Yes) .map_err(serde::ser::Error::custom)?; s.serialize_bytes(&bytes) } fn ark_de<'de, D, A: CanonicalDeserialize>(data: D) -> Result where D: serde::de::Deserializer<'de>, { let s: Vec = serde::de::Deserialize::deserialize(data)?; let a = A::deserialize_with_mode(s.as_slice(), Compress::Yes, Validate::Yes); a.map_err(serde::de::Error::custom) } #[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize)] pub enum Operation { Mul, MMul, Add, Sub, Eq, Neq, Lt, Gt, Leq, Geq, Lor, Shl, Shr, Band, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Node { Input(usize), Constant(U256), #[serde(serialize_with = "ark_se", deserialize_with = "ark_de")] MontConstant(Fr), Op(Operation, usize, usize), } impl Operation { pub fn eval(&self, a: U256, b: U256) -> U256 { use Operation::*; match self { Add => a.add_mod(b, M), Sub => a.add_mod(M - b, M), Mul => a.mul_mod(b, M), Eq => U256::from(a == b), Neq => U256::from(a != b), Lt => U256::from(a < b), Gt => U256::from(a > b), Leq => U256::from(a <= b), Geq => U256::from(a >= b), Lor => U256::from(a != U256::ZERO || b != U256::ZERO), Shl => compute_shl_uint(a, b), Shr => compute_shr_uint(a, b), Band => a.bitand(b), _ => unimplemented!("operator {:?} not implemented", self), } } pub fn eval_fr(&self, a: Fr, b: Fr) -> Fr { use Operation::*; match self { Add => a + b, Sub => a - b, Mul => a * b, _ => unimplemented!("operator {:?} not implemented for Montgomery", self), } } } fn compute_shl_uint(a: U256, b: U256) -> U256 { debug_assert!(b.lt(&U256::from(256))); let ls_limb = b.as_limbs()[0]; a.shl(ls_limb as usize) } fn compute_shr_uint(a: U256, b: U256) -> U256 { debug_assert!(b.lt(&U256::from(256))); let ls_limb = b.as_limbs()[0]; a.shr(ls_limb as usize) } /// All references must be backwards. fn assert_valid(nodes: &[Node]) { for (i, &node) in nodes.iter().enumerate() { if let Node::Op(_, a, b) = node { assert!(a < i); assert!(b < i); } } } pub fn optimize(nodes: &mut Vec, outputs: &mut [usize]) { tree_shake(nodes, outputs); propagate(nodes); value_numbering(nodes, outputs); constants(nodes); tree_shake(nodes, outputs); montgomery_form(nodes); } #[allow(clippy::unnecessary_fallible_conversions)] // Prevents the false positive on line 143 pub fn evaluate(nodes: &[Node], inputs: &[U256], outputs: &[usize]) -> Vec { // assert_valid(nodes); // Evaluate the graph. let mut values = Vec::with_capacity(nodes.len()); for &node in nodes.iter() { let value = match node { Node::Constant(c) => Fr::new(c.into()), Node::MontConstant(c) => c, Node::Input(i) => Fr::new(inputs[i].into()), Node::Op(op, a, b) => op.eval_fr(values[a], values[b]), }; values.push(value); } // Convert from Montgomery form and return the outputs. let mut out = vec![U256::ZERO; outputs.len()]; for i in 0..outputs.len() { out[i] = U256::try_from(values[outputs[i]].into_bigint()).unwrap(); } out } /// Constant propagation pub fn propagate(nodes: &mut [Node]) { assert_valid(nodes); let mut constants = 0_usize; for i in 0..nodes.len() { if let Node::Op(op, a, b) = nodes[i] { if let (Node::Constant(va), Node::Constant(vb)) = (nodes[a], nodes[b]) { nodes[i] = Node::Constant(op.eval(va, vb)); constants += 1; } else if a == b { // Not constant but equal use Operation::*; if let Some(c) = match op { Eq | Leq | Geq => Some(true), Neq | Lt | Gt => Some(false), _ => None, } { nodes[i] = Node::Constant(U256::from(c)); constants += 1; } } } } eprintln!("Propagated {constants} constants"); } /// Remove unused nodes pub fn tree_shake(nodes: &mut Vec, outputs: &mut [usize]) { assert_valid(nodes); // Mark all nodes that are used. let mut used = vec![false; nodes.len()]; for &i in outputs.iter() { used[i] = true; } // Work backwards from end as all references are backwards. for i in (0..nodes.len()).rev() { if used[i] { if let Node::Op(_, a, b) = nodes[i] { used[a] = true; used[b] = true; } } } // Remove unused nodes let n = nodes.len(); let mut retain = used.iter(); nodes.retain(|_| *retain.next().unwrap()); let removed = n - nodes.len(); // Renumber references. let mut renumber = vec![None; n]; let mut index = 0; for (i, &used) in used.iter().enumerate() { if used { renumber[i] = Some(index); index += 1; } } assert_eq!(index, nodes.len()); for (&used, renumber) in used.iter().zip(renumber.iter()) { assert_eq!(used, renumber.is_some()); } // Renumber references. for node in nodes.iter_mut() { if let Node::Op(_, a, b) = node { *a = renumber[*a].unwrap(); *b = renumber[*b].unwrap(); } } for output in outputs.iter_mut() { *output = renumber[*output].unwrap(); } eprintln!("Removed {removed} unused nodes"); } /// Randomly evaluate the graph fn random_eval(nodes: &mut [Node]) -> Vec { let mut rng = rand::thread_rng(); let mut values = Vec::with_capacity(nodes.len()); let mut inputs = HashMap::new(); let mut prfs = HashMap::new(); for node in nodes.iter() { use Operation::*; let value = match node { // Constants evaluate to themselves Node::Constant(c) => *c, Node::MontConstant(_c) => unimplemented!("should not be used"), // Algebraic Ops are evaluated directly // Since the field is large, by Swartz-Zippel if // two values are the same then they are likely algebraically equal. Node::Op(op @ (Add | Sub | Mul), a, b) => op.eval(values[*a], values[*b]), // Input and non-algebraic ops are random functions // TODO: https://github.com/recmo/uint/issues/95 and use .gen_range(..M) Node::Input(i) => *inputs.entry(*i).or_insert_with(|| rng.gen::() % M), Node::Op(op, a, b) => *prfs .entry((*op, values[*a], values[*b])) .or_insert_with(|| rng.gen::() % M), }; values.push(value); } values } /// Value numbering pub fn value_numbering(nodes: &mut [Node], outputs: &mut [usize]) { assert_valid(nodes); // Evaluate the graph in random field elements. let values = random_eval(nodes); // Find all nodes with the same value. let mut value_map = HashMap::new(); for (i, &value) in values.iter().enumerate() { value_map.entry(value).or_insert_with(Vec::new).push(i); } // For nodes that are the same, pick the first index. let mut renumber = Vec::with_capacity(nodes.len()); for value in values { renumber.push(value_map[&value][0]); } // Renumber references. for node in nodes.iter_mut() { if let Node::Op(_, a, b) = node { *a = renumber[*a]; *b = renumber[*b]; } } for output in outputs.iter_mut() { *output = renumber[*output]; } eprintln!("Global value numbering applied"); } /// Probabilistic constant determination pub fn constants(nodes: &mut [Node]) { assert_valid(nodes); // Evaluate the graph in random field elements. let values_a = random_eval(nodes); let values_b = random_eval(nodes); // Find all nodes with the same value. let mut constants = 0; for i in 0..nodes.len() { if let Node::Constant(_) = nodes[i] { continue; } if values_a[i] == values_b[i] { nodes[i] = Node::Constant(values_a[i]); constants += 1; } } eprintln!("Found {} constants", constants); } /// Convert to Montgomery form pub fn montgomery_form(nodes: &mut [Node]) { for node in nodes.iter_mut() { use Node::*; use Operation::*; match node { Constant(c) => *node = MontConstant(Fr::new((*c).into())), MontConstant(..) => (), Input(..) => (), Op(Add | Sub | Mul, ..) => (), Op(..) => unimplemented!("Operators Montgomery form"), } } eprintln!("Converted to Montgomery form"); } ================================================ FILE: crates/circom-witness-rs/src/lib.rs ================================================ mod field; pub mod graph; #[cfg(feature = "build-witness")] pub mod generate; use std::collections::HashMap; use ruint::aliases::U256; use serde::{Deserialize, Serialize}; use crate::graph::Node; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct HashSignalInfo { pub hash: u64, pub signalid: u64, pub signalsize: u64, } pub struct Graph { pub nodes: Vec, pub signals: Vec, pub input_mapping: Vec, } fn fnv1a(s: &str) -> u64 { let mut hash: u64 = 0xCBF29CE484222325; for c in s.bytes() { hash ^= c as u64; hash = hash.wrapping_mul(0x100000001B3); } hash } /// Loads the graph from bytes pub fn init_graph(graph_bytes: &[u8]) -> color_eyre::Result { let (nodes, signals, input_mapping): (Vec, Vec, Vec) = postcard::from_bytes(graph_bytes)?; Ok(Graph { nodes, signals, input_mapping, }) } /// Calculates the number of needed inputs pub fn get_inputs_size(graph: &Graph) -> usize { let mut start = false; let mut max_index = 0usize; for &node in graph.nodes.iter() { if let Node::Input(i) = node { if i > max_index { max_index = i; } start = true } else if start { break; } } max_index + 1 } /// Allocates inputs vec with position 0 set to 1 pub fn get_inputs_buffer(size: usize) -> Vec { let mut inputs = vec![U256::ZERO; size]; inputs[0] = U256::from(1); inputs } /// Calculates the position of the given signal in the inputs buffer pub fn get_input_mapping(input_list: &Vec, graph: &Graph) -> HashMap { let mut input_mapping = HashMap::new(); for key in input_list { let h = fnv1a(key); let pos = graph .input_mapping .iter() .position(|x| x.hash == h) .unwrap(); let si = (graph.input_mapping[pos].signalid) as usize; input_mapping.insert(key.to_string(), si); } input_mapping } /// Sets all provided inputs given the mapping and inputs buffer pub fn populate_inputs( input_list: &HashMap>, input_mapping: &HashMap, input_buffer: &mut [U256], ) { for (key, value) in input_list { let start = input_mapping[key]; let end = start + value.len(); input_buffer[start..end].copy_from_slice(value); } } /// Calculate witness based on serialized graph and inputs pub fn calculate_witness( input_list: HashMap>, graph: &Graph, ) -> color_eyre::Result> { let mut inputs_buffer = get_inputs_buffer(get_inputs_size(graph)); let input_mapping = get_input_mapping(&input_list.keys().cloned().collect(), graph); populate_inputs(&input_list, &input_mapping, &mut inputs_buffer); Ok(graph::evaluate( &graph.nodes, &inputs_buffer, &graph.signals, )) } ================================================ FILE: crates/hasher/Cargo.toml ================================================ [package] name = "semaphore-rs-hasher" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] bytemuck.workspace = true ================================================ FILE: crates/hasher/src/lib.rs ================================================ use bytemuck::Pod; /// Hash types, values and algorithms for a Merkle tree pub trait Hasher { /// Type of the leaf and node hashes type Hash; /// Compute the hash of an intermediate node fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash; } /// A marker trait that indicates some useful properties of a hash type /// /// It's not strictly necessary, but for many implementations it's a useful set of constraints pub trait Hash: Pod + Eq + Send + Sync {} impl Hash for T where T: Pod + Eq + Send + Sync {} ================================================ FILE: crates/js/.gitignore ================================================ /target **/*.rs.bk Cargo.lock bin/ pkg/ pkg-nodejs/ pkg-bundler/ pkg-web/ wasm-pack.log ================================================ FILE: crates/js/Cargo.toml ================================================ [package] name = "semaphore-rs-js" version = "0.3.0" edition.workspace = true publish = false # independently versioned; not part of the workspace release cycle homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [lib] crate-type = ["cdylib", "rlib"] [dependencies] semaphore-rs-proof = { workspace = true, default-features = false } ruint.workspace = true hex.workspace = true wasm-bindgen.workspace = true js-sys.workspace = true getrandom.workspace = true [dev-dependencies] wasm-bindgen-test.workspace = true ================================================ FILE: crates/js/README.md ================================================ # WASM bindings for semaphore-rs related functionality This crate exposes semaphore-rs functionality to WASM. Currently it only exposes proof compression. ## Building & publishing wasm-pack doesn't allow us to compile to a single target for node and browser usage. Instead we'll publish a package for each target. The `build_and_publish.sh` script handles all of that. To build and publish a new version simply run `./build_and_publish.sh`. Note that the package will likely fail to publish if using your own npm account. To only check the build output run `DRY_RUN=1 ./build_and_publish.sh`. ## Example Refer to `example/index.mjs` or `example/index.ts` for usage ================================================ FILE: crates/js/build.mjs ================================================ #!/usr/bin/env node // build.mjs // This script performs the following steps: // 1. Uses `cargo metadata` to extract package metadata. // 2. Constructs a detailed package.json using that metadata. // 3. Invokes `cargo build --target wasm32-unknown-unknown` for the local package. // 4. Locates the wasm artifact (by searching upward for the target directory), // and runs wasm-bindgen (with --target web, --omit-imports, --omit-default-module-path) // outputting the JS bindings into a pkg directory. // 5. Generates an inline loader script that embeds the wasm as a base64 string. // 6. Publishes the package (unless the `--dry-run` CLI argument is present). import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; const isDryRun = process.argv.includes('--dry-run'); async function main() { // 1. Extract package metadata using cargo metadata. console.log("Extracting package metadata via cargo metadata..."); const metadataJson = execSync('cargo metadata --no-deps --format-version 1', { encoding: 'utf-8' }); const metadata = JSON.parse(metadataJson); const manifestPath = path.resolve('Cargo.toml'); const pkgMeta = metadata.packages.find(pkg => path.resolve(pkg.manifest_path) === manifestPath); if (!pkgMeta) { throw new Error("Could not find package metadata for the current Cargo.toml"); } const pkgName = pkgMeta.name; const pkgVersion = pkgMeta.version; const pkgDescription = pkgMeta.description || ""; const pkgLicense = pkgMeta.license || ""; const pkgHomepage = pkgMeta.homepage || ""; const pkgRepository = pkgMeta.repository || ""; const pkgKeywords = pkgMeta.keywords || []; // We'll use authors as collaborators. const pkgCollaborators = pkgMeta.authors || []; // Convert crate name to snake_case for file naming (dashes become underscores). const pkgBaseName = pkgName.replace(/-/g, '_'); // wasm-bindgen will output files named like `.js` and `_bg.wasm` const wasmBindgenJs = `${pkgBaseName}.js`; const wasmBindgenWasm = `${pkgBaseName}_bg.wasm`; const wasmBindgenDts = `${pkgBaseName}.d.ts`; // 2. Build the Rust crate for the wasm target. console.log("Building the Rust project with cargo..."); execSync('cargo build --target wasm32-unknown-unknown', { stdio: 'inherit' }); // 3. Locate the target directory by searching upward from the current directory. let targetDir = null; let currentDir = process.cwd(); while (currentDir !== path.parse(currentDir).root) { const potentialTarget = path.join(currentDir, 'target'); if (fs.existsSync(potentialTarget)) { targetDir = potentialTarget; break; } currentDir = path.dirname(currentDir); } if (!targetDir) { throw new Error("Could not locate the target directory"); } // Assume a debug build; the wasm artifact should be at: // target/wasm32-unknown-unknown/debug/.wasm const wasmArtifactPath = path.join(targetDir, 'wasm32-unknown-unknown', 'debug', `${pkgBaseName}.wasm`); if (!fs.existsSync(wasmArtifactPath)) { throw new Error(`Wasm artifact not found at ${wasmArtifactPath}`); } // 4. Run wasm-bindgen on the artifact. console.log("Running wasm-bindgen..."); const pkgDir = path.resolve('pkg'); if (!fs.existsSync(pkgDir)) { fs.mkdirSync(pkgDir); } const wasmBindgenCmd = `wasm-bindgen ${wasmArtifactPath} --out-dir ${pkgDir} --target web --omit-imports --omit-default-module-path`; execSync(wasmBindgenCmd, { stdio: 'inherit' }); // 5. Construct the inline loader. const wasmOutputPath = path.join(pkgDir, wasmBindgenWasm); if (!fs.existsSync(wasmOutputPath)) { throw new Error(`Wasm file not found in pkg directory: ${wasmOutputPath}`); } const wasmBuffer = fs.readFileSync(wasmOutputPath); const wasmBase64 = wasmBuffer.toString('base64'); const inlineLoaderContent = ` // This file is auto-generated by build.mjs. // It inlines the wasm module as a base64 string and loads it synchronously. import { initSync } from './${wasmBindgenJs}'; const base64Wasm = "${wasmBase64}"; // Convert a base64 string to a Uint8Array. function base64ToUint8Array(base64) { if (typeof atob === 'function') { const binaryString = atob(base64); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes; } else if (typeof Buffer === 'function') { return new Uint8Array(Buffer.from(base64, 'base64')); } else { throw new Error('No base64 decoder available'); } } const wasmBytes = base64ToUint8Array(base64Wasm); // Initialize the generated bindings with the inlined wasm instance. initSync({ module: wasmBytes }); export * from './${wasmBindgenJs}'; `.trim(); const inlineLoaderPath = path.join(pkgDir, 'index.js'); fs.writeFileSync(inlineLoaderPath, inlineLoaderContent); console.log(`Generated inline loader: ${inlineLoaderPath}`); // 6. Construct a fleshed-out package.json. const packageJson = { name: pkgName, type: "module", collaborators: pkgCollaborators, description: pkgDescription, version: pkgVersion, license: pkgLicense, repository: pkgRepository ? { type: "git", url: pkgRepository } : undefined, files: [ // With inlined wasm, ship the loader and generated JS bindings. "index.js", wasmBindgenJs, wasmBindgenDts ], main: "index.js", homepage: pkgHomepage, types: wasmBindgenDts, sideEffects: [ "./snippets/*" ], keywords: pkgKeywords }; // Remove any keys that are undefined. Object.keys(packageJson).forEach(key => { if (packageJson[key] === undefined) { delete packageJson[key]; } }); const pkgJsonPath = path.join(pkgDir, 'package.json'); fs.writeFileSync(pkgJsonPath, JSON.stringify(packageJson, null, 2)); console.log(`Updated package.json: ${pkgJsonPath}`); // 7. Publish the package unless --dry-run is provided. if (!isDryRun) { console.log("Publishing package..."); execSync('npm publish', { stdio: 'inherit', cwd: pkgDir }); } else { console.log("--dry-run flag present, skipping npm publish."); } console.log("Build complete: Wasm module inlined and package.json regenerated."); } main().catch(err => { console.error(err); process.exit(1); }); ================================================ FILE: crates/js/example/.gitignore ================================================ node_modules/ ================================================ FILE: crates/js/example/index.mjs ================================================ import { compressProof, decompressProof } from "semaphore-rs-js"; const proof = [ "0x2d77679b613036865f4518894c80691cf65338fe7834fe3dd5f98c4f0f5a9e6d", "0x24018e845edf74d69528a63eed053296a397df13a1d08873e2b2d673837b31c3", "0x099d39b2cbca524b5916ac97dbc4afc1b8a5f59d65ba583fc49ec2677226e926", "0x0da5812d7b4e0beb22d25c194431674396aec70751873edb9ac8c933ba1f0f2e", "0x0723caca23efb9aa44db59ead0eeb28c2efb9c766d9a3f994ed047179e37b347", "0x02166d9fc2d4cf446b120e5663880e0927825aa36a02b896ac0f3a5ef6e0239b", "0x287fb1d0415a734ba76df9eb50ca6758bb806272f8fe40e3adbad3a850c05167", "0x1240cf8aa43cf4ea4a2d8dffac653a6467cefd0f19e129cffad85299d6705444", ]; const compressed = compressProof(proof); const decompressed = decompressProof(compressed); for (let i = 0; i < 8; i++) { if (proof[i] !== decompressed[i]) { console.log("Proof not equal after decompression"); } } ================================================ FILE: crates/js/example/index.ts ================================================ import { compressProof, decompressProof } from "semaphore-rs-js"; const proof: [string, string, string, string, string, string, string, string] = [ "0x2d77679b613036865f4518894c80691cf65338fe7834fe3dd5f98c4f0f5a9e6d", "0x24018e845edf74d69528a63eed053296a397df13a1d08873e2b2d673837b31c3", "0x099d39b2cbca524b5916ac97dbc4afc1b8a5f59d65ba583fc49ec2677226e926", "0x0da5812d7b4e0beb22d25c194431674396aec70751873edb9ac8c933ba1f0f2e", "0x0723caca23efb9aa44db59ead0eeb28c2efb9c766d9a3f994ed047179e37b347", "0x02166d9fc2d4cf446b120e5663880e0927825aa36a02b896ac0f3a5ef6e0239b", "0x287fb1d0415a734ba76df9eb50ca6758bb806272f8fe40e3adbad3a850c05167", "0x1240cf8aa43cf4ea4a2d8dffac653a6467cefd0f19e129cffad85299d6705444", ]; const compressed = compressProof(proof); const decompressed = decompressProof(compressed); for (let i = 0; i < 8; i++) { if (proof[i] !== decompressed[i]) { console.log("Proof not equal after decompression"); } } ================================================ FILE: crates/js/example/package.json ================================================ { "name": "testing", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "description": "", "dependencies": { "semaphore-rs-js": "0.3.0" } } ================================================ FILE: crates/js/src/lib.rs ================================================ use js_sys::Array; use ruint::aliases::U256; use semaphore_rs_proof::{compression::CompressedProof, Proof}; use wasm_bindgen::prelude::*; /// Compresses a Groth16 proof #[wasm_bindgen( js_name = "compressProof", unchecked_return_type = "[string, string, string, string]", return_description = "An array of 4 0x prefixed, hex encoded strings representing a compressed proof" )] pub fn compress_proof( #[wasm_bindgen( unchecked_param_type = "[string, string, string, string, string, string, string, string]", param_description = "An array of 8 hex encoded strings (with optional 0x prefixes) that represent an uncompressed proof" )] proof: Array, ) -> Result { let proof: Vec = proof .iter() .map(|v| v.as_string().unwrap_or_default()) .collect(); let proof = from_vec(proof)?; let proof = Proof::from_flat(proof); let proof = semaphore_rs_proof::compression::compress_proof(proof) .ok_or_else(|| JsError::new("Failed to compress proof"))? .flatten(); Ok(to_js_array(proof)) } /// Decompresses a Groth16 proof #[wasm_bindgen( js_name = "decompressProof", unchecked_return_type = "[string, string, string, string, string, string, string, string]", return_description = "An array of 8 0x prefixed, hex encoded strings representing an uncompressed proof" )] pub fn decompress_proof( #[wasm_bindgen( js_name = "compressedProof", unchecked_param_type = "[string, string, string, string]", param_description = "An array of 4 hex encoded strings (with optional 0x prefixes) that represent a compressed proof" )] compressed_proof: Array, ) -> Result { let compressed_proof: Vec = compressed_proof .iter() .map(|v| v.as_string().unwrap_or_default()) .collect(); let proof = from_vec(compressed_proof)?; let proof = CompressedProof::from_flat(proof); let proof = semaphore_rs_proof::compression::decompress_proof(proof) .ok_or_else(|| JsError::new("Failed to decompress proof"))?; let proof = proof.flatten(); Ok(to_js_array(proof)) } fn from_vec(proof: Vec) -> Result<[U256; N], JsError> { if proof.len() != N { return Err(JsError::new(&format!("Proof length must be {N}"))); } let proof: Vec = proof .into_iter() .map(|s| { U256::from_str_radix(s.trim_start_matches("0x"), 16) .map_err(|err| JsError::new(&err.to_string())) }) .collect::>()?; let proof: [U256; N] = proof.try_into().unwrap(); Ok(proof) } fn to_js_array(arr: [U256; N]) -> Array { let js_array = Array::new(); arr.iter().take(N).for_each(|v| { js_array.push(&JsValue::from_str(&format!("{:#066x}", v))); }); js_array } ================================================ FILE: crates/keccak/Cargo.toml ================================================ [package] name = "semaphore-rs-keccak" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] semaphore-rs-hasher.workspace = true tiny-keccak = { workspace = true, features = ["keccak"] } [features] default = ["sha3"] sha3 = ["tiny-keccak/sha3"] ================================================ FILE: crates/keccak/src/keccak.rs ================================================ use semaphore_rs_hasher::Hasher; use tiny_keccak::{Hasher as _, Keccak}; pub struct Keccak256; impl Hasher for Keccak256 { type Hash = [u8; 32]; fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash { let mut keccak = Keccak::v256(); let mut output = [0; 32]; keccak.update(left); keccak.update(right); keccak.finalize(&mut output); output } } ================================================ FILE: crates/keccak/src/lib.rs ================================================ pub mod keccak; #[cfg(feature = "sha3")] pub mod sha3; ================================================ FILE: crates/keccak/src/sha3.rs ================================================ use semaphore_rs_hasher::Hasher; use tiny_keccak::{Hasher as _, Sha3}; pub struct Sha3_256; impl Hasher for Sha3_256 { type Hash = [u8; 32]; fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash { let mut sha3_hasher = Sha3::v256(); sha3_hasher.update(left); sha3_hasher.update(right); let mut out = [0u8; 32]; sha3_hasher.finalize(&mut out); out } } ================================================ FILE: crates/poseidon/Cargo.toml ================================================ [package] name = "semaphore-rs-poseidon" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] semaphore-rs-hasher.workspace = true ark-bn254.workspace = true ark-ff.workspace = true once_cell.workspace = true ruint.workspace = true ================================================ FILE: crates/poseidon/src/constants.rs ================================================ use ruint::aliases::U256; use ruint::uint; uint! { pub const M1: [[U256; 2]; 2] = [ [ 0x066f6f85d6f68a85ec10345351a23a3aaf07f38af8c952a7bceca70bd2af7ad5_U256, 0x2b9d4b4110c9ae997782e1509b1d0fdb20a7c02bbd8bea7305462b9f8125b1e8_U256, ], [ 0x0cc57cdbb08507d62bf67a4493cc262fb6c09d557013fff1f573f431221f8ff9_U256, 0x1274e649a32ed355a31a6ed69724e1adade857e86eb5c3a121bcd147943203c8_U256, ], ]; pub const C1: [[U256; 2]; 64] = [ [ 0x09c46e9ec68e9bd4fe1faaba294cba38a71aa177534cdd1b6c7dc0dbd0abd7a7_U256, 0x0c0356530896eec42a97ed937f3135cfc5142b3ae405b8343c1d83ffa604cb81_U256, ], [ 0x1e28a1d935698ad1142e51182bb54cf4a00ea5aabd6268bd317ea977cc154a30_U256, 0x27af2d831a9d2748080965db30e298e40e5757c3e008db964cf9e2b12b91251f_U256, ], [ 0x1e6f11ce60fc8f513a6a3cfe16ae175a41291462f214cd0879aaf43545b74e03_U256, 0x2a67384d3bbd5e438541819cb681f0be04462ed14c3613d8f719206268d142d3_U256, ], [ 0x0b66fdf356093a611609f8e12fbfecf0b985e381f025188936408f5d5c9f45d0_U256, 0x012ee3ec1e78d470830c61093c2ade370b26c83cc5cebeeddaa6852dbdb09e21_U256, ], [ 0x0252ba5f6760bfbdfd88f67f8175e3fd6cd1c431b099b6bb2d108e7b445bb1b9_U256, 0x179474cceca5ff676c6bec3cef54296354391a8935ff71d6ef5aeaad7ca932f1_U256, ], [ 0x2c24261379a51bfa9228ff4a503fd4ed9c1f974a264969b37e1a2589bbed2b91_U256, 0x1cc1d7b62692e63eac2f288bd0695b43c2f63f5001fc0fc553e66c0551801b05_U256, ], [ 0x255059301aada98bb2ed55f852979e9600784dbf17fbacd05d9eff5fd9c91b56_U256, 0x28437be3ac1cb2e479e1f5c0eccd32b3aea24234970a8193b11c29ce7e59efd9_U256, ], [ 0x28216a442f2e1f711ca4fa6b53766eb118548da8fb4f78d4338762c37f5f2043_U256, 0x2c1f47cd17fa5adf1f39f4e7056dd03feee1efce03094581131f2377323482c9_U256, ], [ 0x07abad02b7a5ebc48632bcc9356ceb7dd9dafca276638a63646b8566a621afc9_U256, 0x0230264601ffdf29275b33ffaab51dfe9429f90880a69cd137da0c4d15f96c3c_U256, ], [ 0x1bc973054e51d905a0f168656497ca40a864414557ee289e717e5d66899aa0a9_U256, 0x2e1c22f964435008206c3157e86341edd249aff5c2d8421f2a6b22288f0a67fc_U256, ], [ 0x1224f38df67c5378121c1d5f461bbc509e8ea1598e46c9f7a70452bc2bba86b8_U256, 0x02e4e69d8ba59e519280b4bd9ed0068fd7bfe8cd9dfeda1969d2989186cde20e_U256, ], [ 0x1f1eccc34aaba0137f5df81fc04ff3ee4f19ee364e653f076d47e9735d98018e_U256, 0x1672ad3d709a353974266c3039a9a7311424448032cd1819eacb8a4d4284f582_U256, ], [ 0x283e3fdc2c6e420c56f44af5192b4ae9cda6961f284d24991d2ed602df8c8fc7_U256, 0x1c2a3d120c550ecfd0db0957170fa013683751f8fdff59d6614fbd69ff394bcc_U256, ], [ 0x216f84877aac6172f7897a7323456efe143a9a43773ea6f296cb6b8177653fbd_U256, 0x2c0d272becf2a75764ba7e8e3e28d12bceaa47ea61ca59a411a1f51552f94788_U256, ], [ 0x16e34299865c0e28484ee7a74c454e9f170a5480abe0508fcb4a6c3d89546f43_U256, 0x175ceba599e96f5b375a232a6fb9cc71772047765802290f48cd939755488fc5_U256, ], [ 0x0c7594440dc48c16fead9e1758b028066aa410bfbc354f54d8c5ffbb44a1ee32_U256, 0x1a3c29bc39f21bb5c466db7d7eb6fd8f760e20013ccf912c92479882d919fd8d_U256, ], [ 0x0ccfdd906f3426e5c0986ea049b253400855d349074f5a6695c8eeabcd22e68f_U256, 0x14f6bc81d9f186f62bdb475ce6c9411866a7a8a3fd065b3ce0e699b67dd9e796_U256, ], [ 0x0962b82789fb3d129702ca70b2f6c5aacc099810c9c495c888edeb7386b97052_U256, 0x1a880af7074d18b3bf20c79de25127bc13284ab01ef02575afef0c8f6a31a86d_U256, ], [ 0x10cba18419a6a332cd5e77f0211c154b20af2924fc20ff3f4c3012bb7ae9311b_U256, 0x057e62a9a8f89b3ebdc76ba63a9eaca8fa27b7319cae3406756a2849f302f10d_U256, ], [ 0x287c971de91dc0abd44adf5384b4988cb961303bbf65cff5afa0413b44280cee_U256, 0x21df3388af1687bbb3bca9da0cca908f1e562bc46d4aba4e6f7f7960e306891d_U256, ], [ 0x1be5c887d25bce703e25cc974d0934cd789df8f70b498fd83eff8b560e1682b3_U256, 0x268da36f76e568fb68117175cea2cd0dd2cb5d42fda5acea48d59c2706a0d5c1_U256, ], [ 0x0e17ab091f6eae50c609beaf5510ececc5d8bb74135ebd05bd06460cc26a5ed6_U256, 0x04d727e728ffa0a67aee535ab074a43091ef62d8cf83d270040f5caa1f62af40_U256, ], [ 0x0ddbd7bf9c29341581b549762bc022ed33702ac10f1bfd862b15417d7e39ca6e_U256, 0x2790eb3351621752768162e82989c6c234f5b0d1d3af9b588a29c49c8789654b_U256, ], [ 0x1e457c601a63b73e4471950193d8a570395f3d9ab8b2fd0984b764206142f9e9_U256, 0x21ae64301dca9625638d6ab2bbe7135ffa90ecd0c43ff91fc4c686fc46e091b0_U256, ], [ 0x0379f63c8ce3468d4da293166f494928854be9e3432e09555858534eed8d350b_U256, 0x002d56420359d0266a744a080809e054ca0e4921a46686ac8c9f58a324c35049_U256, ], [ 0x123158e5965b5d9b1d68b3cd32e10bbeda8d62459e21f4090fc2c5af963515a6_U256, 0x0be29fc40847a941661d14bbf6cbe0420fbb2b6f52836d4e60c80eb49cad9ec1_U256, ], [ 0x1ac96991dec2bb0557716142015a453c36db9d859cad5f9a233802f24fdf4c1a_U256, 0x1596443f763dbcc25f4964fc61d23b3e5e12c9fa97f18a9251ca3355bcb0627e_U256, ], [ 0x12e0bcd3654bdfa76b2861d4ec3aeae0f1857d9f17e715aed6d049eae3ba3212_U256, 0x0fc92b4f1bbea82b9ea73d4af9af2a50ceabac7f37154b1904e6c76c7cf964ba_U256, ], [ 0x1f9c0b1610446442d6f2e592a8013f40b14f7c7722236f4f9c7e965233872762_U256, 0x0ebd74244ae72675f8cde06157a782f4050d914da38b4c058d159f643dbbf4d3_U256, ], [ 0x2cb7f0ed39e16e9f69a9fafd4ab951c03b0671e97346ee397a839839dccfc6d1_U256, 0x1a9d6e2ecff022cc5605443ee41bab20ce761d0514ce526690c72bca7352d9bf_U256, ], [ 0x2a115439607f335a5ea83c3bc44a9331d0c13326a9a7ba3087da182d648ec72f_U256, 0x23f9b6529b5d040d15b8fa7aee3e3410e738b56305cd44f29535c115c5a4c060_U256, ], [ 0x05872c16db0f72a2249ac6ba484bb9c3a3ce97c16d58b68b260eb939f0e6e8a7_U256, 0x1300bdee08bb7824ca20fb80118075f40219b6151d55b5c52b624a7cdeddf6a7_U256, ], [ 0x19b9b63d2f108e17e63817863a8f6c288d7ad29916d98cb1072e4e7b7d52b376_U256, 0x015bee1357e3c015b5bda237668522f613d1c88726b5ec4224a20128481b4f7f_U256, ], [ 0x2953736e94bb6b9f1b9707a4f1615e4efe1e1ce4bab218cbea92c785b128ffd1_U256, 0x0b069353ba091618862f806180c0385f851b98d372b45f544ce7266ed6608dfc_U256, ], [ 0x304f74d461ccc13115e4e0bcfb93817e55aeb7eb9306b64e4f588ac97d81f429_U256, 0x15bbf146ce9bca09e8a33f5e77dfe4f5aad2a164a4617a4cb8ee5415cde913fc_U256, ], [ 0x0ab4dfe0c2742cde44901031487964ed9b8f4b850405c10ca9ff23859572c8c6_U256, 0x0e32db320a044e3197f45f7649a19675ef5eedfea546dea9251de39f9639779a_U256, ], [ 0x0a1756aa1f378ca4b27635a78b6888e66797733a82774896a3078efa516da016_U256, 0x044c4a33b10f693447fd17177f952ef895e61d328f85efa94254d6a2a25d93ef_U256, ], [ 0x2ed3611b725b8a70be655b537f66f700fe0879d79a496891d37b07b5466c4b8b_U256, 0x1f9ba4e8bab7ce42c8ecc3d722aa2e0eadfdeb9cfdd347b5d8339ea7120858aa_U256, ], [ 0x1b233043052e8c288f7ee907a84e518aa38e82ac4502066db74056f865c5d3da_U256, 0x2431e1cc164bb8d074031ab72bd55b4c902053bfc0f14db0ca2f97b020875954_U256, ], [ 0x082f934c91f5aac330cd6953a0a7db45a13e322097583319a791f273965801fd_U256, 0x2b9a0a223e7538b0a34be074315542a3c77245e2ae7cbe999ad6bb930c48997c_U256, ], [ 0x0e1cd91edd2cfa2cceb85483b887a9be8164163e75a8a00eb0b589cc70214e7d_U256, 0x2e1eac0f2bfdfd63c951f61477e3698999774f19854d00f588d324601cebe2f9_U256, ], [ 0x0cbfa95f37fb74060c76158e769d6d157345784d8efdb33c23d748115b500b83_U256, 0x08f05b3be923ed44d65ad49d8a61e9a676d991e3a77513d9980c232dfa4a4f84_U256, ], [ 0x22719e2a070bcd0852bf8e21984d0443e7284925dc0758a325a2dd510c047ef6_U256, 0x041f596a9ee1cb2bc060f7fcc3a1ab4c7bdbf036119982c0f41f62b2f26830c0_U256, ], [ 0x233fd35de1be520a87628eb06f6b1d4c021be1c2d0dc464a19fcdd0986b10f89_U256, 0x0524b46d1aa87a5e4325e0a423ebc810d31e078aa1b4707eefcb453c61c9c267_U256, ], [ 0x2c34f424c81e5716ce47fcac894b85824227bb954b0f3199cc4486237c515211_U256, 0x0b5f2a4b63387819207effc2b5541fb72dd2025b5457cc97f33010327de4915e_U256, ], [ 0x22207856082ccc54c5b72fe439d2cfd6c17435d2f57af6ceaefac41fe05c659f_U256, 0x24d57a8bf5da63fe4e24159b7f8950b5cdfb210194caf79f27854048ce2c8171_U256, ], [ 0x0afab181fdd5e0583b371d75bd693f98374ad7097bb01a8573919bb23b79396e_U256, 0x2dba9b108f208772998a52efac7cbd5676c0057194c16c0bf16290d62b1128ee_U256, ], [ 0x26349b66edb8b16f56f881c788f53f83cbb83de0bd592b255aff13e6bce420b3_U256, 0x25af7ce0e5e10357685e95f92339753ad81a56d28ecc193b235288a3e6f137db_U256, ], [ 0x25b4ce7bd2294390c094d6a55edd68b970eed7aae88b2bff1f7c0187fe35011f_U256, 0x22c543f10f6c89ec387e53f1908a88e5de9cef28ebdf30b18cb9d54c1e02b631_U256, ], [ 0x0236f93e7789c4724fc7908a9f191e1e425e906a919d7a34df668e74882f87a9_U256, 0x29350b401166ca010e7d27e37d05da99652bdae114eb01659cb497af980c4b52_U256, ], [ 0x0eed787d65820d3f6bd31bbab547f75a65edb75d844ebb89ee1260916652363f_U256, 0x07cc1170f13b46f2036a753f520b3291fdcd0e99bd94297d1906f656f4de6fad_U256, ], [ 0x22b939233b1d7205f49bcf613a3d30b1908786d7f9f5d10c2059435689e8acea_U256, 0x01451762a0aab81c8aad1dc8bc33e870740f083a5aa85438add650ace60ae5a6_U256, ], [ 0x23506bb5d8727d4461fabf1025d46d1fe32eaa61dec7da57e704fec0892fce89_U256, 0x2e484c44e838aea0bac06ae3f71bdd092a3709531e1efea97f8bd68907355522_U256, ], [ 0x0f4bc7d07ebafd64379e78c50bd2e42baf4a594545cedc2545418da26835b54c_U256, 0x1f4d3c8f6583e9e5fa76637862faaee851582388725df460e620996d50d8e74e_U256, ], [ 0x093514e0c70711f82660d07be0e4a988fae02abc7b681d9153eb9bcb48fe7389_U256, 0x1adab0c8e2b3bad346699a2b5f3bc03643ee83ece47228f24a58e0a347e153d8_U256, ], [ 0x1672b1726057d99dd14709ebb474641a378c1b94b8072bac1a22dbef9e80dad2_U256, 0x1dfd53d4576af2e38f44f53fdcab468cc5d8e2fae0acc4ee30d47b239b479c14_U256, ], [ 0x0c6888a10b75b0f3a70a36263a37e17fe6d77d640f6fc3debc7f207753205c60_U256, 0x1addb933a65be77092b34a7e77d12fe8611a61e00ee6848b85091ecca9d1e508_U256, ], [ 0x00d7540dcd268a845c10ae18d1de933cf638ff5425f0afff7935628e299d1791_U256, 0x140c0e42687e9ead01b2827a5664ca9c26fedde4acd99db1d316939d20b82c0e_U256, ], [ 0x2f0c3a115d4317d191ba89b8d13d1806c20a0f9b24f8c5edc091e2ae56565984_U256, 0x0c4ee778ff7c14553006ed220cf9c81008a0cff670b22b82d8c538a1dc958c61_U256, ], [ 0x1704f2766d46f82c3693f00440ccc3609424ed26c0acc66227c3d7485de74c69_U256, 0x2f2d19cc3ea5d78ea7a02c1b51d244abf0769c9f8544e40239b66fe9009c3cfa_U256, ], [ 0x1ae03853b75fcaba5053f112e2a8e8dcdd7ee6cb9cfed9c7d6c766a806fc6629_U256, 0x0971aabf795241df51d131d0fa61aa5f3556921b2d6f014e4e41a86ddaf056d5_U256, ], [ 0x1408c316e6014e1a91d4cf6b6e0de73eda624f8380df1c875f5c29f7bfe2f646_U256, 0x1667f3fe2edbe850248abe42b543093b6c89f1f773ef285341691f39822ef5bd_U256, ], [ 0x13bf7c5d0d2c4376a48b0a03557cdf915b81718409e5c133424c69576500fe37_U256, 0x07620a6dfb0b6cec3016adf3d3533c24024b95347856b79719bc0ba743a62c2c_U256, ], [ 0x1574c7ef0c43545f36a8ca08bdbdd8b075d2959e2f322b731675de3e1982b4d0_U256, 0x269e4b5b7a2eb21afd567970a717ceec5bd4184571c254fdc06e03a7ff8378f0_U256, ], ]; pub const M: [[U256; 3]; 3] = [ [ 0x109b7f411ba0e4c9b2b70caf5c36a7b194be7c11ad24378bfedb68592ba8118b_U256, 0x16ed41e13bb9c0c66ae119424fddbcbc9314dc9fdbdeea55d6c64543dc4903e0_U256, 0x2b90bba00fca0589f617e7dcbfe82e0df706ab640ceb247b791a93b74e36736d_U256, ], [ 0x2969f27eed31a480b9c36c764379dbca2cc8fdd1415c3dded62940bcde0bd771_U256, 0x2e2419f9ec02ec394c9871c832963dc1b89d743c8c7b964029b2311687b1fe23_U256, 0x101071f0032379b697315876690f053d148d4e109f5fb065c8aacc55a0f89bfa_U256, ], [ 0x143021ec686a3f330d5f9e654638065ce6cd79e28c5b3753326244ee65a1b1a7_U256, 0x176cc029695ad02582a70eff08a6fd99d057e12e58e7d7b6b16cdfabc8ee2911_U256, 0x19a3fc0a56702bf417ba7fee3802593fa644470307043f7773279cd71d25d5e0_U256, ], ]; pub const C: [[U256; 3]; 65] = [ [ 0x0ee9a592ba9a9518d05986d656f40c2114c4993c11bb29938d21d47304cd8e6e_U256, 0x00f1445235f2148c5986587169fc1bcd887b08d4d00868df5696fff40956e864_U256, 0x08dff3487e8ac99e1f29a058d0fa80b930c728730b7ab36ce879f3890ecf73f5_U256, ], [ 0x2f27be690fdaee46c3ce28f7532b13c856c35342c84bda6e20966310fadc01d0_U256, 0x2b2ae1acf68b7b8d2416bebf3d4f6234b763fe04b8043ee48b8327bebca16cf2_U256, 0x0319d062072bef7ecca5eac06f97d4d55952c175ab6b03eae64b44c7dbf11cfa_U256, ], [ 0x28813dcaebaeaa828a376df87af4a63bc8b7bf27ad49c6298ef7b387bf28526d_U256, 0x2727673b2ccbc903f181bf38e1c1d40d2033865200c352bc150928adddf9cb78_U256, 0x234ec45ca27727c2e74abd2b2a1494cd6efbd43e340587d6b8fb9e31e65cc632_U256, ], [ 0x15b52534031ae18f7f862cb2cf7cf760ab10a8150a337b1ccd99ff6e8797d428_U256, 0x0dc8fad6d9e4b35f5ed9a3d186b79ce38e0e8a8d1b58b132d701d4eecf68d1f6_U256, 0x1bcd95ffc211fbca600f705fad3fb567ea4eb378f62e1fec97805518a47e4d9c_U256, ], [ 0x10520b0ab721cadfe9eff81b016fc34dc76da36c2578937817cb978d069de559_U256, 0x1f6d48149b8e7f7d9b257d8ed5fbbaf42932498075fed0ace88a9eb81f5627f6_U256, 0x1d9655f652309014d29e00ef35a2089bfff8dc1c816f0dc9ca34bdb5460c8705_U256, ], [ 0x04df5a56ff95bcafb051f7b1cd43a99ba731ff67e47032058fe3d4185697cc7d_U256, 0x0672d995f8fff640151b3d290cedaf148690a10a8c8424a7f6ec282b6e4be828_U256, 0x099952b414884454b21200d7ffafdd5f0c9a9dcc06f2708e9fc1d8209b5c75b9_U256, ], [ 0x052cba2255dfd00c7c483143ba8d469448e43586a9b4cd9183fd0e843a6b9fa6_U256, 0x0b8badee690adb8eb0bd74712b7999af82de55707251ad7716077cb93c464ddc_U256, 0x119b1590f13307af5a1ee651020c07c749c15d60683a8050b963d0a8e4b2bdd1_U256, ], [ 0x03150b7cd6d5d17b2529d36be0f67b832c4acfc884ef4ee5ce15be0bfb4a8d09_U256, 0x2cc6182c5e14546e3cf1951f173912355374efb83d80898abe69cb317c9ea565_U256, 0x005032551e6378c450cfe129a404b3764218cadedac14e2b92d2cd73111bf0f9_U256, ], [ 0x233237e3289baa34bb147e972ebcb9516469c399fcc069fb88f9da2cc28276b5_U256, 0x05c8f4f4ebd4a6e3c980d31674bfbe6323037f21b34ae5a4e80c2d4c24d60280_U256, 0x0a7b1db13042d396ba05d818a319f25252bcf35ef3aeed91ee1f09b2590fc65b_U256, ], [ 0x2a73b71f9b210cf5b14296572c9d32dbf156e2b086ff47dc5df542365a404ec0_U256, 0x1ac9b0417abcc9a1935107e9ffc91dc3ec18f2c4dbe7f22976a760bb5c50c460_U256, 0x12c0339ae08374823fabb076707ef479269f3e4d6cb104349015ee046dc93fc0_U256, ], [ 0x0b7475b102a165ad7f5b18db4e1e704f52900aa3253baac68246682e56e9a28e_U256, 0x037c2849e191ca3edb1c5e49f6e8b8917c843e379366f2ea32ab3aa88d7f8448_U256, 0x05a6811f8556f014e92674661e217e9bd5206c5c93a07dc145fdb176a716346f_U256, ], [ 0x29a795e7d98028946e947b75d54e9f044076e87a7b2883b47b675ef5f38bd66e_U256, 0x20439a0c84b322eb45a3857afc18f5826e8c7382c8a1585c507be199981fd22f_U256, 0x2e0ba8d94d9ecf4a94ec2050c7371ff1bb50f27799a84b6d4a2a6f2a0982c887_U256, ], [ 0x143fd115ce08fb27ca38eb7cce822b4517822cd2109048d2e6d0ddcca17d71c8_U256, 0x0c64cbecb1c734b857968dbbdcf813cdf8611659323dbcbfc84323623be9caf1_U256, 0x028a305847c683f646fca925c163ff5ae74f348d62c2b670f1426cef9403da53_U256, ], [ 0x2e4ef510ff0b6fda5fa940ab4c4380f26a6bcb64d89427b824d6755b5db9e30c_U256, 0x0081c95bc43384e663d79270c956ce3b8925b4f6d033b078b96384f50579400e_U256, 0x2ed5f0c91cbd9749187e2fade687e05ee2491b349c039a0bba8a9f4023a0bb38_U256, ], [ 0x30509991f88da3504bbf374ed5aae2f03448a22c76234c8c990f01f33a735206_U256, 0x1c3f20fd55409a53221b7c4d49a356b9f0a1119fb2067b41a7529094424ec6ad_U256, 0x10b4e7f3ab5df003049514459b6e18eec46bb2213e8e131e170887b47ddcb96c_U256, ], [ 0x2a1982979c3ff7f43ddd543d891c2abddd80f804c077d775039aa3502e43adef_U256, 0x1c74ee64f15e1db6feddbead56d6d55dba431ebc396c9af95cad0f1315bd5c91_U256, 0x07533ec850ba7f98eab9303cace01b4b9e4f2e8b82708cfa9c2fe45a0ae146a0_U256, ], [ 0x21576b438e500449a151e4eeaf17b154285c68f42d42c1808a11abf3764c0750_U256, 0x2f17c0559b8fe79608ad5ca193d62f10bce8384c815f0906743d6930836d4a9e_U256, 0x2d477e3862d07708a79e8aae946170bc9775a4201318474ae665b0b1b7e2730e_U256, ], [ 0x162f5243967064c390e095577984f291afba2266c38f5abcd89be0f5b2747eab_U256, 0x2b4cb233ede9ba48264ecd2c8ae50d1ad7a8596a87f29f8a7777a70092393311_U256, 0x2c8fbcb2dd8573dc1dbaf8f4622854776db2eece6d85c4cf4254e7c35e03b07a_U256, ], [ 0x1d6f347725e4816af2ff453f0cd56b199e1b61e9f601e9ade5e88db870949da9_U256, 0x204b0c397f4ebe71ebc2d8b3df5b913df9e6ac02b68d31324cd49af5c4565529_U256, 0x0c4cb9dc3c4fd8174f1149b3c63c3c2f9ecb827cd7dc25534ff8fb75bc79c502_U256, ], [ 0x174ad61a1448c899a25416474f4930301e5c49475279e0639a616ddc45bc7b54_U256, 0x1a96177bcf4d8d89f759df4ec2f3cde2eaaa28c177cc0fa13a9816d49a38d2ef_U256, 0x066d04b24331d71cd0ef8054bc60c4ff05202c126a233c1a8242ace360b8a30a_U256, ], [ 0x2a4c4fc6ec0b0cf52195782871c6dd3b381cc65f72e02ad527037a62aa1bd804_U256, 0x13ab2d136ccf37d447e9f2e14a7cedc95e727f8446f6d9d7e55afc01219fd649_U256, 0x1121552fca26061619d24d843dc82769c1b04fcec26f55194c2e3e869acc6a9a_U256, ], [ 0x00ef653322b13d6c889bc81715c37d77a6cd267d595c4a8909a5546c7c97cff1_U256, 0x0e25483e45a665208b261d8ba74051e6400c776d652595d9845aca35d8a397d3_U256, 0x29f536dcb9dd7682245264659e15d88e395ac3d4dde92d8c46448db979eeba89_U256, ], [ 0x2a56ef9f2c53febadfda33575dbdbd885a124e2780bbea170e456baace0fa5be_U256, 0x1c8361c78eb5cf5decfb7a2d17b5c409f2ae2999a46762e8ee416240a8cb9af1_U256, 0x151aff5f38b20a0fc0473089aaf0206b83e8e68a764507bfd3d0ab4be74319c5_U256, ], [ 0x04c6187e41ed881dc1b239c88f7f9d43a9f52fc8c8b6cdd1e76e47615b51f100_U256, 0x13b37bd80f4d27fb10d84331f6fb6d534b81c61ed15776449e801b7ddc9c2967_U256, 0x01a5c536273c2d9df578bfbd32c17b7a2ce3664c2a52032c9321ceb1c4e8a8e4_U256, ], [ 0x2ab3561834ca73835ad05f5d7acb950b4a9a2c666b9726da832239065b7c3b02_U256, 0x1d4d8ec291e720db200fe6d686c0d613acaf6af4e95d3bf69f7ed516a597b646_U256, 0x041294d2cc484d228f5784fe7919fd2bb925351240a04b711514c9c80b65af1d_U256, ], [ 0x154ac98e01708c611c4fa715991f004898f57939d126e392042971dd90e81fc6_U256, 0x0b339d8acca7d4f83eedd84093aef51050b3684c88f8b0b04524563bc6ea4da4_U256, 0x0955e49e6610c94254a4f84cfbab344598f0e71eaff4a7dd81ed95b50839c82e_U256, ], [ 0x06746a6156eba54426b9e22206f15abca9a6f41e6f535c6f3525401ea0654626_U256, 0x0f18f5a0ecd1423c496f3820c549c27838e5790e2bd0a196ac917c7ff32077fb_U256, 0x04f6eeca1751f7308ac59eff5beb261e4bb563583ede7bc92a738223d6f76e13_U256, ], [ 0x2b56973364c4c4f5c1a3ec4da3cdce038811eb116fb3e45bc1768d26fc0b3758_U256, 0x123769dd49d5b054dcd76b89804b1bcb8e1392b385716a5d83feb65d437f29ef_U256, 0x2147b424fc48c80a88ee52b91169aacea989f6446471150994257b2fb01c63e9_U256, ], [ 0x0fdc1f58548b85701a6c5505ea332a29647e6f34ad4243c2ea54ad897cebe54d_U256, 0x12373a8251fea004df68abcf0f7786d4bceff28c5dbbe0c3944f685cc0a0b1f2_U256, 0x21e4f4ea5f35f85bad7ea52ff742c9e8a642756b6af44203dd8a1f35c1a90035_U256, ], [ 0x16243916d69d2ca3dfb4722224d4c462b57366492f45e90d8a81934f1bc3b147_U256, 0x1efbe46dd7a578b4f66f9adbc88b4378abc21566e1a0453ca13a4159cac04ac2_U256, 0x07ea5e8537cf5dd08886020e23a7f387d468d5525be66f853b672cc96a88969a_U256, ], [ 0x05a8c4f9968b8aa3b7b478a30f9a5b63650f19a75e7ce11ca9fe16c0b76c00bc_U256, 0x20f057712cc21654fbfe59bd345e8dac3f7818c701b9c7882d9d57b72a32e83f_U256, 0x04a12ededa9dfd689672f8c67fee31636dcd8e88d01d49019bd90b33eb33db69_U256, ], [ 0x27e88d8c15f37dcee44f1e5425a51decbd136ce5091a6767e49ec9544ccd101a_U256, 0x2feed17b84285ed9b8a5c8c5e95a41f66e096619a7703223176c41ee433de4d1_U256, 0x1ed7cc76edf45c7c404241420f729cf394e5942911312a0d6972b8bd53aff2b8_U256, ], [ 0x15742e99b9bfa323157ff8c586f5660eac6783476144cdcadf2874be45466b1a_U256, 0x1aac285387f65e82c895fc6887ddf40577107454c6ec0317284f033f27d0c785_U256, 0x25851c3c845d4790f9ddadbdb6057357832e2e7a49775f71ec75a96554d67c77_U256, ], [ 0x15a5821565cc2ec2ce78457db197edf353b7ebba2c5523370ddccc3d9f146a67_U256, 0x2411d57a4813b9980efa7e31a1db5966dcf64f36044277502f15485f28c71727_U256, 0x002e6f8d6520cd4713e335b8c0b6d2e647e9a98e12f4cd2558828b5ef6cb4c9b_U256, ], [ 0x2ff7bc8f4380cde997da00b616b0fcd1af8f0e91e2fe1ed7398834609e0315d2_U256, 0x00b9831b948525595ee02724471bcd182e9521f6b7bb68f1e93be4febb0d3cbe_U256, 0x0a2f53768b8ebf6a86913b0e57c04e011ca408648a4743a87d77adbf0c9c3512_U256, ], [ 0x00248156142fd0373a479f91ff239e960f599ff7e94be69b7f2a290305e1198d_U256, 0x171d5620b87bfb1328cf8c02ab3f0c9a397196aa6a542c2350eb512a2b2bcda9_U256, 0x170a4f55536f7dc970087c7c10d6fad760c952172dd54dd99d1045e4ec34a808_U256, ], [ 0x29aba33f799fe66c2ef3134aea04336ecc37e38c1cd211ba482eca17e2dbfae1_U256, 0x1e9bc179a4fdd758fdd1bb1945088d47e70d114a03f6a0e8b5ba650369e64973_U256, 0x1dd269799b660fad58f7f4892dfb0b5afeaad869a9c4b44f9c9e1c43bdaf8f09_U256, ], [ 0x22cdbc8b70117ad1401181d02e15459e7ccd426fe869c7c95d1dd2cb0f24af38_U256, 0x0ef042e454771c533a9f57a55c503fcefd3150f52ed94a7cd5ba93b9c7dacefd_U256, 0x11609e06ad6c8fe2f287f3036037e8851318e8b08a0359a03b304ffca62e8284_U256, ], [ 0x1166d9e554616dba9e753eea427c17b7fecd58c076dfe42708b08f5b783aa9af_U256, 0x2de52989431a859593413026354413db177fbf4cd2ac0b56f855a888357ee466_U256, 0x3006eb4ffc7a85819a6da492f3a8ac1df51aee5b17b8e89d74bf01cf5f71e9ad_U256, ], [ 0x2af41fbb61ba8a80fdcf6fff9e3f6f422993fe8f0a4639f962344c8225145086_U256, 0x119e684de476155fe5a6b41a8ebc85db8718ab27889e85e781b214bace4827c3_U256, 0x1835b786e2e8925e188bea59ae363537b51248c23828f047cff784b97b3fd800_U256, ], [ 0x28201a34c594dfa34d794996c6433a20d152bac2a7905c926c40e285ab32eeb6_U256, 0x083efd7a27d1751094e80fefaf78b000864c82eb571187724a761f88c22cc4e7_U256, 0x0b6f88a3577199526158e61ceea27be811c16df7774dd8519e079564f61fd13b_U256, ], [ 0x0ec868e6d15e51d9644f66e1d6471a94589511ca00d29e1014390e6ee4254f5b_U256, 0x2af33e3f866771271ac0c9b3ed2e1142ecd3e74b939cd40d00d937ab84c98591_U256, 0x0b520211f904b5e7d09b5d961c6ace7734568c547dd6858b364ce5e47951f178_U256, ], [ 0x0b2d722d0919a1aad8db58f10062a92ea0c56ac4270e822cca228620188a1d40_U256, 0x1f790d4d7f8cf094d980ceb37c2453e957b54a9991ca38bbe0061d1ed6e562d4_U256, 0x0171eb95dfbf7d1eaea97cd385f780150885c16235a2a6a8da92ceb01e504233_U256, ], [ 0x0c2d0e3b5fd57549329bf6885da66b9b790b40defd2c8650762305381b168873_U256, 0x1162fb28689c27154e5a8228b4e72b377cbcafa589e283c35d3803054407a18d_U256, 0x2f1459b65dee441b64ad386a91e8310f282c5a92a89e19921623ef8249711bc0_U256, ], [ 0x1e6ff3216b688c3d996d74367d5cd4c1bc489d46754eb712c243f70d1b53cfbb_U256, 0x01ca8be73832b8d0681487d27d157802d741a6f36cdc2a0576881f9326478875_U256, 0x1f7735706ffe9fc586f976d5bdf223dc680286080b10cea00b9b5de315f9650e_U256, ], [ 0x2522b60f4ea3307640a0c2dce041fba921ac10a3d5f096ef4745ca838285f019_U256, 0x23f0bee001b1029d5255075ddc957f833418cad4f52b6c3f8ce16c235572575b_U256, 0x2bc1ae8b8ddbb81fcaac2d44555ed5685d142633e9df905f66d9401093082d59_U256, ], [ 0x0f9406b8296564a37304507b8dba3ed162371273a07b1fc98011fcd6ad72205f_U256, 0x2360a8eb0cc7defa67b72998de90714e17e75b174a52ee4acb126c8cd995f0a8_U256, 0x15871a5cddead976804c803cbaef255eb4815a5e96df8b006dcbbc2767f88948_U256, ], [ 0x193a56766998ee9e0a8652dd2f3b1da0362f4f54f72379544f957ccdeefb420f_U256, 0x2a394a43934f86982f9be56ff4fab1703b2e63c8ad334834e4309805e777ae0f_U256, 0x1859954cfeb8695f3e8b635dcb345192892cd11223443ba7b4166e8876c0d142_U256, ], [ 0x04e1181763050e58013444dbcb99f1902b11bc25d90bbdca408d3819f4fed32b_U256, 0x0fdb253dee83869d40c335ea64de8c5bb10eb82db08b5e8b1f5e5552bfd05f23_U256, 0x058cbe8a9a5027bdaa4efb623adead6275f08686f1c08984a9d7c5bae9b4f1c0_U256, ], [ 0x1382edce9971e186497eadb1aeb1f52b23b4b83bef023ab0d15228b4cceca59a_U256, 0x03464990f045c6ee0819ca51fd11b0be7f61b8eb99f14b77e1e6634601d9e8b5_U256, 0x23f7bfc8720dc296fff33b41f98ff83c6fcab4605db2eb5aaa5bc137aeb70a58_U256, ], [ 0x0a59a158e3eec2117e6e94e7f0e9decf18c3ffd5e1531a9219636158bbaf62f2_U256, 0x06ec54c80381c052b58bf23b312ffd3ce2c4eba065420af8f4c23ed0075fd07b_U256, 0x118872dc832e0eb5476b56648e867ec8b09340f7a7bcb1b4962f0ff9ed1f9d01_U256, ], [ 0x13d69fa127d834165ad5c7cba7ad59ed52e0b0f0e42d7fea95e1906b520921b1_U256, 0x169a177f63ea681270b1c6877a73d21bde143942fb71dc55fd8a49f19f10c77b_U256, 0x04ef51591c6ead97ef42f287adce40d93abeb032b922f66ffb7e9a5a7450544d_U256, ], [ 0x256e175a1dc079390ecd7ca703fb2e3b19ec61805d4f03ced5f45ee6dd0f69ec_U256, 0x30102d28636abd5fe5f2af412ff6004f75cc360d3205dd2da002813d3e2ceeb2_U256, 0x10998e42dfcd3bbf1c0714bc73eb1bf40443a3fa99bef4a31fd31be182fcc792_U256, ], [ 0x193edd8e9fcf3d7625fa7d24b598a1d89f3362eaf4d582efecad76f879e36860_U256, 0x18168afd34f2d915d0368ce80b7b3347d1c7a561ce611425f2664d7aa51f0b5d_U256, 0x29383c01ebd3b6ab0c017656ebe658b6a328ec77bc33626e29e2e95b33ea6111_U256, ], [ 0x10646d2f2603de39a1f4ae5e7771a64a702db6e86fb76ab600bf573f9010c711_U256, 0x0beb5e07d1b27145f575f1395a55bf132f90c25b40da7b3864d0242dcb1117fb_U256, 0x16d685252078c133dc0d3ecad62b5c8830f95bb2e54b59abdffbf018d96fa336_U256, ], [ 0x0a6abd1d833938f33c74154e0404b4b40a555bbbec21ddfafd672dd62047f01a_U256, 0x1a679f5d36eb7b5c8ea12a4c2dedc8feb12dffeec450317270a6f19b34cf1860_U256, 0x0980fb233bd456c23974d50e0ebfde4726a423eada4e8f6ffbc7592e3f1b93d6_U256, ], [ 0x161b42232e61b84cbf1810af93a38fc0cece3d5628c9282003ebacb5c312c72b_U256, 0x0ada10a90c7f0520950f7d47a60d5e6a493f09787f1564e5d09203db47de1a0b_U256, 0x1a730d372310ba82320345a29ac4238ed3f07a8a2b4e121bb50ddb9af407f451_U256, ], [ 0x2c8120f268ef054f817064c369dda7ea908377feaba5c4dffbda10ef58e8c556_U256, 0x1c7c8824f758753fa57c00789c684217b930e95313bcb73e6e7b8649a4968f70_U256, 0x2cd9ed31f5f8691c8e39e4077a74faa0f400ad8b491eb3f7b47b27fa3fd1cf77_U256, ], [ 0x23ff4f9d46813457cf60d92f57618399a5e022ac321ca550854ae23918a22eea_U256, 0x09945a5d147a4f66ceece6405dddd9d0af5a2c5103529407dff1ea58f180426d_U256, 0x188d9c528025d4c2b67660c6b771b90f7c7da6eaa29d3f268a6dd223ec6fc630_U256, ], [ 0x3050e37996596b7f81f68311431d8734dba7d926d3633595e0c0d8ddf4f0f47f_U256, 0x15af1169396830a91600ca8102c35c426ceae5461e3f95d89d829518d30afd78_U256, 0x1da6d09885432ea9a06d9f37f873d985dae933e351466b2904284da3320d8acc_U256, ], [ 0x2796ea90d269af29f5f8acf33921124e4e4fad3dbe658945e546ee411ddaa9cb_U256, 0x202d7dd1da0f6b4b0325c8b3307742f01e15612ec8e9304a7cb0319e01d32d60_U256, 0x096d6790d05bb759156a952ba263d672a2d7f9c788f4c831a29dace4c0f8be5f_U256, ], [ 0x054efa1f65b0fce283808965275d877b438da23ce5b13e1963798cb1447d25a4_U256, 0x1b162f83d917e93edb3308c29802deb9d8aa690113b2e14864ccf6e18e4165f1_U256, 0x21e5241e12564dd6fd9f1cdd2a0de39eedfefc1466cc568ec5ceb745a0506edc_U256, ], [ 0x1cfb5662e8cf5ac9226a80ee17b36abecb73ab5f87e161927b4349e10e4bdf08_U256, 0x0f21177e302a771bbae6d8d1ecb373b62c99af346220ac0129c53f666eb24100_U256, 0x1671522374606992affb0dd7f71b12bec4236aede6290546bcef7e1f515c2320_U256, ], [ 0x0fa3ec5b9488259c2eb4cf24501bfad9be2ec9e42c5cc8ccd419d2a692cad870_U256, 0x193c0e04e0bd298357cb266c1506080ed36edce85c648cc085e8c57b1ab54bba_U256, 0x102adf8ef74735a27e9128306dcbc3c99f6f7291cd406578ce14ea2adaba68f8_U256, ], [ 0x0fe0af7858e49859e2a54d6f1ad945b1316aa24bfbdd23ae40a6d0cb70c3eab1_U256, 0x216f6717bbc7dedb08536a2220843f4e2da5f1daa9ebdefde8a5ea7344798d22_U256, 0x1da55cc900f0d21f4a3e694391918a1b3c23b2ac773c6b3ef88e2e4228325161_U256, ], ]; } ================================================ FILE: crates/poseidon/src/lib.rs ================================================ use ruint::aliases::U256; use semaphore_rs_hasher::Hasher; pub mod constants; pub mod poseidon; pub struct Poseidon; impl Hasher for Poseidon { type Hash = U256; fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash { poseidon::hash2(*left, *right) } } ================================================ FILE: crates/poseidon/src/poseidon.rs ================================================ use ark_bn254::Fr; use ark_ff::{Field, Zero}; use once_cell::sync::Lazy; use ruint::aliases::U256; use crate::constants; static M1: Lazy<[[Fr; 2]; 2]> = Lazy::new(|| { constants::M1 .iter() .map(|row| { row.iter() .map(Fr::try_from) .collect::, _>>() .unwrap() .try_into() .unwrap() }) .collect::>() .try_into() .unwrap() }); static C1: Lazy<[[Fr; 2]; 64]> = Lazy::new(|| { constants::C1 .iter() .map(|row| { row.iter() .map(Fr::try_from) .collect::, _>>() .unwrap() .try_into() .unwrap() }) .collect::>() .try_into() .unwrap() }); static M: Lazy<[[Fr; 3]; 3]> = Lazy::new(|| { constants::M .iter() .map(|row| { row.iter() .map(Fr::try_from) .collect::, _>>() .unwrap() .try_into() .unwrap() }) .collect::>() .try_into() .unwrap() }); static C: Lazy<[[Fr; 3]; 65]> = Lazy::new(|| { constants::C .iter() .map(|row| { row.iter() .map(Fr::try_from) .collect::, _>>() .unwrap() .try_into() .unwrap() }) .collect::>() .try_into() .unwrap() }); /// Compute the one-value Poseidon hash function. /// /// # Panics /// /// Panics if `input` is not a valid field element. #[must_use] pub fn hash1(value: U256) -> U256 { let value = value.try_into().unwrap(); let mut state = [Fr::zero(), value]; for i in 0..64 { // Add round constants state[0] += C1[i][0]; state[1] += C1[i][1]; // SubWords, S-Box: Exponentiate state[0] = state[0].pow([5]); if !(4..60).contains(&i) { state[1] = state[1].pow([5]); } // MixLayer: Multiply by maximum distance separable matrix state = [ M1[0][0] * state[0] + M1[0][1] * state[1], M1[1][0] * state[0] + M1[1][1] * state[1], ]; } state[0].into() } /// Compute the two-value Poseidon hash function. /// /// # Panics /// /// Panics if `left`, `right` are not a valid field element. #[must_use] pub fn hash2(left: U256, right: U256) -> U256 { let left = left.try_into().unwrap(); let right = right.try_into().unwrap(); let mut state = [Fr::zero(), left, right]; for i in 0..65 { // Add round constants state[0] += C[i][0]; state[1] += C[i][1]; state[2] += C[i][2]; // SubWords, S-Box: Exponentiate state[0] = state[0].pow([5]); if !(4..61).contains(&i) { state[1] = state[1].pow([5]); state[2] = state[2].pow([5]); } // MixLayer: Multiply by maximum distance separable matrix state = [ M[0][0] * state[0] + M[0][1] * state[1] + M[0][2] * state[2], M[1][0] * state[0] + M[1][1] * state[1] + M[1][2] * state[2], M[2][0] * state[0] + M[2][1] * state[1] + M[2][2] * state[2], ]; } state[0].into() } #[cfg(test)] mod tests { use ruint::uint; use super::*; #[test] fn test_hash1() { uint! { assert_eq!(hash1(0_U256), 0x2a09a9fd93c590c26b91effbb2499f07e8f7aa12e2b4940a3aed2411cb65e11c_U256); } } #[test] fn test_hash2() { uint! { assert_eq!(hash2(0_U256, 0_U256), 0x2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864_U256); assert_eq!(hash2(31213_U256, 132_U256), 0x303f59cd0831b5633bcda50514521b33776b5d4280eb5868ba1dbbe2e4d76ab5_U256); } } } ================================================ FILE: crates/proof/Cargo.toml ================================================ [package] name = "semaphore-rs-proof" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] semaphore-rs-utils.workspace = true semaphore-rs-ark-circom = { workspace = true, optional = true } ruint.workspace = true serde.workspace = true serde_json.workspace = true ark-ec = { workspace = true, optional = true } ark-groth16 = { workspace = true, optional = true } ark-bn254 = { workspace = true, optional = true } alloy-core = { workspace = true } lazy_static.workspace = true getrandom.workspace = true hex.workspace = true [features] default = ["ark"] ark = ["dep:semaphore-rs-ark-circom", "dep:ark-ec", "dep:ark-groth16", "dep:ark-bn254"] ================================================ FILE: crates/proof/src/ark.rs ================================================ use super::Proof; use ark_bn254::Config; use ark_ec::bn::Bn; use ark_groth16::Proof as ArkProof; use semaphore_rs_ark_circom::ethereum::AffineError; impl From>> for Proof { fn from(proof: ArkProof>) -> Self { let proof = semaphore_rs_ark_circom::ethereum::Proof::from(proof); let (a, b, c) = proof.as_tuple(); Self(a, b, c) } } impl TryFrom for ArkProof> { type Error = AffineError; fn try_from(proof: Proof) -> Result { let eth_proof = semaphore_rs_ark_circom::ethereum::Proof { a: semaphore_rs_ark_circom::ethereum::G1 { x: proof.0 .0, y: proof.0 .1, }, #[rustfmt::skip] // Rustfmt inserts some confusing spaces b: semaphore_rs_ark_circom::ethereum::G2 { // The order of coefficients is flipped. x: [proof.1.0[1], proof.1.0[0]], y: [proof.1.1[1], proof.1.1[0]], }, c: semaphore_rs_ark_circom::ethereum::G1 { x: proof.2 .0, y: proof.2 .1, }, }; // This conversion can fail if points are not on the curve. eth_proof.try_into() } } ================================================ FILE: crates/proof/src/compression.rs ================================================ //! Groth16 proof compression //! //! Ported from https://github.com/worldcoin/world-id-state-bridge/blob/main/src/SemaphoreVerifier.sol //! //! Based upon work in https://xn--2-umb.com/23/bn254-compression/ use ruint::aliases::U256; use ruint::uint; use serde::{Deserialize, Serialize}; use super::{Proof, G1, G2}; use lazy_static::lazy_static; /// Base field Fp order P pub const P: U256 = uint! { 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47_U256 }; // A helper for a frequently used constants pub const ONE: U256 = uint! { 1_U256 }; pub const TWO: U256 = uint! { 2_U256 }; pub const THREE: U256 = uint! { 3_U256 }; pub const FOUR: U256 = uint! { 4_U256 }; lazy_static! { /// Exponent for the square root in Fp pub static ref EXP_SQRT_FP: U256 = (P + ONE) / FOUR; /// Exponent for the inverse in Fp pub static ref EXP_INVERSE_FP: U256 = P - TWO; } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CompressedProof(pub U256, pub (U256, U256), pub U256); impl CompressedProof { pub const fn from_flat(flat: [U256; 4]) -> Self { let [a, b0, b1, c] = flat; Self(a, (b0, b1), c) } pub const fn flatten(self) -> [U256; 4] { let Self(a, (b0, b1), c) = self; [a, b0, b1, c] } } pub fn compress_proof(proof: Proof) -> Option { let Proof(g1a, g2, g1b) = proof; // NOTE: Order of real and imaginary parts in the proof data is flipped let ([x0, x1], [y0, y1]) = g2; let g2 = ([x1, x0], [y1, y0]); let a = compress_g1(g1a)?; // NOTE: G2 compressed repr is flipped let (c0, c1) = compress_g2(g2)?; let c = (c1, c0); let b = compress_g1(g1b)?; Some(CompressedProof(a, c, b)) } pub fn decompress_proof(compressed: CompressedProof) -> Option { let CompressedProof(a, c, b) = compressed; let g1a = decompress_g1(a)?; // NOTE: G2 compressed repr is flipped let (c1, c0) = c; let c = (c0, c1); let g2 = decompress_g2(c)?; let g1b = decompress_g1(b)?; // Unswap let ([x1, x0], [y1, y0]) = g2; let g2 = ([x0, x1], [y0, y1]); Some(Proof(g1a, g2, g1b)) } pub fn compress_g1((x, y): G1) -> Option { if x >= P || y >= P { return None; // Point not in field } if x == U256::ZERO && y == U256::ZERO { return Some(U256::ZERO); // Point at infinity } let y_pos = sqrt_fp(x.pow_mod(THREE, P).add_mod(THREE, P))?; if y == y_pos { Some(x << 1) } else if y == neg_fp(y_pos) { Some(x << 1 | ONE) } else { None } } pub fn decompress_g1(c: U256) -> Option { if c == U256::ZERO { return Some((U256::ZERO, U256::ZERO)); // Point at infinity } let negate = c & ONE == ONE; let x: U256 = c >> 1; if x >= P { return None; } let y2 = x.pow_mod(THREE, P).add_mod(THREE, P); let mut y = sqrt_fp(y2)?; if negate { y = neg_fp(y); } Some((x, y)) } /// Compresses the pub fn compress_g2(([x0, x1], [y0, y1]): G2) -> Option<(U256, U256)> { if x0 >= P || x1 >= P || y0 >= P || y1 >= P { return None; // Point not in field } if (x0 | x1 | y0 | y1) == U256::ZERO { return Some((U256::ZERO, U256::ZERO)); // Point at infinity } // Compute y^2 let n3ab = x0.mul_mod(x1, P).mul_mod(P - THREE, P); let a_3 = x0.pow_mod(THREE, P); let b_3 = x1.pow_mod(THREE, P); let y0_pos = U256::from(27) .mul_mod(U256::from(82).inv_mod(P).unwrap(), P) .add_mod(a_3.add_mod(n3ab.mul_mod(x1, P), P), P); let y1_pos = neg_fp( THREE .mul_mod(U256::from(82).inv_mod(P).unwrap(), P) .add_mod(b_3.add_mod(n3ab.mul_mod(x0, P), P), P), ); // Determine hint bit let d = sqrt_fp( y0_pos .mul_mod(y0_pos, P) .add_mod(y1_pos.mul_mod(y1_pos, P), P), )?; let hint = !is_square_fp(y0_pos.add_mod(d, P).mul_mod(TWO.inv_mod(P).unwrap(), P)); // Recover y let (new_y0_pos, new_y1_pos) = sqrt_fp2(y0_pos, y1_pos, hint)?; let hint = if hint { TWO } else { U256::ZERO }; if y0 == new_y0_pos && y1 == new_y1_pos { Some(((x0 << 2) | hint, x1)) } else if y0 == neg_fp(new_y0_pos) && y1 == neg_fp(new_y1_pos) { Some(((x0 << 2) | hint | ONE, x1)) } else { None } } pub fn decompress_g2((c0, c1): (U256, U256)) -> Option { if c0 == U256::ZERO && c1 == U256::ZERO { return Some(([U256::ZERO, U256::ZERO], [U256::ZERO, U256::ZERO])); // Point at infinity } let negate = c0 & ONE == ONE; let hint = c0 & TWO == TWO; let x0: U256 = c0 >> 2; let x1 = c1; if x0 >= P || x1 >= P { return None; } let n3ab = x0.mul_mod(x1, P).mul_mod(P - THREE, P); let a_3 = x0.pow_mod(THREE, P); let b_3 = x1.pow_mod(THREE, P); let y0 = U256::from(27) .mul_mod(U256::from(82).inv_mod(P)?, P) .add_mod(a_3.add_mod(n3ab.mul_mod(x1, P), P), P); let y1 = neg_fp( THREE .mul_mod(U256::from(82).inv_mod(P)?, P) .add_mod(b_3.add_mod(n3ab.mul_mod(x0, P), P), P), ); let (mut y0, mut y1) = sqrt_fp2(y0, y1, hint)?; if negate { y0 = neg_fp(y0); y1 = neg_fp(y1); } Some(([x0, x1], [y0, y1])) } fn sqrt_fp(a: U256) -> Option { let x = a.pow_mod(*EXP_SQRT_FP, P); if x.mul_mod(x, P) == a { Some(x) } else { None } } fn sqrt_fp2(a0: U256, a1: U256, hint: bool) -> Option<(U256, U256)> { let mut d = sqrt_fp(a0.pow_mod(TWO, P).add_mod(a1.pow_mod(TWO, P), P))?; if hint { d = neg_fp(d); } let frac_1_2 = ONE.mul_mod(TWO.inv_mod(P)?, P); let x0 = sqrt_fp(a0.add_mod(d, P).mul_mod(frac_1_2, P))?; let x1 = a1.mul_mod(invert_fp(x0.mul_mod(TWO, P))?, P); if a0 != x0.pow_mod(TWO, P).add_mod(neg_fp(x1.pow_mod(TWO, P)), P) || a1 != TWO.mul_mod(x0.mul_mod(x1, P), P) { return None; } Some((x0, x1)) } fn is_square_fp(a: U256) -> bool { let x = a.pow_mod(*EXP_SQRT_FP, P); x.mul_mod(x, P) == a } /// Inversion in Fp /// /// Returns a number x such that a * x = 1 in Fp /// Returns None if the inverse does not exist fn invert_fp(a: U256) -> Option { let x = a.pow_mod(*EXP_INVERSE_FP, P); if a.mul_mod(x, P) != ONE { return None; } Some(x) } fn neg_fp(a: U256) -> U256 { P.wrapping_sub(a % P) % P } #[cfg(test)] mod tests { use super::*; #[test] fn inversion() { let v = uint! { 4598362786468342265918458423096940256393720972438048893356218087518821823203_U256 }; let inverted = invert_fp(v).unwrap(); let exp_inverted = uint! { 4182222526301715069940346543278816173622053692765626450942898397518664864041_U256 }; assert_eq!(exp_inverted, inverted); } #[test] fn square_root_fp() { let v = uint! { 14471043194638943579446425262583282548539507047061604313953794288955195726209_U256 }; let exp_sqrt = uint! { 13741342543520938546471415319044405232187715299443307089577869276344592329757_U256 }; let sqrt = sqrt_fp(v).unwrap(); assert_eq!(exp_sqrt, sqrt); } #[test] fn square_root_fp_2() { let (a, b) = sqrt_fp2(uint!{17473058728477435457299093362519578563618705081729024467362715416915525458528_U256}, uint!{17683468329848516541101685027677188007795188556813329975791177956431310972350_U256}, false).unwrap(); let exp_a = uint! {10193706077588260514783319931179623845729747565730309463634080055351233087269_U256}; let exp_b = uint! {2911435556167431587172450261242327574185987927358833959334220021362478804490_U256}; assert_eq!(exp_a, a); assert_eq!(exp_b, b); } // The literal values below are taken from the proof in the following tx: https://etherscan.io/tx/0x53309842294be8c2b9fd694c4e86a5ab031c0d58750978fb3d6f60de16eaa897 // Raw proof data is: // 20565048055856194013099208963146657799256893353279242520150547463020687826541 // 16286013012747852737396822706018267259565592188907848191354824303311847109059 // 4348608846293503080802796983494208797681981448804902149317789801083784587558 // 6172488348732750834133346196464201580503416389945891763609808290085997580078 // 3229429189805934086496276224876305383924675874777054942516982958483565949767 // 944252930093106871283598150477854448876343937304805759422971930315581301659 // 18318130744212307125672524358864792312717149086464333958791498157127232409959 // 8256141885907329266852096557308020923997215847794048916749940281741155521604 // // Note that for the G2 compression test the order of real and imaginary is flipped // // The expected compressed data is generated with the SemaphoreVerifier implementation // in world-id-state-bridge using chisel. // // Unfortunately the `compress_g1` and `compress_g2` methods are set to `internal` so // the approach is a little hacky, but steps to regenerate these values are as follows: // 1. Change `internal` to `public` in `SemaphoreVerifier.sol` // 2. Start `chisel` // 3. Execute the following in chisel repl // ``` // > import {SemaphoreVerifier} from "src/SemaphoreVerifier.sol"; // > SemaphoreVerifier ve = new SemaphoreVerifier(); // ``` // 4. Now you can generate the expected data fixtures using e.g. // ``` // > ve.compress_g1(0x19ded61ab5c58fdb12367526c6bc04b9186d0980c4b6fd48a44093e80f9b4206, 0x2e619a034be10e9aab294f1c77a480378e84782c8519449aef0c8f6952382bda) // ``` // Note that for some reason chisel doesn't handle multiple return values that well, so you // might have to pattern match the return types, e.g. // ``` // > (uint256 a, uint256 b) = ve.compress_g2(...); // > a; // Type: uint256 // ├ Hex: 0x1dd212f101a320736a9662cac57929556777fad3e7882b022d4ba3261cf14db6 // ├ Hex (full word): 0x1dd212f101a320736a9662cac57929556777fad3e7882b022d4ba3261cf14db6 // └ Decimal: 13488241221471993734368286196608381596836013455766665997449768358320614231478 // ``` #[test] fn proof_compression() { let flat_proof: [U256; 8] = uint! { [ 20565048055856194013099208963146657799256893353279242520150547463020687826541_U256, 16286013012747852737396822706018267259565592188907848191354824303311847109059_U256, 4348608846293503080802796983494208797681981448804902149317789801083784587558_U256, 6172488348732750834133346196464201580503416389945891763609808290085997580078_U256, 3229429189805934086496276224876305383924675874777054942516982958483565949767_U256, 944252930093106871283598150477854448876343937304805759422971930315581301659_U256, 18318130744212307125672524358864792312717149086464333958791498157127232409959_U256, 8256141885907329266852096557308020923997215847794048916749940281741155521604_U256, ]}; let proof = Proof::from_flat(flat_proof); let compressed = compress_proof(proof).unwrap(); let exp_flat_compressed: [U256; 4] = uint! {[ 41130096111712388026198417926293315598513786706558485040301094926041375653083_U256, 4348608846293503080802796983494208797681981448804902149317789801083784587558_U256, 24689953394931003336533384785856806322013665559783567054439233160343990320315_U256, 36636261488424614251345048717729584625434298172928667917582996314254464819918_U256, ]}; assert_eq!(exp_flat_compressed, compressed.flatten()); let decompressed = decompress_proof(compressed).unwrap(); assert_eq!(proof, decompressed); } #[test] fn g1_compression() { let point: G1 = uint! { ( 0x19ded61ab5c58fdb12367526c6bc04b9186d0980c4b6fd48a44093e80f9b4206_U256, 0x2e619a034be10e9aab294f1c77a480378e84782c8519449aef0c8f6952382bda_U256 ) }; let exp_compressed = uint! { 0x33bdac356b8b1fb6246cea4d8d78097230da1301896dfa91488127d01f36840c_U256 }; let compressed = compress_g1(point).unwrap(); assert_eq!(exp_compressed, compressed); let decompressed = decompress_g1(compressed).unwrap(); assert_eq!(point, decompressed); } #[test] fn g2_compression() { let point: G2 = uint! { ( [ 0x077484BC4068C81CDAA598B2B15E4A5559DDFEB4F9E20AC08B52E8C9873C536D_U256, 0x25E744163329AABFB40086C09E0B54D09DFBD302CE975E71150133E46E75F0AA_U256, ], [ 0x20AF3E3AFED950A86937F4319100B19A1141FF59DA42B9670CFA57E5D83BE618_U256, 0x089C901AA5603652F8CC748F04907233C63A75302244D67FF974B05AF09948D2_U256, ] ) }; let compressed = compress_g2(point).unwrap(); let exp_compressed = uint! { (0x1dd212f101a320736a9662cac57929556777fad3e7882b022d4ba3261cf14db6_U256, 0x25e744163329aabfb40086c09e0b54d09dfbd302ce975e71150133e46e75f0aa_U256) }; assert_eq!(exp_compressed, compressed); let decompressed = decompress_g2(compressed).unwrap(); assert_eq!(point, decompressed); } #[test] fn deser() { let s = r#"["0x1",["0x2","0x3"],"0x4"]"#; let deserialized: CompressedProof = serde_json::from_str(s).unwrap(); let reserialized = serde_json::to_string(&deserialized).unwrap(); assert_eq!(s, reserialized); } } ================================================ FILE: crates/proof/src/lib.rs ================================================ use ruint::aliases::U256; use serde::{Deserialize, Serialize}; #[cfg(feature = "ark")] mod ark; pub mod compression; pub mod packing; // Matches the private G1Tup type in ark-circom. pub type G1 = (U256, U256); // Matches the private G2Tup type in ark-circom. pub type G2 = ([U256; 2], [U256; 2]); /// Wrap a proof object so we have serde support #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Proof(pub G1, pub G2, pub G1); impl Proof { pub const fn from_flat(flat: [U256; 8]) -> Self { let [x0, x1, x2, x3, x4, x5, x6, x7] = flat; Self((x0, x1), ([x2, x3], [x4, x5]), (x6, x7)) } pub const fn flatten(self) -> [U256; 8] { let Self((a0, a1), ([bx0, bx1], [by0, by1]), (c0, c1)) = self; [a0, a1, bx0, bx1, by0, by1, c0, c1] } } #[cfg(test)] mod tests { use super::*; #[test] fn deser() { let s = r#"[["0x1","0x2"],[["0x3","0x4"],["0x5","0x6"]],["0x7","0x8"]]"#; let deserialized: Proof = serde_json::from_str(s).unwrap(); let reserialized = serde_json::to_string(&deserialized).unwrap(); assert_eq!(s, reserialized); } } ================================================ FILE: crates/proof/src/packing.rs ================================================ use std::{ fmt::Display, str::{from_utf8, FromStr}, }; use alloy_core::sol_types::{ sol_data::{FixedArray, Uint}, SolType, SolValue, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::Proof; use semaphore_rs_utils::{bytes_from_hex, bytes_to_hex, deserialize_bytes, serialize_bytes}; /// A packed proof is a representation of the ZKP in a single attribute (as /// opposed to array of arrays) which is easier to transport #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PackedProof(pub [u8; 256]); impl From for PackedProof { fn from(proof: Proof) -> Self { let flat_proof = [ proof.0 .0, proof.0 .1, proof.1 .0[0], proof.1 .0[1], proof.1 .1[0], proof.1 .1[1], proof.2 .0, proof.2 .1, ]; let bytes = flat_proof.abi_encode(); let mut encoded = [0u8; 256]; encoded.copy_from_slice(&bytes[..256]); Self(encoded) } } impl From for Proof { fn from(proof: PackedProof) -> Self { let decoded = FixedArray::, 8>::abi_decode(&proof.0).unwrap(); let a = (decoded[0], decoded[1]); let b = ([decoded[2], decoded[3]], [decoded[4], decoded[5]]); let c = (decoded[6], decoded[7]); Self(a, b, c) } } impl Display for PackedProof { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let hex = bytes_to_hex::<256, 514>(&self.0); write!( f, "{}", from_utf8(&hex).expect("failed to convert to string") ) } } impl FromStr for PackedProof { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { bytes_from_hex::<256>(s).map(Self) } } impl Serialize for PackedProof { fn serialize(&self, serializer: S) -> Result { serialize_bytes::<256, 514, S>(serializer, &self.0) } } impl<'de> Deserialize<'de> for PackedProof { fn deserialize>(deserializer: D) -> Result { let bytes = deserialize_bytes::<256, _>(deserializer)?; Ok(Self(bytes)) } } #[cfg(test)] pub mod test { use super::*; use ruint::aliases::U256; #[test] fn test_serializing_proof_into_packed_proof() { let proof = Proof( (U256::from(1), U256::from(2)), ( [U256::from(3), U256::from(4)], [U256::from(5), U256::from(6)], ), (U256::from(7), U256::from(8)), ); let packed_proof = PackedProof::from(proof); assert_eq!(packed_proof.to_string(), "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008"); let proof2 = Proof::from(packed_proof); assert_eq!(proof, proof2); } #[test] fn test_parse_from_string() { let packed_proof_str = "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008"; let packed_proof = PackedProof::from_str(packed_proof_str).unwrap(); let expected_proof = Proof( (U256::from(1), U256::from(2)), ( [U256::from(3), U256::from(4)], [U256::from(5), U256::from(6)], ), (U256::from(7), U256::from(8)), ); let proof: Proof = packed_proof.into(); assert_eq!(proof, expected_proof); } #[test] fn test_parse_from_string_without_prefix() { // note the lack of 0x prefix let packed_proof_str = "00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008"; let packed_proof = PackedProof::from_str(packed_proof_str).unwrap(); let expected_proof = Proof( (U256::from(5), U256::from(6)), ( [U256::from(3), U256::from(4)], [U256::from(5), U256::from(6)], ), (U256::from(7), U256::from(8)), ); let proof: Proof = packed_proof.into(); assert_eq!(proof, expected_proof); } #[test] fn test_serialize_proof_to_json() { let packed_proof_str = "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008"; let packed_proof = PackedProof::from_str(packed_proof_str).unwrap(); let proof: Proof = packed_proof.into(); let serialized = serde_json::to_value(proof).unwrap(); assert_eq!( serialized, serde_json::json!([ ["0x1", "0x2"], [["0x3", "0x4"], ["0x5", "0x6"]], ["0x7", "0x8"] ]) ); } #[test] fn test_serialize_proof_to_json_real_numbers() { let packed_proof_str = "0x15c1fc6907219676890dfe147ee6f10b580c7881dddacb1567b3bcbfc513a54d233afda3efff43a7631990d2e79470abcbae3ccad4b920476e64745bfe97bb0a0c8c7d7434c382d590d601d951c29c8463d555867db70f9e84f7741c81c2e1e6241d2ddf1c9e6670a24109a0e9c915cd6e07d0248a384dd38d3c91e9b0419f5f0b23c5467a06eff56cc2c246ada1e7d5705afc4dc8b43fd5a6972c679a2019c5091ed6522f7924d3674d08966a008f947f9aa016a4100bb12f911326f3e1befd0acdf5a5996e00933206cbec48f3bbdcee2a4ca75f8db911c00001e5a05474872446d6f1c1506837392a30fdc73d66fd89f4e1b1a5d14b93e2ad0c5f7b777520"; let packed_proof = PackedProof::from_str(packed_proof_str).unwrap(); let proof: Proof = packed_proof.into(); let serialized = serde_json::to_value(proof).unwrap(); assert_eq!( serialized, serde_json::json!([ [ "0x15c1fc6907219676890dfe147ee6f10b580c7881dddacb1567b3bcbfc513a54d", "0x233afda3efff43a7631990d2e79470abcbae3ccad4b920476e64745bfe97bb0a" ], [ [ "0xc8c7d7434c382d590d601d951c29c8463d555867db70f9e84f7741c81c2e1e6", "0x241d2ddf1c9e6670a24109a0e9c915cd6e07d0248a384dd38d3c91e9b0419f5f" ], [ "0xb23c5467a06eff56cc2c246ada1e7d5705afc4dc8b43fd5a6972c679a2019c5", "0x91ed6522f7924d3674d08966a008f947f9aa016a4100bb12f911326f3e1befd" ] ], [ "0xacdf5a5996e00933206cbec48f3bbdcee2a4ca75f8db911c00001e5a0547487", "0x2446d6f1c1506837392a30fdc73d66fd89f4e1b1a5d14b93e2ad0c5f7b777520" ] ]) ); } #[test] fn test_deserialize_proof_from_json() { let proof_str = "[ [ \"0x15c1fc6907219676890dfe147ee6f10b580c7881dddacb1567b3bcbfc513a54d\", \"0x233afda3efff43a7631990d2e79470abcbae3ccad4b920476e64745bfe97bb0a\" ], [ [ \"0xc8c7d7434c382d590d601d951c29c8463d555867db70f9e84f7741c81c2e1e6\", \"0x241d2ddf1c9e6670a24109a0e9c915cd6e07d0248a384dd38d3c91e9b0419f5f\" ], [ \"0xb23c5467a06eff56cc2c246ada1e7d5705afc4dc8b43fd5a6972c679a2019c5\", \"0x91ed6522f7924d3674d08966a008f947f9aa016a4100bb12f911326f3e1befd\" ] ], [ \"0xacdf5a5996e00933206cbec48f3bbdcee2a4ca75f8db911c00001e5a0547487\", \"0x2446d6f1c1506837392a30fdc73d66fd89f4e1b1a5d14b93e2ad0c5f7b777520\" ] ]"; let proof = serde_json::from_str::(proof_str).unwrap(); let packed_proof = PackedProof::from(proof); let expected_proof = "0x15c1fc6907219676890dfe147ee6f10b580c7881dddacb1567b3bcbfc513a54d233afda3efff43a7631990d2e79470abcbae3ccad4b920476e64745bfe97bb0a0c8c7d7434c382d590d601d951c29c8463d555867db70f9e84f7741c81c2e1e6241d2ddf1c9e6670a24109a0e9c915cd6e07d0248a384dd38d3c91e9b0419f5f0b23c5467a06eff56cc2c246ada1e7d5705afc4dc8b43fd5a6972c679a2019c5091ed6522f7924d3674d08966a008f947f9aa016a4100bb12f911326f3e1befd0acdf5a5996e00933206cbec48f3bbdcee2a4ca75f8db911c00001e5a05474872446d6f1c1506837392a30fdc73d66fd89f4e1b1a5d14b93e2ad0c5f7b777520"; assert_eq!(packed_proof.to_string(), expected_proof); } #[test] fn test_invalid_parsing() { // note this is only 7 numbers let packed_proof_str = "0x0000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007"; PackedProof::from_str(packed_proof_str).expect_err("parsing should fail"); // not a valid number let packed_proof_str = "0000000000000000p000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008"; PackedProof::from_str(packed_proof_str).expect_err("parsing should fail"); // completely invalid let packed_proof_str = "0x0"; PackedProof::from_str(packed_proof_str).expect_err("parsing should fail"); } } ================================================ FILE: crates/semaphore/Cargo.toml ================================================ [package] name = "semaphore-rs" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] # Internal semaphore-rs-utils.workspace = true semaphore-rs-ark-zkey.workspace = true semaphore-rs-ark-circom.workspace = true semaphore-rs-proof = { workspace = true, features = ["ark"] } semaphore-rs-poseidon.workspace = true semaphore-rs-hasher.workspace = true semaphore-rs-keccak.workspace = true semaphore-rs-trees.workspace = true semaphore-rs-storage.workspace = true semaphore-rs-depth-config.workspace = true semaphore-rs-depth-macros.workspace = true semaphore-rs-witness.workspace = true # 3rd Party bincode.workspace = true bytemuck.workspace = true color-eyre.workspace = true hex.workspace = true hex-literal.workspace = true itertools.workspace = true lazy_static.workspace = true num-bigint.workspace = true once_cell.workspace = true rand.workspace = true rayon.workspace = true ruint.workspace = true serde.workspace = true sha2.workspace = true thiserror.workspace = true tiny-keccak.workspace = true zeroize.workspace = true # Ark ark-bn254.workspace = true ark-ec.workspace = true ark-ff.workspace = true ark-groth16.workspace = true ark-relations.workspace = true ark-std.workspace = true [dev-dependencies] serial_test.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } criterion.workspace = true bincode.workspace = true proptest.workspace = true rand_chacha.workspace = true serde_json.workspace = true tempfile.workspace = true tiny-keccak.workspace = true tracing-test.workspace = true [build-dependencies] semaphore-rs-ark-zkey.workspace = true color-eyre.workspace = true reqwest.workspace = true semaphore-rs-depth-config.workspace = true [[bench]] name = "cascading_merkle_tree" harness = false [[bench]] name = "lazy_merkle_tree" harness = false [features] default = [] depth_16 = [ "semaphore-rs-depth-config/depth_16", "semaphore-rs-depth-macros/depth_16", ] depth_20 = [ "semaphore-rs-depth-config/depth_20", "semaphore-rs-depth-macros/depth_20", ] depth_30 = [ "semaphore-rs-depth-config/depth_30", "semaphore-rs-depth-macros/depth_30", ] ================================================ FILE: crates/semaphore/README.md ================================================ # 🦀 semaphore-rs Rust support library for using [semaphore](https://github.com/appliedzkp/semaphore). It's mostly a Rust rewrite of [zk-kit](https://github.com/appliedzkp/zk-kit), but just focuses on semaphore (for now) and still covers a much smaller scope. It's using [ark-circom](https://github.com/gakonst/ark-circom) under the hood for generating the groth16 proofs. ## Usage Add this line to your `cargo.toml`: ```toml semaphore = { git = "https://github.com/worldcoin/semaphore-rs" } ``` ## Building semaphore circuits 1. Check out submodule (if not done before already): `git submodule update --init --recursive` 1. Install semaphore dependencies `cd semaphore && npm install` 1. Compile circuits `npm exec ts-node ./scripts/compile-circuits.ts` 1. You'll find the `zkey` and `wasm` file in `semaphore/build/snark` ## Example Example as in `src/lib.rs`, run with `cargo test`. ```rust,no_run use semaphore_rs::{get_supported_depths, hash_to_field, Field, identity::Identity, poseidon_tree::LazyPoseidonTree, protocol::*}; use num_bigint::BigInt; // generate identity let mut secret = *b"secret"; let id = Identity::from_secret(&mut secret, None); // Get the first available tree depth. This is controlled by the crate features. let depth = get_supported_depths()[0]; // generate merkle tree let leaf = Field::from(0); let mut tree = LazyPoseidonTree::new(depth, leaf).derived(); tree = tree.update(0, &id.commitment()); let merkle_proof = tree.proof(0); let root = tree.root(); // change signal and external_nullifier here let signal_hash = hash_to_field(b"xxx"); let external_nullifier_hash = hash_to_field(b"appId"); let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash); let proof = generate_proof(&id, &merkle_proof, external_nullifier_hash, signal_hash).unwrap(); let success = verify_proof(root, nullifier_hash, signal_hash, external_nullifier_hash, &proof, depth).unwrap(); assert!(success); ``` ================================================ FILE: crates/semaphore/benches/cascading_merkle_tree.rs ================================================ use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use semaphore_rs::Field; use semaphore_rs_hasher::Hasher; use semaphore_rs_poseidon::Poseidon; use semaphore_rs_storage::MmapVec; use semaphore_rs_trees::cascading::CascadingMerkleTree; criterion_main!(cascading_merkle_tree); criterion_group!( cascading_merkle_tree, bench_cascading_validate, bench_cascading_create_dense_tree, bench_cascading_create_dense_mmap_tree, bench_cascading_restore_dense_mmap_tree, bench_cascading_dense_tree_reads, bench_cascading_dense_mmap_tree_reads, bench_cascading_dense_tree_writes, bench_cascading_dense_mmap_tree_writes, bench_cascading_proof_from_hash ); struct TreeValues { depth: usize, empty_value: H::Hash, initial_values: Vec, } fn bench_cascading_proof_from_hash(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); criterion.bench_function("bench_cascading_proof_from_hash", |b| { let leaf = Field::from(234123412341usize); b.iter_batched_ref( || { let mut tree = CascadingMerkleTree::::new_with_leaves( vec![], tree_value.depth, &tree_value.empty_value, &tree_value.initial_values, ); tree.set_leaf(1 << 13, leaf); tree }, |tree| { let _ = tree.proof_from_hash(leaf); }, BatchSize::SmallInput, ); }); } fn bench_cascading_validate(criterion: &mut Criterion) { let tree_values = [ create_values_for_tree(4), create_values_for_tree(10), create_values_for_tree(14), ]; let mut group = criterion.benchmark_group("bench_cascading_validate"); for value in tree_values.iter() { let tree = CascadingMerkleTree::::new_with_leaves( vec![], value.depth, &value.empty_value, &value.initial_values, ); group.bench_with_input( BenchmarkId::from_parameter(format!("validate_{}", value.depth)), value, |bencher: &mut criterion::Bencher, _| { bencher.iter(|| { tree.validate().unwrap(); }); }, ); } group.finish(); } fn bench_cascading_create_dense_tree(criterion: &mut Criterion) { let tree_values = [ create_values_for_tree(4), create_values_for_tree(10), create_values_for_tree(14), ]; let mut group = criterion.benchmark_group("bench_cascading_create_dense_tree"); for value in tree_values.iter() { group.bench_with_input( BenchmarkId::from_parameter(format!("create_dense_tree_depth_{}", value.depth)), value, |bencher: &mut criterion::Bencher, value| { bencher.iter(|| { let _tree = CascadingMerkleTree::::new_with_leaves( vec![], value.depth, &value.empty_value, &value.initial_values, ); let _root = _tree.root(); }); }, ); } group.finish(); } fn bench_cascading_create_dense_mmap_tree(criterion: &mut Criterion) { let tree_values = [ create_values_for_tree(4), create_values_for_tree(10), create_values_for_tree(14), ]; let mut group = criterion.benchmark_group("bench_cascading_create_dense_mmap_tree"); for value in tree_values.iter() { group.bench_with_input( BenchmarkId::from_parameter(format!("create_dense_mmap_tree_depth_{}", value.depth)), value, |bencher: &mut criterion::Bencher, value| { bencher.iter(|| { let tempfile = tempfile::tempfile().unwrap(); let storage: MmapVec<_> = unsafe { MmapVec::create(tempfile).unwrap() }; let _tree: CascadingMerkleTree = CascadingMerkleTree::new_with_leaves( storage, value.depth, &value.empty_value, &value.initial_values, ); let _root = _tree.root(); }); }, ); } group.finish(); } fn bench_cascading_restore_dense_mmap_tree(criterion: &mut Criterion) { let tree_values = vec![ create_values_for_tree(4), create_values_for_tree(10), create_values_for_tree(14), ]; let mut group = criterion.benchmark_group("bench_cascading_restore_dense_mmap_tree"); (0..3).zip(tree_values).for_each(|(id, value)| { let tempfile = tempfile::NamedTempFile::new().unwrap(); let path = tempfile.path(); let storage: MmapVec<_> = unsafe { MmapVec::create_from_path(path).unwrap() }; { let tree: CascadingMerkleTree = CascadingMerkleTree::new_with_leaves( storage, value.depth, &value.empty_value, &value.initial_values, ); let _ = tree.root(); } group.bench_with_input( BenchmarkId::from_parameter(format!("restore_dense_mmap_tree_depth_{}", value.depth)), &(id, value), |bencher: &mut criterion::Bencher, (_id, value)| { bencher.iter(|| { let storage = unsafe { MmapVec::restore_from_path(path).unwrap() }; let _tree: CascadingMerkleTree = CascadingMerkleTree::restore(storage, value.depth, &value.empty_value) .unwrap(); let _root = _tree.root(); }); }, ); }); group.finish(); } fn bench_cascading_dense_tree_reads(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let tree = CascadingMerkleTree::::new_with_leaves( vec![], tree_value.depth, &tree_value.empty_value, &tree_value.initial_values, ); criterion.bench_function("dense tree reads", |b| { b.iter(|| { // read all leaves, and compare to ones in tree value ((1 << (tree_value.depth - 1))..(1 << tree_value.depth)).for_each(|index| { let _proof = tree.proof(index); }) }) }); } fn bench_cascading_dense_mmap_tree_reads(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let file = tempfile::tempfile().unwrap(); let storage = unsafe { MmapVec::create(file).unwrap() }; let tree = CascadingMerkleTree::::new_with_leaves( storage, tree_value.depth, &tree_value.empty_value, &tree_value.initial_values, ); criterion.bench_function("dense mmap tree reads", |b| { b.iter(|| { // read all leaves, and compare to ones in tree value ((1 << (tree.depth() - 1))..(1 << tree.depth())).for_each(|index| { let _proof = tree.proof(index); }) }) }); } fn bench_cascading_dense_tree_writes(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let value = Field::from(123_456); criterion.bench_function("dense tree writes", |b| { b.iter_batched_ref( || { CascadingMerkleTree::::new_with_leaves( vec![], tree_value.depth, &tree_value.empty_value, &tree_value.initial_values, ) }, |tree| { tree.set_leaf(9000, value); }, BatchSize::SmallInput, ); }); } fn bench_cascading_dense_mmap_tree_writes(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let value = Field::from(123_456); criterion.bench_function("dense mmap tree writes", |b| { b.iter_batched_ref( || { let file = tempfile::tempfile().unwrap(); let storage = unsafe { MmapVec::create(file).unwrap() }; CascadingMerkleTree::::new_with_leaves( storage, tree_value.depth, &tree_value.empty_value, &tree_value.initial_values, ) }, |tree| { tree.set_leaf(9000, value); }, BatchSize::SmallInput, ); }); } fn create_values_for_tree(depth: usize) -> TreeValues { let empty_value = Field::from(0); let initial_values: Vec> = (0..(1 << depth)).map(Field::from).collect(); TreeValues { depth, empty_value, initial_values, } } ================================================ FILE: crates/semaphore/benches/lazy_merkle_tree.rs ================================================ use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use semaphore_rs::poseidon_tree::LazyPoseidonTree; use semaphore_rs::Field; use semaphore_rs_hasher::Hasher; use semaphore_rs_poseidon::Poseidon; criterion_main!(lazy_merkle_tree); criterion_group!( lazy_merkle_tree, bench_create_dense_tree, bench_create_dense_mmap_tree, bench_restore_dense_mmap_tree, bench_dense_tree_reads, bench_dense_mmap_tree_reads, bench_dense_tree_writes, bench_dense_mmap_tree_writes, ); struct TreeValues { depth: usize, prefix_depth: usize, empty_value: H::Hash, initial_values: Vec, } fn bench_create_dense_tree(criterion: &mut Criterion) { let tree_values = [ create_values_for_tree(4), create_values_for_tree(10), create_values_for_tree(14), ]; let mut group = criterion.benchmark_group("bench_create_dense_tree"); for value in tree_values.iter() { group.bench_with_input( BenchmarkId::from_parameter(format!("create_dense_tree_depth_{}", value.depth)), value, |bencher: &mut criterion::Bencher, value| { bencher.iter(|| { let _tree = LazyPoseidonTree::new_with_dense_prefix_with_initial_values( value.depth, value.prefix_depth, &value.empty_value, &value.initial_values, ); let _root = _tree.root(); }); }, ); } group.finish(); } fn bench_create_dense_mmap_tree(criterion: &mut Criterion) { let tree_values = [ create_values_for_tree(4), create_values_for_tree(10), create_values_for_tree(14), ]; let mut group = criterion.benchmark_group("bench_create_dense_mmap_tree"); for value in tree_values.iter() { group.bench_with_input( BenchmarkId::from_parameter(format!("create_dense_mmap_tree_depth_{}", value.depth)), value, |bencher: &mut criterion::Bencher, value| { let file = tempfile::NamedTempFile::new().unwrap(); let path = file.path().to_str().unwrap(); bencher.iter(|| { let _tree = LazyPoseidonTree::new_mmapped_with_dense_prefix_with_init_values( value.depth, value.prefix_depth, &value.empty_value, &value.initial_values, path, ) .unwrap(); let _root = _tree.root(); }); }, ); } group.finish(); // remove created mmap file let _ = std::fs::remove_file("./testfile"); } fn bench_restore_dense_mmap_tree(criterion: &mut Criterion) { let tree_values = [ create_values_for_tree(4), create_values_for_tree(10), create_values_for_tree(14), ]; let mut group = criterion.benchmark_group("bench_restore_dense_mmap_tree"); (0..3).zip(tree_values).for_each(|(id, value)| { let file = tempfile::NamedTempFile::new().unwrap(); let path = file.path().to_str().unwrap(); { let _tree = LazyPoseidonTree::new_mmapped_with_dense_prefix_with_init_values( value.depth, value.prefix_depth, &value.empty_value, &value.initial_values, path, ) .unwrap(); let _root = _tree.root(); } group.bench_with_input( BenchmarkId::from_parameter(format!("restore_dense_mmap_tree_depth_{}", value.depth)), &(id, value), |bencher: &mut criterion::Bencher, (_id, value)| { bencher.iter(|| { let _tree = LazyPoseidonTree::attempt_dense_mmap_restore( value.depth, value.depth, &value.empty_value, path, ) .unwrap(); let _root = _tree.root(); }); }, ); }); group.finish(); } fn bench_dense_tree_reads(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let tree = LazyPoseidonTree::new_with_dense_prefix_with_initial_values( tree_value.depth, tree_value.prefix_depth, &tree_value.empty_value, &tree_value.initial_values, ); criterion.bench_function("dense tree reads", |b| { b.iter(|| { // read all leaves, and compare to ones in tree value ((1 << (tree_value.depth - 1))..(1 << tree_value.depth)).for_each(|index| { let _proof = tree.proof(index); }) }) }); } fn bench_dense_mmap_tree_reads(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let file = tempfile::NamedTempFile::new().unwrap(); let path = file.path().to_str().unwrap(); let tree = LazyPoseidonTree::new_mmapped_with_dense_prefix_with_init_values( tree_value.depth, tree_value.prefix_depth, &tree_value.empty_value, &tree_value.initial_values, path, ) .unwrap(); criterion.bench_function("dense mmap tree reads", |b| { b.iter(|| { // read all leaves, and compare to ones in tree value ((1 << (tree.depth() - 1))..(1 << tree.depth())).for_each(|index| { let _proof = tree.proof(index); }) }) }); } fn bench_dense_tree_writes(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let value = Field::from(123_456); criterion.bench_function("dense tree writes", |b| { b.iter_batched( || { LazyPoseidonTree::new_with_dense_prefix_with_initial_values( tree_value.depth, tree_value.prefix_depth, &tree_value.empty_value, &tree_value.initial_values, ) }, |tree| { let _new_tree = tree.update_with_mutation(9000, &value); }, BatchSize::SmallInput, ); }); } fn bench_dense_mmap_tree_writes(criterion: &mut Criterion) { let tree_value = create_values_for_tree(14); let file = tempfile::NamedTempFile::new().unwrap(); let path = file.path().to_str().unwrap(); let value = Field::from(123_456); criterion.bench_function("dense mmap tree writes", |b| { b.iter_batched( || { LazyPoseidonTree::new_mmapped_with_dense_prefix_with_init_values( tree_value.depth, tree_value.prefix_depth, &tree_value.empty_value, &tree_value.initial_values, path, ) .unwrap() }, |tree| { let _new_tree = tree.update_with_mutation(9000, &value); }, BatchSize::SmallInput, ); }); } fn create_values_for_tree(depth: usize) -> TreeValues { let prefix_depth = depth; let empty_value = Field::from(0); let initial_values: Vec> = (0..(1 << depth)).map(Field::from).collect(); TreeValues { depth, prefix_depth, empty_value, initial_values, } } ================================================ FILE: crates/semaphore/build.rs ================================================ use std::fs::{create_dir, create_dir_all, File}; use std::path::{absolute, Path, PathBuf}; use color_eyre::eyre::Result; extern crate reqwest; const SEMAPHORE_FILES_PATH: &str = "semaphore_files"; const SEMAPHORE_DOWNLOAD_URL: &str = "https://www.trusted-setup-pse.org/semaphore"; fn download_and_store_binary(url: &str, path: impl AsRef) -> Result<()> { let path = path.as_ref(); let mut resp = reqwest::blocking::get(url).unwrap_or_else(|_| panic!("Failed to download file: {url}")); let mut file = File::create(path).unwrap_or_else(|_| panic!("Failed to create file: {}", path.display())); resp.copy_to(&mut file)?; Ok(()) } fn create_arkzkey(path: PathBuf) -> Result { let mut ark_zkey_path = path.clone(); ark_zkey_path.set_extension("arkzkey"); let (original_proving_key, original_constraint_matrices) = semaphore_rs_ark_zkey::read_proving_key_and_matrices_from_zkey( path.to_str().expect("Failed to convert path."), )?; semaphore_rs_ark_zkey::convert_zkey( original_proving_key, original_constraint_matrices, ark_zkey_path.to_str().unwrap(), )?; Ok(ark_zkey_path) } fn build_circuit(depth: usize) -> Result<()> { let out_dir = std::env::var("OUT_DIR").expect("Missing out dir var"); let base_path = Path::new(&out_dir).join(SEMAPHORE_FILES_PATH); if !base_path.exists() { create_dir_all(&base_path)?; } let depth_str = depth.to_string(); let depth_subfolder = base_path.join(&depth_str); if !Path::new(&depth_subfolder).exists() { create_dir(&depth_subfolder)?; } let filename = "semaphore"; let download_url = format!("{SEMAPHORE_DOWNLOAD_URL}/{depth_str}/{filename}.zkey"); let path = Path::new(&depth_subfolder).join(format!("{filename}.zkey")); download_and_store_binary(&download_url, &path)?; create_arkzkey(path)?; let ark_zkey_path = Path::new(&depth_subfolder).join(format!("{filename}.arkzkey")); // Compute absolute paths let arkzkey_file = absolute(ark_zkey_path)?; let graph_file = absolute( Path::new("graphs") .join(depth.to_string()) .join("graph.bin"), )?; println!("graph_file = {}", graph_file.display()); assert!(arkzkey_file.exists()); assert!(graph_file.exists()); // Export generated paths println!( "cargo:rustc-env=BUILD_RS_ARKZKEY_FILE_{}={}", depth, arkzkey_file.display() ); println!( "cargo:rustc-env=BUILD_RS_GRAPH_FILE_{}={}", depth, graph_file.display() ); Ok(()) } fn main() -> Result<()> { // We don't build the circuit for `docs.rs`, as the docs.rs build doesn't have network access. if std::env::var("DOCS_RS").is_ok() { println!("building for docs.rs, skipping Semaphore circuit builds"); return Ok(()); } for depth in semaphore_rs_depth_config::get_supported_depths() { build_circuit(*depth)?; } Ok(()) } ================================================ FILE: crates/semaphore/examples/abort/main.rs ================================================ use color_eyre::Result; use itertools::Itertools; use rand::Rng; use ruint::aliases::U256; use semaphore_rs_hasher::Hasher; use semaphore_rs_poseidon::Poseidon; use semaphore_rs_storage::MmapVec; use semaphore_rs_trees::cascading::CascadingMerkleTree; use semaphore_rs_trees::lazy::LazyMerkleTree; use std::{env, process::Stdio}; static FILE_PATH: &str = "target/debug/examples/abort.mmap"; static BIN_PATH: &str = "target/debug/examples/abort"; static ITERATIONS: usize = 20; static INITIAL_LEAVES: usize = 10; /// A test that interupts writes to the mmap merkle trees /// to simulate a crash, and to check if restoring the tree /// is successful /// /// Run this binary with no arguments to run the tests /// `RUSTFLAGS="-C panic=abort" cargo run --example abort` #[tokio::main] async fn main() -> Result<()> { let args: Vec = env::args().collect(); // initialize if args.len() == 1 { run()?; } else if args.len() == 2 && args[1] == "cascade_restore" { cascade_restore()?; } else if args.len() == 2 && args[1] == "cascade_init" { cascade_init()?; } else if args.len() == 2 && args[1] == "lazy_restore" { lazy_restore()?; } else if args.len() == 2 && args[1] == "lazy_init" { lazy_init()?; } else { panic!("invalid arguments"); } Ok(()) } fn run() -> Result<()> { let cascade_failures = run_test("cascade")?; let lazy_failures = run_test("lazy")?; println!("\nAll Tests Complete!"); println!("Cascade failure rate: {cascade_failures}/{ITERATIONS}"); println!("Lazy failure rate: {lazy_failures}/{ITERATIONS}"); Ok(()) } fn run_test(prefix: &str) -> Result { let mut failures = 0u32; println!("Running {prefix} test"); for i in 0..ITERATIONS { println!("\n{prefix} run #{i}"); let output = std::process::Command::new(BIN_PATH) .arg(format!("{prefix}_init")) .stdout(Stdio::piped()) .output()?; let stdout = String::from_utf8(output.stdout)?; print!("{}", stdout); let stderr = String::from_utf8(output.stderr)?; print!("{}", stderr); let output = std::process::Command::new(BIN_PATH) .arg(format!("{prefix}_restore")) .stdout(Stdio::piped()) .output()?; let stdout = String::from_utf8(output.stdout)?; print!("{}", stdout); let stderr = String::from_utf8(output.stderr)?; if !stderr.is_empty() { print!("{}", stderr); failures += 1; } } println!("\n{prefix} test complete"); Ok(failures) } fn cascade_init() -> Result<()> { let mmap_vec: MmapVec<::Hash> = unsafe { MmapVec::create_from_path(FILE_PATH)? }; let leaves = vec![Default::default(); INITIAL_LEAVES]; let mut tree = CascadingMerkleTree::::new_with_leaves( mmap_vec, 30, &Default::default(), &leaves, ); let _handle = tokio::spawn(async move { for _ in 0..15 { tree.push(U256::from(2)).unwrap(); } }); let mut rng = rand::thread_rng(); let millis: u64 = rng.gen_range(0..50); std::thread::sleep(std::time::Duration::from_millis(millis)); panic!(""); } fn cascade_restore() -> Result<()> { let file = std::fs::OpenOptions::new() .read(true) .write(true) .open(FILE_PATH)?; let mmap_vec: MmapVec<::Hash> = unsafe { MmapVec::restore(file)? }; let tree = CascadingMerkleTree::::restore(mmap_vec, 30, &Default::default())?; println!("tree length: {}", tree.num_leaves()); tree.validate()?; Ok(()) } fn lazy_init() -> Result<()> { let leaves = vec![Default::default(); INITIAL_LEAVES]; let mut tree = LazyMerkleTree::::new_mmapped_with_dense_prefix_with_init_values( 30, 13, &Default::default(), &leaves, FILE_PATH, )?; let _handle = std::thread::spawn(move || { for i in INITIAL_LEAVES..(INITIAL_LEAVES + 15) { tree = tree.update_with_mutation(i, &U256::from(2)); } }); let mut rng = rand::thread_rng(); let millis: u64 = rng.gen_range(0..50); std::thread::sleep(std::time::Duration::from_millis(millis)); panic!(""); } fn lazy_restore() -> Result<()> { let tree = LazyMerkleTree::::attempt_dense_mmap_restore( 30, 13, &Default::default(), FILE_PATH, )?; let leaves = tree.leaves().take(20).collect_vec(); println!("tree length: {leaves:?}"); Ok(()) } ================================================ FILE: crates/semaphore/src/circuit.rs ================================================ #![allow(unused)] use ark_bn254::{Bn254, Fr}; use ark_groth16::ProvingKey; use ark_relations::r1cs::ConstraintMatrices; use once_cell::sync::Lazy; use semaphore_rs_depth_config::{get_depth_index, get_supported_depth_count}; use semaphore_rs_depth_macros::array_for_depths; const ZKEY_BYTES: [&[u8]; get_supported_depth_count()] = array_for_depths!(|depth| include_bytes!(env!(concat!("BUILD_RS_ARKZKEY_FILE_", depth)))); const GRAPH_BYTES: [&[u8]; get_supported_depth_count()] = array_for_depths!(|depth| include_bytes!(env!(concat!("BUILD_RS_GRAPH_FILE_", depth)))); static ZKEY: [Lazy<(ProvingKey, ConstraintMatrices)>; get_supported_depth_count()] = array_for_depths!(|depth| Lazy::new(|| { semaphore_rs_ark_zkey::read_arkzkey_from_bytes(ZKEY_BYTES[get_depth_index(depth).unwrap()]) .expect("zkey should be valid") })); #[must_use] pub fn zkey(depth: usize) -> &'static (ProvingKey, ConstraintMatrices) { let index = get_depth_index(depth).unwrap_or_else(|| panic!("depth {depth} is not supported")); &ZKEY[index] } #[must_use] pub fn graph(depth: usize) -> &'static [u8] { let index = get_depth_index(depth).unwrap_or_else(|| panic!("depth {depth} is not supported")); GRAPH_BYTES[index] } ================================================ FILE: crates/semaphore/src/field.rs ================================================ use ruint::{aliases::U256, uint}; use semaphore_rs_utils::keccak256; /// An element of the BN254 scalar field Fr. /// /// Represented as a big-endian byte vector without Montgomery reduction. // TODO: Make sure value is always reduced. pub type Field = U256; // See pub const MODULUS: Field = uint!(21888242871839275222246405745257275088548364400416034343698204186575808495617_U256); /// Hash arbitrary data to a field element. /// /// This is used to create `signal_hash` and `external_nullifier_hash`. #[must_use] #[allow(clippy::module_name_repetitions)] #[allow(clippy::missing_panics_doc)] pub fn hash_to_field(data: &[u8]) -> Field { // Never panics because the target uint is large enough. let n = U256::try_from_be_slice(&keccak256(data)).unwrap(); // Shift right one byte to make it fit in the field n >> 8 } ================================================ FILE: crates/semaphore/src/hash.rs ================================================ use core::{ fmt::{Debug, Display}, str, str::FromStr, }; use num_bigint::{BigInt, Sign}; use ruint::aliases::U256; use semaphore_rs_utils::{bytes_from_hex, bytes_to_hex, deserialize_bytes, serialize_bytes}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Container for 256-bit hash values. #[derive(Clone, Copy, PartialEq, Eq, Default)] pub struct Hash(pub [u8; 32]); impl Hash { #[must_use] pub const fn from_bytes_be(bytes: [u8; 32]) -> Self { Self(bytes) } #[must_use] pub const fn as_bytes_be(&self) -> &[u8; 32] { &self.0 } } /// Conversion from U256 impl From<&Hash> for U256 { fn from(hash: &Hash) -> Self { Self::from_be_bytes(*hash.as_bytes_be()) } } /// Conversion to U256 impl From for Hash { fn from(u256: U256) -> Self { Self::from_bytes_be(u256.to_be_bytes::<32>()) } } /// Conversion from vec impl From> for Hash { fn from(vec: Vec) -> Self { let mut bytes = [0_u8; 32]; bytes.copy_from_slice(&vec[0..32]); Self::from_bytes_be(bytes) } } /// Conversion to `BigInt` impl From for BigInt { fn from(hash: Hash) -> Self { Self::from_bytes_be(Sign::Plus, hash.as_bytes_be()) } } impl From<&Hash> for BigInt { fn from(hash: &Hash) -> Self { Self::from_bytes_be(Sign::Plus, hash.as_bytes_be()) } } impl Debug for Hash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let hex = bytes_to_hex::<32, 66>(&self.0); let hex_str = str::from_utf8(&hex).expect("hex is always valid utf8"); write!(f, "Field({hex_str})") } } impl Display for Hash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let hex = bytes_to_hex::<32, 66>(&self.0); let hex_str = str::from_utf8(&hex).expect("hex is always valid utf8"); write!(f, "{hex_str}") } } /// Parse Hash from hex string. /// Hex strings can be upper/lower/mixed case and have an optional `0x` prefix /// but they must always be exactly 32 bytes. impl FromStr for Hash { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { bytes_from_hex::<32>(s).map(Self) } } /// Serialize hashes into human readable hex strings or byte arrays. /// Hex strings are lower case without prefix and always 32 bytes. impl Serialize for Hash { fn serialize(&self, serializer: S) -> Result { serialize_bytes::<32, 66, S>(serializer, &self.0) } } /// Deserialize human readable hex strings or byte arrays into hashes. /// Hex strings can be upper/lower/mixed case and have an optional `0x` prefix /// but they must always be exactly 32 bytes. impl<'de> Deserialize<'de> for Hash { fn deserialize>(deserializer: D) -> Result { let bytes = deserialize_bytes::<32, _>(deserializer)?; Ok(Self(bytes)) } } #[cfg(test)] pub mod test { use super::*; use hex_literal::hex; use serde_json::{from_str, to_string}; #[test] fn test_serialize() { let hash = Hash([0; 32]); assert_eq!( to_string(&hash).unwrap(), "\"0x0000000000000000000000000000000000000000000000000000000000000000\"" ); let hash = Hash(hex!( "1c4823575d154474ee3e5ac838d002456a815181437afd14f126da58a9912bbe" )); assert_eq!( to_string(&hash).unwrap(), "\"0x1c4823575d154474ee3e5ac838d002456a815181437afd14f126da58a9912bbe\"" ); } #[test] fn test_deserialize() { assert_eq!( from_str::( "\"0x1c4823575d154474ee3e5ac838d002456a815181437afd14f126da58a9912bbe\"" ) .unwrap(), Hash(hex!( "1c4823575d154474ee3e5ac838d002456a815181437afd14f126da58a9912bbe" )) ); assert_eq!( from_str::( "\"0X1C4823575d154474EE3e5ac838d002456a815181437afd14f126da58a9912bbe\"" ) .unwrap(), Hash(hex!( "1c4823575d154474ee3e5ac838d002456a815181437afd14f126da58a9912bbe" )) ); } } ================================================ FILE: crates/semaphore/src/identity.rs ================================================ use sha2::{Digest, Sha256}; use zeroize::Zeroize; use crate::field::MODULUS; use crate::Field; #[derive(Clone, PartialEq, Eq, Debug)] pub struct Identity { pub trapdoor: Field, pub nullifier: Field, } /// Implements the private key derivation function from zk-kit. /// /// See fn derive_field(seed_hex: &[u8; 64], suffix: &[u8]) -> Field { let mut hasher = Sha256::new(); hasher.update(seed_hex); hasher.update(suffix); Field::try_from_be_slice(hasher.finalize().as_ref()).unwrap() % MODULUS } pub fn seed_hex(seed: &[u8]) -> [u8; 64] { let mut hasher = Sha256::new(); hasher.update(seed); let bytes: [u8; 32] = hasher.finalize().into(); let mut result = [0_u8; 64]; hex::encode_to_slice(bytes, &mut result[..]).expect("output buffer is correctly sized"); result } impl Identity { #[must_use] #[deprecated(since = "0.2.0", note = "please use `from_secret` instead")] pub fn from_seed(seed: &[u8]) -> Self { let seed_hex = seed_hex(seed); Self { trapdoor: derive_field(&seed_hex, b"identity_trapdoor"), nullifier: derive_field(&seed_hex, b"identity_nullifier"), } } #[must_use] pub fn from_secret(secret: &mut [u8], trapdoor_seed: Option<&[u8]>) -> Self { let mut secret_hex = seed_hex(secret); secret.zeroize(); Self::from_hashed_secret(&mut secret_hex, trapdoor_seed) } #[must_use] pub fn from_hashed_secret(secret_hex: &mut [u8; 64], trapdoor_seed: Option<&[u8]>) -> Self { let identity = Self { trapdoor: derive_field(secret_hex, trapdoor_seed.unwrap_or(b"identity_trapdoor")), nullifier: derive_field(secret_hex, b"identity_nullifier"), }; secret_hex.zeroize(); identity } #[must_use] pub fn secret_hash(&self) -> Field { semaphore_rs_poseidon::poseidon::hash2(self.nullifier, self.trapdoor) } #[must_use] pub fn commitment(&self) -> Field { semaphore_rs_poseidon::poseidon::hash1(self.secret_hash()) } } ================================================ FILE: crates/semaphore/src/lib.rs ================================================ #![doc = include_str!("../README.md")] mod circuit; mod field; pub mod hash; pub mod identity; pub mod packed_proof; pub mod poseidon_tree; pub mod protocol; pub use semaphore_rs_depth_config::get_supported_depths; // Export types pub use crate::field::{hash_to_field, Field, MODULUS}; #[allow(dead_code)] #[cfg(test)] mod test { use std::thread::spawn; use semaphore_rs_depth_macros::test_all_depths; use crate::identity::Identity; use crate::poseidon_tree::LazyPoseidonTree; use crate::protocol::{generate_nullifier_hash, generate_proof, verify_proof}; use crate::{hash_to_field, protocol, Field}; #[test] fn test_field_serde() { let value = Field::from(0x1234_5678); let serialized = serde_json::to_value(value).unwrap(); let deserialized: Field = serde_json::from_value(serialized).unwrap(); assert_eq!(value, deserialized); } fn test_end_to_end( identity: &mut [u8], external_nullifier: &[u8], signal: &[u8], depth: usize, ) { let leaf = Field::from(0); // generate identity let id = Identity::from_secret(identity, None); // generate merkle tree let mut tree = LazyPoseidonTree::new(depth, leaf).derived(); tree = tree.update(0, &id.commitment()); let merkle_proof = tree.proof(0); let root = tree.root(); let signal_hash = hash_to_field(signal); let external_nullifier_hash = hash_to_field(external_nullifier); let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash); let proof = generate_proof(&id, &merkle_proof, external_nullifier_hash, signal_hash).unwrap(); for _ in 0..5 { let success = verify_proof( root, nullifier_hash, signal_hash, external_nullifier_hash, &proof, depth, ) .unwrap(); assert!(success); } } #[test_all_depths] fn test_auth_flow(depth: usize) { let mut secret = *b"oh so secret"; let id = Identity::from_secret(&mut secret[..], None); let signal_hash = hash_to_field(b"signal"); let external_nullifier_hash = hash_to_field(b"appId"); let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash); let id_commitment = id.commitment(); let proof = protocol::authentication::generate_proof( depth, &id, external_nullifier_hash, signal_hash, ) .unwrap(); let success = protocol::authentication::verify_proof( depth, id_commitment, nullifier_hash, signal_hash, external_nullifier_hash, &proof, ) .unwrap(); assert!(success); } #[test_all_depths] fn test_single_impl(depth: usize) { // Note that rust will still run tests in parallel let mut hello = *b"hello"; test_end_to_end(&mut hello, b"appId", b"xxx", depth); } #[test_all_depths] fn test_parallel_impl(depth: usize) { // Note that this does not guarantee a concurrency issue will be detected. // For that we need much more sophisticated static analysis tooling like // loom. See let mut a_id = *b"hello"; let mut b_id = *b"secret"; let a = spawn(move || test_end_to_end(&mut a_id, b"appId", b"xxx", depth)); let b = spawn(move || test_end_to_end(&mut b_id, b"test", b"signal", depth)); a.join().unwrap(); b.join().unwrap(); } } ================================================ FILE: crates/semaphore/src/packed_proof.rs ================================================ // Re-export for backwards compatibility pub use semaphore_rs_proof::packing::PackedProof; ================================================ FILE: crates/semaphore/src/poseidon_tree.rs ================================================ use semaphore_rs_hasher::Hasher; use semaphore_rs_poseidon::Poseidon; use semaphore_rs_trees::imt::MerkleTree; #[cfg(not(target_arch = "wasm32"))] use semaphore_rs_trees::lazy::LazyMerkleTree; pub type PoseidonTree = MerkleTree; #[cfg(not(target_arch = "wasm32"))] pub type LazyPoseidonTree = LazyMerkleTree; pub type Branch = semaphore_rs_trees::Branch<::Hash>; pub type Proof = semaphore_rs_trees::InclusionProof; ================================================ FILE: crates/semaphore/src/protocol/authentication.rs ================================================ use crate::{ identity::Identity, protocol::{Proof, ProofError}, Field, }; use semaphore_rs_poseidon::poseidon::hash2; use semaphore_rs_trees::{Branch, InclusionProof}; fn empty_hashes(depth: usize) -> Vec { let mut empty = Vec::with_capacity(depth); let mut hash = Field::from(0); for _ in 0..depth { empty.push(hash); hash = hash2(hash, hash); } empty } fn authentication_merkle_proof(depth: usize) -> InclusionProof { InclusionProof(empty_hashes(depth).into_iter().map(Branch::Left).collect()) } fn authentication_root(depth: usize, id_commitment: Field) -> Field { empty_hashes(depth).into_iter().fold(id_commitment, hash2) } pub fn generate_proof( depth: usize, identity: &Identity, ext_nullifier_hash: Field, signal_hash: Field, ) -> Result { let merkle_proof = authentication_merkle_proof(depth); super::generate_proof(identity, &merkle_proof, ext_nullifier_hash, signal_hash) } pub fn verify_proof( depth: usize, id_commitment: Field, nullifier_hash: Field, signal_hash: Field, ext_nullifier_hash: Field, proof: &Proof, ) -> Result { let root = authentication_root(depth, id_commitment); super::verify_proof( root, nullifier_hash, signal_hash, ext_nullifier_hash, proof, depth, ) } #[cfg(test)] mod tests { use semaphore_rs_depth_macros::test_all_depths; use crate::{hash_to_field, identity::Identity, protocol::generate_nullifier_hash}; use super::*; /// Validates the generate/verify round-trip for the authentication API. #[test_all_depths] fn test_round_trip(depth: usize) { let mut secret = *b"test secret seed"; let id = Identity::from_secret(&mut secret, None); let signal_hash = hash_to_field(b"signal"); let external_nullifier_hash = hash_to_field(b"app_id"); let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash); let proof = generate_proof(depth, &id, external_nullifier_hash, signal_hash) .expect("proof generation should succeed"); let valid = verify_proof( depth, id.commitment(), nullifier_hash, signal_hash, external_nullifier_hash, &proof, ) .expect("proof verification should succeed"); assert!(valid); } } ================================================ FILE: crates/semaphore/src/protocol/mod.rs ================================================ use std::collections::HashMap; use ark_bn254::Fr; use ark_ff::PrimeField; use ark_groth16::{prepare_verifying_key, Groth16}; use ark_relations::r1cs::SynthesisError; use ark_std::UniformRand; use color_eyre::Result; use once_cell::sync::Lazy; use rand::{thread_rng, Rng}; use semaphore_rs_ark_circom::ethereum::AffineError; use semaphore_rs_ark_circom::CircomReduction; use semaphore_rs_depth_config::{get_depth_index, get_supported_depth_count}; use semaphore_rs_depth_macros::array_for_depths; use semaphore_rs_poseidon::Poseidon; use semaphore_rs_trees::{Branch, InclusionProof}; use semaphore_rs_witness::Graph; use thiserror::Error; use crate::circuit::zkey; use crate::identity::Identity; use crate::Field; pub use semaphore_rs_proof::compression; pub use semaphore_rs_proof::Proof; pub mod authentication; static WITHESS_GRAPH: [Lazy; get_supported_depth_count()] = array_for_depths!(|depth| { Lazy::new(|| { semaphore_rs_witness::init_graph(crate::circuit::graph(depth)) .expect("Failed to initialize Graph") }) }); /// Preloads the ZKEY in memory to skip the lazy loading at first verification pub fn warmup_for_verification(tree_depth: usize) { let _zkey = zkey(tree_depth); } /// Helper to merkle proof into a bigint vector /// TODO: we should create a From trait for this fn merkle_proof_to_vec(proof: &InclusionProof) -> Vec { proof .0 .iter() .map(|x| match x { Branch::Left(value) | Branch::Right(value) => *value, }) .collect() } /// Generates the nullifier hash #[must_use] pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field { semaphore_rs_poseidon::poseidon::hash2(external_nullifier, identity.nullifier) } #[derive(Error, Debug)] pub enum ProofError { #[error("Error reading circuit key: {0}")] CircuitKeyError(#[from] std::io::Error), #[error("Error producing witness: {0}")] WitnessError(color_eyre::Report), #[error("Error producing proof: {0}")] SynthesisError(#[from] SynthesisError), #[error("Error converting public input: {0}")] ToFieldError(#[from] ruint::ToFieldError), #[error(transparent)] G1AffineError(#[from] AffineError), } /// Generates a semaphore proof /// /// # Errors /// /// Returns a [`ProofError`] if proving fails. pub fn generate_proof( identity: &Identity, merkle_proof: &InclusionProof, external_nullifier_hash: Field, signal_hash: Field, ) -> Result { generate_proof_rng( identity, merkle_proof, external_nullifier_hash, signal_hash, &mut thread_rng(), ) } /// Generates a semaphore proof from entropy /// /// # Errors /// /// Returns a [`ProofError`] if proving fails. pub fn generate_proof_rng( identity: &Identity, merkle_proof: &InclusionProof, external_nullifier_hash: Field, signal_hash: Field, rng: &mut impl Rng, ) -> Result { generate_proof_rs( identity, merkle_proof, external_nullifier_hash, signal_hash, ark_bn254::Fr::rand(rng), ark_bn254::Fr::rand(rng), ) } fn generate_proof_rs( identity: &Identity, merkle_proof: &InclusionProof, external_nullifier_hash: Field, signal_hash: Field, r: ark_bn254::Fr, s: ark_bn254::Fr, ) -> Result { let depth = merkle_proof.0.len(); let full_assignment = generate_witness(identity, merkle_proof, external_nullifier_hash, signal_hash); let zkey = zkey(depth); let ark_proof = Groth16::<_, CircomReduction>::create_proof_with_reduction_and_matrices( &zkey.0, r, s, &zkey.1, zkey.1.num_instance_variables, zkey.1.num_constraints, full_assignment.as_slice(), )?; let proof = ark_proof.into(); Ok(proof) } pub fn generate_witness( identity: &Identity, merkle_proof: &InclusionProof, external_nullifier_hash: Field, signal_hash: Field, ) -> Vec { let depth = merkle_proof.0.len(); let inputs = HashMap::from([ ("identityNullifier".to_owned(), vec![identity.nullifier]), ("identityTrapdoor".to_owned(), vec![identity.trapdoor]), ("treePathIndices".to_owned(), path_index(merkle_proof)), ("treeSiblings".to_owned(), merkle_proof_to_vec(merkle_proof)), ( "externalNullifier".to_owned(), vec![external_nullifier_hash], ), ("signalHash".to_owned(), vec![signal_hash]), ]); let graph = &WITHESS_GRAPH [get_depth_index(depth).unwrap_or_else(|| panic!("Depth {depth} not supported"))]; let witness = semaphore_rs_witness::calculate_witness(inputs, graph).unwrap(); witness .into_iter() .map(|x| Fr::from_bigint(x.into()).expect("Couldn't cast U256 to BigInteger")) .collect::>() } /// Compute path index #[must_use] pub fn path_index(proof: &InclusionProof) -> Vec { proof .0 .iter() .map(|branch| match branch { Branch::Left(_) => Field::from(0), Branch::Right(_) => Field::from(1), }) .collect() } /// Verifies a given semaphore proof /// /// # Errors /// /// Returns a [`ProofError`] if verifying fails. Verification failure does not /// necessarily mean the proof is incorrect. pub fn verify_proof( root: Field, nullifier_hash: Field, signal_hash: Field, external_nullifier_hash: Field, proof: &Proof, tree_depth: usize, ) -> Result { let zkey = zkey(tree_depth); let pvk = prepare_verifying_key(&zkey.0.vk); let public_inputs = [root, nullifier_hash, signal_hash, external_nullifier_hash] .iter() .map(ark_bn254::Fr::try_from) .collect::, _>>()?; let ark_proof = (*proof).try_into()?; let result = Groth16::<_, CircomReduction>::verify_proof(&pvk, &ark_proof, &public_inputs[..])?; Ok(result) } #[cfg(test)] #[allow(dead_code)] mod test { use ark_bn254::Config; use ark_ec::bn::Bn; use ark_groth16::Proof as ArkProof; use rand::SeedableRng as _; use rand_chacha::ChaChaRng; use semaphore_rs_depth_macros::test_all_depths; use serde_json::json; use super::*; use crate::hash_to_field; use crate::poseidon_tree::LazyPoseidonTree; fn arb_proof(seed: u64, depth: usize) -> Proof { // Deterministic randomness for testing let mut rng = ChaChaRng::seed_from_u64(seed); // generate identity let mut seed: [u8; 16] = rng.gen(); let id = Identity::from_secret(seed.as_mut(), None); // generate merkle tree let leaf = Field::from(0); let mut tree = LazyPoseidonTree::new(depth, leaf).derived(); tree = tree.update(0, &id.commitment()); let merkle_proof = tree.proof(0); let external_nullifier: [u8; 16] = rng.gen(); let external_nullifier_hash = hash_to_field(&external_nullifier); let signal: [u8; 16] = rng.gen(); let signal_hash = hash_to_field(&signal); generate_proof_rng( &id, &merkle_proof, external_nullifier_hash, signal_hash, &mut rng, ) .unwrap() } #[test_all_depths] fn test_proof_cast_roundtrip(depth: usize) { let proof = arb_proof(123, depth); let ark_proof: ArkProof> = proof.try_into().unwrap(); let result: Proof = ark_proof.into(); assert_eq!(proof, result); } #[test_all_depths] fn test_proof_serialize(depth: usize) { let proof = arb_proof(456, depth); let json = serde_json::to_value(proof).unwrap(); let valid_values = match depth { 16 => json!([ [ "0xe4267974945a50a541e90a399ed9211752216a3e4e1cefab1f0bcd8925ea56e", "0xdd9ada36c50d3f1bf75abe5c5ad7d0a29355b74fc3f604aa108b8886a6ac7f8" ], [ [ "0x1621577ad2f90fe2e7ec6f675751693515c3b7e91ee228f1db47fe3aba7c0450", "0x2b07bc915b377f8c7126c2d46636632cdbcb426b446a06edf3320939ee4e1911" ], [ "0xf40e93e057c7521720448b3d443eac36ff48705312181c41bd78981923be41a", "0x9ce138011687b44a08b979a85b3b122e7335254a02d4fbae7b38b57653c7eb0" ] ], [ "0x295b30c0c025a2b176de1220acdb5f95119a8938689d73076f02bb6d01601fbb", "0xc71250468b955584be8769b047f79614df1176a7a64683f14c27889d47e614" ] ]), 20 => json!([ [ "0x2296e314c88daf893769f4ed0cad8a7f584b39db6ebd4bba230591b5d78f48b3", "0x2e5d33bf993b8e4aba7c06ee82ff7dd674857b491c46f53eda4365ecbf3e5fde" ], [ [ "0x277c239fa1cf9e8a7ca65ef09371bee470aad7936583a0b48e60f6a76f17a97c", "0x2b21c607eff04f704e546451dcd27c5f090639074a54b45e345337e09d0ab3d0" ], [ "0x73fde4daa004ecb853159e54b98cdd204e7874008f91581601881c968607451", "0x171ee4d007b9286d91b581f6d38902e5befc3876b96c71bc178b5f5e8dbf1e40" ] ], [ "0x25afbb8fef95d8481e9e49b4a94848473794447d032fdde2cd73a0d6318b6c3c", "0x2a24e19699e2d8495357cf9b65fb215cebbcda2817b1627758a330e57db5c4b9" ] ]), 30 => json!([ [ "0x19ded61ab5c58fdb12367526c6bc04b9186d0980c4b6fd48a44093e80f9b4206", "0x2e619a034be10e9aab294f1c77a480378e84782c8519449aef0c8f6952382bda" ], [ [ "0x2202954c0cdb43dc240d56c3a60d125dbc676f8d97bfeac5987500eb0ff4b9a1", "0x35f5b9d8bfba1341fe9fabef6f46d242e1b22c4006ed3ae3f240f0409b20799" ], [ "0x13ef645aeaffda30d38c1df68d79d9682d3d002a388e5672fe9b9c7f3224acd7", "0x10a45a9a99cfaf9aef84ab40c5fdad411e800e24471f24ec76addb74b9e041af" ] ], [ "0x1f72d009494e8694cf608c54131e7d565625d59e4637ea77cbf2620c719e8c77", "0x19ee17159b599f6f4b2294d4fb29760d2dc1b58adc0519ce546ad274928f6bc4" ] ]), _ => panic!("unexpected depth: {}", depth), }; assert_eq!(json, valid_values); } } ================================================ FILE: crates/semaphore/src/util.rs ================================================ ================================================ FILE: crates/semaphore-depth-config/Cargo.toml ================================================ [package] name = "semaphore-rs-depth-config" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [features] depth_16 = [] depth_20 = [] depth_30 = [] ================================================ FILE: crates/semaphore-depth-config/src/lib.rs ================================================ #![allow(unused)] pub const fn get_supported_depth_count() -> usize { let mut res = 0; #[cfg(feature = "depth_16")] { res += 1; } #[cfg(feature = "depth_20")] { res += 1; } #[cfg(feature = "depth_30")] { res += 1; } res } #[allow(unused_assignments)] const fn gen_supported_depths() -> [usize; get_supported_depth_count()] { let mut res = [0; get_supported_depth_count()]; let mut i = 0; #[cfg(feature = "depth_16")] { res[i] = 16; i += 1; } #[cfg(feature = "depth_20")] { res[i] = 20; i += 1; } #[cfg(feature = "depth_30")] { res[i] = 30; i += 1; } res } static SUPPORTED_DEPTHS: [usize; get_supported_depth_count()] = gen_supported_depths(); pub fn get_supported_depths() -> &'static [usize] { &SUPPORTED_DEPTHS } #[allow(unused_assignments)] pub const fn get_depth_index(depth: usize) -> Option { let mut i = 0; #[cfg(feature = "depth_16")] { if depth == 16 { return Some(i); } i += 1; } #[cfg(feature = "depth_20")] { if depth == 20 { return Some(i); } i += 1; } #[cfg(feature = "depth_30")] { if depth == 30 { return Some(i); } i += 1; } None } ================================================ FILE: crates/semaphore-depth-macros/Cargo.toml ================================================ [package] name = "semaphore-rs-depth-macros" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [features] depth_16 = ["semaphore-rs-depth-config/depth_16"] depth_20 = ["semaphore-rs-depth-config/depth_20"] depth_30 = ["semaphore-rs-depth-config/depth_30"] [lib] proc-macro = true [dependencies] semaphore-rs-depth-config.workspace = true itertools ={ workspace = true } syn.workspace = true proc-macro2.workspace = true quote.workspace = true ================================================ FILE: crates/semaphore-depth-macros/src/lib.rs ================================================ use proc_macro::TokenStream; use quote::{format_ident, quote}; use semaphore_rs_depth_config::get_supported_depths; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, parse_quote, visit_mut::VisitMut, Ident, Token, }; /// Multi-depth test generator /// /// This macro is used to generate a test for each supported depth. /// It expects to annotate a function with a single argument, and will generate /// test cases delegating to that function for each supported depth. /// /// For example, /// ``` /// use semaphore_rs_depth_macros::test_all_depths; /// #[test_all_depths] /// fn test_depth_non_zero(depth: usize) { /// assert!(depth > 0); /// } /// ``` /// with `depth_16` and `depth_30` features active will generate the following /// code: /// ```no_run /// fn test_depth_non_zero(depth: usize) { /// assert!(depth > 0); /// } /// /// #[test] /// fn test_depth_non_zero_depth_16() { /// test_depth_non_zero(16); /// } /// /// #[test] /// fn test_depth_non_zero_depth_30() { /// test_depth_non_zero(30); /// } /// ``` #[proc_macro_attribute] pub fn test_all_depths(_attr: TokenStream, item: TokenStream) -> TokenStream { let fun = parse_macro_input!(item as syn::ItemFn); let fun_name = &fun.sig.ident; let original_fun = quote! { #fun }; let mut result = TokenStream::from(original_fun); for depth in get_supported_depths() { let fun_name_versioned = format_ident!("{}_depth_{}", fun_name, depth); let tokens = quote! { #[test] fn #fun_name_versioned() { #fun_name(#depth); } }; result.extend(TokenStream::from(tokens)); } result } #[derive(Debug)] struct ArrayForDepthsInput { replaced_ident: Ident, expr: syn::Expr, } #[derive(Debug)] struct MacroArgs { args: Vec, } impl Parse for MacroArgs { fn parse(input: ParseStream) -> syn::Result { let mut args = Vec::new(); while !input.is_empty() { args.push(input.parse::()?); if input.is_empty() { break; } input.parse::()?; } Ok(MacroArgs { args }) } } impl MacroArgs { fn tokens(&self) -> proc_macro2::TokenStream { let args = &self.args; quote! { #(#args),* } } } struct IdentReplacer(Ident, syn::Expr); impl VisitMut for IdentReplacer { fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { match expr { syn::Expr::Path(ident) => { if ident.path.is_ident(&self.0) { *expr = self.1.clone(); } } syn::Expr::Macro(mcr) => { let Ok(mut args) = mcr.mac.parse_body::() else { return; }; for arg in &mut args.args { self.visit_expr_mut(arg); } mcr.mac.tokens = args.tokens(); } _ => syn::visit_mut::visit_expr_mut(self, expr), } } } impl Parse for ArrayForDepthsInput { fn parse(input: ParseStream) -> syn::Result { input.parse::()?; let replaced_ident = input.parse::()?; input.parse::()?; let expr = input.parse::()?; Ok(ArrayForDepthsInput { replaced_ident, expr, }) } } /// Macro to generate code for multiple depths. /// /// Generates an array of expressions, where the given identifier is replaced /// with each supported depth. The argument must use closure syntax, but this /// is pure syntactic, the closure expression gets unrolled statically. /// /// This macro also descends into other macros, as long as they use standard /// Rust syntax for arguments. Any non-standard macros will be ignored in the /// expansion of this. /// /// For example, `array_for_depths!(|depth| depth + 5)`, with only `depth_16` /// and `depth_30` supported, will generate `[16 + 5, 30 + 5]`, and /// `array_for_depths!(|depth| concat!("foo", depth))` will generate /// `[concat!("foo", 16), concat!("foo", 30)]`. #[proc_macro] pub fn array_for_depths(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ArrayForDepthsInput); let items = get_supported_depths() .iter() .map(|depth| { let mut replacer = IdentReplacer(input.replaced_ident.clone(), parse_quote!(#depth)); let mut expr = input.expr.clone(); replacer.visit_expr_mut(&mut expr); expr }) .collect::>(); let array = quote! { [#(#items),*] }; array.into() } ================================================ FILE: crates/storage/Cargo.toml ================================================ [package] name = "semaphore-rs-storage" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] bytemuck.workspace = true color-eyre.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] mmap-rs.workspace = true tempfile.workspace = true ================================================ FILE: crates/storage/src/lib.rs ================================================ use std::ops::{Deref, DerefMut}; pub trait GenericStorage: Deref + DerefMut + Extend + Send + Sync { fn push(&mut self, value: T); fn extend_from_slice(&mut self, slice: &[T]); fn clear(&mut self); } impl GenericStorage for Vec { fn push(&mut self, value: T) { self.push(value); } fn extend_from_slice(&mut self, slice: &[T]) { Vec::extend_from_slice(self, slice); } fn clear(&mut self) { self.clear(); } } #[cfg(not(target_arch = "wasm32"))] mod native; #[cfg(not(target_arch = "wasm32"))] pub use native::MmapVec; ================================================ FILE: crates/storage/src/native/mmap_vec.rs ================================================ use std::fs::{File, OpenOptions}; use std::ops::{Deref, DerefMut}; use std::path::Path; use bytemuck::Pod; use color_eyre::eyre::{ensure, Context}; use mmap_rs::{MmapFlags, MmapMut, MmapOptions}; const META_SIZE: usize = std::mem::size_of::(); pub struct MmapVec { // This must be Option to properly uphold aliasing access safety guarantees // Look at the `resize` method for more details mmap: Option, file: File, capacity: usize, phantom: std::marker::PhantomData, } // Public API impl MmapVec { /// Creates a new MmapVec from a file path. /// Any existing data in the file will be truncated. /// /// # Safety /// Same requirements as `create` pub unsafe fn create_from_path(file_path: impl AsRef) -> color_eyre::Result { let file = OpenOptions::new() .read(true) .write(true) .create(true) .truncate(true) .open(file_path)?; Self::create(file) } /// Creates a new MmapVec from a file. /// Any existing data in the file will be truncated. /// /// # Safety /// This method requires that the safety requirements of [`mmap_rs::MmapOptions::with_file`](https://docs.rs/mmap-rs/0.6.1/mmap_rs/struct.MmapOptions.html#method.with_file) are upheld. /// /// Notably this means that there can exist no other mutable mappings to the /// same file in this process or any other pub unsafe fn create(file: File) -> color_eyre::Result { file.set_len(0)?; file.set_len(META_SIZE as u64) .context("Failed to resize underlying file")?; let mut s = Self::restore(file)?; s.set_storage_len(0); Ok(s) } /// Restores an MmapVec from a file path. /// /// # Safety /// Same requirements as `restore` pub unsafe fn restore_from_path(file_path: impl AsRef) -> color_eyre::Result { let file = OpenOptions::new() .read(true) .write(true) .create(true) .truncate(false) .open(file_path)?; Self::restore(file) } /// Restores an MmapVec from a file. This should not panic. /// /// # Safety /// This method requires that the safety requirements of [`mmap_rs::MmapOptions::with_file`](https://docs.rs/mmap-rs/0.6.1/mmap_rs/struct.MmapOptions.html#method.with_file) are upheld. /// /// Additioanally the caller must ensure that the file contains valid data. /// /// Notably this means that there can exist no other mutable mappings to the /// same file in this process or any other pub unsafe fn restore(file: File) -> color_eyre::Result { assert!(std::mem::size_of::() != 0); let mut byte_len = file.metadata()?.len() as usize; if byte_len < META_SIZE { file.set_len(0)?; file.set_len(META_SIZE as u64)?; byte_len = META_SIZE; } let data_len = byte_len.saturating_sub(META_SIZE); ensure!( data_len.is_multiple_of(std::mem::size_of::()), "data must be divisible by size of T" ); let capacity = data_len / std::mem::size_of::(); let mmap = MmapOptions::new(byte_len)? .with_file(&file, 0) .with_flags(MmapFlags::SHARED) .map_mut()?; let s = Self { mmap: Some(mmap), file, capacity, phantom: std::marker::PhantomData, }; let len = s.storage_len(); ensure!(len <= capacity, "len must be lower than capacity"); Ok(s) } pub fn clear(&mut self) { self.set_storage_len(0); } pub fn push(&mut self, v: T) { let len = self.storage_len(); let capacity = self.capacity; let new_len = len + 1; if new_len > capacity { self.resize(new_len.next_power_of_two()); } self.capacity_slice_mut()[len] = v; self.set_storage_len(new_len); } pub fn extend_from_slice(&mut self, slice: &[T]) { let len = self.storage_len(); let capacity = self.capacity; let new_len = len + slice.len(); if new_len >= capacity { self.resize(new_len.next_power_of_two()); } self.capacity_slice_mut()[len..(new_len)].copy_from_slice(slice); self.set_storage_len(new_len); } pub fn resize(&mut self, new_capacity: usize) { let new_file_len = META_SIZE + new_capacity * std::mem::size_of::(); self.file .set_len(new_file_len as u64) .expect("Failed to resize underlying file"); // # Safety // MmapMut requires that no other instance of MmapMut exists that has access // to the same file. // // In order to uphold that we must first drop the current MmapMut instance. // // MmapMut also requires that the caller must ensure that no other process // has access to the same file at the same time. // This requirement is upheld at the instantiation of MmapVec and must hold true // for its entire lifetime. Therefore it must be upheld here as well. unsafe { self.mmap = None; self.mmap = Some( MmapOptions::new(new_file_len) .expect("cannot create memory map") .with_file(&self.file, 0) .with_flags(MmapFlags::SHARED) .map_mut() .expect("cannot build memory map"), ); } self.capacity = new_capacity; } fn set_storage_len(&mut self, new_len: usize) { let slice: &mut [usize] = bytemuck::cast_slice_mut(&mut self.mmap.as_mut().unwrap()[..META_SIZE]); slice[0] = new_len; } fn storage_len(&self) -> usize { bytemuck::cast_slice(&self.mmap.as_ref().unwrap()[..META_SIZE])[0] } fn capacity_slice(&self) -> &[T] { bytemuck::cast_slice(&self.mmap.as_ref().unwrap().as_slice()[META_SIZE..]) } fn capacity_slice_mut(&mut self) -> &mut [T] { bytemuck::cast_slice_mut(&mut self.mmap.as_mut().unwrap().as_mut_slice()[META_SIZE..]) } } impl Extend for MmapVec where T: Pod, { fn extend>(&mut self, iter: I) { for item in iter { self.push(item); } } } impl Deref for MmapVec where T: Pod, { type Target = [T]; fn deref(&self) -> &Self::Target { &self.capacity_slice()[..self.storage_len()] } } impl DerefMut for MmapVec where T: Pod, { fn deref_mut(&mut self) -> &mut Self::Target { let len = self.storage_len(); &mut self.capacity_slice_mut()[..len] } } impl std::fmt::Debug for MmapVec where T: Pod + std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let contents: &[T] = self.deref(); f.debug_struct("MmapVec") .field("contents", &contents) .field("capacity", &self.capacity) .finish() } } #[cfg(test)] mod tests { use super::*; #[test] #[allow(clippy::manual_bits)] fn test_capacity_push() { let f = tempfile::NamedTempFile::new().unwrap(); let file_path = f.path().to_owned(); let mut storage: MmapVec = unsafe { MmapVec::create(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len(), META_SIZE as u64 ); storage.push(0); assert_eq!(storage.capacity, 1); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() + META_SIZE ); storage.push(0); assert_eq!(storage.capacity, 2); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 2 + META_SIZE ); storage.push(0); assert_eq!(storage.capacity, 4); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 4 + META_SIZE ); storage.push(0); assert_eq!(storage.capacity, 4); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 4 + META_SIZE ); storage.push(0); assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); } #[test] #[allow(clippy::manual_bits)] fn test_capacity_extend() { let f = tempfile::NamedTempFile::new().unwrap(); let file_path = f.path().to_owned(); let mut storage: MmapVec = unsafe { MmapVec::create(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len(), META_SIZE as u64 ); storage.extend_from_slice(&[0, 0]); assert_eq!(storage.capacity, 2); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 2 + META_SIZE ); storage.extend_from_slice(&[0, 0, 0]); assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); storage.extend_from_slice(&[0]); assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); } #[test] #[allow(clippy::manual_bits)] fn test_capacity_create() { let f = tempfile::NamedTempFile::new().unwrap(); let file_path = f.path().to_owned(); let mut storage: MmapVec = unsafe { MmapVec::create(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, META_SIZE ); storage.extend_from_slice(&[0, 0, 0, 0, 0]); assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); let storage: MmapVec = unsafe { MmapVec::create(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, META_SIZE ); } #[test] #[allow(clippy::manual_bits)] fn test_capacity_create_from_path() { let f = tempfile::NamedTempFile::new().unwrap(); let file_path = f.path().to_owned(); let mut storage: MmapVec = unsafe { MmapVec::create(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, META_SIZE ); storage.extend_from_slice(&[0, 0, 0, 0, 0]); assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); let storage: MmapVec = unsafe { MmapVec::create_from_path(&file_path).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, META_SIZE ); } #[test] #[allow(clippy::manual_bits)] fn test_capacity_restore() { let f = tempfile::NamedTempFile::new().unwrap(); let file_path = f.path().to_owned(); let mut storage: MmapVec = unsafe { MmapVec::create(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, META_SIZE ); storage.extend_from_slice(&[0, 0, 0, 0, 0]); assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); let storage: MmapVec = unsafe { MmapVec::restore(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); } #[test] #[allow(clippy::manual_bits)] fn test_capacity_restore_from_path() { let f = tempfile::NamedTempFile::new().unwrap(); let file_path = f.path().to_owned(); let mut storage: MmapVec = unsafe { MmapVec::create(f.reopen().unwrap()).unwrap() }; assert_eq!(storage.capacity, 0); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, META_SIZE ); storage.extend_from_slice(&[0, 0, 0, 0, 0]); assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); let storage: MmapVec = unsafe { MmapVec::restore_from_path(&file_path).unwrap() }; assert_eq!(storage.capacity, 8); assert_eq!( std::fs::metadata(&file_path).unwrap().len() as usize, std::mem::size_of::() * 8 + META_SIZE ); } #[test] fn test_mmap_vec() { let f = tempfile::tempfile().unwrap(); let mut storage: MmapVec = unsafe { MmapVec::create(f.try_clone().unwrap()).unwrap() }; println!("{storage:?}"); storage.resize(2); println!("{storage:?}"); storage.push(u32::MAX); println!("{storage:?}"); storage.push(2); println!("{storage:?}"); storage.resize(4); println!("{storage:?}"); storage.push(42); println!("{storage:?}"); storage.push(4); println!("{storage:?}"); assert_eq!(storage.len(), 4); println!("{storage:?}"); assert_eq!(storage[0], u32::MAX); assert_eq!(storage[1], 2); assert_eq!(storage[2], 42); assert_eq!(storage[3], 4); drop(storage); let restored: MmapVec = unsafe { MmapVec::restore(f).unwrap() }; assert_eq!(restored.len(), 4); assert_eq!(restored[0], u32::MAX); assert_eq!(restored[1], 2); assert_eq!(restored[2], 42); assert_eq!(restored[3], 4); } } ================================================ FILE: crates/storage/src/native/mod.rs ================================================ mod mmap_vec; use bytemuck::Pod; pub use mmap_vec::MmapVec; impl super::GenericStorage for MmapVec { fn push(&mut self, value: T) { self.push(value); } fn extend_from_slice(&mut self, slice: &[T]) { self.extend_from_slice(slice); } fn clear(&mut self) { self.clear(); } } ================================================ FILE: crates/trees/Cargo.toml ================================================ [package] name = "semaphore-rs-trees" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] # Internal semaphore-rs-hasher.workspace = true semaphore-rs-storage.workspace = true semaphore-rs-ark-circom.workspace = true # 3rd Party bytemuck.workspace = true color-eyre.workspace = true derive-where.workspace = true hex.workspace = true hex-literal.workspace = true itertools.workspace = true once_cell.workspace = true rayon.workspace = true ruint.workspace = true serde.workspace = true thiserror.workspace = true tiny-keccak = { workspace = true, features = ["sha3"] } # Ark ark-bn254.workspace = true ark-ec.workspace = true ark-ff.workspace = true ark-groth16.workspace = true ark-relations.workspace = true ark-std.workspace = true [dev-dependencies] semaphore-rs-poseidon.workspace = true semaphore-rs-keccak.workspace = true rand.workspace = true serial_test.workspace = true tempfile.workspace = true test-case.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] mmap-rs.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] # Enable the JS backend for getrandom (pulled in transitively via rand) so # that this crate compiles for wasm32-unknown-unknown. getrandom.workspace = true ================================================ FILE: crates/trees/src/cascading/mod.rs ================================================ use std::fmt::Debug; use bytemuck::Pod; use color_eyre::eyre::{ensure, Result}; use derive_where::derive_where; use semaphore_rs_hasher::Hasher; use crate::proof::{Branch, InclusionProof}; mod storage_ops; use self::storage_ops::{sparse_fill_partial_subtree, StorageOps}; /// A dynamically growable array represented merkle tree. /// /// The left most branch of the tree consists of progressively increasing powers /// of two. The right child of each power of two looks like a traditionally /// indexed binary tree offset by its parent. /// /// The underlying storage is a 1-indexed dynamically growable array that is /// always a power of two in length. The tree is built succesively from the /// bottom left to the top right. /// /// The zeroth index of the underlying storage is used to store the number of /// leaves in the tree. Because of this, the Hash used must be able to be cast /// as a usize. If this is not possible, the code will panic at runtime. /// /// ```markdown /// 8 /// 4 9 /// 2 5 10 11 /// 1 3 6 7 12 13 14 15 /// /// Leaves are 0 indexed /// 0 1 2 3 4 5 6 7 /// ``` #[derive_where(Clone; ::Hash: Clone, S: Clone)] #[derive_where(PartialEq; ::Hash: PartialEq, S: PartialEq)] #[derive_where(Eq; ::Hash: Eq, S: Eq)] #[derive_where(Debug; ::Hash: Debug, S: Debug)] pub struct CascadingMerkleTree::Hash>> where H: Hasher, { depth: usize, root: H::Hash, empty_value: H::Hash, sparse_column: Vec, storage: S, _marker: std::marker::PhantomData, } impl CascadingMerkleTree where H: Hasher, ::Hash: Copy + Pod + Eq + Send + Sync, ::Hash: Debug, S: StorageOps, { /// Use to open a previously initialized tree pub fn restore( storage: S, depth: usize, empty_value: &H::Hash, ) -> Result> { let tree = Self::restore_unchecked(storage, depth, empty_value)?; tree.validate()?; Ok(tree) } /// Restores a tree from the provided storage /// /// Invalid storage will result in unpredictable behavior pub fn restore_unchecked( storage: S, depth: usize, empty_value: &H::Hash, ) -> Result> { let len = storage.len(); storage.validate_const()?; ensure!(depth > 0, "Tree depth must be greater than 0"); ensure!( len <= 2usize.checked_pow(depth as u32 + 1).unwrap(), "Storage length must be less than or equal to 2^(depth + 1)" ); let sparse_column = Self::sparse_column(depth, empty_value); let mut tree = CascadingMerkleTree { depth, root: *empty_value, empty_value: *empty_value, sparse_column, storage, _marker: std::marker::PhantomData, }; tree.recompute_root(); let num_leaves = tree.num_leaves(); ensure!( num_leaves <= len >> 1, "Number of leaves ({num_leaves}) must be less than or equal to half the storage \ length ({len})" ); Ok(tree) } /// Create and initialize a tree in the provided storage /// /// initializes an empty tree #[must_use] pub fn new(storage: S, depth: usize, empty_value: &H::Hash) -> CascadingMerkleTree { Self::new_with_leaves(storage, depth, empty_value, &[]) } /// Create and initialize a tree in the provided storage #[must_use] pub fn new_with_leaves( mut storage: S, depth: usize, empty_value: &H::Hash, leaves: &[H::Hash], ) -> CascadingMerkleTree { assert!(depth > 0, "Tree depth must be greater than 0"); assert!( leaves.len() <= (1 << depth), "Number of leaves must be less than or equal to the capacity of the tree" ); let sparse_column = Self::sparse_column(depth, empty_value); storage.populate_with_leaves(&sparse_column, empty_value, leaves); let mut tree = CascadingMerkleTree { depth, root: *empty_value, empty_value: *empty_value, sparse_column, storage, _marker: std::marker::PhantomData, }; tree.recompute_root(); tree } /// Returns the depth of the tree. #[must_use] pub const fn depth(&self) -> usize { self.depth } /// Returns the empty value of the tree. #[must_use] pub const fn empty_value(&self) -> H::Hash { self.empty_value } /// Returns the root of the tree. #[must_use] pub const fn root(&self) -> H::Hash { self.root } /// Returns the the total number of leaves that have been inserted into the /// tree. It's important to note that this is not the same as total /// capacity of leaves. Leaves that have manually been set to empty /// values are not considered. #[must_use] pub fn num_leaves(&self) -> usize { self.storage.num_leaves() } /// Sets the value at the given index. /// /// If the leaf index is greater than the current number of leaves, the tree is extended with /// empty values up to the given leaf index. pub fn set_leaf(&mut self, leaf: usize, value: H::Hash) { let num_leaves = self.storage.num_leaves(); if leaf < num_leaves { let index = storage_ops::index_from_leaf(leaf); self.storage[index] = value; self.storage.propagate_up(index); self.recompute_root(); } else { let num_zeros = leaf - num_leaves; let mut new = Vec::with_capacity(num_zeros + 1); new.resize(num_zeros, self.empty_value); new.push(value); self.extend_from_slice(&new); } } /// Pushes a new leaf to the tree, increasing the number of leaves by one. /// /// # Errors /// /// Returns an error if the number of leaves exceeds the capacity of the tree. pub fn push(&mut self, leaf: H::Hash) -> Result<()> { let num_leaves = self.num_leaves(); ensure!( num_leaves < (1 << self.depth), "Cannot push more leaves than the capacity of the tree" ); let index = storage_ops::index_from_leaf(num_leaves); let storage_len = self.storage.len(); // If the index is out of bounds, we need to reallocate the storage // we must always have 2^n leaves for any n if index >= storage_len { debug_assert!(storage_len.is_power_of_two()); self.storage .extend(std::iter::repeat_n(self.empty_value, storage_len)); let subtree = &mut self.storage[storage_len..(storage_len << 1)]; sparse_fill_partial_subtree::(subtree, &self.sparse_column, 0..(storage_len >> 1)); } self.storage[index] = leaf; self.storage.increment_num_leaves(1); self.storage.propagate_up(index); self.recompute_root(); Ok(()) } /// Returns the Merkle proof for the given leaf. /// /// # TODO: /// Currently the branch which connects the storage tip to the root /// is not stored persistenetly. Repeated requests for proofs in between /// tree updates result in recomputing the same hashes when this could be /// avoided. /// /// # Panics /// /// Panics if the leaf index is not less than the current /// number of leaves. #[must_use] pub fn proof(&self, leaf: usize) -> InclusionProof { assert!(leaf < self.num_leaves(), "Leaf index out of bounds"); let mut proof = Vec::with_capacity(self.depth); let storage_depth = storage_ops::subtree_depth(&self.storage); let mut index = storage_ops::index_from_leaf(leaf); for _ in 0..storage_depth { match storage_ops::sibling(index) { Branch::Left(sibling_index) => { proof.push(Branch::Left(self.storage[sibling_index])); } Branch::Right(sibling_index) => { proof.push(Branch::Right(self.storage[sibling_index])); } } index = storage_ops::parent(index); } let remainder = self.sparse_column[storage_depth..(self.sparse_column.len() - 1)] .iter() .map(|&val| Branch::Left(val)); proof.extend(remainder); InclusionProof(proof) } /// Returns the Merkle proof for the given leaf hash. /// Leaves are scanned from right to left. /// This is a slow operation and `proof` should be used when possible. #[must_use] pub fn proof_from_hash(&self, leaf: H::Hash) -> Option> { let leaf = self.get_leaf_from_hash(leaf)?; Some(self.proof(leaf)) } /// Verifies the given proof for the given value. #[must_use] pub fn verify(&self, value: H::Hash, proof: &InclusionProof) -> bool { proof.root(value) == self.root() } /// Returns the node hash at the given index. /// /// # Panics /// /// Panics if either the depth or offset is out of bounds. #[must_use] pub fn get_node(&self, depth: usize, offset: usize) -> H::Hash { let height = self.depth - depth; let index = storage_ops::index_height_offset(height, offset); match self.storage.get(index) { Some(hash) => *hash, None => { if offset == 0 { self.compute_from_storage_tip(depth) } else { self.sparse_column[height] } } } } /// Returns the hash at the given leaf index. #[must_use] pub fn get_leaf(&self, leaf: usize) -> H::Hash { let index = storage_ops::index_from_leaf(leaf); self.storage.get(index).copied().unwrap_or(self.empty_value) } /// Returns the leaf index for the given leaf hash. #[must_use] pub fn get_leaf_from_hash(&self, hash: H::Hash) -> Option { let num_leaves = self.num_leaves(); if num_leaves == 0 { return None; } let mut end = storage_ops::index_from_leaf(num_leaves - 1) + 1; // 4 let prev_pow = end.next_power_of_two() >> 1; let mut start = prev_pow + (prev_pow >> 1); loop { match (start..end).rev().find(|&i| self.storage[i] == hash) { Some(index) => { return Some(storage_ops::leaf_from_index(index)); } None => { if start == 1 { return None; } start /= 2; end = (start + 1).next_power_of_two(); } } } } /// Returns an iterator over all leaf hashes. pub fn leaves(&self) -> impl Iterator + '_ { self.storage.leaves() } /// Returns the `sparse_column` for the given depth and empty_value. /// This columns represents empty values sequentially hashed together up to /// the top of the tree. /// Index 0 represents the bottom layer of the tree. #[must_use] fn sparse_column(depth: usize, empty_value: &H::Hash) -> Vec { (0..depth + 1) .scan(*empty_value, |state, _| { let val = *state; *state = H::hash_node(&val, &val); Some(val) }) .collect() } /// Returns the root of the tree. /// Hashes are recomputed from the storage tip. fn recompute_root(&mut self) -> H::Hash { let hash = self.compute_from_storage_tip(0); self.root = hash; hash } /// Recomputes hashess from the storage tip up to the given depth. /// The hash returned is the hash of the left most branch of the tree. fn compute_from_storage_tip(&self, depth: usize) -> H::Hash { let storage_root = self.storage.storage_root(); let storage_depth = self.storage.storage_depth(); let mut hash = storage_root; for i in storage_depth..(self.depth - depth) { hash = H::hash_node(&hash, &self.sparse_column[i]); } hash } /// Validates all elements of the storage, ensuring that they /// correspond to a valid tree. pub fn validate(&self) -> Result<()> { debug_assert_eq!( self.root, self.compute_from_storage_tip(0), "Root hash does not match recomputed root hash" ); self.storage.validate(&self.empty_value) } /// Extends the tree with the given leaves in parallel. /// /// ```markdown /// subtree_power = ilog2(8) = 3 /// /// 8 (subtree) /// 4 [ 9 ] /// 2 5 [ 10 11 ] /// 1 3 6 7 [12 13 14 15] /// ``` pub fn extend_from_slice(&mut self, leaves: &[H::Hash]) { if leaves.is_empty() { return; } let num_new_leaves = leaves.len(); let storage_len = self.storage.len(); let current_leaves = self.num_leaves(); let total_leaves = current_leaves + num_new_leaves; assert!( total_leaves <= (1 << self.depth), "Cannot extend more leaves than the capacity of the tree" ); let new_last_leaf_index = storage_ops::index_from_leaf(total_leaves - 1); // If the index is out of bounds, we need to resize the storage // we must always have 2^n leaves for any n if new_last_leaf_index >= storage_len { let next_power_of_two = new_last_leaf_index.next_power_of_two(); let diff = next_power_of_two - storage_len; self.storage .extend(std::iter::repeat_n(self.empty_value, diff)); } // Represense the power of the first subtree that has been modified let first_subtree_power = ((current_leaves + 1).next_power_of_two()).ilog2(); // Represense the power of the last subtree that has been modified let last_subtree_power = ((total_leaves).next_power_of_two()).ilog2(); let mut remaining_leaves = leaves; // We iterate over subsequently larger subtrees which have been // modified by the new leaves. for subtree_power in first_subtree_power..=last_subtree_power { // We have a special case for subtree_power = 0 // because the subtree is completely empty. // This represents the very bottom left of the tree. // parent_index represents the index of the parent node of the subtree. // It is the power of two on the left most branch of the tree. let parent_index = if subtree_power == 0 { let (leaf_slice, remaining) = remaining_leaves.split_at(1); remaining_leaves = remaining; self.storage[1] = leaf_slice[0]; continue; } else { 1 << subtree_power }; // The slice of the storage that contains the subtree let subtree_slice = &mut self.storage[parent_index..(parent_index << 1)]; let (_depth, width) = storage_ops::subtree_depth_width(subtree_slice); // leaf_start represents the leaf index of the subtree where we should begin // inserting the new leaves. let leaf_start = if subtree_power == first_subtree_power { current_leaves - ((current_leaves + 1).next_power_of_two() >> 1) } else { 0 }; // The number of leaves to be inserted into this subtree. let leaves_to_take = (width - leaf_start).min(remaining_leaves.len()); let (leaf_slice, remaining) = remaining_leaves.split_at(leaves_to_take); remaining_leaves = remaining; // Extend the subtree with the new leaves beginning at leaf_start let root = if leaf_start == 0 { storage_ops::init_subtree_with_leaves::( subtree_slice, &self.sparse_column, leaf_slice, ) } else { storage_ops::extend_subtree_with_leaves::(subtree_slice, leaf_start, leaf_slice) }; // sibling_hash represents the hash of the sibling of the tip of this subtree. let sibling_hash = self.storage[1 << (subtree_power - 1)]; // Update the parent node of the tip of this subtree. self.storage[parent_index] = H::hash_node(&sibling_hash, &root); } // Update the number of leaves in the tree. self.storage.set_num_leaves(total_leaves); self.recompute_root(); } } #[cfg(test)] mod tests { use rand::{thread_rng, Rng}; use semaphore_rs_hasher::Hasher; use semaphore_rs_keccak::keccak::Keccak256; use semaphore_rs_storage::{GenericStorage, MmapVec}; use serial_test::serial; use super::*; #[derive(Debug, Clone, PartialEq, Eq)] pub struct TestHasher; impl Hasher for TestHasher { type Hash = usize; fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash { left + right } } pub fn debug_tree(tree: &CascadingMerkleTree) where H: Hasher, ::Hash: Debug + Copy, S: GenericStorage + std::fmt::Debug, { println!("{tree:?}"); debug_storage::(&tree.storage); } pub fn debug_storage(storage: &S) where H: Hasher, ::Hash: Debug + Copy, S: std::ops::Deref::Hash]> + std::fmt::Debug, { let storage_depth = storage.len().ilog2(); let storage_len = storage.len(); let root_index = storage_len >> 1; let mut previous = vec![root_index]; println!("{:?}", vec![storage[root_index]]); for _ in 1..storage_depth { let next = previous .iter() .flat_map(|&i| storage_ops::children(i)) .collect::>(); previous = next.iter().flat_map(|&(l, r)| [l, r]).collect(); let row = previous.iter().map(|&i| storage[i]).collect::>(); println!("{row:?}"); } } #[test] fn test_index_from_leaf() { let mut leaf_indeces = Vec::new(); for i in 0..16 { leaf_indeces.push(storage_ops::index_from_leaf(i)); } let expected_leaves = vec![1, 3, 6, 7, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, 31]; assert_eq!(leaf_indeces, expected_leaves); println!("Leaf indeces: {:?}", leaf_indeces); } #[test] fn test_index_height_offset() { let expected = vec![ ((0, 0), 1), ((0, 1), 3), ((0, 2), 6), ((0, 3), 7), ((0, 4), 12), ((0, 5), 13), ((0, 6), 14), ((0, 7), 15), ((1, 0), 2), ((1, 1), 5), ((1, 2), 10), ((1, 3), 11), ((2, 0), 4), ((2, 1), 9), ((3, 0), 8), ]; for ((height, offset), result) in expected { println!( "Height: {}, Offset: {}, expected: {}", height, offset, result ); assert_eq!(storage_ops::index_height_offset(height, offset), result); } } #[test] fn test_parent() { let mut parents = Vec::new(); for i in 1..16 { parents.push((i, storage_ops::parent(i))); } let expected_parents = vec![ (1, 2), (2, 4), (3, 2), (4, 8), (5, 4), (6, 5), (7, 5), (8, 16), (9, 8), (10, 9), (11, 9), (12, 10), (13, 10), (14, 11), (15, 11), ]; assert_eq!(parents, expected_parents); println!("Parents: {:?}", parents); } #[test] fn test_sibling() { let mut siblings = Vec::new(); for i in 1..16 { siblings.push((i, storage_ops::sibling(i))); } use Branch::*; let expected_siblings = vec![ (1, Left(3)), (2, Left(5)), (3, Right(1)), (4, Left(9)), (5, Right(2)), (6, Left(7)), (7, Right(6)), (8, Left(17)), (9, Right(4)), (10, Left(11)), (11, Right(10)), (12, Left(13)), (13, Right(12)), (14, Left(15)), (15, Right(14)), ]; assert_eq!(siblings, expected_siblings); println!("Siblings: {:?}", siblings); } #[test] fn test_children() { let mut children = Vec::new(); for i in 1..16 { children.push((i, storage_ops::children(i))); } let expected_siblings = vec![ (1, None), (2, Some((1, 3))), (3, None), (4, Some((2, 5))), (5, Some((6, 7))), (6, None), (7, None), (8, Some((4, 9))), (9, Some((10, 11))), (10, Some((12, 13))), (11, Some((14, 15))), (12, None), (13, None), (14, None), (15, None), ]; assert_eq!(children, expected_siblings); println!("Siblings: {:?}", children); } #[test] fn test_invalid_storage() { let _ = CascadingMerkleTree::::restore_unchecked(vec![2, 1, 1, 1, 1], 1, &0) .expect_err("invalid storage len"); let _ = CascadingMerkleTree::::restore_unchecked(vec![3, 1, 1, 1], 1, &0) .expect_err("invalid num leaves"); let _ = CascadingMerkleTree::::restore_unchecked(vec![3, 1, 1, 1, 1, 1, 1], 1, &0) .expect_err("len too long for depth"); } #[should_panic] #[test] fn test_hash_too_small() { #[derive(Debug, Clone, PartialEq, Eq)] struct InvalidHasher; impl Hasher for InvalidHasher { type Hash = u32; fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash { left + right } } let _ = CascadingMerkleTree::::new_with_leaves(vec![], 1, &0, &[]); } #[test] fn test_min_sized_tree() { let num_leaves = 1; let leaves = vec![1; num_leaves]; let empty = 0; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 1, &empty, &leaves); tree.validate().unwrap(); debug_tree(&tree); } #[test] fn test_set_leaf() { let num_leaves = 4; let leaves = vec![1; num_leaves]; let empty = 0; let mut tree = CascadingMerkleTree::::new_with_leaves(vec![], 2, &empty, &leaves); tree.set_leaf(3, 2); tree.validate().unwrap(); let expected = CascadingMerkleTree::::new_with_leaves(vec![], 2, &empty, &[1, 1, 1, 2]); assert_eq!(tree, expected); } #[test] fn test_set_leaf_extend() { let num_leaves = 2; let leaves = vec![1; num_leaves]; let empty = 0; let mut tree = CascadingMerkleTree::::new_with_leaves(vec![], 2, &empty, &leaves); tree.set_leaf(3, 2); tree.validate().unwrap(); let expected = CascadingMerkleTree::::new_with_leaves(vec![], 2, &empty, &[1, 1, 0, 2]); assert_eq!(tree, expected); } #[should_panic] #[test] fn test_zero_depth_tree() { let num_leaves = 1; let leaves = vec![1; num_leaves]; let empty = 0; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 0, &empty, &leaves); debug_tree(&tree); } #[test] fn test_odd_leaves() { let num_leaves = 5; let leaves = vec![1; num_leaves]; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 10, &0, &leaves); let expected = CascadingMerkleTree:: { depth: 10, root: 5, empty_value: 0, sparse_column: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], storage: vec![5, 1, 2, 1, 4, 2, 1, 1, 5, 1, 1, 0, 1, 0, 0, 0], _marker: std::marker::PhantomData, }; debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree, expected); } #[test] fn test_even_leaves() { let num_leaves = 1 << 3; let leaves = vec![1; num_leaves]; let empty = 0; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 10, &empty, &leaves); let expected = CascadingMerkleTree:: { depth: 10, root: 8, empty_value: 0, sparse_column: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], storage: vec![8, 1, 2, 1, 4, 2, 1, 1, 8, 4, 2, 2, 1, 1, 1, 1], _marker: std::marker::PhantomData, }; debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree, expected); } #[test] fn test_no_leaves() { let leaves = vec![]; let empty = 0; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 10, &empty, &leaves); let expected = CascadingMerkleTree:: { depth: 10, root: 0, empty_value: 0, sparse_column: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], storage: vec![0, 0], _marker: std::marker::PhantomData, }; debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree, expected); } #[test] fn test_sparse_column() { let leaves = vec![]; let empty = 1; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 10, &empty, &leaves); let expected = CascadingMerkleTree:: { depth: 10, root: 1024, empty_value: 1, sparse_column: vec![1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024], storage: vec![0, 1], _marker: std::marker::PhantomData, }; debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree, expected); } #[test] fn test_compute_root() { let num_leaves = 1 << 3; let leaves = vec![0; num_leaves]; let empty = 1; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 4, &empty, &leaves); let expected = CascadingMerkleTree:: { depth: 4, root: 8, empty_value: 1, sparse_column: vec![1, 2, 4, 8, 16], storage: vec![8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], _marker: std::marker::PhantomData, }; debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree, expected); } #[test] fn test_get_node() { let num_leaves = 3; let leaves = vec![3; num_leaves]; let empty = 1; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 3, &empty, &leaves); debug_tree(&tree); tree.validate().unwrap(); let expected = vec![ ((3, 0), 3), ((3, 1), 3), ((3, 2), 3), ((3, 3), 1), ((3, 4), 1), ((3, 5), 1), ((3, 6), 1), ((3, 7), 1), ((2, 0), 6), ((2, 1), 4), ((2, 2), 2), ((2, 3), 2), ((1, 0), 10), ((1, 1), 4), ((0, 0), 14), ]; for ((depth, offset), result) in expected { println!("Depth: {}, Offset: {}, expected: {}", depth, offset, result); assert_eq!(tree.get_node(depth, offset), result); } } #[test] fn test_get_leaf_from_hash() { let empty = 0; let mut tree = CascadingMerkleTree::::new_with_leaves(vec![], 10, &empty, &[]); tree.validate().unwrap(); for i in 1..=64 { tree.push(i).unwrap(); tree.validate().unwrap(); let first = tree.get_leaf_from_hash(1).unwrap(); let this = tree.get_leaf_from_hash(i).unwrap(); assert_eq!(first, 0); assert_eq!(this, i - 1); } assert!(tree.get_leaf_from_hash(65).is_none()); } #[test] fn test_row_indices() { let num_leaves = 12; let leaves = vec![3; num_leaves]; let empty = 1; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 4, &empty, &leaves); tree.validate().unwrap(); debug_tree(&tree); let expected = vec![ ( 0, vec![ 1usize, 3, 6, 7, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, 31, ], ), (1, vec![2, 5, 10, 11, 20, 21, 22, 23]), (2, vec![4, 9, 18, 19]), (3, vec![8, 17]), (4, vec![16]), ]; for (height, result) in expected { println!("Height: {}, expected: {:?}", height, result); assert_eq!( as StorageOps>::row_indices(&tree.storage, height) .collect::>(), result ); } } #[test] fn test_row() { let leaves = vec![1, 2, 3, 4, 5, 6]; let empty = 0; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 20, &empty, &leaves); tree.validate().unwrap(); debug_tree(&tree); let expected = vec![ (0, vec![1, 2, 3, 4, 5, 6, 0, 0]), (1, vec![3, 7, 11, 0]), (2, vec![10, 11]), (3, vec![21]), ]; for (height, result) in expected { println!("Height: {}, expected: {:?}", height, result); assert_eq!( as StorageOps>::row(&tree.storage, height) .collect::>(), result ); // assert_eq!(tree.storage.row_indices(height).collect(), result); } } #[test] fn test_proof_from_hash() { let leaves = vec![1, 2, 3, 4, 5, 6]; let empty = 1; let tree = CascadingMerkleTree::::new_with_leaves(vec![], 4, &empty, &leaves); debug_tree(&tree); tree.validate().unwrap(); let expected = vec![ ( 1, vec![ Branch::Left(2), Branch::Left(7), Branch::Left(13), Branch::Left(8), ], ), ( 2, vec![ Branch::Right(1), Branch::Left(7), Branch::Left(13), Branch::Left(8), ], ), ( 3, vec![ Branch::Left(4), Branch::Right(3), Branch::Left(13), Branch::Left(8), ], ), ( 4, vec![ Branch::Right(3), Branch::Right(3), Branch::Left(13), Branch::Left(8), ], ), ( 5, vec![ Branch::Left(6), Branch::Left(2), Branch::Right(10), Branch::Left(8), ], ), ( 6, vec![ Branch::Right(5), Branch::Left(2), Branch::Right(10), Branch::Left(8), ], ), ]; for (leaf, expected_proof) in expected { let proof = tree.proof_from_hash(leaf).unwrap(); assert_eq!(proof.0, expected_proof); assert!(tree.verify(leaf, &proof)); } } #[test] fn test_leaves() { let mut tree = CascadingMerkleTree::::new(vec![], 22, &0); debug_tree(&tree); tree.validate().unwrap(); let expected: Vec = vec![]; assert_eq!(tree.leaves().collect::>(), expected); tree.push(1).unwrap(); debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree.leaves().collect::>(), vec![1]); tree.push(1).unwrap(); debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree.leaves().collect::>(), vec![1, 1]); tree.push(1).unwrap(); debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree.leaves().collect::>(), vec![1, 1, 1]); tree.push(1).unwrap(); debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree.leaves().collect::>(), vec![1, 1, 1, 1]); } type Hash = ::Hash; #[test] fn test_extend_from_slice_keccak() -> color_eyre::Result<()> { let leaves = (0..1 << 5) .map(|n: u64| { let b = n.to_be_bytes(); let mut hash = [0; 32]; hash[..8].copy_from_slice(&b); hash }) .collect::>(); // Create expected tree let expected_tree = CascadingMerkleTree::::new_with_leaves(vec![], 10, &[0; 32], &leaves); let mut tree = CascadingMerkleTree::::new(vec![], 10, &[0; 32]); tree.extend_from_slice(&leaves); assert_eq!( tree.leaves().collect::>(), expected_tree.leaves().collect::>() ); assert_eq!(tree.root(), expected_tree.root()); Ok(()) } #[test] fn test_push_beyond_depth() { let mut tree = CascadingMerkleTree::::new(vec![], 2, &1); for _ in 0..4 { tree.push(2).unwrap(); } let _ = tree.push(2).unwrap_err(); } #[test] #[should_panic] fn test_init_beyond_depth() { let _tree = CascadingMerkleTree::::new_with_leaves(vec![], 2, &1, &[2; 5]); } #[test] #[should_panic] fn test_extend_beyond_depth() { let mut tree = CascadingMerkleTree::::new(vec![], 2, &1); tree.extend_from_slice(&[2; 4]); tree.extend_from_slice(&[2; 1]); } #[test] fn test_push() { let mut tree = CascadingMerkleTree::::new(vec![], 30, &1); let mut vec = vec![]; for _ in 0..300 { tree.push(2).unwrap(); vec.push(2); debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree.leaves().collect::>(), vec); } } #[test] fn test_extend_from_slice() { for increment in 1..20 { let mut tree = CascadingMerkleTree::::new(vec![], 30, &1); let mut vec = vec![]; for _ in 0..20 { tree.extend_from_slice(&vec![2; increment]); vec.extend_from_slice(&vec![2; increment]); debug_tree(&tree); tree.validate().unwrap(); assert_eq!(tree.leaves().collect::>(), vec); } } } #[test] fn test_extend_from_slice_2() { for increment in 1..20 { let mut tree = CascadingMerkleTree::::new(vec![], 30, &[0; 32]); let mut vec = vec![]; for _ in 0..20 { let slice = (0..increment) .map(|_| { let mut hash = [0; 32]; let mut rng = thread_rng(); rng.fill(&mut hash); hash }) .collect::>(); tree.extend_from_slice(&slice); vec.extend_from_slice(&slice); tree.validate().unwrap(); assert_eq!(tree.leaves().collect::>(), vec); } } } #[test] fn test_vec_realloc_speed() { let empty = 0; let leaves = vec![1; 1 << 20]; let mut tree = CascadingMerkleTree::>::new_with_leaves(vec![], 30, &empty, &leaves); let start = std::time::Instant::now(); tree.push(1).unwrap(); let elapsed = start.elapsed(); println!( "Leaf index: {}, Time: {:?}ms", tree.num_leaves(), elapsed.as_millis() ); } #[test] #[serial] fn test_mmap_realloc_speed() { let empty = 0; let leaves = vec![1; 1 << 20]; println!("Create tempfile"); let tempfile = tempfile::tempfile().unwrap(); println!("Init mmap"); let mmap_vec: MmapVec<_> = unsafe { MmapVec::restore(tempfile).unwrap() }; println!("Init tree"); let mut tree = CascadingMerkleTree::>::new_with_leaves( mmap_vec, 30, &empty, &leaves, ); println!("test push"); let start = std::time::Instant::now(); tree.push(1).unwrap(); let elapsed = start.elapsed(); println!( "Leaf index: {}, Time: {:?}ms", tree.num_leaves(), elapsed.as_millis() ); } #[test] #[serial] fn test_restore_from_cache() -> color_eyre::Result<()> { let mut rng = rand::thread_rng(); let leaves: Vec = (0..1 << 2) .map(|_| { let mut hash = [0; 32]; rng.fill(&mut hash); hash }) .collect::>(); // Create a new tmp file for mmap storage let tempfile = tempfile::NamedTempFile::new()?; let file_path = tempfile.path().to_owned(); // Initialize the expected tree let mmap_vec: MmapVec<_> = unsafe { MmapVec::restore(tempfile.reopen()?).unwrap() }; let expected_tree = CascadingMerkleTree::>::new_with_leaves( mmap_vec, 3, &[0; 32], &leaves, ); let expected_root = expected_tree.root(); let expected_leaves = expected_tree.leaves().collect::>(); drop(expected_tree); // Restore the tree let mmap_vec: MmapVec<_> = unsafe { MmapVec::restore_from_path(file_path).unwrap() }; let tree = CascadingMerkleTree::>::restore(mmap_vec, 3, &[0; 32])?; // Assert that the root and the leaves are as expected assert_eq!(tree.root(), expected_root); assert_eq!(tree.leaves().collect::>(), expected_leaves); Ok(()) } } ================================================ FILE: crates/trees/src/cascading/storage_ops.rs ================================================ use std::ops::{Deref, DerefMut, Range}; use bytemuck::Pod; use color_eyre::eyre::{bail, ensure}; use color_eyre::Result; use rayon::prelude::*; use semaphore_rs_hasher::Hasher; use semaphore_rs_storage::GenericStorage; use crate::proof::Branch; pub trait StorageOps: GenericStorage + Deref + DerefMut + Send + Sync + Sized where H: Hasher, ::Hash: Copy + Pod + Eq + Send + Sync, { /// Clears the current storage and initializes it with the given leaves. fn populate_with_leaves( &mut self, sparse_column: &[H::Hash], empty_value: &H::Hash, leaves: &[H::Hash], ) { let num_leaves = leaves.len(); let base_len = num_leaves.next_power_of_two(); let storage_size = base_len << 1; self.clear(); self.extend(std::iter::repeat_n(*empty_value, storage_size)); let depth = base_len.ilog2(); // We iterate over subsequently larger subtrees let mut sibling_hash = *leaves.first().unwrap_or(empty_value); self[1] = sibling_hash; for subtree_power in 1..(depth + 1) { let parent_index = 1 << subtree_power; let subtree_slice = &mut self[parent_index..(parent_index << 1)]; let leaf_start = parent_index >> 1; let leaf_end = parent_index.min(num_leaves); let leaf_slice = &leaves[leaf_start..leaf_end]; let root = init_subtree_with_leaves::(subtree_slice, sparse_column, leaf_slice); let hash = H::hash_node(&sibling_hash, &root); self[parent_index] = hash; sibling_hash = hash; } self.set_num_leaves(num_leaves); } /// Returns an iterator over all leaves including those that have noe been /// set. fn leaves(&self) -> impl Iterator + '_ { self.row_indices(0) .take(self.num_leaves()) .map(move |i| self[i]) } fn row_indices(&self, height: usize) -> impl Iterator + Send + '_ { let storage_height = (self.len().ilog2() - 1) as usize; let width = if height > storage_height { 0 } else { let height_diff = storage_height - height; 1 << height_diff }; row_indices(height).take(width) } fn row(&self, height: usize) -> impl Iterator + Send + '_ { self.row_indices(height).map(move |i| self[i]) } /// Returns the root hash of the growable storage, not the top level root. fn storage_root(&self) -> H::Hash { self[self.len() >> 1] } /// Returns the depth of growable storage, not the top level root. fn storage_depth(&self) -> usize { subtree_depth(self) } /// Sets the number of leaves. fn set_num_leaves(&mut self, amount: usize) { let leaf_counter: &mut [usize] = bytemuck::cast_slice_mut(&mut self[0..1]); leaf_counter[0] = amount; } fn num_leaves(&self) -> usize { bytemuck::cast_slice(&self[0..1])[0] } /// Increments the number of leaves. fn increment_num_leaves(&mut self, amount: usize) { let leaf_counter: &mut [usize] = bytemuck::cast_slice_mut(&mut self[0..1]); leaf_counter[0] += amount; } /// Propagates new hashes up the top of the subtree. fn propagate_up(&mut self, mut index: usize) -> Option<()> { loop { let (left, right) = match sibling(index) { Branch::Left(sibling) => (index, sibling), Branch::Right(sibling) => (sibling, index), }; let left_hash = self.get(left)?; let right_hash = self.get(right)?; let parent_index = parent(index); self[parent_index] = H::hash_node(left_hash, right_hash); index = parent_index; } } /// Performs partial constant time validation of the storage. fn validate_const(&self) -> Result<()> { let len = self.len(); ensure!( len.is_power_of_two(), "Storage length ({len}) must be a power of 2" ); ensure!(len > 1, "Storage length ({len}) must be greater than 1"); Ok(()) } /// Validates all elements of the storage, ensuring that they /// correspond to a valid tree. fn validate(&self, empty_value: &H::Hash) -> Result<()> { self.validate_const()?; let len = self.len(); let width = len >> 1; let depth = width.ilog2() as usize; let num_leaves = self.num_leaves(); let first_empty = index_from_leaf(num_leaves); if first_empty < len { self[first_empty..].par_iter().try_for_each(|hash| { if hash != empty_value { bail!("Storage contains non-empty values past the last leaf"); } Ok(()) })?; } for height in 0..=depth { let row = self.row(height); let parents = self.row(height + 1); let row_couple = itertools::Itertools::tuples(row); parents .zip(row_couple) .par_bridge() .try_for_each(|(parent, (left, right))| { let expected = H::hash_node(&left, &right); if parent != expected { bail!("Invalid hash"); } Ok(()) })?; } Ok(()) } } impl StorageOps for S where H: Hasher, S: GenericStorage, ::Hash: Copy + Pod + Eq + Send + Sync, { } /// Assumes that slice len is a power of 2 #[inline] pub fn subtree_depth(storage_slice: &[H]) -> usize { let len = storage_slice.len(); debug_assert!(len.is_power_of_two()); debug_assert!(len > 1); (len >> 1).ilog2() as usize } pub fn sibling(i: usize) -> Branch { let next_pow = i.next_power_of_two(); if i == next_pow { return Branch::Left((i << 1) + 1); } let prev_pow = next_pow >> 1; if i - 1 == prev_pow { return Branch::Right(prev_pow >> 1); } if i & 1 == 0 { // even Branch::Left(i + 1) } else { // odd Branch::Right(i - 1) } } pub fn parent(i: usize) -> usize { if i.is_power_of_two() { return i << 1; } let prev_pow = i.next_power_of_two() >> 1; let shifted = i - prev_pow; let shifted_parent = shifted >> 1; shifted_parent + prev_pow } // leaves are 0 indexed pub fn index_from_leaf(leaf: usize) -> usize { leaf + (leaf + 1).next_power_of_two() } pub fn leaf_from_index(index: usize) -> usize { let next = (index + 1).next_power_of_two(); let prev = next >> 1; index - prev } pub fn index_height_offset(height: usize, offset: usize) -> usize { if offset == 0 { return 1 << height; } let leaf = offset * (1 << height); let subtree_size = (leaf + 1).next_power_of_two(); let offset_node = leaf >> height; offset_node + subtree_size } #[cfg(test)] pub fn children(i: usize) -> Option<(usize, usize)> { let next_pow = i.next_power_of_two(); if i == next_pow { if i == 1 { return None; } let left = i >> 1; let right = i + 1; return Some((left, right)); } let prev_pow = next_pow >> 1; let half = prev_pow >> 1; let offset = i - prev_pow; if offset >= half { return None; } let offset_left = offset * 2; let offset_right = offset_left + 1; Some((prev_pow + offset_left, prev_pow + offset_right)) } /// Initialize a subtree with the given leaves in parallel. /// /// O(n) time complexity /// /// Subtrees are 1 indexed and directly attached to the left most branch /// of the main tree. /// /// This function assumes that storage is already initialized with empty /// values and is the correct length for the subtree. /// If 'leaves' is not long enough, the remaining leaves will be left empty /// /// storage.len() must be a power of 2 and greater than or equal to 2 /// storage is 1 indexed /// /// ```markdown /// 8 (subtree) /// 4 [ 9 ] /// 2 5 [ 10 11 ] /// 1 3 6 7 [12 13 14 15] /// ``` pub fn init_subtree_with_leaves( subtree: &mut [H::Hash], sparse_column: &[H::Hash], leaves: &[H::Hash], ) -> H::Hash where H: Hasher, ::Hash: Copy + Pod + Eq + Send + Sync, { let (_depth, width) = subtree_depth_width(subtree); // Set the leaves subtree[(width)..(width + leaves.len())] .par_iter_mut() .zip(leaves.par_iter()) .for_each(|(val, leaf)| { *val = *leaf; }); // For empty values to the right of the newly set leaves // we can prapogate the sparse column up the tree // in O(log(n)) hashes sparse_fill_partial_subtree::(subtree, sparse_column, leaves.len()..width); // For newly set leaves we can prapogate the hashes up the tree // in O(n) hashes propagate_partial_subtree::(subtree, 0..leaves.len()); subtree[1] } /// Extend leaves onto a preexisting subtree. This method assumes that the /// sparse column has already been applied to all rows /// /// O(n) time complexity /// /// Subtrees are 1 indexed and directly attached to the left most branch /// of the main tree. /// /// This function assumes that storage is already initialized with empty /// values and is the correct length for the subtree. /// If 'leaves' is not long enough, the remaining leaves will be left empty /// /// storage.len() must be a power of 2 and greater than or equal to 2 /// storage is 1 indexed /// /// ```markdown /// 8 (subtree) /// 4 [ 9 ] /// 2 5 [ 10 11 ] /// 1 3 6 7 [12 13 14 15] /// ``` pub fn extend_subtree_with_leaves( subtree: &mut [H::Hash], start: usize, leaves: &[H::Hash], ) -> H::Hash where H: Hasher, ::Hash: Copy + Pod + Eq + Send + Sync, { let (_depth, width) = subtree_depth_width(subtree); // Set the leaves subtree[(width + start)..(width + start + leaves.len())] .par_iter_mut() .zip(leaves.par_iter()) .for_each(|(val, leaf)| { *val = *leaf; }); // For newly set leaves we can propagate the hashes up the tree // in O(n) hashes propagate_partial_subtree::(subtree, start..start + leaves.len()); subtree[1] } /// Propagate hashes up a subtree with leaves within a given range. /// /// O(n) time complexity /// /// Subtrees are 1 indexed and directly attached to the left most branch /// of the main tree. /// /// This function assumes that the tree is in a valid state except for the /// newly added leaves. /// /// storage.len() must be a power of 2 and greater than or equal to 2 /// storage is 1 indexed /// /// ```markdown /// 8 (subtree) /// 4 [ 9 ] /// 2 5 [ 10 11 ] /// 1 3 6 7 [12 13 14 15] /// ``` pub fn propagate_partial_subtree(subtree: &mut [H::Hash], mut range: Range) -> H::Hash where H: Hasher, ::Hash: Copy + Pod + Eq + Send + Sync, { let depth = subtree_depth(subtree); // Iterate over mutable layers of the tree for current_depth in (1..=depth).rev() { if range.is_empty() { break; } // Split the subtree into relavent layers let (top, child_layer) = subtree.split_at_mut(1 << current_depth); let parent_layer = &mut top[(1 << (current_depth - 1))..]; // Update the range to match the new parent layer range.start /= 2; range.end = ((range.end - 1) / 2) + 1; parent_layer[range.clone()] .par_iter_mut() .enumerate() .for_each(|(i, value)| { let i = i + range.start; let left = &child_layer[2 * i]; let right = &child_layer[2 * i + 1]; *value = H::hash_node(left, right); }); } subtree[1] } /// Propagates empty hashes up the tree within a given range. /// /// O(log(n)) time complexity /// /// Subtrees are 1 indexed and directly attached to the left most branch /// of the main tree. /// /// This function will overwrite any existing or dependent values withing the /// range. It assumes that the base layer has already been initialized with /// empty values. /// /// storage.len() must be a power of 2 and greater than or equal to 2 /// storage is 1 indexed /// /// ```markdown /// 8 (subtree) /// 4 [ 9 ] /// 2 5 [ 10 11 ] /// 1 3 6 7 [12 13 14 15] /// ``` pub fn sparse_fill_partial_subtree( subtree: &mut [H::Hash], sparse_column: &[H::Hash], mut range: Range, ) -> H::Hash where H: Hasher, ::Hash: Copy + Pod + Eq + Send + Sync, { let depth = subtree_depth(subtree); // Iterate over mutable layers of the tree for current_depth in (1..=depth).rev() { if range.is_empty() { break; } // Split the subtree into relavent layers let (top, _child_layer) = subtree.split_at_mut(1 << current_depth); let parent_layer = &mut top[(1 << (current_depth - 1))..]; // Update the range to match the new parent layer range.start /= 2; range.end = ((range.end - 1) / 2) + 1; parent_layer[range.clone()].par_iter_mut().for_each(|i| { *i = sparse_column[depth + 1 - current_depth]; }); } subtree[1] } fn row_indices(height: usize) -> impl Iterator + Send { let first = 1 << height; let iter_1 = first..(first + 1); let next = (first << 1) + 1; let iter_2 = (0..).scan(next, |next, i| { let slice_len = 1 << i; let res = *next..(*next + slice_len); *next *= 2; Some(res) }); std::iter::once(iter_1).chain(iter_2).flatten() } /// Assumes that slice len is a power of 2 #[inline] pub fn subtree_depth_width(storage_slice: &[H]) -> (usize, usize) { let len = storage_slice.len(); debug_assert!(len.is_power_of_two()); debug_assert!(len > 1); let width = len >> 1; let depth = width.ilog2() as usize; (depth, width) } #[cfg(test)] mod tests { use semaphore_rs_keccak::keccak::Keccak256; use semaphore_rs_storage::MmapVec; use super::super::tests::TestHasher; use super::*; fn test_is_storage_ops(_s: &S) where S: StorageOps, { } // A compile time test to verify that MmapVec is StorageOps #[allow(unused)] fn test_mmap_vec_is_storage_ops(s: MmapVec<::Hash>) { test_is_storage_ops(&s); } #[test] fn test_sparse_fill_partial_subtree() { let mut storage = vec![1; 16]; let sparse_column = vec![1, 2, 4, 8, 16]; sparse_fill_partial_subtree::(&mut storage, &sparse_column, 4..8); let expected = vec![1, 8, 1, 4, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1]; assert_eq!(storage, expected); } #[test] fn test_propagate_partial_subtree_noop_on_empty_range() { let mut subtree = vec![0usize; 4]; // depth=1 subtree let before = subtree.clone(); propagate_partial_subtree::(&mut subtree, 0..0); assert_eq!(subtree, before); } #[test] fn test_sparse_fill_partial_subtree_noop_on_empty_range() { let mut subtree = vec![0usize; 4]; let sparse_column = vec![0, 0, 0]; let before = subtree.clone(); sparse_fill_partial_subtree::(&mut subtree, &sparse_column, 0..0); assert_eq!(subtree, before); } } ================================================ FILE: crates/trees/src/imt/mod.rs ================================================ //! Implements basic binary Merkle trees use std::fmt::Debug; use std::iter::{once, repeat_n, successors}; use bytemuck::Pod; use derive_where::derive_where; use semaphore_rs_hasher::Hasher; use crate::proof::{Branch, InclusionProof}; /// Merkle tree with all leaf and intermediate hashes stored #[derive_where(Clone; ::Hash: Clone)] #[derive_where(PartialEq; ::Hash: PartialEq)] #[derive_where(Eq; ::Hash: Eq)] #[derive_where(Debug; ::Hash: Debug)] pub struct MerkleTree where H: Hasher, { /// Depth of the tree, # of layers including leaf layer depth: usize, /// Hash value of empty subtrees of given depth, starting at leaf level empty: Vec, /// Hash values of tree nodes and leaves, breadth first order nodes: Vec, } /// For a given node index, return the parent node index /// Returns None if there is no parent (root node) const fn parent(index: usize) -> Option { if index <= 1 { None } else { Some(index >> 1) } } /// For a given node index, return index of the first (left) child. const fn left_child(index: usize) -> usize { index << 1 } const fn depth(index: usize) -> usize { // `n.next_power_of_two()` will return `n` iff `n` is a power of two. // The extra offset corrects this. if index <= 1 { return 0; } index.ilog2() as usize } impl MerkleTree where H: Hasher, ::Hash: Clone + Copy + Pod + Eq + Debug, { /// Creates a new `MerkleTree` /// * `depth` - The depth of the tree, including the root. This is 1 greater /// than the `treeLevels` argument to the Semaphore contract. pub fn new(depth: usize, initial_leaf: H::Hash) -> Self { // Compute empty node values, leaf to root let empty = successors(Some(initial_leaf), |prev| Some(H::hash_node(prev, prev))) .take(depth + 1) .collect::>(); // Compute node values let first_node = std::iter::once(initial_leaf); let nodes = empty .iter() .rev() .enumerate() .flat_map(|(depth, hash)| repeat_n(hash, 1 << depth)) .cloned(); let nodes = first_node.chain(nodes).collect(); Self { depth, empty, nodes, } } #[must_use] pub fn num_leaves(&self) -> usize { 1 << self.depth } #[must_use] pub fn root(&self) -> H::Hash { self.nodes[1] } pub fn set(&mut self, leaf: usize, hash: H::Hash) { self.set_range(leaf, once(hash)); } pub fn set_range>(&mut self, start: usize, hashes: I) { let index = self.num_leaves() + start; let mut count = 0; // TODO: Error/panic when hashes is longer than available leafs for (leaf, hash) in self.nodes[index..].iter_mut().zip(hashes) { *leaf = hash; count += 1; } if count != 0 { self.update_nodes(index, index + (count - 1)); } } fn update_nodes(&mut self, start: usize, end: usize) { debug_assert_eq!(depth(start), depth(end)); if let (Some(start), Some(end)) = (parent(start), parent(end)) { for parent in start..=end { let child = left_child(parent); self.nodes[parent] = H::hash_node(&self.nodes[child], &self.nodes[child + 1]); } self.update_nodes(start, end); } } #[must_use] pub fn proof(&self, leaf: usize) -> Option> { if leaf >= self.num_leaves() { return None; } let mut index = self.num_leaves() + leaf; let mut path = Vec::with_capacity(self.depth); while let Some(parent) = parent(index) { // Add proof for node at index to parent path.push(match index & 1 { 1 => Branch::Right(self.nodes[index - 1]), 0 => Branch::Left(self.nodes[index + 1]), _ => unreachable!(), }); index = parent; } Some(InclusionProof(path)) } #[must_use] pub fn verify(&self, hash: H::Hash, proof: &InclusionProof) -> bool { proof.root(hash) == self.root() } #[must_use] pub fn leaves(&self) -> &[H::Hash] { &self.nodes[(self.num_leaves() - 1)..] } } impl InclusionProof { /// Compute the leaf index for this proof #[must_use] pub fn leaf_index(&self) -> usize { self.0.iter().rev().fold(0, |index, branch| match branch { Branch::Left(_) => index << 1, Branch::Right(_) => (index << 1) + 1, }) } /// Compute the Merkle root given a leaf hash #[must_use] pub fn root(&self, hash: H::Hash) -> H::Hash { self.0.iter().fold(hash, |hash, branch| match branch { Branch::Left(sibling) => H::hash_node(&hash, sibling), Branch::Right(sibling) => H::hash_node(sibling, &hash), }) } } #[cfg(test)] pub mod test { use hex_literal::hex; use ruint::aliases::U256; use semaphore_rs_keccak::keccak::Keccak256; use semaphore_rs_poseidon::Poseidon; use test_case::test_case; use super::*; #[test_case(0 => None)] #[test_case(1 => None)] #[test_case(2 => Some(1))] #[test_case(3 => Some(1))] #[test_case(4 => Some(2))] #[test_case(5 => Some(2))] #[test_case(6 => Some(3))] #[test_case(27 => Some(13))] fn parent_of(index: usize) -> Option { parent(index) } #[test_case(0 => 0 ; "Nonsense case")] #[test_case(1 => 2)] #[test_case(2 => 4)] #[test_case(3 => 6)] fn left_child_of(index: usize) -> usize { left_child(index) } #[test_case(0 => 0)] #[test_case(1 => 0)] #[test_case(2 => 1)] #[test_case(3 => 1)] #[test_case(6 => 2)] fn depth_of(index: usize) -> usize { depth(index) } #[test_case(2 => hex!("b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30"))] fn empty_keccak(depth: usize) -> [u8; 32] { let tree = MerkleTree::::new(depth, [0; 32]); tree.root() } #[test] fn simple_poseidon() { let mut tree = MerkleTree::::new(10, U256::ZERO); let expected_root = ruint::uint!( 12413880268183407374852357075976609371175688755676981206018884971008854919922_U256 ); assert_eq!(tree.root(), expected_root); tree.set(0, ruint::uint!(1_U256)); let expected_root = ruint::uint!( 467068234150758165281816522946040748310650451788100792957402532717155514893_U256 ); assert_eq!(tree.root(), expected_root); } } ================================================ FILE: crates/trees/src/lazy/mod.rs ================================================ use std::fs::OpenOptions; use std::io::Write; use std::iter::{once, repeat_n, successors}; use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::str::FromStr; use std::sync::{Arc, Mutex}; use mmap_rs::{MmapFlags, MmapMut, MmapOptions}; use rayon::prelude::*; use semaphore_rs_hasher::{Hash, Hasher}; use thiserror::Error; use crate::{Branch, InclusionProof}; pub trait VersionMarker {} #[derive(Debug)] pub struct Canonical; impl VersionMarker for Canonical {} #[derive(Debug)] pub struct Derived; impl VersionMarker for Derived {} /// A storage-optimized merkle tree. /// /// It has a certain linear-buffer represented /// prefix subtree and the rest of the tree is represented using lazy, /// pointer-based structures. This makes it possible to hold even large trees in /// memory, assuming only a relatively small subset is ever modified. /// /// It exposes an immutable API, so that multiple versions can be kept in memory /// while reusing as much structure as possible. /// /// The update method also allows the specification of a mutability hint, which /// can be used to vastly improve storage characteristics, but also requires the /// caller to ensure certain additional invariants hold. See /// [`LazyMerkleTree::update_with_mutation`] for details. pub struct LazyMerkleTree { tree: AnyTree, _version: V, } impl LazyMerkleTree where H: Hasher, ::Hash: Hash, Version: VersionMarker, { /// Creates a new, fully lazy (without any dense prefix) tree. #[must_use] pub fn new(depth: usize, empty_value: H::Hash) -> LazyMerkleTree { LazyMerkleTree { tree: AnyTree::new(depth, empty_value), _version: Canonical, } } /// Creates a new tree with a dense prefix of the given depth. #[must_use] pub fn new_with_dense_prefix( depth: usize, prefix_depth: usize, empty_value: &H::Hash, ) -> LazyMerkleTree { LazyMerkleTree { tree: AnyTree::new_with_dense_prefix(depth, prefix_depth, empty_value), _version: Canonical, } } /// Creates a new tree with a dense prefix of the given depth, and with /// initial leaves populated from the given slice. #[must_use] pub fn new_with_dense_prefix_with_initial_values( depth: usize, prefix_depth: usize, empty_value: &H::Hash, initial_values: &[H::Hash], ) -> LazyMerkleTree { LazyMerkleTree { tree: AnyTree::new_with_dense_prefix_with_initial_values( depth, prefix_depth, empty_value, initial_values, ), _version: Canonical, } } /// Creates a new memory mapped file specified by path and creates a tree /// with dense prefix of the given depth with initial values pub fn new_mmapped_with_dense_prefix_with_init_values( depth: usize, prefix_depth: usize, empty_value: &H::Hash, initial_values: &[H::Hash], file_path: &str, ) -> Result, DenseMMapError> { Ok(LazyMerkleTree { tree: AnyTree::new_mmapped_with_dense_prefix_with_init_values( depth, prefix_depth, empty_value, initial_values, file_path, )?, _version: Canonical, }) } /// Attempts to restore previous tree state from memory mapped file /// /// # Errors /// - dense mmap tree restore failed pub fn attempt_dense_mmap_restore( depth: usize, prefix_depth: usize, empty_leaf: &H::Hash, file_path: &str, ) -> Result, DenseMMapError> { Ok(LazyMerkleTree { tree: AnyTree::try_restore_dense_mmap_tree_state( depth, prefix_depth, empty_leaf, file_path, )?, _version: Canonical, }) } /// Returns the depth of the tree. #[must_use] pub const fn depth(&self) -> usize { self.tree.depth() } /// Returns the root of the tree. #[must_use] pub fn root(&self) -> H::Hash { self.tree.root() } /// Sets the value at the given index to the given value. This is fully /// immutable, returning a new tree and leaving the old one unchanged. /// Reuses as much memory as possible, allocating only `depth` nodes. #[must_use] pub fn update(&self, index: usize, value: &H::Hash) -> LazyMerkleTree { LazyMerkleTree { tree: self .tree .update_with_mutation_condition(index, value, false), _version: Derived, } } /// Returns the Merkle proof for the given index. #[must_use] pub fn proof(&self, index: usize) -> InclusionProof { self.tree.proof(index) } /// Verifies the given proof for the given value. #[must_use] pub fn verify(&self, value: H::Hash, proof: &InclusionProof) -> bool { proof.root(value) == self.root() } /// Returns the value at the given index. #[must_use] pub fn get_leaf(&self, index: usize) -> H::Hash { self.tree.get_leaf(index) } /// Returns an iterator over all leaves. pub fn leaves(&self) -> impl Iterator + '_ { // TODO this could be made faster by a custom iterator (0..(1 << self.depth())).map(|i| self.get_leaf(i)) } } impl LazyMerkleTree where H: Hasher, ::Hash: Hash, { /// Sets the value at the given index to the given value. This is a mutable /// operation, that will modify any dense subtrees in place. /// /// This has potential consequences for the soundness of the whole /// structure: /// it has the potential to invalidate some trees that share nodes with /// this one, so if many versions are kept at once, special care must be /// taken when calling this. The only trees that are guaranteed to still be /// valid after this operation, are those that already specify the same /// value at the given index. For example, if a linear history of updates is /// kept in memory, this operation is a good way to "flatten" updates into /// the oldest kept version. /// /// This operation is useful for storage optimizations, as it avoids /// allocating any new memory in dense subtrees. #[must_use] pub fn update_with_mutation(self, index: usize, value: &H::Hash) -> Self { Self { tree: self.tree.update_with_mutation_condition(index, value, true), _version: Canonical, } } /// Gives a `Derived` version of this tree. Useful for initializing /// versioned trees. #[must_use] pub fn derived(&self) -> LazyMerkleTree { LazyMerkleTree { tree: self.tree.clone(), _version: Derived, } } } impl Clone for LazyMerkleTree where H: Hasher, ::Hash: Hash, { fn clone(&self) -> Self { Self { tree: self.tree.clone(), _version: Derived, } } } enum AnyTree { Empty(EmptyTree), Sparse(SparseTree), Dense(DenseTree), DenseMMap(DenseMMapTree), } impl AnyTree where H: Hasher, ::Hash: Hash, { fn new(depth: usize, empty_value: H::Hash) -> Self { Self::Empty(EmptyTree::new(depth, empty_value)) } fn new_with_dense_prefix_with_initial_values( depth: usize, prefix_depth: usize, empty_value: &H::Hash, initial_values: &[H::Hash], ) -> Self { assert!(depth >= prefix_depth); let dense = DenseTree::new_with_values(initial_values, empty_value, prefix_depth); let mut result: Self = dense.into(); let mut current_depth = prefix_depth; while current_depth < depth { result = SparseTree::new(result, EmptyTree::new(current_depth, *empty_value).into()).into(); current_depth += 1; } result } fn new_with_dense_prefix(depth: usize, prefix_depth: usize, empty_value: &H::Hash) -> Self { assert!(depth >= prefix_depth); let mut result: Self = EmptyTree::new(prefix_depth, *empty_value) .alloc_dense() .into(); let mut current_depth = prefix_depth; while current_depth < depth { result = SparseTree::new(result, EmptyTree::new(current_depth, *empty_value).into()).into(); current_depth += 1; } result } fn new_mmapped_with_dense_prefix_with_init_values( depth: usize, prefix_depth: usize, empty_value: &H::Hash, initial_values: &[H::Hash], file_path: &str, ) -> Result { assert!(depth >= prefix_depth); let dense = DenseMMapTree::new_with_values(initial_values, empty_value, prefix_depth, file_path)?; let mut result: Self = dense.into(); let mut current_depth = prefix_depth; while current_depth < depth { result = SparseTree::new(result, EmptyTree::new(current_depth, *empty_value).into()).into(); current_depth += 1; } Ok(result) } fn try_restore_dense_mmap_tree_state( depth: usize, prefix_depth: usize, empty_leaf: &H::Hash, file_path: &str, ) -> Result { let dense_mmap = DenseMMapTree::attempt_restore(empty_leaf, prefix_depth, file_path)?; let mut result: Self = dense_mmap.into(); let mut current_depth = prefix_depth; while current_depth < depth { result = SparseTree::new(result, EmptyTree::new(current_depth, *empty_leaf).into()).into(); current_depth += 1; } Ok(result) } const fn depth(&self) -> usize { match self { Self::Empty(tree) => tree.depth, Self::Sparse(tree) => tree.depth, Self::Dense(tree) => tree.depth, Self::DenseMMap(tree) => tree.depth, } } fn root(&self) -> H::Hash { match self { Self::Empty(tree) => tree.root(), Self::Sparse(tree) => tree.root(), Self::Dense(tree) => tree.root(), Self::DenseMMap(tree) => tree.root(), } } fn proof(&self, index: usize) -> InclusionProof { assert!(index < (1 << self.depth())); let mut path = Vec::with_capacity(self.depth()); match self { Self::Empty(tree) => tree.write_proof(index, &mut path), Self::Sparse(tree) => tree.write_proof(index, &mut path), Self::Dense(tree) => tree.write_proof(index, &mut path), Self::DenseMMap(tree) => tree.write_proof(index, &mut path), } path.reverse(); InclusionProof(path) } fn write_proof(&self, index: usize, path: &mut Vec>) { match self { Self::Empty(tree) => tree.write_proof(index, path), Self::Sparse(tree) => tree.write_proof(index, path), Self::Dense(tree) => tree.write_proof(index, path), Self::DenseMMap(tree) => tree.write_proof(index, path), } } fn update_with_mutation_condition( &self, index: usize, value: &H::Hash, is_mutation_allowed: bool, ) -> Self { match self { Self::Empty(tree) => tree .update_with_mutation_condition(index, value, is_mutation_allowed) .into(), Self::Sparse(tree) => tree .update_with_mutation_condition(index, value, is_mutation_allowed) .into(), Self::Dense(tree) => { tree.update_with_mutation_condition(index, value, is_mutation_allowed) } Self::DenseMMap(tree) => { tree.update_with_mutation_condition(index, value, is_mutation_allowed) } } } fn get_leaf(&self, index: usize) -> H::Hash { match self { Self::Empty(tree) => tree.get_leaf(), Self::Sparse(tree) => tree.get_leaf(index), Self::Dense(tree) => tree.get_leaf(index), Self::DenseMMap(tree) => tree.get_leaf(index), } } } impl Clone for AnyTree where H: Hasher, ::Hash: Hash, { fn clone(&self) -> Self { match self { Self::Empty(t) => t.clone().into(), Self::Sparse(t) => t.clone().into(), Self::Dense(t) => t.clone().into(), Self::DenseMMap(t) => t.clone().into(), } } } impl From> for AnyTree { fn from(tree: EmptyTree) -> Self { Self::Empty(tree) } } impl From> for AnyTree { fn from(tree: SparseTree) -> Self { Self::Sparse(tree) } } impl From> for AnyTree { fn from(tree: DenseTree) -> Self { Self::Dense(tree) } } impl From> for AnyTree { fn from(tree: DenseMMapTree) -> Self { Self::DenseMMap(tree) } } struct EmptyTree { depth: usize, empty_tree_values: Arc>, } impl Clone for EmptyTree { fn clone(&self) -> Self { Self { depth: self.depth, empty_tree_values: self.empty_tree_values.clone(), } } } impl EmptyTree where H: Hasher, ::Hash: Hash, { #[must_use] fn new(depth: usize, empty_value: H::Hash) -> Self { let empty_tree_values = { let values = successors(Some(empty_value), |value| Some(H::hash_node(value, value))) .take(depth + 1) .collect(); Arc::new(values) }; Self { depth, empty_tree_values, } } fn write_proof(&self, index: usize, path: &mut Vec>) { for depth in (1..=self.depth).rev() { let val = self.empty_tree_values[depth - 1]; let branch = if get_turn_at_depth(index, depth) == Turn::Left { Branch::Left(val) } else { Branch::Right(val) }; path.push(branch); } } #[must_use] fn update_with_mutation_condition( &self, index: usize, value: &H::Hash, is_mutation_allowed: bool, ) -> SparseTree { self.alloc_sparse() .update_with_mutation_condition(index, value, is_mutation_allowed) } #[must_use] fn alloc_sparse(&self) -> SparseTree { if self.depth == 0 { SparseTree::new_leaf(self.root()) } else { let next_child: Self = Self { depth: self.depth - 1, empty_tree_values: self.empty_tree_values.clone(), }; SparseTree::new(next_child.clone().into(), next_child.into()) } } #[must_use] fn alloc_dense(&self) -> DenseTree { let values = self .empty_tree_values .iter() .rev() .enumerate() .flat_map(|(depth, value)| repeat_n(value, 1 << depth)); let padded_values = once(&self.empty_tree_values[0]) .chain(values) .cloned() .collect(); DenseTree { depth: self.depth, root_index: 1, storage: Arc::new(Mutex::new(padded_values)), } } #[must_use] fn root(&self) -> H::Hash { self.empty_tree_values[self.depth] } fn get_leaf(&self) -> H::Hash { self.empty_tree_values[0] } } struct Children { left: Arc>, right: Arc>, } impl Clone for Children { fn clone(&self) -> Self { Self { left: self.left.clone(), right: self.right.clone(), } } } struct SparseTree { depth: usize, root: H::Hash, children: Option>, } #[derive(Debug, PartialEq, Eq)] enum Turn { Left, Right, } const fn get_turn_at_depth(index: usize, depth: usize) -> Turn { if index & (1 << (depth - 1)) == 0 { Turn::Left } else { Turn::Right } } const fn clear_turn_at_depth(index: usize, depth: usize) -> usize { index & !(1 << (depth - 1)) } impl From> for SparseTree where H: Hasher, ::Hash: Hash, { fn from(children: Children) -> Self { assert_eq!(children.left.depth(), children.right.depth()); let (depth, root) = { let left = children.left.clone(); let right = children.right.clone(); let depth = left.depth() + 1; let root = H::hash_node(&left.root(), &right.root()); (depth, root) }; Self { depth, root, children: Some(children), } } } impl Clone for SparseTree where H: Hasher, ::Hash: Hash, { fn clone(&self) -> Self { Self { depth: self.depth, root: self.root, children: self.children.clone(), } } } impl SparseTree where H: Hasher, ::Hash: Hash, { fn new(left: AnyTree, right: AnyTree) -> Self { assert_eq!(left.depth(), right.depth()); let children = Children { left: Arc::new(left), right: Arc::new(right), }; children.into() } const fn new_leaf(value: H::Hash) -> Self { Self { depth: 0, root: value, children: None, } } fn write_proof(&self, index: usize, path: &mut Vec>) { if let Some(children) = &self.children { let next_index = clear_turn_at_depth(index, self.depth); if get_turn_at_depth(index, self.depth) == Turn::Left { path.push(Branch::Left(children.right.root())); children.left.write_proof(next_index, path); } else { path.push(Branch::Right(children.left.root())); children.right.write_proof(next_index, path); } } } #[must_use] fn update_with_mutation_condition( &self, index: usize, value: &H::Hash, is_mutation_allowed: bool, ) -> Self { let Some(children) = &self.children else { // no children – this is a leaf return Self::new_leaf(*value); }; let next_index = clear_turn_at_depth(index, self.depth); let children = if get_turn_at_depth(index, self.depth) == Turn::Left { let left = &children.left; let new_left = left.update_with_mutation_condition(next_index, value, is_mutation_allowed); Children { left: Arc::new(new_left), right: children.right.clone(), } } else { let right = &children.right; let new_right = right.update_with_mutation_condition(next_index, value, is_mutation_allowed); Children { left: children.left.clone(), right: Arc::new(new_right), } }; children.into() } fn root(&self) -> H::Hash { self.root } fn get_leaf(&self, index: usize) -> H::Hash { self.children.as_ref().map_or_else( || self.root, |children| { let next_index = clear_turn_at_depth(index, self.depth); if get_turn_at_depth(index, self.depth) == Turn::Left { children.left.get_leaf(next_index) } else { children.right.get_leaf(next_index) } }, ) } } #[derive(Debug)] struct DenseTree { depth: usize, root_index: usize, storage: Arc>>, } impl Clone for DenseTree { fn clone(&self) -> Self { Self { depth: self.depth, root_index: self.root_index, storage: self.storage.clone(), } } } impl DenseTree where H: Hasher, ::Hash: Hash, { fn vec_from_values(values: &[H::Hash], empty_value: &H::Hash, depth: usize) -> Vec { let leaf_count = 1 << depth; let storage_size = 1 << (depth + 1); let mut storage = Vec::with_capacity(storage_size); let empties = repeat_n(empty_value, leaf_count); storage.extend(empties); storage.extend_from_slice(values); if values.len() < leaf_count { let empties = repeat_n(empty_value, leaf_count - values.len()); storage.extend(empties); } // We iterate over mutable layers of the tree for current_depth in (1..=depth).rev() { let (top, child_layer) = storage.split_at_mut(1 << current_depth); let parent_layer = &mut top[(1 << (current_depth - 1))..]; parent_layer .par_iter_mut() .enumerate() .for_each(|(i, value)| { let left = &child_layer[2 * i]; let right = &child_layer[2 * i + 1]; *value = H::hash_node(left, right); }); } storage } fn new_with_values(values: &[H::Hash], empty_value: &H::Hash, depth: usize) -> Self { let storage = Self::vec_from_values(values, empty_value, depth); Self { depth, root_index: 1, storage: Arc::new(Mutex::new(storage)), } } fn with_ref(&self, fun: F) -> R where F: FnOnce(DenseTreeRef) -> R, { let guard = self.storage.lock().expect("lock poisoned, terminating"); let r = DenseTreeRef { depth: self.depth, root_index: self.root_index, storage: &guard, locked_storage: &self.storage, }; fun(r) } fn write_proof(&self, index: usize, path: &mut Vec>) { self.with_ref(|r| r.write_proof(index, path)); } fn get_leaf(&self, index: usize) -> H::Hash { self.with_ref(|r| { let leaf_index_in_dense_tree = index + (self.root_index << self.depth); r.storage[leaf_index_in_dense_tree] }) } fn update_with_mutation_condition( &self, index: usize, value: &H::Hash, is_mutation_allowed: bool, ) -> AnyTree { if is_mutation_allowed { self.update_with_mutation(index, value); self.clone().into() } else { self.with_ref(|r| r.update(index, value)).into() } } fn update_with_mutation(&self, index: usize, value: &H::Hash) { let mut storage = self.storage.lock().expect("lock poisoned, terminating"); let leaf_index_in_dense_tree = index + (self.root_index << self.depth); storage[leaf_index_in_dense_tree] = *value; let mut current = leaf_index_in_dense_tree / 2; while current > 0 { let left = &storage[2 * current]; let right = &storage[2 * current + 1]; storage[current] = H::hash_node(left, right); current /= 2; } } fn root(&self) -> H::Hash { self.storage.lock().unwrap()[self.root_index] } } struct DenseTreeRef<'a, H: Hasher> { depth: usize, root_index: usize, storage: &'a Vec, locked_storage: &'a Arc>>, } impl From> for DenseTree { fn from(value: DenseTreeRef) -> Self { Self { depth: value.depth, root_index: value.root_index, storage: value.locked_storage.clone(), } } } impl From> for AnyTree { fn from(value: DenseTreeRef) -> Self { Self::Dense(value.into()) } } impl DenseTreeRef<'_, H> where H: Hasher, ::Hash: Hash, { fn root(&self) -> H::Hash { self.storage[self.root_index] } const fn left(&self) -> DenseTreeRef<'_, H> { Self { depth: self.depth - 1, root_index: 2 * self.root_index, storage: self.storage, locked_storage: self.locked_storage, } } const fn right(&self) -> DenseTreeRef<'_, H> { Self { depth: self.depth - 1, root_index: 2 * self.root_index + 1, storage: self.storage, locked_storage: self.locked_storage, } } fn write_proof(&self, index: usize, path: &mut Vec>) { if self.depth == 0 { return; } let next_index = clear_turn_at_depth(index, self.depth); if get_turn_at_depth(index, self.depth) == Turn::Left { path.push(Branch::Left(self.right().root())); self.left().write_proof(next_index, path); } else { path.push(Branch::Right(self.left().root())); self.right().write_proof(next_index, path); } } fn update(&self, index: usize, hash: &H::Hash) -> SparseTree { if self.depth == 0 { return SparseTree::new_leaf(*hash); } let next_index = clear_turn_at_depth(index, self.depth); if get_turn_at_depth(index, self.depth) == Turn::Left { let left = self.left(); let new_left = left.update(next_index, hash); let right = self.right(); let new_root = H::hash_node(&new_left.root(), &right.root()); SparseTree { children: Some(Children { left: Arc::new(new_left.into()), right: Arc::new(self.right().into()), }), root: new_root, depth: self.depth, } } else { let right = self.right(); let new_right = right.update(next_index, hash); let left = self.left(); let new_root = H::hash_node(&left.root(), &new_right.root()); SparseTree { children: Some(Children { left: Arc::new(self.left().into()), right: Arc::new(new_right.into()), }), root: new_root, depth: self.depth, } } } } struct DenseMMapTree { depth: usize, root_index: usize, storage: Arc>>, } impl Clone for DenseMMapTree { fn clone(&self) -> Self { Self { depth: self.depth, root_index: self.root_index, storage: self.storage.clone(), } } } impl DenseMMapTree where H: Hasher, ::Hash: Hash, { /// Creates a new `DenseMMapTree` with initial values and depth /// /// # Errors /// /// - returns Err if path buf failed to be created with provided string /// - returns Err if mmap creation fails fn new_with_values( values: &[H::Hash], empty_value: &H::Hash, depth: usize, mmap_file_path: &str, ) -> Result { let path_buf = match PathBuf::from_str(mmap_file_path) { Ok(pb) => pb, Err(_e) => return Err(DenseMMapError::FailedToCreatePathBuf), }; let storage = DenseTree::::vec_from_values(values, empty_value, depth); let mmap = MmapMutWrapper::new_from_storage(path_buf, &storage)?; Ok(Self { depth, root_index: 1, storage: Arc::new(Mutex::new(mmap)), }) } /// Given the file path and tree depth, /// it attempts to restore the memory map /// /// # Errors /// /// - returns Err if path buf creation fails /// - Derives errors from `MmapMutWrapper` /// /// # Panics /// /// - mutex lock is poisoned fn attempt_restore( empty_leaf: &H::Hash, depth: usize, mmap_file_path: &str, ) -> Result { let path_buf = match PathBuf::from_str(mmap_file_path) { Ok(pb) => pb, Err(_e) => return Err(DenseMMapError::FailedToCreatePathBuf), }; let mmap = MmapMutWrapper::attempt_restore(empty_leaf, depth, path_buf)?; Ok(Self { depth, root_index: 1, storage: Arc::new(Mutex::new(mmap)), }) } fn with_ref(&self, fun: F) -> R where F: FnOnce(DenseTreeMMapRef) -> R, { let guard = self.storage.lock().expect("lock poisoned, terminating"); let r = DenseTreeMMapRef { depth: self.depth, root_index: self.root_index, storage: &guard, locked_storage: &self.storage, }; fun(r) } fn write_proof(&self, index: usize, path: &mut Vec>) { self.with_ref(|r| r.write_proof(index, path)); } fn get_leaf(&self, index: usize) -> H::Hash { self.with_ref(|r| { let leaf_index_in_dense_tree = index + (self.root_index << self.depth); r.storage[leaf_index_in_dense_tree] }) } fn update_with_mutation_condition( &self, index: usize, value: &H::Hash, is_mutation_allowed: bool, ) -> AnyTree { if is_mutation_allowed { self.update_with_mutation(index, value); self.clone().into() } else { self.with_ref(|r| r.update(index, value)).into() } } fn update_with_mutation(&self, index: usize, value: &H::Hash) { let mut storage = self.storage.lock().expect("lock poisoned, terminating"); let leaf_index_in_dense_tree = index + (self.root_index << self.depth); storage[leaf_index_in_dense_tree] = *value; let mut current = leaf_index_in_dense_tree / 2; while current > 0 { let left = &storage[2 * current]; let right = &storage[2 * current + 1]; storage[current] = H::hash_node(left, right); current /= 2; } } fn root(&self) -> H::Hash { self.storage.lock().expect("lock poisoned")[self.root_index] } } struct DenseTreeMMapRef<'a, H: Hasher> { depth: usize, root_index: usize, storage: &'a MmapMutWrapper, locked_storage: &'a Arc>>, } impl From> for DenseMMapTree { fn from(value: DenseTreeMMapRef) -> Self { Self { depth: value.depth, root_index: value.root_index, storage: value.locked_storage.clone(), } } } impl From> for AnyTree { fn from(value: DenseTreeMMapRef) -> Self { Self::DenseMMap(value.into()) } } impl DenseTreeMMapRef<'_, H> where H: Hasher, ::Hash: Hash, { fn root(&self) -> H::Hash { self.storage[self.root_index] } const fn left(&self) -> DenseTreeMMapRef<'_, H> { Self { depth: self.depth - 1, root_index: 2 * self.root_index, storage: self.storage, locked_storage: self.locked_storage, } } const fn right(&self) -> DenseTreeMMapRef<'_, H> { Self { depth: self.depth - 1, root_index: 2 * self.root_index + 1, storage: self.storage, locked_storage: self.locked_storage, } } fn write_proof(&self, index: usize, path: &mut Vec>) { if self.depth == 0 { return; } let next_index = clear_turn_at_depth(index, self.depth); if get_turn_at_depth(index, self.depth) == Turn::Left { path.push(Branch::Left(self.right().root())); self.left().write_proof(next_index, path); } else { path.push(Branch::Right(self.left().root())); self.right().write_proof(next_index, path); } } fn update(&self, index: usize, hash: &H::Hash) -> SparseTree { if self.depth == 0 { return SparseTree::new_leaf(*hash); } let next_index = clear_turn_at_depth(index, self.depth); if get_turn_at_depth(index, self.depth) == Turn::Left { let left = self.left(); let new_left = left.update(next_index, hash); let right = self.right(); let new_root = H::hash_node(&new_left.root(), &right.root()); SparseTree { children: Some(Children { left: Arc::new(new_left.into()), right: Arc::new(self.right().into()), }), root: new_root, depth: self.depth, } } else { let right = self.right(); let new_right = right.update(next_index, hash); let left = self.left(); let new_root = H::hash_node(&left.root(), &new_right.root()); SparseTree { children: Some(Children { left: Arc::new(self.left().into()), right: Arc::new(new_right.into()), }), root: new_root, depth: self.depth, } } } } pub struct MmapMutWrapper { mmap: MmapMut, phantom: std::marker::PhantomData, } impl MmapMutWrapper where H: Hasher, ::Hash: Hash, { /// Creates a new memory map backed with file with provided size /// and fills the entire map with initial value /// /// # Errors /// /// - returns Err if file creation has failed /// - returns Err if bytes couldn't be written to file /// /// # Panics /// /// - empty hash value serialization failed /// - file size cannot be set /// - file is too large, possible truncation can occur /// - cannot build memory map pub fn new_from_storage( file_path: PathBuf, storage: &[H::Hash], ) -> Result { // Safety: potential uninitialized padding from `H::Hash` is safe to use if // we're casting back to the same type. let buf = bytemuck::cast_slice(storage); let buf_len = buf.len(); let mut file = match OpenOptions::new() .read(true) .write(true) .create(true) .truncate(true) .open(file_path) { Ok(file) => file, Err(_e) => return Err(DenseMMapError::FileCreationFailed), }; file.set_len(buf_len as u64).expect("cannot set file size"); if file.write_all(buf).is_err() { return Err(DenseMMapError::FileCannotWriteBytes); } let mmap = unsafe { MmapOptions::new(usize::try_from(buf_len as u64).expect("file size truncated")) .expect("cannot create memory map") .with_file(&file, 0) .with_flags(MmapFlags::SHARED) .map_mut() .expect("cannot build memory map") }; Ok(Self { mmap, phantom: std::marker::PhantomData, }) } /// Given the file path and tree depth, /// it attempts to restore the memory map /// /// # Errors /// /// - returns Err if file doesn't exist /// - returns Err if file size doesn't match the expected tree size /// /// # Panics /// /// - cannot get file metadata to check for file length /// - truncated file size when attempting to build memory map /// - cannot build memory map pub fn attempt_restore( empty_leaf: &H::Hash, depth: usize, file_path: PathBuf, ) -> Result { let file = match OpenOptions::new().read(true).write(true).open(file_path) { Ok(file) => file, Err(_e) => return Err(DenseMMapError::FileDoesntExist), }; let size_of_empty_leaf = std::mem::size_of_val(empty_leaf); let expected_file_size = (1 << (depth + 1)) * size_of_empty_leaf as u64; if expected_file_size != file.metadata().expect("cannot get file metadata").len() { return Err(DenseMMapError::FileSizeShouldMatchTree); } let mmap = unsafe { MmapOptions::new( usize::try_from(expected_file_size).expect("expected file size truncated"), ) .expect("cannot create memory map") .with_file(&file, 0) .with_flags(MmapFlags::SHARED) .map_mut() .expect("cannot build memory map") }; Ok(Self { mmap, phantom: std::marker::PhantomData, }) } } impl Deref for MmapMutWrapper where H: Hasher, ::Hash: Hash, { type Target = [H::Hash]; fn deref(&self) -> &Self::Target { bytemuck::cast_slice(self.mmap.as_slice()) } } impl DerefMut for MmapMutWrapper where H: Hasher, ::Hash: Hash, { fn deref_mut(&mut self) -> &mut Self::Target { bytemuck::cast_slice_mut(self.mmap.as_mut_slice()) } } #[derive(Error, Debug)] pub enum DenseMMapError { #[error("file size should match expected tree size")] FileSizeShouldMatchTree, #[error("file doesn't exist")] FileDoesntExist, #[error("failed to create a file")] FileCreationFailed, #[error("cannot write bytes to file")] FileCannotWriteBytes, #[error("failed to create pathbuf")] FailedToCreatePathBuf, } #[cfg(test)] mod tests { use hex_literal::hex; use semaphore_rs_hasher::Hasher; use semaphore_rs_keccak::keccak::Keccak256; use super::*; struct TestHasher; impl Hasher for TestHasher { type Hash = u64; fn hash_node(left: &Self::Hash, right: &Self::Hash) -> Self::Hash { left + 2 * right + 1 } } #[test] fn test_updates_in_sparse() { let tree_1 = LazyMerkleTree::::new(2, 0); assert_eq!(tree_1.root(), 4); let tree_2 = tree_1.update(0, &1); assert_eq!(tree_1.root(), 4); assert_eq!(tree_2.root(), 5); let tree_3 = tree_2.update(2, &2); assert_eq!(tree_1.root(), 4); assert_eq!(tree_2.root(), 5); assert_eq!(tree_3.root(), 9); } #[test] fn test_updates_in_dense() { let tree_1 = LazyMerkleTree::::new_with_dense_prefix(2, 2, &0); assert_eq!(tree_1.root(), 4); let tree_2 = tree_1.update(0, &1); assert_eq!(tree_1.root(), 4); assert_eq!(tree_2.root(), 5); let tree_3 = tree_2.update(2, &2); assert_eq!(tree_1.root(), 4); assert_eq!(tree_2.root(), 5); assert_eq!(tree_3.root(), 9); } #[test] fn test_mutable_updates_in_dense() { let tree = LazyMerkleTree::::new_with_dense_prefix(2, 2, &[0; 32]); let original_tree = LazyMerkleTree { tree: tree.tree.clone(), _version: Derived, }; assert_eq!( original_tree.root(), hex!("b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30") ); let tree = tree.update_with_mutation( 0, &hex!("0000000000000000000000000000000000000000000000000000000000000001"), ); assert_eq!( original_tree.root(), hex!("c1ba1812ff680ce84c1d5b4f1087eeb08147a4d510f3496b2849df3a73f5af95") ); let tree = tree.update_with_mutation( 1, &hex!("0000000000000000000000000000000000000000000000000000000000000002"), ); assert_eq!( original_tree.root(), hex!("893760ec5b5bee236f29e85aef64f17139c3c1b7ff24ce64eb6315fca0f2485b") ); let tree = tree.update_with_mutation( 2, &hex!("0000000000000000000000000000000000000000000000000000000000000003"), ); assert_eq!( original_tree.root(), hex!("222ff5e0b5877792c2bc1670e2ccd0c2c97cd7bb1672a57d598db05092d3d72c") ); let _tree = tree.update_with_mutation( 3, &hex!("0000000000000000000000000000000000000000000000000000000000000004"), ); assert_eq!( original_tree.root(), hex!("a9bb8c3f1f12e9aa903a50c47f314b57610a3ab32f2d463293f58836def38d36") ); } #[test] fn test_mutable_updates_in_dense_with_dense_prefix() { let h0 = [0; 32]; let h1 = hex!("0000000000000000000000000000000000000000000000000000000000000001"); let h2 = hex!("0000000000000000000000000000000000000000000000000000000000000002"); let h3 = hex!("0000000000000000000000000000000000000000000000000000000000000003"); let h4 = hex!("0000000000000000000000000000000000000000000000000000000000000004"); let tree = LazyMerkleTree::::new_with_dense_prefix(2, 1, &[0; 32]); let original_tree = LazyMerkleTree { tree: tree.tree.clone(), _version: Derived, }; assert_eq!( tree.root(), hex!("b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30") ); let t1 = tree.update_with_mutation(0, &h1); assert_eq!( t1.root(), hex!("c1ba1812ff680ce84c1d5b4f1087eeb08147a4d510f3496b2849df3a73f5af95") ); let t2 = t1.update_with_mutation(1, &h2); assert_eq!( t2.root(), hex!("893760ec5b5bee236f29e85aef64f17139c3c1b7ff24ce64eb6315fca0f2485b") ); let t3 = t2.update_with_mutation(2, &h3); assert_eq!( t3.root(), hex!("222ff5e0b5877792c2bc1670e2ccd0c2c97cd7bb1672a57d598db05092d3d72c") ); let t4 = t3.update_with_mutation(3, &h4); assert_eq!( t4.root(), hex!("a9bb8c3f1f12e9aa903a50c47f314b57610a3ab32f2d463293f58836def38d36") ); // first two leaves are in the dense subtree, the rest is sparse, therefore only // first 2 get updated inplace. assert_eq!( original_tree.leaves().collect::>(), vec![h1, h2, h0, h0] ); // all leaves are updated in the properly tracked tree assert_eq!(t4.leaves().collect::>(), vec![h1, h2, h3, h4]); } #[test] fn test_proof() { let tree = LazyMerkleTree::::new_with_dense_prefix(2, 1, &[0; 32]); let tree = tree.update_with_mutation( 0, &hex!("0000000000000000000000000000000000000000000000000000000000000001"), ); let tree = tree.update_with_mutation( 1, &hex!("0000000000000000000000000000000000000000000000000000000000000002"), ); let tree = tree.update_with_mutation( 2, &hex!("0000000000000000000000000000000000000000000000000000000000000003"), ); let tree = tree.update_with_mutation( 3, &hex!("0000000000000000000000000000000000000000000000000000000000000004"), ); let proof = tree.proof(2); assert_eq!(proof.leaf_index(), 2); assert!(tree.verify( hex!("0000000000000000000000000000000000000000000000000000000000000003"), &proof )); assert!(!tree.verify( hex!("0000000000000000000000000000000000000000000000000000000000000001"), &proof )); } #[test] fn test_giant_tree_with_initial_vals() { let h0 = [0; 32]; let h1 = hex!("0000000000000000000000000000000000000000000000000000000000000001"); let h2 = hex!("0000000000000000000000000000000000000000000000000000000000000002"); let h3 = hex!("0000000000000000000000000000000000000000000000000000000000000003"); let h4 = hex!("0000000000000000000000000000000000000000000000000000000000000004"); let updates: Vec<(usize, _)> = vec![(0, h1), (1, h2), (2, h3), (3, h4)]; let mut from_empty = LazyMerkleTree::::new_with_dense_prefix(63, 10, &h0).derived(); for (ix, hash) in &updates { from_empty = from_empty.update(*ix, hash); } let from_initial_vals = LazyMerkleTree::::new_with_dense_prefix_with_initial_values( 63, 10, &h0, &[h1, h2, h3, h4], ) .derived(); assert_eq!(from_empty.root(), from_initial_vals.root()); } #[test] fn test_giant_trees() { let h0 = [0; 32]; let h1 = hex!("0000000000000000000000000000000000000000000000000000000000000001"); let h2 = hex!("0000000000000000000000000000000000000000000000000000000000000002"); let h3 = hex!("0000000000000000000000000000000000000000000000000000000000000003"); let h4 = hex!("0000000000000000000000000000000000000000000000000000000000000004"); let updates: Vec<(usize, _)> = vec![ (1, h1), (2, h2), (1_000_000_000, h3), (1_000_000_000_000, h4), ]; let mut tree = LazyMerkleTree::::new_with_dense_prefix(63, 10, &h0).derived(); for (ix, hash) in &updates { tree = tree.update(*ix, hash); } for (ix, hash) in &updates { let proof = tree.proof(*ix); assert_eq!(proof.root(*hash), tree.root()); } let first_three_leaves = tree.leaves().take(3).collect::>(); assert_eq!(first_three_leaves, vec![h0, h1, h2]); let mut tree = LazyMerkleTree::::new_with_dense_prefix(63, 10, &h0); let original_tree = tree.derived(); for (ix, hash) in &updates { tree = tree.update_with_mutation(*ix, hash); } for (ix, hash) in &updates { let proof = tree.proof(*ix); assert_eq!(proof.root(*hash), tree.root()); } let first_three_leaves = original_tree.leaves().take(3).collect::>(); assert_eq!(first_three_leaves, vec![h0, h1, h2]); let first_three_leaves = tree.leaves().take(3).collect::>(); assert_eq!(first_three_leaves, vec![h0, h1, h2]); } #[test] fn test_dense_mmap_tree() { let h0 = [0; 32]; let h1 = hex!("0000000000000000000000000000000000000000000000000000000000000001"); let h2 = hex!("0000000000000000000000000000000000000000000000000000000000000002"); let h3 = hex!("0000000000000000000000000000000000000000000000000000000000000003"); let h4 = hex!("0000000000000000000000000000000000000000000000000000000000000004"); let h5 = hex!("0000000000000000000000000000000000000000000000000000000000000005"); let h6 = hex!("0000000000000000000000000000000000000000000000000000000000000006"); let h7 = hex!("0000000000000000000000000000000000000000000000000000000000000007"); let h8 = hex!("0000000000000000000000000000000000000000000000000000000000000008"); let initial_values = vec![h1, h2, h3, h4, h5, h6, h7, h8]; let tree: LazyMerkleTree = LazyMerkleTree::::new_mmapped_with_dense_prefix_with_init_values( 3, 3, &h0, &initial_values, "./testfile", ) .unwrap(); let tree_leaves = tree.leaves().collect::>(); assert_eq!(tree_leaves, initial_values); let proof_h1 = tree.proof(0); assert!(tree.verify(h1, &proof_h1)); let proof_h2 = tree.proof(1); assert!(tree.verify(h2, &proof_h2)); // drop a tree, the mmap file should still be there drop(tree); let tree: LazyMerkleTree = LazyMerkleTree::::attempt_dense_mmap_restore(3, 3, &h0, "./testfile") .unwrap(); // repeat asserts again let tree_leaves = tree.leaves().collect::>(); assert_eq!(tree_leaves, initial_values); let proof_h1 = tree.proof(0); assert!(tree.verify(h1, &proof_h1)); let proof_h2 = tree.proof(1); assert!(tree.verify(h2, &proof_h2)); // remove mmap file at the end std::fs::remove_file("./testfile").unwrap(); } } ================================================ FILE: crates/trees/src/lib.rs ================================================ pub mod cascading; pub mod imt; #[cfg(not(target_arch = "wasm32"))] pub mod lazy; pub mod proof; pub use proof::{Branch, InclusionProof}; ================================================ FILE: crates/trees/src/proof.rs ================================================ use std::fmt::Debug; use derive_where::derive_where; use semaphore_rs_hasher::Hasher; use serde::{Deserialize, Serialize}; /// Merkle proof path, bottom to top. #[derive_where(Clone; ::Hash: Clone)] #[derive_where(PartialEq; ::Hash: PartialEq)] #[derive_where(Eq; ::Hash: Eq)] #[derive_where(Debug; ::Hash: Debug)] pub struct InclusionProof(pub Vec>) where H: Hasher; /// Element of a Merkle proof #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Branch { /// Left branch taken, value is the right sibling hash. Left(T), /// Right branch taken, value is the left sibling hash. Right(T), } impl Serialize for InclusionProof where H: Hasher, H::Hash: Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.0.serialize(serializer) } } impl<'de, H> Deserialize<'de> for InclusionProof where H: Hasher, H::Hash: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let branches = Vec::deserialize(deserializer)?; Ok(InclusionProof(branches)) } } impl Branch { /// Get the inner value #[must_use] pub fn into_inner(self) -> T { match self { Self::Left(sibling) => sibling, Self::Right(sibling) => sibling, } } } impl Debug for Branch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Left(arg0) => f.debug_tuple("Left").field(arg0).finish(), Self::Right(arg0) => f.debug_tuple("Right").field(arg0).finish(), } } } ================================================ FILE: crates/trees/tests/equivalent.rs ================================================ use rand::{thread_rng, Rng}; use ruint::aliases::U256; use semaphore_rs_poseidon::Poseidon; use semaphore_rs_trees::cascading::CascadingMerkleTree; use semaphore_rs_trees::imt::MerkleTree; use semaphore_rs_trees::lazy::{Canonical, LazyMerkleTree}; const DEPTH: usize = 20; const DENSE_PREFIX: usize = 16; const NUM_LEAVES: usize = 100; type HashType = Poseidon; const EMPTY_VALUE: U256 = U256::ZERO; #[test] fn equivalent() { let mut lazy: LazyMerkleTree = LazyMerkleTree::::new_with_dense_prefix( DEPTH, DENSE_PREFIX, &EMPTY_VALUE, ); let mut lazy_derived = lazy.derived(); let mut imt: MerkleTree = MerkleTree::new(DEPTH, EMPTY_VALUE); let mut cascading: CascadingMerkleTree = CascadingMerkleTree::new(vec![], DEPTH, &EMPTY_VALUE); assert_eq!(lazy.root(), cascading.root()); assert_eq!(lazy.root(), imt.root()); let mut rng = thread_rng(); let random_leaves = (0..NUM_LEAVES) .map(|_| { let mut limbs = [0u64; 4]; for limb in limbs.iter_mut() { *limb = rng.gen(); } // zero last to fit in field limbs[3] &= 0x0FFFFFFFFFFFFFFF; U256::from_limbs(limbs) }) .collect::>(); for (i, leaf) in random_leaves.iter().enumerate() { lazy_derived = lazy_derived.update(i, leaf); imt.set(i, *leaf); cascading.push(*leaf).unwrap(); } // Lazy & IMT both return the total (i.e. max) number of leaves assert_eq!(lazy.leaves().count(), lazy_derived.leaves().count()); assert_eq!(lazy.leaves().count(), imt.num_leaves()); // Cascading returns the current number of leaves assert_eq!(cascading.num_leaves(), NUM_LEAVES); assert_eq!(lazy_derived.root(), cascading.root()); assert_eq!(lazy_derived.root(), imt.root()); // Mutably update the canonical lazy tree for (i, leaf) in random_leaves.iter().enumerate() { lazy = lazy.update_with_mutation(i, leaf); } assert_eq!(lazy.root(), cascading.root()); for (i, leaf) in random_leaves.iter().enumerate() { let cascading_proof = cascading.proof(i); let lazy_proof = lazy.proof(i); let imt_proof = imt.proof(i).unwrap(); assert_eq!(cascading_proof, lazy_proof); assert_eq!(cascading_proof, imt_proof); assert!(cascading.verify(*leaf, &cascading_proof)); assert!(lazy.verify(*leaf, &cascading_proof)); assert!(imt.verify(*leaf, &cascading_proof)); } } ================================================ FILE: crates/utils/Cargo.toml ================================================ [package] name = "semaphore-rs-utils" version.workspace = true edition.workspace = true homepage.workspace = true license.workspace = true repository.workspace = true authors.workspace = true description.workspace = true keywords.workspace = true categories.workspace = true [dependencies] serde.workspace = true tiny-keccak.workspace = true hex.workspace = true [dev-dependencies] serde_json.workspace = true bincode.workspace = true ================================================ FILE: crates/utils/src/lib.rs ================================================ use core::{ fmt::{Formatter, Result as FmtResult}, str, }; use serde::{ de::{Error as DeError, Visitor}, Deserializer, Serializer, }; use tiny_keccak::{Hasher as _, Keccak}; pub fn keccak256(bytes: &[u8]) -> [u8; 32] { let mut output = [0; 32]; let mut hasher = Keccak::v256(); hasher.update(bytes); hasher.finalize(&mut output); output } pub fn bytes_to_hex(bytes: &[u8; N]) -> [u8; M] { // TODO: Replace `M` with a const expression once it's stable. debug_assert_eq!(M, 2 * N + 2); let mut result = [0u8; M]; result[0] = b'0'; result[1] = b'x'; hex::encode_to_slice(&bytes[..], &mut result[2..]).expect("the buffer is correctly sized"); result } /// Helper to serialize byte arrays pub fn serialize_bytes( serializer: S, bytes: &[u8; N], ) -> Result { // TODO: Replace `M` with a const expression once it's stable. debug_assert_eq!(M, 2 * N + 2); if serializer.is_human_readable() { // Write as a 0x prefixed lower-case hex string let buffer = bytes_to_hex::(bytes); let string = str::from_utf8(&buffer).expect("the buffer is valid UTF-8"); serializer.serialize_str(string) } else { // Write as bytes directly serializer.serialize_bytes(&bytes[..]) } } /// Helper to deserialize byte arrays from hex strings /// /// TODO: How does it handle strings that are to short? pub fn bytes_from_hex(s: &str) -> Result<[u8; N], hex::FromHexError> { let str = trim_hex_prefix(s); let mut result = [0_u8; N]; hex::decode_to_slice(str, &mut result)?; Ok(result) } /// Helper function to remove optionally `0x` prefix from hex strings. fn trim_hex_prefix(str: &str) -> &str { str.trim_start_matches("0x").trim_start_matches("0X") } /// Helper to deserialize byte arrays. pub fn deserialize_bytes<'de, const N: usize, D: Deserializer<'de>>( deserializer: D, ) -> Result<[u8; N], D::Error> { if deserializer.is_human_readable() { struct StrVisitor; impl Visitor<'_> for StrVisitor { type Value = [u8; N]; fn expecting(&self, formatter: &mut Formatter) -> FmtResult { write!(formatter, "a {N} byte hex string") } fn visit_str(self, value: &str) -> Result where E: DeError, { bytes_from_hex(value).map_err(|e| E::custom(format!("Error in hex: {e}"))) } } deserializer.deserialize_str(StrVisitor) } else { struct ByteVisitor; impl Visitor<'_> for ByteVisitor { type Value = [u8; N]; fn expecting(&self, formatter: &mut Formatter) -> FmtResult { write!(formatter, "{N} bytes of binary data") } fn visit_bytes(self, value: &[u8]) -> Result where E: DeError, { if value.len() != N { return Err(E::invalid_length(value.len(), &self)); } let mut result = [0_u8; N]; result.copy_from_slice(value); Ok(result) } } deserializer.deserialize_bytes(ByteVisitor) } } #[cfg(test)] mod test { use super::*; #[test] fn test_serialize_bytes_hex() { let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let mut ser = serde_json::Serializer::new(Vec::new()); serialize_bytes::<16, 34, _>(&mut ser, &bytes).unwrap(); let json = ser.into_inner(); assert_eq!(json, b"\"0x0102030405060708090a0b0c0d0e0f10\""); } #[test] fn test_serialize_bytes_bin() { let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let mut bin: Vec = Vec::new(); { let mut ser = bincode::Serializer::new(&mut bin, bincode::options()); serialize_bytes::<16, 34, _>(&mut ser, &bytes).unwrap(); } // Bincode appears to prefix with a length. assert_eq!( bin, [16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] ); } } ================================================ FILE: cspell.json ================================================ { "version": "0.2", "ignorePaths": [], "dictionaryDefinitions": [], "dictionaries": [], "words": [ "biguint", "chacha", "circom", "groth", "hasher", "keccak", "merkle", "mmaped", "modpow", "mulmod", "proptest", "Repr", "Seedable", "snarkfiles", "thiserror", "zkey" ], "ignoreWords": [], "import": [] } ================================================ FILE: mit-license.md ================================================ # The MIT License (MIT) Copyright © 2021-2023 Worldcoin Foundation 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.** ================================================ FILE: publish_all.sh ================================================ #!/usr/bin/env bash set -e cargo publish -p semaphore-rs-utils cargo publish -p semaphore-rs-ark-circom cargo publish -p semaphore-rs-proof cargo publish -p semaphore-rs-ark-zkey cargo publish -p semaphore-rs-hasher cargo publish -p semaphore-rs-poseidon cargo publish -p semaphore-rs-keccak cargo publish -p semaphore-rs-storage cargo publish -p semaphore-rs-trees cargo publish -p semaphore-rs-depth-config cargo publish -p semaphore-rs-depth-macros cargo publish -p semaphore-rs-witness cargo publish -p semaphore-rs ================================================ FILE: release-plz.toml ================================================ [workspace] # Automatically create GitHub releases for each package. # This creates tags like: semaphore-rs-v0.5.1, semaphore-rs-hasher-v0.5.1, etc. git_release_enable = true git_release_type = "auto" pr_labels = ["release"] release = false # only process specifically defined packages publish = false # only process specifically defined packages changelog_update = false # Publish only specific packages to crates.io. # semaphore-rs-js is excluded — it carries its own independent version (0.3.0 # vs workspace 0.5.0) and is managed separately. # release-plz resolves publish order automatically from the Cargo dependency graph. # semaphore-rs is the "primary" package that owns the root CHANGELOG.md. # changelog_include pulls in changes from every other published crate so that # we get a single, aggregated changelog at the repo root. All other packages # have changelog_update = false to avoid overwriting the same file. [[package]] name = "semaphore-rs" release = true publish = true changelog_update = true changelog_path = "CHANGELOG.md" changelog_include = [ "semaphore-rs-utils", "semaphore-rs-ark-circom", "semaphore-rs-proof", "semaphore-rs-ark-zkey", "semaphore-rs-hasher", "semaphore-rs-poseidon", "semaphore-rs-keccak", "semaphore-rs-storage", "semaphore-rs-trees", "semaphore-rs-depth-config", "semaphore-rs-depth-macros", "semaphore-rs-witness", ] [[package]] name = "semaphore-rs-utils" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-ark-circom" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-proof" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-ark-zkey" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-hasher" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-poseidon" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-keccak" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-storage" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-trees" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-depth-config" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-depth-macros" release = true publish = true changelog_update = false [[package]] name = "semaphore-rs-witness" release = true publish = true changelog_update = false ================================================ FILE: supply-chain/audits.toml ================================================ # cargo-vet audits file [audits] [[trusted.aho-corasick]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-03-28" end = "2024-06-15" [[trusted.byteorder]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-06-09" end = "2024-06-15" [[trusted.csv]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-04-05" end = "2024-06-15" [[trusted.csv-core]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-06-26" end = "2024-06-15" [[trusted.memchr]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-07-07" end = "2024-06-15" [[trusted.proc-macro2]] criteria = "safe-to-deploy" user-id = 3618 # David Tolnay (dtolnay) start = "2019-04-23" end = "2024-06-14" [[trusted.regex]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-02-27" end = "2024-06-15" [[trusted.regex-automata]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-02-25" end = "2024-06-15" [[trusted.regex-syntax]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-03-30" end = "2024-06-15" [[trusted.same-file]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-07-16" end = "2024-06-15" [[trusted.serde]] criteria = "safe-to-deploy" user-id = 3618 # David Tolnay (dtolnay) start = "2019-03-01" end = "2024-06-14" [[trusted.serde_derive]] criteria = "safe-to-deploy" user-id = 3618 # David Tolnay (dtolnay) start = "2019-03-01" end = "2024-06-14" [[trusted.ucd-trie]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-07-21" end = "2024-06-15" [[trusted.walkdir]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-06-09" end = "2024-06-15" [[trusted.winapi-util]] criteria = "safe-to-deploy" user-id = 189 # Andrew Gallant (BurntSushi) start = "2020-01-11" end = "2024-06-15" ================================================ FILE: supply-chain/config.toml ================================================ # cargo-vet config file [cargo-vet] version = "0.9" [imports.bytecodealliance] url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml" [imports.embark] url = "https://raw.githubusercontent.com/EmbarkStudios/rust-ecosystem/main/audits.toml" [imports.google] url = "https://raw.githubusercontent.com/google/supply-chain/main/audits.toml" [imports.isrg] url = "https://raw.githubusercontent.com/divviup/libprio-rs/main/supply-chain/audits.toml" [imports.mozilla] url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" [policy.ark-groth16] audit-as-crates-io = true [policy.semaphore] audit-as-crates-io = false [[exemptions.addr2line]] version = "0.19.0" criteria = "safe-to-deploy" [[exemptions.ahash]] version = "0.7.6" criteria = "safe-to-deploy" [[exemptions.alloy-rlp]] version = "0.3.4" criteria = "safe-to-deploy" [[exemptions.ark-bn254]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-crypto-primitives]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-ec]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-ff]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-ff]] version = "0.4.2" criteria = "safe-to-deploy" [[exemptions.ark-ff-asm]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-ff-asm]] version = "0.4.2" criteria = "safe-to-deploy" [[exemptions.ark-ff-macros]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-ff-macros]] version = "0.4.2" criteria = "safe-to-deploy" [[exemptions.ark-groth16]] version = "0.3.0@git:765817f77a6e14964c6f264d565b18676b11bd59" criteria = "safe-to-deploy" [[exemptions.ark-poly]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-relations]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-serialize]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-serialize]] version = "0.4.2" criteria = "safe-to-deploy" [[exemptions.ark-serialize-derive]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-snark]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-std]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ark-std]] version = "0.4.0" criteria = "safe-to-deploy" [[exemptions.arrayvec]] version = "0.7.4" criteria = "safe-to-deploy" [[exemptions.auto_impl]] version = "1.2.0" criteria = "safe-to-deploy" [[exemptions.backtrace]] version = "0.3.71" criteria = "safe-to-deploy" [[exemptions.base16ct]] version = "0.2.0" criteria = "safe-to-deploy" [[exemptions.base64]] version = "0.21.7" criteria = "safe-to-deploy" [[exemptions.base64ct]] version = "1.6.0" criteria = "safe-to-deploy" [[exemptions.bincode]] version = "1.3.3" criteria = "safe-to-deploy" [[exemptions.bitflags]] version = "1.3.2" criteria = "safe-to-deploy" [[exemptions.bitvec]] version = "1.0.1" criteria = "safe-to-deploy" [[exemptions.blake2]] version = "0.9.2" criteria = "safe-to-deploy" [[exemptions.block-buffer]] version = "0.10.4" criteria = "safe-to-deploy" [[exemptions.byte-slice-cast]] version = "1.2.2" criteria = "safe-to-deploy" [[exemptions.bytecheck]] version = "0.6.12" criteria = "safe-to-deploy" [[exemptions.bytecheck_derive]] version = "0.6.12" criteria = "safe-to-deploy" [[exemptions.bytes]] version = "1.6.0" criteria = "safe-to-deploy" [[exemptions.cast]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.cc]] version = "1.0.90" criteria = "safe-to-deploy" [[exemptions.chrono]] version = "0.4.37" criteria = "safe-to-deploy" [[exemptions.ciborium]] version = "0.2.2" criteria = "safe-to-run" [[exemptions.ciborium-io]] version = "0.2.2" criteria = "safe-to-run" [[exemptions.ciborium-ll]] version = "0.2.2" criteria = "safe-to-run" [[exemptions.clap]] version = "2.34.0" criteria = "safe-to-deploy" [[exemptions.color-eyre]] version = "0.5.11" criteria = "safe-to-deploy" [[exemptions.color-eyre]] version = "0.6.3" criteria = "safe-to-deploy" [[exemptions.color-spantrace]] version = "0.1.6" criteria = "safe-to-deploy" [[exemptions.color-spantrace]] version = "0.2.1" criteria = "safe-to-deploy" [[exemptions.combine]] version = "4.6.6" criteria = "safe-to-deploy" [[exemptions.const-hex]] version = "1.11.3" criteria = "safe-to-deploy" [[exemptions.const-oid]] version = "0.9.6" criteria = "safe-to-deploy" [[exemptions.core-foundation]] version = "0.9.4" criteria = "safe-to-deploy" [[exemptions.corosensei]] version = "0.1.4" criteria = "safe-to-deploy" [[exemptions.cpufeatures]] version = "0.2.12" criteria = "safe-to-deploy" [[exemptions.crc32fast]] version = "1.4.0" criteria = "safe-to-deploy" [[exemptions.criterion]] version = "0.3.6" criteria = "safe-to-deploy" [[exemptions.criterion-plot]] version = "0.4.5" criteria = "safe-to-deploy" [[exemptions.crossbeam-deque]] version = "0.8.5" criteria = "safe-to-deploy" [[exemptions.crossbeam-epoch]] version = "0.9.18" criteria = "safe-to-deploy" [[exemptions.crossbeam-utils]] version = "0.8.8" criteria = "safe-to-deploy" [[exemptions.crypto-bigint]] version = "0.5.5" criteria = "safe-to-deploy" [[exemptions.crypto-mac]] version = "0.8.0" criteria = "safe-to-deploy" [[exemptions.darling]] version = "0.20.8" criteria = "safe-to-deploy" [[exemptions.darling_core]] version = "0.20.8" criteria = "safe-to-deploy" [[exemptions.darling_macro]] version = "0.20.8" criteria = "safe-to-deploy" [[exemptions.dashmap]] version = "5.5.3" criteria = "safe-to-run" [[exemptions.der]] version = "0.7.9" criteria = "safe-to-deploy" [[exemptions.derivative]] version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.digest]] version = "0.9.0" criteria = "safe-to-deploy" [[exemptions.ecdsa]] version = "0.16.9" criteria = "safe-to-deploy" [[exemptions.either]] version = "1.10.0" criteria = "safe-to-deploy" [[exemptions.elliptic-curve]] version = "0.13.8" criteria = "safe-to-deploy" [[exemptions.enum-as-inner]] version = "0.6.0" criteria = "safe-to-deploy" [[exemptions.enum-iterator]] version = "0.7.0" criteria = "safe-to-deploy" [[exemptions.enum-iterator-derive]] version = "0.7.0" criteria = "safe-to-deploy" [[exemptions.enumset]] version = "1.1.3" criteria = "safe-to-deploy" [[exemptions.enumset_derive]] version = "0.8.1" criteria = "safe-to-deploy" [[exemptions.errno]] version = "0.3.8" criteria = "safe-to-deploy" [[exemptions.ethabi]] version = "18.0.0" criteria = "safe-to-deploy" [[exemptions.ethbloom]] version = "0.13.0" criteria = "safe-to-deploy" [[exemptions.ethereum-types]] version = "0.14.1" criteria = "safe-to-deploy" [[exemptions.eyre]] version = "0.6.12" criteria = "safe-to-deploy" [[exemptions.fallible-iterator]] version = "0.2.0" criteria = "safe-to-deploy" [[exemptions.fastrand]] version = "2.0.2" criteria = "safe-to-deploy" [[exemptions.fastrlp]] version = "0.3.1" criteria = "safe-to-deploy" [[exemptions.ff]] version = "0.13.0" criteria = "safe-to-deploy" [[exemptions.fixed-hash]] version = "0.8.0" criteria = "safe-to-deploy" [[exemptions.funty]] version = "2.0.0" criteria = "safe-to-deploy" [[exemptions.futures]] version = "0.3.30" criteria = "safe-to-run" [[exemptions.futures-channel]] version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.futures-core]] version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.futures-executor]] version = "0.3.30" criteria = "safe-to-run" [[exemptions.futures-io]] version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.futures-sink]] version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.futures-task]] version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.futures-util]] version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.generic-array]] version = "0.14.7" criteria = "safe-to-deploy" [[exemptions.getrandom]] version = "0.2.9" criteria = "safe-to-deploy" [[exemptions.gimli]] version = "0.26.2" criteria = "safe-to-deploy" [[exemptions.gimli]] version = "0.28.1" criteria = "safe-to-deploy" [[exemptions.group]] version = "0.13.0" criteria = "safe-to-deploy" [[exemptions.h2]] version = "0.3.26" criteria = "safe-to-deploy" [[exemptions.half]] version = "1.8.3" criteria = "safe-to-deploy" [[exemptions.half]] version = "2.4.1" criteria = "safe-to-run" [[exemptions.hashbrown]] version = "0.11.2" criteria = "safe-to-deploy" [[exemptions.hashbrown]] version = "0.14.3" criteria = "safe-to-deploy" [[exemptions.hermit-abi]] version = "0.1.19" criteria = "safe-to-deploy" [[exemptions.hermit-abi]] version = "0.3.9" criteria = "safe-to-run" [[exemptions.hex-literal]] version = "0.4.1" criteria = "safe-to-deploy" [[exemptions.home]] version = "0.5.9" criteria = "safe-to-deploy" [[exemptions.http]] version = "0.2.12" criteria = "safe-to-deploy" [[exemptions.http-body]] version = "0.4.6" criteria = "safe-to-deploy" [[exemptions.httparse]] version = "1.8.0" criteria = "safe-to-deploy" [[exemptions.hyper]] version = "0.14.28" criteria = "safe-to-deploy" [[exemptions.hyper-tls]] version = "0.5.0" criteria = "safe-to-deploy" [[exemptions.impl-codec]] version = "0.6.0" criteria = "safe-to-deploy" [[exemptions.impl-rlp]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.impl-serde]] version = "0.4.0" criteria = "safe-to-deploy" [[exemptions.impl-trait-for-tuples]] version = "0.2.2" criteria = "safe-to-deploy" [[exemptions.indenter]] version = "0.3.3" criteria = "safe-to-deploy" [[exemptions.indexmap]] version = "1.9.3" criteria = "safe-to-deploy" [[exemptions.indexmap]] version = "2.2.6" criteria = "safe-to-deploy" [[exemptions.ipnet]] version = "2.9.0" criteria = "safe-to-deploy" [[exemptions.is-terminal]] version = "0.4.12" criteria = "safe-to-run" [[exemptions.itertools]] version = "0.10.5" criteria = "safe-to-deploy" [[exemptions.js-sys]] version = "0.3.69" criteria = "safe-to-deploy" [[exemptions.k256]] version = "0.13.3" criteria = "safe-to-deploy" [[exemptions.keccak]] version = "0.1.5" criteria = "safe-to-deploy" [[exemptions.libc]] version = "0.2.146" criteria = "safe-to-deploy" [[exemptions.libloading]] version = "0.7.4" criteria = "safe-to-deploy" [[exemptions.libm]] version = "0.2.8" criteria = "safe-to-deploy" [[exemptions.linux-raw-sys]] version = "0.4.13" criteria = "safe-to-deploy" [[exemptions.lock_api]] version = "0.4.11" criteria = "safe-to-run" [[exemptions.log]] version = "0.4.21" criteria = "safe-to-deploy" [[exemptions.loupe]] version = "0.1.3" criteria = "safe-to-deploy" [[exemptions.loupe-derive]] version = "0.1.3" criteria = "safe-to-deploy" [[exemptions.mach]] version = "0.3.2" criteria = "safe-to-deploy" [[exemptions.memmap2]] version = "0.5.10" criteria = "safe-to-deploy" [[exemptions.memoffset]] version = "0.6.5" criteria = "safe-to-deploy" [[exemptions.mime]] version = "0.3.17" criteria = "safe-to-deploy" [[exemptions.miniz_oxide]] version = "0.7.2" criteria = "safe-to-deploy" [[exemptions.mio]] version = "0.8.11" criteria = "safe-to-deploy" [[exemptions.mmap-rs]] version = "0.6.1" criteria = "safe-to-deploy" [[exemptions.more-asserts]] version = "0.2.2" criteria = "safe-to-deploy" [[exemptions.nix]] version = "0.26.4" criteria = "safe-to-deploy" [[exemptions.num]] version = "0.4.1" criteria = "safe-to-deploy" [[exemptions.num-complex]] version = "0.4.5" criteria = "safe-to-deploy" [[exemptions.num_enum]] version = "0.7.2" criteria = "safe-to-deploy" [[exemptions.num_enum_derive]] version = "0.7.2" criteria = "safe-to-deploy" [[exemptions.object]] version = "0.28.4" criteria = "safe-to-deploy" [[exemptions.object]] version = "0.32.2" criteria = "safe-to-deploy" [[exemptions.once_cell]] version = "1.17.2" criteria = "safe-to-deploy" [[exemptions.oorandom]] version = "11.1.3" criteria = "safe-to-deploy" [[exemptions.opaque-debug]] version = "0.3.1" criteria = "safe-to-deploy" [[exemptions.open-fastrlp]] version = "0.1.4" criteria = "safe-to-deploy" [[exemptions.open-fastrlp-derive]] version = "0.1.1" criteria = "safe-to-deploy" [[exemptions.owo-colors]] version = "1.3.0" criteria = "safe-to-deploy" [[exemptions.owo-colors]] version = "3.5.0" criteria = "safe-to-deploy" [[exemptions.parity-scale-codec]] version = "3.6.9" criteria = "safe-to-deploy" [[exemptions.parity-scale-codec-derive]] version = "3.6.9" criteria = "safe-to-deploy" [[exemptions.parking_lot_core]] version = "0.9.9" criteria = "safe-to-run" [[exemptions.paste]] version = "1.0.14" criteria = "safe-to-deploy" [[exemptions.pest]] version = "2.7.9" criteria = "safe-to-deploy" [[exemptions.pin-project-lite]] version = "0.2.14" criteria = "safe-to-deploy" [[exemptions.pkcs8]] version = "0.10.2" criteria = "safe-to-deploy" [[exemptions.pkg-config]] version = "0.3.30" criteria = "safe-to-deploy" [[exemptions.plotters]] version = "0.3.5" criteria = "safe-to-deploy" [[exemptions.plotters-backend]] version = "0.3.5" criteria = "safe-to-deploy" [[exemptions.plotters-svg]] version = "0.3.5" criteria = "safe-to-deploy" [[exemptions.ppv-lite86]] version = "0.2.17" criteria = "safe-to-deploy" [[exemptions.primitive-types]] version = "0.12.2" criteria = "safe-to-deploy" [[exemptions.proc-macro-crate]] version = "1.3.1" criteria = "safe-to-deploy" [[exemptions.proc-macro-crate]] version = "2.0.0" criteria = "safe-to-deploy" [[exemptions.proc-macro-crate]] version = "3.1.0" criteria = "safe-to-deploy" [[exemptions.proc-macro-error]] version = "1.0.4" criteria = "safe-to-deploy" [[exemptions.proptest]] version = "1.4.0" criteria = "safe-to-deploy" [[exemptions.ptr_meta]] version = "0.1.4" criteria = "safe-to-deploy" [[exemptions.ptr_meta_derive]] version = "0.1.4" criteria = "safe-to-deploy" [[exemptions.quick-error]] version = "1.2.3" criteria = "safe-to-deploy" [[exemptions.radium]] version = "0.7.0" criteria = "safe-to-deploy" [[exemptions.rand]] version = "0.8.5" criteria = "safe-to-deploy" [[exemptions.rand_xorshift]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.redox_syscall]] version = "0.4.1" criteria = "safe-to-run" [[exemptions.regalloc]] version = "0.0.34" criteria = "safe-to-deploy" [[exemptions.region]] version = "3.0.2" criteria = "safe-to-deploy" [[exemptions.rend]] version = "0.4.2" criteria = "safe-to-deploy" [[exemptions.reqwest]] version = "0.11.27" criteria = "safe-to-deploy" [[exemptions.rfc6979]] version = "0.4.0" criteria = "safe-to-deploy" [[exemptions.rkyv]] version = "0.7.44" criteria = "safe-to-deploy" [[exemptions.rkyv_derive]] version = "0.7.44" criteria = "safe-to-deploy" [[exemptions.rlp]] version = "0.5.2" criteria = "safe-to-deploy" [[exemptions.rlp-derive]] version = "0.1.0" criteria = "safe-to-deploy" [[exemptions.ruint]] version = "1.12.1" criteria = "safe-to-deploy" [[exemptions.ruint-macro]] version = "1.2.0" criteria = "safe-to-deploy" [[exemptions.rustc-demangle]] version = "0.1.23" criteria = "safe-to-deploy" [[exemptions.rustc-hex]] version = "2.1.0" criteria = "safe-to-deploy" [[exemptions.rustc_version]] version = "0.3.3" criteria = "safe-to-deploy" [[exemptions.rustc_version]] version = "0.4.0" criteria = "safe-to-deploy" [[exemptions.rustix]] version = "0.38.32" criteria = "safe-to-deploy" [[exemptions.rustls-pemfile]] version = "1.0.4" criteria = "safe-to-deploy" [[exemptions.rusty-fork]] version = "0.3.0" criteria = "safe-to-deploy" [[exemptions.ryu]] version = "1.0.17" criteria = "safe-to-deploy" [[exemptions.scale-info]] version = "2.11.1" criteria = "safe-to-deploy" [[exemptions.scale-info-derive]] version = "2.11.1" criteria = "safe-to-deploy" [[exemptions.schannel]] version = "0.1.23" criteria = "safe-to-deploy" [[exemptions.scopeguard]] version = "1.2.0" criteria = "safe-to-deploy" [[exemptions.seahash]] version = "4.1.0" criteria = "safe-to-deploy" [[exemptions.sec1]] version = "0.7.3" criteria = "safe-to-deploy" [[exemptions.security-framework]] version = "2.10.0" criteria = "safe-to-deploy" [[exemptions.security-framework-sys]] version = "2.10.0" criteria = "safe-to-deploy" [[exemptions.semver]] version = "0.11.0" criteria = "safe-to-deploy" [[exemptions.semver]] version = "1.0.22" criteria = "safe-to-deploy" [[exemptions.semver-parser]] version = "0.10.2" criteria = "safe-to-deploy" [[exemptions.serde_bytes]] version = "0.11.14" criteria = "safe-to-deploy" [[exemptions.serde_json]] version = "1.0.115" criteria = "safe-to-deploy" [[exemptions.serde_urlencoded]] version = "0.7.1" criteria = "safe-to-deploy" [[exemptions.serial_test]] version = "3.0.0" criteria = "safe-to-run" [[exemptions.serial_test_derive]] version = "3.0.0" criteria = "safe-to-run" [[exemptions.sha2]] version = "0.10.8" criteria = "safe-to-deploy" [[exemptions.sharded-slab]] version = "0.1.7" criteria = "safe-to-deploy" [[exemptions.signature]] version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.simdutf8]] version = "0.1.4" criteria = "safe-to-deploy" [[exemptions.slab]] version = "0.4.9" criteria = "safe-to-deploy" [[exemptions.smallvec]] version = "1.13.2" criteria = "safe-to-deploy" [[exemptions.socket2]] version = "0.5.6" criteria = "safe-to-deploy" [[exemptions.spki]] version = "0.7.3" criteria = "safe-to-deploy" [[exemptions.stable_deref_trait]] version = "1.2.0" criteria = "safe-to-deploy" [[exemptions.strum]] version = "0.26.2" criteria = "safe-to-deploy" [[exemptions.strum_macros]] version = "0.26.2" criteria = "safe-to-deploy" [[exemptions.syn]] version = "1.0.109" criteria = "safe-to-deploy" [[exemptions.syn]] version = "2.0.58" criteria = "safe-to-deploy" [[exemptions.sync_wrapper]] version = "0.1.2" criteria = "safe-to-deploy" [[exemptions.sysctl]] version = "0.5.5" criteria = "safe-to-deploy" [[exemptions.system-configuration]] version = "0.5.1" criteria = "safe-to-deploy" [[exemptions.system-configuration-sys]] version = "0.5.0" criteria = "safe-to-deploy" [[exemptions.target-lexicon]] version = "0.12.14" criteria = "safe-to-deploy" [[exemptions.tempfile]] version = "3.10.1" criteria = "safe-to-deploy" [[exemptions.textwrap]] version = "0.11.0" criteria = "safe-to-deploy" [[exemptions.thiserror]] version = "1.0.58" criteria = "safe-to-deploy" [[exemptions.thiserror-impl]] version = "1.0.58" criteria = "safe-to-deploy" [[exemptions.thread_local]] version = "1.1.8" criteria = "safe-to-deploy" [[exemptions.tiny-keccak]] version = "2.0.2" criteria = "safe-to-deploy" [[exemptions.tinytemplate]] version = "1.2.1" criteria = "safe-to-deploy" [[exemptions.tinyvec_macros]] version = "0.1.1" criteria = "safe-to-deploy" [[exemptions.tokio]] version = "1.37.0" criteria = "safe-to-deploy" [[exemptions.tokio-util]] version = "0.7.10" criteria = "safe-to-deploy" [[exemptions.toml_datetime]] version = "0.6.5" criteria = "safe-to-deploy" [[exemptions.toml_edit]] version = "0.19.15" criteria = "safe-to-deploy" [[exemptions.toml_edit]] version = "0.20.7" criteria = "safe-to-deploy" [[exemptions.toml_edit]] version = "0.21.1" criteria = "safe-to-deploy" [[exemptions.tower-service]] version = "0.3.2" criteria = "safe-to-deploy" [[exemptions.tracing]] version = "0.1.40" criteria = "safe-to-deploy" [[exemptions.tracing-attributes]] version = "0.1.27" criteria = "safe-to-deploy" [[exemptions.tracing-core]] version = "0.1.32" criteria = "safe-to-deploy" [[exemptions.tracing-error]] version = "0.1.2" criteria = "safe-to-deploy" [[exemptions.tracing-error]] version = "0.2.0" criteria = "safe-to-deploy" [[exemptions.tracing-log]] version = "0.2.0" criteria = "safe-to-deploy" [[exemptions.tracing-subscriber]] version = "0.2.25" criteria = "safe-to-deploy" [[exemptions.tracing-subscriber]] version = "0.3.18" criteria = "safe-to-deploy" [[exemptions.tracing-test]] version = "0.2.4" criteria = "safe-to-run" [[exemptions.tracing-test-macro]] version = "0.2.4" criteria = "safe-to-run" [[exemptions.try-lock]] version = "0.2.5" criteria = "safe-to-deploy" [[exemptions.typenum]] version = "1.17.0" criteria = "safe-to-deploy" [[exemptions.uint]] version = "0.9.5" criteria = "safe-to-deploy" [[exemptions.unarray]] version = "0.1.4" criteria = "safe-to-deploy" [[exemptions.uuid]] version = "1.8.0" criteria = "safe-to-deploy" [[exemptions.wait-timeout]] version = "0.2.0" criteria = "safe-to-deploy" [[exemptions.want]] version = "0.3.1" criteria = "safe-to-deploy" [[exemptions.wasi]] version = "0.11.0+wasi-snapshot-preview1" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen]] version = "0.2.92" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-backend]] version = "0.2.92" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-futures]] version = "0.4.42" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-macro]] version = "0.2.92" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-macro-support]] version = "0.2.92" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-shared]] version = "0.2.92" criteria = "safe-to-deploy" [[exemptions.wasm-encoder]] version = "0.202.0" criteria = "safe-to-deploy" [[exemptions.wasmparser]] version = "0.83.0" criteria = "safe-to-deploy" [[exemptions.wast]] version = "202.0.0" criteria = "safe-to-deploy" [[exemptions.wat]] version = "1.202.0" criteria = "safe-to-deploy" [[exemptions.web-sys]] version = "0.3.69" criteria = "safe-to-deploy" [[exemptions.which]] version = "4.4.2" criteria = "safe-to-deploy" [[exemptions.widestring]] version = "1.0.2" criteria = "safe-to-deploy" [[exemptions.winapi]] version = "0.3.9" criteria = "safe-to-deploy" [[exemptions.winapi-i686-pc-windows-gnu]] version = "0.4.0" criteria = "safe-to-deploy" [[exemptions.winapi-x86_64-pc-windows-gnu]] version = "0.4.0" criteria = "safe-to-deploy" [[exemptions.windows]] version = "0.48.0" criteria = "safe-to-deploy" [[exemptions.windows-sys]] version = "0.33.0" criteria = "safe-to-deploy" [[exemptions.windows-sys]] version = "0.48.0" criteria = "safe-to-deploy" [[exemptions.windows-sys]] version = "0.52.0" criteria = "safe-to-deploy" [[exemptions.windows-targets]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows-targets]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.windows_aarch64_gnullvm]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows_aarch64_gnullvm]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.windows_aarch64_msvc]] version = "0.33.0" criteria = "safe-to-deploy" [[exemptions.windows_aarch64_msvc]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows_aarch64_msvc]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.windows_i686_gnu]] version = "0.33.0" criteria = "safe-to-deploy" [[exemptions.windows_i686_gnu]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows_i686_gnu]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.windows_i686_msvc]] version = "0.33.0" criteria = "safe-to-deploy" [[exemptions.windows_i686_msvc]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows_i686_msvc]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_gnu]] version = "0.33.0" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_gnu]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_gnu]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_gnullvm]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_gnullvm]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_msvc]] version = "0.33.0" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_msvc]] version = "0.48.5" criteria = "safe-to-deploy" [[exemptions.windows_x86_64_msvc]] version = "0.52.4" criteria = "safe-to-deploy" [[exemptions.winnow]] version = "0.5.40" criteria = "safe-to-deploy" [[exemptions.winreg]] version = "0.50.0" criteria = "safe-to-deploy" [[exemptions.wyz]] version = "0.5.1" criteria = "safe-to-deploy" [[exemptions.zeroize]] version = "1.7.0" criteria = "safe-to-deploy" [[exemptions.zeroize_derive]] version = "1.4.2" criteria = "safe-to-deploy"