diff --git a/Makefile b/Makefile index ff9b3e6f4..596d77f0a 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,10 @@ else NIM_PARAMS := $(NIM_PARAMS) -d:release endif +ifeq ($(RLN), true) +NIM_PARAMS := $(NIM_PARAMS) -d:rln +endif + deps: | deps-common nat-libs waku.nims rlnlib ifneq ($(USE_LIBBACKTRACE), 0) deps: | libbacktrace @@ -116,11 +120,15 @@ else endif installganache: +ifeq ($(RLN), true) npm install ganache-cli; npx ganache-cli -p 8540 -g 0 -l 3000000000000& +endif rlnlib: +ifeq ($(RLN), true) cargo build --manifest-path vendor/rln/Cargo.toml - +endif + test2: | build deps installganache echo -e $(BUILD_MSG) "build/$@" && \ $(ENV_SCRIPT) nim test2 $(NIM_PARAMS) waku.nims diff --git a/tests/all_tests_v2.nim b/tests/all_tests_v2.nim index c2452e3ba..5ccb64e95 100644 --- a/tests/all_tests_v2.nim +++ b/tests/all_tests_v2.nim @@ -12,11 +12,14 @@ import ./v2/test_jsonrpc_waku, ./v2/test_peer_manager, ./v2/test_web3, # TODO remove it when rln-relay tests get finalized - ./v2/test_waku_rln_relay, ./v2/test_waku_bridge, ./v2/test_peer_storage, ./v2/test_waku_keepalive +when defined(rln): + import ./v2/test_waku_rln_relay + + # TODO Only enable this once swap module is integrated more nicely as a dependency, i.e. as submodule with CI etc # For PoC execute it manually and run separate module here: https://github.com/vacp2p/swap-contracts-module # ./v2/test_waku_swap_contracts diff --git a/tests/v2/test_waku_rln_relay.nim b/tests/v2/test_waku_rln_relay.nim index e78bdc635..8c2b078b4 100644 --- a/tests/v2/test_waku_rln_relay.nim +++ b/tests/v2/test_waku_rln_relay.nim @@ -1,3 +1,4 @@ + {.used.} import @@ -5,11 +6,12 @@ import testutils/unittests, chronos, chronicles, stint, web3, stew/byteutils, stew/shims/net as stewNet, libp2p/crypto/crypto, - ../../waku/v2/protocol/waku_rln_relay/[rln, waku_rln_relay_utils], + ../../waku/v2/protocol/waku_rln_relay/[rln, waku_rln_relay_utils, waku_rln_relay_types], ../../waku/v2/node/wakunode2, ../test_helpers, ./test_utils + # the address of Ethereum client (ganache-cli for now) # TODO this address in hardcoded in the code, we may need to take it as input from the user const EthClient = "ws://localhost:8540/" @@ -266,7 +268,7 @@ suite "Waku rln relay": # 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 @@ -284,7 +286,7 @@ suite "Waku rln relay": key.get().publicKey != empty debug "the generated membership key pair: ", key - + test "get_root Nim binding": # create an RLN instance which also includes an empty Merkle tree var @@ -334,7 +336,7 @@ suite "Waku rln relay": var member_is_added = update_next_member(ctxPtr, pkBufferPtr) check: member_is_added == true - + test "delete_member Nim wrapper": # create an RLN instance which also includes an empty Merkle tree var @@ -346,7 +348,7 @@ suite "Waku rln relay": var deleted_member_index = uint(0) let deletion_success = delete_member(ctxPtr, deleted_member_index) doAssert(deletion_success) - + test "Merkle tree consistency check between deletion and insertion": # create an RLN instance var @@ -534,15 +536,12 @@ suite "Waku rln relay": debug "shareY", shareY debug "nullifier", nullifier - var f = 0.uint32 let verifyIsSuccessful = verify(ctxPtr, addr proof, addr f) doAssert(verifyIsSuccessful) # f = 0 means the proof is verified doAssert(f == 0) - - # create and test a bad proof # prepare a bad authentication object with a wrong peer's index var badIndex = 8 diff --git a/tests/v2/test_wakunode.nim b/tests/v2/test_wakunode.nim index 299ac237a..92974eff1 100644 --- a/tests/v2/test_wakunode.nim +++ b/tests/v2/test_wakunode.nim @@ -524,61 +524,62 @@ procSuite "WakuNode": await node2.stop() await node3.stop() - asyncTest "testing rln-relay with mocked zkp": + when defined(rln): + asyncTest "testing rln-relay with mocked zkp": - let - # publisher node - nodeKey1 = crypto.PrivateKey.random(Secp256k1, rng[])[] - node1 = WakuNode.init(nodeKey1, ValidIpAddress.init("0.0.0.0"), Port(60000)) - # Relay node - nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[] - node2 = WakuNode.init(nodeKey2, ValidIpAddress.init("0.0.0.0"), Port(60002)) - # Subscriber - nodeKey3 = crypto.PrivateKey.random(Secp256k1, rng[])[] - node3 = WakuNode.init(nodeKey3, ValidIpAddress.init("0.0.0.0"), Port(60003)) + let + # publisher node + nodeKey1 = crypto.PrivateKey.random(Secp256k1, rng[])[] + node1 = WakuNode.init(nodeKey1, ValidIpAddress.init("0.0.0.0"), Port(60000)) + # Relay node + nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[] + node2 = WakuNode.init(nodeKey2, ValidIpAddress.init("0.0.0.0"), Port(60002)) + # Subscriber + nodeKey3 = crypto.PrivateKey.random(Secp256k1, rng[])[] + node3 = WakuNode.init(nodeKey3, ValidIpAddress.init("0.0.0.0"), Port(60003)) - pubSubTopic = "defaultTopic" - contentTopic1 = ContentTopic("/waku/2/default-content/proto") - payload = "hello world".toBytes() - message1 = WakuMessage(payload: payload, contentTopic: contentTopic1) + pubSubTopic = "defaultTopic" + contentTopic1 = ContentTopic("/waku/2/default-content/proto") + payload = "hello world".toBytes() + message1 = WakuMessage(payload: payload, contentTopic: contentTopic1) - # start all the nodes - await node1.start() - node1.mountRelay(@[pubSubTopic]) + # start all the nodes + await node1.start() + node1.mountRelay(@[pubSubTopic]) - await node2.start() - node2.mountRelay(@[pubSubTopic]) - node2.addRLNRelayValidator(pubSubTopic) + await node2.start() + node2.mountRelay(@[pubSubTopic]) + node2.addRLNRelayValidator(pubSubTopic) - await node3.start() - node3.mountRelay(@[pubSubTopic]) + await node3.start() + node3.mountRelay(@[pubSubTopic]) - await node1.connectToNodes(@[node2.peerInfo]) - await node3.connectToNodes(@[node2.peerInfo]) + await node1.connectToNodes(@[node2.peerInfo]) + await node3.connectToNodes(@[node2.peerInfo]) - var completionFut = newFuture[bool]() - proc relayHandler(topic: string, data: seq[byte]) {.async, gcsafe.} = - let msg = WakuMessage.init(data) - if msg.isOk(): - let val = msg.value() - debug "The received topic:", topic - if topic == pubSubTopic: - completionFut.complete(true) + var completionFut = newFuture[bool]() + proc relayHandler(topic: string, data: seq[byte]) {.async, gcsafe.} = + let msg = WakuMessage.init(data) + if msg.isOk(): + let val = msg.value() + debug "The received topic:", topic + if topic == pubSubTopic: + completionFut.complete(true) - node3.subscribe(pubSubTopic, relayHandler) - await sleepAsync(2000.millis) + node3.subscribe(pubSubTopic, relayHandler) + await sleepAsync(2000.millis) - await node1.publish(pubSubTopic, message1, rlnRelayEnabled = true) - await sleepAsync(2000.millis) + await node1.publish(pubSubTopic, message1, rlnRelayEnabled = true) + await sleepAsync(2000.millis) - check: - (await completionFut.withTimeout(10.seconds)) == true - - await node1.stop() - await node2.stop() - await node3.stop() + check: + (await completionFut.withTimeout(10.seconds)) == true + + await node1.stop() + await node2.stop() + await node3.stop() asyncTest "Relay protocol is started correctly": let diff --git a/waku/v2/node/wakunode2.nim b/waku/v2/node/wakunode2.nim index bcbf994a6..4bd4f798c 100644 --- a/waku/v2/node/wakunode2.nim +++ b/waku/v2/node/wakunode2.nim @@ -16,8 +16,8 @@ import ../protocol/waku_store/waku_store, ../protocol/waku_swap/waku_swap, ../protocol/waku_filter/waku_filter, - ../protocol/waku_rln_relay/[rln,waku_rln_relay_utils], ../protocol/waku_lightpush/waku_lightpush, + ../protocol/waku_rln_relay/waku_rln_relay_types, ../protocol/waku_keepalive/waku_keepalive, ../utils/peers, ./storage/message/message_store, @@ -25,6 +25,9 @@ import ../utils/requests, ./peer_manager/peer_manager +when defined(rln): + import ../protocol/waku_rln_relay/[rln, waku_rln_relay_utils] + declarePublicCounter waku_node_messages, "number of messages received", ["type"] declarePublicGauge waku_node_filters, "number of content filter subscriptions" declarePublicGauge waku_node_errors, "number of wakunode errors", ["type"] @@ -304,13 +307,14 @@ proc publish*(node: WakuNode, topic: Topic, message: WakuMessage, rlnRelayEnabl debug "publish", topic=topic, contentTopic=message.contentTopic var publishingMessage = message - if rlnRelayEnabled: - # if rln relay is enabled then a proof must be generated and added to the waku message - let - proof = proofGen(message.payload) - ## TODO here since the message is immutable we have to make a copy of it and then attach the proof to its duplicate - ## TODO however, it might be better to change message type to mutable (i.e., var) so that we can add the proof field to the original message - publishingMessage = WakuMessage(payload: message.payload, contentTopic: message.contentTopic, version: message.version, proof: proof) + when defined(rln): + if rlnRelayEnabled: + # if rln relay is enabled then a proof must be generated and added to the waku message + let + proof = proofGen(message.payload) + ## TODO here since the message is immutable we have to make a copy of it and then attach the proof to its duplicate + ## TODO however, it might be better to change message type to mutable (i.e., var) so that we can add the proof field to the original message + publishingMessage = WakuMessage(payload: message.payload, contentTopic: message.contentTopic, version: message.version, proof: proof) let data = message.encode().buffer @@ -408,52 +412,54 @@ proc mountStore*(node: WakuNode, store: MessageStore = nil, persistMessages: boo if persistMessages: node.subscriptions.subscribe(WakuStoreCodec, node.wakuStore.subscription()) -proc mountRlnRelay*(node: WakuNode, ethClientAddress: Option[string] = none(string), ethAccountAddress: Option[Address] = none(Address), membershipContractAddress: Option[Address] = none(Address)) {.async.} = - # TODO return a bool value to indicate the success of the call - # check whether inputs are provided - doAssert(ethClientAddress.isSome()) - doAssert(ethAccountAddress.isSome()) - doAssert(membershipContractAddress.isSome()) +when defined(rln): + proc mountRlnRelay*(node: WakuNode, ethClientAddress: Option[string] = none(string), ethAccountAddress: Option[Address] = none(Address), membershipContractAddress: Option[Address] = none(Address)) {.async.} = + # TODO return a bool value to indicate the success of the call + # check whether inputs are provided + doAssert(ethClientAddress.isSome()) + doAssert(ethAccountAddress.isSome()) + doAssert(membershipContractAddress.isSome()) - # create an RLN instance - var - ctx = RLN[Bn256]() - ctxPtr = addr(ctx) - doAssert(createRLNInstance(32, ctxPtr)) + # create an RLN instance + var + ctx = RLN[Bn256]() + ctxPtr = addr(ctx) + doAssert(createRLNInstance(32, ctxPtr)) - # generate the membership keys - let membershipKeyPair = membershipKeyGen(ctxPtr) - # check whether keys are generated - doAssert(membershipKeyPair.isSome()) - debug "the membership key for the rln relay is generated" + # generate the membership keys + let membershipKeyPair = membershipKeyGen(ctxPtr) + # check whether keys are generated + doAssert(membershipKeyPair.isSome()) + debug "the membership key for the rln relay is generated" - # initialize the WakuRLNRelay - var rlnPeer = WakuRLNRelay(membershipKeyPair: membershipKeyPair.get(), - ethClientAddress: ethClientAddress.get(), - ethAccountAddress: ethAccountAddress.get(), - membershipContractAddress: membershipContractAddress.get()) - - # register the rln-relay peer to the membership contract - let is_successful = await rlnPeer.register() - # check whether registration is done - doAssert(is_successful) - debug "peer is successfully registered into the membership contract" + # initialize the WakuRLNRelay + var rlnPeer = WakuRLNRelay(membershipKeyPair: membershipKeyPair.get(), + ethClientAddress: ethClientAddress.get(), + ethAccountAddress: ethAccountAddress.get(), + membershipContractAddress: membershipContractAddress.get()) + + # register the rln-relay peer to the membership contract + let is_successful = await rlnPeer.register() + # check whether registration is done + doAssert(is_successful) + debug "peer is successfully registered into the membership contract" - node.wakuRlnRelay = rlnPeer + node.wakuRlnRelay = rlnPeer -proc addRLNRelayValidator*(node: WakuNode, pubsubTopic: string) = - ## this procedure is a thin wrapper for the pubsub addValidator method - ## it sets message validator on the given pubsubTopic, the validator will check that - ## all the messages published in the pubsubTopic have a valid zero-knowledge proof - proc validator(topic: string, message: messages.Message): Future[ValidationResult] {.async.} = - let msg = WakuMessage.init(message.data) - if msg.isOk(): - # check the proof - if proofVrfy(msg.value().payload, msg.value().proof): - result = ValidationResult.Accept - # set a validator for the pubsubTopic - let pb = PubSub(node.wakuRelay) - pb.addValidator(pubsubTopic, validator) +when defined(rln): + proc addRLNRelayValidator*(node: WakuNode, pubsubTopic: string) = + ## this procedure is a thin wrapper for the pubsub addValidator method + ## it sets message validator on the given pubsubTopic, the validator will check that + ## all the messages published in the pubsubTopic have a valid zero-knowledge proof + proc validator(topic: string, message: messages.Message): Future[ValidationResult] {.async.} = + let msg = WakuMessage.init(message.data) + if msg.isOk(): + # check the proof + if proofVrfy(msg.value().payload, msg.value().proof): + result = ValidationResult.Accept + # set a validator for the pubsubTopic + let pb = PubSub(node.wakuRelay) + pb.addValidator(pubsubTopic, validator) proc mountRelay*(node: WakuNode, topics: seq[string] = newSeq[string](), @@ -494,14 +500,14 @@ proc mountRelay*(node: WakuNode, # Reconnect to previous relay peers. This will respect a backoff period, if necessary waitFor node.peerManager.reconnectPeers(WakuRelayCodec, wakuRelay.parameters.pruneBackoff + chronos.seconds(BackoffSlackTime)) - - if rlnRelayEnabled: - # TODO pass rln relay inputs to this proc, right now it uses default values that are set in the mountRlnRelay proc - info "WakuRLNRelay is enabled" - waitFor mountRlnRelay(node) - # TODO currently the message validator is set for the defaultTopic, this can be configurable to accept other pubsub topics as well - addRLNRelayValidator(node, defaultTopic) - info "WakuRLNRelay is mounted successfully" + when defined(rln): + if rlnRelayEnabled: + # TODO pass rln relay inputs to this proc, right now it uses default values that are set in the mountRlnRelay proc + info "WakuRLNRelay is enabled" + waitFor mountRlnRelay(node) + # TODO currently the message validator is set for the defaultTopic, this can be configurable to accept other pubsub topics as well + addRLNRelayValidator(node, defaultTopic) + info "WakuRLNRelay is mounted successfully" if node.started: # Node has already started. Start the WakuRelay protocol diff --git a/waku/v2/protocol/waku_rln_relay/rln.nim b/waku/v2/protocol/waku_rln_relay/rln.nim index d4851fbf8..51fe2ba28 100644 --- a/waku/v2/protocol/waku_rln_relay/rln.nim +++ b/waku/v2/protocol/waku_rln_relay/rln.nim @@ -1,7 +1,8 @@ # this module contains the Nim wrappers for the rln library https://github.com/kilic/rln/blob/3bbec368a4adc68cd5f9bfae80b17e1bbb4ef373/src/ffi.rs -import os - +import + os, + waku_rln_relay_types const libPath = "vendor/rln/target/debug/" when defined(Windows): @@ -11,11 +12,9 @@ elif defined(Linux): elif defined(MacOsX): const libName* = libPath / "librln.dylib" - # all the following procedures are Nim wrappers for the functions defined in libName +# all the following procedures are Nim wrappers for the functions defined in libName {.push dynlib: libName, raises: [Defect].} -type RLN*[E] {.incompleteStruct.} = object -type Bn256* = pointer ## Buffer struct is taken from # https://github.com/celo-org/celo-threshold-bls-rs/blob/master/crates/threshold-bls-ffi/src/ffi.rs @@ -26,11 +25,11 @@ type Buffer* = object type Auth* = object secret_buffer*: ptr Buffer index*: uint - + #------------------------------ Merkle Tree operations ----------------------------------------- proc update_next_member*(ctx: ptr RLN[Bn256], - input_buffer: ptr Buffer): bool {.importc: "update_next_member".} + input_buffer: ptr Buffer): bool {.importc: "update_next_member".} proc delete_member*(ctx: ptr RLN[Bn256], index: uint): bool {.importc: "delete_member".} @@ -41,13 +40,13 @@ proc get_root*(ctx: ptr RLN[Bn256], output_buffer: ptr Buffer): bool {.importc: proc key_gen*(ctx: ptr RLN[Bn256], keypair_buffer: ptr Buffer): bool {.importc: "key_gen".} proc generate_proof*(ctx: ptr RLN[Bn256], - input_buffer: ptr Buffer, - auth: ptr Auth, - output_buffer: ptr Buffer): bool {.importc: "generate_proof".} + input_buffer: ptr Buffer, + auth: ptr Auth, + output_buffer: ptr Buffer): bool {.importc: "generate_proof".} proc verify*(ctx: ptr RLN[Bn256], - proof_buffer: ptr Buffer, - result_ptr: ptr uint32): bool {.importc: "verify".} + proof_buffer: ptr Buffer, + result_ptr: ptr uint32): bool {.importc: "verify".} #---------------------------------------------------------------------------------------------- #-------------------------------- Common procedures ------------------------------------------- @@ -56,7 +55,7 @@ proc new_circuit_from_params*(merkle_depth: uint, ctx: ptr (ptr RLN[Bn256])): bool {.importc: "new_circuit_from_params".} proc hash*(ctx: ptr RLN[Bn256], - inputs_buffer: ptr Buffer, - input_len: uint, - output_buffer: ptr Buffer): bool {.importc: "hash".} + inputs_buffer: ptr Buffer, + input_len: uint, + output_buffer: ptr Buffer): bool {.importc: "hash".} {.pop.} \ No newline at end of file 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 new file mode 100644 index 000000000..28cdfe45b --- /dev/null +++ b/waku/v2/protocol/waku_rln_relay/waku_rln_relay_types.nim @@ -0,0 +1,32 @@ +import + chronicles, options, chronos, stint, + web3, + eth/keys + +## 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] {.incompleteStruct.} = object + +# Custom data types defined for waku rln relay ------------------------- +type MembershipKeyPair* = object + secretKey*: array[32, byte] + publicKey*: array[32, byte] + +type WakuRLNRelay* = object + membershipKeyPair*: MembershipKeyPair + 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] + membershipContractAddress*: Address + +# 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 + MembershipFee* = 5.u256 + Depth* = 32.u256 + # TODO the EthClient should be an input to the rln-relay + EthClient* = "ws://localhost:8540/" \ 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 05b963052..e6b5b86ea 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 @@ -2,29 +2,8 @@ import chronicles, options, chronos, stint, web3, stew/byteutils, - eth/keys, - rln - -type MembershipKeyPair* = object - secretKey*: array[32, byte] - publicKey*: array[32, byte] - -type WakuRLNRelay* = object - membershipKeyPair*: MembershipKeyPair - 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] - membershipContractAddress*: Address - -# inputs of the membership contract constructor -const - MembershipFee* = 5.u256 - Depth* = 32.u256 - # TODO the EthClient should be an input to the rln-relay - EthClient* = "ws://localhost:8540/" + rln, + waku_rln_relay_types # membership contract interface contract(MembershipContract):