diff --git a/mixnet/config.py b/mixnet/config.py index 140a913..3cb873f 100644 --- a/mixnet/config.py +++ b/mixnet/config.py @@ -1,7 +1,8 @@ from __future__ import annotations +import hashlib import random -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import List from cryptography.hazmat.primitives.asymmetric.x25519 import ( @@ -26,10 +27,19 @@ class NodeConfig: peering_degree: int mix_path_length: int # TODO: use this when creating Sphinx packets + def id(self, short=False) -> str: + id = ( + hashlib.sha256(self.private_key.public_key().public_bytes_raw()) + .digest() + .hex() + ) + return id[:8] if short else id + @dataclass class MixMembership: nodes: List[NodeInfo] + rng: random.Random = field(default_factory=random.Random) def generate_route(self, num_hops: int, last_mix: NodeInfo) -> list[NodeInfo]: """ @@ -43,7 +53,7 @@ class MixMembership: """ Choose a mix node as a mix destination that will reconstruct a message from Sphinx packets. """ - return random.choice(self.nodes) + return self.rng.choice(self.nodes) @dataclass diff --git a/mixnet/node.py b/mixnet/node.py index a2e110c..b24134e 100644 --- a/mixnet/node.py +++ b/mixnet/node.py @@ -62,7 +62,7 @@ class Node: if msg_with_flag is not None: flag, msg = PacketBuilder.parse_msg_and_flag(msg_with_flag) if flag == MessageFlag.MESSAGE_FLAG_REAL: - print(f"Broadcasting message finally: {msg}") + print(f"{self.config.id(True)}: Broadcasting message finally: {msg}") await self.broadcast_channel.put(msg) def connect( @@ -93,7 +93,7 @@ class Node: ) async def send_message(self, msg: bytes): - print(f"Sending message: {msg}") + print(f"{self.config.id(True)}: Sending message: {msg}") for packet, _ in PacketBuilder.build_real_packets( msg, self.global_config.membership ): diff --git a/mixnet/sim/config.py b/mixnet/sim/config.py index bdffb7a..f788d90 100644 --- a/mixnet/sim/config.py +++ b/mixnet/sim/config.py @@ -1,5 +1,7 @@ from __future__ import annotations +import hashlib +import random from dataclasses import dataclass import dacite @@ -19,7 +21,11 @@ class Config: def load(cls, yaml_path: str) -> Config: with open(yaml_path, "r") as f: data = yaml.safe_load(f) - config = dacite.from_dict(data_class=Config, data=data) + config = dacite.from_dict( + data_class=Config, + data=data, + config=dacite.Config(type_hooks={random.Random: seed_to_random}), + ) # Validations config.simulation.validate() @@ -41,33 +47,72 @@ class SimulationConfig: @dataclass class LogicConfig: - lottery_interval_sec: float - sender_prob: float + sender_lottery: LotteryConfig def validate(self): - assert self.lottery_interval_sec > 0 - assert self.sender_prob > 0 + self.sender_lottery.validate() + + +@dataclass +class LotteryConfig: + interval_sec: float + probability: float + seed: random.Random + + def validate(self): + assert self.interval_sec > 0 + assert self.probability > 0 + assert self.seed is not None @dataclass class MixnetConfig: num_nodes: int transmission_rate_per_sec: int - peering_degree: int - max_mix_path_length: int + peering: PeeringConfig + mix_path: MixPathConfig def validate(self): assert self.num_nodes > 0 assert self.transmission_rate_per_sec > 0 - assert self.peering_degree > 0 - assert self.max_mix_path_length > 0 + self.peering.validate() + self.mix_path.validate() def node_configs(self) -> list[NodeConfig]: return [ NodeConfig( - X25519PrivateKey.generate(), - self.peering_degree, + self._gen_private_key(i), + self.peering.degree, self.transmission_rate_per_sec, ) - for _ in range(self.num_nodes) + for i in range(self.num_nodes) ] + + def _gen_private_key(self, node_idx: int) -> X25519PrivateKey: + return X25519PrivateKey.from_private_bytes( + hashlib.sha256(node_idx.to_bytes(4, "big")).digest()[:32] + ) + + +@dataclass +class PeeringConfig: + degree: int + seed: random.Random + + def validate(self): + assert self.degree > 0 + assert self.seed is not None + + +@dataclass +class MixPathConfig: + max_length: int + seed: random.Random + + def validate(self): + assert self.max_length > 0 + assert self.seed is not None + + +def seed_to_random(seed: int) -> random.Random: + return random.Random(seed) diff --git a/mixnet/sim/config.yaml b/mixnet/sim/config.yaml index f605b42..9c7f15e 100644 --- a/mixnet/sim/config.yaml +++ b/mixnet/sim/config.yaml @@ -3,11 +3,17 @@ simulation: net_latency_sec: 0.01 logic: - lottery_interval_sec: 1 - sender_prob: 0.01 + sender_lottery: + interval_sec: 1 + probability: 0.01 + seed: 10 mixnet: num_nodes: 5 transmission_rate_per_sec: 10 - peering_degree: 6 - max_mix_path_length: 3 + peering: + degree: 6 + seed: 0 + mix_path: + max_length: 3 + seed: 3 diff --git a/mixnet/sim/simulation.py b/mixnet/sim/simulation.py index 38fde6b..b5520b8 100644 --- a/mixnet/sim/simulation.py +++ b/mixnet/sim/simulation.py @@ -1,5 +1,3 @@ -import random - import usim import mixnet.framework.usim as usimfw @@ -16,7 +14,6 @@ class Simulation: framework: Framework def __init__(self, config: Config): - random.seed() self.config = config async def run(self): @@ -39,10 +36,11 @@ class Simulation: [ NodeInfo(node_config.private_key.public_key()) for node_config in node_configs - ] + ], + self.config.mixnet.mix_path.seed, ), self.config.mixnet.transmission_rate_per_sec, - self.config.mixnet.max_mix_path_length, + self.config.mixnet.mix_path.max_length, ) nodes = [ Node(self.framework, node_config, global_config) @@ -66,7 +64,8 @@ class Simulation: return MeteredRemoteSimplexConnection(self.config.simulation, self.framework) async def run_logic(self, node: Node): + lottery_config = self.config.logic.sender_lottery while True: - await (usim.time + self.config.logic.lottery_interval_sec) - if random.random() < self.config.logic.sender_prob: + await (usim.time + lottery_config.interval_sec) + if lottery_config.seed.random() < lottery_config.probability: await node.send_message(b"selected block")