Compare commits

..

21 Commits

Author SHA1 Message Date
Sydhds
4077357e3f Add merkle tree glossary + mermaid graph example (#298) 2025-04-18 15:11:55 +02:00
Sydhds
84d9799d09 Add poseidon hash benchmark + optim (#300) 2025-04-18 15:08:43 +02:00
Sydhds
c576af8e62 fix(tree): fix OptimalMerkleTree set_range & override_range performance issue (#295) 2025-04-16 17:40:58 +02:00
Sydhds
81470b9678 Add poseidon hash unit test (against ref values) (#299) 2025-04-16 16:23:58 +02:00
Vinh Trịnh
9d4198c205 feat(rln-wasm): bring back wasm support for zerokit
# Bring Back WebAssembly Support for ZeroKit

- Update minor versions of all dependencies.
- Update documentation to reflect these changes.
- ~~Vendor `wasmer` v4.4.0 in [my git
repository](https://github.com/vinhtc27/wasmer) for `ark-circom`
v0.5.0.~~
- Resolve `wasm-pack` build failures (`os error 2`) caused by a Node.js
version mismatch.
- Restore the previous CI pipeline for the `rln-wasm` feature and update
to the stable toolchain.
- ~~Use `ark-circom` with the `wasm` feature for WebAssembly
compatibility and the `rln.wasm` file for witness calculation.~~
- ~~Fix dependency issues related to `ark-circom` v0.5.0, which
currently uses `wasmer` v4.4.0 and is affected by this
[issue](https://github.com/rust-lang/rust/issues/91632#issuecomment-1477914703).~~
- Install WABT with `brew` and `apt-get` instead of cloning to fix
`wasm-strip not found` issue in the CI workflow.
- Install `wasm-pack` with `curl` instead of using `wasm-pack-action` to
fix parse exception error in the CI workflow.
- Use the `.wasm` file with JS bindings for witness calculation, which
is generated from [`iden3/circom`](https://github.com/iden3/circom)
during circuit compilation. This allows witness computation outside RLN
instance.
- Refactor the `rln` module by moving circuit-related files to the
`src/circuit` folder for better organization.
- Remove `ark-circom` and `wasmer` by cloning the
[CircomReduction](3c95ed98e2/src/circom/qap.rs (L12))
struct and the
[read_zkey](3c95ed98e2/src/zkey.rs (L53))
function into the `rln` module, which reduces the repository's build
size and speeds up compilation time and the CI workflow duration.
- These change also address
[#282](https://github.com/vacp2p/zerokit/issues/282) by removing
`wasmer` and `wasmer-wasix`, which lack x32 system support.
- Benchmark `rln-wasm` with `wasm_bindgen_test`, covering RLN instance
creation, key generation, witness calculation, proving, and
verification. Also, add them to `v0.6.1` in
[benchmark-v0.6.1](https://github.com/vacp2p/zerokit/tree/benchmark-v0.6.1)
for comparison.
- Add `arkzkey` feature for rln-wasm, including tests, benchmarks, CI
workflow updates, and related documentation.
- Benchmark rln-wasm in the browser using HTML, covering initialization,
RLN instance creation, proving, and verification; fork to the
`benchmark-v0.7.0` branch for later use
[here](https://github.com/vacp2p/zerokit/tree/benchmark-v0.7.0).
- Fix clippy error: "this `repeat().take()` can be written more
concisely" on CI workflow for `utils` module.
([error](https://github.com/vacp2p/zerokit/actions/runs/14258579070/job/39965568013))
- Update Makefile.toml to be able to run `make build`, `make test`, and
`make bench` from root and inside each modules.
2025-04-08 13:37:18 +07:00
markoburcul
c60e0c33fc nix: add flake and derivation for android-arm64 arch
Referenced issue: https://github.com/waku-org/nwaku/issues/3232
2025-04-04 10:50:26 +02:00
Ekaterina Broslavskaya
ba467d370c chore(rln): update dependencies and refactor code for compatibility (#291)
While publishing the release on crate io it turned out that we can't use
libraries without a version as it was for arc-circom.

During the upgrade to the new version it was also discovered that it is
possible to speed up input preparation for witness calculator by 4 times
by switching from bigint to Fr:


![image](https://github.com/user-attachments/assets/53962387-308b-4aae-8af2-dbd0d3f62369)

it was also checked that it is also possible to use iden3 as a
sub-module instead of copying code, but benchmarks showed that the new
iden3 version with u256 calculations and subsequent conversion of the
result to Fr is slower than the current implementation:


![image](https://github.com/user-attachments/assets/f950f089-b66a-4a13-a86f-f391caf32b4f)

----

- Updated dependencies to their latest versions, including ark-ff,
ark-bn254, ark-std, and others to 0.5.0.
- Refactored circuit and iden3calc modules to use Fr instead of BigInt
for better type consistency.
- Improved utility functions for type conversions between Fr and U256.
- Adjusted Cargo.toml files for rln and utils to reflect new dependency
versions and features.
- Enhanced documentation and comments for clarity on changes made.

This update ensures compatibility with the latest versions of the Ark
framework and improves overall code quality.
2025-03-14 20:56:00 +07:00
Ekaterina Broslavskaya
ffd5851d7d release(rln): update version to 0.7.0 (#290) 2025-03-14 13:24:32 +07:00
Vinh Trịnh
759d312680 fix(rln): resolve circuit parameter mismatch for customizable compilation (#288)
Trying out ideas to solve problems from this [Discord
conversation](https://discord.com/channels/864066763682218004/1344911294092017716):

- Completely remove the verification key file and use `vk_from_raw` or
derive it from ZK.
- To handle tree height mismatches, improve the documentation to provide
users with clear guidelines, preventing the generation of an incorrect
`graph.bin` file.
- Remove compressed `ark-zkey` usage and conduct benchmarking and
testing.
- Add documentation for the specific Circom compiler version 2.1.0.
- Update documentation on `rln.circom` parameters `N` and `M`.

---------

Co-authored-by: seemenkina <seemenkina@gmail.com>
2025-03-14 13:17:52 +07:00
Ekaterina Broslavskaya
fb0ffd74a3 docs: prepare readme and documentation for new release (#284)
Update documentation for rln, utils and zerokit itself

---------

Co-authored-by: ksr <kaiserd@users.noreply.github.com>
Co-authored-by: kaiserd <1684595+kaiserd@users.noreply.github.com>
2025-03-10 19:47:49 +07:00
Vinh Trịnh
9d8372be39 fix(rln): review and refine utility functions in rln (#287) 2025-03-06 18:04:57 +07:00
Ekaterina Broslavskaya
de9c0d5072 feat(rln): Add custom iden3 graph data support for RLN (#286)
Brought back functionality that was removed during the migration to
iden3

- Modify circuit module to include graph data loading and calculation
- Update RLN struct to store graph data
- Adjust proof generation and FFI methods to use graph data
- Update benchmarks and tests to use sample size instead of measurement
time
- Add new methods for graph data retrieval and handling
2025-02-27 11:45:41 +07:00
Ekaterina Broslavskaya
5c60ec7cce fix(build): pin ark-circom version and solve local building problem (#285)
ark-circom update their branch to new dependency version and we can't
support it right now. So I pin it on previous commit

also during the local testing I find out that cargo make doesn't work
correctly with new rln-cli examples (it calls "cargo" "build"
"--all-features") and it's wrong behaviour
2025-02-25 18:11:25 +07:00
Vinh Trịnh
8793965650 fix(rln-cli): improve configuration handling, add relay and stateless example (#280)
Fix Configuration Handling:
+ Document the expected format of the configuration file.
+ Provide an example configuration file in rln-cli to guide users.
+ Ensure that the CLI provides useful error messages if the
configuration is missing or incorrect.

Implement Relay and Stateless RLN Examples
+ Add Relay example to illustrate RLN’s practical use case.
+ Add Stateless RLN example, where an externally built Merkle tree
provides paths and roots for RLN stateless feature.
2025-02-25 12:04:05 +07:00
Ekaterina Broslavskaya
1930ca1610 ci: fixed problem with nightly build after updating dependencies (#283)
Return the exception settings for run-cli. Also remove the build for the
x32 architecture, because the new version of ark-circom depends on
wasmer-wasix, which cannot be built on the x32 architecture.

See this issue for more details:
https://github.com/vacp2p/zerokit/issues/282
2025-02-21 13:36:53 +07:00
Ekaterina Broslavskaya
4b4169d7a7 chore(release): v0.6.1 (#281)
Changes to not lose the changes from the minor tag for the latest
working version with wasm
2025-02-20 21:39:17 +07:00
Ekaterina Broslavskaya
8a3e33be41 chore: Update dependencies to latest versions (#276)
* chore: Update dependencies to latest versions

Upgrade various dependencies across multiple crates to their latest compatible versions

* chore: Update Cargo.toml dependency versions to latest compatible releases

Upgrade serde and serde_json dependency versions using more flexible version specifiers

* chore: Update Cargo dependencies to latest compatible versions

Upgrade Ark and Wasmer dependencies to their latest minor versions, including:
- ark-circom from 0.1.0 to 0.5.0
- ark-zkey from 0.1.0 to 0.1.2
- wasmer from 2.3.0 to 2.3.x
- num-traits to 0.2.19

Also update Makefile to improve cross-platform build process for wabt

* chore: Update Ark dependencies to latest patch versions

* chore: Revert ark-zkey dependency and update Wasmer to 4.4.0

Remove ark-zkey optional dependency and update Wasmer to version 4.4.0 across multiple crates. Modify circuit module to include necessary structs and functions previously provided by ark-zkey.

* chore: Update zkey path to use uncompressed arkzkey file

* Remove rln-wasm package and related configurations

Clean up project structure by removing the rln-wasm package, associated GitHub workflows, and WASM-specific configurations. This includes:
- Removing rln-wasm directory and its contents
- Updating Cargo.toml workspace configuration
- Removing WASM-specific build and test configurations from CI workflows
- Removing WASM-related dependencies and features from RLN crate

* Fix CI workflow for default feature testing

* rollback to default

* Fix CI workflow to support default feature testing

* Fix CI workflow syntax for feature testing condition

* Update README.md with clearer testing instructions for RLN module
2025-02-07 17:44:19 +07:00
Aleksei Vambol
7bb2444ba4 Update "Acknowledgments" in README.md 2025-01-30 19:25:30 +02:00
withbest
00f8d039a8 chore: remove redundant words in comment (#274)
Signed-off-by: withbest <seekseat@outlook.com.>
2025-01-13 20:56:27 +07:00
Aleksei Vambol
e39f156fff Replace the ark-zkey witness calculator with the one of iden3 (#273)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Delete rln/resources/tree_height_20/rln.wasm

* Changes in accordance with the results of the first round of reviewing.

* Formatting

* Acknowledgements and a readme fix

* Minor change: Vec memory allocation
2024-12-25 12:38:35 +02:00
Ekaterina Broslavskaya
8b04930583 Freeze rust version (#272) 2024-12-04 21:50:52 +07:00
74 changed files with 6237 additions and 3847 deletions

5
.github/labels.yml vendored
View File

@@ -90,11 +90,6 @@
description: go-waku-productionization track (Waku Product)
color: 9DEA79
# Tracks within zk-WASM project
- name: track:kickoff
description: Kickoff track (zk-WASM)
color: 06B6C8
# Tracks within RAD project
- name: track:waku-specs
description: Waku specs track (RAD)

View File

@@ -71,34 +71,48 @@ jobs:
run: make installdeps
- name: cargo-make test
run: |
cargo make test_${{ matrix.feature }} --release
if [ ${{ matrix.feature }} == default ]; then
cargo make test --release
else
cargo make test_${{ matrix.feature }} --release
fi
working-directory: ${{ matrix.crate }}
rln-wasm:
strategy:
matrix:
platform: [ ubuntu-latest, macos-latest ]
feature: [ "default", "arkzkey" ]
runs-on: ${{ matrix.platform }}
timeout-minutes: 60
name: test - rln-wasm - ${{ matrix.platform }}
name: test - rln-wasm - ${{ matrix.platform }} - ${{ matrix.feature }}
steps:
- uses: actions/checkout@v3
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
# TODO: Update to stable once wasmer supports it
toolchain: 1.82.0
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- name: Install Dependencies
run: make installdeps
- name: Install wasm-pack
uses: jetli/wasm-pack-action@v0.3.0
- run: cargo make build
- name: cargo-make build
run: |
if [ ${{ matrix.feature }} == default ]; then
cargo make build
else
cargo make build_${{ matrix.feature }}
fi
working-directory: rln-wasm
- run: cargo make test --release
- name: cargo-make test
run: |
if [ ${{ matrix.feature }} == default ]; then
cargo make test --release
else
cargo make test_${{ matrix.feature }} --release
fi
working-directory: rln-wasm
lint:
@@ -106,7 +120,7 @@ jobs:
matrix:
# we run lint tests only on ubuntu
platform: [ ubuntu-latest ]
crate: [ rln, utils ]
crate: [ rln, rln-wasm, utils ]
runs-on: ${{ matrix.platform }}
timeout-minutes: 60
@@ -131,11 +145,8 @@ jobs:
- name: cargo clippy
if: success() || failure()
run: |
cargo clippy --release -- -D warnings
cargo clippy --release
working-directory: ${{ matrix.crate }}
# We skip clippy on rln-wasm, since wasm target is managed by cargo make
# Currently not treating warnings as error, too noisy
# -- -D warnings
benchmark-utils:
# run only in pull requests

View File

@@ -12,7 +12,7 @@ jobs:
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- i686-unknown-linux-gnu
# - i686-unknown-linux-gnu
include:
- feature: stateless
cargo_args: --exclude rln-cli
@@ -33,7 +33,7 @@ jobs:
run: make installdeps
- name: cross build
run: |
cross build --release --target ${{ matrix.target }} --features ${{ matrix.feature }} --workspace --exclude rln-wasm ${{ matrix.cargo_args }}
cross build --release --target ${{ matrix.target }} --features ${{ matrix.feature }} --workspace ${{ matrix.cargo_args }}
mkdir release
cp target/${{ matrix.target }}/release/librln* release/
tar -czvf ${{ matrix.target }}-${{ matrix.feature }}-rln.tar.gz release/
@@ -72,7 +72,7 @@ jobs:
run: make installdeps
- name: cross build
run: |
cross build --release --target ${{ matrix.target }} --features ${{ matrix.feature }} --workspace --exclude rln-wasm ${{ matrix.cargo_args }}
cross build --release --target ${{ matrix.target }} --features ${{ matrix.feature }} --workspace ${{ matrix.cargo_args }}
mkdir release
cp target/${{ matrix.target }}/release/librln* release/
tar -czvf ${{ matrix.target }}-${{ matrix.feature }}-rln.tar.gz release/
@@ -94,14 +94,11 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
# TODO: Update to stable once wasmer supports it
toolchain: 1.82.0
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- name: Install dependencies
run: make installdeps
- name: Install wasm-pack
uses: jetli/wasm-pack-action@v0.3.0
- name: cross make build
run: |
cross make build

5
.gitignore vendored
View File

@@ -3,12 +3,15 @@
*.log
tmp/
rln/pmtree_db
rln-cli/database
# Generated by Cargo
# will have compiled files and executables
debug/
target/
wabt/
# Generated by Nix
result/
# These are backup files generated by rustfmt
**/*.rs.bk

View File

@@ -1,3 +1,5 @@
# CHANGE LOG
## 2023-02-28 v0.2
This release contains:
@@ -10,7 +12,6 @@ This release contains:
- Dual License under Apache 2.0 and MIT
- RLN compiles as a static library, which can be consumed through a C FFI
## 2022-09-19 v0.1
Initial beta release.

2841
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[workspace]
members = ["rln", "rln-cli", "rln-wasm", "utils"]
default-members = ["rln", "rln-cli", "utils"]
default-members = ["rln", "rln-cli", "rln-wasm", "utils"]
resolver = "2"
# Compilation profile for any non-workspace member.
@@ -8,7 +8,3 @@ resolver = "2"
# while having neglible impact on incremental build times.
[profile.dev.package."*"]
opt-level = 3
[profile.release.package."rln-wasm"]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

View File

@@ -1,4 +1,4 @@
.PHONY: all installdeps build test clean
.PHONY: all installdeps build test bench clean
all: .pre-build build
@@ -13,19 +13,21 @@ endif
installdeps: .pre-build
ifeq ($(shell uname),Darwin)
# commented due to https://github.com/orgs/Homebrew/discussions/4612
# @brew update
@brew update
@brew install cmake ninja
else ifeq ($(shell uname),Linux)
@sudo apt-get update
@sudo apt-get install -y cmake ninja-build
endif
@git -C "wabt" pull || git clone --recursive https://github.com/WebAssembly/wabt.git "wabt"
@cd wabt && mkdir -p build && cd build && cmake .. -GNinja && ninja && sudo ninja install
@which wasm-pack || cargo install wasm-pack
# nvm already checks if it's installed, and no-ops if it is
@curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
@. ${HOME}/.nvm/nvm.sh && nvm install 18.20.2 && nvm use 18.20.2;
@if [ ! -d "$$HOME/.nvm" ]; then \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash; \
fi
@bash -c 'export NVM_DIR="$$HOME/.nvm" && \
[ -s "$$NVM_DIR/nvm.sh" ] && \. "$$NVM_DIR/nvm.sh" && \
nvm install 22.14.0 && \
nvm use 22.14.0'
@curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
@echo "\033[1;32m>>> Now run this command to activate Node.js 22.14.0: \033[1;33msource $$HOME/.nvm/nvm.sh && nvm use 22.14.0\033[0m"
build: .pre-build
@cargo make build
@@ -33,5 +35,8 @@ build: .pre-build
test: .pre-build
@cargo make test
bench: .pre-build
@cargo make bench
clean:
@cargo clean
@cargo clean

View File

@@ -1,45 +1,81 @@
# Zerokit
A set of Zero Knowledge modules, written in Rust and designed to be used in other system programming environments.
[![Crates.io](https://img.shields.io/crates/v/rln.svg)](https://crates.io/crates/rln)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/vacp2p/zerokit/ci.yml?branch=master&label=CI)](https://github.com/vacp2p/zerokit/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
## Initial scope
A collection of Zero Knowledge modules written in Rust and designed to be used in other system programming environments.
Focus on RLN and being able to use [Circom](https://iden3.io/circom) based
version through ark-circom, as opposed to the native one that currently exists
in Rust.
## Overview
## Acknowledgements
Zerokit provides zero-knowledge cryptographic primitives with a focus on performance, security, and usability.
The current focus is on Rate-Limiting Nullifier [RLN](https://github.com/Rate-Limiting-Nullifier) implementation.
- Uses [ark-circom](https://github.com/gakonst/ark-circom), Rust wrapper around Circom.
Current implementation is based on the following [specification](https://github.com/vacp2p/rfc-index/blob/main/vac/raw/rln-v2.md)
and focused on RLNv2 which allows to set a rate limit for the number of messages that can be sent by a user.
- Inspired by Applied ZKP group work, e.g. [zk-kit](https://github.com/appliedzkp/zk-kit).
## Features
- [RLN library](https://github.com/kilic/rln) written in Rust based on Bellman.
- **RLN Implementation**: Efficient Rate-Limiting Nullifier using zkSNARKs
- **Circom Compatibility**: Uses Circom-based circuits for RLN
- **Cross-Platform**: Support for multiple architectures (see compatibility note below)
- **FFI-Friendly**: Easy to integrate with other languages
- [semaphore-rs](https://github.com/worldcoin/semaphore-rs) written in Rust based on ark-circom.
## Architecture
## Users
Zerokit is used by -
- [nwaku](https://github.com/waku-org/nwaku)
- [js-rln](https://github.com/waku-org/js-rln)
Zerokit currently focuses on RLN (Rate-Limiting Nullifier) implementation using [Circom](https://iden3.io/circom) circuits through ark-circom, providing an alternative to existing native Rust implementations.
## Build and Test
To install missing dependencies, run the following commands from the root folder
> [!IMPORTANT]
> For WASM support or x32 architecture builds, use version `0.6.1`. The current version has dependency issues for these platforms. WASM support will return in a future release.
### Install Dependencies
```bash
make installdeps
```
To build and test all crates, run the following commands from the root folder
### Build and Test All Crates
```bash
make build
make test
```
## Release assets
## Release Assets
We use [`cross-rs`](https://github.com/cross-rs/cross) to cross-compile and generate release assets for rln.
We use [`cross-rs`](https://github.com/cross-rs/cross) to cross-compile and generate release assets:
```bash
# Example: Build for specific target
cross build --target x86_64-unknown-linux-gnu --release -p rln
```
## Used By
Zerokit powers zero-knowledge functionality in:
- [**nwaku**](https://github.com/waku-org/nwaku) - Nim implementation of the Waku v2 protocol
- [**js-rln**](https://github.com/waku-org/js-rln) - JavaScript bindings for RLN
## Acknowledgements
- Inspired by [Applied ZKP](https://zkp.science/) group work, including [zk-kit](https://github.com/appliedzkp/zk-kit)
- Uses [ark-circom](https://github.com/gakonst/ark-circom) for zkey and Groth16 proof generation
- Witness calculation based on [circom-witnesscalc](https://github.com/iden3/circom-witnesscalc) by iden3.
The execution graph file used by this code has been generated by means of the same iden3 software.
> [!IMPORTANT]
> The circom-witnesscalc code fragments have been borrowed instead of depending on this crate,
because its types of input and output data were incompatible with the corresponding zerokit code fragments,
and circom-witnesscalc has some dependencies, which are redundant for our purpose.
## Documentation
For detailed documentation on each module:
```bash
cargo doc --open
```

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1740603184,
"narHash": "sha256-t+VaahjQAWyA+Ctn2idyo1yxRIYpaDxMgHkgCNiMJa4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f44bd8ca21e026135061a0a57dcf3d0775b67a49",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f44bd8ca21e026135061a0a57dcf3d0775b67a49",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

38
flake.nix Normal file
View File

@@ -0,0 +1,38 @@
{
description = "A flake for building zerokit";
inputs = {
# Version 24.11
nixpkgs.url = "github:NixOS/nixpkgs?rev=f44bd8ca21e026135061a0a57dcf3d0775b67a49";
};
outputs = { self, nixpkgs }:
let
stableSystems = [
"x86_64-linux" "aarch64-linux"
"x86_64-darwin" "aarch64-darwin"
"x86_64-windows" "i686-linux"
"i686-windows"
];
forAllSystems = nixpkgs.lib.genAttrs stableSystems;
pkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in rec
{
packages = forAllSystems (system: let
pkgs = pkgsFor.${system};
in rec {
zerokit-android-arm64 = pkgs.callPackage ./nix/default.nix { target-platform="aarch64-android-prebuilt"; rust-target= "aarch64-linux-android"; };
default = zerokit-android-arm64;
});
devShells = forAllSystems (system: let
pkgs = pkgsFor.${system};
in {
default = pkgs.mkShell {
inputsFrom = [
packages.${system}.default
];
};
});
};
}

35
nix/default.nix Normal file
View File

@@ -0,0 +1,35 @@
{
pkgs,
target-platform ? "aarch64-android-prebuilt",
rust-target ? "aarch64-linux-android",
}:
pkgs.pkgsCross.${target-platform}.rustPlatform.buildRustPackage {
pname = "zerokit";
version = "nightly";
src = ../.;
cargoLock = {
lockFile = ../Cargo.lock;
allowBuiltinFetchGit = true;
};
CARGO_HOME = "/tmp";
buildPhase = ''
pushd rln
cargo rustc --crate-type=cdylib --release --lib --target=${rust-target}
popd
'';
installPhase = ''
mkdir -p $out/
cp ./target/${rust-target}/release/librln.so $out/
'';
meta = with pkgs.lib; {
description = "Zerokit";
license = licenses.mit;
};
}

View File

@@ -1,13 +1,27 @@
[package]
name = "rln-cli"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
[[example]]
name = "relay"
path = "src/examples/relay.rs"
[[example]]
name = "stateless"
path = "src/examples/stateless.rs"
required-features = ["stateless"]
[dependencies]
rln = { path = "../rln", default-features = true, features = ["arkzkey"] }
clap = { version = "4.2.7", features = ["cargo", "derive", "env"]}
clap_derive = { version = "=4.2.0" }
color-eyre = "=0.6.2"
# serialization
serde_json = "1.0.48"
serde = { version = "1.0.130", features = ["derive"] }
rln = { path = "../rln", default-features = false }
zerokit_utils = { path = "../utils" }
clap = { version = "4.5.35", features = ["cargo", "derive", "env"] }
clap_derive = { version = "4.5.32" }
color-eyre = "0.6.3"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
[features]
default = []
arkzkey = ["rln/arkzkey"]
stateless = ["rln/stateless"]

9
rln-cli/Makefile.toml Normal file
View File

@@ -0,0 +1,9 @@
[tasks.build]
command = "cargo"
args = ["build"]
[tasks.test]
disabled = true
[tasks.bench]
disabled = true

170
rln-cli/README.md Normal file
View File

@@ -0,0 +1,170 @@
# Zerokit RLN-CLI
The Zerokit RLN-CLI provides a command-line interface for interacting with the public API of the [Zerokit RLN Module](../rln/README.md).
It also contain:
+ [Relay Example](#relay-example) to demonstrate the use of the RLN module for spam prevention.
+ [Stateless Example](#stateless-example) to demonstrate the use of the RLN module for stateless features.
## Configuration
The CLI can be configured using a JSON configuration file (see the [example](example.config.json)).
You can specify the configuration file path using the `RLN_CONFIG_PATH` environment variable:
```bash
export RLN_CONFIG_PATH=example.config.json
```
Alternatively, you can provide the configuration file path as an argument for each command:
```bash
RLN_CONFIG_PATH=example.config.json cargo run -- <SUBCOMMAND> [OPTIONS]
```
If the configuration file is empty, default settings will be used, but the tree data folder will be temporary and not saved to the preconfigured path.
We recommend using the example config, as all commands (except `new` and `create-with-params`) require an initialized RLN instance.
## Feature Flags
The CLI supports optional features. To enable the **arkzkey** feature, run:
```bash
cargo run --features arkzkey -- <SUBCOMMAND> [OPTIONS]
```
For more details, refer to the [Zerokit RLN Module](../rln/README.md) documentation.
## Relay Example
The following [Example](src/examples/relay.rs) demonstrates how RLN enables spam prevention in anonymous environments for multple users.
You can run the example using the following command:
```bash
cargo run --example relay
```
or with the **arkzkey** feature flag:
```bash
cargo run --example relay --features arkzkey
```
You can also change **MESSAGE_LIMIT** and **TREEE_HEIGHT** in the [relay.rs](src/examples/relay.rs) file to see how the RLN instance behaves with different parameters.
The customize **TREEE_HEIGHT** constant differs from the default value of `20` should follow [Custom Circuit Compilation](../rln/README.md#advanced-custom-circuit-compilation) instructions.
## Stateless Example
The following [Example](src/examples/stateless.rs) demonstrates how RLN can be used for stateless features by creating the Merkle tree outside of RLN instance.
This example function similarly to the [Relay Example](#relay-example) but uses a stateless RLN and seperate Merkle tree.
You can run the example using the following command:
```bash
cargo run --example stateless --features stateless
```
or with the **arkzkey** feature flag:
```bash
cargo run --example stateless --features stateless,arkzkey
```
## CLI Commands
### Instance Management
To initialize a new RLN instance:
```bash
cargo run new --tree-height <HEIGHT>
```
To initialize an RLN instance with custom parameters:
```bash
cargo run new-with-params --resources-path <PATH> --tree-height <HEIGHT>
```
To update the Merkle tree height:
```bash
cargo run set-tree --tree-height <HEIGHT>
```
### Leaf Operations
To set a single leaf:
```bash
cargo run set-leaf --index <INDEX> --input <INPUT_PATH>
```
To set multiple leaves:
```bash
cargo run set-multiple-leaves --index <START_INDEX> --input <INPUT_PATH>
```
To reset multiple leaves:
```bash
cargo run reset-multiple-leaves --input <INPUT_PATH>
```
To set the next available leaf:
```bash
cargo run set-next-leaf --input <INPUT_PATH>
```
To delete a specific leaf:
```bash
cargo run delete-leaf --index <INDEX>
```
### Proof Operations
To generate a proof:
```bash
cargo run prove --input <INPUT_PATH>
```
To generate an RLN proof:
```bash
cargo run generate-proof --input <INPUT_PATH>
```
To verify a proof:
```bash
cargo run verify --input <PROOF_PATH>
```
To verify a proof with multiple Merkle roots:
```bash
cargo run verify-with-roots --input <INPUT_PATH> --roots <ROOTS_PATH>
```
### Tree Information
To retrieve the current Merkle root:
```bash
cargo run get-root
```
To obtain a Merkle proof for a specific index:
```bash
cargo run get-proof --index <INDEX>
```

View File

@@ -0,0 +1,11 @@
{
"tree_config": {
"path": "database",
"temporary": false,
"cache_capacity": 150000,
"flush_every_ms": 12000,
"mode": "HighThroughput",
"use_compression": false
},
"tree_height": 20
}

View File

@@ -1,49 +1,51 @@
use std::path::PathBuf;
use clap::Subcommand;
use rln::circuit::TEST_TREE_HEIGHT;
#[derive(Subcommand)]
pub(crate) enum Commands {
New {
#[arg(short, long, default_value_t = TEST_TREE_HEIGHT)]
tree_height: usize,
/// Sets a custom config file
#[arg(short, long)]
config: PathBuf,
},
NewWithParams {
#[arg(short, long, default_value_t = TEST_TREE_HEIGHT)]
tree_height: usize,
/// Sets a custom config file
#[arg(short, long)]
config: PathBuf,
#[arg(short, long)]
tree_config_input: PathBuf,
#[arg(short, long, default_value = "../rln/resources/tree_height_20")]
resources_path: PathBuf,
},
SetTree {
#[arg(short, long, default_value_t = TEST_TREE_HEIGHT)]
tree_height: usize,
},
SetLeaf {
#[arg(short, long)]
index: usize,
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
SetMultipleLeaves {
#[arg(short, long)]
index: usize,
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
ResetMultipleLeaves {
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
SetNextLeaf {
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
DeleteLeaf {
#[arg(short, long)]
index: usize,
},
GetRoot,
GetProof {
#[arg(short, long)]
index: usize,
},
Prove {
@@ -52,7 +54,7 @@ pub(crate) enum Commands {
},
Verify {
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
GenerateProof {
#[arg(short, long)]

View File

@@ -1,8 +1,10 @@
use color_eyre::Result;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::Read, path::PathBuf};
pub const RLN_STATE_PATH: &str = "RLN_STATE_PATH";
use color_eyre::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const RLN_CONFIG_PATH: &str = "RLN_CONFIG_PATH";
#[derive(Default, Serialize, Deserialize)]
pub(crate) struct Config {
@@ -11,19 +13,26 @@ pub(crate) struct Config {
#[derive(Default, Serialize, Deserialize)]
pub(crate) struct InnerConfig {
pub file: PathBuf,
pub tree_height: usize,
pub tree_config: Value,
}
impl Config {
pub(crate) fn load_config() -> Result<Config> {
let path = PathBuf::from(std::env::var(RLN_STATE_PATH)?);
match std::env::var(RLN_CONFIG_PATH) {
Ok(env) => {
let path = PathBuf::from(env);
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let inner: InnerConfig = serde_json::from_str(&contents)?;
Ok(Config { inner: Some(inner) })
}
Err(_) => Ok(Config::default()),
}
}
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let state: Config = serde_json::from_str(&contents)?;
Ok(state)
pub(crate) fn as_bytes(&self) -> Vec<u8> {
serde_json::to_string(&self.inner).unwrap().into_bytes()
}
}

View File

@@ -0,0 +1,317 @@
use std::{
collections::HashMap,
fs::File,
io::{stdin, stdout, Cursor, Read, Write},
path::{Path, PathBuf},
};
use clap::{Parser, Subcommand};
use color_eyre::{eyre::eyre, Result};
use rln::{
circuit::Fr,
hashers::{hash_to_field, poseidon_hash},
protocol::{keygen, prepare_prove_input, prepare_verify_input},
public::RLN,
utils::{bytes_le_to_fr, fr_to_bytes_le, generate_input_buffer},
};
const MESSAGE_LIMIT: u32 = 1;
const TREEE_HEIGHT: usize = 20;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List,
Register,
Send {
#[arg(short, long)]
user_index: usize,
#[arg(short, long)]
message_id: u32,
#[arg(short, long)]
signal: String,
},
Clear,
Exit,
}
#[derive(Debug, Clone)]
struct Identity {
identity_secret_hash: Fr,
id_commitment: Fr,
}
impl Identity {
fn new() -> Self {
let (identity_secret_hash, id_commitment) = keygen();
Identity {
identity_secret_hash,
id_commitment,
}
}
}
struct RLNSystem {
rln: RLN,
used_nullifiers: HashMap<[u8; 32], Vec<u8>>,
local_identities: HashMap<usize, Identity>,
}
impl RLNSystem {
fn new() -> Result<Self> {
let mut resources: Vec<Vec<u8>> = Vec::new();
let resources_path: PathBuf = format!("../rln/resources/tree_height_{TREEE_HEIGHT}").into();
#[cfg(feature = "arkzkey")]
let filenames = ["rln_final.arkzkey", "graph.bin"];
#[cfg(not(feature = "arkzkey"))]
let filenames = ["rln_final.zkey", "graph.bin"];
for filename in filenames {
let fullpath = resources_path.join(Path::new(filename));
let mut file = File::open(&fullpath)?;
let metadata = std::fs::metadata(&fullpath)?;
let mut output_buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut output_buffer)?;
resources.push(output_buffer);
}
let rln = RLN::new_with_params(
TREEE_HEIGHT,
resources[0].clone(),
resources[1].clone(),
generate_input_buffer(),
)?;
println!("RLN instance initialized successfully");
Ok(RLNSystem {
rln,
used_nullifiers: HashMap::new(),
local_identities: HashMap::new(),
})
}
fn list_users(&self) {
if self.local_identities.is_empty() {
println!("No users registered yet.");
return;
}
println!("Registered users:");
for (index, identity) in &self.local_identities {
println!("User Index: {index}");
println!("+ Identity Secret Hash: {}", identity.identity_secret_hash);
println!("+ Identity Commitment: {}", identity.id_commitment);
println!();
}
}
fn register_user(&mut self) -> Result<usize> {
let index = self.rln.leaves_set();
let identity = Identity::new();
let rate_commitment = poseidon_hash(&[identity.id_commitment, Fr::from(MESSAGE_LIMIT)]);
let mut buffer = Cursor::new(fr_to_bytes_le(&rate_commitment));
match self.rln.set_next_leaf(&mut buffer) {
Ok(_) => {
println!("Registered User Index: {index}");
println!("+ Identity secret hash: {}", identity.identity_secret_hash);
println!("+ Identity commitment: {},", identity.id_commitment);
self.local_identities.insert(index, identity);
}
Err(_) => {
println!("Maximum user limit reached: 2^{TREEE_HEIGHT}");
}
};
Ok(index)
}
fn generate_proof(
&mut self,
user_index: usize,
message_id: u32,
signal: &str,
external_nullifier: Fr,
) -> Result<Vec<u8>> {
let identity = match self.local_identities.get(&user_index) {
Some(identity) => identity,
None => return Err(eyre!("user index {user_index} not found")),
};
let serialized = prepare_prove_input(
identity.identity_secret_hash,
user_index,
Fr::from(MESSAGE_LIMIT),
Fr::from(message_id),
external_nullifier,
signal.as_bytes(),
);
let mut input_buffer = Cursor::new(serialized);
let mut output_buffer = Cursor::new(Vec::new());
self.rln
.generate_rln_proof(&mut input_buffer, &mut output_buffer)?;
println!("Proof generated successfully:");
println!("+ User Index: {user_index}");
println!("+ Message ID: {message_id}");
println!("+ Signal: {signal}");
Ok(output_buffer.into_inner())
}
fn verify_proof(&mut self, proof_data: Vec<u8>, signal: &str) -> Result<()> {
let proof_with_signal = prepare_verify_input(proof_data.clone(), signal.as_bytes());
let mut input_buffer = Cursor::new(proof_with_signal);
match self.rln.verify_rln_proof(&mut input_buffer) {
Ok(true) => {
let nullifier = &proof_data[256..288];
let nullifier_key: [u8; 32] = nullifier.try_into()?;
if let Some(previous_proof) = self.used_nullifiers.get(&nullifier_key) {
self.handle_duplicate_message_id(previous_proof.clone(), proof_data)?;
return Ok(());
}
self.used_nullifiers.insert(nullifier_key, proof_data);
println!("Message verified and accepted");
}
Ok(false) => {
println!("Verification failed: message_id must be unique within the epoch and satisfy 0 <= message_id < MESSAGE_LIMIT: {MESSAGE_LIMIT}");
}
Err(err) => return Err(err),
}
Ok(())
}
fn handle_duplicate_message_id(
&mut self,
previous_proof: Vec<u8>,
current_proof: Vec<u8>,
) -> Result<()> {
let x = &current_proof[192..224];
let y = &current_proof[224..256];
let prev_x = &previous_proof[192..224];
let prev_y = &previous_proof[224..256];
if x == prev_x && y == prev_y {
return Err(eyre!("this exact message and signal has already been sent"));
}
let mut proof1 = Cursor::new(previous_proof);
let mut proof2 = Cursor::new(current_proof);
let mut output = Cursor::new(Vec::new());
match self
.rln
.recover_id_secret(&mut proof1, &mut proof2, &mut output)
{
Ok(_) => {
let output_data = output.into_inner();
let (leaked_identity_secret_hash, _) = bytes_le_to_fr(&output_data);
if let Some((user_index, identity)) = self
.local_identities
.iter()
.find(|(_, identity)| {
identity.identity_secret_hash == leaked_identity_secret_hash
})
.map(|(index, identity)| (*index, identity))
{
let real_identity_secret_hash = identity.identity_secret_hash;
if leaked_identity_secret_hash != real_identity_secret_hash {
Err(eyre!("identity secret hash mismatch {leaked_identity_secret_hash} != {real_identity_secret_hash}"))
} else {
println!("DUPLICATE message ID detected! Reveal identity secret hash: {leaked_identity_secret_hash}");
self.local_identities.remove(&user_index);
self.rln.delete_leaf(user_index)?;
println!("User index {user_index} has been SLASHED");
Ok(())
}
} else {
Err(eyre!(
"user identity secret hash {leaked_identity_secret_hash} not found"
))
}
}
Err(err) => Err(eyre!("Failed to recover identity secret: {err}")),
}
}
}
fn main() -> Result<()> {
println!("Initializing RLN instance...");
print!("\x1B[2J\x1B[1;1H");
let mut rln_system = RLNSystem::new()?;
let rln_epoch = hash_to_field(b"epoch");
let rln_identifier = hash_to_field(b"rln-identifier");
let external_nullifier = poseidon_hash(&[rln_epoch, rln_identifier]);
println!("RLN Relay Example:");
println!("Message Limit: {MESSAGE_LIMIT}");
println!("----------------------------------");
println!();
show_commands();
loop {
print!("\n> ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
let trimmed = input.trim();
let args = std::iter::once("").chain(trimmed.split_whitespace());
match Cli::try_parse_from(args) {
Ok(cli) => match cli.command {
Commands::List => {
rln_system.list_users();
}
Commands::Register => {
rln_system.register_user()?;
}
Commands::Send {
user_index,
message_id,
signal,
} => {
match rln_system.generate_proof(
user_index,
message_id,
&signal,
external_nullifier,
) {
Ok(proof) => {
if let Err(err) = rln_system.verify_proof(proof, &signal) {
println!("Verification error: {err}");
};
}
Err(err) => {
println!("Proof generation error: {err}");
}
}
}
Commands::Clear => {
print!("\x1B[2J\x1B[1;1H");
show_commands();
}
Commands::Exit => {
break;
}
},
Err(err) => {
eprintln!("Command error: {err}");
}
}
}
Ok(())
}
fn show_commands() {
println!("Available commands:");
println!(" list - List registered users");
println!(" register - Register a new user index");
println!(" send -u <index> -m <message_id> -s <signal> - Send a message with proof");
println!(" clear - Clear the screen");
println!(" exit - Exit the program");
}

View File

@@ -0,0 +1,314 @@
#![cfg(feature = "stateless")]
use std::{
collections::HashMap,
io::{stdin, stdout, Cursor, Write},
};
use clap::{Parser, Subcommand};
use color_eyre::{eyre::eyre, Result};
use rln::{
circuit::{Fr, TEST_TREE_HEIGHT},
hashers::{hash_to_field, poseidon_hash},
poseidon_tree::PoseidonTree,
protocol::{keygen, prepare_verify_input, rln_witness_from_values, serialize_witness},
public::RLN,
utils::{bytes_le_to_fr, fr_to_bytes_le},
};
use zerokit_utils::ZerokitMerkleTree;
const MESSAGE_LIMIT: u32 = 1;
type ConfigOf<T> = <T as ZerokitMerkleTree>::Config;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List,
Register,
Send {
#[arg(short, long)]
user_index: usize,
#[arg(short, long)]
message_id: u32,
#[arg(short, long)]
signal: String,
},
Clear,
Exit,
}
#[derive(Debug, Clone)]
struct Identity {
identity_secret_hash: Fr,
id_commitment: Fr,
}
impl Identity {
fn new() -> Self {
let (identity_secret_hash, id_commitment) = keygen();
Identity {
identity_secret_hash,
id_commitment,
}
}
}
struct RLNSystem {
rln: RLN,
tree: PoseidonTree,
used_nullifiers: HashMap<[u8; 32], Vec<u8>>,
local_identities: HashMap<usize, Identity>,
}
impl RLNSystem {
fn new() -> Result<Self> {
let rln = RLN::new()?;
let default_leaf = Fr::from(0);
let tree = PoseidonTree::new(
TEST_TREE_HEIGHT,
default_leaf,
ConfigOf::<PoseidonTree>::default(),
)?;
Ok(RLNSystem {
rln,
tree,
used_nullifiers: HashMap::new(),
local_identities: HashMap::new(),
})
}
fn list_users(&self) {
if self.local_identities.is_empty() {
println!("No users registered yet.");
return;
}
println!("Registered users:");
for (index, identity) in &self.local_identities {
println!("User Index: {index}");
println!("+ Identity Secret Hash: {}", identity.identity_secret_hash);
println!("+ Identity Commitment: {}", identity.id_commitment);
println!();
}
}
fn register_user(&mut self) -> Result<usize> {
let index = self.tree.leaves_set();
let identity = Identity::new();
let rate_commitment = poseidon_hash(&[identity.id_commitment, Fr::from(MESSAGE_LIMIT)]);
self.tree.update_next(rate_commitment)?;
println!("Registered User Index: {index}");
println!("+ Identity secret hash: {}", identity.identity_secret_hash);
println!("+ Identity commitment: {}", identity.id_commitment);
self.local_identities.insert(index, identity);
Ok(index)
}
fn generate_proof(
&mut self,
user_index: usize,
message_id: u32,
signal: &str,
external_nullifier: Fr,
) -> Result<Vec<u8>> {
let identity = match self.local_identities.get(&user_index) {
Some(identity) => identity,
None => return Err(eyre!("user index {user_index} not found")),
};
let merkle_proof = self.tree.proof(user_index)?;
let x = hash_to_field(signal.as_bytes());
let rln_witness = rln_witness_from_values(
identity.identity_secret_hash,
&merkle_proof,
x,
external_nullifier,
Fr::from(MESSAGE_LIMIT),
Fr::from(message_id),
)?;
let serialized = serialize_witness(&rln_witness)?;
let mut input_buffer = Cursor::new(serialized);
let mut output_buffer = Cursor::new(Vec::new());
self.rln
.generate_rln_proof_with_witness(&mut input_buffer, &mut output_buffer)?;
println!("Proof generated successfully:");
println!("+ User Index: {user_index}");
println!("+ Message ID: {message_id}");
println!("+ Signal: {signal}");
Ok(output_buffer.into_inner())
}
fn verify_proof(&mut self, proof_data: Vec<u8>, signal: &str) -> Result<()> {
let proof_with_signal = prepare_verify_input(proof_data.clone(), signal.as_bytes());
let mut input_buffer = Cursor::new(proof_with_signal);
let root = self.tree.root();
let roots_serialized = fr_to_bytes_le(&root);
let mut roots_buffer = Cursor::new(roots_serialized);
match self
.rln
.verify_with_roots(&mut input_buffer, &mut roots_buffer)
{
Ok(true) => {
let nullifier = &proof_data[256..288];
let nullifier_key: [u8; 32] = nullifier.try_into()?;
if let Some(previous_proof) = self.used_nullifiers.get(&nullifier_key) {
self.handle_duplicate_message_id(previous_proof.clone(), proof_data)?;
return Ok(());
}
self.used_nullifiers.insert(nullifier_key, proof_data);
println!("Message verified and accepted");
}
Ok(false) => {
println!("Verification failed: message_id must be unique within the epoch and satisfy 0 <= message_id < MESSAGE_LIMIT: {MESSAGE_LIMIT}");
}
Err(err) => return Err(err.into()),
}
Ok(())
}
fn handle_duplicate_message_id(
&mut self,
previous_proof: Vec<u8>,
current_proof: Vec<u8>,
) -> Result<()> {
let x = &current_proof[192..224];
let y = &current_proof[224..256];
let prev_x = &previous_proof[192..224];
let prev_y = &previous_proof[224..256];
if x == prev_x && y == prev_y {
return Err(eyre!("this exact message and signal has already been sent"));
}
let mut proof1 = Cursor::new(previous_proof);
let mut proof2 = Cursor::new(current_proof);
let mut output = Cursor::new(Vec::new());
match self
.rln
.recover_id_secret(&mut proof1, &mut proof2, &mut output)
{
Ok(_) => {
let output_data = output.into_inner();
let (leaked_identity_secret_hash, _) = bytes_le_to_fr(&output_data);
if let Some((user_index, identity)) = self
.local_identities
.iter()
.find(|(_, identity)| {
identity.identity_secret_hash == leaked_identity_secret_hash
})
.map(|(index, identity)| (*index, identity))
{
let real_identity_secret_hash = identity.identity_secret_hash;
if leaked_identity_secret_hash != real_identity_secret_hash {
Err(eyre!("identity secret hash mismatch {leaked_identity_secret_hash} != {real_identity_secret_hash}"))
} else {
println!("DUPLICATE message ID detected! Reveal identity secret hash: {leaked_identity_secret_hash}");
self.local_identities.remove(&user_index);
println!("User index {user_index} has been SLASHED");
Ok(())
}
} else {
Err(eyre!(
"user identity secret hash {leaked_identity_secret_hash} not found"
))
}
}
Err(err) => Err(eyre!("Failed to recover identity secret: {err}")),
}
}
}
fn main() -> Result<()> {
println!("Initializing RLN instance...");
print!("\x1B[2J\x1B[1;1H");
let mut rln_system = RLNSystem::new()?;
let rln_epoch = hash_to_field(b"epoch");
let rln_identifier = hash_to_field(b"rln-identifier");
let external_nullifier = poseidon_hash(&[rln_epoch, rln_identifier]);
println!("RLN Stateless Relay Example:");
println!("Message Limit: {MESSAGE_LIMIT}");
println!("----------------------------------");
println!();
show_commands();
loop {
print!("\n> ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
let trimmed = input.trim();
let args = std::iter::once("").chain(trimmed.split_whitespace());
match Cli::try_parse_from(args) {
Ok(cli) => match cli.command {
Commands::List => {
rln_system.list_users();
}
Commands::Register => {
rln_system.register_user()?;
}
Commands::Send {
user_index,
message_id,
signal,
} => {
match rln_system.generate_proof(
user_index,
message_id,
&signal,
external_nullifier,
) {
Ok(proof) => {
if let Err(err) = rln_system.verify_proof(proof, &signal) {
println!("Verification error: {err}");
};
}
Err(err) => {
println!("Proof generation error: {err}");
}
}
}
Commands::Clear => {
print!("\x1B[2J\x1B[1;1H");
show_commands();
}
Commands::Exit => {
break;
}
},
Err(err) => {
eprintln!("Command error: {err}");
}
}
}
Ok(())
}
fn show_commands() {
println!("Available commands:");
println!(" list - List registered users");
println!(" register - Register a new user index");
println!(" send -u <index> -m <message_id> -s <signal> - Send a message with proof");
println!(" clear - Clear the screen");
println!(" exit - Exit the program");
}

View File

@@ -1,9 +1,18 @@
use std::{fs::File, io::Read, path::Path};
use std::{
fs::File,
io::{Cursor, Read},
path::Path,
};
use clap::Parser;
use color_eyre::{Report, Result};
use color_eyre::{eyre::Report, Result};
use commands::Commands;
use rln::public::RLN;
use config::{Config, InnerConfig};
use rln::{
public::RLN,
utils::{bytes_le_to_fr, bytes_le_to_vec_fr},
};
use serde_json::json;
use state::State;
mod commands;
@@ -20,78 +29,97 @@ struct Cli {
fn main() -> Result<()> {
let cli = Cli::parse();
let mut state = State::load_state()?;
let mut state = match &cli.command {
Some(Commands::New { .. }) | Some(Commands::NewWithParams { .. }) => State::default(),
_ => State::load_state()?,
};
match &cli.command {
Some(Commands::New {
tree_height,
config,
}) => {
let resources = File::open(&config)?;
state.rln = Some(RLN::new(*tree_height, resources)?);
match cli.command {
Some(Commands::New { tree_height }) => {
let config = Config::load_config()?;
state.rln = if let Some(InnerConfig { tree_height, .. }) = config.inner {
println!("Initializing RLN with custom config");
Some(RLN::new(tree_height, Cursor::new(config.as_bytes()))?)
} else {
println!("Initializing RLN with default config");
Some(RLN::new(tree_height, Cursor::new(json!({}).to_string()))?)
};
Ok(())
}
Some(Commands::NewWithParams {
tree_height,
config,
tree_config_input,
resources_path,
}) => {
let mut resources: Vec<Vec<u8>> = Vec::new();
#[cfg(feature = "arkzkey")]
let filenames = ["rln.wasm", "rln_final.arkzkey", "verification_key.arkvkey"];
let filenames = ["rln_final.arkzkey", "graph.bin"];
#[cfg(not(feature = "arkzkey"))]
let filenames = ["rln.wasm", "rln_final.zkey", "verification_key.arkvkey"];
let filenames = ["rln_final.zkey", "graph.bin"];
for filename in filenames {
let fullpath = config.join(Path::new(filename));
let fullpath = resources_path.join(Path::new(filename));
let mut file = File::open(&fullpath)?;
let metadata = std::fs::metadata(&fullpath)?;
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer)?;
resources.push(buffer);
let mut output_buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut output_buffer)?;
resources.push(output_buffer);
}
let tree_config_input_file = File::open(&tree_config_input)?;
state.rln = Some(RLN::new_with_params(
*tree_height,
resources[0].clone(),
resources[1].clone(),
resources[2].clone(),
tree_config_input_file,
)?);
let config = Config::load_config()?;
if let Some(InnerConfig {
tree_height,
tree_config,
}) = config.inner
{
println!("Initializing RLN with custom config");
state.rln = Some(RLN::new_with_params(
tree_height,
resources[0].clone(),
resources[1].clone(),
Cursor::new(tree_config.to_string().as_bytes()),
)?)
} else {
println!("Initializing RLN with default config");
state.rln = Some(RLN::new_with_params(
tree_height,
resources[0].clone(),
resources[1].clone(),
Cursor::new(json!({}).to_string()),
)?)
};
Ok(())
}
Some(Commands::SetTree { tree_height }) => {
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.set_tree(*tree_height)?;
.set_tree(tree_height)?;
Ok(())
}
Some(Commands::SetLeaf { index, file }) => {
let input_data = File::open(&file)?;
Some(Commands::SetLeaf { index, input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.set_leaf(*index, input_data)?;
.set_leaf(index, input_data)?;
Ok(())
}
Some(Commands::SetMultipleLeaves { index, file }) => {
let input_data = File::open(&file)?;
Some(Commands::SetMultipleLeaves { index, input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.set_leaves_from(*index, input_data)?;
.set_leaves_from(index, input_data)?;
Ok(())
}
Some(Commands::ResetMultipleLeaves { file }) => {
let input_data = File::open(&file)?;
Some(Commands::ResetMultipleLeaves { input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.init_tree_with_leaves(input_data)?;
Ok(())
}
Some(Commands::SetNextLeaf { file }) => {
let input_data = File::open(&file)?;
Some(Commands::SetNextLeaf { input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
@@ -102,60 +130,73 @@ fn main() -> Result<()> {
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.delete_leaf(*index)?;
Ok(())
}
Some(Commands::GetRoot) => {
let writer = std::io::stdout();
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_root(writer)?;
Ok(())
}
Some(Commands::GetProof { index }) => {
let writer = std::io::stdout();
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_proof(*index, writer)?;
.delete_leaf(index)?;
Ok(())
}
Some(Commands::Prove { input }) => {
let input_data = File::open(&input)?;
let writer = std::io::stdout();
let input_data = File::open(input)?;
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.prove(input_data, writer)?;
.prove(input_data, &mut output_buffer)?;
let proof = output_buffer.into_inner();
println!("proof: {:?}", proof);
Ok(())
}
Some(Commands::Verify { file }) => {
let input_data = File::open(&file)?;
state
Some(Commands::Verify { input }) => {
let input_data = File::open(input)?;
let verified = state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.verify(input_data)?;
println!("verified: {:?}", verified);
Ok(())
}
Some(Commands::GenerateProof { input }) => {
let input_data = File::open(&input)?;
let writer = std::io::stdout();
let input_data = File::open(input)?;
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.generate_rln_proof(input_data, writer)?;
.generate_rln_proof(input_data, &mut output_buffer)?;
let proof = output_buffer.into_inner();
println!("proof: {:?}", proof);
Ok(())
}
Some(Commands::VerifyWithRoots { input, roots }) => {
let input_data = File::open(&input)?;
let roots_data = File::open(&roots)?;
let input_data = File::open(input)?;
let roots_data = File::open(roots)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.verify_with_roots(input_data, roots_data)?;
Ok(())
}
Some(Commands::GetRoot) => {
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_root(&mut output_buffer)
.unwrap();
let (root, _) = bytes_le_to_fr(&output_buffer.into_inner());
println!("root: {root}");
Ok(())
}
Some(Commands::GetProof { index }) => {
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_proof(index, &mut output_buffer)?;
let output_buffer_inner = output_buffer.into_inner();
let (path_elements, _) = bytes_le_to_vec_fr(&output_buffer_inner)?;
for (index, element) in path_elements.iter().enumerate() {
println!("path element {}: {}", index, element);
}
Ok(())
}
None => Ok(()),
}
}

View File

@@ -1,6 +1,7 @@
use std::io::Cursor;
use color_eyre::Result;
use rln::public::RLN;
use std::fs::File;
use crate::config::{Config, InnerConfig};
@@ -12,9 +13,8 @@ pub(crate) struct State {
impl State {
pub(crate) fn load_state() -> Result<State> {
let config = Config::load_config()?;
let rln = if let Some(InnerConfig { file, tree_height }) = config.inner {
let resources = File::open(&file)?;
Some(RLN::new(tree_height, resources)?)
let rln = if let Some(InnerConfig { tree_height, .. }) = config.inner {
Some(RLN::new(tree_height, Cursor::new(config.as_bytes()))?)
} else {
None
};

View File

@@ -6,34 +6,34 @@ license = "MIT or Apache2"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
required-features = ["stateless"]
[dependencies]
rln = { path = "../rln", default-features = false, features = [
"wasm",
"stateless",
], version = "=0.6.1"}
num-bigint = { version = "0.4", default-features = false, features = [
rln = { path = "../rln", default-features = false }
num-bigint = { version = "0.4.6", default-features = false, features = [
"rand",
"serde",
] }
wasmer = { version = "2.3", default-features = false, features = ["js", "std"] }
web-sys = { version = "0.3", features = ["console"] }
getrandom = { version = "0.2.7", default-features = false, features = ["js"] }
wasm-bindgen = "0.2.63"
serde-wasm-bindgen = "0.4"
js-sys = "0.3.59"
serde_json = "1.0.85"
wasm-bindgen = "0.2.100"
serde-wasm-bindgen = "0.6.5"
js-sys = "0.3.77"
serde_json = "1.0"
# The `console_error_panic_hook` crate provides better debugging of panics by
# The `console_error_panic_xhook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }
zerokit_utils = { version = "0.5.1", path = "../utils" }
zerokit_utils = { path = "../utils" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.15", features = ["js"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.13"
wasm-bindgen-futures = "0.4.33"
wasm-bindgen-test = "0.3.50"
wasm-bindgen-futures = "0.4.50"
[features]
default = ["console_error_panic_hook"]
stateless = ["rln/stateless"]
arkzkey = ["rln/arkzkey"]

View File

@@ -1,22 +1,53 @@
[tasks.pack-build]
command = "wasm-pack"
args = ["build", "--release", "--target", "web", "--scope", "waku"]
[tasks.pack-rename]
script = "sed -i.bak 's/rln-wasm/zerokit-rln-wasm/g' pkg/package.json && rm pkg/package.json.bak"
[tasks.build]
clear = true
dependencies = ["pack-build", "pack-rename", "post-build"]
dependencies = ["pack_build", "pack_rename"]
[tasks.post-build]
command = "wasm-strip"
args = ["./pkg/rln_wasm_bg.wasm"]
[tasks.build_arkzkey]
clear = true
dependencies = ["pack_build_arkzkey", "pack_rename"]
[tasks.pack_build]
command = "wasm-pack"
args = ["build", "--release", "--target", "web", "--scope", "waku"]
env = { "RUSTFLAGS" = "--cfg feature=\"stateless\"" }
[tasks.pack_build_arkzkey]
command = "wasm-pack"
args = ["build", "--release", "--target", "web", "--scope", "waku"]
env = { "RUSTFLAGS" = "--cfg feature=\"stateless\" --cfg feature=\"arkzkey\"" }
[tasks.pack_rename]
script = "sed -i.bak 's/rln-wasm/zerokit-rln-wasm/g' pkg/package.json && rm pkg/package.json.bak"
[tasks.test]
command = "wasm-pack"
args = ["test", "--release", "--node"]
dependencies = ["build"]
args = [
"test",
"--release",
"--node",
"--target",
"wasm32-unknown-unknown",
"--",
"--nocapture",
]
env = { "RUSTFLAGS" = "--cfg feature=\"stateless\"" }
[tasks.test_arkzkey]
command = "wasm-pack"
args = [
"test",
"--release",
"--node",
"--target",
"wasm32-unknown-unknown",
"--",
"--nocapture",
]
env = { "RUSTFLAGS" = "--cfg feature=\"stateless\" --cfg feature=\"arkzkey\"" }
dependencies = ["build_arkzkey"]
[tasks.bench]
disabled = true
[tasks.login]
command = "wasm-pack"
@@ -25,7 +56,3 @@ args = ["login"]
[tasks.publish]
command = "wasm-pack"
args = ["publish", "--access", "public", "--target", "web"]
[tasks.bench]
command = "echo"
args = ["'No benchmarks available for this project'"]

View File

@@ -1,41 +1,55 @@
# RLN for WASM
This library is used in [waku-org/js-rln](https://github.com/waku-org/js-rln/)
## Building the library
1. Install `wasm-pack`
```
> **Note**: This project requires `wasm-pack` for compiling Rust to WebAssembly and `cargo-make` for running the build commands. Make sure both are installed before proceeding.
Install `wasm-pack`:
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
2. Install `cargo-make`
```
Install `cargo-make`
```bash
cargo install cargo-make
```
OR
Or install everything needed for `zerokit` at the root of the repository:
```
```bash
make installdeps
```
3. Compile zerokit for `wasm32-unknown-unknown`:
```
## Building the library
First, navigate to the rln-wasm directory:
```bash
cd rln-wasm
cargo make build
```
4. Compile a slimmer version of zerokit for `wasm32-unknown-unknown`:
```
cd rln-wasm
cargo make post-build
```
## Running tests
Compile zerokit for `wasm32-unknown-unknown`:
```bash
cargo make build
```
cd rln-wasm
Or compile with the **arkzkey** feature enabled
```bash
cargo make build_arkzkey
```
## Running tests and benchmarks
```bash
cargo make test
```
## Publishing a npm package
Or test with the **arkzkey** feature enabled
```bash
cargo make test_arkzkey
```
cd rln-wasm
cargo make login
cargo make publish
```

View File

@@ -1,331 +1,324 @@
module.exports = async function builder(code, options) {
options = options || {};
options = options || {};
let wasmModule;
try {
wasmModule = await WebAssembly.compile(code);
} catch (err) {
console.log(err);
console.log(
"\nTry to run circom --c in order to generate c++ code instead\n"
);
throw new Error(err);
}
let wasmModule;
try {
wasmModule = await WebAssembly.compile(code);
} catch (err) {
console.log(err);
console.log("\nTry to run circom --c in order to generate c++ code instead\n");
throw new Error(err);
}
let wc;
let wc;
let errStr = "";
let msgStr = "";
const instance = await WebAssembly.instantiate(wasmModule, {
runtime: {
exceptionHandler : function(code) {
let err;
if (code == 1) {
err = "Signal not found.\n";
} else if (code == 2) {
err = "Too many signals set.\n";
} else if (code == 3) {
err = "Signal already set.\n";
} else if (code == 4) {
err = "Assert Failed.\n";
} else if (code == 5) {
err = "Not enough memory.\n";
} else if (code == 6) {
err = "Input signal array access exceeds the size.\n";
} else {
err = "Unknown error.\n";
}
throw new Error(err + errStr);
},
printErrorMessage : function() {
errStr += getMessage() + "\n";
// console.error(getMessage());
},
writeBufferMessage : function() {
const msg = getMessage();
// Any calls to `log()` will always end with a `\n`, so that's when we print and reset
if (msg === "\n") {
console.log(msgStr);
msgStr = "";
} else {
// If we've buffered other content, put a space in between the items
if (msgStr !== "") {
msgStr += " "
}
// Then append the message to the message we are creating
msgStr += msg;
}
},
showSharedRWMemory : function() {
printSharedRWMemory ();
}
let errStr = "";
let msgStr = "";
const instance = await WebAssembly.instantiate(wasmModule, {
runtime: {
exceptionHandler: function (code) {
let err;
if (code == 1) {
err = "Signal not found.\n";
} else if (code == 2) {
err = "Too many signals set.\n";
} else if (code == 3) {
err = "Signal already set.\n";
} else if (code == 4) {
err = "Assert Failed.\n";
} else if (code == 5) {
err = "Not enough memory.\n";
} else if (code == 6) {
err = "Input signal array access exceeds the size.\n";
} else {
err = "Unknown error.\n";
}
});
throw new Error(err + errStr);
},
printErrorMessage: function () {
errStr += getMessage() + "\n";
// console.error(getMessage());
},
writeBufferMessage: function () {
const msg = getMessage();
// Any calls to `log()` will always end with a `\n`, so that's when we print and reset
if (msg === "\n") {
console.log(msgStr);
msgStr = "";
} else {
// If we've buffered other content, put a space in between the items
if (msgStr !== "") {
msgStr += " ";
}
// Then append the message to the message we are creating
msgStr += msg;
}
},
showSharedRWMemory: function () {
printSharedRWMemory();
},
},
});
const sanityCheck =
options
// options &&
// (
// options.sanityCheck ||
// options.logGetSignal ||
// options.logSetSignal ||
// options.logStartComponent ||
// options.logFinishComponent
// );
const sanityCheck = options;
// options &&
// (
// options.sanityCheck ||
// options.logGetSignal ||
// options.logSetSignal ||
// options.logStartComponent ||
// options.logFinishComponent
// );
wc = new WitnessCalculator(instance, sanityCheck);
return wc;
wc = new WitnessCalculator(instance, sanityCheck);
return wc;
function getMessage() {
var message = "";
var c = instance.exports.getMessageChar();
while ( c != 0 ) {
message += String.fromCharCode(c);
c = instance.exports.getMessageChar();
}
return message;
function getMessage() {
var message = "";
var c = instance.exports.getMessageChar();
while (c != 0) {
message += String.fromCharCode(c);
c = instance.exports.getMessageChar();
}
function printSharedRWMemory () {
const shared_rw_memory_size = instance.exports.getFieldNumLen32();
const arr = new Uint32Array(shared_rw_memory_size);
for (let j=0; j<shared_rw_memory_size; j++) {
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
}
return message;
}
// If we've buffered other content, put a space in between the items
if (msgStr !== "") {
msgStr += " "
}
// Then append the value to the message we are creating
msgStr += (fromArray32(arr).toString());
}
function printSharedRWMemory() {
const shared_rw_memory_size = instance.exports.getFieldNumLen32();
const arr = new Uint32Array(shared_rw_memory_size);
for (let j = 0; j < shared_rw_memory_size; j++) {
arr[shared_rw_memory_size - 1 - j] =
instance.exports.readSharedRWMemory(j);
}
// If we've buffered other content, put a space in between the items
if (msgStr !== "") {
msgStr += " ";
}
// Then append the value to the message we are creating
msgStr += fromArray32(arr).toString();
}
};
class WitnessCalculator {
constructor(instance, sanityCheck) {
this.instance = instance;
constructor(instance, sanityCheck) {
this.instance = instance;
this.version = this.instance.exports.getVersion();
this.n32 = this.instance.exports.getFieldNumLen32();
this.version = this.instance.exports.getVersion();
this.n32 = this.instance.exports.getFieldNumLen32();
this.instance.exports.getRawPrime();
const arr = new Uint32Array(this.n32);
for (let i=0; i<this.n32; i++) {
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
this.instance.exports.getRawPrime();
const arr = new Uint32Array(this.n32);
for (let i = 0; i < this.n32; i++) {
arr[this.n32 - 1 - i] = this.instance.exports.readSharedRWMemory(i);
}
this.prime = fromArray32(arr);
this.witnessSize = this.instance.exports.getWitnessSize();
this.sanityCheck = sanityCheck;
}
circom_version() {
return this.instance.exports.getVersion();
}
async _doCalculateWitness(input, sanityCheck) {
//input is assumed to be a map from signals to arrays of bigints
this.instance.exports.init(this.sanityCheck || sanityCheck ? 1 : 0);
const keys = Object.keys(input);
var input_counter = 0;
keys.forEach((k) => {
const h = fnvHash(k);
const hMSB = parseInt(h.slice(0, 8), 16);
const hLSB = parseInt(h.slice(8, 16), 16);
const fArr = flatArray(input[k]);
let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
if (signalSize < 0) {
throw new Error(`Signal ${k} not found\n`);
}
if (fArr.length < signalSize) {
throw new Error(`Not enough values for input signal ${k}\n`);
}
if (fArr.length > signalSize) {
throw new Error(`Too many values for input signal ${k}\n`);
}
for (let i = 0; i < fArr.length; i++) {
const arrFr = toArray32(BigInt(fArr[i]) % this.prime, this.n32);
for (let j = 0; j < this.n32; j++) {
this.instance.exports.writeSharedRWMemory(j, arrFr[this.n32 - 1 - j]);
}
this.prime = fromArray32(arr);
this.witnessSize = this.instance.exports.getWitnessSize();
this.sanityCheck = sanityCheck;
}
circom_version() {
return this.instance.exports.getVersion();
}
async _doCalculateWitness(input, sanityCheck) {
//input is assumed to be a map from signals to arrays of bigints
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
const keys = Object.keys(input);
var input_counter = 0;
keys.forEach( (k) => {
const h = fnvHash(k);
const hMSB = parseInt(h.slice(0,8), 16);
const hLSB = parseInt(h.slice(8,16), 16);
const fArr = flatArray(input[k]);
let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
if (signalSize < 0){
throw new Error(`Signal ${k} not found\n`);
}
if (fArr.length < signalSize) {
throw new Error(`Not enough values for input signal ${k}\n`);
}
if (fArr.length > signalSize) {
throw new Error(`Too many values for input signal ${k}\n`);
}
for (let i=0; i<fArr.length; i++) {
const arrFr = toArray32(BigInt(fArr[i])%this.prime,this.n32)
for (let j=0; j<this.n32; j++) {
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
}
try {
this.instance.exports.setInputSignal(hMSB, hLSB,i);
input_counter++;
} catch (err) {
// console.log(`After adding signal ${i} of ${k}`)
throw new Error(err);
}
}
});
if (input_counter < this.instance.exports.getInputSize()) {
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
}
}
async calculateWitness(input, sanityCheck) {
const w = [];
await this._doCalculateWitness(input, sanityCheck);
for (let i=0; i<this.witnessSize; i++) {
this.instance.exports.getWitness(i);
const arr = new Uint32Array(this.n32);
for (let j=0; j<this.n32; j++) {
arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
}
w.push(fromArray32(arr));
try {
this.instance.exports.setInputSignal(hMSB, hLSB, i);
input_counter++;
} catch (err) {
// console.log(`After adding signal ${i} of ${k}`)
throw new Error(err);
}
return w;
}
});
if (input_counter < this.instance.exports.getInputSize()) {
throw new Error(
`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`
);
}
}
async calculateBinWitness(input, sanityCheck) {
async calculateWitness(input, sanityCheck) {
const w = [];
const buff32 = new Uint32Array(this.witnessSize*this.n32);
const buff = new Uint8Array( buff32.buffer);
await this._doCalculateWitness(input, sanityCheck);
await this._doCalculateWitness(input, sanityCheck);
for (let i=0; i<this.witnessSize; i++) {
this.instance.exports.getWitness(i);
const pos = i*this.n32;
for (let j=0; j<this.n32; j++) {
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
}
}
return buff;
}
async calculateWTNSBin(input, sanityCheck) {
const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
const buff = new Uint8Array( buff32.buffer);
await this._doCalculateWitness(input, sanityCheck);
//"wtns"
buff[0] = "w".charCodeAt(0)
buff[1] = "t".charCodeAt(0)
buff[2] = "n".charCodeAt(0)
buff[3] = "s".charCodeAt(0)
//version 2
buff32[1] = 2;
//number of sections: 2
buff32[2] = 2;
//id section 1
buff32[3] = 1;
const n8 = this.n32*4;
//id section 1 length in 64bytes
const idSection1length = 8 + n8;
const idSection1lengthHex = idSection1length.toString(16);
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
//this.n32
buff32[6] = n8;
//prime number
this.instance.exports.getRawPrime();
var pos = 7;
for (let j=0; j<this.n32; j++) {
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
}
pos += this.n32;
// witness size
buff32[pos] = this.witnessSize;
pos++;
//id section 2
buff32[pos] = 2;
pos++;
// section 2 length
const idSection2length = n8*this.witnessSize;
const idSection2lengthHex = idSection2length.toString(16);
buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
pos += 2;
for (let i=0; i<this.witnessSize; i++) {
this.instance.exports.getWitness(i);
for (let j=0; j<this.n32; j++) {
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
}
pos += this.n32;
}
return buff;
for (let i = 0; i < this.witnessSize; i++) {
this.instance.exports.getWitness(i);
const arr = new Uint32Array(this.n32);
for (let j = 0; j < this.n32; j++) {
arr[this.n32 - 1 - j] = this.instance.exports.readSharedRWMemory(j);
}
w.push(fromArray32(arr));
}
return w;
}
async calculateBinWitness(input, sanityCheck) {
const buff32 = new Uint32Array(this.witnessSize * this.n32);
const buff = new Uint8Array(buff32.buffer);
await this._doCalculateWitness(input, sanityCheck);
for (let i = 0; i < this.witnessSize; i++) {
this.instance.exports.getWitness(i);
const pos = i * this.n32;
for (let j = 0; j < this.n32; j++) {
buff32[pos + j] = this.instance.exports.readSharedRWMemory(j);
}
}
return buff;
}
async calculateWTNSBin(input, sanityCheck) {
const buff32 = new Uint32Array(this.witnessSize * this.n32 + this.n32 + 11);
const buff = new Uint8Array(buff32.buffer);
await this._doCalculateWitness(input, sanityCheck);
//"wtns"
buff[0] = "w".charCodeAt(0);
buff[1] = "t".charCodeAt(0);
buff[2] = "n".charCodeAt(0);
buff[3] = "s".charCodeAt(0);
//version 2
buff32[1] = 2;
//number of sections: 2
buff32[2] = 2;
//id section 1
buff32[3] = 1;
const n8 = this.n32 * 4;
//id section 1 length in 64bytes
const idSection1length = 8 + n8;
const idSection1lengthHex = idSection1length.toString(16);
buff32[4] = parseInt(idSection1lengthHex.slice(0, 8), 16);
buff32[5] = parseInt(idSection1lengthHex.slice(8, 16), 16);
//this.n32
buff32[6] = n8;
//prime number
this.instance.exports.getRawPrime();
var pos = 7;
for (let j = 0; j < this.n32; j++) {
buff32[pos + j] = this.instance.exports.readSharedRWMemory(j);
}
pos += this.n32;
// witness size
buff32[pos] = this.witnessSize;
pos++;
//id section 2
buff32[pos] = 2;
pos++;
// section 2 length
const idSection2length = n8 * this.witnessSize;
const idSection2lengthHex = idSection2length.toString(16);
buff32[pos] = parseInt(idSection2lengthHex.slice(0, 8), 16);
buff32[pos + 1] = parseInt(idSection2lengthHex.slice(8, 16), 16);
pos += 2;
for (let i = 0; i < this.witnessSize; i++) {
this.instance.exports.getWitness(i);
for (let j = 0; j < this.n32; j++) {
buff32[pos + j] = this.instance.exports.readSharedRWMemory(j);
}
pos += this.n32;
}
return buff;
}
}
function toArray32(rem,size) {
const res = []; //new Uint32Array(size); //has no unshift
const radix = BigInt(0x100000000);
while (rem) {
res.unshift( Number(rem % radix));
rem = rem / radix;
function toArray32(rem, size) {
const res = []; //new Uint32Array(size); //has no unshift
const radix = BigInt(0x100000000);
while (rem) {
res.unshift(Number(rem % radix));
rem = rem / radix;
}
if (size) {
var i = size - res.length;
while (i > 0) {
res.unshift(0);
i--;
}
if (size) {
var i = size - res.length;
while (i>0) {
res.unshift(0);
i--;
}
}
return res;
}
return res;
}
function fromArray32(arr) { //returns a BigInt
var res = BigInt(0);
const radix = BigInt(0x100000000);
for (let i = 0; i<arr.length; i++) {
res = res*radix + BigInt(arr[i]);
}
return res;
function fromArray32(arr) {
//returns a BigInt
var res = BigInt(0);
const radix = BigInt(0x100000000);
for (let i = 0; i < arr.length; i++) {
res = res * radix + BigInt(arr[i]);
}
return res;
}
function flatArray(a) {
var res = [];
fillArray(res, a);
return res;
var res = [];
fillArray(res, a);
return res;
function fillArray(res, a) {
if (Array.isArray(a)) {
for (let i=0; i<a.length; i++) {
fillArray(res, a[i]);
}
} else {
res.push(a);
}
function fillArray(res, a) {
if (Array.isArray(a)) {
for (let i = 0; i < a.length; i++) {
fillArray(res, a[i]);
}
} else {
res.push(a);
}
}
}
function fnvHash(str) {
const uint64_max = BigInt(2) ** BigInt(64);
let hash = BigInt("0xCBF29CE484222325");
for (var i = 0; i < str.length; i++) {
hash ^= BigInt(str[i].charCodeAt());
hash *= BigInt(0x100000001B3);
hash %= uint64_max;
}
let shash = hash.toString(16);
let n = 16 - shash.length;
shash = '0'.repeat(n).concat(shash);
return shash;
const uint64_max = BigInt(2) ** BigInt(64);
let hash = BigInt("0xCBF29CE484222325");
for (var i = 0; i < str.length; i++) {
hash ^= BigInt(str[i].charCodeAt());
hash *= BigInt(0x100000001b3);
hash %= uint64_max;
}
let shash = hash.toString(16);
let n = 16 - shash.length;
shash = "0".repeat(n).concat(shash);
return shash;
}

View File

@@ -1,16 +1,12 @@
#![cfg(target_arch = "wasm32")]
extern crate wasm_bindgen;
extern crate web_sys;
use std::vec::Vec;
use js_sys::{BigInt as JsBigInt, Object, Uint8Array};
use num_bigint::BigInt;
use rln::public::{hash, poseidon_hash, RLN};
use std::vec::Vec;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[wasm_bindgen(js_name = initPanicHook)]
pub fn init_panic_hook() {
console_error_panic_hook::set_once();
}
@@ -61,24 +57,6 @@ macro_rules! call_with_output_and_error_msg {
};
}
// Macro to call_with_error_msg methods with arbitrary amount of arguments,
// First argument to the macro is context,
// second is the actual method on `RLNWrapper`
// rest are all other arguments to the method
macro_rules! call_with_error_msg {
($instance:expr, $method:ident, $error_msg:expr $(, $arg:expr)*) => {
{
let new_instance: &mut RLNWrapper = $instance.process();
if let Err(err) = new_instance.instance.$method($($arg.process()),*) {
Err(format!("Msg: {:#?}, Error: {:#?}", $error_msg, err))
} else {
Ok(())
}
}
}
}
macro_rules! call {
($instance:expr, $method:ident $(, $arg:expr)*) => {
{
@@ -181,19 +159,15 @@ impl<'a> ProcessArg for &'a [u8] {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[wasm_bindgen(js_name = newRLN)]
pub fn wasm_new(
zkey: Uint8Array,
vk: Uint8Array,
) -> Result<*mut RLNWrapper, String> {
let instance = RLN::new_with_params(zkey.to_vec(), vk.to_vec())
.map_err(|err| format!("{:#?}", err))?;
pub fn wasm_new(zkey: Uint8Array) -> Result<*mut RLNWrapper, String> {
let instance = RLN::new_with_params(zkey.to_vec()).map_err(|err| format!("{:#?}", err))?;
let wrapper = RLNWrapper { instance };
Ok(Box::into_raw(Box::new(wrapper)))
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[wasm_bindgen(js_name = RLNWitnessToJson)]
pub fn rln_witness_to_json(
#[wasm_bindgen(js_name = rlnWitnessToJson)]
pub fn wasm_rln_witness_to_json(
ctx: *mut RLNWrapper,
serialized_witness: Uint8Array,
) -> Result<Object, String> {
@@ -208,8 +182,8 @@ pub fn rln_witness_to_json(
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[wasm_bindgen]
pub fn generate_rln_proof_with_witness(
#[wasm_bindgen(js_name = generateRLNProofWithWitness)]
pub fn wasm_generate_rln_proof_with_witness(
ctx: *mut RLNWrapper,
calculated_witness: Vec<JsBigInt>,
serialized_witness: Uint8Array,

View File

@@ -3,16 +3,24 @@ const fs = require("fs");
// Utils functions for loading circom witness calculator and reading files from test
module.exports = {
read_file: function (path) {
return fs.readFileSync(path);
},
read_file: function (path) {
return fs.readFileSync(path);
},
calculateWitness: async function(circom_path, inputs){
const wc = require("resources/witness_calculator.js");
const wasmFile = fs.readFileSync(circom_path);
const wasmFileBuffer = wasmFile.slice(wasmFile.byteOffset, wasmFile.byteOffset + wasmFile.byteLength);
const witnessCalculator = await wc(wasmFileBuffer);
const calculatedWitness = await witnessCalculator.calculateWitness(inputs, false);
return JSON.stringify(calculatedWitness, (key, value) => typeof value === "bigint" ? value.toString() : value);
}
}
calculateWitness: async function (circom_path, inputs) {
const wc = require("../resources/witness_calculator.js");
const wasmFile = fs.readFileSync(circom_path);
const wasmFileBuffer = wasmFile.slice(
wasmFile.byteOffset,
wasmFile.byteOffset + wasmFile.byteLength
);
const witnessCalculator = await wc(wasmFileBuffer);
const calculatedWitness = await witnessCalculator.calculateWitness(
inputs,
false
);
return JSON.stringify(calculatedWitness, (key, value) =>
typeof value === "bigint" ? value.toString() : value
);
},
};

View File

@@ -2,18 +2,16 @@
#[cfg(test)]
mod tests {
use js_sys::{BigInt as JsBigInt, Object, Uint8Array};
use js_sys::{BigInt as JsBigInt, Date, Object, Uint8Array};
use rln::circuit::{Fr, TEST_TREE_HEIGHT};
use rln::hashers::{hash_to_field, poseidon_hash};
use rln::utils::{bytes_le_to_fr, fr_to_bytes_le, normalize_usize};
use rln_wasm::*;
use rln::poseidon_tree::PoseidonTree;
use rln::utils::vec_fr_to_bytes_le;
use rln::protocol::{prepare_verify_input, rln_witness_from_values, serialize_witness};
use rln::utils::{bytes_le_to_fr, fr_to_bytes_le};
use rln_wasm::*;
use wasm_bindgen::{prelude::*, JsValue};
use wasm_bindgen_test::wasm_bindgen_test;
use zerokit_utils::merkle_tree::merkle_tree::ZerokitMerkleTree;
use zerokit_utils::ZerokitMerkleProof;
use rln::utils::vec_u8_to_bytes_le;
#[wasm_bindgen(module = "src/utils.js")]
extern "C" {
@@ -24,100 +22,268 @@ mod tests {
async fn calculateWitness(circom_path: &str, input: Object) -> Result<JsValue, JsValue>;
}
#[cfg(feature = "arkzkey")]
const ZKEY_PATH: &str = "../rln/resources/tree_height_20/rln_final.arkzkey";
#[cfg(not(feature = "arkzkey"))]
const ZKEY_PATH: &str = "../rln/resources/tree_height_20/rln_final.zkey";
const CIRCOM_PATH: &str = "../rln/resources/tree_height_20/rln.wasm";
#[wasm_bindgen_test]
pub async fn test_basic_flow() {
let tree_height = TEST_TREE_HEIGHT;
let circom_path = format!("../rln/resources/tree_height_{TEST_TREE_HEIGHT}/rln.wasm");
let zkey_path = format!("../rln/resources/tree_height_{TEST_TREE_HEIGHT}/rln_final.zkey");
let vk_path =
format!("../rln/resources/tree_height_{TEST_TREE_HEIGHT}/verification_key.arkvkey");
let zkey = read_file(&zkey_path).unwrap();
let vk = read_file(&vk_path).unwrap();
pub async fn rln_wasm_benchmark() {
let mut results = String::from("\nbenchmarks:\n");
let iterations = 10;
// Creating an instance of RLN
let rln_instance = wasm_new(zkey, vk).unwrap();
let zkey = read_file(&ZKEY_PATH).expect("Failed to read zkey file");
let mut tree = PoseidonTree::default(TEST_TREE_HEIGHT).unwrap();
// Benchmark wasm_new
let start_wasm_new = Date::now();
for _ in 0..iterations {
let _ = wasm_new(zkey.clone()).expect("Failed to create RLN instance");
}
let wasm_new_result = Date::now() - start_wasm_new;
// Creating membership key
let mem_keys = wasm_key_gen(rln_instance).unwrap();
// Create RLN instance for other benchmarks
let rln_instance = wasm_new(zkey).expect("Failed to create RLN instance");
let mut tree = PoseidonTree::default(TEST_TREE_HEIGHT).expect("Failed to create tree");
// Benchmark wasm_key_gen
let start_wasm_key_gen = Date::now();
for _ in 0..iterations {
let _ = wasm_key_gen(rln_instance).expect("Failed to generate keys");
}
let wasm_key_gen_result = Date::now() - start_wasm_key_gen;
// Generate identity pair for other benchmarks
let mem_keys = wasm_key_gen(rln_instance).expect("Failed to generate keys");
let id_key = mem_keys.subarray(0, 32);
let id_commitment = mem_keys.subarray(32, 64);
let (identity_secret_hash, _) = bytes_le_to_fr(&id_key.to_vec());
let (id_commitment, _) = bytes_le_to_fr(&mem_keys.subarray(32, 64).to_vec());
// Prepare the message
let signal = b"Hello World";
let epoch = hash_to_field(b"test-epoch");
let rln_identifier = hash_to_field(b"test-rln-identifier");
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
let identity_index = tree.leaves_set();
let user_message_limit = Fr::from(100);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
tree.update_next(rate_commitment)
.expect("Failed to update tree");
let message_id = Fr::from(0);
let signal: [u8; 32] = [0; 32];
let x = hash_to_field(&signal);
let merkle_proof = tree
.proof(identity_index)
.expect("Failed to generate merkle proof");
let rln_witness = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x,
external_nullifier,
user_message_limit,
message_id,
)
.expect("Failed to create RLN witness");
let serialized_witness =
serialize_witness(&rln_witness).expect("Failed to serialize witness");
let witness_buffer = Uint8Array::from(&serialized_witness[..]);
let json_inputs = wasm_rln_witness_to_json(rln_instance, witness_buffer.clone())
.expect("Failed to convert witness to JSON");
// Benchmark calculateWitness
let start_calculate_witness = Date::now();
for _ in 0..iterations {
let _ = calculateWitness(&CIRCOM_PATH, json_inputs.clone())
.await
.expect("Failed to calculate witness");
}
let calculate_witness_result = Date::now() - start_calculate_witness;
// Calculate witness for other benchmarks
let calculated_witness_json = calculateWitness(&CIRCOM_PATH, json_inputs)
.await
.expect("Failed to calculate witness")
.as_string()
.expect("Failed to convert calculated witness to string");
let calculated_witness_vec_str: Vec<String> =
serde_json::from_str(&calculated_witness_json).expect("Failed to parse JSON");
let calculated_witness: Vec<JsBigInt> = calculated_witness_vec_str
.iter()
.map(|x| JsBigInt::new(&x.into()).expect("Failed to create JsBigInt"))
.collect();
// Benchmark wasm_generate_rln_proof_with_witness
let start_wasm_generate_rln_proof_with_witness = Date::now();
for _ in 0..iterations {
let _ = wasm_generate_rln_proof_with_witness(
rln_instance,
calculated_witness.clone(),
witness_buffer.clone(),
)
.expect("Failed to generate proof");
}
let wasm_generate_rln_proof_with_witness_result =
Date::now() - start_wasm_generate_rln_proof_with_witness;
// Generate a proof for other benchmarks
let proof =
wasm_generate_rln_proof_with_witness(rln_instance, calculated_witness, witness_buffer)
.expect("Failed to generate proof");
let proof_data = proof.to_vec();
let verify_input = prepare_verify_input(proof_data, &signal);
let input_buffer = Uint8Array::from(&verify_input[..]);
let root = tree.root();
let roots_serialized = fr_to_bytes_le(&root);
let roots_buffer = Uint8Array::from(&roots_serialized[..]);
// Benchmark wasm_verify_with_roots
let start_wasm_verify_with_roots = Date::now();
for _ in 0..iterations {
let _ =
wasm_verify_with_roots(rln_instance, input_buffer.clone(), roots_buffer.clone())
.expect("Failed to verify proof");
}
let wasm_verify_with_roots_result = Date::now() - start_wasm_verify_with_roots;
// Verify the proof with the root
let is_proof_valid = wasm_verify_with_roots(rln_instance, input_buffer, roots_buffer)
.expect("Failed to verify proof");
assert!(is_proof_valid, "verification failed");
// Format and display results
let format_duration = |duration_ms: f64| -> String {
let avg_ms = duration_ms / (iterations as f64);
if avg_ms >= 1000.0 {
format!("{:.3} s", avg_ms / 1000.0)
} else {
format!("{:.3} ms", avg_ms)
}
};
results.push_str(&format!("wasm_new: {}\n", format_duration(wasm_new_result)));
results.push_str(&format!(
"wasm_key_gen: {}\n",
format_duration(wasm_key_gen_result)
));
results.push_str(&format!(
"calculateWitness: {}\n",
format_duration(calculate_witness_result)
));
results.push_str(&format!(
"wasm_generate_rln_proof_with_witness: {}\n",
format_duration(wasm_generate_rln_proof_with_witness_result)
));
results.push_str(&format!(
"wasm_verify_with_roots: {}\n",
format_duration(wasm_verify_with_roots_result)
));
// Log the results
wasm_bindgen_test::console_log!("{results}");
}
#[wasm_bindgen_test]
pub async fn rln_wasm_test() {
// Read the zkey file
let zkey = read_file(&ZKEY_PATH).expect("Failed to read zkey file");
// Create RLN instance and separated tree
let rln_instance = wasm_new(zkey).expect("Failed to create RLN instance");
let mut tree = PoseidonTree::default(TEST_TREE_HEIGHT).expect("Failed to create tree");
// Setting up the epoch and rln_identifier
let epoch = hash_to_field(b"test-epoch");
let rln_identifier = hash_to_field(b"test-rln-identifier");
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
let external_nullifier = fr_to_bytes_le(&external_nullifier);
// Generate identity pair
let mem_keys = wasm_key_gen(rln_instance).expect("Failed to generate keys");
let (identity_secret_hash, _) = bytes_le_to_fr(&mem_keys.subarray(0, 32).to_vec());
let (id_commitment, _) = bytes_le_to_fr(&mem_keys.subarray(32, 64).to_vec());
// Get index of the identity
let identity_index = tree.leaves_set();
// Setting up the user message limit
let user_message_limit = Fr::from(100);
let message_id = fr_to_bytes_le(&Fr::from(0));
let (id_commitment_fr, _) = bytes_le_to_fr(&id_commitment.to_vec()[..]);
let rate_commitment = poseidon_hash(&[id_commitment_fr, user_message_limit]);
tree.update_next(rate_commitment).unwrap();
// Updating the tree with the rate commitment
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
tree.update_next(rate_commitment)
.expect("Failed to update tree");
let x = hash_to_field(signal);
let merkle_proof = tree.proof(identity_index).expect("proof should exist");
let path_elements = merkle_proof.get_path_elements();
let identity_path_index = merkle_proof.get_path_index();
// Generate merkle proof
let merkle_proof = tree
.proof(identity_index)
.expect("Failed to generate merkle proof");
// Create message id and signal
let message_id = Fr::from(0);
let signal: [u8; 32] = [0; 32];
let x = hash_to_field(&signal);
// Serializing the message
let mut serialized_vec: Vec<u8> = Vec::new();
serialized_vec.append(&mut id_key.to_vec());
serialized_vec.append(&mut fr_to_bytes_le(&user_message_limit).to_vec());
serialized_vec.append(&mut message_id.to_vec());
serialized_vec.append(&mut vec_fr_to_bytes_le(&path_elements).unwrap());
serialized_vec.append(&mut vec_u8_to_bytes_le(&identity_path_index).unwrap());
serialized_vec.append(&mut fr_to_bytes_le(&x));
serialized_vec.append(&mut external_nullifier.to_vec());
let serialized_message = Uint8Array::from(&serialized_vec[..]);
// Prepare input for witness calculation
let rln_witness = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x,
external_nullifier,
user_message_limit,
message_id,
)
.expect("Failed to create RLN witness");
// Serialize the rln witness
let serialized_witness =
serialize_witness(&rln_witness).expect("Failed to serialize witness");
// Convert the serialized witness to a Uint8Array
let witness_buffer = Uint8Array::from(&serialized_witness[..]);
// Obtaining inputs that should be sent to circom witness calculator
let json_inputs =
rln_witness_to_json(rln_instance, serialized_message.clone()).unwrap();
let json_inputs = wasm_rln_witness_to_json(rln_instance, witness_buffer.clone())
.expect("Failed to convert witness to JSON");
// Calculating witness with JS
// (Using a JSON since wasm_bindgen does not like Result<Vec<JsBigInt>,JsValue>)
let calculated_witness_json = calculateWitness(&circom_path, json_inputs)
let calculated_witness_json = calculateWitness(&CIRCOM_PATH, json_inputs)
.await
.unwrap()
.expect("Failed to calculate witness")
.as_string()
.unwrap();
.expect("Failed to convert calculated witness to string");
let calculated_witness_vec_str: Vec<String> =
serde_json::from_str(&calculated_witness_json).unwrap();
serde_json::from_str(&calculated_witness_json).expect("Failed to parse JSON");
let calculated_witness: Vec<JsBigInt> = calculated_witness_vec_str
.iter()
.map(|x| JsBigInt::new(&x.into()).unwrap())
.map(|x| JsBigInt::new(&x.into()).expect("Failed to create JsBigInt"))
.collect();
// Generating proof
let proof = generate_rln_proof_with_witness(
rln_instance,
calculated_witness.into(),
serialized_message,
)
.unwrap();
// Generate a proof from the calculated witness
let proof =
wasm_generate_rln_proof_with_witness(rln_instance, calculated_witness, witness_buffer)
.expect("Failed to generate proof");
// Add signal_len | signal
let mut proof_bytes = proof.to_vec();
proof_bytes.append(&mut normalize_usize(signal.len()));
proof_bytes.append(&mut signal.to_vec());
let proof_with_signal = Uint8Array::from(&proof_bytes[..]);
// Validating Proof with Roots
// Prepare the root for verification
let root = tree.root();
let root_le = fr_to_bytes_le(&root);
let roots = Uint8Array::from(&root_le[..]);
let proof_with_signal = Uint8Array::from(&proof_bytes[..]);
let roots_serialized = fr_to_bytes_le(&root);
let roots_buffer = Uint8Array::from(&roots_serialized[..]);
let is_proof_valid = wasm_verify_with_roots(rln_instance, proof_with_signal, roots);
assert!(is_proof_valid.unwrap(), "verifying proof with roots failed");
// Prepare input for proof verification
let proof_data = proof.to_vec();
let verify_input = prepare_verify_input(proof_data, &signal);
let input_buffer = Uint8Array::from(&verify_input[..]);
// Verify the proof with the root
let is_proof_valid = wasm_verify_with_roots(rln_instance, input_buffer, roots_buffer)
.expect("Failed to verify proof");
assert!(is_proof_valid, "verification failed");
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "rln"
version = "0.6.1"
version = "0.7.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "APIs to manage, compute and verify zkSNARK proofs and RLN primitives"
@@ -15,88 +15,81 @@ bench = false
# This flag disable cargo doctests, i.e. testing example code-snippets in documentation
doctest = false
[dependencies]
# ZKP Generation
ark-ec = { version = "=0.4.1", default-features = false }
ark-ff = { version = "=0.4.1", default-features = false, features = ["asm"] }
ark-std = { version = "=0.4.0", default-features = false }
ark-bn254 = { version = "=0.4.0" }
ark-groth16 = { version = "=0.4.0", features = [
ark-bn254 = { version = "0.5.0", features = ["std"] }
ark-relations = { version = "0.5.1", features = ["std"] }
ark-ff = { version = "0.5.0", default-features = false, features = [
"parallel",
], default-features = false }
ark-relations = { version = "=0.4.0", default-features = false, features = [
"std",
] }
ark-serialize = { version = "=0.4.1", default-features = false }
ark-circom = { version = "=0.1.0", default-features = false, features = [
"circom-2",
ark-ec = { version = "0.5.0", default-features = false, features = [
"parallel",
] }
ark-zkey = { version = "0.1.0", optional = true, default-features = false }
# WASM
wasmer = { version = "=2.3.0", default-features = false }
ark-std = { version = "0.5.0", default-features = false, features = [
"parallel",
] }
ark-poly = { version = "0.5.0", default-features = false, features = [
"parallel",
] }
ark-groth16 = { version = "0.5.0", default-features = false, features = [
"parallel",
] }
ark-serialize = { version = "0.5.0", default-features = false, features = [
"parallel",
] }
# error handling
color-eyre = "=0.6.2"
thiserror = "=1.0.39"
color-eyre = "0.6.3"
thiserror = "2.0.12"
# utilities
cfg-if = "=1.0"
num-bigint = { version = "=0.4.3", default-features = false, features = [
byteorder = "1.5.0"
cfg-if = "1.0"
num-bigint = { version = "0.4.6", default-features = false, features = [
"rand",
"std",
] }
num-traits = "=0.2.15"
once_cell = "=1.17.1"
lazy_static = "=1.4.0"
rand = "=0.8.5"
rand_chacha = "=0.3.1"
tiny-keccak = { version = "=2.0.2", features = ["keccak"] }
utils = { package = "zerokit_utils", version = "=0.5.1", path = "../utils/", default-features = false }
num-traits = "0.2.19"
once_cell = "1.21.3"
lazy_static = "1.5.0"
rand = "0.8.5"
rand_chacha = "0.3.1"
ruint = { version = "1.14.0", features = ["rand", "serde", "ark-ff-04"] }
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
utils = { package = "zerokit_utils", version = "0.5.2", path = "../utils", default-features = false }
# serialization
serde_json = "=1.0.96"
serde = { version = "=1.0.163", features = ["derive"] }
prost = "0.13.5"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
document-features = { version = "=0.2.10", optional = true }
document-features = { version = "0.2.11", optional = true }
[dev-dependencies]
sled = "=0.34.7"
criterion = { version = "=0.4.0", features = ["html_reports"] }
sled = "0.34.7"
criterion = { version = "0.4.0", features = ["html_reports"] }
[features]
default = ["parallel", "wasmer/sys-default", "pmtree-ft"]
parallel = [
"ark-ec/parallel",
"ark-ff/parallel",
"ark-std/parallel",
"ark-groth16/parallel",
"utils/parallel",
]
wasm = ["wasmer/js", "wasmer/std"]
default = ["pmtree-ft"]
fullmerkletree = ["default"]
arkzkey = ["ark-zkey"]
stateless = []
arkzkey = []
# Note: pmtree feature is still experimental
pmtree-ft = ["utils/pmtree-ft"]
[[bench]]
name = "pmtree_benchmark"
harness = false
[[bench]]
name = "circuit_loading_benchmark"
harness = false
[[bench]]
name = "circuit_loading_arkzkey_benchmark"
harness = false
required-features = ["arkzkey"]
[[bench]]
name = "circuit_deser_benchmark"
name = "circuit_loading_benchmark"
harness = false
[[bench]]
name = "pmtree_benchmark"
harness = false
[[bench]]

View File

@@ -2,9 +2,9 @@
command = "cargo"
args = ["build", "--release"]
[tasks.test_default]
[tasks.test]
command = "cargo"
args = ["test", "--release"]
args = ["test", "--release", "--", "--nocapture"]
[tasks.test_stateless]
command = "cargo"

View File

@@ -1,65 +1,17 @@
# Zerokit RLN Module
This module provides APIs to manage, compute and verify [RLN](https://rfc.vac.dev/spec/32/) zkSNARK proofs and RLN primitives.
[![Crates.io](https://img.shields.io/crates/v/rln.svg)](https://crates.io/crates/rln)
## Pre-requisites
The Zerokit RLN Module provides a Rust implementation for working with Rate-Limiting Nullifier [RLN](https://rfc.vac.dev/spec/32/) zkSNARK proofs and primitives. This module allows you to:
### Install dependencies and clone repo
- Generate and verify RLN proofs
- Work with Merkle trees for commitment storage
- Implement rate-limiting mechanisms for distributed systems
```sh
make installdeps
git clone https://github.com/vacp2p/zerokit.git
cd zerokit/rln
```
## Quick Start
### Build and Test
To build and test, run the following commands within the module folder
```bash
cargo make build
cargo make test
```
### Compile ZK circuits
The `rln` (https://github.com/rate-limiting-nullifier/circom-rln) repository, which contains the RLN circuit implementation is a submodule of zerokit RLN.
To compile the RLN circuit
```sh
# Update submodules
git submodule update --init --recursive
# Install rln dependencies
cd vendor/rln/ && npm install
# Build circuits
./scripts/build-circuits.sh rln
# Copy over assets
cp build/zkeyFiles/rln-final.zkey ../../resources/tree_height_15
cp build/zkeyFiles/rln.wasm ../../resources/tree_height_15
```
Note that the above code snippet will compile a RLN circuit with a Merkle tree of height equal `15` based on the default value set in `vendor/rln/circuit/rln.circom`.
In order to compile a RLN circuit with Merkle tree height `N`, it suffices to change `vendor/rln/circuit/rln.circom` to
```
pragma circom 2.0.0;
include "./rln-base.circom";
component main {public [x, epoch, rln_identifier ]} = RLN(N);
```
However, if `N` is too big, this might require a bigger Powers of Tau ceremony than the one hardcoded in `./scripts/build-circuits.sh`, which is `2^14`.
In such case we refer to the official [Circom documentation](https://docs.circom.io/getting-started/proving-circuits/#powers-of-tau) for instructions on how to run an appropriate Powers of Tau ceremony and Phase 2 in order to compile the desired circuit.
Currently, the `rln` module comes with 2 [pre-compiled](https://github.com/vacp2p/zerokit/tree/master/rln/resources) RLN circuits having Merkle tree of height `20` and `32`, respectively.
## Getting started
> [!IMPORTANT]
> Version 0.6.1 is required for WASM support or x32 architecture. Current version doesn't support these platforms due to dependency issues. WASM support will return in a future release.
### Add RLN as dependency
@@ -70,136 +22,239 @@ We start by adding zerokit RLN to our `Cargo.toml`
rln = { git = "https://github.com/vacp2p/zerokit" }
```
### Create a RLN object
## Basic Usage Example
First, we need to create a RLN object for a chosen input Merkle tree size.
Note that we need to pass to RLN object constructor the path where the circuit (`rln.wasm`, built for the input tree size), the corresponding proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) and verification key (`verification_key.arkvkey`, optional) are found.
Note that we need to pass to RLN object constructor the path where the graph file (`graph.bin`, built for the input tree size), the corresponding proving key (`rln_final.zkey`) or (`rln_final_uncompr.arkzkey`) and verification key (`verification_key.arkvkey`, optional) are found.
In the following we will use [cursors](https://doc.rust-lang.org/std/io/struct.Cursor.html) as readers/writers for interfacing with RLN public APIs.
```rust
use rln::protocol::*;
use rln::public::*;
use std::io::Cursor;
// We set the RLN parameters:
// - the tree height;
// - the tree config, if it is not defined, the default value will be set
let tree_height = 20;
let input = Cursor::new(json!({}).to_string());
use rln::{
circuit::Fr,
hashers::{hash_to_field, poseidon_hash},
protocol::{keygen, prepare_prove_input, prepare_verify_input},
public::RLN,
utils::fr_to_bytes_le,
};
use serde_json::json;
// We create a new RLN instance
let mut rln = RLN::new(tree_height, input);
fn main() {
// 1. Initialize RLN with parameters:
// - the tree height;
// - the tree config, if it is not defined, the default value will be set
let tree_height = 20;
let input = Cursor::new(json!({}).to_string());
let mut rln = RLN::new(tree_height, input).unwrap();
// 2. Generate an identity keypair
let (identity_secret_hash, id_commitment) = keygen();
// 3. Add a rate commitment to the Merkle tree
let id_index = 10;
let user_message_limit = Fr::from(10);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
let mut buffer = Cursor::new(fr_to_bytes_le(&rate_commitment));
rln.set_leaf(id_index, &mut buffer).unwrap();
// 4. Set up external nullifier (epoch + app identifier)
// We generate epoch from a date seed and we ensure is
// mapped to a field element by hashing-to-field its content
let epoch = hash_to_field(b"Today at noon, this year");
// We generate rln_identifier from a date seed and we ensure is
// mapped to a field element by hashing-to-field its content
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < user_message_limit
let message_id = Fr::from(1);
// 5. Generate and verify a proof for a message
let signal = b"RLN is awesome";
// 6. Prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | external_nullifier<32> | user_message_limit<32> | message_id<32> | signal_len<8> | signal<var> ]
let prove_input = prepare_prove_input(
identity_secret_hash,
id_index,
user_message_limit,
message_id,
external_nullifier,
signal,
);
// 7. Generate a RLN proof
// We generate a RLN proof for proof_input
let mut input_buffer = Cursor::new(prove_input);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut input_buffer, &mut output_buffer)
.unwrap();
// We get the public outputs returned by the circuit evaluation
// The byte vector `proof_data` is serialized as `[ zk-proof | tree_root | external_nullifier | share_x | share_y | nullifier ]`.
let proof_data = output_buffer.into_inner();
// 8. Verify a RLN proof
// Input buffer is serialized as `[proof_data | signal_len | signal ]`, where `proof_data` is (computed as) the output obtained by `generate_rln_proof`.
let verify_data = prepare_verify_input(proof_data, signal);
// We verify the zk-proof against the provided proof values
let mut input_buffer = Cursor::new(verify_data);
let verified = rln.verify_rln_proof(&mut input_buffer).unwrap();
// We ensure the proof is valid
assert!(verified);
}
```
### Generate an identity keypair
We generate an identity keypair
```rust
// We generate an identity pair
let mut buffer = Cursor::new(Vec::<u8>::new());
rln.key_gen(&mut buffer).unwrap();
// We deserialize the keygen output to obtain
// the identity_secret and id_commitment
let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner());
```
### Add Rate commitment to the RLN Merkle tree
```rust
// We define the tree index where id_commitment will be added
let id_index = 10;
let user_message_limit = 10;
// We serialize id_commitment and pass it to set_leaf
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
let mut buffer = Cursor::new(serialize_field_element(rate_commitment));
rln.set_leaf(id_index, &mut buffer).unwrap();
```
Note that when tree leaves are not explicitly set by the user (in this example, all those with index less and greater than `10`), their values is set to an hardcoded default (all-`0` bytes in current implementation).
### Set external nullifier
### Comments for the code above for point 4
The `external nullifier` includes two parameters.
The first one is `epoch` and it's used to identify messages received in a certain time frame. It usually corresponds to the current UNIX time but can also be set to a random value or generated by a seed, provided that it corresponds to a field element.
The first one is `epoch` and it's used to identify messages received in a certain time frame.
It usually corresponds to the current UNIX time but can also be set to a random value or generated by a seed, provided that it corresponds to a field element.
The second one is `rln_identifier` and it's used to prevent a RLN ZK proof generated for one application to be re-used in another one.
```rust
// We generate epoch from a date seed and we ensure is
// mapped to a field element by hashing-to-field its content
let epoch = hash_to_field(b"Today at noon, this year");
// We generate rln_identifier from a date seed and we ensure is
// mapped to a field element by hashing-to-field its content
let rln_identifier = hash_to_field(b"test-rln-identifier");
### Features
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
- **Multiple Backend Support**: Choose between different zkey formats with feature flags
- `arkzkey`: Use the optimized Arkworks-compatible zkey format (faster loading)
- `stateless`: For stateless proof verification
- **Pre-compiled Circuits**: Ready-to-use circuits with Merkle tree height of 20
## Building and Testing
### Prerequisites
```sh
git clone https://github.com/vacp2p/zerokit.git
make installdeps
cd zerokit/rln
```
### Set signal
### Build Commands
The signal is the message for which we are computing a RLN proof.
```sh
# Build with default features
cargo make build
```rust
// We set our signal
let signal = b"RLN is awesome";
# Test with default features
cargo make test
# Test with specific features
cargo make test_arkzkey # For arkzkey feature
cargo make test_stateless # For stateless feature
```
### Generate a RLN proof
## Advanced: Custom Circuit Compilation
We prepare the input to the proof generation routine.
The `rln` (<https://github.com/rate-limiting-nullifier/circom-rln>) repository, which contains the RLN circuit implementation is using for pre-compiled RLN circuit for zerokit RLN.
If you want to compile your own RLN circuit, you can follow the instructions below.
Input buffer is serialized as `[ identity_key | id_index | external_nullifier | user_message_limit | message_id | signal_len | signal ]`.
### 1. Compile ZK Circuits for getting the zkey and verification key files
```rust
// We prepare input to the proof generation routine
let proof_input = prepare_prove_input(identity_secret_hash, id_index, external_nullifier, signal);
This script actually generates not only the zkey and verification key files for the RLN circuit, but also the execution wasm file used for witness calculation.
However, the wasm file is not needed for the `rln` module, because current implementation uses the iden3 graph file for witness calculation.
This graph file is generated by the `circom-witnesscalc` tool in [step 2](#2-generate-witness-calculation-graph).
To customize the circuit parameters, modify `circom-rln/circuits/rln.circom`:
```circom
pragma circom 2.1.0;
include "./rln.circom";
component main { public [x, externalNullifier] } = RLN(N, M);
```
We are now ready to generate a RLN ZK proof along with the _public outputs_ of the ZK circuit evaluation.
Where:
```rust
- `N`: Merkle tree height, determining the maximum membership capacity (2^N members).
// We generate a RLN proof for proof_input
let mut in_buffer = Cursor::new(proof_input);
let mut out_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut in_buffer, &mut out_buffer)
.unwrap();
- `M`: Bit size for range checks, setting an upper bound for the number of messages per epoch (2^M messages).
// We get the public outputs returned by the circuit evaluation
let proof_data = out_buffer.into_inner();
> [!NOTE]
> However, if `N` is too big, this might require a larger Powers of Tau ceremony than the one hardcoded in `./scripts/build-circuits.sh`, which is `2^14`. \
> In such case, we refer to the official [Circom documentation](https://docs.circom.io/getting-started/proving-circuits/#powers-of-tau) for instructions on how to run an appropriate Powers of Tau ceremony and Phase 2 in order to compile the desired circuit. \
> Additionally, while `M` sets an upper bound on the number of messages per epoch (`2^M`), you can configure lower message limit for your use case, as long as it satisfies `user_message_limit ≤ 2^M`. \
> Currently, the `rln` module comes with a [pre-compiled](https://github.com/vacp2p/zerokit/tree/master/rln/resources) RLN circuit with a Merkle tree of height `20` and a bit size of `16`, allowing up to `2^20` registered members and a `2^16` message limit per epoch.
#### Install circom compiler
You can follow the instructions below or refer to the [installing Circom](https://docs.circom.io/getting-started/installation/#installing-circom) guide for more details, but make sure to use the specific version `v2.1.0`.
```sh
# Clone the circom repository
git clone https://github.com/iden3/circom.git
# Checkout the specific version
cd circom && git checkout v2.1.0
# Build the circom compiler
cargo build --release
# Install the circom binary globally
cargo install --path circom
# Check the circom version to ensure it's v2.1.0
circom --version
```
The byte vector `proof_data` is serialized as `[ zk-proof | tree_root | external_nullifier | share_x | share_y | nullifier ]`.
#### Generate the zkey and verification key files example
### Verify a RLN proof
```sh
# Clone the circom-rln repository
git clone https://github.com/rate-limiting-nullifier/circom-rln
We prepare the input to the proof verification routine.
# Install dependencies
cd circom-rln && npm install
Input buffer is serialized as `[proof_data | signal_len | signal ]`, where `proof_data` is (computed as) the output obtained by `generate_rln_proof`.
# Build circuits
./scripts/build-circuits.sh rln
```rust
// We prepare input to the proof verification routine
let verify_data = prepare_verify_input(proof_data, signal);
// We verify the zk-proof against the provided proof values
let mut in_buffer = Cursor::new(verify_data);
let verified = rln.verify(&mut in_buffer).unwrap();
# Use the generated zkey file in subsequent steps
cp zkeyFiles/rln/final.zkey <path_to_rln_final.zkey>
```
We check if the proof verification was successful:
### 2. Generate Witness Calculation Graph
```rust
// We ensure the proof is valid
assert!(verified);
The execution graph file used for witness calculation can be compiled following instructions in the [circom-witnesscalc](https://github.com/iden3/circom-witnesscalc) repository.
As mentioned in step 1, we should use `rln.circom` file from `circom-rln` repository.
```sh
# Clone the circom-witnesscalc repository
git clone https://github.com/iden3/circom-witnesscalc
# Load the submodules
cd circom-witnesscalc && git submodule update --init --recursive
# Build the circom-witnesscalc tool
cargo build
# Generate the witness calculation graph
cargo run --package circom_witnesscalc --bin build-circuit ../circom-rln/circuits/rln.circom <path_to_graph.bin>
```
## Get involved!
The `rln` module comes with [pre-compiled](https://github.com/vacp2p/zerokit/tree/master/rln/resources) execution graph files for the RLN circuit.
### 3. Generate Arkzkey Representation for zkey and verification key files
For faster loading, compile the zkey file into the arkzkey format using [ark-zkey](https://github.com/seemenkina/ark-zkey). This is fork of the [original](https://github.com/zkmopro/ark-zkey) repository with the uncompressed zkey support.
```sh
# Clone the ark-zkey repository
git clone https://github.com/seemenkina/ark-zkey.git
# Build the ark-zkey tool
cd ark-zkey && cargo build
# Generate the arkzkey representation for the zkey file
cargo run --bin arkzkey-util <path_to_rln_final.zkey>
```
Currently, the `rln` module comes with [pre-compiled](https://github.com/vacp2p/zerokit/tree/master/rln/resources) arkzkey keys for the RLN circuit.
## Get involved
Zerokit RLN public and FFI APIs allow interaction with many more features than what briefly showcased above.
@@ -210,3 +265,20 @@ cargo doc --no-deps
```
and look at unit tests to have an hint on how to interface and use them.
## Detailed Protocol Flow
1. **Identity Creation**: Generate a secret key and commitment
2. **Rate Commitment**: Add commitment to a Merkle tree
3. **External Nullifier Setup**: Combine epoch and application identifier
4. **Proof Generation**: Create a zkSNARK proof that:
- Proves membership in the Merkle tree
- Ensures rate-limiting constraints are satisfied
- Generates a nullifier to prevent double-usage
5. **Proof Verification**: Verify the proof without revealing the prover's identity
## Getting Involved
- Check the [unit tests](https://github.com/vacp2p/zerokit/tree/master/rln/tests) for more usage examples
- [RFC specification](https://rfc.vac.dev/spec/32/) for the Rate-Limiting Nullifier protocol
- [GitHub repository](https://github.com/vacp2p/zerokit) for the latest updates

View File

@@ -1,22 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use rln::circuit::{vk_from_ark_serialized, VK_BYTES};
// Here we benchmark how long the deserialization of the
// verifying_key takes, only testing the json => verifying_key conversion,
// and skipping conversion from bytes => string => serde_json::Value
pub fn vk_deserialize_benchmark(c: &mut Criterion) {
let vk = VK_BYTES;
c.bench_function("vk::vk_from_ark_serialized", |b| {
b.iter(|| {
let _ = vk_from_ark_serialized(vk);
})
});
}
criterion_group! {
name = benches;
config = Criterion::default().measurement_time(std::time::Duration::from_secs(10));
targets = vk_deserialize_benchmark
}
criterion_main!(benches);

View File

@@ -1,11 +1,8 @@
use criterion::{criterion_group, criterion_main, Criterion};
use rln::circuit::{
read_arkzkey_from_bytes_compressed, read_arkzkey_from_bytes_uncompressed, ARKZKEY_BYTES,
ARKZKEY_BYTES_UNCOMPR,
};
use rln::circuit::{read_arkzkey_from_bytes_uncompressed, ARKZKEY_BYTES};
pub fn uncompressed_bench(c: &mut Criterion) {
let arkzkey = ARKZKEY_BYTES_UNCOMPR.to_vec();
let arkzkey = ARKZKEY_BYTES.to_vec();
let size = arkzkey.len() as f32;
println!(
"Size of uncompressed arkzkey: {:.2?} MB",
@@ -19,25 +16,10 @@ pub fn uncompressed_bench(c: &mut Criterion) {
})
});
}
pub fn compressed_bench(c: &mut Criterion) {
let arkzkey = ARKZKEY_BYTES.to_vec();
let size = arkzkey.len() as f32;
println!(
"Size of compressed arkzkey: {:.2?} MB",
size / 1024.0 / 1024.0
);
c.bench_function("arkzkey::arkzkey_from_raw_compressed", |b| {
b.iter(|| {
let r = read_arkzkey_from_bytes_compressed(&arkzkey);
assert_eq!(r.is_ok(), true);
})
});
}
criterion_group! {
name = benches;
config = Criterion::default().measurement_time(std::time::Duration::from_secs(250));
targets = uncompressed_bench, compressed_bench
config = Criterion::default().sample_size(10);
targets = uncompressed_bench
}
criterion_main!(benches);

View File

@@ -1,5 +1,5 @@
use ark_circom::read_zkey;
use criterion::{criterion_group, criterion_main, Criterion};
use rln::circuit::zkey::read_zkey;
use std::io::Cursor;
pub fn zkey_load_benchmark(c: &mut Criterion) {
@@ -18,7 +18,7 @@ pub fn zkey_load_benchmark(c: &mut Criterion) {
criterion_group! {
name = benches;
config = Criterion::default().measurement_time(std::time::Duration::from_secs(250));
config = Criterion::default().sample_size(10);
targets = zkey_load_benchmark
}
criterion_main!(benches);

View File

@@ -21,7 +21,7 @@ pub fn pmtree_benchmark(c: &mut Criterion) {
c.bench_function("Pmtree::override_range", |b| {
b.iter(|| {
tree.override_range(0, leaves.clone(), [0, 1, 2, 3])
tree.override_range(0, leaves.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
})
});

Binary file not shown.

View File

@@ -1,225 +0,0 @@
// This crate provides interfaces for the zero-knowledge circuit and keys
use ark_bn254::{
Bn254, Fq as ArkFq, Fq2 as ArkFq2, Fr as ArkFr, G1Affine as ArkG1Affine,
G1Projective as ArkG1Projective, G2Affine as ArkG2Affine, G2Projective as ArkG2Projective,
};
use ark_groth16::{ProvingKey, VerifyingKey};
use ark_relations::r1cs::ConstraintMatrices;
use ark_serialize::CanonicalDeserialize;
use cfg_if::cfg_if;
use color_eyre::{Report, Result};
#[cfg(not(target_arch = "wasm32"))]
use {
ark_circom::WitnessCalculator,
lazy_static::lazy_static,
std::sync::{Arc, Mutex},
wasmer::{Module, Store},
};
#[cfg(feature = "arkzkey")]
use {
ark_zkey::{read_arkzkey_from_bytes, SerializableConstraintMatrices, SerializableProvingKey},
color_eyre::eyre::WrapErr,
};
#[cfg(not(feature = "arkzkey"))]
use {ark_circom::read_zkey, std::io::Cursor};
#[cfg(feature = "arkzkey")]
pub const ARKZKEY_BYTES: &[u8] = include_bytes!("../resources/tree_height_20/rln_final.arkzkey");
#[cfg(feature = "arkzkey")]
pub const ARKZKEY_BYTES_UNCOMPR: &[u8] =
include_bytes!("../resources/tree_height_20/rln_final_uncompr.arkzkey");
pub const ZKEY_BYTES: &[u8] = include_bytes!("../resources/tree_height_20/rln_final.zkey");
pub const VK_BYTES: &[u8] = include_bytes!("../resources/tree_height_20/verification_key.arkvkey");
const WASM_BYTES: &[u8] = include_bytes!("../resources/tree_height_20/rln.wasm");
#[cfg(not(target_arch = "wasm32"))]
lazy_static! {
#[cfg(not(target_arch = "wasm32"))]
static ref ZKEY: (ProvingKey<Curve>, ConstraintMatrices<Fr>) = {
cfg_if! {
if #[cfg(feature = "arkzkey")] {
read_arkzkey_from_bytes_uncompressed(ARKZKEY_BYTES_UNCOMPR).expect("Failed to read arkzkey")
} else {
let mut reader = Cursor::new(ZKEY_BYTES);
read_zkey(&mut reader).expect("Failed to read zkey")
}
}
};
#[cfg(not(target_arch = "wasm32"))]
static ref VK: VerifyingKey<Curve> = vk_from_ark_serialized(VK_BYTES).expect("Failed to read vk");
#[cfg(not(target_arch = "wasm32"))]
static ref WITNESS_CALCULATOR: Arc<Mutex<WitnessCalculator>> = {
circom_from_raw(WASM_BYTES).expect("Failed to create witness calculator")
};
}
pub const TEST_TREE_HEIGHT: usize = 20;
// The following types define the pairing friendly elliptic curve, the underlying finite fields and groups default to this module
// Note that proofs are serialized assuming Fr to be 4x8 = 32 bytes in size. Hence, changing to a curve with different encoding will make proof verification to fail
pub type Curve = Bn254;
pub type Fr = ArkFr;
pub type Fq = ArkFq;
pub type Fq2 = ArkFq2;
pub type G1Affine = ArkG1Affine;
pub type G1Projective = ArkG1Projective;
pub type G2Affine = ArkG2Affine;
pub type G2Projective = ArkG2Projective;
// Loads the proving key using a bytes vector
pub fn zkey_from_raw(zkey_data: &[u8]) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
if zkey_data.is_empty() {
return Err(Report::msg("No proving key found!"));
}
let proving_key_and_matrices = match () {
#[cfg(feature = "arkzkey")]
() => read_arkzkey_from_bytes(zkey_data)?,
#[cfg(not(feature = "arkzkey"))]
() => {
let mut reader = Cursor::new(zkey_data);
read_zkey(&mut reader)?
}
};
Ok(proving_key_and_matrices)
}
// Loads the proving key
#[cfg(not(target_arch = "wasm32"))]
pub fn zkey_from_folder() -> &'static (ProvingKey<Curve>, ConstraintMatrices<Fr>) {
&ZKEY
}
// Loads the verification key from a bytes vector
pub fn vk_from_raw(vk_data: &[u8], zkey_data: &[u8]) -> Result<VerifyingKey<Curve>> {
if !vk_data.is_empty() {
return vk_from_ark_serialized(vk_data);
}
if !zkey_data.is_empty() {
let (proving_key, _matrices) = zkey_from_raw(zkey_data)?;
return Ok(proving_key.vk);
}
Err(Report::msg("No proving/verification key found!"))
}
// Loads the verification key
#[cfg(not(target_arch = "wasm32"))]
pub fn vk_from_folder() -> &'static VerifyingKey<Curve> {
&VK
}
// Initializes the witness calculator using a bytes vector
#[cfg(not(target_arch = "wasm32"))]
pub fn circom_from_raw(wasm_buffer: &[u8]) -> Result<Arc<Mutex<WitnessCalculator>>> {
let module = Module::new(&Store::default(), wasm_buffer)?;
let result = WitnessCalculator::from_module(module)?;
Ok(Arc::new(Mutex::new(result)))
}
// Initializes the witness calculator
#[cfg(not(target_arch = "wasm32"))]
pub fn circom_from_folder() -> &'static Arc<Mutex<WitnessCalculator>> {
&WITNESS_CALCULATOR
}
// Computes the verification key from a bytes vector containing pre-processed ark-serialized verification key
// uncompressed, unchecked
pub fn vk_from_ark_serialized(data: &[u8]) -> Result<VerifyingKey<Curve>> {
let vk = VerifyingKey::<Curve>::deserialize_uncompressed_unchecked(data)?;
Ok(vk)
}
// Checks verification key to be correct with respect to proving key
#[cfg(not(target_arch = "wasm32"))]
pub fn check_vk_from_zkey(verifying_key: VerifyingKey<Curve>) -> Result<()> {
let (proving_key, _matrices) = zkey_from_folder();
if proving_key.vk == verifying_key {
Ok(())
} else {
Err(Report::msg("verifying_keys are not equal"))
}
}
////////////////////////////////////////////////////////
// Functions from [arkz-key](https://github.com/zkmopro/ark-zkey/blob/main/src/lib.rs#L106)
// without print and allow to choose between compressed and uncompressed arkzkey
////////////////////////////////////////////////////////
#[cfg(feature = "arkzkey")]
pub fn read_arkzkey_from_bytes_uncompressed(
arkzkey_data: &[u8],
) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
if arkzkey_data.is_empty() {
return Err(Report::msg("No proving key found!"));
}
let mut cursor = std::io::Cursor::new(arkzkey_data);
let serialized_proving_key =
SerializableProvingKey::deserialize_uncompressed_unchecked(&mut cursor)
.wrap_err("Failed to deserialize proving key")?;
let serialized_constraint_matrices =
SerializableConstraintMatrices::deserialize_uncompressed_unchecked(&mut cursor)
.wrap_err("Failed to deserialize constraint matrices")?;
// Get on right form for API
let proving_key: ProvingKey<Bn254> = serialized_proving_key.0;
let constraint_matrices: ConstraintMatrices<ark_bn254::Fr> = 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))
}
#[cfg(feature = "arkzkey")]
pub fn read_arkzkey_from_bytes_compressed(
arkzkey_data: &[u8],
) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
if arkzkey_data.is_empty() {
return Err(Report::msg("No proving key found!"));
}
let mut cursor = std::io::Cursor::new(arkzkey_data);
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<Bn254> = serialized_proving_key.0;
let constraint_matrices: ConstraintMatrices<ark_bn254::Fr> = 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))
}

View File

@@ -0,0 +1,73 @@
// This file is based on the code by iden3. Its preimage can be found here:
// https://github.com/iden3/circom-witnesscalc/blob/5cb365b6e4d9052ecc69d4567fcf5bc061c20e94/src/lib.rs
pub mod graph;
pub mod proto;
pub mod storage;
use ruint::aliases::U256;
use std::collections::HashMap;
use storage::deserialize_witnesscalc_graph;
use crate::circuit::Fr;
use graph::{fr_to_u256, Node};
pub type InputSignalsInfo = HashMap<String, (usize, usize)>;
pub fn calc_witness<I: IntoIterator<Item = (String, Vec<Fr>)>>(
inputs: I,
graph_data: &[u8],
) -> Vec<Fr> {
let inputs: HashMap<String, Vec<U256>> = inputs
.into_iter()
.map(|(key, value)| (key, value.iter().map(fr_to_u256).collect()))
.collect();
let (nodes, signals, input_mapping): (Vec<Node>, Vec<usize>, InputSignalsInfo) =
deserialize_witnesscalc_graph(std::io::Cursor::new(graph_data)).unwrap();
let mut inputs_buffer = get_inputs_buffer(get_inputs_size(&nodes));
populate_inputs(&inputs, &input_mapping, &mut inputs_buffer);
graph::evaluate(&nodes, inputs_buffer.as_slice(), &signals)
}
fn get_inputs_size(nodes: &[Node]) -> usize {
let mut start = false;
let mut max_index = 0usize;
for &node in nodes.iter() {
if let Node::Input(i) = node {
if i > max_index {
max_index = i;
}
start = true
} else if start {
break;
}
}
max_index + 1
}
fn populate_inputs(
input_list: &HashMap<String, Vec<U256>>,
inputs_info: &InputSignalsInfo,
input_buffer: &mut [U256],
) {
for (key, value) in input_list {
let (offset, len) = inputs_info[key];
if len != value.len() {
panic!("Invalid input length for {}", key);
}
for (i, v) in value.iter().enumerate() {
input_buffer[offset + i] = *v;
}
}
}
/// Allocates inputs vec with position 0 set to 1
fn get_inputs_buffer(size: usize) -> Vec<U256> {
let mut inputs = vec![U256::ZERO; size];
inputs[0] = U256::from(1);
inputs
}

View File

@@ -0,0 +1,957 @@
// This file is based on the code by iden3. Its preimage can be found here:
// https://github.com/iden3/circom-witnesscalc/blob/5cb365b6e4d9052ecc69d4567fcf5bc061c20e94/src/graph.rs
use ark_ff::{BigInt, BigInteger, One, PrimeField, Zero};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
use rand::Rng;
use ruint::{aliases::U256, uint};
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
collections::HashMap,
error::Error,
ops::{BitAnd, BitOr, BitXor, Deref, Shl, Shr},
};
use crate::circuit::iden3calc::proto;
use crate::circuit::Fr;
pub const M: U256 =
uint!(21888242871839275222246405745257275088548364400416034343698204186575808495617_U256);
fn ark_se<S, A: CanonicalSerialize>(a: &A, s: S) -> Result<S::Ok, S::Error>
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<A, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s: Vec<u8> = 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)
}
#[inline(always)]
pub fn fr_to_u256(x: &Fr) -> U256 {
U256::from_limbs(x.into_bigint().0)
}
#[inline(always)]
pub fn u256_to_fr(x: &U256) -> Fr {
Fr::from_bigint(BigInt::new(x.into_limbs())).expect("Failed to convert U256 to Fr")
}
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Operation {
Mul,
Div,
Add,
Sub,
Pow,
Idiv,
Mod,
Eq,
Neq,
Lt,
Gt,
Leq,
Geq,
Land,
Lor,
Shl,
Shr,
Bor,
Band,
Bxor,
}
impl Operation {
// TODO: rewrite to &U256 type
pub fn eval(&self, a: U256, b: U256) -> U256 {
use Operation::*;
match self {
Mul => a.mul_mod(b, M),
Div => {
if b == U256::ZERO {
// as we are simulating a circuit execution with signals
// values all equal to 0, just return 0 here in case of
// division by zero
U256::ZERO
} else {
a.mul_mod(b.inv_mod(M).unwrap(), M)
}
}
Add => a.add_mod(b, M),
Sub => a.add_mod(M - b, M),
Pow => a.pow_mod(b, M),
Mod => a.div_rem(b).1,
Eq => U256::from(a == b),
Neq => U256::from(a != b),
Lt => u_lt(&a, &b),
Gt => u_gt(&a, &b),
Leq => u_lte(&a, &b),
Geq => u_gte(&a, &b),
Land => U256::from(a != U256::ZERO && b != U256::ZERO),
Lor => U256::from(a != U256::ZERO || b != U256::ZERO),
Shl => compute_shl_uint(a, b),
Shr => compute_shr_uint(a, b),
// TODO test with conner case when it is possible to get the number
// bigger then modulus
Bor => a.bitor(b),
Band => a.bitand(b),
// TODO test with conner case when it is possible to get the number
// bigger then modulus
Bxor => a.bitxor(b),
Idiv => a / b,
}
}
pub fn eval_fr(&self, a: Fr, b: Fr) -> Fr {
use Operation::*;
match self {
Mul => a * b,
// We always should return something on the circuit execution.
// So in case of division by 0 we would return 0. And the proof
// should be invalid in the end.
Div => {
if b.is_zero() {
Fr::zero()
} else {
a / b
}
}
Add => a + b,
Sub => a - b,
Idiv => {
if b.is_zero() {
Fr::zero()
} else {
let a_u256 = fr_to_u256(&a);
let b_u256 = fr_to_u256(&b);
u256_to_fr(&(a_u256 / b_u256))
}
}
Mod => {
if b.is_zero() {
Fr::zero()
} else {
let a_u256 = fr_to_u256(&a);
let b_u256 = fr_to_u256(&b);
u256_to_fr(&(a_u256 % b_u256))
}
}
Eq => match a.cmp(&b) {
Ordering::Equal => Fr::one(),
_ => Fr::zero(),
},
Neq => match a.cmp(&b) {
Ordering::Equal => Fr::zero(),
_ => Fr::one(),
},
Lt => u256_to_fr(&u_lt(&fr_to_u256(&a), &fr_to_u256(&b))),
Gt => u256_to_fr(&u_gt(&fr_to_u256(&a), &fr_to_u256(&b))),
Leq => u256_to_fr(&u_lte(&fr_to_u256(&a), &fr_to_u256(&b))),
Geq => u256_to_fr(&u_gte(&fr_to_u256(&a), &fr_to_u256(&b))),
Land => {
if a.is_zero() || b.is_zero() {
Fr::zero()
} else {
Fr::one()
}
}
Lor => {
if a.is_zero() && b.is_zero() {
Fr::zero()
} else {
Fr::one()
}
}
Shl => shl(a, b),
Shr => shr(a, b),
Bor => bit_or(a, b),
Band => bit_and(a, b),
Bxor => bit_xor(a, b),
// TODO implement other operators
_ => unimplemented!("operator {:?} not implemented for Montgomery", self),
}
}
}
impl From<&Operation> for proto::DuoOp {
fn from(v: &Operation) -> Self {
match v {
Operation::Mul => proto::DuoOp::Mul,
Operation::Div => proto::DuoOp::Div,
Operation::Add => proto::DuoOp::Add,
Operation::Sub => proto::DuoOp::Sub,
Operation::Pow => proto::DuoOp::Pow,
Operation::Idiv => proto::DuoOp::Idiv,
Operation::Mod => proto::DuoOp::Mod,
Operation::Eq => proto::DuoOp::Eq,
Operation::Neq => proto::DuoOp::Neq,
Operation::Lt => proto::DuoOp::Lt,
Operation::Gt => proto::DuoOp::Gt,
Operation::Leq => proto::DuoOp::Leq,
Operation::Geq => proto::DuoOp::Geq,
Operation::Land => proto::DuoOp::Land,
Operation::Lor => proto::DuoOp::Lor,
Operation::Shl => proto::DuoOp::Shl,
Operation::Shr => proto::DuoOp::Shr,
Operation::Bor => proto::DuoOp::Bor,
Operation::Band => proto::DuoOp::Band,
Operation::Bxor => proto::DuoOp::Bxor,
}
}
}
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum UnoOperation {
Neg,
Id, // identity - just return self
}
impl UnoOperation {
pub fn eval(&self, a: U256) -> U256 {
match self {
UnoOperation::Neg => {
if a == U256::ZERO {
U256::ZERO
} else {
M - a
}
}
UnoOperation::Id => a,
}
}
pub fn eval_fr(&self, a: Fr) -> Fr {
match self {
UnoOperation::Neg => {
if a.is_zero() {
Fr::zero()
} else {
let mut x = Fr::MODULUS;
x.sub_with_borrow(&a.into_bigint());
Fr::from_bigint(x).unwrap()
}
}
_ => unimplemented!("uno operator {:?} not implemented for Montgomery", self),
}
}
}
impl From<&UnoOperation> for proto::UnoOp {
fn from(v: &UnoOperation) -> Self {
match v {
UnoOperation::Neg => proto::UnoOp::Neg,
UnoOperation::Id => proto::UnoOp::Id,
}
}
}
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum TresOperation {
TernCond,
}
impl TresOperation {
pub fn eval(&self, a: U256, b: U256, c: U256) -> U256 {
match self {
TresOperation::TernCond => {
if a == U256::ZERO {
c
} else {
b
}
}
}
}
pub fn eval_fr(&self, a: Fr, b: Fr, c: Fr) -> Fr {
match self {
TresOperation::TernCond => {
if a.is_zero() {
c
} else {
b
}
}
}
}
}
impl From<&TresOperation> for proto::TresOp {
fn from(v: &TresOperation) -> Self {
match v {
TresOperation::TernCond => proto::TresOp::TernCond,
}
}
}
#[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),
UnoOp(UnoOperation, usize),
Op(Operation, usize, usize),
TresOp(TresOperation, usize, usize, usize),
}
// TODO remove pub from Vec<Node>
#[derive(Default)]
pub struct Nodes(pub Vec<Node>);
impl Nodes {
pub fn new() -> Self {
Nodes(Vec::new())
}
pub fn to_const(&self, idx: NodeIdx) -> Result<U256, NodeConstErr> {
let me = self.0.get(idx.0).ok_or(NodeConstErr::EmptyNode(idx))?;
match me {
Node::Constant(v) => Ok(*v),
Node::UnoOp(op, a) => Ok(op.eval(self.to_const(NodeIdx(*a))?)),
Node::Op(op, a, b) => {
Ok(op.eval(self.to_const(NodeIdx(*a))?, self.to_const(NodeIdx(*b))?))
}
Node::TresOp(op, a, b, c) => Ok(op.eval(
self.to_const(NodeIdx(*a))?,
self.to_const(NodeIdx(*b))?,
self.to_const(NodeIdx(*c))?,
)),
Node::Input(_) => Err(NodeConstErr::InputSignal),
Node::MontConstant(_) => {
panic!("MontConstant should not be used here")
}
}
}
pub fn push(&mut self, n: Node) -> NodeIdx {
self.0.push(n);
NodeIdx(self.0.len() - 1)
}
pub fn get(&self, idx: NodeIdx) -> Option<&Node> {
self.0.get(idx.0)
}
}
impl Deref for Nodes {
type Target = Vec<Node>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Copy, Clone)]
pub struct NodeIdx(pub usize);
impl From<usize> for NodeIdx {
fn from(v: usize) -> Self {
NodeIdx(v)
}
}
#[derive(Debug)]
pub enum NodeConstErr {
EmptyNode(NodeIdx),
InputSignal,
}
impl std::fmt::Display for NodeConstErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NodeConstErr::EmptyNode(idx) => {
write!(f, "empty node at index {}", idx.0)
}
NodeConstErr::InputSignal => {
write!(f, "input signal is not a constant")
}
}
}
}
impl Error for NodeConstErr {}
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);
} else if let Node::UnoOp(_, a) = node {
assert!(a < i);
} else if let Node::TresOp(_, a, b, c) = node {
assert!(a < i);
assert!(b < i);
assert!(c < i);
}
}
}
pub fn optimize(nodes: &mut Vec<Node>, outputs: &mut [usize]) {
tree_shake(nodes, outputs);
propagate(nodes);
value_numbering(nodes, outputs);
constants(nodes);
tree_shake(nodes, outputs);
montgomery_form(nodes);
}
pub fn evaluate(nodes: &[Node], inputs: &[U256], outputs: &[usize]) -> Vec<Fr> {
// 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) => u256_to_fr(&c),
Node::MontConstant(c) => c,
Node::Input(i) => u256_to_fr(&inputs[i]),
Node::Op(op, a, b) => op.eval_fr(values[a], values[b]),
Node::UnoOp(op, a) => op.eval_fr(values[a]),
Node::TresOp(op, a, b, c) => op.eval_fr(values[a], values[b], values[c]),
};
values.push(value);
}
// Convert from Montgomery form and return the outputs.
let mut out = vec![Fr::from(0); outputs.len()];
for i in 0..outputs.len() {
out[i] = values[outputs[i]];
}
out
}
/// Constant propagation
pub fn propagate(nodes: &mut [Node]) {
assert_valid(nodes);
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));
} 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));
}
}
} else if let Node::UnoOp(op, a) = nodes[i] {
if let Node::Constant(va) = nodes[a] {
nodes[i] = Node::Constant(op.eval(va));
}
} else if let Node::TresOp(op, a, b, c) = nodes[i] {
if let (Node::Constant(va), Node::Constant(vb), Node::Constant(vc)) =
(nodes[a], nodes[b], nodes[c])
{
nodes[i] = Node::Constant(op.eval(va, vb, vc));
}
}
}
}
/// Remove unused nodes
pub fn tree_shake(nodes: &mut Vec<Node>, 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;
}
if let Node::UnoOp(_, a) = nodes[i] {
used[a] = true;
}
if let Node::TresOp(_, a, b, c) = nodes[i] {
used[a] = true;
used[b] = true;
used[c] = true;
}
}
}
// Remove unused nodes
let n = nodes.len();
let mut retain = used.iter();
nodes.retain(|_| *retain.next().unwrap());
// 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();
}
if let Node::UnoOp(_, a) = node {
*a = renumber[*a].unwrap();
}
if let Node::TresOp(_, a, b, c) = node {
*a = renumber[*a].unwrap();
*b = renumber[*b].unwrap();
*c = renumber[*c].unwrap();
}
}
for output in outputs.iter_mut() {
*output = renumber[*output].unwrap();
}
}
/// Randomly evaluate the graph
fn random_eval(nodes: &mut [Node]) -> Vec<U256> {
let mut rng = rand::thread_rng();
let mut values = Vec::with_capacity(nodes.len());
let mut inputs = HashMap::new();
let mut prfs = HashMap::new();
let mut prfs_uno = HashMap::new();
let mut prfs_tres = HashMap::new();
for node in nodes.iter() {
use Operation::*;
let value = match node {
// Constants evaluate to themselves
Node::Constant(c) => *c,
Node::MontConstant(_) => 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::<U256>() % M),
Node::Op(op, a, b) => *prfs
.entry((*op, values[*a], values[*b]))
.or_insert_with(|| rng.gen::<U256>() % M),
Node::UnoOp(op, a) => *prfs_uno
.entry((*op, values[*a]))
.or_insert_with(|| rng.gen::<U256>() % M),
Node::TresOp(op, a, b, c) => *prfs_tres
.entry((*op, values[*a], values[*b], values[*c]))
.or_insert_with(|| rng.gen::<U256>() % 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 renumber: Vec<_> = values.into_iter().map(|v| value_map[&v][0]).collect();
// Renumber references.
for node in nodes.iter_mut() {
if let Node::Op(_, a, b) = node {
*a = renumber[*a];
*b = renumber[*b];
}
if let Node::UnoOp(_, a) = node {
*a = renumber[*a];
}
if let Node::TresOp(_, a, b, c) = node {
*a = renumber[*a];
*b = renumber[*b];
*c = renumber[*c];
}
}
for output in outputs.iter_mut() {
*output = renumber[*output];
}
}
/// 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.
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]);
}
}
}
/// 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(u256_to_fr(c)),
MontConstant(..) => (),
Input(..) => (),
Op(
Mul | Div | Add | Sub | Idiv | Mod | Eq | Neq | Lt | Gt | Leq | Geq | Land | Lor
| Shl | Shr | Bor | Band | Bxor,
..,
) => (),
Op(op @ Pow, ..) => unimplemented!("Operators Montgomery form: {:?}", op),
UnoOp(UnoOperation::Neg, ..) => (),
UnoOp(op, ..) => unimplemented!("Uno Operators Montgomery form: {:?}", op),
TresOp(TresOperation::TernCond, ..) => (),
}
}
}
fn shl(a: Fr, b: Fr) -> Fr {
if b.is_zero() {
return a;
}
if b.cmp(&Fr::from(Fr::MODULUS_BIT_SIZE)).is_ge() {
return Fr::zero();
}
let n = b.into_bigint().0[0] as u32;
let a = a.into_bigint();
Fr::from_bigint(a << n).unwrap()
}
fn shr(a: Fr, b: Fr) -> Fr {
if b.is_zero() {
return a;
}
match b.cmp(&Fr::from(254u64)) {
Ordering::Equal => return Fr::zero(),
Ordering::Greater => return Fr::zero(),
_ => (),
};
let mut n = b.into_bigint().to_bytes_le()[0];
let mut result = a.into_bigint();
let c = result.as_mut();
while n >= 64 {
for i in 0..3 {
c[i as usize] = c[(i + 1) as usize];
}
c[3] = 0;
n -= 64;
}
if n == 0 {
return Fr::from_bigint(result).unwrap();
}
let mask: u64 = (1 << n) - 1;
let mut carrier: u64 = c[3] & mask;
c[3] >>= n;
for i in (0..3).rev() {
let new_carrier = c[i] & mask;
c[i] = (c[i] >> n) | (carrier << (64 - n));
carrier = new_carrier;
}
Fr::from_bigint(result).unwrap()
}
fn bit_and(a: Fr, b: Fr) -> Fr {
let a = a.into_bigint();
let b = b.into_bigint();
let c: [u64; 4] = [
a.0[0] & b.0[0],
a.0[1] & b.0[1],
a.0[2] & b.0[2],
a.0[3] & b.0[3],
];
let mut d: BigInt<4> = BigInt::new(c);
if d > Fr::MODULUS {
d.sub_with_borrow(&Fr::MODULUS);
}
Fr::from_bigint(d).unwrap()
}
fn bit_or(a: Fr, b: Fr) -> Fr {
let a = a.into_bigint();
let b = b.into_bigint();
let c: [u64; 4] = [
a.0[0] | b.0[0],
a.0[1] | b.0[1],
a.0[2] | b.0[2],
a.0[3] | b.0[3],
];
let mut d: BigInt<4> = BigInt::new(c);
if d > Fr::MODULUS {
d.sub_with_borrow(&Fr::MODULUS);
}
Fr::from_bigint(d).unwrap()
}
fn bit_xor(a: Fr, b: Fr) -> Fr {
let a = a.into_bigint();
let b = b.into_bigint();
let c: [u64; 4] = [
a.0[0] ^ b.0[0],
a.0[1] ^ b.0[1],
a.0[2] ^ b.0[2],
a.0[3] ^ b.0[3],
];
let mut d: BigInt<4> = BigInt::new(c);
if d > Fr::MODULUS {
d.sub_with_borrow(&Fr::MODULUS);
}
Fr::from_bigint(d).unwrap()
}
// M / 2
const HALF_M: U256 =
uint!(10944121435919637611123202872628637544274182200208017171849102093287904247808_U256);
fn u_gte(a: &U256, b: &U256) -> U256 {
let a_neg = &HALF_M < a;
let b_neg = &HALF_M < b;
match (a_neg, b_neg) {
(false, false) => U256::from(a >= b),
(true, false) => uint!(0_U256),
(false, true) => uint!(1_U256),
(true, true) => U256::from(a >= b),
}
}
fn u_lte(a: &U256, b: &U256) -> U256 {
let a_neg = &HALF_M < a;
let b_neg = &HALF_M < b;
match (a_neg, b_neg) {
(false, false) => U256::from(a <= b),
(true, false) => uint!(1_U256),
(false, true) => uint!(0_U256),
(true, true) => U256::from(a <= b),
}
}
fn u_gt(a: &U256, b: &U256) -> U256 {
let a_neg = &HALF_M < a;
let b_neg = &HALF_M < b;
match (a_neg, b_neg) {
(false, false) => U256::from(a > b),
(true, false) => uint!(0_U256),
(false, true) => uint!(1_U256),
(true, true) => U256::from(a > b),
}
}
fn u_lt(a: &U256, b: &U256) -> U256 {
let a_neg = &HALF_M < a;
let b_neg = &HALF_M < b;
match (a_neg, b_neg) {
(false, false) => U256::from(a < b),
(true, false) => uint!(1_U256),
(false, true) => uint!(0_U256),
(true, true) => U256::from(a < b),
}
}
#[cfg(test)]
mod tests {
use super::*;
use ruint::uint;
use std::ops::Div;
use std::str::FromStr;
#[test]
fn test_ok() {
let a = Fr::from(4u64);
let b = Fr::from(2u64);
let c = shl(a, b);
assert_eq!(c.cmp(&Fr::from(16u64)), Ordering::Equal)
}
#[test]
fn test_div() {
assert_eq!(
Operation::Div.eval_fr(Fr::from(2u64), Fr::from(3u64)),
Fr::from_str(
"7296080957279758407415468581752425029516121466805344781232734728858602831873"
)
.unwrap()
);
assert_eq!(
Operation::Div.eval_fr(Fr::from(6u64), Fr::from(2u64)),
Fr::from_str("3").unwrap()
);
assert_eq!(
Operation::Div.eval_fr(Fr::from(7u64), Fr::from(2u64)),
Fr::from_str(
"10944121435919637611123202872628637544274182200208017171849102093287904247812"
)
.unwrap()
);
}
#[test]
fn test_idiv() {
assert_eq!(
Operation::Idiv.eval_fr(Fr::from(2u64), Fr::from(3u64)),
Fr::from_str("0").unwrap()
);
assert_eq!(
Operation::Idiv.eval_fr(Fr::from(6u64), Fr::from(2u64)),
Fr::from_str("3").unwrap()
);
assert_eq!(
Operation::Idiv.eval_fr(Fr::from(7u64), Fr::from(2u64)),
Fr::from_str("3").unwrap()
);
}
#[test]
fn test_fr_mod() {
assert_eq!(
Operation::Mod.eval_fr(Fr::from(7u64), Fr::from(2u64)),
Fr::from_str("1").unwrap()
);
assert_eq!(
Operation::Mod.eval_fr(Fr::from(7u64), Fr::from(9u64)),
Fr::from_str("7").unwrap()
);
}
#[test]
fn test_u_gte() {
let result = u_gte(&uint!(10_U256), &uint!(3_U256));
assert_eq!(result, uint!(1_U256));
let result = u_gte(&uint!(3_U256), &uint!(3_U256));
assert_eq!(result, uint!(1_U256));
let result = u_gte(&uint!(2_U256), &uint!(3_U256));
assert_eq!(result, uint!(0_U256));
// -1 >= 3 => 0
let result = u_gte(
&uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495616_U256
),
&uint!(3_U256),
);
assert_eq!(result, uint!(0_U256));
// -1 >= -2 => 1
let result = u_gte(
&uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495616_U256
),
&uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495615_U256
),
);
assert_eq!(result, uint!(1_U256));
// -2 >= -1 => 0
let result = u_gte(
&uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495615_U256
),
&uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495616_U256
),
);
assert_eq!(result, uint!(0_U256));
// -2 == -2 => 1
let result = u_gte(
&uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495615_U256
),
&uint!(
21888242871839275222246405745257275088548364400416034343698204186575808495615_U256
),
);
assert_eq!(result, uint!(1_U256));
}
#[test]
fn test_x() {
let x = M.div(uint!(2_U256));
println!("x: {:?}", x.as_limbs());
println!("x: {}", M);
}
#[test]
fn test_2() {
let nodes: Vec<Node> = vec![];
// let node = nodes[0];
let node = nodes.get(0);
println!("{:?}", node);
}
}

View File

@@ -0,0 +1,117 @@
// This file has been generated by prost-build during compilation of the code by iden3
// and modified manually. The *.proto file used to generate this on can be found here:
// https://github.com/iden3/circom-witnesscalc/blob/5cb365b6e4d9052ecc69d4567fcf5bc061c20e94/protos/messages.proto
use std::collections::HashMap;
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BigUInt {
#[prost(bytes = "vec", tag = "1")]
pub value_le: Vec<u8>,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct InputNode {
#[prost(uint32, tag = "1")]
pub idx: u32,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ConstantNode {
#[prost(message, optional, tag = "1")]
pub value: Option<BigUInt>,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct UnoOpNode {
#[prost(enumeration = "UnoOp", tag = "1")]
pub op: i32,
#[prost(uint32, tag = "2")]
pub a_idx: u32,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct DuoOpNode {
#[prost(enumeration = "DuoOp", tag = "1")]
pub op: i32,
#[prost(uint32, tag = "2")]
pub a_idx: u32,
#[prost(uint32, tag = "3")]
pub b_idx: u32,
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct TresOpNode {
#[prost(enumeration = "TresOp", tag = "1")]
pub op: i32,
#[prost(uint32, tag = "2")]
pub a_idx: u32,
#[prost(uint32, tag = "3")]
pub b_idx: u32,
#[prost(uint32, tag = "4")]
pub c_idx: u32,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Node {
#[prost(oneof = "node::Node", tags = "1, 2, 3, 4, 5")]
pub node: Option<node::Node>,
}
/// Nested message and enum types in `Node`.
pub mod node {
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Node {
#[prost(message, tag = "1")]
Input(super::InputNode),
#[prost(message, tag = "2")]
Constant(super::ConstantNode),
#[prost(message, tag = "3")]
UnoOp(super::UnoOpNode),
#[prost(message, tag = "4")]
DuoOp(super::DuoOpNode),
#[prost(message, tag = "5")]
TresOp(super::TresOpNode),
}
}
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct SignalDescription {
#[prost(uint32, tag = "1")]
pub offset: u32,
#[prost(uint32, tag = "2")]
pub len: u32,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GraphMetadata {
#[prost(uint32, repeated, tag = "1")]
pub witness_signals: Vec<u32>,
#[prost(map = "string, message", tag = "2")]
pub inputs: HashMap<String, SignalDescription>,
}
#[derive(Clone, Copy, Debug, PartialEq, ::prost::Enumeration)]
pub enum DuoOp {
Mul = 0,
Div = 1,
Add = 2,
Sub = 3,
Pow = 4,
Idiv = 5,
Mod = 6,
Eq = 7,
Neq = 8,
Lt = 9,
Gt = 10,
Leq = 11,
Geq = 12,
Land = 13,
Lor = 14,
Shl = 15,
Shr = 16,
Bor = 17,
Band = 18,
Bxor = 19,
}
#[derive(Clone, Copy, Debug, PartialEq, ::prost::Enumeration)]
pub enum UnoOp {
Neg = 0,
Id = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, ::prost::Enumeration)]
pub enum TresOp {
TernCond = 0,
}

View File

@@ -0,0 +1,497 @@
// This file is based on the code by iden3. Its preimage can be found here:
// https://github.com/iden3/circom-witnesscalc/blob/5cb365b6e4d9052ecc69d4567fcf5bc061c20e94/src/storage.rs
use ark_bn254::Fr;
use ark_ff::PrimeField;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use prost::Message;
use std::io::{Read, Write};
use crate::circuit::iden3calc::{
graph,
graph::{Operation, TresOperation, UnoOperation},
proto, InputSignalsInfo,
};
// format of the wtns.graph file:
// + magic line: wtns.graph.001
// + 4 bytes unsigned LE 32-bit integer: number of nodes
// + series of protobuf serialized nodes. Each node prefixed by varint length
// + protobuf serialized GraphMetadata
// + 8 bytes unsigned LE 64-bit integer: offset of GraphMetadata message
const WITNESSCALC_GRAPH_MAGIC: &[u8] = b"wtns.graph.001";
const MAX_VARINT_LENGTH: usize = 10;
impl From<proto::Node> for graph::Node {
fn from(value: proto::Node) -> Self {
match value.node.unwrap() {
proto::node::Node::Input(input_node) => graph::Node::Input(input_node.idx as usize),
proto::node::Node::Constant(constant_node) => {
let i = constant_node.value.unwrap();
graph::Node::MontConstant(Fr::from_le_bytes_mod_order(i.value_le.as_slice()))
}
proto::node::Node::UnoOp(uno_op_node) => {
let op = proto::UnoOp::try_from(uno_op_node.op).unwrap();
graph::Node::UnoOp(op.into(), uno_op_node.a_idx as usize)
}
proto::node::Node::DuoOp(duo_op_node) => {
let op = proto::DuoOp::try_from(duo_op_node.op).unwrap();
graph::Node::Op(
op.into(),
duo_op_node.a_idx as usize,
duo_op_node.b_idx as usize,
)
}
proto::node::Node::TresOp(tres_op_node) => {
let op = proto::TresOp::try_from(tres_op_node.op).unwrap();
graph::Node::TresOp(
op.into(),
tres_op_node.a_idx as usize,
tres_op_node.b_idx as usize,
tres_op_node.c_idx as usize,
)
}
}
}
}
impl From<&graph::Node> for proto::node::Node {
fn from(node: &graph::Node) -> Self {
match node {
graph::Node::Input(i) => proto::node::Node::Input(proto::InputNode { idx: *i as u32 }),
graph::Node::Constant(_) => {
panic!("We are not supposed to write Constant to the witnesscalc graph. All Constant should be converted to MontConstant.");
}
graph::Node::UnoOp(op, a) => {
let op = proto::UnoOp::from(op);
proto::node::Node::UnoOp(proto::UnoOpNode {
op: op as i32,
a_idx: *a as u32,
})
}
graph::Node::Op(op, a, b) => proto::node::Node::DuoOp(proto::DuoOpNode {
op: proto::DuoOp::from(op) as i32,
a_idx: *a as u32,
b_idx: *b as u32,
}),
graph::Node::TresOp(op, a, b, c) => proto::node::Node::TresOp(proto::TresOpNode {
op: proto::TresOp::from(op) as i32,
a_idx: *a as u32,
b_idx: *b as u32,
c_idx: *c as u32,
}),
graph::Node::MontConstant(c) => {
let bi = Into::<num_bigint::BigUint>::into(*c);
let i = proto::BigUInt {
value_le: bi.to_bytes_le(),
};
proto::node::Node::Constant(proto::ConstantNode { value: Some(i) })
}
}
}
}
impl From<proto::UnoOp> for UnoOperation {
fn from(value: proto::UnoOp) -> Self {
match value {
proto::UnoOp::Neg => UnoOperation::Neg,
proto::UnoOp::Id => UnoOperation::Id,
}
}
}
impl From<proto::DuoOp> for Operation {
fn from(value: proto::DuoOp) -> Self {
match value {
proto::DuoOp::Mul => Operation::Mul,
proto::DuoOp::Div => Operation::Div,
proto::DuoOp::Add => Operation::Add,
proto::DuoOp::Sub => Operation::Sub,
proto::DuoOp::Pow => Operation::Pow,
proto::DuoOp::Idiv => Operation::Idiv,
proto::DuoOp::Mod => Operation::Mod,
proto::DuoOp::Eq => Operation::Eq,
proto::DuoOp::Neq => Operation::Neq,
proto::DuoOp::Lt => Operation::Lt,
proto::DuoOp::Gt => Operation::Gt,
proto::DuoOp::Leq => Operation::Leq,
proto::DuoOp::Geq => Operation::Geq,
proto::DuoOp::Land => Operation::Land,
proto::DuoOp::Lor => Operation::Lor,
proto::DuoOp::Shl => Operation::Shl,
proto::DuoOp::Shr => Operation::Shr,
proto::DuoOp::Bor => Operation::Bor,
proto::DuoOp::Band => Operation::Band,
proto::DuoOp::Bxor => Operation::Bxor,
}
}
}
impl From<proto::TresOp> for graph::TresOperation {
fn from(value: proto::TresOp) -> Self {
match value {
proto::TresOp::TernCond => TresOperation::TernCond,
}
}
}
pub fn serialize_witnesscalc_graph<T: Write>(
mut w: T,
nodes: &Vec<graph::Node>,
witness_signals: &[usize],
input_signals: &InputSignalsInfo,
) -> std::io::Result<()> {
let mut ptr = 0usize;
w.write_all(WITNESSCALC_GRAPH_MAGIC).unwrap();
ptr += WITNESSCALC_GRAPH_MAGIC.len();
w.write_u64::<LittleEndian>(nodes.len() as u64)?;
ptr += 8;
let metadata = proto::GraphMetadata {
witness_signals: witness_signals
.iter()
.map(|x| *x as u32)
.collect::<Vec<u32>>(),
inputs: input_signals
.iter()
.map(|(k, v)| {
let sig = proto::SignalDescription {
offset: v.0 as u32,
len: v.1 as u32,
};
(k.clone(), sig)
})
.collect(),
};
// capacity of buf should be enough to hold the largest message + 10 bytes
// of varint length
let mut buf = Vec::with_capacity(metadata.encoded_len() + MAX_VARINT_LENGTH);
for node in nodes {
let node_pb = proto::Node {
node: Some(proto::node::Node::from(node)),
};
assert_eq!(buf.len(), 0);
node_pb.encode_length_delimited(&mut buf)?;
ptr += buf.len();
w.write_all(&buf)?;
buf.clear();
}
metadata.encode_length_delimited(&mut buf)?;
w.write_all(&buf)?;
buf.clear();
w.write_u64::<LittleEndian>(ptr as u64)?;
Ok(())
}
fn read_message_length<R: Read>(rw: &mut WriteBackReader<R>) -> std::io::Result<usize> {
let mut buf = [0u8; MAX_VARINT_LENGTH];
let bytes_read = rw.read(&mut buf)?;
if bytes_read == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"Unexpected EOF",
));
}
let len_delimiter = prost::decode_length_delimiter(buf.as_ref())?;
let lnln = prost::length_delimiter_len(len_delimiter);
if lnln < bytes_read {
rw.write_all(&buf[lnln..bytes_read])?;
}
Ok(len_delimiter)
}
fn read_message<R: Read, M: Message + std::default::Default>(
rw: &mut WriteBackReader<R>,
) -> std::io::Result<M> {
let ln = read_message_length(rw)?;
let mut buf = vec![0u8; ln];
let bytes_read = rw.read(&mut buf)?;
if bytes_read != ln {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"Unexpected EOF",
));
}
let msg = prost::Message::decode(&buf[..])?;
Ok(msg)
}
pub fn deserialize_witnesscalc_graph(
r: impl Read,
) -> std::io::Result<(Vec<graph::Node>, Vec<usize>, InputSignalsInfo)> {
let mut br = WriteBackReader::new(r);
let mut magic = [0u8; WITNESSCALC_GRAPH_MAGIC.len()];
br.read_exact(&mut magic)?;
if !magic.eq(WITNESSCALC_GRAPH_MAGIC) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid magic",
));
}
let nodes_num = br.read_u64::<LittleEndian>()?;
let mut nodes = Vec::with_capacity(nodes_num as usize);
for _ in 0..nodes_num {
let n: proto::Node = read_message(&mut br)?;
let n2: graph::Node = n.into();
nodes.push(n2);
}
let md: proto::GraphMetadata = read_message(&mut br)?;
let witness_signals = md
.witness_signals
.iter()
.map(|x| *x as usize)
.collect::<Vec<usize>>();
let input_signals = md
.inputs
.iter()
.map(|(k, v)| (k.clone(), (v.offset as usize, v.len as usize)))
.collect::<InputSignalsInfo>();
Ok((nodes, witness_signals, input_signals))
}
struct WriteBackReader<R: Read> {
reader: R,
buffer: Vec<u8>,
}
impl<R: Read> WriteBackReader<R> {
fn new(reader: R) -> Self {
WriteBackReader {
reader,
buffer: Vec::new(),
}
}
}
impl<R: Read> Read for WriteBackReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let mut n = 0usize;
if !self.buffer.is_empty() {
n = std::cmp::min(buf.len(), self.buffer.len());
self.buffer[self.buffer.len() - n..]
.iter()
.rev()
.enumerate()
.for_each(|(i, x)| {
buf[i] = *x;
});
self.buffer.truncate(self.buffer.len() - n);
}
while n < buf.len() {
let m = self.reader.read(&mut buf[n..])?;
if m == 0 {
break;
}
n += m;
}
Ok(n)
}
}
impl<R: Read> Write for WriteBackReader<R> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.buffer.reserve(buf.len());
self.buffer.extend(buf.iter().rev());
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use byteorder::ByteOrder;
use core::str::FromStr;
use graph::{Operation, TresOperation, UnoOperation};
use std::collections::HashMap;
#[test]
fn test_read_message() {
let mut buf = Vec::new();
let n1 = proto::Node {
node: Some(proto::node::Node::Input(proto::InputNode { idx: 1 })),
};
n1.encode_length_delimited(&mut buf).unwrap();
let n2 = proto::Node {
node: Some(proto::node::Node::Input(proto::InputNode { idx: 2 })),
};
n2.encode_length_delimited(&mut buf).unwrap();
let mut reader = std::io::Cursor::new(&buf);
let mut rw = WriteBackReader::new(&mut reader);
let got_n1: proto::Node = read_message(&mut rw).unwrap();
assert!(n1.eq(&got_n1));
let got_n2: proto::Node = read_message(&mut rw).unwrap();
assert!(n2.eq(&got_n2));
assert_eq!(reader.position(), buf.len() as u64);
}
#[test]
fn test_read_message_variant() {
let nodes = vec![
proto::Node {
node: Some(proto::node::Node::from(&graph::Node::Input(0))),
},
proto::Node {
node: Some(proto::node::Node::from(&graph::Node::MontConstant(
Fr::from_str("1").unwrap(),
))),
},
proto::Node {
node: Some(proto::node::Node::from(&graph::Node::UnoOp(
UnoOperation::Id,
4,
))),
},
proto::Node {
node: Some(proto::node::Node::from(&graph::Node::Op(
Operation::Mul,
5,
6,
))),
},
proto::Node {
node: Some(proto::node::Node::from(&graph::Node::TresOp(
TresOperation::TernCond,
7,
8,
9,
))),
},
];
let mut buf = Vec::new();
for n in &nodes {
n.encode_length_delimited(&mut buf).unwrap();
}
let mut nodes_got: Vec<proto::Node> = Vec::new();
let mut reader = std::io::Cursor::new(&buf);
let mut rw = WriteBackReader::new(&mut reader);
for _ in 0..nodes.len() {
nodes_got.push(read_message(&mut rw).unwrap());
}
assert_eq!(nodes, nodes_got);
}
#[test]
fn test_write_back_reader() {
let data = [1u8, 2, 3, 4, 5, 6];
let mut r = WriteBackReader::new(std::io::Cursor::new(&data));
let buf = &mut [0u8; 5];
r.read(buf).unwrap();
assert_eq!(buf, &[1, 2, 3, 4, 5]);
// return [4, 5] to reader
r.write(&buf[3..]).unwrap();
// return [2, 3] to reader
r.write(&buf[1..3]).unwrap();
buf.fill(0);
// read 3 bytes, expect [2, 3, 4] after returns
let mut n = r.read(&mut buf[..3]).unwrap();
assert_eq!(n, 3);
assert_eq!(buf, &[2, 3, 4, 0, 0]);
buf.fill(0);
// read everything left in reader
n = r.read(buf).unwrap();
assert_eq!(n, 2);
assert_eq!(buf, &[5, 6, 0, 0, 0]);
}
#[test]
fn test_deserialize_inputs() {
let nodes = vec![
graph::Node::Input(0),
graph::Node::MontConstant(Fr::from_str("1").unwrap()),
graph::Node::UnoOp(UnoOperation::Id, 4),
graph::Node::Op(Operation::Mul, 5, 6),
graph::Node::TresOp(TresOperation::TernCond, 7, 8, 9),
];
let witness_signals = vec![4, 1];
let mut input_signals: InputSignalsInfo = HashMap::new();
input_signals.insert("sig1".to_string(), (1, 3));
input_signals.insert("sig2".to_string(), (5, 1));
let mut tmp = Vec::new();
serialize_witnesscalc_graph(&mut tmp, &nodes, &witness_signals, &input_signals).unwrap();
let mut reader = std::io::Cursor::new(&tmp);
let (nodes_res, witness_signals_res, input_signals_res) =
deserialize_witnesscalc_graph(&mut reader).unwrap();
assert_eq!(nodes, nodes_res);
assert_eq!(input_signals, input_signals_res);
assert_eq!(witness_signals, witness_signals_res);
let metadata_start = LittleEndian::read_u64(&tmp[tmp.len() - 8..]);
let mt_reader = std::io::Cursor::new(&tmp[metadata_start as usize..]);
let mut rw = WriteBackReader::new(mt_reader);
let metadata: proto::GraphMetadata = read_message(&mut rw).unwrap();
let metadata_want = proto::GraphMetadata {
witness_signals: vec![4, 1],
inputs: input_signals
.iter()
.map(|(k, v)| {
(
k.clone(),
proto::SignalDescription {
offset: v.0 as u32,
len: v.1 as u32,
},
)
})
.collect(),
};
assert_eq!(metadata, metadata_want);
}
}

161
rln/src/circuit/mod.rs Normal file
View File

@@ -0,0 +1,161 @@
// This crate provides interfaces for the zero-knowledge circuit and keys
pub mod iden3calc;
pub mod qap;
pub mod zkey;
use ::lazy_static::lazy_static;
use ark_bn254::{
Bn254, Fq as ArkFq, Fq2 as ArkFq2, Fr as ArkFr, G1Affine as ArkG1Affine,
G1Projective as ArkG1Projective, G2Affine as ArkG2Affine, G2Projective as ArkG2Projective,
};
use ark_groth16::ProvingKey;
use ark_relations::r1cs::ConstraintMatrices;
use cfg_if::cfg_if;
use color_eyre::{Report, Result};
use crate::circuit::iden3calc::calc_witness;
#[cfg(feature = "arkzkey")]
use {
ark_ff::Field, ark_serialize::CanonicalDeserialize, ark_serialize::CanonicalSerialize,
color_eyre::eyre::WrapErr,
};
#[cfg(not(feature = "arkzkey"))]
use {crate::circuit::zkey::read_zkey, std::io::Cursor};
#[cfg(feature = "arkzkey")]
pub const ARKZKEY_BYTES: &[u8] = include_bytes!("../../resources/tree_height_20/rln_final.arkzkey");
pub const ZKEY_BYTES: &[u8] = include_bytes!("../../resources/tree_height_20/rln_final.zkey");
#[cfg(not(target_arch = "wasm32"))]
const GRAPH_BYTES: &[u8] = include_bytes!("../../resources/tree_height_20/graph.bin");
lazy_static! {
static ref ZKEY: (ProvingKey<Curve>, ConstraintMatrices<Fr>) = {
cfg_if! {
if #[cfg(feature = "arkzkey")] {
read_arkzkey_from_bytes_uncompressed(ARKZKEY_BYTES).expect("Failed to read arkzkey")
} else {
let mut reader = Cursor::new(ZKEY_BYTES);
read_zkey(&mut reader).expect("Failed to read zkey")
}
}
};
}
pub const TEST_TREE_HEIGHT: usize = 20;
// The following types define the pairing friendly elliptic curve, the underlying finite fields and groups default to this module
// Note that proofs are serialized assuming Fr to be 4x8 = 32 bytes in size. Hence, changing to a curve with different encoding will make proof verification to fail
pub type Curve = Bn254;
pub type Fr = ArkFr;
pub type Fq = ArkFq;
pub type Fq2 = ArkFq2;
pub type G1Affine = ArkG1Affine;
pub type G1Projective = ArkG1Projective;
pub type G2Affine = ArkG2Affine;
pub type G2Projective = ArkG2Projective;
// Loads the proving key using a bytes vector
pub fn zkey_from_raw(zkey_data: &[u8]) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
if zkey_data.is_empty() {
return Err(Report::msg("No proving key found!"));
}
let proving_key_and_matrices = match () {
#[cfg(feature = "arkzkey")]
() => read_arkzkey_from_bytes_uncompressed(zkey_data)?,
#[cfg(not(feature = "arkzkey"))]
() => {
let mut reader = Cursor::new(zkey_data);
read_zkey(&mut reader)?
}
};
Ok(proving_key_and_matrices)
}
// Loads the proving key
#[cfg(not(target_arch = "wasm32"))]
pub fn zkey_from_folder() -> &'static (ProvingKey<Curve>, ConstraintMatrices<Fr>) {
&ZKEY
}
pub fn calculate_rln_witness<I: IntoIterator<Item = (String, Vec<Fr>)>>(
inputs: I,
graph_data: &[u8],
) -> Vec<Fr> {
calc_witness(inputs, graph_data)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn graph_from_folder() -> &'static [u8] {
GRAPH_BYTES
}
////////////////////////////////////////////////////////
// Functions and structs from [arkz-key](https://github.com/zkmopro/ark-zkey/blob/main/src/lib.rs#L106)
// without print and allow to choose between compressed and uncompressed arkzkey
////////////////////////////////////////////////////////
#[cfg(feature = "arkzkey")]
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
pub struct SerializableProvingKey(pub ProvingKey<Bn254>);
#[cfg(feature = "arkzkey")]
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
pub struct SerializableConstraintMatrices<F: Field> {
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<F>,
pub b: SerializableMatrix<F>,
pub c: SerializableMatrix<F>,
}
#[cfg(feature = "arkzkey")]
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
pub struct SerializableMatrix<F: Field> {
pub data: Vec<Vec<(F, usize)>>,
}
#[cfg(feature = "arkzkey")]
pub fn read_arkzkey_from_bytes_uncompressed(
arkzkey_data: &[u8],
) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
if arkzkey_data.is_empty() {
return Err(Report::msg("No proving key found!"));
}
let mut cursor = std::io::Cursor::new(arkzkey_data);
let serialized_proving_key =
SerializableProvingKey::deserialize_uncompressed_unchecked(&mut cursor)
.wrap_err("Failed to deserialize proving key")?;
let serialized_constraint_matrices =
SerializableConstraintMatrices::deserialize_uncompressed_unchecked(&mut cursor)
.wrap_err("Failed to deserialize constraint matrices")?;
// Get on right form for API
let proving_key: ProvingKey<Bn254> = serialized_proving_key.0;
let constraint_matrices: ConstraintMatrices<ark_bn254::Fr> = 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))
}

114
rln/src/circuit/qap.rs Normal file
View File

@@ -0,0 +1,114 @@
// This file is based on the code by arkworks. Its preimage can be found here:
// https://github.com/arkworks-rs/circom-compat/blob/3c95ed98e23a408b4d99a53e483a9bba39685a4e/src/circom/qap.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<F: PrimeField, D: EvaluationDomain<F>>(
cs: ConstraintSystemRef<F>,
t: &F,
) -> Result<(Vec<F>, Vec<F>, Vec<F>, F, usize, usize), SynthesisError> {
LibsnarkReduction::instance_map_with_evaluation::<F, D>(cs, t)
}
fn witness_map_from_matrices<F: PrimeField, D: EvaluationDomain<F>>(
matrices: &ConstraintMatrices<F>,
num_inputs: usize,
num_constraints: usize,
full_assignment: &[F],
) -> Result<Vec<F>, 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];
#[allow(unexpected_cfgs)]
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];
#[allow(unexpected_cfgs)]
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);
#[allow(unexpected_cfgs)]
cfg_iter_mut!(ab)
.zip(c)
.for_each(|(ab_i, c_i)| *ab_i -= &c_i);
Ok(ab)
}
fn h_query_scalars<F: PrimeField, D: EvaluationDomain<F>>(
max_power: usize,
t: F,
_: F,
delta_inverse: F,
) -> Result<Vec<F>, SynthesisError> {
// the usual H query has domain-1 powers. Z has domain powers. So HZ has 2*domain-1 powers.
#[allow(unexpected_cfgs)]
let mut scalars = cfg_into_iter!(0..2 * max_power + 1)
.map(|i| delta_inverse * t.pow([i as u64]))
.collect::<Vec<_>>();
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);
#[allow(unexpected_cfgs)]
Ok(cfg_into_iter!(scalars).skip(1).step_by(2).collect())
}
}

371
rln/src/circuit/zkey.rs Normal file
View File

@@ -0,0 +1,371 @@
// This file is based on the code by arkworks. Its preimage can be found here:
// https://github.com/arkworks-rs/circom-compat/blob/3c95ed98e23a408b4d99a53e483a9bba39685a4e/src/zkey.rs
//! ZKey Parsing
//!
//! Each ZKey file is broken into sections:
//! Header(1)
//! Prover Type 1 Groth
//! HeaderGroth(2)
//! n8q
//! q
//! n8r
//! r
//! NVars
//! NPub
//! DomainSize (multiple of 2
//! alpha1
//! beta1
//! delta1
//! beta2
//! gamma2
//! delta2
//! IC(3)
//! Coefs(4)
//! PointsA(5)
//! PointsB1(6)
//! PointsB2(7)
//! PointsC(8)
//! PointsH(9)
//! Contributions(10)
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<T> = Result<T, SerializationError>;
#[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<R: Read + Seek>(
reader: &mut R,
) -> IoResult<(ProvingKey<Bn254>, ConstraintMatrices<Fr>)> {
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<u32, Vec<Section>>,
reader: &'a mut R,
}
impl<'a, R: Read + Seek> BinFile<'a, R> {
fn new(reader: &'a mut R) -> IoResult<Self> {
let mut magic = [0u8; 4];
reader.read_exact(&mut magic)?;
let version = reader.read_u32::<LittleEndian>()?;
let num_sections = reader.read_u32::<LittleEndian>()?;
let mut sections = HashMap::new();
for _ in 0..num_sections {
let section_id = reader.read_u32::<LittleEndian>()?;
let section_length = reader.read_u64::<LittleEndian>()?;
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<ProvingKey<Bn254>> {
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::<Bn254> {
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::<Bn254> {
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<HeaderGroth> {
let section = self.get_section(2);
let header = HeaderGroth::new(&mut self.reader, &section)?;
Ok(header)
}
fn ic(&mut self, n_public: usize) -> IoResult<Vec<G1Affine>> {
// 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<ConstraintMatrices<Fr>> {
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::<LittleEndian>()?;
// 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::<LittleEndian>()?;
let constraint: u32 = self.reader.read_u32::<LittleEndian>()?;
let signal: u32 = self.reader.read_u32::<LittleEndian>()?;
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<Vec<G1Affine>> {
self.g1_section(n_vars, 5)
}
fn b_g1_query(&mut self, n_vars: usize) -> IoResult<Vec<G1Affine>> {
self.g1_section(n_vars, 6)
}
fn b_g2_query(&mut self, n_vars: usize) -> IoResult<Vec<G2Affine>> {
self.g2_section(n_vars, 7)
}
fn l_query(&mut self, n_vars: usize) -> IoResult<Vec<G1Affine>> {
self.g1_section(n_vars, 8)
}
fn h_query(&mut self, n_vars: usize) -> IoResult<Vec<G1Affine>> {
self.g1_section(n_vars, 9)
}
fn g1_section(&mut self, num: usize, section_id: usize) -> IoResult<Vec<G1Affine>> {
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<Vec<G2Affine>> {
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<R: Read>(reader: &mut R) -> IoResult<Self> {
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<R: Read + Seek>(reader: &mut R, section: &Section) -> IoResult<Self> {
reader.seek(SeekFrom::Start(section.position))?;
Self::read(reader)
}
fn read<R: Read>(mut reader: &mut R) -> IoResult<Self> {
// TODO: Impl From<u32> 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<R: Read>(reader: &mut R) -> IoResult<Fr> {
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<R: Read>(reader: &mut R) -> IoResult<Fq> {
let bigint = BigInteger256::deserialize_uncompressed(reader)?;
// if you use Fq::new it multiplies by R
Ok(Fq::new_unchecked(bigint))
}
pub fn deserialize_field2<R: Read>(reader: &mut R) -> IoResult<Fq2> {
let c0 = deserialize_field(reader)?;
let c1 = deserialize_field(reader)?;
Ok(Fq2::new(c0, c1))
}
fn deserialize_g1<R: Read>(reader: &mut R) -> IoResult<G1Affine> {
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<R: Read>(reader: &mut R) -> IoResult<G2Affine> {
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<R: Read>(reader: &mut R, n_vars: u32) -> IoResult<Vec<G1Affine>> {
(0..n_vars).map(|_| deserialize_g1(reader)).collect()
}
fn deserialize_g2_vec<R: Read>(reader: &mut R, n_vars: u32) -> IoResult<Vec<G2Affine>> {
(0..n_vars).map(|_| deserialize_g2(reader)).collect()
}

View File

@@ -8,6 +8,7 @@ use crate::public::{hash as public_hash, poseidon_hash as public_poseidon_hash,
// First argument to the macro is context,
// second is the actual method on `RLN`
// rest are all other arguments to the method
#[cfg(not(feature = "stateless"))]
macro_rules! call {
($instance:expr, $method:ident $(, $arg:expr)*) => {
{
@@ -228,17 +229,15 @@ pub extern "C" fn new(ctx: *mut *mut RLN) -> bool {
#[no_mangle]
pub extern "C" fn new_with_params(
tree_height: usize,
circom_buffer: *const Buffer,
zkey_buffer: *const Buffer,
vk_buffer: *const Buffer,
graph_data: *const Buffer,
tree_config: *const Buffer,
ctx: *mut *mut RLN,
) -> bool {
match RLN::new_with_params(
tree_height,
circom_buffer.process().to_vec(),
zkey_buffer.process().to_vec(),
vk_buffer.process().to_vec(),
graph_data.process().to_vec(),
tree_config.process(),
) {
Ok(rln) => {
@@ -256,15 +255,13 @@ pub extern "C" fn new_with_params(
#[cfg(feature = "stateless")]
#[no_mangle]
pub extern "C" fn new_with_params(
circom_buffer: *const Buffer,
zkey_buffer: *const Buffer,
vk_buffer: *const Buffer,
graph_buffer: *const Buffer,
ctx: *mut *mut RLN,
) -> bool {
match RLN::new_with_params(
circom_buffer.process().to_vec(),
zkey_buffer.process().to_vec(),
vk_buffer.process().to_vec(),
graph_buffer.process().to_vec(),
) {
Ok(rln) => {
unsafe { *ctx = Box::into_raw(Box::new(rln)) };

View File

@@ -23,7 +23,7 @@ static POSEIDON: Lazy<Poseidon<Fr>> = Lazy::new(|| Poseidon::<Fr>::from(&ROUND_P
pub fn poseidon_hash(input: &[Fr]) -> Fr {
POSEIDON
.hash(input.to_vec())
.hash(input)
.expect("hash with fixed input size can't fail")
}

View File

@@ -1,6 +1,6 @@
#![allow(dead_code)]
pub mod circuit;
#[cfg(not(target_arch = "wasm32"))]
pub mod ffi;
pub mod hashers;
#[cfg(feature = "pmtree-ft")]
pub mod pm_tree_adapter;
@@ -10,6 +10,3 @@ pub mod public;
#[cfg(test)]
pub mod public_api_tests;
pub mod utils;
#[cfg(not(target_arch = "wasm32"))]
pub mod ffi;

View File

@@ -244,7 +244,7 @@ impl ZerokitMerkleTree for PmTree {
(0, 0) => Err(Report::msg("no leaves or indices to be removed")),
(1, 0) => self.set(start, leaves[0]),
(0, 1) => self.delete(indices[0]),
(_, 0) => self.set_range(start, leaves),
(_, 0) => self.set_range(start, leaves.into_iter()),
(0, _) => self.remove_indices(&indices),
(_, _) => self.remove_indices_and_set_leaves(start, leaves, &indices),
}

View File

@@ -1,9 +1,8 @@
// This crate collects all the underlying primitives used to implement RLN
use ark_circom::{CircomReduction, WitnessCalculator};
use ark_bn254::Fr;
use ark_groth16::{prepare_verifying_key, Groth16, Proof as ArkProof, ProvingKey, VerifyingKey};
use ark_relations::r1cs::ConstraintMatrices;
use ark_relations::r1cs::SynthesisError;
use ark_relations::r1cs::{ConstraintMatrices, SynthesisError};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{rand::thread_rng, UniformRand};
use color_eyre::{Report, Result};
@@ -11,20 +10,16 @@ use num_bigint::BigInt;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Mutex;
#[cfg(debug_assertions)]
#[cfg(test)]
use std::time::Instant;
use thiserror::Error;
use tiny_keccak::{Hasher as _, Keccak};
use crate::circuit::{Curve, Fr};
use crate::hashers::hash_to_field;
use crate::hashers::poseidon_hash;
use crate::circuit::{calculate_rln_witness, qap::CircomReduction, Curve};
use crate::hashers::{hash_to_field, poseidon_hash};
use crate::poseidon_tree::*;
use crate::public::RLN_IDENTIFIER;
use crate::utils::*;
use cfg_if::cfg_if;
use utils::{ZerokitMerkleProof, ZerokitMerkleTree};
///////////////////////////////////////////////////////
@@ -103,18 +98,26 @@ pub fn deserialize_identity_tuple(serialized: Vec<u8>) -> (Fr, Fr, Fr, Fr) {
/// # Errors
///
/// Returns an error if `rln_witness.message_id` is not within `rln_witness.user_message_limit`.
/// input data is [ identity_secret<32> | user_message_limit<32> | message_id<32> | path_elements[<32>] | identity_path_index<8> | x<32> | external_nullifier<32> ]
pub fn serialize_witness(rln_witness: &RLNWitnessInput) -> Result<Vec<u8>> {
// Check if message_id is within user_message_limit
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&rln_witness.identity_secret));
serialized.append(&mut fr_to_bytes_le(&rln_witness.user_message_limit));
serialized.append(&mut fr_to_bytes_le(&rln_witness.message_id));
serialized.append(&mut vec_fr_to_bytes_le(&rln_witness.path_elements)?);
serialized.append(&mut vec_u8_to_bytes_le(&rln_witness.identity_path_index)?);
serialized.append(&mut fr_to_bytes_le(&rln_witness.x));
serialized.append(&mut fr_to_bytes_le(&rln_witness.external_nullifier));
// Calculate capacity for Vec:
// - 5 fixed field elements: identity_secret, user_message_limit, message_id, x, external_nullifier
// - variable number of path elements
// - identity_path_index (variable size)
let mut serialized: Vec<u8> = Vec::with_capacity(
fr_byte_size() * (5 + rln_witness.path_elements.len())
+ rln_witness.identity_path_index.len(),
);
serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.identity_secret));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.user_message_limit));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.message_id));
serialized.extend_from_slice(&vec_fr_to_bytes_le(&rln_witness.path_elements)?);
serialized.extend_from_slice(&vec_u8_to_bytes_le(&rln_witness.identity_path_index)?);
serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.x));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_witness.external_nullifier));
Ok(serialized)
}
@@ -222,7 +225,7 @@ pub fn proof_inputs_to_rln_witness(
))
}
/// Creates `RLNWitnessInput` from it's fields.
/// Creates [`RLNWitnessInput`] from it's fields.
///
/// # Errors
///
@@ -309,14 +312,17 @@ pub fn proof_values_from_witness(rln_witness: &RLNWitnessInput) -> Result<RLNPro
})
}
/// input_data is [ root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
pub fn serialize_proof_values(rln_proof_values: &RLNProofValues) -> Vec<u8> {
let mut serialized: Vec<u8> = Vec::new();
// Calculate capacity for Vec:
// 5 field elements: root, external_nullifier, x, y, nullifier
let mut serialized = Vec::with_capacity(fr_byte_size() * 5);
serialized.append(&mut fr_to_bytes_le(&rln_proof_values.root));
serialized.append(&mut fr_to_bytes_le(&rln_proof_values.external_nullifier));
serialized.append(&mut fr_to_bytes_le(&rln_proof_values.x));
serialized.append(&mut fr_to_bytes_le(&rln_proof_values.y));
serialized.append(&mut fr_to_bytes_le(&rln_proof_values.nullifier));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_proof_values.root));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_proof_values.external_nullifier));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_proof_values.x));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_proof_values.y));
serialized.extend_from_slice(&fr_to_bytes_le(&rln_proof_values.nullifier));
serialized
}
@@ -353,30 +359,43 @@ pub fn deserialize_proof_values(serialized: &[u8]) -> (RLNProofValues, usize) {
)
}
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
pub fn prepare_prove_input(
identity_secret: Fr,
id_index: usize,
user_message_limit: Fr,
message_id: Fr,
external_nullifier: Fr,
signal: &[u8],
) -> Vec<u8> {
let mut serialized: Vec<u8> = Vec::new();
// Calculate capacity for Vec:
// - 4 field elements: identity_secret, user_message_limit, message_id, external_nullifier
// - 16 bytes for two normalized usize values (id_index<8> + signal_len<8>)
// - variable length signal data
let mut serialized = Vec::with_capacity(fr_byte_size() * 4 + 16 + signal.len()); // length of 4 fr elements + 16 bytes (id_index + len) + signal length
serialized.append(&mut fr_to_bytes_le(&identity_secret));
serialized.append(&mut normalize_usize(id_index));
serialized.append(&mut fr_to_bytes_le(&external_nullifier));
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.to_vec());
serialized.extend_from_slice(&fr_to_bytes_le(&identity_secret));
serialized.extend_from_slice(&normalize_usize(id_index));
serialized.extend_from_slice(&fr_to_bytes_le(&user_message_limit));
serialized.extend_from_slice(&fr_to_bytes_le(&message_id));
serialized.extend_from_slice(&fr_to_bytes_le(&external_nullifier));
serialized.extend_from_slice(&normalize_usize(signal.len()));
serialized.extend_from_slice(signal);
serialized
}
#[allow(clippy::redundant_clone)]
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
pub fn prepare_verify_input(proof_data: Vec<u8>, signal: &[u8]) -> Vec<u8> {
let mut serialized: Vec<u8> = Vec::new();
// Calculate capacity for Vec:
// - proof_data contains the proof and proof values (proof<128> + root<32> + external_nullifier<32> + x<32> + y<32> + nullifier<32>)
// - 8 bytes for normalized signal length value (signal_len<8>)
// - variable length signal data
let mut serialized = Vec::with_capacity(proof_data.len() + 8 + signal.len());
serialized.append(&mut proof_data.clone());
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.to_vec());
serialized.extend(proof_data);
serialized.extend_from_slice(&normalize_usize(signal.len()));
serialized.extend_from_slice(signal);
serialized
}
@@ -544,13 +563,13 @@ pub fn generate_proof_with_witness(
proving_key: &(ProvingKey<Curve>, ConstraintMatrices<Fr>),
) -> Result<ArkProof<Curve>, ProofError> {
// If in debug mode, we measure and later print time take to compute witness
#[cfg(debug_assertions)]
#[cfg(test)]
let now = Instant::now();
let full_assignment =
calculate_witness_element::<Curve>(witness).map_err(ProofError::WitnessError)?;
#[cfg(debug_assertions)]
#[cfg(test)]
println!("witness generation took: {:.2?}", now.elapsed());
// Random Values
@@ -559,7 +578,7 @@ pub fn generate_proof_with_witness(
let s = Fr::rand(&mut rng);
// If in debug mode, we measure and later print time take to compute proof
#[cfg(debug_assertions)]
#[cfg(test)]
let now = Instant::now();
let proof = Groth16::<_, CircomReduction>::create_proof_with_reduction_and_matrices(
@@ -572,7 +591,7 @@ pub fn generate_proof_with_witness(
full_assignment.as_slice(),
)?;
#[cfg(debug_assertions)]
#[cfg(test)]
println!("proof generation took: {:.2?}", now.elapsed());
Ok(proof)
@@ -585,40 +604,23 @@ pub fn generate_proof_with_witness(
/// Returns an error if `rln_witness.message_id` is not within `rln_witness.user_message_limit`.
pub fn inputs_for_witness_calculation(
rln_witness: &RLNWitnessInput,
) -> Result<[(&str, Vec<BigInt>); 7]> {
) -> Result<[(&str, Vec<Fr>); 7]> {
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
// We convert the path indexes to field elements
// TODO: check if necessary
let mut path_elements = Vec::new();
for v in rln_witness.path_elements.iter() {
path_elements.push(to_bigint(v)?);
}
let mut identity_path_index = Vec::new();
let mut identity_path_index = Vec::with_capacity(rln_witness.identity_path_index.len());
rln_witness
.identity_path_index
.iter()
.for_each(|v| identity_path_index.push(BigInt::from(*v)));
.for_each(|v| identity_path_index.push(Fr::from(*v)));
Ok([
(
"identitySecret",
vec![to_bigint(&rln_witness.identity_secret)?],
),
(
"userMessageLimit",
vec![to_bigint(&rln_witness.user_message_limit)?],
),
("messageId", vec![to_bigint(&rln_witness.message_id)?]),
("pathElements", path_elements),
("identitySecret", vec![rln_witness.identity_secret]),
("userMessageLimit", vec![rln_witness.user_message_limit]),
("messageId", vec![rln_witness.message_id]),
("pathElements", rln_witness.path_elements.clone()),
("identityPathIndex", identity_path_index),
("x", vec![to_bigint(&rln_witness.x)?]),
(
"externalNullifier",
vec![to_bigint(&rln_witness.external_nullifier)?],
),
("x", vec![rln_witness.x]),
("externalNullifier", vec![rln_witness.external_nullifier]),
])
}
@@ -628,34 +630,20 @@ pub fn inputs_for_witness_calculation(
///
/// Returns a [`ProofError`] if proving fails.
pub fn generate_proof(
#[cfg(not(target_arch = "wasm32"))] witness_calculator: &Mutex<WitnessCalculator>,
#[cfg(target_arch = "wasm32")] witness_calculator: &mut WitnessCalculator,
proving_key: &(ProvingKey<Curve>, ConstraintMatrices<Fr>),
rln_witness: &RLNWitnessInput,
graph_data: &[u8],
) -> Result<ArkProof<Curve>, ProofError> {
let inputs = inputs_for_witness_calculation(rln_witness)?
.into_iter()
.map(|(name, values)| (name.to_string(), values));
// If in debug mode, we measure and later print time take to compute witness
#[cfg(debug_assertions)]
#[cfg(test)]
let now = Instant::now();
let full_assignment = calculate_rln_witness(inputs, graph_data);
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
let full_assignment = witness_calculator
.calculate_witness_element::<Curve, _>(inputs, false)
.map_err(ProofError::WitnessError)?;
} else {
let full_assignment = witness_calculator
.lock()
.expect("witness_calculator mutex should not get poisoned")
.calculate_witness_element::<Curve, _>(inputs, false)
.map_err(ProofError::WitnessError)?;
}
}
#[cfg(debug_assertions)]
#[cfg(test)]
println!("witness generation took: {:.2?}", now.elapsed());
// Random Values
@@ -664,7 +652,7 @@ pub fn generate_proof(
let s = Fr::rand(&mut rng);
// If in debug mode, we measure and later print time take to compute proof
#[cfg(debug_assertions)]
#[cfg(test)]
let now = Instant::now();
let proof = Groth16::<_, CircomReduction>::create_proof_with_reduction_and_matrices(
&proving_key.0,
@@ -676,7 +664,7 @@ pub fn generate_proof(
full_assignment.as_slice(),
)?;
#[cfg(debug_assertions)]
#[cfg(test)]
println!("proof generation took: {:.2?}", now.elapsed());
Ok(proof)
@@ -707,12 +695,12 @@ pub fn verify_proof(
//let pr: ArkProof<Curve> = (*proof).into();
// If in debug mode, we measure and later print time take to verify proof
#[cfg(debug_assertions)]
#[cfg(test)]
let now = Instant::now();
let verified = Groth16::<_, CircomReduction>::verify_proof(&pvk, proof, &inputs)?;
#[cfg(debug_assertions)]
#[cfg(test)]
println!("verify took: {:.2?}", now.elapsed());
Ok(verified)
@@ -739,7 +727,7 @@ where
a.map_err(serde::de::Error::custom)
}
/// Converts a JSON value into [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object.
/// Converts a JSON value into [`RLNWitnessInput`] object.
///
/// # Errors
///
@@ -751,7 +739,7 @@ pub fn rln_witness_from_json(input_json: serde_json::Value) -> Result<RLNWitness
Ok(rln_witness)
}
/// Converts a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object to the corresponding JSON serialization.
/// Converts a [`RLNWitnessInput`] object to the corresponding JSON serialization.
///
/// # Errors
///
@@ -763,7 +751,7 @@ pub fn rln_witness_to_json(rln_witness: &RLNWitnessInput) -> Result<serde_json::
Ok(rln_witness_json)
}
/// Converts a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object to the corresponding JSON serialization.
/// Converts a [`RLNWitnessInput`] object to the corresponding JSON serialization.
/// Before serialisation the data should be translated into big int for further calculation in the witness calculator.
///
/// # Errors

View File

@@ -1,34 +1,31 @@
use crate::circuit::{vk_from_raw, zkey_from_raw, Curve, Fr};
/// This is the main public API for RLN module. It is used by the FFI, and should be
/// used by tests etc. as well
#[cfg(not(feature = "stateless"))]
use {
crate::{circuit::TEST_TREE_HEIGHT, poseidon_tree::PoseidonTree},
serde_json::{json, Value},
std::str::FromStr,
utils::{Hasher, ZerokitMerkleProof, ZerokitMerkleTree},
};
use crate::circuit::{zkey_from_raw, Curve, Fr};
use crate::hashers::{hash_to_field, poseidon_hash as utils_poseidon_hash};
use crate::protocol::*;
use crate::utils::*;
/// This is the main public API for RLN module. It is used by the FFI, and should be
/// used by tests etc. as well
use ark_groth16::Proof as ArkProof;
use ark_groth16::{ProvingKey, VerifyingKey};
#[cfg(not(target_arch = "wasm32"))]
use {
crate::circuit::{graph_from_folder, zkey_from_folder},
std::default::Default,
};
use ark_groth16::{Proof as ArkProof, ProvingKey, VerifyingKey};
use ark_relations::r1cs::ConstraintMatrices;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, Write};
use cfg_if::cfg_if;
use color_eyre::{Report, Result};
use std::io::Cursor;
use utils::{ZerokitMerkleProof, ZerokitMerkleTree};
cfg_if! {
if #[cfg(not(target_arch = "wasm32"))] {
use std::default::Default;
use std::sync::Mutex;
use crate::circuit::{circom_from_folder, vk_from_folder, circom_from_raw, zkey_from_folder, TEST_TREE_HEIGHT};
use ark_circom::WitnessCalculator;
use crate::poseidon_tree::PoseidonTree;
use serde_json::{json, Value};
use utils::{Hasher};
use std::sync::Arc;
use std::str::FromStr;
} else {
use std::marker::*;
use num_bigint::BigInt;
}
}
#[cfg(target_arch = "wasm32")]
use num_bigint::BigInt;
/// The application-specific RLN identifier.
///
@@ -43,16 +40,10 @@ pub const RLN_IDENTIFIER: &[u8] = b"zerokit/rln/010203040506070809";
pub struct RLN {
proving_key: (ProvingKey<Curve>, ConstraintMatrices<Fr>),
pub(crate) verification_key: VerifyingKey<Curve>,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) graph_data: Vec<u8>,
#[cfg(not(feature = "stateless"))]
pub(crate) tree: PoseidonTree,
// The witness calculator can't be loaded in zerokit. Since this struct
// contains a lifetime, a PhantomData is necessary to avoid a compiler
// error since the lifetime is not being used
#[cfg(not(target_arch = "wasm32"))]
pub(crate) witness_calculator: Arc<Mutex<WitnessCalculator>>,
#[cfg(target_arch = "wasm32")]
_marker: PhantomData<()>,
}
impl RLN {
@@ -81,10 +72,9 @@ impl RLN {
let rln_config: Value = serde_json::from_str(&String::from_utf8(input)?)?;
let tree_config = rln_config["tree_config"].to_string();
let witness_calculator = circom_from_folder();
let proving_key = zkey_from_folder();
let verification_key = vk_from_folder();
let proving_key = zkey_from_folder().to_owned();
let verification_key = proving_key.0.vk.to_owned();
let graph_data = graph_from_folder().to_owned();
let tree_config: <PoseidonTree as ZerokitMerkleTree>::Config = if tree_config.is_empty() {
<PoseidonTree as ZerokitMerkleTree>::Config::default()
@@ -100,13 +90,11 @@ impl RLN {
)?;
Ok(RLN {
witness_calculator: witness_calculator.to_owned(),
proving_key: proving_key.to_owned(),
verification_key: verification_key.to_owned(),
proving_key,
verification_key,
graph_data,
#[cfg(not(feature = "stateless"))]
tree,
#[cfg(target_arch = "wasm32")]
_marker: PhantomData,
})
}
@@ -121,17 +109,14 @@ impl RLN {
#[cfg_attr(docsrs, doc(cfg(feature = "stateless")))]
#[cfg(all(not(target_arch = "wasm32"), feature = "stateless"))]
pub fn new() -> Result<RLN> {
#[cfg(not(target_arch = "wasm32"))]
let witness_calculator = circom_from_folder();
let proving_key = zkey_from_folder();
let verification_key = vk_from_folder();
let proving_key = zkey_from_folder().to_owned();
let verification_key = proving_key.0.vk.to_owned();
let graph_data = graph_from_folder().to_owned();
Ok(RLN {
witness_calculator: witness_calculator.to_owned(),
proving_key: proving_key.to_owned(),
verification_key: verification_key.to_owned(),
#[cfg(target_arch = "wasm32")]
_marker: PhantomData,
proving_key,
verification_key,
graph_data,
})
}
@@ -139,9 +124,8 @@ impl RLN {
///
/// Input parameters are
/// - `tree_height`: the height of the internal Merkle tree
/// - `circom_vec`: a byte vector containing the ZK circuit (`rln.wasm`) as binary file
/// - `zkey_vec`: a byte vector containing to the proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) as binary file
/// - `vk_vec`: a byte vector containing to the verification key (`verification_key.arkvkey`) as binary file
/// - `graph_data`: a byte vector containing the graph data (`graph.bin`) as binary file
/// - `tree_config_input`: a reader for a string containing a json with the merkle tree configuration
///
/// Example:
@@ -153,38 +137,34 @@ impl RLN {
/// let resources_folder = "./resources/tree_height_20/";
///
/// let mut resources: Vec<Vec<u8>> = Vec::new();
/// for filename in ["rln.wasm", "rln_final.zkey", "verification_key.arkvkey"] {
/// for filename in ["rln_final.zkey", "graph.bin"] {
/// let fullpath = format!("{resources_folder}{filename}");
/// let mut file = File::open(&fullpath).expect("no file found");
/// let metadata = std::fs::metadata(&fullpath).expect("unable to read metadata");
/// let mut buffer = vec![0; metadata.len() as usize];
/// file.read_exact(&mut buffer).expect("buffer overflow");
/// resources.push(buffer);
/// let tree_config = "{}".to_string();
/// let tree_config_input = &Buffer::from(tree_config.as_bytes());
/// }
///
/// let tree_config = "".to_string();
/// let tree_config_buffer = &Buffer::from(tree_config.as_bytes());
///
/// let mut rln = RLN::new_with_params(
/// tree_height,
/// resources[0].clone(),
/// resources[1].clone(),
/// resources[2].clone(),
/// tree_config_input,
/// tree_config_buffer,
/// );
/// ```
#[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))]
pub fn new_with_params<R: Read>(
tree_height: usize,
circom_vec: Vec<u8>,
zkey_vec: Vec<u8>,
vk_vec: Vec<u8>,
graph_data: Vec<u8>,
mut tree_config_input: R,
) -> Result<RLN> {
#[cfg(not(target_arch = "wasm32"))]
let witness_calculator = circom_from_raw(&circom_vec)?;
let proving_key = zkey_from_raw(&zkey_vec)?;
let verification_key = vk_from_raw(&vk_vec, &zkey_vec)?;
let verification_key = proving_key.0.vk.to_owned();
let mut tree_config_vec: Vec<u8> = Vec::new();
tree_config_input.read_to_end(&mut tree_config_vec)?;
@@ -204,22 +184,19 @@ impl RLN {
)?;
Ok(RLN {
witness_calculator,
proving_key,
verification_key,
graph_data,
#[cfg(not(feature = "stateless"))]
tree,
#[cfg(target_arch = "wasm32")]
_marker: PhantomData,
})
}
/// Creates a new stateless RLN object by passing circuit resources as byte vectors.
///
/// Input parameters are
/// - `circom_vec`: a byte vector containing the ZK circuit (`rln.wasm`) as binary file
/// - `zkey_vec`: a byte vector containing to the proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) as binary file
/// - `vk_vec`: a byte vector containing to the verification key (`verification_key.arkvkey`) as binary file
/// - `graph_data`: a byte vector containing the graph data (`graph.bin`) as binary file
///
/// Example:
/// ```
@@ -229,7 +206,7 @@ impl RLN {
/// let resources_folder = "./resources/tree_height_20/";
///
/// let mut resources: Vec<Vec<u8>> = Vec::new();
/// for filename in ["rln.wasm", "rln_final.zkey", "verification_key.arkvkey"] {
/// for filename in ["rln_final.zkey", "graph.bin"] {
/// let fullpath = format!("{resources_folder}{filename}");
/// let mut file = File::open(&fullpath).expect("no file found");
/// let metadata = std::fs::metadata(&fullpath).expect("unable to read metadata");
@@ -241,60 +218,47 @@ impl RLN {
/// let mut rln = RLN::new_with_params(
/// resources[0].clone(),
/// resources[1].clone(),
/// resources[2].clone(),
/// );
/// ```
#[cfg(all(not(target_arch = "wasm32"), feature = "stateless"))]
pub fn new_with_params(circom_vec: Vec<u8>, zkey_vec: Vec<u8>, vk_vec: Vec<u8>) -> Result<RLN> {
let witness_calculator = circom_from_raw(&circom_vec)?;
pub fn new_with_params(zkey_vec: Vec<u8>, graph_data: Vec<u8>) -> Result<RLN> {
let proving_key = zkey_from_raw(&zkey_vec)?;
let verification_key = vk_from_raw(&vk_vec, &zkey_vec)?;
let verification_key = proving_key.0.vk.to_owned();
Ok(RLN {
witness_calculator,
proving_key,
verification_key,
graph_data,
})
}
/// Creates a new stateless RLN object by passing circuit resources as byte vectors.
/// Creates a new stateless RLN object by passing circuit resources as a byte vector.
///
/// Input parameters are
/// - `zkey_vec`: a byte vector containing to the proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) as binary file
/// - `vk_vec`: a byte vector containing to the verification key (`verification_key.arkvkey`) as binary file
/// - `zkey_vec`: a byte vector containing the proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) as binary file
///
/// Example:
/// ```
/// use std::fs::File;
/// use std::io::Read;
///
/// let resources_folder = "./resources/tree_height_20/";
/// let zkey_path = "./resources/tree_height_20/rln_final.zkey";
///
/// let mut resources: Vec<Vec<u8>> = Vec::new();
/// for filename in ["rln_final.zkey", "verification_key.arkvkey"] {
/// let fullpath = format!("{resources_folder}{filename}");
/// let mut file = File::open(&fullpath).expect("no file found");
/// let metadata = std::fs::metadata(&fullpath).expect("unable to read metadata");
/// let mut buffer = vec![0; metadata.len() as usize];
/// file.read_exact(&mut buffer).expect("buffer overflow");
/// resources.push(buffer);
/// }
/// let mut file = File::open(zkey_path).expect("Failed to open file");
/// let metadata = std::fs::metadata(zkey_path).expect("Failed to read metadata");
/// let mut zkey_vec = vec![0; metadata.len() as usize];
/// file.read_exact(&mut zkey_vec).expect("Failed to read file");
///
/// let mut rln = RLN::new_with_params(
/// resources[0].clone(),
/// resources[1].clone(),
/// );
/// let mut rln = RLN::new_with_params(zkey_vec)?;
/// ```
#[cfg(all(target_arch = "wasm32", feature = "stateless"))]
pub fn new_with_params(zkey_vec: Vec<u8>, vk_vec: Vec<u8>) -> Result<RLN> {
pub fn new_with_params(zkey_vec: Vec<u8>) -> Result<RLN> {
let proving_key = zkey_from_raw(&zkey_vec)?;
let verification_key = vk_from_raw(&vk_vec, &zkey_vec)?;
let verification_key = proving_key.0.vk.to_owned();
Ok(RLN {
proving_key,
verification_key,
_marker: PhantomData,
})
}
@@ -421,7 +385,7 @@ impl RLN {
// We set the leaves
self.tree
.override_range(index, leaves, [])
.override_range(index, leaves.into_iter(), [].into_iter())
.map_err(|_| Report::msg("Could not set leaves"))?;
Ok(())
}
@@ -504,7 +468,7 @@ impl RLN {
// We set the leaves
self.tree
.override_range(index, leaves, indices)
.override_range(index, leaves.into_iter(), indices.into_iter())
.map_err(|e| Report::msg(format!("Could not perform the batch operation: {e}")))?;
Ok(())
}
@@ -752,10 +716,10 @@ impl RLN {
////////////////////////////////////////////////////////
// zkSNARK APIs
////////////////////////////////////////////////////////
/// Computes a zkSNARK RLN proof using a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput).
/// Computes a zkSNARK RLN proof using a [`RLNWitnessInput`].
///
/// Input values are:
/// - `input_data`: a reader for the serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object, containing the public and private inputs to the ZK circuits (serialization done using [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness))
/// - `input_data`: a reader for the serialization of a [`RLNWitnessInput`] object, containing the public and private inputs to the ZK circuits (serialization done using [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness))
///
/// Output values are:
/// - `output_data`: a writer receiving the serialization of the zkSNARK proof
@@ -780,17 +744,11 @@ impl RLN {
mut output_data: W,
) -> Result<()> {
// We read input RLN witness and we serialize_compressed it
let mut serialized: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized)?;
let (rln_witness, _) = deserialize_witness(&serialized)?;
let mut serialized_witness: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized_witness)?;
let (rln_witness, _) = deserialize_witness(&serialized_witness)?;
/*
if self.witness_calculator.is_none() {
self.witness_calculator = CIRCOM(&self.resources_folder);
}
*/
let proof = generate_proof(&self.witness_calculator, &self.proving_key, &rln_witness)?;
let proof = generate_proof(&self.proving_key, &rln_witness, &self.graph_data)?;
// Note: we export a serialization of ark-groth16::Proof not semaphore::Proof
proof.serialize_compressed(&mut output_data)?;
@@ -872,26 +830,25 @@ impl RLN {
/// let mut buffer = Cursor::new(fr_to_bytes_le(&rate_commitment));
/// rln.set_leaf(identity_index, &mut buffer).unwrap();
///
/// // We generate a random signal
/// let mut rng = rand::thread_rng();
/// let signal: [u8; 32] = rng.gen();
///
/// // We generate a random epoch
/// let epoch = hash_to_field(b"test-epoch");
/// // We generate a random rln_identifier
/// let rln_identifier = hash_to_field(b"test-rln-identifier");
/// let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
/// // We generate a external nullifier
/// let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
/// // We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
/// let message_id = Fr::from(1);
///
/// // We prepare input for generate_rln_proof API
/// // input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
/// let mut serialized: Vec<u8> = Vec::new();
/// serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
/// serialized.append(&mut normalize_usize(identity_index));
/// serialized.append(&mut fr_to_bytes_le(&user_message_limit));
/// serialized.append(&mut fr_to_bytes_le(&Fr::from(1))); // message_id
/// serialized.append(&mut fr_to_bytes_le(&external_nullifier));
/// serialized.append(&mut normalize_usize(signal_len).resize(8,0));
/// serialized.append(&mut signal.to_vec());
/// let prove_input = prepare_prove_input(
/// identity_secret_hash,
/// identity_index,
/// user_message_limit,
/// message_id,
/// external_nullifier,
/// &signal,
/// );
///
/// let mut input_buffer = Cursor::new(serialized);
/// let mut output_buffer = Cursor::new(Vec::<u8>::new());
@@ -913,7 +870,7 @@ impl RLN {
let (rln_witness, _) = proof_inputs_to_rln_witness(&mut self.tree, &witness_byte)?;
let proof_values = proof_values_from_witness(&rln_witness)?;
let proof = generate_proof(&self.witness_calculator, &self.proving_key, &rln_witness)?;
let proof = generate_proof(&self.proving_key, &rln_witness, &self.graph_data)?;
// Note: we export a serialization of ark-groth16::Proof not semaphore::Proof
// This proof is compressed, i.e. 128 bytes long
@@ -923,45 +880,43 @@ impl RLN {
Ok(())
}
// TODO: this function seems to use redundant witness (as bigint and serialized) and should be refactored
// Generate RLN Proof using a witness calculated from outside zerokit
//
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
#[cfg(target_arch = "wasm32")]
pub fn generate_rln_proof_with_witness<W: Write>(
&mut self,
calculated_witness: Vec<BigInt>,
rln_witness_vec: Vec<u8>,
mut output_data: W,
) -> Result<()> {
let (rln_witness, _) = deserialize_witness(&rln_witness_vec[..])?;
let proof_values = proof_values_from_witness(&rln_witness)?;
let proof = generate_proof_with_witness(calculated_witness, &self.proving_key).unwrap();
// Note: we export a serialization of ark-groth16::Proof not semaphore::Proof
// This proof is compressed, i.e. 128 bytes long
proof.serialize_compressed(&mut output_data)?;
output_data.write_all(&serialize_proof_values(&proof_values))?;
Ok(())
}
// Generate RLN Proof using a witness calculated from outside zerokit
//
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
// we skip it from documentation for now
/// Generate RLN Proof using a witness calculated from outside zerokit
///
/// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
#[cfg(not(target_arch = "wasm32"))]
pub fn generate_rln_proof_with_witness<R: Read, W: Write>(
&mut self,
mut input_data: R,
mut output_data: W,
) -> Result<()> {
let mut witness_byte: Vec<u8> = Vec::new();
input_data.read_to_end(&mut witness_byte)?;
let (rln_witness, _) = deserialize_witness(&witness_byte)?;
let mut serialized_witness: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized_witness)?;
let (rln_witness, _) = deserialize_witness(&serialized_witness)?;
let proof_values = proof_values_from_witness(&rln_witness)?;
let proof = generate_proof(&self.witness_calculator, &self.proving_key, &rln_witness)?;
let proof = generate_proof(&self.proving_key, &rln_witness, &self.graph_data)?;
// Note: we export a serialization of ark-groth16::Proof not semaphore::Proof
// This proof is compressed, i.e. 128 bytes long
proof.serialize_compressed(&mut output_data)?;
output_data.write_all(&serialize_proof_values(&proof_values))?;
Ok(())
}
/// Generate RLN Proof using a witness calculated from outside zerokit
///
/// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
#[cfg(target_arch = "wasm32")]
pub fn generate_rln_proof_with_witness<W: Write>(
&mut self,
calculated_witness: Vec<BigInt>,
serialized_witness: Vec<u8>,
mut output_data: W,
) -> Result<()> {
let (rln_witness, _) = deserialize_witness(&serialized_witness[..])?;
let proof_values = proof_values_from_witness(&rln_witness)?;
let proof = generate_proof_with_witness(calculated_witness, &self.proving_key).unwrap();
// Note: we export a serialization of ark-groth16::Proof not semaphore::Proof
// This proof is compressed, i.e. 128 bytes long
@@ -990,10 +945,9 @@ impl RLN {
/// // We prepare input for verify_rln_proof API
/// // input_data is `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var>]`
/// // that is [ proof_data || signal_len<8> | signal<var> ]
/// proof_data.append(&mut normalize_usize(signal_len));
/// proof_data.append(&mut signal.to_vec());
/// let verify_input = prepare_verify_input(proof_data, &signal);
///
/// let mut input_buffer = Cursor::new(proof_data);
/// let mut input_buffer = Cursor::new(verify_input);
/// let verified = rln.verify_rln_proof(&mut input_buffer).unwrap();
///
/// assert!(verified);
@@ -1069,7 +1023,7 @@ impl RLN {
/// roots_serialized.append(&mut fr_to_bytes_le(&root));
/// roots_buffer = Cursor::new(roots_serialized.clone());
/// let verified = rln
/// .verify_with_roots(&mut input_buffer.clone(), &mut roots_buffer)
/// .verify_with_roots(&mut input_buffer, &mut roots_buffer)
/// .unwrap();
///
/// assert!(verified);
@@ -1357,12 +1311,12 @@ impl RLN {
Ok(())
}
/// Returns the serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) populated from the identity secret, the Merkle tree index, the user message limit, the message id, the external nullifier (which include epoch and rln identifier) and signal.
/// Returns the serialization of a [`RLNWitnessInput`] populated from the identity secret, the Merkle tree index, the user message limit, the message id, the external nullifier (which include epoch and rln identifier) and signal.
///
/// Input values are:
/// - `input_data`: a reader for the serialization of `[ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]`
///
/// The function returns the corresponding [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object serialized using [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness).
/// The function returns the corresponding [`RLNWitnessInput`] object serialized using [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness).
#[cfg(not(feature = "stateless"))]
pub fn get_serialized_rln_witness<R: Read>(&mut self, mut input_data: R) -> Result<Vec<u8>> {
// We read input RLN witness and we serialize_compressed it
@@ -1373,24 +1327,24 @@ impl RLN {
serialize_witness(&rln_witness)
}
/// Converts a byte serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object to the corresponding JSON serialization.
/// Converts a byte serialization of a [`RLNWitnessInput`] object to the corresponding JSON serialization.
///
/// Input values are:
/// - `serialized_witness`: the byte serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object (serialization done with [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness)).
/// - `serialized_witness`: the byte serialization of a [`RLNWitnessInput`] object (serialization done with [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness)).
///
/// The function returns the corresponding JSON encoding of the input [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object.
/// The function returns the corresponding JSON encoding of the input [`RLNWitnessInput`] object.
pub fn get_rln_witness_json(&mut self, serialized_witness: &[u8]) -> Result<serde_json::Value> {
let (rln_witness, _) = deserialize_witness(serialized_witness)?;
rln_witness_to_json(&rln_witness)
}
/// Converts a byte serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object to the corresponding JSON serialization.
/// Converts a byte serialization of a [`RLNWitnessInput`] object to the corresponding JSON serialization.
/// Before serialization the data will be translated into big int for further calculation in the witness calculator.
///
/// Input values are:
/// - `serialized_witness`: the byte serialization of a [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object (serialization done with [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness)).
/// - `serialized_witness`: the byte serialization of a [`RLNWitnessInput`] object (serialization done with [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness)).
///
/// The function returns the corresponding JSON encoding of the input [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object.
/// The function returns the corresponding JSON encoding of the input [`RLNWitnessInput`] object.
pub fn get_rln_witness_bigint_json(
&mut self,
serialized_witness: &[u8],

View File

@@ -9,28 +9,28 @@ use std::str::FromStr;
use serde_json::{json, Value};
fn fq_from_str(s: String) -> ark_bn254::Fq {
fn fq_from_str(s: &str) -> ark_bn254::Fq {
ark_bn254::Fq::from_str(&s).unwrap()
}
fn g1_from_str(g1: &[String]) -> ark_bn254::G1Affine {
let x = fq_from_str(g1[0].clone());
let y = fq_from_str(g1[1].clone());
let z = fq_from_str(g1[2].clone());
let x = fq_from_str(&g1[0]);
let y = fq_from_str(&g1[1]);
let z = fq_from_str(&g1[2]);
ark_bn254::G1Affine::from(ark_bn254::G1Projective::new(x, y, z))
}
fn g2_from_str(g2: &[Vec<String>]) -> ark_bn254::G2Affine {
let c0 = fq_from_str(g2[0][0].clone());
let c1 = fq_from_str(g2[0][1].clone());
let c0 = fq_from_str(&g2[0][0]);
let c1 = fq_from_str(&g2[0][1]);
let x = ark_bn254::Fq2::new(c0, c1);
let c0 = fq_from_str(g2[1][0].clone());
let c1 = fq_from_str(g2[1][1].clone());
let c0 = fq_from_str(&g2[1][0]);
let c1 = fq_from_str(&g2[1][1]);
let y = ark_bn254::Fq2::new(c0, c1);
let c0 = fq_from_str(g2[2][0].clone());
let c1 = fq_from_str(g2[2][1].clone());
let c0 = fq_from_str(&g2[2][0]);
let c1 = fq_from_str(&g2[2][1]);
let z = ark_bn254::Fq2::new(c0, c1);
ark_bn254::G2Affine::from(ark_bn254::G2Projective::new(x, y, z))
@@ -486,7 +486,6 @@ mod tree_test {
assert_eq!(received_leaf, Fr::from(0));
}
#[allow(unused_must_use)]
#[test]
// This test checks if `set_leaves_from` throws an error when the index is out of bounds
fn test_set_leaves_bad_index() {
@@ -511,6 +510,8 @@ mod tree_test {
// We add leaves in a batch into the tree
let mut buffer = Cursor::new(vec_fr_to_bytes_le(&leaves).unwrap());
#[allow(unused_must_use)]
rln.set_leaves_from(bad_index, &mut buffer)
.expect_err("Should throw an error");
@@ -619,35 +620,36 @@ mod tree_test {
let epoch = hash_to_field(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// We prepare input for generate_rln_proof API
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut normalize_usize(identity_index));
serialized.append(&mut fr_to_bytes_le(&user_message_limit));
serialized.append(&mut fr_to_bytes_le(&Fr::from(1)));
serialized.append(&mut fr_to_bytes_le(&utils_poseidon_hash(&[
epoch,
rln_identifier,
])));
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.to_vec());
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let prove_input = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal,
);
let mut input_buffer = Cursor::new(serialized);
let mut input_buffer = Cursor::new(prove_input);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut input_buffer, &mut output_buffer)
.unwrap();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let mut proof_data = output_buffer.into_inner();
let proof_data = output_buffer.into_inner();
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data || signal_len<8> | signal<var> ]
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
let verify_input = prepare_verify_input(proof_data, &signal);
let mut input_buffer = Cursor::new(proof_data);
let mut input_buffer = Cursor::new(verify_input);
let verified = rln.verify_rln_proof(&mut input_buffer).unwrap();
assert!(verified);
@@ -690,22 +692,23 @@ mod tree_test {
let epoch = hash_to_field(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// We prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut normalize_usize(identity_index));
serialized.append(&mut fr_to_bytes_le(&user_message_limit));
serialized.append(&mut fr_to_bytes_le(&Fr::from(1)));
serialized.append(&mut fr_to_bytes_le(&utils_poseidon_hash(&[
epoch,
rln_identifier,
])));
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.to_vec());
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let prove_input = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal,
);
let mut input_buffer = Cursor::new(serialized);
let mut input_buffer = Cursor::new(prove_input);
// We read input RLN witness and we serialize_compressed it
let mut witness_byte: Vec<u8> = Vec::new();
@@ -720,16 +723,15 @@ mod tree_test {
rln.generate_rln_proof_with_witness(&mut input_buffer, &mut output_buffer)
.unwrap();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let mut proof_data = output_buffer.into_inner();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data = output_buffer.into_inner();
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data || signal_len<8> | signal<var> ]
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
let verify_input = prepare_verify_input(proof_data, &signal);
let mut input_buffer = Cursor::new(proof_data);
let mut input_buffer = Cursor::new(verify_input);
let verified = rln.verify_rln_proof(&mut input_buffer).unwrap();
assert!(verified);
@@ -773,33 +775,35 @@ mod tree_test {
let epoch = hash_to_field(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// We prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | external_nullifier<32> | user_message_limit<32> | message_id<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut normalize_usize(identity_index));
serialized.append(&mut fr_to_bytes_le(&user_message_limit));
serialized.append(&mut fr_to_bytes_le(&Fr::from(1)));
serialized.append(&mut fr_to_bytes_le(&external_nullifier));
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.to_vec());
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let prove_input = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal,
);
let mut input_buffer = Cursor::new(serialized);
let mut input_buffer = Cursor::new(prove_input);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut input_buffer, &mut output_buffer)
.unwrap();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let mut proof_data = output_buffer.into_inner();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data = output_buffer.into_inner();
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// input_data is [ proof<128> |// We prepare input for verify_rln_proof API root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data || signal_len<8> | signal<var> ]
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
let input_buffer = Cursor::new(proof_data);
let verify_input = prepare_verify_input(proof_data, &signal);
let mut input_buffer = Cursor::new(verify_input);
// If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified
let mut roots_serialized: Vec<u8> = Vec::new();
@@ -828,9 +832,9 @@ mod tree_test {
// We add the real root and we check if now the proof is verified
roots_serialized.append(&mut fr_to_bytes_le(&root));
roots_buffer = Cursor::new(roots_serialized.clone());
roots_buffer = Cursor::new(roots_serialized);
let verified = rln
.verify_with_roots(&mut input_buffer.clone(), &mut roots_buffer)
.verify_with_roots(&mut input_buffer, &mut roots_buffer)
.unwrap();
assert!(verified);
@@ -846,7 +850,6 @@ mod tree_test {
// Generate identity pair
let (identity_secret_hash, id_commitment) = keygen();
let user_message_limit = Fr::from(100);
let message_id = Fr::from(0);
let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]);
// We set as leaf id_commitment after storing its index
@@ -864,41 +867,49 @@ mod tree_test {
let epoch = hash_to_field(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// We generate two proofs using same epoch but different signals.
// We prepare input for generate_rln_proof API
let mut serialized1: Vec<u8> = Vec::new();
serialized1.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized1.append(&mut normalize_usize(identity_index));
serialized1.append(&mut fr_to_bytes_le(&user_message_limit));
serialized1.append(&mut fr_to_bytes_le(&message_id));
serialized1.append(&mut fr_to_bytes_le(&external_nullifier));
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let prove_input1 = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal1,
);
// The first part is the same for both proof input, so we clone
let mut serialized2 = serialized1.clone();
// We attach the first signal to the first proof input
serialized1.append(&mut normalize_usize(signal1.len()));
serialized1.append(&mut signal1.to_vec());
// We attach the second signal to the second proof input
serialized2.append(&mut normalize_usize(signal2.len()));
serialized2.append(&mut signal2.to_vec());
let prove_input2 = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal2,
);
// We generate the first proof
let mut input_buffer = Cursor::new(serialized1);
let mut input_buffer = Cursor::new(prove_input1);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut input_buffer, &mut output_buffer)
.unwrap();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_1 = output_buffer.into_inner();
// We generate the second proof
let mut input_buffer = Cursor::new(serialized2);
let mut input_buffer = Cursor::new(prove_input2);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut input_buffer, &mut output_buffer)
.unwrap();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_2 = output_buffer.into_inner();
let mut input_proof_data_1 = Cursor::new(proof_data_1.clone());
@@ -935,25 +946,24 @@ mod tree_test {
let signal3: [u8; 32] = rng.gen();
// We prepare proof input. Note that epoch is the same as before
let mut serialized3: Vec<u8> = Vec::new();
serialized3.append(&mut fr_to_bytes_le(&identity_secret_hash_new));
serialized3.append(&mut normalize_usize(identity_index_new));
serialized3.append(&mut fr_to_bytes_le(&user_message_limit));
serialized3.append(&mut fr_to_bytes_le(&message_id));
serialized3.append(&mut fr_to_bytes_le(&external_nullifier));
serialized3.append(&mut normalize_usize(signal3.len()));
serialized3.append(&mut signal3.to_vec());
let prove_input3 = prepare_prove_input(
identity_secret_hash,
identity_index_new,
user_message_limit,
message_id,
external_nullifier,
&signal3,
);
// We generate the proof
let mut input_buffer = Cursor::new(serialized3);
let mut input_buffer = Cursor::new(prove_input3);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut input_buffer, &mut output_buffer)
.unwrap();
let proof_data_3 = output_buffer.into_inner();
// We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new)
let mut input_proof_data_1 = Cursor::new(proof_data_1.clone());
let mut input_proof_data_1 = Cursor::new(proof_data_1);
let mut input_proof_data_3 = Cursor::new(proof_data_3);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.recover_id_secret(
@@ -1022,7 +1032,7 @@ mod stateless_test {
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | external_nullifier<32> | user_message_limit<32> | message_id<32> | signal_len<8> | signal<var> ]
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let x = hash_to_field(&signal);
let merkle_proof = tree.proof(identity_index).expect("proof should exist");
@@ -1043,15 +1053,15 @@ mod stateless_test {
rln.generate_rln_proof_with_witness(&mut input_buffer, &mut output_buffer)
.unwrap();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let mut proof_data = output_buffer.into_inner();
// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data = output_buffer.into_inner();
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data || signal_len<8> | signal<var> ]
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
let input_buffer = Cursor::new(proof_data);
let verify_input = prepare_verify_input(proof_data, &signal);
let mut input_buffer = Cursor::new(verify_input);
// If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified
let mut roots_serialized: Vec<u8> = Vec::new();
@@ -1078,9 +1088,9 @@ mod stateless_test {
// We add the real root and we check if now the proof is verified
roots_serialized.append(&mut fr_to_bytes_le(&root));
roots_buffer = Cursor::new(roots_serialized.clone());
roots_buffer = Cursor::new(roots_serialized);
let verified = rln
.verify_with_roots(&mut input_buffer.clone(), &mut roots_buffer)
.verify_with_roots(&mut input_buffer, &mut roots_buffer)
.unwrap();
assert!(verified);
@@ -1205,7 +1215,7 @@ mod stateless_test {
.unwrap();
let proof_data_3 = output_buffer.into_inner();
let mut input_proof_data_1 = Cursor::new(proof_data_1.clone());
let mut input_proof_data_1 = Cursor::new(proof_data_1);
let mut input_proof_data_3 = Cursor::new(proof_data_3);
let mut output_buffer = Cursor::new(Vec::<u8>::new());
rln.recover_id_secret(

View File

@@ -1,24 +1,26 @@
// This crate provides cross-module useful utilities (mainly type conversions) not necessarily specific to RLN
use crate::circuit::Fr;
use ark_ff::PrimeField;
use color_eyre::{Report, Result};
use num_bigint::{BigInt, BigUint};
use num_traits::Num;
use serde_json::json;
use std::io::Cursor;
use std::iter::Extend;
use crate::circuit::Fr;
#[inline(always)]
pub fn to_bigint(el: &Fr) -> Result<BigInt> {
let res: BigUint = (*el).into();
Ok(res.into())
Ok(BigUint::from(*el).into())
}
#[inline(always)]
pub fn fr_byte_size() -> usize {
let mbs = <Fr as PrimeField>::MODULUS_BIT_SIZE;
((mbs + 64 - (mbs % 64)) / 8) as usize
}
#[inline(always)]
pub fn str_to_fr(input: &str, radix: u32) -> Result<Fr> {
if !(radix == 10 || radix == 16) {
return Err(Report::msg("wrong radix"));
@@ -37,6 +39,7 @@ pub fn str_to_fr(input: &str, radix: u32) -> Result<Fr> {
}
}
#[inline(always)]
pub fn bytes_le_to_fr(input: &[u8]) -> (Fr, usize) {
let el_size = fr_byte_size();
(
@@ -45,77 +48,50 @@ pub fn bytes_le_to_fr(input: &[u8]) -> (Fr, usize) {
)
}
pub fn bytes_be_to_fr(input: &[u8]) -> (Fr, usize) {
let el_size = fr_byte_size();
(
Fr::from(BigUint::from_bytes_be(&input[0..el_size])),
el_size,
)
}
#[inline(always)]
pub fn fr_to_bytes_le(input: &Fr) -> Vec<u8> {
let input_biguint: BigUint = (*input).into();
let mut res = input_biguint.to_bytes_le();
//BigUint conversion ignores most significant zero bytes. We restore them otherwise serialization will fail (length % 8 != 0)
while res.len() != fr_byte_size() {
res.push(0);
}
res
}
pub fn fr_to_bytes_be(input: &Fr) -> Vec<u8> {
let input_biguint: BigUint = (*input).into();
let mut res = input_biguint.to_bytes_be();
// BigUint conversion ignores most significant zero bytes. We restore them otherwise serialization might fail
// Fr elements are stored using 64 bits nimbs
while res.len() != fr_byte_size() {
res.insert(0, 0);
}
res.resize(fr_byte_size(), 0);
res
}
#[inline(always)]
pub fn vec_fr_to_bytes_le(input: &[Fr]) -> Result<Vec<u8>> {
let mut bytes: Vec<u8> = Vec::new();
//We store the vector length
bytes.extend(u64::try_from(input.len())?.to_le_bytes().to_vec());
// Calculate capacity for Vec:
// - 8 bytes for normalized vector length (usize)
// - each Fr element requires fr_byte_size() bytes (typically 32 bytes)
let mut bytes = Vec::with_capacity(8 + input.len() * fr_byte_size());
// We store the vector length
bytes.extend_from_slice(&normalize_usize(input.len()));
// We store each element
input.iter().for_each(|el| bytes.extend(fr_to_bytes_le(el)));
Ok(bytes)
}
pub fn vec_fr_to_bytes_be(input: &[Fr]) -> Result<Vec<u8>> {
let mut bytes: Vec<u8> = Vec::new();
//We store the vector length
bytes.extend(u64::try_from(input.len())?.to_be_bytes().to_vec());
// We store each element
input.iter().for_each(|el| bytes.extend(fr_to_bytes_be(el)));
for el in input {
bytes.extend_from_slice(&fr_to_bytes_le(el));
}
Ok(bytes)
}
#[inline(always)]
pub fn vec_u8_to_bytes_le(input: &[u8]) -> Result<Vec<u8>> {
let mut bytes: Vec<u8> = Vec::new();
//We store the vector length
bytes.extend(u64::try_from(input.len())?.to_le_bytes().to_vec());
// Calculate capacity for Vec:
// - 8 bytes for normalized vector length (usize)
// - variable length input data
let mut bytes = Vec::with_capacity(8 + input.len());
bytes.extend(input);
Ok(bytes)
}
pub fn vec_u8_to_bytes_be(input: Vec<u8>) -> Result<Vec<u8>> {
let mut bytes: Vec<u8> = Vec::new();
//We store the vector length
bytes.extend(u64::try_from(input.len())?.to_be_bytes().to_vec());
bytes.extend(input);
// We store the vector length
bytes.extend_from_slice(&normalize_usize(input.len()));
// We store the input
bytes.extend_from_slice(input);
Ok(bytes)
}
#[inline(always)]
pub fn bytes_le_to_vec_u8(input: &[u8]) -> Result<(Vec<u8>, usize)> {
let mut read: usize = 0;
@@ -128,19 +104,7 @@ pub fn bytes_le_to_vec_u8(input: &[u8]) -> Result<(Vec<u8>, usize)> {
Ok((res, read))
}
pub fn bytes_be_to_vec_u8(input: &[u8]) -> Result<(Vec<u8>, usize)> {
let mut read: usize = 0;
let len = usize::try_from(u64::from_be_bytes(input[0..8].try_into()?))?;
read += 8;
let res = input[8..8 + len].to_vec();
read += res.len();
Ok((res, read))
}
#[inline(always)]
pub fn bytes_le_to_vec_fr(input: &[u8]) -> Result<(Vec<Fr>, usize)> {
let mut read: usize = 0;
let mut res: Vec<Fr> = Vec::new();
@@ -158,29 +122,7 @@ pub fn bytes_le_to_vec_fr(input: &[u8]) -> Result<(Vec<Fr>, usize)> {
Ok((res, read))
}
pub fn bytes_be_to_vec_fr(input: &[u8]) -> Result<(Vec<Fr>, usize)> {
let mut read: usize = 0;
let mut res: Vec<Fr> = Vec::new();
let len = usize::try_from(u64::from_be_bytes(input[0..8].try_into()?))?;
read += 8;
let el_size = fr_byte_size();
for i in 0..len {
let (curr_el, _) = bytes_be_to_fr(&input[8 + el_size * i..8 + el_size * (i + 1)]);
res.push(curr_el);
read += el_size;
}
Ok((res, read))
}
pub fn normalize_usize(input: usize) -> Vec<u8> {
let mut normalized_usize = input.to_le_bytes().to_vec();
normalized_usize.resize(8, 0);
normalized_usize
}
#[inline(always)]
pub fn bytes_le_to_vec_usize(input: &[u8]) -> Result<Vec<usize>> {
let nof_elem = usize::try_from(u64::from_le_bytes(input[0..8].try_into()?))?;
if nof_elem == 0 {
@@ -194,141 +136,18 @@ pub fn bytes_le_to_vec_usize(input: &[u8]) -> Result<Vec<usize>> {
}
}
// using for test
/// Normalizes a `usize` into an 8-byte array, ensuring consistency across architectures.
/// On 32-bit systems, the result is zero-padded to 8 bytes.
/// On 64-bit systems, it directly represents the `usize` value.
#[inline(always)]
pub fn normalize_usize(input: usize) -> [u8; 8] {
let mut bytes = [0u8; 8];
let input_bytes = input.to_le_bytes();
bytes[..input_bytes.len()].copy_from_slice(&input_bytes);
bytes
}
#[inline(always)] // using for test
pub fn generate_input_buffer() -> Cursor<String> {
Cursor::new(json!({}).to_string())
}
/* Old conversion utilities between different libraries data types
// Conversion Utilities between poseidon-rs Field and arkworks Fr (in order to call directly poseidon-rs' poseidon_hash)
use ff::{PrimeField as _, PrimeFieldRepr as _};
use poseidon_rs::Fr as PosFr;
pub fn fr_to_posfr(value: Fr) -> PosFr {
let mut bytes = [0_u8; 32];
let byte_vec = value.into_repr().to_bytes_be();
bytes.copy_from_slice(&byte_vec[..]);
let mut repr = <PosFr as ff::PrimeField>::Repr::default();
repr.read_be(&bytes[..])
.expect("read from correctly sized slice always succeeds");
PosFr::from_repr(repr).expect("value is always in range")
}
pub fn posfr_to_fr(value: PosFr) -> Fr {
let mut bytes = [0u8; 32];
value
.into_repr()
.write_be(&mut bytes[..])
.expect("write to correctly sized slice always succeeds");
Fr::from_be_bytes_mod_order(&bytes)
}
// Conversion Utilities between semaphore-rs Field and arkworks Fr
use semaphore::Field;
pub fn to_fr(el: &Field) -> Fr {
Fr::try_from(*el).unwrap()
}
pub fn to_field(el: &Fr) -> Field {
(*el).try_into().unwrap()
}
pub fn vec_to_fr(v: &[Field]) -> Vec<Fr> {
v.iter().map(|el| to_fr(el)).collect()
}
pub fn vec_to_field(v: &[Fr]) -> Vec<Field> {
v.iter().map(|el| to_field(el)).collect()
}
pub fn vec_fr_to_field(input: &[Fr]) -> Vec<Field> {
input.iter().map(|el| to_field(el)).collect()
}
pub fn vec_field_to_fr(input: &[Field]) -> Vec<Fr> {
input.iter().map(|el| to_fr(el)).collect()
}
pub fn str_to_field(input: String, radix: i32) -> Field {
assert!((radix == 10) || (radix == 16));
// We remove any quote present and we trim
let single_quote: char = '\"';
let input_clean = input.replace(single_quote, "");
let input_clean = input_clean.trim();
if radix == 10 {
Field::from_str(&format!(
"{:01$x}",
BigUint::from_str(input_clean).unwrap(),
64
))
.unwrap()
} else {
let input_clean = input_clean.replace("0x", "");
Field::from_str(&format!("{:0>64}", &input_clean)).unwrap()
}
}
pub fn bytes_le_to_field(input: &[u8]) -> (Field, usize) {
let (fr_el, read) = bytes_le_to_fr(input);
(to_field(&fr_el), read)
}
pub fn bytes_be_to_field(input: &[u8]) -> (Field, usize) {
let (fr_el, read) = bytes_be_to_fr(input);
(to_field(&fr_el), read)
}
pub fn field_to_bytes_le(input: &Field) -> Vec<u8> {
fr_to_bytes_le(&to_fr(input))
}
pub fn field_to_bytes_be(input: &Field) -> Vec<u8> {
fr_to_bytes_be(&to_fr(input))
}
pub fn vec_field_to_bytes_le(input: &[Field]) -> Vec<u8> {
vec_fr_to_bytes_le(&vec_field_to_fr(input))
}
pub fn vec_field_to_bytes_be(input: &[Field]) -> Vec<u8> {
vec_fr_to_bytes_be(&vec_field_to_fr(input))
}
pub fn bytes_le_to_vec_field(input: &[u8]) -> (Vec<Field>, usize) {
let (vec_fr, read) = bytes_le_to_vec_fr(input);
(vec_fr_to_field(&vec_fr), read)
}
pub fn bytes_be_to_vec_field(input: &[u8]) -> (Vec<Field>, usize) {
let (vec_fr, read) = bytes_be_to_vec_fr(input);
(vec_fr_to_field(&vec_fr), read)
}
// Arithmetic over Field elements (wrapped over arkworks algebra crate)
pub fn add(a: &Field, b: &Field) -> Field {
to_field(&(to_fr(a) + to_fr(b)))
}
pub fn mul(a: &Field, b: &Field) -> Field {
to_field(&(to_fr(a) * to_fr(b)))
}
pub fn div(a: &Field, b: &Field) -> Field {
to_field(&(to_fr(a) / to_fr(b)))
}
pub fn inv(a: &Field) -> Field {
to_field(&(Fr::from(1) / to_fr(a)))
}
*/

View File

@@ -156,6 +156,7 @@ mod test {
// random number between 0..no_of_leaves
let mut rng = thread_rng();
let set_index = rng.gen_range(0..NO_OF_LEAVES) as usize;
println!("set_index: {}", set_index);
// We add leaves in a batch into the tree
set_leaves_init(rln_pointer, &leaves);
@@ -176,7 +177,10 @@ mod test {
// We get the root of the tree obtained adding leaves in batch
let root_batch_with_custom_index = get_tree_root(rln_pointer);
assert_eq!(root_batch_with_init, root_batch_with_custom_index);
assert_eq!(
root_batch_with_init, root_batch_with_custom_index,
"root batch !="
);
// We reset the tree to default
let success = set_tree(rln_pointer, TEST_TREE_HEIGHT);
@@ -192,7 +196,10 @@ mod test {
// We get the root of the tree obtained adding leaves using the internal index
let root_single_additions = get_tree_root(rln_pointer);
assert_eq!(root_batch_with_init, root_single_additions);
assert_eq!(
root_batch_with_init, root_single_additions,
"root single additions !="
);
}
#[test]
@@ -408,15 +415,6 @@ mod test {
// We obtain the root from the RLN instance
let root_rln_folder = get_tree_root(rln_pointer);
// Reading the raw data from the files required for instantiating a RLN instance using raw data
let circom_path = "./resources/tree_height_20/rln.wasm";
let mut circom_file = File::open(&circom_path).expect("no file found");
let metadata = std::fs::metadata(&circom_path).expect("unable to read metadata");
let mut circom_buffer = vec![0; metadata.len() as usize];
circom_file
.read_exact(&mut circom_buffer)
.expect("buffer overflow");
#[cfg(feature = "arkzkey")]
let zkey_path = "./resources/tree_height_20/rln_final.arkzkey";
#[cfg(not(feature = "arkzkey"))]
@@ -428,15 +426,17 @@ mod test {
.read_exact(&mut zkey_buffer)
.expect("buffer overflow");
let vk_path = "./resources/tree_height_20/verification_key.arkvkey";
let mut vk_file = File::open(&vk_path).expect("no file found");
let metadata = std::fs::metadata(&vk_path).expect("unable to read metadata");
let mut vk_buffer = vec![0; metadata.len() as usize];
vk_file.read_exact(&mut vk_buffer).expect("buffer overflow");
let circom_data = &Buffer::from(&circom_buffer[..]);
let zkey_data = &Buffer::from(&zkey_buffer[..]);
let vk_data = &Buffer::from(&vk_buffer[..]);
let graph_data = "./resources/tree_height_20/graph.bin";
let mut graph_file = File::open(&graph_data).expect("no file found");
let metadata = std::fs::metadata(&graph_data).expect("unable to read metadata");
let mut graph_buffer = vec![0; metadata.len() as usize];
graph_file
.read_exact(&mut graph_buffer)
.expect("buffer overflow");
let graph_data = &Buffer::from(&graph_buffer[..]);
// Creating a RLN instance passing the raw data
let mut rln_pointer_raw_bytes = MaybeUninit::<*mut RLN>::uninit();
@@ -444,9 +444,8 @@ mod test {
let tree_config_buffer = &Buffer::from(tree_config.as_bytes());
let success = new_with_params(
TEST_TREE_HEIGHT,
circom_data,
zkey_data,
vk_data,
graph_data,
tree_config_buffer,
rln_pointer_raw_bytes.as_mut_ptr(),
);
@@ -486,10 +485,13 @@ mod test {
// We generate a random epoch
let epoch = hash_to_field(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
let message_id = Fr::from(0);
let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]);
// We set as leaf rate_commitment, its index would be equal to no_of_leaves
@@ -500,27 +502,25 @@ mod test {
// We prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut normalize_usize(identity_index));
serialized.append(&mut fr_to_bytes_le(&user_message_limit));
serialized.append(&mut fr_to_bytes_le(&message_id));
serialized.append(&mut fr_to_bytes_le(&external_nullifier));
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.to_vec());
let prove_input = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal,
);
// We call generate_rln_proof
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let mut proof_data = rln_proof_gen(rln_pointer, serialized.as_ref());
let proof_data = rln_proof_gen(rln_pointer, prove_input.as_ref());
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data | signal_len<8> | signal<var> ]
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
// that is [ proof_data || signal_len<8> | signal<var> ]
let verify_input = prepare_verify_input(proof_data, &signal);
// We call verify_rln_proof
let input_buffer = &Buffer::from(proof_data.as_ref());
let input_buffer = &Buffer::from(verify_input.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let success = verify_rln_proof(rln_pointer, input_buffer, proof_is_valid_ptr);
@@ -553,11 +553,12 @@ mod test {
// We generate a random epoch
let epoch = hash_to_field(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
let user_message_limit = Fr::from(100);
let message_id = Fr::from(0);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// We set as leaf rate_commitment, its index would be equal to no_of_leaves
let leaf_ser = fr_to_bytes_le(&rate_commitment);
@@ -567,24 +568,23 @@ mod test {
// We prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized.append(&mut normalize_usize(identity_index));
serialized.append(&mut fr_to_bytes_le(&user_message_limit));
serialized.append(&mut fr_to_bytes_le(&message_id));
serialized.append(&mut fr_to_bytes_le(&external_nullifier));
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.to_vec());
let prove_input = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal,
);
// We call generate_rln_proof
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let mut proof_data = rln_proof_gen(rln_pointer, serialized.as_ref());
let proof_data = rln_proof_gen(rln_pointer, prove_input.as_ref());
// We prepare input for verify_rln_proof API
// input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var> ]
// that is [ proof_data | signal_len<8> | signal<var> ]
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
// that is [ proof_data || signal_len<8> | signal<var> ]
let verify_input = prepare_verify_input(proof_data.clone(), &signal);
// We test verify_with_roots
@@ -592,7 +592,7 @@ mod test {
// In this case, since no root is provided, proof's root check is skipped and proof is verified if other proof values are valid
let mut roots_data: Vec<u8> = Vec::new();
let input_buffer = &Buffer::from(proof_data.as_ref());
let input_buffer = &Buffer::from(verify_input.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
@@ -606,7 +606,7 @@ mod test {
for _ in 0..5 {
roots_data.append(&mut fr_to_bytes_le(&Fr::rand(&mut rng)));
}
let input_buffer = &Buffer::from(proof_data.as_ref());
let input_buffer = &Buffer::from(verify_input.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
@@ -622,7 +622,7 @@ mod test {
// We include the root and verify the proof
roots_data.append(&mut fr_to_bytes_le(&root));
let input_buffer = &Buffer::from(proof_data.as_ref());
let input_buffer = &Buffer::from(verify_input.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
@@ -643,7 +643,6 @@ mod test {
let (identity_secret_hash, id_commitment) = identity_pair_gen(rln_pointer);
let user_message_limit = Fr::from(100);
let message_id = Fr::from(0);
let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]);
// We set as leaf rate_commitment, its index would be equal to 0 since tree is empty
@@ -665,36 +664,40 @@ mod test {
// We generate a random epoch
let epoch = hash_to_field(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// We prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
let mut serialized1: Vec<u8> = Vec::new();
serialized1.append(&mut fr_to_bytes_le(&identity_secret_hash));
serialized1.append(&mut normalize_usize(identity_index));
serialized1.append(&mut fr_to_bytes_le(&user_message_limit));
serialized1.append(&mut fr_to_bytes_le(&message_id));
serialized1.append(&mut fr_to_bytes_le(&external_nullifier));
// input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]
let prove_input1 = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal1,
);
// The first part is the same for both proof input, so we clone
let mut serialized2 = serialized1.clone();
// We attach the first signal to the first proof input
serialized1.append(&mut normalize_usize(signal1.len()));
serialized1.append(&mut signal1.to_vec());
// We attach the second signal to the first proof input
serialized2.append(&mut normalize_usize(signal2.len()));
serialized2.append(&mut signal2.to_vec());
let prove_input2 = prepare_prove_input(
identity_secret_hash,
identity_index,
user_message_limit,
message_id,
external_nullifier,
&signal2,
);
// We call generate_rln_proof for first proof values
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_1 = rln_proof_gen(rln_pointer, serialized1.as_ref());
let proof_data_1 = rln_proof_gen(rln_pointer, prove_input1.as_ref());
// We call generate_rln_proof
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_2 = rln_proof_gen(rln_pointer, serialized2.as_ref());
let proof_data_2 = rln_proof_gen(rln_pointer, prove_input2.as_ref());
let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref());
let input_proof_buffer_2 = &Buffer::from(proof_data_2.as_ref());
@@ -737,18 +740,18 @@ mod test {
// We prepare input for generate_rln_proof API
// input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
// Note that epoch is the same as before
let mut serialized: Vec<u8> = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity_secret_hash_new));
serialized.append(&mut normalize_usize(identity_index_new));
serialized.append(&mut fr_to_bytes_le(&user_message_limit));
serialized.append(&mut fr_to_bytes_le(&message_id));
serialized.append(&mut fr_to_bytes_le(&external_nullifier));
serialized.append(&mut normalize_usize(signal3.len()));
serialized.append(&mut signal3.to_vec());
let prove_input3 = prepare_prove_input(
identity_secret_hash,
identity_index_new,
user_message_limit,
message_id,
external_nullifier,
&signal3,
);
// We call generate_rln_proof
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_3 = rln_proof_gen(rln_pointer, serialized.as_ref());
let proof_data_3 = rln_proof_gen(rln_pointer, prove_input3.as_ref());
// We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new)
@@ -1211,15 +1214,14 @@ mod stateless_test {
.unwrap();
let serialized = serialize_witness(&rln_witness).unwrap();
let mut proof_data = rln_proof_gen_with_witness(rln_pointer, serialized.as_ref());
let proof_data = rln_proof_gen_with_witness(rln_pointer, serialized.as_ref());
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
let verify_input = prepare_verify_input(proof_data.clone(), &signal);
// If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified
let mut roots_data: Vec<u8> = Vec::new();
let input_buffer = &Buffer::from(proof_data.as_ref());
let input_buffer = &Buffer::from(verify_input.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
@@ -1233,7 +1235,7 @@ mod stateless_test {
for _ in 0..5 {
roots_data.append(&mut fr_to_bytes_le(&Fr::rand(&mut rng)));
}
let input_buffer = &Buffer::from(proof_data.as_ref());
let input_buffer = &Buffer::from(verify_input.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
@@ -1248,7 +1250,7 @@ mod stateless_test {
// We add the real root and we check if now the proof is verified
roots_data.append(&mut fr_to_bytes_le(&root));
let input_buffer = &Buffer::from(proof_data.as_ref());
let input_buffer = &Buffer::from(verify_input.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;

View File

@@ -23,6 +23,7 @@ mod test {
assert_eq!(proof.leaf_index(), i);
tree_opt.set(i, leaves[i]).unwrap();
assert_eq!(tree_opt.root(), tree_full.root());
let proof = tree_opt.proof(i).expect("index should be set");
assert_eq!(proof.leaf_index(), i);
}
@@ -37,11 +38,11 @@ mod test {
#[test]
fn test_subtree_root() {
const DEPTH: usize = 3;
const LEAVES_LEN: usize = 6;
const LEAVES_LEN: usize = 8;
let mut tree = PoseidonTree::default(DEPTH).unwrap();
let leaves: Vec<Fr> = (0..LEAVES_LEN).map(|s| Fr::from(s as i32)).collect();
let _ = tree.set_range(0, leaves);
let _ = tree.set_range(0, leaves.into_iter());
for i in 0..LEAVES_LEN {
// check leaves
@@ -78,7 +79,7 @@ mod test {
let leaves: Vec<Fr> = (0..nof_leaves).map(|s| Fr::from(s as i32)).collect();
// check set_range
let _ = tree.set_range(0, leaves.clone());
let _ = tree.set_range(0, leaves.clone().into_iter());
assert!(tree.get_empty_leaves_indices().is_empty());
let mut vec_idxs = Vec::new();
@@ -98,26 +99,28 @@ mod test {
// check remove_indices_and_set_leaves inside override_range function
assert!(tree.get_empty_leaves_indices().is_empty());
let leaves_2: Vec<Fr> = (0..2).map(|s| Fr::from(s as i32)).collect();
tree.override_range(0, leaves_2.clone(), [0, 1, 2, 3])
tree.override_range(0, leaves_2.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert_eq!(tree.get_empty_leaves_indices(), vec![2, 3]);
// check remove_indices inside override_range function
tree.override_range(0, [], [0, 1]).unwrap();
tree.override_range(0, [].into_iter(), [0, 1].into_iter())
.unwrap();
assert_eq!(tree.get_empty_leaves_indices(), vec![0, 1, 2, 3]);
// check set_range inside override_range function
tree.override_range(0, leaves_2.clone(), []).unwrap();
tree.override_range(0, leaves_2.clone().into_iter(), [].into_iter())
.unwrap();
assert_eq!(tree.get_empty_leaves_indices(), vec![2, 3]);
let leaves_4: Vec<Fr> = (0..4).map(|s| Fr::from(s as i32)).collect();
// check if the indexes for write and delete are the same
tree.override_range(0, leaves_4.clone(), [0, 1, 2, 3])
tree.override_range(0, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert!(tree.get_empty_leaves_indices().is_empty());
// check if indexes for deletion are before indexes for overwriting
tree.override_range(4, leaves_4.clone(), [0, 1, 2, 3])
tree.override_range(4, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
// The result will be like this, because in the set_range function in pmtree
// the next_index value is increased not by the number of elements to insert,
@@ -128,7 +131,7 @@ mod test {
);
// check if the indices for write and delete do not overlap completely
tree.override_range(2, leaves_4.clone(), [0, 1, 2, 3])
tree.override_range(2, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
// The result will be like this, because in the set_range function in pmtree
// the next_index value is increased not by the number of elements to insert,

View File

@@ -1,8 +1,8 @@
#[cfg(test)]
mod test {
use ark_ff::BigInt;
use rln::circuit::zkey_from_folder;
use rln::circuit::{circom_from_folder, vk_from_folder, Fr, TEST_TREE_HEIGHT};
use rln::circuit::{graph_from_folder, zkey_from_folder};
use rln::circuit::{Fr, TEST_TREE_HEIGHT};
use rln::hashers::{hash_to_field, poseidon_hash};
use rln::poseidon_tree::PoseidonTree;
use rln::protocol::*;
@@ -128,9 +128,8 @@ mod test {
fn test_witness_from_json() {
// We generate all relevant keys
let proving_key = zkey_from_folder();
let verification_key = vk_from_folder();
let builder = circom_from_folder();
let verification_key = &proving_key.0.vk;
let graph_data = graph_from_folder();
// We compute witness from the json input
let rln_witness = get_test_witness();
let rln_witness_json = rln_witness_to_json(&rln_witness).unwrap();
@@ -138,7 +137,7 @@ mod test {
assert_eq!(rln_witness_deser, rln_witness);
// Let's generate a zkSNARK proof
let proof = generate_proof(builder, &proving_key, &rln_witness_deser).unwrap();
let proof = generate_proof(&proving_key, &rln_witness_deser, &graph_data).unwrap();
let proof_values = proof_values_from_witness(&rln_witness_deser).unwrap();
// Let's verify the proof
@@ -157,11 +156,11 @@ mod test {
// We generate all relevant keys
let proving_key = zkey_from_folder();
let verification_key = vk_from_folder();
let builder = circom_from_folder();
let verification_key = &proving_key.0.vk;
let graph_data = graph_from_folder();
// Let's generate a zkSNARK proof
let proof = generate_proof(builder, &proving_key, &rln_witness_deser).unwrap();
let proof = generate_proof(&proving_key, &rln_witness_deser, &graph_data).unwrap();
let proof_values = proof_values_from_witness(&rln_witness_deser).unwrap();

View File

@@ -1,11 +1,16 @@
#[cfg(test)]
mod test {
use ark_ff::BigInt;
#[cfg(not(feature = "stateless"))]
use {
ark_ff::BigInt,
rln::{circuit::TEST_TREE_HEIGHT, protocol::compute_tree_root},
};
use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng;
use rln::circuit::{Fr, TEST_TREE_HEIGHT};
use rln::circuit::Fr;
use rln::hashers::{hash_to_field, poseidon_hash as utils_poseidon_hash, ROUND_PARAMS};
use rln::protocol::{compute_tree_root, deserialize_identity_tuple};
use rln::protocol::deserialize_identity_tuple;
use rln::public::{hash as public_hash, poseidon_hash as public_poseidon_hash, RLN};
use rln::utils::*;
use std::io::Cursor;

View File

@@ -1,6 +1,6 @@
[package]
name = "zerokit_utils"
version = "0.5.1"
version = "0.5.2"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Various utilities for Zerokit"
@@ -12,29 +12,34 @@ repository = "https://github.com/vacp2p/zerokit"
bench = false
[dependencies]
ark-ff = { version = "=0.4.1", default-features = false, features = ["asm"] }
num-bigint = { version = "=0.4.3", default-features = false, features = [
ark-ff = { version = "0.5.0", default-features = false, features = [
"parallel",
] }
num-bigint = { version = "0.4.6", default-features = false, features = [
"rand",
] }
color-eyre = "=0.6.2"
pmtree = { package = "vacp2p_pmtree", version = "=2.0.2", optional = true }
sled = "=0.34.7"
serde = "=1.0.163"
lazy_static = "1.4.0"
color-eyre = "0.6.3"
pmtree = { package = "vacp2p_pmtree", version = "2.0.2", optional = true }
sled = "0.34.7"
serde = "1.0"
lazy_static = "1.5.0"
hex = "0.4"
[dev-dependencies]
ark-bn254 = "=0.4.0"
num-traits = "=0.2.15"
hex-literal = "=0.3.4"
tiny-keccak = { version = "=2.0.2", features = ["keccak"] }
criterion = { version = "=0.4.0", features = ["html_reports"] }
ark-bn254 = { version = "0.5.0", features = ["std"] }
num-traits = "0.2.19"
hex-literal = "1.0.0"
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
criterion = { version = "0.4.0", features = ["html_reports"] }
[features]
default = ["parallel"]
parallel = ["ark-ff/parallel"]
default = []
pmtree-ft = ["pmtree"]
[[bench]]
name = "merkle_tree_benchmark"
harness = false
[[bench]]
name = "poseidon_benchmark"
harness = false

View File

@@ -8,4 +8,4 @@ args = ["test", "--release"]
[tasks.bench]
command = "cargo"
args = ["bench"]
args = ["bench"]

View File

@@ -1,15 +1,111 @@
# Utils crate
# Zerokit Utils Crate
## Building
[![Crates.io](https://img.shields.io/crates/v/zerokit_utils.svg)](https://crates.io/crates/zerokit_utils)
1. `cargo build`
Cryptographic primitives for zero-knowledge applications, featuring efficient Merkle tree implementations and a Poseidon hash function.
## Testing
## Overview
1. `cargo test`
This crate provides core cryptographic components optimized for zero-knowledge proof systems:
## Benchmarking
1. Multiple Merkle tree implementations with different space/time tradeoffs
2. A Poseidon hash implementation
1. `cargo bench`
## Merkle Tree Implementations
To view the results of the benchmark, open the `target/criterion/report/index.html` file generated after the bench
The crate supports two interchangeable Merkle tree implementations:
- **FullMerkleTree**
- Stores each tree node in memory
- **OptimalMerkleTree**
- Only stores nodes used to prove accumulation of set leaves
### Implementation notes
Glossary:
* depth: level of leaves if we count from levels from 0
* number of levels: depth + 1
* capacity (== number of leaves) -- 1 << depth
* total number of nodes: 1 << (depth + 1)) - 1
So for instance:
* depth: 3
* number of levels: 4
* capacity (number of leaves): 8
* total number of nodes: 15
```mermaid
flowchart TD
A[Root] --> N1
A[Root] --> N2
N1 --> N3
N1 --> N4
N2 --> N5
N2 --> N6
N3 -->|Leaf| L1
N3 -->|Leaf| L2
N4 -->|Leaf| L3
N4 -->|Leaf| L4
N5 -->|Leaf| L5
N5 -->|Leaf| L6
N6 -->|Leaf| L7
N6 -->|Leaf| L8
```
## Poseidon Hash Implementation
This crate provides an implementation to compute the Poseidon hash round constants and MDS matrices:
- **Customizable parameters**: Supports different security levels and input sizes
- **Arkworks-friendly**: Adapted to work over arkworks field traits and custom data structures
### Security Note
The MDS matrices are generated iteratively using the Grain LFSR until certain criteria are met.
According to the paper, such matrices must respect specific conditions which are checked by 3 different algorithms in the reference implementation.
These validation algorithms are not currently implemented in this crate.
For the hardcoded parameters, the first random matrix generated satisfies these conditions.
If using different parameters, you should check against the reference implementation how many matrices are generated before outputting the correct one,
and pass this number to the `skip_matrices` parameter of the `find_poseidon_ark_and_mds` function.
## Installation
Add Zerokit Utils to your Rust project:
```toml
[dependencies]
zerokit-utils = "0.5.1"
```
## Performance Considerations
- **FullMerkleTree**: Use when memory is abundant and operation speed is critical
- **OptimalMerkleTree**: Use when memory efficiency is more important than raw speed
- **Poseidon**: Offers a good balance between security and performance for ZK applications
## Building and Testing
```bash
# Build the crate
cargo make build
# Run tests
cargo make test
# Run benchmarks
cargo make bench
```
To view the results of the benchmark, open the `target/criterion/report/index.html` file generated after the bench
## Acknowledgements
- The Merkle tree implementations are adapted from:
- [kilic/rln](https://github.com/kilic/rln/blob/master/src/merkle.rs)
- [worldcoin/semaphore-rs](https://github.com/worldcoin/semaphore-rs/blob/d462a4372f1fd9c27610f2acfe4841fab1d396aa/src/merkle_tree.rs)
- The Poseidon implementation references:
- [Poseidon reference implementation](https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_parameters_grain.sage)

View File

@@ -75,7 +75,8 @@ pub fn optimal_merkle_tree_benchmark(c: &mut Criterion) {
c.bench_function("OptimalMerkleTree::override_range", |b| {
b.iter(|| {
tree.override_range(0, *LEAVES, [0, 1, 2, 3]).unwrap();
tree.override_range(0, LEAVES.into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
})
});
@@ -123,7 +124,8 @@ pub fn full_merkle_tree_benchmark(c: &mut Criterion) {
c.bench_function("FullMerkleTree::override_range", |b| {
b.iter(|| {
tree.override_range(0, *LEAVES, [0, 1, 2, 3]).unwrap();
tree.override_range(0, LEAVES.into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
})
});

View File

@@ -0,0 +1,65 @@
use ark_bn254::Fr;
use criterion::{
black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput,
};
use zerokit_utils::Poseidon;
const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [
(2, 8, 56, 0),
(3, 8, 57, 0),
(4, 8, 56, 0),
(5, 8, 60, 0),
(6, 8, 60, 0),
(7, 8, 63, 0),
(8, 8, 64, 0),
(9, 8, 63, 0),
];
pub fn poseidon_benchmark(c: &mut Criterion) {
let hasher = Poseidon::<Fr>::from(&ROUND_PARAMS);
let mut group = c.benchmark_group("poseidon Fr");
for size in [10u32, 100, 1000].iter() {
group.throughput(Throughput::Elements(*size as u64));
group.bench_with_input(BenchmarkId::new("Array hash", size), size, |b, &size| {
b.iter_batched(
// Setup: create values for each benchmark iteration
|| {
let mut values = Vec::with_capacity(size as usize);
for i in 0..size {
values.push([Fr::from(i)]);
}
values
},
// Actual benchmark
|values| {
for v in values.iter() {
let _ = hasher.hash(black_box(&v[..]));
}
},
BatchSize::SmallInput,
)
});
}
// Benchmark single hash operation separately
group.bench_function("Single hash", |b| {
let input = [Fr::from(u64::MAX)];
b.iter(|| {
let _ = hasher.hash(black_box(&input[..]));
})
});
group.finish();
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_millis(500))
.measurement_time(std::time::Duration::from_secs(4))
.sample_size(20);
targets = poseidon_benchmark
}
criterion_main!(benches);

View File

@@ -3,7 +3,7 @@ use color_eyre::{Report, Result};
use std::{
cmp::max,
fmt::Debug,
iter::{once, repeat, successors},
iter::{once, repeat_n, successors},
str::FromStr,
};
@@ -89,7 +89,7 @@ where
.iter()
.rev()
.enumerate()
.flat_map(|(levels, hash)| repeat(hash).take(1 << levels))
.flat_map(|(levels, hash)| repeat_n(hash, 1 << levels))
.cloned()
.collect::<Vec<_>>();
debug_assert!(nodes.len() == (1 << (depth + 1)) - 1);
@@ -125,7 +125,6 @@ where
self.next_index
}
#[must_use]
// Returns the root of the tree
fn root(&self) -> FrOf<Self::Hasher> {
self.nodes[0]
@@ -237,7 +236,7 @@ where
self.cached_leaves_indices[i] = 0;
}
self.set_range(start, set_values)
self.set_range(start, set_values.into_iter())
.map_err(|e| Report::msg(e.to_string()))
}
@@ -257,7 +256,7 @@ where
Ok(())
}
// Computes a merkle proof the the leaf at the specified index
// Computes a merkle proof the leaf at the specified index
fn proof(&self, leaf: usize) -> Result<FullMerkleProof<H>> {
if leaf >= self.capacity() {
return Err(Report::msg("index exceeds set size"));
@@ -341,14 +340,12 @@ impl<H: Hasher> ZerokitMerkleProof for FullMerkleProof<H> {
type Index = u8;
type Hasher = H;
#[must_use]
// Returns the length of a Merkle proof
fn length(&self) -> usize {
self.0.len()
}
/// Computes the leaf index corresponding to a Merkle proof
#[must_use]
fn leaf_index(&self) -> usize {
self.0.iter().rev().fold(0, |index, branch| match branch {
FullMerkleBranch::Left(_) => index << 1,
@@ -356,7 +353,6 @@ impl<H: Hasher> ZerokitMerkleProof for FullMerkleProof<H> {
})
}
#[must_use]
/// Returns the path elements forming a Merkle proof
fn get_path_elements(&self) -> Vec<FrOf<Self::Hasher>> {
self.0
@@ -368,7 +364,6 @@ impl<H: Hasher> ZerokitMerkleProof for FullMerkleProof<H> {
}
/// Returns the path indexes forming a Merkle proof
#[must_use]
fn get_path_index(&self) -> Vec<Self::Index> {
self.0
.iter()
@@ -380,7 +375,6 @@ impl<H: Hasher> ZerokitMerkleProof for FullMerkleProof<H> {
}
/// Computes the Merkle root corresponding by iteratively hashing a Merkle proof with a given input leaf
#[must_use]
fn compute_root_from(&self, hash: &FrOf<Self::Hasher>) -> FrOf<Self::Hasher> {
self.0.iter().fold(*hash, |hash, branch| match branch {
FullMerkleBranch::Left(sibling) => H::hash(&[hash, *sibling]),

View File

@@ -54,13 +54,13 @@ pub trait ZerokitMerkleTree {
fn set(&mut self, index: usize, leaf: FrOf<Self::Hasher>) -> Result<()>;
fn set_range<I>(&mut self, start: usize, leaves: I) -> Result<()>
where
I: IntoIterator<Item = FrOf<Self::Hasher>>;
I: ExactSizeIterator<Item = FrOf<Self::Hasher>>;
fn get(&self, index: usize) -> Result<FrOf<Self::Hasher>>;
fn get_empty_leaves_indices(&self) -> Vec<usize>;
fn override_range<I, J>(&mut self, start: usize, leaves: I, to_remove_indices: J) -> Result<()>
where
I: IntoIterator<Item = FrOf<Self::Hasher>>,
J: IntoIterator<Item = usize>;
I: ExactSizeIterator<Item = FrOf<Self::Hasher>>,
J: ExactSizeIterator<Item = usize>;
fn update_next(&mut self, leaf: FrOf<Self::Hasher>) -> Result<()>;
fn delete(&mut self, index: usize) -> Result<()>;
fn proof(&self, index: usize) -> Result<Self::Proof>;

View File

@@ -1,10 +1,10 @@
use crate::merkle_tree::{Hasher, ZerokitMerkleProof, ZerokitMerkleTree};
use crate::FrOf;
use color_eyre::{Report, Result};
use std::cmp::min;
use std::collections::HashMap;
use std::str::FromStr;
use std::{cmp::max, fmt::Debug};
////////////////////////////////////////////////////////////
///// Optimal Merkle Tree Implementation
////////////////////////////////////////////////////////////
@@ -81,9 +81,9 @@ where
}
cached_nodes.reverse();
Ok(OptimalMerkleTree {
cached_nodes: cached_nodes.clone(),
cached_nodes,
depth,
nodes: HashMap::new(),
nodes: HashMap::with_capacity(1 << depth),
cached_leaves_indices: vec![0; 1 << depth],
next_index: 0,
metadata: Vec::new(),
@@ -109,7 +109,6 @@ where
self.next_index
}
#[must_use]
// Returns the root of the tree
fn root(&self) -> H::Fr {
self.get_node(0, 0)
@@ -137,7 +136,7 @@ where
return Err(Report::msg("index exceeds set size"));
}
self.nodes.insert((self.depth, index), leaf);
self.recalculate_from(index)?;
self.update_hashes(index, 1)?;
self.next_index = max(self.next_index, index + 1);
self.cached_leaves_indices[index] = 1;
Ok(())
@@ -162,25 +161,29 @@ where
}
// Sets multiple leaves from the specified tree index
fn set_range<I: IntoIterator<Item = H::Fr>>(&mut self, start: usize, leaves: I) -> Result<()> {
let leaves = leaves.into_iter().collect::<Vec<_>>();
fn set_range<I: ExactSizeIterator<Item = H::Fr>>(
&mut self,
start: usize,
leaves: I,
) -> Result<()> {
// check if the range is valid
if start + leaves.len() > self.capacity() {
let leaves_len = leaves.len();
if start + leaves_len > self.capacity() {
return Err(Report::msg("provided range exceeds set size"));
}
for (i, leaf) in leaves.iter().enumerate() {
self.nodes.insert((self.depth, start + i), *leaf);
for (i, leaf) in leaves.enumerate() {
self.nodes.insert((self.depth, start + i), leaf);
self.cached_leaves_indices[start + i] = 1;
self.recalculate_from(start + i)?;
}
self.next_index = max(self.next_index, start + leaves.len());
self.update_hashes(start, leaves_len)?;
self.next_index = max(self.next_index, start + leaves_len);
Ok(())
}
fn override_range<I, J>(&mut self, start: usize, leaves: I, indices: J) -> Result<()>
where
I: IntoIterator<Item = FrOf<Self::Hasher>>,
J: IntoIterator<Item = usize>,
I: ExactSizeIterator<Item = FrOf<Self::Hasher>>,
J: ExactSizeIterator<Item = usize>,
{
let indices = indices.into_iter().collect::<Vec<_>>();
let min_index = *indices.first().unwrap();
@@ -205,7 +208,7 @@ where
self.cached_leaves_indices[i] = 0;
}
self.set_range(start, set_values)
self.set_range(start, set_values.into_iter())
.map_err(|e| Report::msg(e.to_string()))
}
@@ -225,7 +228,7 @@ where
Ok(())
}
// Computes a merkle proof the the leaf at the specified index
// Computes a merkle proof the leaf at the specified index
fn proof(&self, index: usize) -> Result<Self::Proof> {
if index >= self.capacity() {
return Err(Report::msg("index exceeds set size"));
@@ -317,6 +320,60 @@ where
}
Ok(())
}
/// Update hashes after some leaves have been set or updated
/// index - first leaf index (which has been set or updated)
/// length - number of elements set or updated
fn update_hashes(&mut self, index: usize, length: usize) -> Result<()> {
// parent depth & index (used to store in the tree)
let mut parent_depth = self.depth - 1; // tree depth (or leaves depth) - 1
let mut parent_index = index >> 1;
let mut parent_index_bak = parent_index;
// maximum index at this depth
let parent_max_index_0 = (1 << parent_depth) / 2;
// Based on given length (number of elements we will update)
// we could restrict the parent_max_index
let current_index_max = if (index + length) % 2 == 0 {
index + length + 2
} else {
index + length + 1
};
let mut parent_max_index = min(current_index_max >> 1, parent_max_index_0);
// current depth & index (used to compute the hash)
// current depth initially == tree depth (or leaves depth)
let mut current_depth = self.depth;
let mut current_index = if index % 2 == 0 { index } else { index - 1 };
let mut current_index_bak = current_index;
loop {
// Hash 2 values at (current depth, current_index) & (current_depth, current_index + 1)
let n_hash = self.hash_couple(current_depth, current_index);
// Insert this hash at (parent_depth, parent_index)
self.nodes.insert((parent_depth, parent_index), n_hash);
if parent_depth == 0 {
// We just set the root hash of the tree - nothing to do anymore
break;
}
// Incr parent index
parent_index += 1;
// Incr current index (+2 because we've just hashed current index & current_index + 1)
current_index += 2;
if parent_index >= parent_max_index {
// reset (aka decr depth & reset indexes)
parent_depth -= 1;
parent_index = parent_index_bak >> 1;
parent_index_bak = parent_index;
parent_max_index >>= 1;
current_depth -= 1;
current_index = current_index_bak >> 1;
current_index_bak = current_index;
}
}
Ok(())
}
}
impl<H: Hasher> ZerokitMerkleProof for OptimalMerkleProof<H>
@@ -326,14 +383,12 @@ where
type Index = u8;
type Hasher = H;
#[must_use]
// Returns the length of a Merkle proof
fn length(&self) -> usize {
self.0.len()
}
/// Computes the leaf index corresponding to a Merkle proof
#[must_use]
fn leaf_index(&self) -> usize {
// In current implementation the path indexes in a proof correspond to the binary representation of the leaf index
let mut binary_repr = self.get_path_index();
@@ -343,19 +398,16 @@ where
.fold(0, |acc, digit| (acc << 1) + usize::from(digit))
}
#[must_use]
/// Returns the path elements forming a Merkle proof
fn get_path_elements(&self) -> Vec<H::Fr> {
self.0.iter().map(|x| x.0).collect()
}
/// Returns the path indexes forming a Merkle proof
#[must_use]
fn get_path_index(&self) -> Vec<u8> {
self.0.iter().map(|x| x.1).collect()
}
#[must_use]
/// Computes the Merkle root corresponding by iteratively hashing a Merkle proof with a given input leaf
fn compute_root_from(&self, leaf: &H::Fr) -> H::Fr {
let mut acc: H::Fr = *leaf;

View File

@@ -9,8 +9,6 @@
// The following implementation was adapted from https://github.com/arkworks-rs/sponge/blob/7d9b3a474c9ddb62890014aeaefcb142ac2b3776/src/poseidon/grain_lfsr.rs
#![allow(dead_code)]
use ark_ff::PrimeField;
use num_bigint::BigUint;
@@ -20,7 +18,6 @@ pub struct PoseidonGrainLFSR {
pub head: usize,
}
#[allow(unused_variables)]
impl PoseidonGrainLFSR {
pub fn new(
is_field: u64,

View File

@@ -7,7 +7,7 @@ use crate::poseidon_constants::find_poseidon_ark_and_mds;
use ark_ff::PrimeField;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoundParamenters<F: PrimeField> {
pub struct RoundParameters<F: PrimeField> {
pub t: usize,
pub n_rounds_f: usize,
pub n_rounds_p: usize,
@@ -17,16 +17,16 @@ pub struct RoundParamenters<F: PrimeField> {
}
pub struct Poseidon<F: PrimeField> {
round_params: Vec<RoundParamenters<F>>,
round_params: Vec<RoundParameters<F>>,
}
impl<F: PrimeField> Poseidon<F> {
// Loads round parameters and generates round constants
// poseidon_params is a vector containing tuples (t, RF, RP, skip_matrices)
// where: t is the rate (input lenght + 1), RF is the number of full rounds, RP is the number of partial rounds
// where: t is the rate (input length + 1), RF is the number of full rounds, RP is the number of partial rounds
// and skip_matrices is a (temporary) parameter used to generate secure MDS matrices (see comments in the description of find_poseidon_ark_and_mds)
// TODO: implement automatic generation of round parameters
pub fn from(poseidon_params: &[(usize, usize, usize, usize)]) -> Self {
let mut read_params = Vec::<RoundParamenters<F>>::new();
let mut read_params = Vec::<RoundParameters<F>>::with_capacity(poseidon_params.len());
for &(t, n_rounds_f, n_rounds_p, skip_matrices) in poseidon_params {
let (ark, mds) = find_poseidon_ark_and_mds::<F>(
@@ -38,7 +38,7 @@ impl<F: PrimeField> Poseidon<F> {
n_rounds_p as u64,
skip_matrices,
);
let rp = RoundParamenters {
let rp = RoundParameters {
t,
n_rounds_p,
n_rounds_f,
@@ -54,24 +54,24 @@ impl<F: PrimeField> Poseidon<F> {
}
}
pub fn get_parameters(&self) -> Vec<RoundParamenters<F>> {
self.round_params.clone()
pub fn get_parameters(&self) -> &Vec<RoundParameters<F>> {
&self.round_params
}
pub fn ark(&self, state: &mut [F], c: &[F], it: usize) {
for i in 0..state.len() {
state[i] += c[it + i];
}
state.iter_mut().enumerate().for_each(|(i, elem)| {
*elem += c[it + i];
});
}
pub fn sbox(&self, n_rounds_f: usize, n_rounds_p: usize, state: &mut [F], i: usize) {
if (i < n_rounds_f / 2) || (i >= n_rounds_f / 2 + n_rounds_p) {
for current_state in &mut state.iter_mut() {
state.iter_mut().for_each(|current_state| {
let aux = *current_state;
*current_state *= *current_state;
*current_state *= *current_state;
*current_state *= aux;
}
})
} else {
let aux = state[0];
state[0] *= state[0];
@@ -80,21 +80,20 @@ impl<F: PrimeField> Poseidon<F> {
}
}
pub fn mix(&self, state: &[F], m: &[Vec<F>]) -> Vec<F> {
let mut new_state: Vec<F> = Vec::new();
pub fn mix_2(&self, state: &[F], m: &[Vec<F>], state_2: &mut [F]) {
for i in 0..state.len() {
new_state.push(F::zero());
for (j, state_item) in state.iter().enumerate() {
let mut mij = m[i][j];
mij *= state_item;
new_state[i] += mij;
// Cache the row reference
let row = &m[i];
let mut acc = F::ZERO;
for j in 0..state.len() {
acc += row[j] * state[j];
}
state_2[i] = acc;
}
new_state.clone()
}
pub fn hash(&self, inp: Vec<F>) -> Result<F, String> {
// Note that the rate t becomes input lenght + 1, hence for lenght N we pick parameters with T = N + 1
pub fn hash(&self, inp: &[F]) -> Result<F, String> {
// Note that the rate t becomes input length + 1; hence for length N we pick parameters with T = N + 1
let t = inp.len() + 1;
// We seek the index (Poseidon's round_params is an ordered vector) for the parameters corresponding to t
@@ -106,8 +105,9 @@ impl<F: PrimeField> Poseidon<F> {
let param_index = param_index.unwrap();
let mut state = vec![F::zero(); t];
state[1..].clone_from_slice(&inp);
let mut state = vec![F::ZERO; t];
let mut state_2 = state.clone();
state[1..].clone_from_slice(inp);
for i in 0..(self.round_params[param_index].n_rounds_f
+ self.round_params[param_index].n_rounds_p)
@@ -123,7 +123,8 @@ impl<F: PrimeField> Poseidon<F> {
&mut state,
i,
);
state = self.mix(&state, &self.round_params[param_index].m);
self.mix_2(&state, &self.round_params[param_index].m, &mut state_2);
std::mem::swap(&mut state, &mut state_2);
}
Ok(state[0])

View File

@@ -107,7 +107,7 @@ pub mod test {
let leaves_4: Vec<TestFr> = (0u32..4).map(TestFr::from).collect();
let mut tree_full = default_full_merkle_tree(depth);
let _ = tree_full.set_range(0, leaves.clone());
let _ = tree_full.set_range(0, leaves.clone().into_iter());
assert!(tree_full.get_empty_leaves_indices().is_empty());
let mut vec_idxs = Vec::new();
@@ -125,31 +125,31 @@ pub mod test {
// Check situation when the number of items to insert is less than the number of items to delete
tree_full
.override_range(0, leaves_2.clone(), [0, 1, 2, 3])
.override_range(0, leaves_2.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
// check if the indexes for write and delete are the same
tree_full
.override_range(0, leaves_4.clone(), [0, 1, 2, 3])
.override_range(0, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert_eq!(tree_full.get_empty_leaves_indices(), vec![]);
// check if indexes for deletion are before indexes for overwriting
tree_full
.override_range(4, leaves_4.clone(), [0, 1, 2, 3])
.override_range(4, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert_eq!(tree_full.get_empty_leaves_indices(), vec![0, 1, 2, 3]);
// check if the indices for write and delete do not overlap completely
tree_full
.override_range(2, leaves_4.clone(), [0, 1, 2, 3])
.override_range(2, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert_eq!(tree_full.get_empty_leaves_indices(), vec![0, 1]);
//// Optimal Merkle Tree Trest
let mut tree_opt = default_optimal_merkle_tree(depth);
let _ = tree_opt.set_range(0, leaves.clone());
let _ = tree_opt.set_range(0, leaves.clone().into_iter());
assert!(tree_opt.get_empty_leaves_indices().is_empty());
let mut vec_idxs = Vec::new();
@@ -166,24 +166,24 @@ pub mod test {
// Check situation when the number of items to insert is less than the number of items to delete
tree_opt
.override_range(0, leaves_2.clone(), [0, 1, 2, 3])
.override_range(0, leaves_2.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
// check if the indexes for write and delete are the same
tree_opt
.override_range(0, leaves_4.clone(), [0, 1, 2, 3])
.override_range(0, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert_eq!(tree_opt.get_empty_leaves_indices(), vec![]);
// check if indexes for deletion are before indexes for overwriting
tree_opt
.override_range(4, leaves_4.clone(), [0, 1, 2, 3])
.override_range(4, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert_eq!(tree_opt.get_empty_leaves_indices(), vec![0, 1, 2, 3]);
// check if the indices for write and delete do not overlap completely
tree_opt
.override_range(2, leaves_4.clone(), [0, 1, 2, 3])
.override_range(2, leaves_4.clone().into_iter(), [0, 1, 2, 3].into_iter())
.unwrap();
assert_eq!(tree_opt.get_empty_leaves_indices(), vec![0, 1]);
}
@@ -191,7 +191,7 @@ pub mod test {
#[test]
fn test_subtree_root() {
let depth = 3;
let nof_leaves: usize = 6;
let nof_leaves: usize = 4;
let leaves: Vec<TestFr> = (0..nof_leaves as u32).map(TestFr::from).collect();
let mut tree_full = default_optimal_merkle_tree(depth);

View File

@@ -3530,7 +3530,8 @@ mod test {
{
// We check if the round constants and matrices correspond to the one generated when instantiating Poseidon with ROUND_PARAMS
let (loaded_c, loaded_m) = load_constants();
let poseidon_parameters = Poseidon::<Fr>::from(&ROUND_PARAMS).get_parameters();
let poseidon_hasher = Poseidon::<Fr>::from(&ROUND_PARAMS);
let poseidon_parameters = poseidon_hasher.get_parameters();
for i in 0..poseidon_parameters.len() {
assert_eq!(loaded_c[i], poseidon_parameters[i].c);
assert_eq!(loaded_m[i], poseidon_parameters[i].m);

View File

@@ -0,0 +1,131 @@
#[cfg(test)]
mod test {
use ark_bn254::Fr;
use ark_ff::{AdditiveGroup, Field};
use std::collections::HashMap;
use std::str::FromStr;
use zerokit_utils::poseidon_hash::Poseidon;
const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [
(2, 8, 56, 0),
(3, 8, 57, 0),
(4, 8, 56, 0),
(5, 8, 60, 0),
(6, 8, 60, 0),
(7, 8, 63, 0),
(8, 8, 64, 0),
(9, 8, 63, 0),
];
#[test]
fn test_poseidon_hash_basic() {
let map = HashMap::from([
(
Fr::ZERO,
Fr::from_str(
"19014214495641488759237505126948346942972912379615652741039992445865937985820",
)
.unwrap(),
),
(
Fr::ONE,
Fr::from_str(
"18586133768512220936620570745912940619677854269274689475585506675881198879027",
)
.unwrap(),
),
(
Fr::from(255),
Fr::from_str(
"20026131459732984724454933360292530547665726761019872861025481903072111625788",
)
.unwrap(),
),
(
Fr::from(u16::MAX),
Fr::from_str(
"12358868638722666642632413418981275677998688723398440898957566982787708451243",
)
.unwrap(),
),
(
Fr::from(u64::MAX),
Fr::from_str(
"17449307747295017006142981453320720946812828330895590310359634430146721583189",
)
.unwrap(),
),
]);
// map (key: what to hash, value: expected value)
for (k, v) in map.into_iter() {
let hasher = Poseidon::<Fr>::from(&ROUND_PARAMS);
let h = hasher.hash(&[k]);
assert_eq!(h.unwrap(), v);
}
}
#[test]
fn test_poseidon_hash_multi() {
// All hashes done in a merkle tree (with leaves: [0, 1, 2, 3, 4, 5, 6, 7])
// ~ leaves
let fr_0 = Fr::ZERO;
let fr_1 = Fr::ONE;
let fr_2 = Fr::from(2);
let fr_3 = Fr::from(3);
let fr_4 = Fr::from(4);
let fr_5 = Fr::from(5);
let fr_6 = Fr::from(6);
let fr_7 = Fr::from(7);
let fr_0_1 = Fr::from_str(
"12583541437132735734108669866114103169564651237895298778035846191048104863326",
)
.unwrap();
let fr_2_3 = Fr::from_str(
"17197790661637433027297685226742709599380837544520340689137581733613433332983",
)
.unwrap();
let fr_4_5 = Fr::from_str(
"756592041685769348226045093946546956867261766023639881791475046640232555043",
)
.unwrap();
let fr_6_7 = Fr::from_str(
"5558359459771725727593826278265342308584225092343962757289948761260561575479",
)
.unwrap();
let fr_0_3 = Fr::from_str(
"3720616653028013822312861221679392249031832781774563366107458835261883914924",
)
.unwrap();
let fr_4_7 = Fr::from_str(
"7960741062684589801276390367952372418815534638314682948141519164356522829957",
)
.unwrap();
// ~ root
let fr_0_7 = Fr::from_str(
"11780650233517635876913804110234352847867393797952240856403268682492028497284",
)
.unwrap();
// map (key: what to hash, value: expected value)
let map = HashMap::from([
((fr_0, fr_1), fr_0_1),
((fr_2, fr_3), fr_2_3),
((fr_4, fr_5), fr_4_5),
((fr_6, fr_7), fr_6_7),
((fr_0_1, fr_2_3), fr_0_3),
((fr_4_5, fr_6_7), fr_4_7),
((fr_0_3, fr_4_7), fr_0_7),
]);
for (k, v) in map.into_iter() {
let hasher = Poseidon::<Fr>::from(&ROUND_PARAMS);
let h = hasher.hash(&[k.0, k.1]);
assert_eq!(h.unwrap(), v);
}
}
}