From b7998de09d1ef04599a699938da69aecfa63cc6f Mon Sep 17 00:00:00 2001 From: Sanaz Taheri Boshrooyeh <35961250+staheri14@users.noreply.github.com> Date: Thu, 26 Aug 2021 16:14:51 -0700 Subject: [PATCH] RLN-Relay static group construction (#708) * tests rln instance as pointer * test insertion * tests deletion * tests proof generation and verification * updates rln instance type and the rln api * deletes old API * removes temporary tests * deletes unused codes * Delete settings.json * reverts the changes in tests v2 * removes an old comment * adds member insertion and deletion * adds getMerkleRoot and unit tests * makes insertMember argument a value type * adds static group creation * adds rln field to WakuRLNRelay type, and enables static group construction inside mountRlnRelay * renames self to membershipKeyPair and removes key generation from mountRlnRelay * renames sk,pk tp idKey and idCommitment * updates mountRlnRelay arguments name * logs created keys * uncomments the key generation and adds explainer about it * enables all the tests * adds comments to the rln relay types * logs error message for the arguments that are not set --- tests/v2/test_waku_rln_relay.nim | 141 ++++++++++++++++-- waku/v2/node/wakunode2.nim | 76 ++++++++-- .../waku_rln_relay/waku_rln_relay_types.nim | 18 ++- .../waku_rln_relay/waku_rln_relay_utils.nim | 33 +++- 4 files changed, 236 insertions(+), 32 deletions(-) diff --git a/tests/v2/test_waku_rln_relay.nim b/tests/v2/test_waku_rln_relay.nim index 465f757df..2a169a0e3 100644 --- a/tests/v2/test_waku_rln_relay.nim +++ b/tests/v2/test_waku_rln_relay.nim @@ -192,6 +192,7 @@ procSuite "Waku rln relay": # initialize the WakuRLNRelay var rlnPeer = WakuRLNRelay(membershipKeyPair: membershipKeyPair.get(), + membershipIndex: uint(0), ethClientAddress: EthClient, ethAccountAddress: ethAccountAddress, membershipContractAddress: contractAddress) @@ -218,8 +219,43 @@ procSuite "Waku rln relay": ethAccountAddress = accounts[9] await web3.close() + # create current peer's pk + var rlnInstance = createRLNInstance(32) + check rlnInstance.isOk == true + var rln = rlnInstance.value + # generate a key pair + var keypair = rln.membershipKeyGen() + doAssert(keypair.isSome()) + + # current peer index in the Merkle tree + let index = uint(5) + + # Create a group of 10 members + var group = newSeq[IDCommitment]() + for i in 0..10: + var member_is_added: bool = false + if (uint(i) == index): + # insert the current peer's pk + group.add(keypair.get().idCommitment) + member_is_added = rln.insertMember(keypair.get().idCommitment) + doAssert(member_is_added) + debug "member key", key=keypair.get().idCommitment.toHex + else: + var memberKeypair = rln.membershipKeyGen() + doAssert(memberKeypair.isSome()) + group.add(memberKeypair.get().idCommitment) + member_is_added = rln.insertMember(memberKeypair.get().idCommitment) + doAssert(member_is_added) + debug "member key", key=memberKeypair.get().idCommitment.toHex + let expectedRoot = rln.getMerkleRoot().value().toHex + debug "expected root ", expectedRoot + # start rln-relay - await node.mountRlnRelay(ethClientAddress = some(EthClient), ethAccountAddress = some(ethAccountAddress), membershipContractAddress = some(membershipContractAddress)) + await node.mountRlnRelay(ethClientAddrOpt = some(EthClient), ethAccAddrOpt = some(ethAccountAddress), memContractAddOpt = some(membershipContractAddress), groupOpt = some(group), memKeyPairOpt = some(keypair.get()), memIndexOpt = some(index)) + let calculatedRoot = node.wakuRlnRelay.rlnInstance.getMerkleRoot().value().toHex + debug "calculated root ", calculatedRoot + + check expectedRoot == calculatedRoot await node.stop() @@ -274,10 +310,10 @@ suite "Waku rln relay": var empty : array[32,byte] check: key.isSome - key.get().secretKey.len == 32 - key.get().publicKey.len == 32 - key.get().secretKey != empty - key.get().publicKey != empty + key.get().idKey.len == 32 + key.get().idCommitment.len == 32 + key.get().idKey != empty + key.get().idCommitment != empty debug "the generated membership key pair: ", key @@ -309,6 +345,24 @@ suite "Waku rln relay": var rootValue2 = cast[ptr array[32,byte]] (root2.`ptr`) let rootHex2 = rootValue2[].toHex + # the two roots must be identical + doAssert(rootHex1 == rootHex2) + test "getMerkleRoot utils": + # create an RLN instance which also includes an empty Merkle tree + var rlnInstance = createRLNInstance(32) + check: + rlnInstance.isOk == true + + # read the Merkle Tree root + var root1 = getMerkleRoot(rlnInstance.value()) + doAssert(root1.isOk) + let rootHex1 = root1.value().toHex + + # read the Merkle Tree root + var root2 = getMerkleRoot(rlnInstance.value()) + doAssert(root2.isOk) + let rootHex2 = root2.value().toHex + # the two roots must be identical doAssert(rootHex1 == rootHex2) @@ -321,7 +375,7 @@ suite "Waku rln relay": # generate a key pair var keypair = membershipKeyGen(rlnInstance.value) doAssert(keypair.isSome()) - var pkBuffer = Buffer(`ptr`: addr(keypair.get().publicKey[0]), len: 32) + var pkBuffer = Buffer(`ptr`: addr(keypair.get().idCommitment[0]), len: 32) let pkBufferPtr = addr pkBuffer # add the member to the tree @@ -340,6 +394,27 @@ suite "Waku rln relay": let deletion_success = delete_member(rlnInstance.value, deleted_member_index) doAssert(deletion_success) + test "insertMember rln utils": + # create an RLN instance which also includes an empty Merkle tree + var rlnInstance = createRLNInstance(32) + check: + rlnInstance.isOk == true + var rln = rlnInstance.value + # generate a key pair + var keypair = rln.membershipKeyGen() + doAssert(keypair.isSome()) + check: + rln.insertMember(keypair.get().idCommitment) + + test "removeMember rln utils": + # create an RLN instance which also includes an empty Merkle tree + var rlnInstance = createRLNInstance(32) + check: + rlnInstance.isOk == true + var rln = rlnInstance.value + check: + rln.removeMember(uint(0)) + test "Merkle tree consistency check between deletion and insertion": # create an RLN instance var rlnInstance = createRLNInstance(32) @@ -357,7 +432,7 @@ suite "Waku rln relay": # generate a key pair var keypair = membershipKeyGen(rlnInstance.value) doAssert(keypair.isSome()) - var pkBuffer = Buffer(`ptr`: addr(keypair.get().publicKey[0]), len: 32) + var pkBuffer = Buffer(`ptr`: addr(keypair.get().idCommitment[0]), len: 32) let pkBufferPtr = addr pkBuffer # add the member to the tree @@ -403,6 +478,52 @@ suite "Waku rln relay": ## The initial root of the tree (empty tree) must be identical to ## the root of the tree after one insertion followed by a deletion doAssert(rootHex1 == rootHex3) + test "Merkle tree consistency check between deletion and insertion using rln utils": + # create an RLN instance + var rlnInstance = createRLNInstance(32) + check: + rlnInstance.isOk == true + var rln = rlnInstance.value() + + # read the Merkle Tree root + var root1 = rln.getMerkleRoot() + doAssert(root1.isOk) + let rootHex1 = root1.value().toHex() + + # generate a key pair + var keypair = rln.membershipKeyGen() + doAssert(keypair.isSome()) + let member_inserted = rln.insertMember(keypair.get().idCommitment) + check member_inserted + + # read the Merkle Tree root after insertion + var root2 = rln.getMerkleRoot() + doAssert(root2.isOk) + let rootHex2 = root2.value().toHex() + + + # delete the first member + var deleted_member_index = uint(0) + let deletion_success = rln.removeMember(deleted_member_index) + doAssert(deletion_success) + + # read the Merkle Tree root after the deletion + var root3 = rln.getMerkleRoot() + doAssert(root3.isOk) + let rootHex3 = root3.value().toHex() + + + debug "The initial root", rootHex1 + debug "The root after insertion", rootHex2 + debug "The root after deletion", rootHex3 + + # the root must change after the insertion + doAssert(not(rootHex1 == rootHex2)) + + ## The initial root of the tree (empty tree) must be identical to + ## the root of the tree after one insertion followed by a deletion + doAssert(rootHex1 == rootHex3) + test "hash Nim Wrappers": # create an RLN instance var rlnInstance = createRLNInstance(32) @@ -446,7 +567,7 @@ suite "Waku rln relay": # create the membership key var auth = membershipKeyGen(rlnInstance.value) - var skBuffer = Buffer(`ptr`: addr(auth.get().secretKey[0]), len: 32) + var skBuffer = Buffer(`ptr`: addr(auth.get().idKey[0]), len: 32) # peer's index in the Merkle Tree var index = 5 @@ -459,11 +580,11 @@ suite "Waku rln relay": var member_is_added: bool = false if (i == index): # insert the current peer's pk - var pkBuffer = Buffer(`ptr`: addr(auth.get().publicKey[0]), len: 32) + var pkBuffer = Buffer(`ptr`: addr(auth.get().idCommitment[0]), len: 32) member_is_added = update_next_member(rlnInstance.value, addr pkBuffer) else: var memberKeys = membershipKeyGen(rlnInstance.value) - var pkBuffer = Buffer(`ptr`: addr(memberKeys.get().publicKey[0]), len: 32) + var pkBuffer = Buffer(`ptr`: addr(memberKeys.get().idCommitment[0]), len: 32) member_is_added = update_next_member(rlnInstance.value, addr pkBuffer) # check the member is added doAssert(member_is_added) diff --git a/waku/v2/node/wakunode2.nim b/waku/v2/node/wakunode2.nim index 240d3cca1..62079e7bc 100644 --- a/waku/v2/node/wakunode2.nim +++ b/waku/v2/node/wakunode2.nim @@ -410,30 +410,74 @@ proc mountStore*(node: WakuNode, store: MessageStore = nil, persistMessages: boo when defined(rln): proc mountRlnRelay*(node: WakuNode, - ethClientAddress: Option[string] = none(string), - ethAccountAddress: Option[Address] = none(Address), - membershipContractAddress: Option[Address] = none(Address)) {.async.} = + ethClientAddrOpt: Option[string] = none(string), + ethAccAddrOpt: Option[Address] = none(Address), + memContractAddOpt: Option[Address] = none(Address), + groupOpt: Option[seq[IDCommitment]] = none(seq[IDCommitment]), + memKeyPairOpt: Option[MembershipKeyPair] = none(MembershipKeyPair), + memIndexOpt: Option[uint] = none(uint)) {.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()) + if ethClientAddrOpt.isNone(): + info "failed to mount rln relay: Ethereum client address is not provided" + return + if ethAccAddrOpt.isNone(): + info "failed to mount rln relay: Ethereum account address is not provided" + return + if memContractAddOpt.isNone(): + info "failed to mount rln relay: membership contract address is not provided" + return + if groupOpt.isNone(): + # TODO this check is not necessary for a dynamic group + info "failed to mount rln relay: group information is not provided" + return + if memKeyPairOpt.isNone(): + info "failed to mount rln relay: membership key of the node is not provided" + return + if memIndexOpt.isNone(): + info "failed to mount rln relay: membership index is not provided" + return + + let + ethClientAddr = ethClientAddrOpt.get() + ethAccAddr = ethAccAddrOpt.get() + memContractAdd = memContractAddOpt.get() + group = groupOpt.get() + memKeyPair = memKeyPairOpt.get() + memIndex = memIndexOpt.get() + + + # check the peer's index and the inclusion of user's identity commitment in the group + doAssert((memKeyPair.idCommitment) == group[int(memIndex)]) # create an RLN instance var rlnInstance = createRLNInstance(32) doAssert(rlnInstance.isOk) + var rln = rlnInstance.value - # generate the membership keys - let membershipKeyPair = membershipKeyGen(rlnInstance.value) - # check whether keys are generated - doAssert(membershipKeyPair.isSome()) - debug "the membership key for the rln relay is generated" + # generate the membership keys if none is provided + # this if condition never gets through for a static group of users + # the node should pass its keys i.e., memKeyPairOpt to the function + if not memKeyPairOpt.isSome: + let membershipKeyPair = rln.membershipKeyGen() + # check whether keys are generated + doAssert(membershipKeyPair.isSome()) + debug "the membership key for the rln relay is generated", idKey=membershipKeyPair.get().idKey.toHex, idCommitment=membershipKeyPair.get().idCommitment.toHex - # initialize the WakuRLNRelay - var rlnPeer = WakuRLNRelay(membershipKeyPair: membershipKeyPair.get(), - ethClientAddress: ethClientAddress.get(), - ethAccountAddress: ethAccountAddress.get(), - membershipContractAddress: membershipContractAddress.get()) + + # add members to the Merkle tree + for index in 0..group.len-1: + let member = group[index] + let member_is_added = rln.insertMember(member) + doAssert(member_is_added) + + # create the WakuRLNRelay + var rlnPeer = WakuRLNRelay(membershipKeyPair: memKeyPair, + membershipIndex: memIndex, + membershipContractAddress: memContractAdd, + ethClientAddress: ethClientAddr, + ethAccountAddress: ethAccAddr, + rlnInstance: rln) # register the rln-relay peer to the membership contract let is_successful = await rlnPeer.register() 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 08cf3d42e..19fa54f97 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 @@ -11,20 +11,32 @@ type Bn256* = pointer type RLN*[E] = pointer +type IDKey* = array[32, byte] +type IDCommitment* = array[32, byte] +# represents a Merkle tree node which is the output of +# Poseidon hash function implemented by rln lib +type MerkleNode* = array[32,byte] + # Custom data types defined for waku rln relay ------------------------- type MembershipKeyPair* = object - secretKey*: array[32, byte] - publicKey*: array[32, byte] + # node's identity key (a secret key) which is selected randomly + idKey*: IDKey + # hash of node's identity key generated by + # Poseidon hash function implemented in rln lib + idCommitment*: IDCommitment type WakuRLNRelay* = object membershipKeyPair*: MembershipKeyPair + membershipIndex*: uint # index of peers in the Merkle tree + 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] - membershipContractAddress*: Address + rlnInstance*: RLN[Bn256] + # inputs of the membership contract constructor # TODO may be able to make these constants private and put them inside the waku_rln_relay_utils 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 0cce6b8e9..cd8bc51ad 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 @@ -12,6 +12,7 @@ logScope: topics = "wakurlnrelayutils" type RLNResult* = Result[RLN[Bn256], string] +type MerkleNodeResult* = Result[MerkleNode, string] # membership contract interface contract(MembershipContract): # TODO define a return type of bool for register method to signify a successful registration @@ -77,7 +78,7 @@ proc membershipKeyGen*(ctxPtr: RLN[Bn256]): Option[MembershipKeyPair] = for (i,x) in public.mpairs: x = generatedKeys[i+32] var - keypair = MembershipKeyPair(secretKey: secret, publicKey: public) + keypair = MembershipKeyPair(idKey: secret, idCommitment: public) return some(keypair) @@ -90,7 +91,7 @@ proc register*(rlnPeer: WakuRLNRelay): Future[bool] {.async.} = # does the signing using the provided key web3.privateKey = rlnPeer.ethAccountPrivateKey var sender = web3.contractSender(MembershipContract, rlnPeer.membershipContractAddress) # creates a Sender object with a web3 field and contract address of type Address - let pk = cast[UInt256](rlnPeer.membershipKeyPair.publicKey) + let pk = cast[UInt256](rlnPeer.membershipKeyPair.idCommitment) discard await sender.register(pk).send(MembershipFee) # TODO check the receipt and then return true/false await web3.close() @@ -102,4 +103,30 @@ proc proofGen*(data: seq[byte]): seq[byte] = proc proofVrfy*(data, proof: seq[byte]): bool = # TODO to implement the actual proof verification logic - return true \ No newline at end of file + return true + +proc insertMember*(rlnInstance: RLN[Bn256], idComm: IDCommitment): bool = + var temp = idComm + var pkBuffer = Buffer(`ptr`: addr(temp[0]), len: 32) + 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: RLN[Bn256], index: uint): 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") + + var rootValue = cast[ptr array[32,byte]] (root.`ptr`) + let merkleNode = rootValue[] + return ok(merkleNode)