From 08b21804410d0f7127c061528ec235ae13fce450 Mon Sep 17 00:00:00 2001 From: Aaryamann Challani <43716372+rymnc@users.noreply.github.com> Date: Mon, 13 Mar 2023 20:09:33 +0530 Subject: [PATCH] fix(rln-relay): msg validation according to new circuit (#1594) --- .../v2/waku_rln_relay/test_waku_rln_relay.nim | 69 ++++++++++------ .../waku_rln_relay/conversion_utils.nim | 3 +- .../waku_rln_relay/protocol_types.nim | 1 + .../protocol/waku_rln_relay/rln/wrappers.nim | 45 +++++++++-- waku/v2/protocol/waku_rln_relay/rln_relay.nim | 81 +++++++------------ 5 files changed, 119 insertions(+), 80 deletions(-) diff --git a/tests/v2/waku_rln_relay/test_waku_rln_relay.nim b/tests/v2/waku_rln_relay/test_waku_rln_relay.nim index 4c37d7c71..86f8825df 100644 --- a/tests/v2/waku_rln_relay/test_waku_rln_relay.nim +++ b/tests/v2/waku_rln_relay/test_waku_rln_relay.nim @@ -354,7 +354,7 @@ suite "Waku rln relay": debug "hash output", hashOutputHex - test "hash utils": + test "sha256 hash utils": # create an RLN instance let rlnInstance = createRLNInstance() require: @@ -364,11 +364,31 @@ suite "Waku rln relay": # prepare the input let msg = "Hello".toBytes() - let hash = sha256(msg) + let hashRes = sha256(msg) check: + hashRes.isOk() "1e32b3ab545c07c8b4a7ab1ca4f46bc31e4fdc29ac3b240ef1d54b4017a26e4c" == - hash.inHex() + hashRes.get().inHex() + + test "poseidon hash utils": + # create an RLN instance + let rlnInstance = createRLNInstance() + require: + rlnInstance.isOk() + let rln = rlnInstance.get() + + # prepare the input + let msg = @["126f4c026cd731979365f79bd345a46d673c5a3f6f588bdc718e6356d02b6fdc".toBytes(), + "1f0e5db2b69d599166ab16219a97b82b662085c93220382b39f9f911d3b943b1".toBytes()] + + let hashRes = poseidon(msg) + + # Value taken from zerokit + check: + hashRes.isOk() + "28a15a991fe3d2a014485c7fa905074bfb55c0909112f865ded2be0a26a932c3" == + hashRes.get().inHex() test "create a list of membership keys and construct a Merkle tree based on the list": let rlnInstance = createRLNInstance() @@ -514,40 +534,43 @@ suite "Waku rln relay": return proof.encode().buffer let - wm1 = WakuMessage(proof: RateLimitProof(epoch: epoch, - nullifier: nullifier1, - shareX: shareX1, - shareY: shareY1).encodeAndGetBuf()) - wm2 = WakuMessage(proof: RateLimitProof(epoch: epoch, - nullifier: nullifier2, - shareX: shareX2, - shareY: shareY2).encodeAndGetBuf()) - wm3 = WakuMessage(proof: RateLimitProof(epoch: epoch, - nullifier: nullifier3, - shareX: shareX3, - shareY: shareY3).encodeAndGetBuf()) + proof1 = RateLimitProof(epoch: epoch, + nullifier: nullifier1, + shareX: shareX1, + shareY: shareY1) + wm1 = WakuMessage(proof: proof1.encodeAndGetBuf()) + proof2 = RateLimitProof(epoch: epoch, + nullifier: nullifier2, + shareX: shareX2, + shareY: shareY2) + wm2 = WakuMessage(proof: proof2.encodeAndGetBuf()) + proof3 = RateLimitProof(epoch: epoch, + nullifier: nullifier3, + shareX: shareX3, + shareY: shareY3) + wm3 = WakuMessage(proof: proof3.encodeAndGetBuf()) # check whether hasDuplicate correctly finds records with the same nullifiers but different secret shares - # no duplicate for wm1 should be found, since the log is empty - let result1 = wakurlnrelay.hasDuplicate(wm1) + # no duplicate for proof1 should be found, since the log is empty + let result1 = wakurlnrelay.hasDuplicate(proof1.extractMetadata().tryGet()) require: result1.isOk() # no duplicate is found result1.value == false # add it to the log - discard wakurlnrelay.updateLog(wm1) + discard wakurlnrelay.updateLog(proof1.extractMetadata().tryGet()) - # # no duplicate for wm2 should be found, its nullifier differs from wm1 - let result2 = wakurlnrelay.hasDuplicate(wm2) + # # no duplicate for proof2 should be found, its nullifier differs from proof1 + let result2 = wakurlnrelay.hasDuplicate(proof2.extractMetadata().tryGet()) require: result2.isOk() # no duplicate is found result2.value == false # add it to the log - discard wakurlnrelay.updateLog(wm2) + discard wakurlnrelay.updateLog(proof2.extractMetadata().tryGet()) - # wm3 has the same nullifier as wm1 but different secret shares, it should be detected as duplicate - let result3 = wakurlnrelay.hasDuplicate(wm3) + # proof3 has the same nullifier as proof1 but different secret shares, it should be detected as duplicate + let result3 = wakurlnrelay.hasDuplicate(proof3.extractMetadata().tryGet()) require: result3.isOk() check: diff --git a/waku/v2/protocol/waku_rln_relay/conversion_utils.nim b/waku/v2/protocol/waku_rln_relay/conversion_utils.nim index 11b8c403a..b6b480938 100644 --- a/waku/v2/protocol/waku_rln_relay/conversion_utils.nim +++ b/waku/v2/protocol/waku_rln_relay/conversion_utils.nim @@ -19,7 +19,8 @@ export web3, chronicles, stint, - constants + constants, + endians2 logScope: topics = "waku rln_relay conversion_utils" diff --git a/waku/v2/protocol/waku_rln_relay/protocol_types.nim b/waku/v2/protocol/waku_rln_relay/protocol_types.nim index a1b56ff7b..e240cde3f 100644 --- a/waku/v2/protocol/waku_rln_relay/protocol_types.nim +++ b/waku/v2/protocol/waku_rln_relay/protocol_types.nim @@ -56,6 +56,7 @@ type ProofMetadata* = object nullifier*: Nullifier shareX*: MerkleNode shareY*: MerkleNode + externalNullifier*: Nullifier type MessageValidationResult* {.pure.} = enum diff --git a/waku/v2/protocol/waku_rln_relay/rln/wrappers.nim b/waku/v2/protocol/waku_rln_relay/rln/wrappers.nim index 7d2a5e093..98dc0c538 100644 --- a/waku/v2/protocol/waku_rln_relay/rln/wrappers.nim +++ b/waku/v2/protocol/waku_rln_relay/rln/wrappers.nim @@ -78,27 +78,60 @@ proc createRLNInstance*(d: int = MerkleTreeDepth): RLNResult = res = createRLNInstanceLocal(d) return res -proc sha256*(data: openArray[byte]): MerkleNode = +proc sha256*(data: openArray[byte]): RlnRelayResult[MerkleNode] = ## a thin layer on top of the Nim wrapper of the sha256 hasher - debug "sha256 hash input", hashhex = data.toHex() + trace "sha256 hash input", hashhex = data.toHex() var lenPrefData = encodeLengthPrefix(data) var hashInputBuffer = lenPrefData.toBuffer() outputBuffer: Buffer # will holds the hash output - debug "sha256 hash input buffer length", bufflen = hashInputBuffer.len + trace "sha256 hash input buffer length", bufflen = hashInputBuffer.len let hashSuccess = sha256(addr hashInputBuffer, addr outputBuffer) # check whether the hash call is done successfully if not hashSuccess: - debug "error in sha256 hash" - return default(MerkleNode) + return err("error in sha256 hash") let output = cast[ptr MerkleNode](outputBuffer.`ptr`)[] - return output + return ok(output) + +proc poseidon*(data: seq[seq[byte]]): RlnRelayResult[array[32, byte]] = + ## a thin layer on top of the Nim wrapper of the poseidon hasher + var inputBytes = serialize(data) + var + hashInputBuffer = inputBytes.toBuffer() + outputBuffer: Buffer # will holds the hash output + trace "poseidon hash input", hashInputBuffer = hashInputBuffer, inputBytes = inputBytes, bufflen = hashInputBuffer.len + + let + hashSuccess = poseidon(addr hashInputBuffer, addr outputBuffer) + + # check whether the hash call is done successfully + if not hashSuccess: + return err("error in poseidon hash") + + let + output = cast[ptr array[32, byte]](outputBuffer.`ptr`)[] + + return ok(output) + +# TODO: collocate this proc with the definition of the RateLimitProof +# and the ProofMetadata types +proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] = + let externalNullifierRes = poseidon(@[@(proof.epoch), + @(proof.rlnIdentifier)]) + if externalNullifierRes.isErr(): + return err("could not construct the external nullifier") + return ok(ProofMetadata( + nullifier: proof.nullifier, + shareX: proof.shareX, + shareY: proof.shareY, + externalNullifier: externalNullifierRes.get() + )) proc proofGen*(rlnInstance: ptr RLN, data: openArray[byte], memKeys: IdentityCredential, memIndex: MembershipIndex, diff --git a/waku/v2/protocol/waku_rln_relay/rln_relay.nim b/waku/v2/protocol/waku_rln_relay/rln_relay.nim index 69b4a0010..3a572f582 100644 --- a/waku/v2/protocol/waku_rln_relay/rln_relay.nim +++ b/waku/v2/protocol/waku_rln_relay/rln_relay.nim @@ -7,7 +7,6 @@ import std/[algorithm, sequtils, strutils, tables, times, os, deques], chronicles, options, chronos, stint, confutils, - strutils, web3, json, web3/ethtypes, eth/keys, @@ -86,37 +85,26 @@ type WakuRLNRelay* = ref object of RootObj lastEpoch*: Epoch # the epoch of the last published rln message groupManager*: GroupManager -proc hasDuplicate*(rlnPeer: WakuRLNRelay, msg: WakuMessage): RlnRelayResult[bool] = +proc hasDuplicate*(rlnPeer: WakuRLNRelay, + proofMetadata: ProofMetadata): RlnRelayResult[bool] = ## returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same - ## epoch and nullifier as `msg`'s epoch and nullifier but different Shamir secret shares + ## epoch and nullifier as `proofMetadata`'s epoch and nullifier but different Shamir secret shares ## otherwise, returns false ## Returns an error if it cannot check for duplicates - let decodeRes = RateLimitProof.init(msg.proof) - if decodeRes.isErr(): - return err("failed to decode the RLN proof") - - let proof = decodeRes.get() - - # extract the proof metadata of the supplied `msg` - let proofMD = ProofMetadata( - nullifier: proof.nullifier, - shareX: proof.shareX, - shareY: proof.shareY - ) - + let externalNullifier = proofMetadata.externalNullifier # check if the epoch exists - if not rlnPeer.nullifierLog.hasKey(proof.epoch): + if not rlnPeer.nullifierLog.hasKey(externalNullifier): return ok(false) try: - if rlnPeer.nullifierLog[proof.epoch].contains(proofMD): + if rlnPeer.nullifierLog[externalNullifier].contains(proofMetadata): # there is an identical record, ignore rhe mag return ok(false) # check for a message with the same nullifier but different secret shares - let matched = rlnPeer.nullifierLog[proof.epoch].filterIt(( - it.nullifier == proofMD.nullifier) and ((it.shareX != proofMD.shareX) or - (it.shareY != proofMD.shareY))) + let matched = rlnPeer.nullifierLog[externalNullifier].filterIt(( + it.nullifier == proofMetadata.nullifier) and ((it.shareX != proofMetadata.shareX) or + (it.shareY != proofMetadata.shareY))) if matched.len != 0: # there is a duplicate @@ -128,39 +116,28 @@ proc hasDuplicate*(rlnPeer: WakuRLNRelay, msg: WakuMessage): RlnRelayResult[bool except KeyError as e: return err("the epoch was not found") -proc updateLog*(rlnPeer: WakuRLNRelay, msg: WakuMessage): RlnRelayResult[bool] = - ## extracts the `ProofMetadata` of the supplied messages `msg` and - ## saves it in the `nullifierLog` of the `rlnPeer` +proc updateLog*(rlnPeer: WakuRLNRelay, + proofMetadata: ProofMetadata): RlnRelayResult[void] = + ## saves supplied proofMetadata `proofMetadata` + ## in the `nullifierLog` of the `rlnPeer` ## Returns an error if it cannot update the log - let decodeRes = RateLimitProof.init(msg.proof) - if decodeRes.isErr(): - return err("failed to decode the RLN proof") - - let proof = decodeRes.get() - - # extract the proof metadata of the supplied `msg` - let proofMD = ProofMetadata( - nullifier: proof.nullifier, - shareX: proof.shareX, - shareY: proof.shareY - ) - debug "proof metadata", proofMD = proofMD - - # check if the epoch exists - if not rlnPeer.nullifierLog.hasKey(proof.epoch): - rlnPeer.nullifierLog[proof.epoch] = @[proofMD] - return ok(true) + let externalNullifier = proofMetadata.externalNullifier + # check if the externalNullifier exists + if not rlnPeer.nullifierLog.hasKey(externalNullifier): + rlnPeer.nullifierLog[externalNullifier] = @[proofMetadata] + return ok() try: # check if an identical record exists - if rlnPeer.nullifierLog[proof.epoch].contains(proofMD): - return ok(true) - # add proofMD to the log - rlnPeer.nullifierLog[proof.epoch].add(proofMD) - return ok(true) + if rlnPeer.nullifierLog[externalNullifier].contains(proofMetadata): + # TODO: slashing logic + return ok() + # add proofMetadata to the log + rlnPeer.nullifierLog[externalNullifier].add(proofMetadata) + return ok() except KeyError as e: - return err("the epoch was not found") + return err("the external nullifier was not found") # should never happen proc getCurrentEpoch*(): Epoch = ## gets the current rln Epoch time @@ -250,7 +227,11 @@ proc validateMessage*(rlnPeer: WakuRLNRelay, msg: WakuMessage, return MessageValidationResult.Invalid # check if double messaging has happened - let hasDup = rlnPeer.hasDuplicate(msg) + let proofMetadataRes = proof.extractMetadata() + if proofMetadataRes.isErr(): + waku_rln_errors_total.inc(labelValues=["proof_metadata_extraction"]) + return MessageValidationResult.Invalid + let hasDup = rlnPeer.hasDuplicate(proofMetadataRes.get()) if hasDup.isErr(): waku_rln_errors_total.inc(labelValues=["duplicate_check"]) elif hasDup.value == true: @@ -261,7 +242,7 @@ proc validateMessage*(rlnPeer: WakuRLNRelay, msg: WakuMessage, # insert the message to the log # the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e., # it will never error out - discard rlnPeer.updateLog(msg) + discard rlnPeer.updateLog(proofMetadataRes.get()) debug "message is valid", payload = string.fromBytes(msg.payload) let rootIndex = rlnPeer.groupManager.indexOfRoot(proof.merkleRoot) waku_rln_valid_messages_total.observe(rootIndex.toFloat())