diff --git a/model/alice.py b/model/alice.py index e81ee44..3d3abee 100644 --- a/model/alice.py +++ b/model/alice.py @@ -9,22 +9,23 @@ from utils import bin_encode, bin_decode class Alice: channel: Channel sent: typing.List[bool] = field(default_factory=list) - my_basises: typing.List[bool] = field(default_factory=list) + my_bases: typing.List[bool] = field(default_factory=list) key: typing.List[bool] = field(default_factory=list) + correctness: typing.List[bool] = field(default_factory=list) def generate_key_and_send(self, n: int = 100): self.sent = [bool(r.randint(0, 1)) for _ in range(n)] - self.my_basises = [bool(r.randint(0, 1)) for _ in range(n)] - for key, basis in zip(self.sent, self.my_basises): + self.my_bases = [bool(r.randint(0, 1)) for _ in range(n)] + for key, basis in zip(self.sent, self.my_bases): self.channel.send_bit(key, basis) def generate_key(self, n: int = 100): self.generate_key_and_send(n) - bobs_basises, bobs_correctness = bin_decode(self.channel.recv_classical(n, is_bob=False)), bin_decode( + bobs_bases, bobs_correctness = bin_decode(self.channel.recv_classical(n, is_bob=False)), bin_decode( self.channel.recv_classical(n, is_bob=False)) - acceptance = [ + self.correctness = [ my_basis == bobs_basis and bobs_correctness for my_basis, bobs_basis, bobs_correctness in - zip(self.my_basises, bobs_basises, bobs_correctness)] - self.channel.send_classical(bin_encode(acceptance), is_bob=False) - self.key = [k for k, c in zip(self.sent, acceptance) if c] + zip(self.my_bases, bobs_bases, bobs_correctness)] + self.channel.send_classical(bin_encode(self.correctness), is_bob=False) + self.key = [k for k, c in zip(self.sent, self.correctness) if c] diff --git a/model/bob.py b/model/bob.py index db13bec..19b5969 100644 --- a/model/bob.py +++ b/model/bob.py @@ -11,19 +11,19 @@ class Bob: channel: Channel my_results: typing.List[typing.Tuple[bool, bool]] = field(default_factory=list) key: typing.List[bool] = field(default_factory=list) - true_basises: typing.List[bool] = field(default_factory=list) - my_basises: typing.List[bool] = field(default_factory=list) + correctness: typing.List[bool] = field(default_factory=list) + my_bases: typing.List[bool] = field(default_factory=list) def recv_photons(self, n: int = 100): - self.my_basises = [bool(r.randint(0, 1)) for _ in range(n)] - self.my_results = self.channel.get_photon_results(self.my_basises) + self.my_bases = [bool(r.randint(0, 1)) for _ in range(n)] + self.my_results = self.channel.get_photon_results(self.my_bases) - def send_basises_and_correctness(self): - self.channel.send_classical(bin_encode(self.my_basises)) - self.channel.send_classical(bin_encode([left + right == 1 for left, right in self.my_results])) + def send_bases_and_correctness(self): + self.channel.send_classical(bin_encode(self.my_bases)) + self.channel.send_classical(bin_encode([left + right >= 1 for left, right in self.my_results])) def generate_key(self, n: int = 100): self.recv_photons(n) - self.send_basises_and_correctness() - correctness = bin_decode(self.channel.recv_classical(n)) - self.key = [(False if k[0] else True) for k, c in zip(self.my_results, correctness) if c] + self.send_bases_and_correctness() + self.correctness = bin_decode(self.channel.recv_classical(n)) + self.key = [(False if k[0] else True) for k, c in zip(self.my_results, self.correctness) if c] diff --git a/model/channel.py b/model/channel.py index 4fb633d..04460d5 100644 --- a/model/channel.py +++ b/model/channel.py @@ -19,7 +19,7 @@ class Channel(abc.ABC): pass @abc.abstractmethod - def get_photon_results(self, basises: typing.Optional[typing.List[bool]]) -> typing.List[typing.Tuple[bool, bool]]: + def get_photon_results(self, bases: typing.Optional[typing.List[bool]]) -> typing.List[typing.Tuple[bool, bool]]: """ Returns tuple of clicks of 0 and 1 for each bit """ @@ -33,14 +33,14 @@ class ChannelSym(Channel): bob_classical_data_pool: str = '' alice_classical_data_pool: str = '' # probability for a photon to go to the wrong detector - p_opt: float = 0.05 + p_opt: float = 0.015 # dark count p_dc: float = 0.05 # the average number of emitted photons mu: float = 1 # eta - the probability for a detector to react to a photon detector_sensitivity: float = 0.8 - transmittance: float = 0.8 + transmittance: float = 0.9 eve: typing.Optional[typing.Any] = None def get_number_of_photos(self) -> int: @@ -70,15 +70,16 @@ class ChannelSym(Channel): # print(f'{["Alice", "Bob"][is_bob]} received {n} characters') return data - def get_photon_results(self, basises: typing.Optional[typing.List[bool]]) -> typing.List[typing.Tuple[bool, bool]]: + def get_photon_results(self, bases: typing.Optional[typing.List[bool]]) -> typing.List[typing.Tuple[bool, bool]]: res = list() - while len(self.bob_photons) < len(basises): + while len(self.bob_photons) < len(bases): pass - for photons, basis in zip(self.bob_photons, basises): + for photons, basis in zip(self.bob_photons, bases): clicks = [random.uniform(0, 1) <= self.p_dc, random.uniform(0, 1) <= self.p_dc] + if random.uniform(0, 1) > self.transmittance: + res.append(clicks) + continue for ph_bit, ph_basis in photons: - if random.uniform(0, 1) > self.transmittance: - continue if random.uniform(0, 1) <= self.detector_sensitivity: # We detected everything correctly if ph_basis != basis: if random.uniform(0, 1) > 0.5: @@ -89,7 +90,7 @@ class ChannelSym(Channel): if random.uniform(0, 1) > self.p_opt: # Photon doesn't flip clicks[ph_bit] = True else: - clicks[not ph_bit] = False + clicks[not ph_bit] = True res.append((clicks[0], clicks[1])) self.bob_photons = list() return res diff --git a/model/eve.py b/model/eve.py index 88c81cb..26da985 100644 --- a/model/eve.py +++ b/model/eve.py @@ -6,12 +6,12 @@ from random import choice, uniform @dataclass class EveBS: hijack_probability: float = 0.45 - my_basises: typing.List[bool] = field(default_factory=list) + my_bases: typing.List[bool] = field(default_factory=list) obtained_data: typing.List[typing.Optional[bool]] = field(default_factory=list) def process_photons(self, photons: typing.List[typing.Tuple[bool, bool]]) -> typing.List[typing.Tuple[bool, bool]]: basis = choice([False, True]) - self.my_basises.append(basis) + self.my_bases.append(basis) clicks = [0, 0] passed = [] for photon in photons: @@ -33,12 +33,12 @@ class EveBS: @dataclass class EvePNS: k: int = 1 - my_basises: typing.List[bool] = field(default_factory=list) + my_bases: typing.List[bool] = field(default_factory=list) obtained_data: typing.List[typing.Optional[bool]] = field(default_factory=list) def process_photons(self, photons: typing.List[typing.Tuple[bool, bool]]) -> typing.List[typing.Tuple[bool, bool]]: basis = choice([False, True]) - self.my_basises.append(basis) + self.my_bases.append(basis) clicks = [0, 0] passed = [] captured = 0 diff --git a/model/main.py b/model/main.py index 1999e0f..33ce368 100644 --- a/model/main.py +++ b/model/main.py @@ -24,46 +24,82 @@ def get_e_and_r(alice, bob, n=1000) -> typing.Tuple[float, float]: return 1 - sum(k1 == k2 for k1, k2 in zip(alice.key, bob.key)) / len(alice.key), len(alice.key) / n +def common_alice_bob_eve(alice: Alice, eve, n=1000) -> float: + return sum(correctness and alice_bit == eve_bit for alice_bit, eve_bit, correctness in + zip(alice.key, eve.obtained_data, alice.correctness)) / n + + channel_parameters = {'p_opt': 0.05, 'p_dc': 0.05, 'mu': 1, 'detector_sensitivity': 0.8, 'transmittance': 0.8} -PAR_NAMES = {'p_opt': 'p_{opt}', 'p_dc': 'p_{dc}', 'mu': r'\mu', 'detector_sensitivity': r'\eta', 'transmittance': 't'} +PAR_NAMES = {'p_opt': 'p_{opt}', 'p_dc': 'p_{dc}', 'mu': r'\mu', + 'detector_sensitivity': r'\eta', 'transmittance': 't', + 'eve:hijack_probability': r'p_{Eve}'} mu, t, p_dc, p_opt, eta = sp.symbols(r'\mu t p_{dc} p_{opt} \eta') -e_theory = 0.5 * (p_dc + p_opt * mu * t * eta) / (2 * p_dc + mu * t * eta) -q_theory = (mu * t * eta + 2 * p_dc) / 2 * (1 - e_theory * 2) +p1 = 2 * p_dc - p_dc ** 2 +p_ea, p_eb = sp.exp(-mu * (1 - eta) * p_opt), sp.exp(mu * (eta + p_opt - eta * p_opt - 1)) +e_theory = 0.5 * (p_dc + t * (1 - p_ea) - p_dc * t * (1 - p_ea)) \ + * (1 - p_dc / 2 - 0.5 * t * (1 - p_eb) + 0.5 * p_dc * t * (1 - p_eb)) +q_theory = 0.5 * (p1 + (1 - p1) * (1 - sp.exp(-mu * eta)) * t) +e_theory /= q_theory -def plot(parameter, values, add_eve: bool = False): - data_r, data_e = [], [] - data_r_theoretical, data_e_theoretical = [], [] - parameters_sp = {PAR_NAMES[pname]: channel_parameters[pname] for pname in PAR_NAMES.keys()} +def plot(parameter, values, add_eve: bool = False, show=False): + data_q, data_e, eve_gains = [], [], [] + data_q_theoretical, data_e_theoretical = [], [] + parameters_sp = {PAR_NAMES[pname]: channel_parameters[pname] for pname in channel_parameters.keys()} n = 10000 for val in values: print(val) - channel = ChannelSym(**{parameter: val}) + channel = ChannelSym(**({parameter: val} if not parameter.startswith('eve:') else {}), + **{par: channel_parameters[par] for par in channel_parameters.keys() if par != parameter}) alice, bob = Alice(channel), Bob(channel) if add_eve: - eve = EveBS() + eve = EveBS(**({parameter.removeprefix('eve:'): val} if parameter.startswith('eve:') else {})) channel.eve = eve run_qkd(alice, bob, n) e, r = get_e_and_r(alice, bob, n) - data_r.append(r) + data_q.append(r) data_e.append(e) + if add_eve: + eve_gains.append(common_alice_bob_eve(alice, eve, n)) parameters_sp[PAR_NAMES.get(parameter, parameter)] = val - data_r_theoretical.append(float(q_theory.evalf(subs=parameters_sp))) + data_q_theoretical.append(float(q_theory.evalf(subs=parameters_sp))) data_e_theoretical.append(float(e_theory.evalf(subs=parameters_sp))) from matplotlib import pyplot as plt - plt.plot(values, data_r, label='$R$') - plt.plot(values, data_e, label='$E$') - plt.plot(values, data_r_theoretical, label='$R_{theory}$') - plt.plot(values, data_e_theoretical, label='$E_{theory}$') + plt.plot(values, data_q, label='$Q$') + plt.plot(values, data_q_theoretical, label='$Q_{theory}$') plt.legend() if not os.path.exists('output'): os.mkdir('output') with open(f'output/dep_{parameter}.json', 'w') as f: - f.write(json.dumps([list(values), data_r, data_e, data_r_theoretical, data_e_theoretical])) + f.write(json.dumps([list(values), data_q, data_e, data_q_theoretical, data_e_theoretical])) plt.xlabel('$' + PAR_NAMES.get(parameter, parameter) + '$') - plt.title('$R$ and $E$ vs. $' + PAR_NAMES.get(parameter, parameter) + '$' + ' with Eve' * add_eve) - plt.savefig(f'output/dep_{parameter}.png') - plt.show() + plt.title('$Q$ vs. $' + PAR_NAMES.get(parameter, parameter) + '$' + ' with Eve' * add_eve) + plt.savefig(f'output/dep_{parameter}_q{"_with_eve" * add_eve}.png') + if show: + plt.show() + else: + plt.cla() + plt.plot(values, data_e, label='$E$') + plt.plot(values, data_e_theoretical, label='$E_{theory}$') + plt.legend() + plt.xlabel('$' + PAR_NAMES.get(parameter, parameter) + '$') + plt.title('$E$ vs. $' + PAR_NAMES.get(parameter, parameter) + '$' + ' with Eve' * add_eve) + plt.savefig(f'output/dep_{parameter}_e{"_with_eve" * add_eve}.png') + if show: + plt.show() + else: + plt.cla() + if add_eve: + plt.plot(values, [g / q for g, q in zip(eve_gains, data_q)], label='$Q_{Eve}$') + plt.title('Eve\'s gains') + plt.legend() + plt.ylabel(r'$\dfrac{Q_{Eve}}{Q}$') + plt.xlabel('$' + PAR_NAMES.get(parameter, parameter) + '$') + plt.savefig(f'output/dep_{parameter}_eve_q.png') + if show: + plt.show() + else: + plt.cla() def run_one(): @@ -94,6 +130,10 @@ def run_one(): if __name__ == '__main__': import numpy as np - plot('p_dc', np.arange(0, 0.1, 0.01)) - plot('detector_sensitivity', np.arange(0.5, 1, 0.05)) - plot('mu', np.arange(0.5, 1.5, 0.1)) + + plot('p_dc', np.arange(0, 0.15, 0.01)) + plot('p_opt', np.arange(0, 0.15, 0.01)) + plot('detector_sensitivity', np.arange(0.1, 1, 0.05)) + plot('mu', np.arange(0.5, 2.5, 0.1)) + plot('transmittance', np.arange(0.4, 1, 0.05)) + # plot('eve:hijack_probability', np.arange(0, 1, 0.05))