Code
qcr:2606.86064.1

Certified Randomness Generation with Amazon Braket

This Amazon Braket notebook demonstrates how to generate robust, high-quality randomness using a real quantum processing unit, a task for which quantum mechanics offers an advantage rooted in physics rather than algorithmic complexity. Unlike classical pseudorandom number generators, which are deterministic and in principle predictable, the outcomes of measuring suitably prepared quantum states are fundamentally random by the laws of quantum mechanics, making quantum hardware a natural source of certifiable entropy. The notebook walks through preparing quantum states whose measurements yield unbiased random bits, submitting the circuits to a Braket QPU, and collecting the measured bitstrings. It then discusses how to assess and post-process the raw output, accounting for device imperfections and bias, to distill robust randomness, and touches on the ideas behind certified or device-independent randomness, where the unpredictability can be verified rather than merely assumed. By running on actual quantum hardware through Amazon Braket and handling the practical realities of noisy measurements, it provides a concrete, applied example of quantum randomness generation, one of the most immediately practical uses of near-term quantum devices.
Qubit
Circuit-based
Uploaded 4 days ago
5
Views
GitHub582
Citing this entry? Use this QCR ID
Uploaded by
QL
QCR Librarian

Overview

amazon-braket/amazon-braket-examples
582262
In [ ]:
# --- Setup cell added by QCR (not part of the original tutorial) ---
# Source: amazon-braket/amazon-braket-examples @ 0c0818f315479aab9deebed7e7ed7533ac581923, Apache License 2.0.
# Installs the example's dependencies. If a later cell still reports a missing
# package, restart the runtime/kernel and run again from the top.
%pip install -q amazon-braket-sdk==1.117.3 matplotlib cvxpy

Robust randomness generation on quantum processing units

In [1]:
# Use Braket SDK Cost Tracking to estimate the cost to run this example
from braket.tracking import Tracker

t = Tracker().start()

Random numbers are a ubiquitous resource in computation and cryptography. For example, in security, random numbers are crucial to creating keys for encryption. Quantum random number generators (QRNGs), that make use of the inherent unpredictability in quantum physics, promise enhanced security compared to standard cryptographic pseudo-random number generators (CPRNGs) based on classical technologies.

In this notebook, we implement our own QRNG. Namely, we program two separate quantum processor units (QPUs) from different suppliers in Amazon Braket to supply two streams of weakly random bits. We then show how to generate physically secure randomness from these two weak sources by means of classical post-processing based on randomness extractors. The prerequisites for the tutorial are a basic understanding of quantum states, quantum measurements, and quantum channels. For a detailed explanation of these concepts we refer to the Amazon Braket notebook Simulating noise on Amazon Braket.

We believe that randomness generation is a practical application of nowadays available noisy and intermediate scale quantum (NISQ) technologies.

Table of contents

Amazon Braket

We start out with some general Amazon Braket imports, as well as some mathemtical tools needed.

In [2]:
# AWS imports: Import Braket SDK modules
import math

# import convex solver
import cvxpy as cp  # general math imports
import numpy as np
from scipy.fft import fft, ifft

from braket.aws import AwsDevice, AwsQuantumTask
from braket.circuits import Circuit
from braket.devices import Devices, LocalSimulator

# set up local simulator device
device = LocalSimulator()

# magic word for producing visualizations in notebook
%matplotlib inline
In [ ]:
# set up Rigetti quantum device
rigetti = AwsDevice(Devices.Rigetti.Cepheus1108Q)

# set up IonQ quantum device
ionq = AwsDevice(Devices.IonQ.ForteEnterprise1)

# set up IQM quantum device
iqm = AwsDevice(Devices.IQM.Garnet)

# simulator alternative: set up the on-demand simulator SV1
simulator = AwsDevice(Devices.Amazon.SV1)

Quantum circuit for randomness generation

Arguably the simplest way of generating a random bit on a quantum computer is as follows:

  • Prepare the basis state vector
  • Apply the Hadamard gate leading to the state vector
  • Measure in the computational basis .

By the laws of quantum physics, the pre-measurement probability distribution is then the uniformly distributed and leads to one random bit .

In the following, we discuss how above protocol is conceptually different from randomness obtained from classical sources and show in detail how it is implemented reliable even when the underlying quantum processing units employed are noisy. By the end of this tutorial, you will be able to create your own random bits from the quantum processing units available on Amazon Braket.

Quick implementation

The Hadmard gate based quantum circuit for generating one random bit can be repeated or run in parallel times, leading to a random bit string of length . The corresponding circuit is easily implemented in Amazon Braket:

In [4]:
# function for Hadamard cirquit
def hadamard_circuit(n_qubits):
    """Function to apply Hadamard gate on each qubit
    input: number of qubits
    """
    # instantiate circuit object
    circuit = Circuit()

    # apply series of Hadamard gates
    for i in range(n_qubits):
        circuit.h(i)

    return circuit


# define circuit
n_qubits = 5
state = hadamard_circuit(n_qubits)

# print circuit
print(state)
T  : │  0  │
      ┌───┐ 
q0 : ─┤ H ├─
      └───┘ 
      ┌───┐ 
q1 : ─┤ H ├─
      └───┘ 
      ┌───┐ 
q2 : ─┤ H ├─
      └───┘ 
      ┌───┐ 
q3 : ─┤ H ├─
      └───┘ 
      ┌───┐ 
q4 : ─┤ H ├─
      └───┘ 
T  : │  0  │

Let us run this Hadamard circuit with qubits in the local quantum simulator for shots:

(Note: We will work on actual QPUs towards the end of this tutorial.)

In [5]:
# run circuit
m_shots = 1
result = device.run(state, shots=m_shots).result()

# get measurement shots
counts = result.measurement_counts.keys()

# print counts
list_one = list(counts)[0]
array_one = np.array([list_one])
print("The output bit string is: ", array_one)
The output bit string is:  ['10010']

Critical assessment

The advantage of such quantum random number generators over implementations based on classical technologies is that the outcomes are intrinsically random. That is, according to the laws of quantum physics, the outcome of the measurement is not only hard to predict, but rather impossible to know before the measurement has taken place.

However, since current quantum processing units are noisy to a certain degree, there are at least three potential problems that need to be addressed:

  • First, the noise acting on all states and operations performed might lead to a systematic bias towards the probability of getting the measurement outcomes or , respectively.
  • Second, even if aforementioned noise is not biased towards certain measurement outcomes, the generated randomness is no longer solely based on intrinsically random quantum effects, but rather partly on the noise present.
  • Third, whereas by the laws of quantum physics a pure quantum state cannot be correlated to the outside world, any noisy acting on the system corresponds to information leaking to the environment. This is because no information is destroyed in quantum physics and hence, a malicious third party knowing about the noise occurring will be able to guess the generated bits (at least up to a certain degree).

When the noise model acting on the quantum processor units is characterized to some degree (e.g., by means of previous benchmarking), these shortcomings can be overcome by employing two independent quantum processor units, together with an appropriate classical post-processing. The latter is based on classical algorithms from the theory of pseudo-randomness, so-called two-source extractors. This is what we discuss next.

In the following, we refer a few times to the theory paper [1] that features formal cryptographic security definitions, together with mathematical proofs, as well as some statistical methods tailored to intermediate scale quantum devices. These pointers can be safely ignored when only interested in the implementation of our QRNG.

Interlude randomness extractors

Two-source extractors allow distillation of physically secure random bits from two independent weak sources of randomness whenever they are sufficiently unpredictable to start with. The relevant measure of unpredictability is thereby given by the min-entropy of the respective sources, defined for the probability distribution as

That is, the min-entropy exactly quantifies how well we can guess the value of the source, or in other words, how unpredictable the source is. For example, for -bit distributions we have , where corresponds to a deterministic distribution containing no randomness and to the perfectly random, uniform distribution.

A two-source extractor is a function such that for any two independent sources with min-entropy at least and , respectively, the output of length is close in variational distance to the perfectly random, uniform distribution of size :

So, two inpedendent sources that are only weakly random get condensed by these algorithms to one output that is (nearly) perfectly random! Importantly, the output becomes truly physically random with no computational assumptions introduced.

Quantum information

For our setting we need an extension of this concept, as a potentially malicious third party can collect quantum information about the weak source of randomness . The corresponding conditional min-entropy is defined as

where denotes the maximal probability allowed by quantum physics to guess the classical value by applying any measurements on the quantum information . We notice that even though does not have a closed form expression, it is efficiently computed by means of a semidefinite program (see the theory notes [1] for details). This is also how we will evaluate the conditional min-entropy quantity later on.

Accordingly, a quantum-proof two-source extractor is then function such that for any two independent sources with quantum conditional min-entropy at least and , respectively, we have for in quantum variational distance

where denotes the fully mixed qubit state. That is, the extractor should not only make the output bits perfectly random, but also decouple them from any outside correlations initially present - up to the security parameter .

For more details about these concepts, we refer to the theory notes [1] and references therein. All that is important to us here, is that there exist quantum-proof two-source extractors with good parameters. Next, we discuss one particular such construction that we subsequently implement in an efficient manner.

Extractor construction

In this paragraph, we provide an explicit construction of a quantum-proof two-source extractor that efficiently provides non-zero output for a wide range of sizes of the inputs and . Namely, we employ a Toeplitz matrices based construction originally discussed in [2]:

For the security parameter and inputs of size and , respectively, the function defined below is a quantum-proof two-source randomness extractor with output size

The function is explicitly given via the vector-matrix multiplication

featuring the Toeplitz matrix

The quantum-proof property of this construction, as well as its complexity, is explicitly disccused in the theory notes [1].

For our setting, we will have sources with linear min-entropy rates for . The Toeplitz construction then works whenever and we can compute the required input size for fixed output size as

A simple example shows that these numbers work well in practice, even for very small input sizes around .

Example

Let us set the security parameter to , say that we have min-entropy sources with linear rates and , and ask for fully random bits. According to the formulae above, together with weakly random bits can be condensed into fully random bits (up to the security parameter ).

Next, we give an efficient implementation of this Toeplitz based construction.

Implementation

The vector-matrix multiplication a priori has asymptotic complexity in big O-notation, which is prohibitive for larger input sizes . However, we discuss in the theory notes [1] that the operation is actually implemented in asymptotic complexity by first embedding the problem into circulant matrices and then making use of the Fast Fourier Transform (FFT). The corresponding code then performs well up to input sizes . The following example demonstrates this implementation:

In [6]:
# work with local simulator for testing Toeplitz construction
device = LocalSimulator()

# set security parameter
power = 8
eps = 10 ** (-power)
print(f"Security parameter: {eps}.")

# set number of output bits
m = 10
print(f"Desired output length: {m} bits.")

# set min-entropy rates for sources
k_1 = 0.8
print(f"Min-entropy rate of first source: {k_1}.")
k_2 = 0.8
print(f"Min-entropy rate of second source: {k_2}")

# required number of input bits (for each source)
n = math.floor((m - 1 - 2 * math.log2(eps)) / (k_1 + k_2 - 1))
print(f"Required length of each input source: {n} bits.")

# quantum circuit for generating weakly random bit string one
n1_qubits = 1
m1_shots = n
state1 = hadamard_circuit(n1_qubits)
result1 = device.run(state1, shots=m1_shots).result()
array_one = result1.measurements.reshape(1, m1_shots * n1_qubits)
# print(array_one)

# quantum circuit for generating weakly random bit string two
n2_qubits = 1
m2_shots = n
state2 = hadamard_circuit(n2_qubits)
result2 = device.run(state2, shots=m2_shots).result()
array_two = result2.measurements.reshape(1, m2_shots * n2_qubits)
# print(array_two)

###
# alternative for generating two bit strings when no quantum source is available:

# create first list of pseudo-random bits
# alternative when no quantum source is available
# list_one = []
# for number in range(n):
#    b = int(random.randint(0, 1))
#    list_one.append(b)
# array_one = np.array([list_one])

# create second list of pseudo-random bits
# list_two = []
# for number in range(n):
#    b = int(random.randint(0, 1))
#    list_two.append(b)
# array_two = np.array([list_two])
###

# computing output of Toeplitz extractor by vector-matrix multiplication
# via efficient Fast Fourier Transform (FFT) as discussed in [1]

# setting up arrays for FFT implementation of Toeplitz
array_two_under = np.array(array_two[0, 0 : n - m])[np.newaxis]
zero_vector = np.zeros((1, n + m - 3), dtype=int)
array_two_zeros = np.hstack((array_two_under, zero_vector))
array_two_over = array_two[0, n - m : n][np.newaxis]
array_one_merged = np.zeros((1, 2 * n - 3), dtype=int)
for i in range(m):
    array_one_merged[0, i] = array_one[0, m - 1 - i]
for j in range(n - m - 1):
    array_one_merged[0, n + m - 2 + j] = array_one[0, n - 2 - j]

# FFT multplication output of Toeplitz
output_fft = np.around(ifft(fft(array_one_merged) * fft(array_two_zeros)).real)
output_addition = output_fft[0, 0:m] + array_two_over
output_final = output_addition.astype(int) % 2
print(f"The {m} random output bits are:\n{output_final}.")
Security parameter: 1e-08.
Desired output length: 10 bits.
Min-entropy rate of first source: 0.8.
Min-entropy rate of second source: 0.8
Required length of each input source: 103 bits.
The 10 random output bits are:
[[1 0 1 0 0 1 1 1 0 1]].

As an alternative, we note that efficient implementations of other quantum-proof two-source extractors are discussed in [3].

Unpredictability of physical sources

Given above methods on randomness extraction, the next step is to give lower bounds on the min-entropy present in the output distributions generated from our -fold Hadamard circuit. For that, we need to model the noise present in the quantum processing units.

Generally, for any given quantum processing unit, the supplier typically publishes some type of noise specification with it. This includes both, the noise characterization of state preparation, as well as the read-out measurements. In case such specifications are not available, or if one wants to double check them, it is in principle possible to benchmark the device. We refer to the theory notes [1] for more details on this and just mention here that we do not need a full characterization of the device, but rather only conservative upper bounds on the noise strength. This then translates into lower bounds on the min-entropy present in the system.

For our case, since we are only applying single qubit gates for our Hadamard circuit, the noise is captured well by single qubit noise models. Moreover, for the state preparation step via Hadamard gates, the typical noise in quantum architectures is uniform, depolarizing noise of some strength . That is, all possible single qubit errors such as bit flip or phase flip errors are equally likely, leading to the effective evolution

mapping any input qubit state onto a linear combination of itself and the maximally mixed qubit state . Note that we now work with general mixed states in order to model classical statistical uncertainty coming from the noise model.

So effectively, before the measurement step, instead of the perfect pure state as defined by the vector , we have the mixed state

at hand.

Next, we note that instead of the ideal measurement device given by , the typical noisy measurement device is described by

with some bias towards reading-out the ground state over . The post-measurement probability distribution is then given as

instead of the perfectly uniform distribution . Note that the former distribution has non-maximal min-entropy

More generally, as measurement devices are the most sensitive element of quantum randomness generation, we discuss in the theory notes [1] methods for benchmarking them (even when only noisy state preparation is available).

Noise as leakage

It is, however, crucial to realize that is not yet the quantity relevant for secure quantum randomness generation. As information is never lost in quantum mechanics, all the noise carries information to the environment, where it can in principle be picked up by an attacker. That is, we need to estimate the conditional min-entropy of the post-measurement probability distribution given any complementary information that leaked into the environment [5].

This is worked out in detail in the theory notes [1] by means of so-called purifications of both, the noisy qubit state , as well as the noisy measurement device , leading to the additional qubit registers and , respectively. The relevant so-called classical-quantum state to consider then takes the form

with a classing part and the quantum parts depending on both noise parameters . The corresponding conditional min-entropy is

with the gap between the conditional and the unconditional term typically being strict.

We reiterate that the reason for mathematically introducing the purification registers and working with the conditional min-entropy, is to make sure that the output bits generated are random even conditioned on the knowledge of the noise. This ensures that the randomness created is from a purely quantum origin and is secure against any eavesdropping from a malicious third party.

We mention that a more detailed discussion of this point is given in the work [5].

Numerical evaluation

In the following, we use the noise parameters and for the quantum processing units and this immediately gives

The next step is to numerically evaluate the conditional min-entropy

This is done by means of a standard semidefinite program (sdp) solver as follows:

In [7]:
# fix noise parameters
lamb = 0.02
mu = 0.98

# purification of rho input state
rho = 0.5 * np.array([[1, 1 - lamb], [1 - lamb, 1]])
eigvals, eigvecs = np.linalg.eig(rho)

rho_vector = (
    math.sqrt(eigvals[0]) * np.kron(eigvecs[:, 0], eigvecs[:, 0])[np.newaxis]
    + math.sqrt(eigvals[1]) * np.kron(eigvecs[:, 1], eigvecs[:, 1])[np.newaxis]
)
rho_pure = np.kron(rho_vector, rho_vector.T)

# sigma state of noisy measurement device
sigma_vector = np.array([[math.sqrt(1 - mu), 0, 0, math.sqrt(mu)]])
sigma_pure = np.kron(sigma_vector, sigma_vector.T)

# omega state relevant for conditional min-entropy
rho_sigma = np.kron(rho_pure, sigma_pure)

id_2 = np.identity(2)
zero = np.array([[1, 0]])
one = np.array([[0, 1]])

zerozero = np.kron(np.kron(zero, id_2), np.kron(zero, id_2))
zeroone = np.kron(np.kron(zero, id_2), np.kron(one, id_2))
onezero = np.kron(np.kron(one, id_2), np.kron(zero, id_2))
oneone = np.kron(np.kron(one, id_2), np.kron(one, id_2))

omega_0 = (
    zerozero @ rho_sigma @ zerozero.T
    + zeroone @ rho_sigma @ zeroone.T
    + onezero @ rho_sigma @ onezero.T
)
omega_1 = oneone @ rho_sigma @ oneone.T

omega = []
omega.append(omega_0)
omega.append(omega_1)

# sdp solver
m = 4  # dimension of quantum side information states
c = 2  # number of classical measurement outcomes

sigma = cp.Variable((m, m), complex=True)  # complex variable
constraints = [sigma >> 0]  # positive semi-definite
constraints += [sigma >> omega[i] for i in range(c)]  # min-entropy constraints
obj = cp.Minimize(cp.real(cp.trace(sigma)))  # objective function

prob = cp.Problem(obj, constraints)  # set up sdp problem
prob.solve(solver=cp.SCS, verbose=True)  # solve sdp problem using splitting conic solver (SCS)
guess = prob.value
qmin_entropy = (-1) * math.log2(guess)
min_entropy = 1 - math.log2(2 - mu * (1 - lamb))

print("\033[1m" + "The coditional min-entropy is: ", qmin_entropy)
print("As a comparison, the unconditional min-entropy is: ", min_entropy)
===============================================================================
                                     CVXPY                                     
                                     v1.6.0                                    
===============================================================================
(CVXPY) Feb 07 04:31:36 PM: Your problem has 16 variables, 48 constraints, and 0 parameters.
(CVXPY) Feb 07 04:31:36 PM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Feb 07 04:31:36 PM: (If you need to solve this problem multiple times, but with different data, consider using parameters.)
(CVXPY) Feb 07 04:31:36 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
(CVXPY) Feb 07 04:31:36 PM: Your problem is compiled with the CPP canonicalization backend.
-------------------------------------------------------------------------------
                                  Compilation                                  
-------------------------------------------------------------------------------
(CVXPY) Feb 07 04:31:36 PM: Compiling problem (target solver=SCS).
(CVXPY) Feb 07 04:31:36 PM: Reduction chain: Complex2Real -> Dcp2Cone -> CvxAttr2Constr -> ConeMatrixStuffing -> SCS
(CVXPY) Feb 07 04:31:36 PM: Applying reduction Complex2Real
(CVXPY) Feb 07 04:31:36 PM: Applying reduction Dcp2Cone
(CVXPY) Feb 07 04:31:36 PM: Applying reduction CvxAttr2Constr
(CVXPY) Feb 07 04:31:36 PM: Applying reduction ConeMatrixStuffing
(CVXPY) Feb 07 04:31:36 PM: Applying reduction SCS
(CVXPY) Feb 07 04:31:36 PM: Finished problem compilation (took 1.280e-02 seconds).
-------------------------------------------------------------------------------
                                Numerical solver                               
-------------------------------------------------------------------------------
(CVXPY) Feb 07 04:31:36 PM: Invoking solver SCS  to obtain a solution.
------------------------------------------------------------------
	       SCS v3.2.5 - Splitting Conic Solver
	(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem:  variables n: 32, constraints m: 108
cones: 	  s: psd vars: 108, ssize: 3
settings: eps_abs: 1.0e-05, eps_rel: 1.0e-05, eps_infeas: 1.0e-07
	  alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
	  max_iters: 100000, normalize: 1, rho_x: 1.00e-06
	  acceleration_lookback: 10, acceleration_interval: 10
lin-sys:  sparse-direct-amd-qdldl
	  nnz(A): 168, nnz(P): 0
------------------------------------------------------------------
 iter | pri res | dua res |   gap   |   obj   |  scale  | time (s)
------------------------------------------------------------------
     0| 3.89e+00  1.00e+00  1.56e+01 -7.07e+00  1.00e-01  7.52e-04 
   250| 9.44e-05  2.51e-05  1.63e-05  6.07e-01  8.20e+00  2.74e-03 
   375| 2.45e-07  1.99e-05  1.38e-07  6.08e-01  2.59e+01  3.86e-03 
------------------------------------------------------------------
status:  solved
timings: total: 3.86e-03s = setup: 4.98e-04s + solve: 3.36e-03s
	 lin-sys: 4.77e-04s, cones: 2.51e-03s, accel: 2.19e-05s
------------------------------------------------------------------
objective = 0.607508
------------------------------------------------------------------
-------------------------------------------------------------------------------
                                    Summary                                    
-------------------------------------------------------------------------------
(CVXPY) Feb 07 04:31:36 PM: Problem status: optimal
(CVXPY) Feb 07 04:31:36 PM: Optimal value: 6.075e-01
(CVXPY) Feb 07 04:31:36 PM: Compilation took 1.280e-02 seconds
(CVXPY) Feb 07 04:31:36 PM: Solver (including time spent in interface) took 4.227e-03 seconds
The coditional min-entropy is:  0.7190235372097399
As a comparison, the unconditional min-entropy is:  0.9439714610772487

That is, for the chosen noise parameters and we find

By varying the noise parameter to other values, one also sees by inspection that the conditional min-entropy is monotone in both and . Importantly, this ensures that the outputted randomness will be safe to use whenever we put a conservative estimate on the noisy strength, even in the absence of an exact characterization of the underlying quantum processing units.

Finally, whenever we run our Hadamard circuit times or on qubits in parallel, the overall conditional min-entropy is just given by

This reason is that we only consider single qubit product noise together with the min-entropy being additive on product states. This then leads to the promised linear min-entropy rates for going into the Toeplitz two-source extractor.

As an added bonus, the Toeplitz two-source extractor used has the so-called strong property. That is, even conditioned on the knowledge of one input string of weakly random bits, the output bits are still fully random. We refer to the technical notes [1] for discussion, a consequence being that if one provider builds in some backdoors in the provided unit, the random generation scheme is still not broken unless the two providers come together to cooperate.

Putting everything together

Now that we have determined the conditional min-entropy of our physical sources and have an efficient quantum-proof two-source extractor in place, all that remains is to put the two pieces together.

  1. First, we specify how many random bits we want to generate, the desired security parameter, and the conditional min-entropy of our weak sources of randomness from the quantum processing units:
In [8]:
# set security parameter
power = 8
eps = 10 ** (-power)
print(f"Security parameter: {eps}.")

# set number of output bits
m = 10
print(f"Desired output length: {m} bits.")

# set min-entropy rates for sources - qmin_entropy from above
k_one = qmin_entropy
print(f"Min-entropy rate of first source: {k_1}.")
k_two = qmin_entropy
print(f"Min-entropy rate of second source: {k_2}.")

# required number of input bits (for each source)
n = math.floor((m - 1 - 2 * math.log2(eps)) / (k_one + k_two - 1))
print(f"Required length of each input source: {n} bits.")
Security parameter: 1e-08.
Desired output length: 10 bits.
Min-entropy rate of first source: 0.8.
Min-entropy rate of second source: 0.8.
Required length of each input source: 141 bits.
  1. At the beginning of the notebook, we loaded two separate QPUs as available in Amazon Braket. We now run on the respective QPUs the Hadamard circuit followed by measurements in the computational basis:

    (Note: If preferred, one can alternatively use the pre-loaded on-demand simulator SV1. In this case, please comment out the QPU code in the cell below and instead uncomment the provided SV1 code.)

In [ ]:
# Use on-demand simulator SV1

# quantum circuit for generating weakly random bit string one (simulate Rigetti source)
n1_q = 1  # alternatively run multiple qubits in parallel
m1_s = int(math.ceil(n / n1_q))
state1 = hadamard_circuit(n1_q)
task1 = simulator.run(state1, shots=m1_s)
result1 = task1.result()
rigetti_task_id = task1.id
array_rigetti = result1.measurements.reshape(1, m1_s * n1_q)
print("The first raw bit string is: ", array_one)

# quantum circuit for generating weakly random bit string two (simulate IonQ source)
n2_q = 1  # alternatively run multiple qubits in parallel
m2_s = int(math.ceil(n / n2_q))
state2 = hadamard_circuit(n2_q)
task2 = simulator.run(state2, shots=m2_s)
result2 = task2.result()
ionq_task_id = task2.id
array_ionq = result2.measurements.reshape(1, m2_s * n2_q)
print("The second raw bit string is: ", array_two)

###

# [Uncomment the following lines to use the Rigetti and IonQ devices. Be aware that a usage cost will incur when you submit the circuit.]
## UNCOMMENT_TO_RUN
## Rigetti: quantum circuit for generating weakly random bit string one
# n1_q = (
#     1  # alternatively use more than one qubit (attention: 32 max + lower bounds on number of shots)
# )
# m1_s = int(math.ceil(n / n1_q))
# state_rigetti = hadamard_circuit(n1_q)
# rigetti_task = rigetti.run(state_rigetti, shots=m1_s, poll_timeout_seconds=5 * 24 * 60 * 60)

# rigetti_task_id = rigetti_task.id
# rigetti_status = rigetti_task.state()
# print("Status of Rigetti task:", rigetti_status)

## IonQ: quantum circuit for generating weakly random bit string two
# n2_q = (
#     1  # alternatively use more than one qubit (attention: 11 max + lower bounds on number of shots)
# )
# m2_s = int(math.ceil(n / n2_q))
# state_ionq = hadamard_circuit(n2_q)
# ionq_task = ionq.run(state_ionq, shots=m2_s, poll_timeout_seconds=5 * 24 * 60 * 60)

# ionq_task_id = ionq_task.id
# ionq_status = ionq_task.state()
# print("Status of IonQ task:", ionq_status)
The first raw bit string is:  [[1 1 0 1 1 0 1 1 1 0 0 0 1 0 0 1 1 1 0 1 0 1 0 1 1 1 1 1 0 0 1 0 1 0 1 1
  1 0 0 0 1 1 0 1 1 1 1 0 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 0 0 1 0 1 0 0 0 1
  1 1 0 1 1 1 0 0 1 1 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 0]]
The second raw bit string is:  [[0 0 0 1 0 1 1 1 0 0 0 1 1 1 0 1 0 1 0 0 0 0 1 1 1 1 1 1 1 0 0 1 1 1 0 1
  1 0 0 0 1 1 0 1 1 1 0 0 1 1 1 1 1 0 1 0 1 1 0 0 1 0 1 1 0 1 1 0 0 1 1 1
  1 0 0 1 0 0 1 1 0 0 0 0 1 1 1 1 0 0 1 1 0 1 0 1 0 0 1 1 1 1 1]]
  1. The quantum tasks have now been sent to the respective QPUs and we can recover the results at any point in time once the status is completed:

    (Note: In case you opted to use the on-demand simulator SV1 instead of the QPUs, please do not run the following cell.)

In [10]:
# recover Rigetti quantum task
task_load_rigetti = AwsQuantumTask(arn=rigetti_task_id)

# print status
status_rigetti = task_load_rigetti.state()
print("Status of Rigetti task:", status_rigetti)
# wait for hybrid job to complete
# terminal_states = ['COMPLETED', 'FAILED', 'CANCELLED']
if status_rigetti == "COMPLETED":
    # get results
    rigetti_results = task_load_rigetti.result()

    # array
    array_rigetti = rigetti_results.measurements.reshape(1, m1_s * n1_q)
    print("The first raw bit string is: ", array_rigetti)

elif status_rigetti in ["FAILED", "CANCELLED"]:
    # print terminal message
    print("Your Rigetti quantum task is in terminal status, but has not completed.")

else:
    # print current status
    print(
        "Sorry, your Rigetti quantum task is still being processed and has not been finalized yet.",
    )

# recover IonQ task
task_load_ionq = AwsQuantumTask(arn=ionq_task_id)

# print status
status_ionq = task_load_ionq.state()
print("Status of IonQ quantum task:", status_ionq)
# wait for hybrid job to complete
# terminal_states = ['COMPLETED', 'FAILED', 'CANCELLED']
if status_ionq == "COMPLETED":
    # get results
    ionq_results = task_load_ionq.result()

    # array
    # print(m1_shots,n1_qubits)
    # print(m2_shots,n2_qubits)
    # print(ionq_results.measurements)
    array_ionq = ionq_results.measurements.reshape(1, m2_s * n2_q)
    print("The second raw bit string is: ", array_ionq)

elif status_ionq in ["FAILED", "CANCELLED"]:
    # print terminal message
    print("Your IonQ quantum task is in terminal status, but has not completed.")

else:
    # print current status
    print("Sorry, your IonQ quantum task is still being processed and has not been finalized yet.")
Status of Rigetti task: COMPLETED
The first raw bit string is:  [[0 0 0 0 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 0 0 1 0 1 1 1 0 0 0 1 1 1
  1 0 1 0 1 1 0 0 0 1 1 0 1 0 1 1 1 1 1 0 1 0 1 1 0 1 1 0 0 1 0 0 0 0 1 0
  0 1 0 1 1 0 0 0 1 1 1 1 1 1 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0
  0 0 0 0 1 1 1 0 1 1 1 0 1 0 1 1 1 1 1 0 1 0 0 0 1 1 1 0 1 1 1 1 0]]
Status of IonQ quantum task: COMPLETED
The second raw bit string is:  [[0 1 1 1 0 0 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 0 0 1 0 1 1 1 1 1 0 1 0 1 1 0
  1 0 0 0 0 1 0 1 1 0 0 0 1 1 1 1 1 0 1 1 0 0 0 1 1 1 1 0 0 0 1 1 0 0 1 0
  0 0 0 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0 1 1 1 1 1 1 0 0 1 0 1 1 0 1 0 1
  1 1 1 1 1 1 0 0 1 0 1 0 1 0 1 0 0 1 1 1 0 1 0 0 0 1 0 1 1 0 0 1 1]]
  1. We run the Toeplitz two-source extractor on the two sequences of raw random input bits:
In [11]:
# setting up arrays for fft implementation of Toeplitz
if status_ionq == "COMPLETED":
    array_two_under = np.array(array_ionq[0, 0 : n - m])[np.newaxis]
    zero_vector = np.zeros((1, n + m - 3), dtype=int)
    array_two_zeros = np.hstack((array_two_under, zero_vector))
    array_two_over = array_ionq[0, n - m : n][np.newaxis]
    array_one_merged = np.zeros((1, 2 * n - 3), dtype=int)
    if status_rigetti == "COMPLETED":
        for i in range(m):
            array_one_merged[0, i] = array_rigetti[0, m - 1 - i]
        for j in range(n - m - 1):
            array_one_merged[0, n + m - 2 + j] = array_rigetti[0, n - 2 - j]

        # fft multplication output of Toeplitz
        output_fft = np.around(ifft(fft(array_one_merged) * fft(array_two_zeros)).real)
        output_addition = output_fft[0, 0:m] + array_two_over
        output_final = output_addition.astype(int) % 2
        print(f"The {m} random output bits are:\n{output_final}.")
    else:
        print(f"Your Rigetti quantum task is in {status_rigetti} state.")
else:
    print(f"Your IonQ quantum task is in {status_ionq} state.")
The 10 random output bits are:
[[1 0 0 1 0 1 1 1 1 0]].
  1. That is it, above bits are random up to the chosen security parameter!

Beyond current implementation

Different quantum processing units are potentially governed by different noise models. Correspondingly, this will lead to different conditional min-entropy rates for the raw sources of randomness. In this notebook and following the theory notes [1], we can change the noise model for the numerical evaluation of the min-entropy (with all other parts remaining the same).

From a cryptographic viewpoint, the quantum hardware providers as well as Amazon Braket have to be trusted in order to end up with secure random numbers. More broadly, we note that there are more intricate ways of generating randomness from quantum sources than presented here, in particular for the use case when one is not ready to trust the underlying quantum hardware because of potential backdoors inbuilt. Such a so-called (semi) device-independent scheme is for example given in [3], with a corresponding implementation in Amazon Braket [7]. For a general in-depth discussion of QRNGs, we refer to the review article [4], as well as the extensive security report [6].

Literature

[1] M. Berta and F. Brandão, Robust randomness generation on quantum computers, available online.

[2] M. Hayashi and T. Tsurumaru, More Efficient Privacy Amplification With Less Random Seeds via Dual Universal Hash Function, IEEE Transactions on Information Theory, 10.1109/TIT.2016.2526018.

[3] C. Foreman, S. Wright, A. Edgington, M. Berta, F. Curchod, Practical randomness and privacy amplification, arXiv:2009.06551.

[4] M. Herrero-Collantes and J.-C. Garcia-Escartin, Quantum random number generators, Review of Modern Physics, 10.1103/RevModPhys.89.015004.

[5] D. Frauchiger, R. Renner, M. Troyer, True randomness from realistic quantum devices, arXiv:1311.4547.

[6] M. Piani, M. Mosca, B. Neill, Quantum random-number generators: practical considerations and use cases, evolutionQ.

[7] Quantum-Proof Cryptography with IronBridge, TKET and Amazon Braket, A. Edgington, C. Foreman, D. Jones, Cambridge Quantum Computing.

In [12]:
print("Quantum Task Summary")
print(t.quantum_tasks_statistics())
print(
    "Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2).",
)
print(
    f"Estimated cost to run this example: {t.qpu_tasks_cost() + t.simulator_tasks_cost():.2f} USD",
)
Quantum Task Summary
{<_Amazon.SV1: 'arn:aws:braket:::device/quantum-simulator/amazon/sv1'>: {'shots': 282, 'tasks': {'COMPLETED': 2}, 'execution_duration': datetime.timedelta(microseconds=4000), 'billed_execution_duration': datetime.timedelta(seconds=6)}}
Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2).
Estimated cost to run this example: 0.01 USD

Join the Discussion

Comments (0)

No comments yet. Be the first to share your thoughts!

Indexed by QCR Librarian

This entry was created automatically from publicly available records. QCR links to public sources and only stores repository content where the license permits redistribution.

Versions

v1 Latest
Jun 15, 2026
qcr:2606.86064.1

Cite all versions? Use the base QCR ID to always reference the latest version of this entry.

Tools used

Amazon Braket SDK

Keywords

randomness
entropy
braket
qpu
sampling

You may also like5