In the previous post, we showed Shor’s Algorithm, the theory behind cracking of the RSA using Quantum Computing. In this post, we will show how to program the algorithm using QISKit in order to appreciate how the theory is implemented.
Brief Review of RSA Cryptography
According to the RSA encryption, we choose 2 prime numbers and
and form a product
. Then we choose a number
that has no common factor with
and
. The set of numbers
and
is the public key.
If we have a message , then the encrypted message (or ciphertext) is computed using the formula.
The public only knows about ,
and
. What we want to do is to decrypt the message
using Quantum Computing by a process known as period finding. The period
is an integer defined by
Once is found, we can compute
satisfying
From there, we can finally decrypt our message
Since is the period:
Example
Let and
. Let
since it has not common factors with
and
. If our message is
, then the encrypted message is
Given only the knowledge of ,
and
, we want to find the original message
.
Overview of Shor’s Algorithm
We will setup two registers for the Shor’s Algorithm. The first register is the input register. These are the qubits whose value we will input into the modular exponentiation function. The output of that function will be stored in the output register.
The modular exponentiation function is defined as:
Shor’s algorithm involves:
1. Generating a superposition of all numbers from 0 to
and computing the modular exponentiation of the the numbers modulo
.
2. We then measure the output register. This will give us a value say . In the process of measurement, this will collapse the state of the input register into
3. Next, we apply the Fourier Transform to the input register. The Fourier Transform is defined in the computational basis is:
So applying this to the input register will give:
In other words, the input register will become a superposition of states whose probability of occurring is given by the square of the probability amplitude above.
4. We then measure the input register to get a value .
5. Compute the period using the continued fraction expansion of
The fraction (for some integer j) will appear as one of the partial sums of the continued fraction expansion.
6. Verify that the computed period is indeed a period by satisfying
7. Compute , the inverse modulo of
using the formula:
where is an integer.
8. Use the computed to compute the message from the ciphertext:
Implementation Using QISKit
There are two important circuits we need to create in order to implement Shor’s algorithm. The first is the modular exponentiation and the second is the Fourier Transform. For the modular exponentiation, we are going to implement a similar function that will give the answer specifically to our problem.
The ciphertext we are trying to decrypt is . Below is a table of result for the modular exponentiation. In the binary representation, the most significant bit is to the left.
If you study the table above, you will notice that for numbers ending in the following suffixes, following mappings are true:
Here is a circuit to implement this mapping:
To verify this, if the input is ends with 00, the output register will flip to 1 because of the NOT gate
. When the input ends in “01”, the two Controlled-NOTs at
will flip the bits
and
. When the input ends in “10”, the two Controlled-NOTs in
will flip the bits at
and
. Finally, when the input ends in “11”, all gates starting from the left will be applied. The net result will be that
and
will be set to 1 while
will be set to 0.
The QISKit code to implement this is:
input_register = QuantumRegister(4,name="qin") output_register = QuantumRegister(4,name="qout") c = ClassicalRegister(8) qc = QuantumCircuit(input_register, output_register) qc.barrier() qc.x(output_register[0]) qc.barrier() qc.cx(input_register[0],output_register[2]) qc.cx(input_register[0],output_register[3]) qc.barrier() qc.cx(input_register[1],output_register[2]) qc.cx(input_register[1],output_register[0]) qc.barrier() qc.ccx(input_register[0],input_register[1],output_register[3]) qc.ccx(input_register[0],input_register[1],output_register[2]) qc.ccx(input_register[0],input_register[1],output_register[1]) qc.ccx(input_register[0],input_register[1],output_register[0]) qc.barrier() qc.draw(output='mpl')
Implementing the Fourier Transform
We will use the Fourier Transform circuit derived in this blog post. Here are the sequence of gate operations to implement the Fourier Transform:
where is the controlled-phase rotation gate defined by
Using QISKit, we can visualize the circuit using the code below:
input_register = QuantumRegister(4,name="qin") output_register = QuantumRegister(4,name="qout") c = ClassicalRegister(4) qc = QuantumCircuit(input_register,c) qc.h(input_register[0]) qc.cu1(math.pi/float(2),input_register[0],input_register[1]) qc.cu1(math.pi/float(2**2),input_register[0],input_register[2]) qc.cu1(math.pi/float(2**3),input_register[0],input_register[3]) qc.barrier() qc.h(input_register[1]) qc.cu1(math.pi/float(2),input_register[1],input_register[2]) qc.cu1(math.pi/float(2**2),input_register[1],input_register[3]) qc.barrier() qc.h(input_register[2]) qc.cu1(math.pi/float(2),input_register[2],input_register[3]) qc.h(input_register[3]) qc.barrier()
The Complete Shor’s Algorithm Circuit
Now that we have created the circuits for the “modular exponentiation” and the Fourier Transform, we will combine them to create the circuit for the period finding algorithm.
Here’s the complete code:
from qiskit import Aer import math from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister, execute from qiskit.tools.visualization import circuit_drawer, plot_histogram from qiskit import BasicAer backend = BasicAer.get_backend('qasm_simulator') input_register = QuantumRegister(4) output_register = QuantumRegister(4) c = ClassicalRegister(8) qc = QuantumCircuit(input_register, output_register,c) qc.h(input_register) qc.barrier() qc.x(output_register[0]) qc.barrier() qc.cx(input_register[0],output_register[2]) qc.cx(input_register[0],output_register[3]) qc.barrier() qc.cx(input_register[1],output_register[2]) qc.cx(input_register[1],output_register[0]) qc.barrier() qc.ccx(input_register[0],input_register[1],output_register[3]) qc.ccx(input_register[0],input_register[1],output_register[2]) qc.ccx(input_register[0],input_register[1],output_register[1]) qc.ccx(input_register[0],input_register[1],output_register[0]) qc.barrier() # measure the output qc.measure([4,5,6,7],[4,5,6,7]) qc.barrier() #Apply the Fourier Transform to the new state of the input register. qc.h(input_register[0]) qc.cu1(math.pi/float(2),input_register[0],input_register[1]) qc.cu1(math.pi/float(2**2),input_register[0],input_register[2]) qc.cu1(math.pi/float(2**3),input_register[0],input_register[3]) qc.barrier() qc.h(input_register[1]) qc.cu1(math.pi/float(2),input_register[1],input_register[2]) qc.cu1(math.pi/float(2**2),input_register[1],input_register[3]) qc.barrier() qc.h(input_register[2]) qc.cu1(math.pi/float(2),input_register[2],input_register[3]) qc.h(input_register[3]) qc.barrier() qc.measure([0,1,2,3],[0,1,2,3]) qc.draw(output='mpl')
We then execute this code 1000 times in order to get the probability distributions.
job = execute(qc, backend,shots=1000) result=job.result() counts= result.get_counts(qc) print(counts) plot_histogram(data=counts, figsize=(14,10))
Interpreting the Result
The count of the resulting states can be printed using
print(counts)
In one of the runs, this is the result we got:
{'01111010': 7, '11010000': 66, '11010011': 7, '00010111': 3, '01000010': 35, '11010110': 1, '11011011': 5, '11010001': 30, '01110101': 1, '01111101': 6, '01110111': 2, '01000001': 42, '11010101': 2, '11011001': 1, '00010011': 7, '11011101': 9, '01001110': 23, '01001111': 63, '01110011': 9, '00011111': 55, '00011110': 22, '00010000': 61, '11010111': 3, '01110000': 57, '01110001': 55, '01110010': 28, '01000110': 4, '00010010': 18, '00010001': 56, '00010101': 3, '00011001': 4, '00011010': 5, '11011110': 35, '00010110': 5, '01001010': 6, '00011101': 8, '11011010': 1, '01001001': 2, '11010010': 26, '01111111': 55, '00011011': 4, '01000101': 3, '01001011': 4, '01000011': 6, '01000000': 60, '01001101': 2, '01111110': 25, '11011111': 57, '01111001': 4, '01110110': 7}
The important qubits to look at are the input qubits. Remember that the qubits are represented from the right to left. So the input qubits are the 4 qubits at the right-most, where the first input qubit is the right-most qubit.
For example, in the result 01111101, the input qubits are the 4 qubits at the right-most:
For this result, the input qubits are 1101. However, because of the design of the Fourier Transform, the order of the qubits are actually reversed and should be 1011, which correspond to the decimal number 11.
Here is a python script shor_expt.py
that will make the output easier to read. It will output the count, together with the reversed order of the qubits and the decimal equivalent:
x={'01111010': 7, '11010000': 66, '11010011': 7, '00010111': 3, '01000010': 35, '11010110': 1, '11011011': 5, '11010001': 30, '01110101': 1, '01111101': 6, '01110111': 2, '01000001': 42, '11010101': 2, '11011001': 1, '00010011': 7, '11011101': 9, '01001110': 23, '01001111': 63, '01110011': 9, '00011111': 55, '00011110': 22, '00010000': 61, '11010111': 3, '01110000': 57, '01110001': 55, '01110010': 28, '01000110': 4, '00010010': 18, '00010001': 56, '00010101': 3, '00011001': 4, '00011010': 5, '11011110': 35, '00010110': 5, '01001010': 6, '00011101': 8, '11011010': 1, '01001001': 2, '11010010': 26, '01111111': 55, '00011011': 4, '01000101': 3, '01001011': 4, '01000011': 6, '01000000': 60, '01001101': 2, '01111110': 25, '11011111': 57, '01111001': 4, '01110110': 7} for k in x.keys(): h=k[4:] hrev = h[::-1] sum = 0 for i in range(0,4): sum += pow(2,3-i)*int(hrev[i]) print("%s %s %s %2d %2d" % ( k, h, hrev, sum, x[k]))
Running this script and sorting the result, we get:
$ python shor_expt.py|sort -r -k 5 11010000 0000 0000 0 66 01001111 1111 1111 15 63 00010000 0000 0000 0 61 01000000 0000 0000 0 60 11011111 1111 1111 15 57 01110000 0000 0000 0 57 00010001 0001 1000 8 56 01111111 1111 1111 15 55 01110001 0001 1000 8 55 00011111 1111 1111 15 55 01000001 0001 1000 8 42
The last column is the count and the column before that is the number which we will use later to compute for the period. We can see that the numbers 0, 15 and 8 occur with very high frequency. We can ignore zero as it will not give us the period. We can use 15 or 8.
Computing the Period
Now that we have a , we can use continued fraction expansion to compute for the period, which is a partial sum of the expansion. If we choose
, we have
which terminates. So the partial sum is also and the period is 16. Verifying if it’s a period, we have
which is indeed a period.
We then compute the inverse modulo using the formula
which will be satisfied if s=5 and . Finally, using
we can compute the message
:
which is our original message!
You can verify that if we use , then the period is
. The correspoding
. Using this value, the message is
which gives us the same value.
This completes our implementation of the Shor’s algorithm using QISKit. By implementing the modular exponentiation function as a mapping, we were able to do away with the complexities of a generic modular exponentiation function that will work for any . This allowed us to focus on how the Shor’s algorithm actually works to decrypt the message encoded using RSA.