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

This commit is contained in:
Aaryamann Challani 2023-03-13 20:09:33 +05:30 committed by GitHub
parent 11c19a3751
commit 08b2180441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 80 deletions

View File

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

View File

@ -19,7 +19,8 @@ export
web3,
chronicles,
stint,
constants
constants,
endians2
logScope:
topics = "waku rln_relay conversion_utils"

View File

@ -56,6 +56,7 @@ type ProofMetadata* = object
nullifier*: Nullifier
shareX*: MerkleNode
shareY*: MerkleNode
externalNullifier*: Nullifier
type
MessageValidationResult* {.pure.} = enum

View File

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

View File

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