Implementing the BB84 protocol simulation
In my simulation of quantum cryptography, I implemented the Bennett and Brassard 1984 (BB84) quantum key distribution protocol. This implementation models the key generation, transmission (with potential eavesdropping), and reconciliation steps.
Representing qubits
In the BB84 protocol, information is encoded in the state of individual qubits. For this simulation, I represent a qubit with its classical bit value (0 or 1) and the basis used to encode it (0 for rectilinear \{| \boldsymbol + \rangle, | \boldsymbol + \rangle \}, 1 for diagonal \{| + \mathbf i \rangle, | -\mathbf i \rangle \}). The BB84Qubit class handles this:
class BB84Qubit:
def __init__(self, bit: int, basis: int):
self._mBit = bit # 0 or 1
self._mBasis = basis # 0: rectilinear (+), 1: diagonal (x)
# ... properties ...
def Measure(self, measurementBasis: int):
if measurementBasis == self._mBasis:
# Measurement in the same basis yields the original bit
return self._mBit
else:
# Measurement in a different basis yields a random outcome
return np.random.randint(0, 2)
Measurement is simulated by the Measure method. If the measurement basis matches the preparation basis, the original bit is returned deterministically. If the bases differ, the outcome is random (0 or 1 with 50\% probability each), reflecting the principles of quantum measurement.
The BB84 protocol simulation
The BB84Protocol class manages the key exchange.
1. Key generation (Alice)
Alice initiates the protocol by generating a sequence of random bits and preparing qubits using randomly chosen bases for each bit.
def generateKey(self, seed: int = None):
if seed is not None:
np.random.seed(seed)
length = self._cfg.KEY_LENGTH
bits = np.random.randint(0, 2, length)
bases = np.random.randint(0, 2, length)
self._qubits_a = [BB84Qubit(b, ba) for b, ba in zip(bits, bases)]
# ...
2. Transmission and potential eavesdropping (quantum channel)
Alice sends her qubits to Bob. This stage is susceptible to eavesdropping. My simulation includes an option (eavesdropping=True) where an eavesdropper (Eve) intercepts each qubit, measures it using a random basis, and resends a new qubit based on her measurement outcome and basis.
def sendKey(self, eavesdropping=False, seed: int = None):
if eavesdropping:
# ... setup ...
for q, b_e in zip(self._qubits_a, bases_e):
m = q.Measure(b_e) # Eve measures
# Eve resends based on her measurement
self._qubits_e.append(BB84Qubit(m, b_e))
self._qubits_b.append(BB84Qubit(m, b_e)) # Bob receives tampered qubit
else:
# No eavesdropping, Bob receives original qubits
self._qubits_b = [BB84Qubit(q.mBit, q.mBasis)
for q in self._qubits_a]
Eve’s actions inevitably introduce errors if she guesses the wrong basis, which Alice and Bob can later detect.
3. Measurement (Bob)
Bob receives the qubits (potentially altered by Eve) and measures each one using his own randomly chosen sequence of bases.
4. Reconciliation (public classical channel)
Alice and Bob communicate over a public classical channel (assumed authenticated) to reconcile their key.
- They compare the bases they used for each qubit.
- They discard the measurement results where their bases did not match. The remaining bits form the “sifted key”.
# Part of reconcileKey method
bases_b = np.random.randint(0, 2, length) # Bob's random bases
bits_b = [q.Measure(b) for q, b in zip(self._qubits_b, bases_b)] # Bob measures
bases_a = [q.mBasis for q in self._qubits_a] # Alice's original bases
# Identify indices where bases match
matches = [i for i in range(length) if bases_a[i] == bases_b[i]]
bits_a_matched = [bits_a[i] for i in matches] # Alice's bits at matched indices
bits_b_matched = [bits_b[i] for i in matches] # Bob's bits at matched indices
5. Error Estimation (QBER)
To detect eavesdropping, Alice and Bob compare a randomly chosen subset of their sifted key bits over the public channel. They calculate the Quantum Bit Error Rate (QBER):
\text{QBER} = \frac{\text{Number of mismatched bits in subset}}{\text{Total number of bits in subset}}
If the QBER exceeds a predefined threshold (self._cfg.QBER), they assume an eavesdropper was present and abort the protocol. Eve’s measurements in random bases will cause disagreements between Alice’s and Bob’s bits even when they used the same basis, increasing the QBER.
# Part of reconcileKey method (continued)
# ... select subset_idx ...
a_subset = [bits_a_matched[i] for i in subset_idx]
b_subset = [bits_b_matched[i] for i in subset_idx]
qber = sum(a != b for a, b in zip(a_subset, b_subset)) / subset_size
if qber < self._cfg.QBER:
# Key is likely secure
# ... generate final key from remaining bits ...
self._isKeyValid = True
self._isKeyCompromised = False
else:
# Eavesdropping detected
self._key = None
self._isKeyValid = False
self._isKeyCompromised = True
6. Final key generation
If the QBER is below the threshold, Alice and Bob use the remaining sifted bits (those not used for QBER estimation) as their shared secret key. The simulation packs these bits into a byte string. Error correction and privacy amplification, which are subsequent steps in a full QKD protocol, are not included in this basic simulation.
Testing
Unit tests verify the simulation’s behavior:
- a valid key can be generated when no eavesdropping occurs,
- eavesdropping leads to a high QBER and prevents valid key generation,
- encryption and decryption using the generated key work correctly if the key is valid.
This simulation provides a practical demonstration of the fundamental principles behind the BB84 protocol and how it uses quantum properties to detect eavesdropping during key exchange.
I previously discussed the theoretical framework in a previous posts here.
For access to the complete simulation code, please visit the GitHub repository here.