91 lines
3.7 KiB
Python
Raw Normal View History

2024-05-09 19:37:39 +09:00
import random
import simpy
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
2024-05-09 19:37:39 +09:00
2024-05-13 08:49:09 +09:00
from sphinx import SphinxPacket, Attachment
from p2p import P2p
2024-05-09 14:29:10 +09:00
class Node:
N_MIXES_IN_PATH = 2
2024-05-13 13:31:57 +09:00
INCENTIVE_TX_SIZE = 512
2024-05-13 09:04:35 +09:00
REAL_PAYLOAD = b"BLOCK"
COVER_PAYLOAD = b"COVER"
2024-05-13 14:12:10 +09:00
PADDING_SEPARATOR = b'\x01'
2024-05-09 19:37:39 +09:00
def __init__(self, id: int, env: simpy.Environment, p2p: P2p):
2024-05-09 19:37:39 +09:00
self.id = id
self.env = env
self.p2p = p2p
self.private_key = X25519PrivateKey.generate()
self.public_key = self.private_key.public_key()
2024-05-09 19:37:39 +09:00
self.action = self.env.process(self.send_message())
2024-05-09 14:29:10 +09:00
def send_message(self):
"""
Creates/encapsulate a message and send it to the network through the mixnet
"""
2024-05-13 09:00:01 +09:00
while True:
2024-05-09 14:29:10 +09:00
msg = self.create_message()
2024-05-13 13:34:34 +09:00
# TODO: Use cover traffic emission rate
2024-05-09 19:37:39 +09:00
yield self.env.timeout(2)
print("Sending a message at time %d" % self.env.now)
self.env.process(self.p2p.broadcast(msg))
2024-05-09 14:29:10 +09:00
def create_message(self) -> SphinxPacket:
2024-05-09 14:29:10 +09:00
"""
Creates a message using the Sphinx format
@return:
"""
2024-05-09 19:37:39 +09:00
mixes = self.p2p.get_nodes(self.N_MIXES_IN_PATH)
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]
2024-05-13 09:04:35 +09:00
payload = random.choice([self.REAL_PAYLOAD, self.COVER_PAYLOAD])
return SphinxPacket(public_keys, incentive_txs, payload)
2024-05-09 14:29:10 +09:00
def receive_message(self, msg: SphinxPacket | bytes):
2024-05-09 14:29:10 +09:00
"""
Receives a message from the network, processes it,
and forwards it to the next mix or the entire network if necessary.
@param msg: the message to be processed
"""
# simulating network latency
yield self.env.timeout(random.randint(0, 3))
if isinstance(msg, SphinxPacket):
msg, incentive_tx = msg.unwrap(self.private_key)
if self.is_my_incentive_tx(incentive_tx):
self.log("Receiving SphinxPacket. It's mine!")
2024-05-13 13:31:57 +09:00
if msg.is_all_unwrapped():
if msg.payload == self.REAL_PAYLOAD:
2024-05-13 14:12:10 +09:00
# Pad the final msg to the same size as a SphinxPacket,
# assuming that the final msg is going to be sent via secure channels (TLS, Noise, etc.)
final_padded_msg = (msg.payload
+ self.PADDING_SEPARATOR
+ bytes(len(msg) - len(msg.payload) - len(self.PADDING_SEPARATOR)))
self.env.process(self.p2p.broadcast(final_padded_msg))
else:
# TODO: use Poisson delay
yield self.env.timeout(random.randint(0, 5))
self.env.process(self.p2p.broadcast(msg))
else:
self.log("Receiving SphinxPacket, but not mine")
else:
2024-05-13 14:12:10 +09:00
final_msg = msg[:msg.rfind(self.PADDING_SEPARATOR)]
self.log("Received final message: %s" % final_msg)
2024-05-13 12:53:14 +09:00
# TODO: This is a dummy logic
@classmethod
def create_incentive_tx(cls, mix_public_key: X25519PublicKey) -> Attachment:
2024-05-13 12:53:14 +09:00
public_key = mix_public_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
public_key += bytes(cls.INCENTIVE_TX_SIZE - len(public_key))
return Attachment(public_key)
def is_my_incentive_tx(self, tx: Attachment) -> bool:
return tx == Node.create_incentive_tx(self.public_key)
def log(self, msg):
print("Node:%d at %d: %s" % (self.id, self.env.now, msg))