diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e74d5ed..051fcfae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: matrix: platform: [ubuntu-latest, macos-latest] crate: [rln] - feature: ["default", "stateless", "multi-message-id"] + feature: ["default", "stateless"] runs-on: ${{ matrix.platform }} timeout-minutes: 60 @@ -83,7 +83,7 @@ jobs: matrix: platform: [ubuntu-latest, macos-latest] crate: [rln-wasm] - feature: ["default", "multi-message-id"] + feature: ["default"] runs-on: ${{ matrix.platform }} timeout-minutes: 60 @@ -96,26 +96,13 @@ jobs: - name: Install dependencies run: make installdeps - name: Build rln-wasm - run: | - if [ ${{ matrix.feature }} == default ]; then - cargo make build - else - FEATURE=$(echo "${{ matrix.feature }}" | tr '-' '_') - cargo make build_${FEATURE} - fi + run: cargo make build working-directory: ${{ matrix.crate }} - name: Test rln-wasm on node - if: matrix.feature == 'default' run: cargo make test --release working-directory: ${{ matrix.crate }} - name: Test rln-wasm on browser - run: | - if [ ${{ matrix.feature }} == default ]; then - cargo make test_browser --release - else - FEATURE=$(echo "${{ matrix.feature }}" | tr '-' '_') - cargo make test_${FEATURE} --release - fi + run: cargo make test_browser --release working-directory: ${{ matrix.crate }} rln-wasm-parallel-test: @@ -206,11 +193,7 @@ jobs: run: | cargo clippy --all-targets --tests --release --features=stateless --no-default-features -- -D warnings working-directory: ${{ matrix.crate }} - - name: Check clippy multi-message-id feature - if: (success() || failure()) && (matrix.crate == 'rln') - run: | - cargo clippy --all-targets --tests --release --features=multi-message-id -- -D warnings - working-directory: ${{ matrix.crate }} + benchmark-utils: # run only on ready PRs diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index 0a76c58b..e1bfc822 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -13,20 +13,12 @@ jobs: features: - ["stateless"] - ["stateless", "parallel"] - - ["stateless", "multi-message-id"] - - ["stateless", "parallel", "multi-message-id"] - ["pmtree-ft"] - ["pmtree-ft", "parallel"] - - ["pmtree-ft", "multi-message-id"] - - ["pmtree-ft", "parallel", "multi-message-id"] - ["fullmerkletree"] - ["fullmerkletree", "parallel"] - - ["fullmerkletree", "multi-message-id"] - - ["fullmerkletree", "parallel", "multi-message-id"] - ["optimalmerkletree"] - ["optimalmerkletree", "parallel"] - - ["optimalmerkletree", "multi-message-id"] - - ["optimalmerkletree", "parallel", "multi-message-id"] target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu] env: FEATURES_CARGO: ${{ join(matrix.features, ',') }} @@ -63,20 +55,12 @@ jobs: features: - ["stateless"] - ["stateless", "parallel"] - - ["stateless", "multi-message-id"] - - ["stateless", "parallel", "multi-message-id"] - ["pmtree-ft"] - ["pmtree-ft", "parallel"] - - ["pmtree-ft", "multi-message-id"] - - ["pmtree-ft", "parallel", "multi-message-id"] - ["fullmerkletree"] - ["fullmerkletree", "parallel"] - - ["fullmerkletree", "multi-message-id"] - - ["fullmerkletree", "parallel", "multi-message-id"] - ["optimalmerkletree"] - ["optimalmerkletree", "parallel"] - - ["optimalmerkletree", "multi-message-id"] - - ["optimalmerkletree", "parallel", "multi-message-id"] target: [x86_64-apple-darwin, aarch64-apple-darwin] env: FEATURES_CARGO: ${{ join(matrix.features, ',') }} @@ -113,8 +97,6 @@ jobs: feature: - "default" - "parallel" - - "multi-message-id" - - "parallel-multi-message-id" - "utils" steps: - name: Checkout sources @@ -138,14 +120,6 @@ jobs: rustup run nightly wasm-pack build --release --target web --scope waku \ --features parallel -Z build-std=panic_abort,std sed -i.bak 's/rln-wasm/zerokit-rln-wasm-parallel/g' pkg/package.json && rm pkg/package.json.bak - elif [[ ${{ matrix.feature }} == "parallel-multi-message-id" ]]; then - env CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals -C link-arg=--shared-memory -C link-arg=--max-memory=1073741824 -C link-arg=--import-memory -C link-arg=--export=__wasm_init_tls -C link-arg=--export=__tls_size -C link-arg=--export=__tls_align -C link-arg=--export=__tls_base" \ - rustup run nightly wasm-pack build --release --target web --scope waku \ - --features parallel,multi-message-id -Z build-std=panic_abort,std - sed -i.bak 's/rln-wasm/zerokit-rln-wasm-parallel-multi-message-id/g' pkg/package.json && rm pkg/package.json.bak - elif [[ ${{ matrix.feature }} == "multi-message-id" ]]; then - wasm-pack build --release --target web --scope waku --features multi-message-id - sed -i.bak 's/rln-wasm/zerokit-rln-wasm-multi-message-id/g' pkg/package.json && rm pkg/package.json.bak elif [[ ${{ matrix.feature }} == "utils" ]]; then wasm-pack build --release --target web --scope waku --no-default-features --features utils sed -i.bak 's/rln-wasm/zerokit-rln-wasm-utils/g' pkg/package.json && rm pkg/package.json.bak diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9cade8b..cc8f7568 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,6 @@ make test # Module-specific testing cd rln && cargo make test_stateless # Test stateless features -cd rln && cargo make test_multi_message_id # Test multi_message_id features cd rln-wasm && cargo make test_browser # Test in browser headless mode cd rln-wasm && cargo make test_parallel # Test parallel features ``` diff --git a/README.md b/README.md index 02c6ff8f..6e299fbc 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ cross build --target x86_64-unknown-linux-gnu --release -p rln 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 +- [**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 diff --git a/rln-cli/Cargo.toml b/rln-cli/Cargo.toml index 4d2a1f30..27fee86b 100644 --- a/rln-cli/Cargo.toml +++ b/rln-cli/Cargo.toml @@ -15,7 +15,6 @@ required-features = ["stateless"] [[example]] name = "multi_message_id" path = "src/examples/multi_message_id.rs" -required-features = ["multi-message-id"] [[example]] name = "partial" @@ -29,7 +28,6 @@ clap = { version = "4.6.0", features = ["cargo", "derive", "env"] } [features] default = ["rln/pmtree-ft", "rln/parallel"] stateless = ["rln/stateless", "rln/parallel"] -multi-message-id = ["rln/pmtree-ft", "rln/parallel", "rln/multi-message-id"] [package.metadata.docs.rs] all-features = true diff --git a/rln-cli/README.md b/rln-cli/README.md index 3f4cbcae..2685a338 100644 --- a/rln-cli/README.md +++ b/rln-cli/README.md @@ -40,17 +40,15 @@ cargo run --example stateless --no-default-features --features stateless ## Multi Message ID Example The following [Multi Message ID Example](src/examples/multi_message_id.rs) demonstrates -how RLN supports consuming multiple message_id units in a single proof -using the multi-message-id feature. +how RLN supports consuming multiple message_id units in a single proof. This example functions similarly to the [Relay Example](#relay-example) -but uses different [resource files](../rln/resources/tree_depth_20/multi_message_id) -that support the multi-message-id feature. +but uses the [multi-message-id resource files](../rln/resources/tree_depth_20/multi_message_id). You can run the example using the following command: ```bash -cargo run --example multi_message_id --features multi-message-id +cargo run --example multi_message_id ``` ## Partial Proof Example @@ -64,7 +62,7 @@ This example functions similarly to the [Relay Example](#relay-example) but demonstrates the partial proof optimization technique for improved proof generation performance. -Cached partial proofs remain usable across tree changes within a small window — +Cached partial proofs remain usable across tree changes within a small window - verify against a bounded set of recent roots (e.g. via [`verify_with_roots`](../rln/src/public.rs)) instead of regenerating immediately. diff --git a/rln-cli/src/examples/multi_message_id.rs b/rln-cli/src/examples/multi_message_id.rs index 16c808f9..d47afefa 100644 --- a/rln-cli/src/examples/multi_message_id.rs +++ b/rln-cli/src/examples/multi_message_id.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "multi-message-id")] - use std::{ collections::HashMap, fs::File, @@ -10,7 +8,7 @@ use std::{ use clap::{Parser, Subcommand}; use rln::prelude::{ hash_to_field_le, keygen, poseidon_hash, recover_id_secret, Fr, IdSecret, PmtreeConfigBuilder, - RLNProofValues, RLNWitnessInput, DEFAULT_MAX_OUT, DEFAULT_TREE_DEPTH, RLN, + RLNProofValues, RLNWitnessInput, DEFAULT_TREE_DEPTH, RLN, }; use zerokit_utils::pm_tree::Mode; @@ -18,8 +16,6 @@ const MESSAGE_LIMIT: u32 = 4; const TREE_DEPTH: usize = DEFAULT_TREE_DEPTH; -const MAX_OUT: usize = DEFAULT_MAX_OUT; - type Result = std::result::Result>; #[derive(Parser)] @@ -93,7 +89,6 @@ impl RLNSystem { .build()?; let rln = RLN::new_with_params( TREE_DEPTH, - MAX_OUT, resources[0].clone(), resources[1].clone(), tree_config, @@ -196,7 +191,7 @@ impl RLNSystem { let (path_elements, identity_path_index) = self.rln.get_merkle_proof(user_index)?; let x = hash_to_field_le(signal.as_bytes()); - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_multi( identity.identity_secret.clone(), Fr::from(MESSAGE_LIMIT), message_ids.clone(), diff --git a/rln-cli/src/examples/partial.rs b/rln-cli/src/examples/partial.rs index beb8c6dd..19af2b0c 100644 --- a/rln-cli/src/examples/partial.rs +++ b/rln-cli/src/examples/partial.rs @@ -204,7 +204,7 @@ impl RLNSystem { for user_index in indices { let identity = self.local_identities[&user_index].clone(); let (path_elements, identity_path_index) = self.rln.get_merkle_proof(user_index)?; - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_single( identity.identity_secret.clone(), Fr::from(MESSAGE_LIMIT), Fr::from(0u32), @@ -250,7 +250,7 @@ impl RLNSystem { "Using cached partial proof for user {user_index} (root {})", cached.root ); - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_single( identity.identity_secret.clone(), Fr::from(MESSAGE_LIMIT), Fr::from(message_id), @@ -266,7 +266,7 @@ impl RLNSystem { "Cached partial proof missing or stale for user {user_index}; generating fresh proof" ); let (path_elements, identity_path_index) = self.rln.get_merkle_proof(user_index)?; - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_single( identity.identity_secret.clone(), Fr::from(MESSAGE_LIMIT), Fr::from(message_id), diff --git a/rln-cli/src/examples/relay.rs b/rln-cli/src/examples/relay.rs index 0523df78..de7e6a4b 100644 --- a/rln-cli/src/examples/relay.rs +++ b/rln-cli/src/examples/relay.rs @@ -148,7 +148,7 @@ impl RLNSystem { let (path_elements, identity_path_index) = self.rln.get_merkle_proof(user_index)?; let x = hash_to_field_le(signal.as_bytes()); - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_single( identity.identity_secret.clone(), Fr::from(MESSAGE_LIMIT), Fr::from(message_id), diff --git a/rln-cli/src/examples/stateless.rs b/rln-cli/src/examples/stateless.rs index 30a94c7f..dad0126a 100644 --- a/rln-cli/src/examples/stateless.rs +++ b/rln-cli/src/examples/stateless.rs @@ -127,7 +127,7 @@ impl RLNSystem { let merkle_proof = self.tree.proof(user_index)?; let x = hash_to_field_le(signal.as_bytes()); - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_single( identity.identity_secret.clone(), Fr::from(MESSAGE_LIMIT), Fr::from(message_id), diff --git a/rln-wasm/Cargo.toml b/rln-wasm/Cargo.toml index 7cde1398..34aef18d 100644 --- a/rln-wasm/Cargo.toml +++ b/rln-wasm/Cargo.toml @@ -48,7 +48,6 @@ default = [] utils = [] panic_hook = ["console_error_panic_hook"] parallel = ["rln/parallel", "wasm-bindgen-rayon", "ark-groth16/parallel"] -multi-message-id = ["rln/multi-message-id"] [package.metadata.docs.rs] all-features = true diff --git a/rln-wasm/Makefile.toml b/rln-wasm/Makefile.toml index 07305c81..8e39b808 100644 --- a/rln-wasm/Makefile.toml +++ b/rln-wasm/Makefile.toml @@ -14,22 +14,6 @@ dependencies = [ clear = true dependencies = ["pack_build_utils", "pack_rename_utils", "pack_add_keywords"] -[tasks.build_multi_message_id] -clear = true -dependencies = [ - "pack_build_multi_message_id", - "pack_rename_multi_message_id", - "pack_add_keywords", -] - -[tasks.build_parallel_multi_message_id] -clear = true -dependencies = [ - "pack_build_parallel_multi_message_id", - "pack_rename_parallel_multi_message_id", - "pack_add_keywords", -] - [tasks.pack_build] command = "wasm-pack" args = ["build", "--release", "--target", "web", "--scope", "waku"] @@ -60,45 +44,6 @@ args = [ [tasks.pack_rename_parallel] script = "sed -i.bak 's/rln-wasm/zerokit-rln-wasm-parallel/g' pkg/package.json && rm pkg/package.json.bak" -[tasks.pack_build_multi_message_id] -command = "wasm-pack" -args = [ - "build", - "--release", - "--target", - "web", - "--scope", - "waku", - "--features", - "multi-message-id", -] - -[tasks.pack_rename_multi_message_id] -script = "sed -i.bak 's/rln-wasm/zerokit-rln-wasm-multi-message-id/g' pkg/package.json && rm pkg/package.json.bak" - -[tasks.pack_build_parallel_multi_message_id] -command = "env" -args = [ - "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS=-C target-feature=+atomics,+bulk-memory,+mutable-globals -C link-arg=--shared-memory -C link-arg=--max-memory=1073741824 -C link-arg=--import-memory -C link-arg=--export=__wasm_init_tls -C link-arg=--export=__tls_size -C link-arg=--export=__tls_align -C link-arg=--export=__tls_base", - "rustup", - "run", - "nightly", - "wasm-pack", - "build", - "--release", - "--target", - "web", - "--scope", - "waku", - "--features", - "parallel,multi-message-id", - "-Z", - "build-std=panic_abort,std", -] - -[tasks.pack_rename_parallel_multi_message_id] -script = "sed -i.bak 's/rln-wasm/zerokit-rln-wasm-parallel-multi-message-id/g' pkg/package.json && rm pkg/package.json.bak" - [tasks.pack_build_utils] command = "wasm-pack" args = [ @@ -172,22 +117,6 @@ args = [ ] dependencies = ["build_parallel"] -[tasks.test_multi_message_id] -command = "wasm-pack" -args = [ - "test", - "--release", - "--chrome", - "--headless", - "--target", - "wasm32-unknown-unknown", - "--features", - "multi-message-id", - "--", - "--nocapture", -] -dependencies = ["build_multi_message_id"] - [tasks.test_utils] command = "wasm-pack" args = [ diff --git a/rln-wasm/README.md b/rln-wasm/README.md index 52151254..ca240f88 100644 --- a/rln-wasm/README.md +++ b/rln-wasm/README.md @@ -14,9 +14,9 @@ to enable RLN functionality in JavaScript/TypeScript applications. > [!NOTE] > This project requires the following tools: > -> - `wasm-pack` (v0.14.0) — for compiling Rust to WebAssembly -> - `cargo-make` — for running build commands -> - `nvm` — to install and manage Node.js (v22.14.0+) +> - `wasm-pack` (v0.14.0) - for compiling Rust to WebAssembly +> - `cargo-make` - for running build commands +> - `nvm` - to install and manage Node.js (v22.14.0+) ### Quick Install @@ -52,8 +52,6 @@ Build commands: ```bash cargo make build # Default → @waku/zerokit-rln-wasm cargo make build_parallel # Parallel → @waku/zerokit-rln-wasm-parallel (requires nightly Rust) -cargo make build_multi_message_id # Multi-message-id → @waku/zerokit-rln-wasm-multi-message-id -cargo make build_parallel_multi_message_id # Parallel + Multi-message-id → @waku/zerokit-rln-wasm-parallel-multi-message-id (requires nightly Rust) cargo make build_utils # Utils only → @waku/zerokit-rln-wasm-utils ``` @@ -66,7 +64,6 @@ cargo make test # Default tests cargo make test_browser # Browser headless mode cargo make test_utils # Utils-only tests cargo make test_parallel # Parallel tests -cargo make test_multi_message_id # Multi-message-id feature tests ``` ## Examples @@ -103,7 +100,7 @@ wasmPkg.nowCallAnyExportedFuncs(); If you're targeting [older browser versions that didn't support WebAssembly threads yet](https://webassembly.org/roadmap/), -you'll want to use both builds — +you'll want to use both builds - the parallel version for modern browsers and the default version as a fallback. Use feature detection to choose the appropriate build on the JavaScript side. diff --git a/rln-wasm/examples/README.md b/rln-wasm/examples/README.md index cfc251b3..ea05f942 100644 --- a/rln-wasm/examples/README.md +++ b/rln-wasm/examples/README.md @@ -4,21 +4,13 @@ This example demonstrates how to use the RLN WASM package in a Node.js environme ## Build the @waku/zerokit-rln-wasm package at the root of rln-wasm module -### Default mode - ```bash cargo make build ``` -### Multi-message-id mode - -```bash -cargo make build_multi_message_id -``` - ## Running the examples -**Note:** Set `MULTI_MESSAGE_ID` constant in [index.js](../examples/index.js) to `true` when testing with multi-message-id features. +**Note:** Set `MULTI_MESSAGE_ID` constant in [index.js](../examples/index.js) to `true` when testing with multi-message-id mode. After building the package in any mode, install dependencies and run: diff --git a/rln-wasm/examples/index.js b/rln-wasm/examples/index.js index eff19783..33291939 100644 --- a/rln-wasm/examples/index.js +++ b/rln-wasm/examples/index.js @@ -271,7 +271,7 @@ async function main() { console.log("\nCreating RLN Witness"); let witness; if (MULTI_MESSAGE_ID) { - witness = new rlnWasm.WasmRLNWitnessInput( + witness = rlnWasm.WasmRLNWitnessInput.newMulti( identitySecret, userMessageLimit, messageIds, @@ -282,7 +282,7 @@ async function main() { selectorUsed, ); } else { - witness = new rlnWasm.WasmRLNWitnessInput( + witness = rlnWasm.WasmRLNWitnessInput.newSingle( identitySecret, userMessageLimit, messageId, @@ -478,7 +478,7 @@ async function main() { console.log("\nCreating second RLN Witness"); let witness2; if (MULTI_MESSAGE_ID) { - witness2 = new rlnWasm.WasmRLNWitnessInput( + witness2 = rlnWasm.WasmRLNWitnessInput.newMulti( identitySecret, userMessageLimit, messageIds2, @@ -489,7 +489,7 @@ async function main() { selectorUsed2, ); } else { - witness2 = new rlnWasm.WasmRLNWitnessInput( + witness2 = rlnWasm.WasmRLNWitnessInput.newSingle( identitySecret, userMessageLimit, messageId2, diff --git a/rln-wasm/src/wasm_rln.rs b/rln-wasm/src/wasm_rln.rs index 94d8f808..1aab1817 100644 --- a/rln-wasm/src/wasm_rln.rs +++ b/rln-wasm/src/wasm_rln.rs @@ -135,19 +135,16 @@ impl WasmRLNProofValues { WasmFr::from(*self.0.external_nullifier()) } - #[cfg(not(feature = "multi-message-id"))] #[wasm_bindgen(getter)] pub fn y(&self) -> WasmFr { WasmFr::from(*self.0.y()) } - #[cfg(not(feature = "multi-message-id"))] #[wasm_bindgen(getter)] pub fn nullifier(&self) -> WasmFr { WasmFr::from(*self.0.nullifier()) } - #[cfg(feature = "multi-message-id")] #[wasm_bindgen(js_name = selectorUsed)] pub fn selector_used(&self) -> Uint8Array { let bytes: Vec = self @@ -159,13 +156,11 @@ impl WasmRLNProofValues { Uint8Array::from(&bytes[..]) } - #[cfg(feature = "multi-message-id")] #[wasm_bindgen(js_name = ys)] pub fn ys(&self) -> VecWasmFr { VecWasmFr::from(self.0.ys().to_vec()) } - #[cfg(feature = "multi-message-id")] #[wasm_bindgen(js_name = nullifiers)] pub fn nullifiers(&self) -> VecWasmFr { VecWasmFr::from(self.0.nullifiers().to_vec()) @@ -232,9 +227,8 @@ pub struct WasmRLNWitnessInput(RLNWitnessInput); #[wasm_bindgen] impl WasmRLNWitnessInput { - #[cfg(not(feature = "multi-message-id"))] - #[wasm_bindgen(constructor)] - pub fn new( + #[wasm_bindgen(js_name = newSingle)] + pub fn new_single( identity_secret: &WasmFr, user_message_limit: &WasmFr, message_id: &WasmFr, @@ -247,7 +241,7 @@ impl WasmRLNWitnessInput { let path_elements: Vec = path_elements.inner(); let identity_path_index: Vec = identity_path_index.to_vec(); - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_single( IdSecret::from(&mut identity_secret_fr), user_message_limit.inner(), message_id.inner(), @@ -261,9 +255,9 @@ impl WasmRLNWitnessInput { Ok(WasmRLNWitnessInput(witness)) } - #[cfg(feature = "multi-message-id")] - #[wasm_bindgen(constructor)] - pub fn new( + #[allow(clippy::too_many_arguments)] + #[wasm_bindgen(js_name = newMulti)] + pub fn new_multi( identity_secret: &WasmFr, user_message_limit: &WasmFr, message_ids: VecWasmFr, @@ -280,7 +274,7 @@ impl WasmRLNWitnessInput { let message_ids: Vec = message_ids.inner(); let selector_used: Vec = selector_used.to_vec().iter().map(|&b| b != 0).collect(); - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_multi( IdSecret::from(&mut identity_secret_fr), user_message_limit.inner(), message_ids, @@ -310,13 +304,11 @@ impl WasmRLNWitnessInput { WasmFr::from(*self.0.user_message_limit()) } - #[cfg(not(feature = "multi-message-id"))] #[wasm_bindgen(js_name = getMessageId)] pub fn get_message_id(&self) -> WasmFr { WasmFr::from(*self.0.message_id()) } - #[cfg(feature = "multi-message-id")] #[wasm_bindgen(js_name = getMessageIds)] pub fn get_message_ids(&self) -> VecWasmFr { VecWasmFr::from(self.0.message_ids().to_vec()) @@ -342,7 +334,6 @@ impl WasmRLNWitnessInput { WasmFr::from(*self.0.external_nullifier()) } - #[cfg(feature = "multi-message-id")] #[wasm_bindgen(js_name = getSelectorUsed)] pub fn get_selector_used(&self) -> Uint8Array { let bytes: Vec = self diff --git a/rln-wasm/tests/browser.rs b/rln-wasm/tests/browser.rs index b45d65dc..dbfda35d 100644 --- a/rln-wasm/tests/browser.rs +++ b/rln-wasm/tests/browser.rs @@ -69,22 +69,11 @@ mod test { const WITNESS_CALCULATOR_JS: &str = include_str!("../resources/witness_calculator.js"); - #[cfg(not(feature = "multi-message-id"))] const ARKZKEY_BYTES: &[u8] = include_bytes!("../../rln/resources/tree_depth_20/rln_final.arkzkey"); - #[cfg(not(feature = "multi-message-id"))] const CIRCOM_BYTES: &[u8] = include_bytes!("../../rln/resources/tree_depth_20/rln.wasm"); - #[cfg(feature = "multi-message-id")] - const ARKZKEY_BYTES: &[u8] = include_bytes!( - "../../rln/resources/tree_depth_20/multi_message_id/max_out_4/rln_final.arkzkey" - ); - - #[cfg(feature = "multi-message-id")] - const CIRCOM_BYTES: &[u8] = - include_bytes!("../../rln/resources/tree_depth_20/multi_message_id/max_out_4/rln.wasm"); - wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] @@ -143,7 +132,6 @@ mod test { let rate_commitment = Hasher::poseidon_hash_pair(&id_commitment, &user_message_limit); tree.update_next(*rate_commitment).unwrap(); - #[cfg(not(feature = "multi-message-id"))] let message_id = WasmFr::from_uint(0); let signal: [u8; 32] = [0; 32]; let x = Hasher::hash_to_field_le(&Uint8Array::from(&signal[..])); @@ -156,42 +144,16 @@ mod test { } let path_index = Uint8Array::from(&merkle_proof.get_path_index()[..]); - #[cfg(not(feature = "multi-message-id"))] - let witness = { - WasmRLNWitnessInput::new( - &identity_secret, - &user_message_limit, - &message_id, - &path_elements, - &path_index, - &x, - &external_nullifier, - ) - .unwrap() - }; - - #[cfg(feature = "multi-message-id")] - let witness = { - let mut message_ids = VecWasmFr::new(); - message_ids.push(&WasmFr::from_uint(0)); - message_ids.push(&WasmFr::from_uint(1)); - message_ids.push(&WasmFr::zero()); - message_ids.push(&WasmFr::zero()); - - let selector_used = Uint8Array::from(&[1u8, 1u8, 0u8, 0u8][..]); - - WasmRLNWitnessInput::new( - &identity_secret, - &user_message_limit, - message_ids, - &path_elements, - &path_index, - &x, - &external_nullifier, - selector_used, - ) - .unwrap() - }; + let witness = WasmRLNWitnessInput::new_single( + &identity_secret, + &user_message_limit, + &message_id, + &path_elements, + &path_index, + &x, + &external_nullifier, + ) + .unwrap(); let bigint_json = witness.to_bigint_json().unwrap(); diff --git a/rln-wasm/tests/node.rs b/rln-wasm/tests/node.rs index 8f1eb8f2..108a7370 100644 --- a/rln-wasm/tests/node.rs +++ b/rln-wasm/tests/node.rs @@ -1,6 +1,5 @@ #![cfg(target_arch = "wasm32")] #![cfg(not(feature = "utils"))] -#![cfg(not(feature = "multi-message-id"))] #[cfg(test)] mod test { @@ -180,7 +179,7 @@ mod test { } let path_index = Uint8Array::from(&merkle_proof.get_path_index()[..]); - let witness = WasmRLNWitnessInput::new( + let witness = WasmRLNWitnessInput::new_single( &identity_secret, &user_message_limit, &message_id, @@ -298,7 +297,7 @@ mod test { // Invalid user message limit (zero) let zero_limit = WasmFr::zero(); - let result = WasmRLNWitnessInput::new( + let result = WasmRLNWitnessInput::new_single( &identity_secret, &zero_limit, &message_id, @@ -311,7 +310,7 @@ mod test { // Invalid message id (>= limit) let invalid_message_id = user_message_limit; - let result = WasmRLNWitnessInput::new( + let result = WasmRLNWitnessInput::new_single( &identity_secret, &user_message_limit, &invalid_message_id, @@ -327,7 +326,7 @@ mod test { for i in 0..path_elements.length().saturating_sub(1) { shorter_path_elements.push(&path_elements.get(i).unwrap()); } - let result = WasmRLNWitnessInput::new( + let result = WasmRLNWitnessInput::new_single( &identity_secret, &user_message_limit, &message_id, @@ -339,7 +338,7 @@ mod test { assert!(result.is_err()); // Witness bytes: truncated and extra data - let valid_witness = WasmRLNWitnessInput::new( + let valid_witness = WasmRLNWitnessInput::new_single( &identity_secret, &user_message_limit, &message_id, diff --git a/rln/Cargo.toml b/rln/Cargo.toml index fcd96ace..590dfea1 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -83,7 +83,7 @@ fullmerkletree = [] # Pre-allocated tree, fastest access optimalmerkletree = [] # Sparse storage, memory efficient pmtree-ft = ["zerokit_utils/pmtree-ft"] # Persistent storage, disk-based headers = ["safer-ffi/headers"] # Generate C header file with safer-ffi -multi-message-id = [] # Support multiple message IDs in single RLN proof +refactors = [] # To avoid clippy checks showing errors in refactor branches [[bench]] name = "pmtree_benchmark" @@ -103,5 +103,9 @@ required-features = ["pmtree-ft"] features = ["parallel", "pmtree-ft"] [[bin]] -name = "generate-headers" +name = "generate_headers" required-features = ["headers"] # Do not build unless generating headers. + +[[bin]] +name = "test_refactors" +required-features = ["refactors"] diff --git a/rln/Makefile.toml b/rln/Makefile.toml index 16602be6..4af3d6d0 100644 --- a/rln/Makefile.toml +++ b/rln/Makefile.toml @@ -18,17 +18,6 @@ args = [ "--nocapture", ] -[tasks.test_multi_message_id] -command = "cargo" -args = [ - "test", - "--release", - "--features", - "multi-message-id", - "--", - "--nocapture", -] - [tasks.bench] command = "cargo" args = ["bench"] diff --git a/rln/README.md b/rln/README.md index e3af8a53..84f7adcf 100644 --- a/rln/README.md +++ b/rln/README.md @@ -163,9 +163,6 @@ cargo make test # Test with stateless features cargo make test_stateless - -# Test with multi_message_id features -cargo make test_multi_message_id ``` ## Advanced: Custom Circuit Compilation @@ -319,11 +316,11 @@ RLN provides C-compatible bindings for integration with C, C++, Nim, and other l The FFI layer is organized into several modules: -- [`ffi_rln.rs`](./src/ffi/ffi_rln.rs) — Implements core RLN functionality, +- [`ffi_rln.rs`](./src/ffi/ffi_rln.rs) - Implements core RLN functionality, including initialization functions, proof generation, and proof verification. -- [`ffi_tree.rs`](./src/ffi/ffi_tree.rs) — Provides all tree-related operations +- [`ffi_tree.rs`](./src/ffi/ffi_tree.rs) - Provides all tree-related operations and helper functions for Merkle tree management. -- [`ffi_utils.rs`](./src/ffi/ffi_utils.rs) — Contains all utility functions and structure definitions +- [`ffi_utils.rs`](./src/ffi/ffi_utils.rs) - Contains all utility functions and structure definitions used across the FFI layer. ## Parallel Processing @@ -338,29 +335,25 @@ for concurrent proof generation. ## Multi-Message-ID -The `multi-message-id` feature flag enables consuming multiple message_id units in a single proof. - -**Key capabilities:** +**How it works:** -- Burn multiple message_id units in one proof execution with corresponding nullifiers -- Use selector bits to determine which message_id slots are consumed -- Generate one proof instead of multiple for better computational efficiency +Multi-message-ID mode allows consuming multiple message_id units in a single proof execution. +Instead of generating one proof per message slot, a single proof covers up to `max_out` slots: -When enabled, the RLN module API allows specifying -multiple message_id values and selector bits during witness creation. -The proof generation and verification processes -are updated accordingly to handle the multi-message-id logic. +- Each slot has a corresponding nullifier and `(x, y)` pair in the proof output +- Selector bits indicate which slots are actively consumed +- Unused slots can be ignored by the verifier **Slashing across modes:** -Two services can independently run in either normal or multi-message-id mode to generate proofs. +Two services can independently run in either single or multi-message-id mode to generate proofs. The full structured format of `RLNWitnessInput` and `RLNProofValues` is only needed for witness calculation, proof generation, and proof verification. -After verification, each active nullifier and its `(x, y)` pair can be extracted individually — -unused slots can be ignored. -These normalized pairs are then stored separately -and checked for duplicate nullifiers via `compute_id_secret` function. +After verification, each active nullifier and its `(x, y)` pair can be extracted individually - +unused slots are ignored. +These normalized pairs are stored separately and checked for duplicate nullifiers +via the `compute_id_secret` function, regardless of which mode generated the proof. ## Partial Proof Generation diff --git a/rln/benches/partial_proof.rs b/rln/benches/partial_proof.rs index 335583bd..840bf9e6 100644 --- a/rln/benches/partial_proof.rs +++ b/rln/benches/partial_proof.rs @@ -33,19 +33,14 @@ fn get_test_witness() -> RLNWitnessInput { let message_id = Fr::from(1); - RLNWitnessInput::new( + RLNWitnessInput::new_single( identity_secret, user_message_limit, - #[cfg(not(feature = "multi-message-id"))] message_id, - #[cfg(feature = "multi-message-id")] - vec![message_id, Fr::from(0), Fr::from(0), Fr::from(0)], merkle_proof.get_path_elements(), merkle_proof.get_path_index(), x, external_nullifier, - #[cfg(feature = "multi-message-id")] - vec![true, false, false, false], ) .unwrap() } @@ -64,8 +59,8 @@ pub fn rln_proof_benchmark(c: &mut Criterion) { let witness = get_test_witness(); let partial_witness = get_partial_witness(&witness); - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); + let proving_key = zkey_single_v1(); + let graph_data = graph_single_v1(); c.bench_function("rln_full_proof", |b| { b.iter(|| { diff --git a/rln/ffi_c_examples/Readme.md b/rln/ffi_c_examples/Readme.md index 5af2d7f0..0ec9ec7f 100644 --- a/rln/ffi_c_examples/Readme.md +++ b/rln/ffi_c_examples/Readme.md @@ -43,8 +43,8 @@ gcc -Wall -DSTATELESS main.c -o main -lrln -L../../target/release ### Compile lib multi-message-id ```bash -cargo build -p rln --release --features multi-message-id -cargo run --features multi-message-id,headers --bin generate-headers +cargo build -p rln --release +cargo run --features headers --bin generate-headers mv -v rln.h rln/ffi_c_examples/ ``` diff --git a/rln/ffi_c_examples/main.c b/rln/ffi_c_examples/main.c index fe53e8df..70e64d83 100644 --- a/rln/ffi_c_examples/main.c +++ b/rln/ffi_c_examples/main.c @@ -73,7 +73,7 @@ int main(int argc, char const *const argv[]) .cap = graph_size}; const char *config_path = "../resources/tree_depth_20/multi_message_id/max_out_4/config.json"; - CResult_FFI_RLN_ptr_Vec_uint8_t ffi_rln_new_result = ffi_rln_new_with_params(TREE_DEPTH, 4, &zkey_vec, &graph_vec, config_path); + CResult_FFI_RLN_ptr_Vec_uint8_t ffi_rln_new_result = ffi_rln_new_with_params(TREE_DEPTH, &zkey_vec, &graph_vec, config_path); free(zkey_data); free(graph_data); @@ -366,7 +366,7 @@ int main(int argc, char const *const argv[]) #ifdef STATELESS CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result = #ifdef MULTI_MESSAGE_ID - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_multi( identity_secret, user_message_limit, &message_ids, @@ -376,7 +376,7 @@ int main(int argc, char const *const argv[]) external_nullifier, &selector_used); #else - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_single( identity_secret, user_message_limit, message_id, @@ -397,7 +397,7 @@ int main(int argc, char const *const argv[]) #else CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result = #ifdef MULTI_MESSAGE_ID - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_multi( identity_secret, user_message_limit, &message_ids, @@ -407,7 +407,7 @@ int main(int argc, char const *const argv[]) external_nullifier, &selector_used); #else - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_single( identity_secret, user_message_limit, message_id, @@ -874,7 +874,7 @@ int main(int argc, char const *const argv[]) #ifdef STATELESS CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result2 = #ifdef MULTI_MESSAGE_ID - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_multi( identity_secret, user_message_limit, &message_ids2, @@ -884,7 +884,7 @@ int main(int argc, char const *const argv[]) external_nullifier, &selector_used2); #else - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_single( identity_secret, user_message_limit, message_id2, @@ -905,7 +905,7 @@ int main(int argc, char const *const argv[]) #else CResult_FFI_RLNWitnessInput_ptr_Vec_uint8_t witness_result2 = #ifdef MULTI_MESSAGE_ID - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_multi( identity_secret, user_message_limit, &message_ids2, @@ -915,7 +915,7 @@ int main(int argc, char const *const argv[]) external_nullifier, &selector_used2); #else - ffi_rln_witness_input_new( + ffi_rln_witness_input_new_single( identity_secret, user_message_limit, message_id2, diff --git a/rln/ffi_nim_examples/README.md b/rln/ffi_nim_examples/README.md index 451bfdd1..a4778363 100644 --- a/rln/ffi_nim_examples/README.md +++ b/rln/ffi_nim_examples/README.md @@ -1,6 +1,6 @@ # RLN FFI Nim example -This example demonstrates how to use the RLN C FFI from Nim in stateful, stateless, and multi-message-id modes. +This example demonstrates how to use the RLN C FFI from Nim in stateful, stateless, and multi-message-id mode. ## Build the RLN library @@ -14,7 +14,7 @@ cargo build -p rln --release cargo build -p rln --release --no-default-features --features stateless # Multi-message-id build -cargo build -p rln --release --features multi-message-id +cargo build -p rln --release ``` This produces the shared library in `target/release`: @@ -44,9 +44,7 @@ Notes: set an rpath or environment variable as shown below. - The example auto-picks a platform-specific default library name. You can override it with `-d:RLN_LIB:"/absolute/path/to/lib"` if needed. -- **Important**: Ensure the RLN library is compiled with the same feature flags as the Nim example. - For example, if you compile the Nim example with `-d:ffiMultiMessageId`, the library must be built - with `--features multi-message-id`. +- **Important**: For stateless mode, ensure the RLN library is compiled with `--features stateless` to match `-d:ffiStateless`. ## Run the example diff --git a/rln/ffi_nim_examples/main.nim b/rln/ffi_nim_examples/main.nim index b0b0200c..04092ae2 100644 --- a/rln/ffi_nim_examples/main.nim +++ b/rln/ffi_nim_examples/main.nim @@ -194,10 +194,10 @@ else: when defined(ffiMultiMessageId): proc ffi_rln_new*(treeDepth: CSize, config: cstring): CResultRLNPtrVecU8 {.importc: "ffi_rln_new", cdecl, dynlib: RLN_LIB.} - proc ffi_rln_new_with_params*(treeDepth: CSize, maxOut: CSize, - zkey_data: ptr Vec_uint8, graph_data: ptr Vec_uint8, - config: cstring): CResultRLNPtrVecU8 {.importc: "ffi_rln_new_with_params", - cdecl, dynlib: RLN_LIB.} + proc ffi_rln_new_with_params*(treeDepth: CSize, zkey_data: ptr Vec_uint8, + graph_data: ptr Vec_uint8, + config: cstring): CResultRLNPtrVecU8 {.importc: "ffi_rln_new_with_params", + cdecl, dynlib: RLN_LIB.} else: proc ffi_rln_new*(treeDepth: CSize, config: cstring): CResultRLNPtrVecU8 {.importc: "ffi_rln_new", cdecl, dynlib: RLN_LIB.} @@ -215,7 +215,7 @@ when defined(ffiMultiMessageId): # RLNWitnessInput functions when defined(ffiMultiMessageId): - proc ffi_rln_witness_input_new*( + proc ffi_rln_witness_input_new_multi*( identity_secret: ptr CFr, user_message_limit: ptr CFr, message_ids: ptr Vec_CFr, @@ -224,10 +224,10 @@ when defined(ffiMultiMessageId): x: ptr CFr, external_nullifier: ptr CFr, selector_used: ptr Vec_bool - ): CResultWitnessInputPtrVecU8 {.importc: "ffi_rln_witness_input_new", cdecl, - dynlib: RLN_LIB.} + ): CResultWitnessInputPtrVecU8 {.importc: "ffi_rln_witness_input_new_multi", + cdecl, dynlib: RLN_LIB.} else: - proc ffi_rln_witness_input_new*( + proc ffi_rln_witness_input_new_single*( identity_secret: ptr CFr, user_message_limit: ptr CFr, message_id: ptr CFr, @@ -235,8 +235,8 @@ else: identity_path_index: ptr Vec_uint8, x: ptr CFr, external_nullifier: ptr CFr - ): CResultWitnessInputPtrVecU8 {.importc: "ffi_rln_witness_input_new", cdecl, - dynlib: RLN_LIB.} + ): CResultWitnessInputPtrVecU8 {.importc: "ffi_rln_witness_input_new_single", + cdecl, dynlib: RLN_LIB.} proc ffi_rln_witness_input_get_version_byte*(witness: ptr ptr FFI_RLNWitnessInput): uint8 {.importc: "ffi_rln_witness_input_get_version_byte", cdecl, dynlib: RLN_LIB.} proc ffi_rln_witness_input_get_identity_secret*(witness: ptr ptr FFI_RLNWitnessInput): ptr CFr {.importc: "ffi_rln_witness_input_get_identity_secret", @@ -506,7 +506,7 @@ when isMainModule: var graphVec = asVecU8(graphData) let config_path = """../resources/tree_depth_20/multi_message_id/max_out_4/config.json""".cstring - rlnRes = ffi_rln_new_with_params(CSize(treeDepth), CSize(4), addr zkeyVec, + rlnRes = ffi_rln_new_with_params(CSize(treeDepth), addr zkeyVec, addr graphVec, config_path) else: let config_path = """../resources/tree_depth_20/config.json""".cstring @@ -613,7 +613,8 @@ when isMainModule: var defaultHashes: array[treeDepth-1, ptr CFr] defaultHashes[0] = ffi_poseidon_hash_pair(defaultLeaf, defaultLeaf) for i in 1..treeDepth-2: - defaultHashes[i] = ffi_poseidon_hash_pair(defaultHashes[i-1], defaultHashes[i-1]) + defaultHashes[i] = ffi_poseidon_hash_pair(defaultHashes[i-1], + defaultHashes[i-1]) var pathElements = ffi_vec_cfr_new(CSize(treeDepth)) ffi_vec_cfr_push(addr pathElements, defaultLeaf) @@ -785,11 +786,11 @@ when isMainModule: echo "\nCreating RLN Witness" when defined(ffiStateless): when defined(ffiMultiMessageId): - var witnessRes = ffi_rln_witness_input_new(identitySecret, + var witnessRes = ffi_rln_witness_input_new_multi(identitySecret, userMessageLimit, addr messageIds, addr pathElements, addr identityPathIndex, x, externalNullifier, addr selectorUsed) else: - var witnessRes = ffi_rln_witness_input_new(identitySecret, + var witnessRes = ffi_rln_witness_input_new_single(identitySecret, userMessageLimit, messageId, addr pathElements, addr identityPathIndex, x, externalNullifier) @@ -801,12 +802,12 @@ when isMainModule: echo " - RLN Witness created successfully" else: when defined(ffiMultiMessageId): - var witnessRes = ffi_rln_witness_input_new(identitySecret, + var witnessRes = ffi_rln_witness_input_new_multi(identitySecret, userMessageLimit, addr messageIds, addr merkleProof.path_elements, addr merkleProof.path_index, x, externalNullifier, addr selectorUsed) else: - var witnessRes = ffi_rln_witness_input_new(identitySecret, + var witnessRes = ffi_rln_witness_input_new_single(identitySecret, userMessageLimit, messageId, addr merkleProof.path_elements, addr merkleProof.path_index, x, externalNullifier) if witnessRes.ok.isNil: @@ -1241,11 +1242,11 @@ when isMainModule: echo "\nCreating second RLN Witness" when defined(ffiStateless): when defined(ffiMultiMessageId): - var witnessRes2 = ffi_rln_witness_input_new(identitySecret, + var witnessRes2 = ffi_rln_witness_input_new_multi(identitySecret, userMessageLimit, addr messageIds2, addr pathElements, addr identityPathIndex, x2, externalNullifier, addr selectorUsed2) else: - var witnessRes2 = ffi_rln_witness_input_new(identitySecret, + var witnessRes2 = ffi_rln_witness_input_new_single(identitySecret, userMessageLimit, messageId2, addr pathElements, addr identityPathIndex, x2, externalNullifier) @@ -1258,12 +1259,12 @@ when isMainModule: echo " - second RLN Witness created successfully" else: when defined(ffiMultiMessageId): - var witnessRes2 = ffi_rln_witness_input_new(identitySecret, + var witnessRes2 = ffi_rln_witness_input_new_multi(identitySecret, userMessageLimit, addr messageIds2, addr merkleProof.path_elements, addr merkleProof.path_index, x2, externalNullifier, addr selectorUsed2) else: - var witnessRes2 = ffi_rln_witness_input_new(identitySecret, + var witnessRes2 = ffi_rln_witness_input_new_single(identitySecret, userMessageLimit, messageId2, addr merkleProof.path_elements, addr merkleProof.path_index, x2, externalNullifier) if witnessRes2.ok.isNil: diff --git a/rln/src/bin/generate-headers.rs b/rln/src/bin/generate_headers.rs similarity index 100% rename from rln/src/bin/generate-headers.rs rename to rln/src/bin/generate_headers.rs diff --git a/rln/src/bin/test_refactors.rs b/rln/src/bin/test_refactors.rs new file mode 100644 index 00000000..ceb2a9f0 --- /dev/null +++ b/rln/src/bin/test_refactors.rs @@ -0,0 +1,24 @@ +use rln::prelude::*; +use zerokit_utils::merkle_tree::ZerokitMerkleTree; + +fn main() -> Result<(), RLNError> { + let zkey = zkey_single_v1().to_owned(); + let graph = graph_single_v1().to_owned(); + let backend = ArkGroth16Backend::new(zkey, graph); + + let rln_stateless = RLNV3::::new(backend.clone()); + + let witness1 = RLNWitnessInputV3::Single(RLNWitnessInputSingle::new()?); + let (proof1, proof_values1) = rln_stateless.generate_proof(witness1)?; + let ok = rln_stateless.verify_proof(&proof1, &proof_values1)?; + assert!(ok); + + let merkle_tree = PmTree::default(DEFAULT_TREE_DEPTH).unwrap(); + let rln_stateful = RLNV3::, ArkGroth16Backend>::new(merkle_tree, backend); + let witness2 = RLNWitnessInputV3::Multi(RLNWitnessInputMulti::new()?); + let (proof2, proof_values2) = rln_stateful.generate_proof(witness2)?; + let ok = rln_stateful.verify_proof(&proof2, &proof_values2)?; + assert!(ok); + + Ok(()) +} diff --git a/rln/src/circuit/error.rs b/rln/src/circuit/error.rs index 55428c46..a28e7639 100644 --- a/rln/src/circuit/error.rs +++ b/rln/src/circuit/error.rs @@ -1,10 +1,12 @@ +use ark_serialize::SerializationError; + /// Errors that can occur during zkey reading operations #[derive(Debug, thiserror::Error)] pub enum ZKeyReadError { #[error("Empty zkey bytes provided")] EmptyBytes, #[error("{0}")] - SerializationError(#[from] ark_serialize::SerializationError), + SerializationError(#[from] SerializationError), } /// Errors that can occur during witness calculation graph reading operations diff --git a/rln/src/circuit/iden3calc.rs b/rln/src/circuit/iden3calc.rs index ce36642b..3f68c3a6 100644 --- a/rln/src/circuit/iden3calc.rs +++ b/rln/src/circuit/iden3calc.rs @@ -44,7 +44,7 @@ pub(crate) fn calc_witness)>>( populate_inputs(&inputs, &graph.input_mapping, &mut inputs_buffer)?; if let Some(v) = inputs.get_mut("identitySecret") { - // DO NOT USE: unsafe { zeroize_flat_type(v) } only clears the Vec pointer, not the data—can cause memory leaks + // DO NOT USE: unsafe { zeroize_flat_type(v) } only clears the Vec pointer, not the data - can cause memory leaks for val in v.iter_mut() { unsafe { zeroize_flat_type(val) }; @@ -88,7 +88,7 @@ pub(crate) fn calc_witness_partial = LazyLock::new(|| { - read_arkzkey_from_bytes_uncompressed(ARKZKEY_BYTES).expect("Default zkey must be valid") +static ARKZKEY_SINGLE_V1: LazyLock = LazyLock::new(|| { + read_arkzkey_from_bytes_uncompressed(ARKZKEY_BYTES_SINGLE_V1) + .expect("Default SingleV1 zkey must be valid") }); -#[cfg(all(not(target_arch = "wasm32"), not(feature = "multi-message-id")))] -static GRAPH: LazyLock = LazyLock::new(|| { - graph_from_raw(GRAPH_BYTES, Some(DEFAULT_TREE_DEPTH)).expect("Default graph must be valid") +#[cfg(not(target_arch = "wasm32"))] +static ARKZKEY_MULTI_V1: LazyLock = LazyLock::new(|| { + read_arkzkey_from_bytes_uncompressed(ARKZKEY_BYTES_MULTI_V1) + .expect("Default MultiV1 zkey must be valid") }); -#[cfg(all(not(target_arch = "wasm32"), feature = "multi-message-id"))] -static GRAPH: LazyLock = LazyLock::new(|| { - graph_from_raw(GRAPH_BYTES, Some(DEFAULT_TREE_DEPTH), Some(DEFAULT_MAX_OUT)) - .expect("Default graph must be valid") +#[cfg(not(target_arch = "wasm32"))] +static GRAPH_SINGLE_V1: LazyLock = LazyLock::new(|| { + graph_from_raw(GRAPH_BYTES_SINGLE_V1, Some(DEFAULT_TREE_DEPTH), None) + .expect("Default SingleV1 graph must be valid") +}); + +#[cfg(not(target_arch = "wasm32"))] +static GRAPH_MULTI_V1: LazyLock = LazyLock::new(|| { + graph_from_raw( + GRAPH_BYTES_MULTI_V1, + Some(DEFAULT_TREE_DEPTH), + Some(DEFAULT_MAX_OUT), + ) + .expect("Default MultiV1 graph must be valid") }); -#[cfg(feature = "multi-message-id")] pub const DEFAULT_MAX_OUT: usize = 4; pub const DEFAULT_TREE_DEPTH: usize = 20; pub const COMPRESS_PROOF_SIZE: usize = 128; @@ -114,9 +126,7 @@ pub struct Graph { pub(crate) nodes: Vec, pub(crate) signals: Vec, pub(crate) input_mapping: InputSignalsInfo, - #[cfg(not(target_arch = "wasm32"))] pub(crate) tree_depth: usize, - #[cfg(feature = "multi-message-id")] pub(crate) max_out: usize, } @@ -135,8 +145,8 @@ pub fn zkey_from_raw(zkey_data: &[u8]) -> Result { #[cfg(not(target_arch = "wasm32"))] pub fn graph_from_raw( graph_data: &[u8], - #[cfg(not(target_arch = "wasm32"))] expected_tree_depth: Option, - #[cfg(feature = "multi-message-id")] expected_max_out: Option, + expected_tree_depth: Option, + expected_max_out: Option, ) -> Result { if graph_data.is_empty() { return Err(GraphReadError::EmptyBytes); @@ -146,7 +156,6 @@ pub fn graph_from_raw( deserialize_witnesscalc_graph(std::io::Cursor::new(graph_data)) .map_err(GraphReadError::GraphDeserialization)?; - #[cfg(not(target_arch = "wasm32"))] let tree_depth = { let depth = input_mapping .get("pathElements") @@ -165,46 +174,52 @@ pub fn graph_from_raw( depth }; - #[cfg(feature = "multi-message-id")] - let max_out = { - let count = input_mapping - .get("messageId") - .map(|(_, len)| *len) - .ok_or_else(|| GraphReadError::MissingSignal("messageId".into()))?; - - if let Some(expected) = expected_max_out { - if expected != count { - return Err(GraphReadError::MaxOutMismatch { - expected, - actual: count, - }); + let max_out = match input_mapping.get("messageId") { + Some((_, count)) => { + if let Some(expected) = expected_max_out { + if expected != *count { + return Err(GraphReadError::MaxOutMismatch { + expected, + actual: *count, + }); + } } + *count } - - count + None => 1, // single-message-id graph: max_out = 1 }; Ok(Graph { nodes, signals, input_mapping, - #[cfg(not(target_arch = "wasm32"))] tree_depth, - #[cfg(feature = "multi-message-id")] max_out, }) } -// Loads default zkey from folder +// Loads default SingleV1 zkey #[cfg(not(target_arch = "wasm32"))] -pub fn zkey_from_folder() -> &'static Zkey { - &ARKZKEY +pub fn zkey_single_v1() -> &'static Zkey { + &ARKZKEY_SINGLE_V1 } -// Loads default parsed graph from folder +// Loads default MultiV1 zkey #[cfg(not(target_arch = "wasm32"))] -pub fn graph_from_folder() -> &'static Graph { - &GRAPH +pub fn zkey_multi_v1() -> &'static Zkey { + &ARKZKEY_MULTI_V1 +} + +// Loads default SingleV1 parsed graph +#[cfg(not(target_arch = "wasm32"))] +pub fn graph_single_v1() -> &'static Graph { + &GRAPH_SINGLE_V1 +} + +// Loads default MultiV1 parsed graph +#[cfg(not(target_arch = "wasm32"))] +pub fn graph_multi_v1() -> &'static Graph { + &GRAPH_MULTI_V1 } // The following functions and structs are based on code from ark-zkey: @@ -261,6 +276,23 @@ fn read_arkzkey_from_bytes_uncompressed(arkzkey_data: &[u8]) -> Result Self { + Self { + _zkey: zkey, + _graph: graph, + } + } +} + #[cfg(test)] mod test { use super::*; @@ -270,9 +302,6 @@ mod test { let err = zkey_from_raw(&[]).unwrap_err(); assert!(matches!(err, ZKeyReadError::EmptyBytes)); - #[cfg(not(feature = "multi-message-id"))] - let err = graph_from_raw(&[], None).err().unwrap(); - #[cfg(feature = "multi-message-id")] let err = graph_from_raw(&[], None, None).err().unwrap(); assert!(matches!(err, GraphReadError::EmptyBytes)); @@ -282,22 +311,16 @@ mod test { #[test] fn test_tree_depth_mismatch() { - #[cfg(not(feature = "multi-message-id"))] - let err = graph_from_raw(GRAPH_BYTES, Some(DEFAULT_TREE_DEPTH + 1)) - .err() - .unwrap(); - #[cfg(feature = "multi-message-id")] - let err = graph_from_raw(GRAPH_BYTES, Some(DEFAULT_TREE_DEPTH + 1), None) + let err = graph_from_raw(GRAPH_BYTES_SINGLE_V1, Some(DEFAULT_TREE_DEPTH + 1), None) .err() .unwrap(); assert!(matches!(err, GraphReadError::TreeDepthMismatch { .. })); } - #[cfg(feature = "multi-message-id")] #[test] fn test_max_out_mismatch() { let err = graph_from_raw( - GRAPH_BYTES, + GRAPH_BYTES_MULTI_V1, Some(DEFAULT_TREE_DEPTH), Some(DEFAULT_MAX_OUT + 1), ) diff --git a/rln/src/error.rs b/rln/src/error.rs index 2f6df647..0cb69fb7 100644 --- a/rln/src/error.rs +++ b/rln/src/error.rs @@ -1,17 +1,23 @@ use std::{array::TryFromSliceError, num::TryFromIntError}; use ark_relations::r1cs::SynthesisError; +use ark_serialize::SerializationError; use num_bigint::{BigInt, ParseBigIntError}; use zerokit_utils::merkle_tree::{FromConfigError, ZerokitMerkleTreeError}; -use crate::circuit::{ - error::{GraphReadError, WitnessCalcError, ZKeyReadError}, - Fr, +use crate::{ + circuit::{ + error::{GraphReadError, WitnessCalcError, ZKeyReadError}, + Fr, + }, + protocol::MessageMode, }; /// Errors that can occur during RLN utility operations (conversions, parsing, etc.) #[derive(Debug, thiserror::Error)] pub enum UtilsError { + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), #[error("Expected radix 10 or 16")] WrongRadix, #[error("Failed to parse big integer: {0}")] @@ -24,6 +30,8 @@ pub enum UtilsError { InsufficientData { expected: usize, actual: usize }, #[error("Non-canonical field element: value is not in [0, r-1]")] NonCanonicalFieldElement, + #[error("Non-canonical bool byte: expected 0x00 or 0x01, got {0:#04x}")] + NonCanonicalBool(u8), } /// Errors that can occur when recovering an identity secret from shares @@ -40,6 +48,8 @@ pub enum RecoverSecretError { /// Errors that can occur during RLN protocol operations (proof generation, verification, etc.) #[derive(Debug, thiserror::Error)] pub enum ProtocolError { + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), #[error("Error producing proof: {0}")] Synthesis(#[from] SynthesisError), #[error("RLN utility error: {0}")] @@ -56,13 +66,10 @@ pub enum ProtocolError { ZeroUserMessageLimit, #[error("Merkle proof length mismatch: expected {0}, got {1}")] InvalidMerkleProofLength(usize, usize), - #[cfg(feature = "multi-message-id")] #[error("The field message_ids must contain at least one message_id")] EmptyMessageIds, - #[cfg(feature = "multi-message-id")] #[error("Duplicate message ID found in message_ids")] DuplicateMessageIds, - #[cfg(feature = "multi-message-id")] #[error("At least one selector_used value must be true")] NoActiveSelectorUsed, #[error("The field {0} has length {1}, but the field {2} has length {3}")] @@ -74,11 +81,14 @@ pub enum ProtocolError { #[error("Merkle tree operation error: {0}")] MerkleTree(#[from] ZerokitMerkleTreeError), #[error("Proof serialization error: {0}")] - SerializationError(#[from] ark_serialize::SerializationError), - #[error("Unknown serialization version: {0:#04x}")] - UnknownSerializationVersion(u8), - #[error("Serialization version {0:#04x} is only valid with the '{1}' feature enabled")] - IncompatibleSerializationVersion(u8, &'static str), + SerializationError(#[from] SerializationError), + #[error("Unknown message mode version byte: {0:#04x}")] + UnknownMessageModeVersionByte(u8), + #[error("Witness message mode {witness_mode} does not match graph mode {graph_mode}")] + MessageModeAndGraphMismatch { + witness_mode: MessageMode, + graph_mode: MessageMode, + }, } /// Errors that can occur during proof verification diff --git a/rln/src/ffi/ffi_rln.rs b/rln/src/ffi/ffi_rln.rs index c67ff25f..70940602 100644 --- a/rln/src/ffi/ffi_rln.rs +++ b/rln/src/ffi/ffi_rln.rs @@ -71,55 +71,10 @@ pub fn ffi_rln_new() -> CResult, repr_c::String> { } } -#[cfg(all(not(feature = "stateless"), not(feature = "multi-message-id")))] -#[ffi_export] -pub fn ffi_rln_new_with_params( - tree_depth: usize, - zkey_data: &repr_c::Vec, - graph_data: &repr_c::Vec, - config_path: char_p::Ref<'_>, -) -> CResult, repr_c::String> { - let config_str = File::open(config_path.to_str()) - .and_then(|mut file| { - let metadata = file.metadata()?; - if metadata.len() > MAX_CONFIG_SIZE { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "Config file too large: {} bytes (max {} bytes)", - metadata.len(), - MAX_CONFIG_SIZE - ), - )); - } - let mut s = String::new(); - file.read_to_string(&mut s)?; - Ok(s) - }) - .unwrap_or_default(); - - match RLN::new_with_params( - tree_depth, - zkey_data.to_vec(), - graph_data.to_vec(), - config_str.as_str(), - ) { - Ok(rln) => CResult { - ok: Some(Box_::new(FFI_RLN(rln))), - err: None, - }, - Err(err) => CResult { - ok: None, - err: Some(err.to_string().into()), - }, - } -} - -#[cfg(all(not(feature = "stateless"), feature = "multi-message-id"))] +#[cfg(not(feature = "stateless"))] #[ffi_export] pub fn ffi_rln_new_with_params( tree_depth: usize, - max_out: usize, zkey_data: &repr_c::Vec, graph_data: &repr_c::Vec, config_path: char_p::Ref<'_>, @@ -145,7 +100,6 @@ pub fn ffi_rln_new_with_params( match RLN::new_with_params( tree_depth, - max_out, zkey_data.to_vec(), graph_data.to_vec(), config_str.as_str(), @@ -161,7 +115,7 @@ pub fn ffi_rln_new_with_params( } } -#[cfg(all(feature = "stateless", not(feature = "multi-message-id")))] +#[cfg(feature = "stateless")] #[ffi_export] pub fn ffi_rln_new_with_params( zkey_data: &repr_c::Vec, @@ -179,25 +133,6 @@ pub fn ffi_rln_new_with_params( } } -#[cfg(all(feature = "stateless", feature = "multi-message-id"))] -#[ffi_export] -pub fn ffi_rln_new_with_params( - zkey_data: &repr_c::Vec, - graph_data: &repr_c::Vec, - max_out: usize, -) -> CResult, repr_c::String> { - match RLN::new_with_params(zkey_data.to_vec(), graph_data.to_vec(), max_out) { - Ok(rln) => CResult { - ok: Some(Box_::new(FFI_RLN(rln))), - err: None, - }, - Err(err) => CResult { - ok: None, - err: Some(err.to_string().into()), - }, - } -} - #[ffi_export] pub fn ffi_rln_free(rln: repr_c::Box) { drop(rln); @@ -208,7 +143,6 @@ pub fn ffi_rln_get_tree_depth(rln: &repr_c::Box) -> usize { rln.0.tree_depth() } -#[cfg(feature = "multi-message-id")] #[ffi_export] pub fn ffi_rln_get_max_out(rln: &repr_c::Box) -> usize { rln.0.max_out() @@ -389,9 +323,8 @@ pub fn ffi_bytes_be_to_rln_partial_proof( #[repr(opaque)] pub struct FFI_RLNWitnessInput(pub(crate) RLNWitnessInput); -#[cfg(not(feature = "multi-message-id"))] #[ffi_export] -pub fn ffi_rln_witness_input_new( +pub fn ffi_rln_witness_input_new_single( identity_secret: &CFr, user_message_limit: &CFr, message_id: &CFr, @@ -404,7 +337,7 @@ pub fn ffi_rln_witness_input_new( let path_elements: Vec = path_elements.iter().map(|cfr| cfr.0).collect(); let identity_path_index: Vec = identity_path_index.iter().copied().collect(); - match RLNWitnessInput::new( + match RLNWitnessInput::new_single( IdSecret::from(&mut identity_secret_fr), user_message_limit.0, message_id.0, @@ -424,9 +357,8 @@ pub fn ffi_rln_witness_input_new( } } -#[cfg(feature = "multi-message-id")] #[ffi_export] -pub fn ffi_rln_witness_input_new( +pub fn ffi_rln_witness_input_new_multi( identity_secret: &CFr, user_message_limit: &CFr, message_ids: &repr_c::Vec, @@ -442,7 +374,7 @@ pub fn ffi_rln_witness_input_new( let message_ids: Vec = message_ids.iter().map(|cfr| cfr.0).collect(); let selector_used: Vec = selector_used.iter().copied().collect(); - let result = RLNWitnessInput::new( + match RLNWitnessInput::new_multi( IdSecret::from(&mut identity_secret_fr), user_message_limit.0, message_ids, @@ -451,9 +383,7 @@ pub fn ffi_rln_witness_input_new( x.0, external_nullifier.0, selector_used, - ); - - match result { + ) { Ok(witness) => CResult { ok: Some(Box_::new(FFI_RLNWitnessInput(witness))), err: None, @@ -484,7 +414,6 @@ pub fn ffi_rln_witness_input_get_user_message_limit( CFr::from(*witness.0.user_message_limit()).into() } -#[cfg(not(feature = "multi-message-id"))] #[ffi_export] pub fn ffi_rln_witness_input_get_message_id( witness: &repr_c::Box, @@ -492,7 +421,6 @@ pub fn ffi_rln_witness_input_get_message_id( CFr::from(*witness.0.message_id()).into() } -#[cfg(feature = "multi-message-id")] #[ffi_export] pub fn ffi_rln_witness_input_get_message_ids( witness: &repr_c::Box, @@ -538,7 +466,6 @@ pub fn ffi_rln_witness_input_get_external_nullifier( CFr::from(*witness.0.external_nullifier()).into() } -#[cfg(feature = "multi-message-id")] #[ffi_export] pub fn ffi_rln_witness_input_get_selector_used( witness: &repr_c::Box, @@ -805,7 +732,6 @@ pub fn ffi_rln_proof_values_get_external_nullifier( CFr::from(*pv.0.external_nullifier()).into() } -#[cfg(not(feature = "multi-message-id"))] #[ffi_export] pub fn ffi_rln_proof_values_get_y( pv: &repr_c::Box, @@ -816,7 +742,6 @@ pub fn ffi_rln_proof_values_get_y( } } -#[cfg(not(feature = "multi-message-id"))] #[ffi_export] pub fn ffi_rln_proof_values_get_nullifier( pv: &repr_c::Box, @@ -827,7 +752,6 @@ pub fn ffi_rln_proof_values_get_nullifier( } } -#[cfg(feature = "multi-message-id")] #[ffi_export] pub fn ffi_rln_proof_values_get_selector_used( pv: &repr_c::Box, @@ -838,7 +762,6 @@ pub fn ffi_rln_proof_values_get_selector_used( } } -#[cfg(feature = "multi-message-id")] #[ffi_export] pub fn ffi_rln_proof_values_get_ys( pv: &repr_c::Box, @@ -855,7 +778,6 @@ pub fn ffi_rln_proof_values_get_ys( } } -#[cfg(feature = "multi-message-id")] #[ffi_export] pub fn ffi_rln_proof_values_get_nullifiers( pv: &repr_c::Box, diff --git a/rln/src/partial_proof.rs b/rln/src/partial_proof.rs index 166ac5b5..139e02dd 100644 --- a/rln/src/partial_proof.rs +++ b/rln/src/partial_proof.rs @@ -8,7 +8,7 @@ use ark_poly::GeneralEvaluationDomain; use ark_relations::r1cs::{ ConstraintMatrices, ConstraintSynthesizer, ConstraintSystem, OptimizationGoal, SynthesisMode, }; -use ark_serialize::*; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{marker::PhantomData, ops::Mul, rand::RngCore, vec::Vec, UniformRand}; use crate::error::ProtocolError; diff --git a/rln/src/pm_tree_adapter.rs b/rln/src/pm_tree_adapter.rs index 6ba5c450..e28696b5 100644 --- a/rln/src/pm_tree_adapter.rs +++ b/rln/src/pm_tree_adapter.rs @@ -284,7 +284,7 @@ impl ZerokitMerkleTree for PmTree { values: I, ) -> Result<(), ZerokitMerkleTreeError> { let v = values.into_iter().collect::>(); - self.tree.set_range(start, v.clone().into_iter())?; + self.tree.set_range(start, v.clone())?; for i in start..v.len() { self.cached_leaves_indices[i] = 1 } diff --git a/rln/src/prelude.rs b/rln/src/prelude.rs index a752558b..cd54ca57 100644 --- a/rln/src/prelude.rs +++ b/rln/src/prelude.rs @@ -1,9 +1,7 @@ // This module re-exports the most commonly used types and functions from the RLN library -#[cfg(feature = "multi-message-id")] -pub use crate::circuit::DEFAULT_MAX_OUT; #[cfg(not(target_arch = "wasm32"))] -pub use crate::circuit::{graph_from_folder, zkey_from_folder}; +pub use crate::circuit::{graph_multi_v1, graph_single_v1, zkey_multi_v1, zkey_single_v1}; #[cfg(feature = "pmtree-ft")] pub use crate::pm_tree_adapter::{FrOf, PmTree, PmTreeProof, PmtreeConfig, PmtreeConfigBuilder}; #[cfg(not(feature = "stateless"))] @@ -12,7 +10,7 @@ pub use crate::poseidon_tree::{MerkleProof, PoseidonTree}; pub use crate::protocol::compute_tree_root; #[cfg(not(target_arch = "wasm32"))] pub use crate::{ - circuit::{graph_from_raw, Graph}, + circuit::{graph_from_raw, ArkGroth16Backend, Graph}, protocol::{ finish_zk_proof, finish_zk_proof_with_rs, generate_partial_zk_proof, generate_zk_proof, generate_zk_proof_with_rs, verify_zk_proof, @@ -21,7 +19,8 @@ pub use crate::{ pub use crate::{ circuit::{ zkey_from_raw, Curve, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective, - PartialProof, Proof, VerifyingKey, Zkey, COMPRESS_PROOF_SIZE, DEFAULT_TREE_DEPTH, + PartialProof, Proof, VerifyingKey, Zkey, COMPRESS_PROOF_SIZE, DEFAULT_MAX_OUT, + DEFAULT_TREE_DEPTH, }, error::{ProtocolError, RLNError, RecoverSecretError, UtilsError, VerifyError}, hashers::{ @@ -38,14 +37,19 @@ pub use crate::{ rln_partial_witness_to_bytes_be, rln_partial_witness_to_bytes_le, rln_proof_to_bytes_be, rln_proof_to_bytes_le, rln_proof_values_to_bytes_be, rln_proof_values_to_bytes_le, rln_witness_to_bigint_json, rln_witness_to_bytes_be, rln_witness_to_bytes_le, - seeded_keygen, RLNPartialWitnessInput, RLNProof, RLNProofValues, RLNWitnessInput, + seeded_keygen, CanonicalDeserializeBE, CanonicalSerializeBE, MessageMode, + RLNPartialWitnessInput, RLNPartialWitnessInputV3, RLNPartialZkProof, RLNProof, + RLNProofValues, RLNProofValuesMulti, RLNProofValuesSingle, RLNProofValuesV3, + RLNWitnessInput, RLNWitnessInputMulti, RLNWitnessInputSingle, RLNWitnessInputV3, + RLNZkProof, RecoverSecret, Stateful, Stateless, FR_BYTE_SIZE, FR_LIMB_BYTE_SIZE, + VEC_LEN_BYTE_SIZE, }, - public::RLN, + public::{RLN, RLNV3}, utils::{ bytes_be_to_fr, bytes_be_to_vec_fr, bytes_be_to_vec_u8, bytes_be_to_vec_usize, bytes_le_to_fr, bytes_le_to_vec_fr, bytes_le_to_vec_u8, bytes_le_to_vec_usize, fr_to_bytes_be, fr_to_bytes_le, normalize_usize_be, normalize_usize_le, str_to_fr, to_bigint, vec_fr_to_bytes_be, vec_fr_to_bytes_le, vec_u8_to_bytes_be, vec_u8_to_bytes_le, - IdSecret, FR_BYTE_SIZE, + IdSecret, }, }; diff --git a/rln/src/protocol/mod.rs b/rln/src/protocol/mod.rs index a5355c33..0e79ec5e 100644 --- a/rln/src/protocol/mod.rs +++ b/rln/src/protocol/mod.rs @@ -1,29 +1,39 @@ // This crate collects all the underlying primitives used to implement RLN mod keygen; +mod mode; mod proof; +mod serialize; mod slashing; -mod version; mod witness; +mod zk; pub use keygen::{extended_keygen, extended_seeded_keygen, keygen, seeded_keygen}; +pub use mode::{MessageMode, Stateful, Stateless}; pub use proof::{ bytes_be_to_rln_partial_proof, bytes_be_to_rln_proof, bytes_be_to_rln_proof_values, bytes_le_to_rln_partial_proof, bytes_le_to_rln_proof, bytes_le_to_rln_proof_values, generate_zk_proof_with_witness, rln_partial_proof_to_bytes_be, rln_partial_proof_to_bytes_le, rln_proof_to_bytes_be, rln_proof_to_bytes_le, rln_proof_values_to_bytes_be, - rln_proof_values_to_bytes_le, verify_zk_proof, RLNProof, RLNProofValues, + rln_proof_values_to_bytes_le, verify_zk_proof, RLNProof, RLNProofValues, RLNProofValuesMulti, + RLNProofValuesSingle, RLNProofValuesV3, }; #[cfg(not(target_arch = "wasm32"))] pub use proof::{ finish_zk_proof, finish_zk_proof_with_rs, generate_partial_zk_proof, generate_zk_proof, generate_zk_proof_with_rs, }; +pub use serialize::{ + CanonicalDeserializeBE, CanonicalSerializeBE, ENUM_TAG_MULTI, ENUM_TAG_SINGLE, ENUM_TAG_SIZE, + FR_BYTE_SIZE, FR_LIMB_BYTE_SIZE, VEC_LEN_BYTE_SIZE, +}; pub use slashing::{compute_id_secret, recover_id_secret}; -pub use version::SerializationVersion; pub use witness::{ bytes_be_to_rln_partial_witness, bytes_be_to_rln_witness, bytes_le_to_rln_partial_witness, bytes_le_to_rln_witness, compute_tree_root, proof_values_from_witness, rln_partial_witness_to_bytes_be, rln_partial_witness_to_bytes_le, rln_witness_to_bigint_json, - rln_witness_to_bytes_be, rln_witness_to_bytes_le, RLNPartialWitnessInput, RLNWitnessInput, + rln_witness_to_bytes_be, rln_witness_to_bytes_le, RLNPartialWitnessInput, + RLNPartialWitnessInputV3, RLNWitnessInput, RLNWitnessInputMulti, RLNWitnessInputSingle, + RLNWitnessInputV3, }; +pub use zk::{RLNPartialZkProof, RLNZkProof, RecoverSecret}; diff --git a/rln/src/protocol/mode.rs b/rln/src/protocol/mode.rs new file mode 100644 index 00000000..578bc390 --- /dev/null +++ b/rln/src/protocol/mode.rs @@ -0,0 +1,180 @@ +use std::fmt; + +#[cfg(not(target_arch = "wasm32"))] +use crate::circuit::Graph; +use crate::{error::ProtocolError, protocol::witness::RLNMessageInputs}; + +/// Size in bytes of the version prefix prepended to every serialized RLN structure. +pub const VERSION_BYTE_SIZE: usize = 1; + +/// Wire-format version and runtime message mode for RLN protocol types. +/// +/// Each variant encodes both the **wire byte** written at the start of every serialized +/// RLN structure and the **runtime behaviour** (how many message-id slots exist per proof). +/// +/// ### Wire layout +/// +/// Every serialized type starts with a single version byte: +/// +/// | Variant | Byte | Spec | +/// |------------|--------|------| +/// | `SingleV1` | `0x00` | | +/// | `MultiV1` | `0x01` | | +/// +/// ### Wire layouts per type +/// +/// **`RLNWitnessInput`** +/// ```text +/// SingleV1: [ 0x00 | identity_secret<32> | user_message_limit<32> | message_id<32> +/// | path_elements | identity_path_index | x<32> | external_nullifier<32> ] +/// MultiV1: [ 0x01 | identity_secret<32> | user_message_limit<32> +/// | path_elements | identity_path_index | x<32> | external_nullifier<32> +/// | message_ids | selector_used ] +/// ``` +/// +/// **`RLNPartialWitnessInput`** +/// ```text +/// SingleV1: [ 0x00 | identity_secret<32> | user_message_limit<32> +/// | path_elements | identity_path_index ] +/// MultiV1: [ 0x01 | identity_secret<32> | user_message_limit<32> +/// | path_elements | identity_path_index ] +/// ``` +/// +/// **`RLNProofValues`** +/// ```text +/// SingleV1: [ 0x00 | y<32> | root<32> | nullifier<32> | x<32> | external_nullifier<32> ] +/// MultiV1: [ 0x01 | ys | root<32> | nullifiers | x<32> | external_nullifier<32> +/// | selector_used ] +/// ``` +/// +/// **`RLNProof`** +/// ```text +/// SingleV1: [ 0x00 | proof<128> | RLNProofValues(0x00) ] +/// MultiV1: [ 0x01 | proof<128> | RLNProofValues(0x01) ] +/// ``` +/// +/// **`PartialProof`** +/// ```text +/// SingleV1: [ 0x00 | partial_proof ] +/// MultiV1: [ 0x01 | partial_proof ] +/// ``` +/// +/// ### Encoding conventions +/// - `<32>` - canonical 32-byte little-endian encoding of +/// [`ark_bn254::Fr`](https://github.com/arkworks-rs/algebra/blob/7ad88c46e859a94ab8e0b19fd8a217c3dc472f1c/curves/bn254/src/fields/fr.rs#L9). +/// - `` - length-prefixed list of `Fr`, except `identity_path_index` which is a +/// length-prefixed `Vec`. +/// - `proof<128>` - an +/// [`ark_groth16::Proof`](https://github.com/arkworks-rs/groth16/blob/9ba21ceab723d6b515a813e17846a0c0ec830c0d/src/data_structures.rs#L9) +/// over +/// [`ark_bn254::Bn254`](https://github.com/arkworks-rs/algebra/blob/7ad88c46e859a94ab8e0b19fd8a217c3dc472f1c/curves/bn254/src/curves/mod.rs#L44), +/// serialized as a fixed 128-byte little-endian canonical form (arkworks default). +/// - `partial_proof` - variable-length little-endian canonical form (arkworks default). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MessageMode { + /// Single message-id mode (RLN v2, wire byte `0x00`). + /// + /// Each proof covers exactly one message slot (`max_out = 1`). + SingleV1, + + /// Multi message-id mode (RLN v2 extension, wire byte `0x01`). + /// + /// Each proof covers up to `max_out` message slots. + MultiV1 { max_out: usize }, +} + +impl MessageMode { + /// Returns the wire-format version byte for this mode. + pub fn version_byte(&self) -> u8 { + match self { + MessageMode::SingleV1 => 0x00, + MessageMode::MultiV1 { .. } => 0x01, + } + } + + /// Returns the maximum number of message-id slots for this mode. + /// + /// Returns `1` for [`MessageMode::SingleV1`] and `max_out` for [`MessageMode::MultiV1`]. + pub fn max_out(&self) -> usize { + match self { + MessageMode::SingleV1 => 1, + MessageMode::MultiV1 { max_out } => *max_out, + } + } +} + +impl fmt::Display for MessageMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MessageMode::SingleV1 => write!(f, "SingleV1"), + MessageMode::MultiV1 { max_out } => write!(f, "MultiV1(max_out={max_out})"), + } + } +} + +/// Parses a version byte into a [`MessageMode`] discriminant for deserialization dispatch. +/// +/// For [`MessageMode::MultiV1`] the `max_out` field is set to `0` - it is a placeholder only. +impl TryFrom for MessageMode { + type Error = ProtocolError; + + fn try_from(byte: u8) -> Result { + match byte { + 0x00 => Ok(MessageMode::SingleV1), + 0x01 => Ok(MessageMode::MultiV1 { max_out: 0 }), + other => Err(ProtocolError::UnknownMessageModeVersionByte(other)), + } + } +} + +impl From<&RLNMessageInputs> for MessageMode { + /// Determines the [`MessageMode`] from the type of the provided message inputs. + fn from(inputs: &RLNMessageInputs) -> Self { + match inputs { + RLNMessageInputs::SingleV1 { .. } => MessageMode::SingleV1, + RLNMessageInputs::MultiV1 { message_ids, .. } => MessageMode::MultiV1 { + max_out: message_ids.len(), + }, + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From<&Graph> for MessageMode { + /// Determines the [`MessageMode`] from the graph's `max_out` value. + fn from(graph: &Graph) -> Self { + if graph.max_out <= 1 { + MessageMode::SingleV1 + } else { + MessageMode::MultiV1 { + max_out: graph.max_out, + } + } + } +} + +#[derive(Debug, Clone)] +pub struct Stateful { + pub tree: T, +} + +impl Stateful { + pub fn new(tree: T) -> Self { + Self { tree } + } + + pub fn tree(&self) -> &T { + &self.tree + } + + pub fn tree_mut(&mut self) -> &mut T { + &mut self.tree + } + + pub fn into_tree(self) -> T { + self.tree + } +} + +#[derive(Debug, Clone)] +pub struct Stateless; diff --git a/rln/src/protocol/proof.rs b/rln/src/protocol/proof.rs index 4ebd508a..3d4e6d5d 100644 --- a/rln/src/protocol/proof.rs +++ b/rln/src/protocol/proof.rs @@ -5,17 +5,16 @@ use ark_std::{rand::thread_rng, UniformRand}; use num_bigint::BigInt; use num_traits::Signed; -use super::version::{SerializationVersion, VERSION_BYTE_SIZE}; #[cfg(not(target_arch = "wasm32"))] use super::witness::{ - inputs_for_partial_witness_calculation, inputs_for_witness_calculation, RLNPartialWitnessInput, - RLNWitnessInput, + inputs_for_partial_witness_calculation, inputs_for_witness_calculation, RLNMessageInputs, + RLNPartialWitnessInput, RLNWitnessInput, }; -#[cfg(feature = "multi-message-id")] -use crate::utils::{ - bytes_be_to_vec_bool, bytes_be_to_vec_fr, bytes_le_to_vec_bool, bytes_le_to_vec_fr, - vec_bool_to_bytes_be, vec_bool_to_bytes_le, vec_fr_to_bytes_be, vec_fr_to_bytes_le, - VEC_LEN_BYTE_SIZE, +use super::{ + mode::{MessageMode, VERSION_BYTE_SIZE}, + witness::RLNWitnessInputV3, + zk::RecoverSecret, + FR_BYTE_SIZE, VEC_LEN_BYTE_SIZE, }; #[cfg(not(target_arch = "wasm32"))] use crate::{ @@ -31,14 +30,18 @@ use crate::{ COMPRESS_PROOF_SIZE, }, error::ProtocolError, - utils::{bytes_be_to_fr, bytes_le_to_fr, fr_to_bytes_be, fr_to_bytes_le, FR_BYTE_SIZE}, + utils::{ + bytes_be_to_fr, bytes_be_to_vec_bool, bytes_be_to_vec_fr, bytes_le_to_fr, + bytes_le_to_vec_bool, bytes_le_to_vec_fr, fr_to_bytes_be, fr_to_bytes_le, + vec_bool_to_bytes_be, vec_bool_to_bytes_le, vec_fr_to_bytes_be, vec_fr_to_bytes_le, + }, }; /// Complete RLN proof. /// /// Combines the Groth16 proof with its public values. /// -/// The serialization format for this type is defined in the `protocol::version` module. +/// The serialization format for this type is defined in the `protocol::mode` module. #[derive(Debug, PartialEq, Clone)] pub struct RLNProof { pub proof: Proof, @@ -55,9 +58,10 @@ impl RLNProof { /// Variant-specific outputs for RLN proof verification. #[derive(Debug, PartialEq, Clone)] pub(crate) enum RLNOutputs { - #[cfg(not(feature = "multi-message-id"))] - SingleV1 { y: Fr, nullifier: Fr }, - #[cfg(feature = "multi-message-id")] + SingleV1 { + y: Fr, + nullifier: Fr, + }, MultiV1 { ys: Vec, nullifiers: Vec, @@ -70,7 +74,7 @@ pub(crate) enum RLNOutputs { /// Contains the circuit's public inputs and outputs. Used in proof verification /// and identity secret recovery when rate limit violations are detected. /// -/// The serialization format for this type is defined in the `protocol::version` module. +/// The serialization format for this type is defined in the `protocol::mode` module. #[derive(Debug, PartialEq, Clone)] pub struct RLNProofValues { root: Fr, @@ -80,9 +84,8 @@ pub struct RLNProofValues { } impl RLNProofValues { - /// Creates a new RLNProofValues instance. - #[cfg(not(feature = "multi-message-id"))] - pub fn new(root: Fr, x: Fr, external_nullifier: Fr, y: Fr, nullifier: Fr) -> Self { + /// Creates a new single message-id RLNProofValues. + pub fn new_single(root: Fr, x: Fr, external_nullifier: Fr, y: Fr, nullifier: Fr) -> Self { Self { root, x, @@ -91,9 +94,8 @@ impl RLNProofValues { } } - /// Creates a new RLNProofValues instance. - #[cfg(feature = "multi-message-id")] - pub fn new( + /// Creates a new multi message-id RLNProofValues. + pub fn new_multi( root: Fr, x: Fr, external_nullifier: Fr, @@ -116,10 +118,8 @@ impl RLNProofValues { /// Returns the version byte corresponding to the proof values variant. pub fn version_byte(&self) -> u8 { match &self.outputs { - #[cfg(not(feature = "multi-message-id"))] - RLNOutputs::SingleV1 { .. } => SerializationVersion::SingleV1.into(), - #[cfg(feature = "multi-message-id")] - RLNOutputs::MultiV1 { .. } => SerializationVersion::MultiV1.into(), + RLNOutputs::SingleV1 { .. } => MessageMode::SingleV1.version_byte(), + RLNOutputs::MultiV1 { .. } => MessageMode::MultiV1 { max_out: 0 }.version_byte(), } } @@ -138,39 +138,54 @@ impl RLNProofValues { &self.external_nullifier } - /// Returns the output `y` value. - #[cfg(not(feature = "multi-message-id"))] + /// Returns the `y` value (only valid for SingleV1). pub fn y(&self) -> &Fr { - let RLNOutputs::SingleV1 { y, .. } = &self.outputs; - y + match &self.outputs { + RLNOutputs::SingleV1 { y, .. } => y, + RLNOutputs::MultiV1 { .. } => { + panic!("y() is not available for MultiV1 proof values; use ys()") + } + } } - /// Returns the nullifier value. - #[cfg(not(feature = "multi-message-id"))] + /// Returns the nullifier (only valid for SingleV1). pub fn nullifier(&self) -> &Fr { - let RLNOutputs::SingleV1 { nullifier, .. } = &self.outputs; - nullifier + match &self.outputs { + RLNOutputs::SingleV1 { nullifier, .. } => nullifier, + RLNOutputs::MultiV1 { .. } => { + panic!("nullifier() is not available for MultiV1 proof values; use nullifiers()") + } + } } - /// Returns the selector flags. - #[cfg(feature = "multi-message-id")] + /// Returns the selector flags (only valid for MultiV1). pub fn selector_used(&self) -> &[bool] { - let RLNOutputs::MultiV1 { selector_used, .. } = &self.outputs; - selector_used + match &self.outputs { + RLNOutputs::MultiV1 { selector_used, .. } => selector_used, + RLNOutputs::SingleV1 { .. } => { + panic!("selector_used() is not available for SingleV1 proof values") + } + } } - /// Returns the per-message-id output `y` values. - #[cfg(feature = "multi-message-id")] + /// Returns the per-message-id `y` values (only valid for MultiV1). pub fn ys(&self) -> &[Fr] { - let RLNOutputs::MultiV1 { ys, .. } = &self.outputs; - ys + match &self.outputs { + RLNOutputs::MultiV1 { ys, .. } => ys, + RLNOutputs::SingleV1 { .. } => { + panic!("ys() is not available for SingleV1 proof values; use y()") + } + } } - /// Returns the per-message-id nullifiers. - #[cfg(feature = "multi-message-id")] + /// Returns the per-message-id nullifiers (only valid for MultiV1). pub fn nullifiers(&self) -> &[Fr] { - let RLNOutputs::MultiV1 { nullifiers, .. } = &self.outputs; - nullifiers + match &self.outputs { + RLNOutputs::MultiV1 { nullifiers, .. } => nullifiers, + RLNOutputs::SingleV1 { .. } => { + panic!("nullifiers() is not available for SingleV1 proof values; use nullifier()") + } + } } } @@ -183,46 +198,40 @@ pub fn rln_proof_values_to_bytes_le(rln_proof_values: &RLNProofValues) -> Vec VERSION_BYTE_SIZE + FR_BYTE_SIZE * 5, + RLNOutputs::MultiV1 { + ys, + nullifiers, + selector_used, + } => { + VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (3 + ys.len() + nullifiers.len()) + + selector_used.len() + + VEC_LEN_BYTE_SIZE * 3 + } + }; let mut bytes = Vec::with_capacity(capacity); bytes.push(rln_proof_values.version_byte()); bytes.extend_from_slice(&fr_to_bytes_le(root)); bytes.extend_from_slice(&fr_to_bytes_le(external_nullifier)); bytes.extend_from_slice(&fr_to_bytes_le(x)); - #[cfg(not(feature = "multi-message-id"))] - { - bytes.extend_from_slice(&fr_to_bytes_le(y)); - bytes.extend_from_slice(&fr_to_bytes_le(nullifier)); - } - #[cfg(feature = "multi-message-id")] - { - bytes.extend_from_slice(&vec_fr_to_bytes_le(ys)); - bytes.extend_from_slice(&vec_fr_to_bytes_le(nullifiers)); - bytes.extend_from_slice(&vec_bool_to_bytes_le(selector_used)); + + match outputs { + RLNOutputs::SingleV1 { y, nullifier } => { + bytes.extend_from_slice(&fr_to_bytes_le(y)); + bytes.extend_from_slice(&fr_to_bytes_le(nullifier)); + } + RLNOutputs::MultiV1 { + ys, + nullifiers, + selector_used, + } => { + bytes.extend_from_slice(&vec_fr_to_bytes_le(ys)); + bytes.extend_from_slice(&vec_fr_to_bytes_le(nullifiers)); + bytes.extend_from_slice(&vec_bool_to_bytes_le(selector_used)); + } } bytes } @@ -236,46 +245,40 @@ pub fn rln_proof_values_to_bytes_be(rln_proof_values: &RLNProofValues) -> Vec VERSION_BYTE_SIZE + FR_BYTE_SIZE * 5, + RLNOutputs::MultiV1 { + ys, + nullifiers, + selector_used, + } => { + VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (3 + ys.len() + nullifiers.len()) + + selector_used.len() + + VEC_LEN_BYTE_SIZE * 3 + } + }; let mut bytes = Vec::with_capacity(capacity); bytes.push(rln_proof_values.version_byte()); bytes.extend_from_slice(&fr_to_bytes_be(root)); bytes.extend_from_slice(&fr_to_bytes_be(external_nullifier)); bytes.extend_from_slice(&fr_to_bytes_be(x)); - #[cfg(not(feature = "multi-message-id"))] - { - bytes.extend_from_slice(&fr_to_bytes_be(y)); - bytes.extend_from_slice(&fr_to_bytes_be(nullifier)); - } - #[cfg(feature = "multi-message-id")] - { - bytes.extend_from_slice(&vec_fr_to_bytes_be(ys)); - bytes.extend_from_slice(&vec_fr_to_bytes_be(nullifiers)); - bytes.extend_from_slice(&vec_bool_to_bytes_be(selector_used)); + + match outputs { + RLNOutputs::SingleV1 { y, nullifier } => { + bytes.extend_from_slice(&fr_to_bytes_be(y)); + bytes.extend_from_slice(&fr_to_bytes_be(nullifier)); + } + RLNOutputs::MultiV1 { + ys, + nullifiers, + selector_used, + } => { + bytes.extend_from_slice(&vec_fr_to_bytes_be(ys)); + bytes.extend_from_slice(&vec_fr_to_bytes_be(nullifiers)); + bytes.extend_from_slice(&vec_bool_to_bytes_be(selector_used)); + } } bytes } @@ -290,7 +293,7 @@ pub fn bytes_le_to_rln_proof_values( return Err(ProtocolError::InvalidReadLen(1, 0)); } - let _version = SerializationVersion::try_from(bytes[0])?; + let version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; let (root, el_size) = bytes_le_to_fr(&bytes[read..])?; @@ -300,40 +303,40 @@ pub fn bytes_le_to_rln_proof_values( let (x, el_size) = bytes_le_to_fr(&bytes[read..])?; read += el_size; - #[cfg(not(feature = "multi-message-id"))] - let proof_values = { - let (y, el_size) = bytes_le_to_fr(&bytes[read..])?; - read += el_size; - let (nullifier, el_size) = bytes_le_to_fr(&bytes[read..])?; - read += el_size; - RLNProofValues::new(root, x, external_nullifier, y, nullifier) - }; - #[cfg(feature = "multi-message-id")] - let proof_values = { - let (ys, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; - read += el_size; - let (nullifiers, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; - read += el_size; - let (selector_used, el_size) = bytes_le_to_vec_bool(&bytes[read..])?; - read += el_size; - - if selector_used.len() != ys.len() { - return Err(ProtocolError::FieldLengthMismatch( - "ys", - ys.len(), - "selector_used", - selector_used.len(), - )); + let proof_values = match version { + MessageMode::SingleV1 => { + let (y, el_size) = bytes_le_to_fr(&bytes[read..])?; + read += el_size; + let (nullifier, el_size) = bytes_le_to_fr(&bytes[read..])?; + read += el_size; + RLNProofValues::new_single(root, x, external_nullifier, y, nullifier) } - if nullifiers.len() != ys.len() { - return Err(ProtocolError::FieldLengthMismatch( - "ys", - ys.len(), - "nullifiers", - nullifiers.len(), - )); + MessageMode::MultiV1 { .. } => { + let (ys, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; + read += el_size; + let (nullifiers, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; + read += el_size; + let (selector_used, el_size) = bytes_le_to_vec_bool(&bytes[read..])?; + read += el_size; + + if selector_used.len() != ys.len() { + return Err(ProtocolError::FieldLengthMismatch( + "ys", + ys.len(), + "selector_used", + selector_used.len(), + )); + } + if nullifiers.len() != ys.len() { + return Err(ProtocolError::FieldLengthMismatch( + "ys", + ys.len(), + "nullifiers", + nullifiers.len(), + )); + } + RLNProofValues::new_multi(root, x, external_nullifier, ys, nullifiers, selector_used) } - RLNProofValues::new(root, x, external_nullifier, ys, nullifiers, selector_used) }; if read != bytes.len() { @@ -352,7 +355,7 @@ pub fn bytes_be_to_rln_proof_values( return Err(ProtocolError::InvalidReadLen(1, 0)); } - let _version = SerializationVersion::try_from(bytes[0])?; + let version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; let (root, el_size) = bytes_be_to_fr(&bytes[read..])?; @@ -362,40 +365,40 @@ pub fn bytes_be_to_rln_proof_values( let (x, el_size) = bytes_be_to_fr(&bytes[read..])?; read += el_size; - #[cfg(not(feature = "multi-message-id"))] - let proof_values = { - let (y, el_size) = bytes_be_to_fr(&bytes[read..])?; - read += el_size; - let (nullifier, el_size) = bytes_be_to_fr(&bytes[read..])?; - read += el_size; - RLNProofValues::new(root, x, external_nullifier, y, nullifier) - }; - #[cfg(feature = "multi-message-id")] - let proof_values = { - let (ys, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; - read += el_size; - let (nullifiers, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; - read += el_size; - let (selector_used, el_size) = bytes_be_to_vec_bool(&bytes[read..])?; - read += el_size; - - if selector_used.len() != ys.len() { - return Err(ProtocolError::FieldLengthMismatch( - "ys", - ys.len(), - "selector_used", - selector_used.len(), - )); + let proof_values = match version { + MessageMode::SingleV1 => { + let (y, el_size) = bytes_be_to_fr(&bytes[read..])?; + read += el_size; + let (nullifier, el_size) = bytes_be_to_fr(&bytes[read..])?; + read += el_size; + RLNProofValues::new_single(root, x, external_nullifier, y, nullifier) } - if nullifiers.len() != ys.len() { - return Err(ProtocolError::FieldLengthMismatch( - "ys", - ys.len(), - "nullifiers", - nullifiers.len(), - )); + MessageMode::MultiV1 { .. } => { + let (ys, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; + read += el_size; + let (nullifiers, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; + read += el_size; + let (selector_used, el_size) = bytes_be_to_vec_bool(&bytes[read..])?; + read += el_size; + + if selector_used.len() != ys.len() { + return Err(ProtocolError::FieldLengthMismatch( + "ys", + ys.len(), + "selector_used", + selector_used.len(), + )); + } + if nullifiers.len() != ys.len() { + return Err(ProtocolError::FieldLengthMismatch( + "ys", + ys.len(), + "nullifiers", + nullifiers.len(), + )); + } + RLNProofValues::new_multi(root, x, external_nullifier, ys, nullifiers, selector_used) } - RLNProofValues::new(root, x, external_nullifier, ys, nullifiers, selector_used) }; if read != bytes.len() { @@ -454,7 +457,7 @@ pub fn bytes_le_to_rln_proof(bytes: &[u8]) -> Result<(RLNProof, usize), Protocol return Err(ProtocolError::InvalidReadLen(1, 0)); } - let _version = SerializationVersion::try_from(bytes[0])?; + let _version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; // Deserialize proof (always LE from arkworks) @@ -493,7 +496,7 @@ pub fn bytes_be_to_rln_proof(bytes: &[u8]) -> Result<(RLNProof, usize), Protocol return Err(ProtocolError::InvalidReadLen(1, 0)); } - let _version = SerializationVersion::try_from(bytes[0])?; + let _version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; // Deserialize proof (always LE from arkworks) @@ -525,14 +528,7 @@ pub fn bytes_be_to_rln_proof(bytes: &[u8]) -> Result<(RLNProof, usize), Protocol impl PartialProof { /// Returns the version byte corresponding to the partial proof variant. pub fn version_byte(&self) -> u8 { - #[cfg(not(feature = "multi-message-id"))] - { - SerializationVersion::SingleV1.into() - } - #[cfg(feature = "multi-message-id")] - { - SerializationVersion::MultiV1.into() - } + MessageMode::SingleV1.version_byte() } } @@ -542,10 +538,7 @@ impl PartialProof { pub fn rln_partial_proof_to_bytes_le( partial_proof: &PartialProof, ) -> Result, ProtocolError> { - #[cfg(not(feature = "multi-message-id"))] - let version_byte: u8 = SerializationVersion::SingleV1.into(); - #[cfg(feature = "multi-message-id")] - let version_byte: u8 = SerializationVersion::MultiV1.into(); + let version_byte: u8 = MessageMode::SingleV1.version_byte(); // The compressed PartialProof size is variable (depends on circuit size). let mut bytes = Vec::new(); @@ -571,7 +564,7 @@ pub fn bytes_le_to_rln_partial_proof(bytes: &[u8]) -> Result<(PartialProof, usiz return Err(ProtocolError::InvalidReadLen(1, 0)); } - let _version = SerializationVersion::try_from(bytes[0])?; + let _version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; let mut bytes_ref = &bytes[read..]; @@ -624,22 +617,22 @@ fn calculated_witness_to_field_elements( /// Validates that a partial witness's dimensions match the graph's expected tree depth. #[cfg(not(target_arch = "wasm32"))] fn validate_partial_witness_against_graph( - witness: &RLNPartialWitnessInput, + partial_witness: &RLNPartialWitnessInput, graph: &Graph, ) -> Result<(), ProtocolError> { let expected_tree_depth = graph.tree_depth; - if witness.path_elements().len() != expected_tree_depth { + if partial_witness.path_elements().len() != expected_tree_depth { return Err(ProtocolError::FieldLengthMismatch( "path_elements", - witness.path_elements().len(), + partial_witness.path_elements().len(), "tree_depth", expected_tree_depth, )); } - if witness.identity_path_index().len() != expected_tree_depth { + if partial_witness.identity_path_index().len() != expected_tree_depth { return Err(ProtocolError::FieldLengthMismatch( "identity_path_index", - witness.identity_path_index().len(), + partial_witness.identity_path_index().len(), "tree_depth", expected_tree_depth, )); @@ -671,21 +664,33 @@ fn validate_witness_against_graph( )); } - #[cfg(feature = "multi-message-id")] + let witness_mode = MessageMode::from(&witness.message_inputs); + let graph_mode = MessageMode::from(graph); + if witness_mode != graph_mode { + return Err(ProtocolError::MessageModeAndGraphMismatch { + witness_mode, + graph_mode, + }); + } + + if let RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + } = &witness.message_inputs { let expected_max_out = graph.max_out; - if witness.message_ids().len() != expected_max_out { + if message_ids.len() != expected_max_out { return Err(ProtocolError::FieldLengthMismatch( "message_ids", - witness.message_ids().len(), + message_ids.len(), "max_out", expected_max_out, )); } - if witness.selector_used().len() != expected_max_out { + if selector_used.len() != expected_max_out { return Err(ProtocolError::FieldLengthMismatch( "selector_used", - witness.selector_used().len(), + selector_used.len(), "max_out", expected_max_out, )); @@ -782,13 +787,9 @@ pub fn generate_partial_zk_proof( graph: &Graph, ) -> Result { validate_partial_witness_against_graph(partial_witness, graph)?; - let inputs = inputs_for_partial_witness_calculation( - partial_witness, - #[cfg(feature = "multi-message-id")] - graph.max_out, - ) - .into_iter() - .map(|(name, values)| (name.to_string(), values)); + let inputs = inputs_for_partial_witness_calculation(partial_witness, graph.max_out) + .into_iter() + .map(|(name, values)| (name.to_string(), values)); let full_assignment = calc_witness_partial(inputs, graph)?; let mut partial_values = Vec::with_capacity(full_assignment.len() - 1); @@ -859,34 +860,30 @@ pub fn verify_zk_proof( proof_values: &RLNProofValues, ) -> Result { // We re-arrange proof-values according to the circuit specification - #[cfg(not(feature = "multi-message-id"))] - let inputs = { - let RLNOutputs::SingleV1 { y, nullifier } = &proof_values.outputs; - vec![ + let inputs = match &proof_values.outputs { + RLNOutputs::SingleV1 { y, nullifier } => vec![ *y, proof_values.root, *nullifier, proof_values.x, proof_values.external_nullifier, - ] - }; - #[cfg(feature = "multi-message-id")] - let inputs = { - let RLNOutputs::MultiV1 { + ], + RLNOutputs::MultiV1 { ys, nullifiers, selector_used, - } = &proof_values.outputs; - let mut inputs = Vec::with_capacity(3 * ys.len() + 3); - inputs.extend_from_slice(ys); - inputs.push(proof_values.root); - inputs.extend_from_slice(nullifiers); - inputs.push(proof_values.x); - inputs.push(proof_values.external_nullifier); - for &used in selector_used.iter() { - inputs.push(Fr::from(used)); + } => { + let mut inputs = Vec::with_capacity(3 * ys.len() + 3); + inputs.extend_from_slice(ys); + inputs.push(proof_values.root); + inputs.extend_from_slice(nullifiers); + inputs.push(proof_values.x); + inputs.push(proof_values.external_nullifier); + for &used in selector_used.iter() { + inputs.push(Fr::from(used)); + } + inputs } - inputs }; // Check that the proof is valid @@ -896,3 +893,92 @@ pub fn verify_zk_proof( Ok(verified) } + +#[derive(Debug, PartialEq, Clone)] +pub enum RLNProofValuesV3 { + Single(RLNProofValuesSingle), + Multi(RLNProofValuesMulti), +} + +impl TryFrom for RLNProofValuesV3 { + type Error = ProtocolError; + + fn try_from(_witness: RLNWitnessInputV3) -> Result { + todo!() + } +} + +impl RecoverSecret for RLNProofValuesV3 { + type Error = ProtocolError; + + fn recover_secret(&self, _other: &Self) -> Result { + todo!() + } +} + +#[derive(Debug, PartialEq, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct RLNProofValuesSingle { + pub y: Fr, + pub root: Fr, + pub nullifier: Fr, + pub x: Fr, + pub external_nullifier: Fr, +} + +impl TryFrom for RLNProofValuesSingle { + type Error = ProtocolError; + + fn try_from(_witness: RLNWitnessInputV3) -> Result { + todo!() + } +} + +impl RecoverSecret for RLNProofValuesSingle { + type Error = ProtocolError; + + fn recover_secret(&self, _other: &Self) -> Result { + todo!() + } +} + +impl RecoverSecret for RLNProofValuesSingle { + type Error = ProtocolError; + + fn recover_secret(&self, _other: &RLNProofValuesMulti) -> Result { + todo!() + } +} + +#[derive(Debug, PartialEq, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct RLNProofValuesMulti { + pub ys: Vec, + pub root: Fr, + pub nullifiers: Vec, + pub x: Fr, + pub external_nullifier: Fr, + pub selector_used: Vec, +} + +impl TryFrom for RLNProofValuesMulti { + type Error = ProtocolError; + + fn try_from(_witness: RLNWitnessInputV3) -> Result { + todo!() + } +} + +impl RecoverSecret for RLNProofValuesMulti { + type Error = ProtocolError; + + fn recover_secret(&self, _other: &Self) -> Result { + todo!() + } +} + +impl RecoverSecret for RLNProofValuesMulti { + type Error = ProtocolError; + + fn recover_secret(&self, _other: &RLNProofValuesSingle) -> Result { + todo!() + } +} diff --git a/rln/src/protocol/serialize.rs b/rln/src/protocol/serialize.rs new file mode 100644 index 00000000..7e928458 --- /dev/null +++ b/rln/src/protocol/serialize.rs @@ -0,0 +1,666 @@ +//! Big-endian serialization is implemented manually via [`CanonicalSerializeBE`] / +//! [`CanonicalDeserializeBE`] traits defined here. Little-endian serialization +//! relies on arkworks' [`CanonicalSerialize`] derive for structs; we hand-write +//! it here only for V3 enums (witness, proof values), since arkworks does not +//! derive enum impls. + +use std::io::{Read, Write}; + +use ark_ff::PrimeField; +use ark_serialize::{ + CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError, Valid, Validate, +}; +use zeroize::Zeroizing; + +use super::{ + proof::{RLNProofValuesMulti, RLNProofValuesSingle, RLNProofValuesV3}, + witness::{ + RLNPartialWitnessInputV3, RLNWitnessInputMulti, RLNWitnessInputSingle, RLNWitnessInputV3, + }, +}; +use crate::{ + circuit::Fr, + error::{ProtocolError, UtilsError}, + utils::{normalize_usize_be, IdSecret}, +}; + +/// Byte size of the enum variant tag prepended to serialized enum types. +pub const ENUM_TAG_SIZE: usize = 1; + +/// Tag byte for the `Single` variant — Single message-id mode. +pub const ENUM_TAG_SINGLE: u8 = 0; + +/// Tag byte for the `Multi` variant — Multi message-id mode. +pub const ENUM_TAG_MULTI: u8 = 1; + +/// Byte size of a `Fr` field element aligned to 64-bit boundary, computed once at compile time. +pub const FR_BYTE_SIZE: usize = { + // Get the modulus bit size of the scalar field + let modulus_bits: u32 = Fr::MODULUS_BIT_SIZE; + // Alignment boundary in bits for field element serialization + let alignment_bits: u32 = 64; + // Align to the next multiple of alignment_bits and convert to bytes + ((modulus_bits + alignment_bits - (modulus_bits % alignment_bits)) / 8) as usize +}; + +/// Byte size of a single 64-bit limb used in `Fr` field element serialization. +pub const FR_LIMB_BYTE_SIZE: usize = 8; + +/// Byte size of the length prefix used when serializing variable-length vectors. +pub const VEC_LEN_BYTE_SIZE: usize = 8; + +pub trait CanonicalSerializeBE { + type Error; + + fn serialize(&self, writer: W) -> Result<(), Self::Error>; + fn serialized_size(&self) -> usize; +} + +pub trait CanonicalDeserializeBE: Sized { + type Error; + + fn deserialize(reader: R) -> Result; +} + +impl CanonicalSerializeBE for Fr { + type Error = UtilsError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + let bigint = self.into_bigint(); + let mut buf = [0u8; FR_BYTE_SIZE]; + for (i, &limb) in bigint.0.iter().rev().enumerate() { + buf[i * FR_LIMB_BYTE_SIZE..(i + 1) * FR_LIMB_BYTE_SIZE] + .copy_from_slice(&limb.to_be_bytes()); + } + writer.write_all(buf.as_ref()).map_err(UtilsError::IoError) + } + + fn serialized_size(&self) -> usize { + FR_BYTE_SIZE + } +} + +impl CanonicalDeserializeBE for Fr { + type Error = UtilsError; + + fn deserialize(mut reader: R) -> Result { + let mut buf = [0u8; FR_BYTE_SIZE]; + reader.read_exact(&mut buf)?; + let mut limbs = [0u64; FR_BYTE_SIZE / FR_LIMB_BYTE_SIZE]; + for (i, limb) in limbs.iter_mut().enumerate() { + let start = i * FR_LIMB_BYTE_SIZE; + *limb = u64::from_be_bytes(buf[start..start + FR_LIMB_BYTE_SIZE].try_into()?); + } + limbs.reverse(); + let bigint = ark_ff::BigInt(limbs); + if bigint >= Fr::MODULUS { + return Err(UtilsError::NonCanonicalFieldElement); + } + Ok(Fr::from(bigint)) + } +} + +impl CanonicalSerializeBE for IdSecret { + type Error = UtilsError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + let bigint = Zeroizing::new(self.into_bigint()); + let mut buf = Zeroizing::new([0u8; FR_BYTE_SIZE]); + for (i, &limb) in bigint.0.iter().rev().enumerate() { + buf[i * FR_LIMB_BYTE_SIZE..(i + 1) * FR_LIMB_BYTE_SIZE] + .copy_from_slice(&limb.to_be_bytes()); + } + writer.write_all(buf.as_ref()).map_err(UtilsError::IoError) + } + + fn serialized_size(&self) -> usize { + FR_BYTE_SIZE + } +} + +impl CanonicalDeserializeBE for IdSecret { + type Error = UtilsError; + + fn deserialize(mut reader: R) -> Result { + let mut buf = Zeroizing::new([0u8; FR_BYTE_SIZE]); + reader.read_exact(buf.as_mut())?; + let mut limbs = Zeroizing::new([0u64; FR_BYTE_SIZE / FR_LIMB_BYTE_SIZE]); + for (i, limb) in limbs.iter_mut().enumerate() { + let start = i * FR_LIMB_BYTE_SIZE; + *limb = u64::from_be_bytes(buf[start..start + FR_LIMB_BYTE_SIZE].try_into()?); + } + limbs.reverse(); + let bigint = Zeroizing::new(ark_ff::BigInt(*limbs)); + if *bigint >= Fr::MODULUS { + return Err(UtilsError::NonCanonicalFieldElement); + } + let mut fr = Fr::from(*bigint); + Ok(IdSecret::from(&mut fr)) + } +} + +impl CanonicalSerializeBE for Vec { + type Error = UtilsError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + writer + .write_all(&normalize_usize_be(self.len())) + .map_err(UtilsError::IoError)?; + for fr in self { + fr.serialize(&mut writer)?; + } + Ok(()) + } + + fn serialized_size(&self) -> usize { + VEC_LEN_BYTE_SIZE + FR_BYTE_SIZE * self.len() + } +} + +impl CanonicalDeserializeBE for Vec { + type Error = UtilsError; + + fn deserialize(mut reader: R) -> Result { + let mut len_buf = [0u8; VEC_LEN_BYTE_SIZE]; + reader.read_exact(&mut len_buf)?; + let count = usize::try_from(u64::from_be_bytes(len_buf))?; + let mut result = Vec::with_capacity(count); + for _ in 0..count { + result.push(Fr::deserialize(&mut reader)?); + } + Ok(result) + } +} + +impl CanonicalSerializeBE for Vec { + type Error = UtilsError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + writer + .write_all(&normalize_usize_be(self.len())) + .map_err(UtilsError::IoError)?; + writer.write_all(self).map_err(UtilsError::IoError) + } + + fn serialized_size(&self) -> usize { + VEC_LEN_BYTE_SIZE + self.len() + } +} + +impl CanonicalDeserializeBE for Vec { + type Error = UtilsError; + + fn deserialize(mut reader: R) -> Result { + let mut len_buf = [0u8; VEC_LEN_BYTE_SIZE]; + reader.read_exact(&mut len_buf)?; + let count = usize::try_from(u64::from_be_bytes(len_buf))?; + let mut result = vec![0u8; count]; + reader.read_exact(&mut result)?; + Ok(result) + } +} + +impl CanonicalSerializeBE for Vec { + type Error = UtilsError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + writer + .write_all(&normalize_usize_be(self.len())) + .map_err(UtilsError::IoError)?; + for &b in self { + writer.write_all(&[b as u8]).map_err(UtilsError::IoError)?; + } + Ok(()) + } + + fn serialized_size(&self) -> usize { + VEC_LEN_BYTE_SIZE + self.len() + } +} + +impl CanonicalDeserializeBE for Vec { + type Error = UtilsError; + + fn deserialize(mut reader: R) -> Result { + let mut len_buf = [0u8; VEC_LEN_BYTE_SIZE]; + reader.read_exact(&mut len_buf)?; + let count = usize::try_from(u64::from_be_bytes(len_buf))?; + let mut raw = vec![0u8; count]; + reader.read_exact(&mut raw)?; + raw.into_iter() + .map(|b| match b { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(UtilsError::NonCanonicalBool(b)), + }) + .collect() + } +} + +impl Valid for RLNWitnessInputV3 { + fn check(&self) -> Result<(), SerializationError> { + match self { + RLNWitnessInputV3::Single(inner) => inner.check(), + RLNWitnessInputV3::Multi(inner) => inner.check(), + } + } +} + +impl CanonicalSerialize for RLNWitnessInputV3 { + fn serialize_with_mode( + &self, + mut writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + match self { + RLNWitnessInputV3::Single(inner) => { + ENUM_TAG_SINGLE.serialize_with_mode(&mut writer, compress)?; + inner.serialize_with_mode(&mut writer, compress) + } + RLNWitnessInputV3::Multi(inner) => { + ENUM_TAG_MULTI.serialize_with_mode(&mut writer, compress)?; + inner.serialize_with_mode(&mut writer, compress) + } + } + } + + fn serialized_size(&self, compress: Compress) -> usize { + ENUM_TAG_SIZE + + match self { + RLNWitnessInputV3::Single(inner) => { + CanonicalSerialize::serialized_size(inner, compress) + } + RLNWitnessInputV3::Multi(inner) => { + CanonicalSerialize::serialized_size(inner, compress) + } + } + } +} + +impl CanonicalDeserialize for RLNWitnessInputV3 { + fn deserialize_with_mode( + mut reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + let tag = u8::deserialize_with_mode(&mut reader, compress, validate)?; + match tag { + ENUM_TAG_SINGLE => Ok(RLNWitnessInputV3::Single( + RLNWitnessInputSingle::deserialize_with_mode(reader, compress, validate)?, + )), + ENUM_TAG_MULTI => Ok(RLNWitnessInputV3::Multi( + RLNWitnessInputMulti::deserialize_with_mode(reader, compress, validate)?, + )), + _ => Err(SerializationError::InvalidData), + } + } +} + +impl CanonicalSerializeBE for RLNWitnessInputV3 { + type Error = ProtocolError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + match self { + RLNWitnessInputV3::Single(inner) => { + writer.write_all(&[ENUM_TAG_SINGLE])?; + inner.serialize(&mut writer) + } + RLNWitnessInputV3::Multi(inner) => { + writer.write_all(&[ENUM_TAG_MULTI])?; + inner.serialize(&mut writer) + } + } + } + + fn serialized_size(&self) -> usize { + ENUM_TAG_SIZE + + match self { + RLNWitnessInputV3::Single(inner) => CanonicalSerializeBE::serialized_size(inner), + RLNWitnessInputV3::Multi(inner) => CanonicalSerializeBE::serialized_size(inner), + } + } +} + +impl CanonicalDeserializeBE for RLNWitnessInputV3 { + type Error = ProtocolError; + + fn deserialize(mut reader: R) -> Result { + let mut tag = [0u8; ENUM_TAG_SIZE]; + reader.read_exact(&mut tag)?; + match tag[0] { + ENUM_TAG_SINGLE => Ok(RLNWitnessInputV3::Single( + RLNWitnessInputSingle::deserialize(reader)?, + )), + ENUM_TAG_MULTI => Ok(RLNWitnessInputV3::Multi(RLNWitnessInputMulti::deserialize( + reader, + )?)), + _ => Err(ProtocolError::SerializationError( + SerializationError::InvalidData, + )), + } + } +} + +impl CanonicalSerializeBE for RLNWitnessInputSingle { + type Error = ProtocolError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + self.identity_secret.serialize(&mut writer)?; + self.user_message_limit.serialize(&mut writer)?; + self.message_id.serialize(&mut writer)?; + self.path_elements.serialize(&mut writer)?; + self.identity_path_index.serialize(&mut writer)?; + self.x.serialize(&mut writer)?; + self.external_nullifier.serialize(&mut writer)?; + Ok(()) + } + + fn serialized_size(&self) -> usize { + CanonicalSerializeBE::serialized_size(&self.identity_secret) + + CanonicalSerializeBE::serialized_size(&self.user_message_limit) + + CanonicalSerializeBE::serialized_size(&self.message_id) + + CanonicalSerializeBE::serialized_size(&self.path_elements) + + CanonicalSerializeBE::serialized_size(&self.identity_path_index) + + CanonicalSerializeBE::serialized_size(&self.x) + + CanonicalSerializeBE::serialized_size(&self.external_nullifier) + } +} + +impl CanonicalDeserializeBE for RLNWitnessInputSingle { + type Error = ProtocolError; + + fn deserialize(mut reader: R) -> Result { + let identity_secret = IdSecret::deserialize(&mut reader)?; + let user_message_limit = Fr::deserialize(&mut reader)?; + let message_id = Fr::deserialize(&mut reader)?; + let path_elements = Vec::::deserialize(&mut reader)?; + let identity_path_index = Vec::::deserialize(&mut reader)?; + let x = Fr::deserialize(&mut reader)?; + let external_nullifier = Fr::deserialize(&mut reader)?; + Ok(Self { + identity_secret, + user_message_limit, + message_id, + path_elements, + identity_path_index, + x, + external_nullifier, + }) + } +} + +impl CanonicalSerializeBE for RLNWitnessInputMulti { + type Error = ProtocolError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + self.identity_secret.serialize(&mut writer)?; + self.user_message_limit.serialize(&mut writer)?; + self.path_elements.serialize(&mut writer)?; + self.identity_path_index.serialize(&mut writer)?; + self.x.serialize(&mut writer)?; + self.external_nullifier.serialize(&mut writer)?; + self.message_ids.serialize(&mut writer)?; + self.selector_used.serialize(&mut writer)?; + Ok(()) + } + + fn serialized_size(&self) -> usize { + CanonicalSerializeBE::serialized_size(&self.identity_secret) + + CanonicalSerializeBE::serialized_size(&self.user_message_limit) + + CanonicalSerializeBE::serialized_size(&self.path_elements) + + CanonicalSerializeBE::serialized_size(&self.identity_path_index) + + CanonicalSerializeBE::serialized_size(&self.x) + + CanonicalSerializeBE::serialized_size(&self.external_nullifier) + + CanonicalSerializeBE::serialized_size(&self.message_ids) + + CanonicalSerializeBE::serialized_size(&self.selector_used) + } +} + +impl CanonicalDeserializeBE for RLNWitnessInputMulti { + type Error = ProtocolError; + + fn deserialize(mut reader: R) -> Result { + let identity_secret = IdSecret::deserialize(&mut reader)?; + let user_message_limit = Fr::deserialize(&mut reader)?; + let path_elements = Vec::::deserialize(&mut reader)?; + let identity_path_index = Vec::::deserialize(&mut reader)?; + let x = Fr::deserialize(&mut reader)?; + let external_nullifier = Fr::deserialize(&mut reader)?; + let message_ids = Vec::::deserialize(&mut reader)?; + let selector_used = Vec::::deserialize(&mut reader)?; + Ok(Self { + identity_secret, + user_message_limit, + path_elements, + identity_path_index, + x, + external_nullifier, + message_ids, + selector_used, + }) + } +} + +impl CanonicalSerializeBE for RLNPartialWitnessInputV3 { + type Error = ProtocolError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + self.identity_secret.serialize(&mut writer)?; + self.user_message_limit.serialize(&mut writer)?; + self.path_elements.serialize(&mut writer)?; + self.identity_path_index.serialize(&mut writer)?; + Ok(()) + } + + fn serialized_size(&self) -> usize { + CanonicalSerializeBE::serialized_size(&self.identity_secret) + + CanonicalSerializeBE::serialized_size(&self.user_message_limit) + + CanonicalSerializeBE::serialized_size(&self.path_elements) + + CanonicalSerializeBE::serialized_size(&self.identity_path_index) + } +} + +impl CanonicalDeserializeBE for RLNPartialWitnessInputV3 { + type Error = ProtocolError; + + fn deserialize(mut reader: R) -> Result { + let identity_secret = IdSecret::deserialize(&mut reader)?; + let user_message_limit = Fr::deserialize(&mut reader)?; + let path_elements = Vec::::deserialize(&mut reader)?; + let identity_path_index = Vec::::deserialize(&mut reader)?; + Ok(Self { + identity_secret, + user_message_limit, + path_elements, + identity_path_index, + }) + } +} + +impl Valid for RLNProofValuesV3 { + fn check(&self) -> Result<(), SerializationError> { + match self { + RLNProofValuesV3::Single(inner) => inner.check(), + RLNProofValuesV3::Multi(inner) => inner.check(), + } + } +} + +impl CanonicalSerialize for RLNProofValuesV3 { + fn serialize_with_mode( + &self, + mut writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + match self { + RLNProofValuesV3::Single(inner) => { + ENUM_TAG_SINGLE.serialize_with_mode(&mut writer, compress)?; + inner.serialize_with_mode(&mut writer, compress) + } + RLNProofValuesV3::Multi(inner) => { + ENUM_TAG_MULTI.serialize_with_mode(&mut writer, compress)?; + inner.serialize_with_mode(&mut writer, compress) + } + } + } + + fn serialized_size(&self, compress: Compress) -> usize { + ENUM_TAG_SIZE + + match self { + RLNProofValuesV3::Single(inner) => { + CanonicalSerialize::serialized_size(inner, compress) + } + RLNProofValuesV3::Multi(inner) => { + CanonicalSerialize::serialized_size(inner, compress) + } + } + } +} + +impl CanonicalDeserialize for RLNProofValuesV3 { + fn deserialize_with_mode( + mut reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + let tag = u8::deserialize_with_mode(&mut reader, compress, validate)?; + match tag { + ENUM_TAG_SINGLE => Ok(RLNProofValuesV3::Single( + RLNProofValuesSingle::deserialize_with_mode(reader, compress, validate)?, + )), + ENUM_TAG_MULTI => Ok(RLNProofValuesV3::Multi( + RLNProofValuesMulti::deserialize_with_mode(reader, compress, validate)?, + )), + _ => Err(SerializationError::InvalidData), + } + } +} + +impl CanonicalSerializeBE for RLNProofValuesV3 { + type Error = ProtocolError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + match self { + RLNProofValuesV3::Single(inner) => { + writer.write_all(&[ENUM_TAG_SINGLE])?; + inner.serialize(&mut writer) + } + RLNProofValuesV3::Multi(inner) => { + writer.write_all(&[ENUM_TAG_MULTI])?; + inner.serialize(&mut writer) + } + } + } + + fn serialized_size(&self) -> usize { + ENUM_TAG_SIZE + + match self { + RLNProofValuesV3::Single(inner) => CanonicalSerializeBE::serialized_size(inner), + RLNProofValuesV3::Multi(inner) => CanonicalSerializeBE::serialized_size(inner), + } + } +} + +impl CanonicalDeserializeBE for RLNProofValuesV3 { + type Error = ProtocolError; + + fn deserialize(mut reader: R) -> Result { + let mut tag = [0u8; ENUM_TAG_SIZE]; + reader.read_exact(&mut tag)?; + match tag[0] { + ENUM_TAG_SINGLE => Ok(RLNProofValuesV3::Single(RLNProofValuesSingle::deserialize( + reader, + )?)), + ENUM_TAG_MULTI => Ok(RLNProofValuesV3::Multi(RLNProofValuesMulti::deserialize( + reader, + )?)), + _ => Err(ProtocolError::SerializationError( + SerializationError::InvalidData, + )), + } + } +} + +impl CanonicalSerializeBE for RLNProofValuesSingle { + type Error = ProtocolError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + self.y.serialize(&mut writer)?; + self.root.serialize(&mut writer)?; + self.nullifier.serialize(&mut writer)?; + self.x.serialize(&mut writer)?; + self.external_nullifier.serialize(&mut writer)?; + Ok(()) + } + + fn serialized_size(&self) -> usize { + CanonicalSerializeBE::serialized_size(&self.y) + + CanonicalSerializeBE::serialized_size(&self.root) + + CanonicalSerializeBE::serialized_size(&self.nullifier) + + CanonicalSerializeBE::serialized_size(&self.x) + + CanonicalSerializeBE::serialized_size(&self.external_nullifier) + } +} + +impl CanonicalDeserializeBE for RLNProofValuesSingle { + type Error = ProtocolError; + + fn deserialize(mut reader: R) -> Result { + let y = Fr::deserialize(&mut reader)?; + let root = Fr::deserialize(&mut reader)?; + let nullifier = Fr::deserialize(&mut reader)?; + let x = Fr::deserialize(&mut reader)?; + let external_nullifier = Fr::deserialize(&mut reader)?; + Ok(Self { + y, + root, + nullifier, + x, + external_nullifier, + }) + } +} + +impl CanonicalSerializeBE for RLNProofValuesMulti { + type Error = ProtocolError; + + fn serialize(&self, mut writer: W) -> Result<(), Self::Error> { + self.ys.serialize(&mut writer)?; + self.root.serialize(&mut writer)?; + self.nullifiers.serialize(&mut writer)?; + self.x.serialize(&mut writer)?; + self.external_nullifier.serialize(&mut writer)?; + self.selector_used.serialize(&mut writer)?; + Ok(()) + } + + fn serialized_size(&self) -> usize { + CanonicalSerializeBE::serialized_size(&self.ys) + + CanonicalSerializeBE::serialized_size(&self.root) + + CanonicalSerializeBE::serialized_size(&self.nullifiers) + + CanonicalSerializeBE::serialized_size(&self.x) + + CanonicalSerializeBE::serialized_size(&self.external_nullifier) + + CanonicalSerializeBE::serialized_size(&self.selector_used) + } +} + +impl CanonicalDeserializeBE for RLNProofValuesMulti { + type Error = ProtocolError; + + fn deserialize(mut reader: R) -> Result { + let ys = Vec::::deserialize(&mut reader)?; + let root = Fr::deserialize(&mut reader)?; + let nullifiers = Vec::::deserialize(&mut reader)?; + let x = Fr::deserialize(&mut reader)?; + let external_nullifier = Fr::deserialize(&mut reader)?; + let selector_used = Vec::::deserialize(&mut reader)?; + Ok(Self { + ys, + root, + nullifiers, + x, + external_nullifier, + selector_used, + }) + } +} diff --git a/rln/src/protocol/slashing.rs b/rln/src/protocol/slashing.rs index f120edec..c3231c0d 100644 --- a/rln/src/protocol/slashing.rs +++ b/rln/src/protocol/slashing.rs @@ -1,8 +1,6 @@ use ark_ff::AdditiveGroup; -#[cfg(feature = "multi-message-id")] -use super::proof::RLNOutputs; -use super::proof::RLNProofValues; +use super::proof::{RLNOutputs, RLNProofValues}; use crate::{circuit::Fr, error::RecoverSecretError, utils::IdSecret}; /// Computes identity secret from two (x, y) shares. @@ -40,7 +38,7 @@ pub fn compute_id_secret( /// Recovers identity secret from two [`RLNProofValues`] with the same external nullifier. /// /// This is a convenience API that accepts two proof values of the **same** variant. -/// For cross-feature slashing (e.g. one share from `SingleV1` and another from `MultiV1`), +/// For cross-mode slashing (e.g. one share from `SingleV1` and another from `MultiV1`), /// extract the `(x, y)` pair and nullifier from each proof value and call [`compute_id_secret`] directly. pub fn recover_id_secret( rln_proof_values_1: &RLNProofValues, @@ -57,26 +55,24 @@ pub fn recover_id_secret( )); } - match (rln_proof_values_1, rln_proof_values_2) { - #[cfg(not(feature = "multi-message-id"))] - (pv1, pv2) => { - let share1 = (*pv1.x(), *pv1.y()); - let share2 = (*pv2.x(), *pv2.y()); + match (&rln_proof_values_1.outputs, &rln_proof_values_2.outputs) { + (RLNOutputs::SingleV1 { y: y1, .. }, RLNOutputs::SingleV1 { y: y2, .. }) => { + let share1 = (*rln_proof_values_1.x(), *y1); + let share2 = (*rln_proof_values_2.x(), *y2); compute_id_secret(share1, share2) } - #[cfg(feature = "multi-message-id")] - (pv1, pv2) => { - let RLNOutputs::MultiV1 { + ( + RLNOutputs::MultiV1 { ys: ys1, nullifiers: nullifiers1, selector_used: selector_used1, - } = &pv1.outputs; - let RLNOutputs::MultiV1 { + }, + RLNOutputs::MultiV1 { ys: ys2, nullifiers: nullifiers2, selector_used: selector_used2, - } = &pv2.outputs; - + }, + ) => { for (i, (nullifier_i, &used_i)) in nullifiers1.iter().zip(selector_used1.iter()).enumerate() { @@ -90,13 +86,15 @@ pub fn recover_id_secret( continue; } if nullifier_i == nullifier_j { - let share1 = (*pv1.x(), ys1[i]); - let share2 = (*pv2.x(), ys2[j]); + let share1 = (*rln_proof_values_1.x(), ys1[i]); + let share2 = (*rln_proof_values_2.x(), ys2[j]); return compute_id_secret(share1, share2); } } } Err(RecoverSecretError::NoMatchingNullifier) } + // Cross-mode slashing: extract (x, y) pairs and call compute_id_secret directly. + _ => Err(RecoverSecretError::NoMatchingNullifier), } } diff --git a/rln/src/protocol/version.rs b/rln/src/protocol/version.rs deleted file mode 100644 index b4c4083e..00000000 --- a/rln/src/protocol/version.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::error::ProtocolError; - -/// Size in bytes of the version prefix. -pub const VERSION_BYTE_SIZE: usize = 1; - -/// Wire-format version tag for serialized RLN structures. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum SerializationVersion { - /// Original single message-id format (RLN v2). - /// - /// RLNWitnessInput: - /// `[ 0x00 | identity_secret<32> | user_message_limit<32> | message_id<32> | path_elements | identity_path_index<32> | x<32> | external_nullifier<32> ]` - /// - /// RLNPartialWitnessInput: - /// `[ 0x00 | identity_secret<32> | user_message_limit<32> | path_elements | identity_path_index ]` - /// - /// RLNProofValues: - /// `[ 0x00 | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]` - /// - /// RLNProof: - /// `[ 0x00 | proof<128> | RLNProofValues(0x00) ]` - /// - /// PartialProof: - /// `[ 0x00 | partial_proof ]` - /// - /// Encoding conventions: - /// - `<32>` = canonical 32-byte encoding of [`ark_bn254::Fr`](https://github.com/arkworks-rs/algebra/blob/7ad88c46e859a94ab8e0b19fd8a217c3dc472f1c/curves/bn254/src/fields/fr.rs#L9). - /// - `` = length-prefixed list of `Fr`, except `identity_path_index`, which is a length-prefixed `Vec`. - /// - `proof<128>` is an - /// [`ark_groth16::Proof`](https://github.com/arkworks-rs/groth16/blob/9ba21ceab723d6b515a813e17846a0c0ec830c0d/src/data_structures.rs#L9) - /// instantiated over - /// [`ark_bn254::Bn254`](https://github.com/arkworks-rs/algebra/blob/7ad88c46e859a94ab8e0b19fd8a217c3dc472f1c/curves/bn254/src/curves/mod.rs#L44), - /// serialized into a fixed 128-byte canonical form in little-endian format (arkworks behavior). - /// - `partial_proof` is a variable-length canonical form in little-endian format (arkworks behavior). - /// - /// Spec: - #[cfg(not(feature = "multi-message-id"))] - SingleV1 = 0x00, - - /// Multi message-id format (RLN v2 extension). - /// - /// RLNWitnessInput: - /// `[ 0x01 | identity_secret<32> | user_message_limit<32> | path_elements | identity_path_index<32> | x<32> | external_nullifier<32> | message_ids | selector_used ]` - /// - /// RLNPartialWitnessInput: - /// `[ 0x01 | identity_secret<32> | user_message_limit<32> | path_elements | identity_path_index ]` - /// - /// RLNProofValues: - /// `[ 0x01 | root<32> | external_nullifier<32> | x<32> | ys | nullifiers | selector_used ]` - /// - /// RLNProof: - /// `[ 0x01 | proof<128> | RLNProofValues(0x01) ]` - /// - /// PartialProof: - /// `[ 0x01 | partial_proof ]` - /// - /// Encoding conventions: - /// - `<32>` = canonical 32-byte encoding of [`ark_bn254::Fr`](https://github.com/arkworks-rs/algebra/blob/7ad88c46e859a94ab8e0b19fd8a217c3dc472f1c/curves/bn254/src/fields/fr.rs#L9). - /// - `` = length-prefixed list of `Fr`, except `identity_path_index`, which is a length-prefixed `Vec`. - /// - `proof<128>` is an - /// [`ark_groth16::Proof`](https://github.com/arkworks-rs/groth16/blob/9ba21ceab723d6b515a813e17846a0c0ec830c0d/src/data_structures.rs#L9) - /// instantiated over - /// [`ark_bn254::Bn254`](https://github.com/arkworks-rs/algebra/blob/7ad88c46e859a94ab8e0b19fd8a217c3dc472f1c/curves/bn254/src/curves/mod.rs#L44), - /// serialized into a fixed 128-byte canonical form in little-endian format (arkworks behavior). - /// - `partial_proof` is a variable-length canonical form in little-endian format (arkworks behavior). - /// - /// Spec: - #[cfg(feature = "multi-message-id")] - MultiV1 = 0x01, -} - -impl From for u8 { - #[inline] - fn from(v: SerializationVersion) -> u8 { - v as u8 - } -} - -impl TryFrom for SerializationVersion { - type Error = ProtocolError; - - fn try_from(byte: u8) -> Result { - match byte { - #[cfg(not(feature = "multi-message-id"))] - 0x00 => Ok(Self::SingleV1), - #[cfg(not(feature = "multi-message-id"))] - 0x01 => Err(ProtocolError::IncompatibleSerializationVersion( - byte, - "multi-message-id", - )), - #[cfg(feature = "multi-message-id")] - 0x01 => Ok(Self::MultiV1), - #[cfg(feature = "multi-message-id")] - 0x00 => Err(ProtocolError::IncompatibleSerializationVersion( - byte, - "not(multi-message-id)", - )), - other => Err(ProtocolError::UnknownSerializationVersion(other)), - } - } -} diff --git a/rln/src/protocol/witness.rs b/rln/src/protocol/witness.rs index 95dd22f8..d30e57e3 100644 --- a/rln/src/protocol/witness.rs +++ b/rln/src/protocol/witness.rs @@ -1,37 +1,34 @@ -#[cfg(feature = "multi-message-id")] use std::collections::HashSet; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use num_bigint::BigInt; use zeroize::Zeroize; use super::{ + mode::{MessageMode, VERSION_BYTE_SIZE}, proof::RLNProofValues, - version::{SerializationVersion, VERSION_BYTE_SIZE}, + FR_BYTE_SIZE, VEC_LEN_BYTE_SIZE, }; #[cfg(not(target_arch = "wasm32"))] use crate::utils::FrOrSecret; -#[cfg(feature = "multi-message-id")] -use crate::utils::{ - bytes_be_to_vec_bool, bytes_le_to_vec_bool, vec_bool_to_bytes_be, vec_bool_to_bytes_le, -}; use crate::{ circuit::Fr, error::ProtocolError, hashers::poseidon_hash, utils::{ - bytes_be_to_fr, bytes_be_to_vec_fr, bytes_be_to_vec_u8, bytes_le_to_fr, bytes_le_to_vec_fr, - bytes_le_to_vec_u8, fr_to_bytes_be, fr_to_bytes_le, to_bigint, vec_fr_to_bytes_be, - vec_fr_to_bytes_le, vec_u8_to_bytes_be, vec_u8_to_bytes_le, IdSecret, FR_BYTE_SIZE, - VEC_LEN_BYTE_SIZE, + bytes_be_to_fr, bytes_be_to_vec_bool, bytes_be_to_vec_fr, bytes_be_to_vec_u8, + bytes_le_to_fr, bytes_le_to_vec_bool, bytes_le_to_vec_fr, bytes_le_to_vec_u8, + fr_to_bytes_be, fr_to_bytes_le, to_bigint, vec_bool_to_bytes_be, vec_bool_to_bytes_le, + vec_fr_to_bytes_be, vec_fr_to_bytes_le, vec_u8_to_bytes_be, vec_u8_to_bytes_le, IdSecret, }, }; /// Variant-specific message inputs for RLN witness. #[derive(Debug, PartialEq, Clone)] pub(crate) enum RLNMessageInputs { - #[cfg(not(feature = "multi-message-id"))] - SingleV1 { message_id: Fr }, - #[cfg(feature = "multi-message-id")] + SingleV1 { + message_id: Fr, + }, MultiV1 { message_ids: Vec, selector_used: Vec, @@ -43,7 +40,7 @@ pub(crate) enum RLNMessageInputs { /// Contains the identity credentials, merkle proof, rate-limiting parameters, /// and signal binding data required to generate a Groth16 proof for the RLN protocol. /// -/// The serialization format for this type is defined in the `protocol::version` module. +/// The serialization format for this type is defined in the `protocol::mode` module. #[derive(Debug, PartialEq, Clone)] pub struct RLNWitnessInput { identity_secret: IdSecret, @@ -60,7 +57,7 @@ pub struct RLNWitnessInput { /// Contains the non-changing inputs used to precompute a partial proof /// before the signal, external nullifier, and message ID are known. /// -/// The serialization format for this type is defined in the `protocol::version` module. +/// The serialization format for this type is defined in the `protocol::mode` module. #[derive(Debug, PartialEq, Clone)] pub struct RLNPartialWitnessInput { identity_secret: IdSecret, @@ -70,25 +67,19 @@ pub struct RLNPartialWitnessInput { } impl RLNWitnessInput { - /// Creates a new RLNWitnessInput instance. - #[allow(clippy::too_many_arguments)] - pub fn new( + /// Creates a new single message-id witness. + pub fn new_single( identity_secret: IdSecret, user_message_limit: Fr, - #[cfg(not(feature = "multi-message-id"))] message_id: Fr, - #[cfg(feature = "multi-message-id")] message_ids: Vec, + message_id: Fr, path_elements: Vec, identity_path_index: Vec, x: Fr, external_nullifier: Fr, - #[cfg(feature = "multi-message-id")] selector_used: Vec, ) -> Result { - // User message limit check if user_message_limit == Fr::from(0) { return Err(ProtocolError::ZeroUserMessageLimit); } - - // Merkle proof length check let path_elements_len = path_elements.len(); let identity_path_index_len = identity_path_index.len(); if path_elements_len != identity_path_index_len { @@ -97,62 +88,76 @@ impl RLNWitnessInput { identity_path_index_len, )); } - - #[cfg(not(feature = "multi-message-id"))] if message_id >= user_message_limit { return Err(ProtocolError::InvalidMessageId( message_id, user_message_limit, )); } + Ok(Self { + identity_secret, + user_message_limit, + path_elements, + identity_path_index, + x, + external_nullifier, + message_inputs: RLNMessageInputs::SingleV1 { message_id }, + }) + } - #[cfg(feature = "multi-message-id")] + /// Creates a new multi message-id witness. + #[allow(clippy::too_many_arguments)] + pub fn new_multi( + identity_secret: IdSecret, + user_message_limit: Fr, + message_ids: Vec, + path_elements: Vec, + identity_path_index: Vec, + x: Fr, + external_nullifier: Fr, + selector_used: Vec, + ) -> Result { + if user_message_limit == Fr::from(0) { + return Err(ProtocolError::ZeroUserMessageLimit); + } + let path_elements_len = path_elements.len(); + let identity_path_index_len = identity_path_index.len(); + if path_elements_len != identity_path_index_len { + return Err(ProtocolError::InvalidMerkleProofLength( + path_elements_len, + identity_path_index_len, + )); + } + if message_ids.is_empty() { + return Err(ProtocolError::EmptyMessageIds); + } + if selector_used.len() != message_ids.len() { + return Err(ProtocolError::FieldLengthMismatch( + "message_ids", + message_ids.len(), + "selector_used", + selector_used.len(), + )); + } + if !selector_used.iter().any(|&s| s) { + return Err(ProtocolError::NoActiveSelectorUsed); + } { - // Message IDs must be non-empty - if message_ids.is_empty() { - return Err(ProtocolError::EmptyMessageIds); - } - // Selector length must match message IDs - if selector_used.len() != message_ids.len() { - return Err(ProtocolError::FieldLengthMismatch( - "message_ids", - message_ids.len(), - "selector_used", - selector_used.len(), - )); - } - // At least one selector must be active - if !selector_used.iter().any(|&s| s) { - return Err(ProtocolError::NoActiveSelectorUsed); - } - // Active message IDs must be unique - { - let mut seen = HashSet::with_capacity(message_ids.len()); - for (id, &used) in message_ids.iter().zip(&selector_used) { - if used && !seen.insert(*id) { - return Err(ProtocolError::DuplicateMessageIds); - } + let mut seen = HashSet::with_capacity(message_ids.len()); + for (id, &used) in message_ids.iter().zip(&selector_used) { + if used && !seen.insert(*id) { + return Err(ProtocolError::DuplicateMessageIds); } } - // Active message IDs must be within range - for (message_id, used) in message_ids.iter().zip(&selector_used) { - if *used && *message_id >= user_message_limit { - return Err(ProtocolError::InvalidMessageId( - *message_id, - user_message_limit, - )); - } + } + for (message_id, used) in message_ids.iter().zip(&selector_used) { + if *used && *message_id >= user_message_limit { + return Err(ProtocolError::InvalidMessageId( + *message_id, + user_message_limit, + )); } } - - #[cfg(not(feature = "multi-message-id"))] - let message_inputs = RLNMessageInputs::SingleV1 { message_id }; - #[cfg(feature = "multi-message-id")] - let message_inputs = RLNMessageInputs::MultiV1 { - message_ids, - selector_used, - }; - Ok(Self { identity_secret, user_message_limit, @@ -160,17 +165,18 @@ impl RLNWitnessInput { identity_path_index, x, external_nullifier, - message_inputs, + message_inputs: RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + }, }) } /// Returns the version byte corresponding to the witness variant. pub fn version_byte(&self) -> u8 { match &self.message_inputs { - #[cfg(not(feature = "multi-message-id"))] - RLNMessageInputs::SingleV1 { .. } => SerializationVersion::SingleV1.into(), - #[cfg(feature = "multi-message-id")] - RLNMessageInputs::MultiV1 { .. } => SerializationVersion::MultiV1.into(), + RLNMessageInputs::SingleV1 { .. } => MessageMode::SingleV1.version_byte(), + RLNMessageInputs::MultiV1 { .. } => MessageMode::MultiV1 { max_out: 0 }.version_byte(), } } @@ -184,18 +190,24 @@ impl RLNWitnessInput { &self.user_message_limit } - /// Returns the message ID. - #[cfg(not(feature = "multi-message-id"))] + /// Returns the message ID (only valid for SingleV1 witnesses). pub fn message_id(&self) -> &Fr { - let RLNMessageInputs::SingleV1 { message_id } = &self.message_inputs; - message_id + match &self.message_inputs { + RLNMessageInputs::SingleV1 { message_id } => message_id, + RLNMessageInputs::MultiV1 { .. } => { + panic!("message_id() is not available for MultiV1 witness; use message_ids()") + } + } } - /// Returns the multi message IDs. - #[cfg(feature = "multi-message-id")] + /// Returns the multi message IDs (only valid for MultiV1 witnesses). pub fn message_ids(&self) -> &[Fr] { - let RLNMessageInputs::MultiV1 { message_ids, .. } = &self.message_inputs; - message_ids + match &self.message_inputs { + RLNMessageInputs::MultiV1 { message_ids, .. } => message_ids, + RLNMessageInputs::SingleV1 { .. } => { + panic!("message_ids() is not available for SingleV1 witness; use message_id()") + } + } } /// Returns the Merkle path elements. @@ -218,11 +230,14 @@ impl RLNWitnessInput { &self.external_nullifier } - /// Returns the selector flags. - #[cfg(feature = "multi-message-id")] + /// Returns the selector flags (only valid for MultiV1 witnesses). pub fn selector_used(&self) -> &[bool] { - let RLNMessageInputs::MultiV1 { selector_used, .. } = &self.message_inputs; - selector_used + match &self.message_inputs { + RLNMessageInputs::MultiV1 { selector_used, .. } => selector_used, + RLNMessageInputs::SingleV1 { .. } => { + panic!("selector_used() is not available for SingleV1 witness") + } + } } } @@ -279,14 +294,7 @@ impl RLNPartialWitnessInput { /// Returns the version byte for this partial witness's serialization format. pub fn version_byte(&self) -> u8 { - #[cfg(not(feature = "multi-message-id"))] - { - SerializationVersion::SingleV1.into() - } - #[cfg(feature = "multi-message-id")] - { - SerializationVersion::MultiV1.into() - } + MessageMode::SingleV1.version_byte() } } @@ -301,159 +309,153 @@ impl From<&RLNWitnessInput> for RLNPartialWitnessInput { } } +/// Converts the witness to JSON with BigInt string representation for the witness calculator. +pub fn rln_witness_to_bigint_json( + witness: &RLNWitnessInput, +) -> Result { + let path_elements_str: Vec = witness + .path_elements + .iter() + .map(|v| to_bigint(v).to_str_radix(10)) + .collect(); + let identity_path_index_str: Vec = witness + .identity_path_index + .iter() + .map(|v| BigInt::from(*v).to_str_radix(10)) + .collect(); + + match &witness.message_inputs { + RLNMessageInputs::SingleV1 { message_id } => Ok(serde_json::json!({ + "identitySecret": to_bigint(&witness.identity_secret).to_str_radix(10), + "userMessageLimit": to_bigint(&witness.user_message_limit).to_str_radix(10), + "messageId": to_bigint(message_id).to_str_radix(10), + "pathElements": path_elements_str, + "identityPathIndex": identity_path_index_str, + "x": to_bigint(&witness.x).to_str_radix(10), + "externalNullifier": to_bigint(&witness.external_nullifier).to_str_radix(10), + })), + RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + } => { + let message_ids_str: Vec = message_ids + .iter() + .map(|id| to_bigint(id).to_str_radix(10)) + .collect(); + let selector_used_str: Vec = selector_used + .iter() + .map(|&v| BigInt::from(v).to_str_radix(10)) + .collect(); + + Ok(serde_json::json!({ + "identitySecret": to_bigint(&witness.identity_secret).to_str_radix(10), + "userMessageLimit": to_bigint(&witness.user_message_limit).to_str_radix(10), + "messageId": message_ids_str, + "selectorUsed": selector_used_str, + "pathElements": path_elements_str, + "identityPathIndex": identity_path_index_str, + "x": to_bigint(&witness.x).to_str_radix(10), + "externalNullifier": to_bigint(&witness.external_nullifier).to_str_radix(10), + })) + } + } +} + /// Serializes an RLN witness to little-endian bytes. pub fn rln_witness_to_bytes_le(witness: &RLNWitnessInput) -> Result, ProtocolError> { - #[cfg(not(feature = "multi-message-id"))] - let RLNMessageInputs::SingleV1 { message_id } = &witness.message_inputs; - #[cfg(feature = "multi-message-id")] - let RLNMessageInputs::MultiV1 { - message_ids, - selector_used, - } = &witness.message_inputs; - - // Calculate capacity for Vec: - // - VERSION_BYTE_SIZE byte for version tag - // - 2 common field elements: identity_secret, user_message_limit - // - variable size of path_elements, identity_path_index - #[cfg(not(feature = "multi-message-id"))] - // - 3 field elements: message_id, x, external_nullifier - // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index) - let capacity = VERSION_BYTE_SIZE - + FR_BYTE_SIZE * (5 + witness.path_elements.len()) - + witness.identity_path_index.len() - + VEC_LEN_BYTE_SIZE * 2; - #[cfg(feature = "multi-message-id")] - // - 2 field elements: x, external_nullifier - // - variable size of message_ids, selector_used - // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index, message_ids, selector_used) - let capacity = VERSION_BYTE_SIZE - + FR_BYTE_SIZE * (4 + witness.path_elements.len() + message_ids.len()) - + witness.identity_path_index.len() - + selector_used.len() - + VEC_LEN_BYTE_SIZE * 4; + let capacity = match &witness.message_inputs { + RLNMessageInputs::SingleV1 { .. } => { + VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (5 + witness.path_elements.len()) + + witness.identity_path_index.len() + + VEC_LEN_BYTE_SIZE * 2 + } + RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + } => { + VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (4 + witness.path_elements.len() + message_ids.len()) + + witness.identity_path_index.len() + + selector_used.len() + + VEC_LEN_BYTE_SIZE * 4 + } + }; let mut bytes = Vec::with_capacity(capacity); bytes.push(witness.version_byte()); bytes.extend_from_slice(&witness.identity_secret.to_bytes_le()); bytes.extend_from_slice(&fr_to_bytes_le(&witness.user_message_limit)); - #[cfg(not(feature = "multi-message-id"))] - { - bytes.extend_from_slice(&fr_to_bytes_le(message_id)); - bytes.extend_from_slice(&vec_fr_to_bytes_le(&witness.path_elements)); - bytes.extend_from_slice(&vec_u8_to_bytes_le(&witness.identity_path_index)); - bytes.extend_from_slice(&fr_to_bytes_le(&witness.x)); - bytes.extend_from_slice(&fr_to_bytes_le(&witness.external_nullifier)); - } - #[cfg(feature = "multi-message-id")] - { - bytes.extend_from_slice(&vec_fr_to_bytes_le(&witness.path_elements)); - bytes.extend_from_slice(&vec_u8_to_bytes_le(&witness.identity_path_index)); - bytes.extend_from_slice(&fr_to_bytes_le(&witness.x)); - bytes.extend_from_slice(&fr_to_bytes_le(&witness.external_nullifier)); - bytes.extend_from_slice(&vec_fr_to_bytes_le(message_ids)); - bytes.extend_from_slice(&vec_bool_to_bytes_le(selector_used)); + + match &witness.message_inputs { + RLNMessageInputs::SingleV1 { message_id } => { + bytes.extend_from_slice(&fr_to_bytes_le(message_id)); + bytes.extend_from_slice(&vec_fr_to_bytes_le(&witness.path_elements)); + bytes.extend_from_slice(&vec_u8_to_bytes_le(&witness.identity_path_index)); + bytes.extend_from_slice(&fr_to_bytes_le(&witness.x)); + bytes.extend_from_slice(&fr_to_bytes_le(&witness.external_nullifier)); + } + RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + } => { + bytes.extend_from_slice(&vec_fr_to_bytes_le(&witness.path_elements)); + bytes.extend_from_slice(&vec_u8_to_bytes_le(&witness.identity_path_index)); + bytes.extend_from_slice(&fr_to_bytes_le(&witness.x)); + bytes.extend_from_slice(&fr_to_bytes_le(&witness.external_nullifier)); + bytes.extend_from_slice(&vec_fr_to_bytes_le(message_ids)); + bytes.extend_from_slice(&vec_bool_to_bytes_le(selector_used)); + } } Ok(bytes) } /// Serializes an RLN witness to big-endian bytes. pub fn rln_witness_to_bytes_be(witness: &RLNWitnessInput) -> Result, ProtocolError> { - #[cfg(not(feature = "multi-message-id"))] - let RLNMessageInputs::SingleV1 { message_id } = &witness.message_inputs; - #[cfg(feature = "multi-message-id")] - let RLNMessageInputs::MultiV1 { - message_ids, - selector_used, - } = &witness.message_inputs; - - // Calculate capacity for Vec: - // - VERSION_BYTE_SIZE byte for version tag - // - 2 common field elements: identity_secret, user_message_limit - // - variable size of path_elements, identity_path_index - #[cfg(not(feature = "multi-message-id"))] - // - 3 field elements: message_id, x, external_nullifier - // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index) - let capacity = VERSION_BYTE_SIZE - + FR_BYTE_SIZE * (5 + witness.path_elements.len()) - + witness.identity_path_index.len() - + VEC_LEN_BYTE_SIZE * 2; - #[cfg(feature = "multi-message-id")] - // - 2 field elements: x, external_nullifier - // - variable size of message_ids, selector_used - // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index, message_ids, selector_used) - let capacity = VERSION_BYTE_SIZE - + FR_BYTE_SIZE * (4 + witness.path_elements.len() + message_ids.len()) - + witness.identity_path_index.len() - + selector_used.len() - + VEC_LEN_BYTE_SIZE * 4; - - let mut bytes = Vec::with_capacity(capacity); - bytes.push(witness.version_byte()); - bytes.extend_from_slice(&witness.identity_secret.to_bytes_be()); - bytes.extend_from_slice(&fr_to_bytes_be(&witness.user_message_limit)); - #[cfg(not(feature = "multi-message-id"))] - { - bytes.extend_from_slice(&fr_to_bytes_be(message_id)); - bytes.extend_from_slice(&vec_fr_to_bytes_be(&witness.path_elements)); - bytes.extend_from_slice(&vec_u8_to_bytes_be(&witness.identity_path_index)); - bytes.extend_from_slice(&fr_to_bytes_be(&witness.x)); - bytes.extend_from_slice(&fr_to_bytes_be(&witness.external_nullifier)); - } - #[cfg(feature = "multi-message-id")] - { - bytes.extend_from_slice(&vec_fr_to_bytes_be(&witness.path_elements)); - bytes.extend_from_slice(&vec_u8_to_bytes_be(&witness.identity_path_index)); - bytes.extend_from_slice(&fr_to_bytes_be(&witness.x)); - bytes.extend_from_slice(&fr_to_bytes_be(&witness.external_nullifier)); - bytes.extend_from_slice(&vec_fr_to_bytes_be(message_ids)); - bytes.extend_from_slice(&vec_bool_to_bytes_be(selector_used)); - } - Ok(bytes) -} - -/// Serializes an RLN partial witness to little-endian bytes. -pub fn rln_partial_witness_to_bytes_le( - witness: &RLNPartialWitnessInput, -) -> Result, ProtocolError> { - // Calculate capacity for Vec: - // - VERSION_BYTE_SIZE byte for version tag - // - 2 field elements: identity_secret, user_message_limit - // - variable size of path_elements, identity_path_index - // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index) - let capacity = VERSION_BYTE_SIZE - + FR_BYTE_SIZE * (2 + witness.path_elements.len()) - + witness.identity_path_index.len() - + VEC_LEN_BYTE_SIZE * 2; - let mut bytes = Vec::with_capacity(capacity); - bytes.push(witness.version_byte()); - bytes.extend_from_slice(&witness.identity_secret.to_bytes_le()); - bytes.extend_from_slice(&fr_to_bytes_le(&witness.user_message_limit)); - bytes.extend_from_slice(&vec_fr_to_bytes_le(&witness.path_elements)); - bytes.extend_from_slice(&vec_u8_to_bytes_le(&witness.identity_path_index)); - - Ok(bytes) -} + let capacity = match &witness.message_inputs { + RLNMessageInputs::SingleV1 { .. } => { + VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (5 + witness.path_elements.len()) + + witness.identity_path_index.len() + + VEC_LEN_BYTE_SIZE * 2 + } + RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + } => { + VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (4 + witness.path_elements.len() + message_ids.len()) + + witness.identity_path_index.len() + + selector_used.len() + + VEC_LEN_BYTE_SIZE * 4 + } + }; -/// Serializes an RLN partial witness to big-endian bytes. -pub fn rln_partial_witness_to_bytes_be( - witness: &RLNPartialWitnessInput, -) -> Result, ProtocolError> { - // Calculate capacity for Vec: - // - VERSION_BYTE_SIZE byte for version tag - // - 2 field elements: identity_secret, user_message_limit - // - variable size of path_elements, identity_path_index - // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index) - let capacity = VERSION_BYTE_SIZE - + FR_BYTE_SIZE * (2 + witness.path_elements.len()) - + witness.identity_path_index.len() - + VEC_LEN_BYTE_SIZE * 2; let mut bytes = Vec::with_capacity(capacity); bytes.push(witness.version_byte()); bytes.extend_from_slice(&witness.identity_secret.to_bytes_be()); bytes.extend_from_slice(&fr_to_bytes_be(&witness.user_message_limit)); - bytes.extend_from_slice(&vec_fr_to_bytes_be(&witness.path_elements)); - bytes.extend_from_slice(&vec_u8_to_bytes_be(&witness.identity_path_index)); + match &witness.message_inputs { + RLNMessageInputs::SingleV1 { message_id } => { + bytes.extend_from_slice(&fr_to_bytes_be(message_id)); + bytes.extend_from_slice(&vec_fr_to_bytes_be(&witness.path_elements)); + bytes.extend_from_slice(&vec_u8_to_bytes_be(&witness.identity_path_index)); + bytes.extend_from_slice(&fr_to_bytes_be(&witness.x)); + bytes.extend_from_slice(&fr_to_bytes_be(&witness.external_nullifier)); + } + RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + } => { + bytes.extend_from_slice(&vec_fr_to_bytes_be(&witness.path_elements)); + bytes.extend_from_slice(&vec_u8_to_bytes_be(&witness.identity_path_index)); + bytes.extend_from_slice(&fr_to_bytes_be(&witness.x)); + bytes.extend_from_slice(&fr_to_bytes_be(&witness.external_nullifier)); + bytes.extend_from_slice(&vec_fr_to_bytes_be(message_ids)); + bytes.extend_from_slice(&vec_bool_to_bytes_be(selector_used)); + } + } Ok(bytes) } @@ -464,8 +466,7 @@ pub fn bytes_le_to_rln_witness(bytes: &[u8]) -> Result<(RLNWitnessInput, usize), if bytes.is_empty() { return Err(ProtocolError::InvalidReadLen(1, 0)); } - - let _version = SerializationVersion::try_from(bytes[0])?; + let version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; let (identity_secret, el_size) = IdSecret::from_bytes_le(&bytes[read..])?; @@ -473,73 +474,70 @@ pub fn bytes_le_to_rln_witness(bytes: &[u8]) -> Result<(RLNWitnessInput, usize), let (user_message_limit, el_size) = bytes_le_to_fr(&bytes[read..])?; read += el_size; - #[cfg(not(feature = "multi-message-id"))] - { - let (message_id, el_size) = bytes_le_to_fr(&bytes[read..])?; - read += el_size; - let (path_elements, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; - read += el_size; - let (identity_path_index, el_size) = bytes_le_to_vec_u8(&bytes[read..])?; - read += el_size; - let (x, el_size) = bytes_le_to_fr(&bytes[read..])?; - read += el_size; - let (external_nullifier, el_size) = bytes_le_to_fr(&bytes[read..])?; - read += el_size; - - if read != bytes.len() { - return Err(ProtocolError::InvalidReadLen(read, bytes.len())); - } - let witness = RLNWitnessInput::new( - identity_secret, - user_message_limit, - message_id, - path_elements, - identity_path_index, - x, - external_nullifier, - )?; - Ok((witness, read)) - } - #[cfg(feature = "multi-message-id")] - { - let (path_elements, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; - read += el_size; - let (identity_path_index, el_size) = bytes_le_to_vec_u8(&bytes[read..])?; - read += el_size; - let (x, el_size) = bytes_le_to_fr(&bytes[read..])?; - read += el_size; - let (external_nullifier, el_size) = bytes_le_to_fr(&bytes[read..])?; - read += el_size; - let (message_ids, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; - read += el_size; - let (selector_used, el_size) = bytes_le_to_vec_bool(&bytes[read..])?; - read += el_size; - - if selector_used.len() != message_ids.len() { - return Err(ProtocolError::FieldLengthMismatch( - "message_ids", - message_ids.len(), - "selector_used", - selector_used.len(), - )); - } - if read != bytes.len() { - return Err(ProtocolError::InvalidReadLen(read, bytes.len())); - } - - Ok(( - RLNWitnessInput::new( + match version { + MessageMode::SingleV1 => { + let (message_id, el_size) = bytes_le_to_fr(&bytes[read..])?; + read += el_size; + let (path_elements, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; + read += el_size; + let (identity_path_index, el_size) = bytes_le_to_vec_u8(&bytes[read..])?; + read += el_size; + let (x, el_size) = bytes_le_to_fr(&bytes[read..])?; + read += el_size; + let (external_nullifier, el_size) = bytes_le_to_fr(&bytes[read..])?; + read += el_size; + if read != bytes.len() { + return Err(ProtocolError::InvalidReadLen(read, bytes.len())); + } + let witness = RLNWitnessInput::new_single( identity_secret, user_message_limit, - message_ids, + message_id, path_elements, identity_path_index, x, external_nullifier, - selector_used, - )?, - read, - )) + )?; + Ok((witness, read)) + } + MessageMode::MultiV1 { .. } => { + let (path_elements, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; + read += el_size; + let (identity_path_index, el_size) = bytes_le_to_vec_u8(&bytes[read..])?; + read += el_size; + let (x, el_size) = bytes_le_to_fr(&bytes[read..])?; + read += el_size; + let (external_nullifier, el_size) = bytes_le_to_fr(&bytes[read..])?; + read += el_size; + let (message_ids, el_size) = bytes_le_to_vec_fr(&bytes[read..])?; + read += el_size; + let (selector_used, el_size) = bytes_le_to_vec_bool(&bytes[read..])?; + read += el_size; + if selector_used.len() != message_ids.len() { + return Err(ProtocolError::FieldLengthMismatch( + "message_ids", + message_ids.len(), + "selector_used", + selector_used.len(), + )); + } + if read != bytes.len() { + return Err(ProtocolError::InvalidReadLen(read, bytes.len())); + } + Ok(( + RLNWitnessInput::new_multi( + identity_secret, + user_message_limit, + message_ids, + path_elements, + identity_path_index, + x, + external_nullifier, + selector_used, + )?, + read, + )) + } } } @@ -550,8 +548,7 @@ pub fn bytes_be_to_rln_witness(bytes: &[u8]) -> Result<(RLNWitnessInput, usize), if bytes.is_empty() { return Err(ProtocolError::InvalidReadLen(1, 0)); } - - let _version = SerializationVersion::try_from(bytes[0])?; + let version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; let (identity_secret, el_size) = IdSecret::from_bytes_be(&bytes[read..])?; @@ -559,76 +556,119 @@ pub fn bytes_be_to_rln_witness(bytes: &[u8]) -> Result<(RLNWitnessInput, usize), let (user_message_limit, el_size) = bytes_be_to_fr(&bytes[read..])?; read += el_size; - #[cfg(not(feature = "multi-message-id"))] - { - let (message_id, el_size) = bytes_be_to_fr(&bytes[read..])?; - read += el_size; - let (path_elements, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; - read += el_size; - let (identity_path_index, el_size) = bytes_be_to_vec_u8(&bytes[read..])?; - read += el_size; - let (x, el_size) = bytes_be_to_fr(&bytes[read..])?; - read += el_size; - let (external_nullifier, el_size) = bytes_be_to_fr(&bytes[read..])?; - read += el_size; - - if read != bytes.len() { - return Err(ProtocolError::InvalidReadLen(read, bytes.len())); - } - let witness = RLNWitnessInput::new( - identity_secret, - user_message_limit, - message_id, - path_elements, - identity_path_index, - x, - external_nullifier, - )?; - Ok((witness, read)) - } - #[cfg(feature = "multi-message-id")] - { - let (path_elements, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; - read += el_size; - let (identity_path_index, el_size) = bytes_be_to_vec_u8(&bytes[read..])?; - read += el_size; - let (x, el_size) = bytes_be_to_fr(&bytes[read..])?; - read += el_size; - let (external_nullifier, el_size) = bytes_be_to_fr(&bytes[read..])?; - read += el_size; - let (message_ids, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; - read += el_size; - let (selector_used, el_size) = bytes_be_to_vec_bool(&bytes[read..])?; - read += el_size; - - if selector_used.len() != message_ids.len() { - return Err(ProtocolError::FieldLengthMismatch( - "message_ids", - message_ids.len(), - "selector_used", - selector_used.len(), - )); - } - if read != bytes.len() { - return Err(ProtocolError::InvalidReadLen(read, bytes.len())); - } - - Ok(( - RLNWitnessInput::new( + match version { + MessageMode::SingleV1 => { + let (message_id, el_size) = bytes_be_to_fr(&bytes[read..])?; + read += el_size; + let (path_elements, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; + read += el_size; + let (identity_path_index, el_size) = bytes_be_to_vec_u8(&bytes[read..])?; + read += el_size; + let (x, el_size) = bytes_be_to_fr(&bytes[read..])?; + read += el_size; + let (external_nullifier, el_size) = bytes_be_to_fr(&bytes[read..])?; + read += el_size; + if read != bytes.len() { + return Err(ProtocolError::InvalidReadLen(read, bytes.len())); + } + let witness = RLNWitnessInput::new_single( identity_secret, user_message_limit, - message_ids, + message_id, path_elements, identity_path_index, x, external_nullifier, - selector_used, - )?, - read, - )) + )?; + Ok((witness, read)) + } + MessageMode::MultiV1 { .. } => { + let (path_elements, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; + read += el_size; + let (identity_path_index, el_size) = bytes_be_to_vec_u8(&bytes[read..])?; + read += el_size; + let (x, el_size) = bytes_be_to_fr(&bytes[read..])?; + read += el_size; + let (external_nullifier, el_size) = bytes_be_to_fr(&bytes[read..])?; + read += el_size; + let (message_ids, el_size) = bytes_be_to_vec_fr(&bytes[read..])?; + read += el_size; + let (selector_used, el_size) = bytes_be_to_vec_bool(&bytes[read..])?; + read += el_size; + if selector_used.len() != message_ids.len() { + return Err(ProtocolError::FieldLengthMismatch( + "message_ids", + message_ids.len(), + "selector_used", + selector_used.len(), + )); + } + if read != bytes.len() { + return Err(ProtocolError::InvalidReadLen(read, bytes.len())); + } + Ok(( + RLNWitnessInput::new_multi( + identity_secret, + user_message_limit, + message_ids, + path_elements, + identity_path_index, + x, + external_nullifier, + selector_used, + )?, + read, + )) + } } } +/// Serializes an RLN partial witness to little-endian bytes. +pub fn rln_partial_witness_to_bytes_le( + partial_witness: &RLNPartialWitnessInput, +) -> Result, ProtocolError> { + // Calculate capacity for Vec: + // - VERSION_BYTE_SIZE byte for version tag + // - 2 field elements: identity_secret, user_message_limit + // - variable size of path_elements, identity_path_index + // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index) + let capacity = VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (2 + partial_witness.path_elements.len()) + + partial_witness.identity_path_index.len() + + VEC_LEN_BYTE_SIZE * 2; + let mut bytes = Vec::with_capacity(capacity); + bytes.push(partial_witness.version_byte()); + bytes.extend_from_slice(&partial_witness.identity_secret.to_bytes_le()); + bytes.extend_from_slice(&fr_to_bytes_le(&partial_witness.user_message_limit)); + bytes.extend_from_slice(&vec_fr_to_bytes_le(&partial_witness.path_elements)); + bytes.extend_from_slice(&vec_u8_to_bytes_le(&partial_witness.identity_path_index)); + + Ok(bytes) +} + +/// Serializes an RLN partial witness to big-endian bytes. +pub fn rln_partial_witness_to_bytes_be( + partial_witness: &RLNPartialWitnessInput, +) -> Result, ProtocolError> { + // Calculate capacity for Vec: + // - VERSION_BYTE_SIZE byte for version tag + // - 2 field elements: identity_secret, user_message_limit + // - variable size of path_elements, identity_path_index + // - VEC_LEN_BYTE_SIZE bytes length prefix per vector (path_elements, identity_path_index) + let capacity = VERSION_BYTE_SIZE + + FR_BYTE_SIZE * (2 + partial_witness.path_elements.len()) + + partial_witness.identity_path_index.len() + + VEC_LEN_BYTE_SIZE * 2; + let mut bytes = Vec::with_capacity(capacity); + bytes.push(partial_witness.version_byte()); + bytes.extend_from_slice(&partial_witness.identity_secret.to_bytes_be()); + bytes.extend_from_slice(&fr_to_bytes_be(&partial_witness.user_message_limit)); + bytes.extend_from_slice(&vec_fr_to_bytes_be(&partial_witness.path_elements)); + bytes.extend_from_slice(&vec_u8_to_bytes_be(&partial_witness.identity_path_index)); + + Ok(bytes) +} + /// Deserializes an RLN partial witness from little-endian bytes. /// /// Returns the deserialized partial witness and the number of bytes read. @@ -639,7 +679,7 @@ pub fn bytes_le_to_rln_partial_witness( return Err(ProtocolError::InvalidReadLen(1, 0)); } - let _version = SerializationVersion::try_from(bytes[0])?; + let _version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; let (identity_secret, el_size) = IdSecret::from_bytes_le(&bytes[read..])?; @@ -679,7 +719,7 @@ pub fn bytes_be_to_rln_partial_witness( return Err(ProtocolError::InvalidReadLen(1, 0)); } - let _version = SerializationVersion::try_from(bytes[0])?; + let _version = MessageMode::try_from(bytes[0])?; let mut read: usize = VERSION_BYTE_SIZE; let (identity_secret, el_size) = IdSecret::from_bytes_be(&bytes[read..])?; @@ -709,62 +749,6 @@ pub fn bytes_be_to_rln_partial_witness( )) } -/// Converts RLN witness to JSON with BigInt string representation for witness calculator. -pub fn rln_witness_to_bigint_json( - witness: &RLNWitnessInput, -) -> Result { - let path_elements_str: Vec = witness - .path_elements - .iter() - .map(|v| to_bigint(v).to_str_radix(10)) - .collect(); - let identity_path_index_str: Vec = witness - .identity_path_index - .iter() - .map(|v| BigInt::from(*v).to_str_radix(10)) - .collect(); - - #[cfg(not(feature = "multi-message-id"))] - { - let RLNMessageInputs::SingleV1 { message_id } = &witness.message_inputs; - Ok(serde_json::json!({ - "identitySecret": to_bigint(&witness.identity_secret).to_str_radix(10), - "userMessageLimit": to_bigint(&witness.user_message_limit).to_str_radix(10), - "messageId": to_bigint(message_id).to_str_radix(10), - "pathElements": path_elements_str, - "identityPathIndex": identity_path_index_str, - "x": to_bigint(&witness.x).to_str_radix(10), - "externalNullifier": to_bigint(&witness.external_nullifier).to_str_radix(10), - })) - } - #[cfg(feature = "multi-message-id")] - { - let RLNMessageInputs::MultiV1 { - message_ids, - selector_used, - } = &witness.message_inputs; - let message_ids_str: Vec = message_ids - .iter() - .map(|id| to_bigint(id).to_str_radix(10)) - .collect(); - let selector_used_str: Vec = selector_used - .iter() - .map(|&v| BigInt::from(v).to_str_radix(10)) - .collect(); - - Ok(serde_json::json!({ - "identitySecret": to_bigint(&witness.identity_secret).to_str_radix(10), - "userMessageLimit": to_bigint(&witness.user_message_limit).to_str_radix(10), - "messageId": message_ids_str, - "selectorUsed": selector_used_str, - "pathElements": path_elements_str, - "identityPathIndex": identity_path_index_str, - "x": to_bigint(&witness.x).to_str_radix(10), - "externalNullifier": to_bigint(&witness.external_nullifier).to_str_radix(10), - })) - } -} - /// Computes RLN proof values from witness input. /// /// Calculates the public outputs (y, nullifier, root) that will be part of the proof. @@ -778,47 +762,40 @@ pub fn proof_values_from_witness(witness: &RLNWitnessInput) -> RLNProofValues { let a_0 = &witness.identity_secret; - #[cfg(not(feature = "multi-message-id"))] - { - let RLNMessageInputs::SingleV1 { message_id } = &witness.message_inputs; - let mut to_hash = [**a_0, witness.external_nullifier, *message_id]; - let a_1 = poseidon_hash(&to_hash); - let y = *(a_0.clone()) + witness.x * a_1; - let nullifier = poseidon_hash(&[a_1]); - to_hash[0].zeroize(); - - RLNProofValues::new(root, witness.x, witness.external_nullifier, y, nullifier) - } - #[cfg(feature = "multi-message-id")] - { - let RLNMessageInputs::MultiV1 { - message_ids, - selector_used, - } = &witness.message_inputs; - let mut ys = Vec::with_capacity(message_ids.len()); - let mut nullifiers = Vec::with_capacity(message_ids.len()); - - for (i, message_id) in message_ids.iter().enumerate() { + match &witness.message_inputs { + RLNMessageInputs::SingleV1 { message_id } => { let mut to_hash = [**a_0, witness.external_nullifier, *message_id]; let a_1 = poseidon_hash(&to_hash); - - let selector = Fr::from(selector_used[i]); - let y = (*(a_0.clone()) + witness.x * a_1) * selector; - let nullifier = poseidon_hash(&[a_1]) * selector; - - ys.push(y); - nullifiers.push(nullifier); + let y = *(a_0.clone()) + witness.x * a_1; + let nullifier = poseidon_hash(&[a_1]); to_hash[0].zeroize(); + RLNProofValues::new_single(root, witness.x, witness.external_nullifier, y, nullifier) + } + RLNMessageInputs::MultiV1 { + message_ids, + selector_used, + } => { + let mut ys = Vec::with_capacity(message_ids.len()); + let mut nullifiers = Vec::with_capacity(message_ids.len()); + for (i, message_id) in message_ids.iter().enumerate() { + let mut to_hash = [**a_0, witness.external_nullifier, *message_id]; + let a_1 = poseidon_hash(&to_hash); + let selector = Fr::from(selector_used[i]); + let y = (*(a_0.clone()) + witness.x * a_1) * selector; + let nullifier = poseidon_hash(&[a_1]) * selector; + ys.push(y); + nullifiers.push(nullifier); + to_hash[0].zeroize(); + } + RLNProofValues::new_multi( + root, + witness.x, + witness.external_nullifier, + ys, + nullifiers, + selector_used.clone(), + ) } - - RLNProofValues::new( - root, - witness.x, - witness.external_nullifier, - ys, - nullifiers, - selector_used.clone(), - ) } } @@ -865,24 +842,22 @@ pub(super) fn inputs_for_witness_calculation( ("userMessageLimit", vec![witness.user_message_limit.into()]), ]; - #[cfg(not(feature = "multi-message-id"))] - { - let RLNMessageInputs::SingleV1 { message_id } = &witness.message_inputs; - inputs.push(("messageId", vec![(*message_id).into()])); - } - #[cfg(feature = "multi-message-id")] - { - let RLNMessageInputs::MultiV1 { + match &witness.message_inputs { + RLNMessageInputs::SingleV1 { message_id } => { + inputs.push(("messageId", vec![(*message_id).into()])); + } + RLNMessageInputs::MultiV1 { message_ids, selector_used, - } = &witness.message_inputs; - inputs.push(( - "messageId", - message_ids.iter().cloned().map(Into::into).collect(), - )); - let selector_used_fr: Vec = - selector_used.iter().map(|&v| Fr::from(v).into()).collect(); - inputs.push(("selectorUsed", selector_used_fr)); + } => { + inputs.push(( + "messageId", + message_ids.iter().cloned().map(Into::into).collect(), + )); + let selector_used_fr: Vec = + selector_used.iter().map(|&v| Fr::from(v).into()).collect(); + inputs.push(("selectorUsed", selector_used_fr)); + } } inputs.push(( @@ -908,7 +883,7 @@ pub(super) fn inputs_for_witness_calculation( #[cfg(not(target_arch = "wasm32"))] pub(super) fn inputs_for_partial_witness_calculation( witness: &RLNPartialWitnessInput, - #[cfg(feature = "multi-message-id")] max_out: usize, + max_out: usize, ) -> Vec<(&'static str, Vec>)> { let mut identity_path_index = Vec::with_capacity(witness.identity_path_index.len()); witness @@ -927,10 +902,9 @@ pub(super) fn inputs_for_partial_witness_calculation( ), ]; - #[cfg(not(feature = "multi-message-id"))] - inputs.push(("messageId", vec![None])); - #[cfg(feature = "multi-message-id")] - { + if max_out == 1 { + inputs.push(("messageId", vec![None])); + } else { inputs.push(("messageId", vec![None; max_out])); inputs.push(("selectorUsed", vec![None; max_out])); } @@ -958,3 +932,124 @@ pub(super) fn inputs_for_partial_witness_calculation( inputs } + +#[derive(Debug, PartialEq, Clone)] +pub enum RLNWitnessInputV3 { + Single(RLNWitnessInputSingle), + Multi(RLNWitnessInputMulti), +} + +#[derive(Debug, PartialEq, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct RLNWitnessInputSingle { + pub(crate) identity_secret: IdSecret, + pub(crate) user_message_limit: Fr, + pub(crate) message_id: Fr, + pub(crate) path_elements: Vec, + pub(crate) identity_path_index: Vec, + pub(crate) x: Fr, + pub(crate) external_nullifier: Fr, +} + +impl RLNWitnessInputSingle { + pub fn new( + identity_secret: IdSecret, + user_message_limit: Fr, + message_id: Fr, + path_elements: Vec, + identity_path_index: Vec, + x: Fr, + external_nullifier: Fr, + ) -> Self { + Self { + identity_secret, + user_message_limit, + message_id, + path_elements, + identity_path_index, + x, + external_nullifier, + } + } +} + +#[derive(Debug, PartialEq, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct RLNWitnessInputMulti { + pub(crate) identity_secret: IdSecret, + pub(crate) user_message_limit: Fr, + pub(crate) path_elements: Vec, + pub(crate) identity_path_index: Vec, + pub(crate) x: Fr, + pub(crate) external_nullifier: Fr, + pub(crate) message_ids: Vec, + pub(crate) selector_used: Vec, +} + +impl RLNWitnessInputMulti { + #[allow(clippy::too_many_arguments)] + pub fn new( + identity_secret: IdSecret, + user_message_limit: Fr, + path_elements: Vec, + identity_path_index: Vec, + x: Fr, + external_nullifier: Fr, + message_ids: Vec, + selector_used: Vec, + ) -> Self { + Self { + identity_secret, + user_message_limit, + path_elements, + identity_path_index, + x, + external_nullifier, + message_ids, + selector_used, + } + } +} + +#[derive(Debug, PartialEq, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct RLNPartialWitnessInputV3 { + pub(crate) identity_secret: IdSecret, + pub(crate) user_message_limit: Fr, + pub(crate) path_elements: Vec, + pub(crate) identity_path_index: Vec, +} + +impl RLNPartialWitnessInputV3 { + pub fn new( + identity_secret: IdSecret, + user_message_limit: Fr, + path_elements: Vec, + identity_path_index: Vec, + ) -> Self { + Self { + identity_secret, + user_message_limit, + path_elements, + identity_path_index, + } + } +} + +impl From<&RLNWitnessInputV3> for RLNPartialWitnessInputV3 { + fn from(witness: &RLNWitnessInputV3) -> Self { + match witness { + RLNWitnessInputV3::Single(w) => RLNPartialWitnessInputV3::from(w), + RLNWitnessInputV3::Multi(w) => RLNPartialWitnessInputV3::from(w), + } + } +} + +impl From<&RLNWitnessInputSingle> for RLNPartialWitnessInputV3 { + fn from(_witness: &RLNWitnessInputSingle) -> Self { + todo!() + } +} + +impl From<&RLNWitnessInputMulti> for RLNPartialWitnessInputV3 { + fn from(_witness: &RLNWitnessInputMulti) -> Self { + todo!() + } +} diff --git a/rln/src/protocol/zk.rs b/rln/src/protocol/zk.rs new file mode 100644 index 00000000..d2615157 --- /dev/null +++ b/rln/src/protocol/zk.rs @@ -0,0 +1,100 @@ +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +use crate::{ + circuit::Fr, + prelude::{CanonicalDeserializeBE, CanonicalSerializeBE}, +}; +#[cfg(not(target_arch = "wasm32"))] +use crate::{ + circuit::{ArkGroth16Backend, PartialProof, Proof}, + error::RLNError, + prelude::RLNPartialWitnessInputV3, + protocol::{RLNProofValuesV3, RLNWitnessInputV3}, +}; + +pub trait RLNZkProof { + type Witness: CanonicalSerialize + + CanonicalDeserialize + + CanonicalSerializeBE + + CanonicalDeserializeBE; + type Values: RecoverSecret + + TryFrom + + CanonicalSerialize + + CanonicalDeserialize + + CanonicalSerializeBE + + CanonicalDeserializeBE; + type Proof: CanonicalSerialize + CanonicalDeserialize; + type Error; + + fn generate_proof( + &self, + witness: Self::Witness, + ) -> Result<(Self::Proof, Self::Values), Self::Error>; + + fn verify(&self, proof: &Self::Proof, values: &Self::Values) -> Result; +} + +pub trait RecoverSecret { + type Error; + + fn recover_secret(&self, other: &Rhs) -> Result; +} + +pub trait RLNPartialZkProof: RLNZkProof { + type PartialWitness: CanonicalSerialize + + CanonicalDeserialize + + CanonicalSerializeBE + + CanonicalDeserializeBE; + type PartialProof: CanonicalSerialize + CanonicalDeserialize; + + fn generate_partial_proof( + &self, + witness: Self::PartialWitness, + ) -> Result; + + fn finish_proof( + &self, + partial: Self::PartialProof, + witness: Self::Witness, + ) -> Result; +} + +#[cfg(not(target_arch = "wasm32"))] +impl RLNZkProof for ArkGroth16Backend { + type Witness = RLNWitnessInputV3; + type Values = RLNProofValuesV3; + type Proof = Proof; + type Error = RLNError; + + fn generate_proof( + &self, + _witness: Self::Witness, + ) -> Result<(Self::Proof, Self::Values), Self::Error> { + todo!() + } + + fn verify(&self, _proof: &Self::Proof, _values: &Self::Values) -> Result { + todo!() + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl RLNPartialZkProof for ArkGroth16Backend { + type PartialWitness = RLNPartialWitnessInputV3; + type PartialProof = PartialProof; + + fn generate_partial_proof( + &self, + _witness: Self::PartialWitness, + ) -> Result { + todo!() + } + + fn finish_proof( + &self, + _partial: Self::PartialProof, + _witness: Self::Witness, + ) -> Result { + todo!() + } +} diff --git a/rln/src/public.rs b/rln/src/public.rs index 700e3ba0..9165626b 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -2,27 +2,26 @@ // It is used by the FFI, WASM and should be used by tests as well use num_bigint::BigInt; +use zerokit_utils::merkle_tree::ZerokitMerkleTree; #[cfg(not(feature = "stateless"))] use { crate::poseidon_tree::PoseidonTree, std::str::FromStr, - zerokit_utils::merkle_tree::{ - Hasher, ZerokitMerkleProof, ZerokitMerkleTree, ZerokitMerkleTreeError, - }, + zerokit_utils::merkle_tree::{Hasher, ZerokitMerkleProof, ZerokitMerkleTreeError}, }; #[cfg(not(target_arch = "wasm32"))] use crate::{ - circuit::{graph_from_folder, graph_from_raw, zkey_from_folder, Graph, PartialProof}, + circuit::{graph_from_raw, graph_single_v1, zkey_single_v1, Graph, PartialProof}, prelude::RLNPartialWitnessInput, - protocol::{finish_zk_proof, generate_partial_zk_proof, generate_zk_proof}, + protocol::{finish_zk_proof, generate_partial_zk_proof, generate_zk_proof, MessageMode}, }; use crate::{ circuit::{zkey_from_raw, Fr, Proof, Zkey}, error::{RLNError, VerifyError}, protocol::{ - generate_zk_proof_with_witness, proof_values_from_witness, verify_zk_proof, RLNProofValues, - RLNWitnessInput, + generate_zk_proof_with_witness, proof_values_from_witness, verify_zk_proof, + RLNPartialZkProof, RLNProofValues, RLNWitnessInput, RLNZkProof, Stateful, Stateless, }, }; @@ -62,6 +61,8 @@ pub struct RLN { pub(crate) graph: Graph, #[cfg(not(feature = "stateless"))] pub(crate) tree: PoseidonTree, + #[cfg(not(target_arch = "wasm32"))] + pub(crate) message_mode: MessageMode, } impl RLN { @@ -100,8 +101,8 @@ impl RLN { /// ``` #[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))] pub fn new(tree_depth: usize, tree_config: T) -> Result { - let zkey = zkey_from_folder().to_owned(); - let graph = graph_from_folder().to_owned(); + let zkey = zkey_single_v1().to_owned(); + let graph = graph_single_v1().to_owned(); let config = tree_config.into_tree_config()?; // We compute a default empty tree @@ -114,8 +115,8 @@ impl RLN { Ok(RLN { zkey, graph, - #[cfg(not(feature = "stateless"))] tree, + message_mode: MessageMode::SingleV1, }) } @@ -128,10 +129,14 @@ impl RLN { /// ``` #[cfg(all(not(target_arch = "wasm32"), feature = "stateless"))] pub fn new() -> Result { - let zkey = zkey_from_folder().to_owned(); - let graph = graph_from_folder().clone(); + let zkey = zkey_single_v1().to_owned(); + let graph = graph_single_v1().clone(); - Ok(RLN { zkey, graph }) + Ok(RLN { + zkey, + graph, + message_mode: MessageMode::SingleV1, + }) } /// Creates a new RLN object by passing circuit resources as byte vectors. @@ -140,7 +145,6 @@ impl RLN { /// - `tree_depth`: the depth of the internal Merkle tree /// - `zkey_data`: a byte vector containing the proving key (`rln_final.arkzkey`) as binary file /// - `graph_data`: a byte vector containing the graph data (`graph.bin`) as binary file - /// - `max_out` (multi-message-id feature): the maximum number of message ID slots the circuit supports /// - `tree_config`: configuration for the Merkle tree (accepts multiple types via TreeConfigInput trait) /// /// Examples: @@ -172,18 +176,13 @@ impl RLN { #[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))] pub fn new_with_params( tree_depth: usize, - #[cfg(feature = "multi-message-id")] max_out: usize, zkey_data: Vec, graph_data: Vec, tree_config: T, ) -> Result { let zkey = zkey_from_raw(&zkey_data)?; - let graph = graph_from_raw( - &graph_data, - Some(tree_depth), - #[cfg(feature = "multi-message-id")] - Some(max_out), - )?; + let graph = graph_from_raw(&graph_data, Some(tree_depth), None)?; + let message_mode = MessageMode::from(&graph); let config = tree_config.into_tree_config()?; @@ -197,8 +196,8 @@ impl RLN { Ok(RLN { zkey, graph, - #[cfg(not(feature = "stateless"))] tree, + message_mode, }) } @@ -228,20 +227,16 @@ impl RLN { /// )?; /// ``` #[cfg(all(not(target_arch = "wasm32"), feature = "stateless"))] - pub fn new_with_params( - zkey_data: Vec, - graph_data: Vec, - #[cfg(feature = "multi-message-id")] max_out: usize, - ) -> Result { + pub fn new_with_params(zkey_data: Vec, graph_data: Vec) -> Result { let zkey = zkey_from_raw(&zkey_data)?; - let graph = graph_from_raw( - &graph_data, - None, - #[cfg(feature = "multi-message-id")] - Some(max_out), - )?; + let graph = graph_from_raw(&graph_data, None, None)?; + let message_mode = MessageMode::from(&graph); - Ok(RLN { zkey, graph }) + Ok(RLN { + zkey, + graph, + message_mode, + }) } /// Creates a new stateless RLN object by passing circuit resources as a byte vector. @@ -275,8 +270,14 @@ impl RLN { self.graph.tree_depth } + #[cfg(not(target_arch = "wasm32"))] + /// Returns the message mode this RLN instance was configured with. + pub fn message_mode(&self) -> MessageMode { + self.message_mode + } + /// Returns the maximum number of message ID slots supported by the graph. - #[cfg(all(feature = "multi-message-id", not(target_arch = "wasm32")))] + #[cfg(not(target_arch = "wasm32"))] pub fn max_out(&self) -> usize { self.graph.max_out } @@ -592,7 +593,7 @@ impl RLN { /// /// Example: /// ``` - /// let proof_values = proof_values_from_witness(&witness); + /// let proof_values = proof_values_from_witness(witness); /// /// // We compute a Groth16 proof /// let zk_proof = rln.generate_zk_proof(&witness)?; @@ -696,7 +697,7 @@ impl RLN { /// let zk_proof = rln.generate_zk_proof(&witness)?; /// /// // We compute proof values directly from witness - /// let proof_values = proof_values_from_witness(&witness); + /// let proof_values = proof_values_from_witness(witness); /// /// // We verify the proof /// let verified = rln.verify_zk_proof(&zk_proof, &proof_values)?; @@ -762,3 +763,88 @@ impl RLN { Ok(true) } } + +pub struct RLNV3 { + pub(crate) _zkp: ZkProof, + pub(crate) state: Mode, +} + +impl RLNV3 { + pub fn new(zkp: ZkProof) -> Self { + Self { + _zkp: zkp, + state: Stateless, + } + } +} + +impl RLNV3, ZkProof> { + pub fn new(tree: T, zkp: ZkProof) -> Self { + Self { + _zkp: zkp, + state: Stateful::new(tree), + } + } +} + +impl RLNV3, ZkProof> { + pub fn tree(&self) -> &T { + self.state.tree() + } + + pub fn tree_mut(&mut self) -> &mut T { + self.state.tree_mut() + } + + pub fn into_tree(self) -> T { + self.state.into_tree() + } +} + +impl RLNV3, ZkProof> { + pub fn tree_depth(&self) -> usize { + todo!() + } + + pub fn get_root(&self) -> Fr { + todo!() + } + + pub fn insert_leaf(&mut self, _index: usize, _leaf: Fr) -> Result<(), RLNError> { + todo!() + } +} + +impl RLNV3 { + pub fn generate_proof( + &self, + _witness: ZkProof::Witness, + ) -> Result<(ZkProof::Proof, ZkProof::Values), ZkProof::Error> { + todo!() + } + + pub fn verify_proof( + &self, + _proof: &ZkProof::Proof, + _values: &ZkProof::Values, + ) -> Result { + todo!() + } +} + +impl RLNV3 { + pub fn generate_partial_proof( + &self, + _partial_witness: ZkProof::PartialWitness, + ) -> Result { + todo!() + } + + pub fn finish_proof( + &self, + _partial_proof: ZkProof::PartialProof, + _witness: ZkProof::Witness, + ) -> Result { + todo!() + } +} diff --git a/rln/src/utils.rs b/rln/src/utils.rs index 5e005a51..9234ac64 100644 --- a/rln/src/utils.rs +++ b/rln/src/utils.rs @@ -12,21 +12,12 @@ use rand::Rng; use ruint::aliases::U256; use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; -use crate::{circuit::Fr, error::UtilsError}; - -/// Byte size of a field element aligned to 64-bit boundary, computed once at compile time. -pub const FR_BYTE_SIZE: usize = { - // Get the modulus bit size of the field - let modulus_bits: u32 = Fr::MODULUS_BIT_SIZE; - // Alignment boundary in bits for field element serialization - let alignment_bits: u32 = 64; - // Align to the next multiple of alignment_bits and convert to bytes - ((modulus_bits + alignment_bits - (modulus_bits % alignment_bits)) / 8) as usize +use crate::{ + circuit::Fr, + error::UtilsError, + protocol::{FR_BYTE_SIZE, VEC_LEN_BYTE_SIZE}, }; -/// Byte size of the length prefix used when serializing variable-length vectors. -pub const VEC_LEN_BYTE_SIZE: usize = 8; - /// 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. @@ -120,7 +111,7 @@ pub fn fr_to_bytes_le(input: &Fr) -> Vec { pub fn fr_to_bytes_be(input: &Fr) -> Vec { let input_biguint: BigUint = (*input).into(); let mut res = input_biguint.to_bytes_be(); - // For BE, insert 0 at the start of the Vec (see also fr_to_bytes_le comments) + // For BE, insert 0 at the start of the Vec let to_insert_count = FR_BYTE_SIZE.saturating_sub(res.len()); if to_insert_count > 0 { // Insert multi 0 at index 0 @@ -497,6 +488,7 @@ impl IdSecret { pub fn to_bytes_be(&self) -> Zeroizing> { let input_biguint: BigUint = self.0.into(); let mut res = input_biguint.to_bytes_be(); + // For BE, insert 0 at the start of the Vec let to_insert_count = FR_BYTE_SIZE.saturating_sub(res.len()); if to_insert_count > 0 { // Insert multi 0 at index 0 diff --git a/rln/tests/ffi.rs b/rln/tests/ffi.rs index add42c92..66ded7a9 100644 --- a/rln/tests/ffi.rs +++ b/rln/tests/ffi.rs @@ -1,5 +1,5 @@ #[cfg(test)] -#[cfg(all(not(feature = "stateless"), not(feature = "multi-message-id")))] +#[cfg(not(feature = "stateless"))] mod test { use std::{fs::File, io::Read}; @@ -78,7 +78,7 @@ mod test { }; // Create witness input - let witness = match ffi_rln_witness_input_new( + let witness = match ffi_rln_witness_input_new_single( identity_secret, user_message_limit, message_id, @@ -874,7 +874,7 @@ mod test { _ => panic!("get merkle proof failed"), }; - let witness = match ffi_rln_witness_input_new( + let witness = match ffi_rln_witness_input_new_single( &CFr::from(*identity_secret), &CFr::from(user_message_limit), &CFr::from(message_id), @@ -1009,7 +1009,7 @@ mod test { let x_cfr = CFr::from(x); let external_nullifier_cfr = CFr::from(external_nullifier); - let result = ffi_rln_witness_input_new( + let result = ffi_rln_witness_input_new_single( &identity_secret_cfr, &user_message_limit_cfr, &invalid_message_id, @@ -1023,7 +1023,7 @@ mod test { // Test invalid witness input (zero user_message_limit) let zero_limit = CFr::from(Fr::from(0)); let valid_message_id = CFr::from(Fr::from(0)); - let result = ffi_rln_witness_input_new( + let result = ffi_rln_witness_input_new_single( &identity_secret_cfr, &zero_limit, &valid_message_id, @@ -1039,7 +1039,7 @@ mod test { merkle_proof.path_elements.iter().cloned().collect(); bad_path_elements_vec.pop(); let bad_path_elements: repr_c::Vec = bad_path_elements_vec.into(); - let result = ffi_rln_witness_input_new( + let result = ffi_rln_witness_input_new_single( &identity_secret_cfr, &user_message_limit_cfr, &valid_message_id, @@ -1054,7 +1054,7 @@ mod test { let mut bad_path_index_vec: Vec = merkle_proof.path_index.iter().copied().collect(); bad_path_index_vec.pop(); let bad_path_index: repr_c::Vec = bad_path_index_vec.into(); - let result = ffi_rln_witness_input_new( + let result = ffi_rln_witness_input_new_single( &identity_secret_cfr, &user_message_limit_cfr, &valid_message_id, @@ -1251,7 +1251,7 @@ mod test { let x = hash_to_field_le(&signal); // Create the full witness via FFI - let full_witness = match ffi_rln_witness_input_new( + let full_witness = match ffi_rln_witness_input_new_single( &CFr::from(*identity_secret), &CFr::from(user_message_limit), &CFr::from(message_id), diff --git a/rln/tests/protocol.rs b/rln/tests/protocol.rs index fbe84696..576a2640 100644 --- a/rln/tests/protocol.rs +++ b/rln/tests/protocol.rs @@ -9,42 +9,6 @@ mod test { type ConfigOf = ::Config; - fn new_single_message_witness( - identity_secret: IdSecret, - user_message_limit: Fr, - message_id: Fr, - path_elements: Vec, - identity_path_index: Vec, - x: Fr, - external_nullifier: Fr, - ) -> Result { - #[cfg(not(feature = "multi-message-id"))] - { - RLNWitnessInput::new( - identity_secret, - user_message_limit, - message_id, - path_elements, - identity_path_index, - x, - external_nullifier, - ) - } - #[cfg(feature = "multi-message-id")] - { - RLNWitnessInput::new( - identity_secret, - user_message_limit, - vec![message_id, Fr::from(0), Fr::from(0), Fr::from(0)], - path_elements, - identity_path_index, - x, - external_nullifier, - vec![true, false, false, false], - ) - } - } - #[test] // We test Merkle tree generation, proofs and verification fn test_merkle_proof() { @@ -151,7 +115,7 @@ mod test { let message_id = Fr::from(1); - new_single_message_witness( + RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -201,7 +165,7 @@ mod test { let message_id = Fr::from(message_id); - new_single_message_witness( + RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -219,8 +183,8 @@ mod test { let witness = get_test_witness(); // We generate all relevant keys - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); + let proving_key = zkey_single_v1(); + let graph_data = graph_single_v1(); // Generate a zkSNARK proof let proof = generate_zk_proof(proving_key, &witness, graph_data).unwrap(); @@ -240,8 +204,8 @@ mod test { let partial_witness = RLNPartialWitnessInput::from(&witness); - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); + let proving_key = zkey_single_v1(); + let graph_data = graph_single_v1(); let partial_proof = generate_partial_zk_proof(proving_key, &partial_witness, graph_data).unwrap(); @@ -260,8 +224,8 @@ mod test { let partial_witness = RLNPartialWitnessInput::from(&witness); - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); + let proving_key = zkey_single_v1(); + let graph_data = graph_single_v1(); let partial_proof = generate_partial_zk_proof(proving_key, &partial_witness, graph_data).unwrap(); @@ -370,8 +334,8 @@ mod test { let witness = get_test_witness(); let partial_witness = RLNPartialWitnessInput::from(&witness); - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); + let proving_key = zkey_single_v1(); + let graph_data = graph_single_v1(); let partial_proof = generate_partial_zk_proof(proving_key, &partial_witness, graph_data).unwrap(); @@ -443,7 +407,7 @@ mod test { // Test valid witness input let valid_message_id = Fr::from(50); - let result = new_single_message_witness( + let result = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, valid_message_id, @@ -456,7 +420,7 @@ mod test { // Test message_id >= user_message_limit (should fail) let invalid_message_id = Fr::from(100); // equal to limit - let result = new_single_message_witness( + let result = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, invalid_message_id, @@ -468,7 +432,7 @@ mod test { assert!(matches!(result, Err(ProtocolError::InvalidMessageId(_, _)))); let invalid_message_id = Fr::from(150); // greater than limit - let result = new_single_message_witness( + let result = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, invalid_message_id, @@ -481,7 +445,7 @@ mod test { // Test user_message_limit = 0 (should fail) let zero_limit = Fr::from(0); - let result = new_single_message_witness( + let result = RLNWitnessInput::new_single( identity_secret, zero_limit, Fr::from(0), @@ -653,8 +617,8 @@ mod test { #[test] fn test_rln_proof_serialization_be_roundtrip() { let witness = get_test_witness(); - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); + let proving_key = zkey_single_v1(); + let graph_data = graph_single_v1(); let proof = generate_zk_proof(proving_key, &witness, graph_data).unwrap(); let proof_values = proof_values_from_witness(&witness); @@ -673,29 +637,19 @@ mod test { #[test] fn test_verify_zk_proof_with_modified_public_value_fails() { let witness = get_test_witness(); - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); + let proving_key = zkey_single_v1(); + let graph_data = graph_single_v1(); let proof = generate_zk_proof(proving_key, &witness, graph_data).unwrap(); let proof_values = proof_values_from_witness(&witness); let new_root = *proof_values.root() + Fr::from(1); - #[cfg(not(feature = "multi-message-id"))] - let mutated_pv = RLNProofValues::new( + let mutated_pv = RLNProofValues::new_single( new_root, *proof_values.x(), *proof_values.external_nullifier(), *proof_values.y(), *proof_values.nullifier(), ); - #[cfg(feature = "multi-message-id")] - let mutated_pv = RLNProofValues::new( - new_root, - *proof_values.x(), - *proof_values.external_nullifier(), - proof_values.ys().to_vec(), - proof_values.nullifiers().to_vec(), - proof_values.selector_used().to_vec(), - ); let verified = verify_zk_proof(&proving_key.0.vk, &proof, &mutated_pv).unwrap(); assert!(!verified); @@ -781,25 +735,10 @@ mod test { json["userMessageLimit"].as_str().unwrap(), to_bigint(witness.user_message_limit()).to_str_radix(10) ); - #[cfg(not(feature = "multi-message-id"))] assert_eq!( json["messageId"].as_str().unwrap(), to_bigint(witness.message_id()).to_str_radix(10) ); - #[cfg(feature = "multi-message-id")] - assert_eq!( - json["messageId"] - .as_array() - .unwrap() - .iter() - .map(|v| v.as_str().unwrap().to_string()) - .collect::>(), - witness - .message_ids() - .iter() - .map(|id| to_bigint(id).to_str_radix(10)) - .collect::>() - ); assert_eq!( json["x"].as_str().unwrap(), to_bigint(witness.x()).to_str_radix(10) @@ -831,25 +770,10 @@ mod test { json2["userMessageLimit"].as_str().unwrap(), to_bigint(witness2.user_message_limit()).to_str_radix(10) ); - #[cfg(not(feature = "multi-message-id"))] assert_eq!( json2["messageId"].as_str().unwrap(), to_bigint(witness2.message_id()).to_str_radix(10) ); - #[cfg(feature = "multi-message-id")] - assert_eq!( - json2["messageId"] - .as_array() - .unwrap() - .iter() - .map(|v| v.as_str().unwrap().to_string()) - .collect::>(), - witness2 - .message_ids() - .iter() - .map(|id| to_bigint(id).to_str_radix(10)) - .collect::>() - ); assert_eq!( json2["x"].as_str().unwrap(), to_bigint(witness2.x()).to_str_radix(10) @@ -869,7 +793,6 @@ mod test { ); } - #[cfg(feature = "multi-message-id")] mod multi_message_id_test { use rln::prelude::*; use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; @@ -906,7 +829,7 @@ mod test { let message_ids = vec![Fr::from(0), Fr::from(1), Fr::from(2), Fr::from(3)]; let selector_used = vec![false, true, true, false]; - RLNWitnessInput::new( + RLNWitnessInput::new_multi( identity_secret, user_message_limit, message_ids, @@ -956,9 +879,6 @@ mod test { include_bytes!("../resources/tree_depth_20/multi_message_id/max_out_4/graph.bin"); let proving_key = zkey_from_raw(arkzkey_bytes).unwrap(); - #[cfg(not(feature = "multi-message-id"))] - let graph_data = graph_from_raw(graph_bytes, Some(DEFAULT_TREE_DEPTH)).unwrap(); - #[cfg(feature = "multi-message-id")] let graph_data = graph_from_raw(graph_bytes, Some(DEFAULT_TREE_DEPTH), Some(4)).unwrap(); diff --git a/rln/tests/public.rs b/rln/tests/public.rs index 12394068..5b1f41b0 100644 --- a/rln/tests/public.rs +++ b/rln/tests/public.rs @@ -42,42 +42,6 @@ mod test { .collect() } - fn new_single_message_witness( - identity_secret: IdSecret, - user_message_limit: Fr, - message_id: Fr, - path_elements: Vec, - identity_path_index: Vec, - x: Fr, - external_nullifier: Fr, - ) -> Result { - #[cfg(not(feature = "multi-message-id"))] - { - RLNWitnessInput::new( - identity_secret, - user_message_limit, - message_id, - path_elements, - identity_path_index, - x, - external_nullifier, - ) - } - #[cfg(feature = "multi-message-id")] - { - RLNWitnessInput::new( - identity_secret, - user_message_limit, - vec![message_id, Fr::from(0), Fr::from(0), Fr::from(0)], - path_elements, - identity_path_index, - x, - external_nullifier, - vec![true, false, false, false], - ) - } - } - fn random_rln_witness(tree_depth: usize) -> Result { let mut rng = thread_rng(); @@ -98,7 +62,7 @@ mod test { let message_id = Fr::from(1); let external_nullifier = poseidon_hash_pair(epoch, rln_identifier); - new_single_message_witness( + RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -116,64 +80,137 @@ mod test { #[cfg(feature = "stateless")] let rln = RLN::new().unwrap(); - #[cfg(not(feature = "multi-message-id"))] - let valid_snarkjs_proof = json!({ - "pi_a": [ - "606446415626469993821291758185575230335423926365686267140465300918089871829", - "14881534001609371078663128199084130129622943308489025453376548677995646280161", - "1" - ], - "pi_b": [ - [ - "18053812507994813734583839134426913715767914942522332114506614735770984570178", - "11219916332635123001710279198522635266707985651975761715977705052386984005181" - ], - [ - "17371289494006920912949790045699521359436706797224428511776122168520286372970", - "14038575727257298083893642903204723310279435927688342924358714639926373603890" - ], - [ - "1", - "0" - ] - ], - "pi_c": [ - "17701377127561410274754535747274973758826089226897242202671882899370780845888", - "12608543716397255084418384146504333522628400182843246910626782513289789807030", - "1" - ], - "protocol": "groth16", - "curve": "bn128" - }); - #[cfg(feature = "multi-message-id")] - let valid_snarkjs_proof = json!({ - "pi_a": [ - "18065030346679405936314703365313027854666139282416381597863520591326000485770", - "14771860444670385955411380174213497474946229693924900012944518111443580986423", - "1" - ], - "pi_b": [ - [ - "6735720011967965811552770307926073251484071544628748265245982358598709514632", - "20834884037174490293404784720629481437908298314108873169352614850721890028313" - ], - [ - "4833697662524472564312290961485074084149848067709427572820222800371260836955", - "17340414833348271743289107618101329696856992134080888054049600143320812961128" - ], - [ - "1", - "0" - ] - ], - "pi_c": [ - "15995592009555866776210915003813915385299392333518806237517816627481425816425", - "1089017666060567296165116465606820653924283171865888164456509348741884249923", - "1" - ], - "protocol": "groth16", - "curve": "bn128" - }); + let (valid_snarkjs_proof, x, valid_proof_values) = match rln.message_mode() { + MessageMode::SingleV1 => { + let proof = json!({ + "pi_a": [ + "606446415626469993821291758185575230335423926365686267140465300918089871829", + "14881534001609371078663128199084130129622943308489025453376548677995646280161", + "1" + ], + "pi_b": [ + [ + "18053812507994813734583839134426913715767914942522332114506614735770984570178", + "11219916332635123001710279198522635266707985651975761715977705052386984005181" + ], + [ + "17371289494006920912949790045699521359436706797224428511776122168520286372970", + "14038575727257298083893642903204723310279435927688342924358714639926373603890" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "17701377127561410274754535747274973758826089226897242202671882899370780845888", + "12608543716397255084418384146504333522628400182843246910626782513289789807030", + "1" + ], + "protocol": "groth16", + "curve": "bn128" + }); + let x = str_to_fr( + "20645213238265527935869146898028115621427162613172918400241870500502509785943", + 10, + ) + .unwrap(); + let proof_values = RLNProofValues::new_single( + str_to_fr( + "8502402278351299594663821509741133196466235670407051417832304486953898514733", + 10, + ) + .unwrap(), + x, + str_to_fr( + "21074405743803627666274838159589343934394162804826017440941339048886754734203", + 10, + ) + .unwrap(), + str_to_fr( + "16401008481486069296141645075505218976370369489687327284155463920202585288271", + 10, + ) + .unwrap(), + str_to_fr( + "9102791780887227194595604713537772536258726662792598131262022534710887343694", + 10, + ) + .unwrap(), + ); + (proof, x, proof_values) + } + MessageMode::MultiV1 { .. } => { + let proof = json!({ + "pi_a": [ + "18065030346679405936314703365313027854666139282416381597863520591326000485770", + "14771860444670385955411380174213497474946229693924900012944518111443580986423", + "1" + ], + "pi_b": [ + [ + "6735720011967965811552770307926073251484071544628748265245982358598709514632", + "20834884037174490293404784720629481437908298314108873169352614850721890028313" + ], + [ + "4833697662524472564312290961485074084149848067709427572820222800371260836955", + "17340414833348271743289107618101329696856992134080888054049600143320812961128" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "15995592009555866776210915003813915385299392333518806237517816627481425816425", + "1089017666060567296165116465606820653924283171865888164456509348741884249923", + "1" + ], + "protocol": "groth16", + "curve": "bn128" + }); + let x = str_to_fr( + "19797305253341717859481321525229680688216104810745023646128001903445473018856", + 10, + ) + .unwrap(); + let proof_values = RLNProofValues::new_multi( + str_to_fr( + "3431095415998240809893928695882631208288185026672939778030884659225595068838", + 10, + ) + .unwrap(), + x, + str_to_fr( + "21092292729219847360221935824233974597185442347481349054190488583986042064831", + 10, + ) + .unwrap(), + vec![ + str_to_fr( + "143052188957058141710854771333369177356024382963719479956590549598262357586", + 10, + ) + .unwrap(), + Fr::from(0), + Fr::from(0), + Fr::from(0), + ], + vec![ + str_to_fr( + "8499590175743632905717993598500718325843782253409297097332874882649203313309", + 10, + ) + .unwrap(), + Fr::from(0), + Fr::from(0), + Fr::from(0), + ], + vec![true, false, false, false], + ); + (proof, x, proof_values) + } + }; let valid_ark_proof = Proof { a: g1_from_str(&value_to_string_vec(&valid_snarkjs_proof["pi_a"])), @@ -188,80 +225,6 @@ mod test { c: g1_from_str(&value_to_string_vec(&valid_snarkjs_proof["pi_c"])), }; - #[cfg(not(feature = "multi-message-id"))] - let x = str_to_fr( - "20645213238265527935869146898028115621427162613172918400241870500502509785943", - 10, - ) - .unwrap(); - #[cfg(feature = "multi-message-id")] - let x = str_to_fr( - "19797305253341717859481321525229680688216104810745023646128001903445473018856", - 10, - ) - .unwrap(); - - #[cfg(not(feature = "multi-message-id"))] - let valid_proof_values = RLNProofValues::new( - str_to_fr( - "8502402278351299594663821509741133196466235670407051417832304486953898514733", - 10, - ) - .unwrap(), - x, - str_to_fr( - "21074405743803627666274838159589343934394162804826017440941339048886754734203", - 10, - ) - .unwrap(), - str_to_fr( - "16401008481486069296141645075505218976370369489687327284155463920202585288271", - 10, - ) - .unwrap(), - str_to_fr( - "9102791780887227194595604713537772536258726662792598131262022534710887343694", - 10, - ) - .unwrap(), - ); - - #[cfg(feature = "multi-message-id")] - let valid_proof_values = RLNProofValues::new( - str_to_fr( - "3431095415998240809893928695882631208288185026672939778030884659225595068838", - 10, - ) - .unwrap(), - x, - str_to_fr( - "21092292729219847360221935824233974597185442347481349054190488583986042064831", - 10, - ) - .unwrap(), - vec![ - str_to_fr( - "143052188957058141710854771333369177356024382963719479956590549598262357586", - 10, - ) - .unwrap(), - Fr::from(0), - Fr::from(0), - Fr::from(0), - ], - vec![ - str_to_fr( - "8499590175743632905717993598500718325843782253409297097332874882649203313309", - 10, - ) - .unwrap(), - Fr::from(0), - Fr::from(0), - Fr::from(0), - ], - vec![true, false, false, false], - ); - let verified = rln .verify_with_roots(&valid_ark_proof, &valid_proof_values, &x, &[]) .is_ok(); @@ -312,48 +275,14 @@ mod test { #[test] fn test_initialization_with_params() { - #[cfg(not(feature = "multi-message-id"))] let zkey_data = include_bytes!("../resources/tree_depth_20/rln_final.arkzkey").to_vec(); - #[cfg(not(feature = "multi-message-id"))] let graph_data = include_bytes!("../resources/tree_depth_20/graph.bin").to_vec(); - #[cfg(feature = "multi-message-id")] - let zkey_data = include_bytes!( - "../resources/tree_depth_20/multi_message_id/max_out_4/rln_final.arkzkey" - ) - .to_vec(); - #[cfg(feature = "multi-message-id")] - let graph_data = - include_bytes!("../resources/tree_depth_20/multi_message_id/max_out_4/graph.bin") - .to_vec(); - - #[cfg(all( - not(target_arch = "wasm32"), - not(feature = "stateless"), - not(feature = "multi-message-id") - ))] + #[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))] assert!(RLN::new_with_params(DEFAULT_TREE_DEPTH, zkey_data, graph_data, "").is_ok()); - #[cfg(all( - not(target_arch = "wasm32"), - not(feature = "stateless"), - feature = "multi-message-id" - ))] - assert!(RLN::new_with_params(DEFAULT_TREE_DEPTH, 4, zkey_data, graph_data, "").is_ok()); - - #[cfg(all( - not(target_arch = "wasm32"), - feature = "stateless", - not(feature = "multi-message-id") - ))] + #[cfg(all(not(target_arch = "wasm32"), feature = "stateless"))] assert!(RLN::new_with_params(zkey_data, graph_data).is_ok()); - - #[cfg(all( - not(target_arch = "wasm32"), - feature = "stateless", - feature = "multi-message-id" - ))] - assert!(RLN::new_with_params(zkey_data, graph_data, 4).is_ok()); } #[cfg(not(feature = "stateless"))] @@ -363,8 +292,6 @@ mod test { use rln::prelude::*; use serde_json::json; - use super::new_single_message_witness; - const NO_OF_LEAVES: usize = 256; fn setup_rln_proof( @@ -402,7 +329,7 @@ mod test { path_elements[0] = Fr::rand(&mut rng); } - let rln_witness = new_single_message_witness( + let rln_witness = RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -810,7 +737,7 @@ mod test { rln.get_merkle_proof(identity_index).unwrap(); // Create RLN witness - let rln_witness = new_single_message_witness( + let rln_witness = RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -877,7 +804,7 @@ mod test { rln.get_merkle_proof(identity_index).unwrap(); // Create RLN witness - let rln_witness = new_single_message_witness( + let rln_witness = RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -945,7 +872,7 @@ mod test { rln.get_merkle_proof(identity_index).unwrap(); // Create RLN witness - let rln_witness = new_single_message_witness( + let rln_witness = RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -1031,7 +958,7 @@ mod test { rln.get_merkle_proof(identity_index).unwrap(); // Create RLN witnesses for both signals - let rln_witness1 = new_single_message_witness( + let rln_witness1 = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, message_id, @@ -1042,7 +969,7 @@ mod test { ) .unwrap(); - let rln_witness2 = new_single_message_witness( + let rln_witness2 = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, message_id, @@ -1085,7 +1012,7 @@ mod test { rln.get_merkle_proof(identity_index_new).unwrap(); // We prepare proof input. Note that epoch is the same as before - let rln_witness3 = new_single_message_witness( + let rln_witness3 = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, message_id, @@ -1147,23 +1074,23 @@ mod test { // Mutate external_nullifier by adding 1 let new_en = *proof_values.external_nullifier() + Fr::from(1); - #[cfg(not(feature = "multi-message-id"))] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - new_en, - *proof_values.y(), - *proof_values.nullifier(), - ); - #[cfg(feature = "multi-message-id")] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - new_en, - proof_values.ys().to_vec(), - proof_values.nullifiers().to_vec(), - proof_values.selector_used().to_vec(), - ); + let mutated_pv = match rln.message_mode() { + MessageMode::SingleV1 => RLNProofValues::new_single( + *proof_values.root(), + *proof_values.x(), + new_en, + *proof_values.y(), + *proof_values.nullifier(), + ), + MessageMode::MultiV1 { .. } => RLNProofValues::new_multi( + *proof_values.root(), + *proof_values.x(), + new_en, + proof_values.ys().to_vec(), + proof_values.nullifiers().to_vec(), + proof_values.selector_used().to_vec(), + ), + }; // Verification should fail let verified = rln.verify_rln_proof(&proof, &mutated_pv, &x).is_ok(); @@ -1189,23 +1116,23 @@ mod test { let (rln, proof, proof_values, x, mut rng) = setup_rln_proof(false); // Mutate nullifier (simulating mutated message_id) - #[cfg(not(feature = "multi-message-id"))] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - *proof_values.external_nullifier(), - *proof_values.y(), - Fr::rand(&mut rng), - ); - #[cfg(feature = "multi-message-id")] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - *proof_values.external_nullifier(), - proof_values.ys().to_vec(), - vec![Fr::rand(&mut rng)], - proof_values.selector_used().to_vec(), - ); + let mutated_pv = match rln.message_mode() { + MessageMode::SingleV1 => RLNProofValues::new_single( + *proof_values.root(), + *proof_values.x(), + *proof_values.external_nullifier(), + *proof_values.y(), + Fr::rand(&mut rng), + ), + MessageMode::MultiV1 { .. } => RLNProofValues::new_multi( + *proof_values.root(), + *proof_values.x(), + *proof_values.external_nullifier(), + proof_values.ys().to_vec(), + vec![Fr::rand(&mut rng)], + proof_values.selector_used().to_vec(), + ), + }; // Verification should fail let verified = rln.verify_rln_proof(&proof, &mutated_pv, &x).is_ok(); @@ -1217,23 +1144,23 @@ mod test { let (rln, proof, proof_values, x, mut rng) = setup_rln_proof(false); // Mutate root (simulating mutated path_element) - #[cfg(not(feature = "multi-message-id"))] - let mutated_pv = RLNProofValues::new( - Fr::rand(&mut rng), - *proof_values.x(), - *proof_values.external_nullifier(), - *proof_values.y(), - *proof_values.nullifier(), - ); - #[cfg(feature = "multi-message-id")] - let mutated_pv = RLNProofValues::new( - Fr::rand(&mut rng), - *proof_values.x(), - *proof_values.external_nullifier(), - proof_values.ys().to_vec(), - proof_values.nullifiers().to_vec(), - proof_values.selector_used().to_vec(), - ); + let mutated_pv = match rln.message_mode() { + MessageMode::SingleV1 => RLNProofValues::new_single( + Fr::rand(&mut rng), + *proof_values.x(), + *proof_values.external_nullifier(), + *proof_values.y(), + *proof_values.nullifier(), + ), + MessageMode::MultiV1 { .. } => RLNProofValues::new_multi( + Fr::rand(&mut rng), + *proof_values.x(), + *proof_values.external_nullifier(), + proof_values.ys().to_vec(), + proof_values.nullifiers().to_vec(), + proof_values.selector_used().to_vec(), + ), + }; // Verification should fail let verified = rln.verify_rln_proof(&proof, &mutated_pv, &x).is_ok(); @@ -1247,23 +1174,23 @@ mod test { // Mutate external_nullifier by adding 1 let new_en = *proof_values.external_nullifier() + Fr::from(1); - #[cfg(not(feature = "multi-message-id"))] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - new_en, - *proof_values.y(), - *proof_values.nullifier(), - ); - #[cfg(feature = "multi-message-id")] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - new_en, - proof_values.ys().to_vec(), - proof_values.nullifiers().to_vec(), - proof_values.selector_used().to_vec(), - ); + let mutated_pv = match rln.message_mode() { + MessageMode::SingleV1 => RLNProofValues::new_single( + *proof_values.root(), + *proof_values.x(), + new_en, + *proof_values.y(), + *proof_values.nullifier(), + ), + MessageMode::MultiV1 { .. } => RLNProofValues::new_multi( + *proof_values.root(), + *proof_values.x(), + new_en, + proof_values.ys().to_vec(), + proof_values.nullifiers().to_vec(), + proof_values.selector_used().to_vec(), + ), + }; // Verification should fail let verified = rln @@ -1293,23 +1220,23 @@ mod test { let roots = vec![rln.get_root()]; // Mutate nullifier (simulating mutated message_id) - #[cfg(not(feature = "multi-message-id"))] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - *proof_values.external_nullifier(), - *proof_values.y(), - Fr::rand(&mut rng), - ); - #[cfg(feature = "multi-message-id")] - let mutated_pv = RLNProofValues::new( - *proof_values.root(), - *proof_values.x(), - *proof_values.external_nullifier(), - proof_values.ys().to_vec(), - vec![Fr::rand(&mut rng)], - proof_values.selector_used().to_vec(), - ); + let mutated_pv = match rln.message_mode() { + MessageMode::SingleV1 => RLNProofValues::new_single( + *proof_values.root(), + *proof_values.x(), + *proof_values.external_nullifier(), + *proof_values.y(), + Fr::rand(&mut rng), + ), + MessageMode::MultiV1 { .. } => RLNProofValues::new_multi( + *proof_values.root(), + *proof_values.x(), + *proof_values.external_nullifier(), + proof_values.ys().to_vec(), + vec![Fr::rand(&mut rng)], + proof_values.selector_used().to_vec(), + ), + }; // Verification should fail let verified = rln @@ -1386,7 +1313,7 @@ mod test { }; use super::DEFAULT_TREE_DEPTH; - use crate::test::{new_single_message_witness, random_rln_witness}; + use crate::test::random_rln_witness; type ConfigOf = ::Config; @@ -1427,7 +1354,7 @@ mod test { let merkle_proof = tree.proof(identity_index).unwrap(); let message_id = Fr::from(1); - let rln_witness = new_single_message_witness( + let rln_witness = RLNWitnessInput::new_single( identity_secret, user_message_limit, message_id, @@ -1509,7 +1436,7 @@ mod test { let merkle_proof = tree.proof(identity_index).unwrap(); let message_id = Fr::from(1); - let rln_witness1 = new_single_message_witness( + let rln_witness1 = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, message_id, @@ -1520,7 +1447,7 @@ mod test { ) .unwrap(); - let rln_witness2 = new_single_message_witness( + let rln_witness2 = RLNWitnessInput::new_single( identity_secret.clone(), user_message_limit, message_id, @@ -1555,7 +1482,7 @@ mod test { let identity_index_new = tree.leaves_set(); let merkle_proof_new = tree.proof(identity_index_new).unwrap(); - let rln_witness3 = new_single_message_witness( + let rln_witness3 = RLNWitnessInput::new_single( identity_secret_new.clone(), user_message_limit, message_id, @@ -1616,7 +1543,7 @@ mod test { } } - #[cfg(feature = "multi-message-id")] + #[cfg(not(feature = "stateless"))] mod multi_message_id_test { use rand::{thread_rng, Rng}; use rln::prelude::*; @@ -1643,7 +1570,7 @@ mod test { // Empty message_ids → EmptyMessageIds assert!(matches!( - RLNWitnessInput::new( + RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, vec![], @@ -1659,7 +1586,7 @@ mod test { // Mismatched selector_used length to message_ids length → FieldLengthMismatch assert!(matches!( - RLNWitnessInput::new( + RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, vec![Fr::from(0), Fr::from(1)], @@ -1675,7 +1602,7 @@ mod test { // Active message_id >= limit → InvalidMessageId assert!(matches!( - RLNWitnessInput::new( + RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, vec![Fr::from(0), Fr::from(10)], @@ -1690,7 +1617,7 @@ mod test { )); // Inactive message_id >= limit → OK - assert!(RLNWitnessInput::new( + assert!(RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, vec![Fr::from(0), Fr::from(10)], @@ -1704,7 +1631,7 @@ mod test { // Zero user_message_limit → ZeroUserMessageLimit assert!(matches!( - RLNWitnessInput::new( + RLNWitnessInput::new_multi( identity_secret.clone(), Fr::from(0), vec![Fr::from(0)], @@ -1720,7 +1647,7 @@ mod test { // Duplicate message_ids → DuplicateMessageIds assert!(matches!( - RLNWitnessInput::new( + RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, vec![Fr::from(5), Fr::from(5), Fr::from(1), Fr::from(2)], @@ -1735,7 +1662,7 @@ mod test { )); // Duplicate message_ids when inactive → OK (only active IDs are checked) - assert!(RLNWitnessInput::new( + assert!(RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, vec![Fr::from(0), Fr::from(0), Fr::from(1), Fr::from(2)], @@ -1749,7 +1676,7 @@ mod test { // All selectors false → NoActiveSelectorUsed assert!(matches!( - RLNWitnessInput::new( + RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, vec![Fr::from(0), Fr::from(1), Fr::from(2), Fr::from(3)], @@ -1764,7 +1691,7 @@ mod test { )); // Valid multi-message witness - assert!(RLNWitnessInput::new( + assert!(RLNWitnessInput::new_multi( identity_secret, user_message_limit, vec![Fr::from(0), Fr::from(1), Fr::from(2), Fr::from(3)], @@ -1787,8 +1714,7 @@ mod test { include_bytes!("../resources/tree_depth_20/multi_message_id/max_out_4/graph.bin") .to_vec(); - let rln = - RLN::new_with_params(DEFAULT_TREE_DEPTH, 4, zkey_data, graph_data, "").unwrap(); + let rln = RLN::new_with_params(DEFAULT_TREE_DEPTH, zkey_data, graph_data, "").unwrap(); let mut rng = thread_rng(); let (identity_secret, _) = keygen(); @@ -1805,7 +1731,7 @@ mod test { let message_ids = vec![Fr::from(0), Fr::from(1), Fr::from(2), Fr::from(3)]; let selector_used = vec![false, true, true, false]; - let witness = RLNWitnessInput::new( + let witness = RLNWitnessInput::new_multi( identity_secret, user_message_limit, message_ids, @@ -1853,8 +1779,7 @@ mod test { include_bytes!("../resources/tree_depth_20/multi_message_id/max_out_4/graph.bin") .to_vec(); - let rln = - RLN::new_with_params(DEFAULT_TREE_DEPTH, 4, zkey_data, graph_data, "").unwrap(); + let rln = RLN::new_with_params(DEFAULT_TREE_DEPTH, zkey_data, graph_data, "").unwrap(); let mut rng = thread_rng(); let (identity_secret, _) = keygen(); @@ -1874,7 +1799,7 @@ mod test { let message_ids = vec![Fr::from(0), Fr::from(1), Fr::from(2), Fr::from(3)]; let selector_used = vec![true, true, false, false]; - let witness1 = RLNWitnessInput::new( + let witness1 = RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, message_ids.clone(), @@ -1886,7 +1811,7 @@ mod test { ) .unwrap(); - let witness2 = RLNWitnessInput::new( + let witness2 = RLNWitnessInput::new_multi( identity_secret.clone(), user_message_limit, message_ids, @@ -1910,7 +1835,7 @@ mod test { let signal3: [u8; 32] = rng.gen(); let x3 = hash_to_field_le(&signal3); - let witness3 = RLNWitnessInput::new( + let witness3 = RLNWitnessInput::new_multi( identity_secret_new, user_message_limit, vec![Fr::from(0), Fr::from(1), Fr::from(2), Fr::from(3)], diff --git a/rln/tests/serialize.rs b/rln/tests/serialize.rs new file mode 100644 index 00000000..07bc814f --- /dev/null +++ b/rln/tests/serialize.rs @@ -0,0 +1,557 @@ +#[cfg(test)] +mod test { + use ark_ff::{BigInteger, PrimeField}; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + use ark_std::{rand::thread_rng, UniformRand}; + use num_bigint::BigUint; + use rln::{ + circuit::{Fr, PartialProof, Proof, DEFAULT_TREE_DEPTH}, + error::UtilsError, + prelude::{ + generate_partial_zk_proof, generate_zk_proof, keygen, CanonicalDeserializeBE, + CanonicalSerializeBE, RLNPartialWitnessInput, RLNPartialWitnessInputV3, + RLNProofValuesMulti, RLNProofValuesSingle, RLNProofValuesV3, RLNWitnessInput, + RLNWitnessInputMulti, RLNWitnessInputSingle, RLNWitnessInputV3, FR_BYTE_SIZE, + }, + protocol::{ENUM_TAG_MULTI, ENUM_TAG_SINGLE}, + utils::IdSecret, + }; + + #[test] + fn test_fr_be_roundtrip() { + let mut rng = thread_rng(); + for _ in 0..10 { + let fr = Fr::rand(&mut rng); + let mut buf = Vec::new(); + fr.serialize(&mut buf).unwrap(); + let deser = Fr::deserialize(buf.as_slice()).unwrap(); + assert_eq!(fr, deser); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&fr)); + } + } + + #[test] + fn test_fr_be_byte_order() { + // BE: MSB at index 0 — Fr(1) should have last byte == 1, all others 0 + let one = Fr::from(1u64); + let mut buf = Vec::new(); + one.serialize(&mut buf).unwrap(); + assert_eq!(buf.len(), FR_BYTE_SIZE); + assert_eq!( + buf[FR_BYTE_SIZE - 1], + 1, + "LSB must be at index FR_BYTE_SIZE-1" + ); + assert!(buf[..FR_BYTE_SIZE - 1].iter().all(|&b| b == 0)); + + // Fr(256) — second-to-last byte should be 1 + let v = Fr::from(256u64); + let mut buf2 = Vec::new(); + v.serialize(&mut buf2).unwrap(); + assert_eq!(buf2[FR_BYTE_SIZE - 2], 1); + assert_eq!(buf2[FR_BYTE_SIZE - 1], 0); + } + + #[test] + fn test_fr_be_non_canonical_rejected() { + let modulus = BigUint::from_bytes_le(&Fr::MODULUS.to_bytes_le()); + + let to_be = |val: &BigUint| -> Vec { + let mut bytes = val.to_bytes_be(); + let pad = FR_BYTE_SIZE.saturating_sub(bytes.len()); + if pad > 0 { + bytes.splice(0..0, std::iter::repeat_n(0, pad)); + } + bytes + }; + + // Modulus itself must be rejected + let modulus_be = to_be(&modulus); + let err = Fr::deserialize(modulus_be.as_slice()).unwrap_err(); + assert!(matches!(err, UtilsError::NonCanonicalFieldElement)); + + // Modulus + 1 must be rejected + let plus_one_be = to_be(&(&modulus + 1u32)); + assert!(matches!( + Fr::deserialize(plus_one_be.as_slice()).unwrap_err(), + UtilsError::NonCanonicalFieldElement + )); + + // All 0xFF must be rejected + let max_bytes = vec![0xFF; FR_BYTE_SIZE]; + assert!(matches!( + Fr::deserialize(max_bytes.as_slice()).unwrap_err(), + UtilsError::NonCanonicalFieldElement + )); + + // Modulus - 1 must succeed and round-trip + let minus_one_be = to_be(&(&modulus - 1u32)); + let fr_max = Fr::deserialize(minus_one_be.as_slice()).unwrap(); + let mut roundtrip = Vec::new(); + fr_max.serialize(&mut roundtrip).unwrap(); + assert_eq!(roundtrip, minus_one_be); + } + + #[test] + fn test_fr_be_insufficient_data_rejected() { + let short = vec![0u8; FR_BYTE_SIZE - 1]; + assert!(Fr::deserialize(short.as_slice()).is_err()); + assert!(Fr::deserialize([].as_slice()).is_err()); + } + + #[test] + fn test_vec_fr_be_roundtrip() { + let mut rng = thread_rng(); + for size in [0, 1, 5, 10] { + let v: Vec = (0..size).map(|_| Fr::rand(&mut rng)).collect(); + let mut buf = Vec::new(); + v.serialize(&mut buf).unwrap(); + let deser = Vec::::deserialize(buf.as_slice()).unwrap(); + assert_eq!(v, deser); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&v)); + } + } + + #[test] + fn test_vec_fr_be_non_canonical_element_rejected() { + // Craft a length-1 vec with modulus as the element — must be rejected + let modulus = BigUint::from_bytes_le(&Fr::MODULUS.to_bytes_le()); + let mut bytes = modulus.to_bytes_be(); + let pad = FR_BYTE_SIZE.saturating_sub(bytes.len()); + if pad > 0 { + bytes.splice(0..0, std::iter::repeat_n(0, pad)); + } + // Prepend length=1 as 8-byte BE + let mut buf = Vec::new(); + buf.extend_from_slice(&1u64.to_be_bytes()); + buf.extend_from_slice(&bytes); + assert!(Vec::::deserialize(buf.as_slice()).is_err()); + } + + #[test] + fn test_vec_fr_be_insufficient_data_rejected() { + // Length prefix says 2 but only 1 element present + let mut buf = Vec::new(); + buf.extend_from_slice(&2u64.to_be_bytes()); + buf.extend_from_slice(&[0u8; FR_BYTE_SIZE]); // only one element + assert!(Vec::::deserialize(buf.as_slice()).is_err()); + } + + #[test] + fn test_vec_u8_be_roundtrip() { + let test_cases: Vec> = vec![ + vec![], + vec![0], + vec![255], + vec![1, 2, 3, 4, 5], + vec![0, 255, 128, 64, 32, 16, 8, 4, 2, 1], + (0..100).collect(), + ]; + for v in test_cases { + let mut buf = Vec::new(); + v.serialize(&mut buf).unwrap(); + let deser = Vec::::deserialize(buf.as_slice()).unwrap(); + assert_eq!(v, deser); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&v)); + } + } + + #[test] + fn test_vec_u8_be_insufficient_data_rejected() { + // Length prefix says 5 but no data follows + let mut buf = Vec::new(); + buf.extend_from_slice(&5u64.to_be_bytes()); + assert!(Vec::::deserialize(buf.as_slice()).is_err()); + } + + #[test] + fn test_vec_bool_be_roundtrip() { + let test_cases = vec![ + vec![], + vec![true], + vec![false], + vec![true, false, true, false, true], + vec![true; 50], + (0..50).map(|i| i % 2 == 0).collect::>(), + ]; + for v in test_cases { + let mut buf = Vec::new(); + v.serialize(&mut buf).unwrap(); + let deser = Vec::::deserialize(buf.as_slice()).unwrap(); + assert_eq!(v, deser); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&v)); + } + } + + #[test] + fn test_vec_bool_be_insufficient_data_rejected() { + // Length prefix says 3 but no data follows + let mut buf = Vec::new(); + buf.extend_from_slice(&3u64.to_be_bytes()); + assert!(Vec::::deserialize(buf.as_slice()).is_err()); + } + + #[test] + fn test_vec_bool_be_non_canonical_byte_rejected() { + // Any byte other than 0x00 or 0x01 must be rejected + for bad in [0x02u8, 0x05, 0xff] { + let mut buf = Vec::new(); + buf.extend_from_slice(&1u64.to_be_bytes()); // length = 1 + buf.push(bad); + assert!( + Vec::::deserialize(buf.as_slice()).is_err(), + "byte {bad:#04x} should have been rejected" + ); + } + } + + #[test] + fn test_id_secret_be_roundtrip() { + let mut rng = thread_rng(); + for _ in 0..10 { + let secret = IdSecret::rand(&mut rng); + let mut buf = Vec::new(); + secret.serialize(&mut buf).unwrap(); + let deser = IdSecret::deserialize(buf.as_slice()).unwrap(); + assert_eq!(secret, deser); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&secret)); + } + } + + #[test] + fn test_id_secret_be_known_value() { + // IdSecret(42) BE should match Fr(42) BE — same field element + let secret = IdSecret::from(&mut Fr::from(42u64)); + let mut secret_buf = Vec::new(); + secret.serialize(&mut secret_buf).unwrap(); + + let fr = Fr::from(42u64); + let mut fr_buf = Vec::new(); + fr.serialize(&mut fr_buf).unwrap(); + + assert_eq!(secret_buf, fr_buf); + } + + #[test] + fn test_id_secret_be_non_canonical_rejected() { + let modulus = BigUint::from_bytes_le(&Fr::MODULUS.to_bytes_le()); + + let to_be = |val: &BigUint| -> Vec { + let mut bytes = val.to_bytes_be(); + let pad = FR_BYTE_SIZE.saturating_sub(bytes.len()); + if pad > 0 { + bytes.splice(0..0, std::iter::repeat_n(0, pad)); + } + bytes + }; + + // Modulus must be rejected + let modulus_be = to_be(&modulus); + assert!(matches!( + IdSecret::deserialize(modulus_be.as_slice()).unwrap_err(), + UtilsError::NonCanonicalFieldElement + )); + + // All 0xFF must be rejected + let max_bytes = vec![0xFF; FR_BYTE_SIZE]; + assert!(matches!( + IdSecret::deserialize(max_bytes.as_slice()).unwrap_err(), + UtilsError::NonCanonicalFieldElement + )); + + // Modulus - 1 must succeed + let minus_one_be = to_be(&(&modulus - 1u32)); + assert!(IdSecret::deserialize(minus_one_be.as_slice()).is_ok()); + } + + #[test] + fn test_id_secret_be_insufficient_data_rejected() { + let short = vec![0u8; FR_BYTE_SIZE - 1]; + assert!(IdSecret::deserialize(short.as_slice()).is_err()); + assert!(IdSecret::deserialize([].as_slice()).is_err()); + } + + fn make_witness_input_single() -> RLNWitnessInputV3 { + RLNWitnessInputV3::Single(RLNWitnessInputSingle::new( + IdSecret::from(&mut Fr::from(42u64)), + Fr::from(10u64), + Fr::from(3u64), + vec![Fr::from(1u64), Fr::from(2u64)], + vec![0u8, 1u8], + Fr::from(5u64), + Fr::from(7u64), + )) + } + + fn make_witness_input_multi() -> RLNWitnessInputV3 { + RLNWitnessInputV3::Multi(RLNWitnessInputMulti::new( + IdSecret::from(&mut Fr::from(99u64)), + Fr::from(10u64), + vec![Fr::from(1u64), Fr::from(2u64)], + vec![0u8, 1u8], + Fr::from(5u64), + Fr::from(7u64), + vec![Fr::from(0u64), Fr::from(1u64)], + vec![true, false], + )) + } + + fn make_partial_witness() -> RLNPartialWitnessInputV3 { + RLNPartialWitnessInputV3::new( + IdSecret::from(&mut Fr::from(42u64)), + Fr::from(10u64), + vec![Fr::from(1u64), Fr::from(2u64)], + vec![0u8, 1u8], + ) + } + + fn make_proof_values_single() -> RLNProofValuesV3 { + RLNProofValuesV3::Single(RLNProofValuesSingle { + root: Fr::from(1u64), + x: Fr::from(2u64), + external_nullifier: Fr::from(3u64), + y: Fr::from(4u64), + nullifier: Fr::from(5u64), + }) + } + + fn make_proof_values_multi() -> RLNProofValuesV3 { + RLNProofValuesV3::Multi(RLNProofValuesMulti { + root: Fr::from(10u64), + x: Fr::from(20u64), + external_nullifier: Fr::from(30u64), + ys: vec![Fr::from(40u64), Fr::from(50u64)], + nullifiers: vec![Fr::from(60u64), Fr::from(70u64)], + selector_used: vec![true, false], + }) + } + + #[cfg(not(target_arch = "wasm32"))] + fn make_proof() -> Proof { + let (identity_secret, _) = keygen(); + let path_elements = vec![Fr::from(0); DEFAULT_TREE_DEPTH]; + let identity_path_index = vec![0; DEFAULT_TREE_DEPTH]; + let witness = RLNWitnessInput::new_single( + identity_secret, + Fr::from(100), + Fr::from(1), + path_elements, + identity_path_index, + Fr::from(1), + Fr::from(100), + ) + .unwrap(); + generate_zk_proof( + rln::circuit::zkey_single_v1(), + &witness, + rln::circuit::graph_single_v1(), + ) + .unwrap() + } + + #[cfg(not(target_arch = "wasm32"))] + fn make_partial_proof() -> PartialProof { + let (identity_secret, _) = keygen(); + let path_elements = vec![Fr::from(0); DEFAULT_TREE_DEPTH]; + let identity_path_index = vec![0; DEFAULT_TREE_DEPTH]; + let partial_witness = RLNPartialWitnessInput::new( + identity_secret, + Fr::from(100), + path_elements, + identity_path_index, + ) + .unwrap(); + generate_partial_zk_proof( + rln::circuit::zkey_single_v1(), + &partial_witness, + rln::circuit::graph_single_v1(), + ) + .unwrap() + } + + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn test_proof_le_compressed_roundtrip() { + let proof = make_proof(); + let mut buf = Vec::new(); + proof.serialize_compressed(&mut buf).unwrap(); + let deser = Proof::deserialize_compressed(buf.as_slice()).unwrap(); + assert_eq!(proof, deser); + assert_eq!(proof.compressed_size(), buf.len()); + } + + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn test_partial_proof_le_compressed_roundtrip() { + let partial = make_partial_proof(); + let mut buf = Vec::new(); + partial.serialize_compressed(&mut buf).unwrap(); + let deser = PartialProof::deserialize_compressed(buf.as_slice()).unwrap(); + assert_eq!(partial, deser); + assert_eq!(partial.compressed_size(), buf.len()); + } + + #[test] + fn test_witness_v3_single_le_compressed_roundtrip() { + let w = make_witness_input_single(); + let mut buf = Vec::new(); + w.serialize_compressed(&mut buf).unwrap(); + let deser = RLNWitnessInputV3::deserialize_compressed(buf.as_slice()).unwrap(); + assert_eq!(w, deser); + assert_eq!(w.compressed_size(), buf.len()); + assert_eq!(buf[0], ENUM_TAG_SINGLE); + } + + #[test] + fn test_witness_v3_multi_le_compressed_roundtrip() { + let w = make_witness_input_multi(); + let mut buf = Vec::new(); + w.serialize_compressed(&mut buf).unwrap(); + let deser = RLNWitnessInputV3::deserialize_compressed(buf.as_slice()).unwrap(); + assert_eq!(w, deser); + assert_eq!(w.compressed_size(), buf.len()); + assert_eq!(buf[0], ENUM_TAG_MULTI); + } + + #[test] + fn test_witness_v3_le_uncompressed_roundtrip() { + for w in [make_witness_input_single(), make_witness_input_multi()] { + let mut buf = Vec::new(); + w.serialize_uncompressed(&mut buf).unwrap(); + let deser = RLNWitnessInputV3::deserialize_uncompressed(buf.as_slice()).unwrap(); + assert_eq!(w, deser); + assert_eq!(w.uncompressed_size(), buf.len()); + } + } + + #[test] + fn test_witness_v3_le_invalid_tag_rejected() { + let mut bad = vec![99u8]; // unknown tag + bad.extend_from_slice(&[0u8; 32]); + assert!(RLNWitnessInputV3::deserialize_compressed(bad.as_slice()).is_err()); + } + + #[test] + fn test_witness_v3_single_be_roundtrip() { + let w = make_witness_input_single(); + let mut buf = Vec::new(); + w.serialize(&mut buf).unwrap(); + assert_eq!(buf[0], ENUM_TAG_SINGLE); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&w)); + let deser = RLNWitnessInputV3::deserialize(buf.as_slice()).unwrap(); + assert_eq!(w, deser); + } + + #[test] + fn test_witness_v3_multi_be_roundtrip() { + let w = make_witness_input_multi(); + let mut buf = Vec::new(); + w.serialize(&mut buf).unwrap(); + assert_eq!(buf[0], ENUM_TAG_MULTI); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&w)); + let deser = RLNWitnessInputV3::deserialize(buf.as_slice()).unwrap(); + assert_eq!(w, deser); + } + + #[test] + fn test_witness_v3_be_invalid_tag_rejected() { + let w = make_witness_input_single(); + let mut buf = Vec::new(); + w.serialize(&mut buf).unwrap(); + buf[0] = 99; // unknown tag + assert!(RLNWitnessInputV3::deserialize(buf.as_slice()).is_err()); + } + + #[test] + fn test_partial_witness_v3_le_compressed_roundtrip() { + let pw = make_partial_witness(); + let mut buf = Vec::new(); + pw.serialize_compressed(&mut buf).unwrap(); + let deser = RLNPartialWitnessInputV3::deserialize_compressed(buf.as_slice()).unwrap(); + assert_eq!(pw, deser); + assert_eq!(pw.compressed_size(), buf.len()); + } + + #[test] + fn test_partial_witness_v3_be_roundtrip() { + let pw = make_partial_witness(); + let mut buf = Vec::new(); + pw.serialize(&mut buf).unwrap(); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&pw)); + let deser = RLNPartialWitnessInputV3::deserialize(buf.as_slice()).unwrap(); + assert_eq!(pw, deser); + } + + #[test] + fn test_proof_values_v3_single_le_compressed_roundtrip() { + let pv = make_proof_values_single(); + let mut buf = Vec::new(); + pv.serialize_compressed(&mut buf).unwrap(); + let deser = RLNProofValuesV3::deserialize_compressed(buf.as_slice()).unwrap(); + assert_eq!(pv, deser); + assert_eq!(pv.compressed_size(), buf.len()); + assert_eq!(buf[0], ENUM_TAG_SINGLE); + } + + #[test] + fn test_proof_values_v3_multi_le_compressed_roundtrip() { + let pv = make_proof_values_multi(); + let mut buf = Vec::new(); + pv.serialize_compressed(&mut buf).unwrap(); + let deser = RLNProofValuesV3::deserialize_compressed(buf.as_slice()).unwrap(); + assert_eq!(pv, deser); + assert_eq!(pv.compressed_size(), buf.len()); + assert_eq!(buf[0], ENUM_TAG_MULTI); + } + + #[test] + fn test_proof_values_v3_le_uncompressed_roundtrip() { + for pv in [make_proof_values_single(), make_proof_values_multi()] { + let mut buf = Vec::new(); + pv.serialize_uncompressed(&mut buf).unwrap(); + let deser = RLNProofValuesV3::deserialize_uncompressed(buf.as_slice()).unwrap(); + assert_eq!(pv, deser); + assert_eq!(pv.uncompressed_size(), buf.len()); + } + } + + #[test] + fn test_proof_values_v3_single_le_invalid_tag_rejected() { + let pv = make_proof_values_single(); + let mut buf = Vec::new(); + pv.serialize_compressed(&mut buf).unwrap(); + buf[0] = 99; // unknown tag + assert!(RLNProofValuesV3::deserialize_compressed(buf.as_slice()).is_err()); + } + + #[test] + fn test_proof_values_v3_single_be_roundtrip() { + let pv = make_proof_values_single(); + let mut buf = Vec::new(); + pv.serialize(&mut buf).unwrap(); + assert_eq!(buf[0], ENUM_TAG_SINGLE); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&pv)); + let deser = RLNProofValuesV3::deserialize(buf.as_slice()).unwrap(); + assert_eq!(pv, deser); + } + + #[test] + fn test_proof_values_v3_multi_be_roundtrip() { + let pv = make_proof_values_multi(); + let mut buf = Vec::new(); + pv.serialize(&mut buf).unwrap(); + assert_eq!(buf[0], ENUM_TAG_MULTI); + assert_eq!(buf.len(), CanonicalSerializeBE::serialized_size(&pv)); + let deser = RLNProofValuesV3::deserialize(buf.as_slice()).unwrap(); + assert_eq!(pv, deser); + } + + #[test] + fn test_proof_values_v3_multi_be_invalid_tag_rejected() { + let pv = make_proof_values_multi(); + let mut buf = Vec::new(); + pv.serialize(&mut buf).unwrap(); + buf[0] = 99; // unknown tag + assert!(RLNProofValuesV3::deserialize(buf.as_slice()).is_err()); + } +} diff --git a/utils/src/merkle_tree/override_range_validation.rs b/utils/src/merkle_tree/override_range_validation.rs index de4c9383..a0fcecbd 100644 --- a/utils/src/merkle_tree/override_range_validation.rs +++ b/utils/src/merkle_tree/override_range_validation.rs @@ -65,7 +65,7 @@ pub fn validate_override_range_inputs( } #[cfg(test)] -mod tests { +mod test { use super::{validate_override_range_inputs, EmptyIndicesPolicy}; use crate::merkle_tree::ZerokitMerkleTreeError;