Skip to main content

qkd

qkd

A QKD protocol simulator with the hardware in the loop, over a native Rust core.

Documentation: https://plutoniumm.github.io/qkd/

What it is

Most CV toolkits let you set the channel transmittance T and the excess noise ξ, then compute a key rate from the same numbers you set. That is the textbook derivation, not a protocol simulation.

qkd inverts it. Excess noise is an output, assembled from parameters you can measure on a bench — laser linewidth, detector electronic noise, quantum efficiency, DAC and ADC bit depth, pilot power ratio, symbol rate, fibre length — with the DSP chain in the loop: pilot-assisted phase recovery, carrier-offset estimation, frame rotation. The residual phase error those estimators leave behind feeds back into the noise budget as ξ_phase. Parameter estimation then works from finite samples with confidence bounds, the way Alice and Bob have to.

import qkd as q

link = q.Link(
    modulation=q.GaussianModulation(v_a=4.0),
    channel=q.Fiber(length=15.0, alpha=0.2),          # hardware, not T
    alice=q.Alice(
        laser=q.Laser(linewidth=10e3),
        pilots=q.Pilots(power_db=12.0, freq=180e6),
        symbol_rate=100e6,
    ),
    bob=q.Bob(
        detector=q.Heterodyne(eta=0.6, v_el=0.1, trusted=True),
        lo=q.LocalLO(linewidth=10e3),
    ),
    dsp=q.DSP(cfo=q.PilotCFO(), phase=q.PilotPhase(), block=32),
    security=q.FiniteSize(beta=0.95, n=1e9),
)
res = link.run(symbols=200_000, seed=7)

res.dsp.v_err       # 0.0075742    rad^2 left by the pilot estimator
res.budget.total    # 0.0304118    SNU at the channel input -- derived from it
res.est.xi          # 0.0142987    what Alice and Bob estimate from the samples
res.key_rate        # 0.0285553    bit/symbol

No line above sets ξ. It is what the linewidth, the pilot ratio, the symbol rate and the recovered phase produce.

Two rules follow the thesis everywhere. Every noise quantity names its plane — ξ at the channel input and at Bob's side differ by a factor of T, so the API refuses to guess which you meant. And trusted versus untrusted detector noise is an explicit flag, because it is a security-model decision, not a physics one.

Underneath sits an ordinary Gaussian CV simulator (symplectic covariance engine, thermal-loss channels, homodyne/heterodyne detection) which is useful on its own, with a truncated-Fock layer beside it for the states a covariance matrix cannot carry.

Status

0.1.0. Never published. pip install qkd does not work and will 404 — the wheel pipeline runs locally and nothing has been uploaded. Build from source (below).

qkd today is 26 Rust files (18,035 lines) plus 6 WGSL shaders (472 lines) under src/, 9,196 lines of Python under qkd/, and 49 test scripts building 1,444 exams across 241 Exams. qkd.__all__ lists 82 public names and is the authoritative API index; qkd._core exposes 199 — 192 functions and 7 classes. Counted 2026-09-04, and stale the moment an engine lands.

Configurations the library cannot compute raise, naming the restriction, rather than approximating. How well each protocol is checked is graded rather than asserted — see Validation, which scores every protocol on two axes: what the theory reproduces (the same number, a correctly signed inequality, or behaviour only) and, separately, whether a published experiment was reproduced.

Protocols

Discrete and continuous variable, prepare-and-measure and entanglement-based. Every row runs end to end and is covered by exams.

Reached through Protocol
q.Link + q.GaussianModulation Gaussian-modulation CV-QKD: homodyne and heterodyne, trusted and untrusted, asymptotic and finite-size, pilot DSP in the loop
q.Link + q.PhaseShiftKeying M-PSK discrete modulation
q.Link + q.BasisKeying BB84 with weak coherent pulses and decoy states; bases=3 is six-state, announce="pair" is SARG04
q.Link + q.PolarisationKeying the same, with the polarisation reference frame named
q.Link + q.TwoStateKeying B92; reference=True selects the strong-reference variant
q.Link + q.DifferentialPhase DPS, with the QBER derived from the delay interferometer
q.Link + q.IntensityKeying COW
q.PairLink + q.SymmetryBound BBM92 over a photon-pair source
q.PairLink + q.ViolationBound E91, priced by an observed CHSH violation instead of a phase error
q.Swap + q.BellDetector CV-MDI, the untrusted-relay topology
q.Swap + q.BellAnalyser MDI-BB84, the same topology with a counting midpoint. Asymptotic
q.Network the graph layer: trusted nodes, q.Hop edges, widest-path routing

Four more engines ship with exams but no q.* wrapper yet, and are reached on qkd._core directly: round-robin differential phase shift (rrdps_*), mode-pairing / asynchronous MDI (pairing_*), a hand-rolled semidefinite phase bound for COW′ (sdp_*), and the loss-tolerant analysis of source flaws (flaw_*). So does the Winick–Lütkenhaus relative-entropy proof for discrete modulation (dm_secure, on the complex-Hermitian numerics herm_*) — a second solver, on a different cone from sdp_*.

Three exported components describe hardware and are refused by q.Link rather than approximated: q.ThresholdArray and q.PnrDetector build a photon-number-resolving POVM no shipped bound can read, and q.TransmittedLO models the oscillator that travels with the signal, where every q.Link runs a locally generated one. Each refusal names what is missing.

Two Python modules sit beside the protocols. qkd.attacks holds the detector-side attacks — saturation, calibration, blinding, time-shift, blanking — reporting what Alice and Bob observe and what Eve holds as separate books of numbers, and refusing to be asked for a key rate, because under every attack modelled there the observables stay at values an unattacked link would produce. qkd.reconcile is the classical layer: LDPC and Cascade reconciliation, privacy amplification, authentication cost.

Four exclusions are settled decisions rather than pending work: MDI-DPS (patented), twin-field / phase-matching / sending-or-not-sending, satellite and free-space channels, and qudits.

Layers

Module What
qkd.gaussian symplectic covariance engine, thermal-loss channels, homodyne/heterodyne, Wigner and Husimi grids
qkd.fock truncated number basis: cat, GKP, squeezed, explicit density matrices, and the Wigner-negativity witness
qkd.budget ξ assembled from hardware, referred between four named planes
qkd.impairments hardware descriptors and the closed forms behind them
qkd.pairs photon-pair coincidence gains, BBM92 and CHSH
qkd.attacks detector-side attacks, as observables against Eve's holdings
qkd.reconcile error correction, privacy amplification, authentication

Build from source

git clone https://github.com/plutoniumm/qkd
cd qkd
./do develop        # compiles the Rust core in place -- this is the import path

You need Python ≥ 3.11 with numpy, plus a Rust toolchain (cargo, rustc). ./do develop runs setup.py build_ext --inplace and installs its own build dependencies. numpy is a hard requirement, not an optional extra — every bulk numeric return crosses the native boundary as an ndarray.

A bare cargo build will fail to link. A PyO3 extension-module cdylib has no Python symbols to resolve against; setup.py build_ext supplies -undefined dynamic_lookup. That failure is expected. Always go through ./do develop.

./do build produces cp311-abi3 wheels for macOS (arm64, x86_64), Linux manylinux_2_28 (aarch64, x86_64) and Windows x86_64, plus an sdist. It has been run; ./do deploy has not.

Compute backends

The core resolves a backend at runtime and reports it:

>>> import qkd
>>> qkd.backend_info()
('gpu', 'f32', 'Apple M2 (Metal)')

That tuple is machine-specific; ('cpu', 'f64', ...) on a machine with no adapter is a correct result, not a failure.

GPU support is on by default, via wgpu (Metal / Vulkan / DX12). It needs no vendor SDK at build time, which is what keeps the local cross-compile matrix working from a single Apple Silicon machine — a CUDA dependency would break it outright and could never be tested here.

Dispatch asks for a precision, not a device. WGSL has no f64, so the GPU path is single-precision, permanently. That suits the DSP layer, which runs over ADC samples carrying 12–16 real bits, where f32's 24-bit mantissa is already past the hardware's resolution. Covariance matrices, symplectic maps and the Holevo bound are 2×2 and 4×4, negligible to compute and sensitive to cancellation, and stay in f64 on the CPU whatever hardware is present.

No q.Link run touches the GPU. The WGSL kernels exist, dispatch, and are measured — Threefry is bit-exact against the Rust past counters of 2³², and DSP statistics agree with the f64 CPU path to about 1e-7 — but they compute the oracle branch, not the DSP-derotated one: the pilot DSP across blocks and the phase-walk carry scan are sequential and stay on the host, where a monolithic f32 scan errs 4.0e-2 rad, the same size as the residual phase noise the simulation exists to measure. run_symbols, and therefore every q.Link run, is CPU. The measured in-kernel throughput figures (313–335 M symbol/s against 10.3 M symbol/s for the CPU pipeline, crossover at 1e3–3e3 symbols) come from a machine under heavy load, so the CPU rows are roughly 2× slow and no single speedup number is claimed.

Development

Everything goes through ./do. There is no Makefile, no pyproject.toml, and no CI.

./do develop           # compile the Rust core in place (debug) -- the import path
./do check             # cargo check + python compileall, fast, no link
./do test [name ...]   # develop + run test/*.py, emit markdown reports
./do bench [name ...]  # release build + bench/*.py through the MDB harness
./do lint [--fix]      # view.lint + black --check + eastwood + svelte-check
./do docs              # vitepress dev server on :3000
./do build             # every wheel -> wheelhouse/, sdist -> dist/  (no upload)
./do deploy            # twine check + upload what build produced

./do needs python on PATH; on a machine that has only python3, activate an environment first. ./do test and ./do bench take file selectors, so ./do test anchors budget re-runs those two alone.

Tests are standalone scripts, not pytest, and emit markdown report tables. To publish them into the docs site:

MDR_OUT="$(pwd)/docs/tests" ./do test

See RELEASING.md for the wheel matrix, how the cross-compiles work, and the three release gates, and docs/architecture.md for the layering.

License

MIT

Release files for qkd 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qkd 0.1.0
File Size Uploaded
qkd-0.1.0.tar.gz 522.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for qkd 0.1.0
File
qkd-0.1.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
qkd-0.1.0-cp311-abi3-manylinux_2_28_x86_64.whl CPython 3.11 abi3 Linux glibc 2.28+ x86-64 Details
qkd-0.1.0-cp311-abi3-manylinux_2_28_aarch64.whl CPython 3.11 abi3 Linux glibc 2.28+ ARM64 Details
qkd-0.1.0-cp311-abi3-macosx_11_0_x86_64.whl CPython 3.11 abi3 macOS 11.0+ x86-64 Details
qkd-0.1.0-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details

Total release size: 14.7 MB

Release files / qkd-0.1.0.tar.gz

Download URL qkd-0.1.0.tar.gz
Size 522.3 kB
Tags Source
SHA-256 checksum
How to use checksums
e0ccbf2775014c89fc60bbe61ad3775b2f65cad5106d02250621914d70ef3b45
BLAKE2b-256 checksum
How to use checksums
4fc86798ca9bcc40791d38c17dd887a0e45f36d34aff05403da43a6b2f2374bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / qkd-0.1.0-cp311-abi3-win_amd64.whl

Download URL qkd-0.1.0-cp311-abi3-win_amd64.whl
Size 3.1 MB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
602bc633246a043b699f302891d833da45eb3d7476d1b3cb11ff0358c028b226
BLAKE2b-256 checksum
How to use checksums
af487ebf1d13a2e69802dee4613aabf96fdaebbf75a339c5cd15fe50b1701c1a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / qkd-0.1.0-cp311-abi3-manylinux_2_28_x86_64.whl

Download URL qkd-0.1.0-cp311-abi3-manylinux_2_28_x86_64.whl
Size 2.9 MB
Tags CPython 3.11 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
27dbf18017ed97e62a63b0af7d9f8daa864c0e5f232725a7e4c763bb56d113da
BLAKE2b-256 checksum
How to use checksums
0c7bc0d6c899df793219846d3643ea87bd99737d56ac32d773e79aa26cc4c428
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / qkd-0.1.0-cp311-abi3-manylinux_2_28_aarch64.whl

Download URL qkd-0.1.0-cp311-abi3-manylinux_2_28_aarch64.whl
Size 2.8 MB
Tags CPython 3.11 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
36fdaba1c71b59e0019f5e4a5491ec05f0a7bdaef1ea6ca4a36bbfb9db634b5b
BLAKE2b-256 checksum
How to use checksums
49d58bf3fc8dfc88988ab20b10d165e193abd4017ec664ad8d3c81d468c0ef31
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / qkd-0.1.0-cp311-abi3-macosx_11_0_x86_64.whl

Download URL qkd-0.1.0-cp311-abi3-macosx_11_0_x86_64.whl
Size 2.8 MB
Tags CPython 3.11 abi3 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
c3800e0cdce29cb78eac67deb9e969a819cd90c31ed0aab2d6e2cba36d8e9587
BLAKE2b-256 checksum
How to use checksums
dc040c064c54f7e1e28355f750241175f9ee2c9ba5adc1628187448ff87e3b4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / qkd-0.1.0-cp311-abi3-macosx_11_0_arm64.whl

Download URL qkd-0.1.0-cp311-abi3-macosx_11_0_arm64.whl
Size 2.6 MB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
9de88031ea7353ccf8f14dd12abb604ece2600b1c9865474f4ec6efc59ec6532
BLAKE2b-256 checksum
How to use checksums
928cc591894ba031858ab17d70c4aabaf5fd4e5fa9fc31ca4ad5a15e1b6303e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

0.2.0

6 release files

This release

0.1.0 This release

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page