Skip to content

Commit 7944ed3

Browse files
Added E91 QKD Implementation
Covers the implementation of E91 QKD, for a predefined num_bits = 2000.
1 parent a71618f commit 7944ed3

1 file changed

Lines changed: 175 additions & 0 deletions

File tree

quantum/e91.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
2+
"""
3+
Implements the E91 quantum key distribution (QKD) protocol.
4+
This protocol uses the principles of quantum entanglement and the violation of
5+
Bell's inequality to securely distribute a secret key
6+
between two parties (Alice and Bob) and to detect
7+
the presence of an eavesdropper (Eve).
8+
9+
How it works:
10+
1. A source (Charlie) generates pairs of entangled qubits in a Bell
11+
state and sends one qubit of each pair to Alice and the other to Bob.
12+
2. Alice and Bob each independently and randomly choose to measure their
13+
incoming qubits in one of three predefined measurement bases.
14+
3. After all measurements are complete, they communicate over a public
15+
channel to compare the bases they chose for each qubit.
16+
4. They divide their measurement results into two sets:
17+
a) Cases where their chosen bases were "compatible" are used to
18+
generate a secret key. Due to entanglement, their results in these
19+
cases should be perfectly correlated.
20+
b) Cases where their bases were "incompatible" are used to test for
21+
eavesdropping by calculating the CHSH inequality parameter 'S'.
22+
5. Quantum mechanics predicts that for an entangled system, |S| can reach
23+
2*sqrt(2) (~2.828), whereas any classical (or eavesdropped) system is
24+
bound by |S| <= 2. If their calculated S-value significantly violates
25+
the classical bound, they can be confident that no eavesdropping
26+
occurred and their generated key is secure.
27+
"""
28+
29+
import numpy as np
30+
import qiskit
31+
import random
32+
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
33+
from qiskit_aer import AerSimulator
34+
35+
36+
def e91_protocol(n_bits: int = 2000) -> dict:
37+
"""
38+
Simulates the E91 QKD protocol for a specified number of bits.
39+
40+
Args:
41+
n_bits: The total number of entangled pairs to be generated and
42+
measured. This determines the potential length of the raw key.
43+
44+
Returns:
45+
A dictionary containing the simulation results:
46+
- "alice_key": Alice's final, sifted secret key.
47+
- "bob_key": Bob's final, sifted secret key.
48+
- "s_value": The calculated CHSH inequality parameter 'S'.
49+
- "eavesdropper_detected": A boolean indicating if |S| <= 2.
50+
- "key_match": A boolean indicating if Alice's and Bob's keys match.
51+
- "key_length": The final length of the sifted keys.
52+
"""
53+
54+
if not isinstance(n_bits, int):
55+
raise TypeError("Number of bits must be an integer.")
56+
if n_bits <= 0:
57+
raise ValueError("Number of bits must be > 0.")
58+
if n_bits > 10000:
59+
raise ValueError("Number of bits is too large to simulate efficiently.")
60+
61+
# Define the measurement angles for Alice and Bob's bases.
62+
# The keys correspond to the basis name, and values are angles in radians.
63+
alice_bases = {'A1': 0, 'A2': np.pi / 8, 'A3': np.pi / 4}
64+
bob_bases = {'B1': np.pi / 8, 'B2': np.pi / 4, 'B3': 3 * np.pi / 8}
65+
66+
# Lists to store the choices and results for each bit.
67+
alice_chosen_bases, bob_chosen_bases = [], []
68+
alice_results, bob_results = [], []
69+
70+
# Get the quantum simulator backend.
71+
backend = AerSimulator()
72+
73+
for _ in range(n_bits):
74+
# Alice and Bob randomly choose their measurement bases.
75+
alice_basis_name = random.choice(list(alice_bases.keys()))
76+
bob_basis_name = random.choice(list(bob_bases.keys()))
77+
alice_angle = alice_bases[alice_basis_name]
78+
bob_angle = bob_bases[bob_basis_name]
79+
80+
# Create a quantum circuit for one entangled pair.
81+
qr = QuantumRegister(2, 'q')
82+
cr = ClassicalRegister(2, 'c')
83+
circuit = QuantumCircuit(qr, cr)
84+
85+
# Create a Bell state |Φ+⟩ = (|00⟩ + |11⟩)/sqrt(2)
86+
circuit.h(qr[0])
87+
circuit.cx(qr[0], qr[1])
88+
89+
# Apply rotations to simulate measurements in the chosen bases.
90+
# We use RY gates, which rotate around the Y-axis of the Bloch sphere.
91+
circuit.ry(-2 * alice_angle, qr[0])
92+
circuit.ry(-2 * bob_angle, qr[1])
93+
94+
# Measure the qubits.
95+
circuit.measure(qr, cr)
96+
97+
# Execute the circuit and get the result.
98+
job = backend.run(circuit, shots=1)
99+
result = list(job.result().get_counts().keys())[0]
100+
101+
# Store choices and results. Qiskit's bit order is reversed.
102+
alice_chosen_bases.append(alice_basis_name)
103+
bob_chosen_bases.append(bob_basis_name)
104+
alice_results.append(int(result[1]))
105+
bob_results.append(int(result[0]))
106+
107+
# Sift for generating the secret key.
108+
# The key is formed when Alice and Bob choose compatible bases.
109+
alice_key, bob_key = [], []
110+
for i in range(n_bits):
111+
is_A2B1 = alice_chosen_bases[i] == 'A2' and bob_chosen_bases[i] == 'B1'
112+
is_A3B2 = alice_chosen_bases[i] == 'A3' and bob_chosen_bases[i] == 'B2'
113+
if is_A2B1 or is_A3B2:
114+
alice_key.append(alice_results[i])
115+
bob_key.append(bob_results[i])
116+
117+
# Sift for the CHSH inequality test (Eve detection).
118+
# We use four specific combinations of bases for the test: a = A1, a' = A3 | b = B1, b' = B3
119+
chsh_correlations = {'ab': [], 'ab_': [], 'a_b': [], 'a_b_': []}
120+
121+
for i in range(n_bits):
122+
a_val = 1 if alice_results[i] == 0 else -1
123+
b_val = 1 if bob_results[i] == 0 else -1
124+
product = a_val * b_val # +1 if correlated, -1 if anti-correlated
125+
126+
alice_basis = alice_chosen_bases[i]
127+
bob_basis = bob_chosen_bases[i]
128+
129+
if alice_basis == 'A1' and bob_basis == 'B1':
130+
chsh_correlations['ab'].append(product)
131+
elif alice_basis == 'A1' and bob_basis == 'B3':
132+
chsh_correlations['ab_'].append(product)
133+
elif alice_basis == 'A3' and bob_basis == 'B1':
134+
chsh_correlations['a_b'].append(product)
135+
elif alice_basis == 'A3' and bob_basis == 'B3':
136+
chsh_correlations['a_b_'].append(product)
137+
138+
# Calculate the expectation value (average correlation) for each combination.
139+
E = {}
140+
for key, values in chsh_correlations.items():
141+
E[key] = np.mean(values) if values else 0
142+
143+
# Calculate the S-value: S = E(a,b) - E(a,b') + E(a',b) + E(a',b')
144+
s_value = E['ab'] - E['ab_'] + E['a_b'] + E['a_b_']
145+
146+
# Check for eavesdropper: |S| > 2 indicates security.
147+
eavesdropper_detected = abs(s_value) <= 2
148+
149+
return {
150+
"alice_key": "".join(map(str, alice_key)),
151+
"bob_key": "".join(map(str, bob_key)),
152+
"s_value": s_value,
153+
"eavesdropper_detected": eavesdropper_detected,
154+
"key_match": alice_key == bob_key,
155+
"key_length": len(alice_key)
156+
}
157+
158+
159+
if __name__ == "__main__":
160+
# num_bits is initialized to 2000
161+
num_bits = 2000
162+
results = e91_protocol(num_bits)
163+
164+
print(f"Total bits simulated: {num_bits}")
165+
print(f"CHSH S-value: {results['s_value']:.4f}")
166+
print(f"Eavesdropper detected: {results['eavesdropper_detected']}")
167+
print(f"Final key length: {results['key_length']}")
168+
print(f"Keys match: {results['key_match']}")
169+
170+
if not results['eavesdropper_detected'] and results['key_match'] and results['key_length'] > 0:
171+
print("\nProtocol successful! Secret key generated securely.")
172+
print(f" Alice's key: {results['alice_key']}")
173+
print(f" Bob's key: {results['bob_key']}")
174+
else:
175+
print("\nProtocol failed or eavesdropper detected. Key discarded.")

0 commit comments

Comments
 (0)