QKD Protocols
This section provides comprehensive documentation of the three QKD protocols implemented in the platform: DPS-QKD, COW-QKD, and BB84-QKD.
DPS-QKD (Differential Phase Shift)
Overview
Differential Phase Shift Quantum Key Distribution (DPS-QKD) is a protocol that encodes information in the phase difference between consecutive optical pulses. It was developed to provide a simple and robust implementation of QKD using standard optical components.
Theory
Principle: Information is encoded in the phase difference between consecutive pulses rather than the absolute phase of individual pulses.
Encoding Scheme: - Bit 0: Phase difference \(\Delta\phi = 0\) - Bit 1: Phase difference \(\Delta\phi = \pi\)
Detection Method: Mach-Zehnder interferometer with two detectors (DM1 and DM2)
Key Features: - Simple phase encoding - Robust against phase drift - Uses standard telecom components - High key rate potential
Mathematical Model
Phase Encoding: For pulse pair \((n-1, n)\): - Random bit \(b \in \{0,1\}\) - Phase difference \(\Delta\phi_n = b \cdot \pi\) - Absolute phase \(\phi_n = \phi_{n-1} + \Delta\phi_n\)
Interference Measurement: The Mach-Zehnder interferometer measures the phase difference: - Detector 1 (DM1) click probability: \(P_1 = \cos^2(\Delta\phi/2)\) - Detector 2 (DM2) click probability: \(P_2 = \sin^2(\Delta\phi/2)\)
Bit Inference: - DM1 click only: \(b_{inferred} = 0\) - DM2 click only: \(b_{inferred} = 1\) - Both or neither click: inconclusive measurement
Implementation
The DPS-QKD implementation is split between sender and receiver classes:
Sender Implementation (simulation.Sender.SenderDPS):
class SenderDPS:
def __init__(self, avg_photon_number=0.2):
self.light_source = LightSource(avg_photon_number)
self.phase_modulator = PhaseModulator()
self.raw_key_bits = []
self.sent_pulses_info = []
self.last_sent_phase = None
def prepare_and_send_pulse(self, time_slot):
photon_count = self.light_source.generate_single_pulse_photon_count()
if self.last_sent_phase is None:
# First pulse - random phase
modulated_phase = random.choice([0.0, math.pi])
current_secret_bit = None
else:
# Encode bit in phase difference
current_secret_bit = random.randint(0, 1)
self.raw_key_bits.append(current_secret_bit)
desired_phase_difference = 0.0 if current_secret_bit == 0 else math.pi
modulated_phase = self.phase_modulator.modulate_phase(
self.last_sent_phase, desired_phase_difference
)
self.last_sent_phase = modulated_phase
return modulated_phase, photon_count
Receiver Implementation (simulation.Receiver.ReceiverDPS):
class ReceiverDPS:
def __init__(self, detector_efficiency=0.9, dark_count_rate=1e-7):
self.mzi = MachZehnderInterferometer()
self.detector_dm1 = SinglePhotonDetector(detector_efficiency, dark_count_rate)
self.detector_dm2 = SinglePhotonDetector(detector_efficiency, dark_count_rate)
def receive_and_measure(self, time_slot, current_pulse_photons,
current_pulse_phase, previous_pulse_photons,
previous_pulse_phase):
# Calculate interference probabilities
prob_dm1, prob_dm2 = self.mzi.interfere_pulses(
previous_pulse_phase, current_pulse_phase
)
# Simulate detection
click_dm1 = self.detector_dm1.detect(1 if random.random() < prob_dm1 else 0)
click_dm2 = self.detector_dm2.detect(1 if random.random() < prob_dm2 else 0)
# Infer bit
if click_dm1 and not click_dm2:
bob_bit = 0
elif click_dm2 and not click_dm1:
bob_bit = 1
else:
bob_bit = None # Inconclusive
return click_dm1, click_dm2, measured_phase_diff, bob_bit
Key Generation Process:
def generate_and_share_key(self, target_node, num_pulses,
pulse_repetition_rate_ns, phase_flip_prob=0.0):
# Generate pulse train
for i in range(num_pulses):
time_slot = i * pulse_repetition_rate_ns
modulated_phase, photon_count = self.prepare_and_send_pulse(time_slot)
# Transmit through channel
channel_processed_pulses = []
for pulse in alice_pulses_sent_info:
received_photons = channel.transmit_pulse(pulse['photon_count'])
# Apply phase flip noise
modulated_phase = pulse['modulated_phase']
if random.random() < phase_flip_prob:
modulated_phase = (modulated_phase + math.pi) % (2 * math.pi)
channel_processed_pulses.append({
'time_slot': pulse['time_slot'],
'received_photon_count': received_photons,
'modulated_phase': modulated_phase
})
# Bob's measurements
bob_clicks_and_inferred_bits = []
for i, current_pulse in enumerate(channel_processed_pulses):
previous_pulse = channel_processed_pulses[i-1] if i > 0 else dummy_pulse
click_dm1, click_dm2, measured_phase_diff, bob_bit = \
target_node.receive_and_measure(
current_pulse['time_slot'],
current_pulse['received_photon_count'],
current_pulse['modulated_phase'],
previous_pulse['received_photon_count'],
previous_pulse['modulated_phase']
)
bob_clicks_and_inferred_bits.append({
'time_slot': current_pulse['time_slot'],
'click_dm1': click_dm1,
'click_dm2': click_dm2,
'measured_phase_diff': measured_phase_diff,
'bob_inferred_bit': bob_bit
})
# Sifting process
alice_sifted_key = []
bob_sifted_key = []
for i in range(1, len(alice_pulses_sent_info)):
alice_pn_minus_1 = alice_pulses_sent_info[i-1]
alice_pn = alice_pulses_sent_info[i]
bob_measurement = bob_clicks_and_inferred_bits[i]
if bob_measurement['bob_inferred_bit'] is not None:
# Calculate Alice's intended bit
alice_intended_delta_phi = (alice_pn['modulated_phase'] -
alice_pn_minus_1['modulated_phase']) % (2 * math.pi)
if alice_intended_delta_phi > math.pi:
alice_intended_delta_phi -= 2 * math.pi
alice_intended_bit = 0 if math.isclose(alice_intended_delta_phi, 0.0) else 1
alice_sifted_key.append(alice_intended_bit)
bob_sifted_key.append(bob_measurement['bob_inferred_bit'])
return alice_sifted_key, bob_sifted_key
Usage Example
from simulation.Network import Network
from main import calculate_qber, postprocessing
# Create network
network = Network()
alice = network.add_node('Alice', avg_photon_number=0.2)
bob = network.add_node('Bob', detector_efficiency=0.9, dark_count_rate=1e-7)
network.connect_nodes('Alice', 'Bob', distance_km=20)
# Generate key
alice_key, bob_key = alice.generate_and_share_key(
bob, num_pulses=10000, pulse_repetition_rate_ns=1, phase_flip_prob=0.05
)
# Analyze results
qber, num_errors = calculate_qber(alice_key, bob_key)
final_key_len, postproc = postprocessing(len(alice_key), qber)
print(f"QBER: {qber:.4f}")
print(f"Sifted key length: {len(alice_key)}")
print(f"Final key length: {final_key_len}")
Performance Characteristics
Sifting Efficiency: ~25% of raw bits
QBER Range: 3-11% for practical systems
Key Rate: High potential due to simple encoding
Security: Based on quantum interference
Hardware Requirements: Standard telecom components
COW-QKD (Coherent One-Way)
Overview
Coherent One-Way Quantum Key Distribution (COW-QKD) is a protocol that uses intensity modulation to encode information and includes monitoring pulses for eavesdropping detection. It was designed to be simple, robust, and secure against various attacks.
Theory
Principle: Information is encoded in the intensity of optical pulses, with monitoring pulses used to detect eavesdropping attempts.
Encoding Scheme: - Bit 0: Vacuum pulse followed by coherent pulse (intensity \(I_0\) then \(I_1\)) - Bit 1: Coherent pulse followed by vacuum pulse (intensity \(I_1\) then \(I_0\)) - Monitoring: Pairs of coherent pulses for eavesdropping detection
Detection Method: Single photon detector with intensity monitoring
Key Features: - Simple intensity modulation - Built-in eavesdropping detection - Robust against various attacks - High key rate potential
Mathematical Model
Pulse Types: - Data Pulse 0: \(I_0 = \mu_{off}\), \(I_1 = \mu_{on}\) - Data Pulse 1: \(I_0 = \mu_{on}\), \(I_1 = \mu_{off}\) - Monitoring Pulse: \(I_0 = \mu_{on}\), \(I_1 = \mu_{on}\)
Detection Model: - Click probability: \(P_{click} = 1 - e^{-\eta I}\) - Where \(\eta\) is detector efficiency and \(I\) is intensity
Sifting Process: - Keep bit if both Alice and Bob identify pulse as data - Discard if either identifies as monitoring - Check monitoring pulses for eavesdropping detection
Implementation
Sender Implementation (simulation.Sender.SenderCOW):
class SenderCOW:
def __init__(self, avg_photon_number=0.2, monitor_pulse_ratio=0.1,
extinction_ratio_db=20.0):
self.light_source = LightSource(avg_photon_number)
self.intensity_modulator = IntensityModulator(extinction_ratio_db)
self.mu = avg_photon_number
self.monitor_pulse_ratio = monitor_pulse_ratio
def prepare_pulse_train(self, num_total_pulses):
self.raw_key_bits = []
self.sent_pulses_info = []
num_pairs = num_total_pulses // 2
time_slot = 0
mu_on = self.intensity_modulator.modulate(self.mu, 'on')
mu_off = self.intensity_modulator.modulate(self.mu, 'off')
for _ in range(num_pairs):
r = random.random()
if r < self.monitor_pulse_ratio:
# Monitoring pulse pair
self.sent_pulses_info.extend([
{
'time_slot': time_slot,
'photon_count': self.light_source.generate_single_pulse_photon_count(mu_on),
'phase': 0.0,
'intended_bit': None,
'pulse_type': 'monitor_first'
},
{
'time_slot': time_slot + 1,
'photon_count': self.light_source.generate_single_pulse_photon_count(mu_on),
'phase': 0.0,
'intended_bit': None,
'pulse_type': 'monitor_second'
}
])
time_slot += 2
else:
# Data pulse pair
bit = random.randint(0, 1)
self.raw_key_bits.append(bit)
if bit == 0:
# Vacuum then coherent
self.sent_pulses_info.extend([
{
'time_slot': time_slot,
'photon_count': self.light_source.generate_single_pulse_photon_count(mu_off),
'phase': 0.0,
'intended_bit': bit,
'pulse_type': 'data_first'
},
{
'time_slot': time_slot + 1,
'photon_count': self.light_source.generate_single_pulse_photon_count(mu_on),
'phase': 0.0,
'intended_bit': bit,
'pulse_type': 'data_second'
}
])
else:
# Coherent then vacuum
self.sent_pulses_info.extend([
{
'time_slot': time_slot,
'photon_count': self.light_source.generate_single_pulse_photon_count(mu_on),
'phase': 0.0,
'intended_bit': bit,
'pulse_type': 'data_first'
},
{
'time_slot': time_slot + 1,
'photon_count': self.light_source.generate_single_pulse_photon_count(mu_off),
'phase': 0.0,
'intended_bit': bit,
'pulse_type': 'data_second'
}
])
time_slot += 2
return self.sent_pulses_info
Receiver Implementation (simulation.Receiver.ReceiverCOW):
class ReceiverCOW:
def __init__(self, detector_efficiency=0.9, dark_count_rate=1e-7,
detection_threshold_photons=0):
self.data_detector = SinglePhotonDetector(detector_efficiency, dark_count_rate)
self.detection_threshold_photons = detection_threshold_photons
self.received_pulses_info = []
def measure_pulse(self, time_slot, incident_photons, pulse_type):
click = self.data_detector.detect(incident_photons)
bob_inferred_bit = None
is_monitoring_click = False
if pulse_type.startswith('data'):
# Data pulse - infer bit based on click
if click:
bob_inferred_bit = 1
else:
bob_inferred_bit = 0
elif pulse_type.startswith('monitor'):
# Monitoring pulse - check for eavesdropping
if click:
is_monitoring_click = True
self.received_pulses_info.append({
'time_slot': time_slot,
'incident_photons': incident_photons,
'click': click,
'bob_inferred_bit': bob_inferred_bit,
'is_monitoring_click': is_monitoring_click,
'pulse_type': pulse_type
})
return click, bob_inferred_bit, is_monitoring_click
Key Generation Process:
def generate_and_share_key_cow(self, target_node, num_pulses,
pulse_repetition_rate_ns, monitor_pulse_ratio=0.1,
detection_threshold_photons=0, phase_flip_prob=0.0,
bit_flip_error_prob=0.0):
# Prepare pulse train
alice_sent_pulses_info = self.cow_sender.prepare_pulse_train(num_pulses)
# Transmit through channel
bob_received_signals = []
for i, sent_pulse in enumerate(alice_sent_pulses_info):
received_photons = channel.transmit_pulse(sent_pulse['photon_count'])
# Apply noise
final_phase = sent_pulse['phase']
if random.random() < phase_flip_prob:
final_phase = (final_phase + math.pi) % (2 * math.pi)
bob_received_signals.append({
'time_slot': sent_pulse['time_slot'],
'received_photons': received_photons,
'pulse_type': sent_pulse['pulse_type'],
'final_phase': final_phase
})
# Bob's measurements
bob_measurements = []
for received_signal in bob_received_signals:
click, bob_inferred_bit, is_monitoring_click = \
target_node.cow_receiver.measure_pulse(
received_signal['time_slot'],
received_signal['received_photons'],
received_signal['pulse_type']
)
bob_measurements.append({
'time_slot': received_signal['time_slot'],
'click': click,
'bob_inferred_bit': bob_inferred_bit,
'is_monitoring_click': is_monitoring_click,
'pulse_type': received_signal['pulse_type']
})
# Sifting process
alice_sifted_key = []
bob_sifted_key = []
for i in range(0, len(alice_sent_pulses_info), 2):
if i + 1 >= len(alice_sent_pulses_info):
break
pulse1 = alice_sent_pulses_info[i]
pulse2 = alice_sent_pulses_info[i + 1]
bob_measurement1 = bob_measurements[i]
bob_measurement2 = bob_measurements[i + 1]
# Check if both pulses are data pulses
if (pulse1['pulse_type'].startswith('data') and
pulse2['pulse_type'].startswith('data')):
# Check if Bob agrees on data pulses
if (bob_measurement1['bob_inferred_bit'] is not None and
bob_measurement2['bob_inferred_bit'] is not None):
# Apply bit flip error
alice_bit = pulse1['intended_bit']
bob_bit = bob_measurement1['bob_inferred_bit']
if random.random() < bit_flip_error_prob:
bob_bit = 1 - bob_bit
alice_sifted_key.append(alice_bit)
bob_sifted_key.append(bob_bit)
return alice_sifted_key, bob_sifted_key
Usage Example
# Create network
network = Network()
alice = network.add_node('Alice', avg_photon_number=0.1)
bob = network.add_node('Bob', detector_efficiency=0.9, dark_count_rate=1e-7)
network.connect_nodes('Alice', 'Bob', distance_km=20)
# Generate key with COW protocol
alice_key, bob_key = alice.generate_and_share_key_cow(
bob, num_pulses=10000, pulse_repetition_rate_ns=1,
monitor_pulse_ratio=0.1, detection_threshold_photons=0,
phase_flip_prob=0.05, bit_flip_error_prob=0.05
)
# Analyze results
qber, num_errors = calculate_qber(alice_key, bob_key)
final_key_len, postproc = postprocessing(len(alice_key), qber)
print(f"COW-QKD Results:")
print(f"QBER: {qber:.4f}")
print(f"Sifted key length: {len(alice_key)}")
print(f"Final key length: {final_key_len}")
Performance Characteristics
Sifting Efficiency: ~40% of raw bits
QBER Range: 3-10% for practical systems
Key Rate: High due to simple intensity modulation
Security: Built-in eavesdropping detection
Hardware Requirements: Standard optical components
BB84-QKD (Bennett-Brassard 1984)
Overview
BB84 is the original quantum key distribution protocol, proposed by Bennett and Brassard in 1984. It uses four quantum states in two conjugate bases to achieve secure key distribution based on the principles of quantum mechanics.
Theory
Principle: Information is encoded in quantum states using two conjugate bases, with security based on the quantum uncertainty principle.
Encoding Scheme: - Rectilinear Basis (R): \(|0\rangle\) (horizontal), \(|1\rangle\) (vertical) - Diagonal Basis (D): \(|+\rangle = \frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)\), \(|-\rangle = \frac{1}{\sqrt{2}}(|0\rangle - |1\rangle)\)
Detection Method: Single photon detector with basis switching
Key Features: - Original QKD protocol - Information-theoretic security - Basis reconciliation - Proven security against various attacks
Mathematical Model
State Preparation: - Random bit \(b \in \{0,1\}\) - Random basis \(B \in \{R,D\}\) - State: \(|\psi\rangle = |b\rangle_B\)
Measurement: - Random basis choice \(B' \in \{R,D\}\) - Measurement outcome: \(b' \in \{0,1\}\) or no detection
Sifting Process: - Keep bit if \(B = B'\) and detection occurred - Discard if \(B \neq B'\) or no detection
Security: Based on quantum uncertainty principle - measuring in wrong basis disturbs the state
Implementation
Sender Implementation (simulation.Sender.SenderBB84):
class SenderBB84:
def __init__(self, avg_photon_number=0.2):
self.light_source = LightSource(avg_photon_number)
self.raw_key_bits = []
self.chosen_bases = []
self.sent_pulses_info = []
def prepare_and_send_pulse(self, time_slot):
chosen_bit = random.randint(0, 1)
self.raw_key_bits.append(chosen_bit)
chosen_basis = random.choice(['R', 'D'])
self.chosen_bases.append(chosen_basis)
if chosen_basis == 'R':
encoded_state = '|0⟩' if chosen_bit == 0 else '|1⟩'
else:
encoded_state = '|+⟩' if chosen_bit == 0 else '|-⟩'
photon_count = self.light_source.generate_single_pulse_photon_count()
pulse_info = {
'time_slot': time_slot,
'photon_count': photon_count,
'encoded_state': encoded_state,
'chosen_bit': chosen_bit,
'chosen_basis': chosen_basis
}
self.sent_pulses_info.append(pulse_info)
return encoded_state, photon_count, chosen_bit, chosen_basis
Receiver Implementation (simulation.Receiver.ReceiverBB84):
class ReceiverBB84:
def __init__(self, detector_efficiency=0.9, dark_count_rate=1e-7):
self.detector = SinglePhotonDetector(detector_efficiency, dark_count_rate)
self.raw_measurements = []
self.chosen_bases = []
self.received_pulses_info = []
def receive_and_measure(self, time_slot, incident_photons, encoded_state):
chosen_basis = random.choice(['R', 'D'])
self.chosen_bases.append(chosen_basis)
click_occurred = self.detector.detect(incident_photons)
measured_bit = None
if click_occurred:
if chosen_basis == 'R':
# Measure in rectilinear basis
if encoded_state == '|0⟩':
measured_bit = 0 if random.random() > 0.02 else 1
elif encoded_state == '|1⟩':
measured_bit = 1 if random.random() > 0.02 else 0
else:
measured_bit = random.randint(0, 1)
else:
# Measure in diagonal basis
if encoded_state == '|+⟩':
measured_bit = 0 if random.random() > 0.02 else 1
elif encoded_state == '|-⟩':
measured_bit = 1 if random.random() > 0.02 else 0
else:
measured_bit = random.randint(0, 1)
self.raw_measurements.append(measured_bit)
measurement_info = {
'time_slot': time_slot,
'incident_photons': incident_photons,
'encoded_state': encoded_state,
'chosen_basis': chosen_basis,
'click_occurred': click_occurred,
'measured_bit': measured_bit
}
self.received_pulses_info.append(measurement_info)
return measured_bit, chosen_basis, click_occurred
Key Generation Process:
def generate_and_share_key_bb84(self, target_node, num_pulses,
pulse_repetition_rate_ns, phase_flip_prob=0.0):
# Generate pulse train
for i in range(num_pulses):
time_slot = i * pulse_repetition_rate_ns
encoded_state, photon_count, chosen_bit, chosen_basis = \
self.bb84_sender.prepare_and_send_pulse(time_slot)
# Transmit through channel
channel_processed_pulses = []
for pulse in alice_pulses_sent_info:
received_photons = channel.transmit_pulse(pulse['photon_count'])
channel_processed_pulses.append({
'time_slot': pulse['time_slot'],
'received_photon_count': received_photons,
'encoded_state': pulse['encoded_state']
})
# Bob's measurements
bob_measurements = []
for pulse in channel_processed_pulses:
measured_bit, chosen_basis, click_occurred = \
target_node.bb84_receiver.receive_and_measure(
pulse['time_slot'],
pulse['received_photon_count'],
pulse['encoded_state']
)
bob_measurements.append({
'time_slot': pulse['time_slot'],
'measured_bit': measured_bit,
'chosen_basis': chosen_basis,
'click_occurred': click_occurred
})
# Sifting process
alice_sifted_key = []
bob_sifted_key = []
for i, alice_pulse in enumerate(alice_pulses_sent_info):
bob_measurement = bob_measurements[i]
# Check if bases match and detection occurred
if (alice_pulse['chosen_basis'] == bob_measurement['chosen_basis'] and
bob_measurement['click_occurred']):
alice_sifted_key.append(alice_pulse['chosen_bit'])
bob_sifted_key.append(bob_measurement['measured_bit'])
return alice_sifted_key, bob_sifted_key
Usage Example
# Create network
network = Network()
alice = network.add_node('Alice', avg_photon_number=0.2)
bob = network.add_node('Bob', detector_efficiency=0.9, dark_count_rate=1e-7)
network.connect_nodes('Alice', 'Bob', distance_km=20)
# Generate key with BB84 protocol
alice_key, bob_key = alice.generate_and_share_key_bb84(
bob, num_pulses=10000, pulse_repetition_rate_ns=1, phase_flip_prob=0.05
)
# Analyze results
qber, num_errors = calculate_qber(alice_key, bob_key)
final_key_len, postproc = postprocessing(len(alice_key), qber)
print(f"BB84-QKD Results:")
print(f"QBER: {qber:.4f}")
print(f"Sifted key length: {len(alice_key)}")
print(f"Final key length: {final_key_len}")
Performance Characteristics
Sifting Efficiency: ~50% of raw bits
QBER Range: 3-11% for practical systems
Key Rate: Moderate due to basis reconciliation
Security: Information-theoretic security
Hardware Requirements: Polarization or phase encoding
Protocol Comparison
Performance Metrics
|----------|——————-|------------|———-|----------------|——————-| | DPS-QKD | ~25% | 3-11% | High | High | Low | | COW-QKD | ~40% | 3-10% | High | High | Low | | BB84-QKD | ~50% | 3-11% | Moderate | Highest | Medium |
Use Case Recommendations
DPS-QKD: - High-speed applications - Simple hardware requirements - Phase-stable environments - Cost-sensitive deployments
COW-QKD: - Security-critical applications - Eavesdropping detection required - Robust performance needed - Monitoring capabilities desired
BB84-QKD: - Maximum security requirements - Research and development - Educational purposes - Standard QKD implementations
Implementation Considerations
DPS-QKD: - Requires phase stability - Sensitive to phase noise - Simple implementation - High key rate potential
COW-QKD: - Requires intensity control - Monitoring pulse overhead - Robust against attacks - Built-in security features
BB84-QKD: - Requires basis switching - Basis reconciliation overhead - Proven security - Standard protocol
Future Protocol Extensions
The platform is designed to easily accommodate new QKD protocols:
Protocol Interface: Standard interface for new protocols
Modular Design: Separate sender/receiver implementations
Parameter Configuration: Flexible parameter system
Analysis Integration: Automatic QBER and key rate analysis
Adding a new protocol involves:
Implementing sender and receiver classes
Adding protocol-specific parameters
Integrating with the network model
Adding frontend support
Updating documentation