{
"cells": [
{
"cell_type": "markdown",
"id": "5b05ec19",
"metadata": {
"tags": [
"remove-cell"
]
},
"source": [
"#### Latex Headers\n",
"$$\\newcommand{\\ket}[1]{\\left|{#1}\\right\\rangle}$$\n",
"$$\\newcommand{\\bra}[1]{\\left\\langle{#1}\\right|}$$\n",
"$$\\newcommand{\\braket}[2]{\\left\\langle{#1}\\middle|{#2}\\right\\rangle}$$\n",
"$$\\newcommand{\\adagger}[0]{\\hat{a}^{\\dagger}}$$\n",
"$$\\newcommand{\\ahat}[0]{\\hat{a}}$$\n",
"$$\\newcommand{\\gtwo}[0]{g^{(2)}}$$\n",
"$$\\newcommand{\\H}[0]{\\ket{H}}$$\n",
"$$\\newcommand{\\V}[0]{\\ket{V}}$$\n",
"$$\\newcommand{\\D}[0]{\\ket{D}}$$\n",
"$$\\newcommand{\\AD}[0]{\\ket{AD}}$$"
]
},
{
"cell_type": "markdown",
"id": "cbe4a6fb",
"metadata": {},
"source": [
"# LAB -- QKD (Week 2)"
]
},
{
"cell_type": "markdown",
"id": "6ce85b07",
"metadata": {},
"source": [
"Now that you know how to operate the quED, we want you to take your knowledge of QKD and generate a key via BB84. To do this efficiently, you'll be developing Python scripts to perform taking the data and to do the post-processing. We've provided you with some barebones scripts to get you started. You should verify that they work as expected, and you can make any changes you wish.\n",
"\n",
"## Process for this lab\n",
"\n",
"quTools provides implementation details on how to do BB84 with the quED that can be found on pages 11 - 13 of their [QKD manual](https://www.qutools.com/files/quED/quED-QKD-manual.pdf). In terms of what you'll accomplish in your lab sessions, you'll follow these steps:\n",
"\n",
"1. Verify that Alice is sending an appropriate [number of photons](#Mean-photon-number) to Bob. \n",
"1. Establish a method for Alice to [encode bits](#Encode-bits) and Bob to decode them\n",
"1. Based on your setup, construct a [model](#Build-a-model) and estimate the secret key rate (SKR) you expect to see.\n",
"1. Generate a raw key, including [taking data](#Take-data) and [sifting the bits](#Bit-sifting)\n",
"1. Perform post-processing with provided [Information Reconciliation/Error Correction](#Information-Reconciliation/Error-Correction) and [Privacy Amplification](#Privacy-Amplification) scripts.\n",
"\n",
"## Aims in the lab\n",
"\n",
"As you work on the above process, keep in mind the following topics you'll need to discuss in your write-up:\n",
"\n",
"1. Justify the security of your set-up\n",
"1. Explain how you developed your model of SKR\n",
"1. Report your experimental SKR and how it compares to the model. Specifically, discuss the error rate you see versus what you expect.\n",
"1. Propose steps for improving SKR. Consider how improving the loss of the system would affect the SKR."
]
},
{
"cell_type": "markdown",
"id": "cea63d30-afb1-4a88-8875-99383ef1aa06",
"metadata": {
"tags": [],
"user_expressions": []
},
"source": [
"## Lab Timeline\n",
"\n",
"This lab is not explicitly broken up by day. Typically, the first day of this lab is used to develop the needed codebase for your QKD measurements (adapting from what is provided). This would include code for calibration, and the sending, receiving, and sifting of the secret key. \n",
"\n",
"The second day is then used to send and recieve data, learning about the importance of various settings in the bit error rate and efficiency of sending your secret key. It would also be used to complete the remainder of the lab, including the exercises at the end on error correction and privacy amplification."
]
},
{
"cell_type": "markdown",
"id": "bba5a044",
"metadata": {
"tags": [],
"user_expressions": []
},
"source": [
"## Mean photon number\n",
"\n",
"Last week you spent some time finding $g^{(2)}(0)$ of the SPDC source and looking at the mean number of photons in a pulse. The motivation for doing that was to verify we had an appropriate source for our QKD setup. This is important because...\n",
"\n",
"### BB84's security is based on the no-cloning theorem\n",
"\n",
"The no-cloning theorem states that for a given quanta in a superposition of eigenstates, it's impossible to produce a perfect copy. For more information, see eg [Wootters and Zurek 1982](\"https://www.nature.com/articles/299802a0\") or [Dieks 1982](\"https://www.sciencedirect.com/science/article/pii/0375960182900846?via%3Dihub\"). In short, it means that if eavesdropper Eve listens in by: (1) intercepting the photons Alice sends to Bob; (2) measuring them; and (3) then pretending to be Alice by sending identical photons to Bob, she'll create errors in the system that Alice and Bob can detect.\n",
"\n",
"When Alice sends more than one photon to Bob, Eve can perform a photon number splitting attack, which you can read more about in [Brassard, Lutkenhaus, Mor, Sanders 2000](\"https://doi.org/10.1103/PhysRevLett.85.1330\")\n",
"\n",
"The HBT set-up showed us that using just one output from the SPDC isn't a true single photon source. So, we can approximate that by modeling it as a weak pulse with Poissonian statistics.\n",
"\n",
"Recall that for a Poisson distribution with mean value $\\lambda$ the probability of obtaining value k is given by\n",
"\n",
"$$P(k) = e^{-\\lambda}\\frac{\\lambda^k}{k!}$$\n",
"\n",
"We can adjust $\\lambda$ by adjusting the laser current and the pulse duration for the pump laser.\n",
"\n",
"```{note}\n",
"**Implementation Note** You need to play with the repetition rate, laser current, and pulse duration to ensure that you are only sending around 1 photon per transmission event. Otherwise your protocol will be vulnerable to eavesdropping and would not be a true implementation of BB84. \n",
"\n",
"**Important** When switching to pulsed mode the repetition rate shown might not be what is set on the instrument. You should hit enter in that box to send the setting to the instrument and ensure what you see is what is actually being used!\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "dccb5bd1",
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The probability of getting more than 1 photons with mean 0.5 is 0.09020401043104986\n"
]
}
],
"source": [
"# Estimate the probability of getting more than k photons\n",
"import scipy\n",
"from scipy.stats import poisson\n",
"\n",
"\n",
"\n",
"def gt(kk,mu):\n",
" xx = 1-poisson.cdf(k=kk, mu=mu)\n",
" return xx\n",
"\n",
"k = 1 # default is 1: how often does Alice send > 1 photon?\n",
"mu = 0.5 # set the mean value (the above text uses lambda instead of mu)\n",
"prob = gt(k,mu)\n",
"\n",
"print(f\"The probability of getting more than {k} photons with mean {mu} is {prob}\")"
]
},
{
"cell_type": "markdown",
"id": "3ef0fae8",
"metadata": {
"jp-MarkdownHeadingCollapsed": true,
"tags": [],
"user_expressions": []
},
"source": [
"## Encode bits\n",
"\n",
"As you recall from your reading, BB84 uses linear polarization to encode bits. The two bases are $\\left[ \\H, \\V \\right]$ (horizontal and vertical) and $\\left[ \\D, \\AD \\right]$ (diagonal and anti-diagonal). For the sake of this lab, the default is that photons in the state $\\H$ or $\\D$ are `0` while photons in $\\V$ or $\\AD$ are `1`. \n",
"\n",
"```{note}\n",
"**Implementation Note** The default setup with the quED has Bob using a linear polarizer, **not** a polarizing beamsplitter. \n",
"\n",
"Keep this in mind while modifying the code below!\n",
"```\n",
"\n",
"```{note}\n",
"**Implementation Note** Note in the example code provided there is a way to implement integration over multiple trials. This integration violates the assumptions of BB84 (do you see why?) and should only be used for debugging the system. (Integration reduces errors so you can check that your polarizer and HWP settings are indeed working as you expect them to.) \n",
"```\n",
"\n",
":::{figure-md} BB84_setup\n",
"
\n",
"\n",
"Setup for BB84 using the QuED.\n",
":::\n",
"\n",
"As you can see in the above figure, with the default quED implementation Bob's terminal is simply a polarizer and a detector. That means that for each bit, Bob has to choose a basis and a value to perform his measurement. \n",
"\n",
"Consider an example with a lossless system. If Alice sends a bit with value `1` in the $\\left[ \\D, \\AD \\right]$ basis and Bob decides to measure in the $\\left[ \\D, \\AD \\right]$ basis but has his polarizer set to $\\D$ (which is `0`) he won't get a click. Even though Alice and Bob are using the same basis, they won't get a bit for their raw key from that photon.\n",
"\n",
"#### Assign random values\n",
"\n",
"The first step is generating the random bits that Alice will send Bob as well as generating the order of which basis Alice will use to transmit each bit and which basis and bit Bob will use to measure it. For the sake of the course, we'll use Python's pseudorandom number generator to make all the choices. "
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "d9b246fe",
"metadata": {},
"outputs": [],
"source": [
"# Import necessary Libraries\n",
"\n",
"# Generate the basis and bit values for each bit from Alice and Bob\n",
"\n",
"import numpy as np\n",
"import random\n",
"import matplotlib.pyplot as plt\n",
"\n",
"LENGTH = 10;\n",
"\n",
"# Are we using PBS and 2 detectors? The default is False\n",
"PBS = False\n",
"\n",
"# Assign bit valuess\n",
"bH = 0\n",
"bV = 1\n",
"bD = 0\n",
"bA = 1\n",
"\n",
"# Initialize the random bases for Alice and Bob. Let 0 be HV and 1 be AD\n",
"basis_Alice = np.zeros((LENGTH))\n",
"basis_Bob = np.zeros((LENGTH))\n",
"\n",
"# Now initialize the bit values for Alice\n",
"bits_Alice = np.zeros((LENGTH))\n",
"bits_Bob = np.zeros((LENGTH)) \n",
"\n",
"# Now set the correct values for Alice and Bob's rotation\n",
"angles_Alice = np.zeros((LENGTH))\n",
"angles_Bob = np.zeros((LENGTH))\n",
"for x in range (LENGTH):\n",
" basis_Alice[x] = random.randint(0,1)\n",
" basis_Bob[x] = random.randint(0,1)\n",
" bits_Alice[x] = random.randint(0,1)\n",
" bits_Bob[x] = random.randint(0,1)\n",
" angles_Alice[x] = 22.5*basis_Alice[x] - 45*bits_Alice[x]\n",
" if PBS:\n",
" angles_Bob[x] = 22.5*basis_Bob[x] # this will rotate Bob's HWP as needed. You'll measure from 2 detectors\n",
" else:\n",
" angles_Bob[x] = 45*basis_Bob[x] - 90*bits_Bob[x] # this will tells us how to rotate the linear polarizer\n",
"\n",
"# print('Alices Basis :',basis_Alice)\n",
"# print('Alices Bits :',bits_Alice)\n",
"# print(\"Alices Angles: %s\" %(angles_Alice))\n",
"# print('Bob basis: ', basis_Bob)\n",
"# print('Bob bits: ',bits_Bob)\n",
"# print('Bob angles:',angles_Bob)\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "50922335",
"metadata": {},
"source": [
"#### Set correct polarization\n",
"\n",
"Unless you want to rotate the half wave plate (HWP) and polarizer manually for every single bit, you're going to want to make sure you can control polarization optics via a Python script. We've provided a basic script for you below, which you can edit as you see fit.\n",
"\n",
"```{note}\n",
"Before you test the script, you need to make sure that your HWP and polarizers are lined up where you think they are, ie if you send $0^{\\circ}$ to the HWP, how do you know if it's actually at $0^{\\circ}$? For the quED set up, you can set the angle to $0^{\\circ}$ through the script or the quTools software and then manually rotate the polarization to get the optics where you want them. Ask one of the instruction staff to show you how to do that if you're unfamiliar with it.\n",
"```\n"
]
},
{
"cell_type": "markdown",
"id": "df7a66de",
"metadata": {},
"source": [
"## Build a model\n",
"\n",
"Now that you know how to encode bits, it would be helpful to estimate what you expect to see in terms of a secret key rate.\n",
"\n",
"To start with, consider the following questions to determine the raw key:\n",
"\n",
"* How fast can Alice encode photons?\n",
"* How often does Alice send Bob a photon? You should take into account both the rep rate of the pump pulses and also how often the pulse sends 0 photons.\n",
"* How often will Bob make a measurement of the correct basis and the correct polarization?\n",
"\n",
"After you've done that, consider how many bits you sacrifice for error correction and privacy amplification, which you may want to wait to do once you've taken data and performed the post-processing."
]
},
{
"cell_type": "markdown",
"id": "d709bba0",
"metadata": {},
"source": [
"## Take data\n",
"\n",
"Now you have all the pieces you need to take data! The cell immediately below this one is ready for you to define a function and get the data you need to create the raw key."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "94769a06-f990-498d-86a6-989a8bb48a97",
"metadata": {
"tags": [
"hide-output"
]
},
"outputs": [
{
"ename": "ModuleNotFoundError",
"evalue": "No module named 'lxml'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[1], line 5\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01murllib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mrequest\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m urlopen\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrequests\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmodels\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m PreparedRequest\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mlxml\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m html\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mre\u001b[39;00m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mtime\u001b[39;00m\n",
"\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'lxml'"
]
}
],
"source": [
"import requests\n",
"import numpy as np\n",
"from urllib.request import urlopen\n",
"from requests.models import PreparedRequest\n",
"from lxml import html\n",
"import re\n",
"import time\n",
"\n",
"# User Parameters\n",
"integrationTimeValue = 1000 # Integration time in ms, min = 100, max = 10000\n",
"pauseTimeValue = 1 # Pause time for motion in s\n",
"url = 'http://169.254.69.140:8080/?'\n",
"\n",
"# Accessible parameters\n",
"diodeCurrent = \"ild\"\n",
"motor1 = \"pm1\"\n",
"motor2 = \"pm2\"\n",
"integrationTime = \"int\"\n",
"countRate = \"cnt\"\n",
"moterRef = \"mref\"\n",
"\n",
"def quED_Access(url, action, param, reply = 0, value = []):\n",
" \"\"\"\n",
" Function that accesses QED via an ethernet connection\n",
" Inputs:\n",
" url - IP adddress of instrument (find in settings)\n",
" action - 'set' or 'get'\n",
" param - the parameter to set or get (see below)\n",
" reply - gives response output text, default false, true to debug\n",
" value - value to pass in, default is empty\n",
" Outputs:\n",
" finalData.response.text - raw string response from instrument\n",
" finalData.name - name of channel measured\n",
" finalData.data - data from measured channel\n",
" \"\"\"\n",
" \n",
" class finalData:\n",
" pass\n",
" \n",
" # For reading values out\n",
" if (value == []):\n",
" params = {'action':action,'param':param}\n",
" req = PreparedRequest()\n",
" req.prepare_url(url, params)\n",
" response = requests.get(req.url)\n",
" \n",
" # For setting values\n",
" else:\n",
" params = {'action':action,'param':param,'value':value}\n",
" req = PreparedRequest()\n",
" req.prepare_url(url, params)\n",
" response = requests.get(req.url)\n",
" \n",
" if reply == 1:\n",
" if action == 'set':\n",
" print(response.text.split(\"