# Qracon- E91 with Qiskit
這是一篇關於如何使用Qiskit建立一個E91演算法並得出金鑰的筆記
接下來我將會介紹E91協議的執行原理和如何執行,希望本程式能夠在讀者理解此演算法的過程中起到些幫助,或是讓讀者在復刻此程式時能帶給讀者更多的樂趣。
Jupyter notebook版本可以在[這裡](https://www.mediafire.com/file/2h2n9jlp52ztwc4/Qracon-_E91_with_Qiskit.ipynb/file)下載到
## 量子糾纏
讓我們假設AB電子原為一體,現在當他們分裂成A和B電子時他們的旋轉方向會因角動量守恆而相反,當我們觀測A電子的自旋方向時,我們便能得知B電子的自旋方向。
這裡便是1種糾纏態,屬於其中一種Bell states $$\frac{1}{\sqrt{2}}(\lvert11\rangle + \lvert00\rangle)$$
而我們這次用到的 singlet state 可寫成以下,且也是屬於Bell state的一種。
$$
\frac{1}{\sqrt{2}}(\lvert01\rangle - \lvert10\rangle)$$
---
## CHSH不等式
要說明CHSH不等式我們得先說明局域實在論,
1.資訊的傳輸不可超越光速 (局域論 Locality)
2.事物的存在不受觀測引響 (實在論 Realism)
讓我們假設有一個人Victor將分別發送$\pm1$給Alice(A)與Bob(B),而A與B將會從X和Y取一個方向量測V發送的訊號。
如此我們便能計算A與B的期望值總和。
$$A_XB_X\ +\ A_XB_Y +\ A_YB_X\ -\ A_YB_Y\ =\ (A_X\ +\ A_Y)B_X\ +\ \ (A_X\ -\ A_Y)B_Y$$
可以看出來$(A_X\ +\ A_Y)$或$(A_X\ -\ A_Y)$其一會是$0$,畢竟結果會是$-1,1$或$1,-1$其中的一個。
所以多次執行後上式的期望值應不大於$2$。$$-2\ \le\ C\ \le\ 2$$
但如果Victor不再發送古典的$\pm1$,而改發送糾纏量子對的話,經過實驗我們發現期望值居然大約等於$2.8$。
$$|CHSH\rangle\ \approx\ 2\sqrt{2}$$
我們可以假設今天Alice可以各自從X和Z方向擇一測量收到的糾纏對,Bob則可以從W或V方向選擇測量,其中 $W = \frac{1}{\sqrt{2}} (X + Z)$, $V = \frac{1}{\sqrt{2}} (-X + Z)$
那我們的測量期望值應為
\begin{eqnarray*}
\langle X \otimes W \rangle_{\psi_s} &= -\frac{1}{\sqrt{2}}, \quad
\langle X \otimes V \rangle_{\psi_s} &= \frac{1}{\sqrt{2}}, \qquad\qquad \\
\langle Z \otimes W \rangle_{\psi_s} &= -\frac{1}{\sqrt{2}}, \quad
\langle Z \otimes V \rangle_{\psi_s} &= -\frac{1}{\sqrt{2}}.
\end{eqnarray*}
$$C =
\langle X\otimes W \rangle - \langle X \otimes V \rangle + \langle Z \otimes W \rangle + \langle Z \otimes V \rangle =
-2 \sqrt{2}. \qquad\qquad $$
這違背了我們之前古典物理中所證明的期望值絕對值應不大於 $2$ 的理論,所以該理論假設的局域論與實在論勢必會有一個不存在於量子力學,這代表了這些量子在觀測前不存在一個確定的值。
當然我們可用此不等式來檢查量子是否糾纏,且這正是我們要做的。
更多內容可以參考[這裡](https://learning.quantum.ibm.com/tutorial/chsh-inequality)
## 金鑰製作
請先確認**qiskit version = 1.1.1**,其餘版本可能導致程式碼失效。
```
from qiskit import __version__
__version__
```
一樣先導入會用到的庫備用
```
import numpy as np
import random
import re
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import Aer
```
製作一個電路使2個qubits成為上面提到的 singlet 糾纏態,並為其建立4個classicalbits。
```
qr = QuantumRegister(2, name="qr")
cr = ClassicalRegister(4, name="cr")
singlet = QuantumCircuit(qr, cr, name='singlet')
singlet.x(qr[0])
singlet.x(qr[1])
singlet.h(qr[0])
singlet.cx(qr[0],qr[1])
#singlet.draw('mpl') #用以觀察電路
```
製作Alice與Bob各自的三個測量方向 $(X,Z,W,V)$。!\begin{align*}
b_i = 1: \quad \vec{a}_1 &= (1,0,0) \quad (X \text{ observable}) &
b_j^{'} = 1: \quad \vec{b}_1 &= \left(\frac{1}{\sqrt{2}},0,\frac{1}{\sqrt{2}}\right) \quad (W \text{ observable})
\\
b_i = 2: \quad \vec{a}_2 &= \left(\frac{1}{\sqrt{2}},0,\frac{1}{\sqrt{2}}\right) \quad (W \text{ observable}) &
b_j^{'} = 2: \quad \vec{b}_2 &= (0,0,1) \quad ( \text{Z observable})
\\
b_i = 3: \quad \vec{a}_3 &= (0,0,1) \quad (Z \text{ observable}) &
b_j^{'} = 3: \quad \vec{b}_3 &= \left(-\frac{1}{\sqrt{2}},0,\frac{1}{\sqrt{2}}\right) \quad (V \text{ observable})
\end{align*}
```
## Alice的觀測器電路
#A1電路用 X 基底測量qubits
measureA1 = QuantumCircuit(qr, cr, name='measureA1')
measureA1.h(qr[0])
measureA1.measure(qr[0],cr[0])
#A2電路用 W 基底測量qubits
measureA2 = QuantumCircuit(qr, cr, name='measureA2')
measureA2.s(qr[0])
measureA2.h(qr[0])
measureA2.t(qr[0])
measureA2.h(qr[0])
measureA2.measure(qr[0],cr[0])
#A3電路用 Z 基底測量qubits
measureA3 = QuantumCircuit(qr, cr, name='measureA3')
measureA3.measure(qr[0],cr[0])
## Bob的觀測器電路
#B1電路用 W 基底測量qubits
measureB1 = QuantumCircuit(qr, cr, name='measureB1')
measureB1.s(qr[1])
measureB1.h(qr[1])
measureB1.t(qr[1])
measureB1.h(qr[1])
measureB1.measure(qr[1],cr[1])
#B2電路用 Z 基底測量qubits
measureB2 = QuantumCircuit(qr, cr, name='measureB2')
measureB2.measure(qr[1],cr[1])
#B3電路用 V 基底測量qubits
measureB3 = QuantumCircuit(qr, cr, name='measureB3')
measureB3.s(qr[1])
measureB3.h(qr[1])
measureB3.tdg(qr[1])
measureB3.h(qr[1])
measureB3.measure(qr[1],cr[1])
#將以上電路合併成List
aliceMeasurements = [measureA1, measureA2, measureA3]
bobMeasurements = [measureB1, measureB2, measureB3]
```
輸入發送的Singlet量子對數量
並隨機選擇Alice與Bob將使用的測量方向
```
numberOfSinglets = 500
aliceMeasurementChoices = [random.randint(1, 3) for i in range(numberOfSinglets)]
bobMeasurementChoices = [random.randint(1, 3) for i in range(numberOfSinglets)]
```
將測量電路根據上翻選擇的測量方向組合至電路中
首先產生一個singlet量子對,並將第一顆給Alice測量,第二顆給Bob測量。
最後再為每個電路命名。
```
circuits = []
for i in range(numberOfSinglets):
#合併singlet電路兩個觀測電路
circuitName = singlet.compose(aliceMeasurements[aliceMeasurementChoices[i]-1]).compose(bobMeasurements[bobMeasurementChoices[i]-1])
#命名電路
circuitName.name=f'{str(j)} ":A"{str(aliceMeasurementChoices[j])}"_B"{str(bobMeasurementChoices[j] + 2)}
#將電路加至List中
circuits.append(circuitName)
```
確認以上製作的量子電路
```
print(circuits[0].name) #可選擇其他circuits查看
circuits[0].draw('mpl')
```
接下來開始執行量子電路。
先選擇模擬器後執行電路,並印出結果。
這次選用可以批量執行電路的qasm_simulator。
```
simulator = Aer.get_backend('qasm_simulator')
result = simulator.run(circuits,shots=1).result()
```
```
result.get_counts(circuits[0])
```
{'0010': 1}
應該會得到類似0010,Alice和Bob的結果儲存在前兩碼'10'上
接著我們利用python的正規表達式re來分類結果。
```
abPatterns = [
re.compile('..00$'), #尋找後兩碼為00的結果(Alice和Bob各得到-1)
re.compile('..01$'), #尋找後兩碼為01的結果
re.compile('..10$'), #尋找後兩碼為10的結果(Alice得到-1、Bob得到1)
re.compile('..11$') # 尋找後兩碼為11的結果
]
```
然後將結果分類進aliceResults和bobResults中
```
aliceResults = [] # Alice的結果(string a)
bobResults = [] # Bob的結果(string a')
for i in range(numberOfSinglets):
#將結果轉入res List中
res = list(result.get_counts(circuits[i]).keys())[0]
if abPatterns[0].search(res): #如果後兩碼為-1,-1
aliceResults.append(-1) #紀錄Alice得到-1
bobResults.append(-1) #紀錄Bob得到-1
if abPatterns[1].search(res):
aliceResults.append(1)
bobResults.append(-1)
if abPatterns[2].search(res): ##如果後兩碼為-1,1
aliceResults.append(-1) #紀錄Alice得到-1
bobResults.append(1) #紀錄Bob得到1
if abPatterns[3].search(res):
aliceResults.append(1)
bobResults.append(1)
```
接著將以上結果比對並製成能夠加密的金鑰。
理論上根據singlet量子對,如果使用相同的觀測電路測量,Alice的結果應與Bob異號。
```
aliceKey = [] # Alice的密碼串
bobKey = [] # Bob的密碼串
#比對Alice與Bob的觀測電路選擇
for i in range(numberOfSinglets):
#如果Alice和Bob用了同一種基底的觀測電路(不是同一種號碼)
if (aliceMeasurementChoices[i] == 2 and bobMeasurementChoices[i] == 1) or (aliceMeasurementChoices[i] == 3 and bobMeasurementChoices[i] == 2):
aliceKey.append(aliceResults[i]) #記錄Alice的密鑰
bobKey.append(- bobResults[i]) #記錄Bob的密鑰,其應為Alice的密鑰的異號
keyLength = len(aliceKey) #密鑰的長度
```
接下來我們比對Alice與Bob的密鑰,理論上應為相同,否則加密便會失敗。
```
abKeyMismatches = 0 #紀錄錯誤的位數
for j in range(keyLength):
if aliceKey[j] != bobKey[j]:
abKeyMismatches += 1
```
計算CHSH期望值
```
#計算CHSH的程式
def chsh_corr(result):
# lists with the counts of measurement results
# each element represents the number of (-1,-1), (-1,1), (1,-1) and (1,1) results respectively
countA1B1 = [0, 0, 0, 0] # XW observable
countA1B3 = [0, 0, 0, 0] # XV observable
countA3B1 = [0, 0, 0, 0] # ZW observable
countA3B3 = [0, 0, 0, 0] # ZV observable
for i in range(numberOfSinglets):
res = list(result.get_counts(circuits[i]).keys())[0]
# if the spins of the qubits of the i-th singlet were projected onto the a_1/b_1 directions
if (aliceMeasurementChoices[i] == 1 and bobMeasurementChoices[i] == 1):
for j in range(4):
if abPatterns[j].search(res):
countA1B1[j] += 1
if (aliceMeasurementChoices[i] == 1 and bobMeasurementChoices[i] == 3):
for j in range(4):
if abPatterns[j].search(res):
countA1B3[j] += 1
if (aliceMeasurementChoices[i] == 3 and bobMeasurementChoices[i] == 1):
for j in range(4):
if abPatterns[j].search(res):
countA3B1[j] += 1
# if the spins of the qubits of the i-th singlet were projected onto the a_3/b_3 directions
if (aliceMeasurementChoices[i] == 3 and bobMeasurementChoices[i] == 3):
for j in range(4):
if abPatterns[j].search(res):
countA3B3[j] += 1
# number of the results obtained from the measurements in a particular basis
total11 = sum(countA1B1)
total13 = sum(countA1B3)
total31 = sum(countA3B1)
total33 = sum(countA3B3)
# expectation values of XW, XV, ZW and ZV observables (2)
expect11 = (countA1B1[0] - countA1B1[1] - countA1B1[2] + countA1B1[3])/total11 # -1/sqrt(2)
expect13 = (countA1B3[0] - countA1B3[1] - countA1B3[2] + countA1B3[3])/total13 # 1/sqrt(2)
expect31 = (countA3B1[0] - countA3B1[1] - countA3B1[2] + countA3B1[3])/total31 # -1/sqrt(2)
expect33 = (countA3B3[0] - countA3B3[1] - countA3B3[2] + countA3B3[3])/total33 # -1/sqrt(2)
corr = expect11 - expect13 + expect31 + expect33 # calculate the CHSC correlation value (3)
return corr
```
接著來看看成果吧。
```
corr = chsh_corr(result) #計算CHSH
#CHSH結果
print('CHSH correlation value: ' + str(round(corr, 3)))
#金鑰
print('Length of the key: ' + str(keyLength))
print('Number of mismatching bits: ' + str(abKeyMismatches) + '\n')
```
CHSH correlation value: -2.607
Length of the key: 106
Number of mismatching bits: 0
可以看到CHSH的期望值大約等於$-2\sqrt{2}\approx-2.828$,這證明了Alice與Bob的量子確實是糾纏在一起的。
並且Alice和Bob的金鑰沒有任何錯誤。
## Eve
讓我們看看如果Eve在Alice與Bob之前提前測量了這些量子會怎樣。
讓我們製作Eve提前觀測的電路
```
#提前用W方向觀測Alice的qubit
measureEA2 = QuantumCircuit(qr, cr, name='measureEA2')
measureEA2.s(qr[0])
measureEA2.h(qr[0])
measureEA2.t(qr[0])
measureEA2.h(qr[0])
measureEA2.measure(qr[0],cr[2])
measureEA2.barrier()
#提前用Z方向觀測Alice的qubit
measureEA3 = QuantumCircuit(qr, cr, name='measureEA3')
measureEA3.measure(qr[0],cr[2])
measureEA3.barrier()
#提前用W方向觀測Bob的qubit
measureEB1 = QuantumCircuit(qr, cr, name='measureEB1')
measureEB1.s(qr[1])
measureEB1.h(qr[1])
measureEB1.t(qr[1])
measureEB1.h(qr[1])
measureEB1.measure(qr[1],cr[3])
measureEB1.barrier()
#提前用Z方向觀測Bob的qubit
measureEB2 = QuantumCircuit(qr, cr, name='measureEB2')
measureEB2.measure(qr[1],cr[3])
measureEB2.barrier()
#合併
eveMeasurements = [measureEA2, measureEA3, measureEB1, measureEB2]
```
接著我們讓Eve隨機選擇使用的觀測方向
```
#第一和第二位分別代表Eve對Alice和Bob的qubit的測量
eveMeasurementChoices = []
for j in range(numberOfSinglets):
if random.uniform(0, 1) <= 0.5: #50%會由WW方向測量
eveMeasurementChoices.append([0, 2])
else: #50%會由ZZ方向測量
eveMeasurementChoices.append([1, 3])
```
和剛剛一樣讓我們合併,以上電路
```
circuits = [] #合併的電路
for j in range(numberOfSinglets):
#合併電路
circuitName = singlet.compose(eveMeasurements[eveMeasurementChoices[j][0]-1]).compose(eveMeasurements[eveMeasurementChoices[j][1]-1]).compose(aliceMeasurements[aliceMeasurementChoices[j]-1]).compose(bobMeasurements[bobMeasurementChoices[j]-1])
#命名電路
circuitName.name=f'{str(j)} ":A"{str(aliceMeasurementChoices[j])}"_B"{str(bobMeasurementChoices[j] + 2)}"_E"{str(eveMeasurementChoices[j][0])}{str(eveMeasurementChoices[j][1] - 1)}'
#合併以上電路至List中
circuits.append(circuitName)
```
用*qasm_simulator*模擬並印出結果
```
simulator = Aer.get_backend('qasm_simulator')
result = simulator.run(circuits,shots=1).result()
print(str(circuits[0].name) + '\t' + str(result.get_counts(circuits[0]))) #可以更改號碼
```
0 ":A"2"_B"5"_E"01 {'0101': 1}
可以看到後兩位不再是00了而是Eve量測後的結果'01',而前面兩位依舊是Alice與Bob的結果。
接著我們修正正規表達式的搜尋方法。
```
ePatterns = [
re.compile('00..$'), #Eve從Alice與Bob的qubits獲得-1,-1
re.compile('01..$'), #Eve從Alice與Bob的qubits獲得1,-1
re.compile('10..$'),
re.compile('11..$')
]
```
接著我們用前面製作的abPatterns與ePatterns來處理我們的結果。
```
aliceResults = []
bobResults = []
#第一和第二位分別儲存Alice與Bob的qubit的量測結果
eveResults = []
for j in range(numberOfSinglets):
res = list(result.get_counts(circuits[j]).keys())[0] # extract a key from the dict and transform it to str
# Alice and Bob
if abPatterns[0].search(res): # check if the key is '..00' (if the measurement results are -1,-1)
aliceResults.append(-1) # Alice got the result -1
bobResults.append(-1) # Bob got the result -1
if abPatterns[1].search(res):
aliceResults.append(1)
bobResults.append(-1)
if abPatterns[2].search(res): # check if the key is '..10' (if the measurement results are -1,1)
aliceResults.append(-1) # Alice got the result -1
bobResults.append(1) # Bob got the result 1
if abPatterns[3].search(res):
aliceResults.append(1)
bobResults.append(1)
# Eve
if ePatterns[0].search(res): # check if the key is '00..'
eveResults.append([-1, -1]) # results of the measurement of Alice's and Bob's qubits are -1,-1
if ePatterns[1].search(res):
eveResults.append([1, -1])
if ePatterns[2].search(res):
eveResults.append([-1, 1])
if ePatterns[3].search(res):
eveResults.append([1, 1])
```
接著生成金鑰。
```
aliceKey = []
bobKey = []
eveKeys = [] # Eve 竊取的金鑰
# 將測量方向的選擇與測量結果做比對
for j in range(numberOfSinglets):
# 如果Alice和Bob用了同一種基底的觀測電路
if (aliceMeasurementChoices[j] == 2 and bobMeasurementChoices[j] == 1) or (aliceMeasurementChoices[j] == 3 and bobMeasurementChoices[j] == 2):
aliceKey.append(aliceResults[j]) # record the i-th result obtained by Alice as the bit of the secret key k
bobKey.append(-bobResults[j]) # record the multiplied by -1 i-th result obtained Bob as the bit of the secret key k'
eveKeys.append([eveResults[j][0], -eveResults[j][1]]) # record the i-th bits of the keys of Eve
keyLength = len(aliceKey) #金鑰位數
```
計算Alice與Bob金鑰之間的錯誤位數。
```
abKeyMismatches = 0 #Alice與Bob的金鑰錯誤位數
eaKeyMismatches = 0 #Eve與Alice的金鑰錯誤位數
ebKeyMismatches = 0 #Eve與Bob的金鑰錯誤位數
for j in range(keyLength):
if aliceKey[j] != bobKey[j]:
abKeyMismatches += 1
if eveKeys[j][0] != aliceKey[j]:
eaKeyMismatches += 1
if eveKeys[j][1] != bobKey[j]:
ebKeyMismatches += 1
```
我們也可以看看Eve竊取了多少百分率的金鑰。
```
eaKnowledge = (keyLength - eaKeyMismatches)/keyLength # Alice金鑰被竊取的百分率
ebKnowledge = (keyLength - ebKeyMismatches)/keyLength #Bob金鑰被竊取的百分率
```
我們也計算CHSH值,用來確認Alice與Bob的金鑰是否還糾纏在一起。
```
corr = chsh_corr(result)
```
結果
```
# CHSH不等式測試
print('CHSH correlation value: ' + str(round(corr, 3)) + '\n')
# 金鑰
print('Length of the key: ' + str(keyLength))
print('Number of mismatching bits: ' + str(abKeyMismatches) + '\n')
print('Eve\'s knowledge of Alice\'s key: ' + str(round(eaKnowledge * 100, 2)) + ' %')
print('Eve\'s knowledge of Bob\'s key: ' + str(round(ebKnowledge * 100, 2)) + ' %')
```
CHSH correlation value: -1.461
Length of the key: 115
Number of mismatching bits: 10
Eve's knowledge of Alice's key: 92.17 %
Eve's knowledge of Bob's key: 93.91 %
可以看到雖然成功Eve獲得了大部分的金鑰,但這卻影響了CHSH不等式的值,使其大幅偏離$-2\sqrt{2}$,這便可讓通訊者注意到加密的異常並放棄這次金鑰交換了。
值得注意的是Alice與Bob的金鑰存在誤差,這是因為Eve在測量完後將qubits發給破壞了糾纏態,使得Alice與Bob後續使用不同方向測量後有機率產生與預期不同的結果,讓最後的金鑰稱成產生了誤差。
## Additional information
創作者 : 莊懷德
參考自 :
[> E91 quantum key distribution protocol](https://github.com/qiskit-community/qiskit-community-tutorials/blob/master/awards/teach_me_qiskit_2018/e91_qkd/e91_quantum_key_distribution_protocol.ipynb)