Zerokit RLN integration in nwaku: alternative approach with inline compilation flag (#1060)

* refactor(rln): add inline compilation flags for RLN zerokit

* update submodule

update submodule

* fix(rln): fix wrong merge conflict resolution

* fix(rln): align hardcoded values in light of vacp2p/zerokit/issues/28
This commit is contained in:
G 2022-08-05 22:58:19 +02:00 committed by GitHub
parent 115982f5c3
commit 8a14b5b243
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 855 additions and 352 deletions

4
.gitmodules vendored
View File

@ -148,3 +148,7 @@
url = https://github.com/status-im/nim-presto.git
ignore = untracked
branch = master
[submodule "vendor/zerokit"]
path = vendor/zerokit
url = https://github.com/vacp2p/zerokit.git
branch = master

View File

@ -75,6 +75,14 @@ else ifeq ($(CI), true)
NIM_PARAMS := $(NIM_PARAMS) -d:rln
endif
# control rln code compilation
ifeq ($(RLNZEROKIT), true)
NIM_PARAMS := $(NIM_PARAMS) -d:rlnzerokit
#To avoid redefinition conflicts, we disable rln zerokit default compilation in CI
#else ifeq ($(CI), true)
#NIM_PARAMS := $(NIM_PARAMS) -d:rlnzerokit
endif
# detecting the os
ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10...
detected_OS := Windows
@ -103,7 +111,7 @@ NIM_PARAMS := $(NIM_PARAMS) -d:discv5_protocol_id:d5waku
GIT_VERSION := "$(shell git describe --abbrev=6 --always --tags)"
NIM_PARAMS := $(NIM_PARAMS) -d:git_version:\"$(GIT_VERSION)\"
deps: | deps-common nat-libs waku.nims rlnlib
deps: | deps-common nat-libs waku.nims rlnlib rlnzerokitlib
ifneq ($(USE_LIBBACKTRACE), 0)
deps: | libbacktrace
endif
@ -173,6 +181,15 @@ else ifeq ($(CI), true)
cargo build --manifest-path vendor/rln/Cargo.toml
endif
rlnzerokitlib:
ifeq ($(RLNZEROKIT), true)
cargo build --manifest-path vendor/zerokit/rln/Cargo.toml --release
#To avoid redefinition conflicts, we disable rln zerokit default compilation in CI
#else ifeq ($(CI), true)
# cargo build --manifest-path vendor/zerokit/rln/Cargo.toml --release
endif
test2: | build deps installganache
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim test2 $(NIM_PARAMS) waku.nims
@ -228,11 +245,16 @@ ifneq ($(USE_LIBBACKTRACE), 0)
+ $(MAKE) -C vendor/nim-libbacktrace clean $(HANDLE_OUTPUT)
endif
cargo clean --manifest-path vendor/rln/Cargo.toml
cargo clean --manifest-path vendor/zerokit/rln/Cargo.toml
# clean the rln build (forces recompile of old crates on next build)
cleanrln:
cargo clean --manifest-path vendor/rln/Cargo.toml
# clean the rln build (forces recompile of old crates on next build)
cleanrlnzerokit:
cargo clean --manifest-path vendor/zerokit/rln/Cargo.toml
endif # "variables.mk" was not included
libwaku.so: | build deps

View File

@ -31,7 +31,7 @@ import
./v2/test_peer_exchange,
./v2/test_waku_noise
when defined(rln):
when defined(rln) or defined(rlnzerokit):
import ./v2/test_waku_rln_relay
when defined(onchain_rln):
import ./v2/test_waku_rln_relay_onchain

View File

@ -64,6 +64,8 @@ procSuite "Waku rln relay":
await node.stop()
suite "Waku rln relay":
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
test "key_gen Nim Wrappers":
var
merkleDepth: csize_t = 32
@ -104,6 +106,31 @@ suite "Waku rln relay":
generatedKeys.len == 64
debug "generated keys: ", generatedKeys
when defined(rlnzerokit):
test "key_gen Nim Wrappers":
var
merkleDepth: csize_t = 20
var rlnInstance = createRLNInstance()
check:
rlnInstance.isOk == true
# keysBufferPtr will hold the generated key pairs i.e., secret and public keys
var
keysBuffer: Buffer
keysBufferPtr = addr(keysBuffer)
done = key_gen(rlnInstance.value(), keysBufferPtr)
check:
# check whether the keys are generated successfully
done == true
if done:
var generatedKeys = cast[ptr array[64, byte]](keysBufferPtr.`ptr`)[]
check:
# the public and secret keys together are 64 bytes
generatedKeys.len == 64
debug "generated keys: ", generatedKeys
test "membership Key Gen":
# create an RLN instance
var rlnInstance = createRLNInstance()
@ -371,9 +398,15 @@ suite "Waku rln relay":
check:
hashSuccess
let outputArr = cast[ptr array[32, byte]](outputBuffer.`ptr`)[]
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
check:
"efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" ==
outputArr.toHex()
when defined(rlnzerokit):
check:
"4c6ea217404bd5f10e243bac29dc4f1ec36bf4a41caba7b4c8075c54abb3321e" ==
outputArr.toHex()
var
hashOutput = cast[ptr array[32, byte]] (outputBuffer.`ptr`)[]
@ -392,9 +425,15 @@ suite "Waku rln relay":
let msg = "Hello".toBytes()
let hash = rln.hash(msg)
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
check:
"efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" ==
hash.toHex()
when defined(rlnzerokit):
check:
"4c6ea217404bd5f10e243bac29dc4f1ec36bf4a41caba7b4c8075c54abb3321e" ==
hash.toHex()
test "create a list of membership keys and construct a Merkle tree based on the list":
let
@ -428,6 +467,7 @@ suite "Waku rln relay":
# compare the calculated root against the correct root
root == STATIC_GROUP_MERKLE_ROOT
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
test "RateLimitProof Protobuf encode/init test":
var
proof: ZKSNARK
@ -458,6 +498,41 @@ suite "Waku rln relay":
decodednsp.isErr == false
decodednsp.value == rateLimitProof
when defined(rlnzerokit):
test "RateLimitProof Protobuf encode/init test":
var
proof: ZKSNARK
merkleRoot: MerkleNode
epoch: Epoch
shareX: MerkleNode
shareY: MerkleNode
nullifier: Nullifier
rlnIdentifier: RlnIdentifier
# populate fields with dummy values
for x in proof.mitems: x = 1
for x in merkleRoot.mitems: x = 2
for x in epoch.mitems: x = 3
for x in shareX.mitems: x = 4
for x in shareY.mitems: x = 5
for x in nullifier.mitems: x = 6
for x in rlnIdentifier.mitems: x = 7
let
rateLimitProof = RateLimitProof(proof: proof,
merkleRoot: merkleRoot,
epoch: epoch,
shareX: shareX,
shareY: shareY,
nullifier: nullifier,
rlnIdentifier: rlnIdentifier)
protobuf = rateLimitProof.encode()
decodednsp = RateLimitProof.init(protobuf.buffer)
check:
decodednsp.isErr == false
decodednsp.value == rateLimitProof
test "test proofVerify and proofGen for a valid proof":
var rlnInstance = createRLNInstance()
check:
@ -600,6 +675,7 @@ suite "Waku rln relay":
for index, x in shareX3.mpairs: shareX3[index] = 3
let shareY3 = shareX3
## TODO: when zerokit rln is integrated, RateLimitProof should be initialized passing a rlnIdentifier too (now implicitely set to 0)
let
wm1 = WakuMessage(proof: RateLimitProof(epoch: epoch,
nullifier: nullifier1, shareX: shareX1, shareY: shareY1))
@ -679,6 +755,8 @@ suite "Waku rln relay":
proofAdded3 = wakuRlnRelay.appendRLNProof(wm3, time+EPOCH_UNIT_SECONDS)
wm4 = WakuMessage(payload: "Invalid message".toBytes())
echo wm1.proof
# checks proofs are added
check:
proofAdded1
@ -702,6 +780,7 @@ suite "Waku rln relay":
msgValidate2 == MessageValidationResult.Spam
msgValidate3 == MessageValidationResult.Valid
msgValidate4 == MessageValidationResult.Invalid
test "toIDCommitment and toUInt256":
# create an instance of rln
var rlnInstance = createRLNInstance()

1
vendor/zerokit vendored Submodule

@ -0,0 +1 @@
Subproject commit 3378aed85716144231e036c076fe3f14ed9538de

View File

@ -6,7 +6,11 @@ import
os,
waku_rln_relay_types
const libPath = "vendor/rln/target/debug/"
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
const libPath = "vendor/rln/target/debug/"
when defined(rlnzerokit):
const libPath = "vendor/zerokit/target/release/"
when defined(Windows):
const libName* = libPath / "rln.dll"
elif defined(Linux):
@ -24,65 +28,174 @@ type Buffer* = object
`ptr`*: ptr uint8
len*: uint
#------------------------------ Merkle Tree operations -----------------------------------------
proc update_next_member*(ctx: RLN[Bn256],
######################################################################
## Kilic's RLN module APIs
######################################################################
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
#------------------------------ Merkle Tree operations -----------------------------------------
proc update_next_member*(ctx: RLN[Bn256],
input_buffer: ptr Buffer): bool {.importc: "update_next_member".}
## input_buffer points to the id commitment byte seq
## the return bool value indicates the success or failure of the operation
## input_buffer points to the id commitment byte seq
## the return bool value indicates the success or failure of the operation
proc delete_member*(ctx: RLN[Bn256], index: uint): bool {.importc: "delete_member".}
## index is the position of the id commitment key to be deleted from the tree
## the deleted id commitment key is replaced with a zero leaf
## the return bool value indicates the success or failure of the operation
proc delete_member*(ctx: RLN[Bn256], index: uint): bool {.importc: "delete_member".}
## index is the position of the id commitment key to be deleted from the tree
## the deleted id commitment key is replaced with a zero leaf
## the return bool value indicates the success or failure of the operation
proc get_root*(ctx: RLN[Bn256], output_buffer: ptr Buffer): bool {.importc: "get_root".}
## get_root populates the passed pointer output_buffer with the current tree root
## the output_buffer holds the Merkle tree root of size 32 bytes
## the return bool value indicates the success or failure of the operation
##
#----------------------------------------------------------------------------------------------
#-------------------------------- zkSNARKs operations -----------------------------------------
proc key_gen*(ctx: RLN[Bn256], keypair_buffer: ptr Buffer): bool {.importc: "key_gen".}
## generates id key and id commitment key serialized inside keypair_buffer as | id_key <32 bytes>| id_commitment_key <32 bytes> |
## id commitment is the poseidon hash of the id key
## the return bool value indicates the success or failure of the operation
proc get_root*(ctx: RLN[Bn256], output_buffer: ptr Buffer): bool {.importc: "get_root".}
## get_root populates the passed pointer output_buffer with the current tree root
## the output_buffer holds the Merkle tree root of size 32 bytes
## the return bool value indicates the success or failure of the operation
##
#----------------------------------------------------------------------------------------------
#-------------------------------- zkSNARKs operations -----------------------------------------
proc key_gen*(ctx: RLN[Bn256], keypair_buffer: ptr Buffer): bool {.importc: "key_gen".}
## generates id key and id commitment key serialized inside keypair_buffer as | id_key <32 bytes>| id_commitment_key <32 bytes> |
## id commitment is the poseidon hash of the id key
## the return bool value indicates the success or failure of the operation
proc generate_proof*(ctx: RLN[Bn256],
proc generate_proof*(ctx: RLN[Bn256],
input_buffer: ptr Buffer,
output_buffer: ptr Buffer): bool {.importc: "generate_proof".}
## input_buffer serialized as [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
## output_buffer holds the proof data and should be parsed as |proof<256>|root<32>|epoch<32>|share_x<32>|share_y<32>|nullifier<32>|
## integers wrapped in <> indicate value sizes in bytes
## the return bool value indicates the success or failure of the operation
##
proc verify*(ctx: RLN[Bn256],
## input_buffer serialized as [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
## output_buffer holds the proof data and should be parsed as |proof<256>|root<32>|epoch<32>|share_x<32>|share_y<32>|nullifier<32>|
## integers wrapped in <> indicate value sizes in bytes
## the return bool value indicates the success or failure of the operation
##
proc verify*(ctx: RLN[Bn256],
proof_buffer: ptr Buffer,
result_ptr: ptr uint32): bool {.importc: "verify".}
## proof_buffer [ proof<256>| root<32>| epoch<32>| share_x<32>| share_y<32>| nullifier<32> | signal_len<8> | signal<var> ]
## the return bool value indicates the success or failure of the call to the verify function
## the result of the verification of the zk proof is stored in the value pointed by result_ptr, where 0 indicates success and 1 is failure
## proof_buffer [ proof<256>| root<32>| epoch<32>| share_x<32>| share_y<32>| nullifier<32> | signal_len<8> | signal<var> ]
## the return bool value indicates the success or failure of the call to the verify function
## the result of the verification of the zk proof is stored in the value pointed by result_ptr, where 0 indicates success and 1 is failure
#----------------------------------------------------------------------------------------------
#-------------------------------- Common procedures -------------------------------------------
#----------------------------------------------------------------------------------------------
#-------------------------------- Common procedures -------------------------------------------
proc new_circuit_from_params*(merkle_depth: uint,
proc new_circuit_from_params*(merkle_depth: uint,
parameters_buffer: ptr Buffer,
ctx: ptr RLN[Bn256]): bool {.importc: "new_circuit_from_params".}
## creates an instance of rln object as defined by the rln lib https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L48
## merkle_depth represent the depth of the Merkle tree
## parameters_buffer holds prover and verifier keys
## ctx holds the final created rln object
## the return bool value indicates the success or failure of the operation
## creates an instance of rln object as defined by the rln lib https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L48
## merkle_depth represent the depth of the Merkle tree
## parameters_buffer holds prover and verifier keys
## ctx holds the final created rln object
## the return bool value indicates the success or failure of the operation
proc hash*(ctx: RLN[Bn256],
proc hash*(ctx: RLN[Bn256],
inputs_buffer: ptr Buffer,
output_buffer: ptr Buffer): bool {.importc: "signal_to_field".}
## as explained in https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L135, it hashes (sha256) the plain text supplied in inputs_buffer and then maps it to a field element
## this proc is used to map arbitrary signals to field element for the sake of proof generation
## inputs_buffer holds the hash input as a byte seq
## the hash output is generated and populated inside output_buffer
## the output_buffer contains 32 bytes hash output
## as explained in https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L135, it hashes (sha256) the plain text supplied in inputs_buffer and then maps it to a field element
## this proc is used to map arbitrary signals to field element for the sake of proof generation
## inputs_buffer holds the hash input as a byte seq
## the hash output is generated and populated inside output_buffer
## the output_buffer contains 32 bytes hash output
######################################################################
## RLN Zerokit module APIs
######################################################################
when defined(rlnzerokit):
#------------------------------ Merkle Tree operations -----------------------------------------
proc update_next_member*(ctx: ptr RLN, input_buffer: ptr Buffer): bool {.importc: "set_next_leaf".}
## adds an element in the merkle tree to the next available position
## input_buffer points to the id commitment byte seq
## the return bool value indicates the success or failure of the operation
proc delete_member*(ctx: ptr RLN, index: uint): bool {.importc: "delete_leaf".}
## index is the position of the id commitment key to be deleted from the tree
## the deleted id commitment key is replaced with a zero leaf
## the return bool value indicates the success or failure of the operation
proc get_root*(ctx: ptr RLN, output_buffer: ptr Buffer): bool {.importc: "get_root".}
## get_root populates the passed pointer output_buffer with the current tree root
## the output_buffer holds the Merkle tree root of size 32 bytes
## the return bool value indicates the success or failure of the operation
proc get_merkle_proof*(ctx: ptr RLN, index: uint, output_buffer: ptr Buffer): bool {.importc: "get_proof".}
## populates the passed pointer output_buffer with the merkle proof for the leaf at position index in the tree stored by ctx
## the output_buffer holds a serialized Merkle proof (vector of 32 bytes nodes)
## the return bool value indicates the success or failure of the operation
proc set_leaf*(ctx: ptr RLN, index: uint, input_buffer: ptr Buffer): bool {.importc: "set_leaf".}
## sets the leaf at position index in the tree stored by ctx to the value passed by input_buffer
## the input_buffer holds a serialized leaf of 32 bytes
## the return bool value indicates the success or failure of the operation
proc set_leaves*(ctx: ptr RLN, input_buffer: ptr Buffer): bool {.importc: "set_leaves".}
## sets multiple leaves in the tree stored by ctx to the value passed by input_buffer
## the input_buffer holds a serialized vector of leaves (32 bytes each)
## leaves are set one after each other starting from index 0
## the return bool value indicates the success or failure of the operation
proc reset_tree*(ctx: ptr RLN, tree_height: uint): bool {.importc: "set_tree".}
## resets the tree stored by ctx to the the empty tree (all leaves set to 0) of height tree_height
## the return bool value indicates the success or failure of the operation
#----------------------------------------------------------------------------------------------
#-------------------------------- zkSNARKs operations -----------------------------------------
proc key_gen*(ctx: ptr RLN, output_buffer: ptr Buffer): bool {.importc: "key_gen".}
## generates id key and id commitment key serialized inside keypair_buffer as | id_key <32 bytes>| id_commitment_key <32 bytes> |
## id commitment is the poseidon hash of the id key
## the return bool value indicates the success or failure of the operation
proc generate_proof*(ctx: ptr RLN,
input_buffer: ptr Buffer,
output_buffer: ptr Buffer): bool {.importc: "generate_rln_proof".}
## input_buffer has to be serialized as [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
## output_buffer holds the proof data and should be parsed as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]
## integers wrapped in <> indicate value sizes in bytes
## the return bool value indicates the success or failure of the operation
proc verify*(ctx: ptr RLN,
proof_buffer: ptr Buffer,
proof_is_valid_ptr: ptr bool): bool {.importc: "verify_rln_proof".}
## proof_buffer has to be serialized as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal<var> ]
## the return bool value indicates the success or failure of the call to the verify function
## the verification of the zk proof is available in proof_is_valid_ptr, where a value of true indicates success and false a failure
proc zk_prove*(ctx: ptr RLN,
input_buffer: ptr Buffer,
output_buffer: ptr Buffer): bool {.importc: "prove".}
## Computes the zkSNARK proof and stores it in output_buffer for input values stored in input_buffer
## input_buffer is serialized as input_data as [ id_key<32> | path_elements<Vec<32>> | identity_path_index<Vec<1>> | x<32> | epoch<32> | rln_identifier<32> ]
## output_buffer holds the proof data and should be parsed as [ proof<128> ]
## path_elements and indentity_path elements serialize a merkle proof for id_key and are vectors of elements of 32 and 1 bytes, respectively (not. Vec<>).
## x is the x coordinate of the Shamir's secret share for which the proof is computed
## epoch is the input epoch (equivalently, the nullifier)
## the return bool value indicates the success or failure of the operation
proc zk_verify*(ctx: ptr RLN,
proof_buffer: ptr Buffer,
proof_is_valid_ptr: ptr bool): bool {.importc: "verify".}
## Verifies the zkSNARK proof passed in proof_buffer
## input_buffer is serialized as input_data as [ proof<128> ]
## the verification of the zk proof is available in proof_is_valid_ptr, where a value of true indicates success and false a failure
## the return bool value indicates the success or failure of the operation
#----------------------------------------------------------------------------------------------
#-------------------------------- Common procedures -------------------------------------------
proc new_circuit*(tree_height: uint, input_buffer: ptr Buffer, ctx: ptr (ptr RLN)): bool {.importc: "new".}
## creates an instance of rln object as defined by the zerokit RLN lib
## merkle_depth represent the depth of the Merkle tree
## input_buffer contains a serialization of the path where the circuit resources can be found (.r1cs, .wasm, .zkey and optionally the verification_key.json)
## ctx holds the final created rln object
## the return bool value indicates the success or failure of the operation
proc hash*(ctx: ptr RLN,
input_buffer: ptr Buffer,
output_buffer: ptr Buffer): bool {.importc: "hash".}
## it hashes (sha256) the plain text supplied in inputs_buffer and then maps it to a field element
## this proc is used to map arbitrary signals to field element for the sake of proof generation
## inputs_buffer holds the hash input as a byte seq
## the hash output is generated and populated inside output_buffer
## the output_buffer contains 32 bytes hash output
{.pop.}

View File

@ -9,11 +9,15 @@ import
stew/arrayops,
../../utils/protobuf
## Bn256 and RLN are Nim wrappers for the data types used in
## the rln library https://github.com/kilic/rln/blob/3bbec368a4adc68cd5f9bfae80b17e1bbb4ef373/src/ffi.rs
type Bn256* = pointer
type RLN*[E] = pointer
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
## Bn256 and RLN are Nim wrappers for the data types used in
## the rln library https://github.com/kilic/rln/blob/3bbec368a4adc68cd5f9bfae80b17e1bbb4ef373/src/ffi.rs
type Bn256* = pointer
type RLN*[E] = pointer
when defined(rlnzerokit):
## RLN is a Nim wrapper for the data types used in zerokit RLN
type RLN* {.incompleteStruct.} = object
type
# identity key as defined in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Membership
@ -21,13 +25,19 @@ type
# hash of identity key as defined ed in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Membership
IDCommitment* = array[32, byte]
type
MerkleNode* = array[32, byte] # Each node of the Merkle tee is a Poseidon hash which is a 32 byte value
Nullifier* = array[32, byte]
ZKSNARK* = array[256, byte]
Epoch* = array[32, byte]
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
type
ZKSNARK* = array[256, byte]
when defined(rlnzerokit):
type
ZKSNARK* = array[128, byte]
RlnIdentifier* = array[32, byte]
# Custom data types defined for waku rln relay -------------------------
type MembershipKeyPair* = object
## user's identity key (a secret key) which is selected randomly
@ -38,13 +48,8 @@ type MembershipKeyPair* = object
# more details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Membership
idCommitment*: IDCommitment
type MembershipIndex* = uint
type RlnMembershipCredentials* = object
membershipKeyPair*: MembershipKeyPair
rlnIndex*: MembershipIndex
type RateLimitProof* = object
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
type RateLimitProof* = object
## RateLimitProof holds the public inputs to rln circuit as
## defined in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Public-Inputs
## the `proof` field carries the actual zkSNARK proof
@ -62,12 +67,40 @@ type RateLimitProof* = object
## see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Nullifiers
nullifier*: Nullifier
when defined(rlnzerokit):
type RateLimitProof* = object
## RateLimitProof holds the public inputs to rln circuit as
## defined in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Public-Inputs
## the `proof` field carries the actual zkSNARK proof
proof*: ZKSNARK
## the root of Merkle tree used for the generation of the `proof`
merkleRoot*: MerkleNode
## the epoch used for the generation of the `proof`
epoch*: Epoch
## shareX and shareY are shares of user's identity key
## these shares are created using Shamir secret sharing scheme
## see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Linear-Equation-amp-SSS
shareX*: MerkleNode
shareY*: MerkleNode
## nullifier enables linking two messages published during the same epoch
## see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Nullifiers
nullifier*: Nullifier
## Application specific RLN Identifier
rlnIdentifier*: RlnIdentifier
type MembershipIndex* = uint
type RlnMembershipCredentials* = object
membershipKeyPair*: MembershipKeyPair
rlnIndex*: MembershipIndex
type ProofMetadata* = object
nullifier*: Nullifier
shareX*: MerkleNode
shareY*: MerkleNode
type WakuRLNRelay* = ref object
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
type WakuRLNRelay* = ref object
membershipKeyPair*: MembershipKeyPair
# membershipIndex denotes the index of a leaf in the Merkle tree
# that contains the pk of the current peer
@ -88,6 +121,28 @@ type WakuRLNRelay* = ref object
# the log of nullifiers and Shamir shares of the past messages grouped per epoch
nullifierLog*: Table[Epoch, seq[ProofMetadata]]
when defined(rlnzerokit):
type WakuRLNRelay* = ref object
membershipKeyPair*: MembershipKeyPair
# membershipIndex denotes the index of a leaf in the Merkle tree
# that contains the pk of the current peer
# this index is used to retrieve the peer's authentication path
membershipIndex*: MembershipIndex
membershipContractAddress*: Address
ethClientAddress*: string
ethAccountAddress*: Address
# this field is required for signing transactions
# TODO may need to erase this ethAccountPrivateKey when is not used
# TODO may need to make ethAccountPrivateKey mandatory
ethAccountPrivateKey*: Option[PrivateKey]
rlnInstance*: ptr RLN
pubsubTopic*: string # the pubsub topic for which rln relay is mounted
# contentTopic should be of type waku_message.ContentTopic, however, due to recursive module dependency, the underlying type of ContentTopic is used instead
# TODO a long-term solution is to place types with recursive dependency inside one file
contentTopic*: string
# the log of nullifiers and Shamir shares of the past messages grouped per epoch
nullifierLog*: Table[Epoch, seq[ProofMetadata]]
type MessageValidationResult* {.pure.} = enum
Valid, Invalid, Spam
@ -111,6 +166,12 @@ const
# the size of poseidon hash output as the number hex digits
HASH_HEX_SIZE* = int(HASH_BIT_SIZE/4)
when defined(rlnzerokit):
const
# The relative folder where the circuit, proving and verification key for RLN can be found
# Note that resources has to be compiled with respect to the above MERKLE_TREE_DEPTH
RLN_RESOURCE_FOLDER* = "vendor/zerokit/rln/resources/tree_height_" & $MERKLE_TREE_DEPTH & "/"
# temporary variables to test waku-rln-relay performance in the static group mode
const
STATIC_GROUP_SIZE* = 100
@ -319,11 +380,21 @@ const
"d1ce3aea6cfb7be132d17e8d76fcbe4b7e34cef3979b4b905acfeff2f6d19724",
"be47b76297791f535f4b56f973a19f07ec22d4eede2a41ff23c696089938bb21")]
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
const
# STATIC_GROUP_MERKLE_ROOT is the root of the Merkle tree constructed from the STATIC_GROUP_KEYS above
# only identity commitments are used for the Merkle tree construction
# the root is created locally, using createMembershipList proc from waku_rln_relay_utils module, and the result is hardcoded in here
STATIC_GROUP_MERKLE_ROOT* = "a1877a553eff12e1b21632a0545a916a5c5b8060ad7cc6c69956741134397b2d"
when defined(rlnzerokit):
const
# STATIC_GROUP_MERKLE_ROOT is the root of the Merkle tree constructed from the STATIC_GROUP_KEYS above
# only identity commitments are used for the Merkle tree construction
# the root is created locally, using createMembershipList proc from waku_rln_relay_utils module, and the result is hardcoded in here
STATIC_GROUP_MERKLE_ROOT* = "9abaf9fda6af9b8237185bfd85b6e56a99ec60c3661e4c8a67f3f6c691603a2d"
const EPOCH_UNIT_SECONDS* = float64(10) # the rln-relay epoch length in seconds
const MAX_CLOCK_GAP_SECONDS* = 20.0 # the maximum clock difference between peers in seconds
@ -359,6 +430,11 @@ proc init*(T: type RateLimitProof, buffer: seq[byte]): ProtoResult[T] =
discard ? pb.getField(6, nullifier)
discard nsp.nullifier.copyFrom(nullifier)
when defined(rlnzerokit):
var rlnIdentifier: seq[byte]
discard ? pb.getField(7, rlnIdentifier)
discard nsp.rlnIdentifier.copyFrom(rlnIdentifier)
return ok(nsp)
proc encode*(nsp: RateLimitProof): ProtoBuffer =
@ -371,6 +447,9 @@ proc encode*(nsp: RateLimitProof): ProtoBuffer =
output.write3(5, nsp.shareY)
output.write3(6, nsp.nullifier)
when defined(rlnzerokit):
output.write3(7, nsp.rlnIdentifier)
output.finish3()
return output

View File

@ -24,7 +24,13 @@ import
logScope:
topics = "wakurlnrelayutils"
type RLNResult* = Result[RLN[Bn256], string]
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
type RLNResult* = Result[RLN[Bn256], string]
when defined(rlnzerokit):
type RLNResult* = Result[ptr RLN, string]
type MerkleNodeResult* = Result[MerkleNode, string]
type RateLimitProofResult* = Result[RateLimitProof, string]
type SpamHandler* = proc(wakuMessage: WakuMessage): void {.gcsafe, closure,
@ -39,7 +45,16 @@ contract(MembershipContract):
# proc withdraw(secret: Uint256, pubkeyIndex: Uint256, receiver: Address)
# proc withdrawBatch( secrets: seq[Uint256], pubkeyIndex: seq[Uint256], receiver: seq[Address])
proc createRLNInstance*(d: int = MERKLE_TREE_DEPTH): RLNResult
proc toBuffer*(x: openArray[byte]): Buffer =
## converts the input to a Buffer object
## the Buffer object is used to communicate data with the rln lib
var temp = @x
let output = Buffer(`ptr`: addr(temp[0]), len: uint(temp.len))
return output
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
proc createRLNInstance*(d: int = MERKLE_TREE_DEPTH): RLNResult
{.raises: [Defect, IOError].} =
## generates an instance of RLN
@ -77,7 +92,7 @@ proc createRLNInstance*(d: int = MERKLE_TREE_DEPTH): RLNResult
return ok(rlnInstance)
proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] =
proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] =
## generates a MembershipKeyPair that can be used for the registration into the rln membership contract
# keysBufferPtr will hold the generated key pairs i.e., secret and public keys
@ -110,6 +125,59 @@ proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] =
return some(keypair)
when defined(rlnzerokit):
proc createRLNInstance*(d: int = MERKLE_TREE_DEPTH): RLNResult
{.raises: [Defect, IOError].} =
## generates an instance of RLN
## An RLN instance supports both zkSNARKs logics and Merkle tree data structure and operations
## d indicates the depth of Merkle tree
var
rlnInstance: ptr RLN
merkleDepth: csize_t = uint(d)
resourcesPathBuffer = RLN_RESOURCE_FOLDER.toOpenArrayByte(0, RLN_RESOURCE_FOLDER.high).toBuffer()
# create an instance of RLN
let res = new_circuit(merkleDepth, addr resourcesPathBuffer, addr rlnInstance)
# check whether the circuit parameters are generated successfully
if (res == false):
debug "error in parameters generation"
return err("error in parameters generation")
return ok(rlnInstance)
proc membershipKeyGen*(ctxPtr: ptr RLN): Option[MembershipKeyPair] =
## generates a MembershipKeyPair that can be used for the registration into the rln membership contract
# keysBufferPtr will hold the generated key pairs i.e., secret and public keys
var
keysBuffer: Buffer
keysBufferPtr = addr(keysBuffer)
done = key_gen(ctxPtr, keysBufferPtr)
# check whether the keys are generated successfully
if(done == false):
debug "error in key generation"
return none(MembershipKeyPair)
var generatedKeys = cast[ptr array[64, byte]](keysBufferPtr.`ptr`)[]
# the public and secret keys together are 64 bytes
if (generatedKeys.len != 64):
debug "the generated keys are invalid"
return none(MembershipKeyPair)
# TODO define a separate proc to decode the generated keys to the secret and public components
var
secret: array[32, byte]
public: array[32, byte]
for (i, x) in secret.mpairs: x = generatedKeys[i]
for (i, x) in public.mpairs: x = generatedKeys[i+32]
var
keypair = MembershipKeyPair(idKey: secret, idCommitment: public)
return some(keypair)
proc toUInt256*(idCommitment: IDCommitment): UInt256 =
let pk = UInt256.fromBytesBE(idCommitment)
return pk
@ -188,14 +256,24 @@ proc appendLength*(input: openArray[byte]): seq[byte] =
output = concat(@len, @input)
return output
proc toBuffer*(x: openArray[byte]): Buffer =
## converts the input to a Buffer object
## the Buffer object is used to communicate data with the rln lib
var temp = @x
let output = Buffer(`ptr`: addr(temp[0]), len: uint(temp.len))
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
proc hash*(rlnInstance: RLN[Bn256], data: openArray[byte]): MerkleNode =
## a thin layer on top of the Nim wrapper of the Poseidon hasher
debug "hash input", hashhex = data.toHex()
var lenPrefData = appendLength(data)
var
hashInputBuffer = lenPrefData.toBuffer()
outputBuffer: Buffer # will holds the hash output
debug "hash input buffer length", bufflen = hashInputBuffer.len
let
hashSuccess = hash(rlnInstance, addr hashInputBuffer, addr outputBuffer)
output = cast[ptr MerkleNode](outputBuffer.`ptr`)[]
return output
proc hash*(rlnInstance: RLN[Bn256], data: openArray[byte]): MerkleNode =
when defined(rlnzerokit):
proc hash*(rlnInstance: ptr RLN, data: openArray[byte]): MerkleNode =
## a thin layer on top of the Nim wrapper of the Poseidon hasher
debug "hash input", hashhex = data.toHex()
var lenPrefData = appendLength(data)
@ -221,7 +299,8 @@ proc serialize(idKey: IDKey, memIndex: MembershipIndex, epoch: Epoch,
let output = concat(@idKey, @memIndexBytes, @epoch, lenPrefMsg)
return output
proc proofGen*(rlnInstance: RLN[Bn256], data: openArray[byte],
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
proc proofGen*(rlnInstance: RLN[Bn256], data: openArray[byte],
memKeys: MembershipKeyPair, memIndex: MembershipIndex,
epoch: Epoch): RateLimitProofResult =
@ -276,7 +355,7 @@ proc proofGen*(rlnInstance: RLN[Bn256], data: openArray[byte],
return ok(output)
proc serialize(proof: RateLimitProof, data: openArray[byte]): seq[byte] =
proc serialize(proof: RateLimitProof, data: openArray[byte]): seq[byte] =
## a private proc to convert RateLimitProof and data to a byte seq
## this conversion is used in the proof verification proc
## the order of serialization is based on https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L205
@ -292,7 +371,7 @@ proc serialize(proof: RateLimitProof, data: openArray[byte]): seq[byte] =
return proofBytes
proc proofVerify*(rlnInstance: RLN[Bn256], data: openArray[byte],
proc proofVerify*(rlnInstance: RLN[Bn256], data: openArray[byte],
proof: RateLimitProof): bool =
var
proofBytes = serialize(proof, data)
@ -309,7 +388,7 @@ proc proofVerify*(rlnInstance: RLN[Bn256], data: openArray[byte],
return true
return false
proc insertMember*(rlnInstance: RLN[Bn256], idComm: IDCommitment): bool =
proc insertMember*(rlnInstance: RLN[Bn256], idComm: IDCommitment): bool =
var pkBuffer = toBuffer(idComm)
let pkBufferPtr = addr pkBuffer
@ -317,11 +396,125 @@ proc insertMember*(rlnInstance: RLN[Bn256], idComm: IDCommitment): bool =
var member_is_added = update_next_member(rlnInstance, pkBufferPtr)
return member_is_added
proc removeMember*(rlnInstance: RLN[Bn256], index: MembershipIndex): bool =
proc removeMember*(rlnInstance: RLN[Bn256], index: MembershipIndex): bool =
let deletion_success = delete_member(rlnInstance, index)
return deletion_success
proc getMerkleRoot*(rlnInstance: RLN[Bn256]): MerkleNodeResult =
proc getMerkleRoot*(rlnInstance: RLN[Bn256]): MerkleNodeResult =
# read the Merkle Tree root after insertion
var
root {.noinit.}: Buffer = Buffer()
rootPtr = addr(root)
get_root_successful = get_root(rlnInstance, rootPtr)
if (not get_root_successful): return err("could not get the root")
if (not (root.len == 32)): return err("wrong output size")
var rootValue = cast[ptr MerkleNode] (root.`ptr`)[]
return ok(rootValue)
when defined(rlnzerokit):
proc proofGen*(rlnInstance: ptr RLN, data: openArray[byte],
memKeys: MembershipKeyPair, memIndex: MembershipIndex,
epoch: Epoch): RateLimitProofResult =
# serialize inputs
let serializedInputs = serialize(idKey = memKeys.idKey,
memIndex = memIndex,
epoch = epoch,
msg = data)
var inputBuffer = toBuffer(serializedInputs)
debug "input buffer ", inputBuffer
# generate the proof
var proof: Buffer
let proofIsSuccessful = generate_proof(rlnInstance, addr inputBuffer, addr proof)
# check whether the generate_proof call is done successfully
if not proofIsSuccessful:
return err("could not generate the proof")
var proofValue = cast[ptr array[320, byte]] (proof.`ptr`)
let proofBytes: array[320, byte] = proofValue[]
debug "proof content", proofHex = proofValue[].toHex
## parse the proof as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]
let
proofOffset = 128
rootOffset = proofOffset + 32
epochOffset = rootOffset + 32
shareXOffset = epochOffset + 32
shareYOffset = shareXOffset + 32
nullifierOffset = shareYOffset + 32
rlnIdentifierOffset = nullifierOffset + 32
var
zkproof: ZKSNARK
proofRoot, shareX, shareY: MerkleNode
epoch: Epoch
nullifier: Nullifier
rlnIdentifier: RlnIdentifier
discard zkproof.copyFrom(proofBytes[0..proofOffset-1])
discard proofRoot.copyFrom(proofBytes[proofOffset..rootOffset-1])
discard epoch.copyFrom(proofBytes[rootOffset..epochOffset-1])
discard shareX.copyFrom(proofBytes[epochOffset..shareXOffset-1])
discard shareY.copyFrom(proofBytes[shareXOffset..shareYOffset-1])
discard nullifier.copyFrom(proofBytes[shareYOffset..nullifierOffset-1])
discard rlnIdentifier.copyFrom(proofBytes[nullifierOffset..rlnIdentifierOffset-1])
let output = RateLimitProof(proof: zkproof,
merkleRoot: proofRoot,
epoch: epoch,
shareX: shareX,
shareY: shareY,
nullifier: nullifier,
rlnIdentifier: rlnIdentifier)
return ok(output)
proc serialize(proof: RateLimitProof, data: openArray[byte]): seq[byte] =
## a private proc to convert RateLimitProof and data to a byte seq
## this conversion is used in the proof verification proc
## [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal<var> ]
let lenPrefMsg = appendLength(@data)
var proofBytes = concat(@(proof.proof),
@(proof.merkleRoot),
@(proof.epoch),
@(proof.shareX),
@(proof.shareY),
@(proof.nullifier),
@(proof.rlnIdentifier),
lenPrefMsg)
return proofBytes
proc proofVerify*(rlnInstance: ptr RLN, data: openArray[byte], proof: RateLimitProof): bool =
var
proofBytes = serialize(proof, data)
proofBuffer = proofBytes.toBuffer()
proof_is_valid: bool
trace "serialized proof", proof = proofBytes.toHex()
let verifyIsSuccessful = verify(rlnInstance, addr proofBuffer, addr proof_is_valid)
if not verifyIsSuccessful:
# something went wrong in verification call
return false
return proof_is_valid
proc insertMember*(rlnInstance: ptr RLN, idComm: IDCommitment): bool =
var pkBuffer = toBuffer(idComm)
let pkBufferPtr = addr pkBuffer
# add the member to the tree
var member_is_added = update_next_member(rlnInstance, pkBufferPtr)
return member_is_added
proc removeMember*(rlnInstance: ptr RLN, index: MembershipIndex): bool =
let deletion_success = delete_member(rlnInstance, index)
return deletion_success
proc getMerkleRoot*(rlnInstance: ptr RLN): MerkleNodeResult =
# read the Merkle Tree root after insertion
var
root {.noinit.}: Buffer = Buffer()
@ -601,7 +794,18 @@ proc appendRLNProof*(rlnPeer: WakuRLNRelay, msg: var WakuMessage,
msg.proof = proof.value
return true
proc addAll*(rlnInstance: RLN[Bn256], list: seq[IDCommitment]): bool =
when defined(rln) or (not defined(rln) and not defined(rlnzerokit)):
proc addAll*(rlnInstance: RLN[Bn256], list: seq[IDCommitment]): bool =
# add members to the Merkle tree of the `rlnInstance`
for i in 0..list.len-1:
let member = list[i]
let member_is_added = rlnInstance.insertMember(member)
if not member_is_added:
return false
return true
when defined(rlnzerokit):
proc addAll*(rlnInstance: ptr RLN, list: seq[IDCommitment]): bool =
# add members to the Merkle tree of the `rlnInstance`
for i in 0..list.len-1:
let member = list[i]
@ -644,6 +848,7 @@ proc handleGroupUpdates*(rlnPeer: WakuRLNRelay, handler: RegistrationEventHandle
# mounts the supplied handler for the registration events emitting from the membership contract
await subscribeToGroupEvents(ethClientUri = rlnPeer.ethClientAddress, ethAccountAddress = rlnPeer.ethAccountAddress, contractAddress = rlnPeer.membershipContractAddress, handler = handler)
proc addRLNRelayValidator*(node: WakuNode, pubsubTopic: string, contentTopic: ContentTopic, spamHandler: Option[SpamHandler] = none(SpamHandler)) =
## this procedure is a thin wrapper for the pubsub addValidator method
## it sets a validator for the waku messages published on the supplied pubsubTopic and contentTopic