mirror of
https://github.com/vacp2p/zerokit.git
synced 2026-01-09 13:47:58 -05:00
Compare commits
152 Commits
expose-has
...
test-tree-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb4ff7571 | ||
|
|
b5afb847f1 | ||
|
|
a7d58926e4 | ||
|
|
e160ac2524 | ||
|
|
eb8eedfdb4 | ||
|
|
c78f1f1534 | ||
|
|
4d62a4d60d | ||
|
|
57b694db5d | ||
|
|
fd568c17b3 | ||
|
|
cf845c6a74 | ||
|
|
fe566b3314 | ||
|
|
0b00c639a0 | ||
|
|
7c801a804e | ||
|
|
9da80dd807 | ||
|
|
bcbd6a97af | ||
|
|
6965cf2852 | ||
|
|
578e0507b3 | ||
|
|
bf1e184da9 | ||
|
|
4473688efa | ||
|
|
c80569d518 | ||
|
|
fd99b6af74 | ||
|
|
65f53e3da3 | ||
|
|
042f8a9739 | ||
|
|
baf474e747 | ||
|
|
dc0b31752c | ||
|
|
36013bf4ba | ||
|
|
211b2d4830 | ||
|
|
5f4bcb74ce | ||
|
|
de5fd36add | ||
|
|
19c0f551c8 | ||
|
|
4133f1f8c3 | ||
|
|
149096f7a6 | ||
|
|
7023e85fce | ||
|
|
a4cafa6adc | ||
|
|
4077357e3f | ||
|
|
84d9799d09 | ||
|
|
c576af8e62 | ||
|
|
81470b9678 | ||
|
|
9d4198c205 | ||
|
|
c60e0c33fc | ||
|
|
ba467d370c | ||
|
|
ffd5851d7d | ||
|
|
759d312680 | ||
|
|
fb0ffd74a3 | ||
|
|
9d8372be39 | ||
|
|
de9c0d5072 | ||
|
|
5c60ec7cce | ||
|
|
8793965650 | ||
|
|
1930ca1610 | ||
|
|
4b4169d7a7 | ||
|
|
8a3e33be41 | ||
|
|
7bb2444ba4 | ||
|
|
00f8d039a8 | ||
|
|
e39f156fff | ||
|
|
8b04930583 | ||
|
|
b9d27039c3 | ||
|
|
49e2517e15 | ||
|
|
6621efd0bb | ||
|
|
4a74ff0d6c | ||
|
|
fc823e7187 | ||
|
|
0d5642492a | ||
|
|
c4579e1917 | ||
|
|
e6238fd722 | ||
|
|
5540ddc993 | ||
|
|
d8f813bc2e | ||
|
|
c6493bd10f | ||
|
|
dd5edd6818 | ||
|
|
85d71a5427 | ||
|
|
7790954c4a | ||
|
|
820240d8c0 | ||
|
|
fe2b224981 | ||
|
|
d3d85c3e3c | ||
|
|
0005b1d61f | ||
|
|
4931b25237 | ||
|
|
652cc3647e | ||
|
|
51939be4a8 | ||
|
|
cd60af5b52 | ||
|
|
8581ac0b78 | ||
|
|
5937a67ee6 | ||
|
|
d96eb59e92 | ||
|
|
a372053047 | ||
|
|
b450bfdb37 | ||
|
|
0521c7349e | ||
|
|
d91a5b3568 | ||
|
|
cf9dbb419d | ||
|
|
aaa12db70d | ||
|
|
30d5f94181 | ||
|
|
ccd2ead847 | ||
|
|
7669d72f9b | ||
|
|
b5760697bc | ||
|
|
5c4e3fc13c | ||
|
|
a92d6428d6 | ||
|
|
e6db05f27c | ||
|
|
25f822e779 | ||
|
|
0997d15d33 | ||
|
|
8614b2a33a | ||
|
|
b903d8d740 | ||
|
|
f73c83b571 | ||
|
|
a86b859b75 | ||
|
|
f8fc455d08 | ||
|
|
b51896c3a7 | ||
|
|
0c5ef6abcf | ||
|
|
a1c292cb2e | ||
|
|
c6c1bfde91 | ||
|
|
bf3d1d3309 | ||
|
|
7110e00674 | ||
|
|
99966d1a6e | ||
|
|
7d63912ace | ||
|
|
ef1da42d94 | ||
|
|
ecb4d9307f | ||
|
|
d1414a44c5 | ||
|
|
6d58320077 | ||
|
|
be2dccfdd0 | ||
|
|
9d4ed68450 | ||
|
|
5cf2b2e05e | ||
|
|
36158e8d08 | ||
|
|
c8cf033f32 | ||
|
|
23d2331b78 | ||
|
|
c6b7a8c0a4 | ||
|
|
4ec93c5e1f | ||
|
|
c83c9902d7 | ||
|
|
131cacab35 | ||
|
|
8a365f0c9e | ||
|
|
c561741339 | ||
|
|
90fdfb9d78 | ||
|
|
56b9285fef | ||
|
|
be88a432d7 | ||
|
|
8cfd83de54 | ||
|
|
2793fe0e24 | ||
|
|
0d35571215 | ||
|
|
9cc86e526e | ||
|
|
ecd056884c | ||
|
|
96497db7c5 | ||
|
|
ba8f011cc1 | ||
|
|
9dc92ec1ce | ||
|
|
75d760c179 | ||
|
|
72a3ce1770 | ||
|
|
b841e725a0 | ||
|
|
3177e3ae74 | ||
|
|
2c4de0484a | ||
|
|
fcd4854037 | ||
|
|
d68dc1ad8e | ||
|
|
8c3d60ed01 | ||
|
|
c2d386cb74 | ||
|
|
8f2c9e3586 | ||
|
|
584c2cf4c0 | ||
|
|
2c4b399126 | ||
|
|
c4b699ddff | ||
|
|
33d3732922 | ||
|
|
654c77dcf6 | ||
|
|
783f875d3b | ||
|
|
fd7d7d9318 |
5
.github/labels.yml
vendored
5
.github/labels.yml
vendored
@@ -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)
|
||||
|
||||
289
.github/workflows/ci.yml
vendored
289
.github/workflows/ci.yml
vendored
@@ -3,203 +3,232 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '!.github/workflows/*.yml'
|
||||
- '!multiplier/src/**'
|
||||
- '!private-settlement/src/**'
|
||||
- '!rln-wasm/**'
|
||||
- '!rln/src/**'
|
||||
- '!rln/resources/**'
|
||||
- '!semaphore/src/**'
|
||||
- '!utils/src/**'
|
||||
- "**.md"
|
||||
- "!.github/workflows/*.yml"
|
||||
- "!rln-wasm/**"
|
||||
- "!rln/src/**"
|
||||
- "!rln/resources/**"
|
||||
- "!utils/src/**"
|
||||
- "!rln-wasm-utils/**"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '!.github/workflows/*.yml'
|
||||
- '!multiplier/src/**'
|
||||
- '!private-settlement/src/**'
|
||||
- '!rln-wasm/**'
|
||||
- '!rln/src/**'
|
||||
- '!rln/resources/**'
|
||||
- '!semaphore/src/**'
|
||||
- '!utils/src/**'
|
||||
- "**.md"
|
||||
- "!.github/workflows/*.yml"
|
||||
- "!rln-wasm/**"
|
||||
- "!rln/src/**"
|
||||
- "!rln/resources/**"
|
||||
- "!utils/src/**"
|
||||
- "!rln-wasm-utils/**"
|
||||
|
||||
|
||||
name: Tests
|
||||
name: CI
|
||||
|
||||
jobs:
|
||||
|
||||
multiplier:
|
||||
utils-test:
|
||||
# skip tests on draft PRs
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest]
|
||||
crate: [utils]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: multiplier - ${{ matrix.platform }}
|
||||
|
||||
name: Test - ${{ matrix.crate }} - ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: cargo-make test
|
||||
- name: Test utils
|
||||
run: |
|
||||
cargo make test --release
|
||||
working-directory: multiplier
|
||||
|
||||
semaphore:
|
||||
cargo make test --release
|
||||
working-directory: ${{ matrix.crate }}
|
||||
|
||||
rln-test:
|
||||
# skip tests on draft PRs
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest]
|
||||
crate: [rln]
|
||||
feature: ["default", "stateless"]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: semaphore - ${{ matrix.platform }}
|
||||
|
||||
name: Test - ${{ matrix.crate }} - ${{ matrix.platform }} - ${{ matrix.feature }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install Dependencies
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: cargo-make test
|
||||
- name: Test rln
|
||||
run: |
|
||||
if [ ${{ matrix.feature }} == default ]; then
|
||||
cargo make test --release
|
||||
working-directory: semaphore
|
||||
else
|
||||
cargo make test_${{ matrix.feature }} --release
|
||||
fi
|
||||
working-directory: ${{ matrix.crate }}
|
||||
|
||||
rln-wasm:
|
||||
rln-wasm-test:
|
||||
# skip tests on draft PRs
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest]
|
||||
crate: [rln-wasm]
|
||||
feature: ["default"]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: rln-wasm - ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
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
|
||||
working-directory: rln-wasm
|
||||
- run: cargo-make test
|
||||
working-directory: rln-wasm
|
||||
|
||||
rln:
|
||||
name: Test - ${{ matrix.crate }} - ${{ matrix.platform }} - ${{ matrix.feature }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: Build rln-wasm
|
||||
run: cargo make build
|
||||
working-directory: ${{ matrix.crate }}
|
||||
- name: Test rln-wasm on node
|
||||
run: cargo make test --release
|
||||
working-directory: ${{ matrix.crate }}
|
||||
- name: Test rln-wasm on browser
|
||||
run: cargo make test_browser --release
|
||||
working-directory: ${{ matrix.crate }}
|
||||
|
||||
rln-wasm-parallel-test:
|
||||
# skip tests on draft PRs
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest]
|
||||
crate: [rln-wasm]
|
||||
feature: ["parallel"]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: rln - ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install Dependencies
|
||||
run: make installdeps
|
||||
- name: cargo-make test
|
||||
run: |
|
||||
cargo make test --release
|
||||
working-directory: rln
|
||||
|
||||
utils:
|
||||
name: Test - ${{ matrix.crate }} - ${{ matrix.platform }} - ${{ matrix.feature }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install nightly toolchain
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: Build rln-wasm in parallel mode
|
||||
run: cargo make build_parallel
|
||||
working-directory: ${{ matrix.crate }}
|
||||
- name: Test rln-wasm in parallel mode on browser
|
||||
run: cargo make test_parallel --release
|
||||
working-directory: ${{ matrix.crate }}
|
||||
|
||||
rln-wasm-utils-test:
|
||||
# skip tests on draft PRs
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest]
|
||||
crate: [rln-wasm-utils]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: utils - ${{ matrix.platform }}
|
||||
|
||||
name: Test - ${{ matrix.crate }} - ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install Dependencies
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: cargo-make test
|
||||
run: |
|
||||
cargo make test --release
|
||||
working-directory: utils
|
||||
- name: Test rln-wasm-utils
|
||||
run: cargo make test --release
|
||||
working-directory: ${{ matrix.crate }}
|
||||
|
||||
lint:
|
||||
# run on both ready and draft PRs
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && !github.event.pull_request.draft)
|
||||
strategy:
|
||||
matrix:
|
||||
# we run lint tests only on ubuntu
|
||||
platform: [ubuntu-latest]
|
||||
crate: [rln, rln-wasm, rln-wasm-utils, utils]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: lint - ${{ matrix.platform }}
|
||||
name: Lint - ${{ matrix.crate }} - ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install Dependencies
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: cargo fmt
|
||||
- name: Check formatting
|
||||
if: success() || failure()
|
||||
run: cargo fmt --all -- --check
|
||||
- name: multiplier - cargo clippy
|
||||
run: cargo fmt -- --check
|
||||
working-directory: ${{ matrix.crate }}
|
||||
- name: Check clippy
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo clippy --release -- -D warnings
|
||||
working-directory: multiplier
|
||||
- name: semaphore - cargo clippy
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo clippy --release -- -D warnings
|
||||
working-directory: semaphore
|
||||
- name: rln - cargo clippy
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo clippy --release -- -D warnings
|
||||
working-directory: rln
|
||||
- name: utils - cargo clippy
|
||||
if: success() || failure()
|
||||
run: |
|
||||
cargo clippy --release -- -D warnings
|
||||
working-directory: utils
|
||||
# We skip clippy on rln-wasm, since wasm target is managed by cargo make
|
||||
# Currently not treating warnings as error, too noisy
|
||||
# -- -D warnings
|
||||
cargo clippy --all-targets --release -- -D warnings
|
||||
working-directory: ${{ matrix.crate }}
|
||||
|
||||
benchmark-utils:
|
||||
# run only on ready pull requests
|
||||
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
|
||||
strategy:
|
||||
matrix:
|
||||
# we run benchmark tests only on ubuntu
|
||||
platform: [ubuntu-latest]
|
||||
crate: [utils]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: Benchmark - ${{ matrix.crate }} - ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: boa-dev/criterion-compare-action@v3
|
||||
with:
|
||||
branchName: ${{ github.base_ref }}
|
||||
cwd: ${{ matrix.crate }}
|
||||
|
||||
benchmark-rln:
|
||||
# run only on ready pull requests
|
||||
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
|
||||
strategy:
|
||||
matrix:
|
||||
# we run benchmark tests only on ubuntu
|
||||
platform: [ubuntu-latest]
|
||||
crate: [rln]
|
||||
feature: ["default"]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
|
||||
name: Benchmark - ${{ matrix.crate }} - ${{ matrix.platform }} - ${{ matrix.feature }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: boa-dev/criterion-compare-action@v3
|
||||
with:
|
||||
branchName: ${{ github.base_ref }}
|
||||
cwd: ${{ matrix.crate }}
|
||||
features: ${{ matrix.feature }}
|
||||
|
||||
188
.github/workflows/nightly-release.yml
vendored
188
.github/workflows/nightly-release.yml
vendored
@@ -6,39 +6,43 @@ on:
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-gnu
|
||||
- i686-unknown-linux-gnu
|
||||
name: Linux build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
feature:
|
||||
- "stateless"
|
||||
- "stateless,parallel"
|
||||
- "pmtree-ft"
|
||||
- "pmtree-ft,parallel"
|
||||
- "fullmerkletree"
|
||||
- "fullmerkletree,parallel"
|
||||
- "optimalmerkletree"
|
||||
- "optimalmerkletree,parallel"
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-gnu
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: cross build
|
||||
- name: Cross build
|
||||
run: |
|
||||
cross build --release --target ${{ matrix.target }} --workspace --exclude rln-wasm
|
||||
cross build --release --target ${{ matrix.target }} --no-default-features --features ${{ matrix.feature }} --workspace
|
||||
mkdir release
|
||||
cp target/${{ matrix.target }}/release/librln* release/
|
||||
tar -czvf ${{ matrix.target }}-rln.tar.gz release/
|
||||
|
||||
tar -czvf ${{ matrix.target }}-${{ matrix.feature }}-rln.tar.gz release/
|
||||
- name: Upload archive artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}-archive
|
||||
path: ${{ matrix.target }}-rln.tar.gz
|
||||
name: ${{ matrix.target }}-${{ matrix.feature }}-archive
|
||||
path: ${{ matrix.target }}-${{ matrix.feature }}-rln.tar.gz
|
||||
retention-days: 2
|
||||
|
||||
macos:
|
||||
@@ -46,81 +50,155 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
feature:
|
||||
- "stateless"
|
||||
- "stateless,parallel"
|
||||
- "pmtree-ft"
|
||||
- "pmtree-ft,parallel"
|
||||
- "fullmerkletree"
|
||||
- "fullmerkletree,parallel"
|
||||
- "optimalmerkletree"
|
||||
- "optimalmerkletree,parallel"
|
||||
target:
|
||||
- x86_64-apple-darwin
|
||||
- aarch64-apple-darwin
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: cross build
|
||||
- name: Cross build
|
||||
run: |
|
||||
cross build --release --target ${{ matrix.target }} --workspace --exclude rln-wasm
|
||||
cross build --release --target ${{ matrix.target }} --no-default-features --features ${{ matrix.feature }} --workspace
|
||||
mkdir release
|
||||
cp target/${{ matrix.target }}/release/librln* release/
|
||||
tar -czvf ${{ matrix.target }}-rln.tar.gz release/
|
||||
|
||||
tar -czvf ${{ matrix.target }}-${{ matrix.feature }}-rln.tar.gz release/
|
||||
- name: Upload archive artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}-archive
|
||||
path: ${{ matrix.target }}-rln.tar.gz
|
||||
name: ${{ matrix.target }}-${{ matrix.feature }}-archive
|
||||
path: ${{ matrix.target }}-${{ matrix.feature }}-rln.tar.gz
|
||||
retention-days: 2
|
||||
|
||||
browser-rln-wasm:
|
||||
name: Browser build (RLN WASM)
|
||||
|
||||
rln-wasm:
|
||||
name: Build rln-wasm
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
feature:
|
||||
- "default"
|
||||
- "parallel"
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
targets: wasm32-unknown-unknown
|
||||
- name: Install nightly toolchain
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
components: rust-src
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: rln-wasm-${{ matrix.feature }}
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
- name: cross make build
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: Install binaryen
|
||||
run: |
|
||||
cross make build
|
||||
mkdir release
|
||||
cp pkg/** release/
|
||||
tar -czvf browser-rln-wasm.tar.gz release/
|
||||
working-directory: rln-wasm
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binaryen
|
||||
- name: Build rln-wasm package
|
||||
run: |
|
||||
if [[ ${{ matrix.feature }} == *parallel* ]]; then
|
||||
env RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals" \
|
||||
rustup run nightly wasm-pack build --release --target web --scope waku \
|
||||
--features ${{ matrix.feature }} -Z build-std=panic_abort,std
|
||||
else
|
||||
wasm-pack build --release --target web --scope waku
|
||||
fi
|
||||
|
||||
sed -i.bak 's/rln-wasm/zerokit-rln-wasm/g' pkg/package.json && rm pkg/package.json.bak
|
||||
|
||||
wasm-opt pkg/rln_wasm_bg.wasm -Oz --strip-debug --strip-dwarf \
|
||||
--remove-unused-module-elements --vacuum -o pkg/rln_wasm_bg.wasm
|
||||
|
||||
mkdir release
|
||||
cp -r pkg/* release/
|
||||
tar -czvf rln-wasm-${{ matrix.feature }}.tar.gz release/
|
||||
working-directory: rln-wasm
|
||||
- name: Upload archive artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: browser-rln-wasm-archive
|
||||
path: rln-wasm/browser-rln-wasm.tar.gz
|
||||
name: rln-wasm-${{ matrix.feature }}-archive
|
||||
path: rln-wasm/rln-wasm-${{ matrix.feature }}.tar.gz
|
||||
retention-days: 2
|
||||
|
||||
rln-wasm-utils:
|
||||
name: Build rln-wasm-utils
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Install stable toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
- name: Install nightly toolchain
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
components: rust-src
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: rln-wasm-utils
|
||||
- name: Install dependencies
|
||||
run: make installdeps
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: Install binaryen
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binaryen
|
||||
- name: Build rln-wasm-utils package
|
||||
run: |
|
||||
wasm-pack build --release --target web --scope waku
|
||||
|
||||
sed -i.bak 's/rln-wasm-utils/zerokit-rln-wasm-utils/g' pkg/package.json && rm pkg/package.json.bak
|
||||
|
||||
wasm-opt pkg/rln_wasm_utils_bg.wasm -Oz --strip-debug --strip-dwarf \
|
||||
--remove-unused-module-elements --vacuum -o pkg/rln_wasm_utils_bg.wasm
|
||||
|
||||
mkdir release
|
||||
cp -r pkg/* release/
|
||||
tar -czvf rln-wasm-utils.tar.gz release/
|
||||
working-directory: rln-wasm-utils
|
||||
- name: Upload archive artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rln-wasm-utils-archive
|
||||
path: rln-wasm-utils/rln-wasm-utils.tar.gz
|
||||
retention-days: 2
|
||||
|
||||
prepare-prerelease:
|
||||
name: Prepare pre-release
|
||||
needs: [linux, macos, browser-rln-wasm]
|
||||
needs: [linux, macos, rln-wasm, rln-wasm-utils]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Delete tag
|
||||
uses: dev-drprasad/delete-tag-and-release@v0.2.1
|
||||
with:
|
||||
@@ -128,7 +206,6 @@ jobs:
|
||||
tag_name: nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create prerelease
|
||||
run: |
|
||||
start_tag=$(gh release list -L 2 --exclude-drafts | grep -v nightly | cut -d$'\t' -f3 | sed -n '1p')
|
||||
@@ -140,9 +217,8 @@ jobs:
|
||||
*-archive/*.tar.gz \
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Delete artifacts
|
||||
uses: geekyeggo/delete-artifact@v1
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
failOnError: false
|
||||
name: |
|
||||
|
||||
2
.github/workflows/sync-labels.yml
vendored
2
.github/workflows/sync-labels.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: micnncim/action-label-syncer@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -1,14 +1,17 @@
|
||||
# Common files to ignore in Rust projects
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
tmp/
|
||||
rln/pmtree_db
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
wabt/
|
||||
# Generated by Cargo will have compiled files and executables
|
||||
/target
|
||||
|
||||
# Generated by rln-cli
|
||||
rln-cli/database
|
||||
|
||||
# Generated by Nix
|
||||
result
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -1,8 +0,0 @@
|
||||
[submodule "rln/vendor/rln"]
|
||||
path = rln/vendor/rln
|
||||
ignore = dirty
|
||||
url = https://github.com/Rate-Limiting-Nullifier/rln_circuits.git
|
||||
[submodule "semaphore/vendor/semaphore"]
|
||||
path = semaphore/vendor/semaphore
|
||||
ignore = dirty
|
||||
url = https://github.com/appliedzkp/semaphore.git
|
||||
@@ -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.
|
||||
|
||||
205
CONTRIBUTING.md
Normal file
205
CONTRIBUTING.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Contributing to Zerokit
|
||||
|
||||
Thank you for your interest in contributing to Zerokit!
|
||||
This guide will discuss how the Zerokit team handles [Commits](#commits),
|
||||
[Pull Requests](#pull-requests) and [Merging](#merging).
|
||||
|
||||
**Note:** We won't force external contributors to follow this verbatim.
|
||||
Following these guidelines definitely helps us in accepting your contributions.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b fix/your-bug-fix` or `git checkout -b feat/your-feature-name`
|
||||
3. Make your changes following our guidelines
|
||||
4. Ensure relevant tests pass (see [testing guidelines](#building-and-testing))
|
||||
5. Commit your changes (signed commits are highly encouraged - see [commit guidelines](#commits))
|
||||
6. Push and create a Pull Request
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install the required dependencies:
|
||||
|
||||
```bash
|
||||
make installdeps
|
||||
```
|
||||
|
||||
Or use Nix:
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
```
|
||||
|
||||
### Building and Testing
|
||||
|
||||
```bash
|
||||
# Build all crates
|
||||
make build
|
||||
|
||||
# Run standard tests
|
||||
make test
|
||||
|
||||
# Module-specific testing
|
||||
cd rln && cargo make test_stateless # Test stateless features
|
||||
cd rln-wasm && cargo make test_browser # Test in browser headless mode
|
||||
cd rln-wasm && cargo make test_parallel # Test parallel features
|
||||
```
|
||||
|
||||
Choose the appropriate test commands based on your changes:
|
||||
|
||||
- Core RLN changes: `make test`
|
||||
- Stateless features: `cargo make test_stateless`
|
||||
- WASM/browser features: `cargo make test_browser`
|
||||
- Parallel computation: `cargo make test_parallel`
|
||||
|
||||
### Tools
|
||||
|
||||
We recommend using the [markdownlint extension](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint)
|
||||
for VS Code to maintain consistent documentation formatting.
|
||||
|
||||
## Commits
|
||||
|
||||
We want to keep our commits small and focused.
|
||||
This allows for easily reviewing individual commits and/or
|
||||
splitting up pull requests when they grow too big.
|
||||
Additionally, this allows us to merge smaller changes quicker and release more often.
|
||||
|
||||
**All commits must be GPG signed.**
|
||||
This ensures the authenticity and integrity of contributions.
|
||||
|
||||
### Conventional Commits
|
||||
|
||||
When making the commit, write the commit message
|
||||
following the [Conventional Commits (v1.0.0)](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
||||
Following this convention allows us to provide an automated release process
|
||||
that also generates a detailed Changelog.
|
||||
|
||||
As described by the specification, our commit messages should be written as:
|
||||
|
||||
```markdown
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
Some examples of this pattern include:
|
||||
|
||||
```markdown
|
||||
feat(rln): add parallel witness calculation support
|
||||
```
|
||||
|
||||
```markdown
|
||||
fix(rln-wasm): resolve memory leak in browser threading
|
||||
```
|
||||
|
||||
```markdown
|
||||
docs: update RLN protocol flow documentation
|
||||
```
|
||||
|
||||
#### Scopes
|
||||
|
||||
Use scopes to improve the Changelog:
|
||||
|
||||
- `rln` - Core RLN implementation
|
||||
- `rln-cli` - Command-line interface
|
||||
- `rln-wasm` - WebAssembly bindings
|
||||
- `rln-wasm-utils` - WebAssembly utilities
|
||||
- `utils` - Cryptographic utilities (Merkle trees, Poseidon hash)
|
||||
- `ci` - Continuous integration
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
Mark breaking changes by adding `!` after the type:
|
||||
|
||||
```markdown
|
||||
feat(rln)!: change proof generation API
|
||||
```
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Before creating a pull request, search for related issues.
|
||||
If none exist, create an issue describing the problem you're solving.
|
||||
|
||||
### CI Flow
|
||||
|
||||
Our continuous integration automatically runs when you create a Pull Request:
|
||||
|
||||
- **Build verification**: All crates compile successfully
|
||||
- **Test execution**: Comprehensive testing across all modules and feature combinations
|
||||
- **Code formatting**: `cargo fmt` compliance
|
||||
- **Linting**: `cargo clippy` checks
|
||||
- **Cross-platform builds**: Testing on multiple platforms
|
||||
|
||||
Ensure the following commands pass before submitting:
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
cargo fmt --all
|
||||
|
||||
# Check for common mistakes
|
||||
cargo clippy --all-targets
|
||||
|
||||
# Run all tests
|
||||
make test
|
||||
```
|
||||
|
||||
### Adding Tests
|
||||
|
||||
Include tests for new functionality:
|
||||
|
||||
- **Unit tests** for specific functions
|
||||
- **Integration tests** for broader functionality
|
||||
- **WASM tests** for browser compatibility
|
||||
|
||||
### Typos and Small Changes
|
||||
|
||||
For minor fixes like typos, please report them as issues instead of opening PRs.
|
||||
This helps us manage resources effectively and ensures meaningful contributions.
|
||||
|
||||
## Merging
|
||||
|
||||
We use "squash merging" for all pull requests.
|
||||
This combines all commits into one commit, so keep pull requests small and focused.
|
||||
|
||||
### Requirements
|
||||
|
||||
- CI checks must pass
|
||||
- At least one maintainer review and approval
|
||||
- All review feedback addressed
|
||||
|
||||
### Squash Guidelines
|
||||
|
||||
When squashing, update the commit title to be a proper Conventional Commit and
|
||||
include any other relevant commits in the body:
|
||||
|
||||
```markdown
|
||||
feat(rln): implement parallel witness calculation (#123)
|
||||
|
||||
fix(tests): resolve memory leak in test suite
|
||||
chore(ci): update rust toolchain version
|
||||
```
|
||||
|
||||
## Roadmap Alignment
|
||||
|
||||
Please refer to our [project roadmap](https://roadmap.vac.dev/) for current development priorities.
|
||||
Consider how your changes align with these strategic goals, when contributing.
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Issues**: Create a GitHub issue for bugs or feature requests
|
||||
- **Discussions**: Use GitHub Discussions for questions
|
||||
- **Documentation**: Check existing docs and unit tests for examples
|
||||
|
||||
## License
|
||||
|
||||
By contributing to Zerokit, you agree that your contributions will be licensed under both MIT and
|
||||
Apache 2.0 licenses, consistent with the project's dual licensing.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Conventional Commits Guide](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- [Project GitHub Repository](https://github.com/vacp2p/zerokit)
|
||||
3281
Cargo.lock
generated
3281
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@@ -1,22 +1,10 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"multiplier",
|
||||
"private-settlement",
|
||||
"semaphore",
|
||||
"rln",
|
||||
"rln-wasm",
|
||||
"utils",
|
||||
]
|
||||
members = ["rln", "utils"]
|
||||
exclude = ["rln-cli", "rln-wasm", "rln-wasm-utils"]
|
||||
resolver = "2"
|
||||
|
||||
# Compilation profile for any non-workspace member.
|
||||
# Dependencies are optimized, even in a dev build. This improves dev performance
|
||||
# while having neglible impact on incremental build times.
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release.package."rln-wasm"]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release.package."semaphore"]
|
||||
codegen-units = 1
|
||||
@@ -29,4 +29,7 @@ image = "ghcr.io/cross-rs/mips64-unknown-linux-gnuabi64:latest"
|
||||
image = "ghcr.io/cross-rs/mips64el-unknown-linux-gnuabi64:latest"
|
||||
|
||||
[target.mipsel-unknown-linux-gnu]
|
||||
image = "ghcr.io/cross-rs/mipsel-unknown-linux-gnu:latest"
|
||||
image = "ghcr.io/cross-rs/mipsel-unknown-linux-gnu:latest"
|
||||
|
||||
[target.aarch64-linux-android]
|
||||
image = "ghcr.io/cross-rs/aarch64-linux-android:edge"
|
||||
|
||||
28
Makefile
28
Makefile
@@ -1,6 +1,6 @@
|
||||
.PHONY: all installdeps build test clean
|
||||
.PHONY: all installdeps build test bench clean
|
||||
|
||||
all: .pre-build build
|
||||
all: installdeps build
|
||||
|
||||
.fetch-submodules:
|
||||
@git submodule update --init --recursive
|
||||
@@ -8,25 +8,31 @@ all: .pre-build build
|
||||
.pre-build: .fetch-submodules
|
||||
@cargo install cargo-make
|
||||
ifdef CI
|
||||
@cargo install cross --git https://github.com/cross-rs/cross --branch main
|
||||
@cargo install cross --git https://github.com/cross-rs/cross.git --rev 1511a28
|
||||
endif
|
||||
|
||||
installdeps: .pre-build
|
||||
ifeq ($(shell uname),Darwin)
|
||||
@brew update
|
||||
@brew install cmake ninja
|
||||
@brew install ninja binaryen
|
||||
else ifeq ($(shell uname),Linux)
|
||||
@sudo apt-get update
|
||||
@sudo apt-get install -y cmake ninja-build
|
||||
@if [ -f /etc/os-release ] && grep -q "ID=nixos" /etc/os-release; then \
|
||||
echo "Detected NixOS, skipping apt-get installation."; \
|
||||
else \
|
||||
sudo apt-get install -y cmake ninja-build binaryen; \
|
||||
fi
|
||||
endif
|
||||
@git clone --recursive https://github.com/WebAssembly/wabt.git
|
||||
@cd wabt && mkdir build && cd build && cmake .. -GNinja && ninja && sudo ninja install
|
||||
@which wasm-pack > /dev/null && wasm-pack --version | grep -q "0.13.1" || cargo install wasm-pack --version=0.13.1
|
||||
@test -s "$$HOME/.nvm/nvm.sh" || curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
|
||||
@bash -c '. "$$HOME/.nvm/nvm.sh"; [ "$$(node -v 2>/dev/null)" = "v22.14.0" ] || nvm install 22.14.0; nvm use 22.14.0; nvm alias default 22.14.0'
|
||||
|
||||
build: .pre-build
|
||||
build: installdeps
|
||||
@cargo make build
|
||||
|
||||
test: .pre-build
|
||||
test: build
|
||||
@cargo make test
|
||||
|
||||
bench: build
|
||||
@cargo make bench
|
||||
|
||||
clean:
|
||||
@cargo clean
|
||||
|
||||
79
README.md
79
README.md
@@ -1,35 +1,86 @@
|
||||
# Zerokit
|
||||
|
||||
A set of Zero Knowledge modules, written in Rust and designed to be used in other system programming environments.
|
||||
[](https://crates.io/crates/rln)
|
||||
[](https://github.com/vacp2p/zerokit/actions)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](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
|
||||
|
||||
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
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
make installdeps
|
||||
```
|
||||
To build and test all crates, run the following commands from the root folder
|
||||
|
||||
#### Use Nix to install dependencies
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
48
flake.lock
generated
Normal file
48
flake.lock
generated
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"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",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748399823,
|
||||
"narHash": "sha256-kahD8D5hOXOsGbNdoLLnqCL887cjHkx98Izc37nDjlA=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "d68a69dc71bc19beb3479800392112c2f6218159",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
69
flake.nix
Normal file
69
flake.nix
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
description = "A flake for building zerokit";
|
||||
|
||||
inputs = {
|
||||
# Version 24.11
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=f44bd8ca21e026135061a0a57dcf3d0775b67a49";
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, rust-overlay }:
|
||||
let
|
||||
stableSystems = [
|
||||
"x86_64-linux" "aarch64-linux"
|
||||
"x86_64-darwin" "aarch64-darwin"
|
||||
"x86_64-windows" "i686-linux"
|
||||
"i686-windows"
|
||||
];
|
||||
forAllSystems = nixpkgs.lib.genAttrs stableSystems;
|
||||
overlays = [
|
||||
(import rust-overlay)
|
||||
(f: p: { inherit rust-overlay; })
|
||||
];
|
||||
pkgsFor = forAllSystems (system: import nixpkgs { inherit system overlays; });
|
||||
in rec
|
||||
{
|
||||
packages = forAllSystems (system: let
|
||||
pkgs = pkgsFor.${system};
|
||||
buildPackage = pkgs.callPackage ./nix/default.nix;
|
||||
buildRln = (buildPackage { src = self; project = "rln"; }).override;
|
||||
in rec {
|
||||
rln = buildRln { };
|
||||
|
||||
rln-linux-arm64 = buildRln {
|
||||
target-platform = "aarch64-multiplatform";
|
||||
rust-target = "aarch64-unknown-linux-gnu";
|
||||
};
|
||||
|
||||
rln-android-arm64 = buildRln {
|
||||
target-platform = "aarch64-android-prebuilt";
|
||||
rust-target = "aarch64-linux-android";
|
||||
};
|
||||
|
||||
rln-ios-arm64 = buildRln {
|
||||
target-platform = "aarch64-darwin";
|
||||
rust-target = "aarch64-apple-ios";
|
||||
};
|
||||
|
||||
# TODO: Remove legacy name for RLN android library
|
||||
zerokit-android-arm64 = rln-android-arm64;
|
||||
|
||||
default = rln;
|
||||
});
|
||||
|
||||
devShells = forAllSystems (system: let
|
||||
pkgs = pkgsFor.${system};
|
||||
in {
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
git cmake cargo-make rustup
|
||||
binaryen ninja gnuplot
|
||||
rust-bin.stable.latest.default
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
[package]
|
||||
name = "multiplier"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
# WASM operations
|
||||
# wasmer = { version = "2.0" }
|
||||
# fnv = { version = "1.0.3", default-features = false }
|
||||
# num = { version = "0.4.0" }
|
||||
# num-traits = { version = "0.2.0", default-features = false }
|
||||
|
||||
# ZKP Generation
|
||||
# ark-ff = { version = "0.3.0", default-features = false, features = ["parallel", "asm"] }
|
||||
ark-std = { version = "0.3.0", default-features = false, features = ["parallel"] }
|
||||
ark-bn254 = { version = "0.3.0" }
|
||||
ark-groth16 = { git = "https://github.com/arkworks-rs/groth16", rev = "765817f", features = ["parallel"] }
|
||||
# ark-poly = { version = "^0.3.0", default-features = false, features = ["parallel"] }
|
||||
ark-serialize = { version = "0.3.0", default-features = false }
|
||||
|
||||
ark-circom = { git = "https://github.com/gakonst/ark-circom", features = ["circom-2"], rev = "35ce5a9" }
|
||||
|
||||
# error handling
|
||||
color-eyre = "0.6.1"
|
||||
|
||||
# decoding of data
|
||||
# hex = "0.4.3"
|
||||
# byteorder = "1.4.3"
|
||||
@@ -1,7 +0,0 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["build", "--release"]
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
args = ["test", "--release"]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Multiplier example
|
||||
|
||||
Example wrapper around a basic Circom circuit to test Circom 2 integration
|
||||
through ark-circom and FFI.
|
||||
|
||||
## Build and Test
|
||||
|
||||
To build and test, run the following commands within the module folder
|
||||
```bash
|
||||
cargo make build
|
||||
cargo make test
|
||||
```
|
||||
|
||||
## FFI
|
||||
|
||||
To generate C or Nim bindings from Rust FFI, use `cbindgen` or `nbindgen`:
|
||||
|
||||
```
|
||||
cbindgen . -o target/multiplier.h
|
||||
nbindgen . -o target/multiplier.nim
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,77 +0,0 @@
|
||||
use crate::public::Multiplier;
|
||||
use std::slice;
|
||||
|
||||
/// Buffer struct is taken from
|
||||
/// https://github.com/celo-org/celo-threshold-bls-rs/blob/master/crates/threshold-bls-ffi/src/ffi.rs
|
||||
///
|
||||
/// Also heavily inspired by https://github.com/kilic/rln/blob/master/src/ffi.rs
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Buffer {
|
||||
pub ptr: *const u8,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Buffer {
|
||||
fn from(src: &[u8]) -> Self {
|
||||
Self {
|
||||
ptr: &src[0] as *const u8,
|
||||
len: src.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&Buffer> for &'a [u8] {
|
||||
fn from(src: &Buffer) -> &'a [u8] {
|
||||
unsafe { slice::from_raw_parts(src.ptr, src.len) }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new_circuit(ctx: *mut *mut Multiplier) -> bool {
|
||||
if let Ok(mul) = Multiplier::new() {
|
||||
unsafe { *ctx = Box::into_raw(Box::new(mul)) };
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn prove(ctx: *const Multiplier, output_buffer: *mut Buffer) -> bool {
|
||||
println!("multiplier ffi: prove");
|
||||
let mul = unsafe { &*ctx };
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
|
||||
match mul.prove(&mut output_data) {
|
||||
Ok(proof_data) => proof_data,
|
||||
Err(_) => return false,
|
||||
};
|
||||
unsafe { *output_buffer = Buffer::from(&output_data[..]) };
|
||||
std::mem::forget(output_data);
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn verify(
|
||||
ctx: *const Multiplier,
|
||||
proof_buffer: *const Buffer,
|
||||
result_ptr: *mut u32,
|
||||
) -> bool {
|
||||
println!("multiplier ffi: verify");
|
||||
let mul = unsafe { &*ctx };
|
||||
let proof_data = <&[u8]>::from(unsafe { &*proof_buffer });
|
||||
if match mul.verify(proof_data) {
|
||||
Ok(verified) => verified,
|
||||
Err(_) => return false,
|
||||
} {
|
||||
unsafe { *result_ptr = 0 };
|
||||
} else {
|
||||
unsafe { *result_ptr = 1 };
|
||||
};
|
||||
true
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod ffi;
|
||||
pub mod public;
|
||||
@@ -1,49 +0,0 @@
|
||||
use ark_circom::{CircomBuilder, CircomConfig};
|
||||
use ark_std::rand::thread_rng;
|
||||
use color_eyre::{Report, Result};
|
||||
|
||||
use ark_bn254::Bn254;
|
||||
use ark_groth16::{
|
||||
create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof,
|
||||
};
|
||||
|
||||
fn groth16_proof_example() -> Result<()> {
|
||||
let cfg = CircomConfig::<Bn254>::new(
|
||||
"./resources/circom2_multiplier2.wasm",
|
||||
"./resources/circom2_multiplier2.r1cs",
|
||||
)?;
|
||||
|
||||
let mut builder = CircomBuilder::new(cfg);
|
||||
builder.push_input("a", 3);
|
||||
builder.push_input("b", 11);
|
||||
|
||||
// create an empty instance for setting it up
|
||||
let circom = builder.setup();
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let params = generate_random_parameters::<Bn254, _, _>(circom, &mut rng)?;
|
||||
|
||||
let circom = builder.build()?;
|
||||
|
||||
let inputs = circom
|
||||
.get_public_inputs()
|
||||
.ok_or(Report::msg("no public inputs"))?;
|
||||
|
||||
let proof = prove(circom, ¶ms, &mut rng)?;
|
||||
|
||||
let pvk = prepare_verifying_key(¶ms.vk);
|
||||
|
||||
match verify_proof(&pvk, &proof, &inputs) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(Report::msg("not verified")),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
|
||||
match groth16_proof_example() {
|
||||
Ok(_) => println!("Success"),
|
||||
Err(_) => println!("Error"),
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
use ark_circom::{CircomBuilder, CircomCircuit, CircomConfig};
|
||||
use ark_std::rand::thread_rng;
|
||||
|
||||
use ark_bn254::Bn254;
|
||||
use ark_groth16::{
|
||||
create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof,
|
||||
Proof, ProvingKey,
|
||||
};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use color_eyre::{Report, Result};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub struct Multiplier {
|
||||
circom: CircomCircuit<Bn254>,
|
||||
params: ProvingKey<Bn254>,
|
||||
}
|
||||
|
||||
impl Multiplier {
|
||||
// TODO Break this apart here
|
||||
pub fn new() -> Result<Multiplier> {
|
||||
let cfg = CircomConfig::<Bn254>::new(
|
||||
"./resources/circom2_multiplier2.wasm",
|
||||
"./resources/circom2_multiplier2.r1cs",
|
||||
)?;
|
||||
|
||||
let mut builder = CircomBuilder::new(cfg);
|
||||
builder.push_input("a", 3);
|
||||
builder.push_input("b", 11);
|
||||
|
||||
// create an empty instance for setting it up
|
||||
let circom = builder.setup();
|
||||
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let params = generate_random_parameters::<Bn254, _, _>(circom, &mut rng)?;
|
||||
|
||||
let circom = builder.build()?;
|
||||
|
||||
Ok(Multiplier { circom, params })
|
||||
}
|
||||
|
||||
// TODO Input Read
|
||||
pub fn prove<W: Write>(&self, result_data: W) -> Result<()> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
// XXX: There's probably a better way to do this
|
||||
let circom = self.circom.clone();
|
||||
let params = self.params.clone();
|
||||
|
||||
let proof = prove(circom, ¶ms, &mut rng)?;
|
||||
|
||||
// XXX: Unclear if this is different from other serialization(s)
|
||||
proof.serialize(result_data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify<R: Read>(&self, input_data: R) -> Result<bool> {
|
||||
let proof = Proof::deserialize(input_data)?;
|
||||
|
||||
let pvk = prepare_verifying_key(&self.params.vk);
|
||||
|
||||
// XXX Part of input data?
|
||||
let inputs = self
|
||||
.circom
|
||||
.get_public_inputs()
|
||||
.ok_or(Report::msg("no public inputs"))?;
|
||||
|
||||
let verified = verify_proof(&pvk, &proof, &inputs)?;
|
||||
|
||||
Ok(verified)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Multiplier {
|
||||
fn default() -> Self {
|
||||
Self::new().unwrap()
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use multiplier::public::Multiplier;
|
||||
|
||||
#[test]
|
||||
fn multiplier_proof() {
|
||||
let mul = Multiplier::new().unwrap();
|
||||
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
let _ = mul.prove(&mut output_data);
|
||||
|
||||
let proof_data = &output_data[..];
|
||||
|
||||
// XXX Pass as arg?
|
||||
//let pvk = prepare_verifying_key(&mul.params.vk);
|
||||
|
||||
let verified = mul.verify(proof_data).unwrap();
|
||||
|
||||
assert!(verified);
|
||||
}
|
||||
}
|
||||
64
nix/default.nix
Normal file
64
nix/default.nix
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
pkgs,
|
||||
rust-overlay,
|
||||
project,
|
||||
src ? ../.,
|
||||
release ? true,
|
||||
target-platform ? null,
|
||||
rust-target ? null,
|
||||
features ? null,
|
||||
}:
|
||||
|
||||
let
|
||||
# Use cross-compilation if target-platform is specified.
|
||||
targetPlatformPkgs = if target-platform != null
|
||||
then pkgs.pkgsCross.${target-platform}
|
||||
else pkgs;
|
||||
|
||||
rust-bin = rust-overlay.lib.mkRustBin { } targetPlatformPkgs.buildPackages;
|
||||
|
||||
# Use Rust and Cargo versions from rust-overlay.
|
||||
rustPlatform = targetPlatformPkgs.makeRustPlatform {
|
||||
cargo = rust-bin.stable.latest.minimal;
|
||||
rustc = rust-bin.stable.latest.minimal;
|
||||
};
|
||||
in rustPlatform.buildRustPackage {
|
||||
pname = "zerokit";
|
||||
version = if src ? rev then src.rev else "nightly";
|
||||
|
||||
# Improve caching of sources
|
||||
src = builtins.path { path = src; name = "zerokit"; };
|
||||
|
||||
cargoLock = {
|
||||
lockFile = src + "/Cargo.lock";
|
||||
allowBuiltinFetchGit = true;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ pkgs.rust-cbindgen ];
|
||||
|
||||
doCheck = false;
|
||||
|
||||
CARGO_HOME = "/tmp";
|
||||
|
||||
buildPhase = ''
|
||||
cargo build --lib \
|
||||
${if release then "--release" else ""} \
|
||||
${if rust-target != null then "--target=${rust-target}" else ""} \
|
||||
${if features != null then "--features=${features}" else ""} \
|
||||
--manifest-path ${project}/Cargo.toml
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
set -eu
|
||||
mkdir -p $out/lib
|
||||
find target -type f -name 'librln.*' -not -path '*/deps/*' -exec cp -v '{}' "$out/lib/" \;
|
||||
mkdir -p $out/include
|
||||
cbindgen ${src}/rln -l c > "$out/include/rln.h"
|
||||
'';
|
||||
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "Zerokit";
|
||||
license = licenses.mit;
|
||||
};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "private-settlement"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@@ -1,7 +0,0 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["build", "--release"]
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
args = ["test", "--release"]
|
||||
@@ -1,11 +0,0 @@
|
||||
# Private Settlement Module
|
||||
|
||||
This module is to provide APIs to manage, compute and verify [Private Settlement](https://rfc.vac.dev/spec/44/) zkSNARK proofs and primitives.
|
||||
|
||||
## Build and Test
|
||||
|
||||
To build and test, run the following commands within the module folder
|
||||
```bash
|
||||
cargo make build
|
||||
cargo make test
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = 2 + 2;
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
rln-cli/Cargo.toml
Normal file
25
rln-cli/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "rln-cli"
|
||||
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", version = "0.8.0", default-features = false }
|
||||
zerokit_utils = { path = "../utils", version = "0.6.0", default-features = false }
|
||||
clap = { version = "4.5.41", features = ["cargo", "derive", "env"] }
|
||||
color-eyre = "0.6.5"
|
||||
serde_json = "1.0.141"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = ["rln/pmtree-ft", "rln/parallel"]
|
||||
stateless = ["rln/stateless", "rln/parallel"]
|
||||
9
rln-cli/Makefile.toml
Normal file
9
rln-cli/Makefile.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["build"]
|
||||
|
||||
[tasks.test]
|
||||
disabled = true
|
||||
|
||||
[tasks.bench]
|
||||
disabled = true
|
||||
148
rln-cli/README.md
Normal file
148
rln-cli/README.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 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.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
You can also change **MESSAGE_LIMIT** and **TREE_DEPTH** in the [relay.rs](src/examples/relay.rs) file to see how the RLN instance behaves with different parameters.
|
||||
|
||||
The customize **TREE_DEPTH** 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 --no-default-features --features stateless
|
||||
```
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### Instance Management
|
||||
|
||||
To initialize a new RLN instance:
|
||||
|
||||
```bash
|
||||
cargo run new --tree-depth <DEPTH>
|
||||
```
|
||||
|
||||
To initialize an RLN instance with custom parameters:
|
||||
|
||||
```bash
|
||||
cargo run new-with-params --resources-path <PATH> --tree-depth <DEPTH>
|
||||
```
|
||||
|
||||
To update the Merkle tree depth:
|
||||
|
||||
```bash
|
||||
cargo run set-tree --tree-depth <DEPTH>
|
||||
```
|
||||
|
||||
### 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>
|
||||
```
|
||||
9
rln-cli/example.config.json
Normal file
9
rln-cli/example.config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"path": "database",
|
||||
"temporary": false,
|
||||
"cache_capacity": 1073741824,
|
||||
"flush_every_ms": 500,
|
||||
"mode": "HighThroughput",
|
||||
"use_compression": false,
|
||||
"tree_depth": 20
|
||||
}
|
||||
69
rln-cli/src/commands.rs
Normal file
69
rln-cli/src/commands.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Subcommand;
|
||||
use rln::circuit::TEST_TREE_DEPTH;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
New {
|
||||
#[arg(short, long, default_value_t = TEST_TREE_DEPTH)]
|
||||
tree_depth: usize,
|
||||
},
|
||||
NewWithParams {
|
||||
#[arg(short, long, default_value_t = TEST_TREE_DEPTH)]
|
||||
tree_depth: usize,
|
||||
#[arg(short, long, default_value = "../rln/resources/tree_depth_10")]
|
||||
resources_path: PathBuf,
|
||||
},
|
||||
SetTree {
|
||||
#[arg(short, long, default_value_t = TEST_TREE_DEPTH)]
|
||||
tree_depth: usize,
|
||||
},
|
||||
SetLeaf {
|
||||
#[arg(short, long)]
|
||||
index: usize,
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
SetMultipleLeaves {
|
||||
#[arg(short, long)]
|
||||
index: usize,
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
ResetMultipleLeaves {
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
SetNextLeaf {
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
DeleteLeaf {
|
||||
#[arg(short, long)]
|
||||
index: usize,
|
||||
},
|
||||
GetRoot,
|
||||
GetProof {
|
||||
#[arg(short, long)]
|
||||
index: usize,
|
||||
},
|
||||
Prove {
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
Verify {
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
GenerateProof {
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
},
|
||||
VerifyWithRoots {
|
||||
#[arg(short, long)]
|
||||
input: PathBuf,
|
||||
#[arg(short, long)]
|
||||
roots: PathBuf,
|
||||
},
|
||||
}
|
||||
31
rln-cli/src/config.rs
Normal file
31
rln-cli/src/config.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
|
||||
use color_eyre::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
pub const RLN_CONFIG_PATH: &str = "RLN_CONFIG_PATH";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct Config {
|
||||
pub tree_config: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn load_config() -> Result<Config> {
|
||||
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 tree_config: Value = serde_json::from_str(&contents)?;
|
||||
println!("Initializing RLN with custom config");
|
||||
Ok(Config {
|
||||
tree_config: Some(tree_config.to_string()),
|
||||
})
|
||||
}
|
||||
Err(_) => Ok(Config { tree_config: None }),
|
||||
}
|
||||
}
|
||||
}
|
||||
315
rln-cli/src/examples/relay.rs
Normal file
315
rln-cli/src/examples/relay.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::{stdin, stdout, Cursor, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use color_eyre::{eyre::eyre, Report, Result};
|
||||
use rln::{
|
||||
circuit::Fr,
|
||||
hashers::{hash_to_field_le, poseidon_hash},
|
||||
protocol::{keygen, prepare_prove_input, prepare_verify_input},
|
||||
public::RLN,
|
||||
utils::{fr_to_bytes_le, generate_input_buffer, IdSecret},
|
||||
};
|
||||
|
||||
const MESSAGE_LIMIT: u32 = 1;
|
||||
|
||||
const TREE_DEPTH: usize = 10;
|
||||
|
||||
#[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: IdSecret,
|
||||
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_depth_{TREE_DEPTH}").into();
|
||||
let filenames = ["rln_final.arkzkey", "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(
|
||||
TREE_DEPTH,
|
||||
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^{TREE_DEPTH}");
|
||||
}
|
||||
};
|
||||
|
||||
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.clone(),
|
||||
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(Report::new(err)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_duplicate_message_id(
|
||||
&mut self,
|
||||
previous_proof: Vec<u8>,
|
||||
current_proof: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let x = ¤t_proof[192..224];
|
||||
let y = ¤t_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, _) = IdSecret::from_bytes_le(&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.clone();
|
||||
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 ******** 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_le(b"epoch");
|
||||
let rln_identifier = hash_to_field_le(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");
|
||||
}
|
||||
316
rln-cli/src/examples/stateless.rs
Normal file
316
rln-cli/src/examples/stateless.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
#![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_DEPTH},
|
||||
hashers::{hash_to_field_le, poseidon_hash, PoseidonHash},
|
||||
protocol::{keygen, prepare_verify_input, rln_witness_from_values, serialize_witness},
|
||||
public::RLN,
|
||||
utils::{fr_to_bytes_le, IdSecret},
|
||||
};
|
||||
use zerokit_utils::{OptimalMerkleTree, ZerokitMerkleProof, 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: IdSecret,
|
||||
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: OptimalMerkleTree<PoseidonHash>,
|
||||
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: OptimalMerkleTree<PoseidonHash> = OptimalMerkleTree::new(
|
||||
TEST_TREE_DEPTH,
|
||||
default_leaf,
|
||||
ConfigOf::<OptimalMerkleTree<PoseidonHash>>::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
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_le(signal.as_bytes());
|
||||
|
||||
let rln_witness = rln_witness_from_values(
|
||||
identity.identity_secret_hash.clone(),
|
||||
merkle_proof.get_path_elements(),
|
||||
merkle_proof.get_path_index(),
|
||||
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 = ¤t_proof[192..224];
|
||||
let y = ¤t_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, _) = IdSecret::from_bytes_le(&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.clone();
|
||||
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: ********"
|
||||
);
|
||||
self.local_identities.remove(&user_index);
|
||||
println!("User index {user_index} has been SLASHED");
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(eyre!("user 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_le(b"epoch");
|
||||
let rln_identifier = hash_to_field_le(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");
|
||||
}
|
||||
195
rln-cli/src/main.rs
Normal file
195
rln-cli/src/main.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Cursor, Read},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::{eyre::Report, Result};
|
||||
use commands::Commands;
|
||||
use config::Config;
|
||||
use rln::{
|
||||
public::RLN,
|
||||
utils::{bytes_le_to_fr, bytes_le_to_vec_fr},
|
||||
};
|
||||
use serde_json::json;
|
||||
use state::State;
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
mod state;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut state = match &cli.command {
|
||||
Some(Commands::New { .. }) | Some(Commands::NewWithParams { .. }) => State::default(),
|
||||
_ => State::load_state()?,
|
||||
};
|
||||
|
||||
match cli.command {
|
||||
Some(Commands::New { tree_depth }) => {
|
||||
let config = Config::load_config()?;
|
||||
state.rln = if let Some(tree_config) = config.tree_config {
|
||||
println!("Initializing RLN with custom config");
|
||||
Some(RLN::new(tree_depth, Cursor::new(tree_config.as_bytes()))?)
|
||||
} else {
|
||||
println!("Initializing RLN with default config");
|
||||
Some(RLN::new(tree_depth, Cursor::new(json!({}).to_string()))?)
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
Some(Commands::NewWithParams {
|
||||
tree_depth,
|
||||
resources_path,
|
||||
}) => {
|
||||
let mut resources: Vec<Vec<u8>> = Vec::new();
|
||||
let filenames = ["rln_final.arkzkey", "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 buffer = vec![0; metadata.len() as usize];
|
||||
file.read_exact(&mut buffer)?;
|
||||
resources.push(buffer);
|
||||
}
|
||||
let config = Config::load_config()?;
|
||||
if let Some(tree_config) = config.tree_config {
|
||||
println!("Initializing RLN with custom config");
|
||||
state.rln = Some(RLN::new_with_params(
|
||||
tree_depth,
|
||||
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_depth,
|
||||
resources[0].clone(),
|
||||
resources[1].clone(),
|
||||
Cursor::new(json!({}).to_string()),
|
||||
)?)
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
Some(Commands::SetTree { tree_depth }) => {
|
||||
state
|
||||
.rln
|
||||
.ok_or(Report::msg("no RLN instance initialized"))?
|
||||
.set_tree(tree_depth)?;
|
||||
Ok(())
|
||||
}
|
||||
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)?;
|
||||
Ok(())
|
||||
}
|
||||
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)?;
|
||||
Ok(())
|
||||
}
|
||||
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 { input }) => {
|
||||
let input_data = File::open(input)?;
|
||||
state
|
||||
.rln
|
||||
.ok_or(Report::msg("no RLN instance initialized"))?
|
||||
.set_next_leaf(input_data)?;
|
||||
Ok(())
|
||||
}
|
||||
Some(Commands::DeleteLeaf { index }) => {
|
||||
state
|
||||
.rln
|
||||
.ok_or(Report::msg("no RLN instance initialized"))?
|
||||
.delete_leaf(index)?;
|
||||
Ok(())
|
||||
}
|
||||
Some(Commands::Prove { input }) => {
|
||||
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, &mut output_buffer)?;
|
||||
let proof = output_buffer.into_inner();
|
||||
println!("proof: {proof:?}");
|
||||
Ok(())
|
||||
}
|
||||
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 mut output_buffer = Cursor::new(Vec::<u8>::new());
|
||||
state
|
||||
.rln
|
||||
.ok_or(Report::msg("no RLN instance initialized"))?
|
||||
.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)?;
|
||||
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(()),
|
||||
}
|
||||
}
|
||||
31
rln-cli/src/state.rs
Normal file
31
rln-cli/src/state.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use color_eyre::Result;
|
||||
use rln::{circuit::TEST_TREE_DEPTH, public::RLN};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct State {
|
||||
pub rln: Option<RLN>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub(crate) fn load_state() -> Result<State> {
|
||||
let config = Config::load_config()?;
|
||||
let rln = if let Some(tree_config) = config.tree_config {
|
||||
let config_json: Value = serde_json::from_str(&tree_config)?;
|
||||
let tree_depth = config_json["tree_depth"]
|
||||
.as_u64()
|
||||
.unwrap_or(TEST_TREE_DEPTH as u64);
|
||||
Some(RLN::new(
|
||||
tree_depth as usize,
|
||||
Cursor::new(tree_config.as_bytes()),
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(State { rln })
|
||||
}
|
||||
}
|
||||
21
rln-wasm-utils/.gitignore
vendored
Normal file
21
rln-wasm-utils/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Common files to ignore in Rust projects
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
tmp/
|
||||
|
||||
# Generated by Cargo will have compiled files and executables
|
||||
/target
|
||||
Cargo.lock
|
||||
|
||||
# Generated by rln-wasm
|
||||
pkg/
|
||||
|
||||
# Generated by Nix
|
||||
result
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
35
rln-wasm-utils/Cargo.toml
Normal file
35
rln-wasm-utils/Cargo.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "rln-wasm-utils"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
# TODO: remove this once we have a proper release
|
||||
rln = { path = "../rln", default-features = false, features = ["stateless"] }
|
||||
js-sys = "0.3.77"
|
||||
wasm-bindgen = "0.2.100"
|
||||
rand = "0.8.5"
|
||||
|
||||
# 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 }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.16", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.50"
|
||||
web-sys = { version = "0.3.77", features = ["console"] }
|
||||
ark-std = { version = "0.5.0", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
36
rln-wasm-utils/Makefile.toml
Normal file
36
rln-wasm-utils/Makefile.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[tasks.build]
|
||||
clear = true
|
||||
dependencies = ["pack_build", "pack_rename", "pack_resize"]
|
||||
|
||||
[tasks.pack_build]
|
||||
command = "wasm-pack"
|
||||
args = ["build", "--release", "--target", "web", "--scope", "waku"]
|
||||
|
||||
[tasks.pack_rename]
|
||||
script = "sed -i.bak 's/rln-wasm-utils/zerokit-rln-wasm-utils/g' pkg/package.json && rm pkg/package.json.bak"
|
||||
|
||||
[tasks.pack_resize]
|
||||
command = "wasm-opt"
|
||||
args = [
|
||||
"pkg/rln_wasm_utils_bg.wasm",
|
||||
"-Oz",
|
||||
"--strip-debug",
|
||||
"--strip-dwarf",
|
||||
"--remove-unused-module-elements",
|
||||
"--vacuum",
|
||||
"-o",
|
||||
"pkg/rln_wasm_utils_bg.wasm",
|
||||
]
|
||||
|
||||
[tasks.test]
|
||||
command = "wasm-pack"
|
||||
args = [
|
||||
"test",
|
||||
"--release",
|
||||
"--node",
|
||||
"--target",
|
||||
"wasm32-unknown-unknown",
|
||||
"--",
|
||||
"--nocapture",
|
||||
]
|
||||
dependencies = ["build"]
|
||||
206
rln-wasm-utils/README.md
Normal file
206
rln-wasm-utils/README.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# RLN WASM Utils
|
||||
|
||||
[](https://badge.fury.io/js/@waku%2Fzerokit-rln-wasm-utils)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
The Zerokit RLN WASM Utils Module provides WebAssembly bindings for Rate-Limiting Nullifier [RLN](https://rfc.vac.dev/spec/32/) cryptographic primitives.
|
||||
This module offers comprehensive functionality for identity generation and hashing needed for RLN applications.
|
||||
|
||||
## Features
|
||||
|
||||
### Identity Generation
|
||||
|
||||
- **Random Identity Generation**: Generate cryptographically secure random identities
|
||||
- **Seeded Identity Generation**: Generate deterministic identities from seeds
|
||||
- **Extended Identity Generation**: Generate extended identities with additional parameters
|
||||
- **Seeded Extended Identity Generation**: Generate deterministic extended identities from seeds
|
||||
- **Endianness Support**: Both little-endian and big-endian serialization support
|
||||
|
||||
### Hashing
|
||||
|
||||
- **Standard Hashing**: Hash arbitrary data to field elements
|
||||
- **Poseidon Hashing**: Advanced cryptographic hashing using Poseidon hash function
|
||||
- **Endianness Support**: Both little-endian and big-endian serialization support
|
||||
|
||||
## API Reference
|
||||
|
||||
### Identity Generation Functions
|
||||
|
||||
#### `generateMembershipKey(isLittleEndian: boolean): Uint8Array`
|
||||
|
||||
Generates a random membership key pair (identity secret and commitment).
|
||||
|
||||
**Inputs:**
|
||||
|
||||
- `isLittleEndian`: Boolean indicating endianness for serialization
|
||||
|
||||
**Outputs:** Serialized identity pair as `Uint8Array` in corresponding endianness
|
||||
|
||||
#### `generateExtendedMembershipKey(isLittleEndian: boolean): Uint8Array`
|
||||
|
||||
Generates an extended membership key with additional parameters.
|
||||
|
||||
**Inputs:**
|
||||
|
||||
- `isLittleEndian`: Boolean indicating endianness for serialization
|
||||
|
||||
**Outputs:** Serialized extended identity tuple as `Uint8Array` in corresponding endianness
|
||||
|
||||
#### `generateSeededMembershipKey(seed: Uint8Array, isLittleEndian: boolean): Uint8Array`
|
||||
|
||||
Generates a deterministic membership key from a seed.
|
||||
|
||||
**Inputs:**
|
||||
|
||||
- `seed`: Seed data as `Uint8Array`
|
||||
- `isLittleEndian`: Boolean indicating endianness for serialization
|
||||
|
||||
**Outputs:** Serialized identity pair as `Uint8Array` in corresponding endianness
|
||||
|
||||
#### `generateSeededExtendedMembershipKey(seed: Uint8Array, isLittleEndian: boolean): Uint8Array`
|
||||
|
||||
Generates a deterministic extended membership key from a seed.
|
||||
|
||||
**Inputs:**
|
||||
|
||||
- `seed`: Seed data as `Uint8Array`
|
||||
- `isLittleEndian`: Boolean indicating endianness for serialization
|
||||
|
||||
**Outputs:** Serialized extended identity tuple as `Uint8Array` in corresponding endianness
|
||||
|
||||
### Hashing Functions
|
||||
|
||||
#### `hash(input: Uint8Array, isLittleEndian: boolean): Uint8Array`
|
||||
|
||||
Hashes input data to a field element.
|
||||
|
||||
**Inputs:**
|
||||
|
||||
- `input`: Input data as `Uint8Array`
|
||||
- `isLittleEndian`: Boolean indicating endianness for serialization
|
||||
|
||||
**Outputs:** Serialized hash result as `Uint8Array` in corresponding endianness
|
||||
|
||||
#### `poseidonHash(input: Uint8Array, isLittleEndian: boolean): Uint8Array`
|
||||
|
||||
Computes Poseidon hash of input field elements.
|
||||
|
||||
**Inputs:**
|
||||
|
||||
- `input`: Serialized field elements as `Uint8Array` (format: length + field elements)
|
||||
- `isLittleEndian`: Boolean indicating endianness for serialization
|
||||
|
||||
**Outputs:** Serialized hash result as `Uint8Array` in corresponding endianness
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### JavaScript/TypeScript
|
||||
|
||||
```javascript
|
||||
import init, {
|
||||
generateMembershipKey,
|
||||
generateSeededMembershipKey,
|
||||
hash,
|
||||
poseidonHash
|
||||
} from '@waku/zerokit-rln-wasm-utils';
|
||||
|
||||
// Initialize the WASM module
|
||||
await init();
|
||||
|
||||
// Generate a random membership key
|
||||
const membershipKey = generateMembershipKey(true); // little-endian
|
||||
console.log('Membership key:', membershipKey);
|
||||
|
||||
// Generate a deterministic membership key from seed
|
||||
const seed = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
const seededKey = generateSeededMembershipKey(seed, true);
|
||||
console.log('Seeded key:', seededKey);
|
||||
|
||||
// Hash some data
|
||||
const input = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
const hashResult = hash(input, true);
|
||||
console.log('Hash result:', hashResult);
|
||||
|
||||
// Poseidon hash with field elements
|
||||
const fieldElements = new Uint8Array([
|
||||
// Length (8 bytes) + field elements (32 bytes each)
|
||||
1, 0, 0, 0, 0, 0, 0, 0, // length = 1
|
||||
// field element data...
|
||||
]);
|
||||
const poseidonResult = poseidonHash(fieldElements, true);
|
||||
console.log('Poseidon hash:', poseidonResult);
|
||||
```
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
> [!NOTE]
|
||||
> This project requires the following tools:
|
||||
>
|
||||
> - `wasm-pack` - for compiling Rust to WebAssembly
|
||||
> - `cargo-make` - for running build commands
|
||||
> - `nvm` - to install and manage Node.js
|
||||
>
|
||||
> Ensure all dependencies are installed before proceeding.
|
||||
|
||||
### Manually
|
||||
|
||||
#### Install `wasm-pack`
|
||||
|
||||
```bash
|
||||
cargo install wasm-pack --version=0.13.1
|
||||
```
|
||||
|
||||
#### Install `cargo-make`
|
||||
|
||||
```bash
|
||||
cargo install cargo-make
|
||||
```
|
||||
|
||||
#### Install `Node.js`
|
||||
|
||||
If you don't have `nvm` (Node Version Manager), install it by following
|
||||
the [installation instructions](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script).
|
||||
|
||||
After installing `nvm`, install and use Node.js `v22.14.0`:
|
||||
|
||||
```bash
|
||||
nvm install 22.14.0
|
||||
nvm use 22.14.0
|
||||
nvm alias default 22.14.0
|
||||
```
|
||||
|
||||
If you already have Node.js installed,
|
||||
check your version with `node -v` command — the version must be strictly greater than 22.
|
||||
|
||||
### Or install everything
|
||||
|
||||
You can run the following command from the root of the repository to install all required dependencies for `zerokit`
|
||||
|
||||
```bash
|
||||
make installdeps
|
||||
```
|
||||
|
||||
## Building the library
|
||||
|
||||
First, navigate to the rln-wasm-utils directory:
|
||||
|
||||
```bash
|
||||
cd rln-wasm-utils
|
||||
```
|
||||
|
||||
Compile rln-wasm-utils for `wasm32-unknown-unknown`:
|
||||
|
||||
```bash
|
||||
cargo make build
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
```bash
|
||||
cargo make test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under both MIT and Apache 2.0 licenses. See the LICENSE files for details.
|
||||
112
rln-wasm-utils/src/lib.rs
Normal file
112
rln-wasm-utils/src/lib.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use js_sys::Uint8Array;
|
||||
use rln::public::{
|
||||
extended_key_gen, hash, key_gen, poseidon_hash, seeded_extended_key_gen, seeded_key_gen,
|
||||
};
|
||||
use std::vec::Vec;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateMembershipKey)]
|
||||
pub fn wasm_key_gen(is_little_endian: bool) -> Result<Uint8Array, String> {
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
if let Err(err) = key_gen(&mut output_data, is_little_endian) {
|
||||
std::mem::forget(output_data);
|
||||
Err(format!(
|
||||
"Msg: could not generate membership keys, Error: {:#?}",
|
||||
err
|
||||
))
|
||||
} else {
|
||||
let result = Uint8Array::from(&output_data[..]);
|
||||
std::mem::forget(output_data);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateExtendedMembershipKey)]
|
||||
pub fn wasm_extended_key_gen(is_little_endian: bool) -> Result<Uint8Array, String> {
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
if let Err(err) = extended_key_gen(&mut output_data, is_little_endian) {
|
||||
std::mem::forget(output_data);
|
||||
Err(format!(
|
||||
"Msg: could not generate membership keys, Error: {:#?}",
|
||||
err
|
||||
))
|
||||
} else {
|
||||
let result = Uint8Array::from(&output_data[..]);
|
||||
std::mem::forget(output_data);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateSeededMembershipKey)]
|
||||
pub fn wasm_seeded_key_gen(seed: Uint8Array, is_little_endian: bool) -> Result<Uint8Array, String> {
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
let input_data = &seed.to_vec()[..];
|
||||
if let Err(err) = seeded_key_gen(input_data, &mut output_data, is_little_endian) {
|
||||
std::mem::forget(output_data);
|
||||
Err(format!(
|
||||
"Msg: could not generate membership key, Error: {:#?}",
|
||||
err
|
||||
))
|
||||
} else {
|
||||
let result = Uint8Array::from(&output_data[..]);
|
||||
std::mem::forget(output_data);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateSeededExtendedMembershipKey)]
|
||||
pub fn wasm_seeded_extended_key_gen(
|
||||
seed: Uint8Array,
|
||||
is_little_endian: bool,
|
||||
) -> Result<Uint8Array, String> {
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
let input_data = &seed.to_vec()[..];
|
||||
if let Err(err) = seeded_extended_key_gen(input_data, &mut output_data, is_little_endian) {
|
||||
std::mem::forget(output_data);
|
||||
Err(format!(
|
||||
"Msg: could not generate membership key, Error: {:#?}",
|
||||
err
|
||||
))
|
||||
} else {
|
||||
let result = Uint8Array::from(&output_data[..]);
|
||||
std::mem::forget(output_data);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = hash)]
|
||||
pub fn wasm_hash(input: Uint8Array, is_little_endian: bool) -> Result<Uint8Array, String> {
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
let input_data = &input.to_vec()[..];
|
||||
if let Err(err) = hash(input_data, &mut output_data, is_little_endian) {
|
||||
std::mem::forget(output_data);
|
||||
Err(format!("Msg: could not generate hash, Error: {:#?}", err))
|
||||
} else {
|
||||
let result = Uint8Array::from(&output_data[..]);
|
||||
std::mem::forget(output_data);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = poseidonHash)]
|
||||
pub fn wasm_poseidon_hash(input: Uint8Array, is_little_endian: bool) -> Result<Uint8Array, String> {
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
let input_data = &input.to_vec()[..];
|
||||
if let Err(err) = poseidon_hash(input_data, &mut output_data, is_little_endian) {
|
||||
std::mem::forget(output_data);
|
||||
Err(format!(
|
||||
"Msg: could not generate poseidon hash, Error: {:#?}",
|
||||
err
|
||||
))
|
||||
} else {
|
||||
let result = Uint8Array::from(&output_data[..]);
|
||||
std::mem::forget(output_data);
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
114
rln-wasm-utils/tests/wasm_utils_test.rs
Normal file
114
rln-wasm-utils/tests/wasm_utils_test.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use ark_std::{UniformRand, rand::thread_rng};
|
||||
use rand::Rng;
|
||||
use rln::circuit::Fr;
|
||||
use rln::hashers::{ROUND_PARAMS, hash_to_field_le, poseidon_hash};
|
||||
use rln::protocol::{
|
||||
deserialize_identity_pair_be, deserialize_identity_pair_le, deserialize_identity_tuple_be,
|
||||
deserialize_identity_tuple_le,
|
||||
};
|
||||
use rln::utils::{bytes_le_to_fr, vec_fr_to_bytes_le};
|
||||
use rln_wasm_utils::{
|
||||
wasm_extended_key_gen, wasm_hash, wasm_key_gen, wasm_poseidon_hash,
|
||||
wasm_seeded_extended_key_gen, wasm_seeded_key_gen,
|
||||
};
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wasm_key_gen() {
|
||||
let result_le = wasm_key_gen(true);
|
||||
assert!(result_le.is_ok());
|
||||
deserialize_identity_pair_le(result_le.unwrap().to_vec());
|
||||
|
||||
let result_be = wasm_key_gen(false);
|
||||
assert!(result_be.is_ok());
|
||||
deserialize_identity_pair_be(result_be.unwrap().to_vec());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wasm_extended_key_gen() {
|
||||
let result_le = wasm_extended_key_gen(true);
|
||||
assert!(result_le.is_ok());
|
||||
deserialize_identity_tuple_le(result_le.unwrap().to_vec());
|
||||
|
||||
let result_be = wasm_extended_key_gen(false);
|
||||
assert!(result_be.is_ok());
|
||||
deserialize_identity_tuple_be(result_be.unwrap().to_vec());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wasm_seeded_key_gen() {
|
||||
// Create a test seed
|
||||
let seed_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let seed = js_sys::Uint8Array::from(&seed_data[..]);
|
||||
|
||||
let result_le = wasm_seeded_key_gen(seed.clone(), true);
|
||||
assert!(result_le.is_ok());
|
||||
let fr_le = deserialize_identity_pair_le(result_le.unwrap().to_vec());
|
||||
|
||||
let result_be = wasm_seeded_key_gen(seed, false);
|
||||
assert!(result_be.is_ok());
|
||||
let fr_be = deserialize_identity_pair_be(result_be.unwrap().to_vec());
|
||||
|
||||
assert_eq!(fr_le, fr_be);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wasm_seeded_extended_key_gen() {
|
||||
// Create a test seed
|
||||
let seed_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let seed = js_sys::Uint8Array::from(&seed_data[..]);
|
||||
|
||||
let result_le = wasm_seeded_extended_key_gen(seed.clone(), true);
|
||||
assert!(result_le.is_ok());
|
||||
let fr_le = deserialize_identity_tuple_le(result_le.unwrap().to_vec());
|
||||
|
||||
let result_be = wasm_seeded_extended_key_gen(seed, false);
|
||||
assert!(result_be.is_ok());
|
||||
let fr_be = deserialize_identity_tuple_be(result_be.unwrap().to_vec());
|
||||
|
||||
assert_eq!(fr_le, fr_be);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wasm_hash() {
|
||||
// Create test input data
|
||||
let signal: [u8; 32] = [0; 32];
|
||||
let input = js_sys::Uint8Array::from(&signal[..]);
|
||||
|
||||
let result_le = wasm_hash(input.clone(), true);
|
||||
assert!(result_le.is_ok());
|
||||
|
||||
let serialized_hash = result_le.unwrap().to_vec();
|
||||
let (hash1, _) = bytes_le_to_fr(&serialized_hash);
|
||||
|
||||
let hash2 = hash_to_field_le(&signal);
|
||||
|
||||
assert_eq!(hash1, hash2);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wasm_poseidon_hash() {
|
||||
let mut rng = thread_rng();
|
||||
let number_of_inputs = rng.gen_range(1..ROUND_PARAMS.len());
|
||||
let mut inputs = Vec::with_capacity(number_of_inputs);
|
||||
for _ in 0..number_of_inputs {
|
||||
inputs.push(Fr::rand(&mut rng));
|
||||
}
|
||||
let inputs_ser = vec_fr_to_bytes_le(&inputs);
|
||||
let input = js_sys::Uint8Array::from(&inputs_ser[..]);
|
||||
|
||||
let expected_hash = poseidon_hash(inputs.as_ref());
|
||||
|
||||
let result_le = wasm_poseidon_hash(input.clone(), true);
|
||||
assert!(result_le.is_ok());
|
||||
|
||||
let serialized_hash = result_le.unwrap().to_vec();
|
||||
let (received_hash, _) = bytes_le_to_fr(&serialized_hash);
|
||||
|
||||
assert_eq!(received_hash, expected_hash);
|
||||
}
|
||||
}
|
||||
21
rln-wasm/.gitignore
vendored
21
rln-wasm/.gitignore
vendored
@@ -1,6 +1,21 @@
|
||||
# Common files to ignore in Rust projects
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
tmp/
|
||||
|
||||
# Generated by Cargo will have compiled files and executables
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
|
||||
# Generated by rln-wasm
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
|
||||
# Generated by Nix
|
||||
result
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
@@ -1,33 +1,47 @@
|
||||
[package]
|
||||
name = "rln-wasm"
|
||||
version = "0.0.7"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT or Apache2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[dependencies]
|
||||
rln = { path = "../rln", default-features = false, features = ["wasm"] }
|
||||
num-bigint = { version = "0.4", 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"
|
||||
rln = { path = "../rln", version = "0.8.0", default-features = false, features = [
|
||||
"stateless",
|
||||
] }
|
||||
rln-wasm-utils = { path = "../rln-wasm-utils", version = "0.1.0", default-features = false }
|
||||
zerokit_utils = { path = "../utils", version = "0.6.0", default-features = false }
|
||||
num-bigint = { version = "0.4.6", default-features = false }
|
||||
js-sys = "0.3.77"
|
||||
wasm-bindgen = "0.2.100"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
wasm-bindgen-rayon = { version = "1.3.0", features = [
|
||||
"no-bundler",
|
||||
], optional = true }
|
||||
|
||||
# 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 }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.13"
|
||||
wasm-bindgen-futures = "0.4.33"
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.16", features = ["js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.141"
|
||||
wasm-bindgen-test = "0.3.50"
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
|
||||
[dev-dependencies.web-sys]
|
||||
version = "0.3.77"
|
||||
features = ["Window", "Navigator"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
parallel = ["rln/parallel", "wasm-bindgen-rayon"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -1,31 +1,99 @@
|
||||
[tasks.pack-build]
|
||||
[tasks.build]
|
||||
clear = true
|
||||
dependencies = ["pack_build", "pack_rename", "pack_resize"]
|
||||
|
||||
[tasks.build_parallel]
|
||||
clear = true
|
||||
dependencies = ["pack_build_parallel", "pack_rename", "pack_resize"]
|
||||
|
||||
[tasks.pack_build]
|
||||
command = "wasm-pack"
|
||||
args = ["build", "--release", "--target", "web", "--scope", "waku"]
|
||||
|
||||
[tasks.pack-rename]
|
||||
[tasks.pack_build_parallel]
|
||||
command = "env"
|
||||
args = [
|
||||
"RUSTFLAGS=-C target-feature=+atomics,+bulk-memory,+mutable-globals",
|
||||
"rustup",
|
||||
"run",
|
||||
"nightly",
|
||||
"wasm-pack",
|
||||
"build",
|
||||
"--release",
|
||||
"--target",
|
||||
"web",
|
||||
"--scope",
|
||||
"waku",
|
||||
"--features",
|
||||
"parallel",
|
||||
"-Z",
|
||||
"build-std=panic_abort,std",
|
||||
]
|
||||
[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"
|
||||
[tasks.pack_resize]
|
||||
command = "wasm-opt"
|
||||
args = [
|
||||
"pkg/rln_wasm_bg.wasm",
|
||||
"-Oz",
|
||||
"--strip-debug",
|
||||
"--strip-dwarf",
|
||||
"--remove-unused-module-elements",
|
||||
"--vacuum",
|
||||
"-o",
|
||||
"pkg/rln_wasm_bg.wasm",
|
||||
]
|
||||
|
||||
[tasks.post-build]
|
||||
command = "wasm-strip"
|
||||
args = ["./pkg/rln_wasm_bg.wasm"]
|
||||
|
||||
[tasks.test]
|
||||
command = "wasm-pack"
|
||||
args = ["test", "--release", "--node"]
|
||||
args = [
|
||||
"test",
|
||||
"--release",
|
||||
"--node",
|
||||
"--target",
|
||||
"wasm32-unknown-unknown",
|
||||
"--",
|
||||
"--nocapture",
|
||||
]
|
||||
dependencies = ["build"]
|
||||
|
||||
[tasks.login]
|
||||
[tasks.test_browser]
|
||||
command = "wasm-pack"
|
||||
args = ["login"]
|
||||
args = [
|
||||
"test",
|
||||
"--release",
|
||||
"--chrome",
|
||||
"--headless",
|
||||
"--target",
|
||||
"wasm32-unknown-unknown",
|
||||
"--",
|
||||
"--nocapture",
|
||||
]
|
||||
dependencies = ["build"]
|
||||
|
||||
[tasks.publish]
|
||||
command = "wasm-pack"
|
||||
args = ["publish", "--access", "public", "--target", "web"]
|
||||
[tasks.test_parallel]
|
||||
command = "env"
|
||||
args = [
|
||||
"RUSTFLAGS=-C target-feature=+atomics,+bulk-memory,+mutable-globals",
|
||||
"rustup",
|
||||
"run",
|
||||
"nightly",
|
||||
"wasm-pack",
|
||||
"test",
|
||||
"--release",
|
||||
"--chrome",
|
||||
"--headless",
|
||||
"--target",
|
||||
"wasm32-unknown-unknown",
|
||||
"--features",
|
||||
"parallel",
|
||||
"-Z",
|
||||
"build-std=panic_abort,std",
|
||||
"--",
|
||||
"--nocapture",
|
||||
]
|
||||
dependencies = ["build_parallel"]
|
||||
|
||||
[tasks.bench]
|
||||
disabled = true
|
||||
|
||||
@@ -1,41 +1,146 @@
|
||||
# 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`
|
||||
```
|
||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
```
|
||||
2. Install `cargo-make`
|
||||
[](https://badge.fury.io/js/@waku%2Fzerokit-rln-wasm)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
The Zerokit RLN WASM Module provides WebAssembly bindings for working with
|
||||
Rate-Limiting Nullifier [RLN](https://rfc.vac.dev/spec/32/) zkSNARK proofs and primitives.
|
||||
This module is used by [waku-org/js-rln](https://github.com/waku-org/js-rln/) to enable
|
||||
RLN functionality in JavaScript/TypeScript applications.
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
> [!NOTE]
|
||||
> This project requires the following tools:
|
||||
>
|
||||
> - `wasm-pack` - for compiling Rust to WebAssembly
|
||||
> - `cargo-make` - for running build commands
|
||||
> - `nvm` - to install and manage Node.js
|
||||
>
|
||||
> Ensure all dependencies are installed before proceeding.
|
||||
|
||||
### Manually
|
||||
|
||||
#### Install `wasm-pack`
|
||||
|
||||
```bash
|
||||
cargo install wasm-pack --version=0.13.1
|
||||
```
|
||||
|
||||
#### Install `cargo-make`
|
||||
|
||||
```bash
|
||||
cargo install cargo-make
|
||||
```
|
||||
|
||||
OR
|
||||
#### Install `Node.js`
|
||||
|
||||
If you don't have `nvm` (Node Version Manager), install it by following
|
||||
the [installation instructions](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script).
|
||||
|
||||
After installing `nvm`, install and use Node.js `v22.14.0`:
|
||||
|
||||
```bash
|
||||
nvm install 22.14.0
|
||||
nvm use 22.14.0
|
||||
nvm alias default 22.14.0
|
||||
```
|
||||
|
||||
If you already have Node.js installed,
|
||||
check your version with `node -v` command — the version must be strictly greater than 22.
|
||||
|
||||
### Or install everything
|
||||
|
||||
You can run the following command from the root of the repository to install all required dependencies for `zerokit`
|
||||
|
||||
```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
|
||||
|
||||
## Running tests and benchmarks
|
||||
|
||||
```bash
|
||||
cargo make test
|
||||
```
|
||||
|
||||
## Publishing a npm package
|
||||
If you want to run the tests in browser headless mode, you can use the following command:
|
||||
|
||||
```bash
|
||||
cargo make test_browser
|
||||
```
|
||||
|
||||
## Parallel computation
|
||||
|
||||
The library supports parallel computation using the `wasm-bindgen-rayon` crate,
|
||||
enabling multi-threaded execution in the browser.
|
||||
|
||||
> [!NOTE]
|
||||
> Parallel support is not enabled by default due to WebAssembly and browser limitations. \
|
||||
> Compiling this feature requires `nightly` Rust.
|
||||
|
||||
### Build Setup
|
||||
|
||||
#### Install `nightly` Rust
|
||||
|
||||
```bash
|
||||
rustup install nightly
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
|
||||
To enable parallel computation for WebAssembly threads, you can use the following command:
|
||||
|
||||
```bash
|
||||
cargo make build_parallel
|
||||
```
|
||||
|
||||
### WebAssembly Threading Support
|
||||
|
||||
Most modern browsers support WebAssembly threads,
|
||||
but they require the following headers to enable `SharedArrayBuffer`, which is necessary for multithreading:
|
||||
|
||||
- Cross-Origin-Opener-Policy: same-origin
|
||||
- Cross-Origin-Embedder-Policy: require-corp
|
||||
|
||||
Without these, the application will fall back to single-threaded mode.
|
||||
|
||||
## Feature detection
|
||||
|
||||
If you're targeting [older browser versions that didn't support WebAssembly threads yet](https://webassembly.org/roadmap/),
|
||||
you'll likely want to create two builds - one with thread support and one without -
|
||||
and use feature detection to choose the right one on the JavaScript side.
|
||||
|
||||
You can use [wasm-feature-detect](https://github.com/GoogleChromeLabs/wasm-feature-detect) library for this purpose.
|
||||
For example, your code might look like this:
|
||||
|
||||
```js
|
||||
import { threads } from 'wasm-feature-detect';
|
||||
|
||||
let wasmPkg;
|
||||
|
||||
if (await threads()) {
|
||||
wasmPkg = await import('./pkg-with-threads/index.js');
|
||||
await wasmPkg.default();
|
||||
await wasmPkg.initThreadPool(navigator.hardwareConcurrency);
|
||||
} else {
|
||||
wasmPkg = await import('./pkg-without-threads/index.js');
|
||||
await wasmPkg.default();
|
||||
}
|
||||
|
||||
wasmPkg.nowCallAnyExportedFuncs();
|
||||
```
|
||||
cd rln-wasm
|
||||
cargo make login
|
||||
cargo make publish
|
||||
```
|
||||
@@ -1,331 +1,328 @@
|
||||
module.exports = async function builder(code, options) {
|
||||
// File generated with https://github.com/iden3/circom
|
||||
// following the instructions from:
|
||||
// https://github.com/vacp2p/zerokit/tree/master/rln#advanced-custom-circuit-compilation
|
||||
|
||||
options = options || {};
|
||||
export async function builder(code, 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;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#![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::RLN;
|
||||
use std::vec::Vec;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[cfg(feature = "parallel")]
|
||||
pub use wasm_bindgen_rayon::init_thread_pool;
|
||||
|
||||
#[wasm_bindgen(js_name = initPanicHook)]
|
||||
pub fn init_panic_hook() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
@@ -19,7 +18,7 @@ pub fn init_panic_hook() {
|
||||
pub struct RLNWrapper {
|
||||
// The purpose of this wrapper is to hold a RLN instance with the 'static lifetime
|
||||
// because wasm_bindgen does not allow returning elements with lifetimes
|
||||
instance: RLN<'static>,
|
||||
instance: RLN,
|
||||
}
|
||||
|
||||
// Macro to call methods with arbitrary amount of arguments,
|
||||
@@ -61,24 +60,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)*) => {
|
||||
{
|
||||
@@ -116,8 +97,8 @@ impl<T> ProcessArg for Vec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ProcessArg for *const RLN<'a> {
|
||||
type ReturnType = &'a RLN<'a>;
|
||||
impl ProcessArg for *const RLN {
|
||||
type ReturnType = &'static RLN;
|
||||
fn process(self) -> Self::ReturnType {
|
||||
unsafe { &*self }
|
||||
}
|
||||
@@ -147,81 +128,31 @@ impl<'a> ProcessArg for &'a [u8] {
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = newRLN)]
|
||||
pub fn wasm_new(
|
||||
tree_height: usize,
|
||||
zkey: Uint8Array,
|
||||
vk: Uint8Array,
|
||||
) -> Result<*mut RLNWrapper, String> {
|
||||
let instance = RLN::new_with_params(tree_height, 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 = getSerializedRLNWitness)]
|
||||
pub fn wasm_get_serialized_rln_witness(
|
||||
ctx: *mut RLNWrapper,
|
||||
input: Uint8Array,
|
||||
) -> Result<Uint8Array, String> {
|
||||
let rln_witness = call!(ctx, get_serialized_rln_witness, &input.to_vec()[..])
|
||||
.map_err(|err| format!("{:#?}", err))?;
|
||||
Ok(Uint8Array::from(&rln_witness[..]))
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = insertMember)]
|
||||
pub fn wasm_set_next_leaf(ctx: *mut RLNWrapper, input: Uint8Array) -> Result<(), String> {
|
||||
call_with_error_msg!(
|
||||
ctx,
|
||||
set_next_leaf,
|
||||
"could not insert member into merkle tree".to_string(),
|
||||
&input.to_vec()[..]
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = setLeavesFrom)]
|
||||
pub fn wasm_set_leaves_from(
|
||||
ctx: *mut RLNWrapper,
|
||||
index: usize,
|
||||
input: Uint8Array,
|
||||
) -> Result<(), String> {
|
||||
call_with_error_msg!(
|
||||
ctx,
|
||||
set_leaves_from,
|
||||
"could not set multiple leaves".to_string(),
|
||||
index,
|
||||
&*input.to_vec()
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = initTreeWithLeaves)]
|
||||
pub fn wasm_init_tree_with_leaves(ctx: *mut RLNWrapper, input: Uint8Array) -> Result<(), String> {
|
||||
call_with_error_msg!(
|
||||
ctx,
|
||||
init_tree_with_leaves,
|
||||
"could not init merkle tree".to_string(),
|
||||
&*input.to_vec()
|
||||
)
|
||||
}
|
||||
|
||||
#[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> {
|
||||
let inputs = call!(ctx, get_rln_witness_json, &serialized_witness.to_vec()[..])
|
||||
.map_err(|err| err.to_string())?;
|
||||
let inputs = call!(
|
||||
ctx,
|
||||
get_rln_witness_bigint_json,
|
||||
&serialized_witness.to_vec()[..]
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
let js_value = serde_wasm_bindgen::to_value(&inputs).map_err(|err| err.to_string())?;
|
||||
Object::from_entries(&js_value).map_err(|err| format!("{:#?}", err))
|
||||
}
|
||||
|
||||
#[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,
|
||||
@@ -248,43 +179,6 @@ pub fn generate_rln_proof_with_witness(
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateMembershipKey)]
|
||||
pub fn wasm_key_gen(ctx: *const RLNWrapper) -> Result<Uint8Array, String> {
|
||||
call_with_output_and_error_msg!(ctx, key_gen, "could not generate membership keys")
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateExtendedMembershipKey)]
|
||||
pub fn wasm_extended_key_gen(ctx: *const RLNWrapper) -> Result<Uint8Array, String> {
|
||||
call_with_output_and_error_msg!(ctx, extended_key_gen, "could not generate membership keys")
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateSeededMembershipKey)]
|
||||
pub fn wasm_seeded_key_gen(ctx: *const RLNWrapper, seed: Uint8Array) -> Result<Uint8Array, String> {
|
||||
call_with_output_and_error_msg!(
|
||||
ctx,
|
||||
seeded_key_gen,
|
||||
"could not generate membership key",
|
||||
&seed.to_vec()[..]
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = generateSeededExtendedMembershipKey)]
|
||||
pub fn wasm_seeded_extended_key_gen(
|
||||
ctx: *const RLNWrapper,
|
||||
seed: Uint8Array,
|
||||
) -> Result<Uint8Array, String> {
|
||||
call_with_output_and_error_msg!(
|
||||
ctx,
|
||||
seeded_extended_key_gen,
|
||||
"could not generate membership key",
|
||||
&seed.to_vec()[..]
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = recovedIDSecret)]
|
||||
pub fn wasm_recover_id_secret(
|
||||
@@ -301,17 +195,6 @@ pub fn wasm_recover_id_secret(
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = verifyRLNProof)]
|
||||
pub fn wasm_verify_rln_proof(ctx: *const RLNWrapper, proof: Uint8Array) -> Result<bool, String> {
|
||||
call_bool_method_with_error_msg!(
|
||||
ctx,
|
||||
verify_rln_proof,
|
||||
"error while verifying rln proof".to_string(),
|
||||
&proof.to_vec()[..]
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = verifyWithRoots)]
|
||||
pub fn wasm_verify_with_roots(
|
||||
@@ -327,9 +210,3 @@ pub fn wasm_verify_with_roots(
|
||||
&roots.to_vec()[..]
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[wasm_bindgen(js_name = getRoot)]
|
||||
pub fn wasm_get_root(ctx: *const RLNWrapper) -> Result<Uint8Array, String> {
|
||||
call_with_output_and_error_msg!(ctx, get_root, "could not obtain root")
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
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);
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
266
rln-wasm/tests/browser.rs
Normal file
266
rln-wasm/tests/browser.rs
Normal file
@@ -0,0 +1,266 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use js_sys::{BigInt as JsBigInt, Date, Object, Uint8Array};
|
||||
use rln::circuit::{Fr, TEST_TREE_DEPTH};
|
||||
use rln::hashers::{hash_to_field_le, poseidon_hash, PoseidonHash};
|
||||
use rln::protocol::{prepare_verify_input, rln_witness_from_values, serialize_witness};
|
||||
use rln::utils::{bytes_le_to_fr, fr_to_bytes_le, IdSecret};
|
||||
use rln_wasm::{
|
||||
wasm_generate_rln_proof_with_witness, wasm_new, wasm_rln_witness_to_json,
|
||||
wasm_verify_with_roots,
|
||||
};
|
||||
use rln_wasm_utils::wasm_key_gen;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
use wasm_bindgen_test::{console_log, wasm_bindgen_test, wasm_bindgen_test_configure};
|
||||
use zerokit_utils::{
|
||||
OptimalMerkleProof, OptimalMerkleTree, ZerokitMerkleProof, ZerokitMerkleTree,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
use {rln_wasm::init_thread_pool, wasm_bindgen_futures::JsFuture, web_sys::window};
|
||||
|
||||
#[wasm_bindgen(inline_js = r#"
|
||||
export function isThreadpoolSupported() {
|
||||
return typeof SharedArrayBuffer !== 'undefined' &&
|
||||
typeof Atomics !== 'undefined' &&
|
||||
typeof crossOriginIsolated !== 'undefined' &&
|
||||
crossOriginIsolated;
|
||||
}
|
||||
|
||||
export function initWitnessCalculator(jsCode) {
|
||||
const processedCode = jsCode
|
||||
.replace(/export\s+async\s+function\s+builder/, 'async function builder')
|
||||
.replace(/export\s*\{\s*builder\s*\};?/g, '');
|
||||
|
||||
const moduleFunc = new Function(processedCode + '\nreturn { builder };');
|
||||
const witnessCalculatorModule = moduleFunc();
|
||||
|
||||
window.witnessCalculatorBuilder = witnessCalculatorModule.builder;
|
||||
|
||||
if (typeof window.witnessCalculatorBuilder !== 'function') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function readFile(data) {
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
export async function calculateWitness(circom_data, inputs) {
|
||||
const wasmBuffer = circom_data instanceof Uint8Array ? circom_data : new Uint8Array(circom_data);
|
||||
const witnessCalculator = await window.witnessCalculatorBuilder(wasmBuffer);
|
||||
const calculatedWitness = await witnessCalculator.calculateWitness(inputs, false);
|
||||
return JSON.stringify(calculatedWitness, (key, value) =>
|
||||
typeof value === "bigint" ? value.toString() : value
|
||||
);
|
||||
}
|
||||
"#)]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch)]
|
||||
fn isThreadpoolSupported() -> Result<bool, JsValue>;
|
||||
|
||||
#[wasm_bindgen(catch)]
|
||||
fn initWitnessCalculator(js: &str) -> Result<bool, JsValue>;
|
||||
|
||||
#[wasm_bindgen(catch)]
|
||||
fn readFile(data: &[u8]) -> Result<Uint8Array, JsValue>;
|
||||
|
||||
#[wasm_bindgen(catch)]
|
||||
async fn calculateWitness(circom_data: &[u8], inputs: Object) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
const WITNESS_CALCULATOR_JS: &str = include_str!("../resources/witness_calculator.js");
|
||||
|
||||
const ARKZKEY_BYTES: &[u8] =
|
||||
include_bytes!("../../rln/resources/tree_depth_10/rln_final.arkzkey");
|
||||
|
||||
const CIRCOM_BYTES: &[u8] = include_bytes!("../../rln/resources/tree_depth_10/rln.wasm");
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
pub async fn rln_wasm_benchmark() {
|
||||
// Check if thread pool is supported
|
||||
#[cfg(feature = "parallel")]
|
||||
if !isThreadpoolSupported().expect("Failed to check thread pool support") {
|
||||
panic!("Thread pool is NOT supported");
|
||||
} else {
|
||||
// Initialize thread pool
|
||||
let cpu_count = window()
|
||||
.expect("Failed to get window")
|
||||
.navigator()
|
||||
.hardware_concurrency() as usize;
|
||||
JsFuture::from(init_thread_pool(cpu_count))
|
||||
.await
|
||||
.expect("Failed to initialize thread pool");
|
||||
}
|
||||
|
||||
// Initialize witness calculator
|
||||
initWitnessCalculator(WITNESS_CALCULATOR_JS)
|
||||
.expect("Failed to initialize witness calculator");
|
||||
|
||||
let mut results = String::from("\nbenchmarks:\n");
|
||||
let iterations = 10;
|
||||
|
||||
let zkey = readFile(&ARKZKEY_BYTES).expect("Failed to read zkey file");
|
||||
|
||||
// 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;
|
||||
|
||||
// Create RLN instance for other benchmarks
|
||||
let rln_instance = wasm_new(zkey).expect("Failed to create RLN instance");
|
||||
let mut tree: OptimalMerkleTree<PoseidonHash> =
|
||||
OptimalMerkleTree::default(TEST_TREE_DEPTH).expect("Failed to create tree");
|
||||
|
||||
// Benchmark wasm_key_gen
|
||||
let start_wasm_key_gen = Date::now();
|
||||
for _ in 0..iterations {
|
||||
let _ = wasm_key_gen(true).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(true).expect("Failed to generate keys");
|
||||
let id_key = mem_keys.subarray(0, 32);
|
||||
let (identity_secret_hash, _) = IdSecret::from_bytes_le(&id_key.to_vec());
|
||||
let (id_commitment, _) = bytes_le_to_fr(&mem_keys.subarray(32, 64).to_vec());
|
||||
|
||||
let epoch = hash_to_field_le(b"test-epoch");
|
||||
let rln_identifier = hash_to_field_le(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_le(&signal);
|
||||
|
||||
let merkle_proof: OptimalMerkleProof<PoseidonHash> = tree
|
||||
.proof(identity_index)
|
||||
.expect("Failed to generate merkle proof");
|
||||
|
||||
let rln_witness = rln_witness_from_values(
|
||||
identity_secret_hash,
|
||||
merkle_proof.get_path_elements(),
|
||||
merkle_proof.get_path_index(),
|
||||
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_BYTES, 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_BYTES, 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
|
||||
console_log!("{results}");
|
||||
}
|
||||
}
|
||||
247
rln-wasm/tests/node.rs
Normal file
247
rln-wasm/tests/node.rs
Normal file
@@ -0,0 +1,247 @@
|
||||
#![cfg(not(feature = "parallel"))]
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use js_sys::{BigInt as JsBigInt, Date, Object, Uint8Array};
|
||||
use rln::circuit::{Fr, TEST_TREE_DEPTH};
|
||||
use rln::hashers::{hash_to_field_le, poseidon_hash, PoseidonHash};
|
||||
use rln::protocol::{prepare_verify_input, rln_witness_from_values, serialize_witness};
|
||||
use rln::utils::{bytes_le_to_fr, fr_to_bytes_le, IdSecret};
|
||||
use rln_wasm::{
|
||||
wasm_generate_rln_proof_with_witness, wasm_new, wasm_rln_witness_to_json,
|
||||
wasm_verify_with_roots,
|
||||
};
|
||||
use rln_wasm_utils::wasm_key_gen;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
use wasm_bindgen_test::{console_log, wasm_bindgen_test};
|
||||
use zerokit_utils::{
|
||||
OptimalMerkleProof, OptimalMerkleTree, ZerokitMerkleProof, ZerokitMerkleTree,
|
||||
};
|
||||
|
||||
const WITNESS_CALCULATOR_JS: &str = include_str!("../resources/witness_calculator.js");
|
||||
|
||||
#[wasm_bindgen(inline_js = r#"
|
||||
const fs = require("fs");
|
||||
|
||||
let witnessCalculatorModule = null;
|
||||
|
||||
module.exports = {
|
||||
initWitnessCalculator: function(code) {
|
||||
const processedCode = code
|
||||
.replace(/export\s+async\s+function\s+builder/, 'async function builder')
|
||||
.replace(/export\s*\{\s*builder\s*\};?/g, '');
|
||||
|
||||
const moduleFunc = new Function(processedCode + '\nreturn { builder };');
|
||||
witnessCalculatorModule = moduleFunc();
|
||||
|
||||
if (typeof witnessCalculatorModule.builder !== 'function') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
readFile: function (path) {
|
||||
return fs.readFileSync(path);
|
||||
},
|
||||
|
||||
calculateWitness: async function (circom_path, inputs) {
|
||||
const wasmFile = fs.readFileSync(circom_path);
|
||||
const wasmFileBuffer = wasmFile.slice(
|
||||
wasmFile.byteOffset,
|
||||
wasmFile.byteOffset + wasmFile.byteLength
|
||||
);
|
||||
const witnessCalculator = await witnessCalculatorModule.builder(wasmFileBuffer);
|
||||
const calculatedWitness = await witnessCalculator.calculateWitness(
|
||||
inputs,
|
||||
false
|
||||
);
|
||||
return JSON.stringify(calculatedWitness, (key, value) =>
|
||||
typeof value === "bigint" ? value.toString() : value
|
||||
);
|
||||
},
|
||||
};
|
||||
"#)]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch)]
|
||||
fn initWitnessCalculator(code: &str) -> Result<bool, JsValue>;
|
||||
|
||||
#[wasm_bindgen(catch)]
|
||||
fn readFile(path: &str) -> Result<Uint8Array, JsValue>;
|
||||
|
||||
#[wasm_bindgen(catch)]
|
||||
async fn calculateWitness(circom_path: &str, input: Object) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
const ARKZKEY_PATH: &str = "../rln/resources/tree_depth_10/rln_final.arkzkey";
|
||||
|
||||
const CIRCOM_PATH: &str = "../rln/resources/tree_depth_10/rln.wasm";
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
pub async fn rln_wasm_benchmark() {
|
||||
// Initialize witness calculator
|
||||
initWitnessCalculator(WITNESS_CALCULATOR_JS)
|
||||
.expect("Failed to initialize witness calculator");
|
||||
|
||||
let mut results = String::from("\nbenchmarks:\n");
|
||||
let iterations = 10;
|
||||
|
||||
let zkey = readFile(&ARKZKEY_PATH).expect("Failed to read zkey file");
|
||||
|
||||
// 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;
|
||||
|
||||
// Create RLN instance for other benchmarks
|
||||
let rln_instance = wasm_new(zkey).expect("Failed to create RLN instance");
|
||||
let mut tree: OptimalMerkleTree<PoseidonHash> =
|
||||
OptimalMerkleTree::default(TEST_TREE_DEPTH).expect("Failed to create tree");
|
||||
|
||||
// Benchmark wasm_key_gen
|
||||
let start_wasm_key_gen = Date::now();
|
||||
for _ in 0..iterations {
|
||||
let _ = wasm_key_gen(true).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(true).expect("Failed to generate keys");
|
||||
let id_key = mem_keys.subarray(0, 32);
|
||||
let (identity_secret_hash, _) = IdSecret::from_bytes_le(&id_key.to_vec());
|
||||
let (id_commitment, _) = bytes_le_to_fr(&mem_keys.subarray(32, 64).to_vec());
|
||||
|
||||
let epoch = hash_to_field_le(b"test-epoch");
|
||||
let rln_identifier = hash_to_field_le(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_le(&signal);
|
||||
|
||||
let merkle_proof: OptimalMerkleProof<PoseidonHash> = tree
|
||||
.proof(identity_index)
|
||||
.expect("Failed to generate merkle proof");
|
||||
|
||||
let rln_witness = rln_witness_from_values(
|
||||
identity_secret_hash,
|
||||
merkle_proof.get_path_elements(),
|
||||
merkle_proof.get_path_index(),
|
||||
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!(
|
||||
"calculate_witness: {}\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
|
||||
console_log!("{results}");
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use js_sys::{BigInt as JsBigInt, Object, Uint8Array};
|
||||
use rln::circuit::TEST_TREE_HEIGHT;
|
||||
use rln::utils::normalize_usize;
|
||||
use rln_wasm::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
#[wasm_bindgen(module = "src/utils.js")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch)]
|
||||
fn read_file(path: &str) -> Result<Uint8Array, JsValue>;
|
||||
|
||||
#[wasm_bindgen(catch)]
|
||||
async fn calculateWitness(circom_path: &str, input: Object) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
#[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.json");
|
||||
let zkey = read_file(&zkey_path).unwrap();
|
||||
let vk = read_file(&vk_path).unwrap();
|
||||
|
||||
// Creating an instance of RLN
|
||||
let rln_instance = wasm_new(tree_height, zkey, vk);
|
||||
|
||||
// Creating membership key
|
||||
let mem_keys = wasm_key_gen(rln_instance).unwrap();
|
||||
let idkey = mem_keys.subarray(0, 32);
|
||||
let idcommitment = mem_keys.subarray(32, 64);
|
||||
|
||||
// Insert PK
|
||||
wasm_set_next_leaf(rln_instance, idcommitment).unwrap();
|
||||
|
||||
// Prepare the message
|
||||
let signal = "Hello World".as_bytes();
|
||||
|
||||
// Setting up the epoch (With 0s for the test)
|
||||
let epoch = Uint8Array::new_with_length(32);
|
||||
epoch.fill(0, 0, 32);
|
||||
|
||||
let identity_index: usize = 0;
|
||||
|
||||
// Serializing the message
|
||||
let mut serialized_vec: Vec<u8> = Vec::new();
|
||||
serialized_vec.append(&mut idkey.to_vec());
|
||||
serialized_vec.append(&mut normalize_usize(identity_index));
|
||||
serialized_vec.append(&mut epoch.to_vec());
|
||||
serialized_vec.append(&mut normalize_usize(signal.len()));
|
||||
serialized_vec.append(&mut signal.to_vec());
|
||||
let serialized_message = Uint8Array::from(&serialized_vec[..]);
|
||||
|
||||
let serialized_rln_witness =
|
||||
wasm_get_serialized_rln_witness(rln_instance, serialized_message);
|
||||
|
||||
// Obtaining inputs that should be sent to circom witness calculator
|
||||
let json_inputs = rln_witness_to_json(rln_instance, serialized_rln_witness.clone());
|
||||
|
||||
// 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)
|
||||
.await
|
||||
.unwrap()
|
||||
.as_string()
|
||||
.unwrap();
|
||||
let calculated_witness_vec_str: Vec<String> =
|
||||
serde_json::from_str(&calculated_witness_json).unwrap();
|
||||
let calculated_witness: Vec<JsBigInt> = calculated_witness_vec_str
|
||||
.iter()
|
||||
.map(|x| JsBigInt::new(&x.into()).unwrap())
|
||||
.collect();
|
||||
|
||||
// Generating proof
|
||||
let proof = generate_rln_proof_with_witness(
|
||||
rln_instance,
|
||||
calculated_witness.into(),
|
||||
serialized_rln_witness,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// 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[..]);
|
||||
|
||||
// Validate Proof
|
||||
let is_proof_valid = wasm_verify_rln_proof(rln_instance, proof_with_signal);
|
||||
|
||||
assert!(
|
||||
is_proof_valid.unwrap(),
|
||||
"validating proof generated with wasm failed"
|
||||
);
|
||||
|
||||
// Validating Proof with Roots
|
||||
let root = wasm_get_root(rln_instance).unwrap();
|
||||
let roots = Uint8Array::from(&root.to_vec()[..]);
|
||||
let proof_with_signal = Uint8Array::from(&proof_bytes[..]);
|
||||
|
||||
let is_proof_valid = wasm_verify_with_roots(rln_instance, proof_with_signal, roots);
|
||||
assert!(is_proof_valid.unwrap(), "verifying proof with roots failed");
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,85 @@
|
||||
[package]
|
||||
name = "rln"
|
||||
version = "0.1.0"
|
||||
version = "0.8.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "APIs to manage, compute and verify zkSNARK proofs and RLN primitives"
|
||||
documentation = "https://github.com/vacp2p/zerokit"
|
||||
homepage = "https://vac.dev"
|
||||
repository = "https://github.com/vacp2p/zerokit"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
crate-type = ["rlib", "staticlib", "cdylib"]
|
||||
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 = ["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 = { git = "https://github.com/gakonst/ark-circom", default-features = false, features = ["circom-2"] }
|
||||
|
||||
# WASM
|
||||
wasmer = { version = "2.3.0", default-features = false }
|
||||
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 }
|
||||
ark-ec = { version = "0.5.0", default-features = false }
|
||||
ark-std = { version = "0.5.0", default-features = false }
|
||||
ark-poly = { version = "0.5.0", default-features = false }
|
||||
ark-groth16 = { version = "0.5.0", default-features = false }
|
||||
ark-serialize = { version = "0.5.0", default-features = false }
|
||||
|
||||
# error handling
|
||||
color-eyre = "=0.6.2"
|
||||
thiserror = "=1.0.38"
|
||||
thiserror = "2.0.12"
|
||||
|
||||
# utilities
|
||||
cfg-if = "=1.0"
|
||||
num-bigint = { version = "=0.4.3", default-features = false, features = ["rand"] }
|
||||
num-traits = "0.2.11"
|
||||
once_cell = "1.14.0"
|
||||
rand = "=0.8.5"
|
||||
rand_chacha = "=0.3.1"
|
||||
tiny-keccak = { version = "=2.0.2", features = ["keccak"] }
|
||||
utils = { path = "../utils/", default-features = false }
|
||||
pmtree = { git = "https://github.com/Rate-Limiting-Nullifier/pmtree", rev = "f6d1a1fecad72cd39e6808e78085091d541dc882", optional = true}
|
||||
rayon = { version = "1.10.0", optional = true }
|
||||
byteorder = "1.5.0"
|
||||
cfg-if = "1.0"
|
||||
num-bigint = { version = "0.4.6", default-features = false, features = ["std"] }
|
||||
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.15.0", features = ["rand", "serde", "ark-ff-04"] }
|
||||
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
|
||||
zeroize = "1.8"
|
||||
tempfile = "3.21.0"
|
||||
utils = { package = "zerokit_utils", version = "0.6.0", path = "../utils", default-features = false }
|
||||
|
||||
# serialization
|
||||
serde_json = "1.0.48"
|
||||
prost = "0.14.1"
|
||||
serde_json = "1.0.141"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
include_dir = "=0.7.3"
|
||||
document-features = { version = "0.2.11", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sled = "=0.34.7"
|
||||
criterion = { version = "0.7.0", features = ["html_reports"] }
|
||||
|
||||
[features]
|
||||
default = ["parallel", "wasmer/sys-default"]
|
||||
parallel = ["ark-ec/parallel", "ark-ff/parallel", "ark-std/parallel", "ark-groth16/parallel", "utils/parallel"]
|
||||
wasm = ["wasmer/js", "wasmer/std"]
|
||||
fullmerkletree = ["default"]
|
||||
default = ["parallel", "pmtree-ft"]
|
||||
stateless = []
|
||||
parallel = [
|
||||
"rayon",
|
||||
"utils/parallel",
|
||||
"ark-ff/parallel",
|
||||
"ark-ec/parallel",
|
||||
"ark-std/parallel",
|
||||
"ark-poly/parallel",
|
||||
"ark-groth16/parallel",
|
||||
"ark-serialize/parallel",
|
||||
]
|
||||
fullmerkletree = [] # Pre-allocated tree, fastest access
|
||||
optimalmerkletree = [] # Sparse storage, memory efficient
|
||||
pmtree-ft = ["utils/pmtree-ft"] # Persistent storage, disk-based
|
||||
|
||||
# Note: pmtree feature is still experimental
|
||||
pmtree-ft = ["default", "pmtree"]
|
||||
[[bench]]
|
||||
name = "pmtree_benchmark"
|
||||
harness = false
|
||||
required-features = ["pmtree-ft"]
|
||||
|
||||
[[bench]]
|
||||
name = "poseidon_tree_benchmark"
|
||||
harness = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -4,4 +4,12 @@ args = ["build", "--release"]
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
args = ["test", "--release"]
|
||||
args = ["test", "--release", "--", "--nocapture"]
|
||||
|
||||
[tasks.test_stateless]
|
||||
command = "cargo"
|
||||
args = ["test", "--release", "--no-default-features", "--features", "stateless"]
|
||||
|
||||
[tasks.bench]
|
||||
command = "cargo"
|
||||
args = ["bench"]
|
||||
|
||||
396
rln/README.md
396
rln/README.md
@@ -1,64 +1,22 @@
|
||||
# Zerokit RLN Module
|
||||
|
||||
This module provides APIs to manage, compute and verify [RLN](https://rfc.vac.dev/spec/32/) zkSNARK proofs and RLN primitives.
|
||||
[](https://crates.io/crates/rln)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
## Pre-requisites
|
||||
### Install dependencies and clone repo
|
||||
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:
|
||||
|
||||
```sh
|
||||
make installdeps
|
||||
git clone https://github.com/vacp2p/zerokit.git
|
||||
cd zerokit/rln
|
||||
```
|
||||
- Generate and verify RLN proofs
|
||||
- Work with Merkle trees for commitment storage
|
||||
- Implement rate-limiting mechanisms for distributed systems
|
||||
|
||||
### Build and Test
|
||||
## Quick Start
|
||||
|
||||
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/privacy-scaling-explorations/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 three [pre-compiled](https://github.com/vacp2p/zerokit/tree/master/rln/resources) RLN circuits having Merkle tree of height `15`, `19` and `20`, respectively.
|
||||
|
||||
## Getting started
|
||||
> [!IMPORTANT]
|
||||
> Version 0.7.0 is the only version that does not support WASM and x32 architecture.
|
||||
> WASM support is available in version 0.8.0 and above.
|
||||
|
||||
### Add RLN as dependency
|
||||
|
||||
@@ -69,128 +27,308 @@ 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.
|
||||
The RLN object constructor requires the following files:
|
||||
|
||||
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`) and verification key (`verification_key.json`, optional) are found.
|
||||
- `rln_final.arkzkey`: The proving key in arkzkey format.
|
||||
- `graph.bin`: The graph file built for the input tree size
|
||||
|
||||
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.
|
||||
Additionally, `rln.wasm` is used for testing in the rln-wasm module.
|
||||
|
||||
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 circuit resource folder (requires a trailing "/").
|
||||
let tree_height = 20;
|
||||
let resources = Cursor::new("../zerokit/rln/resources/tree_height_20/");
|
||||
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, resources);
|
||||
fn main() {
|
||||
// 1. Initialize RLN with parameters:
|
||||
// - the tree depth;
|
||||
// - the tree config, if it is not defined, the default value will be set
|
||||
let tree_depth = 10;
|
||||
let input = Cursor::new(json!({}).to_string());
|
||||
let mut rln = RLN::new(tree_depth, 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
|
||||
### Comments for the code above for point 4
|
||||
|
||||
We generate an identity keypair
|
||||
The `external nullifier` includes two parameters.
|
||||
|
||||
```rust
|
||||
// We generate an identity pair
|
||||
let mut buffer = Cursor::new(Vec::<u8>::new());
|
||||
rln.key_gen(&mut buffer).unwrap();
|
||||
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.
|
||||
|
||||
// We deserialize the keygen output to obtain
|
||||
// the identiy_secret and id_commitment
|
||||
let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner());
|
||||
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.
|
||||
|
||||
### Features
|
||||
|
||||
- **Stateless Mode**: Allows the use of RLN without maintaining state of the Merkle tree.
|
||||
- **Pre-compiled Circuits**: Ready-to-use circuits with Merkle tree depth of 20
|
||||
- **Wasm Support**: WebAssembly bindings via rln-wasm crate with features like:
|
||||
- Browser and Node.js compatibility
|
||||
- Optional parallel feature support using [wasm-bindgen-rayon](https://github.com/RReverser/wasm-bindgen-rayon)
|
||||
- Headless browser testing capabilities
|
||||
- **Merkle Tree Implementations**: Multiple tree variants optimized for different use cases:
|
||||
- **Full Merkle Tree**: Fastest access with complete pre-allocated tree in memory. Best for frequent random access (enable with `fullmerkletree` feature).
|
||||
- **Optimal Merkle Tree**: Memory-efficient sparse storage using HashMap. Ideal for partially populated trees (enable with `optimalmerkletree` feature).
|
||||
- **Persistent Merkle Tree**: Disk-based storage with [sled](https://github.com/spacejam/sled) for persistence across application restarts and large datasets (enable with `pmtree-ft` feature).
|
||||
|
||||
## Building and Testing
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```sh
|
||||
git clone https://github.com/vacp2p/zerokit.git
|
||||
make installdeps
|
||||
cd zerokit/rln
|
||||
```
|
||||
|
||||
### Add ID commitment to the RLN Merkle tree
|
||||
### Build Commands
|
||||
|
||||
```rust
|
||||
// We define the tree index where id_commitment will be added
|
||||
let id_index = 10;
|
||||
```sh
|
||||
# Build with default features
|
||||
cargo make build
|
||||
|
||||
// We serialize id_commitment and pass it to set_leaf
|
||||
let mut buffer = Cursor::new(serialize_field_element(id_commitment));
|
||||
rln.set_leaf(id_index, &mut buffer).unwrap();
|
||||
# Test with default features
|
||||
cargo make test
|
||||
|
||||
# Test with stateless features
|
||||
cargo make test_stateless
|
||||
```
|
||||
|
||||
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).
|
||||
## Advanced: Custom Circuit Compilation
|
||||
|
||||
### Set epoch
|
||||
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.
|
||||
|
||||
The epoch, sometimes referred to as _external nullifier_, is 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.
|
||||
### 1. Compile ZK Circuits for getting the zkey file
|
||||
|
||||
```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");
|
||||
```
|
||||
### Set signal
|
||||
This script actually generates not only the zkey file 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).
|
||||
|
||||
The signal is the message for which we are computing a RLN proof.
|
||||
To customize the circuit parameters, modify `circom-rln/circuits/rln.circom`:
|
||||
|
||||
```rust
|
||||
// We set our signal
|
||||
let signal = b"RLN is awesome";
|
||||
```circom
|
||||
pragma circom 2.1.0;
|
||||
include "./rln.circom";
|
||||
component main { public [x, externalNullifier] } = RLN(N, M);
|
||||
```
|
||||
|
||||
### Generate a RLN proof
|
||||
Where:
|
||||
|
||||
We prepare the input to the proof generation routine.
|
||||
- `N`: Merkle tree depth, determining the maximum membership capacity (2^N members).
|
||||
|
||||
Input buffer is serialized as `[ identity_key | id_index | epoch | signal_len | signal ]`.
|
||||
- `M`: Bit size for range checks, setting an upper bound for the number of messages per epoch (2^M messages).
|
||||
|
||||
```rust
|
||||
// We prepare input to the proof generation routine
|
||||
let proof_input = prepare_prove_input(identity_secret_hash, id_index, epoch, signal);
|
||||
> [!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 depth `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
|
||||
```
|
||||
|
||||
We are now ready to generate a RLN ZK proof along with the _public outputs_ of the ZK circuit evaluation.
|
||||
#### Generate the zkey and verification key files example
|
||||
|
||||
```rust
|
||||
```sh
|
||||
# Clone the circom-rln repository
|
||||
git clone https://github.com/rate-limiting-nullifier/circom-rln
|
||||
|
||||
// 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();
|
||||
# Install dependencies
|
||||
cd circom-rln && npm install
|
||||
|
||||
// We get the public outputs returned by the circuit evaluation
|
||||
let proof_data = out_buffer.into_inner();
|
||||
# Build circuits
|
||||
./scripts/build-circuits.sh rln
|
||||
|
||||
# Use the generated zkey file in subsequent steps
|
||||
cp zkeyFiles/rln/final.zkey <path_to_rln_final.zkey>
|
||||
```
|
||||
|
||||
The byte vector `proof_data` is serialized as `[ zk-proof | tree_root | epoch | share_x | share_y | nullifier | rln_identifier ]`.
|
||||
### 2. Generate Witness Calculation Graph
|
||||
|
||||
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.
|
||||
|
||||
### Verify a RLN proof
|
||||
```sh
|
||||
# Clone the circom-witnesscalc repository
|
||||
git clone https://github.com/iden3/circom-witnesscalc
|
||||
|
||||
We prepare the input to the proof verification routine.
|
||||
# Load the submodules
|
||||
cd circom-witnesscalc && git submodule update --init --recursive
|
||||
|
||||
Input buffer is serialized as `[proof_data | signal_len | signal ]`, where `proof_data` is (computed as) the output obtained by `generate_rln_proof`.
|
||||
# Build the circom-witnesscalc tool
|
||||
cargo build
|
||||
|
||||
```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();
|
||||
# Generate the witness calculation graph
|
||||
cargo run --package circom_witnesscalc --bin build-circuit ../circom-rln/circuits/rln.circom <path_to_graph.bin>
|
||||
```
|
||||
|
||||
We check if the proof verification was successful:
|
||||
```rust
|
||||
// We ensure the proof is valid
|
||||
assert!(verified);
|
||||
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 file
|
||||
|
||||
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 arkzkey 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>
|
||||
```
|
||||
|
||||
## Get involved!
|
||||
This will generate the `rln_final.arkzkey` file, which is used by the `rln` module.
|
||||
|
||||
Currently, the `rln` module comes with
|
||||
[pre-compiled](https://github.com/vacp2p/zerokit/tree/master/rln/resources) arkzkey keys for the RLN circuit.
|
||||
|
||||
> [!NOTE]
|
||||
> You can use this [convert_zkey.sh](./convert_zkey.sh) script
|
||||
> to automate the process of generating the arkzkey file from any zkey file
|
||||
|
||||
Run the script as follows:
|
||||
|
||||
```sh
|
||||
chmod +x ./convert_zkey.sh
|
||||
./convert_zkey.sh <path_to_rln_final.zkey>
|
||||
```
|
||||
|
||||
## Get involved
|
||||
|
||||
Zerokit RLN public and FFI APIs allow interaction with many more features than what briefly showcased above.
|
||||
|
||||
We invite you to check our API documentation by running
|
||||
|
||||
```rust
|
||||
cargo doc --no-deps
|
||||
```
|
||||
and look at unit tests to have an hint on how to interface and use them.
|
||||
|
||||
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
|
||||
|
||||
50
rln/benches/pmtree_benchmark.rs
Normal file
50
rln/benches/pmtree_benchmark.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use rln::{circuit::Fr, pm_tree_adapter::PmTree};
|
||||
use utils::ZerokitMerkleTree;
|
||||
|
||||
pub fn pmtree_benchmark(c: &mut Criterion) {
|
||||
let mut tree = PmTree::default(2).unwrap();
|
||||
|
||||
let leaves: Vec<Fr> = (0..4).map(Fr::from).collect();
|
||||
|
||||
c.bench_function("Pmtree::set", |b| {
|
||||
b.iter(|| {
|
||||
tree.set(0, leaves[0]).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("Pmtree:delete", |b| {
|
||||
b.iter(|| {
|
||||
tree.delete(0).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("Pmtree::override_range", |b| {
|
||||
b.iter(|| {
|
||||
tree.override_range(0, leaves.clone().into_iter(), [0, 1, 2, 3].into_iter())
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("Pmtree::get", |b| {
|
||||
b.iter(|| {
|
||||
tree.get(0).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
// check intermediate node getter which required additional computation of sub root index
|
||||
c.bench_function("Pmtree::get_subtree_root", |b| {
|
||||
b.iter(|| {
|
||||
tree.get_subtree_root(1, 0).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("Pmtree::get_empty_leaves_indices", |b| {
|
||||
b.iter(|| {
|
||||
tree.get_empty_leaves_indices();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, pmtree_benchmark);
|
||||
criterion_main!(benches);
|
||||
79
rln/benches/poseidon_tree_benchmark.rs
Normal file
79
rln/benches/poseidon_tree_benchmark.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use rln::{
|
||||
circuit::{Fr, TEST_TREE_DEPTH},
|
||||
hashers::PoseidonHash,
|
||||
};
|
||||
use utils::{FullMerkleTree, OptimalMerkleTree, ZerokitMerkleTree};
|
||||
|
||||
pub fn get_leaves(n: u32) -> Vec<Fr> {
|
||||
(0..n).map(Fr::from).collect()
|
||||
}
|
||||
|
||||
pub fn optimal_merkle_tree_poseidon_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("OptimalMerkleTree::<Poseidon>::full_depth_gen", |b| {
|
||||
b.iter(|| {
|
||||
OptimalMerkleTree::<PoseidonHash>::default(TEST_TREE_DEPTH).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
let mut group = c.benchmark_group("Set");
|
||||
for &n in [1u32, 10, 100].iter() {
|
||||
let leaves = get_leaves(n);
|
||||
|
||||
let mut tree = OptimalMerkleTree::<PoseidonHash>::default(TEST_TREE_DEPTH).unwrap();
|
||||
group.bench_function(
|
||||
BenchmarkId::new("OptimalMerkleTree::<Poseidon>::set", n),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
for (i, l) in leaves.iter().enumerate() {
|
||||
let _ = tree.set(i, *l);
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
group.bench_function(
|
||||
BenchmarkId::new("OptimalMerkleTree::<Poseidon>::set_range", n),
|
||||
|b| b.iter(|| tree.set_range(0, leaves.iter().cloned())),
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
pub fn full_merkle_tree_poseidon_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("FullMerkleTree::<Poseidon>::full_depth_gen", |b| {
|
||||
b.iter(|| {
|
||||
FullMerkleTree::<PoseidonHash>::default(TEST_TREE_DEPTH).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
let mut group = c.benchmark_group("Set");
|
||||
for &n in [1u32, 10, 100].iter() {
|
||||
let leaves = get_leaves(n);
|
||||
|
||||
let mut tree = FullMerkleTree::<PoseidonHash>::default(TEST_TREE_DEPTH).unwrap();
|
||||
group.bench_function(
|
||||
BenchmarkId::new("FullMerkleTree::<Poseidon>::set", n),
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
for (i, l) in leaves.iter().enumerate() {
|
||||
let _ = tree.set(i, *l);
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
group.bench_function(
|
||||
BenchmarkId::new("FullMerkleTree::<Poseidon>::set_range", n),
|
||||
|b| b.iter(|| tree.set_range(0, leaves.iter().cloned())),
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
optimal_merkle_tree_poseidon_benchmark,
|
||||
full_merkle_tree_poseidon_benchmark
|
||||
);
|
||||
criterion_main!(benches);
|
||||
53
rln/convert_zkey.sh
Executable file
53
rln/convert_zkey.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Convert zkey to arkzkey using /tmp directory
|
||||
# Usage: ./convert.sh <path_to_zkey_file>
|
||||
|
||||
set -e
|
||||
|
||||
# Check input
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 <path_to_zkey_file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ZKEY_FILE="$1"
|
||||
|
||||
if [ ! -f "$ZKEY_FILE" ]; then
|
||||
echo "Error: File '$ZKEY_FILE' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get absolute path before changing directories
|
||||
ZKEY_ABSOLUTE_PATH=$(realpath "$ZKEY_FILE")
|
||||
|
||||
# Create temp directory in /tmp
|
||||
TEMP_DIR="/tmp/ark-zkey-$$"
|
||||
echo "Using temp directory: $TEMP_DIR"
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
echo "Cleaning up temp directory: $TEMP_DIR"
|
||||
rm -rf "$TEMP_DIR"
|
||||
}
|
||||
|
||||
# Setup cleanup trap
|
||||
trap cleanup EXIT
|
||||
|
||||
# Create temp directory and clone ark-zkey
|
||||
mkdir -p "$TEMP_DIR"
|
||||
cd "$TEMP_DIR"
|
||||
git clone https://github.com/seemenkina/ark-zkey.git
|
||||
cd ark-zkey
|
||||
cargo build
|
||||
|
||||
# Convert
|
||||
cargo run --bin arkzkey-util "$ZKEY_ABSOLUTE_PATH"
|
||||
|
||||
# Check if arkzkey file was created (tool creates it in same directory as input)
|
||||
ARKZKEY_FILE="${ZKEY_ABSOLUTE_PATH%.zkey}.arkzkey"
|
||||
|
||||
if [ ! -f "$ARKZKEY_FILE" ]; then
|
||||
echo "Could not find generated .arkzkey file at $ARKZKEY_FILE"
|
||||
exit 1
|
||||
fi
|
||||
BIN
rln/resources/tree_depth_10/graph.bin
Normal file
BIN
rln/resources/tree_depth_10/graph.bin
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_10/rln.wasm
Normal file
BIN
rln/resources/tree_depth_10/rln.wasm
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_10/rln_final.arkzkey
Normal file
BIN
rln/resources/tree_depth_10/rln_final.arkzkey
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_20/graph.bin
Normal file
BIN
rln/resources/tree_depth_20/graph.bin
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_20/rln.wasm
Normal file
BIN
rln/resources/tree_depth_20/rln.wasm
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_20/rln_final.arkzkey
Normal file
BIN
rln/resources/tree_depth_20/rln_final.arkzkey
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_30/graph.bin
Normal file
BIN
rln/resources/tree_depth_30/graph.bin
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_30/rln.wasm
Normal file
BIN
rln/resources/tree_depth_30/rln.wasm
Normal file
Binary file not shown.
BIN
rln/resources/tree_depth_30/rln_final.arkzkey
Normal file
BIN
rln/resources/tree_depth_30/rln_final.arkzkey
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,119 +0,0 @@
|
||||
{
|
||||
"protocol": "groth16",
|
||||
"curve": "bn128",
|
||||
"nPublic": 6,
|
||||
"vk_alpha_1": [
|
||||
"20124996762962216725442980738609010303800849578410091356605067053491763969391",
|
||||
"9118593021526896828671519912099489027245924097793322973632351264852174143923",
|
||||
"1"
|
||||
],
|
||||
"vk_beta_2": [
|
||||
[
|
||||
"4693952934005375501364248788849686435240706020501681709396105298107971354382",
|
||||
"14346958885444710485362620645446987998958218205939139994511461437152241966681"
|
||||
],
|
||||
[
|
||||
"16851772916911573982706166384196538392731905827088356034885868448550849804972",
|
||||
"823612331030938060799959717749043047845343400798220427319188951998582076532"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_gamma_2": [
|
||||
[
|
||||
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
|
||||
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
|
||||
],
|
||||
[
|
||||
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
|
||||
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_delta_2": [
|
||||
[
|
||||
"1361919643088555407518565462732544232965454074504004321739078395285189557133",
|
||||
"20823246840633598579879223919854294301857184404415306521912631074982696570306"
|
||||
],
|
||||
[
|
||||
"7088590198103342249937795923142619828109070290720888704402714617857746884833",
|
||||
"8191367139632195506244169264298620546181137131063303219908889318280111188437"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_alphabeta_12": [
|
||||
[
|
||||
[
|
||||
"12608968655665301215455851857466367636344427685631271961542642719683786103711",
|
||||
"9849575605876329747382930567422916152871921500826003490242628251047652318086"
|
||||
],
|
||||
[
|
||||
"6322029441245076030714726551623552073612922718416871603535535085523083939021",
|
||||
"8700115492541474338049149013125102281865518624059015445617546140629435818912"
|
||||
],
|
||||
[
|
||||
"10674973475340072635573101639867487770811074181475255667220644196793546640210",
|
||||
"2926286967251299230490668407790788696102889214647256022788211245826267484824"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"9660441540778523475944706619139394922744328902833875392144658911530830074820",
|
||||
"19548113127774514328631808547691096362144426239827206966690021428110281506546"
|
||||
],
|
||||
[
|
||||
"1870837942477655969123169532603615788122896469891695773961478956740992497097",
|
||||
"12536105729661705698805725105036536744930776470051238187456307227425796690780"
|
||||
],
|
||||
[
|
||||
"21811903352654147452884857281720047789720483752548991551595462057142824037334",
|
||||
"19021616763967199151052893283384285352200445499680068407023236283004353578353"
|
||||
]
|
||||
]
|
||||
],
|
||||
"IC": [
|
||||
[
|
||||
"17643142412395322664866141827318671249236739056291610144830020671604112279111",
|
||||
"13273439661778801509295280274403992505521239023074387826870538372514206268318",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"12325966053136615826793633393742326952102053533176311103856731330114882211366",
|
||||
"6439956820140153832120005353467272867287237423425778281905068783317736451260",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"20405310272367450124741832665322768131899487413829191383721623069139009993137",
|
||||
"21336772016824870564600007750206596010566056069977718959140462128560786193566",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"4007669092231576644992949839487535590075070172447826102934640178940614212519",
|
||||
"7597503385395289202372182678960254605827199004598882158153019657732525465207",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"4545695279389338758267531646940033299700127241196839077811942492841603458462",
|
||||
"6635771967009274882904456432128877995932122611166121203658485990305433499873",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"7876954805169515500747828488548350352651069599547377092970620945851311591012",
|
||||
"7571431725691513008054581132582771105743462534789373657638701712901679323321",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"5563973122249220346301217166900152021860462617567141574881706390202619333219",
|
||||
"5147729144109676590873823097632042430451708874867871369293332620382492068692",
|
||||
"1"
|
||||
]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,119 +0,0 @@
|
||||
{
|
||||
"protocol": "groth16",
|
||||
"curve": "bn128",
|
||||
"nPublic": 6,
|
||||
"vk_alpha_1": [
|
||||
"20124996762962216725442980738609010303800849578410091356605067053491763969391",
|
||||
"9118593021526896828671519912099489027245924097793322973632351264852174143923",
|
||||
"1"
|
||||
],
|
||||
"vk_beta_2": [
|
||||
[
|
||||
"4693952934005375501364248788849686435240706020501681709396105298107971354382",
|
||||
"14346958885444710485362620645446987998958218205939139994511461437152241966681"
|
||||
],
|
||||
[
|
||||
"16851772916911573982706166384196538392731905827088356034885868448550849804972",
|
||||
"823612331030938060799959717749043047845343400798220427319188951998582076532"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_gamma_2": [
|
||||
[
|
||||
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
|
||||
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
|
||||
],
|
||||
[
|
||||
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
|
||||
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_delta_2": [
|
||||
[
|
||||
"16125279975606773676640811113051624654121459921695914044301154938920321009721",
|
||||
"14844345250267029614093295465313288254479124604567709177260777529651293576873"
|
||||
],
|
||||
[
|
||||
"20349277326920398483890518242229158117668855310237215044647746783223259766294",
|
||||
"19338776107510040969200058390413661029003750817172740054990168933780935479540"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_alphabeta_12": [
|
||||
[
|
||||
[
|
||||
"12608968655665301215455851857466367636344427685631271961542642719683786103711",
|
||||
"9849575605876329747382930567422916152871921500826003490242628251047652318086"
|
||||
],
|
||||
[
|
||||
"6322029441245076030714726551623552073612922718416871603535535085523083939021",
|
||||
"8700115492541474338049149013125102281865518624059015445617546140629435818912"
|
||||
],
|
||||
[
|
||||
"10674973475340072635573101639867487770811074181475255667220644196793546640210",
|
||||
"2926286967251299230490668407790788696102889214647256022788211245826267484824"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"9660441540778523475944706619139394922744328902833875392144658911530830074820",
|
||||
"19548113127774514328631808547691096362144426239827206966690021428110281506546"
|
||||
],
|
||||
[
|
||||
"1870837942477655969123169532603615788122896469891695773961478956740992497097",
|
||||
"12536105729661705698805725105036536744930776470051238187456307227425796690780"
|
||||
],
|
||||
[
|
||||
"21811903352654147452884857281720047789720483752548991551595462057142824037334",
|
||||
"19021616763967199151052893283384285352200445499680068407023236283004353578353"
|
||||
]
|
||||
]
|
||||
],
|
||||
"IC": [
|
||||
[
|
||||
"5645604624116784480262312750033349186912223090668673154853165165224747369512",
|
||||
"5656337658385597582701340925622307146226708710361427687425735166776477641124",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"8216930132302312821663833393171053651364962198587857550991047765311607638330",
|
||||
"19934865864074163318938688021560358348660709566570123384268356491416384822148",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"11046959016591768534564223076484566731774575511709349452804727872479525392631",
|
||||
"9401797690410912638766111919371607085248054251975419812613989999345815833269",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"13216594148914395028254776738842380005944817065680915990743659996725367876414",
|
||||
"11541283802841111343960351782994043892623551381569479006737253908665900144087",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"6957074593219251760608960101283708711892008557897337713430173510328411964571",
|
||||
"21673833055087220750009279957462375662312260098732685145862504142183400549467",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"20795071270535109448604057031148356571036039566776607847840379441839742201050",
|
||||
"21654952744643117202636583766828639581880877547772465264383291983528268115687",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"19143058772755719660075704757531991493801758701561469885274062297246796623789",
|
||||
"3996020163280925980543600106196205910576345230982361007978823537163123181007",
|
||||
"1"
|
||||
]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,119 +0,0 @@
|
||||
{
|
||||
"protocol": "groth16",
|
||||
"curve": "bn128",
|
||||
"nPublic": 6,
|
||||
"vk_alpha_1": [
|
||||
"20124996762962216725442980738609010303800849578410091356605067053491763969391",
|
||||
"9118593021526896828671519912099489027245924097793322973632351264852174143923",
|
||||
"1"
|
||||
],
|
||||
"vk_beta_2": [
|
||||
[
|
||||
"4693952934005375501364248788849686435240706020501681709396105298107971354382",
|
||||
"14346958885444710485362620645446987998958218205939139994511461437152241966681"
|
||||
],
|
||||
[
|
||||
"16851772916911573982706166384196538392731905827088356034885868448550849804972",
|
||||
"823612331030938060799959717749043047845343400798220427319188951998582076532"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_gamma_2": [
|
||||
[
|
||||
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
|
||||
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
|
||||
],
|
||||
[
|
||||
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
|
||||
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_delta_2": [
|
||||
[
|
||||
"8353516066399360694538747105302262515182301251524941126222712285088022964076",
|
||||
"9329524012539638256356482961742014315122377605267454801030953882967973561832"
|
||||
],
|
||||
[
|
||||
"16805391589556134376869247619848130874761233086443465978238468412168162326401",
|
||||
"10111259694977636294287802909665108497237922060047080343914303287629927847739"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_alphabeta_12": [
|
||||
[
|
||||
[
|
||||
"12608968655665301215455851857466367636344427685631271961542642719683786103711",
|
||||
"9849575605876329747382930567422916152871921500826003490242628251047652318086"
|
||||
],
|
||||
[
|
||||
"6322029441245076030714726551623552073612922718416871603535535085523083939021",
|
||||
"8700115492541474338049149013125102281865518624059015445617546140629435818912"
|
||||
],
|
||||
[
|
||||
"10674973475340072635573101639867487770811074181475255667220644196793546640210",
|
||||
"2926286967251299230490668407790788696102889214647256022788211245826267484824"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"9660441540778523475944706619139394922744328902833875392144658911530830074820",
|
||||
"19548113127774514328631808547691096362144426239827206966690021428110281506546"
|
||||
],
|
||||
[
|
||||
"1870837942477655969123169532603615788122896469891695773961478956740992497097",
|
||||
"12536105729661705698805725105036536744930776470051238187456307227425796690780"
|
||||
],
|
||||
[
|
||||
"21811903352654147452884857281720047789720483752548991551595462057142824037334",
|
||||
"19021616763967199151052893283384285352200445499680068407023236283004353578353"
|
||||
]
|
||||
]
|
||||
],
|
||||
"IC": [
|
||||
[
|
||||
"11992897507809711711025355300535923222599547639134311050809253678876341466909",
|
||||
"17181525095924075896332561978747020491074338784673526378866503154966799128110",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"17018665030246167677911144513385572506766200776123272044534328594850561667818",
|
||||
"18601114175490465275436712413925513066546725461375425769709566180981674884464",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"18799470100699658367834559797874857804183288553462108031963980039244731716542",
|
||||
"13064227487174191981628537974951887429496059857753101852163607049188825592007",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"17432501889058124609368103715904104425610382063762621017593209214189134571156",
|
||||
"13406815149699834788256141097399354592751313348962590382887503595131085938635",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"10320964835612716439094703312987075811498239445882526576970512041988148264481",
|
||||
"9024164961646353611176283204118089412001502110138072989569118393359029324867",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"718355081067365548229685160476620267257521491773976402837645005858953849298",
|
||||
"14635482993933988261008156660773180150752190597753512086153001683711587601974",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"11777720285956632126519898515392071627539405001940313098390150593689568177535",
|
||||
"8483603647274280691250972408211651407952870456587066148445913156086740744515",
|
||||
"1"
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,268 +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_circom::read_zkey;
|
||||
use ark_groth16::{ProvingKey, VerifyingKey};
|
||||
use ark_relations::r1cs::ConstraintMatrices;
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::{Report, Result};
|
||||
use num_bigint::BigUint;
|
||||
use serde_json::Value;
|
||||
use std::io::Cursor;
|
||||
use std::str::FromStr;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(target_arch = "wasm32"))] {
|
||||
use ark_circom::{WitnessCalculator};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Mutex;
|
||||
use wasmer::{Module, Store};
|
||||
use include_dir::{include_dir, Dir};
|
||||
use std::path::Path;
|
||||
}
|
||||
}
|
||||
|
||||
const ZKEY_FILENAME: &str = "rln_final.zkey";
|
||||
const VK_FILENAME: &str = "verification_key.json";
|
||||
const WASM_FILENAME: &str = "rln.wasm";
|
||||
|
||||
// These parameters are used for tests
|
||||
// Note that the circuit and keys in TEST_RESOURCES_FOLDER are compiled for Merkle trees of height 15, 19 and 20
|
||||
// Changing these parameters to other values than these defaults will cause zkSNARK proof verification to fail
|
||||
pub const TEST_PARAMETERS_INDEX: usize = 2;
|
||||
pub const TEST_TREE_HEIGHT: usize = [15, 19, 20][TEST_PARAMETERS_INDEX];
|
||||
pub const TEST_RESOURCES_FOLDER: &str =
|
||||
["tree_height_15", "tree_height_19", "tree_height_20"][TEST_PARAMETERS_INDEX];
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
static RESOURCES_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/resources");
|
||||
|
||||
// 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: &Vec<u8>) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
|
||||
if !zkey_data.is_empty() {
|
||||
let mut c = Cursor::new(zkey_data);
|
||||
let proving_key_and_matrices = read_zkey(&mut c)?;
|
||||
Ok(proving_key_and_matrices)
|
||||
} else {
|
||||
Err(Report::msg("No proving key found!"))
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the proving key
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn zkey_from_folder(
|
||||
resources_folder: &str,
|
||||
) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
|
||||
let zkey = RESOURCES_DIR.get_file(Path::new(resources_folder).join(ZKEY_FILENAME));
|
||||
if let Some(zkey) = zkey {
|
||||
let mut c = Cursor::new(zkey.contents());
|
||||
let proving_key_and_matrices = read_zkey(&mut c)?;
|
||||
Ok(proving_key_and_matrices)
|
||||
} else {
|
||||
Err(Report::msg("No proving key found!"))
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the verification key from a bytes vector
|
||||
pub fn vk_from_raw(vk_data: &Vec<u8>, zkey_data: &Vec<u8>) -> Result<VerifyingKey<Curve>> {
|
||||
let verifying_key: VerifyingKey<Curve>;
|
||||
|
||||
if !vk_data.is_empty() {
|
||||
verifying_key = vk_from_vector(vk_data)?;
|
||||
Ok(verifying_key)
|
||||
} else if !zkey_data.is_empty() {
|
||||
let (proving_key, _matrices) = zkey_from_raw(zkey_data)?;
|
||||
verifying_key = proving_key.vk;
|
||||
Ok(verifying_key)
|
||||
} else {
|
||||
Err(Report::msg("No proving/verification key found!"))
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the verification key
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn vk_from_folder(resources_folder: &str) -> Result<VerifyingKey<Curve>> {
|
||||
let vk = RESOURCES_DIR.get_file(Path::new(resources_folder).join(VK_FILENAME));
|
||||
let zkey = RESOURCES_DIR.get_file(Path::new(resources_folder).join(ZKEY_FILENAME));
|
||||
|
||||
let verifying_key: VerifyingKey<Curve>;
|
||||
if let Some(vk) = vk {
|
||||
verifying_key = vk_from_json(vk.contents_utf8().ok_or(Report::msg(
|
||||
"Could not read verification key from JSON file!",
|
||||
))?)?;
|
||||
Ok(verifying_key)
|
||||
} else if let Some(_zkey) = zkey {
|
||||
let (proving_key, _matrices) = zkey_from_folder(resources_folder)?;
|
||||
verifying_key = proving_key.vk;
|
||||
Ok(verifying_key)
|
||||
} else {
|
||||
Err(Report::msg("No proving/verification key found!"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
static WITNESS_CALCULATOR: OnceCell<Mutex<WitnessCalculator>> = OnceCell::new();
|
||||
|
||||
// Initializes the witness calculator using a bytes vector
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn circom_from_raw(wasm_buffer: Vec<u8>) -> Result<&'static Mutex<WitnessCalculator>> {
|
||||
WITNESS_CALCULATOR.get_or_try_init(|| {
|
||||
let store = Store::default();
|
||||
let module = Module::new(&store, wasm_buffer)?;
|
||||
let result = WitnessCalculator::from_module(module)?;
|
||||
Ok::<Mutex<WitnessCalculator>, Report>(Mutex::new(result))
|
||||
})
|
||||
}
|
||||
|
||||
// Initializes the witness calculator
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn circom_from_folder(resources_folder: &str) -> Result<&'static Mutex<WitnessCalculator>> {
|
||||
// We read the wasm file
|
||||
let wasm = RESOURCES_DIR.get_file(Path::new(resources_folder).join(WASM_FILENAME));
|
||||
|
||||
if let Some(wasm) = wasm {
|
||||
let wasm_buffer = wasm.contents();
|
||||
circom_from_raw(wasm_buffer.to_vec())
|
||||
} else {
|
||||
Err(Report::msg("No wasm file found!"))
|
||||
}
|
||||
}
|
||||
|
||||
// The following function implementations are taken/adapted from https://github.com/gakonst/ark-circom/blob/1732e15d6313fe176b0b1abb858ac9e095d0dbd7/src/zkey.rs
|
||||
|
||||
// Utilities to convert a json verification key in a groth16::VerificationKey
|
||||
fn fq_from_str(s: &str) -> Result<Fq> {
|
||||
Ok(Fq::try_from(BigUint::from_str(s)?)?)
|
||||
}
|
||||
|
||||
// Extracts the element in G1 corresponding to its JSON serialization
|
||||
fn json_to_g1(json: &Value, key: &str) -> Result<G1Affine> {
|
||||
let els: Vec<String> = json
|
||||
.get(key)
|
||||
.ok_or(Report::msg("no json value"))?
|
||||
.as_array()
|
||||
.ok_or(Report::msg("value not an array"))?
|
||||
.iter()
|
||||
.map(|i| i.as_str().ok_or(Report::msg("element is not a string")))
|
||||
.map(|x| x.map(|v| v.to_owned()))
|
||||
.collect::<Result<Vec<String>>>()?;
|
||||
|
||||
Ok(G1Affine::from(G1Projective::new(
|
||||
fq_from_str(&els[0])?,
|
||||
fq_from_str(&els[1])?,
|
||||
fq_from_str(&els[2])?,
|
||||
)))
|
||||
}
|
||||
|
||||
// Extracts the vector of G1 elements corresponding to its JSON serialization
|
||||
fn json_to_g1_vec(json: &Value, key: &str) -> Result<Vec<G1Affine>> {
|
||||
let els: Vec<Vec<String>> = json
|
||||
.get(key)
|
||||
.ok_or(Report::msg("no json value"))?
|
||||
.as_array()
|
||||
.ok_or(Report::msg("value not an array"))?
|
||||
.iter()
|
||||
.map(|i| {
|
||||
i.as_array()
|
||||
.ok_or(Report::msg("element is not an array"))
|
||||
.and_then(|array| {
|
||||
array
|
||||
.iter()
|
||||
.map(|x| x.as_str().ok_or(Report::msg("element is not a string")))
|
||||
.map(|x| x.map(|v| v.to_owned()))
|
||||
.collect::<Result<Vec<String>>>()
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<Vec<String>>>>()?;
|
||||
|
||||
let mut res = vec![];
|
||||
for coords in els {
|
||||
res.push(G1Affine::from(G1Projective::new(
|
||||
fq_from_str(&coords[0])?,
|
||||
fq_from_str(&coords[1])?,
|
||||
fq_from_str(&coords[2])?,
|
||||
)))
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// Extracts the element in G2 corresponding to its JSON serialization
|
||||
fn json_to_g2(json: &Value, key: &str) -> Result<G2Affine> {
|
||||
let els: Vec<Vec<String>> = json
|
||||
.get(key)
|
||||
.ok_or(Report::msg("no json value"))?
|
||||
.as_array()
|
||||
.ok_or(Report::msg("value not an array"))?
|
||||
.iter()
|
||||
.map(|i| {
|
||||
i.as_array()
|
||||
.ok_or(Report::msg("element is not an array"))
|
||||
.and_then(|array| {
|
||||
array
|
||||
.iter()
|
||||
.map(|x| x.as_str().ok_or(Report::msg("element is not a string")))
|
||||
.map(|x| x.map(|v| v.to_owned()))
|
||||
.collect::<Result<Vec<String>>>()
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<Vec<String>>>>()?;
|
||||
|
||||
let x = Fq2::new(fq_from_str(&els[0][0])?, fq_from_str(&els[0][1])?);
|
||||
let y = Fq2::new(fq_from_str(&els[1][0])?, fq_from_str(&els[1][1])?);
|
||||
let z = Fq2::new(fq_from_str(&els[2][0])?, fq_from_str(&els[2][1])?);
|
||||
Ok(G2Affine::from(G2Projective::new(x, y, z)))
|
||||
}
|
||||
|
||||
// Converts JSON to a VerifyingKey
|
||||
fn to_verifying_key(json: serde_json::Value) -> Result<VerifyingKey<Curve>> {
|
||||
Ok(VerifyingKey {
|
||||
alpha_g1: json_to_g1(&json, "vk_alpha_1")?,
|
||||
beta_g2: json_to_g2(&json, "vk_beta_2")?,
|
||||
gamma_g2: json_to_g2(&json, "vk_gamma_2")?,
|
||||
delta_g2: json_to_g2(&json, "vk_delta_2")?,
|
||||
gamma_abc_g1: json_to_g1_vec(&json, "IC")?,
|
||||
})
|
||||
}
|
||||
|
||||
// Computes the verification key from its JSON serialization
|
||||
fn vk_from_json(vk: &str) -> Result<VerifyingKey<Curve>> {
|
||||
let json: Value = serde_json::from_str(vk)?;
|
||||
to_verifying_key(json)
|
||||
}
|
||||
|
||||
// Computes the verification key from a bytes vector containing its JSON serialization
|
||||
fn vk_from_vector(vk: &[u8]) -> Result<VerifyingKey<Curve>> {
|
||||
let json = String::from_utf8(vk.to_vec())?;
|
||||
let json: Value = serde_json::from_str(&json)?;
|
||||
|
||||
to_verifying_key(json)
|
||||
}
|
||||
|
||||
// Checks verification key to be correct with respect to proving key
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn check_vk_from_zkey(
|
||||
resources_folder: &str,
|
||||
verifying_key: VerifyingKey<Curve>,
|
||||
) -> Result<()> {
|
||||
let (proving_key, _matrices) = zkey_from_folder(resources_folder)?;
|
||||
if proving_key.vk == verifying_key {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Report::msg("verifying_keys are not equal"))
|
||||
}
|
||||
}
|
||||
7
rln/src/circuit/error.rs
Normal file
7
rln/src/circuit/error.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ZKeyReadError {
|
||||
#[error("No proving key found!")]
|
||||
EmptyBytes,
|
||||
#[error("{0}")]
|
||||
SerializationError(#[from] ark_serialize::SerializationError),
|
||||
}
|
||||
94
rln/src/circuit/iden3calc.rs
Normal file
94
rln/src/circuit/iden3calc.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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 zeroize::zeroize_flat_type;
|
||||
|
||||
use crate::circuit::iden3calc::graph::fr_to_u256;
|
||||
use crate::circuit::Fr;
|
||||
use crate::utils::FrOrSecret;
|
||||
use graph::Node;
|
||||
|
||||
pub type InputSignalsInfo = HashMap<String, (usize, usize)>;
|
||||
|
||||
pub fn calc_witness<I: IntoIterator<Item = (String, Vec<FrOrSecret>)>>(
|
||||
inputs: I,
|
||||
graph_data: &[u8],
|
||||
) -> Vec<Fr> {
|
||||
let mut inputs: HashMap<String, Vec<U256>> = inputs
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
(
|
||||
key,
|
||||
value
|
||||
.iter()
|
||||
.map(|f_| match f_ {
|
||||
FrOrSecret::IdSecret(s) => s.to_u256(),
|
||||
FrOrSecret::Fr(f) => fr_to_u256(f),
|
||||
})
|
||||
.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);
|
||||
if let Some(v) = inputs.get_mut("identitySecret") {
|
||||
// ~== v[0] = U256::ZERO;
|
||||
unsafe { zeroize_flat_type(v) };
|
||||
}
|
||||
let res = graph::evaluate(&nodes, inputs_buffer.as_slice(), &signals);
|
||||
inputs_buffer.iter_mut().for_each(|i| {
|
||||
unsafe { zeroize_flat_type(i) };
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
957
rln/src/circuit/iden3calc/graph.rs
Normal file
957
rln/src/circuit/iden3calc/graph.rs
Normal 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::{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.first();
|
||||
println!("{node:?}");
|
||||
}
|
||||
}
|
||||
117
rln/src/circuit/iden3calc/proto.rs
Normal file
117
rln/src/circuit/iden3calc/proto.rs
Normal 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,
|
||||
}
|
||||
497
rln/src/circuit/iden3calc/storage.rs
Normal file
497
rln/src/circuit/iden3calc/storage.rs
Normal 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_exact(buf).unwrap();
|
||||
assert_eq!(buf, &[1, 2, 3, 4, 5]);
|
||||
|
||||
// return [4, 5] to reader
|
||||
r.write_all(&buf[3..]).unwrap();
|
||||
// return [2, 3] to reader
|
||||
r.write_all(&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);
|
||||
}
|
||||
}
|
||||
132
rln/src/circuit/mod.rs
Normal file
132
rln/src/circuit/mod.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
// This crate provides interfaces for the zero-knowledge circuit and keys
|
||||
|
||||
pub mod error;
|
||||
pub mod iden3calc;
|
||||
pub mod qap;
|
||||
|
||||
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 crate::circuit::error::ZKeyReadError;
|
||||
use crate::circuit::iden3calc::calc_witness;
|
||||
|
||||
use {ark_ff::Field, ark_serialize::CanonicalDeserialize, ark_serialize::CanonicalSerialize};
|
||||
|
||||
use crate::utils::FrOrSecret;
|
||||
|
||||
pub const ARKZKEY_BYTES: &[u8] = include_bytes!("../../resources/tree_depth_10/rln_final.arkzkey");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const GRAPH_BYTES: &[u8] = include_bytes!("../../resources/tree_depth_10/graph.bin");
|
||||
|
||||
lazy_static! {
|
||||
static ref ARKZKEY: (ProvingKey<Curve>, ConstraintMatrices<Fr>) =
|
||||
read_arkzkey_from_bytes_uncompressed(ARKZKEY_BYTES).expect("Failed to read arkzkey");
|
||||
}
|
||||
|
||||
pub const TEST_TREE_DEPTH: usize = 10;
|
||||
|
||||
// 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>), ZKeyReadError> {
|
||||
if zkey_data.is_empty() {
|
||||
return Err(ZKeyReadError::EmptyBytes);
|
||||
}
|
||||
|
||||
let proving_key_and_matrices = read_arkzkey_from_bytes_uncompressed(zkey_data)?;
|
||||
|
||||
Ok(proving_key_and_matrices)
|
||||
}
|
||||
|
||||
// Loads the proving key
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn zkey_from_folder() -> &'static (ProvingKey<Curve>, ConstraintMatrices<Fr>) {
|
||||
&ARKZKEY
|
||||
}
|
||||
|
||||
pub fn calculate_rln_witness<I: IntoIterator<Item = (String, Vec<FrOrSecret>)>>(
|
||||
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
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
|
||||
pub struct SerializableProvingKey(pub ProvingKey<Bn254>);
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
|
||||
pub struct SerializableMatrix<F: Field> {
|
||||
pub data: Vec<Vec<(F, usize)>>,
|
||||
}
|
||||
|
||||
pub fn read_arkzkey_from_bytes_uncompressed(
|
||||
arkzkey_data: &[u8],
|
||||
) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>), ZKeyReadError> {
|
||||
if arkzkey_data.is_empty() {
|
||||
return Err(ZKeyReadError::EmptyBytes);
|
||||
}
|
||||
|
||||
let mut cursor = std::io::Cursor::new(arkzkey_data);
|
||||
|
||||
let serialized_proving_key =
|
||||
SerializableProvingKey::deserialize_uncompressed_unchecked(&mut cursor)?;
|
||||
|
||||
let serialized_constraint_matrices =
|
||||
SerializableConstraintMatrices::deserialize_uncompressed_unchecked(&mut cursor)?;
|
||||
|
||||
// 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))
|
||||
}
|
||||
120
rln/src/circuit/qap.rs
Normal file
120
rln/src/circuit/qap.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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};
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::iter::{
|
||||
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator,
|
||||
IntoParallelRefMutIterator, ParallelIterator,
|
||||
};
|
||||
|
||||
/// 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())
|
||||
}
|
||||
}
|
||||
79
rln/src/error.rs
Normal file
79
rln/src/error.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use crate::circuit::error::ZKeyReadError;
|
||||
use ark_bn254::Fr;
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_serialize::SerializationError;
|
||||
use num_bigint::{BigInt, ParseBigIntError};
|
||||
use std::array::TryFromSliceError;
|
||||
use std::num::TryFromIntError;
|
||||
use std::string::FromUtf8Error;
|
||||
use thiserror::Error;
|
||||
use utils::error::{FromConfigError, ZerokitMerkleTreeError};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConversionError {
|
||||
#[error("Expected radix 10 or 16")]
|
||||
WrongRadix,
|
||||
#[error("{0}")]
|
||||
ParseBigInt(#[from] ParseBigIntError),
|
||||
#[error("{0}")]
|
||||
ToUsize(#[from] TryFromIntError),
|
||||
#[error("{0}")]
|
||||
FromSlice(#[from] TryFromSliceError),
|
||||
#[error("Input data too short: expected at least {expected} bytes, got {actual} bytes")]
|
||||
InsufficientData { expected: usize, actual: usize },
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ProofError {
|
||||
#[error("{0}")]
|
||||
ProtocolError(#[from] ProtocolError),
|
||||
#[error("Error producing proof: {0}")]
|
||||
SynthesisError(#[from] SynthesisError),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ProtocolError {
|
||||
#[error("{0}")]
|
||||
Conversion(#[from] ConversionError),
|
||||
#[error("Expected to read {0} bytes but read only {1} bytes")]
|
||||
InvalidReadLen(usize, usize),
|
||||
#[error("Cannot convert bigint {0:?} to biguint")]
|
||||
BigUintConversion(BigInt),
|
||||
#[error("{0}")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
#[error("Message id ({0}) is not within user_message_limit ({1})")]
|
||||
InvalidMessageId(Fr, Fr),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ComputeIdSecretError {
|
||||
/// Usually it means that the same signal is used to recover the user secret hash
|
||||
#[error("Cannot recover secret: division by zero")]
|
||||
DivisionByZero,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RLNError {
|
||||
#[error("I/O error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
#[error("Utf8 error: {0}")]
|
||||
Utf8(#[from] FromUtf8Error),
|
||||
#[error("Serde json error: {0}")]
|
||||
JSON(#[from] serde_json::Error),
|
||||
#[error("Config error: {0}")]
|
||||
Config(#[from] FromConfigError),
|
||||
#[error("Serialization error: {0}")]
|
||||
Serialization(#[from] SerializationError),
|
||||
#[error("Merkle tree error: {0}")]
|
||||
MerkleTree(#[from] ZerokitMerkleTreeError),
|
||||
#[error("ZKey error: {0}")]
|
||||
ZKey(#[from] ZKeyReadError),
|
||||
#[error("Conversion error: {0}")]
|
||||
Conversion(#[from] ConversionError),
|
||||
#[error("Protocol error: {0}")]
|
||||
Protocol(#[from] ProtocolError),
|
||||
#[error("Proof error: {0}")]
|
||||
Proof(#[from] ProofError),
|
||||
#[error("Unable to extract secret")]
|
||||
RecoverSecret(#[from] ComputeIdSecretError),
|
||||
}
|
||||
408
rln/src/ffi.rs
408
rln/src/ffi.rs
@@ -2,17 +2,31 @@
|
||||
|
||||
use std::slice;
|
||||
|
||||
use crate::public::{hash as public_hash, poseidon_hash as public_poseidon_hash, RLN};
|
||||
use crate::public::{
|
||||
extended_key_gen as public_extended_key_gen, hash as public_hash, key_gen as public_key_gen,
|
||||
poseidon_hash as public_poseidon_hash,
|
||||
seeded_extended_key_gen as public_seeded_extended_key_gen,
|
||||
seeded_key_gen as public_seeded_key_gen, RLN,
|
||||
};
|
||||
|
||||
// Macro to call methods with arbitrary amount of arguments,
|
||||
// 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)*) => {
|
||||
{
|
||||
let new_instance: &mut RLN = $instance.process();
|
||||
new_instance.$method($($arg.process()),*).is_ok()
|
||||
match new_instance.$method($($arg.process()),*) {
|
||||
Ok(()) => {
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("execution error: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,13 +44,17 @@ macro_rules! call_with_output_arg {
|
||||
{
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
let new_instance = $instance.process();
|
||||
if new_instance.$method(&mut output_data).is_ok() {
|
||||
unsafe { *$output_arg = Buffer::from(&output_data[..]) };
|
||||
std::mem::forget(output_data);
|
||||
true
|
||||
} else {
|
||||
std::mem::forget(output_data);
|
||||
false
|
||||
match new_instance.$method(&mut output_data) {
|
||||
Ok(()) => {
|
||||
unsafe { *$output_arg = Buffer::from(&output_data[..]) };
|
||||
std::mem::forget(output_data);
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
std::mem::forget(output_data);
|
||||
eprintln!("execution error: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -44,13 +62,17 @@ macro_rules! call_with_output_arg {
|
||||
{
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
let new_instance = $instance.process();
|
||||
if new_instance.$method($($arg.process()),*, &mut output_data).is_ok() {
|
||||
unsafe { *$output_arg = Buffer::from(&output_data[..]) };
|
||||
std::mem::forget(output_data);
|
||||
true
|
||||
} else {
|
||||
std::mem::forget(output_data);
|
||||
false
|
||||
match new_instance.$method($($arg.process()),*, &mut output_data) {
|
||||
Ok(()) => {
|
||||
unsafe { *$output_arg = Buffer::from(&output_data[..]) };
|
||||
std::mem::forget(output_data);
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
std::mem::forget(output_data);
|
||||
eprintln!("execution error: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -63,19 +85,48 @@ macro_rules! call_with_output_arg {
|
||||
// Second argument is the output buffer argument
|
||||
// The remaining arguments are all other inputs to the method
|
||||
macro_rules! no_ctx_call_with_output_arg {
|
||||
($method:ident, $output_arg:expr, $( $arg:expr ),* ) => {
|
||||
{
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
if $method($($arg.process()),*, &mut output_data).is_ok() {
|
||||
($method:ident, $output_arg:expr, $input_arg:expr, $endianness_arg:expr) => {{
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
match $method(
|
||||
$input_arg.process(),
|
||||
&mut output_data,
|
||||
$endianness_arg.process(),
|
||||
) {
|
||||
Ok(()) => {
|
||||
unsafe { *$output_arg = Buffer::from(&output_data[..]) };
|
||||
std::mem::forget(output_data);
|
||||
true
|
||||
} else {
|
||||
}
|
||||
Err(err) => {
|
||||
std::mem::forget(output_data);
|
||||
eprintln!("execution error: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// Macro to call methods with arbitrary amount of arguments,
|
||||
// which are not implemented in a ctx RLN object
|
||||
// First argument is the method to call
|
||||
// Second argument is the output buffer argument
|
||||
// The remaining arguments are all other inputs to the method
|
||||
macro_rules! no_ctx_call_with_output_arg_and_endianness {
|
||||
($method:ident, $output_arg:expr, $endianness_arg:expr) => {{
|
||||
let mut output_data: Vec<u8> = Vec::new();
|
||||
match $method(&mut output_data, $endianness_arg.process()) {
|
||||
Ok(()) => {
|
||||
unsafe { *$output_arg = Buffer::from(&output_data[..]) };
|
||||
std::mem::forget(output_data);
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
std::mem::forget(output_data);
|
||||
eprintln!("execution error: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// Macro to call methods with arbitrary amount of arguments,
|
||||
@@ -89,8 +140,11 @@ macro_rules! call_with_bool_arg {
|
||||
{
|
||||
let new_instance = $instance.process();
|
||||
if match new_instance.$method($($arg.process()),*,) {
|
||||
Ok(verified) => verified,
|
||||
Err(_) => return false,
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
eprintln!("execution error: {err}");
|
||||
return false
|
||||
},
|
||||
} {
|
||||
unsafe { *$bool_arg = true };
|
||||
} else {
|
||||
@@ -120,24 +174,31 @@ impl ProcessArg for *const Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ProcessArg for *const RLN<'a> {
|
||||
type ReturnType = &'a RLN<'a>;
|
||||
impl ProcessArg for *const RLN {
|
||||
type ReturnType = &'static RLN;
|
||||
fn process(self) -> Self::ReturnType {
|
||||
unsafe { &*self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ProcessArg for *mut RLN<'a> {
|
||||
type ReturnType = &'a mut RLN<'a>;
|
||||
impl ProcessArg for *mut RLN {
|
||||
type ReturnType = &'static mut RLN;
|
||||
fn process(self) -> Self::ReturnType {
|
||||
unsafe { &mut *self }
|
||||
}
|
||||
}
|
||||
|
||||
/// Buffer struct is taken from
|
||||
/// <https://github.com/celo-org/celo-threshold-bls-rs/blob/master/crates/threshold-bls-ffi/src/ffi.rs>
|
||||
///
|
||||
/// Also heavily inspired by <https://github.com/kilic/rln/blob/master/src/ffi.rs>
|
||||
impl ProcessArg for bool {
|
||||
type ReturnType = bool;
|
||||
fn process(self) -> Self::ReturnType {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
///// Buffer struct is taken from
|
||||
///// <https://github.com/celo-org/celo-threshold-bls-rs/blob/master/crates/threshold-bls-ffi/src/ffi.rs>
|
||||
/////
|
||||
///// Also heavily inspired by <https://github.com/kilic/rln/blob/master/src/ffi.rs>
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -169,35 +230,84 @@ impl<'a> From<&Buffer> for &'a [u8] {
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new(tree_height: usize, input_buffer: *const Buffer, ctx: *mut *mut RLN) -> bool {
|
||||
if let Ok(rln) = RLN::new(tree_height, input_buffer.process()) {
|
||||
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
|
||||
true
|
||||
} else {
|
||||
false
|
||||
pub extern "C" fn new(tree_depth: usize, input_buffer: *const Buffer, ctx: *mut *mut RLN) -> bool {
|
||||
match RLN::new(tree_depth, input_buffer.process()) {
|
||||
Ok(rln) => {
|
||||
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("could not instantiate rln: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[cfg(feature = "stateless")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new(ctx: *mut *mut RLN) -> bool {
|
||||
match RLN::new() {
|
||||
Ok(rln) => {
|
||||
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("could not instantiate rln: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new_with_params(
|
||||
tree_height: usize,
|
||||
circom_buffer: *const Buffer,
|
||||
tree_depth: usize,
|
||||
zkey_buffer: *const Buffer,
|
||||
vk_buffer: *const Buffer,
|
||||
graph_data: *const Buffer,
|
||||
tree_config: *const Buffer,
|
||||
ctx: *mut *mut RLN,
|
||||
) -> bool {
|
||||
if let Ok(rln) = RLN::new_with_params(
|
||||
tree_height,
|
||||
circom_buffer.process().to_vec(),
|
||||
match RLN::new_with_params(
|
||||
tree_depth,
|
||||
zkey_buffer.process().to_vec(),
|
||||
vk_buffer.process().to_vec(),
|
||||
graph_data.process().to_vec(),
|
||||
tree_config.process(),
|
||||
) {
|
||||
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
|
||||
true
|
||||
} else {
|
||||
false
|
||||
Ok(rln) => {
|
||||
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("could not instantiate rln: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[cfg(feature = "stateless")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new_with_params(
|
||||
zkey_buffer: *const Buffer,
|
||||
graph_buffer: *const Buffer,
|
||||
ctx: *mut *mut RLN,
|
||||
) -> bool {
|
||||
match RLN::new_with_params(
|
||||
zkey_buffer.process().to_vec(),
|
||||
graph_buffer.process().to_vec(),
|
||||
) {
|
||||
Ok(rln) => {
|
||||
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("could not instantiate rln: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,30 +316,49 @@ pub extern "C" fn new_with_params(
|
||||
////////////////////////////////////////////////////////
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_tree(ctx: *mut RLN, tree_height: usize) -> bool {
|
||||
call!(ctx, set_tree, tree_height)
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn set_tree(ctx: *mut RLN, tree_depth: usize) -> bool {
|
||||
call!(ctx, set_tree, tree_depth)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn delete_leaf(ctx: *mut RLN, index: usize) -> bool {
|
||||
call!(ctx, delete_leaf, index)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn set_leaf(ctx: *mut RLN, index: usize, input_buffer: *const Buffer) -> bool {
|
||||
call!(ctx, set_leaf, index, input_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn get_leaf(ctx: *mut RLN, index: usize, output_buffer: *mut Buffer) -> bool {
|
||||
call_with_output_arg!(ctx, get_leaf, output_buffer, index)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn leaves_set(ctx: *mut RLN) -> usize {
|
||||
ctx.process().leaves_set()
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn set_next_leaf(ctx: *mut RLN, input_buffer: *const Buffer) -> bool {
|
||||
call!(ctx, set_next_leaf, input_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn set_leaves_from(
|
||||
ctx: *mut RLN,
|
||||
index: usize,
|
||||
@@ -240,18 +369,50 @@ pub extern "C" fn set_leaves_from(
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn init_tree_with_leaves(ctx: *mut RLN, input_buffer: *const Buffer) -> bool {
|
||||
call!(ctx, init_tree_with_leaves, input_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn atomic_operation(
|
||||
ctx: *mut RLN,
|
||||
index: usize,
|
||||
leaves_buffer: *const Buffer,
|
||||
indices_buffer: *const Buffer,
|
||||
) -> bool {
|
||||
call!(ctx, atomic_operation, index, leaves_buffer, indices_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn seq_atomic_operation(
|
||||
ctx: *mut RLN,
|
||||
leaves_buffer: *const Buffer,
|
||||
indices_buffer: *const Buffer,
|
||||
) -> bool {
|
||||
call!(
|
||||
ctx,
|
||||
atomic_operation,
|
||||
ctx.process().leaves_set(),
|
||||
leaves_buffer,
|
||||
indices_buffer
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn get_root(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
|
||||
call_with_output_arg!(ctx, get_root, output_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn get_proof(ctx: *const RLN, index: usize, output_buffer: *mut Buffer) -> bool {
|
||||
call_with_output_arg!(ctx, get_proof, output_buffer, index)
|
||||
}
|
||||
@@ -281,6 +442,7 @@ pub extern "C" fn verify(
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn generate_rln_proof(
|
||||
ctx: *mut RLN,
|
||||
input_buffer: *const Buffer,
|
||||
@@ -291,6 +453,22 @@ pub extern "C" fn generate_rln_proof(
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn generate_rln_proof_with_witness(
|
||||
ctx: *mut RLN,
|
||||
input_buffer: *const Buffer,
|
||||
output_buffer: *mut Buffer,
|
||||
) -> bool {
|
||||
call_with_output_arg!(
|
||||
ctx,
|
||||
generate_rln_proof_with_witness,
|
||||
output_buffer,
|
||||
input_buffer
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn verify_rln_proof(
|
||||
ctx: *const RLN,
|
||||
proof_buffer: *const Buffer,
|
||||
@@ -319,38 +497,6 @@ pub extern "C" fn verify_with_roots(
|
||||
////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
////////////////////////////////////////////////////////
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn key_gen(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
|
||||
call_with_output_arg!(ctx, key_gen, output_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn seeded_key_gen(
|
||||
ctx: *const RLN,
|
||||
input_buffer: *const Buffer,
|
||||
output_buffer: *mut Buffer,
|
||||
) -> bool {
|
||||
call_with_output_arg!(ctx, seeded_key_gen, output_buffer, input_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn extended_key_gen(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
|
||||
call_with_output_arg!(ctx, extended_key_gen, output_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn seeded_extended_key_gen(
|
||||
ctx: *const RLN,
|
||||
input_buffer: *const Buffer,
|
||||
output_buffer: *mut Buffer,
|
||||
) -> bool {
|
||||
call_with_output_arg!(ctx, seeded_extended_key_gen, output_buffer, input_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn recover_id_secret(
|
||||
@@ -368,14 +514,102 @@ pub extern "C" fn recover_id_secret(
|
||||
)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// Persistent metadata APIs
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn hash(input_buffer: *const Buffer, output_buffer: *mut Buffer) -> bool {
|
||||
no_ctx_call_with_output_arg!(public_hash, output_buffer, input_buffer)
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn set_metadata(ctx: *mut RLN, input_buffer: *const Buffer) -> bool {
|
||||
call!(ctx, set_metadata, input_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn poseidon_hash(input_buffer: *const Buffer, output_buffer: *mut Buffer) -> bool {
|
||||
no_ctx_call_with_output_arg!(public_poseidon_hash, output_buffer, input_buffer)
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn get_metadata(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
|
||||
call_with_output_arg!(ctx, get_metadata, output_buffer)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub extern "C" fn flush(ctx: *mut RLN) -> bool {
|
||||
call!(ctx, flush)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// Utils APIs
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn hash(
|
||||
input_buffer: *const Buffer,
|
||||
output_buffer: *mut Buffer,
|
||||
is_little_endian: bool,
|
||||
) -> bool {
|
||||
no_ctx_call_with_output_arg!(public_hash, output_buffer, input_buffer, is_little_endian)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn poseidon_hash(
|
||||
input_buffer: *const Buffer,
|
||||
output_buffer: *mut Buffer,
|
||||
is_little_endian: bool,
|
||||
) -> bool {
|
||||
no_ctx_call_with_output_arg!(
|
||||
public_poseidon_hash,
|
||||
output_buffer,
|
||||
input_buffer,
|
||||
is_little_endian
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn key_gen(output_buffer: *mut Buffer, is_little_endian: bool) -> bool {
|
||||
no_ctx_call_with_output_arg_and_endianness!(public_key_gen, output_buffer, is_little_endian)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn seeded_key_gen(
|
||||
input_buffer: *const Buffer,
|
||||
output_buffer: *mut Buffer,
|
||||
is_little_endian: bool,
|
||||
) -> bool {
|
||||
no_ctx_call_with_output_arg!(
|
||||
public_seeded_key_gen,
|
||||
output_buffer,
|
||||
input_buffer,
|
||||
is_little_endian
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn extended_key_gen(output_buffer: *mut Buffer, is_little_endian: bool) -> bool {
|
||||
no_ctx_call_with_output_arg_and_endianness!(
|
||||
public_extended_key_gen,
|
||||
output_buffer,
|
||||
is_little_endian
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn seeded_extended_key_gen(
|
||||
input_buffer: *const Buffer,
|
||||
output_buffer: *mut Buffer,
|
||||
is_little_endian: bool,
|
||||
) -> bool {
|
||||
no_ctx_call_with_output_arg!(
|
||||
public_seeded_extended_key_gen,
|
||||
output_buffer,
|
||||
input_buffer,
|
||||
is_little_endian
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// This crate instantiate the Poseidon hash algorithm
|
||||
|
||||
use crate::{circuit::Fr, utils::bytes_le_to_fr};
|
||||
/// This crate instantiates the Poseidon hash algorithm.
|
||||
use crate::{
|
||||
circuit::Fr,
|
||||
utils::{bytes_be_to_fr, bytes_le_to_fr},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use tiny_keccak::{Hasher, Keccak};
|
||||
use utils::poseidon::Poseidon;
|
||||
|
||||
// These indexed constants hardcodes the supported round parameters tuples (t, RF, RN, SKIP_MATRICES) for the Bn254 scalar field
|
||||
// SKIP_MATRICES is the index of the randomly generated secure MDS matrix. See security note in the zerokit_utils::poseidon::poseidon_constants crate on this.
|
||||
// TODO: generate these parameters
|
||||
/// These indexed constants hardcode the supported round parameters tuples (t, RF, RN, SKIP_MATRICES) for the Bn254 scalar field.
|
||||
/// SKIP_MATRICES is the index of the randomly generated secure MDS matrix.
|
||||
/// TODO: generate these parameters
|
||||
pub const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [
|
||||
(2, 8, 56, 0),
|
||||
(3, 8, 57, 0),
|
||||
@@ -19,19 +21,35 @@ pub const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [
|
||||
(9, 8, 63, 0),
|
||||
];
|
||||
|
||||
// Poseidon Hash wrapper over above implementation. Adapted from semaphore-rs poseidon hash wrapper.
|
||||
/// Poseidon Hash wrapper over above implementation.
|
||||
static POSEIDON: Lazy<Poseidon<Fr>> = Lazy::new(|| Poseidon::<Fr>::from(&ROUND_PARAMS));
|
||||
|
||||
pub fn poseidon_hash(input: &[Fr]) -> Fr {
|
||||
POSEIDON
|
||||
.hash(input.to_vec())
|
||||
.hash(input)
|
||||
.expect("hash with fixed input size can't fail")
|
||||
}
|
||||
|
||||
// Hashes arbitrary signal to the underlying prime field
|
||||
pub fn hash_to_field(signal: &[u8]) -> Fr {
|
||||
/// The zerokit RLN Merkle tree Hasher.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PoseidonHash;
|
||||
|
||||
/// The default Hasher trait used by Merkle tree implementation in utils.
|
||||
impl utils::merkle_tree::Hasher for PoseidonHash {
|
||||
type Fr = Fr;
|
||||
|
||||
fn default_leaf() -> Self::Fr {
|
||||
Self::Fr::from(0)
|
||||
}
|
||||
|
||||
fn hash(inputs: &[Self::Fr]) -> Self::Fr {
|
||||
poseidon_hash(inputs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Hashes arbitrary signal to the underlying prime field.
|
||||
pub fn hash_to_field_le(signal: &[u8]) -> Fr {
|
||||
// We hash the input signal using Keccak256
|
||||
// (note that a bigger curve order might require a bigger hash blocksize)
|
||||
let mut hash = [0; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(signal);
|
||||
@@ -41,3 +59,19 @@ pub fn hash_to_field(signal: &[u8]) -> Fr {
|
||||
let (el, _) = bytes_le_to_fr(hash.as_ref());
|
||||
el
|
||||
}
|
||||
|
||||
/// Hashes arbitrary signal to the underlying prime field.
|
||||
pub fn hash_to_field_be(signal: &[u8]) -> Fr {
|
||||
// We hash the input signal using Keccak256
|
||||
let mut hash = [0; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(signal);
|
||||
hasher.finalize(&mut hash);
|
||||
|
||||
// Reverse the bytes to get big endian representation
|
||||
hash.reverse();
|
||||
|
||||
// We export the hash as a field element
|
||||
let (el, _) = bytes_be_to_fr(hash.as_ref());
|
||||
el
|
||||
}
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod circuit;
|
||||
pub mod error;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod ffi;
|
||||
pub mod hashers;
|
||||
#[cfg(feature = "pmtree-ft")]
|
||||
pub mod pm_tree_adapter;
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub mod poseidon_tree;
|
||||
pub mod protocol;
|
||||
pub mod public;
|
||||
#[cfg(test)]
|
||||
pub mod public_api_tests;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod ffi;
|
||||
// Ensure that only one Merkle tree feature is enabled at a time
|
||||
#[cfg(any(
|
||||
all(feature = "fullmerkletree", feature = "optimalmerkletree"),
|
||||
all(feature = "fullmerkletree", feature = "pmtree-ft"),
|
||||
all(feature = "optimalmerkletree", feature = "pmtree-ft"),
|
||||
))]
|
||||
compile_error!(
|
||||
"Only one of `fullmerkletree`, `optimalmerkletree`, or `pmtree-ft` can be enabled at a time."
|
||||
);
|
||||
|
||||
// Ensure that the `stateless` feature is not enabled with any Merkle tree features
|
||||
#[cfg(all(
|
||||
feature = "stateless",
|
||||
any(
|
||||
feature = "fullmerkletree",
|
||||
feature = "optimalmerkletree",
|
||||
feature = "pmtree-ft"
|
||||
)
|
||||
))]
|
||||
compile_error!("Cannot enable any Merkle tree features with stateless");
|
||||
|
||||
505
rln/src/pm_tree_adapter.rs
Normal file
505
rln/src/pm_tree_adapter.rs
Normal file
@@ -0,0 +1,505 @@
|
||||
use serde_json::Value;
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tempfile::Builder;
|
||||
|
||||
use crate::circuit::Fr;
|
||||
use crate::hashers::{poseidon_hash, PoseidonHash};
|
||||
use crate::utils::{bytes_le_to_fr, fr_to_bytes_le};
|
||||
use utils::error::{FromConfigError, ZerokitMerkleTreeError};
|
||||
use utils::pmtree::tree::Key;
|
||||
use utils::pmtree::{Database, Hasher, PmtreeErrorKind};
|
||||
use utils::{pmtree, Config, Mode, SledDB, ZerokitMerkleProof, ZerokitMerkleTree};
|
||||
|
||||
const METADATA_KEY: [u8; 8] = *b"metadata";
|
||||
|
||||
pub struct PmTree {
|
||||
tree: pmtree::MerkleTree<SledDB, PoseidonHash>,
|
||||
/// The indices of leaves which are set into zero upto next_index.
|
||||
/// Set to 0 if the leaf is empty and set to 1 in otherwise.
|
||||
cached_leaves_indices: Vec<u8>,
|
||||
// metadata that an application may use to store additional information
|
||||
metadata: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct PmTreeProof {
|
||||
proof: pmtree::tree::MerkleProof<PoseidonHash>,
|
||||
}
|
||||
|
||||
pub type FrOf<H> = <H as Hasher>::Fr;
|
||||
|
||||
// The pmtree Hasher trait used by pmtree Merkle tree
|
||||
impl Hasher for PoseidonHash {
|
||||
type Fr = Fr;
|
||||
|
||||
fn serialize(value: Self::Fr) -> pmtree::Value {
|
||||
fr_to_bytes_le(&value)
|
||||
}
|
||||
|
||||
fn deserialize(value: pmtree::Value) -> Self::Fr {
|
||||
let (fr, _) = bytes_le_to_fr(&value);
|
||||
fr
|
||||
}
|
||||
|
||||
fn default_leaf() -> Self::Fr {
|
||||
Fr::from(0)
|
||||
}
|
||||
|
||||
fn hash(inputs: &[Self::Fr]) -> Self::Fr {
|
||||
poseidon_hash(inputs)
|
||||
}
|
||||
}
|
||||
|
||||
fn default_tmp_path() -> PathBuf {
|
||||
Builder::new()
|
||||
.prefix("pmtree-")
|
||||
.tempfile()
|
||||
.expect("Failed to create temp file")
|
||||
.into_temp_path()
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
const DEFAULT_TEMPORARY: bool = true;
|
||||
const DEFAULT_CACHE_CAPACITY: u64 = 1073741824; // 1 Gigabyte
|
||||
const DEFAULT_FLUSH_EVERY_MS: u64 = 500; // 500 Milliseconds
|
||||
const DEFAULT_MODE: Mode = Mode::HighThroughput;
|
||||
const DEFAULT_USE_COMPRESSION: bool = false;
|
||||
|
||||
pub struct PmtreeConfigBuilder {
|
||||
path: Option<PathBuf>,
|
||||
temporary: bool,
|
||||
cache_capacity: u64,
|
||||
flush_every_ms: u64,
|
||||
mode: Mode,
|
||||
use_compression: bool,
|
||||
}
|
||||
|
||||
impl PmtreeConfigBuilder {
|
||||
fn new() -> Self {
|
||||
PmtreeConfigBuilder {
|
||||
path: None,
|
||||
temporary: DEFAULT_TEMPORARY,
|
||||
cache_capacity: DEFAULT_CACHE_CAPACITY,
|
||||
flush_every_ms: DEFAULT_FLUSH_EVERY_MS,
|
||||
mode: DEFAULT_MODE,
|
||||
use_compression: DEFAULT_USE_COMPRESSION,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path<P: Into<PathBuf>>(mut self, path: P) -> Self {
|
||||
self.path = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn temporary(mut self, temporary: bool) -> Self {
|
||||
self.temporary = temporary;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cache_capacity(mut self, capacity: u64) -> Self {
|
||||
self.cache_capacity = capacity;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn flush_every_ms(mut self, ms: u64) -> Self {
|
||||
self.flush_every_ms = ms;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mode(mut self, mode: Mode) -> Self {
|
||||
self.mode = mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn use_compression(mut self, compression: bool) -> Self {
|
||||
self.use_compression = compression;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<PmtreeConfig, FromConfigError> {
|
||||
let path = match (self.temporary, self.path) {
|
||||
(true, None) => default_tmp_path(),
|
||||
(false, None) => return Err(FromConfigError::MissingPath),
|
||||
(true, Some(path)) if path.exists() => return Err(FromConfigError::PathExists),
|
||||
(_, Some(path)) => path,
|
||||
};
|
||||
|
||||
let config = Config::new()
|
||||
.temporary(self.temporary)
|
||||
.path(path)
|
||||
.cache_capacity(self.cache_capacity)
|
||||
.flush_every_ms(Some(self.flush_every_ms))
|
||||
.mode(self.mode)
|
||||
.use_compression(self.use_compression);
|
||||
|
||||
Ok(PmtreeConfig(config))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PmtreeConfig(Config);
|
||||
|
||||
impl PmtreeConfig {
|
||||
pub fn builder() -> PmtreeConfigBuilder {
|
||||
PmtreeConfigBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PmtreeConfig {
|
||||
type Err = FromConfigError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let config: Value = serde_json::from_str(s)?;
|
||||
|
||||
let path = config["path"].as_str();
|
||||
let path = path.map(PathBuf::from);
|
||||
let temporary = config["temporary"].as_bool();
|
||||
let cache_capacity = config["cache_capacity"].as_u64();
|
||||
let flush_every_ms = config["flush_every_ms"].as_u64();
|
||||
let mode = match config["mode"].as_str() {
|
||||
Some("HighThroughput") => Mode::HighThroughput,
|
||||
Some("LowSpace") => Mode::LowSpace,
|
||||
_ => Mode::HighThroughput,
|
||||
};
|
||||
let use_compression = config["use_compression"].as_bool();
|
||||
|
||||
if let (Some(true), Some(path)) = (temporary, path.as_ref()) {
|
||||
if path.exists() {
|
||||
return Err(FromConfigError::PathExists);
|
||||
}
|
||||
}
|
||||
|
||||
let config = Config::new()
|
||||
.temporary(temporary.unwrap_or(DEFAULT_TEMPORARY))
|
||||
.path(path.unwrap_or(default_tmp_path()))
|
||||
.cache_capacity(cache_capacity.unwrap_or(DEFAULT_CACHE_CAPACITY))
|
||||
.flush_every_ms(flush_every_ms)
|
||||
.mode(mode)
|
||||
.use_compression(use_compression.unwrap_or(false));
|
||||
Ok(PmtreeConfig(config))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PmtreeConfig {
|
||||
fn default() -> Self {
|
||||
Self::builder()
|
||||
.build()
|
||||
.expect("Default configuration should never fail")
|
||||
}
|
||||
}
|
||||
impl Debug for PmtreeConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for PmtreeConfig {
|
||||
fn clone(&self) -> Self {
|
||||
PmtreeConfig(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZerokitMerkleTree for PmTree {
|
||||
type Proof = PmTreeProof;
|
||||
type Hasher = PoseidonHash;
|
||||
type Config = PmtreeConfig;
|
||||
|
||||
fn default(depth: usize) -> Result<Self, ZerokitMerkleTreeError> {
|
||||
let default_config = PmtreeConfig::default();
|
||||
PmTree::new(depth, Self::Hasher::default_leaf(), default_config)
|
||||
}
|
||||
|
||||
fn new(
|
||||
depth: usize,
|
||||
_default_leaf: FrOf<Self::Hasher>,
|
||||
config: Self::Config,
|
||||
) -> Result<Self, ZerokitMerkleTreeError> {
|
||||
let tree_loaded = pmtree::MerkleTree::load(config.clone().0);
|
||||
let tree = match tree_loaded {
|
||||
Ok(tree) => tree,
|
||||
Err(_) => pmtree::MerkleTree::new(depth, config.0)?,
|
||||
};
|
||||
|
||||
Ok(PmTree {
|
||||
tree,
|
||||
cached_leaves_indices: vec![0; 1 << depth],
|
||||
metadata: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn depth(&self) -> usize {
|
||||
self.tree.depth()
|
||||
}
|
||||
|
||||
fn capacity(&self) -> usize {
|
||||
self.tree.capacity()
|
||||
}
|
||||
|
||||
fn leaves_set(&self) -> usize {
|
||||
self.tree.leaves_set()
|
||||
}
|
||||
|
||||
fn root(&self) -> FrOf<Self::Hasher> {
|
||||
self.tree.root()
|
||||
}
|
||||
|
||||
fn set(
|
||||
&mut self,
|
||||
index: usize,
|
||||
leaf: FrOf<Self::Hasher>,
|
||||
) -> Result<(), ZerokitMerkleTreeError> {
|
||||
self.tree.set(index, leaf)?;
|
||||
self.cached_leaves_indices[index] = 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_range<I: IntoIterator<Item = FrOf<Self::Hasher>>>(
|
||||
&mut self,
|
||||
start: usize,
|
||||
values: I,
|
||||
) -> Result<(), ZerokitMerkleTreeError> {
|
||||
let v = values.into_iter().collect::<Vec<_>>();
|
||||
self.tree.set_range(start, v.clone().into_iter())?;
|
||||
for i in start..v.len() {
|
||||
self.cached_leaves_indices[i] = 1
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(&self, index: usize) -> Result<FrOf<Self::Hasher>, ZerokitMerkleTreeError> {
|
||||
self.tree
|
||||
.get(index)
|
||||
.map_err(ZerokitMerkleTreeError::PmtreeErrorKind)
|
||||
}
|
||||
|
||||
fn get_subtree_root(
|
||||
&self,
|
||||
n: usize,
|
||||
index: usize,
|
||||
) -> Result<FrOf<Self::Hasher>, ZerokitMerkleTreeError> {
|
||||
if n > self.depth() {
|
||||
return Err(ZerokitMerkleTreeError::InvalidLevel);
|
||||
}
|
||||
if index >= self.capacity() {
|
||||
return Err(ZerokitMerkleTreeError::InvalidLeaf);
|
||||
}
|
||||
if n == 0 {
|
||||
Ok(self.root())
|
||||
} else if n == self.depth() {
|
||||
self.get(index)
|
||||
} else {
|
||||
let node = self
|
||||
.tree
|
||||
.get_elem(Key::new(n, index >> (self.depth() - n)))
|
||||
.unwrap();
|
||||
Ok(node)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_empty_leaves_indices(&self) -> Vec<usize> {
|
||||
let next_idx = self.leaves_set();
|
||||
self.cached_leaves_indices
|
||||
.iter()
|
||||
.take(next_idx)
|
||||
.enumerate()
|
||||
.filter(|&(_, &v)| v == 0u8)
|
||||
.map(|(idx, _)| idx)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn override_range<I: IntoIterator<Item = FrOf<Self::Hasher>>, J: IntoIterator<Item = usize>>(
|
||||
&mut self,
|
||||
start: usize,
|
||||
leaves: I,
|
||||
indices: J,
|
||||
) -> Result<(), ZerokitMerkleTreeError> {
|
||||
let leaves = leaves.into_iter().collect::<Vec<_>>();
|
||||
let mut indices = indices.into_iter().collect::<Vec<_>>();
|
||||
indices.sort();
|
||||
|
||||
match (leaves.len(), indices.len()) {
|
||||
(0, 0) => Err(ZerokitMerkleTreeError::InvalidLeaf),
|
||||
(1, 0) => self.set(start, leaves[0]),
|
||||
(0, 1) => self.delete(indices[0]),
|
||||
(_, 0) => self.set_range(start, leaves.into_iter()),
|
||||
(0, _) => self
|
||||
.remove_indices(&indices)
|
||||
.map_err(ZerokitMerkleTreeError::PmtreeErrorKind),
|
||||
(_, _) => self
|
||||
.remove_indices_and_set_leaves(start, leaves, &indices)
|
||||
.map_err(ZerokitMerkleTreeError::PmtreeErrorKind),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_next(&mut self, leaf: FrOf<Self::Hasher>) -> Result<(), ZerokitMerkleTreeError> {
|
||||
self.tree
|
||||
.update_next(leaf)
|
||||
.map_err(ZerokitMerkleTreeError::PmtreeErrorKind)
|
||||
}
|
||||
|
||||
/// Delete a leaf in the merkle tree given its index
|
||||
///
|
||||
/// Deleting a leaf is done by resetting it to its default value. Note that the next_index field
|
||||
/// will not be changed (== previously used index cannot be reused - this to avoid replay
|
||||
/// attacks or unexpected and very hard to tackle issues)
|
||||
fn delete(&mut self, index: usize) -> Result<(), ZerokitMerkleTreeError> {
|
||||
self.tree
|
||||
.delete(index)
|
||||
.map_err(ZerokitMerkleTreeError::PmtreeErrorKind)?;
|
||||
self.cached_leaves_indices[index] = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn proof(&self, index: usize) -> Result<Self::Proof, ZerokitMerkleTreeError> {
|
||||
let proof = self.tree.proof(index)?;
|
||||
Ok(PmTreeProof { proof })
|
||||
}
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
leaf: &FrOf<Self::Hasher>,
|
||||
witness: &Self::Proof,
|
||||
) -> Result<bool, ZerokitMerkleTreeError> {
|
||||
if self.tree.verify(leaf, &witness.proof) {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(ZerokitMerkleTreeError::InvalidWitness)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_metadata(&mut self, metadata: &[u8]) -> Result<(), ZerokitMerkleTreeError> {
|
||||
self.tree
|
||||
.db
|
||||
.put(METADATA_KEY, metadata.to_vec())
|
||||
.map_err(ZerokitMerkleTreeError::PmtreeErrorKind)?;
|
||||
self.metadata = metadata.to_vec();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Result<Vec<u8>, ZerokitMerkleTreeError> {
|
||||
if !self.metadata.is_empty() {
|
||||
return Ok(self.metadata.clone());
|
||||
}
|
||||
// if empty, try searching the db
|
||||
let data = self.tree.db.get(METADATA_KEY)?;
|
||||
|
||||
if data.is_none() {
|
||||
// send empty Metadata
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
Ok(data.unwrap())
|
||||
}
|
||||
|
||||
fn close_db_connection(&mut self) -> Result<(), ZerokitMerkleTreeError> {
|
||||
self.tree
|
||||
.db
|
||||
.close()
|
||||
.map_err(ZerokitMerkleTreeError::PmtreeErrorKind)
|
||||
}
|
||||
}
|
||||
|
||||
type PmTreeHasher = <PmTree as ZerokitMerkleTree>::Hasher;
|
||||
type FrOfPmTreeHasher = FrOf<PmTreeHasher>;
|
||||
|
||||
impl PmTree {
|
||||
fn remove_indices(&mut self, indices: &[usize]) -> Result<(), PmtreeErrorKind> {
|
||||
let start = indices[0];
|
||||
let end = indices.last().unwrap() + 1;
|
||||
|
||||
let new_leaves = (start..end).map(|_| PmTreeHasher::default_leaf());
|
||||
|
||||
self.tree.set_range(start, new_leaves)?;
|
||||
|
||||
for i in start..end {
|
||||
self.cached_leaves_indices[i] = 0
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_indices_and_set_leaves(
|
||||
&mut self,
|
||||
start: usize,
|
||||
leaves: Vec<FrOfPmTreeHasher>,
|
||||
indices: &[usize],
|
||||
) -> Result<(), PmtreeErrorKind> {
|
||||
let min_index = *indices.first().unwrap();
|
||||
let max_index = start + leaves.len();
|
||||
|
||||
let mut set_values = vec![PmTreeHasher::default_leaf(); max_index - min_index];
|
||||
|
||||
for i in min_index..start {
|
||||
if !indices.contains(&i) {
|
||||
let value = self.tree.get(i)?;
|
||||
set_values[i - min_index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (i, &leaf) in leaves.iter().enumerate() {
|
||||
set_values[start - min_index + i] = leaf;
|
||||
}
|
||||
|
||||
self.tree.set_range(start, set_values)?;
|
||||
|
||||
for i in indices {
|
||||
self.cached_leaves_indices[*i] = 0;
|
||||
}
|
||||
|
||||
for i in start..(max_index - min_index) {
|
||||
self.cached_leaves_indices[i] = 1
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZerokitMerkleProof for PmTreeProof {
|
||||
type Index = u8;
|
||||
type Hasher = PoseidonHash;
|
||||
|
||||
fn length(&self) -> usize {
|
||||
self.proof.length()
|
||||
}
|
||||
|
||||
fn leaf_index(&self) -> usize {
|
||||
self.proof.leaf_index()
|
||||
}
|
||||
|
||||
fn get_path_elements(&self) -> Vec<FrOf<Self::Hasher>> {
|
||||
self.proof.get_path_elements()
|
||||
}
|
||||
|
||||
fn get_path_index(&self) -> Vec<Self::Index> {
|
||||
self.proof.get_path_index()
|
||||
}
|
||||
fn compute_root_from(&self, leaf: &FrOf<Self::Hasher>) -> FrOf<Self::Hasher> {
|
||||
self.proof.compute_root_from(leaf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_pmtree_json_config() {
|
||||
let json = r#"
|
||||
{
|
||||
"path": "pmtree-123456",
|
||||
"temporary": false,
|
||||
"cache_capacity": 1073741824,
|
||||
"flush_every_ms": 500,
|
||||
"mode": "HighThroughput",
|
||||
"use_compression": false
|
||||
}"#;
|
||||
|
||||
let _: PmtreeConfig = json.parse().expect("Failed to parse JSON config");
|
||||
|
||||
let _ = PmtreeConfig::builder()
|
||||
.path(default_tmp_path())
|
||||
.temporary(DEFAULT_TEMPORARY)
|
||||
.cache_capacity(DEFAULT_CACHE_CAPACITY)
|
||||
.mode(DEFAULT_MODE)
|
||||
.use_compression(DEFAULT_USE_COMPRESSION)
|
||||
.build()
|
||||
.expect("Failed to build config");
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,32 @@
|
||||
// This crate defines the RLN module default Merkle tree implementation and its Hasher
|
||||
// Implementation inspired by https://github.com/worldcoin/semaphore-rs/blob/d462a4372f1fd9c27610f2acfe4841fab1d396aa/src/poseidon_tree.rs
|
||||
|
||||
// Implementation inspired by https://github.com/worldcoin/semaphore-rs/blob/d462a4372f1fd9c27610f2acfe4841fab1d396aa/src/poseidon_tree.rs (no differences)
|
||||
#![cfg(not(feature = "stateless"))]
|
||||
|
||||
use crate::circuit::Fr;
|
||||
use crate::hashers::poseidon_hash;
|
||||
use cfg_if::cfg_if;
|
||||
use utils::merkle_tree::*;
|
||||
|
||||
#[cfg(feature = "pmtree-ft")]
|
||||
use crate::utils::{bytes_le_to_fr, fr_to_bytes_le};
|
||||
#[cfg(feature = "pmtree-ft")]
|
||||
use pmtree::*;
|
||||
|
||||
// The zerokit RLN default Merkle tree implementation is the OptimalMerkleTree.
|
||||
// To switch to FullMerkleTree implementation, it is enough to enable the fullmerkletree feature
|
||||
// The zerokit RLN default Merkle tree implementation is the PMTree from the vacp2p_pmtree crate
|
||||
// To switch to FullMerkleTree or OptimalMerkleTree, enable the corresponding feature in the Cargo.toml file
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "fullmerkletree")] {
|
||||
use utils::{FullMerkleTree, FullMerkleProof};
|
||||
use crate::hashers::PoseidonHash;
|
||||
|
||||
pub type PoseidonTree = FullMerkleTree<PoseidonHash>;
|
||||
pub type MerkleProof = FullMerkleProof<PoseidonHash>;
|
||||
} else {
|
||||
} else if #[cfg(feature = "optimalmerkletree")] {
|
||||
use utils::{OptimalMerkleTree, OptimalMerkleProof};
|
||||
use crate::hashers::PoseidonHash;
|
||||
|
||||
pub type PoseidonTree = OptimalMerkleTree<PoseidonHash>;
|
||||
pub type MerkleProof = OptimalMerkleProof<PoseidonHash>;
|
||||
}
|
||||
}
|
||||
|
||||
// The zerokit RLN Merkle tree Hasher
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PoseidonHash;
|
||||
|
||||
// The default Hasher trait used by Merkle tree implementation in utils
|
||||
impl utils::merkle_tree::Hasher for PoseidonHash {
|
||||
type Fr = Fr;
|
||||
|
||||
fn default_leaf() -> Self::Fr {
|
||||
Self::Fr::from(0)
|
||||
}
|
||||
|
||||
fn hash(inputs: &[Self::Fr]) -> Self::Fr {
|
||||
poseidon_hash(inputs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "pmtree-ft")]
|
||||
// The pmtree Hasher trait used by pmtree Merkle tree
|
||||
impl pmtree::Hasher for PoseidonHash {
|
||||
type Fr = Fr;
|
||||
|
||||
fn default_leaf() -> Self::Fr {
|
||||
Fr::from(0)
|
||||
}
|
||||
|
||||
fn serialize(value: Self::Fr) -> Value {
|
||||
fr_to_bytes_le(&value)
|
||||
}
|
||||
|
||||
fn deserialize(value: Value) -> Self::Fr {
|
||||
let (fr, _) = bytes_le_to_fr(&value);
|
||||
fr
|
||||
}
|
||||
|
||||
fn hash(inputs: &[Self::Fr]) -> Self::Fr {
|
||||
poseidon_hash(inputs)
|
||||
} else if #[cfg(feature = "pmtree-ft")] {
|
||||
use crate::pm_tree_adapter::{PmTree, PmTreeProof};
|
||||
|
||||
pub type PoseidonTree = PmTree;
|
||||
pub type MerkleProof = PmTreeProof;
|
||||
} else {
|
||||
compile_error!("One of the features `fullmerkletree`, `optimalmerkletree`, or `pmtree-ft` must be enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,54 @@
|
||||
// This crate collects all the underlying primitives used to implement RLN
|
||||
|
||||
use ark_circom::{CircomReduction, WitnessCalculator};
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
use {
|
||||
crate::error::ConversionError,
|
||||
crate::poseidon_tree::PoseidonTree,
|
||||
utils::{ZerokitMerkleProof, ZerokitMerkleTree},
|
||||
};
|
||||
|
||||
use crate::circuit::{calculate_rln_witness, qap::CircomReduction, Curve};
|
||||
use crate::error::{ComputeIdSecretError, ProofError, ProtocolError};
|
||||
use crate::hashers::{hash_to_field_le, poseidon_hash};
|
||||
use crate::public::RLN_IDENTIFIER;
|
||||
use crate::utils::{
|
||||
bytes_be_to_fr, bytes_le_to_fr, bytes_le_to_vec_fr, bytes_le_to_vec_u8, fr_byte_size,
|
||||
fr_to_bytes_le, normalize_usize_le, to_bigint, vec_fr_to_bytes_le, vec_u8_to_bytes_le,
|
||||
FrOrSecret, IdSecret,
|
||||
};
|
||||
use ark_bn254::{Fr, FrConfig};
|
||||
use ark_ff::{AdditiveGroup, Fp, MontBackend};
|
||||
use ark_groth16::{prepare_verifying_key, Groth16, Proof as ArkProof, ProvingKey, VerifyingKey};
|
||||
use ark_relations::r1cs::ConstraintMatrices;
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use ark_std::{rand::thread_rng, UniformRand};
|
||||
use color_eyre::{Report, Result};
|
||||
use num_bigint::BigInt;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::sync::Mutex;
|
||||
#[cfg(debug_assertions)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[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::poseidon_tree::*;
|
||||
use crate::public::RLN_IDENTIFIER;
|
||||
use crate::utils::*;
|
||||
use cfg_if::cfg_if;
|
||||
use utils::{ZerokitMerkleProof, ZerokitMerkleTree};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
///////////////////////////////////////////////////////
|
||||
// RLN Witness data structure and utility functions
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RLNWitnessInput {
|
||||
identity_secret: Fr,
|
||||
#[serde(serialize_with = "ark_se", deserialize_with = "ark_de")]
|
||||
identity_secret: IdSecret,
|
||||
#[serde(serialize_with = "ark_se", deserialize_with = "ark_de")]
|
||||
user_message_limit: Fr,
|
||||
#[serde(serialize_with = "ark_se", deserialize_with = "ark_de")]
|
||||
message_id: Fr,
|
||||
#[serde(serialize_with = "ark_se", deserialize_with = "ark_de")]
|
||||
path_elements: Vec<Fr>,
|
||||
identity_path_index: Vec<u8>,
|
||||
#[serde(serialize_with = "ark_se", deserialize_with = "ark_de")]
|
||||
x: Fr,
|
||||
epoch: Fr,
|
||||
rln_identifier: Fr,
|
||||
#[serde(serialize_with = "ark_se", deserialize_with = "ark_de")]
|
||||
external_nullifier: Fr,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -47,8 +59,7 @@ pub struct RLNProofValues {
|
||||
pub root: Fr,
|
||||
// Public Inputs:
|
||||
pub x: Fr,
|
||||
pub epoch: Fr,
|
||||
pub rln_identifier: Fr,
|
||||
pub external_nullifier: Fr,
|
||||
}
|
||||
|
||||
pub fn serialize_field_element(element: Fr) -> Vec<u8> {
|
||||
@@ -61,14 +72,21 @@ pub fn deserialize_field_element(serialized: Vec<u8>) -> Fr {
|
||||
element
|
||||
}
|
||||
|
||||
pub fn deserialize_identity_pair(serialized: Vec<u8>) -> (Fr, Fr) {
|
||||
pub fn deserialize_identity_pair_le(serialized: Vec<u8>) -> (Fr, Fr) {
|
||||
let (identity_secret_hash, read) = bytes_le_to_fr(&serialized);
|
||||
let (id_commitment, _) = bytes_le_to_fr(&serialized[read..]);
|
||||
|
||||
(identity_secret_hash, id_commitment)
|
||||
}
|
||||
|
||||
pub fn deserialize_identity_tuple(serialized: Vec<u8>) -> (Fr, Fr, Fr, Fr) {
|
||||
pub fn deserialize_identity_pair_be(serialized: Vec<u8>) -> (Fr, Fr) {
|
||||
let (identity_secret_hash, read) = bytes_be_to_fr(&serialized);
|
||||
let (id_commitment, _) = bytes_be_to_fr(&serialized[read..]);
|
||||
|
||||
(identity_secret_hash, id_commitment)
|
||||
}
|
||||
|
||||
pub fn deserialize_identity_tuple_le(serialized: Vec<u8>) -> (Fr, Fr, Fr, Fr) {
|
||||
let mut all_read = 0;
|
||||
|
||||
let (identity_trapdoor, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
@@ -90,25 +108,76 @@ pub fn deserialize_identity_tuple(serialized: Vec<u8>) -> (Fr, Fr, Fr, Fr) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serialize_witness(rln_witness: &RLNWitnessInput) -> Result<Vec<u8>> {
|
||||
let mut serialized: Vec<u8> = Vec::new();
|
||||
pub fn deserialize_identity_tuple_be(serialized: Vec<u8>) -> (Fr, Fr, Fr, Fr) {
|
||||
let mut all_read = 0;
|
||||
|
||||
serialized.append(&mut fr_to_bytes_le(&rln_witness.identity_secret));
|
||||
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.epoch));
|
||||
serialized.append(&mut fr_to_bytes_le(&rln_witness.rln_identifier));
|
||||
let (identity_trapdoor, read) = bytes_be_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (identity_nullifier, read) = bytes_be_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (identity_secret_hash, read) = bytes_be_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (identity_commitment, _) = bytes_be_to_fr(&serialized[all_read..]);
|
||||
|
||||
(
|
||||
identity_trapdoor,
|
||||
identity_nullifier,
|
||||
identity_secret_hash,
|
||||
identity_commitment,
|
||||
)
|
||||
}
|
||||
|
||||
/// Serializes witness
|
||||
///
|
||||
/// # 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>, ProtocolError> {
|
||||
// Check if message_id is within user_message_limit
|
||||
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
|
||||
|
||||
// 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(&rln_witness.identity_secret.to_bytes_le());
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn deserialize_witness(serialized: &[u8]) -> Result<(RLNWitnessInput, usize)> {
|
||||
/// Deserializes witness
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `message_id` is not within `user_message_limit`.
|
||||
pub fn deserialize_witness(serialized: &[u8]) -> Result<(RLNWitnessInput, usize), ProtocolError> {
|
||||
let mut all_read: usize = 0;
|
||||
|
||||
let (identity_secret, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
let (identity_secret, read) = IdSecret::from_bytes_le(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (user_message_limit, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (message_id, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
message_id_range_check(&message_id, &user_message_limit)?;
|
||||
|
||||
let (path_elements, read) = bytes_le_to_vec_fr(&serialized[all_read..])?;
|
||||
all_read += read;
|
||||
|
||||
@@ -118,15 +187,11 @@ pub fn deserialize_witness(serialized: &[u8]) -> Result<(RLNWitnessInput, usize)
|
||||
let (x, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (epoch, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
let (external_nullifier, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (rln_identifier, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
// TODO: check rln_identifier against public::RLN_IDENTIFIER
|
||||
if serialized.len() != all_read {
|
||||
return Err(Report::msg("serialized length is not equal to all_read"));
|
||||
return Err(ProtocolError::InvalidReadLen(serialized.len(), all_read));
|
||||
}
|
||||
|
||||
Ok((
|
||||
@@ -135,8 +200,9 @@ pub fn deserialize_witness(serialized: &[u8]) -> Result<(RLNWitnessInput, usize)
|
||||
path_elements,
|
||||
identity_path_index,
|
||||
x,
|
||||
epoch,
|
||||
rln_identifier,
|
||||
external_nullifier,
|
||||
user_message_limit,
|
||||
message_id,
|
||||
},
|
||||
all_read,
|
||||
))
|
||||
@@ -144,24 +210,41 @@ pub fn deserialize_witness(serialized: &[u8]) -> Result<(RLNWitnessInput, usize)
|
||||
|
||||
// This function deserializes input for kilic's rln generate_proof public API
|
||||
// https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L148
|
||||
// input_data is [ identity_secret<32> | id_index<8> | epoch<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> ]
|
||||
// return value is a rln witness populated according to this information
|
||||
#[cfg(not(feature = "stateless"))]
|
||||
pub fn proof_inputs_to_rln_witness(
|
||||
tree: &mut PoseidonTree,
|
||||
serialized: &[u8],
|
||||
) -> Result<(RLNWitnessInput, usize)> {
|
||||
) -> Result<(RLNWitnessInput, usize), ProtocolError> {
|
||||
let mut all_read: usize = 0;
|
||||
|
||||
let (identity_secret, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
let (identity_secret, read) = IdSecret::from_bytes_le(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let id_index = usize::from_le_bytes(serialized[all_read..all_read + 8].try_into()?);
|
||||
let id_index = usize::try_from(u64::from_le_bytes(
|
||||
serialized[all_read..all_read + 8]
|
||||
.try_into()
|
||||
.map_err(ConversionError::FromSlice)?,
|
||||
))
|
||||
.map_err(ConversionError::ToUsize)?;
|
||||
all_read += 8;
|
||||
|
||||
let (epoch, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
let (user_message_limit, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let signal_len = usize::from_le_bytes(serialized[all_read..all_read + 8].try_into()?);
|
||||
let (message_id, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (external_nullifier, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let signal_len = usize::try_from(u64::from_le_bytes(
|
||||
serialized[all_read..all_read + 8]
|
||||
.try_into()
|
||||
.map_err(ConversionError::FromSlice)?,
|
||||
))
|
||||
.map_err(ConversionError::ToUsize)?;
|
||||
all_read += 8;
|
||||
|
||||
let signal: Vec<u8> = serialized[all_read..all_read + signal_len].to_vec();
|
||||
@@ -170,148 +253,126 @@ pub fn proof_inputs_to_rln_witness(
|
||||
let path_elements = merkle_proof.get_path_elements();
|
||||
let identity_path_index = merkle_proof.get_path_index();
|
||||
|
||||
let x = hash_to_field(&signal);
|
||||
|
||||
let rln_identifier = hash_to_field(RLN_IDENTIFIER);
|
||||
let x = hash_to_field_le(&signal);
|
||||
|
||||
Ok((
|
||||
RLNWitnessInput {
|
||||
identity_secret,
|
||||
path_elements,
|
||||
identity_path_index,
|
||||
user_message_limit,
|
||||
message_id,
|
||||
x,
|
||||
epoch,
|
||||
rln_identifier,
|
||||
external_nullifier,
|
||||
},
|
||||
all_read,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn rln_witness_from_json(input_json_str: &str) -> Result<RLNWitnessInput> {
|
||||
let input_json: serde_json::Value =
|
||||
serde_json::from_str(input_json_str).expect("JSON was not well-formatted");
|
||||
|
||||
let identity_secret = str_to_fr(&input_json["identity_secret"].to_string(), 10)?;
|
||||
|
||||
let path_elements = input_json["path_elements"]
|
||||
.as_array()
|
||||
.ok_or(Report::msg("not an array"))?
|
||||
.iter()
|
||||
.map(|v| str_to_fr(&v.to_string(), 10))
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let identity_path_index_array = input_json["identity_path_index"]
|
||||
.as_array()
|
||||
.ok_or(Report::msg("not an arrray"))?;
|
||||
|
||||
let mut identity_path_index: Vec<u8> = vec![];
|
||||
|
||||
for v in identity_path_index_array {
|
||||
identity_path_index.push(v.as_u64().ok_or(Report::msg("not a u64 value"))? as u8);
|
||||
}
|
||||
|
||||
let x = str_to_fr(&input_json["x"].to_string(), 10)?;
|
||||
|
||||
let epoch = str_to_fr(&input_json["epoch"].to_string(), 16)?;
|
||||
|
||||
let rln_identifier = str_to_fr(&input_json["rln_identifier"].to_string(), 10)?;
|
||||
|
||||
// TODO: check rln_identifier against public::RLN_IDENTIFIER
|
||||
/// Creates [`RLNWitnessInput`] from it's fields.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `message_id` is not within `user_message_limit`.
|
||||
pub fn rln_witness_from_values(
|
||||
identity_secret: IdSecret,
|
||||
path_elements: Vec<Fp<MontBackend<FrConfig, 4>, 4>>,
|
||||
identity_path_index: Vec<u8>,
|
||||
x: Fr,
|
||||
external_nullifier: Fr,
|
||||
user_message_limit: Fr,
|
||||
message_id: Fr,
|
||||
) -> Result<RLNWitnessInput, ProtocolError> {
|
||||
message_id_range_check(&message_id, &user_message_limit)?;
|
||||
|
||||
Ok(RLNWitnessInput {
|
||||
identity_secret,
|
||||
path_elements,
|
||||
identity_path_index,
|
||||
x,
|
||||
epoch,
|
||||
rln_identifier,
|
||||
external_nullifier,
|
||||
user_message_limit,
|
||||
message_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn rln_witness_from_values(
|
||||
identity_secret: Fr,
|
||||
merkle_proof: &MerkleProof,
|
||||
x: Fr,
|
||||
epoch: Fr,
|
||||
//rln_identifier: Fr,
|
||||
) -> RLNWitnessInput {
|
||||
let path_elements = merkle_proof.get_path_elements();
|
||||
let identity_path_index = merkle_proof.get_path_index();
|
||||
let rln_identifier = hash_to_field(RLN_IDENTIFIER);
|
||||
|
||||
RLNWitnessInput {
|
||||
identity_secret,
|
||||
path_elements,
|
||||
identity_path_index,
|
||||
x,
|
||||
epoch,
|
||||
rln_identifier,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_rln_witness(tree_height: usize) -> RLNWitnessInput {
|
||||
pub fn random_rln_witness(tree_depth: usize) -> RLNWitnessInput {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let identity_secret = hash_to_field(&rng.gen::<[u8; 32]>());
|
||||
let x = hash_to_field(&rng.gen::<[u8; 32]>());
|
||||
let epoch = hash_to_field(&rng.gen::<[u8; 32]>());
|
||||
let rln_identifier = hash_to_field(RLN_IDENTIFIER); //hash_to_field(&rng.gen::<[u8; 32]>());
|
||||
let identity_secret = IdSecret::rand(&mut rng);
|
||||
let x = hash_to_field_le(&rng.gen::<[u8; 32]>());
|
||||
let epoch = hash_to_field_le(&rng.gen::<[u8; 32]>());
|
||||
let rln_identifier = hash_to_field_le(RLN_IDENTIFIER);
|
||||
|
||||
let mut path_elements: Vec<Fr> = Vec::new();
|
||||
let mut identity_path_index: Vec<u8> = Vec::new();
|
||||
|
||||
for _ in 0..tree_height {
|
||||
path_elements.push(hash_to_field(&rng.gen::<[u8; 32]>()));
|
||||
for _ in 0..tree_depth {
|
||||
path_elements.push(hash_to_field_le(&rng.gen::<[u8; 32]>()));
|
||||
identity_path_index.push(rng.gen_range(0..2) as u8);
|
||||
}
|
||||
|
||||
let user_message_limit = Fr::from(100);
|
||||
let message_id = Fr::from(1);
|
||||
|
||||
RLNWitnessInput {
|
||||
identity_secret,
|
||||
path_elements,
|
||||
identity_path_index,
|
||||
x,
|
||||
epoch,
|
||||
rln_identifier,
|
||||
external_nullifier: poseidon_hash(&[epoch, rln_identifier]),
|
||||
user_message_limit,
|
||||
message_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proof_values_from_witness(rln_witness: &RLNWitnessInput) -> RLNProofValues {
|
||||
pub fn proof_values_from_witness(
|
||||
rln_witness: &RLNWitnessInput,
|
||||
) -> Result<RLNProofValues, ProtocolError> {
|
||||
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
|
||||
|
||||
// y share
|
||||
let external_nullifier = poseidon_hash(&[rln_witness.epoch, rln_witness.rln_identifier]);
|
||||
let a_0 = rln_witness.identity_secret;
|
||||
let a_1 = poseidon_hash(&[a_0, external_nullifier]);
|
||||
let y = a_0 + rln_witness.x * a_1;
|
||||
let a_0 = &rln_witness.identity_secret;
|
||||
let mut to_hash = [
|
||||
*(a_0.clone()),
|
||||
rln_witness.external_nullifier,
|
||||
rln_witness.message_id,
|
||||
];
|
||||
let a_1 = poseidon_hash(&to_hash);
|
||||
let y = *(a_0.clone()) + rln_witness.x * a_1;
|
||||
|
||||
// Nullifier
|
||||
let nullifier = poseidon_hash(&[a_1]);
|
||||
to_hash[0].zeroize();
|
||||
|
||||
// Merkle tree root computations
|
||||
let root = compute_tree_root(
|
||||
&rln_witness.identity_secret,
|
||||
&rln_witness.user_message_limit,
|
||||
&rln_witness.path_elements,
|
||||
&rln_witness.identity_path_index,
|
||||
true,
|
||||
);
|
||||
|
||||
RLNProofValues {
|
||||
Ok(RLNProofValues {
|
||||
y,
|
||||
nullifier,
|
||||
root,
|
||||
x: rln_witness.x,
|
||||
epoch: rln_witness.epoch,
|
||||
rln_identifier: rln_witness.rln_identifier,
|
||||
}
|
||||
external_nullifier: rln_witness.external_nullifier,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.epoch));
|
||||
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.append(&mut fr_to_bytes_le(&rln_proof_values.rln_identifier));
|
||||
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
|
||||
}
|
||||
@@ -324,7 +385,7 @@ pub fn deserialize_proof_values(serialized: &[u8]) -> (RLNProofValues, usize) {
|
||||
let (root, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (epoch, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
let (external_nullifier, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (x, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
@@ -336,46 +397,55 @@ pub fn deserialize_proof_values(serialized: &[u8]) -> (RLNProofValues, usize) {
|
||||
let (nullifier, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
let (rln_identifier, read) = bytes_le_to_fr(&serialized[all_read..]);
|
||||
all_read += read;
|
||||
|
||||
(
|
||||
RLNProofValues {
|
||||
y,
|
||||
nullifier,
|
||||
root,
|
||||
x,
|
||||
epoch,
|
||||
rln_identifier,
|
||||
external_nullifier,
|
||||
},
|
||||
all_read,
|
||||
)
|
||||
}
|
||||
|
||||
// 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,
|
||||
identity_secret: IdSecret,
|
||||
id_index: usize,
|
||||
epoch: Fr,
|
||||
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(&epoch));
|
||||
serialized.append(&mut normalize_usize(signal.len()));
|
||||
serialized.append(&mut signal.to_vec());
|
||||
serialized.extend_from_slice(&identity_secret.to_bytes_le());
|
||||
serialized.extend_from_slice(&normalize_usize_le(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_le(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_le(signal.len()));
|
||||
serialized.extend_from_slice(signal);
|
||||
|
||||
serialized
|
||||
}
|
||||
@@ -385,15 +455,16 @@ pub fn prepare_verify_input(proof_data: Vec<u8>, signal: &[u8]) -> Vec<u8> {
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
pub fn compute_tree_root(
|
||||
leaf: &Fr,
|
||||
identity_secret: &IdSecret,
|
||||
user_message_limit: &Fr,
|
||||
path_elements: &[Fr],
|
||||
identity_path_index: &[u8],
|
||||
hash_leaf: bool,
|
||||
) -> Fr {
|
||||
let mut root = *leaf;
|
||||
if hash_leaf {
|
||||
root = poseidon_hash(&[root]);
|
||||
}
|
||||
let mut to_hash = [*identity_secret.clone()];
|
||||
let id_commitment = poseidon_hash(&to_hash);
|
||||
to_hash[0].zeroize();
|
||||
|
||||
let mut root = poseidon_hash(&[id_commitment, *user_message_limit]);
|
||||
|
||||
for i in 0..identity_path_index.len() {
|
||||
if identity_path_index[i] == 0 {
|
||||
@@ -413,10 +484,12 @@ pub fn compute_tree_root(
|
||||
// Generates a tuple (identity_secret_hash, id_commitment) where
|
||||
// identity_secret_hash is random and id_commitment = PoseidonHash(identity_secret_hash)
|
||||
// RNG is instantiated using thread_rng()
|
||||
pub fn keygen() -> (Fr, Fr) {
|
||||
pub fn keygen() -> (IdSecret, Fr) {
|
||||
let mut rng = thread_rng();
|
||||
let identity_secret_hash = Fr::rand(&mut rng);
|
||||
let id_commitment = poseidon_hash(&[identity_secret_hash]);
|
||||
let identity_secret_hash = IdSecret::rand(&mut rng);
|
||||
let mut to_hash = [*identity_secret_hash.clone()];
|
||||
let id_commitment = poseidon_hash(&to_hash);
|
||||
to_hash[0].zeroize();
|
||||
(identity_secret_hash, id_commitment)
|
||||
}
|
||||
|
||||
@@ -487,8 +560,7 @@ pub fn extended_seeded_keygen(signal: &[u8]) -> (Fr, Fr, Fr, Fr) {
|
||||
pub fn compute_id_secret(
|
||||
share1: (Fr, Fr),
|
||||
share2: (Fr, Fr),
|
||||
external_nullifier: Fr,
|
||||
) -> Result<Fr, String> {
|
||||
) -> Result<IdSecret, ComputeIdSecretError> {
|
||||
// Assuming a0 is the identity secret and a1 = poseidonHash([a0, external_nullifier]),
|
||||
// a (x,y) share satisfies the following relation
|
||||
// y = a_0 + x * a_1
|
||||
@@ -498,17 +570,16 @@ pub fn compute_id_secret(
|
||||
// If the two input shares were computed for the same external_nullifier and identity secret, we can recover the latter
|
||||
// y1 = a_0 + x1 * a_1
|
||||
// y2 = a_0 + x2 * a_1
|
||||
let a_1 = (y1 - y2) / (x1 - x2);
|
||||
let a_0 = y1 - x1 * a_1;
|
||||
|
||||
// If shares come from the same polynomial, a0 is correctly recovered and a1 = poseidonHash([a0, external_nullifier])
|
||||
let computed_a_1 = poseidon_hash(&[a_0, external_nullifier]);
|
||||
if (x1 - x2) != Fr::ZERO {
|
||||
let a_1 = (y1 - y2) / (x1 - x2);
|
||||
let mut a_0 = y1 - x1 * a_1;
|
||||
|
||||
if a_1 == computed_a_1 {
|
||||
// We successfully recovered the identity secret
|
||||
Ok(a_0)
|
||||
// If shares come from the same polynomial, a0 is correctly recovered and a1 = poseidonHash([a0, external_nullifier])
|
||||
let id_secret = IdSecret::from(&mut a_0);
|
||||
Ok(id_secret)
|
||||
} else {
|
||||
Err("Cannot recover identity_secret_hash from provided shares".into())
|
||||
Err(ComputeIdSecretError::DivisionByZero)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,19 +587,9 @@ pub fn compute_id_secret(
|
||||
// zkSNARK utility functions
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ProofError {
|
||||
#[error("Error reading circuit key: {0}")]
|
||||
CircuitKeyError(#[from] Report),
|
||||
#[error("Error producing witness: {0}")]
|
||||
WitnessError(Report),
|
||||
#[error("Error producing proof: {0}")]
|
||||
SynthesisError(#[from] SynthesisError),
|
||||
}
|
||||
|
||||
fn calculate_witness_element<E: ark_ec::pairing::Pairing>(
|
||||
witness: Vec<BigInt>,
|
||||
) -> Result<Vec<E::ScalarField>> {
|
||||
) -> Result<Vec<E::ScalarField>, ProtocolError> {
|
||||
use ark_ff::PrimeField;
|
||||
let modulus = <E::ScalarField as PrimeField>::MODULUS;
|
||||
|
||||
@@ -541,9 +602,9 @@ fn calculate_witness_element<E: ark_ec::pairing::Pairing>(
|
||||
modulus.into()
|
||||
- w.abs()
|
||||
.to_biguint()
|
||||
.ok_or(Report::msg("not a biguint value"))?
|
||||
.ok_or(ProtocolError::BigUintConversion(w))?
|
||||
} else {
|
||||
w.to_biguint().ok_or(Report::msg("not a biguint value"))?
|
||||
w.to_biguint().ok_or(ProtocolError::BigUintConversion(w))?
|
||||
};
|
||||
witness_vec.push(E::ScalarField::from(w))
|
||||
}
|
||||
@@ -556,13 +617,12 @@ 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)?;
|
||||
let full_assignment = calculate_witness_element::<Curve>(witness)?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(test)]
|
||||
println!("witness generation took: {:.2?}", now.elapsed());
|
||||
|
||||
// Random Values
|
||||
@@ -571,7 +631,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(
|
||||
@@ -584,41 +644,55 @@ pub fn generate_proof_with_witness(
|
||||
full_assignment.as_slice(),
|
||||
)?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(test)]
|
||||
println!("proof generation took: {:.2?}", now.elapsed());
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
/// Formats inputs for witness calculation
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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>); 6]> {
|
||||
// We confert the path indexes to field elements
|
||||
// TODO: check if necessary
|
||||
let mut path_elements = Vec::new();
|
||||
) -> Result<[(&str, Vec<FrOrSecret>); 7], ProtocolError> {
|
||||
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
|
||||
|
||||
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([
|
||||
(
|
||||
"identity_secret",
|
||||
vec![to_bigint(&rln_witness.identity_secret)?],
|
||||
"identitySecret",
|
||||
vec![rln_witness.identity_secret.clone().into()],
|
||||
),
|
||||
("path_elements", path_elements),
|
||||
("identity_path_index", identity_path_index),
|
||||
("x", vec![to_bigint(&rln_witness.x)?]),
|
||||
("epoch", vec![to_bigint(&rln_witness.epoch)?]),
|
||||
(
|
||||
"rln_identifier",
|
||||
vec![to_bigint(&rln_witness.rln_identifier)?],
|
||||
"userMessageLimit",
|
||||
vec![rln_witness.user_message_limit.into()],
|
||||
),
|
||||
("messageId", vec![rln_witness.message_id.into()]),
|
||||
(
|
||||
"pathElements",
|
||||
rln_witness
|
||||
.path_elements
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
),
|
||||
(
|
||||
"identityPathIndex",
|
||||
identity_path_index.into_iter().map(Into::into).collect(),
|
||||
),
|
||||
("x", vec![rln_witness.x.into()]),
|
||||
(
|
||||
"externalNullifier",
|
||||
vec![rln_witness.external_nullifier.into()],
|
||||
),
|
||||
])
|
||||
}
|
||||
@@ -629,34 +703,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
|
||||
@@ -665,9 +725,8 @@ 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,
|
||||
r,
|
||||
@@ -678,7 +737,7 @@ pub fn generate_proof(
|
||||
full_assignment.as_slice(),
|
||||
)?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(test)]
|
||||
println!("proof generation took: {:.2?}", now.elapsed());
|
||||
|
||||
Ok(proof)
|
||||
@@ -701,8 +760,7 @@ pub fn verify_proof(
|
||||
proof_values.root,
|
||||
proof_values.nullifier,
|
||||
proof_values.x,
|
||||
proof_values.epoch,
|
||||
proof_values.rln_identifier,
|
||||
proof_values.external_nullifier,
|
||||
];
|
||||
|
||||
// Check that the proof is valid
|
||||
@@ -710,26 +768,81 @@ 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)
|
||||
}
|
||||
|
||||
/// Get CIRCOM JSON inputs
|
||||
// auxiliary function for serialisation Fr to json using ark serilize
|
||||
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_compressed(&mut bytes)
|
||||
.map_err(serde::ser::Error::custom)?;
|
||||
s.serialize_bytes(&bytes)
|
||||
}
|
||||
|
||||
// auxiliary function for deserialisation Fr to json using ark serilize
|
||||
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_compressed_unchecked(s.as_slice());
|
||||
a.map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
/// Converts a JSON value into [`RLNWitnessInput`] object.
|
||||
///
|
||||
/// Returns a JSON object containing the inputs necessary to calculate
|
||||
/// the witness with CIRCOM on javascript
|
||||
pub fn get_json_inputs(rln_witness: &RLNWitnessInput) -> Result<serde_json::Value> {
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `rln_witness.message_id` is not within `rln_witness.user_message_limit`.
|
||||
pub fn rln_witness_from_json(
|
||||
input_json: serde_json::Value,
|
||||
) -> Result<RLNWitnessInput, ProtocolError> {
|
||||
let rln_witness: RLNWitnessInput = serde_json::from_value(input_json).unwrap();
|
||||
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
|
||||
|
||||
Ok(rln_witness)
|
||||
}
|
||||
|
||||
/// Converts a [`RLNWitnessInput`] object to the corresponding JSON serialization.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `message_id` is not within `user_message_limit`.
|
||||
pub fn rln_witness_to_json(
|
||||
rln_witness: &RLNWitnessInput,
|
||||
) -> Result<serde_json::Value, ProtocolError> {
|
||||
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
|
||||
|
||||
let rln_witness_json = serde_json::to_value(rln_witness)?;
|
||||
Ok(rln_witness_json)
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// Returns an error if `message_id` is not within `user_message_limit`.
|
||||
pub fn rln_witness_to_bigint_json(
|
||||
rln_witness: &RLNWitnessInput,
|
||||
) -> Result<serde_json::Value, ProtocolError> {
|
||||
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
|
||||
|
||||
let mut path_elements = Vec::new();
|
||||
|
||||
for v in rln_witness.path_elements.iter() {
|
||||
path_elements.push(to_bigint(v)?.to_str_radix(10));
|
||||
path_elements.push(to_bigint(v).to_str_radix(10));
|
||||
}
|
||||
|
||||
let mut identity_path_index = Vec::new();
|
||||
@@ -739,13 +852,27 @@ pub fn get_json_inputs(rln_witness: &RLNWitnessInput) -> Result<serde_json::Valu
|
||||
.for_each(|v| identity_path_index.push(BigInt::from(*v).to_str_radix(10)));
|
||||
|
||||
let inputs = serde_json::json!({
|
||||
"identity_secret": to_bigint(&rln_witness.identity_secret)?.to_str_radix(10),
|
||||
"path_elements": path_elements,
|
||||
"identity_path_index": identity_path_index,
|
||||
"x": to_bigint(&rln_witness.x)?.to_str_radix(10),
|
||||
"epoch": format!("0x{:064x}", to_bigint(&rln_witness.epoch)?),
|
||||
"rln_identifier": to_bigint(&rln_witness.rln_identifier)?.to_str_radix(10),
|
||||
"identitySecret": to_bigint(&rln_witness.identity_secret).to_str_radix(10),
|
||||
"userMessageLimit": to_bigint(&rln_witness.user_message_limit).to_str_radix(10),
|
||||
"messageId": to_bigint(&rln_witness.message_id).to_str_radix(10),
|
||||
"pathElements": path_elements,
|
||||
"identityPathIndex": identity_path_index,
|
||||
"x": to_bigint(&rln_witness.x).to_str_radix(10),
|
||||
"externalNullifier": to_bigint(&rln_witness.external_nullifier).to_str_radix(10),
|
||||
});
|
||||
|
||||
Ok(inputs)
|
||||
}
|
||||
|
||||
pub fn message_id_range_check(
|
||||
message_id: &Fr,
|
||||
user_message_limit: &Fr,
|
||||
) -> Result<(), ProtocolError> {
|
||||
if message_id > user_message_limit {
|
||||
return Err(ProtocolError::InvalidMessageId(
|
||||
*message_id,
|
||||
*user_message_limit,
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
1835
rln/src/public.rs
1835
rln/src/public.rs
File diff suppressed because it is too large
Load Diff
1286
rln/src/public_api_tests.rs
Normal file
1286
rln/src/public_api_tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
463
rln/src/utils.rs
463
rln/src/utils.rs
@@ -1,25 +1,34 @@
|
||||
// This crate provides cross-module useful utilities (mainly type conversions) not necessarily specific to RLN
|
||||
|
||||
use crate::circuit::Fr;
|
||||
use crate::error::ConversionError;
|
||||
use ark_ff::PrimeField;
|
||||
use color_eyre::{Report, Result};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use ark_std::UniformRand;
|
||||
use num_bigint::{BigInt, BigUint};
|
||||
use num_traits::Num;
|
||||
use std::iter::Extend;
|
||||
use rand::Rng;
|
||||
use ruint::aliases::U256;
|
||||
use serde_json::json;
|
||||
use std::io::Cursor;
|
||||
use std::ops::Deref;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
pub fn to_bigint(el: &Fr) -> Result<BigInt> {
|
||||
let res: BigUint = (*el).try_into()?;
|
||||
Ok(res.into())
|
||||
#[inline(always)]
|
||||
pub fn to_bigint(el: &Fr) -> BigInt {
|
||||
BigUint::from(*el).into()
|
||||
}
|
||||
|
||||
pub fn fr_byte_size() -> usize {
|
||||
#[inline(always)]
|
||||
pub const fn fr_byte_size() -> usize {
|
||||
let mbs = <Fr as PrimeField>::MODULUS_BIT_SIZE;
|
||||
((mbs + 64 - (mbs % 64)) / 8) as usize
|
||||
}
|
||||
|
||||
pub fn str_to_fr(input: &str, radix: u32) -> Result<Fr> {
|
||||
#[inline(always)]
|
||||
pub fn str_to_fr(input: &str, radix: u32) -> Result<Fr, ConversionError> {
|
||||
if !(radix == 10 || radix == 16) {
|
||||
return Err(Report::msg("wrong radix"));
|
||||
return Err(ConversionError::WrongRadix);
|
||||
}
|
||||
|
||||
// We remove any quote present and we trim
|
||||
@@ -28,13 +37,14 @@ pub fn str_to_fr(input: &str, radix: u32) -> Result<Fr> {
|
||||
input_clean = input_clean.trim().to_string();
|
||||
|
||||
if radix == 10 {
|
||||
Ok(BigUint::from_str_radix(&input_clean, radix)?.try_into()?)
|
||||
Ok(BigUint::from_str_radix(&input_clean, radix)?.into())
|
||||
} else {
|
||||
input_clean = input_clean.replace("0x", "");
|
||||
Ok(BigUint::from_str_radix(&input_clean, radix)?.try_into()?)
|
||||
Ok(BigUint::from_str_radix(&input_clean, radix)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bytes_le_to_fr(input: &[u8]) -> (Fr, usize) {
|
||||
let el_size = fr_byte_size();
|
||||
(
|
||||
@@ -43,6 +53,7 @@ pub fn bytes_le_to_fr(input: &[u8]) -> (Fr, usize) {
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bytes_be_to_fr(input: &[u8]) -> (Fr, usize) {
|
||||
let el_size = fr_byte_size();
|
||||
(
|
||||
@@ -51,264 +62,360 @@ pub fn bytes_be_to_fr(input: &[u8]) -> (Fr, usize) {
|
||||
)
|
||||
}
|
||||
|
||||
#[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.resize(fr_byte_size(), 0);
|
||||
res
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
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);
|
||||
// For BE, insert 0 at the start of the Vec (see also fr_to_bytes_le comments)
|
||||
let to_insert_count = fr_byte_size().saturating_sub(res.len());
|
||||
if to_insert_count > 0 {
|
||||
// Insert multi 0 at index 0
|
||||
res.splice(0..0, std::iter::repeat_n(0, to_insert_count));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
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(input.len().to_le_bytes().to_vec());
|
||||
#[inline(always)]
|
||||
pub fn vec_fr_to_bytes_le(input: &[Fr]) -> Vec<u8> {
|
||||
// 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_le(input.len()));
|
||||
|
||||
// We store each element
|
||||
input.iter().for_each(|el| bytes.extend(fr_to_bytes_le(el)));
|
||||
for el in input {
|
||||
bytes.extend_from_slice(&fr_to_bytes_le(el));
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
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(input.len().to_be_bytes().to_vec());
|
||||
#[inline(always)]
|
||||
pub fn vec_fr_to_bytes_be(input: &[Fr]) -> Vec<u8> {
|
||||
// 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_be(input.len()));
|
||||
|
||||
// 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_be(el));
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
bytes
|
||||
}
|
||||
|
||||
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(input.len().to_le_bytes().to_vec());
|
||||
#[inline(always)]
|
||||
pub fn vec_u8_to_bytes_le(input: &[u8]) -> Vec<u8> {
|
||||
// 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);
|
||||
// We store the vector length
|
||||
bytes.extend_from_slice(&normalize_usize_le(input.len()));
|
||||
|
||||
Ok(bytes)
|
||||
// We store the input
|
||||
bytes.extend_from_slice(input);
|
||||
|
||||
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(input.len().to_be_bytes().to_vec());
|
||||
#[inline(always)]
|
||||
pub fn vec_u8_to_bytes_be(input: &[u8]) -> Vec<u8> {
|
||||
// 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);
|
||||
// We store the vector length
|
||||
bytes.extend_from_slice(&normalize_usize_be(input.len()));
|
||||
|
||||
Ok(bytes)
|
||||
// We store the input
|
||||
bytes.extend_from_slice(input);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn bytes_le_to_vec_u8(input: &[u8]) -> Result<(Vec<u8>, usize)> {
|
||||
#[inline(always)]
|
||||
pub fn bytes_le_to_vec_u8(input: &[u8]) -> Result<(Vec<u8>, usize), ConversionError> {
|
||||
let mut read: usize = 0;
|
||||
|
||||
let len = usize::from_le_bytes(input[0..8].try_into()?);
|
||||
if input.len() < 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let len = usize::try_from(u64::from_le_bytes(input[0..8].try_into()?))?;
|
||||
read += 8;
|
||||
|
||||
if input.len() < 8 + len {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8 + len,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let res = input[8..8 + len].to_vec();
|
||||
read += res.len();
|
||||
|
||||
Ok((res, read))
|
||||
}
|
||||
|
||||
pub fn bytes_be_to_vec_u8(input: &[u8]) -> Result<(Vec<u8>, usize)> {
|
||||
#[inline(always)]
|
||||
pub fn bytes_be_to_vec_u8(input: &[u8]) -> Result<(Vec<u8>, usize), ConversionError> {
|
||||
let mut read: usize = 0;
|
||||
|
||||
let len = usize::from_be_bytes(input[0..8].try_into()?);
|
||||
if input.len() < 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let len = usize::try_from(u64::from_be_bytes(input[0..8].try_into()?))?;
|
||||
read += 8;
|
||||
|
||||
if input.len() < 8 + len {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8 + len,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let res = input[8..8 + len].to_vec();
|
||||
|
||||
read += res.len();
|
||||
|
||||
Ok((res, read))
|
||||
}
|
||||
|
||||
pub fn bytes_le_to_vec_fr(input: &[u8]) -> Result<(Vec<Fr>, usize)> {
|
||||
#[inline(always)]
|
||||
pub fn bytes_le_to_vec_fr(input: &[u8]) -> Result<(Vec<Fr>, usize), ConversionError> {
|
||||
let mut read: usize = 0;
|
||||
let mut res: Vec<Fr> = Vec::new();
|
||||
|
||||
let len = usize::from_le_bytes(input[0..8].try_into()?);
|
||||
if input.len() < 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let len = usize::try_from(u64::from_le_bytes(input[0..8].try_into()?))?;
|
||||
read += 8;
|
||||
|
||||
let el_size = fr_byte_size();
|
||||
if input.len() < 8 + len * el_size {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8 + len * el_size,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let mut res: Vec<Fr> = Vec::with_capacity(len);
|
||||
for i in 0..len {
|
||||
let (curr_el, _) = bytes_le_to_fr(&input[8 + el_size * i..8 + el_size * (i + 1)]);
|
||||
res.push(curr_el);
|
||||
read += el_size;
|
||||
}
|
||||
|
||||
Ok((res, read))
|
||||
}
|
||||
|
||||
pub fn bytes_be_to_vec_fr(input: &[u8]) -> Result<(Vec<Fr>, usize)> {
|
||||
#[inline(always)]
|
||||
pub fn bytes_be_to_vec_fr(input: &[u8]) -> Result<(Vec<Fr>, usize), ConversionError> {
|
||||
let mut read: usize = 0;
|
||||
let mut res: Vec<Fr> = Vec::new();
|
||||
|
||||
let len = usize::from_be_bytes(input[0..8].try_into()?);
|
||||
if input.len() < 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let len = usize::try_from(u64::from_be_bytes(input[0..8].try_into()?))?;
|
||||
read += 8;
|
||||
|
||||
let el_size = fr_byte_size();
|
||||
if input.len() < 8 + len * el_size {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8 + len * el_size,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let mut res: Vec<Fr> = Vec::with_capacity(len);
|
||||
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
|
||||
}
|
||||
|
||||
/* 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()
|
||||
#[inline(always)]
|
||||
pub fn bytes_le_to_vec_usize(input: &[u8]) -> Result<Vec<usize>, ConversionError> {
|
||||
if input.len() < 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let nof_elem = usize::try_from(u64::from_le_bytes(input[0..8].try_into()?))?;
|
||||
if nof_elem == 0 {
|
||||
Ok(vec![])
|
||||
} else {
|
||||
let input_clean = input_clean.replace("0x", "");
|
||||
Field::from_str(&format!("{:0>64}", &input_clean)).unwrap()
|
||||
if input.len() < 8 + nof_elem * 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8 + nof_elem * 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let elements: Vec<usize> = input[8..]
|
||||
.chunks(8)
|
||||
.take(nof_elem)
|
||||
.map(|ch| usize::from_le_bytes(ch[0..8].try_into().unwrap()))
|
||||
.collect();
|
||||
Ok(elements)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_le_to_field(input: &[u8]) -> (Field, usize) {
|
||||
let (fr_el, read) = bytes_le_to_fr(input);
|
||||
(to_field(&fr_el), read)
|
||||
#[inline(always)]
|
||||
pub fn bytes_be_to_vec_usize(input: &[u8]) -> Result<Vec<usize>, ConversionError> {
|
||||
if input.len() < 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let nof_elem = usize::try_from(u64::from_be_bytes(input[0..8].try_into()?))?;
|
||||
if nof_elem == 0 {
|
||||
Ok(vec![])
|
||||
} else {
|
||||
if input.len() < 8 + nof_elem * 8 {
|
||||
return Err(ConversionError::InsufficientData {
|
||||
expected: 8 + nof_elem * 8,
|
||||
actual: input.len(),
|
||||
});
|
||||
}
|
||||
let elements: Vec<usize> = input[8..]
|
||||
.chunks(8)
|
||||
.take(nof_elem)
|
||||
.map(|ch| usize::from_be_bytes(ch[0..8].try_into().unwrap()))
|
||||
.collect();
|
||||
Ok(elements)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_be_to_field(input: &[u8]) -> (Field, usize) {
|
||||
let (fr_el, read) = bytes_be_to_fr(input);
|
||||
(to_field(&fr_el), read)
|
||||
/// 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_le(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
|
||||
}
|
||||
|
||||
|
||||
pub fn field_to_bytes_le(input: &Field) -> Vec<u8> {
|
||||
fr_to_bytes_le(&to_fr(input))
|
||||
/// 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_be(input: usize) -> [u8; 8] {
|
||||
let mut bytes = [0u8; 8];
|
||||
let input_bytes = input.to_be_bytes();
|
||||
bytes[..input_bytes.len()].copy_from_slice(&input_bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn field_to_bytes_be(input: &Field) -> Vec<u8> {
|
||||
fr_to_bytes_be(&to_fr(input))
|
||||
#[inline(always)] // using for test
|
||||
pub fn generate_input_buffer() -> Cursor<String> {
|
||||
Cursor::new(json!({}).to_string())
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Zeroize, ZeroizeOnDrop, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize,
|
||||
)]
|
||||
pub struct IdSecret(ark_bn254::Fr);
|
||||
|
||||
pub fn vec_field_to_bytes_le(input: &[Field]) -> Vec<u8> {
|
||||
vec_fr_to_bytes_le(&vec_field_to_fr(input))
|
||||
impl IdSecret {
|
||||
pub fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self {
|
||||
let mut fr = Fr::rand(rng);
|
||||
let res = Self::from(&mut fr);
|
||||
// No need to zeroize fr (already zeroiz'ed in from implementation)
|
||||
#[allow(clippy::let_and_return)]
|
||||
res
|
||||
}
|
||||
|
||||
pub fn from_bytes_le(input: &[u8]) -> (Self, usize) {
|
||||
let el_size = fr_byte_size();
|
||||
let b_uint = BigUint::from_bytes_le(&input[0..el_size]);
|
||||
let mut fr = Fr::from(b_uint);
|
||||
let res = IdSecret::from(&mut fr);
|
||||
// Note: no zeroize on b_uint as it has been moved
|
||||
(res, el_size)
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes_le(&self) -> Zeroizing<Vec<u8>> {
|
||||
let input_biguint: BigUint = self.0.into();
|
||||
let mut res = input_biguint.to_bytes_le();
|
||||
res.resize(fr_byte_size(), 0);
|
||||
Zeroizing::new(res)
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes_be(&self) -> Zeroizing<Vec<u8>> {
|
||||
let input_biguint: BigUint = self.0.into();
|
||||
let mut res = input_biguint.to_bytes_be();
|
||||
let to_insert_count = fr_byte_size().saturating_sub(res.len());
|
||||
if to_insert_count > 0 {
|
||||
// Insert multi 0 at index 0
|
||||
res.splice(0..0, std::iter::repeat_n(0, to_insert_count));
|
||||
}
|
||||
Zeroizing::new(res)
|
||||
}
|
||||
|
||||
/// Warning: this can leak the secret value
|
||||
/// Warning: Leaked value is of type 'U256' which implement Copy (every copy will not be zeroized)
|
||||
pub(crate) fn to_u256(&self) -> U256 {
|
||||
let mut big_int = self.0.into_bigint();
|
||||
let res = U256::from_limbs(big_int.0);
|
||||
big_int.zeroize();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vec_field_to_bytes_be(input: &[Field]) -> Vec<u8> {
|
||||
vec_fr_to_bytes_be(&vec_field_to_fr(input))
|
||||
impl From<&mut Fr> for IdSecret {
|
||||
fn from(value: &mut Fr) -> Self {
|
||||
let id_secret = Self(*value);
|
||||
value.zeroize();
|
||||
id_secret
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IdSecret {
|
||||
type Target = Fr;
|
||||
|
||||
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)
|
||||
/// Deref to &Fr
|
||||
///
|
||||
/// Warning: this can leak the secret value
|
||||
/// Warning: Leaked value is of type 'Fr' which implement Copy (every copy will not be zeroized)
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
|
||||
pub enum FrOrSecret {
|
||||
IdSecret(IdSecret),
|
||||
Fr(Fr),
|
||||
}
|
||||
|
||||
// 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)))
|
||||
impl From<Fr> for FrOrSecret {
|
||||
fn from(value: Fr) -> Self {
|
||||
FrOrSecret::Fr(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mul(a: &Field, b: &Field) -> Field {
|
||||
to_field(&(to_fr(a) * to_fr(b)))
|
||||
impl From<IdSecret> for FrOrSecret {
|
||||
fn from(value: IdSecret) -> Self {
|
||||
FrOrSecret::IdSecret(value)
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
*/
|
||||
|
||||
1453
rln/tests/ffi.rs
1453
rln/tests/ffi.rs
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user