15 unstable releases (3 breaking)
0.5.1 | Jun 20, 2024 |
---|---|
0.4.3 | Apr 8, 2024 |
0.4.2 | Mar 7, 2024 |
0.4.1 | Oct 4, 2023 |
0.3.0 | Jul 31, 2023 |
#12 in #zk-proofs
4.5MB
3.5K
SLoC
Zerokit RLN Module
This module provides APIs to manage, compute and verify RLN zkSNARK proofs and RLN primitives.
Pre-requisites
Install dependencies and clone repo
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
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
# 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 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 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
[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 as readers/writers for interfacing with RLN public APIs.
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
// We generate an identity pair
let mut buffer = Cursor::new(Vec::<u8>::new());
rln.key_gen(&mut buffer).unwrap();
// We deserialize the keygen output to obtain
// the identity_secret and id_commitment
let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner());
Add Rate commitment to the RLN Merkle tree
// 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.
// 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.
// 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 ]
.
// 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.
// We generate a RLN proof for proof_input
let mut in_buffer = Cursor::new(proof_input);
let mut out_buffer = Cursor::new(Vec::<u8>::new());
rln.generate_rln_proof(&mut in_buffer, &mut out_buffer)
.unwrap();
// 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
.
// 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:
// 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
cargo doc --no-deps
and look at unit tests to have an hint on how to interface and use them.
Dependencies
~27–39MB
~549K SLoC