CirqでBB84を実装してみた

量子プログラミングのライブラリCirqを使って、量子鍵配送プロトコルBB84を実装してみました。

Cirq

Googleが公開している量子プログラミングのライブラリです。(https://cirq.readthedocs.io/en/stable/)

以前Cirqでフーリエ変換を実装したことがあるので、興味のある方はそちらもどうぞ。(http://tech.innovation.co.jp/2019/03/16/Cirq.html)

BB84

量子鍵配送のプロトコルです。(https://ja.wikipedia.org/wiki/BB84)

BB84をざっくり説明するとこんな感じです(間違ってたら教えてください)

  • 送信者はランダムにビット列を生成する
  • それぞれのビットをqubitにエンコードして受信者に送信する。その際、X基底・H基底のどちらをランダムに選択してエンコードする
  • 受信者はそれぞれのqubitをデコードして値を読み取る。その際、X基底・H基底のどちらをランダムに選択してデコードする
  • 送信者と受信者は何番目のビットをどちらの基底でエンコード・デコードしたか教え合う(この通信は量子以外の、通常の通信でよい)
  • 送信者は基底が一致したビットを最初に生成したビット列から抽出し、秘密鍵とする
  • 受信者は基底が一致したビットを読み取ったビット列から抽出し、秘密鍵とする

このプロトコルは、量子の通信経路・古典の通信経路のどちらを盗聴されても安全に鍵を配送できるように設計されています。

実装

これをCirqで普通に実装すると

1_OCGARsgVUDXRSnwMMBnyiw

こんな感じの回路がずらっと並ぶだけで、いまいち面白みに欠けます。そこで、量子テレポーテーションで送るように実装してみました。

量子テレポーテーション : https://ja.wikipedia.org/wiki/%E9%87%8F%E5%AD%90%E3%83%86%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3

できたもの

main.py で全体の処理を実行します。alice.py が送信者で bob.py が受信者になります。network.py が量子テレポーテーションで送信する部分です。

import cirq
from alice import Alice
from bob import Bob
from network import Network
def main():
# 使用するbit数を決める
bit_count = int(input('Bit count : '))
alice = Alice(bit_count)
bob = Bob()
network = Network()
for index in range(bit_count):
# ビットの初期値を決め、ランダムな基底でエンコードする
circuit, qubits = network.send(alice.encode_function(index))
# アリスの手元のビットを観測する
alice.measure_qubits(circuit, qubits[0:2])
# ボブに送ったビットに反映させる
network.reflect_measure_results(circuit, qubits)
# ランダムな基底でデコードする
bob.measure_qubits(circuit, qubits[2])
# アリスとボブは互いに何番目のビットをどちらの基底でエンコード・デコードしたか教え合う
# 基底が一致したビットを抽出し、秘密鍵とする
alice.set_secret_key(bob.decode_gates)
bob.set_secret_key(alice.encode_gates)
if __name__ == '__main__':
main()
view raw cirq-bb84-main hosted with ❤ by GitHub
import cirq
import random
class Alice:
def __init__(self, bit_count):
self._initial_bits = [random.choice([0, 1]) for _ in range(bit_count)]
self.encode_gates = [random.choice([0, 1]) for _ in range(bit_count)]
self._secret_key = ''
def encode_function(self, index):
def encode_function(qubit):
initial_bit = self._initial_bits[index]
if initial_bit == 1:
yield cirq.X(qubit)
gate = self.encode_gates[index]
# 0:X基底、1:H基底
if gate == 0:
yield cirq.X(qubit)
else:
yield cirq.H(qubit)
return encode_function
def measure_qubits(self, circuit, qubits):
# qubitを測定する
circuit.append(cirq.measure(*[qubits[0], qubits[1]], key='alice'))
def set_secret_key(self, decode_gates):
self._secret_key = ''
for index, gate in enumerate(self.encode_gates):
# エンコードしたゲートとデコードしたゲートが同じであれば、
# 対応するinitial_bitを秘密鍵のbitに追加
if gate == decode_gates[index]:
self._secret_key += str(self._initial_bits[index])
print('alice_secret_key : ' + self._secret_key)
view raw cirq-bb84-alice hosted with ❤ by GitHub
import cirq
import random
class Bob:
def __init__(self):
self._result_bits = []
self.decode_gates = []
self._secret_key = ''
def measure_qubits(self, circuit, qubit):
# 0:X基底、1:H基底
bit = random.choice([0, 1])
self.decode_gates.append(bit)
if bit == 0:
circuit.append(cirq.X(qubit))
else:
circuit.append(cirq.H(qubit))
# qubitを測定する
circuit.append(cirq.measure(*[qubit], key='bob'))
result = cirq.Simulator().run(circuit)
self._result_bits.append(result.measurements['bob'][0][0])
print(circuit)
def set_secret_key(self, encode_gates):
self._secret_key = ''
for index, gate in enumerate(self.decode_gates):
# エンコードしたゲートとデコードしたゲートが同じであれば、
# 対応するresult_bitを秘密鍵のbitに追加
if gate == encode_gates[index]:
self._secret_key += str(self._result_bits[index])
print('bob_secret_key : ' + self._secret_key)
view raw cirq-bb84-bob hosted with ❤ by GitHub
import cirq
class Network:
def send(self, encode_function):
qubits = [cirq.LineQubit(i) for i in range(3)]
circuit = cirq.Circuit(
encode_function(qubits[0]),
cirq.H(qubits[1]),
cirq.CNOT(qubits[1], qubits[2]),
cirq.CNOT(qubits[0], qubits[1]),
cirq.H(qubits[0])
)
return circuit, qubits
def reflect_measure_results(self, circuit, qubits):
# 測定結果が1ならボブのqubitにXゲートを追加
circuit.append([
cirq.CX(qubits[1], qubits[2]),
])
# 測定結果が1ならボブのqubitにZゲートを追加
circuit.append([
cirq.CZ(qubits[0], qubits[2]),
])
view raw cirq-bb84-network hosted with ❤ by GitHub

main.py を実行し、鍵生成に使用するビット数を入力します。

1_MKwohWeXr3Lh8ex6Kz40yw

試しに4ビットを使用して鍵を生成すると、以下のようになりました。

1_f9PSNOCfMISuQsiO5RJrbA

生成された鍵が表示されています。元のビット数が少ないので、できた鍵が1ビットしかありません。

以下の部分で1ビット分の情報をAliceからBobに送信しています。複雑に見えますが、殆どがテレポーテーションの為のゲートです。

1_Ad6oYzwGGySux2d-Bji6CQ

256ビットを使って生成してみました。

1_S6_yA-aBcjjt4y1L5r6MdA

このビット数でも互いの鍵が一致しているので、正常に動いているようです。

通信を盗聴された場合

量子の通信経路を盗聴者Eveによって盗聴された場合、以下のような出力になります。

1_zoUFHI4PE-ia3SCRdgsJLg

このように互いの鍵がバラバラになります。これはEveがqubitを観測した時に、ビットの状態が確定したためです。これによって盗聴されたことを検知することができます。

そのため、「鍵を使用する前に先頭の数ビットをお互いに公開し、一致してなければその鍵を使うのをやめる、一致していれば公開していない部分を鍵として通信を行う」という決まりを設けておけば、安全に通信ができます。

おわりに

CirqでBB84を実装してみました。回路自体は単純なので、思ったより簡単に実装できました。今後はもう少し難しい回路にも挑戦してみたいと思います。