Skip to main content

quantrs2_core/networking/
bb84.rs

1//! BB84 Quantum Key Distribution protocol simulation.
2//!
3//! Implements the Bennett-Brassard 1984 (BB84) QKD protocol with:
4//! - Alice's qubit preparation in rectilinear (Z) and diagonal (X) bases
5//! - Optional eavesdropping by Eve (intercept-resend attack)
6//! - Depolarizing channel noise
7//! - Bob's measurement in random basis
8//! - Sifting: retain bits where Alice and Bob chose the same basis
9//! - Quantum bit error rate (QBER) estimation from a sample
10//! - Privacy amplification via XOR hashing to half the sifted key length
11//! - Eavesdrop detection: `qber > 0.10`
12
13use crate::error::{QuantRS2Error, QuantRS2Result};
14use crate::networking::channel::{
15    ket_minus, ket_one, ket_plus, ket_zero, measure_computational, pure_state_density,
16    DepolarizingChannel, NoiseChannel,
17};
18use scirs2_core::ndarray::Array2;
19use scirs2_core::random::prelude::*;
20use scirs2_core::random::ChaCha20Rng;
21use scirs2_core::Complex64;
22use std::f64::consts::SQRT_2;
23
24// ---------------------------------------------------------------------------
25// Helper: convert u64 seed → 32-byte array for ChaCha20
26// ---------------------------------------------------------------------------
27fn seed_from_u64(seed: u64) -> [u8; 32] {
28    let mut bytes = [0u8; 32];
29    let s = seed.to_le_bytes();
30    bytes[..8].copy_from_slice(&s);
31    bytes[8..16].copy_from_slice(&s);
32    bytes[16..24].copy_from_slice(&s);
33    bytes[24..32].copy_from_slice(&s);
34    bytes
35}
36
37// ---------------------------------------------------------------------------
38// BB84 measurement bases
39// ---------------------------------------------------------------------------
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42enum Bb84Basis {
43    /// {|0⟩, |1⟩}
44    Rectilinear,
45    /// {|+⟩, |−⟩}
46    Diagonal,
47}
48
49/// Prepare qubit state for given bit and basis.
50fn prepare_qubit(bit: bool, basis: Bb84Basis) -> [Complex64; 2] {
51    match (bit, basis) {
52        (false, Bb84Basis::Rectilinear) => ket_zero(),
53        (true, Bb84Basis::Rectilinear) => ket_one(),
54        (false, Bb84Basis::Diagonal) => ket_plus(),
55        (true, Bb84Basis::Diagonal) => ket_minus(),
56    }
57}
58
59/// Measure qubit density matrix in given basis; returns the bit.
60fn measure_in_basis(rho: &Array2<Complex64>, basis: Bb84Basis, rand_val: f64) -> bool {
61    match basis {
62        Bb84Basis::Rectilinear => {
63            let (outcome, _) = measure_computational(rho, rand_val);
64            outcome
65        }
66        Bb84Basis::Diagonal => {
67            // Rotate to Z basis by applying H, then measure.
68            // H ρ H†: uses (H ρ H)_ij formula.
69            let h = 1.0 / SQRT_2;
70            let rho00 = rho[[0, 0]];
71            let rho01 = rho[[0, 1]];
72            let rho10 = rho[[1, 0]];
73            let rho11 = rho[[1, 1]];
74            let mut rho_rot = Array2::<Complex64>::zeros((2, 2));
75            rho_rot[[0, 0]] = Complex64::new(h * h * (rho00 + rho01 + rho10 + rho11).re, 0.0);
76            rho_rot[[1, 1]] = Complex64::new(h * h * (rho00 - rho01 - rho10 + rho11).re, 0.0);
77            let (outcome, _) = measure_computational(&rho_rot, rand_val);
78            outcome
79        }
80    }
81}
82
83// ---------------------------------------------------------------------------
84// BB84 Protocol
85// ---------------------------------------------------------------------------
86
87/// BB84 Quantum Key Distribution protocol.
88#[derive(Debug, Clone)]
89pub struct Bb84Protocol {
90    /// Number of raw qubits Alice sends.
91    pub n_bits: usize,
92    /// Channel depolarizing error probability per qubit in [0, 1].
93    pub error_rate: f64,
94    /// Fraction of qubits Eve intercepts (intercept-resend attack) in [0, 1].
95    pub eavesdrop_rate: f64,
96    /// Seed for the random number generator.
97    pub rng_seed: u64,
98}
99
100/// Result of running the BB84 protocol.
101#[derive(Debug, Clone)]
102pub struct Bb84Result {
103    /// Number of raw qubits sent.
104    pub raw_bits: usize,
105    /// Sifted key (after basis reconciliation, excluding QBER sample).
106    pub sifted_key: Vec<bool>,
107    /// Quantum bit error rate estimated from sample.
108    pub qber: f64,
109    /// Final secret key after privacy amplification.
110    pub secret_key: Vec<bool>,
111    /// Whether eavesdropping was detected (`qber > 0.10`).
112    pub detected_eavesdrop: bool,
113}
114
115impl Bb84Protocol {
116    /// Create a new BB84 protocol instance.
117    pub fn new(n_bits: usize, error_rate: f64, eavesdrop_rate: f64, rng_seed: u64) -> Self {
118        Self {
119            n_bits,
120            error_rate: error_rate.clamp(0.0, 1.0),
121            eavesdrop_rate: eavesdrop_rate.clamp(0.0, 1.0),
122            rng_seed,
123        }
124    }
125
126    /// Execute the BB84 protocol and return the result.
127    pub fn run(&self) -> QuantRS2Result<Bb84Result> {
128        if self.n_bits == 0 {
129            return Err(QuantRS2Error::InvalidInput(
130                "n_bits must be > 0".to_string(),
131            ));
132        }
133
134        let mut rng = ChaCha20Rng::from_seed(seed_from_u64(self.rng_seed));
135        let depolarizing = DepolarizingChannel::new(self.error_rate);
136
137        // Alice: random bits and bases
138        let alice_bits: Vec<bool> = (0..self.n_bits).map(|_| rng.random::<bool>()).collect();
139        let alice_bases: Vec<Bb84Basis> = (0..self.n_bits)
140            .map(|_| {
141                if rng.random::<bool>() {
142                    Bb84Basis::Rectilinear
143                } else {
144                    Bb84Basis::Diagonal
145                }
146            })
147            .collect();
148
149        // Bob: random measurement bases
150        let bob_bases: Vec<Bb84Basis> = (0..self.n_bits)
151            .map(|_| {
152                if rng.random::<bool>() {
153                    Bb84Basis::Rectilinear
154                } else {
155                    Bb84Basis::Diagonal
156                }
157            })
158            .collect();
159
160        // Per-qubit transmission
161        let mut bob_bits: Vec<bool> = Vec::with_capacity(self.n_bits);
162
163        for i in 0..self.n_bits {
164            let alice_state = prepare_qubit(alice_bits[i], alice_bases[i]);
165
166            // Eve intercept-resend
167            let eve_threshold: f64 = rng.random();
168            let state_after_eve = if eve_threshold < self.eavesdrop_rate {
169                let eve_basis = if rng.random::<bool>() {
170                    Bb84Basis::Rectilinear
171                } else {
172                    Bb84Basis::Diagonal
173                };
174                let eve_rho = pure_state_density(&alice_state);
175                let eve_rand: f64 = rng.random();
176                let eve_bit = measure_in_basis(&eve_rho, eve_basis, eve_rand);
177                prepare_qubit(eve_bit, eve_basis)
178            } else {
179                alice_state
180            };
181
182            // Channel noise
183            let mut rho = pure_state_density(&state_after_eve);
184            depolarizing.apply(&mut rho);
185
186            // Bob measures
187            let bob_rand: f64 = rng.random();
188            bob_bits.push(measure_in_basis(&rho, bob_bases[i], bob_rand));
189        }
190
191        // Sifting
192        let mut alice_sifted: Vec<bool> = Vec::new();
193        let mut bob_sifted: Vec<bool> = Vec::new();
194        for i in 0..self.n_bits {
195            if alice_bases[i] == bob_bases[i] {
196                alice_sifted.push(alice_bits[i]);
197                bob_sifted.push(bob_bits[i]);
198            }
199        }
200
201        if alice_sifted.is_empty() {
202            return Ok(Bb84Result {
203                raw_bits: self.n_bits,
204                sifted_key: vec![],
205                qber: 0.0,
206                secret_key: vec![],
207                detected_eavesdrop: false,
208            });
209        }
210
211        // QBER estimation on ~20% sample
212        let sample_size = (alice_sifted.len() / 5).max(1);
213        let errors: usize = (0..sample_size)
214            .filter(|&k| alice_sifted[k] != bob_sifted[k])
215            .count();
216        let qber = errors as f64 / sample_size as f64;
217
218        // Keep remaining sifted bits (exclude sample)
219        let sifted_key: Vec<bool> = bob_sifted[sample_size..].to_vec();
220
221        // Privacy amplification
222        let secret_key = privacy_amplification(&sifted_key);
223        let detected_eavesdrop = qber > 0.10;
224
225        Ok(Bb84Result {
226            raw_bits: self.n_bits,
227            sifted_key,
228            qber,
229            secret_key,
230            detected_eavesdrop,
231        })
232    }
233}
234
235/// Privacy amplification: XOR neighbouring pairs → half-length key.
236fn privacy_amplification(key: &[bool]) -> Vec<bool> {
237    let n = key.len() & !1;
238    (0..n / 2).map(|i| key[2 * i] ^ key[2 * i + 1]).collect()
239}
240
241// ---------------------------------------------------------------------------
242// Tests
243// ---------------------------------------------------------------------------
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn bb84_no_noise_no_eve_low_qber() {
251        let proto = Bb84Protocol::new(2000, 0.0, 0.0, 7);
252        let result = proto.run().expect("bb84 run");
253        assert!(
254            result.qber < 0.05,
255            "expected low QBER without noise, got {}",
256            result.qber
257        );
258        assert!(!result.detected_eavesdrop);
259    }
260
261    #[test]
262    fn bb84_full_eavesdrop_qber_near_quarter() {
263        let proto = Bb84Protocol::new(4000, 0.0, 1.0, 13);
264        let result = proto.run().expect("bb84 run");
265        // Full intercept-resend attack → QBER ≈ 25%
266        assert!(
267            result.qber > 0.15,
268            "expected QBER ≈ 0.25 with full eavesdropping, got {}",
269            result.qber
270        );
271        assert!(result.detected_eavesdrop);
272    }
273
274    #[test]
275    fn bb84_secret_key_half_sifted_length() {
276        let proto = Bb84Protocol::new(2000, 0.0, 0.0, 55);
277        let result = proto.run().expect("bb84 run");
278        if !result.sifted_key.is_empty() {
279            let expected = result.sifted_key.len() / 2;
280            assert_eq!(result.secret_key.len(), expected);
281        }
282    }
283}