Skip to content

Swap and network topologies ​

Alice and Bob both send; an untrusted station between them measures and announces, and the announcement correlates the senders. Neither detects anything.

Boughtdetector side channels leave the threat model: the proof never trusts the relay, so a relay detector has no trusted= flag (the split)
Paida two-arm channel and a harsher loss budget: relay position matters, and the symmetric position is not the optimum

Why it is called a swap ​

Preparing and sending a state is equivalent to measuring half an entangled pair, so this topology is an EPR source per end with a Bell measurement between them: entanglement swapping, the time reversal of BBM92. Lo, Curty and Qi introduced it so, on the time-reversed EPR idea of Biham, Huttner and Mor and of Inamori.

Why the middle may be untrusteda Bell measurement projects onto a joint property and reveals nothing about either input
Why not "relay"no photon travels from Alice to Bob. Both terminate at the station, the joint measurement consumes them, and only a classical announcement leaves
Caveatno entanglement exists anywhere: Alice and Bob prepare Gaussian-modulated coherent states. Swapping is the virtual protocol of the security proof; the hardware mixes the arrivals on a balanced beamsplitter and homodynes conjugate quadratures

q.Swap ​

Swap resolves components into a plan, refuses what it cannot compute, and returns a SwapResult. The qubit midpoint, q.BellAnalyser with q.TestBasisBound: MDI-BB84.

python
import qkd as q

res = q.Swap(
    alice=q.Sender(modulation=q.GaussianModulation(v_a=1e5)),
    bob=q.Sender(modulation=q.GaussianModulation(v_a=1e5)),
    relay=q.Relay(bell=q.BellDetector(eta=1.0, v_el=0.0)),
    channels=(q.Channel(T=1.0), q.Channel(T=0.1)),
    security=q.Asymptotic(beta=1.0),
).run()

res.key_rate     # 0.077336  bits per relay use
res.chi          # 22.0      equivalent noise
run() signatureno symbols, no seed: a closed form over the configured hardware
VA cap105. res.key_rate takes no VA, but res.attack evaluates the post-relay covariance at the configured one, and past 105 its conditional blocks are differences of numbers that have spent their mantissa
Where the cap raisesat run(): GaussianModulation is shared with the point-to-point path, which has no cap
python
q.Sender(modulation=q.GaussianModulation(v_a=1e6))   # constructs fine
# ... Swap(...).run()
# ValueError: va must be <= 1e5, got 1000000: the conditional blocks cancel
#             catastrophically and the rate exceeds its asymptotic bound. Use
#             cvmdi_rate for the large-modulation limit

The component tree ​

ParameterUnitDefaultDescription
Swap.alice, .bob—requiredTwo Senders. Both send; neither measures. The rate is not symmetric under exchanging them.
Swap.relay—requiredThe untrusted Relay.
Swap.channels—required(alice's arm, bob's arm), each a Channel or Fiber. Exactly two.
Swap.security—NoneOn a q.BellDetector midpoint, Asymptotic or q.TwoModeBound(block=q.GaussianBlock(…)); on a q.BellAnalyser one, q.TestBasisBound. None always fails at run(): no security model is safe to fall back to.
Swap.environment—NoneA CorrelatedEnvironment. None is the independent pair.
Sender.modulation—requiredGaussianModulation beside a BellDetector; a basis- or polarisation-keyed modulation beside a BellAnalyser.
Sender.pulsesNoneIntensity FWHM of this sender's temporal mode. Refused on the covariance path, which has no two-source mode model. Beside a BellAnalyser, widths on both senders derive misalign_test; one width alone, or widths beside a pinned misalign_test, is refused.
Relay.bell—requiredA BellDetector or a BellAnalyser.
BellDetector.eta—requiredRelay detection efficiency η∈(0,1].
BellDetector.v_elSNUrequiredRelay electronic noise vel≥0.
CorrelatedEnvironment.x, .pSNUrequiredEve's cross-arm environment correlations g, g′. No neutral default exists; the uncertainty principle caps the pair jointly, and a combination describing no state is refused.

What run() returns ​

FieldDescription
key_rateThe bound, bits per relay use: the worst attack compatible with the observed χ, in the large-modulation limit at the Shannon reconciliation limit, so the configured β does not reach it. Clamped at ≥0.
chiThe equivalent noise the two arms carry.
floorWhat two pure-loss arms would carry; see below.
leastThe domain bound, where the noise term vanishes. χ≤least describes no state and raises.
attackAttack(i_ab, chi_e, key_rate) at the configured finite VA and β, unclamped.
explainThe labelled plan, with no stages key: a relay run has one stage.

Only SwapResult.key_rate is clamped. explain()["key_bound"] and ["key_raw"] carry the unclamped bound; Attack.key_rate (explain()["key_attack"]) is never clamped.

The continuous-variable relay ​

Each sender sends a Gaussian-modulated coherent state down its own thermal-loss arm, transmissivity τa or τb. The relay mixes the arrivals on a balanced beamsplitter, homodynes x on one output and p on the other, and broadcasts both outcomes, which condition the joint state of the senders' kept modes.

Implementationtwo thermal-loss channels, a beamsplitter and two homodynes, conditioned through the Schur complement of State.condition; no bespoke relay formula
Rolesthe encoder's variable becomes the key; the decoder combines his variable with the announcement to reconstruct it. Reconciliation direction is not symmetric

Everything depends on one number ​

In the large-modulation limit the rate is a closed form in the two transmissivities and one equivalent noise χ.

χloss, two pure-loss arms2(τa+τb)τaτb
Excess noise enters aseach arm's environment variance ω=1+τξ/(1−τ), ξ at the channel input. A lossless arm has no environment mode and no excess noise
res.floorχloss — a reference point, not a lower bound. Eve may correlate the arms' environments to help the Bell measurement, driving χ below its pure-loss value within the uncertainty principle
res.least(τa+τb)2τaτb, where the noise term reaches zero; below it no covariance matrix exists and inputs raise. 4 for equal arms at any loss, the pole of the symmetric branch's logarithm
Relay imperfectionsfolded into the two arms: τ→ητ, ω→η(1−τ)ω+(1−η)+vel1−ητ
Why the fold is exactequal loss on every mode commutes with any passive linear network, so detector efficiency after the balanced coupler is a loss on both arms before it

Relay position is the result ​

Total transmittance fixed at τaτb=0.1, relay sliding along it: pure-loss arms, perfect relay detector, VA=105, unclampedres.attack.key_rate (res.key_rate is 0 on every row but the last):

Relay positionτa (encoder arm)Rate, bits per relay use
at the decoder0.1−4.6123
0.2−3.5727
symmetric0.1=0.3162−2.8120
0.5−1.9631
0.9−0.4649
at the encoder1.0+0.0773

The rate climbs monotonically and crosses zero only at the last step: the symmetric relay is 2.9 bits below the encoder-side one. Exchanging the arms at fixed total loss costs key when the longer arm moves to the encoder's side.

Zero crossings against Pirandola et al., Nat. Photon. 9, 397 (2015), arXiv:1312.4104, are graded at Validation. With the decoder's arm lossless the rate is log2(τ/((1−τ)e)), zero at τ=e/(1+e), asserted to 10−6; the symmetric row, read from prose, to 5×10−3. With the relay adjacent to the encoder there is no crossing: the rate stays positive past 300 km.

What res.attack is, and is not ​

res.key_rate — cvmdi_rateres.attack — cvmdi_point
Isa security bound: the worst attack compatible with the observed χone specified attack: the two environment variances and the correlations g, g′ the link was configured with
Modulationthe large-modulation limit; takes no VAthe finite VA configured
Reconciliationthe Shannon limit, so the configured β does not reach itthe configured β, as key=βIAB−χE
Returnsa rate(IAB,χE,key)
Why unclampedmost of the (τa,τb) plane cannot distil, and the depth of the deficit measures how far a position is from viable
Joinraising the modulation drives the point onto the asymptote, and is tested
attack.key_rate above key_ratepossible: cvmdi_rate reads the observed noise as the worst attack producing it, cvmdi_point prices one named Eve at finite VA. A correlated attack of the helping sign hands the relay an entanglement resource. That it also exceeds the repeaterless capacity is UNREPRODUCED: test_relay_bounded finds neither rate above capacity in any of the 4,518 configurations the engine accepts out of 40,000 drawn, closest approach 0.41, and a 200,000-draw search maximising R−PLOB peaks at −0.33. b92_ceiling and cow_rate escape PLOB, pinned in test/capacity.py's Escapes; neither is CV-MDI
Wrong signfights the correlations the Bell measurement establishes and costs more key than two independent entangling cloners, so the equivalent noise takes g and g′, not only ωa, ωb
PLOBevery pure-loss configuration sits strictly under −log2⁡(1−T), Pirandola et al., Nat. Commun. 8, 15043 (2017)

Where it is not ​

Asymmetric modulation and a symbol-level relay: roadmap.

Twin-field, phase-matching and sending-or-not-sending: permanent exclusion.

Graded numbers: Validation.

q.Network ​

A graph of key-holding nodes. Every vertex is a q.Node holding key in the clear; every edge is a q.Hop carrying one q.Link or q.Swap.

python
import qkd as q

net = q.Network(
    nodes={"IITM": q.Node(), "ERNET": q.Node(), "NIC": q.Node()},
    edges=[
        q.Hop(ends=("IITM", "ERNET"), link=cow, clock=1e9),
        q.Hop(ends=("ERNET", "NIC"),  link=dps, clock=1e9),
    ],
)

res = net.run()
res.rates[("IITM", "ERNET")]      # bits per second
res.rates[("ERNET", "IITM")]      # the same; pairs read either way round

route = res.route("IITM", "NIC")
route.hops        # (("IITM", "ERNET"), ("ERNET", "NIC"))
route.trusts      # ("ERNET",)
route.bottleneck  # bits per second, the minimum over the hops
route.security    # "trusted-node key relay through ERNET; a key-management
                  #  throughput, not a quantum bound"

An untrusted station is not a vertex ​

A q.Swap is one edge. Its untrusted midpoint is interior to that edge and in no vertex set, segment key or route: the data structure cannot express it.

Whyuntrusted relays do not compose: chaining two swaps needs quantum memory or a repeater
What it buysdetector-side-channel immunity for one segment, at a worse loss budget. No added reach
What it costs in the modeltwo swap edges through one building each carry their own q.Relay; one detector pair serving two segments is not expressible
Consequence for routinga swap segment is one edge between its senders; the router sees neither arm and no name to route through

There is no end-to-end key rate ​

Neither NetworkResult nor Route has one; asking raises with the reason:

python
res.key_rate
# AttributeError: a network has no key rate. Per-segment rates are in
#   res.segments and res.rates; a path number is res.route(a, b).bottleneck,
#   which is a KEY-MANAGEMENT THROUGHPUT and not a security bound, because no
#   quantum bound spans a trusted node

route.bottleneck is the minimum hop rate in bits per second: hop-by-hop key relay spends one local key bit per relayed bit. route.trusts names every intermediate node, each of which held the end-to-end key in the clear. It is empty only on a single-hop route, the one case where the number is a quantum claim.

The component tree ​

ParameterUnitDefaultDescription
Network.nodes—required{name: q.Node}; the key is the name. A q.Relay is refused: an untrusted station holds no key
Network.edges—requiredA list of q.Hop, not a dict keyed by pair, so no parallel clocks= mapping can disagree
Hop.ends—requiredTwo distinct node names. (transmitter, receiver) for a q.Link; (alice, bob) for a q.Swap, following its encoder convention
Hop.link—requiredA q.Link or a q.Swap, verbatim, run and multiplied by the clock: a network reports what the standalone payload reports
Hop.clockHzrequiredSymbols per second on the quadrature families, emitted pulses per second on the click ones
Node——Carries nothing; physical descriptions live on the edge

The clock is mandatory. LinkResult.key_rate is bits per symbol on Gaussian modulation and M-PSK, per emitted pulse on the click families; SwapResult.key_rate is per swap use. A minimum over a mixed path would mix three units. It cannot come from q.Alice.symbol_rate: q.Link refuses an Alice on the intensity-keyed path.

What Network.run() returns ​

FieldDescription
segments{(a, b): LinkResult | SwapResult}, per-shot rates in each payload's own unit. Reads either way round
rates{(a, b): bits per second}, the only unit that compares across families
nodes{name: NodeReport} with degree, segments and total, the sum of the incident segments in bits per second
explainthe labelled plan, with trust, units, end_to_end and contention rows always present

run(symbols=…, seed=…) reaches the q.Link payloads alone; a q.Swap is a closed form and takes neither.

Routing ​

res.route(a, b) returns the widest path — largest bottleneck, ties broken by fewest hops and so fewest trusted nodes — key relay being limited by its narrowest hop.

Refused
An unknown endpointnames the nodes the network does have
A route from a node to itselfa route joins two distinct nodes
A disconnected pairsays so, noting that an untrusted swap station is never a vertex

Contention is not modelled. A node relaying two concurrent routes splits its key pool, so two route() calls on one result cannot both be achieved.

What the graph layer does not model ​

Absent
Authentication key consumptionhop-by-hop relay spends key authenticating every round, so delivered key is strictly below bottleneck
Key pool dynamicsno buffering, scheduling, refill or contention
The relay operation itselfthe XOR of local keys, its failure modes, its latency
Timeno availability, no outage, no drift
Parallel segments between one pairrefused: their key pools would add and nothing here tracks that
Point to multipointa receiver shared by several transmitters; hardware lives on the edge
Wavelengthno component carries one, so per-edge Raman crosstalk is not derived from a channel plan
Multi-path key splittingroute() returns one path
The control planekey management APIs, SDN control, and everything above the physical layer