336 lines
15 KiB
Python
336 lines
15 KiB
Python
import cirq
|
||
import numpy as np
|
||
|
||
|
||
def bitstring(bits):
|
||
"""Преобразует список битов в строку, состоящую из символов '0' и '1'.
|
||
|
||
Аргументы:
|
||
bits -- список или массив битов (0 или 1)
|
||
|
||
Возвращает:
|
||
строку, например, '0101101'
|
||
"""
|
||
return "".join(str(int(b)) for b in bits)
|
||
|
||
|
||
class BB84(object):
|
||
|
||
def __init__(self, repetitions=1, random_seed=None):
|
||
"""
|
||
Инициализация протокола BB84.
|
||
|
||
repetitions -- число повторений измерений (обычно 1, для статистики можно больше)
|
||
random_seed -- зерно генератора случайных чисел для воспроизводимости (если None, сгенерируется случайно)
|
||
"""
|
||
super().__init__()
|
||
if random_seed is None:
|
||
random_seed = np.random.randint(0, 2**31 - 1) # Генерируем случайное зерно
|
||
np.random.seed(random_seed) # Настраиваем генератор случайных чисел
|
||
self.repetitions = repetitions # Количество повторов симуляции схемы
|
||
self.circuits = {} # Словарь для хранения схем с разными названиями
|
||
|
||
def setup_demo(self, num_qubits):
|
||
"""
|
||
Генерация случайных параметров для одной сессии BB84.
|
||
|
||
Выбираются:
|
||
- Базисы Алисы (0 - вычислительный, 1 - базис Адамара)
|
||
- Состояния Алисы для каждого кубита (0 или 1)
|
||
- Базисы Боба (случайные 0 или 1)
|
||
- Базисы Евы (случайные 0 или 1)
|
||
Также вычисляется ожидаемый секретный ключ для совпадающих базисов Алисы и Боба
|
||
"""
|
||
self.alice_basis = [
|
||
np.random.randint(0, 2) for _ in range(num_qubits)
|
||
] # 0 или 1
|
||
self.alice_state = [
|
||
np.random.randint(0, 2) for _ in range(num_qubits)
|
||
] # 0 или 1
|
||
self.bob_basis = [np.random.randint(0, 2) for _ in range(num_qubits)]
|
||
self.eve_basis = [np.random.randint(0, 2) for _ in range(num_qubits)]
|
||
|
||
# Формируем ожидаемый ключ - отбираем биты Алисы там, где базисы совпали с Бобом
|
||
self.expected_key = bitstring(
|
||
[
|
||
self.alice_state[i]
|
||
for i in range(num_qubits)
|
||
if self.alice_basis[i] == self.bob_basis[i]
|
||
]
|
||
)
|
||
|
||
def make_bb84_circuit(
|
||
self, num_qubits, sender_basis, receiver_basis, sender_state, name="1"
|
||
):
|
||
"""
|
||
Построение квантовой схемы для протокола BB84.
|
||
|
||
Параметры:
|
||
num_qubits -- число кубитов
|
||
sender_basis -- базисы отправителя (0 - вычислительный, 1 - Адамара)
|
||
receiver_basis -- базисы получателя (0 или 1)
|
||
sender_state -- биты состояния отправителя (0 или 1)
|
||
name -- название схемы (ключ в словаре circuits)
|
||
|
||
Алгоритм:
|
||
1) Отправитель подготавливает кубиты:
|
||
- Применяет X к кубитам с состоянием 1 (переключение |0> -> |1>)
|
||
- Применяет H к кубитам, если базис Адамара (1)
|
||
2) Получатель измеряет кубиты:
|
||
- Применяет H перед измерением, если базис Адамара (1)
|
||
3) Все кубиты измеряются
|
||
"""
|
||
self.qubits = cirq.LineQubit.range(num_qubits) # Создание линии кубитов
|
||
self.circuits[name] = cirq.Circuit() # Инициализация пустой схемы
|
||
|
||
sender_flips = [] # Операции X для изменения состояния с |0> на |1>
|
||
sender_rotations = [] # Операции H для переключения базиса
|
||
|
||
# Цикл по кубитам на стороне отправителя
|
||
for index in range(num_qubits):
|
||
if sender_state[index] == 1:
|
||
sender_flips.append(
|
||
cirq.X(self.qubits[index])
|
||
) # Применяем X, если бит 1
|
||
if sender_basis[index] == 1:
|
||
sender_rotations.append(
|
||
cirq.H(self.qubits[index])
|
||
) # Применяем H, если выбран базис Адамара
|
||
|
||
# Добавляем операции отправителя в схему как отдельные "слои" времени (Moments),
|
||
# что соответствует выполнению этих операций одновременно независимо для разных кубитов
|
||
self.circuits[name].append(cirq.Moment(sender_flips))
|
||
self.circuits[name].append(cirq.Moment(sender_rotations))
|
||
|
||
# Операции получателя - подготовка базиса измерения
|
||
recv_basis_choice = []
|
||
for index, rbasis in enumerate(receiver_basis):
|
||
if rbasis == 1:
|
||
recv_basis_choice.append(
|
||
cirq.H(self.qubits[index])
|
||
) # Применяем H для базиса Адамара перед измерением
|
||
|
||
self.circuits[name].append(
|
||
cirq.Moment(recv_basis_choice)
|
||
) # Добавляем перед измерением
|
||
self.circuits[name].append(
|
||
cirq.Moment(cirq.measure_each(*self.qubits))
|
||
) # Измеряем все кубиты
|
||
|
||
def simulate_base_protocol(self, num_qubits=8, print_legend=None):
|
||
"""
|
||
Симуляция базового протокола BB84 без присутствия перехватчика (Евы).
|
||
|
||
- Генерация параметров
|
||
- Создание схемы для Алисы и Боба
|
||
- Симуляция измерений
|
||
- Выделение ключа на основе совпадения базисов
|
||
- Печать схемы и результатов
|
||
"""
|
||
self.setup_demo(num_qubits)
|
||
|
||
# Строим схему взаимодействия Алисы и Боба
|
||
self.make_bb84_circuit(
|
||
num_qubits, self.alice_basis, self.bob_basis, self.alice_state, name="base"
|
||
)
|
||
|
||
# Запуск симуляции схемы c заданным числом повторений
|
||
result = cirq.Simulator().run(
|
||
program=self.circuits["base"], repetitions=self.repetitions
|
||
)
|
||
|
||
# Получение бит результата измерений: для каждого кубита берётся первый результат первого повторения (.item())
|
||
result_bitstring = bitstring(
|
||
[int(result.measurements[str(q)][0].item()) for q in self.qubits]
|
||
)
|
||
|
||
# Формируем ключ Боба только для тех позиций, где базисы совпали с Алисой
|
||
obtained_key = "".join(
|
||
[
|
||
result_bitstring[i]
|
||
for i in range(num_qubits)
|
||
if self.alice_basis[i] == self.bob_basis[i]
|
||
]
|
||
)
|
||
|
||
# Выводим схему и результаты
|
||
print("########## Печать схемы ##########")
|
||
print(self.circuits["base"])
|
||
print("########## Печать результатов ##########")
|
||
self.print_results(
|
||
self.alice_basis,
|
||
self.bob_basis,
|
||
self.alice_state,
|
||
self.expected_key,
|
||
obtained_key,
|
||
)
|
||
print(
|
||
"Общий секретный ключ успешно распределен и может использоваться для шифрования."
|
||
)
|
||
|
||
def simulate_eavesdropped_protocol(self, num_qubits=8, print_legend=None):
|
||
"""
|
||
Симуляция протокола BB84 с перехватом.
|
||
|
||
- Генерация параметров
|
||
- Симуляция перехвата Евы между Алисой и Бобом
|
||
- Симуляция передачи от Евы к Бобу
|
||
- Формирование ключей по разным базисам
|
||
- Печать схемы и результатов
|
||
"""
|
||
self.setup_demo(num_qubits)
|
||
|
||
# Создаем схему передачи Алиса -> Ева (с их базисами)
|
||
self.make_bb84_circuit(
|
||
num_qubits,
|
||
self.alice_basis,
|
||
self.eve_basis,
|
||
self.alice_state,
|
||
name="alice_eve",
|
||
)
|
||
|
||
# Симуляция измерений Евы
|
||
result = cirq.Simulator().run(
|
||
program=self.circuits["alice_eve"], repetitions=self.repetitions
|
||
)
|
||
# Ева сохраняет измеренные биты
|
||
eve_state = [int(result.measurements[str(q)][0].item()) for q in self.qubits]
|
||
|
||
# Ева формирует новую схему для передачи Бобу, используя свои измерения и выбранный базис
|
||
self.make_bb84_circuit(
|
||
num_qubits, self.eve_basis, self.bob_basis, eve_state, name="eve_bob"
|
||
)
|
||
|
||
# Симуляция измерений Боба
|
||
result = cirq.Simulator().run(
|
||
program=self.circuits["eve_bob"], repetitions=self.repetitions
|
||
)
|
||
result_bitstring = bitstring(
|
||
[int(result.measurements[str(q)][0].item()) for q in self.qubits]
|
||
)
|
||
|
||
# Формируем ключи для анализа совпадения (Алиса-Ева и Алиса-Боб)
|
||
intercepted_key = "".join(
|
||
[
|
||
result_bitstring[i]
|
||
for i in range(num_qubits)
|
||
if self.alice_basis[i] == self.eve_basis[i]
|
||
]
|
||
)
|
||
obtained_key = "".join(
|
||
[
|
||
result_bitstring[i]
|
||
for i in range(num_qubits)
|
||
if self.alice_basis[i] == self.bob_basis[i]
|
||
]
|
||
)
|
||
|
||
# Объединяем цепочки схем Алиса->Ева и Ева->Боб для вывода
|
||
circuit = self.circuits["alice_eve"] + self.circuits["eve_bob"]
|
||
print("########## Печать схемы ##########")
|
||
print(circuit)
|
||
print("########## Печать результатов ##########")
|
||
self.print_eavesdropped_results(
|
||
self.alice_basis,
|
||
self.bob_basis,
|
||
self.eve_basis,
|
||
self.alice_state,
|
||
eve_state,
|
||
self.expected_key,
|
||
obtained_key,
|
||
)
|
||
|
||
print("Ева попыталась подслушать и повторно отправить кубиты.")
|
||
print("Это приводит к ошибкам в ключе, которые обнаруживает Боб.")
|
||
|
||
def print_results(
|
||
self, alice_basis, bob_basis, alice_state, expected_key, obtained_key
|
||
):
|
||
"""
|
||
Вывод результатов базового протокола без перехвата.
|
||
|
||
Печатаются:
|
||
- исходное сообщение Алисы,
|
||
- базисы Алисы и Боба,
|
||
- совпадения базисов (в виде X),
|
||
- ключи Алисы и Боба.
|
||
"""
|
||
num_qubits = len(alice_basis)
|
||
basis_match = "".join(
|
||
["X" if alice_basis[i] == bob_basis[i] else "_" for i in range(num_qubits)]
|
||
)
|
||
alice_basis_str = "".join(
|
||
["C" if alice_basis[i] == 0 else "H" for i in range(num_qubits)]
|
||
)
|
||
bob_basis_str = "".join(
|
||
["C" if bob_basis[i] == 0 else "H" for i in range(num_qubits)]
|
||
)
|
||
|
||
print(f"Только Алиса знает сообщение:\t{bitstring(alice_state)}")
|
||
print("Базисы Алисы и Боба (C - вычислительный, H - Адамара):")
|
||
print(f"Алиса: {alice_basis_str}")
|
||
print(f"Боб: {bob_basis_str}")
|
||
print(f"Совпадение базисов: {basis_match}")
|
||
print(f"Ключ Алисы: {expected_key}")
|
||
print(f"Ключ Боба: {obtained_key}")
|
||
|
||
def print_eavesdropped_results(
|
||
self,
|
||
alice_basis,
|
||
bob_basis,
|
||
eve_basis,
|
||
alice_state,
|
||
eve_state,
|
||
expected_key,
|
||
obtained_key,
|
||
):
|
||
"""
|
||
Вывод результатов протокола с перехватом.
|
||
|
||
Печатаются:
|
||
- исходное сообщение Алисы,
|
||
- предположение Евы (ее измеренные биты),
|
||
- базисы Алисы, Евы и Боба,
|
||
- совпадения базисов,
|
||
- ключи Алисы, Евы (ставится равным ключу Алисы, тк она пытается угадать),
|
||
и ключ Боба.
|
||
"""
|
||
num_qubits = len(alice_basis)
|
||
eve_basis_match = "".join(
|
||
["X" if alice_basis[i] == eve_basis[i] else "_" for i in range(num_qubits)]
|
||
)
|
||
bob_basis_match = "".join(
|
||
["X" if alice_basis[i] == bob_basis[i] else "_" for i in range(num_qubits)]
|
||
)
|
||
alice_basis_str = "".join(
|
||
["C" if alice_basis[i] == 0 else "H" for i in range(num_qubits)]
|
||
)
|
||
eve_basis_str = "".join(
|
||
["C" if eve_basis[i] == 0 else "H" for i in range(num_qubits)]
|
||
)
|
||
bob_basis_str = "".join(
|
||
["C" if bob_basis[i] == 0 else "H" for i in range(num_qubits)]
|
||
)
|
||
|
||
print(f"Только Алиса знает сообщение:\t{bitstring(alice_state)}")
|
||
print(f"Предположение Евы о cообщении:\t{bitstring(eve_state)}")
|
||
print("Базисы (C - вычислительный, H - Адамара):")
|
||
print(f"Алиса: {alice_basis_str}")
|
||
print(f"Ева: {eve_basis_str}")
|
||
print(f"Боб: {bob_basis_str}")
|
||
print(f"Совпадение базисов Алисы и Евы: {eve_basis_match}")
|
||
print(f"Совпадение базисов Алисы и Боба: {bob_basis_match}")
|
||
print(f"Ключ Алисы: {expected_key}")
|
||
print(f"Ключ Евы: {expected_key}") # По идее, Ева пытается угадать ключ Алисы
|
||
print(f"Ключ Боба: {obtained_key}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
bb84 = BB84()
|
||
|
||
print("=== БАЗОВЫЙ ПРОТОКОЛ BB84 ===")
|
||
bb84.simulate_base_protocol(num_qubits=8, print_legend=True)
|
||
|
||
print("\n\n=== ПРОТОКОЛ BB84 С ПЕРЕХВАТОМ ===")
|
||
bb84.simulate_eavesdropped_protocol(num_qubits=8, print_legend=False)
|