Demo 1:Shor 破 RSA

第 16 章 译码与量子密码分析 · 本章演示 · 代码演示总索引

可视化:单子图 —— 3+3 qubit QPE 估 r=4 + GCD

对应教程:第 16 章 译码与量子密码分析 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"

实现要点

核心:Shor 算法把整数分解归约为周期寻找。给定 N = 15,随机选 a = 7, f(x) = 7^x mod 15 以 r = 4 为周期。量子部分用相位估计 (QPE) 提取 s/r 的足够精确二进近似;经典后处理通过连分数展开恢复 r,再用 gcd(a^(r/2) ± 1, N) 得到非平凡因子。本 demo 用 uniqc 的 qpe_circuit helper 构造周期 4 的酉算子 U(在 |000⟩-|011⟩ 子空间上的本征值为 e^{2πi s/4}, s=0,1,2,3),跑 3-比特 QPE,测出 phase s/4,然后 连分数得到 r = 4,最后 gcd(7^2 ± 1, 15) = 3, 5 恢复因子。

完整源码

demos/ch16-cryptanalysis-coding/01_shor_rsa.py
  1"""
  2ch16-cryptanalysis-coding demo 1: Shor's algorithm factoring N = 15 = 3 x 5
  3
  4对应教程:chenzhaoyun.com/quantum-tutorial/ch16-cryptanalysis-coding/quantum-cryptanalysis.html
  5核心:Shor 算法把整数分解归约为周期寻找。给定 N = 15,随机选 a = 7,
  6     f(x) = 7^x mod 15 以 r = 4 为周期。量子部分用相位估计 (QPE) 提取
  7     s/r 的足够精确二进近似;经典后处理通过连分数展开恢复 r,再用
  8     gcd(a^(r/2) ± 1, N) 得到非平凡因子。本 demo 用 uniqc 的 qpe_circuit
  9     helper 构造周期 4 的酉算子 U(在 |000⟩-|011⟩ 子空间上的本征值为
 10     e^{2πi s/4}, s=0,1,2,3),跑 3-比特 QPE,测出 phase s/4,然后
 11     连分数得到 r = 4,最后 gcd(7^2 ± 1, 15) = 3, 5 恢复因子。
 12
 13依赖:pip install unified-quantum[visualization]
 14运行:python demos/ch16-cryptanalysis-coding/01_shor_rsa.py
 15"""
 16from __future__ import annotations
 17
 18from fractions import Fraction
 19from math import gcd
 20from pathlib import Path
 21
 22import matplotlib
 23matplotlib.use("Agg")
 24import matplotlib.pyplot as plt
 25import numpy as np
 26
 27from uniqc import Circuit, submit_task, wait_for_result
 28from uniqc.algorithms.core.circuits.qpe import qpe_circuit
 29
 30OUT = Path(__file__).parent
 31N_TARGET = 15
 32A_BASE = 7
 33N_PRECISION = 3  # 8 buckets; enough to resolve r = 4
 34N_SYSTEM = 3     # 3-qubit system with |000>,|001>,|010>,|011> as the r=4 eigenspace
 35SHOTS = 1500
 36
 37
 38# === 1. Classical pre-check: verify period of f(x) = 7^x mod 15 ===
 39def classical_period(a: int, n: int, max_x: int = 32) -> int:
 40    for r in range(1, max_x + 1):
 41        if pow(a, r, n) == 1:
 42            return r
 43    return -1
 44
 45
 46def classical_powers(a: int, n: int, count: int) -> list[int]:
 47    return [pow(a, x, n) for x in range(count)]
 48
 49
 50powers = classical_powers(A_BASE, N_TARGET, 9)
 51true_r = classical_period(A_BASE, N_TARGET)
 52print("ch16-cryptanalysis-coding-demo-1: Shor for N = 15 = 3 x 5")
 53print(f"  classical pre-check: f(x) = {A_BASE}^x mod {N_TARGET}")
 54print(f"    first 9 powers: {powers}")
 55print(f"    detected period r = {true_r}")
 56
 57
 58# === 2. Build QPE circuit for the modular-exponentiation unitary U ===
 59# A textbook Shor with N = 15, a = 7 needs U_7 acting on 4 qubits. We model it
 60# with a 3-qubit diagonal unitary U whose |000>,|001>,|010>,|011> subspace
 61# has eigenvalues exactly e^{2πi s/4} for s = 0, 1, 2, 3. This gives the same
 62# period-4 phase structure as the real 7^x mod 15 modular exponentiation on
 63# its 4-cycle eigenspace.
 64#   RZ(π/2) on q0  ->  contributes α to |q0=1> states
 65#   RZ(π) on q1    ->  contributes β to |q1=1> states
 66#   RZ(-3π/2) on q2 ->  sets the global phase so the 4 target phases match {0, π/2, π, 3π/2}
 67unitary = Circuit()
 68unitary.rz(0, np.pi / 2.0)
 69unitary.rz(1, np.pi)
 70unitary.rz(2, -3.0 * np.pi / 2.0)
 71
 72state_prep = Circuit()
 73state_prep.h(0); state_prep.h(1); state_prep.h(2)  # |+++>
 74
 75
 76# === 3. Submit QPE and collect phase-register samples ===
 77qpe = qpe_circuit(
 78    n_precision=N_PRECISION,
 79    unitary_circuit=unitary,
 80    state_prep=state_prep,
 81    measure=True,
 82)
 83
 84task_id = submit_task(circuit=qpe, backend="dummy:local:simulator", shots=SHOTS)
 85result = wait_for_result(task_id)
 86counts = result.counts
 87total = sum(int(v) for v in counts.values())
 88
 89
 90# === 4. Convert phase-register samples to integer s in [0, 2^N) ===
 91def bs_to_int(bs: str, n: int) -> int:
 92    return int(bs, 2) if len(bs) == n else int(bs[-n:], 2)
 93
 94
 95n_buckets = 1 << N_PRECISION
 96s_hist = np.zeros(n_buckets, dtype=np.int64)
 97for bs, cnt in counts.items():
 98    s_hist[bs_to_int(bs, N_PRECISION)] += int(cnt)
 99s_probs = s_hist / max(total, 1)
100
101
102# === 5. Continued-fraction post-processing: each s -> fraction s / 2^N -> r ===
103def fraction_from_phase(s: int, n: int) -> Fraction:
104    return Fraction(s, 1 << n).limit_denominator(64)
105
106
107# Expected theoretical phases s/r for r = 4 with N_PRECISION bits
108expected_s = [0, 2, 4, 6]   # s/8 = 0, 1/4, 1/2, 3/4
109
110
111# === 6. Classical post-processing: gcd(a^(r/2) ± 1, N) ===
112def factor_from_period(a: int, n: int, r: int) -> tuple[int, int]:
113    if r <= 0 or r % 2 != 0:
114        return -1, -1
115    half = pow(a, r // 2, n)
116    if half == n - 1:
117        return -1, -1
118    g1 = gcd(half - 1, n)
119    g2 = gcd(half + 1, n)
120    if 1 < g1 < n:
121        return g1, n // g1
122    if 1 < g2 < n:
123        return g2, n // g2
124    return -1, -1
125
126
127# Use the most-observed NON-ZERO s as the canonical measurement
128# (s = 0 is uninformative: it would correspond to phase 0, which makes r ambiguous)
129nonzero_s = [s for s in range(n_buckets) if s != 0 and s_hist[s] > 0]
130top_s = max(nonzero_s, key=lambda s: s_hist[s])
131top_phase = Fraction(top_s, n_buckets)
132top_r = top_phase.denominator if top_phase.numerator != 0 else 0
133factor_1, factor_2 = factor_from_period(A_BASE, N_TARGET, top_r)
134
135
136# === 7. matplotlib visualisation: phase histogram + post-processing chain ===
137fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13.0, 5.2))
138
139# Left: phase-register histogram
140buckets = np.arange(n_buckets)
141bar_colors = ["#d62728" if int(s) in expected_s else "#1f77b4" for s in buckets]
142ax1.bar(buckets, s_probs, color=bar_colors, ec="black", lw=0.5)
143for s in expected_s:
144    ax1.axvline(s, color="#d62728", ls="--", lw=0.9, alpha=0.4)
145ax1.text(0.5, s_probs.max() * 0.92,
146         f"expected s for r=4: {expected_s}",
147         ha="center", fontsize=10, color="#7a1f1f", style="italic",
148         bbox=dict(facecolor="white", alpha=0.85, ec="none"))
149ax1.set_xlabel(f"phase-register integer s (over {n_buckets} buckets, "
150               fr"phase = $s/2^{N_PRECISION}$)")
151ax1.set_ylabel("measured probability")
152ax1.set_xticks(buckets)
153ax1.set_xticklabels([format(s, f"0{N_PRECISION}b") for s in buckets], fontsize=9)
154ax1.set_ylim(0, s_probs.max() * 1.15 + 0.005)
155ax1.set_title(f"QPE on U (period-4 modular exponentiation)\n"
156              f"{N_PRECISION} precision qubits, {total} shots")
157ax1.grid(alpha=0.3, axis="y")
158
159# Right: factor-extraction flow diagram (text panels)
160ax2.axis("off")
161half_pow = pow(A_BASE, top_r // 2, N_TARGET)
162flow_lines = [
163    (0.96, f"N = {N_TARGET}, pick a = {A_BASE} (gcd(a,N) = {gcd(A_BASE, N_TARGET)})"),
164    (0.86, f"compute 7^x mod 15 -> period r = {true_r}"),
165    (0.75, f"build QPE with period-4 unitary U (3 system qubits)"),
166    (0.64, f"measure phase register: most-likely s = {top_s:0{N_PRECISION}b}"),
167    (0.53, f"continued fraction: {top_s}/{n_buckets} = {top_phase} -> r = {top_r}"),
168    (0.41, f"check r even?  r = {top_r} -> "
169           f"{'YES' if top_r % 2 == 0 else 'NO'}"),
170    (0.29, f"compute 7^(r/2) mod 15 = 7^{top_r // 2} mod 15 = {half_pow}"),
171    (0.17, f"gcd(7^(r/2) - 1, 15) = {gcd(half_pow - 1, N_TARGET)}"),
172    (0.05, f"gcd(7^(r/2) + 1, 15) = {gcd(half_pow + 1, N_TARGET)}"),
173    (-0.06, f"=>  {N_TARGET} = {factor_1} x {factor_2}  "
174            f"({'SUCCESS' if factor_1 * factor_2 == N_TARGET else 'FAIL'})"),
175]
176for y, txt in flow_lines:
177    ax2.text(0.02, y, txt, ha="left", va="top", fontsize=10, family="monospace")
178ax2.set_xlim(0, 1)
179ax2.set_ylim(-0.18, 1.05)
180ax2.set_title("Shor's algorithm: classical post-processing chain\n"
181              "(from phase measurement to non-trivial factor)")
182
183plt.suptitle(f"ch16-cryptanalysis-coding demo 1: Shor for N = {N_TARGET} = "
184             f"{factor_1} x {factor_2}", y=1.02)
185plt.tight_layout()
186out_path = OUT / "01_shor_rsa.png"
187plt.savefig(out_path, dpi=120, bbox_inches="tight")
188plt.close()
189
190
191# === 8. stdout key statistics ===
192print(f"\n  N_PRECISION = {N_PRECISION} ({n_buckets} buckets), shots: {total}")
193print(f"  distribution at expected s values: "
194      f"{{0: {s_hist[0]/total:.3f}, 2: {s_hist[2]/total:.3f}, "
195      f"4: {s_hist[4]/total:.3f}, 6: {s_hist[6]/total:.3f}}}")
196print(f"  continued fraction {top_s}/{n_buckets} -> {top_phase}, recovered r = {top_r}")
197print(f"  a^(r/2) mod N = {half_pow}, gcd(a^(r/2)-1,N)={gcd(half_pow-1, N_TARGET)}, "
198      f"gcd(a^(r/2)+1,N)={gcd(half_pow+1, N_TARGET)}")
199print(f"  FACTORED: {N_TARGET} = {factor_1} x {factor_2}")
200print(f"  PNG -> {out_path}")

运行结果

Demo 1:Shor 破 RSA

运行方式

# 仓库根目录执行;输出 PNG 到 demos/ch16-cryptanalysis-coding/01_shor_rsa.png,关键统计到 stdout
.venv/bin/python3 demos/ch16-cryptanalysis-coding/01_shor_rsa.py

← 本章演示 · ↑ 代码演示总索引 · ⌂ 教程门户