6.1 KiB
Zerokit RLN Module
This module provides APIs to manage, compute and verify RLN zkSNARK proofs and RLN primitives.
Pre-requisites
Install
git clone https://github.com/vacp2p/zerokit.git
cd zerokit/rln
Implemented tests can be executed by running within the module folder
cargo test --release
Compile ZK circuits
The rln
(https://github.com/privacy-scaling-explorations/rln) repository, which contains the RLN circuit implementation is a submodule of zerokit RLN.
To compile the RLN circuit
# 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 three pre-compiled RLN circuits having Merkle tree of height 15
, 19
and 20
, 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
) and verification key (verification_key.json
, 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 circuit resource folder (requires a trailing "/").
let tree_height = 20;
let resources = Cursor::new("../zerokit/rln/resources/tree_height_20/");
// We create a new RLN instance
let mut rln = RLN::new(tree_height, resources);
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 identiy_secret and id_commitment
let (identity_secret_hash, id_commitment) = deserialize_identity_pair(buffer.into_inner());
Add ID commitment to the RLN Merkle tree
// We define the tree index where id_commitment will be added
let id_index = 10;
// We serialize id_commitment and pass it to set_leaf
let mut buffer = Cursor::new(serialize_field_element(id_commitment));
rln.set_leaf(id_index, &mut buffer).unwrap();
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 epoch
The epoch, sometimes referred to as external nullifier, is used to identify messages received in a certain time frame. It usually corresponds to the current UNIX time but can also be set to a random value or generated by a seed, provided that it corresponds to a field element.
// We generate epoch from a date seed and we ensure is
// mapped to a field element by hashing-to-field its content
let epoch = hash_to_field(b"Today at noon, this year");
Set signal
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 | epoch | signal_len | signal ]
.
// We prepare input to the proof generation routine
let proof_input = prepare_prove_input(identity_secret_hash, id_index, epoch, 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 | epoch | share_x | share_y | nullifier | rln_identifier ]
.
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.