Welcome to pyqrypto’s documentation!

pyrypto is a library of reversible quantum circuits for basic functions used in classical cryptography. As of now it implements the ARX-box Alzette and the associated tweakable block cipher TRAX from the Sparkle-suite and their necessary gates. It is easily expandable to more ciphers.

This library provides high level operations on quantum registers by tracking the individual qubits. Because of this, permutations can be implemented for free. Additionally, the quantum cost of a circuit containing these operations can be automatically computed.

This library is based on Qiskit.

Implemented Gates

These are the quantum gates currently implemented by this project.

Examples

Here are some usage examples for pyqrypto. Checkout the examples folder for more.

Simple Usage

Here is an example on how to use pyqrypto to generate a simple circuit that operates on qubit vectors.

from pyqrypto.register_operations import RegisterCircuit
from qiskit import QuantumRegister

# Create two 4 qubit registers
X1 = QuantumRegister(4, name="X")
Y1 = QuantumRegister(4, name="Y")

# Create a register circuit from registers X and Y.
# A register circuit can operate on quantum registers instead of on individual qubits.
qc = RegisterCircuit(X1, Y1, name="rCircuit")

# Rotate left register X by 3 qubits.
# A register can be seen as a "view" on logical qubits,
# so rotating a register just yields another view on these qubits with swapped indexes.
X2 = qc.ror(X1, 3)

# Let's XOR X2 Y1. The result will be stored in X3.
# Note here that X3 = X2 because xor doesn't modify the view.
X3 = qc.xor(X2, Y1)

# If we print the resulting circuit, we can see the XOR was done on a rotated version of X1.
print(qc.decompose())
#           ┌───┐
# X_0: ─────┤ X ├──────────
#           └─┬─┘┌───┐
# X_1: ───────┼──┤ X ├─────
#             │  └─┬─┘┌───┐
# X_2: ───────┼────┼──┤ X ├
#      ┌───┐  │    │  └─┬─┘
# X_3: ┤ X ├──┼────┼────┼──
#      └─┬─┘  │    │    │
# Y_0: ──■────┼────┼────┼──
#             │    │    │
# Y_1: ───────■────┼────┼──
#                  │    │
# Y_2: ────────────■────┼──
#                       │
# Y_3: ─────────────────■──

The library also provides handy tools to add preparation steps and measurements to a circuit operating on quantum registers. The registers can be initialized to an integer initial value, and measurement gates can be automatically added and the values of the output of the classical registers converted to integers. This allows testing the circuit on real data to make sure the implementation is correct.

Let’s add a preparation and a measurement step to our previous example and simulate it.

Note

You need to install the qiskit-aer extra dependency to use the simulation feature.

from pyqrypto.register_operations import make_circuit, run_circuit
from qiskit import qasm3

# Create a final circuit with measurement and preparation steps
# Let's initialize X1 to 4 and Y1 to 11
# Let's also measure X2 and Y1 at the end of the circuit
# It is possible to measure any QuantumRegistrer that has its qubits in the circuit
# This means we can measure the final state of any "view" of the qubits
# However note that the qubits will be in their final state during the measurement
# If we tried to measure X1, we wouldn't get the initial value of X1
# but the value of X2 left rotated by 3 bits
# This is because the value of X1 was overwritten by the XOR operation.
# This is why it is important to keep track of your registers during operations!
final_circuit = make_circuit(qc, [4, 11], [X1, Y1], [X2, Y1])

# We can print the final circuit
# As you can see the measurements are done on
print(final_circuit)
#       ┌─────────────┐ ┌───────┐   ┌─┐
#  X_0: ┤0            ├─┤1      ├───┤M├──────────────────
#       │             │ │       │   └╥┘┌─┐
#  X_1: ┤1            ├─┤2      ├────╫─┤M├───────────────
#       │  rPrepare 4 │ │       │    ║ └╥┘┌─┐
#  X_2: ┤2            ├─┤3      ├────╫──╫─┤M├────────────
#       │             │ │       │┌─┐ ║  ║ └╥┘
#  X_3: ┤3            ├─┤0      ├┤M├─╫──╫──╫─────────────
#       ├─────────────┴┐│  Rxor │└╥┘ ║  ║  ║ ┌─┐
#  Y_0: ┤0             ├┤4      ├─╫──╫──╫──╫─┤M├─────────
#       │              ││       │ ║  ║  ║  ║ └╥┘┌─┐
#  Y_1: ┤1             ├┤5      ├─╫──╫──╫──╫──╫─┤M├──────
#       │  rPrepare 11 ││       │ ║  ║  ║  ║  ║ └╥┘┌─┐
#  Y_2: ┤2             ├┤6      ├─╫──╫──╫──╫──╫──╫─┤M├───
#       │              ││       │ ║  ║  ║  ║  ║  ║ └╥┘┌─┐
#  Y_3: ┤3             ├┤7      ├─╫──╫──╫──╫──╫──╫──╫─┤M├
#       └──────────────┘└───────┘ ║  ║  ║  ║  ║  ║  ║ └╥┘
# c0: 4/══════════════════════════╩══╩══╩══╩══╬══╬══╬══╬═
#                                 0  1  2  3  ║  ║  ║  ║
# c1: 4/══════════════════════════════════════╩══╩══╩══╩═
#                                             0  1  2  3

# We can generate the QASM of that circuit and save it to a file
# This can be used to run the circuit on an actual quantum computer
with open("circuit.qasm", "w") as f:
    qasm3.dump(final_circuit, f)

# Let's run this circuit in a simulation and gather the result
results = run_circuit(final_circuit)

# We can verify that ror(4, 3)^11 = 3, and that Y1 was unchanged.
print(results)
# [3, 11]

Addition

Here is an example on how to do a 8-bit addition using a ripple-carry adder:

# Let's create two 8 bit quantum registers
X1 = QuantumRegister(8, name="X")
Y = QuantumRegister(8, name="Y")

qc = RegisterCircuit(X1, Y)

# X2 <- X1 + Y
# By default, the adder used is a ripple-carry adder
X2 = qc.add(X1, Y)

# Print the circuit
print(qc.decompose(reps=2))

# Let's test it by adding 74 and 42
final_circuit = make_circuit(qc, [74, 42], [X1, Y], [X2])

result = run_circuit(final_circuit)

# 74+42 = 116
print("Result:", result)

It is also possible to use a carry-lookahead adder to obtain a shallower circuit, at the expense of ancilla qubits:

n = 32
a = random.getrandbits(n)
b = random.getrandbits(n)

print(f"Performing operation {a}+{b}={a+b}")

# Instanciate registers
A = QuantumRegister(n, name="A")
B = QuantumRegister(n, name="B")
# Instanciate enough ancilla qubits
ancillas = AncillaRegister(RegisterDKRSCarryLookaheadAdder.get_num_ancilla_qubits(n))

# Build circuit
qc = RegisterCircuit(A, B, ancillas)
qc.add(A, B, ancillas=ancillas, mode="lookahead")

# Print statistics about the circuit
print("Circuit stats:", qc.stats)

# Add initialization and measurements
final_circuit = make_circuit(qc, [a, b], [A, B], [A])

# Run the circuit
result = run_circuit(final_circuit, method="matrix_product_state")

print("Result:", result[0])

Alzette

Here’s how to use the Alzette gate:

from itertools import chain
from pathlib import Path

from pyqrypto.register_operations import RegisterCircuit, make_circuit, run_circuit
from pyqrypto.sparkle import Alzette, c_alzette
from qiskit import QuantumRegister, qasm3

n = 8
# Create two quantum registers
X = QuantumRegister(n, name="X")
Y = QuantumRegister(n, name="Y")
a = 384973 % 2**n
b = 1238444859 % 2**n
c = 0xB7E15162 % 2**n
print(f"Running alzette_{c}({a}, {b})")


qc = RegisterCircuit(X, Y)

# Add the Alzette get
gate = Alzette(X, Y, c)
qc.append(gate, list(chain(*gate.inputs)))

# Save the QASM to a file
with Path("alzette.qasm").open("w") as f:
    qasm3.dump(qc.decompose(reps=2), f)

final_circuit = make_circuit(qc, [a, b], [X, Y], [X, Y])

# Simulate the circuit
result = run_circuit(final_circuit, method="automatic")

# Print some statistics about the circuit (depth, gate counts and quantum cost)
print("Circuit stats:", qc.stats)
# Make sure the classical result matches the quantum result
print(f"Classical result: {c_alzette(a, b, c, n)}")
print(f"Quantum simulated result: {result}")

TRAX-L

Here’s how to use the TraxlEnc gate:

import random
from itertools import chain
from pathlib import Path

from pyqrypto.register_operations import RegisterCircuit, make_circuit, run_circuit
from pyqrypto.sparkle import TraxlEnc, c_traxl_enc, c_traxl_genkeys
from qiskit import QuantumRegister, qasm3
from qiskit.circuit.quantumregister import AncillaRegister

random.seed(42)

# Create the inputs, tweak and encryption key
n = 256
x = [random.getrandbits(n // 8) for _ in range(4)]
y = [random.getrandbits(n // 8) for _ in range(4)]
tweak = [random.getrandbits(n // 8) for _ in range(4)]
key = [random.getrandbits(n // 8) for _ in range(8)]
print("x:", list(map(hex, x)))
print("y:", list(map(hex, y)))
print("key:", list(map(hex, key)))
print("tweak:", list(map(hex, tweak)))

# Create the quantum registers
X = [QuantumRegister(n // 8, name=f"X{i}") for i in range(4)]
Y = [QuantumRegister(n // 8, name=f"Y{i}") for i in range(4)]
K = [QuantumRegister(n // 8, name=f"K{i}") for i in range(8)]
ancillas = AncillaRegister(TraxlEnc.get_num_ancilla_qubits(n))

# Create the circuit and add the TRAX-L encryption gate
qc = RegisterCircuit(*X, *Y, *K, ancillas)
gate = TraxlEnc(X, Y, K, tweak, ancillas)
qc.append(gate, list(chain(*gate.inputs)))

# Print circuit statistics
print(qc.stats)

# Save the QASM to a file
with Path("traxl.qasm").open("w") as f:
    qasm3.dump(qc.decompose(reps=2), f)

# Add state preparation and measurement
final_circuit = make_circuit(qc, x + y + key, gate.inputs[:-1], gate.outputs[:-1])

# Run the simulation (it might take a while)
result = run_circuit(final_circuit, method="matrix_product_state")

# Print the results
print("Quantum simulated results:")
print(f"quantum_x: {list(map(hex, result[0:4]))}")
print(f"quantum_y: {list(map(hex, result[4:8]))}")
print(f"quantum_last_subkey: {list(map(hex, result[8:16]))}")

# Do the same computation classically to compare the results
subkeys = c_traxl_genkeys(key, n=n // 8)
true_x, true_y = c_traxl_enc(x, y, subkeys, tweak, n=n // 8)
print(f"last_subkeys: {list(map(hex, subkeys[-8::]))}")
print(f"true_x: {list(map(hex, true_x))}")
print(f"true_y: {list(map(hex, true_y))}")

assert true_x == result[0:4] and true_y == result[4:8]