Embed Robustness into `mixnet.py` (#61)

This commit is contained in:
Youngjoon Lee 2024-02-08 15:39:50 +09:00 committed by GitHub
parent cde1e92c9e
commit 5dd7b2730a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 180 additions and 262 deletions

View File

@ -4,49 +4,47 @@ import asyncio
from contextlib import suppress from contextlib import suppress
from typing import Self from typing import Self
from mixnet.config import MixnetConfig from mixnet.config import MixClientConfig, MixnetTopology
from mixnet.node import PacketQueue from mixnet.node import PacketQueue
from mixnet.packet import PacketBuilder from mixnet.packet import PacketBuilder
from mixnet.poisson import poisson_interval_sec from mixnet.poisson import poisson_interval_sec
class MixClient: class MixClient:
__config: MixnetConfig config: MixClientConfig
real_packet_queue: PacketQueue
__real_packet_queue: PacketQueue outbound_socket: PacketQueue
__outbound_socket: PacketQueue task: asyncio.Task # A reference just to prevent task from being garbage collected
__task: asyncio.Task # A reference just to prevent task from being garbage collected
@classmethod @classmethod
async def new( async def new(
cls, cls,
config: MixnetConfig, config: MixClientConfig,
) -> Self: ) -> Self:
self = cls() self = cls()
self.__config = config self.config = config
self.__real_packet_queue = asyncio.Queue() self.real_packet_queue = asyncio.Queue()
self.__outbound_socket = asyncio.Queue() self.outbound_socket = asyncio.Queue()
self.__task = asyncio.create_task(self.__run()) self.task = asyncio.create_task(self.__run())
return self return self
def set_config(self, config: MixnetConfig) -> None: def set_topology(self, topology: MixnetTopology) -> None:
""" """
Replace the old config with the new config received Replace the old topology with the new topology received
In real implementations, this method may be integrated in a long-running task. In real implementations, this method may be integrated in a long-running task.
Here in the spec, this method has been simplified as a setter, assuming the single-thread test environment. Here in the spec, this method has been simplified as a setter, assuming the single-thread test environment.
""" """
self.__config = config self.config.topology = topology
def get_config(self) -> MixnetConfig: # Only for testing
return self.__config def get_topology(self) -> MixnetTopology:
return self.config.topology
async def send_message(self, msg: bytes) -> None: async def send_message(self, msg: bytes) -> None:
packets_and_routes = PacketBuilder.build_real_packets( packets_and_routes = PacketBuilder.build_real_packets(msg, self.config.topology)
msg, self.__config.topology
)
for packet, route in packets_and_routes: for packet, route in packets_and_routes:
await self.__real_packet_queue.put((route[0].addr, packet)) await self.real_packet_queue.put((route[0].addr, packet))
def subscribe_messages(self) -> "asyncio.Queue[bytes]": def subscribe_messages(self) -> "asyncio.Queue[bytes]":
""" """
@ -54,10 +52,6 @@ class MixClient:
""" """
return asyncio.Queue() return asyncio.Queue()
@property
def outbound_socket(self) -> PacketQueue:
return self.__outbound_socket
async def __run(self): async def __run(self):
""" """
Emit packets at the Poisson emission_rate_per_min. Emit packets at the Poisson emission_rate_per_min.
@ -73,7 +67,7 @@ class MixClient:
emission_notifier_queue = asyncio.Queue() emission_notifier_queue = asyncio.Queue()
_ = asyncio.create_task( _ = asyncio.create_task(
self.__emission_notifier( self.__emission_notifier(
self.__config.emission_rate_per_min, emission_notifier_queue self.config.emission_rate_per_min, emission_notifier_queue
) )
) )
@ -81,7 +75,7 @@ class MixClient:
# Wait until the next emission time # Wait until the next emission time
_ = await emission_notifier_queue.get() _ = await emission_notifier_queue.get()
try: try:
await self.__emit(self.__config.redundancy, redundant_real_packet_queue) await self.__emit(self.config.redundancy, redundant_real_packet_queue)
finally: finally:
# Python convention: indicate that the previously enqueued task has been processed # Python convention: indicate that the previously enqueued task has been processed
emission_notifier_queue.task_done() emission_notifier_queue.task_done()
@ -93,23 +87,23 @@ class MixClient:
): ):
if not redundant_real_packet_queue.empty(): if not redundant_real_packet_queue.empty():
addr, packet = redundant_real_packet_queue.get_nowait() addr, packet = redundant_real_packet_queue.get_nowait()
await self.__outbound_socket.put((addr, packet)) await self.outbound_socket.put((addr, packet))
return return
if not self.__real_packet_queue.empty(): if not self.real_packet_queue.empty():
addr, packet = self.__real_packet_queue.get_nowait() addr, packet = self.real_packet_queue.get_nowait()
# Schedule redundant real packets # Schedule redundant real packets
for _ in range(redundancy - 1): for _ in range(redundancy - 1):
redundant_real_packet_queue.put_nowait((addr, packet)) redundant_real_packet_queue.put_nowait((addr, packet))
await self.__outbound_socket.put((addr, packet)) await self.outbound_socket.put((addr, packet))
packets_and_routes = PacketBuilder.build_drop_cover_packets( packets_and_routes = PacketBuilder.build_drop_cover_packets(
b"drop cover", self.__config.topology b"drop cover", self.config.topology
) )
# We have a for loop here, but we expect that the total num of packets is 1 # We have a for loop here, but we expect that the total num of packets is 1
# because the dummy message is short. # because the dummy message is short.
for packet, route in packets_and_routes: for packet, route in packets_and_routes:
await self.__outbound_socket.put((route[0].addr, packet)) await self.outbound_socket.put((route[0].addr, packet))
async def __emission_notifier( async def __emission_notifier(
self, emission_rate_per_min: int, queue: asyncio.Queue self, emission_rate_per_min: int, queue: asyncio.Queue
@ -119,6 +113,6 @@ class MixClient:
queue.put_nowait(None) queue.put_nowait(None)
async def cancel(self) -> None: async def cancel(self) -> None:
self.__task.cancel() self.task.cancel()
with suppress(asyncio.CancelledError): with suppress(asyncio.CancelledError):
await self.__task await self.task

View File

@ -11,22 +11,59 @@ from cryptography.hazmat.primitives.asymmetric.x25519 import (
from pysphinx.node import Node from pysphinx.node import Node
from mixnet.bls import BlsPrivateKey, BlsPublicKey from mixnet.bls import BlsPrivateKey, BlsPublicKey
from mixnet.fisheryates import FisherYates
@dataclass @dataclass
class MixnetConfig: class MixnetConfig:
topology_config: MixnetTopologyConfig
mixclient_config: MixClientConfig
mixnode_config: MixNodeConfig
@dataclass
class MixnetTopologyConfig:
mixnode_candidates: List[MixNodeInfo]
size: MixnetTopologySize
entropy: bytes
@dataclass
class MixClientConfig:
emission_rate_per_min: int # Poisson rate parameter: lambda emission_rate_per_min: int # Poisson rate parameter: lambda
redundancy: int redundancy: int
delay_rate_per_min: int # Poisson rate parameter: mu
topology: MixnetTopology topology: MixnetTopology
@dataclass
class MixNodeConfig:
encryption_private_key: X25519PrivateKey
delay_rate_per_min: int # Poisson rate parameter: mu
@dataclass @dataclass
class MixnetTopology: class MixnetTopology:
# In production, this can be a 1-D array, which is accessible by indexes. # In production, this can be a 1-D array, which is accessible by indexes.
# Here, we use a 2-D array for readability. # Here, we use a 2-D array for readability.
layers: List[List[MixNodeInfo]] layers: List[List[MixNodeInfo]]
def __init__(
self,
config: MixnetTopologyConfig,
) -> None:
"""
Build a new topology deterministically using an entropy and a given set of candidates.
"""
shuffled = FisherYates.shuffle(config.mixnode_candidates, config.entropy)
sampled = shuffled[: config.size.num_total_mixnodes()]
layers = []
for layer_id in range(config.size.num_layers):
start = layer_id * config.size.num_mixnodes_per_layer
layer = sampled[start : start + config.size.num_mixnodes_per_layer]
layers.append(layer)
self.layers = layers
def generate_route(self, mix_destination: MixNodeInfo) -> list[MixNodeInfo]: def generate_route(self, mix_destination: MixNodeInfo) -> list[MixNodeInfo]:
""" """
Generate a mix route for a Sphinx packet. Generate a mix route for a Sphinx packet.
@ -45,6 +82,15 @@ class MixnetTopology:
return random.choice(self.layers[-1]) return random.choice(self.layers[-1])
@dataclass
class MixnetTopologySize:
num_layers: int
num_mixnodes_per_layer: int
def num_total_mixnodes(self) -> int:
return self.num_layers * self.num_mixnodes_per_layer
# 32-byte that represents an IP address and a port of a mix node. # 32-byte that represents an IP address and a port of a mix node.
NodeAddress: TypeAlias = bytes NodeAddress: TypeAlias = bytes

View File

@ -1,51 +1,62 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from typing import Self from contextlib import suppress
from typing import Self, TypeAlias
from cryptography.hazmat.primitives.asymmetric.x25519 import (
X25519PrivateKey,
)
from mixnet.client import MixClient from mixnet.client import MixClient
from mixnet.config import MixnetConfig from mixnet.config import MixnetConfig, MixnetTopology, MixnetTopologyConfig
from mixnet.node import MixNode from mixnet.node import MixNode
EntropyQueue: TypeAlias = "asyncio.Queue[bytes]"
class Mixnet: class Mixnet:
__mixclient: MixClient topology_config: MixnetTopologyConfig
__mixnode: MixNode
mixclient: MixClient
mixnode: MixNode
entropy_queue: EntropyQueue
task: asyncio.Task # A reference just to prevent task from being garbage collected
@classmethod @classmethod
async def new( async def new(
cls, cls,
encryption_private_key: X25519PrivateKey,
config: MixnetConfig, config: MixnetConfig,
entropy_queue: EntropyQueue,
) -> Self: ) -> Self:
self = cls() self = cls()
self.__mixclient = await MixClient.new(config) self.topology_config = config.topology_config
self.__mixnode = await MixNode.new(encryption_private_key, config) self.mixclient = await MixClient.new(config.mixclient_config)
self.mixnode = await MixNode.new(config.mixnode_config)
self.entropy_queue = entropy_queue
self.task = asyncio.create_task(self.__consume_entropy())
return self return self
async def publish_message(self, msg: bytes) -> None: async def publish_message(self, msg: bytes) -> None:
await self.__mixclient.send_message(msg) await self.mixclient.send_message(msg)
def subscribe_messages(self) -> "asyncio.Queue[bytes]": def subscribe_messages(self) -> "asyncio.Queue[bytes]":
return self.__mixclient.subscribe_messages() return self.mixclient.subscribe_messages()
def set_config(self, config: MixnetConfig) -> None: async def __consume_entropy(
""" self,
Replace the old config with the new config received. ) -> None:
while True:
entropy = await self.entropy_queue.get()
self.topology_config.entropy = entropy
In real implementations, this method should be a long-running task, accepting configs periodically. topology = MixnetTopology(self.topology_config)
Here in the spec, this method has been simplified as a setter, assuming the single-thread test environment. self.mixclient.set_topology(topology)
"""
self.__mixclient.set_config(config)
self.__mixnode.set_config(config)
def get_config(self) -> MixnetConfig:
return self.__mixclient.get_config()
async def cancel(self) -> None: async def cancel(self) -> None:
await self.__mixclient.cancel() self.task.cancel()
await self.__mixnode.cancel() with suppress(asyncio.CancelledError):
await self.task
await self.mixclient.cancel()
await self.mixnode.cancel()
# Only for testing
def get_topology(self) -> MixnetTopology:
return self.mixclient.get_topology()

View File

@ -15,7 +15,7 @@ from pysphinx.sphinx import (
UnknownHeaderTypeError, UnknownHeaderTypeError,
) )
from mixnet.config import MixnetConfig, NodeAddress from mixnet.config import MixNodeConfig, NodeAddress
from mixnet.poisson import poisson_interval_sec from mixnet.poisson import poisson_interval_sec
PacketQueue: TypeAlias = "asyncio.Queue[Tuple[NodeAddress, SphinxPacket]]" PacketQueue: TypeAlias = "asyncio.Queue[Tuple[NodeAddress, SphinxPacket]]"
@ -32,30 +32,24 @@ class MixNode:
in order to define the MixNode as a simple dataclass for clarity. in order to define the MixNode as a simple dataclass for clarity.
""" """
__config: MixnetConfig config: MixNodeConfig
inbound_socket: PacketQueue inbound_socket: PacketQueue
outbound_socket: PacketPayloadQueue outbound_socket: PacketPayloadQueue
__task: asyncio.Task # A reference just to prevent task from being garbage collected task: asyncio.Task # A reference just to prevent task from being garbage collected
@classmethod @classmethod
async def new( async def new(
cls, cls,
encryption_private_key: X25519PrivateKey, config: MixNodeConfig,
config: MixnetConfig,
) -> Self: ) -> Self:
self = cls() self = cls()
self.__config = config self.config = config
self.__establish_connections()
self.inbound_socket = asyncio.Queue() self.inbound_socket = asyncio.Queue()
self.outbound_socket = asyncio.Queue() self.outbound_socket = asyncio.Queue()
self.__task = asyncio.create_task(self.__run(encryption_private_key)) self.task = asyncio.create_task(self.__run())
return self return self
async def __run( async def __run(self):
self,
encryption_private_key: X25519PrivateKey,
):
""" """
Read SphinxPackets from inbound socket and spawn a thread for each packet to process it. Read SphinxPackets from inbound socket and spawn a thread for each packet to process it.
@ -70,7 +64,9 @@ class MixNode:
_, packet = await self.inbound_socket.get() _, packet = await self.inbound_socket.get()
task = asyncio.create_task( task = asyncio.create_task(
self.__process_packet( self.__process_packet(
packet, encryption_private_key, self.__config.delay_rate_per_min packet,
self.config.encryption_private_key,
self.config.delay_rate_per_min,
) )
) )
self.tasks.add(task) self.tasks.add(task)
@ -105,30 +101,7 @@ class MixNode:
case _: case _:
raise UnknownHeaderTypeError raise UnknownHeaderTypeError
def set_config(self, config: MixnetConfig) -> None:
"""
Replace the old config with the new config received.
If topology has been changed, start establishing new network connections in background.
In real implementations, this method may be integrated in a long-running task.
Here in the spec, this method has been simplified as a setter, assuming the single-thread test environment.
"""
if self.__config.topology != config.topology:
self.__establish_connections()
self.__config = config
def __establish_connections(self) -> None:
"""
Establish network connections in advance based on the topology received.
This is just a preparation to forward subsequent packets as quickly as possible,
but this is not a strict requirement.
In real implementations, this should be a background task.
"""
pass
async def cancel(self) -> None: async def cancel(self) -> None:
self.__task.cancel() self.task.cancel()
with suppress(asyncio.CancelledError): with suppress(asyncio.CancelledError):
await self.__task await self.task

View File

@ -1,100 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List
from mixnet.config import MixnetConfig, MixnetTopology, MixNodeInfo
from mixnet.fisheryates import FisherYates
from mixnet.mixnet import Mixnet
class Robustness:
"""
A robustness layer is placed on top of a mixnet layer and a consensus layer,
to separate their responsibilities and minimize dependencies between them.
For v1, the role of robustness layer is building a new mixnet topology
and injecting it to the mixnet layer,
whenever a new entropy is received from the consensus layer.
A static list of nodes is used for building topologies deterministically.
This can be changed in later versions.
In later versions, the robustness layer will have more responsibilities.
"""
def __init__(
self,
config: RobustnessConfig,
mixnet: Mixnet,
) -> None:
self.__config = config
self.__mixnet = mixnet
def set_entropy(self, entropy: bytes) -> None:
"""
Given a entropy received, build a new topology and send it to mixnet.
In v1, this doesn't change any mixnet config except topology.
In real implementations, this method should be a long-running task, consuming entropy periodically.
Here in the spec, this method has been simplified as a setter, assuming the single-thread test environment.
"""
self.__config.mixnet.mixnet_layer_config.topology = self.build_topology(
self.__config.mixnet.mixnode_candidates,
self.__config.mixnet.topology_size,
entropy,
)
self.__mixnet.set_config(self.__config.mixnet.mixnet_layer_config)
@staticmethod
def build_topology(
mixnode_candidates: List[MixNodeInfo],
mixnet_topology_size: MixnetTopologySize,
entropy: bytes,
) -> MixnetTopology:
"""
Build a new topology deterministically using an entropy and a given set of candidates.
"""
shuffled = FisherYates.shuffle(mixnode_candidates, entropy)
sampled = shuffled[: mixnet_topology_size.num_total_mixnodes()]
layers = []
for layer_id in range(mixnet_topology_size.num_layers):
start = layer_id * mixnet_topology_size.num_mixnodes_per_layer
layer = sampled[start : start + mixnet_topology_size.num_mixnodes_per_layer]
layers.append(layer)
return MixnetTopology(layers)
@dataclass
class RobustnessConfig:
"""In v1, the robustness layer manages configs only for the mixnet layer."""
mixnet: RobustnessMixnetConfig
class RobustnessMixnetConfig:
"""
Configurations for the mixnet layer
These configurations are meant to be changed over time according to other parameters from other layers (e.g. consensus).
"""
def __init__(
self,
mixnode_candidates: List[MixNodeInfo],
mixnet_topology_size: MixnetTopologySize,
mixnet_layer_config: MixnetConfig,
) -> None:
assert mixnet_topology_size.num_total_mixnodes() <= len(mixnode_candidates)
self.mixnode_candidates = mixnode_candidates
self.topology_size = mixnet_topology_size
# A config to be injected to the mixnet layer whenever it is updated
self.mixnet_layer_config = mixnet_layer_config
@dataclass
class MixnetTopologySize:
num_layers: int
num_mixnodes_per_layer: int
def num_total_mixnodes(self) -> int:
return self.num_layers * self.num_mixnodes_per_layer

View File

@ -6,7 +6,7 @@ import numpy
from mixnet.client import MixClient from mixnet.client import MixClient
from mixnet.poisson import poisson_mean_interval_sec from mixnet.poisson import poisson_mean_interval_sec
from mixnet.test_utils import ( from mixnet.test_utils import (
init_robustness_mixnet_config, init_mixnet_config,
with_test_timeout, with_test_timeout,
) )
from mixnet.utils import random_bytes from mixnet.utils import random_bytes
@ -15,7 +15,7 @@ from mixnet.utils import random_bytes
class TestMixClient(IsolatedAsyncioTestCase): class TestMixClient(IsolatedAsyncioTestCase):
@with_test_timeout(100) @with_test_timeout(100)
async def test_mixclient(self): async def test_mixclient(self):
config = init_robustness_mixnet_config().mixnet_layer_config config = init_mixnet_config().mixclient_config
config.emission_rate_per_min = 30 config.emission_rate_per_min = 30
config.redundancy = 3 config.redundancy = 3

View File

@ -1,26 +1,20 @@
import asyncio
from unittest import IsolatedAsyncioTestCase from unittest import IsolatedAsyncioTestCase
from mixnet.mixnet import Mixnet from mixnet.mixnet import Mixnet
from mixnet.robustness import Robustness, RobustnessConfig from mixnet.test_utils import init_mixnet_config
from mixnet.test_utils import init_robustness_mixnet_config
class TestMixnet(IsolatedAsyncioTestCase): class TestMixnet(IsolatedAsyncioTestCase):
async def test_topology_from_robustness(self): async def test_topology_from_robustness(self):
robustness_mixnet_config = init_robustness_mixnet_config() config = init_mixnet_config()
entropy_queue = asyncio.Queue()
mixnet = await Mixnet.new( mixnet = await Mixnet.new(config, entropy_queue)
robustness_mixnet_config.mixnode_candidates[0].encryption_private_key,
robustness_mixnet_config.mixnet_layer_config,
)
try: try:
robustness = Robustness(RobustnessConfig(robustness_mixnet_config), mixnet) old_topology = config.mixclient_config.topology
self.assertEqual( await entropy_queue.put(b"new entropy")
robustness_mixnet_config.mixnet_layer_config, mixnet.get_config() await asyncio.sleep(1)
) self.assertNotEqual(old_topology, mixnet.get_topology())
old_topology = robustness_mixnet_config.mixnet_layer_config.topology
robustness.set_entropy(b"new entropy")
self.assertNotEqual(old_topology, mixnet.get_config().topology)
finally: finally:
await mixnet.cancel() await mixnet.cancel()

View File

@ -9,7 +9,7 @@ from mixnet.node import MixNode, NodeAddress, PacketQueue
from mixnet.packet import PacketBuilder from mixnet.packet import PacketBuilder
from mixnet.poisson import poisson_interval_sec, poisson_mean_interval_sec from mixnet.poisson import poisson_interval_sec, poisson_mean_interval_sec
from mixnet.test_utils import ( from mixnet.test_utils import (
init_robustness_mixnet_config, init_mixnet_config,
with_test_timeout, with_test_timeout,
) )
@ -24,14 +24,17 @@ class TestMixNodeRunner(IsolatedAsyncioTestCase):
and if processing is delayed according to an exponential distribution with a rate `mu`, and if processing is delayed according to an exponential distribution with a rate `mu`,
the rate of outputs should be `lambda`. the rate of outputs should be `lambda`.
""" """
config = init_robustness_mixnet_config().mixnet_layer_config config = init_mixnet_config()
config.emission_rate_per_min = 120 # lambda (= 2msg/sec) config.mixclient_config.emission_rate_per_min = 120 # lambda (= 2msg/sec)
config.delay_rate_per_min = 30 # mu (= 2s delay on average) config.mixnode_config.delay_rate_per_min = 30 # mu (= 2s delay on average)
packet, route = PacketBuilder.build_real_packets(b"msg", config.topology)[0] packet, route = PacketBuilder.build_real_packets(
b"msg", config.mixclient_config.topology
)[0]
# Start only the first mix node for testing # Start only the first mix node for testing
mixnode = await MixNode.new(route[0].encryption_private_key, config) config.mixnode_config.encryption_private_key = route[0].encryption_private_key
mixnode = await MixNode.new(config.mixnode_config)
try: try:
# Send packets to the first mix node in a Poisson distribution # Send packets to the first mix node in a Poisson distribution
packet_count = 100 packet_count = 100
@ -43,7 +46,7 @@ class TestMixNodeRunner(IsolatedAsyncioTestCase):
packet, packet,
route[0].addr, route[0].addr,
packet_count, packet_count,
config.emission_rate_per_min, config.mixclient_config.emission_rate_per_min,
sent_packet_queue, sent_packet_queue,
) )
) )
@ -77,14 +80,19 @@ class TestMixNodeRunner(IsolatedAsyncioTestCase):
# a mean interval between outputs must be `1/lambda`. # a mean interval between outputs must be `1/lambda`.
self.assertAlmostEqual( self.assertAlmostEqual(
float(numpy.mean(intervals)), float(numpy.mean(intervals)),
poisson_mean_interval_sec(config.emission_rate_per_min), poisson_mean_interval_sec(
config.mixclient_config.emission_rate_per_min
),
delta=1.0, delta=1.0,
) )
# If runner is a M/M/inf queue, # If runner is a M/M/inf queue,
# a mean number of jobs being processed/scheduled in the runner must be `lambda/mu`. # a mean number of jobs being processed/scheduled in the runner must be `lambda/mu`.
self.assertAlmostEqual( self.assertAlmostEqual(
float(numpy.mean(num_jobs)), float(numpy.mean(num_jobs)),
round(config.emission_rate_per_min / config.delay_rate_per_min), round(
config.mixclient_config.emission_rate_per_min
/ config.mixnode_config.delay_rate_per_min
),
delta=1.5, delta=1.5,
) )
finally: finally:

View File

@ -10,13 +10,13 @@ from mixnet.packet import (
MessageReconstructor, MessageReconstructor,
PacketBuilder, PacketBuilder,
) )
from mixnet.test_utils import init_robustness_mixnet_config from mixnet.test_utils import init_mixnet_config
from mixnet.utils import random_bytes from mixnet.utils import random_bytes
class TestPacket(TestCase): class TestPacket(TestCase):
def test_real_packet(self): def test_real_packet(self):
topology = init_robustness_mixnet_config().mixnet_layer_config.topology topology = init_mixnet_config().mixclient_config.topology
msg = random_bytes(3500) msg = random_bytes(3500)
packets_and_routes = PacketBuilder.build_real_packets(msg, topology) packets_and_routes = PacketBuilder.build_real_packets(msg, topology)
self.assertEqual(4, len(packets_and_routes)) self.assertEqual(4, len(packets_and_routes))
@ -47,7 +47,7 @@ class TestPacket(TestCase):
) )
def test_cover_packet(self): def test_cover_packet(self):
topology = init_robustness_mixnet_config().mixnet_layer_config.topology topology = init_mixnet_config().mixclient_config.topology
msg = b"cover" msg = b"cover"
packets_and_routes = PacketBuilder.build_drop_cover_packets(msg, topology) packets_and_routes = PacketBuilder.build_drop_cover_packets(msg, topology)
self.assertEqual(1, len(packets_and_routes)) self.assertEqual(1, len(packets_and_routes))

View File

@ -1,14 +0,0 @@
from unittest import TestCase
from mixnet.test_utils import init_robustness_mixnet_config
class TestRobustness(TestCase):
def test_build_topology(self):
robustness_mixnet_config = init_robustness_mixnet_config()
topology = robustness_mixnet_config.mixnet_layer_config.topology
topology_size = robustness_mixnet_config.topology_size
self.assertEqual(len(topology.layers), topology_size.num_layers)
for layer in topology.layers:
self.assertEqual(len(layer), topology_size.num_mixnodes_per_layer)

View File

@ -3,8 +3,15 @@ import asyncio
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from mixnet.bls import generate_bls from mixnet.bls import generate_bls
from mixnet.config import MixnetConfig, MixNodeInfo from mixnet.config import (
from mixnet.robustness import MixnetTopologySize, Robustness, RobustnessMixnetConfig MixClientConfig,
MixNodeConfig,
MixnetConfig,
MixNodeInfo,
MixnetTopology,
MixnetTopologyConfig,
MixnetTopologySize,
)
from mixnet.utils import random_bytes from mixnet.utils import random_bytes
@ -19,22 +26,21 @@ def with_test_timeout(t):
return wrapper return wrapper
def init_robustness_mixnet_config() -> RobustnessMixnetConfig: def init_mixnet_config() -> MixnetConfig:
mixnode_candidates = [ topology_config = MixnetTopologyConfig(
MixNodeInfo( [
generate_bls(), MixNodeInfo(
X25519PrivateKey.generate(), generate_bls(),
random_bytes(32), X25519PrivateKey.generate(),
) random_bytes(32),
for _ in range(12) )
] for _ in range(12)
topology_size = MixnetTopologySize(3, 3) ],
mixnet_layer_config = MixnetConfig( MixnetTopologySize(3, 3),
30, b"entropy",
3,
30,
Robustness.build_topology(mixnode_candidates, topology_size, b"entropy"),
) )
return RobustnessMixnetConfig( mixclient_config = MixClientConfig(30, 3, MixnetTopology(topology_config))
mixnode_candidates, topology_size, mixnet_layer_config mixnode_config = MixNodeConfig(
topology_config.mixnode_candidates[0].encryption_private_key, 30
) )
return MixnetConfig(topology_config, mixclient_config, mixnode_config)