- Quantum random numbers
- Shot noise
- EMCCD camera
- Min entropy
- NIST randomness tests
- Cryptography
Every time a bank issues a session key, a federated learning server masks a client update, or a privacy system adds noise to a query, it needs numbers nobody could have guessed. Most of the time those numbers come from an algorithm, and an algorithm, however clever, is deterministic. Know its seed and you know everything it will ever say.
At the Indian Institute of Space Science and Technology in Thiruvananthapuram, physicists Mohamed Armoon Shaliq and Ashok Kumar went looking for randomness somewhere an attacker cannot reach, the photon by photon jitter of laser light as it lands on a camera sensor. Their preprint on arXiv reports random bits at an instantaneous rate of 5.92 gigabits per second, with no software randomness extractor in the loop, from a single scientific camera.
Key points
- The generator images two equal laser beams on an electron multiplying CCD and treats each pixel’s photon shot noise as an independent entropy source.
- Subtracting consecutive frames removes the static beam profile, and background frames remove electronic offsets, leaving what the authors verify as pure quantum shot noise.
- Keeping the three least significant bits of each pixel gives 2.025 million bits per six frame acquisition and a measured min entropy of 0.9966 per bit.
- The instantaneous rate is 5.92 Gbps, but the camera’s serial readout limits sustained output to 7.5 Mbps, a gap of roughly eight hundred times.
- Output passes NIST SP 800-22 and Diehard. Our own analysis finds that reusing frames across pairs creates exact dependencies between bits that those tests cannot detect.
Why randomness is harder than it looks
Computers are built to be predictable, which makes them poor sources of surprise. Pseudo random number generators stretch a short seed into a long stream that looks random, and for simulations and model training that is usually exactly what you want. The trouble starts when the stream protects something. If an attacker learns or guesses the seed, the whole sequence falls, and the paper points to this seed vulnerability as a practical security risk.
True random number generators sample a physical process instead. Thermal noise in a resistor is one classic option, but the authors make a sharper point. Thermal noise is chaotic rather than fundamentally unpredictable, since in principle it follows classical physics. Quantum processes are different. The moment a photon is detected, or not, is not determined by any hidden variable an adversary could model.
Quantum random number generators have a long history. Early designs used radioactive decay, which is awkward and slow. Optical designs took over because light is fast and plays well with fiber networks. Jennewein and colleagues built a compact generator in 2000 around single photons choosing a path at a beam splitter. Later designs moved to attenuated lasers, photon arrival times, laser phase noise and the vacuum fluctuations measured by homodyne detection, as in the 2010 work by Gabriel and colleagues in Nature Photonics. A 2014 study by Sanguinetti and colleagues even generated quantum random numbers with the camera of a mobile phone.
Most of these share a feature. They measure one signal over time. That makes them sensitive to timing jitter, and many rely on a randomness extractor, a cryptographic post processing step that discards part of the raw output to remove bias. As Ma and colleagues showed in 2013, that step can cost a lot of throughput.
Randomness spread across space, not time
The idea in this paper is to stop measuring one detector many times and start measuring many detectors at once. A camera sensor is a grid of hundreds of thousands of photon counters. If each one sees independent quantum noise, every frame delivers a large block of random values in parallel.
The physics of a coherent state
An ideal single mode laser emits light in a coherent state, the quantum state closest to a classical wave. Written in the photon number basis it is
where \(|\alpha|^2\) is the mean photon number. The probability of detecting exactly \(n\) photons is Poissonian.
That variance equal to the mean is shot noise. It is not a flaw in the laser or the detector. It is a property of the quantum state itself, and it is the entropy source here. Each pixel receives a slightly different random number of photons every exposure, and no measurement of the previous frame helps predict the next.
The optical bench
The setup is compact. A titanium sapphire laser at 795 nanometers is pulsed to one microsecond by an acousto optic modulator. A half wave plate and a polarizing beam splitter divide each pulse into two beams of equal power, which land side by side on one EMCCD camera. Using two balanced beams on one sensor doubles throughput and also sets up the noise checks described below.
Timing matters. The camera runs in kinetic mode, where the 512 by 512 active area plus a 512 by 512 storage region is split into six frames of 170 by 512 pixels. Each 10 microsecond exposure is triggered so that the one microsecond pulse arrives six microseconds in. The charge then shifts down at 300 nanoseconds per row, which takes 51 microseconds for 170 rows, giving 57 microseconds between frames. Six frames stack up on the chip before being read out together.
Stripping away everything classical
A raw frame is mostly the smooth Gaussian profile of the beam, which is entirely predictable. Subtracting two consecutive frames cancels that static profile and leaves only the fluctuation between them. To remove electronic noise as well, the authors record a background frame without light right after each signal frame and subtract it too. What remains, they argue, is the spatial quantum noise.
The generator never tries to predict the beam. It throws the beam away by subtraction and keeps only the frame to frame jitter, which for a coherent state is set by quantum statistics rather than by anything in the laser or the lab.
Proving the noise is quantum
Any physical generator has to answer a skeptical question. How do you know the noise you are digitizing is quantum and not some classical artifact, such as readout noise, pixel cross talk or a slow drift? The paper uses four checks.
Cross correlation between the two beams
If the two beams shared a classical noise source, their fluctuation images would be correlated. The authors compute a normalized spatial cross correlation, scanning a 100 by 100 pixel patch of the right beam across a 150 by 150 pixel patch of the left.
The resulting map shows no peak. Running the same calculation within a single beam gives an autocorrelation that stays randomly scattered near zero across 100 pixels of spatial shift. So pixels are not leaking into their neighbors, and the two beams are not sharing noise.
The noise ratio test
The sharper test is whether the variance matches shot noise exactly. The authors define a noise ratio, the variance of the difference between the two beams’ frame to frame fluctuations divided by their total mean photon count.
For pure Poisson noise this ratio should sit at one. Here is where it gets interesting. The authors then bin pixels into super pixels up to 20 by 20. Uncorrelated noise keeps the ratio at one no matter how pixels are grouped, while correlated electronic noise such as clock induced charge would make it drift. Across 100 images the measured ratio stays close to one at every bin size.
Signal to background
Finally, total noise is split into a shot noise part and a measured background part, \(\sigma_{\text{total}}^2 = \sigma_{\text{shot}}^2 + \sigma_{\text{bg}}^2\). The shot noise to background ratio is about 29 dB, which means classical noise contributes roughly one part in eight hundred of the variance. The authors also report testing how the method breaks down as classical noise grows and shot noise shrinks.
Such quantum noise serves as a high entropy source, enabling an instantaneous random bit generation rate of 5.92 Gbps without algorithmic randomness extraction.Shaliq and Kumar, arXiv:2607.26486
From fluctuations to bits
Each six frame acquisition yields every possible frame pair from the six frames, fifteen pairs, and each pair gives a fluctuation image for each beam. That makes 30 fluctuation images per acquisition. The absolute value of each pixel is stored as a 16 bit unsigned integer, matching the camera’s analog to digital converter.
Which bits are random? The high order bits of a pixel value are not, since they mostly reflect how bright that part of the beam is. The low order bits are a different story. When the spread of a distribution is much wider than one step of the digitizer, the lowest bits become nearly equally likely to be zero or one. The authors quantify this with min entropy.
A perfectly uniform bit has a min entropy of one. The measured value stays close to one up to about six bits per pixel and then drops as higher bits are included. The authors take a cautious three least significant bits.
The arithmetic is simple. A 150 by 150 pixel region times 30 images times 3 bits gives 2.025 million bits per acquisition, with a min entropy of 0.9966 per bit. The six frames take 342 microseconds to acquire, so the instantaneous rate is
Using the full 170 by 512 pixel frame would raise that to a projected 11.45 Gbps. Scaled up to 2,400 acquisitions, a total of 4.86 billion bits, the min entropy is 0.998890.
The number that matters for deployment
This is not a small distinction. The 5.92 Gbps figure describes how fast the physics produces entropy on the chip. It is not how fast bits leave the device. The camera reads pixels serially through a horizontal amplifier running at 1 MHz, a low noise setting chosen to protect the fluctuations. Digitizing one acquisition takes about 270 milliseconds, so the sustained rate is about 7.5 Mbps.
| Quantity | Value | What limits it |
|---|---|---|
| Bits per six frame acquisition | 2.025 × 106 | ROI size, 30 images, 3 bits per pixel |
| Acquisition time | 342 µs | Exposure and row transfer timing |
| Instantaneous rate | 5.92 Gbps | Entropy produced on the sensor |
| Projected rate, full frame | 11.45 Gbps | Using all 170 × 512 pixels |
| Readout time per acquisition | About 270 ms | 1 MHz serial horizontal readout |
| Sustained rate | 7.5 Mbps | Camera electronics, not the quantum source |
| Min entropy per bit, 2,400 acquisitions | 0.998890 | Near uniform low order bits |
All values from the preprint. The sustained rate is what an application would actually receive today.
The authors say this plainly, and they note the camera could digitize at up to 20 MHz, which could push sustained output into the hundreds of megabits per second at the cost of higher read noise. They also used the camera’s low noise mode rather than its electron multiplying gain, because the laser pulses were bright enough not to need it. Readers comparing this generator with commercial devices should compare against 7.5 Mbps, not 5.92 Gbps.
Statistical testing
The raw output, with no post processing, was split into 400 sequences of one million bits each and run through NIST SP 800-22, the standard battery for cryptographic randomness. A sequence passes a test at a p value of at least 0.01, at least 97.5 percent of sequences must pass, and the p values must be uniform with a global p value of at least 0.0001. All tests passed. The bitstream also passed the Marsaglia Diehard battery, using the convention that a p value between 0.01 and 0.99 counts as a pass. A Pearson autocorrelation over the first 100 lags of a hundred million bit sample stayed near 10−4.
These results are good. They are also the kind of evidence that has a known ceiling, which brings us to our own analysis.
A dependency the tests cannot see
While building the simulation that accompanies this article, we checked one design choice closely. The 30 fluctuation images come from 15 frame pairs drawn from only six frames, so most pairs share a frame with other pairs. Raw values from pairs that share a frame are strongly correlated. In our simulation, the fluctuation images from frames zero and one and from frames zero and two had a correlation of about 0.5. Their least significant bits, however, showed essentially no pairwise correlation, which is consistent with the paper’s clean statistics.
The deeper issue is a three way relation. Camera counts are integers, and on integers
Taking an absolute value does not change parity, so the lowest bit of the third image is exactly the exclusive or of the other two. In our simulation with integer counts, the relation held in 100 percent of pixels. Of the 15 pair parities at any pixel for any one beam, only five are independent. The other ten are determined by them.
Why would NIST and Diehard miss this? The dependent bits sit in different images, which end up tens of thousands of positions apart in the concatenated stream, well beyond the 100 lag autocorrelation check. Each pair of those bits is uncorrelated, and only a combination of three reveals the dependency. General purpose test batteries are not designed to find such specific structure. Higher order bits are constrained in a more complicated way through carries.
Two caveats apply. Our analysis follows the processing as the paper describes it, and the authors may handle pairing in a way the text does not spell out. And the effect does not make the output useless. It means the true entropy per output bit is lower than the per bit min entropy estimate suggests. A conservative fix is to use only disjoint pairs, frames zero and one, two and three, four and five. That keeps 6 of the 30 images and gives about 1.18 Gbps instantaneous, still fast. Another is to keep all pairs but pass the output through a seeded extractor sized to an entropy estimate that accounts for the shared frames.
Passing NIST SP 800-22 shows a stream has no obvious statistical defects. It does not prove each bit is independent. The combinatorial frame pairing multiplies throughput by reusing frames, and for bit zero that reuse creates exact dependencies that an attacker who knows the design could exploit.
We also hit a cautionary bug of our own. An early version of our simulation stored camera values in 32 bit floats. Near two hundred thousand counts those floats sit on a grid of one sixty fourth, so exact half values are common, and the default round half to even rule nudged the lowest bit toward zero. The monobit test failed. It is a small reminder that least significant bit extraction is sensitive to every deterministic step between photon and bit.
Where this sits among quantum random number generators
| Approach | Example in the paper’s references | Main trade off |
|---|---|---|
| Single photon at a beam splitter | Jennewein et al., 2000 | Clean principle, low rate, detector bias |
| Photon arrival time | Wayne et al., 2009 | Sensitive to timing jitter |
| Vacuum or shot noise with homodyne detection | Gabriel et al., 2010. Shen et al., 2010 | High bandwidth, usually needs extraction |
| Laser phase noise | Guo et al., 2010 | Fast, depends on laser characterization |
| Consumer camera sensor | Sanguinetti et al., 2014 | Cheap and portable, lower quality signal |
| Source device independent protocols | Marangon et al., 2017. Cheng et al., 2024 | Stronger security guarantees, more complex |
| Spatial shot noise on an EMCCD, this work | Shaliq and Kumar, 2026 | Massively parallel and extractor free, readout bound |
Qualitative comparison drawn from the preprint’s reference list. Rates of the other systems are not reported in this paper, so none are quoted here.
The authors see their work as complementary to source device independent designs, which certify randomness under weaker trust assumptions, and suggest combining parallel spatial acquisition with such a security framework as future work. That combination would address exactly the kind of structural question raised above, because a source independent protocol bounds entropy from physics rather than from how random the output looks.
The work also builds on earlier research from Ashok Kumar with Alberto Marino on spatial quantum correlations and squeezing measured with EMCCD cameras, including a 2019 Physical Review A paper on characterizing twin beams with exactly this kind of detector. That background shows in the careful noise characterization.
Why a machine learning site cares about random numbers
For ordinary model training, a good pseudo random generator is the right tool. You want reproducible weight initialization, dropout masks and data shuffles, and a quantum source would make experiments harder to repeat. But several parts of modern AI depend on randomness that must be unpredictable, not merely well distributed.
Differential privacy adds calibrated noise to protect individuals in a dataset, and that protection is only as good as the noise is unpredictable. Secure aggregation in federated learning, a topic we covered in our review of federated learning for edge cyberattack detection, relies on random masks and keys so the server never sees individual updates. Watermarking schemes for language model output, discussed in our piece on how distillation can forge and erase LLM watermarks, depend on secret random keys. And authentication for drones and other autonomous systems, surveyed in our review of Internet of Drones protocols, needs fresh randomness for every session.
There is also a quantum computing connection. Quantum machine learning experiments, such as the hybrid circuits in the quantum gated sonar detector or the entanglement classifiers in our article on neural networks that classify entanglement, run on hardware whose outputs are themselves samples of quantum randomness. Understanding how to verify that randomness, and how easily classical artifacts slip in, is part of the same skill set.
Honest limitations
The sustained rate of 7.5 Mbps is set by the camera readout, and raising it means accepting more read noise. The headline 5.92 Gbps is an on chip figure.
The system needs a scientific EMCCD with deep thermoelectric cooling and a stabilized pulsed laser. The authors list readout speed, cooling and careful detector noise characterization as the main limitations. This is a laboratory instrument, not yet a chip.
Statistical test suites show absence of detectable defects, not unpredictability. The min entropy figures are empirical estimates from the output distribution, not a worst case bound conditioned on what an adversary might know about classical noise, which here is small at a 29 dB shot noise to background ratio but not zero.
As shown above, reusing frames across 15 pairs creates exact parity relations in the lowest bit that general test batteries do not detect, so the effective entropy of the full stream is lower than the per bit estimate.
Finally, this is a preprint, now in its second version on arXiv, and has not yet been peer reviewed.
PyTorch simulation of the full pipeline
The code below reproduces every step of the method on a simulated camera, so readers can run it without optics. It models two coherent state beams with Poisson photon counts, background frames and integer readout, then performs frame and background subtraction, cross correlation, the noise ratio test under binning, 16 bit conversion, least significant bit extraction, min entropy, autocorrelation and three NIST style tests. It also checks the rate arithmetic from the paper and demonstrates the shared frame parity relation described above, along with the disjoint pair alternative.
# qrng.py
# Spatial shot noise QRNG pipeline, a PyTorch simulation and analysis toolkit
# written from the method in Shaliq and Kumar, arXiv:2607.26486 (2026).
# Real data would come from EMCCD kinetic frames. Here a Poisson camera model
# stands in for the hardware so every step can be run and tested.
from __future__ import annotations
import itertools
import math
import torch
import torch.nn.functional as F
# ------------------------------------------------------------------
# 1. Camera model, two coherent state beams on one sensor
# ------------------------------------------------------------------
class TwinBeamEMCCD:
"""Simulates the kinetic mode acquisition: n_frames frames of H x W pixels,
each holding a left and right Gaussian spot of equal mean photon number.
Photon counts are Poisson (coherent state statistics, Eq. 2), and the
readout adds Gaussian background noise plus a static offset."""
def __init__(self, H=170, W=512, peak_photons=200_000.0, waist=45.0,
read_noise=2.0, offset=100.0, seed=0):
self.H, self.W = H, W
self.gen = torch.Generator().manual_seed(seed)
# float64 on purpose. In float32, values near 2e5 sit on a 1/64 grid, so
# exact .5 values are common and round half to even biases the LSB.
y = torch.arange(H, dtype=torch.float64)[:, None]
x = torch.arange(W, dtype=torch.float64)[None, :]
cy, cl, cr = H / 2, W * 0.22, W * 0.78
spot = lambda cx: torch.exp(-((x - cx) ** 2 + (y - cy) ** 2) / (2 * waist ** 2))
self.mean_img = peak_photons * (spot(cl) + spot(cr)) # static classical profile
self.read_noise, self.offset = read_noise, offset
self.left_c, self.right_c, self.cy = int(cl), int(cr), int(cy)
def frames(self, n_frames=6, light=True):
lam = self.mean_img.expand(n_frames, -1, -1) if light else torch.zeros(n_frames, self.H, self.W, dtype=torch.float64)
counts = torch.poisson(lam, generator=self.gen)
noise = self.read_noise * torch.randn(counts.shape, generator=self.gen, dtype=torch.float64)
return (counts + noise + self.offset).round() # ADC outputs integers
def crop(img, cx, cy, size):
h = size // 2
return img[..., cy - h:cy - h + size, cx - h:cx - h + size]
# ------------------------------------------------------------------
# 2. Extract spatial quantum noise by frame subtraction
# ------------------------------------------------------------------
def fluctuation(frames, bg_frames, i, j):
"""delta I = (F_i - B_i) - (F_j - B_j). Subtracting consecutive frames removes
the static DC profile, subtracting background frames removes electronic offsets."""
return (frames[i] - bg_frames[i]) - (frames[j] - bg_frames[j])
def all_pairs(n_frames=6):
"""All unordered frame pairs, 15 for six frames. With two beams that gives
the 30 fluctuation images per acquisition used in the paper."""
return list(itertools.combinations(range(n_frames), 2))
def disjoint_pairs(n_frames=6):
"""(0,1), (2,3), (4,5). No frame is reused, so every fluctuation image
is statistically independent. Costs a factor of five in bits."""
return [(i, i + 1) for i in range(0, n_frames - 1, 2)]
# ------------------------------------------------------------------
# 3. Verification statistics (Eqs. 3 and 4)
# ------------------------------------------------------------------
def cross_correlation(dA, dB):
"""Normalized 2D cross correlation map C(i, j), Eq. 3.
dB (smaller) is scanned over dA without flipping, like the paper."""
a = dA[None, None].double()
b = dB[None, None].double()
num = F.conv2d(a, b) # correlation, not convolution
ones = torch.ones_like(b)
local_a2 = F.conv2d(a ** 2, ones)
den = torch.sqrt(local_a2 * (b ** 2).sum())
return (num / den)[0, 0]
def noise_ratio(dL, dR, NL_mean, NR_mean, bin_size=1):
"""NR = Var[(N_L,n - N_L,n+1) - (N_R,n - N_R,n+1)] / (<N_L> + <N_R>), Eq. 4.
Binning into super pixels should leave NR near 1 for pure shot noise."""
if bin_size > 1:
dL = F.avg_pool2d(dL[None, None], bin_size)[0, 0] * bin_size ** 2
dR = F.avg_pool2d(dR[None, None], bin_size)[0, 0] * bin_size ** 2
NL_mean = F.avg_pool2d(NL_mean[None, None], bin_size)[0, 0] * bin_size ** 2
NR_mean = F.avg_pool2d(NR_mean[None, None], bin_size)[0, 0] * bin_size ** 2
diff = dL - dR
# each difference of two frames doubles the per frame variance
return float(diff.var() / (2 * (NL_mean.mean() + NR_mean.mean())))
# ------------------------------------------------------------------
# 4. Bit extraction and min entropy
# ------------------------------------------------------------------
def to_uint16(delta):
"""Absolute value then 16 bit unsigned integer, matching the ADC depth."""
return delta.abs().round().clamp(0, 65535).to(torch.int32)
def extract_lsbs(u16, n_bits=3):
"""Keep the n least significant bits of every pixel, flattened to a bit tensor."""
shifts = torch.arange(n_bits, dtype=torch.int32)
bits = (u16.reshape(-1, 1) >> shifts) & 1
return bits.reshape(-1).to(torch.uint8)
def min_entropy_per_bit(u16, n_bits):
"""H_min = -log2(max_x Pr[X = x]) over n bit symbols, divided by n (per bit)."""
sym = (u16 & ((1 << n_bits) - 1)).reshape(-1)
counts = torch.bincount(sym, minlength=1 << n_bits).double()
p_max = counts.max() / counts.sum()
return float(-torch.log2(p_max) / n_bits)
def autocorrelation(bits, max_lag=100):
x = bits.double() * 2 - 1
x = x - x.mean()
var = (x * x).mean()
return torch.stack([(x[:-k] * x[k:]).mean() / var for k in range(1, max_lag + 1)])
# ------------------------------------------------------------------
# 5. Three NIST SP 800-22 style tests (frequency, block frequency, runs)
# ------------------------------------------------------------------
def monobit_p(bits):
n = bits.numel()
s = (bits.double() * 2 - 1).sum()
return math.erfc(abs(float(s)) / math.sqrt(2 * n))
def block_frequency_p(bits, M=128):
n_blocks = bits.numel() // M
pi = bits[: n_blocks * M].double().reshape(n_blocks, M).mean(1)
chi2 = 4 * M * ((pi - 0.5) ** 2).sum()
return float(torch.special.gammaincc(torch.tensor(n_blocks / 2.0, dtype=torch.float64), chi2 / 2))
def runs_p(bits):
n = bits.numel()
pi = bits.double().mean()
if abs(float(pi) - 0.5) >= 2 / math.sqrt(n):
return 0.0
v = 1 + int((bits[1:] != bits[:-1]).sum())
num = abs(v - 2 * n * float(pi) * (1 - float(pi)))
return math.erfc(num / (2 * math.sqrt(2 * n) * float(pi) * (1 - float(pi))))
# ------------------------------------------------------------------
# 6. End to end acquisition
# ------------------------------------------------------------------
def acquire_bits(cam, roi=150, n_bits=3, n_frames=6, pairs="all"):
"""One kinetic acquisition -> fluctuation images -> LSB bitstream.
pairs="all" reproduces the paper's 30 images, "disjoint" keeps 6."""
frames, bg = cam.frames(n_frames), cam.frames(n_frames, light=False)
pair_list = all_pairs(n_frames) if pairs == "all" else disjoint_pairs(n_frames)
streams, images = [], []
for i, j in pair_list:
d = fluctuation(frames, bg, i, j)
for cx in (cam.left_c, cam.right_c):
img = crop(d, cx, cam.cy, roi)
images.append(img)
streams.append(extract_lsbs(to_uint16(img), n_bits))
return torch.cat(streams), images, frames, bg
def generation_rate(bits_per_acq, acq_time_s):
return bits_per_acq / acq_time_s
# ------------------------------------------------------------------
# Smoke test
# ------------------------------------------------------------------
if __name__ == "__main__":
cam = TwinBeamEMCCD(seed=1)
bits, images, frames, bg = acquire_bits(cam)
# Bit budget and rate arithmetic from the paper
per_acq = 150 * 150 * 30 * 3
assert per_acq == 2_025_000 and bits.numel() == per_acq
inst = generation_rate(per_acq, 342e-6)
sustained = generation_rate(per_acq, 0.270)
print(f"Instantaneous rate {inst / 1e9:.2f} Gbps, sustained {sustained / 1e6:.1f} Mbps")
assert abs(inst / 1e9 - 5.92) < 0.01 and abs(sustained / 1e6 - 7.5) < 0.01
# Shot noise check, NR close to 1 and flat under binning
# Averaged over consecutive frame pairs of 20 acquisitions, as the paper
# averages over 100 images, because large bins leave few super pixels.
mL = crop(cam.mean_img, cam.left_c, cam.cy, 100)
mR = crop(cam.mean_img, cam.right_c, cam.cy, 100)
nr_runs = {b: [] for b in (1, 2, 5, 10, 20)}
for _ in range(20):
fr, bgr = cam.frames(), cam.frames(light=False)
for n in range(5):
d = fluctuation(fr, bgr, n, n + 1)
dL, dR = crop(d, cam.left_c, cam.cy, 100), crop(d, cam.right_c, cam.cy, 100)
for b in nr_runs:
nr_runs[b].append(noise_ratio(dL, dR, mL, mR, b))
nrs = [sum(v) / len(v) for v in nr_runs.values()]
print("Noise ratio by bin size:", [round(x, 3) for x in nrs])
assert all(0.95 < x < 1.05 for x in nrs)
# Cross correlation between beams shows no peak
big = crop(fluctuation(frames, bg, 0, 1), cam.left_c, cam.cy, 150)
small = crop(fluctuation(frames, bg, 0, 1), cam.right_c, cam.cy, 100)
C = cross_correlation(big, small)
print("Max |cross correlation|:", round(float(C.abs().max()), 4))
assert C.abs().max() < 0.05
# Min entropy versus bits per pixel, pooled over all 30 images.
# Uniform LSBs need the fluctuation spread to be much wider than 2^n counts,
# which is why entropy falls as more bits per pixel are taken.
u = torch.cat([to_uint16(im).reshape(-1) for im in images])
for nb in (1, 3, 6, 10):
print(f"H_min per bit at {nb} bits:", round(min_entropy_per_bit(u, nb), 4))
assert min_entropy_per_bit(u, 3) > 0.99
# Temporal independence and three NIST style tests
ac = autocorrelation(bits[:1_000_000])
print("Max |autocorrelation| over 100 lags:", f"{float(ac.abs().max()):.1e}")
assert ac.abs().max() < 5e-3
seq = bits[:1_000_000]
ps = {"monobit": monobit_p(seq), "block": block_frequency_p(seq), "runs": runs_p(seq)}
print("p values:", {k: round(v, 4) for k, v in ps.items()})
assert all(p >= 0.01 for p in ps.values())
# Pairs that share a frame. Raw values of (F0 - F1) and (F0 - F2) are
# correlated, yet their LSBs look independent to pairwise statistics.
idx = {p: 2 * k for k, p in enumerate(all_pairs())} # left beam images
raw_corr = torch.corrcoef(torch.stack([images[idx[(0, 1)]].reshape(-1),
images[idx[(0, 2)]].reshape(-1)]))[0, 1]
la = extract_lsbs(to_uint16(images[idx[(0, 1)]]), 1).double()
lb = extract_lsbs(to_uint16(images[idx[(0, 2)]]), 1).double()
lsb_corr = torch.corrcoef(torch.stack([la, lb]))[0, 1]
print(f"Shared frame pairs, raw corr {float(raw_corr):.3f}, LSB corr {float(lsb_corr):.4f}")
assert raw_corr > 0.4 and abs(lsb_corr) < 0.01
# But a three way relation survives. Because F0 - F2 = (F0 - F1) + (F1 - F2)
# on integer counts, and |x| keeps parity, bit 0 of the third image is the
# XOR of the other two. Pairwise tests and NIST style tests cannot see this.
b01 = to_uint16(images[idx[(0, 1)]]) & 1
b12 = to_uint16(images[idx[(1, 2)]]) & 1
b02 = to_uint16(images[idx[(0, 2)]]) & 1
xor_rate = float(((b01 ^ b12) == b02).double().mean())
print(f"Bit 0 of (F0-F2) equals XOR of the other two in {xor_rate:.1%} of pixels")
assert xor_rate == 1.0
# Conservative option, disjoint pairs only
safe_bits, _, _, _ = acquire_bits(cam, pairs="disjoint")
print(f"Disjoint pairs: {safe_bits.numel():,} bits per acquisition, "
f"{generation_rate(safe_bits.numel(), 342e-6) / 1e9:.2f} Gbps instantaneous")
assert safe_bits.numel() == 150 * 150 * 6 * 3
print("All smoke tests passed.")
Conclusion
This preprint shows that a camera can be a very large, very parallel quantum random number generator. By treating each pixel of an EMCCD as its own shot noise detector and subtracting away the predictable beam, Shaliq and Kumar extract random bits at an instantaneous 5.92 Gbps, verify through cross correlation, binning and signal to background measurements that the noise is quantum in origin, and pass the standard NIST and Diehard batteries without a software extractor.
The conceptual shift is from time to space. Most optical generators sample one detector very fast and fight timing jitter and bias with extraction. This design samples hundreds of thousands of detectors at once and relies on the fact that, for bright coherent light, the lowest bits of each pixel are nearly uniform by physics.
The approach should travel. Any imaging sensor with low enough read noise and a stable light source could, in principle, be turned into an entropy source. As scientific CMOS sensors improve their readout speed, the gap between the on chip rate and the sustained rate could close considerably. The same careful noise accounting is useful for anyone validating quantum hardware outputs.
The limits are real. Throughput today is 7.5 Mbps, the hardware is laboratory grade, and statistical tests can only rule out visible defects. Our own analysis suggests the combinatorial pairing of frames trades some genuine independence for rate, which a disjoint pairing or an entropy aware extractor would resolve. None of that undoes the core result, that spatial shot noise is a clean and abundant entropy source.
The next step the authors propose, combining this parallel acquisition with a source independent security framework, is the right one. Randomness that is fast is useful. Randomness that is fast and provably unpredictable is what cryptography and privacy preserving AI actually need.
Frequently asked questions
What is a quantum random number generator?
It is a device that produces random numbers from a quantum process, such as photon detection, whose outcomes cannot be predicted even in principle. This differs from pseudo random generators, which are algorithms that produce a predictable sequence from a seed.
How does this generator use a camera to make random numbers?
It images two equal laser beams on an EMCCD camera. Subtracting consecutive frames removes the steady beam shape and leaves each pixel’s random photon count fluctuation, whose three lowest bits are kept as random output.
How fast is the spatial shot noise generator?
The instantaneous rate is 5.92 Gbps, with a projected 11.45 Gbps using the full sensor. The sustained output is 7.5 Mbps, limited by the camera’s serial readout rather than the quantum source.
Does it need a randomness extractor?
The authors report output that passes NIST SP 800-22 and Diehard tests without extraction. Our analysis suggests reused frames create dependencies in the lowest bit, so an extractor or disjoint frame pairing would give stronger guarantees.
How do the authors show the noise is quantum?
They show no cross correlation between the two beams, no spatial autocorrelation within a beam, a noise ratio near one at every pixel binning size as expected for Poisson shot noise, and a shot noise to background ratio of about 29 dB.
Why does randomness matter for machine learning?
Model training usually prefers reproducible pseudo random numbers. Unpredictable randomness matters for differential privacy noise, secure aggregation in federated learning, watermark keys and authentication, where a predictable source would weaken security.
Read the original research
The preprint includes the full setup diagram, correlation maps and test results.
arXiv abstract page Full paper PDFShaliq, M. A., and Kumar, A. Quantum random number generation using spatial quantum noise of light. arXiv:2607.26486v2, quantum physics section, August 2026. Department of Physics, Indian Institute of Space Science and Technology, Thiruvananthapuram, Kerala, India.
This analysis is based on the published paper and an independent evaluation of its claims. The PyTorch code is an independent simulation written from the paper’s description, not the authors’ software, and the frame pairing analysis is our own.
