AES-128算法的量子电路实现

Keywords: #技术 #AES #Python #量子算法
Release Date: 2025-05-24

使用 Python 对 AES-128 的量子电路实现进行模拟,量子电路实现与经典实现的不同之处在于,其量子电路使用的是量子门,而非经典实现中的运算门,同时其核心在于比特级运算,这对于以字为运算单位的 AES 算法来说,实现起来并不简单。

量子算法的原理这里就不过多介绍,具体可通过量子算法导论学习。

在实现之前,可以通过 AES-128 算法的经典实现 了解 AES 算法的原理。

下面直接开始算法的实现。

本文基于 pipeline 结构的 NCT(NOT + CNOT + Toffoli)量子电路来实现 AES-128 算法。

  • pipeline 结构:基于 out-place 结构所需量子比特数较大线路深度更低
  • round-in-place 结构:基于 in-place 结构所需量子比特数较少线路深度更高

因为使用 pipeline 结构,所以需要保存每一轮的量子比特。

首先同样是定义一些常量和工具方法。

# 轮常数 省略第一轮
RC = [0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000]

# CNOT NOT Toffoli 三个门电路的模拟实现
def CNOT(qubit_a, qubit_b):
    return qubit_a, qubit_b ^ qubit_a

def NOT(qubit):
    return qubit ^ 1

def TOFF(qubit_a, qubit_b, qubit_c):
    return qubit_a, qubit_b, qubit_c ^ (qubit_a * qubit_b)

# SWAP门的模拟实现 即等于3个CNOT门
def SWAP(qubit_a, qubit_b):
    qubit_a, qubit_b = CNOT(qubit_a, qubit_b) # a = a, b = a ^ b
    qubit_b, qubit_a = CNOT(qubit_b, qubit_a) # b = a ^ b, a = a ^ b ^ a = b
    qubit_a, qubit_b = CNOT(qubit_a, qubit_b) # a = b, b = a ^ b ^ b = a
    return qubit_a, qubit_b # a = b  b = a

# 取消计算 将辅助比特释放
def uncompute():
    pass
"""
工具类
"""
# 将输入的十六进制字符串转换为128比特的量子比特列表
def hex_to_qubits(hex_str):
    # 将十六进制字符串转换为二进制字符串
    bin_str = bin(int(hex_str, 16))[2:].zfill(128)
    # 将二进制字符串转换为量子比特列表
    qubits = [int(bit) for bit in bin_str]
    return qubits

# 将量子比特列表转换为十六进制字符串
def qubits_to_hex(qubits):
    # 将量子比特列表转换为二进制字符串
    bin_str = ''.join([str(qubit) for qubit in qubits])
    # 将二进制字符串转换为十六进制字符串
    hex_str = hex(int(bin_str, 2))[2:].zfill(32)
    return hex_str.upper()

S 盒字节代换实现。

这一部分的实现使用的是Huang Z, Sun S. Synthesizing quantum circuits of aes with lower t-depth and less qubits [C]//Agrawal S, Lin D. Advances in Cryptology– ASIACRYPT 2022. Cham: Springer Nature Switzerland, 2022: 614-644. 给出的使用8比特输入输出和120辅助量子比特的out-place结构。

因为使用out-place结构所以需要取消计算的过程。

代码如下:
# S盒 8比特输入输出和68辅助量子比特 分16组
def s_box(U, T):
    # U输入, T辅助, S输出
    S = [0] * 8
    S[2], S[5] = CNOT(S[2], S[5])
    S[0], S[3] = CNOT(S[0], S[3])
    S[6], S[7] = CNOT(S[6], S[7])
    S[1], S[4] = CNOT(S[1], S[4])
    S[3], S[1] = CNOT(S[3], S[1])
    S[1], S[0] = CNOT(S[1], S[0])
    S[0], S[7] = CNOT(S[0], S[7])
    S[7], S[4] = CNOT(S[7], S[4])
    S[4], S[6] = CNOT(S[4], S[6])
    S[7], S[2] = CNOT(S[7], S[2])
    S[6], S[2] = CNOT(S[6], S[2])
    S[0], S[5] = CNOT(S[0], S[5])
    S[3], S[7] = CNOT(S[3], S[7])
    U[6], U[4] = CNOT(U[6], U[4])
    U[2], U[1] = CNOT(U[2], U[1])
    U[6], T[4] = CNOT(U[6], T[4])
    U[5], T[6] = CNOT(U[5], T[6])
    U[1], T[3] = CNOT(U[1], T[3])
    U[3], T[8] = CNOT(U[3], T[8])
    U[5], T[8] = CNOT(U[5], T[8])
    U[0], U[3] = CNOT(U[0], U[3])
    U[4], U[2] = CNOT(U[4], U[2])
    U[5], U[2] = CNOT(U[5], U[2])
    U[6], U[5] = CNOT(U[6], U[5])
    U[3], U[4] = CNOT(U[3], U[4])
    U[0], T[4] = CNOT(U[0], T[4])
    U[1], T[7] = CNOT(U[1], T[7])
    U[0], T[6] = CNOT(U[0], T[6])
    U[7], U[1] = CNOT(U[7], U[1])
    U[3], T[0] = CNOT(U[3], T[0])
    U[1], U[0] = CNOT(U[1], U[0])
    U[1], U[6] = CNOT(U[1], U[6])
    U[4], T[7] = CNOT(U[4], T[7])
    U[4], T[1] = CNOT(U[4], T[1])
    U[2], T[3] = CNOT(U[2], T[3])
    U[2], T[9] = CNOT(U[2], T[9])
    U[2], T[5] = CNOT(U[2], T[5])
    U[1], U[2] = CNOT(U[1], U[2])
    U[4], T[9] = CNOT(U[4], T[9])
    U[3], T[2] = CNOT(U[3], T[2])
    U[5], T[0] = CNOT(U[5], T[0])
    U[0], U[5] = CNOT(U[0], U[5])
    U[0], U[3] = CNOT(U[0], U[3])
    U[7], U[4] = CNOT(U[7], U[4])
    U[4], U[5], T[10] = TOFF(U[4], U[5], T[10])
    U[3], U[7], T[11] = TOFF(U[3], U[7], T[11])
    U[1], U[6], T[12] = TOFF(U[1], U[6], T[12])
    U[0], U[2], T[13] = TOFF(U[0], U[2], T[13])
    T[0], T[1], T[14] = TOFF(T[0], T[1], T[14])
    T[2], T[3], T[15] = TOFF(T[2], T[3], T[15])
    T[4], T[5], T[16] = TOFF(T[4], T[5], T[16])
    T[6], T[7], T[17] = TOFF(T[6], T[7], T[17])
    T[8], T[9], T[18] = TOFF(T[8], T[9], T[18])
    U[0], T[13] = CNOT(U[0], T[13])
    T[4], T[0] = CNOT(T[4], T[0])
    T[15], T[17] = CNOT(T[15], T[17])
    T[9], T[10] = CNOT(T[9], T[10])
    T[35], T[22] = CNOT(T[35], T[22])
    T[7], T[14] = CNOT(T[7], T[14])
    T[19], U[5] = CNOT(T[19], U[5])
    U[3], T[3] = CNOT(U[3], T[3])
    T[31], T[2] = CNOT(T[31], T[2])
    T[5], T[1] = CNOT(T[5], T[1])
    T[8], T[23] = CNOT(T[8], T[23])
    T[24], T[37] = CNOT(T[24], T[37])
    U[2], U[4] = CNOT(U[2], U[4])
    T[6], T[25] = CNOT(T[6], T[25])
    T[4], U[0] = CNOT(T[4], U[0])
    T[16], T[13] = CNOT(T[16], T[13])
    T[15], T[18] = CNOT(T[15], T[18])
    T[8], T[0] = CNOT(T[8], T[0])
    U[1], T[35] = CNOT(U[1], T[35])
    T[6], T[14] = CNOT(T[6], T[14])
    T[7], U[7] = CNOT(T[7], U[7])
    T[19], T[33] = CNOT(T[19], T[33])
    T[9], T[32] = CNOT(T[9], T[32])
    T[38], T[3] = CNOT(T[38], T[3])
    T[5], T[30] = CNOT(T[5], T[30])
    T[17], T[13] = CNOT(T[17], T[13])
    U[0], U[6] = CNOT(U[0], U[6])
    T[8], T[10] = CNOT(T[8], T[10])
    T[4], T[12] = CNOT(T[4], T[12])
    T[18], T[16] = CNOT(T[18], T[16])
    T[0], T[29] = CNOT(T[0], T[29])
    T[34], T[35] = CNOT(T[34], T[35])
    T[9], U[7] = CNOT(T[9], U[7])
    T[6], U[3] = CNOT(T[6], U[3])
    T[7], T[30] = CNOT(T[7], T[30])
    U[2], T[32] = CNOT(U[2], T[32])
    T[5], T[36] = CNOT(T[5], T[36])
    U[0], U[5] = CNOT(U[0], U[5])
    T[13], U[7] = CNOT(T[13], U[7])
    T[12], T[16] = CNOT(T[12], T[16])
    T[10], T[18] = CNOT(T[10], T[18])
    T[0], U[4] = CNOT(T[0], U[4])
    T[11], T[17] = CNOT(T[11], T[17])
    T[5], T[34] = CNOT(T[5], T[34])
    T[8], U[3] = CNOT(T[8], U[3])
    T[6], T[2] = CNOT(T[6], T[2])
    T[9], T[36] = CNOT(T[9], T[36])
    T[7], T[38] = CNOT(T[7], T[38])
    T[4], T[23] = CNOT(T[4], T[23])
    U[2], T[30] = CNOT(U[2], T[30])
    U[0], T[1] = CNOT(U[0], T[1])
    T[13], U[1] = CNOT(T[13], U[1])
    T[14], T[18] = CNOT(T[14], T[18])
    T[16], T[0] = CNOT(T[16], T[0])
    T[6], U[5] = CNOT(T[6], U[5])
    T[9], T[10] = CNOT(T[9], T[10])
    U[2], T[34] = CNOT(U[2], T[34])
    T[8], T[2] = CNOT(T[8], T[2])
    T[7], U[4] = CNOT(T[7], U[4])
    T[4], T[12] = CNOT(T[4], T[12])
    U[0], T[20] = CNOT(U[0], T[20])
    U[2], T[13] = CNOT(U[2], T[13])
    T[14], T[17] = CNOT(T[14], T[17])
    T[18], T[19] = CNOT(T[18], T[19])
    T[16], U[1] = CNOT(T[16], U[1])
    T[6], T[1] = CNOT(T[6], T[1])
    T[0], U[4] = CNOT(T[0], U[4])
    T[34], T[35] = CNOT(T[34], T[35])
    T[8], T[25] = CNOT(T[8], T[25])
    T[9], T[38] = CNOT(T[9], T[38])
    T[7], T[32] = CNOT(T[7], T[32])
    T[4], U[0] = CNOT(T[4], U[0])
    T[13], T[31] = CNOT(T[13], T[31])
    T[7], T[14] = CNOT(T[7], T[14])
    T[18], T[16] = CNOT(T[18], T[16])
    T[5], T[0] = CNOT(T[5], T[0])
    T[19], U[5] = CNOT(T[19], U[5])
    U[1], T[35] = CNOT(U[1], T[35])
    T[9], T[1] = CNOT(T[9], T[1])
    T[8], T[10] = CNOT(T[8], T[10])
    T[38], T[3] = CNOT(T[38], T[3])
    T[17], T[21] = CNOT(T[17], T[21])
    T[17], T[13] = CNOT(T[17], T[13])
    U[0], U[3] = CNOT(U[0], U[3])
    T[5], T[16] = CNOT(T[5], T[16])
    T[6], T[14] = CNOT(T[6], T[14])
    T[0], T[29] = CNOT(T[0], T[29])
    T[31], T[2] = CNOT(T[31], T[2])
    T[19], T[33] = CNOT(T[19], T[33])
    T[35], T[22] = CNOT(T[35], T[22])
    T[17], T[18] = CNOT(T[17], T[18])
    T[13], T[24] = CNOT(T[13], T[24])
    U[3], T[3] = CNOT(U[3], T[3])
    T[16], T[27] = CNOT(T[16], T[27])
    T[16], T[13] = CNOT(T[16], T[13])
    T[17], U[3] = CNOT(T[17], U[3])
    T[18], T[26] = CNOT(T[18], T[26])
    T[24], T[37] = CNOT(T[24], T[37])
    T[18], U[6] = CNOT(T[18], U[6])
    T[13], T[28] = CNOT(T[13], T[28])
    U[4], U[5], T[39] = TOFF(U[4], U[5], T[39])
    U[3], U[7], T[40] = TOFF(U[3], U[7], T[40])
    U[1], U[6], T[41] = TOFF(U[1], U[6], T[41])
    T[0], T[1], T[42] = TOFF(T[0], T[1], T[42])
    T[2], T[3], T[43] = TOFF(T[2], T[3], T[43])
    T[19], T[20], T[44] = TOFF(T[19], T[20], T[44])
    U[0], T[21], T[45] = TOFF(U[0], T[21], T[45])
    T[22], T[23], T[46] = TOFF(T[22], T[23], T[46])
    T[24], T[25], T[47] = TOFF(T[24], T[25], T[47])
    T[4], T[26], T[48] = TOFF(T[4], T[26], T[48])
    T[6], T[27], T[49] = TOFF(T[6], T[27], T[49])
    T[8], T[28], T[50] = TOFF(T[8], T[28], T[50])
    T[29], T[30], T[51] = TOFF(T[29], T[30], T[51])
    T[31], T[32], T[52] = TOFF(T[31], T[32], T[52])
    T[33], T[34], T[53] = TOFF(T[33], T[34], T[53])
    U[2], T[17], T[54] = TOFF(U[2], T[17], T[54])
    T[35], T[36], T[55] = TOFF(T[35], T[36], T[55])
    T[37], T[38], T[56] = TOFF(T[37], T[38], T[56])
    T[5], T[18], T[57] = TOFF(T[5], T[18], T[57])
    T[7], T[16], T[58] = TOFF(T[7], T[16], T[58])
    T[9], T[13], T[59] = TOFF(T[9], T[13], T[59])
    T[13], T[16] = CNOT(T[13], T[16])
    T[18], T[26] = CNOT(T[18], T[26])
    T[56], T[57] = CNOT(T[56], T[57])
    T[21], T[33] = CNOT(T[21], T[33])
    T[41], T[27] = CNOT(T[41], T[27])
    T[40], T[25] = CNOT(T[40], T[25])
    T[58], T[54] = CNOT(T[58], T[54])
    T[47], T[48] = CNOT(T[47], T[48])
    T[59], T[53] = CNOT(T[59], T[53])
    T[49], T[45] = CNOT(T[49], T[45])
    T[50], T[24] = CNOT(T[50], T[24])
    T[9], T[38] = CNOT(T[9], T[38])
    T[22], T[29] = CNOT(T[22], T[29])
    U[2], T[5] = CNOT(U[2], T[5])
    T[39], T[31] = CNOT(T[39], T[31])
    T[8], T[6] = CNOT(T[8], T[6])
    T[55], T[30] = CNOT(T[55], T[30])
    T[41], T[13] = CNOT(T[41], T[13])
    T[57], T[34] = CNOT(T[57], T[34])
    T[40], T[21] = CNOT(T[40], T[21])
    T[48], T[26] = CNOT(T[48], T[26])
    T[59], T[30] = CNOT(T[59], T[30])
    T[16], T[24] = CNOT(T[16], T[24])
    T[9], T[36] = CNOT(T[9], T[36])
    T[43], T[49] = CNOT(T[43], T[49])
    T[52], T[58] = CNOT(T[52], T[58])
    T[7], T[38] = CNOT(T[7], T[38])
    T[39], T[27] = CNOT(T[39], T[27])
    T[4], T[8] = CNOT(T[4], T[8])
    T[17], T[29] = CNOT(T[17], T[29])
    T[6], T[25] = CNOT(T[6], T[25])
    T[41], T[18] = CNOT(T[41], T[18])
    T[13], T[31] = CNOT(T[13], T[31])
    T[53], T[34] = CNOT(T[53], T[34])
    T[54], T[57] = CNOT(T[54], T[57])
    T[39], T[40] = CNOT(T[39], T[40])
    T[50], T[26] = CNOT(T[50], T[26])
    T[45], T[48] = CNOT(T[45], T[48])
    T[56], T[30] = CNOT(T[56], T[30])
    T[7], T[9] = CNOT(T[7], T[9])
    T[17], T[21] = CNOT(T[17], T[21])
    T[42], T[24] = CNOT(T[42], T[24])
    T[5], T[36] = CNOT(T[5], T[36])
    T[16], T[22] = CNOT(T[16], T[22])
    T[8], T[23] = CNOT(T[8], T[23])
    T[41], T[28] = CNOT(T[41], T[28])
    T[13], T[25] = CNOT(T[13], T[25])
    T[53], T[54] = CNOT(T[53], T[54])
    T[18], T[22] = CNOT(T[18], T[22])
    T[40], T[29] = CNOT(T[40], T[29])
    T[50], T[45] = CNOT(T[50], T[45])
    U[2], T[9] = CNOT(U[2], T[9])
    T[51], T[30] = CNOT(T[51], T[30])
    T[44], T[26] = CNOT(T[44], T[26])
    T[5], T[7] = CNOT(T[5], T[7])
    T[47], T[24] = CNOT(T[47], T[24])
    T[16], T[37] = CNOT(T[16], T[37])
    T[17], T[31] = CNOT(T[17], T[31])
    T[18], T[41] = CNOT(T[18], T[41])
    T[13], T[21] = CNOT(T[13], T[21])
    T[59], T[53] = CNOT(T[59], T[53])
    T[16], T[40] = CNOT(T[16], T[40])
    T[49], T[50] = CNOT(T[49], T[50])
    T[39], T[22] = CNOT(T[39], T[22])
    T[9], T[32] = CNOT(T[9], T[32])
    T[44], T[45] = CNOT(T[44], T[45])
    T[5], T[34] = CNOT(T[5], T[34])
    T[46], T[24] = CNOT(T[46], T[24])
    U[2], T[36] = CNOT(U[2], T[36])
    T[7], T[30] = CNOT(T[7], T[30])
    T[13], T[18] = CNOT(T[13], T[18])
    T[58], T[59] = CNOT(T[58], T[59])
    T[40], T[23] = CNOT(T[40], T[23])
    T[46], T[49] = CNOT(T[46], T[49])
    T[41], T[33] = CNOT(T[41], T[33])
    T[42], T[50] = CNOT(T[42], T[50])
    T[22], T[29] = CNOT(T[22], T[29])
    T[13], T[28] = CNOT(T[13], T[28])
    T[56], T[58] = CNOT(T[56], T[58])
    T[47], T[49] = CNOT(T[47], T[49])
    T[51], T[59] = CNOT(T[51], T[59])
    T[18], T[35] = CNOT(T[18], T[35])
    T[40], T[32] = CNOT(T[40], T[32])
    T[21], T[33] = CNOT(T[21], T[33])
    T[16], T[13] = CNOT(T[16], T[13])
    T[40], T[28] = CNOT(T[40], T[28])
    T[55], T[58] = CNOT(T[55], T[58])
    T[39], T[13] = CNOT(T[39], T[13])
    T[21], T[24], T[35] = TOFF(T[21], T[24], T[35])
    T[22], T[49], T[65] = TOFF(T[22], T[49], T[65])
    T[23], T[50], T[61] = TOFF(T[23], T[50], T[61])
    T[25], T[26], T[36] = TOFF(T[25], T[26], T[36])
    T[27], T[48], T[62] = TOFF(T[27], T[48], T[62])
    T[28], T[45], T[66] = TOFF(T[28], T[45], T[66])
    T[29], T[30], T[37] = TOFF(T[29], T[30], T[37])
    T[31], T[58], T[67] = TOFF(T[31], T[58], T[67])
    T[32], T[59], T[63] = TOFF(T[32], T[59], T[63])
    T[33], T[34], T[38] = TOFF(T[33], T[34], T[38])
    T[13], T[57], T[64] = TOFF(T[13], T[57], T[64])
    T[40], T[54], T[60] = TOFF(T[40], T[54], T[60])
    T[60], S[0] = CNOT(T[60], S[0])
    T[61], S[1] = CNOT(T[61], S[1])
    T[62], S[2] = CNOT(T[62], S[2])
    T[63], S[3] = CNOT(T[63], S[3])
    T[64], S[4] = CNOT(T[64], S[4])
    T[65], S[5] = CNOT(T[65], S[5])
    T[66], S[6] = CNOT(T[66], S[6])
    T[67], S[7] = CNOT(T[67], S[7])
    T[35], S[1] = CNOT(T[35], S[1])
    T[38], S[0] = CNOT(T[38], S[0])
    T[38], S[4] = CNOT(T[38], S[4])
    S[3], S[7] = CNOT(S[3], S[7])
    T[35], S[5] = CNOT(T[35], S[5])
    S[0], S[5] = CNOT(S[0], S[5])
    T[37], S[3] = CNOT(T[37], S[3])
    S[6], S[2] = CNOT(S[6], S[2])
    S[7], S[2] = CNOT(S[7], S[2])
    S[4], S[6] = CNOT(S[4], S[6])
    S[7], S[4] = CNOT(S[7], S[4])
    S[0], S[7] = CNOT(S[0], S[7])
    S[1], S[0] = CNOT(S[1], S[0])
    T[36], S[6] = CNOT(T[36], S[6])
    S[3], S[1] = CNOT(S[3], S[1])
    S[1], S[4] = CNOT(S[1], S[4])
    S[6], S[7] = CNOT(S[6], S[7])
    S[0], S[3] = CNOT(S[0], S[3])
    S[2], S[5] = CNOT(S[2], S[5])
    S[1] = NOT(S[1])
    S[2] = NOT(S[2])
    S[6] = NOT(S[6])
    S[7] = NOT(S[7])
    T[40], T[54], T[60] = TOFF(T[40], T[54], T[60])
    T[13], T[57], T[64] = TOFF(T[13], T[57], T[64])
    T[33], T[34], T[38] = TOFF(T[33], T[34], T[38])
    T[32], T[59], T[63] = TOFF(T[32], T[59], T[63])
    T[31], T[58], T[67] = TOFF(T[31], T[58], T[67])
    T[29], T[30], T[37] = TOFF(T[29], T[30], T[37])
    T[28], T[45], T[66] = TOFF(T[28], T[45], T[66])
    T[27], T[48], T[62] = TOFF(T[27], T[48], T[62])
    T[25], T[26], T[36] = TOFF(T[25], T[26], T[36])
    T[23], T[50], T[61] = TOFF(T[23], T[50], T[61])
    T[22], T[49], T[65] = TOFF(T[22], T[49], T[65])
    T[21], T[24], T[35] = TOFF(T[21], T[24], T[35])
    T[39], T[13] = CNOT(T[39], T[13])
    T[55], T[58] = CNOT(T[55], T[58])
    T[40], T[28] = CNOT(T[40], T[28])
    T[16], T[13] = CNOT(T[16], T[13])
    T[21], T[33] = CNOT(T[21], T[33])
    T[40], T[32] = CNOT(T[40], T[32])
    T[18], T[35] = CNOT(T[18], T[35])
    T[51], T[59] = CNOT(T[51], T[59])
    T[47], T[49] = CNOT(T[47], T[49])
    T[56], T[58] = CNOT(T[56], T[58])
    T[13], T[28] = CNOT(T[13], T[28])
    T[22], T[29] = CNOT(T[22], T[29])
    T[42], T[50] = CNOT(T[42], T[50])
    T[41], T[33] = CNOT(T[41], T[33])
    T[46], T[49] = CNOT(T[46], T[49])
    T[40], T[23] = CNOT(T[40], T[23])
    T[58], T[59] = CNOT(T[58], T[59])
    T[13], T[18] = CNOT(T[13], T[18])
    T[7], T[30] = CNOT(T[7], T[30])
    U[2], T[36] = CNOT(U[2], T[36])
    T[46], T[24] = CNOT(T[46], T[24])
    T[5], T[34] = CNOT(T[5], T[34])
    T[44], T[45] = CNOT(T[44], T[45])
    T[9], T[32] = CNOT(T[9], T[32])
    T[39], T[22] = CNOT(T[39], T[22])
    T[49], T[50] = CNOT(T[49], T[50])
    T[16], T[40] = CNOT(T[16], T[40])
    T[59], T[53] = CNOT(T[59], T[53])
    T[13], T[21] = CNOT(T[13], T[21])
    T[18], T[41] = CNOT(T[18], T[41])
    T[17], T[31] = CNOT(T[17], T[31])
    T[16], T[37] = CNOT(T[16], T[37])
    T[47], T[24] = CNOT(T[47], T[24])
    T[5], T[7] = CNOT(T[5], T[7])
    T[44], T[26] = CNOT(T[44], T[26])
    T[51], T[30] = CNOT(T[51], T[30])
    U[2], T[9] = CNOT(U[2], T[9])
    T[50], T[45] = CNOT(T[50], T[45])
    T[40], T[29] = CNOT(T[40], T[29])
    T[18], T[22] = CNOT(T[18], T[22])
    T[53], T[54] = CNOT(T[53], T[54])
    T[13], T[25] = CNOT(T[13], T[25])
    T[41], T[28] = CNOT(T[41], T[28])
    T[8], T[23] = CNOT(T[8], T[23])
    T[16], T[22] = CNOT(T[16], T[22])
    T[5], T[36] = CNOT(T[5], T[36])
    T[42], T[24] = CNOT(T[42], T[24])
    T[17], T[21] = CNOT(T[17], T[21])
    T[7], T[9] = CNOT(T[7], T[9])
    T[56], T[30] = CNOT(T[56], T[30])
    T[45], T[48] = CNOT(T[45], T[48])
    T[50], T[26] = CNOT(T[50], T[26])
    T[39], T[40] = CNOT(T[39], T[40])
    T[54], T[57] = CNOT(T[54], T[57])
    T[53], T[34] = CNOT(T[53], T[34])
    T[13], T[31] = CNOT(T[13], T[31])
    T[41], T[18] = CNOT(T[41], T[18])
    T[6], T[25] = CNOT(T[6], T[25])
    T[17], T[29] = CNOT(T[17], T[29])
    T[4], T[8] = CNOT(T[4], T[8])
    T[39], T[27] = CNOT(T[39], T[27])
    T[7], T[38] = CNOT(T[7], T[38])
    T[52], T[58] = CNOT(T[52], T[58])
    T[43], T[49] = CNOT(T[43], T[49])
    T[9], T[36] = CNOT(T[9], T[36])
    T[16], T[24] = CNOT(T[16], T[24])
    T[59], T[30] = CNOT(T[59], T[30])
    T[48], T[26] = CNOT(T[48], T[26])
    T[40], T[21] = CNOT(T[40], T[21])
    T[57], T[34] = CNOT(T[57], T[34])
    T[41], T[13] = CNOT(T[41], T[13])
    T[55], T[30] = CNOT(T[55], T[30])
    T[8], T[6] = CNOT(T[8], T[6])
    T[39], T[31] = CNOT(T[39], T[31])
    U[2], T[5] = CNOT(U[2], T[5])
    T[22], T[29] = CNOT(T[22], T[29])
    T[9], T[38] = CNOT(T[9], T[38])
    T[50], T[24] = CNOT(T[50], T[24])
    T[49], T[45] = CNOT(T[49], T[45])
    T[59], T[53] = CNOT(T[59], T[53])
    T[47], T[48] = CNOT(T[47], T[48])
    T[58], T[54] = CNOT(T[58], T[54])
    T[40], T[25] = CNOT(T[40], T[25])
    T[41], T[27] = CNOT(T[41], T[27])
    T[21], T[33] = CNOT(T[21], T[33])
    T[56], T[57] = CNOT(T[56], T[57])
    T[18], T[26] = CNOT(T[18], T[26])
    T[13], T[16] = CNOT(T[13], T[16])
    T[9], T[13], T[59] = TOFF(T[9], T[13], T[59])
    T[7], T[16], T[58] = TOFF(T[7], T[16], T[58])
    T[5], T[18], T[57] = TOFF(T[5], T[18], T[57])
    T[37], T[38], T[56] = TOFF(T[37], T[38], T[56])
    T[35], T[36], T[55] = TOFF(T[35], T[36], T[55])
    U[2], T[17], T[54] = TOFF(U[2], T[17], T[54])
    T[33], T[34], T[53] = TOFF(T[33], T[34], T[53])
    T[31], T[32], T[52] = TOFF(T[31], T[32], T[52])
    T[29], T[30], T[51] = TOFF(T[29], T[30], T[51])
    T[8], T[28], T[50] = TOFF(T[8], T[28], T[50])
    T[6], T[27], T[49] = TOFF(T[6], T[27], T[49])
    T[4], T[26], T[48] = TOFF(T[4], T[26], T[48])
    T[24], T[25], T[47] = TOFF(T[24], T[25], T[47])
    T[22], T[23], T[46] = TOFF(T[22], T[23], T[46])
    U[0], T[21], T[45] = TOFF(U[0], T[21], T[45])
    T[19], T[20], T[44] = TOFF(T[19], T[20], T[44])
    T[2], T[3], T[43] = TOFF(T[2], T[3], T[43])
    T[0], T[1], T[42] = TOFF(T[0], T[1], T[42])
    U[1], U[6], T[41] = TOFF(U[1], U[6], T[41])
    U[3], U[7], T[40] = TOFF(U[3], U[7], T[40])
    U[4], U[5], T[39] = TOFF(U[4], U[5], T[39])
    T[13], T[28] = CNOT(T[13], T[28])
    T[18], U[6] = CNOT(T[18], U[6])
    T[24], T[37] = CNOT(T[24], T[37])
    T[18], T[26] = CNOT(T[18], T[26])
    T[17], U[3] = CNOT(T[17], U[3])
    T[16], T[13] = CNOT(T[16], T[13])
    T[16], T[27] = CNOT(T[16], T[27])
    U[3], T[3] = CNOT(U[3], T[3])
    T[13], T[24] = CNOT(T[13], T[24])
    T[17], T[18] = CNOT(T[17], T[18])
    T[35], T[22] = CNOT(T[35], T[22])
    T[19], T[33] = CNOT(T[19], T[33])
    T[31], T[2] = CNOT(T[31], T[2])
    T[0], T[29] = CNOT(T[0], T[29])
    T[6], T[14] = CNOT(T[6], T[14])
    T[5], T[16] = CNOT(T[5], T[16])
    U[0], U[3] = CNOT(U[0], U[3])
    T[17], T[13] = CNOT(T[17], T[13])
    T[17], T[21] = CNOT(T[17], T[21])
    T[38], T[3] = CNOT(T[38], T[3])
    T[8], T[10] = CNOT(T[8], T[10])
    T[9], T[1] = CNOT(T[9], T[1])
    U[1], T[35] = CNOT(U[1], T[35])
    T[19], U[5] = CNOT(T[19], U[5])
    T[5], T[0] = CNOT(T[5], T[0])
    T[18], T[16] = CNOT(T[18], T[16])
    T[7], T[14] = CNOT(T[7], T[14])
    T[13], T[31] = CNOT(T[13], T[31])
    T[4], U[0] = CNOT(T[4], U[0])
    T[7], T[32] = CNOT(T[7], T[32])
    T[9], T[38] = CNOT(T[9], T[38])
    T[8], T[25] = CNOT(T[8], T[25])
    T[34], T[35] = CNOT(T[34], T[35])
    T[0], U[4] = CNOT(T[0], U[4])
    T[6], T[1] = CNOT(T[6], T[1])
    T[16], U[1] = CNOT(T[16], U[1])
    T[18], T[19] = CNOT(T[18], T[19])
    T[14], T[17] = CNOT(T[14], T[17])
    U[2], T[13] = CNOT(U[2], T[13])
    U[0], T[20] = CNOT(U[0], T[20])
    T[4], T[12] = CNOT(T[4], T[12])
    T[7], U[4] = CNOT(T[7], U[4])
    T[8], T[2] = CNOT(T[8], T[2])
    U[2], T[34] = CNOT(U[2], T[34])
    T[9], T[10] = CNOT(T[9], T[10])
    T[6], U[5] = CNOT(T[6], U[5])
    T[16], T[0] = CNOT(T[16], T[0])
    T[14], T[18] = CNOT(T[14], T[18])
    T[13], U[1] = CNOT(T[13], U[1])
    U[0], T[1] = CNOT(U[0], T[1])
    U[2], T[30] = CNOT(U[2], T[30])
    T[4], T[23] = CNOT(T[4], T[23])
    T[7], T[38] = CNOT(T[7], T[38])
    T[9], T[36] = CNOT(T[9], T[36])
    T[6], T[2] = CNOT(T[6], T[2])
    T[8], U[3] = CNOT(T[8], U[3])
    T[5], T[34] = CNOT(T[5], T[34])
    T[11], T[17] = CNOT(T[11], T[17])
    T[0], U[4] = CNOT(T[0], U[4])
    T[10], T[18] = CNOT(T[10], T[18])
    T[12], T[16] = CNOT(T[12], T[16])
    T[13], U[7] = CNOT(T[13], U[7])
    U[0], U[5] = CNOT(U[0], U[5])
    T[5], T[36] = CNOT(T[5], T[36])
    U[2], T[32] = CNOT(U[2], T[32])
    T[7], T[30] = CNOT(T[7], T[30])
    T[6], U[3] = CNOT(T[6], U[3])
    T[9], U[7] = CNOT(T[9], U[7])
    T[34], T[35] = CNOT(T[34], T[35])
    T[0], T[29] = CNOT(T[0], T[29])
    T[18], T[16] = CNOT(T[18], T[16])
    T[4], T[12] = CNOT(T[4], T[12])
    T[8], T[10] = CNOT(T[8], T[10])
    U[0], U[6] = CNOT(U[0], U[6])
    T[17], T[13] = CNOT(T[17], T[13])
    T[5], T[30] = CNOT(T[5], T[30])
    T[38], T[3] = CNOT(T[38], T[3])
    T[9], T[32] = CNOT(T[9], T[32])
    T[19], T[33] = CNOT(T[19], T[33])
    T[7], U[7] = CNOT(T[7], U[7])
    T[6], T[14] = CNOT(T[6], T[14])
    U[1], T[35] = CNOT(U[1], T[35])
    T[8], T[0] = CNOT(T[8], T[0])
    T[15], T[18] = CNOT(T[15], T[18])
    T[16], T[13] = CNOT(T[16], T[13])
    T[4], U[0] = CNOT(T[4], U[0])
    T[6], T[25] = CNOT(T[6], T[25])
    U[2], U[4] = CNOT(U[2], U[4])
    T[24], T[37] = CNOT(T[24], T[37])
    T[8], T[23] = CNOT(T[8], T[23])
    T[5], T[1] = CNOT(T[5], T[1])
    T[31], T[2] = CNOT(T[31], T[2])
    U[3], T[3] = CNOT(U[3], T[3])
    T[19], U[5] = CNOT(T[19], U[5])
    T[7], T[14] = CNOT(T[7], T[14])
    T[35], T[22] = CNOT(T[35], T[22])
    T[9], T[10] = CNOT(T[9], T[10])
    T[15], T[17] = CNOT(T[15], T[17])
    T[4], T[0] = CNOT(T[4], T[0])
    U[0], T[13] = CNOT(U[0], T[13])
    T[8], T[9], T[18] = TOFF(T[8], T[9], T[18])
    T[6], T[7], T[17] = TOFF(T[6], T[7], T[17])
    T[4], T[5], T[16] = TOFF(T[4], T[5], T[16])
    T[2], T[3], T[15] = TOFF(T[2], T[3], T[15])
    T[0], T[1], T[14] = TOFF(T[0], T[1], T[14])
    U[0], U[2], T[13] = TOFF(U[0], U[2], T[13])
    U[1], U[6], T[12] = TOFF(U[1], U[6], T[12])
    U[3], U[7], T[11] = TOFF(U[3], U[7], T[11])
    U[4], U[5], T[10] = TOFF(U[4], U[5], T[10])
    U[7], U[4] = CNOT(U[7], U[4])
    U[0], U[3] = CNOT(U[0], U[3])
    U[0], U[5] = CNOT(U[0], U[5])
    U[5], T[0] = CNOT(U[5], T[0])
    U[3], T[2] = CNOT(U[3], T[2])
    U[4], T[9] = CNOT(U[4], T[9])
    U[1], U[2] = CNOT(U[1], U[2])
    U[2], T[5] = CNOT(U[2], T[5])
    U[2], T[9] = CNOT(U[2], T[9])
    U[2], T[3] = CNOT(U[2], T[3])
    U[4], T[1] = CNOT(U[4], T[1])
    U[4], T[7] = CNOT(U[4], T[7])
    U[1], U[6] = CNOT(U[1], U[6])
    U[1], U[0] = CNOT(U[1], U[0])
    U[3], T[0] = CNOT(U[3], T[0])
    U[7], U[1] = CNOT(U[7], U[1])
    U[0], T[6] = CNOT(U[0], T[6])
    U[1], T[7] = CNOT(U[1], T[7])
    U[0], T[4] = CNOT(U[0], T[4])
    U[3], U[4] = CNOT(U[3], U[4])
    U[6], U[5] = CNOT(U[6], U[5])
    U[5], U[2] = CNOT(U[5], U[2])
    U[4], U[2] = CNOT(U[4], U[2])
    U[0], U[3] = CNOT(U[0], U[3])
    U[5], T[8] = CNOT(U[5], T[8])
    U[3], T[8] = CNOT(U[3], T[8])
    U[1], T[3] = CNOT(U[1], T[3])
    U[5], T[6] = CNOT(U[5], T[6])
    U[6], T[4] = CNOT(U[6], T[4])
    U[2], U[1] = CNOT(U[2], U[1])
    U[6], U[4] = CNOT(U[6], U[4])
    return S

由于比特运算,所以 S 盒代码代码很长。


密钥扩展实现。

这一部分实现使用的是Jaques S, Naehrig M, Roetteler M, et al. Implementing grover oracles for quantum key search on aes and lowmc [J]. 2019. 给出的in-place结构。

# 密钥扩展 生成11轮子密钥
def key_expansion(key_qubits):
    round_keys = [[] for _ in range(11)]  # 创建11轮子密钥的二维列表
    round_keys[0] = key_qubits.copy()  # 第一轮子密钥
    # 剩下10轮子密钥 循环生成
    for i in range(10):
        w0 = key_qubits[:32]
        w1 = key_qubits[32:64]
        w2 = key_qubits[64:96]
        w3 = key_qubits[96:128]
        old_w3 = w3.copy()
        # w3 循环左移1位 用swap实现
        for j in range(8): 
            w3[j], w3[j+8] = SWAP(w3[j], w3[j+8])
            w3[j+8], w3[j+16] = SWAP(w3[j+8], w3[j+16])
            w3[j+16], w3[j+24] = SWAP(w3[j+16], w3[j+24])
        # S盒 字节代换
        T = [0] * 68 * 4 # 可通过取消计算 减少比特消耗
        for j in range(4):
            w3[8*j:8*j+8] = s_box(w3[8*j:8*j+8], T[68*j:68*j+68])
        # w3与RC逐位异或
        for j in range(32):
            if (RC[i] >> j) & 1 == 1: # 手动判断
                w3[31-j] = NOT(w3[31-j])
        # 异或生成w4 w5 w6 w7
        w4 = [0] * 32
        w5 = [0] * 32
        w6 = [0] * 32
        w7 = [0] * 32
        for j in range(32):
            _, w4[j] = CNOT(w0[j], w3[j])
            _, w5[j] = CNOT(w1[j], w4[j])
            _, w6[j] = CNOT(w2[j], w5[j])
            _, w7[j] = CNOT(old_w3[j], w6[j])
        key_qubits = w4 + w5 + w6 + w7
        round_keys[i+1] = key_qubits.copy()  # 存储当前轮次的密钥
    return round_keys

行移位实现。

这一步可以使用SWAP门实现,即等于3个CNOT门。后续也可以通过“换线”等高级操作来实现。

使用比特表示 AES 的运算矩阵,如下所示。

0-7   32-39  64-71  96-103
8-15  40-47  72-79  104-111
16-23 48-55  80-87  112-119
24-31 56-63  88-95  120-127
# 行移位 输入128bit
def shift_rows(X):
    # 104-111 与 8-15 交换, 8-15 与 40-47 交换, 40-47 与 72-79 交换
    for i in range(8):
        X[104+i], X[8+i] = SWAP(X[104+i], X[8+i])
        X[8+i], X[40+i] = SWAP(X[8+i], X[40+i])
        X[40+i], X[72+i] = SWAP(X[40+i], X[72+i])
    # 16-23 与 80-87 交换, 48-55 与 112-119 交换
    for i in range(8):
        X[16+i], X[80+i] = SWAP(X[16+i], X[80+i])
        X[48+i], X[112+i] = SWAP(X[48+i], X[112+i])
    # 24-31 与 120-127 交换, 120-127 与 88-95 交换, 88-95 与 56-63 交换
    for i in range(8):
        X[24+i], X[120+i] = SWAP(X[24+i], X[120+i])
        X[120+i], X[88+i] = SWAP(X[120+i], X[88+i])
        X[88+i], X[56+i] = SWAP(X[88+i], X[56+i])
    return X

列混淆实现。

同样使用的是Huang论文中的实现方法。

代码如下:
# 列混淆 输入32bit 分4组
def mix_columns(X):
    X[7], X[23] = CNOT(X[7], X[23])
    X[6], X[22] = CNOT(X[6], X[22])
    X[5], X[21] = CNOT(X[5], X[21])
    X[4], X[20] = CNOT(X[4], X[20])
    X[3], X[19] = CNOT(X[3], X[19])
    X[2], X[18] = CNOT(X[2], X[18])
    X[1], X[17] = CNOT(X[1], X[17])
    X[0], X[16] = CNOT(X[0], X[16])
    X[15], X[31] = CNOT(X[15], X[31])
    X[14], X[30] = CNOT(X[14], X[30])
    X[13], X[29] = CNOT(X[13], X[29])
    X[12], X[28] = CNOT(X[12], X[28])
    X[11], X[27] = CNOT(X[11], X[27])
    X[10], X[26] = CNOT(X[10], X[26])
    X[9], X[25] = CNOT(X[9], X[25])
    X[8], X[24] = CNOT(X[8], X[24])
    X[7], X[15] = CNOT(X[7], X[15])
    X[6], X[14] = CNOT(X[6], X[14])
    X[5], X[13] = CNOT(X[5], X[13])
    X[4], X[12] = CNOT(X[4], X[12])
    X[3], X[11] = CNOT(X[3], X[11])
    X[2], X[10] = CNOT(X[2], X[10])
    X[1], X[9] = CNOT(X[1], X[9])
    X[0], X[8] = CNOT(X[0], X[8])
    X[23], X[31] = CNOT(X[23], X[31])
    X[22], X[30] = CNOT(X[22], X[30])
    X[21], X[29] = CNOT(X[21], X[29])
    X[20], X[28] = CNOT(X[20], X[28])
    X[19], X[27] = CNOT(X[19], X[27])
    X[18], X[26] = CNOT(X[18], X[26])
    X[17], X[25] = CNOT(X[17], X[25])
    X[16], X[24] = CNOT(X[16], X[24])
    X[8], X[3] = CNOT(X[8], X[3])
    X[8], X[4] = CNOT(X[8], X[4])
    X[8], X[6] = CNOT(X[8], X[6])
    X[15], X[6] = CNOT(X[15], X[6])
    X[14], X[5] = CNOT(X[14], X[5])
    X[13], X[4] = CNOT(X[13], X[4])
    X[12], X[3] = CNOT(X[12], X[3])
    X[11], X[2] = CNOT(X[11], X[2])
    X[10], X[1] = CNOT(X[10], X[1])
    X[9], X[0] = CNOT(X[9], X[0])
    X[8], X[7] = CNOT(X[8], X[7])
    X[23], X[14] = CNOT(X[23], X[14])
    X[22], X[13] = CNOT(X[22], X[13])
    X[21], X[12] = CNOT(X[21], X[12])
    X[20], X[11] = CNOT(X[20], X[11])
    X[19], X[10] = CNOT(X[19], X[10])
    X[18], X[9] = CNOT(X[18], X[9])
    X[17], X[8] = CNOT(X[17], X[8])
    X[16], X[15] = CNOT(X[16], X[15])
    X[31], X[7] = CNOT(X[31], X[7])
    X[30], X[6] = CNOT(X[30], X[6])
    X[29], X[5] = CNOT(X[29], X[5])
    X[28], X[4] = CNOT(X[28], X[4])
    X[27], X[3] = CNOT(X[27], X[3])
    X[26], X[2] = CNOT(X[26], X[2])
    X[25], X[1] = CNOT(X[25], X[1])
    X[24], X[0] = CNOT(X[24], X[0])
    X[24], X[22] = CNOT(X[24], X[22])
    X[16], X[14] = CNOT(X[16], X[14])
    X[24], X[20] = CNOT(X[24], X[20])
    X[16], X[12] = CNOT(X[16], X[12])
    X[24], X[19] = CNOT(X[24], X[19])
    X[16], X[11] = CNOT(X[16], X[11])
    X[31], X[22] = CNOT(X[31], X[22])
    X[30], X[21] = CNOT(X[30], X[21])
    X[29], X[20] = CNOT(X[29], X[20])
    X[28], X[19] = CNOT(X[28], X[19])
    X[27], X[18] = CNOT(X[27], X[18])
    X[26], X[17] = CNOT(X[26], X[17])
    X[25], X[16] = CNOT(X[25], X[16])
    X[24], X[23] = CNOT(X[24], X[23])
    X[7], X[23] = CNOT(X[7], X[23])
    X[6], X[22] = CNOT(X[6], X[22])
    X[5], X[21] = CNOT(X[5], X[21])
    X[4], X[20] = CNOT(X[4], X[20])
    X[3], X[19] = CNOT(X[3], X[19])
    X[2], X[18] = CNOT(X[2], X[18])
    X[1], X[17] = CNOT(X[1], X[17])
    X[0], X[16] = CNOT(X[0], X[16])
    X[15], X[31] = CNOT(X[15], X[31])
    X[14], X[30] = CNOT(X[14], X[30])
    X[13], X[29] = CNOT(X[13], X[29])
    X[12], X[28] = CNOT(X[12], X[28])
    X[11], X[27] = CNOT(X[11], X[27])
    X[10], X[26] = CNOT(X[10], X[26])
    X[9], X[25] = CNOT(X[9], X[25])
    X[8], X[24] = CNOT(X[8], X[24])
    X[7], X[15] = CNOT(X[7], X[15])
    X[6], X[14] = CNOT(X[6], X[14])
    X[5], X[13] = CNOT(X[5], X[13])
    X[4], X[12] = CNOT(X[4], X[12])
    X[3], X[11] = CNOT(X[3], X[11])
    X[2], X[10] = CNOT(X[2], X[10])
    X[1], X[9] = CNOT(X[1], X[9])
    X[0], X[8] = CNOT(X[0], X[8])
    X[23], X[31] = CNOT(X[23], X[31])
    X[22], X[30] = CNOT(X[22], X[30])
    X[21], X[29] = CNOT(X[21], X[29])
    X[20], X[28] = CNOT(X[20], X[28])
    X[19], X[27] = CNOT(X[19], X[27])
    X[18], X[26] = CNOT(X[18], X[26])
    X[17], X[25] = CNOT(X[17], X[25])
    X[16], X[24] = CNOT(X[16], X[24])
    return X

轮密钥加实现。

这一步只需要 CNOT 门就可以实现。

# 轮密钥加
def add_round_key(state_qubits, key_qubits):
    for i in range(128):
        _, state_qubits[i] = CNOT(key_qubits[i], state_qubits[i])
    return state_qubits

最后就是加密函数和测试代码。

# 加密
def encrypt(plaintext_qubits, key_qubits):
    round_keys = key_expansion(key_qubits)
    # R0
    state_qubits = add_round_key(plaintext_qubits, round_keys[0])
    # R1 - R10
    for i in range(1, 11):
        # 字节代换
        T = [0] * 68 * 16
        for j in range(16):
            state_qubits[8*j:8*j+8] = s_box(state_qubits[8*j:8*j+8], T[68*j:68*j+68])
        # 行移位
        state_qubits = shift_rows(state_qubits)
        # 列混淆
        if i != 10:
            for j in range(4):
                state_qubits[32*j:32*j+32] = mix_columns(state_qubits[32*j:32*j+32])
        # 轮密钥加
        state_qubits = add_round_key(state_qubits, round_keys[i])
    return state_qubits
"""
加密测试用例
"""
def encrypt_test():
    try:
        # test case 1
        plaintext = hex_to_qubits("00112233445566778899AABBCCDDEEFF")
        key = hex_to_qubits("2B7E151628AED2A6ABF7158809CF4F3C")
        ciphertext = encrypt(plaintext, key)
        assert qubits_to_hex(ciphertext) == "8DF4E9AAC5C7573A27D8D055D6E4D64B"
        # test case 2
        plaintext = hex_to_qubits("34F61A19C754110DA892362BAC078B99")
        key = hex_to_qubits("0123456789ABCDEF0123456789ABCDEF")
        ciphertext = encrypt(plaintext, key)
        assert qubits_to_hex(ciphertext) == "5CFB2FD936BB4F0E372A8246044183E2"
        # test case 3
        plaintext = hex_to_qubits("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
        key = hex_to_qubits("11111111111111111111111111111111")
        ciphertext = encrypt(plaintext, key)
        assert qubits_to_hex(ciphertext) == "24E08A84E6D1C9FD104A2BEB32D783D5"
        # test case 4
        plaintext = hex_to_qubits("12345678123456781234567812345678")
        key = hex_to_qubits("12345678123456781234567812345678")
        ciphertext = encrypt(plaintext, key)
        assert qubits_to_hex(ciphertext) == "D7EEEE18C420FAF0DC7DB5CA73A2B817"
        # test case 5
        plaintext = hex_to_qubits("3243F6A8885A308D313198A2E0370734")
        key = hex_to_qubits("2B7E151628AED2A6ABF7158809CF4F3C")
        ciphertext = encrypt(plaintext, key)
        assert qubits_to_hex(ciphertext) == "3925841D02DC09FBDC118597196A0B32"
    except AssertionError:
        print("X encrypt test failed X")
    else:
        print("√ encrypt test passed √")