Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(rln-relay): msg validation according to new circuit #1594

Merged
merged 1 commit into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 46 additions & 23 deletions tests/v2/waku_rln_relay/test_waku_rln_relay.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion waku/v2/protocol/waku_rln_relay/conversion_utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export
web3,
chronicles,
stint,
constants
constants,
endians2

logScope:
topics = "waku rln_relay conversion_utils"
Expand Down
1 change: 1 addition & 0 deletions waku/v2/protocol/waku_rln_relay/protocol_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type ProofMetadata* = object
nullifier*: Nullifier
shareX*: MerkleNode
shareY*: MerkleNode
externalNullifier*: Nullifier

type
MessageValidationResult* {.pure.} = enum
Expand Down
45 changes: 39 additions & 6 deletions waku/v2/protocol/waku_rln_relay/rln/wrappers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
81 changes: 31 additions & 50 deletions waku/v2/protocol/waku_rln_relay/rln_relay.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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())
Expand Down