mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-01-05 15:33:08 +00:00
chore: add timestamp and ephemeral for opt-in dos validator (#1713)
This commit is contained in:
parent
8951ec6119
commit
5a8fcffa41
@ -4,16 +4,20 @@ else:
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/math,
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
stew/byteutils,
|
||||
stew/endians2,
|
||||
libp2p/protocols/pubsub/gossipsub,
|
||||
libp2p/protocols/pubsub/rpc/messages,
|
||||
libp2p/protocols/pubsub/errors,
|
||||
nimcrypto/sha2,
|
||||
secp256k1
|
||||
|
||||
const MessageWindowInSec = 5*60 # +- 5 minutes
|
||||
|
||||
import
|
||||
../../waku/v2/waku_relay/protocol,
|
||||
../../waku/v2/waku_core
|
||||
@ -29,9 +33,22 @@ proc msgHash*(pubSubTopic: string, msg: WakuMessage): array[32, byte] =
|
||||
ctx.update(pubsubTopic.toBytes())
|
||||
ctx.update(msg.payload)
|
||||
ctx.update(msg.contentTopic.toBytes())
|
||||
ctx.update(msg.timestamp.uint64.toBytes(Endianness.littleEndian))
|
||||
ctx.update(if msg.ephemeral: @[1.byte] else: @[0.byte])
|
||||
|
||||
return ctx.finish()
|
||||
|
||||
proc withinTimeWindow*(msg: WakuMessage): bool =
|
||||
# Returns true if the message timestamp is:
|
||||
# abs(now - msg.timestamp) < MessageWindowInSec
|
||||
let ts = msg.timestamp
|
||||
let now = getNowInNanosecondTime()
|
||||
let window = getNanosecondTime(MessageWindowInSec)
|
||||
|
||||
if abs(now - ts) < window:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc addSignedTopicValidator*(w: WakuRelay, topic: PubsubTopic, publicTopicKey: SkPublicKey) =
|
||||
debug "adding validator to signed topic", topic=topic, publicTopicKey=publicTopicKey
|
||||
|
||||
@ -40,11 +57,13 @@ proc addSignedTopicValidator*(w: WakuRelay, topic: PubsubTopic, publicTopicKey:
|
||||
var outcome = errors.ValidationResult.Reject
|
||||
|
||||
if msg.isOk():
|
||||
let msgHash = SkMessage(topic.msgHash(msg.get))
|
||||
let recoveredSignature = SkSignature.fromRaw(msg.get.meta)
|
||||
if recoveredSignature.isOk():
|
||||
if recoveredSignature.get.verify(msgHash, publicTopicKey):
|
||||
outcome = errors.ValidationResult.Accept
|
||||
if msg.get.timestamp != 0:
|
||||
if msg.get.withinTimeWindow():
|
||||
let msgHash = SkMessage(topic.msgHash(msg.get))
|
||||
let recoveredSignature = SkSignature.fromRaw(msg.get.meta)
|
||||
if recoveredSignature.isOk():
|
||||
if recoveredSignature.get.verify(msgHash, publicTopicKey):
|
||||
outcome = errors.ValidationResult.Accept
|
||||
|
||||
waku_msg_validator_signed_outcome.inc(labelValues = [$outcome])
|
||||
return outcome
|
||||
|
||||
@ -92,7 +92,7 @@ suite "WakuNode2 - Validators":
|
||||
# Stop all nodes
|
||||
await allFutures(nodes.mapIt(it.stop()))
|
||||
|
||||
asyncTest "Spam protected topic rejects non-signed and wrongly-signed messages":
|
||||
asyncTest "Spam protected topic rejects non-signed/wrongly-signed/no-timestamp messages":
|
||||
# Create 5 nodes
|
||||
let nodes = toSeq(0..<5).mapIt(newTestWakuNode(generateSecp256k1Key(), ValidIpAddress.init("0.0.0.0"), Port(0)))
|
||||
|
||||
@ -156,15 +156,41 @@ suite "WakuNode2 - Validators":
|
||||
version: 2, timestamp: now(), ephemeral: true)
|
||||
await nodes[i].publish(spamProtectedTopic, unsignedMessage)
|
||||
|
||||
# Each node sends 10 messages that dont contain timestamp (total = 50)
|
||||
for i in 0..<5:
|
||||
for j in 0..<10:
|
||||
let unsignedMessage = WakuMessage(
|
||||
payload: urandom(1*(10^3)), contentTopic: spamProtectedTopic,
|
||||
version: 2, timestamp: 0, ephemeral: true)
|
||||
await nodes[i].publish(spamProtectedTopic, unsignedMessage)
|
||||
|
||||
# Each node sends 10 messages way BEFORE than the current timestmap (total = 50)
|
||||
for i in 0..<5:
|
||||
for j in 0..<10:
|
||||
let beforeTimestamp = now() - getNanosecondTime(6*60)
|
||||
let unsignedMessage = WakuMessage(
|
||||
payload: urandom(1*(10^3)), contentTopic: spamProtectedTopic,
|
||||
version: 2, timestamp: beforeTimestamp, ephemeral: true)
|
||||
await nodes[i].publish(spamProtectedTopic, unsignedMessage)
|
||||
|
||||
# Each node sends 10 messages way LATER than the current timestmap (total = 50)
|
||||
for i in 0..<5:
|
||||
for j in 0..<10:
|
||||
let afterTimestamp = now() - getNanosecondTime(6*60)
|
||||
let unsignedMessage = WakuMessage(
|
||||
payload: urandom(1*(10^3)), contentTopic: spamProtectedTopic,
|
||||
version: 2, timestamp: afterTimestamp, ephemeral: true)
|
||||
await nodes[i].publish(spamProtectedTopic, unsignedMessage)
|
||||
|
||||
# Wait for gossip
|
||||
await sleepAsync(2.seconds)
|
||||
|
||||
# Since we have a full mesh with 5 nodes and each one publishes 50+50 msgs
|
||||
# there are 500 messages being sent.
|
||||
# 100 are received ok in the handler (first hop)
|
||||
# 400 are are wrong so rejected (rejected not relayed)
|
||||
# Since we have a full mesh with 5 nodes and each one publishes 50+50+50+50+50 msgs
|
||||
# there are 1250 messages being sent.
|
||||
# 250 are received ok in the handler (first hop)
|
||||
# 1000 are are wrong so rejected (rejected not relayed)
|
||||
check:
|
||||
msgReceived == 100
|
||||
msgReceived == 250
|
||||
|
||||
var msgRejected = 0
|
||||
for i in 0..<5:
|
||||
@ -172,7 +198,7 @@ suite "WakuNode2 - Validators":
|
||||
msgRejected += v.topicInfos[spamProtectedTopic].invalidMessageDeliveries.int
|
||||
|
||||
check:
|
||||
msgRejected == 400
|
||||
msgRejected == 1000
|
||||
|
||||
await allFutures(nodes.mapIt(it.stop()))
|
||||
|
||||
@ -273,10 +299,12 @@ suite "WakuNode2 - Validators":
|
||||
let contentTopic = "content-topic"
|
||||
let pubsubTopic = "pubsub-topic"
|
||||
let payload = "1A12E077D0E89F9CAC11FBBB6A676C86120B5AD3E248B1F180E98F15EE43D2DFCF62F00C92737B2FF6F59B3ABA02773314B991C41DC19ADB0AD8C17C8E26757B"
|
||||
let timestamp = 1683208172339052800
|
||||
let ephemeral = true
|
||||
|
||||
# expected values
|
||||
let expectedMsgAppHash = "0914369D6D0C13783A8E86409FE42C68D8E8296456B9A9468C845006BCE5B9B2"
|
||||
let expectedSignature = "B139487797A242291E0DD3F689777E559FB749D565D55FF202C18E24F21312A555043437B4F808BB0D21D542D703873DC712D76A3BAF1C5C8FF754210D894AD4"
|
||||
let expectedMsgAppHash = "662F8C20A335F170BD60ABC1F02AD66F0C6A6EE285DA2A53C95259E7937C0AE9"
|
||||
let expectedSignature = "127FA211B2514F0E974A055392946DC1A14052182A6ABEFB8A6CD7C51DA1BF2E40595D28EF1A9488797C297EED3AAC45430005FB3A7F037BDD9FC4BD99F59E63"
|
||||
|
||||
let secretKey = SkSecretKey.fromHex(privateKey).expect("valid key")
|
||||
|
||||
@ -286,7 +314,7 @@ suite "WakuNode2 - Validators":
|
||||
|
||||
var msg = WakuMessage(
|
||||
payload: payload.fromHex(), contentTopic: contentTopic,
|
||||
version: 2, timestamp: now(), ephemeral: true)
|
||||
version: 2, timestamp: timestamp, ephemeral: ephemeral)
|
||||
|
||||
let msgAppHash = pubsubTopic.msgHash(msg)
|
||||
let signature = secretKey.sign(SkMessage(msgAppHash)).toRaw()
|
||||
@ -294,4 +322,3 @@ suite "WakuNode2 - Validators":
|
||||
check:
|
||||
msgAppHash.toHex() == expectedMsgAppHash
|
||||
signature.toHex() == expectedSignature
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ type WakuMessage* = object
|
||||
# Number to discriminate different types of payload encryption.
|
||||
# Compatibility with Whisper/WakuV1.
|
||||
version*: uint32
|
||||
# Sender generated timestamp. Deprecated. Superseded by `meta` attribute.
|
||||
# Sender generated timestamp.
|
||||
timestamp*: Timestamp
|
||||
# The ephemeral attribute indicates signifies the transient nature of the
|
||||
# message (if the message should be stored).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user