diff --git a/mixnet/v2/sim/config.py b/mixnet/v2/sim/config.py index 0c6328f..e3721a7 100644 --- a/mixnet/v2/sim/config.py +++ b/mixnet/v2/sim/config.py @@ -1,4 +1,8 @@ from dataclasses import dataclass +from typing import Self + +import dacite +import yaml @dataclass @@ -7,5 +11,24 @@ class Config: num_nodes: int num_mix_layers: int message_interval: int - message_prob: float + real_message_prob: float + cover_message_prob: float max_message_prep_time: float + + @classmethod + def load(cls, yaml_path: str) -> Self: + with open(yaml_path, "r") as f: + data = yaml.safe_load(f) + config = dacite.from_dict(data_class=Config, data=data) + + # Validations + assert config.running_time > 0 + assert config.num_nodes > 0 + assert 0 < config.num_mix_layers <= config.num_nodes + assert config.message_interval > 0 + assert config.real_message_prob >= 0 + assert config.cover_message_prob >= 0 + assert config.real_message_prob + config.cover_message_prob <= 1 + assert config.max_message_prep_time >= 0 + + return config diff --git a/mixnet/v2/sim/config.yaml b/mixnet/v2/sim/config.yaml index e692487..27229bf 100644 --- a/mixnet/v2/sim/config.yaml +++ b/mixnet/v2/sim/config.yaml @@ -2,5 +2,8 @@ running_time: 30 num_nodes: 5 num_mix_layers: 3 message_interval: 1 -message_prob: 0.2 +# (real_message_prob + cover_message_prob) should be <= 1 +# If the sum is < 1, then the remaining probability is for sending nothing. +real_message_prob: 0.1 +cover_message_prob: 0.4 max_message_prep_time: 0.3 \ No newline at end of file diff --git a/mixnet/v2/sim/main.py b/mixnet/v2/sim/main.py index 460c58b..1bc549d 100644 --- a/mixnet/v2/sim/main.py +++ b/mixnet/v2/sim/main.py @@ -1,10 +1,8 @@ import argparse -import dacite import matplotlib.pyplot as plt import pandas as pd import seaborn -import yaml from config import Config from simulation import Simulation @@ -14,10 +12,7 @@ if __name__ == "__main__": parser.add_argument("--config", type=str, required=True, help="Configuration file path") args = parser.parse_args() - with open(args.config, "r") as f: - config = yaml.safe_load(f) - config = dacite.from_dict(data_class=Config, data=config) - + config = Config.load(args.config) sim = Simulation(config) sim.run() diff --git a/mixnet/v2/sim/node.py b/mixnet/v2/sim/node.py index 1bb5dae..f821393 100644 --- a/mixnet/v2/sim/node.py +++ b/mixnet/v2/sim/node.py @@ -29,23 +29,29 @@ class Node: Creates/encapsulate a message and send it to the network through the mixnet """ while True: - # TODO: Use the realistic cover traffic emission rate yield self.env.timeout(self.config.message_interval) - if not self.is_message_sender(): + payload = self.payload_to_send() + if payload is None: # nothing to send in this turn continue prep_time = random.uniform(0, self.config.max_message_prep_time) yield self.env.timeout(prep_time) self.log("Sending a message to the mixnet") - msg = self.create_message() + msg = self.create_message(payload) self.env.process(self.p2p.broadcast(msg)) - def is_message_sender(self) -> bool: - return random.random() < self.config.message_prob + def payload_to_send(self) -> bytes | None: + rnd = random.random() + if rnd < self.config.real_message_prob: + return self.REAL_PAYLOAD + elif rnd < self.config.real_message_prob + self.config.cover_message_prob: + return self.COVER_PAYLOAD + else: + return None - def create_message(self) -> SphinxPacket: + def create_message(self, payload: bytes) -> SphinxPacket: """ Creates a message using the Sphinx format @return: @@ -54,7 +60,6 @@ class Node: public_keys = [mix.public_key for mix in mixes] # TODO: replace with realistic tx incentive_txs = [Node.create_incentive_tx(mix.public_key) for mix in mixes] - payload = random.choice([self.REAL_PAYLOAD, self.COVER_PAYLOAD]) return SphinxPacket(public_keys, incentive_txs, payload) def receive_message(self, msg: SphinxPacket | bytes): @@ -78,6 +83,8 @@ class Node: + self.PADDING_SEPARATOR + bytes(len(msg) - len(msg.payload) - len(self.PADDING_SEPARATOR))) self.env.process(self.p2p.broadcast(final_padded_msg)) + else: + self.log("Dropping a cover message: %s" % msg.payload) else: # TODO: use Poisson delay or something else yield self.env.timeout(random.randint(0, 5))