├── qkdsim ├── __init__.py ├── qkdutils.py ├── b92.py ├── bb84.py ├── e91.py └── simulations.py ├── requirements.txt ├── docs └── qkd-report.pdf ├── setup.py ├── README.md ├── LICENSE ├── .gitignore └── tests └── test_qkd.py /qkdsim/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | qit 2 | pycrypto 3 | -------------------------------------------------------------------------------- /docs/qkd-report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cotaylor/qkdsim/HEAD/docs/qkd-report.pdf -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='qkdsim', 4 | version='1.0', 5 | description='Quantum Key Distribution Simulation Library', 6 | author='Conner Taylor', 7 | author_email='cmtaylor15@gmail.com', 8 | url='https://github.com/cotaylor/qkdsim', 9 | packages=find_packages(), 10 | install_requires=[ 11 | 'qit', 12 | 'pycrypto' 13 | ] 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qkd-simulation 2 | Quantum Key Distribution simulation project for CSCI498: Introduction to Quantum Computing 3 | 4 | Includes a survey and simulation of the BB84, B92, and E91 protocols for quantum key distribution, as well as solutions to selected exercises from Nielsen \& Chuang's _Quantum Computation and Quantum Information_. 5 | 6 | ## Installation 7 | ```bash 8 | pip install -r requirements.txt 9 | python setup.py install 10 | ``` 11 | 12 | ## Example Usage 13 | ```python 14 | import qkdsim.simulations as sim 15 | sim.runBB84() 16 | ``` 17 | 18 | ## Modules used: 19 | NumPy - www.numpy.org 20 | 21 | PyCrypto - https://www.dlitz.net/software/pycrypto 22 | 23 | Quantum Information Toolkit (QIT) - qit.sourceforge.net 24 | 25 | Nose - https://pypi.python.org/pypi/nose/1.3.7 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Conner Taylor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Latex 2 | *.aux 3 | *.log 4 | *.tex 5 | *.cls 6 | images/ 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *,cover 53 | .hypothesis/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # IPython Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # Distutils 99 | MANIFEST -------------------------------------------------------------------------------- /qkdsim/qkdutils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from Crypto.Random import random 3 | import qit 4 | 5 | MAX_PRINT_SIZE = 56 6 | 7 | def bitFormat(bits): 8 | """Return a printable representation of the given list of bools representing bits.""" 9 | if len(bits) < MAX_PRINT_SIZE: 10 | out = ', '.join(['1' if b == True else '0' if b == False else '-' for b in bits]) 11 | return '[' + out + ']' 12 | else: 13 | binaryString = "" 14 | for j in range(len(bits)): 15 | if bits[j]: 16 | binaryString += "1" 17 | else: binaryString += "0" 18 | return hex(int(binaryString, 2)) 19 | 20 | def detectEavesdrop(key1, key2, errorRate): 21 | """Return True if Alice and Bob detect Eve's interference, False otherwise.""" 22 | if len(key1) == 0 or len(key2) == 0: 23 | return True 24 | if len(key1) != len(key2): 25 | return True 26 | 27 | tolerance = errorRate * 1.2 28 | mismatch = sum([1 for k in range(len(key1)) if key1[k] != key2[k]]) 29 | if abs((float(mismatch) / len(key1)) - errorRate) > tolerance: 30 | return True 31 | 32 | return False 33 | 34 | def discloseHalf(key1, key2): 35 | """Return the tuple (announce1, keep1, announce2, keep2), where 36 | announce2, announce2 = bit values to announce and discard 37 | keep1, keep2 = bit values of new shared keys 38 | """ 39 | # Disclose every other bit 40 | announce1 = [key1[j] for j in range(len(key1)) if j % 2 == 0] 41 | keep1 = [key1[j] for j in range(len(key1)) if j % 2] 42 | announce2 = [key2[j] for j in range(len(key2)) if j % 2 == 0] 43 | keep2 = [key2[j] for j in range(len(key2)) if j % 2] 44 | return (announce1, keep1, announce2, keep2) 45 | 46 | def equivState(state1, state2): 47 | """Return True if state1 and state2 represent the same quantum state.""" 48 | return np.array_equal(state1.prob(), state2.prob()) 49 | 50 | def getRandomBits(length): 51 | """Return a list of bits with given length, each either 0 or 1 with equal probability.""" 52 | bitstring = [] 53 | for j in range(length): 54 | bitstring.append(random.choice([True, False])) 55 | return bitstring 56 | -------------------------------------------------------------------------------- /qkdsim/b92.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import qit 3 | import qkdsim.qkdutils as util 4 | 5 | def simulateNoise(bits, errorRate): 6 | """Simulate channel noise for the B92 protocol.""" 7 | for k in range(len(bits)): 8 | p = np.random.random_sample() 9 | if p < errorRate: bits[k] = flipState(bits[k]) 10 | 11 | return bits 12 | 13 | def decodeState(state, basis): 14 | """Return a bool corresponding to the result of measuring the given state using one of two 15 | filters. 16 | If basis=0, the filter will pass antidiagonal photons and absorb diagonal photons. 17 | If basis=1, the filter will pass horizontal photons and absorb vertical photons. 18 | This corresponds to measuring the correct result 1/4 of the time, otherwise measuring nothing. 19 | """ 20 | # Save the original bit Alice sent 21 | aliceBit = True 22 | if util.equivState(state, qit.state('0')): 23 | aliceBit = False 24 | 25 | # Apply chosen filter 26 | if basis == 0: 27 | state = state.u_propagate(qit.H) 28 | _, result = state.measure() 29 | 30 | if result: 31 | return aliceBit 32 | else: 33 | return None 34 | 35 | def simulateEavesdrop(state, basis): 36 | result = decodeState(state, basis) 37 | if result != None: 38 | return encodeBit(result) 39 | 40 | return None 41 | 42 | def encodeBit(value): 43 | """Return the quantum state representing the B92 encoding of the given binary value.""" 44 | q = qit.state('0') 45 | if value: 46 | return q.u_propagate(qit.H) 47 | else: 48 | return q 49 | 50 | def encodeKey(key): 51 | """Return a list of quantum states corresponding to the B92 encoding of the given binary string.""" 52 | encodedKey = [] 53 | for k in range(len(key)): 54 | encodedKey.append(encodeBit(key[k])) 55 | 56 | return encodedKey 57 | 58 | def flipState(state): 59 | """Perform the transformation corresponding to a bit flip in the B92 protocol.""" 60 | return state.u_propagate(qit.H) 61 | 62 | def matchKeys(keyA, keyB): 63 | """Return the tuple (keyA, keyB) after removing bits where Bob's measured photon 64 | was absorbed. Assumes a value of -1 in keyB represents an absorbed photon. 65 | """ 66 | match = [False if k == -1 else True for k in keyB] 67 | keyB = [keyB[k] for k in range(len(keyB)) if match[k]] 68 | 69 | while len(match) < len(keyA): 70 | match.append(True) 71 | keyA = [keyA[k] for k in range(len(keyA)) if match[k]] 72 | 73 | return (keyA, keyB) 74 | -------------------------------------------------------------------------------- /qkdsim/bb84.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import qit 3 | import qkdsim.qkdutils as util 4 | 5 | def simulateNoise(bits, errorRate): 6 | """Simulate channel noise, each bit can be flipped with probability given by errorRate.""" 7 | for k in range(len(bits)): 8 | p = np.random.random_sample() 9 | if p < errorRate: 10 | bits[k] = flipState(bits[k]) 11 | 12 | return bits 13 | 14 | def decodeState(state, basis): 15 | """Return a bool corresponding to the result of measuring the given state in the given basis.""" 16 | # Change basis if necessary 17 | if basis: 18 | state = state.u_propagate(qit.H) 19 | 20 | _, result = state.measure() 21 | return bool(result) 22 | 23 | def simulateEavesdrop(state, basis): 24 | """Measure an intercepted quantum state in the given basis, and attempt to hide the 25 | operation by re-encoding the measurement result in the same basis. Return the new state. 26 | """ 27 | result = decodeState(state, basis) 28 | return encodeBit(result, basis) 29 | 30 | def encodeBit(value, basis): 31 | """Return the quantum state representing the encoding of the given binary value in the given basis.""" 32 | q = qit.state('0') 33 | 34 | if value: 35 | q = q.u_propagate(qit.sx) # Apply Pauli X operator to flip the qubit 36 | if basis: 37 | q = q.u_propagate(qit.H) # Apply Hadamard operator to change basis 38 | 39 | return q 40 | 41 | def encodeKey(key, bases): 42 | """Return a list of quantum states corresponding to individual qubits prepared using the 43 | following encoding: 44 | key | bases | state 45 | 0 | 0 | +1 |0> 46 | 0 | 1 | +0.7071 |0> +0.7071 |1> 47 | 1 | 0 | +1 |1> 48 | 1 | 1 | +0.7071 |0> -0.7071 |1> 49 | key and bases are lists of bools representing binary values 50 | """ 51 | # TODO: is this really necessary? 52 | if (len(key) != len(bases)): 53 | print("Invalid args: lists must be the same length") 54 | return -1 55 | 56 | encodedKey = [] 57 | for k in range(len(key)): 58 | encodedKey.append(encodeBit(key[k], bases[k])) 59 | 60 | return encodedKey 61 | 62 | def flipState(state): 63 | """Perform the transformation corresponding to a bit flip on the given quantum state 64 | and return it. 65 | """ 66 | if util.equivState(state, qit.state('0')) or util.equivState(state, qit.state('1')): 67 | return state.u_propagate(qit.sx) 68 | else: 69 | return state.u_propagate(qit.sz) 70 | 71 | def matchKeys(key1, key2, bases1, bases2): 72 | """If bases1[k] != bases2[k], discard bit k from both keys. 73 | Returns a tuple containing the resulting keys. 74 | """ 75 | match = np.logical_not(np.logical_xor(bases1, bases2)) 76 | newKey1 = [key1[k] for k in range(len(key1)) if match[k]] 77 | newKey2 = [key2[k] for k in range(len(key1)) if match[k]] 78 | return (newKey1, newKey2) 79 | -------------------------------------------------------------------------------- /qkdsim/e91.py: -------------------------------------------------------------------------------- 1 | from math import pi, cos, sqrt 2 | import numpy as np 3 | from Crypto.Random import random 4 | 5 | def chooseAxes(numBits): 6 | """Return Alice and Bob's randomly chosen mstment axes for the specified 7 | number of qubits in the E91 protocol: 8 | A chooses from (0, pi/4, pi/2) with equal probability, 9 | B chooses from (pi/4, pi/2, 3pi/4) with equal probability. 10 | """ 11 | choicesA = [0, pi/8, pi/4] 12 | choicesB = [0, pi/8, -pi/8] 13 | basesA = [] 14 | basesB = [] 15 | for j in range(numBits): 16 | basesA.append(random.choice(choicesA)) 17 | basesB.append(random.choice(choicesB)) 18 | 19 | return (basesA, basesB) 20 | 21 | def formatBasesForPrint(bases): 22 | """Return printable representation of E91 basis choices for Alice and Bob. 23 | value | angle 24 | 1 | 0 25 | 2 | pi/8 26 | 3 | pi/4 27 | 4 | -pi/8 28 | """ 29 | out = [] 30 | for j in range(len(bases)): 31 | if bases[j] == 0: 32 | out.append(1) 33 | elif bases[j] == pi/8: 34 | out.append(2) 35 | elif bases[j] == pi/4: 36 | out.append(3) 37 | elif bases[j] == -pi/8: 38 | out.append(4) 39 | 40 | return out 41 | 42 | def matchKeys(key1, key2, bases1, bases2): 43 | """Return the tuple (key1, key2, discard1, discard2) after removing bits where Alice 44 | and Bob selected incompatible axes of measurement in the E91 protocol. 45 | """ 46 | match = [True if bases1[k] == bases2[k] else False for k in range(len(bases1))] 47 | discard1 = [key1[k] for k in range(len(key1)) if not(match[k])] 48 | key1 = [key1[k] for k in range(len(key1)) if match[k]] 49 | discard2 = [key2[k] for k in range(len(key2)) if not(match[k])] 50 | key2 = [not(key2[k]) for k in range(len(key2)) if match[k]] 51 | 52 | return (key1, key2, discard1, discard2) 53 | 54 | def measureEntangledState(basisA, basisB, errorRate=0.0): 55 | """Return Alice and Bob's measurement results on a pair of maximally 56 | entangled qubits. basis[A,B] contain Alice and Bob's axes of mstment. 57 | """ 58 | # Alice measures either basis state with equal probability 59 | # -1 will correspond to False (0) and +1 will correspond to True (1) 60 | resultA = random.choice([-1, 1]) 61 | 62 | # If Alice and Bob chose the same axis of mstment, Bob's result is 63 | # perfectly anti-correlated with Alice's. Otherwise its correlation 64 | # coefficient is given by -cos[2(basisA-basisB)]. We use the result 65 | # r to generate a correlated random number that gives Bob's result. 66 | r = -1 * cos(2 * (basisA - basisB)) 67 | r2 = r ** 2 68 | ve = 1 - r2 69 | SD = sqrt(ve) 70 | e = np.random.normal(0, SD) 71 | resultB = resultA * r + e 72 | 73 | resultA = False if resultA < 0 else True 74 | resultB = False if resultB < 0 else True 75 | 76 | if errorRate: 77 | samples = np.random.rand(2) 78 | if samples[0] < errorRate: resultA = not(resultA) 79 | if samples[1] < errorRate: resultB = not(resultB) 80 | 81 | return (resultA, resultB) 82 | -------------------------------------------------------------------------------- /tests/test_qkd.py: -------------------------------------------------------------------------------- 1 | import qkdutils as util 2 | from math import pi 3 | import qit 4 | import numpy as np 5 | import protocols 6 | import bb84, b92, e91 7 | 8 | def test_runBB84(): 9 | numTrials = 20 10 | numBits = 2048 11 | 12 | for j in range(numTrials): 13 | assert(len(simulations.runBB84(numBits, False, 0.0, False)) >= 3*numBits/4) 14 | assert(simulations.runBB84(numBits, True, 0.0, False) == -1) 15 | 16 | 17 | def test_simulateB92(): 18 | numTrials = 20 19 | numBits = 2048 20 | 21 | for j in range(numTrials): 22 | assert(len(simulations.runB92(numBits, False, 0.0, False)) >= 3*numBits/4) 23 | assert(simulations.runB92(numBits, True, 0.0, False) == -1) 24 | 25 | 26 | def test_decodeStateB92(): 27 | # Test probabilistic results match our expectations: 28 | # If sent == basis, measure the value of sent 50% of the time 29 | # If sent not measured, nothing comes through the filter 30 | numTrials = 50 31 | numBits = 1024 32 | sent = util.getRandomBits(numBits) 33 | bases = util.getRandomBits(numBits) 34 | tolerance = 0.05 35 | 36 | for j in range(numTrials): 37 | seen = 0 38 | 39 | for k in range(numBits): 40 | q = qit.state('0') 41 | if sent[k]: q = q.u_propagate(qit.H) 42 | 43 | result = b92.decodeState(q, bases[k]) 44 | if result != None: 45 | seen += 1 46 | assert(result == sent[k]) 47 | 48 | # Expect to get ~1/4 of the original key material 49 | print(float(seen)/numBits) 50 | assert(abs(float(seen)/numBits - 0.25) < tolerance) 51 | 52 | 53 | def test_decodeStateBB84_deterministic(): 54 | # Test deterministic cases 55 | q = qit.state('0') 56 | assert(bb84.decodeState(q, 0) == False) 57 | q = q.u_propagate(qit.H) 58 | assert(bb84.decodeState(q, 1) == False) 59 | q = qit.state('1') 60 | assert(bb84.decodeState(q, 0) == True) 61 | q = q.u_propagate(qit.H) 62 | assert(bb84.decodeState(q, 1) == True) 63 | 64 | 65 | def test_decodeStateBB84_probabilistic(): 66 | # Test probabilistic measurement is roughly even 67 | numTrials = 10000 68 | tolerance = 0.1 * numTrials 69 | 70 | q = qit.state('0') 71 | counts = [0, 0] 72 | for j in range(numTrials): 73 | counts[bb84.decodeState(q, 1)] += 1 74 | assert abs(counts[0] - counts[1]) < tolerance 75 | 76 | q = q.u_propagate(qit.H) 77 | counts = [0, 0] 78 | for j in range(numTrials): 79 | counts[bb84.decodeState(q, 0)] += 1 80 | assert(abs(counts[0] - counts[1]) < tolerance) 81 | 82 | q = qit.state('1') 83 | counts = [0, 0] 84 | for j in range(numTrials): 85 | counts[bb84.decodeState(q, 1)] += 1 86 | assert(abs(counts[0] - counts[1]) < tolerance) 87 | 88 | q = q.u_propagate(qit.H) 89 | counts = [0, 0] 90 | for j in range(numTrials): 91 | counts[bb84.decodeState(q, 0)] += 1 92 | assert(abs(counts[0] - counts[1]) < tolerance) 93 | 94 | 95 | def test_discloseHalf(): 96 | numTrials = 128 97 | 98 | for j in range(numTrials): 99 | key1 = util.getRandomBits(j) 100 | key2 = util.getRandomBits(j) 101 | announce1, key1, announce2, key2 = util.discloseHalf(key1, key2) 102 | assert(len(key1) + len(announce1) == j) 103 | assert(len(key2) + len(announce2) == j) 104 | 105 | 106 | def test_simulateE91(): 107 | numTrials = 20 108 | numBits = 2048 109 | 110 | for j in range(numTrials): 111 | assert(len(simulations.runE91(numBits)) >= 3*numBits/4) 112 | 113 | 114 | def test_encodeBitBB84(): 115 | # Only test the 4 cases in our encoding strategy 116 | assert(util.equivState(bb84.encodeBit(0,0), qit.state('0'))) 117 | assert(util.equivState(bb84.encodeBit(1,0), qit.state('1'))) 118 | assert(util.equivState(bb84.encodeBit(0,1), qit.state('0').u_propagate(qit.H))) 119 | assert(util.equivState(bb84.encodeBit(1,1), qit.state('1').u_propagate(qit.H))) 120 | 121 | 122 | def test_getRandomBits(): 123 | counts = [0, 0] 124 | numBits = 1024 125 | numTrials = 100 126 | tolerance = 0.1 * numBits 127 | 128 | for j in range(numTrials): 129 | bits = util.getRandomBits(numBits) 130 | counts[0] = len([j for j in bits if j==0]) 131 | counts[1] = len([j for j in bits if j==1]) 132 | print(abs(counts[0]-counts[1])) 133 | assert(abs(counts[0]-counts[1]) < tolerance) 134 | 135 | 136 | def test_matchKeysBB84(): 137 | numTrials = 100 138 | numBits = 256 139 | 140 | for j in range(numTrials): 141 | key1 = util.getRandomBits(numBits) 142 | bases1 = util.getRandomBits(numBits) 143 | sent = bb84.encodeKey(key1, bases1) 144 | bases2 = util.getRandomBits(numBits) 145 | key2 = [] 146 | for k in range(numBits): 147 | key2.append(bb84.decodeState(sent[k], bases2[k])) 148 | 149 | result1, result2 = bb84.matchKeys(key1, key2, bases1, bases2) 150 | assert(result1 == result2) 151 | 152 | 153 | def test_measureEntangledState(): 154 | numTrials = 10000 155 | 156 | for j in range(numTrials): 157 | (basisA, basisB) = e91.chooseAxes(1) 158 | (A, B) = e91.measureEntangledState(basisA[0], basisB[0]) 159 | if basisA == basisB: 160 | assert(A != B) # Bob's result must be anti-correlated with Alice's 161 | -------------------------------------------------------------------------------- /qkdsim/simulations.py: -------------------------------------------------------------------------------- 1 | import qkdsim.bb84 as bb84 2 | import qkdsim.b92 as b92 3 | import qkdsim.e91 as e91 4 | import qkdsim.qkdutils as util 5 | 6 | def runBB84(n, eve=False, errorRate=0.0, verbose=True): 7 | """Simulation of Bennett & Brassard's 1984 protocol for quantum key distribution with 8 | n initial bits in the raw key. 9 | If eve is set to True, assumes the presence of an eavesdropper attempting an 10 | intercept-resend attack. 11 | """ 12 | numBits = 5 * n 13 | 14 | if verbose: 15 | print("\n=====BB84 protocol=====\n%d initial bits, ~%d key bits" % (numBits, n)) 16 | if eve: print("with eavesdropping") 17 | else: print("without eavesdropping") 18 | if errorRate: print("with channel noise") 19 | else: print("without channel noise") 20 | 21 | # Alice generates a random bit string to be encoded 22 | rawKey = util.getRandomBits(numBits) 23 | 24 | # Alice also randomly chooses which basis to use when encoding each bit 25 | # 0: computational basis; 1: Hadamard basis 26 | bases_A = util.getRandomBits(numBits) 27 | 28 | print("\nAlice generates %d random bits to be encoded:\n%s" % (numBits, util.bitFormat(rawKey))) 29 | print("For each bit, Alice randomly chooses one of two non-orthogonal sets of bases:\n%s" % util.bitFormat(bases_A)) 30 | 31 | if verbose: 32 | print("\nAlice encodes each bit according to the following strategy:"\ 33 | "\n value | basis | state"\ 34 | "\n 0 | 0 | +1 |0>"\ 35 | "\n 0 | 1 | +0.7071 (|0> + |1>)"\ 36 | "\n 1 | 0 | +1 |1>"\ 37 | "\n 1 | 1 | +0.7071 (|0> - |1>)"\ 38 | "\nShe then sends each qubit one by one to Bob over a quantum channel.\n") 39 | 40 | # Alice prepares n qubits, with the kth qubit in state |0> or |1> in either the computational 41 | # basis or the Hadamard basis, depending on the value of the kth bit in each bitstring 42 | sent_A = bb84.encodeKey(rawKey, bases_A) 43 | 44 | # QKD guarantees with high probability we will detect any eavesdropping 45 | 46 | if eve: 47 | if verbose: 48 | print("Eve intercepts each qubit as it travels to Bob. Because it is not possible"\ 49 | "\nto clone quantum states, she must measure each qubit before re-sending to"\ 50 | "\nBob. Every time she measures a qubit in the 'wrong' basis, she has a 50%"\ 51 | "\nprobability of being detected.\n") 52 | 53 | # No matter what strategy Eve uses to select bases, the probability she will be detected 54 | # is always 1-(3/4)^numBits if Alice chose her bases randomly 55 | bases_E = util.getRandomBits(numBits) 56 | print("Eve chooses a random basis to measure each qubit in:\n%s" % util.bitFormat(bases_E)) 57 | 58 | # Eve measures each qubit and attempts to cover her tracks 59 | for k in range(numBits): 60 | sent_A[k] = bb84.simulateEavesdrop(sent_A[k], bases_E[k]) 61 | 62 | if verbose: print("\nEve attempts to hide her actions by re-encoding her measurement result"\ 63 | "\nbefore re-sending the qubits to Bob.\n") 64 | 65 | # Introduce error due to noise 66 | sent_A = bb84.simulateNoise(sent_A, errorRate) 67 | 68 | # Bob measures each qubit in a randomly chosen basis 69 | bases_B = util.getRandomBits(numBits) 70 | key_B = [] 71 | for k in range(numBits): 72 | key_B.append(bb84.decodeState(sent_A[k], bases_B[k])) 73 | 74 | print("Bob chooses a random basis to measure each qubit in:\n%s" % util.bitFormat(bases_B)) 75 | print("Bob's measurement results:\n%s" % util.bitFormat(key_B)) 76 | 77 | # Alice and Bob discard any bits where they chose different bases. 78 | key_A, key_B = bb84.matchKeys(rawKey, key_B, bases_A, bases_B) 79 | numBits = len(key_A) 80 | 81 | if verbose: 82 | print("\nBob announces when he has measured the last qubit and discloses"\ 83 | "\nthe bases he used for each measurement. Alice and Bob then discard"\ 84 | "\nany bits where they chose different bases.\n") 85 | 86 | print("Alice's key after discarding mismatches:\n%s" % util.bitFormat(key_A)) 87 | print("Bob's key after discarding mismatches:\n%s" % util.bitFormat(key_B)) 88 | 89 | # Alice and Bob sacrifice a subset of their bits to try to detect Eve 90 | announce_A, key_A, announce_B, key_B = util.discloseHalf(key_A, key_B) 91 | if verbose: 92 | print("\nAlice and Bob sacrifice %d of their %d shared bits and publicly announce"\ 93 | "\ntheir values. They agree to disclose every other bit of their shared key.\n" % (len(announce_A), numBits)) 94 | 95 | print("Alice's announced bits:\n%s" % util.bitFormat(announce_A)) 96 | print("Bob's announced bits:\n%s" % util.bitFormat(announce_B)) 97 | 98 | numBits = len(key_A) 99 | print("Alice's remaining %d-bit key:\n%s" % (numBits, util.bitFormat(key_A))) 100 | print("Bob's remaining %d-bit key:\n%s" % (numBits, util.bitFormat(key_B))) 101 | 102 | print("Expected error rate: %f" % errorRate) 103 | print("Actual error rate: %f" % (float(sum([1 for k in range(len(key_A)) if key_A[k] != key_B[k]]))/len(key_A))) 104 | 105 | if util.detectEavesdrop(key_A, key_B, errorRate): 106 | print("\nAlice and Bob detect Eve's interference and abort the protocol.") 107 | return -1 108 | 109 | return key_A 110 | 111 | def runB92(n, eve=False, errorRate=0.0, verbose=True): 112 | """Simulation of Bennet's 1992 protocol for quantum key distribution with n initial 113 | bits in the raw key. If eve is set to True, assumes the presence of an eavesdropper 114 | attempting an intercept-resend attack. errorRate represents the probability that a bit 115 | will be flipped when Bob measures it. 116 | """ 117 | 118 | numBits = 8 * n 119 | 120 | if verbose: 121 | print("\n=====B92 protocol=====\n%d initial bits, ~%d key bits" % (numBits, n)) 122 | if eve: print("with eavesdropping") 123 | else: print("without eavesdropping") 124 | if errorRate: print("with channel noise") 125 | else: print("without channel noise") 126 | 127 | # Alice generates a random bit string to be encoded 128 | rawKey = util.getRandomBits(numBits) 129 | print("\nAlice generates %d random bits to be encoded:\n%s" % (numBits, util.bitFormat(rawKey))) 130 | 131 | # Alice encodes each bit as a qubit as |0> in either the computational or Hadamard basis 132 | sent_A = b92.encodeKey(rawKey) 133 | if verbose: 134 | print("Alice encodes each bit according to the following strategy:"\ 135 | "\n value | state"\ 136 | "\n 0 | +1 |0>"\ 137 | "\n 1 | +0.7071 (|0> + |1>)"\ 138 | "\nShe then sends each qubit one by one to Bob over a quantum channel.\n") 139 | 140 | # QKD guarantees with high probability we will detect any eavesdropping 141 | if eve: 142 | if verbose: 143 | print("Eve intercepts each qubit as it travels to Bob. Because it is not possible"\ 144 | "\nto clone quantum states, she must measure each qubit before re-sending to Bob.\n") 145 | 146 | # Eve randomly selects a filter to use for each qubit 147 | bases_E = util.getRandomBits(numBits) 148 | print("Eve chooses a random filter to measure each qubit with:\n%s" % util.bitFormat(bases_E)) 149 | 150 | # Eve measures each qubit and attempts to cover her tracks 151 | temp = [] 152 | for k in range(numBits): 153 | result = b92.simulateEavesdrop(sent_A[k], bases_E[k]) 154 | if result != None: temp.append(result) 155 | 156 | sent_A = temp 157 | numBits = len(sent_A) 158 | 159 | if verbose: print("\nEve attempts to hide her actions by re-encoding her measurement result"\ 160 | "\nbefore re-sending the qubits to Bob.\n") 161 | 162 | # Introduce error due to noise 163 | sent_A = b92.simulateNoise(sent_A, errorRate) 164 | 165 | # Bob measures each qubit in a randomly chosen basis 166 | bases_B = util.getRandomBits(numBits) 167 | key_B = [] 168 | for k in range(numBits): 169 | result = b92.decodeState(sent_A[k], bases_B[k]) 170 | if result == None: key_B.append(-1) 171 | else: key_B.append(result) 172 | 173 | print("Bob chooses a random filter to measure each qubit with:\n%s" % util.bitFormat(bases_B)) 174 | print("Bob's measurement results:\n%s" % util.bitFormat(key_B)) 175 | 176 | # Discard bits where Bob did not see a result 177 | key_A, key_B = b92.matchKeys(rawKey, key_B) 178 | numBits = len(key_B) 179 | 180 | if verbose: 181 | print("\nBob announces which photons were completely absorbed and"\ 182 | "\nAlice and Bob discard the corresponding bits from their keys.\n") 183 | print("Alice's sifted key:\n%s" % util.bitFormat(key_A)) 184 | print("Bob's sifted key:\n%s" % util.bitFormat(key_B)) 185 | 186 | # Compare key information 187 | if len(key_A) != len(key_B): 188 | print("\nAlice and Bob announce the lengths of their keys. Since Alice's"\ 189 | "\nkey is %d bits and Bob's is %d bits, they are able to detect"\ 190 | "\nEve's interference and abort the protocol.\n" % (len(key_A), len(key_B))) 191 | return -1 192 | 193 | announce_A, key_A, announce_B, key_B = util.discloseHalf(key_A, key_B) 194 | if verbose: 195 | print("\nAlice and Bob sacrifice %d of their %d shared bits and publicly announce"\ 196 | "\ntheir values. They agree to disclose every other bit of their shared key.\n" % (len(announce_A), numBits)) 197 | print("Alice's announced bits:\n%s" % util.bitFormat(announce_A)) 198 | print("Bob's announced bits:\n%s" % util.bitFormat(announce_B)) 199 | 200 | numBits = len(key_A) 201 | print("Alice's remaining %d-bit key:\n%s" % (numBits, util.bitFormat(key_A))) 202 | print("Bob's remaining %d-bit key:\n%s" % (numBits, util.bitFormat(key_B))) 203 | 204 | print("Expected error rate: %f" % errorRate) 205 | print("Actual error rate: %f" % (float(sum([1 for k in range(len(key_A)) if key_A[k] != key_B[k]]))/len(key_A))) 206 | 207 | if util.detectEavesdrop(key_A, key_B, errorRate): 208 | print("\nAlice and Bob detect Eve's interference and abort the protocol.\n") 209 | return -1 210 | 211 | return key_A 212 | 213 | def runE91(n, errorRate=0.0, verbose=True): 214 | """Simulation of Ekert's 1991 entanglement-based protocol for quantum key distribution.""" 215 | numBits = 5 * n 216 | 217 | if verbose: 218 | print("\n=====E91 protocol=====\n%d initial bits, ~%d key bits" % (numBits, n)) 219 | print("without eavesdropping") 220 | if errorRate: print("with channel noise\n") 221 | else: print("without channel noise\n") 222 | 223 | # A trusted mediator generates pairs of particles in the singlet state 224 | # +0.7071 |0> -0.7071 |1> 225 | # and sends one particle from each pair to Alice and the other to Bob. 226 | # Alice randomly offsets her axis of measurement by one of the following: 227 | # [0, pi/8, pi/4] 228 | # Bob randomly offsets his axis of measurement by one of the following: 229 | # [0, pi/8, -pi/8] 230 | bases_A, bases_B = e91.chooseAxes(numBits) 231 | key_A, key_B = [], [] 232 | 233 | for j in range(numBits): 234 | (new_A, new_B) = e91.measureEntangledState(bases_A[j], bases_B[j], errorRate) 235 | key_A.append(new_A) 236 | key_B.append(new_B) 237 | 238 | print("Alice's randomly chosen axes of measurement:\n%s" % e91.formatBasesForPrint(bases_A)) 239 | print("Bob's randomly chosen axes of measurement:\n%s" % e91.formatBasesForPrint(bases_B)) 240 | print("Alice's measurement results:\n%s" % util.bitFormat(key_A)) 241 | print("Bob's measurement results:\n%s" % util.bitFormat(key_B)) 242 | 243 | key_A, key_B, discard_A, discard_B = e91.matchKeys(key_A, key_B, bases_A, bases_B) 244 | print("Alice's %d discarded bits:\n%s" % (len(discard_A), util.bitFormat(discard_A))) 245 | print("Bob's %d discarded bits:\n%s" % (len(discard_B), util.bitFormat(discard_B))) 246 | 247 | print("Alice's %d-bit sifted key:\n%s" % (len(key_A), util.bitFormat(key_A))) 248 | print("Bob's %d-bit sifted key:\n%s" % (len(key_B), util.bitFormat(key_B))) 249 | 250 | return key_A 251 | --------------------------------------------------------------------------------