From 8a14b5b2438d61f11b65ec91b0b969a36c6d8954 Mon Sep 17 00:00:00 2001 From: G <28568419+s1fr0@users.noreply.github.com> Date: Fri, 5 Aug 2022 22:58:19 +0200 Subject: [PATCH] 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 --- .gitmodules | 4 + Makefile | 24 +- tests/all_tests_v2.nim | 2 +- tests/v2/test_waku_rln_relay.nim | 215 ++++--- vendor/zerokit | 1 + waku/v2/protocol/waku_rln_relay/rln.nim | 221 +++++-- .../waku_rln_relay/waku_rln_relay_types.nim | 179 ++++-- .../waku_rln_relay/waku_rln_relay_utils.nim | 561 ++++++++++++------ 8 files changed, 855 insertions(+), 352 deletions(-) create mode 160000 vendor/zerokit diff --git a/.gitmodules b/.gitmodules index 65e22f1eb..45137a686 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/Makefile b/Makefile index b3cd58daa..6cc978537 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/tests/all_tests_v2.nim b/tests/all_tests_v2.nim index f810eccbb..9dacc80cc 100644 --- a/tests/all_tests_v2.nim +++ b/tests/all_tests_v2.nim @@ -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 diff --git a/tests/v2/test_waku_rln_relay.nim b/tests/v2/test_waku_rln_relay.nim index 5dee0d387..8f6bf39a6 100644 --- a/tests/v2/test_waku_rln_relay.nim +++ b/tests/v2/test_waku_rln_relay.nim @@ -64,45 +64,72 @@ procSuite "Waku rln relay": await node.stop() suite "Waku rln relay": - test "key_gen Nim Wrappers": - var - merkleDepth: csize_t = 32 - # parameters.key contains the parameters related to the Poseidon hasher - # to generate this file, clone this repo https://github.com/kilic/rln - # and run the following command in the root directory of the cloned project - # cargo run --example export_test_keys - # the file is generated separately and copied here - parameters = readFile("waku/v2/protocol/waku_rln_relay/parameters.key") - pbytes = parameters.toBytes() - len: csize_t = uint(pbytes.len) - parametersBuffer = Buffer(`ptr`: addr(pbytes[0]), len: len) - check: - # check the parameters.key is not empty - pbytes.len != 0 - var - rlnInstance: RLN[Bn256] - let res = new_circuit_from_params(merkleDepth, addr parametersBuffer, - addr rlnInstance) - check: - # check whether the circuit parameters are generated successfully - res == true - - # keysBufferPtr will hold the generated key pairs i.e., secret and public keys - var - keysBuffer: Buffer - keysBufferPtr = addr(keysBuffer) - done = key_gen(rlnInstance, keysBufferPtr) - check: - # check whether the keys are generated successfully - done == true - - if done: - var generatedKeys = cast[ptr array[64, byte]](keysBufferPtr.`ptr`)[] + when defined(rln) or (not defined(rln) and not defined(rlnzerokit)): + test "key_gen Nim Wrappers": + var + merkleDepth: csize_t = 32 + # parameters.key contains the parameters related to the Poseidon hasher + # to generate this file, clone this repo https://github.com/kilic/rln + # and run the following command in the root directory of the cloned project + # cargo run --example export_test_keys + # the file is generated separately and copied here + parameters = readFile("waku/v2/protocol/waku_rln_relay/parameters.key") + pbytes = parameters.toBytes() + len: csize_t = uint(pbytes.len) + parametersBuffer = Buffer(`ptr`: addr(pbytes[0]), len: len) check: - # the public and secret keys together are 64 bytes - generatedKeys.len == 64 - debug "generated keys: ", generatedKeys + # check the parameters.key is not empty + pbytes.len != 0 + + var + rlnInstance: RLN[Bn256] + let res = new_circuit_from_params(merkleDepth, addr parametersBuffer, + addr rlnInstance) + check: + # check whether the circuit parameters are generated successfully + res == true + + # keysBufferPtr will hold the generated key pairs i.e., secret and public keys + var + keysBuffer: Buffer + keysBufferPtr = addr(keysBuffer) + done = key_gen(rlnInstance, 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 + + 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 @@ -371,8 +398,14 @@ suite "Waku rln relay": check: hashSuccess let outputArr = cast[ptr array[32, byte]](outputBuffer.`ptr`)[] - check: - "efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" == + + when defined(rln) or (not defined(rln) and not defined(rlnzerokit)): + check: + "efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" == + outputArr.toHex() + when defined(rlnzerokit): + check: + "4c6ea217404bd5f10e243bac29dc4f1ec36bf4a41caba7b4c8075c54abb3321e" == outputArr.toHex() var @@ -392,8 +425,14 @@ suite "Waku rln relay": let msg = "Hello".toBytes() let hash = rln.hash(msg) - check: - "efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" == + + 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": @@ -428,35 +467,71 @@ suite "Waku rln relay": # compare the calculated root against the correct root root == STATIC_GROUP_MERKLE_ROOT - test "RateLimitProof Protobuf encode/init test": - var - proof: ZKSNARK - merkleRoot: MerkleNode - epoch: Epoch - shareX: MerkleNode - shareY: MerkleNode - nullifier: Nullifier - # 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 + when defined(rln) or (not defined(rln) and not defined(rlnzerokit)): + test "RateLimitProof Protobuf encode/init test": + var + proof: ZKSNARK + merkleRoot: MerkleNode + epoch: Epoch + shareX: MerkleNode + shareY: MerkleNode + nullifier: Nullifier + # 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 - let - rateLimitProof = RateLimitProof(proof: proof, - merkleRoot: merkleRoot, - epoch: epoch, - shareX: shareX, - shareY: shareY, - nullifier: nullifier) - protobuf = rateLimitProof.encode() - decodednsp = RateLimitProof.init(protobuf.buffer) + let + rateLimitProof = RateLimitProof(proof: proof, + merkleRoot: merkleRoot, + epoch: epoch, + shareX: shareX, + shareY: shareY, + nullifier: nullifier) + protobuf = rateLimitProof.encode() + decodednsp = RateLimitProof.init(protobuf.buffer) - check: - decodednsp.isErr == false - decodednsp.value == rateLimitProof + check: + 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() @@ -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() diff --git a/vendor/zerokit b/vendor/zerokit new file mode 160000 index 000000000..3378aed85 --- /dev/null +++ b/vendor/zerokit @@ -0,0 +1 @@ +Subproject commit 3378aed85716144231e036c076fe3f14ed9538de diff --git a/waku/v2/protocol/waku_rln_relay/rln.nim b/waku/v2/protocol/waku_rln_relay/rln.nim index 7edd85f4a..c14eea0b1 100644 --- a/waku/v2/protocol/waku_rln_relay/rln.nim +++ b/waku/v2/protocol/waku_rln_relay/rln.nim @@ -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): @@ -23,66 +27,175 @@ elif defined(MacOsX): type Buffer* = object `ptr`*: ptr uint8 len*: uint - -#------------------------------ 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 -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 +###################################################################### +## Kilic's RLN module APIs +###################################################################### -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 +when defined(rln) or (not defined(rln) and not defined(rlnzerokit)): -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 ] -## 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 ] -## 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 + #------------------------------ 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 + + 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 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 ] + ## 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 ] + ## 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, - 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 + 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 -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 + 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 + + +###################################################################### +## 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 ] + ## 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 ] + ## 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> | identity_path_index> | 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.} diff --git a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim index 4bef87677..1cc5dcc46 100644 --- a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim +++ b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim @@ -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,55 +48,100 @@ type MembershipKeyPair* = object # more details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Membership idCommitment*: IDCommitment +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 + 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 + +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 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 - type ProofMetadata* = object nullifier*: Nullifier shareX*: MerkleNode shareY*: MerkleNode -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*: RLN[Bn256] - 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]] +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 + # 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*: RLN[Bn256] + 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]] + +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 @@ -94,7 +149,7 @@ type MessageValidationResult* {.pure.} = enum # RLN membership key and index files path const RLN_CREDENTIALS_FILEPATH* = "rlnCredentials.txt" - + # inputs of the membership contract constructor # TODO may be able to make these constants private and put them inside the waku_rln_relay_utils const @@ -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,10 +380,20 @@ const "d1ce3aea6cfb7be132d17e8d76fcbe4b7e34cef3979b4b905acfeff2f6d19724", "be47b76297791f535f4b56f973a19f07ec22d4eede2a41ff23c696089938bb21")] - # 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(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 + return output \ No newline at end of file diff --git a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim index bebe76169..05faff506 100644 --- a/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim +++ b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_utils.nim @@ -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,76 +45,138 @@ 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 - {.raises: [Defect, IOError].} = +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 - ## 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: RLN[Bn256] - merkleDepth: csize_t = uint(d) - ## parameters.key contains the prover and verifier keys - ## to generate this file, clone this repo https://github.com/kilic/rln - ## and run the following command in the root directory of the cloned project - ## cargo run --example export_test_keys - ## the file is generated separately and copied here - ## parameters are function of tree depth and poseidon hasher - ## to generate parameters for a different tree depth, change the tree size in the following line of rln library - ## https://github.com/kilic/rln/blob/3bbec368a4adc68cd5f9bfae80b17e1bbb4ef373/examples/export_test_keys/main.rs#L4 - ## and then proceed as explained above - parameters = readFile("waku/v2/protocol/waku_rln_relay/parameters.key") - pbytes = parameters.toBytes() - len: csize_t = uint(pbytes.len) - parametersBuffer = Buffer(`ptr`: addr(pbytes[0]), len: len) +when defined(rln) or (not defined(rln) and not defined(rlnzerokit)): - # check the parameters.key is not empty - if (pbytes.len == 0): - debug "error in parameters.key" - return err("error in parameters.key") + proc createRLNInstance*(d: int = MERKLE_TREE_DEPTH): RLNResult + {.raises: [Defect, IOError].} = - # create an instance of RLN - let res = new_circuit_from_params(merkleDepth, addr parametersBuffer, - 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) + ## 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: RLN[Bn256] + merkleDepth: csize_t = uint(d) + ## parameters.key contains the prover and verifier keys + ## to generate this file, clone this repo https://github.com/kilic/rln + ## and run the following command in the root directory of the cloned project + ## cargo run --example export_test_keys + ## the file is generated separately and copied here + ## parameters are function of tree depth and poseidon hasher + ## to generate parameters for a different tree depth, change the tree size in the following line of rln library + ## https://github.com/kilic/rln/blob/3bbec368a4adc68cd5f9bfae80b17e1bbb4ef373/examples/export_test_keys/main.rs#L4 + ## and then proceed as explained above + parameters = readFile("waku/v2/protocol/waku_rln_relay/parameters.key") + pbytes = parameters.toBytes() + len: csize_t = uint(pbytes.len) + parametersBuffer = Buffer(`ptr`: addr(pbytes[0]), len: len) - -proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] = - ## generates a MembershipKeyPair that can be used for the registration into the rln membership contract + # check the parameters.key is not empty + if (pbytes.len == 0): + debug "error in parameters.key" + return err("error in parameters.key") - # keysBufferPtr will hold the generated key pairs i.e., secret and public keys - var - keysBuffer: Buffer - keysBufferPtr = addr(keysBuffer) - done = key_gen(ctxPtr, keysBufferPtr) + # create an instance of RLN + let res = new_circuit_from_params(merkleDepth, addr parametersBuffer, + 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) - # check whether the keys are generated successfully - if(done == false): - debug "error in key generation" - return none(MembershipKeyPair) + + proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] = + ## generates a MembershipKeyPair that can be used for the registration into the rln membership contract - 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) + # keysBufferPtr will hold the generated key pairs i.e., secret and public keys + var + keysBuffer: Buffer + keysBufferPtr = addr(keysBuffer) + done = key_gen(ctxPtr, keysBufferPtr) - # 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] + # check whether the keys are generated successfully + if(done == false): + debug "error in key generation" + return none(MembershipKeyPair) - var - keypair = MembershipKeyPair(idKey: secret, idCommitment: public) + 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) + 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) @@ -188,27 +256,37 @@ 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)) - return output +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 -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`)[] - debug "hash input buffer length", bufflen = hashInputBuffer.len - let - hashSuccess = hash(rlnInstance, addr hashInputBuffer, addr outputBuffer) - output = cast[ptr MerkleNode](outputBuffer.`ptr`)[] + return output - return output +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) + 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 serialize(idKey: IDKey, memIndex: MembershipIndex, epoch: Epoch, msg: openArray[byte]): seq[byte] = @@ -221,117 +299,232 @@ 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], - memKeys: MembershipKeyPair, memIndex: MembershipIndex, - epoch: Epoch): RateLimitProofResult = +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 = - # serialize inputs - let serializedInputs = serialize(idKey = memKeys.idKey, - memIndex = memIndex, - epoch = epoch, - msg = data) - var inputBuffer = toBuffer(serializedInputs) + # serialize inputs + let serializedInputs = serialize(idKey = memKeys.idKey, + memIndex = memIndex, + epoch = epoch, + msg = data) + var inputBuffer = toBuffer(serializedInputs) - debug "input buffer ", inputBuffer + 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") + # 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[416, byte]] (proof.`ptr`) - let proofBytes: array[416, byte] = proofValue[] - debug "proof content", proofHex = proofValue[].toHex + var proofValue = cast[ptr array[416, byte]] (proof.`ptr`) + let proofBytes: array[416, byte] = proofValue[] + debug "proof content", proofHex = proofValue[].toHex - ## parse the proof as |zkSNARKs<256>|root<32>|epoch<32>|share_x<32>|share_y<32>|nullifier<32>| - let - proofOffset = 256 - rootOffset = proofOffset + 32 - epochOffset = rootOffset + 32 - shareXOffset = epochOffset + 32 - shareYOffset = shareXOffset + 32 - nullifierOffset = shareYOffset + 32 + ## parse the proof as |zkSNARKs<256>|root<32>|epoch<32>|share_x<32>|share_y<32>|nullifier<32>| + let + proofOffset = 256 + rootOffset = proofOffset + 32 + epochOffset = rootOffset + 32 + shareXOffset = epochOffset + 32 + shareYOffset = shareXOffset + 32 + nullifierOffset = shareYOffset + 32 - var - zkproof: ZKSNARK - proofRoot, shareX, shareY: MerkleNode - epoch: Epoch - nullifier: Nullifier + var + zkproof: ZKSNARK + proofRoot, shareX, shareY: MerkleNode + epoch: Epoch + nullifier: Nullifier - 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 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]) - let output = RateLimitProof(proof: zkproof, - merkleRoot: proofRoot, - epoch: epoch, - shareX: shareX, - shareY: shareY, - nullifier: nullifier) + let output = RateLimitProof(proof: zkproof, + merkleRoot: proofRoot, + epoch: epoch, + shareX: shareX, + shareY: shareY, + nullifier: nullifier) - return ok(output) + 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 - ## the order of serialization is based on https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L205 - ## [ proof<256>| root<32>| epoch<32>| share_x<32>| share_y<32>| nullifier<32> | signal_len<8> | signal ] - let lenPrefMsg = appendLength(@data) - var proofBytes = concat(@(proof.proof), - @(proof.merkleRoot), - @(proof.epoch), - @(proof.shareX), - @(proof.shareY), - @(proof.nullifier), - lenPrefMsg) + 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 + ## [ proof<256>| root<32>| epoch<32>| share_x<32>| share_y<32>| nullifier<32> | signal_len<8> | signal ] + let lenPrefMsg = appendLength(@data) + var proofBytes = concat(@(proof.proof), + @(proof.merkleRoot), + @(proof.epoch), + @(proof.shareX), + @(proof.shareY), + @(proof.nullifier), + lenPrefMsg) - return proofBytes + 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) - proofBuffer = proofBytes.toBuffer() - f = 0.uint32 - trace "serialized proof", proof = proofBytes.toHex() + var + proofBytes = serialize(proof, data) + proofBuffer = proofBytes.toBuffer() + f = 0.uint32 + trace "serialized proof", proof = proofBytes.toHex() - let verifyIsSuccessful = verify(rlnInstance, addr proofBuffer, addr f) - if not verifyIsSuccessful: - # something went wrong in verification + let verifyIsSuccessful = verify(rlnInstance, addr proofBuffer, addr f) + if not verifyIsSuccessful: + # something went wrong in verification + return false + # f = 0 means the proof is verified + if f == 0: + return true return false - # f = 0 means the proof is verified - if f == 0: - return true - return false -proc insertMember*(rlnInstance: RLN[Bn256], idComm: IDCommitment): bool = - var pkBuffer = toBuffer(idComm) - let pkBufferPtr = addr pkBuffer + proc insertMember*(rlnInstance: RLN[Bn256], 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 + # add the member to the tree + var member_is_added = update_next_member(rlnInstance, pkBufferPtr) + return member_is_added -proc removeMember*(rlnInstance: RLN[Bn256], index: MembershipIndex): bool = - let deletion_success = delete_member(rlnInstance, index) - return deletion_success + proc removeMember*(rlnInstance: RLN[Bn256], index: MembershipIndex): bool = + let deletion_success = delete_member(rlnInstance, index) + return deletion_success -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") + 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) + 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 ] + 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() + 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) proc toMembershipKeyPairs*(groupKeys: seq[(string, string)]): seq[ MembershipKeyPair] {.raises: [Defect, ValueError].} = @@ -601,14 +794,25 @@ proc appendRLNProof*(rlnPeer: WakuRLNRelay, msg: var WakuMessage, msg.proof = proof.value return true -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(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] + let member_is_added = rlnInstance.insertMember(member) + if not member_is_added: + return false + return true # the types of inputs to this handler matches the MemberRegistered event/proc defined in the MembershipContract interface type RegistrationEventHandler = proc(pubkey: Uint256, index: Uint256): void {.gcsafe, closure, raises: [Defect].} @@ -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 @@ -904,4 +1109,4 @@ proc mountRlnRelay*(node: WakuNode, conf: WakuNodeConf|Chat2Conf, spamHandler: O info "no rln credential is provided" waitFor node.mountRlnRelayDynamic(memContractAddr = ethMemContractAddress, ethClientAddr = ethClientAddr, ethAccAddr = ethAccountAddr, ethAccountPrivKeyOpt = ethAccountPrivKeyOpt, pubsubTopic = conf.rlnRelayPubsubTopic, - contentTopic = conf.rlnRelayContentTopic, spamHandler = spamHandler) + contentTopic = conf.rlnRelayContentTopic, spamHandler = spamHandler) \ No newline at end of file