mirror of
https://github.com/waku-org/nwaku.git
synced 2025-02-10 14:06:38 +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
|
debug "hash output", hashOutputHex
|
||||||
|
|
||||||
test "hash utils":
|
test "sha256 hash utils":
|
||||||
# create an RLN instance
|
# create an RLN instance
|
||||||
let rlnInstance = createRLNInstance()
|
let rlnInstance = createRLNInstance()
|
||||||
require:
|
require:
|
||||||
@ -364,11 +364,31 @@ suite "Waku rln relay":
|
|||||||
# prepare the input
|
# prepare the input
|
||||||
let msg = "Hello".toBytes()
|
let msg = "Hello".toBytes()
|
||||||
|
|
||||||
let hash = sha256(msg)
|
let hashRes = sha256(msg)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
hashRes.isOk()
|
||||||
"1e32b3ab545c07c8b4a7ab1ca4f46bc31e4fdc29ac3b240ef1d54b4017a26e4c" ==
|
"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":
|
test "create a list of membership keys and construct a Merkle tree based on the list":
|
||||||
let rlnInstance = createRLNInstance()
|
let rlnInstance = createRLNInstance()
|
||||||
@ -514,40 +534,43 @@ suite "Waku rln relay":
|
|||||||
return proof.encode().buffer
|
return proof.encode().buffer
|
||||||
|
|
||||||
let
|
let
|
||||||
wm1 = WakuMessage(proof: RateLimitProof(epoch: epoch,
|
proof1 = RateLimitProof(epoch: epoch,
|
||||||
nullifier: nullifier1,
|
nullifier: nullifier1,
|
||||||
shareX: shareX1,
|
shareX: shareX1,
|
||||||
shareY: shareY1).encodeAndGetBuf())
|
shareY: shareY1)
|
||||||
wm2 = WakuMessage(proof: RateLimitProof(epoch: epoch,
|
wm1 = WakuMessage(proof: proof1.encodeAndGetBuf())
|
||||||
|
proof2 = RateLimitProof(epoch: epoch,
|
||||||
nullifier: nullifier2,
|
nullifier: nullifier2,
|
||||||
shareX: shareX2,
|
shareX: shareX2,
|
||||||
shareY: shareY2).encodeAndGetBuf())
|
shareY: shareY2)
|
||||||
wm3 = WakuMessage(proof: RateLimitProof(epoch: epoch,
|
wm2 = WakuMessage(proof: proof2.encodeAndGetBuf())
|
||||||
|
proof3 = RateLimitProof(epoch: epoch,
|
||||||
nullifier: nullifier3,
|
nullifier: nullifier3,
|
||||||
shareX: shareX3,
|
shareX: shareX3,
|
||||||
shareY: shareY3).encodeAndGetBuf())
|
shareY: shareY3)
|
||||||
|
wm3 = WakuMessage(proof: proof3.encodeAndGetBuf())
|
||||||
|
|
||||||
# check whether hasDuplicate correctly finds records with the same nullifiers but different secret shares
|
# 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
|
# no duplicate for proof1 should be found, since the log is empty
|
||||||
let result1 = wakurlnrelay.hasDuplicate(wm1)
|
let result1 = wakurlnrelay.hasDuplicate(proof1.extractMetadata().tryGet())
|
||||||
require:
|
require:
|
||||||
result1.isOk()
|
result1.isOk()
|
||||||
# no duplicate is found
|
# no duplicate is found
|
||||||
result1.value == false
|
result1.value == false
|
||||||
# add it to the log
|
# 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
|
# # no duplicate for proof2 should be found, its nullifier differs from proof1
|
||||||
let result2 = wakurlnrelay.hasDuplicate(wm2)
|
let result2 = wakurlnrelay.hasDuplicate(proof2.extractMetadata().tryGet())
|
||||||
require:
|
require:
|
||||||
result2.isOk()
|
result2.isOk()
|
||||||
# no duplicate is found
|
# no duplicate is found
|
||||||
result2.value == false
|
result2.value == false
|
||||||
# add it to the log
|
# 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
|
# proof3 has the same nullifier as proof1 but different secret shares, it should be detected as duplicate
|
||||||
let result3 = wakurlnrelay.hasDuplicate(wm3)
|
let result3 = wakurlnrelay.hasDuplicate(proof3.extractMetadata().tryGet())
|
||||||
require:
|
require:
|
||||||
result3.isOk()
|
result3.isOk()
|
||||||
check:
|
check:
|
||||||
|
@ -19,7 +19,8 @@ export
|
|||||||
web3,
|
web3,
|
||||||
chronicles,
|
chronicles,
|
||||||
stint,
|
stint,
|
||||||
constants
|
constants,
|
||||||
|
endians2
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "waku rln_relay conversion_utils"
|
topics = "waku rln_relay conversion_utils"
|
||||||
|
@ -56,6 +56,7 @@ type ProofMetadata* = object
|
|||||||
nullifier*: Nullifier
|
nullifier*: Nullifier
|
||||||
shareX*: MerkleNode
|
shareX*: MerkleNode
|
||||||
shareY*: MerkleNode
|
shareY*: MerkleNode
|
||||||
|
externalNullifier*: Nullifier
|
||||||
|
|
||||||
type
|
type
|
||||||
MessageValidationResult* {.pure.} = enum
|
MessageValidationResult* {.pure.} = enum
|
||||||
|
@ -78,27 +78,60 @@ proc createRLNInstance*(d: int = MerkleTreeDepth): RLNResult =
|
|||||||
res = createRLNInstanceLocal(d)
|
res = createRLNInstanceLocal(d)
|
||||||
return res
|
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
|
## 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 lenPrefData = encodeLengthPrefix(data)
|
||||||
var
|
var
|
||||||
hashInputBuffer = lenPrefData.toBuffer()
|
hashInputBuffer = lenPrefData.toBuffer()
|
||||||
outputBuffer: Buffer # will holds the hash output
|
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
|
let
|
||||||
hashSuccess = sha256(addr hashInputBuffer, addr outputBuffer)
|
hashSuccess = sha256(addr hashInputBuffer, addr outputBuffer)
|
||||||
|
|
||||||
# check whether the hash call is done successfully
|
# check whether the hash call is done successfully
|
||||||
if not hashSuccess:
|
if not hashSuccess:
|
||||||
debug "error in sha256 hash"
|
return err("error in sha256 hash")
|
||||||
return default(MerkleNode)
|
|
||||||
|
|
||||||
let
|
let
|
||||||
output = cast[ptr MerkleNode](outputBuffer.`ptr`)[]
|
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],
|
proc proofGen*(rlnInstance: ptr RLN, data: openArray[byte],
|
||||||
memKeys: IdentityCredential, memIndex: MembershipIndex,
|
memKeys: IdentityCredential, memIndex: MembershipIndex,
|
||||||
|
@ -7,7 +7,6 @@ import
|
|||||||
std/[algorithm, sequtils, strutils, tables, times, os, deques],
|
std/[algorithm, sequtils, strutils, tables, times, os, deques],
|
||||||
chronicles, options, chronos, stint,
|
chronicles, options, chronos, stint,
|
||||||
confutils,
|
confutils,
|
||||||
strutils,
|
|
||||||
web3, json,
|
web3, json,
|
||||||
web3/ethtypes,
|
web3/ethtypes,
|
||||||
eth/keys,
|
eth/keys,
|
||||||
@ -86,37 +85,26 @@ type WakuRLNRelay* = ref object of RootObj
|
|||||||
lastEpoch*: Epoch # the epoch of the last published rln message
|
lastEpoch*: Epoch # the epoch of the last published rln message
|
||||||
groupManager*: GroupManager
|
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
|
## 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
|
## otherwise, returns false
|
||||||
## Returns an error if it cannot check for duplicates
|
## Returns an error if it cannot check for duplicates
|
||||||
|
|
||||||
let decodeRes = RateLimitProof.init(msg.proof)
|
let externalNullifier = proofMetadata.externalNullifier
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
# check if the epoch exists
|
# check if the epoch exists
|
||||||
if not rlnPeer.nullifierLog.hasKey(proof.epoch):
|
if not rlnPeer.nullifierLog.hasKey(externalNullifier):
|
||||||
return ok(false)
|
return ok(false)
|
||||||
try:
|
try:
|
||||||
if rlnPeer.nullifierLog[proof.epoch].contains(proofMD):
|
if rlnPeer.nullifierLog[externalNullifier].contains(proofMetadata):
|
||||||
# there is an identical record, ignore rhe mag
|
# there is an identical record, ignore rhe mag
|
||||||
return ok(false)
|
return ok(false)
|
||||||
|
|
||||||
# check for a message with the same nullifier but different secret shares
|
# check for a message with the same nullifier but different secret shares
|
||||||
let matched = rlnPeer.nullifierLog[proof.epoch].filterIt((
|
let matched = rlnPeer.nullifierLog[externalNullifier].filterIt((
|
||||||
it.nullifier == proofMD.nullifier) and ((it.shareX != proofMD.shareX) or
|
it.nullifier == proofMetadata.nullifier) and ((it.shareX != proofMetadata.shareX) or
|
||||||
(it.shareY != proofMD.shareY)))
|
(it.shareY != proofMetadata.shareY)))
|
||||||
|
|
||||||
if matched.len != 0:
|
if matched.len != 0:
|
||||||
# there is a duplicate
|
# there is a duplicate
|
||||||
@ -128,39 +116,28 @@ proc hasDuplicate*(rlnPeer: WakuRLNRelay, msg: WakuMessage): RlnRelayResult[bool
|
|||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
return err("the epoch was not found")
|
return err("the epoch was not found")
|
||||||
|
|
||||||
proc updateLog*(rlnPeer: WakuRLNRelay, msg: WakuMessage): RlnRelayResult[bool] =
|
proc updateLog*(rlnPeer: WakuRLNRelay,
|
||||||
## extracts the `ProofMetadata` of the supplied messages `msg` and
|
proofMetadata: ProofMetadata): RlnRelayResult[void] =
|
||||||
## saves it in the `nullifierLog` of the `rlnPeer`
|
## saves supplied proofMetadata `proofMetadata`
|
||||||
|
## in the `nullifierLog` of the `rlnPeer`
|
||||||
## Returns an error if it cannot update the log
|
## Returns an error if it cannot update the log
|
||||||
|
|
||||||
let decodeRes = RateLimitProof.init(msg.proof)
|
let externalNullifier = proofMetadata.externalNullifier
|
||||||
if decodeRes.isErr():
|
# check if the externalNullifier exists
|
||||||
return err("failed to decode the RLN proof")
|
if not rlnPeer.nullifierLog.hasKey(externalNullifier):
|
||||||
|
rlnPeer.nullifierLog[externalNullifier] = @[proofMetadata]
|
||||||
let proof = decodeRes.get()
|
return ok()
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# check if an identical record exists
|
# check if an identical record exists
|
||||||
if rlnPeer.nullifierLog[proof.epoch].contains(proofMD):
|
if rlnPeer.nullifierLog[externalNullifier].contains(proofMetadata):
|
||||||
return ok(true)
|
# TODO: slashing logic
|
||||||
# add proofMD to the log
|
return ok()
|
||||||
rlnPeer.nullifierLog[proof.epoch].add(proofMD)
|
# add proofMetadata to the log
|
||||||
return ok(true)
|
rlnPeer.nullifierLog[externalNullifier].add(proofMetadata)
|
||||||
|
return ok()
|
||||||
except KeyError as e:
|
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 =
|
proc getCurrentEpoch*(): Epoch =
|
||||||
## gets the current rln Epoch time
|
## gets the current rln Epoch time
|
||||||
@ -250,7 +227,11 @@ proc validateMessage*(rlnPeer: WakuRLNRelay, msg: WakuMessage,
|
|||||||
return MessageValidationResult.Invalid
|
return MessageValidationResult.Invalid
|
||||||
|
|
||||||
# check if double messaging has happened
|
# 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():
|
if hasDup.isErr():
|
||||||
waku_rln_errors_total.inc(labelValues=["duplicate_check"])
|
waku_rln_errors_total.inc(labelValues=["duplicate_check"])
|
||||||
elif hasDup.value == true:
|
elif hasDup.value == true:
|
||||||
@ -261,7 +242,7 @@ proc validateMessage*(rlnPeer: WakuRLNRelay, msg: WakuMessage,
|
|||||||
# insert the message to the log
|
# insert the message to the log
|
||||||
# the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e.,
|
# the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e.,
|
||||||
# it will never error out
|
# it will never error out
|
||||||
discard rlnPeer.updateLog(msg)
|
discard rlnPeer.updateLog(proofMetadataRes.get())
|
||||||
debug "message is valid", payload = string.fromBytes(msg.payload)
|
debug "message is valid", payload = string.fromBytes(msg.payload)
|
||||||
let rootIndex = rlnPeer.groupManager.indexOfRoot(proof.merkleRoot)
|
let rootIndex = rlnPeer.groupManager.indexOfRoot(proof.merkleRoot)
|
||||||
waku_rln_valid_messages_total.observe(rootIndex.toFloat())
|
waku_rln_valid_messages_total.observe(rootIndex.toFloat())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user