Demo 1:Shor 破 RSA¶
可视化:单子图 —— 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}")
运行结果¶

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