mirror of
https://github.com/vacp2p/zerokit.git
synced 2026-01-08 21:28:11 -05:00
RLN FFI Nim example
This example demonstrates how to use the RLN C FFI from Nim in both stateless and non-stateless modes. It covers:
- Creating an RLN handle (stateless or with Merkle tree backend)
- Generating identity keys and commitments
- Building a witness (mock Merkle path in stateless mode, real Merkle proof in non-stateless mode)
- Generating and verifying a proof
- Serializing/deserializing FFI objects (CFr, Vec<CFr>, RLNWitnessInput, RLNProof, RLNProofValues)
- Simulating a double-signaling attack and recovering the identity secret
Build the RLN library
From the repository root:
# Stateless build (no tree APIs)
cargo build -p rln --release --no-default-features --features stateless
# Non-stateless build (with tree APIs)
cargo build -p rln --release
This produces the shared library in target/release:
- macOS:
librln.dylib - Linux:
librln.so - Windows:
rln.dll
Build the Nim example (two modes)
From this directory:
# Stateless mode (no tree APIs, uses mock Merkle path)
nim c -d:release -d:ffiStateless main.nim
# Non-stateless mode (uses exported tree APIs to insert leaf and fetch proof)
nim c -d:release main.nim
Notes:
- The example links dynamically. If your OS linker cannot find the library at runtime, 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.
Run the example
Ensure the dynamic loader can find the RLN library, then run the binary.
macOS:
DYLD_LIBRARY_PATH=../../target/release ./main
Linux:
LD_LIBRARY_PATH=../../target/release ./main
Windows (PowerShell):
$env:PATH = "$PWD\..\..\target\release;$env:PATH"
./main.exe
You should see detailed output showing each step, for example:
Creating RLN instance
RLN instance created successfully
Generating identity keys
Identity generated
- identity_secret = ...
- id_commitment = ...
Creating message limit
- user_message_limit = ...
Computing rate commitment
- rate_commitment = ...
CFr serialization: CFr <-> bytes
- serialized rate_commitment = ...
- deserialized rate_commitment = ...
Vec<CFr> serialization: Vec<CFr> <-> bytes
- serialized keys = ...
- deserialized keys = ...
... (Merkle path, hashing, witness, proof, verification, and slashing steps) ...
Proof verified successfully
Slashing successful: Identity is recovered!
What the example does
Stateless mode
- Creates an RLN handle via the stateless constructor.
- Generates identity keys, sets a
user_message_limitandmessage_id. - Hashes a signal, epoch, and RLN identifier to field elements.
- Computes
rateCommitment = Poseidon(id_commitment, user_message_limit). - Builds a mock Merkle path for an empty depth-20 tree at index 0 (no exported tree APIs):
- Path siblings: level 0 sibling is
0, then each level uses precomputed default hashesH(0,0),H(H(0,0),H(0,0)), ... - Path indices: all zeros (left at every level)
- Root: folds the path upwards with
rateCommitmentat index 0
- Path siblings: level 0 sibling is
- Builds the witness, generates the proof, and verifies it with
ffi_verify_with_roots, passing a one-element roots vector containing the computed root. - Simulates a double-signaling attack and recovers the identity secret from two proofs.
Non-stateless mode
- Creates an RLN handle with a Merkle tree backend and configuration.
- Generates identity keys and computes
rateCommitment = Poseidon(id_commitment, user_message_limit). - Inserts the leaf with
ffi_set_next_leafand fetches a real Merkle path for index 0 viaffi_get_merkle_proof. - Builds the witness from the exported proof, generates the proof, and verifies with
ffi_verify_rln_proofusing the current tree root. - Simulates a double-signaling attack and recovers the identity secret from two proofs.