Cambridge Quantum
Published in

Cambridge Quantum

Quantum-Proof Cryptography with IronBridge, TKET and Amazon Braket

Generating perfectly random numbers using cloud-based quantum computers

Why Quantum Computers Threaten Random Number Generation

The goal of a random number generator is to produce completely unbiased and private streams of random bits. Each bit should have a 50% chance of being a zero or a one, with an attacker having no ability to predict this. This is what makes them usable in cryptography.

How IronBridge Generates Keys Using Quantum Computers

IronBridge relies on the intrinsic randomness of quantum-mechanical systems. Unlike standard QRNG approaches, we treat the quantum computer as a black box and pass it circuits to execute. The output of the circuits is used to generate our entropy, as well as acting as a self-test to ensure the quantum computer is functioning correctly. This means we no longer have to place complete trust in the device.

Running IronBridge Circuits on Amazon Braket

To test the protocol on Amazon Braket, we ran four different three-qubit IronBridge circuits on an IonQ device. The circuits were composed of H, CX, S and Sdg gates. These are all supported by the Braket SDK. Therefore the only thing we needed to do before passing the circuit to the BraketBackend was a noise-aware placement of the qubits:

from pytket.routing import NoiseAwarePlacementNoiseAwarePlacement(backend.device()).place(circuit)
M = |<001> + <010> + <100> - <111>|
<001> = [P(000|001) + P(011|001)+ P(101|001) + P(110|001)] - [P(001|001) + P(010|001) + P(100|001) + P(111|001)]

Contact Us to Learn More and Trial the Technology

If you would like to trial our QRNG technology, get in touch with us. We are happy to share examples of our entropy for statistical testing or to discuss proof-of-concept and production deployments.

Tutorial: Using Amazon Braket with CQC TKET

CQC TKET is an advanced quantum compiler that can interface with a wide range of hardware, handling the full cycle of compilation, dispatch and results retrieval via a unified Python interface. The compilation steps can be controlled by the user to suit the nature of the quantum circuit being run. Each backend also defines a set of default compilation passes at different optimization levels, chosen to give good results in most cases. When device calibration data are available, these passes take care of placing the logical qubit on the physical device to minimize the overall noise and achieve the most accurate results possible.

  • trapped-ion gate-model devices (from IonQ);
  • superconducting gate-model devices (from Rigetti).

Getting Started

First, install the latest versions of pytket and pytket-braket:

pip install --upgrade pytket pytket-braket

Running Circuits

Let’s create a very simple circuit in pytket and then run it on the IonQ device through Braket.

from pytket import Circuitbell_circ = Circuit(2).H(0).CX(0,1)
from pytket.backends.braket import BraketBackendS3_BUCKET = "amazon-braket-test"
S3_FOLDER = "test-folder"
ionq_backend = BraketBackend(
job_handle = ionq_backend.process_circuit(bell_circ, n_shots=20)
result = ionq_backend.get_result(job_handle)
from pytket.circuit import Bitdef get_cbits(backend, circuit):
return [Bit(backend.device().nodes.index(q)) for q in circuit.qubits]
cbits = get_cbits(ionq_backend, bell_circ)
counts = result.get_counts(cbits=cbits)
Counter({(0, 0): 11, (1, 1): 9})

Diversion: Quantifying Noise

As a quick and light-hearted demonstration of the kind of experiment we can perform using TKET and Amazon Braket together, let’s have a competition between the two gate-model machines. We’ll construct an 11-qubit circuit that “secretly” implements the identity, optimize it for the two platforms using TKET, run it, and then see which comes closest to outputting the zero state.

include "";
qreg q[3];
u3(1.5*pi,0.0*pi,1.5*pi) q[0];
u3(0.5*pi,0.75*pi,1.25*pi) q[1];
u3(0.5*pi,0.0*pi,1.0*pi) q[2];
cx q[1],q[2];
u3(0.25*pi,0.25*pi,1.75*pi) q[1];
cx q[1],q[2];
u3(0.5*pi,0.0*pi,0.25*pi) q[1];
u3(3.5*pi,1.75*pi,0.0*pi) q[2];
cx q[2],q[0];
u3(0.5*pi,1.75*pi,0.0*pi) q[0];
cx q[1],q[0];
u1(0.25*pi) q[0];
cx q[2],q[0];
u1(0.25*pi) q[0];
cx q[1],q[0];
u3(1.5*pi,1.5*pi,1.75*pi) q[0];
u3(0.5*pi,0.75*pi,1.25*pi) q[1];
cx q[2],q[0];
u1(0.5*pi) q[0];
u3(0.5*pi,0.0*pi,0.5*pi) q[2];
cx q[1],q[2];
u3(0.25*pi,0.25*pi,1.75*pi) q[1];
cx q[1],q[2];
u3(0.5*pi,0.0*pi,0.25*pi) q[1];
u3(3.5*pi,0.25*pi,0.0*pi) q[2];
cx q[2],q[0];
u3(0.5*pi,1.75*pi,0.0*pi) q[0];
cx q[1],q[0];
u1(0.25*pi) q[0];
cx q[2],q[0];
u1(0.25*pi) q[0];
cx q[1],q[0];
u1(0.25*pi) q[0];
cx q[2],q[0];
import numpy as np
from pytket.qasm import circuit_from_qasm
c_id3 = circuit_from_qasm("q3_id.qasm")sv_backend = BraketBackend(local=True)c = c_id3.copy()
h = sv_backend.process_circuit(c)
r = sv_backend.get_result(h)
sv = r.get_state()
zero_sv = np.zeros(8, dtype=complex); zero_sv[0] = 1
assert np.allclose(sv/sv[0], zero_sv)
from pytket.circuit import OpTypec_id11 = Circuit(11)
qbs = [0, 1, 2]
for _ in range(5):
c_id11.add_circuit(c_id3, qbs)
for i in range(3):
qbs[i] += 2; qbs[i] %= 11
print(f"c_id11: depth = {c_id11.depth()}, CX count = {c_id11.n_gates_of_type(OpType.CX)}")
rigetti_backend = BraketBackend(
ionq_c = c_id11.copy()
ionq_backend.compile_circuit(ionq_c, optimisation_level=2)
rigetti_c = c_id11.copy()
rigetti_backend.compile_circuit(rigetti_c, optimisation_level=2)
ionq_h = ionq_backend.process_circuit(ionq_c, n_shots=8192)
rigetti_h = rigetti_backend.process_circuit(rigetti_c, n_shots=8192)
print(f"ionq   : depth = {ionq_c.depth()}, CX count = {ionq_c.n_gates_of_type(OpType.CX)}")
print(f"rigetti: depth = {rigetti_c.depth()}, CX count = {rigetti_c.n_gates_of_type(OpType.CX)}")ionq : depth = 228, CX count = 65
rigetti: depth = 280, CX count = 138
ionq_r = ionq_backend.get_result(ionq_h)
rigetti_r = rigetti_backend.get_result(rigetti_h)
def noise_level(result, cbits):
counts = result.get_counts(cbits=cbits)
n_ones, n_bits = 0, 0
for bits, count in counts.items():
n_ones += count * sum(bits)
n_bits += count * len(bits)
return n_ones / n_bits
ionq_cbits = get_cbits(ionq_backend, ionq_c)
rigetti_cbits = get_cbits(rigetti_backend, rigetti_c)
print("ionq : noise =", noise_level(ionq_r, ionq_cbits))
print("rigetti: noise =", noise_level(rigetti_r, rigetti_cbits))
ionq   : noise = 0.2552712180397727
rigetti: noise = 0.48872514204545453

Simulating Large Circuits

Amazon Braket also has a powerful tensor-network simulator, which can simulate very large circuits (up to about 50 qubits) as long as the entanglement connections are reasonably sparse.

c_id50 = Circuit(50)
qbs = [0, 1, 2]
for _ in range(25):
c_id50.add_circuit(c_id3, qbs)
for i in range(3):
qbs[i] += 3; qbs[i] %= 50
tensor_backend = BraketBackend(
tensor_c = c_id50.copy()
tensor_backend.compile_circuit(tensor_c, optimisation_level=2)
print(f"circuit depth = {tensor_c.depth()}, CX count = {tensor_c.n_gates_of_type(OpType.CX)}")
tensor_h = tensor_backend.process_circuit(tensor_c, n_shots=1)
tensor_r = tensor_backend.get_result(tensor_h)
result = tesnor_r.get_shots()[0]
assert all(x == 0 for x in result)

Get in Touch to Learn More

If you’d like to learn more about any of the topics in this article, such as TKET or our IronBridge QRNG, please contact us.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store