diff --git a/.gitignore b/.gitignore index 7d160e963..5413ff0ec 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,6 @@ node_modules/ /.update.timestamp # Ignore Jetbrains IDE files -.idea/ \ No newline at end of file +.idea/ +rlnCredentials.txt +testPath.txt diff --git a/tests/v2/test_waku_rln_relay.nim b/tests/v2/test_waku_rln_relay.nim index 0bd472768..5dee0d387 100644 --- a/tests/v2/test_waku_rln_relay.nim +++ b/tests/v2/test_waku_rln_relay.nim @@ -6,6 +6,7 @@ import testutils/unittests, chronos, chronicles, stint, stew/byteutils, stew/shims/net as stewNet, libp2p/crypto/crypto, + json, ../../waku/v2/protocol/waku_rln_relay/[rln, waku_rln_relay_utils, waku_rln_relay_types], ../../waku/v2/node/wakunode2, @@ -720,3 +721,39 @@ suite "Waku rln relay": # check that the conversion has not distorted the original value check: keypair.get().idCommitment == idCommitment + + test "Read Persistent RLN credentials": + # create an RLN instance + var rlnInstance = createRLNInstance() + check: + rlnInstance.isOk == true + + var key = membershipKeyGen(rlnInstance.value) + var empty: array[32, byte] + check: + key.isSome + 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 + + let + k = key.get() + index = MembershipIndex(1) + + var rlnMembershipCredentials = RlnMembershipCredentials(membershipKeyPair: k, rlnIndex: index) + + let path = "testPath.txt" + + # Write RLN credentials + writeFile(path, pretty(%rlnMembershipCredentials)) + + var credentials = readPersistentRlnCredentials(path) + + check: + credentials.membershipKeyPair == k + credentials.rlnIndex == index + + 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 31516438e..4bef87677 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 @@ -38,6 +38,12 @@ type MembershipKeyPair* = object # more details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Membership idCommitment*: IDCommitment +type MembershipIndex* = uint + +type RlnMembershipCredentials* = object + membershipKeyPair*: MembershipKeyPair + rlnIndex*: MembershipIndex + type RateLimitProof* = object ## RateLimitProof holds the public inputs to rln circuit as ## defined in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Public-Inputs @@ -56,8 +62,6 @@ type RateLimitProof* = object ## see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Nullifiers nullifier*: Nullifier -type MembershipIndex* = uint - type ProofMetadata* = object nullifier*: Nullifier shareX*: MerkleNode @@ -87,6 +91,10 @@ type WakuRLNRelay* = ref object type MessageValidationResult* {.pure.} = enum Valid, Invalid, Spam +# 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 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 4cbbd7f4f..bebe76169 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,6 +2,8 @@ import std/sequtils, tables, times, + std/streams, + std/os, chronicles, options, chronos, stint, confutils, web3, json, @@ -741,7 +743,6 @@ proc mountRlnRelayStatic*(node: WakuNode, node.wakuRlnRelay = rlnPeer - proc mountRlnRelayDynamic*(node: WakuNode, ethClientAddr: string = "", ethAccAddr: web3.Address, @@ -788,9 +789,19 @@ proc mountRlnRelayDynamic*(node: WakuNode, else: # if no eth private key is available, skip registration debug "running waku-rln-relay in relay-only mode" else: + debug "Peer is already registered to the membership contract" keyPair = memKeyPair.get() rlnIndex = memIndex.get() + var + rlnMembershipCredentials = RlnMembershipCredentials(membershipKeyPair: keyPair, rlnIndex: rlnIndex) + + # Since the files are stored as a raw text file, it is highly susceptible to theft. + # The files needs some encryption to resolve this. + + # Write RLN credentials + writeFile(RLN_CREDENTIALS_FILEPATH, pretty(%rlnMembershipCredentials)) + # create the WakuRLNRelay var rlnPeer = WakuRLNRelay(membershipKeyPair: keyPair, membershipIndex: rlnIndex, @@ -821,8 +832,21 @@ proc mountRlnRelayDynamic*(node: WakuNode, node.wakuRlnRelay = rlnPeer +proc readPersistentRlnCredentials*(path: string) : RlnMembershipCredentials {.raises: [Defect, OSError, IOError, Exception].} = + info "Rln credentials exist in file" + # With regards to printing the keys, it is purely for debugging purposes so that the user becomes explicitly aware of the current keys in use when nwaku is started. + # Note that this is only until the RLN contract being used is the one deployed on Goerli testnet. + # These prints need to omitted once RLN contract is deployed on Ethereum mainnet and using valuable funds for staking. + + let entireRlnCredentialsFile = readFile(path) -proc mountRlnRelay*(node: WakuNode, conf: WakuNodeConf|Chat2Conf, spamHandler: Option[SpamHandler] = none(SpamHandler)) {.raises: [Defect, ValueError, IOError, CatchableError].} = + let jsonObject = parseJson(entireRlnCredentialsFile) + let deserializedRlnCredentials = to(jsonObject, RlnMembershipCredentials) + + debug "Deserialized Rln credentials", rlnCredentials=deserializedRlnCredentials + result = deserializedRlnCredentials + +proc mountRlnRelay*(node: WakuNode, conf: WakuNodeConf|Chat2Conf, spamHandler: Option[SpamHandler] = none(SpamHandler)) {.raises: [Defect, ValueError, IOError, CatchableError, Exception].} = if not conf.rlnRelayDynamic: info " setting up waku-rln-relay in on-chain mode... " # set up rln relay inputs @@ -866,9 +890,18 @@ proc mountRlnRelay*(node: WakuNode, conf: WakuNodeConf|Chat2Conf, spamHandler: O let keyPair = @[(rlnRelayId, rlnRelayIdCommitmentKey)] let memKeyPair = keyPair.toMembershipKeyPairs()[0] # mount the rln relay protocol in the on-chain/dynamic mode - waitFor node.mountRlnRelayDynamic(memContractAddr = ethMemContractAddress, ethClientAddr = ethClientAddr, memKeyPair = some(memKeyPair), memIndex = some(rlnRelayIndex), ethAccAddr = ethAccountAddr, ethAccountPrivKeyOpt = ethAccountPrivKeyOpt, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic, spamHandler = spamHandler) + waitFor node.mountRlnRelayDynamic(memContractAddr = ethMemContractAddress, ethClientAddr = ethClientAddr, + memKeyPair = some(memKeyPair), memIndex = some(rlnRelayIndex), ethAccAddr = ethAccountAddr, + ethAccountPrivKeyOpt = ethAccountPrivKeyOpt, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic, spamHandler = spamHandler) + elif fileExists(RLN_CREDENTIALS_FILEPATH): + var credentials = readPersistentRlnCredentials(RLN_CREDENTIALS_FILEPATH) + waitFor node.mountRlnRelayDynamic(memContractAddr = ethMemContractAddress, ethClientAddr = ethClientAddr, + memKeyPair = some(credentials.membershipKeyPair), memIndex = some(credentials.rlnIndex), ethAccAddr = ethAccountAddr, + ethAccountPrivKeyOpt = ethAccountPrivKeyOpt, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic, spamHandler = spamHandler) else: # no rln credential is provided # mount the rln relay protocol in the on-chain/dynamic mode - waitFor node.mountRlnRelayDynamic(memContractAddr = ethMemContractAddress, ethClientAddr = ethClientAddr, ethAccAddr = ethAccountAddr, ethAccountPrivKeyOpt = ethAccountPrivKeyOpt, pubsubTopic = conf.rlnRelayPubsubTopic, contentTopic = conf.rlnRelayContentTopic, spamHandler = spamHandler) - + 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)