# Zerokit RLN Module This module provides APIs to manage, compute and verify [RLN](https://rfc.vac.dev/spec/32/) zkSNARK proofs and RLN primitives. ## Pre-requisites ### Install dependencies and clone repo ```sh make installdeps git clone https://github.com/vacp2p/zerokit.git cd zerokit/rln ``` ### Build and Test To build and test, run the following commands within the module folder ```bash cargo make build cargo make test ``` ### Compile ZK circuits The `rln` (https://github.com/rate-limiting-nullifier/circom-rln) repository, which contains the RLN circuit implementation is a submodule of zerokit RLN. To compile the RLN circuit ```sh # Update submodules git submodule update --init --recursive # Install rln dependencies cd vendor/rln/ && npm install # Build circuits ./scripts/build-circuits.sh rln # Copy over assets cp build/zkeyFiles/rln-final.zkey ../../resources/tree_height_15 cp build/zkeyFiles/rln.wasm ../../resources/tree_height_15 ``` Note that the above code snippet will compile a RLN circuit with a Merkle tree of height equal `15` based on the default value set in `vendor/rln/circuit/rln.circom`. In order to compile a RLN circuit with Merkle tree height `N`, it suffices to change `vendor/rln/circuit/rln.circom` to ``` pragma circom 2.0.0; include "./rln-base.circom"; component main {public [x, epoch, rln_identifier ]} = RLN(N); ``` However, if `N` is too big, this might require a bigger Powers of Tau ceremony than the one hardcoded in `./scripts/build-circuits.sh`, which is `2^14`. In such case we refer to the official [Circom documentation](https://docs.circom.io/getting-started/proving-circuits/#powers-of-tau) for instructions on how to run an appropriate Powers of Tau ceremony and Phase 2 in order to compile the desired circuit. Currently, the `rln` module comes with 2 [pre-compiled](https://github.com/vacp2p/zerokit/tree/master/rln/resources) RLN circuits having Merkle tree of height `20` and `32`, respectively. ## Getting started ### Add RLN as dependency We start by adding zerokit RLN to our `Cargo.toml` ```toml [dependencies] rln = { git = "https://github.com/vacp2p/zerokit" } ``` ### Create a RLN object First, we need to create a RLN object for a chosen input Merkle tree size. Note that we need to pass to RLN object constructor the path where the circuit (`rln.wasm`, built for the input tree size), the corresponding proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) and verification key (`verification_key.arkvkey`, optional) are found. In the following we will use [cursors](https://doc.rust-lang.org/std/io/struct.Cursor.html) as readers/writers for interfacing with RLN public APIs. ```rust use rln::protocol::*; use rln::public::*; use std::io::Cursor; // We set the RLN parameters: // - the tree height; // - the tree config, if it is not defined, the default value will be set let tree_height = 20; let input = Cursor::new(json!({}).to_string()); // We create a new RLN instance let mut rln = RLN::new(tree_height, input); ``` ### Generate an identity keypair We generate an identity keypair ```rust // We generate an identity pair let mut buffer = Cursor::new(Vec::::new()); rln.key_gen(&mut buffer).unwrap(); // We deserialize the keygen output to obtain // the identity_secret and id_commitment let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner()); ``` ### Add Rate commitment to the RLN Merkle tree ```rust // We define the tree index where id_commitment will be added let id_index = 10; let user_message_limit = 10; // We serialize id_commitment and pass it to set_leaf let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]); let mut buffer = Cursor::new(serialize_field_element(rate_commitment)); rln.set_leaf(id_index, &mut buffer).unwrap(); ``` Note that when tree leaves are not explicitly set by the user (in this example, all those with index less and greater than `10`), their values is set to an hardcoded default (all-`0` bytes in current implementation). ### Set external nullifier The `external nullifier` includes two parameters. The first one is `epoch` and it's used to identify messages received in a certain time frame. It usually corresponds to the current UNIX time but can also be set to a random value or generated by a seed, provided that it corresponds to a field element. The second one is `rln_identifier` and it's used to prevent a RLN ZK proof generated for one application to be re-used in another one. ```rust // We generate epoch from a date seed and we ensure is // mapped to a field element by hashing-to-field its content let epoch = hash_to_field(b"Today at noon, this year"); // We generate rln_identifier from a date seed and we ensure is // mapped to a field element by hashing-to-field its content let rln_identifier = hash_to_field(b"test-rln-identifier"); let external_nullifier = poseidon_hash(&[epoch, rln_identifier]); ``` ### Set signal The signal is the message for which we are computing a RLN proof. ```rust // We set our signal let signal = b"RLN is awesome"; ``` ### Generate a RLN proof We prepare the input to the proof generation routine. Input buffer is serialized as `[ identity_key | id_index | external_nullifier | user_message_limit | message_id | signal_len | signal ]`. ```rust // We prepare input to the proof generation routine let proof_input = prepare_prove_input(identity_secret_hash, id_index, external_nullifier, signal); ``` We are now ready to generate a RLN ZK proof along with the _public outputs_ of the ZK circuit evaluation. ```rust // We generate a RLN proof for proof_input let mut in_buffer = Cursor::new(proof_input); let mut out_buffer = Cursor::new(Vec::::new()); rln.generate_rln_proof(&mut in_buffer, &mut out_buffer) .unwrap(); // We get the public outputs returned by the circuit evaluation let proof_data = out_buffer.into_inner(); ``` The byte vector `proof_data` is serialized as `[ zk-proof | tree_root | external_nullifier | share_x | share_y | nullifier ]`. ### Verify a RLN proof We prepare the input to the proof verification routine. Input buffer is serialized as `[proof_data | signal_len | signal ]`, where `proof_data` is (computed as) the output obtained by `generate_rln_proof`. ```rust // We prepare input to the proof verification routine let verify_data = prepare_verify_input(proof_data, signal); // We verify the zk-proof against the provided proof values let mut in_buffer = Cursor::new(verify_data); let verified = rln.verify(&mut in_buffer).unwrap(); ``` We check if the proof verification was successful: ```rust // We ensure the proof is valid assert!(verified); ``` ## Get involved! Zerokit RLN public and FFI APIs allow interaction with many more features than what briefly showcased above. We invite you to check our API documentation by running ```rust cargo doc --no-deps ``` and look at unit tests to have an hint on how to interface and use them.