mirror of
https://github.com/waku-org/nwaku.git
synced 2025-01-26 23:02:30 +00:00
fix(rln-relay): msg validation according to new circuit (#1594)
This commit is contained in:
parent
11c19a3751
commit
08b2180441
@ -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:
|
||||
|
@ -19,7 +19,8 @@ export
|
||||
web3,
|
||||
chronicles,
|
||||
stint,
|
||||
constants
|
||||
constants,
|
||||
endians2
|
||||
|
||||
logScope:
|
||||
topics = "waku rln_relay conversion_utils"
|
||||
|
@ -56,6 +56,7 @@ type ProofMetadata* = object
|
||||
nullifier*: Nullifier
|
||||
shareX*: MerkleNode
|
||||
shareY*: MerkleNode
|
||||
externalNullifier*: Nullifier
|
||||
|
||||
type
|
||||
MessageValidationResult* {.pure.} = enum
|
||||
|
@ -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,
|
||||
|
@ -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())
|
||||
|
Loading…
x
Reference in New Issue
Block a user