# `kvant.py`The first function in `kvant.py` is basically the application of the ket notation on a binary number.
```pydef get_state(x):    l = [0] * 2**len(x)    l[int(x, 2)] = 1    return l```In computer science terms, it maps a binary number string $b$ of length $n$ (corresponding to a decimal number $x$) to an array of size $2^n$ whose entries are all $0$ except for the $x^ \text{th}$ one, which is $1$. An example:$$\verb|get_state('01')| = |{01}> = \begin{pmatrix}0\\\\1\\\\0\\\\0\end{pmatrix} = \verb|[0, 1, 0, 0]|$$
The next two functions should be self explanatory:
```pydef str2bin(s):    return ''.join(bin(ord(i))[2:].zfill(8) for i in s)
def bin2str(a):    return ''.join(chr(int(a[i:i+8], 2)) for i in range(0, len(a), 8))```
## Encoding functions---
### `encode_1()`
Now comes the interesting part. The following function expects an initial state vector and creates a single qubit quantum circuit. Thus the initial state must be an array of size $2^1$.
```pydef encode_1(initial_state):    qc = QuantumCircuit(1)    qc.initialize(initial_state)    qc.x(0)    qc.save_statevector()    qobj = assemble(qc)    state = sim.run(qobj).result()    return list(state.get_statevector().real.astype(int))```It is to be noted that the above circuit performs the Qiskit `.x()` gate ( `NOT` ) on the $0^\text{th}$ qubit of the circuit, returning the resultant state vector. Given our definitions of $|{0}>$ and $|{1}>$, the possible results can be as follows:
$$\verb|NOT| \lvert{0}\rangle = \verb|NOT| \begin{pmatrix} 1\\\\0\end{pmatrix} = \begin{pmatrix} 0\\\\1\end{pmatrix} = \lvert{1}\rangle$$
$$\verb|NOT| \lvert{1}\rangle = \verb|NOT| \begin{pmatrix} 0\\\\1\end{pmatrix} = \begin{pmatrix} 1\\\\0\end{pmatrix} = \lvert{0}\rangle$$
### `encode_2()`
The second encoding function happens to be a 2-qubit circuit, hence expecting a state vector of size $2^2$ as an input.
```pydef encode_2(initial_state):    qc = QuantumCircuit(2)    qc.initialize(initial_state) qc.cx(0, 1)    qc.save_statevector()    qobj = assemble(qc)    state = sim.run(qobj).result()    return list(state.get_statevector().real.astype(int))```This time however, a `.cx()` gate ( `CNOT` ) is being applied to the 2 qubits – the $0^\text{th}$ and $1^\text{st}$ bit being the target and control bits respectively (*damn qiskit for inverting their order*). The `CNOT` gate simply flips the target bit, only if the control bit is set to 1 and does nothing otherwise. Fortunately, mathematics comes to the rescue with a handy matrix definition for the `CNOT` gate:
$$\verb|CNOT| := \begin{pmatrix} 1&0&0&0\\\\0&0&0&1\\\\0&0&1&0\\\\0&1&0&0\\\\\end{pmatrix}$$
Its effect on a 2-qubit state vector $M$ is replicable by swapping the $1^\text{st}$ and $3^\text{rd}$ entries of $M$ *(starting from 0)*, i.e.
$$\verb|CNOT| \begin{pmatrix}a\\\\b\\\\c\\\\d\end{pmatrix} = \begin{pmatrix} 1&0&0&0\\\\0&0&0&1\\\\0&0&1&0\\\\0&1&0&0\\\\\end{pmatrix} \begin{pmatrix}a\\\\b\\\\c\\\\d\end{pmatrix}= \begin{pmatrix}a\\\\d\\\\c\\\\b\end{pmatrix}$$
An example with a prepared state $\lvert{11}$:
$$\verb|CNOT| \lvert{11} = \verb|CNOT| \begin{pmatrix}0\\\\0\\\\0\\\\1\end{pmatrix} = \begin{pmatrix}0\\\\1\\\\0\\\\0\end{pmatrix} = \verb|[0,1,0,0]|$$
### `encode_3()`
The third encoding function is a 3-qubit circuit, hence expecting a state vector of size $2^3$ as an input.
```def encode_3(initial_state):    qc = QuantumCircuit(3)    qc.initialize(initial_state)    qc.ccx(0, 1, 2)    qc.save_statevector()    qobj = assemble(qc)    state = sim.run(qobj).result()    return list(state.get_statevector().real.astype(int))```
The last encoding function is a `.ccx()` gate ( `CCNOT` ), also known as the Toffoli gate, which acts on 3 qubits. The `CCNOT` gate is a controlled version of the `CNOT` gate, i.e. it flips the target bit only if **both control bits** are set to 1 and does nothing otherwise. The `CCNOT` gate is defined as follows:
$$\verb|CCNOT| := \begin{pmatrix}1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\\\0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\\\0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\\\0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\\\0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\\\\end{pmatrix}$$
Again, it can be seen as swapping the $6^\text{th}$ and $7^{th}$ entries *(starting from 0)* of a 3-qubit state vector $M$ like so:
$$\verb|CCNOT| \begin{pmatrix}a\\\\b\\\\c\\\\d\\\\e\\\\f\\\\g\\\\h\end{pmatrix} = \begin{pmatrix}1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\\\0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\\\0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\\\0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\\\0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\\\\end{pmatrix} \begin{pmatrix}a\\\\b\\\\c\\\\d\\\\e\\\\f\\\\g\\\\h\end{pmatrix}= \begin{pmatrix}a\\\\b\\\\c\\\\d\\\\e\\\\f\\\\h\\\\g\end{pmatrix}$$ ---
## Encrypting functions
### encrypt_1/2/3()
The `encrypt_1/2/3()` functions are the ones that actually perform the *'encryption'*. The first one iterates over every bit, applying the `encode_1()` to it and concatenating all the entries of the resulting state vectors. The second and third functions do the same, but with the `encode_2()` and `encode_3()` functions over every pairs and triples of bits respectively.
**Note:** After every encrypting function, the length of the encrypted message doubles!
### Dry run using `'a'`Consider the encryption with the following string – `'a'`
```text'a' = 97```
↓ str2bin()```text01100001 ```
↓ encode_1()
```textnot |0> not |1> not |1> not |0> not |0> not |0> not |0> not |1>```
↓ computing!```text|1> |0> |0> |1> |1> |1> |1> |0> ```
↓ expanding the states!```text01 10 10 01 01 01 01 10 ```
↓ encode_2() `remember, first bit is target and second bit is control````textcnot |01> cnot |10> cnot |10> ...```
↓ computing!```text|11> |10> |10> ...```
↓ expanding the states!```text0001 0010 0010 0001 0001 0001 0001 0010```
↓ group every 3 bits *(last one is wrong due to the short length of the message — length of message should be a multiple of 3)*```text000 100 100 010 000 100 010 001 000 100 10  ```
↓ encode_3()```textccnot |000> ccnot |100> ccnot |100> ...```
↓ computing!```text|000> |100> |100> ...```
↓ expanding the states!```text10000000 00001000 0000100 ...```
Now that we've understood how the encrpytion occurs, it's just a matter of reversing each function,  by doing the right swaps and collapsing the array to the previous, shorter, bit string. 
---
## My Solution
```pydef str2bin(s):    return ''.join(bin(ord(i))[2:].zfill(8) for i in s)
def bin2str(a):    return ''.join(chr(int(a[i:i+8], 2)) for i in range(0, len(a), 8))
def decrypt_1(enc_1):    dec_bin = ''    for i in range(0, len(enc_1), 2):        state = [int(x) for x in enc_1[i:i+2]]        dec_bin += str(bin(state.index(0))[2:]) # swap 0 and 1    return dec_bin
def decrypt_2(enc_2):    enc_1 = ''    for i in range(0, len(enc_2), 4):        state = [int(x) for x in enc_2[i:i+4]]        state[1], state[3] = state[3], state[1] # CNOT swap        enc_1 += str(bin(state.index(1))[2:].zfill(2))    return enc_1
def decrypt_3(enc_3):    enc_2 = ''    for i in range(0, len(enc_3), 8):        state = [int(x) for x in enc_3[i:i+8]]        state[3], state[7] = state[7], state[3] # CCNOT swap        enc_2 += str(bin(state.index(1))[2:].zfill(3))    return enc_2
with open('enc.txt', 'r') as f:    enc = str2bin(f.read()) # read file and convert to binary
flag = bin2str(decrypt_1(decrypt_2(decrypt_3(enc)))) # full decrypt and convert to string
print(flag)```
Flag = `CyberErudites{W3lc0m3_7o_th3_w0rld_0f_Qu4ntum_C0mput1ng!}`
