nwaku/tests/wakunode2/test_validators.nim

416 lines
14 KiB
Nim
Raw Permalink Normal View History

{.used.}
import
std/[sequtils, sysrand, math],
stew/shims/net as stewNet,
testutils/unittests,
chronos,
libp2p/crypto/crypto,
libp2p/crypto/secp,
libp2p/multiaddress,
libp2p/protocols/pubsub/pubsub,
libp2p/protocols/pubsub/gossipsub,
libp2p/multihash,
secp256k1
import
waku/[
waku_core,
node/peer_manager,
waku_node,
waku_relay,
factory/external_config,
factory/validator_signed,
],
../testlib/wakucore,
../testlib/wakunode
suite "WakuNode2 - Validators":
asyncTest "Spam protected topic accepts signed messages":
# Create 5 nodes
let nodes = toSeq(0 ..< 5).mapIt(
newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0))
)
# Protected shard and key to sign
let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7)
let secretKey = SkSecretKey
.fromHex("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6")
.expect("valid key")
let publicKey = secretKey.toPublicKey()
let shardsPrivateKeys = {spamProtectedShard: secretKey}.toTable
let shardsPublicKeys = {spamProtectedShard: publicKey}.toTable
# Start all the nodes and mount relay with protected topic
await allFutures(nodes.mapIt(it.start()))
# Mount relay for all nodes
await allFutures(nodes.mapIt(it.mountRelay()))
# Add signed message validator to all nodes. They will only route signed messages
for node in nodes:
var signedShards: seq[ProtectedShard]
for shard, publicKey in shardsPublicKeys:
signedShards.add(ProtectedShard(shard: shard.shardId, key: publicKey))
node.wakuRelay.addSignedShardsValidator(
signedShards, spamProtectedShard.clusterId
)
# Connect the nodes in a full mesh
for i in 0 ..< 5:
for j in 0 ..< 5:
if i == j:
continue
let connOk = await nodes[i].peerManager.connectPeer(
nodes[j].switch.peerInfo.toRemotePeerInfo()
)
require connOk
# Connection triggers different actions, wait for them
await sleepAsync(500.millis)
var msgReceived = 0
proc handler(pubsubTopic: PubsubTopic, data: WakuMessage) {.async, gcsafe.} =
msgReceived += 1
# Subscribe all nodes to the same topic/handler
for node in nodes:
discard node.wakuRelay.subscribe($spamProtectedShard, handler)
await sleepAsync(500.millis)
# Each node publishes 10 signed messages
for i in 0 ..< 5:
for j in 0 ..< 10:
var msg = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: now(),
ephemeral: true,
)
# Include signature
msg.meta =
secretKey.sign(SkMessage(spamProtectedShard.msgHash(msg))).toRaw()[0 .. 63]
discard await nodes[i].publish(some($spamProtectedShard), msg)
# Wait for gossip
await sleepAsync(2.seconds)
# 50 messages were sent to 5 peers = 250 messages
check:
msgReceived == 250
# No invalid messages were received by any peer
for i in 0 ..< 5:
for k, v in nodes[i].wakuRelay.peerStats.mpairs:
check:
v.topicInfos[spamProtectedShard].invalidMessageDeliveries == 0.0
# Stop all nodes
await allFutures(nodes.mapIt(it.stop()))
asyncTest "Spam protected topic rejects non-signed/wrongly-signed/no-timestamp messages":
# Create 5 nodes
let nodes = toSeq(0 ..< 5).mapIt(
newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0))
)
# Protected shard and key to sign
let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7)
let secretKey = SkSecretKey
.fromHex("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6")
.expect("valid key")
let publicKey = secretKey.toPublicKey()
let shardsPrivateKeys = {spamProtectedShard: secretKey}.toTable
let shardsPublicKeys = {spamProtectedShard: publicKey}.toTable
# Non whitelisted secret key
let wrongSecretKey = SkSecretKey
.fromHex("32ad0cc8edeb9f8a3e8635c5fe5bd200b9247a33da5e7171bd012691805151f3")
.expect("valid key")
# Start all the nodes and mount relay with protected topic
await allFutures(nodes.mapIt(it.start()))
# Mount relay with spam protected topics
await allFutures(nodes.mapIt(it.mountRelay()))
# Add signed message validator to all nodes. They will only route signed messages
for node in nodes:
var signedShards: seq[ProtectedShard]
for shard, publicKey in shardsPublicKeys:
signedShards.add(ProtectedShard(shard: shard.shardId, key: publicKey))
node.wakuRelay.addSignedShardsValidator(
signedShards, spamProtectedShard.clusterId
)
# Connect the nodes in a full mesh
for i in 0 ..< 5:
for j in 0 ..< 5:
if i == j:
continue
let connOk = await nodes[i].peerManager.connectPeer(
nodes[j].switch.peerInfo.toRemotePeerInfo()
)
require connOk
var msgReceived = 0
proc handler(pubsubTopic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
msgReceived += 1
# Connection triggers different actions, wait for them
await sleepAsync(500.millis)
# Subscribe all nodes to the same topic/handler
for node in nodes:
discard node.wakuRelay.subscribe($spamProtectedShard, handler)
await sleepAsync(500.millis)
# Each node sends 5 messages, signed but with a non-whitelisted key (total = 25)
for i in 0 ..< 5:
for j in 0 ..< 5:
var msg = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: now(),
ephemeral: true,
)
# Sign the message with a wrong key
msg.meta = wrongSecretKey.sign(SkMessage(spamProtectedShard.msgHash(msg))).toRaw()[
0 .. 63
]
discard await nodes[i].publish(some($spamProtectedShard), msg)
# Each node sends 5 messages that are not signed (total = 25)
for i in 0 ..< 5:
for j in 0 ..< 5:
let unsignedMessage = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: now(),
ephemeral: true,
)
discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage)
# Each node sends 5 messages that dont contain timestamp (total = 25)
for i in 0 ..< 5:
for j in 0 ..< 5:
let unsignedMessage = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: 0,
ephemeral: true,
)
discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage)
# Each node sends 5 messages way BEFORE than the current timestmap (total = 25)
for i in 0 ..< 5:
for j in 0 ..< 5:
let beforeTimestamp = now() - getNanosecondTime(6 * 60)
let unsignedMessage = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: beforeTimestamp,
ephemeral: true,
)
discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage)
# Each node sends 5 messages way LATER than the current timestmap (total = 25)
for i in 0 ..< 5:
for j in 0 ..< 5:
let afterTimestamp = now() - getNanosecondTime(6 * 60)
let unsignedMessage = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: afterTimestamp,
ephemeral: true,
)
discard await nodes[i].publish(some($spamProtectedShard), unsignedMessage)
# Since we have a full mesh with 5 nodes and each one publishes 25+25+25+25+25 msgs
# there are 625 messages being sent.
# 125 are received ok in the handler (first hop)
# 500 are are wrong so rejected (rejected not relayed)
var msgRejected = 0
# Active wait for the messages to be delivered across the mesh
for i in 0 ..< 100:
msgRejected = 0
for i in 0 ..< 5:
for k, v in nodes[i].wakuRelay.peerStats.mpairs:
msgRejected += v.topicInfos[spamProtectedShard].invalidMessageDeliveries.int
if msgReceived == 125 and msgRejected == 500:
break
else:
await sleepAsync(100.milliseconds)
check:
msgReceived == 125
msgRejected == 500
await allFutures(nodes.mapIt(it.stop()))
asyncTest "Spam protected topic rejects a spammer node":
# Create 5 nodes
let nodes = toSeq(0 ..< 5).mapIt(
newTestWakuNode(generateSecp256k1Key(), parseIpAddress("0.0.0.0"), Port(0))
)
# Protected shard and key to sign
let spamProtectedShard = RelayShard(clusterId: 0, shardId: 7)
let secretKey = SkSecretKey
.fromHex("5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6")
.expect("valid key")
let publicKey = secretKey.toPublicKey()
let shardsPrivateKeys = {spamProtectedShard: secretKey}.toTable
let shardsPublicKeys = {spamProtectedShard: publicKey}.toTable
# Non whitelisted secret key
let wrongSecretKey = SkSecretKey
.fromHex("32ad0cc8edeb9f8a3e8635c5fe5bd200b9247a33da5e7171bd012691805151f3")
.expect("valid key")
# Start all the nodes and mount relay with protected topic
await allFutures(nodes.mapIt(it.start()))
# Mount relay for all nodes
await allFutures(nodes.mapIt(it.mountRelay()))
var msgReceived = 0
proc handler(pubsubTopic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
msgReceived += 1
# Subscribe all nodes to the same topic/handler
for node in nodes:
discard node.wakuRelay.subscribe($spamProtectedShard, handler)
await sleepAsync(500.millis)
# Add signed message validator to all nodes. They will only route signed messages
for node in nodes:
var signedShards: seq[ProtectedShard]
for shard, publicKey in shardsPublicKeys:
signedShards.add(ProtectedShard(shard: shard.shardId, key: publicKey))
node.wakuRelay.addSignedShardsValidator(
signedShards, spamProtectedShard.clusterId
)
# nodes[0] is connected only to nodes[1]
let connOk1 = await nodes[0].peerManager.connectPeer(
nodes[1].switch.peerInfo.toRemotePeerInfo()
)
require connOk1
# rest of nodes[1..4] are connected in a full mesh
for i in 1 ..< 5:
for j in 1 ..< 5:
if i == j:
continue
let connOk2 = await nodes[i].peerManager.connectPeer(
nodes[j].switch.peerInfo.toRemotePeerInfo()
)
require connOk2
# Connection triggers different actions, wait for them
await sleepAsync(500.millis)
# nodes[0] spams 50 non signed messages (nodes[0] just knows of nodes[1])
for j in 0 ..< 50:
let unsignedMessage = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: now(),
ephemeral: true,
)
discard await nodes[0].publish(some($spamProtectedShard), unsignedMessage)
# nodes[0] spams 50 wrongly signed messages (nodes[0] just knows of nodes[1])
for j in 0 ..< 50:
var msg = WakuMessage(
payload: urandom(1 * (10 ^ 3)),
contentTopic: spamProtectedShard,
version: 2,
timestamp: now(),
ephemeral: true,
)
# Sign the message with a wrong key
msg.meta =
wrongSecretKey.sign(SkMessage(spamProtectedShard.msgHash(msg))).toRaw()[0 .. 63]
discard await nodes[0].publish(some($spamProtectedShard), msg)
# Wait for gossip
await sleepAsync(2.seconds)
# only 100 messages are received (50 + 50) which demonstrate
# nodes[1] doest gossip invalid messages.
check:
msgReceived == 100
# peer1 got invalid messages from peer0
let p0Id = nodes[0].peerInfo.peerId
check:
nodes[1].wakuRelay.peerStats[p0Id].topicInfos[spamProtectedShard].invalidMessageDeliveries ==
100.0
# peer1 did not gossip further, so no other node rx invalid messages
for i in 0 ..< 5:
for k, v in nodes[i].wakuRelay.peerStats.mpairs:
if k == p0Id and i == 1:
continue
check:
v.topicInfos[spamProtectedShard].invalidMessageDeliveries == 0.0
# Stop all nodes
await allFutures(nodes.mapIt(it.stop()))
asyncTest "Tests vectors":
# keys
let privateKey = "5526a8990317c9b7b58d07843d270f9cd1d9aaee129294c1c478abf7261dd9e6"
let publicKey =
"049c5fac802da41e07e6cdf51c3b9a6351ad5e65921527f2df5b7d59fd9b56ab02bab736cdcfc37f25095e78127500da371947217a8cd5186ab890ea866211c3f6"
# message
let contentTopic = "content-topic"
let pubsubTopic = "pubsub-topic"
let payload =
"1A12E077D0E89F9CAC11FBBB6A676C86120B5AD3E248B1F180E98F15EE43D2DFCF62F00C92737B2FF6F59B3ABA02773314B991C41DC19ADB0AD8C17C8E26757B"
let timestamp = 1683208172339052800
let ephemeral = true
# expected values
let expectedMsgAppHash =
"662F8C20A335F170BD60ABC1F02AD66F0C6A6EE285DA2A53C95259E7937C0AE9"
let expectedSignature =
"127FA211B2514F0E974A055392946DC1A14052182A6ABEFB8A6CD7C51DA1BF2E40595D28EF1A9488797C297EED3AAC45430005FB3A7F037BDD9FC4BD99F59E63"
let secretKey = SkSecretKey.fromHex(privateKey).expect("valid key")
check:
secretKey.toPublicKey().toHex() == publicKey
secretKey.toHex() == privateKey
var msg = WakuMessage(
payload: payload.fromHex(),
contentTopic: contentTopic,
version: 2,
timestamp: timestamp,
ephemeral: ephemeral,
)
let msgAppHash = pubsubTopic.msgHash(msg)
let signature = secretKey.sign(SkMessage(msgAppHash)).toRaw()
check:
msgAppHash.toHex() == expectedMsgAppHash
signature.toHex() == expectedSignature