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
This commit is contained in:
Sanaz Taheri Boshrooyeh 2021-08-26 16:14:51 -07:00 committed by GitHub
parent 9a45aa7055
commit b7998de09d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 236 additions and 32 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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
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)