<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mathematics on kenji.blog</title><link>http://kenji.blog/categories/mathematics/</link><description>Recent content in Mathematics on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ja</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 22:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/categories/mathematics/index.xml" rel="self" type="application/rss+xml"/><item><title>フェルマーの小定理を利用した暗号化の基礎と実装</title><link>http://kenji.blog/p/fermats-little-theorem-cryptography-implementation/</link><pubDate>Fri, 11 Sep 2026 22:00:00 +0900</pubDate><guid>http://kenji.blog/p/fermats-little-theorem-cryptography-implementation/</guid><description>&lt;img src="http://kenji.blog/p/fermats-little-theorem-cryptography-implementation/img/eyecatch.jpg" alt="Featured image of post フェルマーの小定理を利用した暗号化の基礎と実装" />&lt;h2 id="1-はじめに現代暗号を支える数学の神秘">1. はじめに：現代暗号を支える数学の神秘
&lt;/h2>&lt;p>現代のデジタル社会、とりわけインターネットを介した通信において、「暗号化」は不可欠な基盤技術となっています。私たちがウェブブラウザでHTTPS経由で安全にウェブサイトを閲覧し、オンラインバンキングで金融取引を行い、メッセージングアプリでプライベートなやり取りができるのは、高度な数学的理論に裏打ちされた暗号プロトコルが背後で機能しているからです。その中でも特に重要な役割を担っているのが「公開鍵暗号方式」であり、その代表格が &lt;strong>RSA暗号&lt;/strong> です。&lt;/p>
&lt;p>RSA暗号をはじめとする多くの暗号アルゴリズムの安全性や正当性は、17世紀のフランスの数学者ピエール・ド・フェルマー（Pierre de Fermat）が発見した非常に美しく、かつ強力な定理に大きく依存しています。それが &lt;strong>フェルマーの小定理（Fermat&amp;rsquo;s Little Theorem）&lt;/strong> です。さらに、これを一般化したレオンハルト・オイラー（Leonhard Euler）の定理も、暗号理論において決定的な役割を果たしています。&lt;/p>
&lt;p>本記事では、フェルマーの小定理という純粋数学の発見が、いかにして現代の実用的な暗号技術、特に「素数判定」や「RSA暗号」へと応用されているのかを、基礎から徹底的に解説します。数学的な証明、暗号化・復号のメカニズム、そして C++ と Python を用いた具体的なアルゴリズムの実装までを網羅する、非常に詳細な技術ガイドとなります。&lt;/p>
&lt;hr>
&lt;h2 id="2-合同式とモジュラ演算の基礎">2. 合同式とモジュラ演算の基礎
&lt;/h2>&lt;p>フェルマーの小定理を理解するためには、まず「モジュラ演算（合同式）」という数学の概念に慣れ親しむ必要があります。モジュラ演算とは、ある決まった数（法、モジュラスと呼ばれる）で割った「余り」に着目した計算体系のことです。時計の文字盤（12時間で1周する）のような計算であるため、「時計の数学」とも呼ばれます。&lt;/p>
&lt;p>整数 $a$ と $b$ を正の整数 $n$ で割った余りが等しいとき、数学的には以下のように記述します。&lt;/p>
$$
a \equiv b \pmod n
$$
&lt;p>これは「$n$ を法として $a$ と $b$ は合同である」と読みます。例えば、17 を 5 で割った余りは 2 であり、12 を 5 で割った余りも 2 です。したがって、次のように書くことができます。&lt;/p>
$$
17 \equiv 12 \pmod 5 \equiv 2 \pmod 5
$$
&lt;p>モジュラ演算においては、通常の四則演算（足し算、引き算、掛け算）がそのまま成り立ちます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>加法&lt;/strong>: $a \equiv b \pmod n$ かつ $c \equiv d \pmod n$ ならば、$a + c \equiv b + d \pmod n$&lt;/li>
&lt;li>&lt;strong>減法&lt;/strong>: $a \equiv b \pmod n$ かつ $c \equiv d \pmod n$ ならば、$a - c \equiv b - d \pmod n$&lt;/li>
&lt;li>&lt;strong>乗法&lt;/strong>: $a \equiv b \pmod n$ かつ $c \equiv d \pmod n$ ならば、$a \times c \equiv b \times d \pmod n$&lt;/li>
&lt;li>&lt;strong>べき乗&lt;/strong>: $a \equiv b \pmod n$ ならば、任意の自然数 $k$ について $a^k \equiv b^k \pmod n$&lt;/li>
&lt;/ol>
&lt;p>ただし、&lt;strong>除法（割り算）&lt;/strong> については注意が必要です。一般に、$a \times c \equiv b \times c \pmod n$ だからといって、両辺を $c$ で割って $a \equiv b \pmod n$ とすることはできません。これが成り立つのは、$c$ と $n$ が互いに素（最大公約数が 1）である場合に限られます。この「モジュラ逆元」の概念は、後述するRSA暗号の鍵生成において極めて重要になります。&lt;/p>
&lt;hr>
&lt;h2 id="3-フェルマーの小定理の数学的背景と証明">3. フェルマーの小定理の数学的背景と証明
&lt;/h2>&lt;p>モジュラ演算の基礎を押さえたところで、本題であるフェルマーの小定理について見ていきましょう。&lt;/p>
&lt;h3 id="31-定理の定義">3.1 定理の定義
&lt;/h3>&lt;p>フェルマーの小定理は次のように定式化されます。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>フェルマーの小定理 (Fermat&amp;rsquo;s Little Theorem)&lt;/strong>
$p$ を素数とし、$a$ を $p$ の倍数ではない（すなわち $a$ と $p$ は互いに素である）任意の整数とする。このとき、次の合同式が成り立つ。
&lt;/p>
$$ a^{p-1} \equiv 1 \pmod p $$
&lt;/blockquote>
&lt;p>また、条件「$a$ が $p$ の倍数でない」を外し、すべての整数 $a$ に対して成り立つ形として表現することも一般的です。その場合は両辺に $a$ を掛けて次のようになります。&lt;/p>
$$
a^p \equiv a \pmod p
$$
&lt;h3 id="32-具体例による確認">3.2 具体例による確認
&lt;/h3>&lt;p>定理が本当に成り立つのか、具体的な数字を使って確認してみましょう。
素数 $p = 5$ とします。$p-1 = 4$ です。$a$ として $p$ の倍数ではない整数を選びます。&lt;/p>
&lt;ul>
&lt;li>$a = 2$ の場合: $2^{5-1} = 2^4 = 16$。$16 \div 5 = 3$ 余り $1$。よって $16 \equiv 1 \pmod 5$。（成立）&lt;/li>
&lt;li>$a = 3$ の場合: $3^{5-1} = 3^4 = 81$。$81 \div 5 = 16$ 余り $1$。よって $81 \equiv 1 \pmod 5$。（成立）&lt;/li>
&lt;li>$a = 4$ の場合: $4^{5-1} = 4^4 = 256$。$256 \div 5 = 51$ 余り $1$。よって $256 \equiv 1 \pmod 5$。（成立）&lt;/li>
&lt;/ul>
&lt;p>このように、どんな $a$ を選んでも（5の倍数でなければ）、4乗して5で割った余りは必ず1になります。魔法のように見えますが、これは素数が持つ美しい性質に由来しています。&lt;/p>
&lt;h3 id="33-定理の数学的証明">3.3 定理の数学的証明
&lt;/h3>&lt;p>なぜこのようなことが成り立つのでしょうか。ここでは剰余類の集合を用いたエレガントな証明を紹介します。&lt;/p>
&lt;p>集合 $S = \{1, 2, 3, \dots, p-1\}$ を考えます。これらは $p$ で割った余りが $1$ から $p-1$ になる整数の代表元です。
ここで、各要素に $p$ と互いに素な整数 $a$ を掛けた新しい集合 $T$ を考えます。
&lt;/p>
$$ T = \{1a, 2a, 3a, \dots, (p-1)a\} $$
&lt;p>この集合 $T$ の各要素を $p$ で割った余りを考えます。驚くべきことに、これらの余りは、順番こそ入れ替わるかもしれませんが、元の集合 $S$ の要素の集合と完全に一致します。
なぜなら：&lt;/p>
&lt;ol>
&lt;li>$T$ の要素が $p$ の倍数になることはありません（$a$ も元の要素も $p$ の倍数ではないため）。&lt;/li>
&lt;li>$T$ の中で $p$ を法として合同になる2つの異なる要素は存在しません。もし $ia \equiv ja \pmod p$ （$i \neq j$）だとすると、$a$ と $p$ は互いに素なので $a$ で割ることができ、$i \equiv j \pmod p$ となって矛盾するからです。&lt;/li>
&lt;/ol>
&lt;p>したがって、$S$ の要素をすべて掛け合わせたものと、$T$ の要素をすべて掛け合わせたものは、$p$ を法として合同になります。&lt;/p>
$$
(1a) \times (2a) \times \dots \times ((p-1)a) \equiv 1 \times 2 \times \dots \times (p-1) \pmod p
$$
&lt;p>左辺を整理すると $a$ が $p-1$ 個あるので、&lt;/p>
$$
a^{p-1} \cdot (p-1)! \equiv (p-1)! \pmod p
$$
&lt;p>$(p-1)!$ は $p$ と互いに素なので、両辺を $(p-1)!$ で割ることができ、最終的に次の定理が導かれます。&lt;/p>
$$
a^{p-1} \equiv 1 \pmod p
$$
&lt;p>これがフェルマーの小定理の証明です。&lt;/p>
&lt;hr>
&lt;h2 id="4-オイラーのトーティエント関数とオイラーの定理">4. オイラーのトーティエント関数とオイラーの定理
&lt;/h2>&lt;p>フェルマーの小定理は「素数 $p$」に関する定理ですが、これを「任意の正の整数 $n$」に一般化したのが、レオンハルト・オイラーです。RSA暗号を理解するには、この拡張が不可欠です。&lt;/p>
&lt;h3 id="41-オイラーのトーティエント関数-phin">4.1 オイラーのトーティエント関数 $\phi(n)$
&lt;/h3>&lt;p>オイラーのトーティエント関数（あるいはオイラーの $\phi$ 関数） $\phi(n)$ は、「$1$ から $n$ までの整数のうち、$n$ と互いに素であるものの個数」を表す関数です。&lt;/p>
&lt;ul>
&lt;li>素数 $p$ の場合、$1$ から $p-1$ までのすべての整数が $p$ と互いに素なので、$\phi(p) = p - 1$ となります。&lt;/li>
&lt;li>2つの異なる素数 $p, q$ について、その積 $n = p \times q$ の場合、$\phi(n)$ は非常に簡単な式で求まります。
$$ \phi(p \times q) = \phi(p) \times \phi(q) = (p - 1)(q - 1) $$&lt;/li>
&lt;/ul>
&lt;p>この性質が、RSA暗号の鍵生成における根幹のロジックとなります。&lt;/p>
&lt;h3 id="42-オイラーの定理">4.2 オイラーの定理
&lt;/h3>&lt;p>オイラーは、フェルマーの小定理を次のように一般化しました。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>オイラーの定理 (Euler&amp;rsquo;s Theorem)&lt;/strong>
正の整数 $n$ と、それと互いに素な整数 $a$ に対して、次が成り立つ。
&lt;/p>
$$ a^{\phi(n)} \equiv 1 \pmod n $$
&lt;/blockquote>
&lt;p>もし $n$ が素数 $p$ であれば $\phi(p) = p - 1$ なので、これはフェルマーの小定理そのもの（$a^{p-1} \equiv 1 \pmod p$）になります。つまり、フェルマーの小定理はオイラーの定理の特殊なケースに過ぎません。&lt;/p>
&lt;hr>
&lt;h2 id="5-巨大な素数を見つけるフェルマーの素数判定法">5. 巨大な素数を見つける：フェルマーの素数判定法
&lt;/h2>&lt;p>暗号技術（RSA暗号やDiffie-Hellman鍵交換など）においては、数百桁にも及ぶ「巨大な素数」を高速に見つけ出す必要があります。しかし、巨大な数 $N$ が素数かどうかを判定するために、$2$ から $\sqrt{N}$ までのすべての数で割り切れるか試す「試し割り法」では、宇宙の寿命ほどの時間がかかってしまいます。&lt;/p>
&lt;p>そこで登場するのが、フェルマーの小定理を逆手に取った「確率的素数判定法」である &lt;strong>フェルマーテスト（Fermat Primality Test）&lt;/strong> です。&lt;/p>
&lt;h3 id="51-確率的素数判定法とは">5.1 確率的素数判定法とは
&lt;/h3>&lt;p>フェルマーの小定理によれば、$p$ が素数ならば、任意の $a$ ($1 &lt; a &lt; p$) に対して $a^{p-1} \equiv 1 \pmod p$ が必ず成立します。
この対偶をとると、「ある $a$ について $a^{p-1} \not\equiv 1 \pmod p$ となった場合、$p$ は&lt;strong>絶対に素数ではない（合成数である）&lt;/strong>」ということが言えます。&lt;/p>
&lt;p>したがって、$N$ が素数かどうかを判定したい場合、ランダムにいくつか $a$ を選び、$a^{N-1} \pmod N$ を計算して $1$ になるかを確認します。もし1回でも $1$ 以外の答えが出れば、$N$ は合成数であると確定します。何度試しても $1$ になる場合、$N$ は「おそらく素数である」と高い確率で判断できます。&lt;/p>
&lt;h3 id="52-アルゴリズムの解説とフローチャート">5.2 アルゴリズムの解説とフローチャート
&lt;/h3>&lt;p>フェルマーテストのアルゴリズムは以下の通りです。&lt;/p>
&lt;div class="mermaid">flowchart TD
Start["開始"] --> Input["判定対象の数 p と、テスト回数 k を入力"]
Input --> LoopStart["i = 0 から k-1 までループ"]
LoopStart --> Condition{"i &lt; k ?"}
Condition -- "Yes" --> RandomA["1 &lt; a &lt; p-1 の範囲でランダムな整数 a を選択"]
RandomA --> Calc["モジュラべき乗 a^(p-1) mod p を計算"]
Calc --> CheckPrime{"結果は 1 か？"}
CheckPrime -- "No" --> ReturnComposite["p は合成数である（確定）"]
CheckPrime -- "Yes" --> Increment["i をインクリメント"]
Increment --> Condition
Condition -- "No" --> ReturnPrime["p はおそらく素数である（確率的）"]
ReturnComposite --> End["終了"]
ReturnPrime --> End&lt;/div>
&lt;h3 id="53-カーマイケル数偽素数の落とし穴">5.3 カーマイケル数（偽素数）の落とし穴
&lt;/h3>&lt;p>フェルマーテストは非常に高速ですが、重大な欠点があります。それは、合成数であるにもかかわらず、すべての $a$ に対して $a^{N-1} \equiv 1 \pmod N$ を満たしてしまう悪魔のような数が存在することです。これを &lt;strong>カーマイケル数 (Carmichael numbers)&lt;/strong> と呼びます。最も小さいカーマイケル数は $561$ ($3 \times 11 \times 17$) です。&lt;/p>
&lt;p>カーマイケル数が存在するため、純粋なフェルマーテストだけでは絶対的な素数判定はできません。そのため、実際の暗号化システム（OpenSSLなど）では、フェルマーテストを改良した &lt;strong>ミラー・ラビン（Miller-Rabin）素数判定法&lt;/strong> が標準的に使用されています。ミラー・ラビン判定法はカーマイケル数を見破ることができるため、誤判定の確率を実質的にゼロにすることができます。&lt;/p>
&lt;h3 id="54-高速なべき乗剰余演算繰り返し二乗法">5.4 高速なべき乗剰余演算（繰り返し二乗法）
&lt;/h3>&lt;p>素数判定アルゴリズムの中で $a^{N-1} \pmod N$ を計算する必要がありますが、$N$ が巨大な場合、$a^{N-1}$ は天文学的な桁数になり、コンピュータのメモリに入りきりません。
これを解決するのが &lt;strong>繰り返し二乗法（Exponentiation by Squaring）&lt;/strong> またはモジュラべき乗演算です。計算の各ステップで剰余（mod N）を取ることで、常に値が $N$ より小さく保たれ、非常に高速（$O(\log N)$の計算量）で計算可能になります。&lt;/p>
&lt;hr>
&lt;h2 id="6-素数判定とべき乗剰余の実装">6. 素数判定とべき乗剰余の実装
&lt;/h2>&lt;p>それでは、フェルマーの素数判定法と繰り返し二乗法を、C++とPythonで実装してみましょう。&lt;/p>
&lt;h3 id="61-c-による実装">6.1 C++ による実装
&lt;/h3>&lt;p>C++では標準の整数型がオーバーフローしやすいため、巨大な数を扱うには多倍長整数ライブラリ（GMPなど）が必要ですが、ここではアルゴリズムを理解するために64bit整数（&lt;code>unsigned long long&lt;/code>）の範囲内での実装を示します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;random&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 高速なべき乗剰余演算 (a^b mod m) - 繰り返し二乗法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="nf">power_mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// bの最下位ビットが1の場合、結果にaを掛ける
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">result&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// オーバーフロー防止のため128bit拡張
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// aを二乗する
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// bを右シフト（半分にする）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">/=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// フェルマーの素数判定法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">iterations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">random_device&lt;/span> &lt;span class="n">rd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mt19937_64&lt;/span> &lt;span class="n">gen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rd&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uniform_int_distribution&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">dis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="o">++&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">gen&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// a^(p-1) mod p が 1 でなければ合成数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">power_mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// おそらく素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1000000007&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 既知の素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; is probably prime.&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; is composite.&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="62-python-による実装">6.2 Python による実装
&lt;/h3>&lt;p>Pythonの標準の整数型は多倍長整数をサポートしており、桁溢れを気にする必要がありません。さらにPythonの組み込み関数 &lt;code>pow(a, b, m)&lt;/code> は内部的に繰り返し二乗法を使用しているため非常に高速です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">random&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> フェルマーの素数判定法を用いた確率的素数判定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">iterations&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2 から p-2 の間でランダムな数 a を選ぶ&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">randint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># a^(p-1) mod p を計算。組み込みの pow は高速。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span> &lt;span class="c1"># 合成数確定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span> &lt;span class="c1"># おそらく素数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># テスト&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">number_to_test&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">104729&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">number_to_test&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">number_to_test&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> はおそらく素数です。&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">number_to_test&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> は合成数です。&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="7-rsa暗号への応用フェルマーとオイラーが結実する場所">7. RSA暗号への応用：フェルマーとオイラーが結実する場所
&lt;/h2>&lt;p>フェルマーの小定理（およびオイラーの定理）の最も偉大な応用先が、1977年にRivest, Shamir, Adlemanの3人によって開発された &lt;strong>RSA暗号&lt;/strong> です。
RSA暗号は「公開鍵暗号」という画期的なシステムであり、暗号化するための鍵（公開鍵）は全世界に公開しておきながら、復号するための鍵（秘密鍵）は受信者本人だけが知っているという仕組みを実現しています。&lt;/p>
&lt;p>この非対称性は、「巨大な合成数の素因数分解は極めて困難である」という計算量的安全性に基づいています。&lt;/p>
&lt;h3 id="71-rsa暗号の仕組み鍵生成暗号化復号">7.1 RSA暗号の仕組み（鍵生成、暗号化、復号）
&lt;/h3>&lt;p>RSA暗号の全体的な通信フローをMermaidのシーケンス図で確認しましょう。&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant Alice["アリス (受信者)"]
participant Bob["ボブ (送信者)"]
Alice->>Alice: "巨大な素数 p, q を生成"
Alice->>Alice: "N = p * q, φ(N) = (p-1)(q-1) を計算"
Alice->>Alice: "公開鍵 e と 秘密鍵 d を計算 (e*d ≡ 1 mod φ(N))"
Alice->>Bob: "公開鍵 (N, e) を送信"
Note over Bob: "平文 M を準備 (M &lt; N)"
Bob->>Bob: "暗号文 C = M^e mod N を計算"
Bob->>Alice: "暗号文 C を送信"
Alice->>Alice: "平文 M = C^d mod N を計算し復号"&lt;/div>
&lt;p>以下に、数学的な詳細ステップを解説します。&lt;/p>
&lt;h4 id="ステップ1鍵の生成受信者アリスの作業">ステップ1：鍵の生成（受信者アリスの作業）
&lt;/h4>&lt;ol>
&lt;li>2つの巨大な素数 $p$ と $q$ をランダムに生成します（ここで前述の素数判定法が使われます）。&lt;/li>
&lt;li>その積 $N = p \times q$ を計算します。この $N$ は公開されます。&lt;/li>
&lt;li>オイラーのトーティエント関数を用いて、$\phi(N) = (p-1)(q-1)$ を計算します。&lt;/li>
&lt;li>$\phi(N)$ と互いに素な整数 $e$ （公開指数）を選びます（よく $e = 65537$ が使われます）。&lt;/li>
&lt;li>$e$ のモジュラ逆元 $d$ （秘密指数）を計算します。すなわち、次を満たす $d$ を見つけます。
$$ e \cdot d \equiv 1 \pmod{\phi(N)} $$
この計算には &lt;strong>拡張ユークリッドの互除法&lt;/strong> が用いられます。&lt;/li>
&lt;/ol>
&lt;p>これで、&lt;strong>公開鍵は $(N, e)$&lt;/strong>、&lt;strong>秘密鍵は $(N, d)$&lt;/strong> となります。（$p, q, \phi(N)$ は直ちに破棄するか厳重に隠匿します）。&lt;/p>
&lt;h4 id="ステップ2暗号化送信者ボブの作業">ステップ2：暗号化（送信者ボブの作業）
&lt;/h4>&lt;p>ボブはアリスにメッセージ $M$ を送りたいとします（$M$ は文字を数値化したもので、$0 \le M &lt; N$ です）。
ボブはアリスの公開鍵 $(N, e)$ を使い、次の計算を行って暗号文 $C$ を作ります。&lt;/p>
$$
C \equiv M^e \pmod N
$$
&lt;p>この $C$ をネットワーク経由でアリスに送信します。&lt;/p>
&lt;h4 id="ステップ3復号受信者アリスの作業">ステップ3：復号（受信者アリスの作業）
&lt;/h4>&lt;p>暗号文 $C$ を受け取ったアリスは、自分だけが知っている秘密鍵 $d$ を使って次の計算を行います。&lt;/p>
$$
M' \equiv C^d \pmod N
$$
&lt;p>驚くべきことに、この計算結果 $M'$ は元のメッセージ $M$ と完全に一致します。&lt;/p>
&lt;h3 id="72-なぜ復号できるのか数学的証明">7.2 なぜ復号できるのか？（数学的証明）
&lt;/h3>&lt;p>ここで、フェルマーの小定理（オイラーの定理）が真価を発揮します。なぜ $C^d \pmod N$ が $M$ に戻るのでしょうか？&lt;/p>
&lt;p>復号の式を展開してみます。
$C \equiv M^e \pmod N$ ですから、
&lt;/p>
$$ C^d \equiv (M^e)^d \equiv M^{ed} \pmod N $$
&lt;p>鍵生成のステップにおいて、$e \cdot d \equiv 1 \pmod{\phi(N)}$ となるように $d$ を選びました。これは、ある整数 $k$ が存在して、次のように書けることを意味します。
&lt;/p>
$$ e \cdot d = 1 + k \cdot \phi(N) $$
&lt;p>これを上の式に代入します。
&lt;/p>
$$ M^{ed} = M^{1 + k \cdot \phi(N)} = M \cdot M^{k \cdot \phi(N)} = M \cdot (M^{\phi(N)})^k \pmod N $$
&lt;p>ここで &lt;strong>オイラーの定理&lt;/strong> ($M^{\phi(N)} \equiv 1 \pmod N$) が登場します。（※厳密には $M$ と $N$ が互いに素である必要がありますが、RSAにおいては $M$ と $N$ が互いに素でない確率は天文学的に低く、かつ中国剰余定理を用いることで互いに素でなくても成立することが証明できます）。&lt;/p>
&lt;p>オイラーの定理を適用すると、$M^{\phi(N)} \equiv 1$ なので、
&lt;/p>
$$ M \cdot (1)^k \equiv M \pmod N $$
&lt;p>見事に $M$ が復元されました！ フェルマーとオイラーが何百年も前に発見した数の性質が、現代のデジタル通信の機密性を完璧に保証しているのです。&lt;/p>
&lt;hr>
&lt;h2 id="8-rsa暗号のトイ実装-python">8. RSA暗号のトイ実装 (Python)
&lt;/h2>&lt;p>理論だけでは実感が湧きにくいため、Pythonを使って実際にRSA暗号の鍵生成・暗号化・復号のプロセスを実装してみましょう。これは教育用の「トイ（おもちゃ）実装」ですが、使われている数学は本物と全く同じです。&lt;/p>
&lt;p>モジュラ逆元 $d$ を求めるための「拡張ユークリッドの互除法」も実装に含めます。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">random&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 最大公約数を求める&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 拡張ユークリッドの互除法 (ax + by = gcd(a,b) の x, y を求める)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># e*d ≡ 1 (mod φ(N)) の d を見つけるために使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">extended_gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">extended_gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">extended_gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">g&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;逆元が存在しません&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">phi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 素数生成関数（簡易版：小さな素数を生成）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getrandbits&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 前述のフェルマーテストの代わりに簡易的に判定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">p&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># RSA 鍵生成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">generate_keypair&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># p と q が同じにならないようにする&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">q&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">q&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">phi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># e は 65537 などの素数がよく使われるが、ここではランダムに選ぶ&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">randrange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">g&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">randrange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 秘密鍵 d の計算&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 公開鍵 (e, n), 秘密鍵 (d, n)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">encrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">plaintext&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># plaintext^e mod n を計算&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cipher&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">ord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">char&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">plaintext&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">cipher&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">decrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ciphertext&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># cipher^d mod n を計算し、文字に戻す&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">plain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nb">chr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">char&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">char&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">ciphertext&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">plain&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 実行例&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;__main__&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--- RSA暗号 トイ実装 ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">public_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">private_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_keypair&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 12bitの素数を使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;公開鍵 (e, n): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">public_key&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;秘密鍵 (d, n): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">private_key&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Hello Math!&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">元のメッセージ: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 暗号化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">encrypted_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">encrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">public_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;暗号文: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">encrypted_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 復号&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">decrypted_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">decrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">private_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encrypted_msg&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;復号されたメッセージ: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">decrypted_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>このコードを実行すると、文字の配列が見慣れない数字の配列（暗号文）に変換され、それが秘密鍵によって見事に元の文字列に復元される様子が確認できます。&lt;/p>
&lt;hr>
&lt;h2 id="9-おわりに数学の美しさと実用性の交差点">9. おわりに：数学の美しさと実用性の交差点
&lt;/h2>&lt;p>ピエール・ド・フェルマーがこの「小定理」を発見した17世紀当時、これが何かの役に立つと考えた人は皆無でした。フェルマー自身も純粋な数学的探求心から数論の研究を行っていました。&lt;/p>
&lt;p>しかし、約300年後の1970年代、コンピュータネットワークの黎明期において、安全な通信プロトコルを確立するために不可欠な暗号技術として、フェルマーの定理は劇的な復活を遂げました。フェルマーの小定理に基づく素数判定技術と、オイラーの定理に基づくRSA暗号は、現代のインターネットインフラを文字通り支えています。&lt;/p>
&lt;p>私たちが日々何気なく送っているLINEのメッセージも、Amazonでの買い物も、すべてはこの $a^{p-1} \equiv 1 \pmod p$ というシンプルで美しい数式の上で踊っているのです。数学がいかに抽象的であっても、いつか必ず人類の役に立つ時が来るということを、フェルマーの小定理は教えてくれています。&lt;/p>
&lt;p>プログラミングや暗号理論を学ぶ上で、その基礎にある数学的構造を理解することは、ブラックボックスとして提供されているライブラリの挙動を深く理解し、よりセキュアなシステムを設計するための大きな武器になるでしょう。&lt;/p></description></item><item><title>格子暗号（Lattice-based cryptography）の数学的直観</title><link>http://kenji.blog/p/lattice-based-cryptography-math-intuition/</link><pubDate>Fri, 11 Sep 2026 21:00:00 +0900</pubDate><guid>http://kenji.blog/p/lattice-based-cryptography-math-intuition/</guid><description>&lt;img src="http://kenji.blog/p/lattice-based-cryptography-math-intuition/img/eyecatch.jpg" alt="Featured image of post 格子暗号（Lattice-based cryptography）の数学的直観" />&lt;h1 id="1-導入ポスト量子暗号pqcの夜明けと格子暗号の台頭">1. 導入：ポスト量子暗号（PQC）の夜明けと格子暗号の台頭
&lt;/h1>&lt;p>現代社会のデジタルインフラを支えているのは、RSA暗号や楕円曲線暗号（ECC）をはじめとする公開鍵暗号技術です。これらの暗号方式は、「素因数分解問題」や「離散対数問題」といった、従来の古典コンピュータでは効率的に解くことができない（指数関数的な時間を要する）と信じられている数学的な困難性に安全性の根拠を置いています。&lt;/p>
&lt;p>しかし、1994年にピーター・ショア（Peter Shor）によって発表された「Shorのアルゴリズム」は、暗号界に激震を走らせました。このアルゴリズムは、大規模な量子コンピュータが実現した暁には、素因数分解問題や離散対数問題を多項式時間で解いてしまうことを数学的に証明したのです。これはつまり、現在広く利用されている公開鍵暗号が、将来的に完全に解読可能になってしまうということを意味しています。&lt;/p>
&lt;p>このような「量子コンピュータの脅威（Quantum Threat）」に対抗するため、量子コンピュータを用いても解読が困難な新しい暗号方式の研究が急務となりました。これが「ポスト量子暗号（Post-Quantum Cryptography: PQC）」または「耐量子計算機暗号」と呼ばれる分野です。&lt;/p>
&lt;p>PQCにはいくつかの有力な候補が存在します。ハッシュベース暗号、符号ベース暗号、多変数多項式暗号、同種写像暗号などが挙げられますが、その中でも現在最も注目を集め、NIST（米国国立標準技術研究所）によるPQC標準化プロセスの中心となっているのが「格子暗号（Lattice-based cryptography）」です。格子暗号は、他の方式と比較して、暗号化・復号の処理速度が非常に高速であり、また「最悪時計算量（Worst-case complexity）」から「平均時計算量（Average-case complexity）」への帰着という、暗号理論において極めて強力な安全性の証明を持つという際立った特徴を持っています。&lt;/p>
&lt;p>本記事では、この格子暗号の基礎となる「格子（Lattice）」の数学的定義から出発し、格子上の困難な問題であるSVP（最短ベクトル問題）やCVP（最近接ベクトル問題）、そして現代の格子暗号の心臓部とも言える「LWE問題（Learning With Errors）」について、数式と幾何学的な直観、そして具体的な数値例を交えて、徹底的に深く解説していきます。&lt;/p>
&lt;h1 id="2-格子latticeの数学的定義と幾何学的直観">2. 格子（Lattice）の数学的定義と幾何学的直観
&lt;/h1>&lt;h2 id="21-ベクトル空間と格子">2.1 ベクトル空間と格子
&lt;/h2>&lt;p>数学において、「格子（Lattice）」とは、$n$次元実ベクトル空間 $\mathbb{R}^n$ 内に規則正しく並んだ離散的な点の集合のことです。線形代数学で学ぶベクトル空間（Vector Space）と似ていますが、決定的な違いがあります。ベクトル空間が基底ベクトルの「実数係数」での線形結合で表される連続的な空間であるのに対し、格子は基底ベクトルの「整数係数」での線形結合で表される離散的な空間です。&lt;/p>
&lt;p>数学的な厳密な定義を与えましょう。$m$次元実ベクトル空間 $\mathbb{R}^m$ における $n$ 本（$n \le m$）の線形独立なベクトル $\mathbf{b}_1, \mathbf{b}_2, \dots, \mathbf{b}_n$ を考えます。これらのベクトルを列ベクトルとして持つ行列を $B = [\mathbf{b}_1, \mathbf{b}_2, \dots, \mathbf{b}_n] \in \mathbb{R}^{m \times n}$ とします。この $B$ を格子の「基底（Basis）」と呼びます。&lt;/p>
&lt;p>この基底 $B$ によって生成される格子 $\mathcal{L}(B)$ は、次のように定義されます。&lt;/p>
$$
\mathcal{L}(B) = \left\{ \sum_{i=1}^{n} x_i \mathbf{b}_i \mathrel{\bigg|} x_i \in \mathbb{Z} \right\} = \{ B \mathbf{x} \mid \mathbf{x} \in \mathbb{Z}^n \}
$$
&lt;p>ここで重要なのは、係数 $x_i$ が実数 $\mathbb{R}$ ではなく、整数 $\mathbb{Z}$ に限定されているという点です。これにより、空間内に無数に存在する連続的な点ではなく、等間隔に配置された交差点のような「離散的な点の集合」が形成されます。&lt;/p>
&lt;h2 id="22-幾何学的なイメージ">2.2 幾何学的なイメージ
&lt;/h2>&lt;p>2次元平面 $\mathbb{R}^2$ の例で考えてみましょう。基底ベクトルとして $\mathbf{b}_1 = \begin{pmatrix} 1 \\ 0 \end{pmatrix}$ と $\mathbf{b}_2 = \begin{pmatrix} 0 \\ 1 \end{pmatrix}$ を選んだ場合、これらによって生成される格子は、座標平面上のすべての整数座標 $(x, y) \in \mathbb{Z}^2$ の集合となります。これは最も単純な「正方格子」です。&lt;/p>
&lt;p>しかし、格子は常に直交しているわけではありません。例えば、$\mathbf{b}_1 = \begin{pmatrix} 2 \\ 1 \end{pmatrix}$ と $\mathbf{b}_2 = \begin{pmatrix} 1 \\ 3 \end{pmatrix}$ という基底を考えると、生成される点は斜めに歪んだ網目の交点のようになります。&lt;/p>
&lt;h2 id="23-基底の非一意性とユニモジュラ変換">2.3 基底の非一意性とユニモジュラ変換
&lt;/h2>&lt;p>格子暗号の安全性の根幹に関わる重要な性質があります。それは、「同一の格子を生成する基底は無数に存在する」ということです。&lt;/p>
&lt;p>例えば、先ほどの $\mathbf{b}_1 = (1, 0)^T, \mathbf{b}_2 = (0, 1)^T$ という基底が生成する $\mathbb{Z}^2$ 格子は、$\mathbf{b}'_1 = (1, 1)^T, \mathbf{b}'_2 = (2, 3)^T$ という基底を使っても全く同じ格子 $\mathbb{Z}^2$ を生成します。&lt;/p>
&lt;p>ある基底 $B$ と別の基底 $B'$ が同じ格子を生成するための必要十分条件は、ある整数成分の行列 $U \in \mathbb{Z}^{n \times n}$ であって、行列式が $\det(U) = \pm 1$ となるものが存在し、
&lt;/p>
$$ B' = B U $$
&lt;p>
と表せることです。このような行列 $U$ を「ユニモジュラ行列（Unimodular matrix）」と呼びます。&lt;/p>
&lt;p>暗号への応用における基本的なアイデアは、「良い基底（直交に近く、短いベクトルからなる基底）」を秘密鍵とし、「悪い基底（互いに極端に斜交し、非常に長いベクトルからなる基底）」を公開鍵として用いることです。悪い基底から良い基底を計算で求めることは、次元が高くなると非常に困難になります。これが格子暗号の基本的な直観です。&lt;/p>
&lt;h1 id="3-格子における計算困難な問題">3. 格子における計算困難な問題
&lt;/h1>&lt;p>格子暗号の安全性は、格子上の特定の数学的問題を解くことの困難性に依存しています。ここでは、最も基本的かつ有名な2つの問題を紹介します。&lt;/p>
&lt;h2 id="31-最短ベクトル問題shortest-vector-problem-svp">3.1 最短ベクトル問題（Shortest Vector Problem: SVP）
&lt;/h2>&lt;p>SVPは、格子理論において最も古典的で有名な問題です。&lt;/p>
&lt;p>&lt;strong>定義（SVP）:&lt;/strong>
任意の格子基底 $B$ が与えられたとき、その格子 $\mathcal{L}(B)$ に属する非ゼロベクトルの中で、ユークリッドノルム（長さ）が最小となるベクトル $\mathbf{v}$ を見つけよ。&lt;/p>
&lt;p>数式で表すと、$\min_{\mathbf{v} \in \mathcal{L}(B) \setminus \{\mathbf{0}\}} \| \mathbf{v} \|$ となる $\mathbf{v}$ を求める問題です。この最小の長さを $\lambda_1(\mathcal{L})$ と書き、「格子の第一連続最小値（First successive minimum）」と呼びます。&lt;/p>
&lt;p>2次元や3次元の低い次元であれば、図を描けば目で見て一番短いベクトルを見つけることができます。あるいは、ガウスの格子簡約アルゴリズムなどを使って効率的に解くことができます。しかし、次元 $n$ が数百〜数千といった高次元になると、SVPを厳密に解くことはNP困難であることが知られています。&lt;/p>
&lt;p>現実の暗号では、厳密な最短ベクトルではなく、「近似的に短いベクトル」を見つける近似SVP（$\gamma$-SVP）が用いられます。近似係数 $\gamma$ が多項式サイズの場合、この問題は依然として非常に難しいと考えられています。&lt;/p>
&lt;h2 id="32-最近接ベクトル問題closest-vector-problem-cvp">3.2 最近接ベクトル問題（Closest Vector Problem: CVP）
&lt;/h2>&lt;p>CVPもまた、格子暗号において極めて重要な問題です。&lt;/p>
&lt;p>&lt;strong>定義（CVP）:&lt;/strong>
任意の格子基底 $B$ と、空間内の任意のターゲットベクトル $\mathbf{t} \in \mathbb{R}^m$ （必ずしも格子点ではない）が与えられたとき、格子点の中で $\mathbf{t}$ に最も近い格子点 $\mathbf{v} \in \mathcal{L}(B)$ を見つけよ。&lt;/p>
&lt;p>数式で表すと、$\min_{\mathbf{v} \in \mathcal{L}(B)} \| \mathbf{v} - \mathbf{t} \|$ となる格子点 $\mathbf{v}$ を探す問題です。&lt;/p>
&lt;p>CVPもSVPと同様に、高次元においてはNP困難です。暗号への応用という観点では、後述するLWE問題は、このCVPの特殊な変種（Bounded Distance Decoding: BDD）と密接な関係があります。&lt;/p>
&lt;h2 id="33-なぜ高次元になると解けないのかlllとbkzの限界">3.3 なぜ高次元になると解けないのか？（LLLとBKZの限界）
&lt;/h2>&lt;p>高次元の格子問題を解くための有名なアルゴリズムとして、LLLアルゴリズム（Lenstra-Lenstra-Lovász algorithm）があります。LLLアルゴリズムは多項式時間で動作し、格子基底をある程度「良い基底」に簡約（Reduction）することができます。しかし、LLLアルゴリズムが見つけることができる最短ベクトルは、真の最短ベクトルの長さに対して指数関数的（$2^{\mathcal{O}(n)}$）な近似係数を持つため、暗号の安全性を破るには至りません。&lt;/p>
&lt;p>LLLを改良したBKZ（Block Korkine-Zolotarev）アルゴリズムなどのより強力な基底簡約アルゴリズムを用いれば、より短いベクトルを見つけることができますが、その計算量はブロックサイズに対して指数関数的に増大します。格子暗号では、このBKZアルゴリズムの実行時間を見積もることで、安全なパラメータ（次元 $n$ の大きさなど）を決定しています。現在のPQCの標準パラメータでは、次元 $n$ は500から1000以上の値が選ばれており、スーパーコンピュータや将来の量子コンピュータを用いても解読には宇宙の年齢以上の時間がかかるとされています。&lt;/p>
&lt;h1 id="4-lwe問題learning-with-errorsの数学的定式化">4. LWE問題（Learning With Errors）の数学的定式化
&lt;/h1>&lt;p>現代の格子暗号の大部分は、2005年にOded Regevによって提唱された「LWE問題（Learning With Errors）」をベースにしています。LWE問題の美しさは、その定式化のシンプルさと、「最悪時計算量から平均時計算量への帰着」という強力な数学的証明を持っている点にあります。&lt;/p>
&lt;h2 id="41-ノイズなしの連立一次方程式">4.1 ノイズなしの連立一次方程式
&lt;/h2>&lt;p>LWE問題を理解するために、まずはノイズのない単純な連立一次方程式を考えてみましょう。
未知の秘密ベクトル $\mathbf{s} \in \mathbb{Z}_q^n$ （各成分は $0$ から $q-1$ の整数）があるとします。ここで $q$ は素数とします。&lt;/p>
&lt;p>ランダムな係数ベクトル $\mathbf{a}_1, \mathbf{a}_2, \dots \in \mathbb{Z}_q^n$ を選び、秘密ベクトル $\mathbf{s}$ との内積を法 $q$ で計算します。
$b_1 = \langle \mathbf{a}_1, \mathbf{s} \rangle \pmod q$
$b_2 = \langle \mathbf{a}_2, \mathbf{s} \rangle \pmod q$
$\vdots$&lt;/p>
&lt;p>十分な数（$n$個以上）の $(\mathbf{a}_i, b_i)$ のペアを与えられた場合、私たちは線形代数における「ガウスの消去法（Gaussian elimination）」を用いることで、容易に秘密ベクトル $\mathbf{s}$ を復元することができます。これは多項式時間で簡単に解ける問題です。&lt;/p>
&lt;h2 id="42-lwe問題の定義ノイズを加える">4.2 LWE問題の定義：ノイズを加える
&lt;/h2>&lt;p>では、この問題にわずかな「ノイズ（誤差）」を加えたらどうなるでしょうか？
これがLWE問題の本質です。&lt;/p>
&lt;p>未知の秘密ベクトル $\mathbf{s} \in \mathbb{Z}_q^n$ に対して、各方程式の結果に小さな誤差 $e_i \in \mathbb{Z}_q$ を加えます。
$b_i = \langle \mathbf{a}_i, \mathbf{s} \rangle + e_i \pmod q$&lt;/p>
&lt;p>ここで、$e_i$ は平均0、標準偏差が比較的小さい（例えば正規分布のような、離散ガウス分布から選ばれた）小さな整数値です。
与えられる情報は、ランダムなベクトル $\mathbf{a}_i$ と、それに誤差を加えて計算された $b_i$ のペアのリストです。
$( \mathbf{a}_1, b_1 ), ( \mathbf{a}_2, b_2 ), \dots, ( \mathbf{a}_m, b_m )$&lt;/p>
&lt;p>これを行列で表現すると非常にスッキリします。
ランダムな行列 $A \in \mathbb{Z}_q^{m \times n}$、秘密ベクトル $\mathbf{s} \in \mathbb{Z}_q^n$、誤差ベクトル $\mathbf{e} \in \mathbb{Z}_q^m$ を用いて、
&lt;/p>
$$ \mathbf{b} = A \mathbf{s} + \mathbf{e} \pmod q $$
&lt;p>
と書けます。与えられるのは $A$ と $\mathbf{b}$ のみです。ここから $\mathbf{s}$ を求めるのが「探索LWE問題（Search LWE problem）」です。&lt;/p>
&lt;p>誤差 $e_i$ が入っているため、ガウスの消去法を使おうとすると、方程式を足し引きする過程で誤差が指数関数的に増幅してしまい、正しい答えに辿り着くことができなくなります。一見すると単純な連立一次方程式に見えますが、この小さなノイズが加わるだけで、問題の難易度がNP困難なレベルへと跳ね上がるのです。&lt;/p>
&lt;h2 id="43-決定lwe問題decision-lwe">4.3 決定LWE問題（Decision LWE）
&lt;/h2>&lt;p>暗号理論の証明において頻繁に用いられるのは、探索LWE問題のバリエーションである「決定LWE問題（Decision LWE problem）」です。&lt;/p>
&lt;p>決定LWE問題とは、以下の2つの分布から得られたサンプルのリストを与えられたとき、それがどちらの分布から来たものかを判定する問題です。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>LWE分布&lt;/strong>: 意図的に計算された $(A, \mathbf{b} = A\mathbf{s} + \mathbf{e} \pmod q)$&lt;/li>
&lt;li>&lt;strong>一様ランダム分布&lt;/strong>: 完全にランダムに選ばれた行列 $A$ とベクトル $\mathbf{u}$ からなる $(A, \mathbf{u})$&lt;/li>
&lt;/ol>
&lt;p>驚くべきことに、LWE問題のパラメータを適切に選べば、LWE分布から得られたペアは、完全にランダムなデータのペアと「計算量的に識別不可能（Computationally Indistinguishable）」になります。この性質が、LWEベースの暗号が「乱数と区別がつかない暗号文」を生成できる根拠となっています。&lt;/p>
&lt;h2 id="44-最悪時計算量から平均時計算量への帰着regevの定理">4.4 最悪時計算量から平均時計算量への帰着（Regevの定理）
&lt;/h2>&lt;p>Oded Regevの最大の功績は、このLWE問題の難しさを、前述の格子問題（SVPやCVP）の難しさに数学的に結びつけたことです。&lt;/p>
&lt;p>彼は量子還元（Quantum reduction）を用いて、「もしLWE問題を平均的に（ランダムに選ばれた $A$ と $\mathbf{e}$ に対して）解くことができる多項式時間のアルゴリズムが存在するならば、任意の格子の最悪ケース（最も難しいケース）のGap-SVPを解くことができる多項式時間の量子アルゴリズムが存在する」ということを証明しました。（後に、Peikertらによって古典的な還元も示されています）。&lt;/p>
&lt;p>これは暗号理論において夢のような性質です。なぜなら、「暗号が破られるのは、我々がたまたま弱い鍵（平均的なケースの一部）を選んでしまったからかもしれない」という懸念を払拭し、「平均的なLWEが解けるなら、格子の全ての難しい問題が解けてしまう（だからLWEは絶対に難しい）」という強力な保証を与えてくれるからです。&lt;/p>
&lt;div class="mermaid">graph TD
A["Worst-case Lattice Problems (Gap-SVP, SIVP)"] -->|Quantum/Classical Reduction| B["Average-case LWE Problem"]
B -->|Cryptographic Construction| C["LWE-based Cryptosystems (PKE, KEM, FHE)"]
style A fill:#ffcccc,stroke:#ff0000,stroke-width:2px,color:#000
style B fill:#ccffcc,stroke:#00aa00,stroke-width:2px,color:#000
style C fill:#ccccff,stroke:#0000ff,stroke-width:2px,color:#000&lt;/div>
&lt;h1 id="5-lweを用いた公開鍵暗号方式regev暗号の構築">5. LWEを用いた公開鍵暗号方式（Regev暗号）の構築
&lt;/h1>&lt;p>LWE問題の困難性を理解したところで、それを使ってどのように暗号化と復号を行うのか、Oded Regevが提案した基本的な公開鍵暗号方式を見ていきましょう。ここでは、1ビットのメッセージ $M \in \{0, 1\}$ を暗号化する最も基本的な仕組みを解説します。&lt;/p>
&lt;h2 id="51-鍵生成key-generation">5.1 鍵生成（Key Generation）
&lt;/h2>&lt;ol>
&lt;li>システムパラメータとして、法となる素数 $q$、次元 $n$、方程式の数 $m$（$m > n \log q$）を決定します。&lt;/li>
&lt;li>秘密鍵として、ベクトル $\mathbf{s} \in \mathbb{Z}_q^n$ をランダムに選びます。&lt;/li>
&lt;li>ランダムな行列 $A \in \mathbb{Z}_q^{m \times n}$ を生成します。&lt;/li>
&lt;li>小さな誤差ベクトル $\mathbf{e} \in \mathbb{Z}_q^m$ を離散ガウス分布などの誤差分布から選びます。&lt;/li>
&lt;li>ベクトル $\mathbf{b} = A \mathbf{s} + \mathbf{e} \pmod q$ を計算します。&lt;/li>
&lt;li>公開鍵（Public Key）は $(A, \mathbf{b})$ となります。&lt;/li>
&lt;li>秘密鍵（Secret Key）は $\mathbf{s}$ となります。&lt;/li>
&lt;/ol>
&lt;p>公開鍵はまさに「LWE問題のインスタンス」そのものです。公開鍵 $(A, \mathbf{b})$ から秘密鍵 $\mathbf{s}$ を求めることは、探索LWE問題を解くことに等しいため、安全性が保証されます。&lt;/p>
&lt;h2 id="52-暗号化encryption">5.2 暗号化（Encryption）
&lt;/h2>&lt;p>アリスはボブの公開鍵 $(A, \mathbf{b})$ を用いて、1ビットのメッセージ $M \in \{0, 1\}$ を暗号化します。&lt;/p>
&lt;ol>
&lt;li>ランダムなバイナリベクトル（成分が0か1）$\mathbf{r} \in \{0, 1\}^m$ を選びます。&lt;/li>
&lt;li>暗号文の前半部分として、ベクトル $\mathbf{u} = A^T \mathbf{r} \pmod q$ を計算します。（$A^T$ は $A$ の転置行列です。つまり、$A$ の行のうち、$\mathbf{r}$ の成分が1である行を足し合わせています）。&lt;/li>
&lt;li>暗号文の後半部分として、スカラー $v = \mathbf{b}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor \pmod q$ を計算します。
（メッセージ $M$ が0なら何も足さず、$1$ なら $q$ のちょうど半分の値 $\lfloor \frac{q}{2} \rfloor$ を足します）。&lt;/li>
&lt;li>暗号文（Ciphertext）は $(\mathbf{u}, v)$ となります。&lt;/li>
&lt;/ol>
&lt;p>暗号化の直観的な意味合いは、公開鍵の行列 $A$ とベクトル $\mathbf{b}$ に対して、「ランダムな部分集合の和」をとることです。決定LWE問題の困難性により、この暗号文 $(\mathbf{u}, v)$ は、完全にランダムなベクトルと一様乱数と区別がつかないように見えます（意味的安全性：Semantic Security）。&lt;/p>
&lt;div class="mermaid">flowchart LR
M["Message M in {0,1}"] --> Enc
PK["Public Key (A, b)"] --> Enc
r["Random binary vector r"] --> Enc
subgraph Enc ["Encryption Process"]
direction TB
u_calc["u = A^T * r mod q"]
v_calc["v = b^T * r + M * floor(q/2) mod q"]
end
Enc --> CT["Ciphertext (u, v)"]&lt;/div>
&lt;h2 id="53-復号decryption">5.3 復号（Decryption）
&lt;/h2>&lt;p>ボブは秘密鍵 $\mathbf{s}$ を用いて暗号文 $(\mathbf{u}, v)$ を復号します。&lt;/p>
&lt;ol>
&lt;li>次の値を計算します： $D = v - \mathbf{s}^T \mathbf{u} \pmod q$&lt;/li>
&lt;li>計算した結果が、$0$ に近ければ $M=0$、$\lfloor \frac{q}{2} \rfloor$ に近ければ $M=1$ として出力します。&lt;/li>
&lt;/ol>
&lt;p>なぜこれで復号できるのか、数学的に展開してみましょう。
$\mathbf{b} = A \mathbf{s} + \mathbf{e}$ であったことを思い出してください。&lt;/p>
$$
\begin{aligned}
v - \mathbf{s}^T \mathbf{u} &amp;= (\mathbf{b}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor) - \mathbf{s}^T (A^T \mathbf{r}) \\
&amp;= ((A \mathbf{s} + \mathbf{e})^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor) - \mathbf{s}^T A^T \mathbf{r} \\
&amp;= (\mathbf{s}^T A^T \mathbf{r} + \mathbf{e}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor) - \mathbf{s}^T A^T \mathbf{r} \\
&amp;= \mathbf{e}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor \pmod q
\end{aligned}
$$
&lt;p>ここで、式の中から $\mathbf{s}^T A^T \mathbf{r}$ が綺麗に相殺されて消えました！
残ったのは $\mathbf{e}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor$ です。&lt;/p>
&lt;p>$\mathbf{e}$ は成分が非常に小さなノイズベクトルであり、$\mathbf{r}$ は成分が0か1のバイナリベクトルです。したがって、それらの内積である $\mathbf{e}^T \mathbf{r}$ も、（パラメータを適切に選べば）比較的小さな値に留まります。&lt;/p>
&lt;ul>
&lt;li>もし $M=0$ ならば、結果は $\mathbf{e}^T \mathbf{r}$ となり、$0$ に近い小さな値になります。&lt;/li>
&lt;li>もし $M=1$ ならば、結果は $\mathbf{e}^T \mathbf{r} + \lfloor \frac{q}{2} \rfloor$ となり、$q$ の半分の値 $\lfloor \frac{q}{2} \rfloor$ の周辺に位置することになります。&lt;/li>
&lt;/ul>
&lt;p>誤差 $\mathbf{e}^T \mathbf{r}$ の絶対値が $\frac{q}{4}$ 未満に収まるようにパラメータが設計されていれば、ボブは計算結果が $0$ と $\lfloor \frac{q}{2} \rfloor$ のどちらに近いかを見るだけで、メッセージ $M$ を正確に判定（復号）することができます。これがLWEベースの暗号が機能する美しいメカニズムです。&lt;/p>
&lt;div class="mermaid">flowchart LR
CT["Ciphertext (u, v)"] --> Dec
SK["Secret Key s"] --> Dec
subgraph Dec ["Decryption Process"]
direction TB
calc["Compute D = v - s^T * u mod q"]
check["Check if D is closer to 0 or q/2"]
end
calc --> check
Dec --> M_out["Recovered Message M"]&lt;/div>
&lt;h1 id="6-具体的な数値を用いたlwe暗号のトイエグザンプル">6. 具体的な数値を用いたLWE暗号のトイ・エグザンプル
&lt;/h1>&lt;p>数式の羅列だけでは実感が湧きにくいと思いますので、実際に非常に小さな数値パラメータを設定して、暗号化から復号までの計算を追ってみましょう。
（※現実の暗号システムでは、安全性確保のため $n$ は500以上、$q$ は数千以上の値が使われます）&lt;/p>
&lt;p>&lt;strong>【パラメータ設定】&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>法 $q = 17$ （素数。したがって値は $0$ から $16$ の範囲をとります）&lt;/li>
&lt;li>次元 $n = 2$&lt;/li>
&lt;li>方程式の数 $m = 4$&lt;/li>
&lt;li>メッセージ $M = 1$ を暗号化するとします。&lt;/li>
&lt;li>メッセージのシフト量：$\lfloor \frac{q}{2} \rfloor = \lfloor \frac{17}{2} \rfloor = 8$&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【1. 鍵生成フェーズ】&lt;/strong>
ボブは秘密鍵 $\mathbf{s}$ と行列 $A$、誤差ベクトル $\mathbf{e}$ をランダムに選びます。
&lt;/p>
$$ \mathbf{s} = \begin{pmatrix} 3 \\ 4 \end{pmatrix} \in \mathbb{Z}_{17}^2 $$
$$ A = \begin{pmatrix} 2 &amp; 15 \\ 1 &amp; 8 \\ 14 &amp; 5 \\ 9 &amp; 10 \end{pmatrix} \in \mathbb{Z}_{17}^{4 \times 2} $$
$$ \mathbf{e} = \begin{pmatrix} 1 \\ -1 \\ 0 \\ 2 \end{pmatrix} \equiv \begin{pmatrix} 1 \\ 16 \\ 0 \\ 2 \end{pmatrix} \pmod{17} $$
&lt;p>次に公開鍵 $\mathbf{b}$ を計算します。
&lt;/p>
$$ A \mathbf{s} = \begin{pmatrix} 2 &amp; 15 \\ 1 &amp; 8 \\ 14 &amp; 5 \\ 9 &amp; 10 \end{pmatrix} \begin{pmatrix} 3 \\ 4 \end{pmatrix} = \begin{pmatrix} 2\times 3 + 15\times 4 \\ 1\times 3 + 8\times 4 \\ 14\times 3 + 5\times 4 \\ 9\times 3 + 10\times 4 \end{pmatrix} = \begin{pmatrix} 6 + 60 \\ 3 + 32 \\ 42 + 20 \\ 27 + 40 \end{pmatrix} = \begin{pmatrix} 66 \\ 35 \\ 62 \\ 67 \end{pmatrix} $$
&lt;p>
これを法 17 で計算します。($66 = 17 \times 3 + 15$ など)
&lt;/p>
$$ A \mathbf{s} \pmod{17} = \begin{pmatrix} 15 \\ 1 \\ 11 \\ 16 \end{pmatrix} $$
&lt;p>
誤差ベクトル $\mathbf{e}$ を足します。
&lt;/p>
$$ \mathbf{b} = A \mathbf{s} + \mathbf{e} = \begin{pmatrix} 15 \\ 1 \\ 11 \\ 16 \end{pmatrix} + \begin{pmatrix} 1 \\ 16 \\ 0 \\ 2 \end{pmatrix} = \begin{pmatrix} 16 \\ 17 \\ 11 \\ 18 \end{pmatrix} \equiv \begin{pmatrix} 16 \\ 0 \\ 11 \\ 1 \end{pmatrix} \pmod{17} $$
&lt;p>公開鍵は $A$ と $\mathbf{b} = (16, 0, 11, 1)^T$ となります。&lt;/p>
&lt;p>&lt;strong>【2. 暗号化フェーズ】&lt;/strong>
アリスはメッセージ $M = 1$ を暗号化します。
ランダムなベクトル $\mathbf{r}$ を選びます。ここでは $\mathbf{r} = (1, 0, 1, 0)^T$ とします。&lt;/p>
&lt;p>$\mathbf{u}$ を計算します。
&lt;/p>
$$ \mathbf{u} = A^T \mathbf{r} = \begin{pmatrix} 2 &amp; 1 &amp; 14 &amp; 9 \\ 15 &amp; 8 &amp; 5 &amp; 10 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \\ 1 \\ 0 \end{pmatrix} = \begin{pmatrix} 2 \times 1 + 14 \times 1 \\ 15 \times 1 + 5 \times 1 \end{pmatrix} = \begin{pmatrix} 16 \\ 20 \end{pmatrix} \equiv \begin{pmatrix} 16 \\ 3 \end{pmatrix} \pmod{17} $$
&lt;p>$v$ を計算します。
&lt;/p>
$$ \mathbf{b}^T \mathbf{r} = (16, 0, 11, 1) \begin{pmatrix} 1 \\ 0 \\ 1 \\ 0 \end{pmatrix} = 16 \times 1 + 11 \times 1 = 27 \equiv 10 \pmod{17} $$
&lt;p>
メッセージ $M=1$ に対応する値 $\lfloor 17/2 \rfloor = 8$ を足します。
&lt;/p>
$$ v = \mathbf{b}^T \mathbf{r} + M \cdot 8 = 10 + 1 \times 8 = 18 \equiv 1 \pmod{17} $$
&lt;p>アリスは暗号文として $(\mathbf{u}, v) = \left( \begin{pmatrix} 16 \\ 3 \end{pmatrix}, 1 \right)$ をボブに送信します。&lt;/p>
&lt;p>&lt;strong>【3. 復号フェーズ】&lt;/strong>
暗号文を受け取ったボブは、秘密鍵 $\mathbf{s} = (3, 4)^T$ を用いて復号します。
復号処理式： $D = v - \mathbf{s}^T \mathbf{u} \pmod{17}$ を計算します。&lt;/p>
$$ \mathbf{s}^T \mathbf{u} = (3, 4) \begin{pmatrix} 16 \\ 3 \end{pmatrix} = 3 \times 16 + 4 \times 3 = 48 + 12 = 60 \equiv 9 \pmod{17} $$
$$ D = v - \mathbf{s}^T \mathbf{u} = 1 - 9 = -8 \pmod{17} $$
&lt;p>ここで、法17の世界では $-8$ は $9$ に等しくなります（$-8 + 17 = 9$）。
得られた値 $D = 9$ を、$0$ と $8$（$\lfloor 17/2 \rfloor$）のどちらに近いか判定します。
$9$ は $0$ よりも $8$ に明らかに近いため、ボブは正しく $M = 1$ を復元することができました！&lt;/p>
&lt;p>なぜ $9$ になったのでしょうか？ 先ほどの証明を思い出しましょう。
誤差部分は $\mathbf{e}^T \mathbf{r} = (1, -1, 0, 2) (1, 0, 1, 0)^T = 1 \times 1 + 0 \times 1 = 1$ となっています。
したがって、計算結果は $\mathbf{e}^T \mathbf{r} + M \cdot 8 = 1 + 8 = 9$ となり、理論通りの値が計算されていることが確認できました。&lt;/p>
&lt;h1 id="7-実用化に向けた進化ring-lwe-と-module-lwe">7. 実用化に向けた進化：Ring-LWE と Module-LWE
&lt;/h1>&lt;p>これまで説明してきた標準的なLWE問題（Standard LWE）は、非常に強力な安全性証明を持っていますが、実用上において致命的な弱点があります。それは「鍵のサイズが巨大になること」と「計算コストが高いこと」です。&lt;/p>
&lt;p>Standard LWEでは、公開鍵に巨大な行列 $A \in \mathbb{Z}_q^{m \times n}$ が含まれます。パラメータ $n$ が数百〜数千になると、この行列のサイズは数メガバイトに達してしまい、インターネット上の通信プロトコル（TLSなど）で毎回送受信するには重すぎます。また、行列とベクトルの乗算には $\mathcal{O}(n^2)$ の計算量がかかります。&lt;/p>
&lt;p>この問題を解決するために導入されたのが、多項式環（Polynomial rings）という代数的な構造を格子に組み込んだ「Ring-LWE（RLWE）」や「Module-LWE（MLWE）」です。&lt;/p>
&lt;h2 id="71-ring-lweの直観">7.1 Ring-LWEの直観
&lt;/h2>&lt;p>Ring-LWEでは、ベクトルや行列を、多項式環 $\mathcal{R}_q = \mathbb{Z}_q[X]/(X^n + 1)$ 上の要素（多項式）に置き換えます。（ここで $n$ は2の冪乗が選ばれます）。&lt;/p>
&lt;p>Standard LWEの公開鍵が行列 $A$ であったのに対し、Ring-LWEでは単一の多項式 $a(x)$ を用います。秘密鍵 $s(x)$ や誤差 $e(x)$ も多項式になります。
方程式は以下のようになります。
&lt;/p>
$$ b(x) = a(x) \cdot s(x) + e(x) \pmod q $$
&lt;p>これは多項式の掛け算なので、高速フーリエ変換（FFT）に類似した「数論変換（Number Theoretic Transform: NTT）」を用いることで、計算量を $\mathcal{O}(n \log n)$ にまで劇的に削減できます。さらに、公開鍵のサイズも行列から単一の多項式へと小さくなるため、データサイズが $\mathcal{O}(n)$ に削減されます。これは通信帯域において圧倒的な優位性をもたらします。&lt;/p>
&lt;p>数学的に見ると、Ring-LWEは一般的な格子ではなく、「イデアル格子（Ideal Lattice）」と呼ばれる特殊な対称性を持った格子上の問題に帰着します。&lt;/p>
&lt;h2 id="72-module-lweとnistの標準化kyber--ml-kem">7.2 Module-LWEとNISTの標準化（Kyber / ML-KEM）
&lt;/h2>&lt;p>Ring-LWEは効率的ですが、イデアル格子の特殊な代数的構造が将来的な攻撃の糸口になるのではないかという一抹の懸念がありました。そこで、Standard LWEの保守的な安全性とRing-LWEの効率性の「いいとこ取り」をしたのが「Module-LWE（MLWE）」です。&lt;/p>
&lt;p>Module-LWEでは、多項式を要素とする小さな行列とベクトルを考えます。つまり、環上のモジュール（加群）を扱います。
現在、NISTがPQCの鍵共有アルゴリズム（KEM）の標準として選定した「CRYSTALS-Kyber」（標準化名称：ML-KEM）は、まさにこのModule-LWE問題の困難性に基づいて構築されています。&lt;/p>
&lt;h1 id="8-なぜ量子コンピュータに対して安全なのか">8. なぜ量子コンピュータに対して安全なのか？
&lt;/h1>&lt;p>最後に、「なぜ格子暗号は量子コンピュータを用いても解読されないと考えられているのか？」という核心部分に触れておきます。&lt;/p>
&lt;p>量子コンピュータがRSA暗号や楕円曲線暗号を破るShorのアルゴリズムは、本質的には「隠れ部分群問題（Hidden Subgroup Problem: HSP）」を解くアルゴリズムです。RSAやECCの背景にある数学的構造（有限アーベル群）は周期性を持っており、量子フーリエ変換（QFT）という量子アルゴリズム特Actions:特有の操作を用いることで、この周期（隠れた部分群）を一気に抽出することができます。&lt;/p>
&lt;p>しかし、格子問題は根本的に異なります。格子にも周期性はありますが、SVPやCVPで求められているのは「最短の距離」や「ノイズの除去」という幾何学的な非線形な性質です。Shorのアルゴリズムのような「アーベル群上の量子フーリエ変換」をそのまま適用しても、格子問題の解答となる有用な情報を効率的に抽出することができません。現在までに、SVPやLWEに対して多項式時間で解くことができる量子アルゴリズムは発見されておらず、量子コンピュータの並列計算能力をもってしても総当たりに近い探索（グローバーのアルゴリズムによる平方根の高速化程度）しか有効な手段がないと広く信じられています。&lt;/p>
&lt;h1 id="9-まとめ">9. まとめ
&lt;/h1>&lt;p>本記事では、格子暗号の数学的直観について、格子の幾何学的定義から始まり、LWE問題の定式化、そして公開鍵暗号の構築に至るまで詳細に解説しました。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>格子（Lattice）&lt;/strong> は、基底ベクトルの整数係数線形結合で表される離散的な空間であり、高次元においては直交に近い「良い基底」を見つけること（SVP）が困難になります。&lt;/li>
&lt;li>&lt;strong>LWE問題（Learning With Errors）&lt;/strong> は、ノイズ付きの連立一次方程式を解く問題であり、これが格子の最悪ケース問題の困難性に結びついているため、強力な安全性の根拠を提供します。&lt;/li>
&lt;li>LWE問題を利用することで、ノイズを意図的に加えたり消去したりする巧妙な仕組みにより、暗号化と復号（&lt;strong>Regev暗号&lt;/strong>）が実現されます。&lt;/li>
&lt;li>現実のプロトコルでは、通信効率と計算速度を高めるために多項式環を用いた &lt;strong>Ring-LWE&lt;/strong> や &lt;strong>Module-LWE&lt;/strong> が採用されており、NIST標準の &lt;strong>ML-KEM&lt;/strong> の基盤となっています。&lt;/li>
&lt;/ol>
&lt;p>量子コンピュータという未曾有の計算パラダイムシフトが迫る中、古典的な線形代数と整数論の深淵から生まれた「格子暗号」が、未来のインターネットセキュリティの基盤を担うというのは非常にロマンのある話です。格子暗号の基礎となる数学は決して難解すぎるものではなく、線形代数と確率の基礎知識があれば十分にその美しい構造を理解することができます。本記事が、PQCの核となる格子暗号の理解への一助となれば幸いです。&lt;/p></description></item><item><title>リーマン予想と素数の分布：現代暗号技術との深い関係</title><link>http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/</link><pubDate>Fri, 11 Sep 2026 16:00:00 +0900</pubDate><guid>http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/</guid><description>&lt;img src="http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/img/eyecatch.jpg" alt="Featured image of post リーマン予想と素数の分布：現代暗号技術との深い関係" />&lt;h1 id="1-はじめに素数という宇宙の神秘とリーマン予想">1. はじめに：素数という宇宙の神秘とリーマン予想
&lt;/h1>&lt;p>「素数（Prime Numbers）」は、1と自分自身でしか割り切れない自然数であり、数学の世界における「原子」とも呼ばれます。2, 3, 5, 7, 11, 13&amp;hellip; と続くこの数列は、一見すると無秩序でランダムに現れるように見えます。古代ギリシャの数学者ユークリッドが「素数が無限に存在すること」を証明して以来、数え切れないほどの数学者たちがこの素数の並びに潜む規則性を解き明かそうと挑んできました。&lt;/p>
&lt;p>その素数の謎に最も肉薄したのが、1859年にドイツの数学者ベルンハルト・リーマン（Bernhard Riemann）が提唱した**「リーマン予想（Riemann Hypothesis）」**です。リーマン予想は、現代数学において最も重要かつ未解決の難問の一つであり、クレイ数学研究所が定めるミレニアム懸賞問題の一つとして100万ドルの賞金が懸けられています。&lt;/p>
&lt;p>一見すると、素数の分布に関する純粋数学の難問は、私たちの日常生活とは無縁に思えるかもしれません。しかし、現代社会のインフラを支えるインターネットのセキュリティ、特に&lt;strong>RSA暗号や楕円曲線暗号（ECC）といった現代暗号技術&lt;/strong>は、巨大な素数の性質に深く依存しています。&lt;/p>
&lt;p>本記事では、素数の分布から素数定理、リーマンゼータ関数、そしてリーマン予想の核心へと至る数学的な旅をし、それがどのようにして現代暗号技術と結びついているのか、そしてもしリーマン予想が証明されたら世界はどうなるのかについて、極めて詳細かつ深く掘り下げて解説します。&lt;/p>
&lt;hr>
&lt;h1 id="2-素数定理と素数の分布ガウスの発見">2. 素数定理と素数の分布：ガウスの発見
&lt;/h1>&lt;p>素数がどのように分布しているのかを理解するために、数学者たちは「ある数 $x$ 以下の素数がいくつ存在するか」を表す&lt;strong>素数計数関数（Prime-counting function）&lt;/strong> $\pi(x)$ を考えました。&lt;/p>
&lt;p>例えば：&lt;/p>
&lt;ul>
&lt;li>$\pi(10) = 4$ （2, 3, 5, 7）&lt;/li>
&lt;li>$\pi(100) = 25$&lt;/li>
&lt;li>$\pi(1000) = 168$&lt;/li>
&lt;/ul>
&lt;p>15歳の天才数学者カール・フリードリヒ・ガウス（Carl Friedrich Gauss）は、膨大な素数の表を計算し、素数の出現頻度が自然対数 $\ln x$ に反比例して減少していくことを見出しました。つまり、ある数 $x$ の付近で素数が見つかる確率は約 $\frac{1}{\ln x}$ であると予想したのです。&lt;/p>
&lt;p>これを積分を用いて表現したものが、&lt;strong>対数積分（Logarithmic integral）&lt;/strong> $\text{Li}(x)$ です。&lt;/p>
$$ \text{Li}(x) = \int_{2}^{x} \frac{dt}{\ln t} $$
&lt;p>ガウスの予想は、後に1896年にジャック・アダマールとシャルル＝ジャン・ド・ラ・ヴァレ・プーサンによって独立に証明され、**素数定理（Prime Number Theorem, PNT）**として確立されました。&lt;/p>
$$ \lim_{x \to \infty} \frac{\pi(x)}{\text{Li}(x)} = 1 $$
&lt;p>または近似的に以下のように表されます。&lt;/p>
$$ \pi(x) \sim \frac{x}{\ln x} $$
&lt;p>この定理により、素数は巨視的に見れば非常に滑らかで予測可能な分布を持っていることがわかりました。しかし、微視的に見れば $\pi(x)$ と $\text{Li}(x)$ の間には常に「誤差」すなわち「揺らぎ」が存在します。この揺らぎの正体こそが、リーマン予想が解き明かそうとしている最大のミステリーなのです。&lt;/p>
&lt;hr>
&lt;h1 id="3-リーマンゼータ関数とオイラー積">3. リーマンゼータ関数とオイラー積
&lt;/h1>&lt;p>素数の分布を解析する上で最強の武器となるのが**リーマンゼータ関数（Riemann Zeta Function）**です。もともとはレオンハルト・オイラー（Leonhard Euler）によって実数 $s > 1$ に対して定義された無限級数でした。&lt;/p>
$$ \zeta(s) = \sum_{n=1}^\infty \frac{1}{n^s} = 1 + \frac{1}{2^s} + \frac{1}{3^s} + \frac{1}{4^s} + \dots $$
&lt;p>オイラーの最大の功績の一つは、この無限級数が、すべての素数 $p$ に関する無限積として表せることを証明したことです。これが**オイラー積（Euler Product Formula）**です。&lt;/p>
$$ \zeta(s) = \prod_{p \text{ prime}} \frac{1}{1 - p^{-s}} = \left( \frac{1}{1 - 2^{-s}} \right) \left( \frac{1}{1 - 3^{-s}} \right) \left( \frac{1}{1 - 5^{-s}} \right) \dots $$
&lt;p>証明の直感的な理解は、右辺の各項を等比級数として展開し、それらを掛け合わせると、算術の基本定理（すべての自然数は素数の積として一意に表される）により、左辺の自然数の逆数和が完全に再構築されるというものです。&lt;/p>
&lt;p>&lt;strong>このたった一つの数式が、解析学（無限級数・連続関数）と数論（素数・離散的な数）を繋ぐ架け橋となりました。&lt;/strong> ゼータ関数を調べることは、素数の分布を調べることと同義なのです。&lt;/p>
&lt;hr>
&lt;h1 id="4-解析接続と複素平面への拡張">4. 解析接続と複素平面への拡張
&lt;/h1>&lt;p>リーマンの天才性は、オイラーが実数のみで考えていた $\zeta(s)$ の変数 $s$ を、**複素数 $s = \sigma + it$（$\sigma$ は実部、$t$ は虚部）**に拡張したことにあります。&lt;/p>
&lt;p>もともとの無限級数は $\sigma > 1$ でしか収束しませんが、リーマンは「解析接続（Analytic Continuation）」という手法を用いて、$s = 1$ の極を除外した全複素平面上で $\zeta(s)$ が意味を持つように定義を拡張しました。&lt;/p>
&lt;p>彼はさらに、ゼータ関数が満たす美しい関数等式（Functional equation）を導き出しました。&lt;/p>
$$ \zeta(s) = 2^s \pi^{s-1} \sin\left(\frac{\pi s}{2}\right) \Gamma(1-s) \zeta(1-s) $$
&lt;p>ここで $\Gamma(x)$ はガンマ関数です。この等式により、右半平面の性質から左半平面の性質を知ることができます。&lt;/p>
&lt;h3 id="零点zeros-of-the-zeta-function">零点（Zeros of the Zeta Function）
&lt;/h3>&lt;p>ゼータ関数の値が 0 になる複素数 $s$ を「零点」と呼びます。
関数等式から、$s$ が負の偶数（$-2, -4, -6, \dots$）のとき、$\sin(\pi s / 2)$ が 0 になるため、$\zeta(s) = 0$ となります。これらは**自明な零点（Trivial zeros）**と呼ばれます。&lt;/p>
&lt;p>しかし、素数の分布において重要なのは、それ以外の零点、すなわち $0 \le \sigma \le 1$ の「臨界領域（Critical strip）」に存在する**非自明な零点（Non-trivial zeros）**です。&lt;/p>
&lt;hr>
&lt;h1 id="5-リーマン予想の核心と明示公式">5. リーマン予想の核心と明示公式
&lt;/h1>&lt;p>リーマンは、少数の零点を計算し、ある驚くべき予想を立てました。これが&lt;strong>リーマン予想&lt;/strong>です。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>リーマン予想 (Riemann Hypothesis)&lt;/strong>
リーマンゼータ関数 $\zeta(s)$ の非自明な零点はすべて、実部が $1/2$ の直線上（$\text{Re}(s) = 1/2$）に存在する。&lt;/p>
&lt;/blockquote>
&lt;p>この実部が1/2の直線を「臨界線（Critical line）」と呼びます。&lt;/p>
&lt;div class="mermaid">graph TD
A["リーマンゼータ関数 ζ(s)"] --> B["解析接続による複素数平面への拡張"]
B --> C["自明な零点 (s = -2, -4, -6 ...)"]
B --> D["非自明な零点 (0 &lt;= Re(s) &lt;= 1)"]
D --> E["リーマン予想"]
E --> F["すべての非自明な零点は Re(s) = 1/2 上にある"]
F --> G["素数分布の誤差項の極限の証明へ"]&lt;/div>
&lt;p>なぜリーマン予想がそれほど重要なのでしょうか？それは、ゼータ関数の零点が素数の分布を&lt;strong>完全に&lt;/strong>決定しているからです。&lt;/p>
&lt;p>リーマンと後の数学者フォン・マンゴルトは、素数の分布を正確に記述する「明示公式（Explicit formula）」を導きました。チェビシェフ関数 $\psi(x)$ を用いると、以下のように表されます。&lt;/p>
$$ \psi(x) = x - \sum_{\rho} \frac{x^\rho}{\rho} - \ln(2\pi) - \frac{1}{2}\ln(1 - x^{-2}) $$
&lt;p>ここで $\rho$ はゼータ関数の非自明な零点をすべてわたる和です。
主項は $x$ （これは素数定理に対応）であり、そこから零点 $\rho$ に依存する波のような項を足し引きすることで、素数の階段状の正確な分布が復元されるのです。非自明な零点は、素数の分布の「周波数（波）」を表していると言えます。&lt;/p>
&lt;p>もしリーマン予想が正しく、すべての非自明な零点 $\rho$ の実部がちょうど $1/2$ であるならば、素数定理の誤差項は理論上考えうる最小の範囲に収まることになります。&lt;/p>
$$ |\pi(x) - \text{Li}(x)| \le \frac{1}{8\pi} \sqrt{x} \ln x \quad \text{for} \quad x \ge 2657 $$
&lt;p>つまり、&lt;strong>リーマン予想が真であれば、素数は私たちが想像し得る限り最も「規則正しく、美しく」分布している&lt;/strong>ことが証明されるのです。&lt;/p>
&lt;hr>
&lt;h1 id="6-現代暗号技術と素数の不可分な関係">6. 現代暗号技術と素数の不可分な関係
&lt;/h1>&lt;p>ここまでは深遠な純粋数学の世界でしたが、この素数の性質は現代のデジタル社会を根底から支えています。その代表が&lt;strong>RSA暗号&lt;/strong>をはじめとする公開鍵暗号方式です。&lt;/p>
&lt;p>インターネットでのクレジットカード決済、パスワードの送信、ブロックチェーンの電子署名など、あらゆる通信の安全性は「素数」に依存しています。&lt;/p>
&lt;h3 id="rsa暗号の仕組み">RSA暗号の仕組み
&lt;/h3>&lt;p>RSA暗号の安全性は、「桁数の大きい合成数の素因数分解は非常に困難である」という数学的事実（素因数分解問題）に基づいています。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>鍵生成&lt;/strong>:
巨大な素数 $p$ と $q$ （例えばそれぞれ2048ビット）をランダムに選びます。
それらを掛け合わせて $N = p \times q$ を計算します。この $N$ が公開鍵の一部となります。
オイラーのトーティエント関数 $\phi(N) = (p-1)(q-1)$ を用いて、秘密鍵 $d$ を生成します。&lt;/p>
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
&lt;/li>
&lt;li>
&lt;p>&lt;strong>暗号化と復号&lt;/strong>:
平文 $M$ は、公開鍵 $e, N$ を用いて暗号文 $C$ に変換されます。
&lt;/p>
$$ C \equiv M^e \pmod{N} $$
&lt;p>
秘密鍵 $d$ を持つ者だけが、復号できます。
&lt;/p>
$$ M \equiv C^d \pmod{N} $$
&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph LR
A["平文 (Plaintext)"] --> B["公開鍵(e, N)で暗号化"]
B --> C["暗号文 (Ciphertext)"]
C --> D["秘密鍵(d)で復号"]
D --> E["元の平文"]
F["攻撃者 (Attacker)"] -- "Nの素因数分解を試みる" --> C
F -.-> G["pとqが分からないとdは計算不能"]&lt;/div>
&lt;p>RSA暗号を破るためには、巨大な $N$ から元の素数 $p$ と $q$ を見つけ出す（素因数分解する）必要があります。現在主流のアルゴリズム（一般数体篩法：GNFSなど）を用いても、何百桁もの数を素因数分解するには、スーパーコンピュータを使っても宇宙の年齢をはるかに超える時間がかかるとされています。&lt;/p>
&lt;hr>
&lt;h1 id="7-リーマン予想が暗号技術に与えるインパクト">7. リーマン予想が暗号技術に与えるインパクト
&lt;/h1>&lt;p>では、純粋数学の頂点にある「リーマン予想」と「暗号技術」はどのように交差するのでしょうか？&lt;/p>
&lt;h3 id="71-素数生成アルゴリズム素数判定と拡張リーマン予想grh">7.1. 素数生成アルゴリズム（素数判定）と拡張リーマン予想（GRH）
&lt;/h3>&lt;p>RSA暗号を運用するためには、最初に巨大な素数 $p$ と $q$ を生成する必要があります。しかし、「ある数が素数かどうか」を確実かつ高速に判定するのは簡単ではありません。&lt;/p>
&lt;p>現在、実用的に使われているのは**ミラー・ラビン素数判定法（Miller-Rabin primality test）**という確率的アルゴリズムです。このアルゴリズムは高速ですが、極めて低い確率で合成数を素数と誤判定する「擬素数」のリスクがあります。&lt;/p>
&lt;p>しかし、リーマン予想をディリクレのL関数に拡張した**「拡張リーマン予想（Generalized Riemann Hypothesis, GRH）」&lt;strong>が真であると仮定すると、話は劇的に変わります。
GRHが真であれば、ミラー・ラビン判定法におけるテスト回数の上限が数学的に保証され、確率的アルゴリズムから&lt;/strong>「決定性多項式時間アルゴリズム」へと昇華**するのです（これは、AKS素数判定法が発見される以前から知られていた重大な事実でした）。&lt;/p>
&lt;p>つまり、リーマン予想（およびその拡張）は、「巨大な素数を絶対の自信を持って高速に生成できるか」という暗号の基盤生成に直接的なお墨付きを与える役割を持っています。&lt;/p>
&lt;h3 id="72-素因数分解アルゴリズムとの関係">7.2. 素因数分解アルゴリズムとの関係
&lt;/h3>&lt;p>暗号を解読する側のアルゴリズム（一般数体篩法など）の計算量を評価する際にも、素数の分布に関する知識が不可欠です。素因数分解アルゴリズムの多くは「滑らかな数（Smooth numbers：小さな素因数しか持たない数）」の分布に依存しています。&lt;/p>
&lt;p>滑らかな数がどの程度の頻度で現れるかを厳密に評価するためには、素数の分布に関する深い理解が必要であり、ここでもゼータ関数やリーマン予想に直結する解析的整数論のテクニックが駆使されています。リーマン予想が証明され、素数分布の誤差が完全に決定されれば、素因数分解アルゴリズムの性能限界もより正確に見極めることが可能になります。&lt;/p>
&lt;hr>
&lt;h1 id="8-もしリーマン予想が証明されたら暗号は破られるのか">8. もしリーマン予想が証明されたら、暗号は破られるのか？
&lt;/h1>&lt;p>都市伝説のように「リーマン予想が解けたらRSA暗号は一瞬で崩壊する」と語られることがありますが、&lt;strong>これは数学的には不正確&lt;/strong>です。&lt;/p>
&lt;p>リーマン予想の証明自体が、直ちに素因数分解を劇的に高速化する魔法のアルゴリズムを生み出すわけではありません。リーマン予想はあくまで素数の「巨視的な分布の規則性」についての定理であり、個別の数 $N$ がどの素数で割り切れるか（局所的な性質）を直接教えてくれるものではないからです。&lt;/p>
&lt;p>しかし、影響はゼロではありません。
リーマン予想が証明される過程で、&lt;strong>「新たな数学的ツール」や「未知の解析手法」が発見される可能性&lt;/strong>が極めて高いからです。歴史を見ても、フェルマーの最終定理やポアンカレ予想が証明された際、その過程で開発された新しい理論が数学全体を大きく飛躍させました。&lt;/p>
&lt;p>もしリーマンゼータ関数の零点の性質を完全に操作できる未知の代数幾何学的手法や、非可換幾何の手法が確立されれば、それが結果として素因数分解の画期的なアルゴリズム（例えば、計算量を多項式時間に落とし込むような古典アルゴリズム）の発見に繋がる可能性は否定できません。その意味で、暗号学者はリーマン予想の動向から決して目を離すことができないのです。&lt;/p>
&lt;h3 id="量子コンピュータとショアのアルゴリズム">量子コンピュータとショアのアルゴリズム
&lt;/h3>&lt;p>暗号技術にとってより直接的で現実的な脅威は、リーマン予想の証明ではなく&lt;strong>量子コンピュータ&lt;/strong>です。1994年にピーター・ショア（Peter Shor）が発表した「ショアのアルゴリズム」は、十分な性能を持つ量子コンピュータがあれば、素因数分解を多項式時間で解けることを証明しました。これにより、RSA暗号や楕円曲線暗号は根本的に破られることになります。&lt;/p>
&lt;p>現在、世界中で量子コンピュータでも解読できない「耐量子計算機暗号（Post-Quantum Cryptography, PQC）」への移行（格子暗号など）が進められています。素数に依存した暗号技術は、ある意味で黄金期を終えようとしているのかもしれませんが、素数そのものの数学的価値が失われることは永遠にありません。&lt;/p>
&lt;hr>
&lt;h1 id="9-結び数学の抽象性と現実社会の交差点">9. 結び：数学の抽象性と現実社会の交差点
&lt;/h1>&lt;div class="mermaid">graph TD
A["純粋数学の探求"] --> B["リーマン予想の解明"]
B --> C["素数分布の完全な理解"]
C --> D["数論・代数幾何学の飛躍的発展"]
D -.-> E["新しい素因数分解アルゴリズムの可能性"]
E -.-> F["暗号技術の安全性評価の更新"]
A --> G["応用数学・計算機科学"]
G --> H["素数判定・暗号生成の効率化"]
H --> F&lt;/div>
&lt;p>古代ギリシャから続く素数への飽くなき探求は、リーマンという天才によって複素数平面上の美しいシンフォニー（ゼータ関数の零点）へと昇華されました。そして驚くべきことに、その純粋無垢な数学の結晶が、数世紀の時を経てインターネット社会の安全を担保する最強の盾として応用されています。&lt;/p>
&lt;p>リーマン予想は、数学が持つ「抽象的な美しさ」と「物理世界・現実社会への驚異的な適用力」を同時に象徴する存在です。&lt;/p>
&lt;p>未だ誰も頂上に到達していないこの巨大な数学の山がいつの日か征服されたとき、私たちは素数という宇宙の真理を完全に理解するとともに、情報化社会の基盤に対して新たな視点を持つことになるでしょう。暗号技術を学ぶことは、そのまま人類の英知の歴史を辿る旅でもあるのです。&lt;/p></description></item><item><title>C++で実装する高速な素数判定アルゴリズム（Miller-Rabin法など）</title><link>http://kenji.blog/p/cpp-fast-prime-testing-miller-rabin/</link><pubDate>Fri, 11 Sep 2026 14:00:00 +0900</pubDate><guid>http://kenji.blog/p/cpp-fast-prime-testing-miller-rabin/</guid><description>&lt;img src="http://kenji.blog/p/cpp-fast-prime-testing-miller-rabin/img/eyecatch.jpg" alt="Featured image of post C++で実装する高速な素数判定アルゴリズム（Miller-Rabin法など）" />&lt;h1 id="はじめになぜ高速な素数判定が必要なのか">はじめに：なぜ高速な素数判定が必要なのか
&lt;/h1>&lt;p>情報科学や暗号理論、あるいは競技プログラミングの世界において、「ある数が素数であるかどうか」を高速かつ正確に判定することは、非常に重要で基礎的な課題です。例えば、現代のインターネット社会のセキュリティを支えているRSA暗号などの公開鍵暗号方式は、巨大な素数の生成とその掛け算の困難性（素因数分解の難しさ）を安全性の根拠としています。したがって、巨大な数が素数であるかを瞬時に見分ける技術は、デジタル社会の根幹を支える技術と言っても過言ではありません。&lt;/p>
&lt;p>また、競技プログラミング（AtCoderやCodeforcesなど）においても、素数判定は頻出のテーマです。制約が $N \le 10^{18}$ といった巨大な入力に対して、1秒以内で素数判定を数万回行うようなシチュエーションでは、伝統的で素朴なアルゴリズムでは到底計算時間（Time Limit Exceeded: TLE）に間に合いません。&lt;/p>
&lt;p>本記事では、素朴な素数判定アルゴリズムから始まり、確率的素数判定法である「フェルマーテスト」、そしてその弱点を克服した実用上最強レベルの高速アルゴリズム「ミラー・ラビン（Miller-Rabin）素数判定法」について、数学的な背景からC++による高度に最適化された実装まで、徹底的に解説します。特に、64ビット整数（$N &lt; 2^{64}$）に対しては、確率的な判定にとどまらず「100%確実に素数判定ができる（決定論的判定）」手法についても詳しく説明し、実践でそのまま使えるC++のソースコードを提供します。&lt;/p>
&lt;hr>
&lt;h1 id="1-素数判定の基礎と試し割り法-trial-division">1. 素数判定の基礎と試し割り法 (Trial Division)
&lt;/h1>&lt;p>素数（Prime number）とは、1と自分自身以外に正の約数を持たない、2以上の自然数のことです。素数の定義に忠実に従えば、ある整数 $N$ が素数かどうかを判定するには、$2$ から $N-1$ までのすべての整数で $N$ を割ってみて、一度も割り切れなければ素数、一度でも割り切れれば合成数（素数でない）と判断できます。&lt;/p>
&lt;p>しかし、この方法は計算量が $O(N)$ となり、$N$ が $10^{18}$ などの巨大な数の場合、現代のコンピュータでも計算に膨大な時間がかかってしまいます。&lt;/p>
&lt;h2 id="試し割り法の最適化sqrtn-までの探索">試し割り法の最適化：$\sqrt{N}$ までの探索
&lt;/h2>&lt;p>合成数 $N$ が $a \times b = N$ （$a \le b$）と表されるとき、必ず $a \le \sqrt{N}$ となります。したがって、素数判定のループは $N-1$ まで回す必要はなく、$\sqrt{N}$ まで調べれば十分です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 試し割り法による素数判定 (O(sqrt(N)))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">is_prime_trial_division&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 3以上の奇数のみ調べる
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>このアルゴリズムの計算量は $O(\sqrt{N})$ です。$N \le 10^{12}$ 程度であれば一瞬で計算できますが、$N \approx 10^{18}$ の場合、ループ回数が約 $10^9$ 回となり、C++の処理系であっても数百ミリ秒から数秒の時間を要するため、複数回の判定には不向きです。&lt;/p>
&lt;hr>
&lt;h1 id="2-フェルマーテスト確率的素数判定の幕開け">2. フェルマーテスト：確率的素数判定の幕開け
&lt;/h1>&lt;p>試し割り法の限界を突破するために考え出されたのが、数論の定理を用いた「確率的アルゴリズム（Probabilistic Algorithm）」です。その代表例が、フェルマーの小定理を利用した「フェルマーテスト（Fermat Primality Test）」です。&lt;/p>
&lt;h2 id="フェルマーの小定理-fermats-little-theorem">フェルマーの小定理 (Fermat&amp;rsquo;s Little Theorem)
&lt;/h2>&lt;p>ピエール・ド・フェルマー（Pierre de Fermat）が発見したこの定理は、次のように主張しています。&lt;/p>
&lt;blockquote>
&lt;p>任意の素数 $p$ と、互いに素な（$p$ の倍数ではない）任意の整数 $a$ について、以下の合同式が成り立つ。
&lt;/p>
$$ a^{p-1} \equiv 1 \pmod p $$
&lt;/blockquote>
&lt;p>この定理の対偶をとると、「ある整数 $N$ と、$N$ と互いに素な整数 $a$ に対して $a^{N-1} \not\equiv 1 \pmod N$ となるならば、$N$ は確実に合成数である」と言えます。この性質を利用して、判定対象の数 $N$ に対してランダムな底（base）$a$ を選び、$a^{N-1} \pmod N$ を計算して $1$ になるか確認するのがフェルマーテストです。&lt;/p>
&lt;h2 id="高速なべき乗剰余繰り返し二乗法">高速なべき乗剰余（繰り返し二乗法）
&lt;/h2>&lt;p>フェルマーテストを行うためには、$a^{N-1} \pmod N$ という巨大なべき乗を高速に計算する必要があります。これには「繰り返し二乗法（Modular Exponentiation / Binary Exponentiation）」を用います。計算量は $O(\log N)$ となり、非常に高速です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 繰り返し二乗法を用いた a^b mod m の計算
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="nf">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128_t&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">res&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128_t&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">b&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>※ ここではオーバーフローを防ぐため、GCC/Clangの拡張である &lt;code>__int128_t&lt;/code>（128ビット整数）を使用して途中の積を保持しています。&lt;/p>
&lt;h2 id="疑似素数とカーマイケル数-carmichael-numbers">疑似素数とカーマイケル数 (Carmichael Numbers)
&lt;/h2>&lt;p>フェルマーテストは非常に強力ですが、致命的な弱点があります。それは、$N$ が合成数であるにもかかわらず、すべての $a$ （$N$ と互いに素な $a$）に対して $a^{N-1} \equiv 1 \pmod N$ が成立してしまうような数が存在するということです。&lt;/p>
&lt;p>このような数を「絶対疑似素数」または「カーマイケル数」と呼びます。最小のカーマイケル数は $561 = 3 \times 11 \times 17$ です。
カーマイケル数が存在するため、フェルマーテストだけでは「確率100%」の決定論的判定を行うことができません。いくら異なる $a$ を試しても、$561$ のような数は常に素数のフリ（騙し）をしてしまいます。&lt;/p>
&lt;hr>
&lt;h1 id="3-ミラーラビン素数判定法-miller-rabin-primality-test">3. ミラー・ラビン素数判定法 (Miller-Rabin Primality Test)
&lt;/h1>&lt;p>フェルマーテストの弱点（カーマイケル数の存在）を見事に克服したのが、ミラー（Gary L. Miller）とラビン（Michael O. Rabin）によって考案された「ミラー・ラビン素数判定法」です。
現在、実用的な高速素数判定アルゴリズムとして、さまざまなプログラミング言語の内部ライブラリや暗号システムの鍵生成で最も広く使われています。&lt;/p>
&lt;h2 id="数学的原理">数学的原理
&lt;/h2>&lt;p>ミラー・ラビンのアルゴリズムは、フェルマーの小定理に加えて、「素数を法とする剰余体（$\mathbb{Z}/p\mathbb{Z}$）においては、$x^2 \equiv 1 \pmod p$ の解は $x \equiv 1$ または $x \equiv -1$ に限られる」という性質を利用します（合成数を法とする場合は、これ以外の非自明な平方根が存在し得ます）。&lt;/p>
&lt;p>判定したい奇数 $N$ から $1$ を引いた $N-1$ は必ず偶数になります。そこで、$N-1$ を $2$ で割れるだけ割り、次のような形式で表します。
&lt;/p>
$$ N-1 = d \cdot 2^s $$
&lt;p>
（ここで、$d$ は奇数、$s \ge 1$）&lt;/p>
&lt;p>任意の底 $a$ （$1 &lt; a &lt; N-1$）について、$a^{N-1} \equiv 1 \pmod N$ であるかをフェルマーの小定理に従って検証しますが、その計算を段階的に行います。
具体的には、$a^d, a^{d \cdot 2}, a^{d \cdot 4}, \ldots, a^{d \cdot 2^s}$ と順番に2乗を繰り返していきます。&lt;/p>
&lt;p>ミラー・ラビンテストが $N$ を「素数である（あるいは強い確率で素数である）」と判定するための条件は、以下の &lt;strong>いずれか&lt;/strong> が成立することです。&lt;/p>
&lt;ol>
&lt;li>$a^d \equiv 1 \pmod N$&lt;/li>
&lt;li>ある $r$ （$0 \le r &lt; s$）が存在して、$a^{d \cdot 2^r} \equiv -1 \pmod N$ が成り立つ。
※ C++のモジュロ演算では $-1 \pmod N$ は $N-1$ となります。&lt;/li>
&lt;/ol>
&lt;p>もし $N$ が素数であれば、任意の $a$ についてこの条件が必ず満たされます。逆に $N$ が合成数であれば、ランダムな $a$ を選んだとき、この条件を満たす確率（騙される確率）は $\frac{1}{4}$ 以下であることが数学的に証明されています。
$k$ 回の独立したテストを行えば、誤判定の確率は $\left(\frac{1}{4}\right)^k$ 以下となり、実用上ゼロとみなすことができます。カーマイケル数のような「絶対にごまかせる」数は存在しません。&lt;/p>
&lt;h2 id="ミラーラビン法のアルゴリズムの流れmermaidフローチャート">ミラー・ラビン法のアルゴリズムの流れ（Mermaidフローチャート）
&lt;/h2>&lt;p>以下の図は、ミラー・ラビン素数判定法の1回のテスト（1つの底 $a$ に対するテスト）の論理的なフローを示しています。&lt;/p>
&lt;div class="mermaid">graph TD
Start["テスト開始 (入力: N, a)"] --> CalcDS["N-1 = d * 2^s を満たす d (奇数) と s を計算"]
CalcDS --> CalcX["x = a^d mod N を計算"]
CalcX --> CheckX1{"x == 1 または x == N-1 ?"}
CheckX1 -- "Yes" --> ReturnTrue["素数の可能性がある (Probably Prime)"]
CheckX1 -- "No" --> LoopStart["r = 1 から s-1 までループ開始"]
LoopStart --> LoopCondition{"r &lt; s ?"}
LoopCondition -- "No" --> ReturnFalse["確実に合成数 (Composite)"]
LoopCondition -- "Yes" --> SquareX["x = (x * x) mod N を計算"]
SquareX --> CheckXMinus1{"x == N - 1 ?"}
CheckXMinus1 -- "Yes" --> ReturnTrue
CheckXMinus1 -- "No" --> CheckXOne{"x == 1 ?"}
CheckXOne -- "Yes" --> ReturnFalse
CheckXOne -- "No" --> LoopNext["r を 1 増加させて次へ"]
LoopNext --> LoopCondition&lt;/div>
&lt;hr>
&lt;h1 id="4-64ビット整数に対する決定論的判定">4. 64ビット整数に対する決定論的判定
&lt;/h1>&lt;p>ミラー・ラビン素数判定法は本来「確率的」アルゴリズムですが、$N$ の上限が固定されている場合、特定の複数の $a$ （底）をすべて試すことで、「100%確実に」素数判定を行うことができます。
これを &lt;strong>決定論的ミラー・ラビンテスト（Deterministic Miller-Rabin Test）&lt;/strong> と呼びます。&lt;/p>
&lt;p>Jim Sinclairらの研究により、$N &lt; 2^{64}$ （約 $1.8 \times 10^{19}$）のすべての整数に対しては、以下の $7$ つの素数を底 $a$ として選んでテストすれば、十分かつ完全に決定論的な判定が可能であることが判明しています。&lt;/p>
&lt;p>&lt;strong>テストすべき底 $a$ のリスト:&lt;/strong>
&lt;code>{2, 325, 9375, 28178, 450775, 9780504, 1795265022}&lt;/code>&lt;/p>
&lt;p>あるいは、よく知られた別のセットとして、以下の $12$ 個の素数を使用することでも $N &lt; 2^{64}$ 以下で完璧に判定できます。
&lt;code>{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}&lt;/code>&lt;/p>
&lt;p>今回は、アルゴリズムの簡潔さと信頼性を高めるために、後者の $12$ 個の素数をベース（あるいはより最適化された $7$ つのベース）として用いる手法を採用します。C++の実装では、条件分岐で範囲を分けることで、テスト回数を最小限に抑える最適化を行います。&lt;/p>
&lt;hr>
&lt;h1 id="5-cによる高度な実装-highly-optimized-c-implementation">5. C++による高度な実装 (Highly Optimized C++ Implementation)
&lt;/h1>&lt;p>それでは、ここまでの数学的な理論とアルゴリズムの設計をまとめ上げ、現代のC++における最強レベルのミラー・ラビン素数判定関数の実装コードを提示します。&lt;/p>
&lt;h2 id="実装のポイント">実装のポイント
&lt;/h2>&lt;ol>
&lt;li>
&lt;p>&lt;strong>64ビット整数の掛け算のオーバーフロー回避:&lt;/strong>
$N \approx 10^{18}$ の場合、モジュロ乗算における $x \times x$ は最大 $10^{36}$ となり、通常の64ビット整数（&lt;code>uint64_t&lt;/code> や &lt;code>long long&lt;/code>）の最大値 $1.8 \times 10^{19}$ を軽々とオーバーフローしてしまいます。
この問題を解決するため、GCCやClangの拡張型である &lt;code>__int128_t&lt;/code>（あるいは &lt;code>unsigned __int128&lt;/code>）を使用し、128ビット精度で計算を行った後にモジュロを取ります。これにより、複雑なアルゴリズムを使わずとも高速に剰余乗算が可能です。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>決定論的ベースの選択:&lt;/strong>
$N$ の値が小さい場合は、少しの底（base）をテストするだけで済むように最適化します。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="完全なcソースコード">完全なC++ソースコード
&lt;/h2>&lt;p>以下に、実戦投入可能な完成版のソースコードを示します。このコードは競技プログラミング等の環境にそのままコピーして利用可能です。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;span class="lnt">87
&lt;/span>&lt;span class="lnt">88
&lt;/span>&lt;span class="lnt">89
&lt;/span>&lt;span class="lnt">90
&lt;/span>&lt;span class="lnt">91
&lt;/span>&lt;span class="lnt">92
&lt;/span>&lt;span class="lnt">93
&lt;/span>&lt;span class="lnt">94
&lt;/span>&lt;span class="lnt">95
&lt;/span>&lt;span class="lnt">96
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;vector&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cstdint&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;initializer_list&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 128ビット整数による高速な (a * b) mod m
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">inline&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="p">)((&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="n">__int128&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 繰り返し二乗法による (base^exp) mod m の計算
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">exp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ミラー・ラビン素数判定法による 64bit 整数の決定論的判定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">is_prime_miller_rabin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 境界値と小さな素数の事前判定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// n-1 = d * 2^s の形に分解する
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">d&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 判定に使用する底 (bases) のリスト
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// N の大きさに応じてテストするベースの数を最小限に抑える最適化
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">bases&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">4759123141ULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">61&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1122004669633ULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">23&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1662803&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// N &amp;lt; 2^64 のすべての数に対して決定論的となる 7 つの底
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">325&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9375&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">28178&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">450775&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9780504&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1795265022&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 各底についてテストを実行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nl">a&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">bases&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// a が n の倍数の場合は判定不能だが素数ではない
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 第一条件クリア、次の底へ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="n">composite&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// s-1 回のループ (x = x^2 mod n)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">composite&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 第二条件クリア、素数の可能性あり
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// どの条件も満たさなければ確実に合成数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">composite&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// すべてのベースで条件をクリアした場合は確実に素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// テスト用のサンプル
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">test_cases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1000000007&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 有名な素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">998244353&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 有名な素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">1000000000000000003&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 10^18近辺の素数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">1000000000000000007&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 合成数 (10^18 + 7)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">561&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// カーマイケル数 (合成数)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">18446744073709551557ULL&lt;/span> &lt;span class="c1">// 2^64 近辺の最大の素数の一つ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nl">n&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">test_cases&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; is &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">is_prime_miller_rabin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s">&amp;#34;Prime&amp;#34;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s">&amp;#34;Composite&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h1 id="6-アルゴリズムの計算量とパフォーマンス評価">6. アルゴリズムの計算量とパフォーマンス評価
&lt;/h1>&lt;p>実装したアルゴリズムの性能について考察します。&lt;/p>
&lt;h2 id="時間計算量-time-complexity">時間計算量 (Time Complexity)
&lt;/h2>&lt;ul>
&lt;li>&lt;strong>試し割り法:&lt;/strong> $O(\sqrt{N})$&lt;/li>
&lt;li>&lt;strong>フェルマーテスト:&lt;/strong> べき乗計算 $O(\log N) \times k$ （$k$ は試行回数）&lt;/li>
&lt;li>&lt;strong>ミラー・ラビン法:&lt;/strong> べき乗計算とループ $O(\log N) \times k$&lt;/li>
&lt;/ul>
&lt;p>64ビット環境（$N \le 2^{64}$）において、上記の決定論的ミラー・ラビン法は、最大でも $7$ つのベースしか検証しません。したがって、$k \le 7$ の定数とみなすことができ、全体の時間計算量は厳密に $O(\log N)$ となります。
最大ケース（$N \approx 10^{19}$）であっても、実行ステップ数は高々 $7 \times 64 = 448$ ステップの基本演算に収まり、実行時間は数マイクロ秒（$10^{-6}$ 秒）以下です。試し割り法の $O(\sqrt{N})$ （ループ回数 $\approx 4 \times 10^9$ 回）と比較すると、&lt;strong>数百万倍の高速化&lt;/strong> が達成されています。&lt;/p>
&lt;h2 id="さらなる最適化モンゴメリ乗算-montgomery-multiplication">さらなる最適化：モンゴメリ乗算 (Montgomery Multiplication)
&lt;/h2>&lt;p>本記事の実装では、128ビット整数の拡張型 &lt;code>__int128_t&lt;/code> を用いて除算（モジュロ演算 &lt;code>%&lt;/code>）を行っています。現代のCPUであっても、整数除算（DIV命令）は加算や乗算に比べて数十サイクルを要するコストの高い命令です。&lt;/p>
&lt;p>より極限の最適化を追求するライブラリ作成者や競技プログラマは、&lt;strong>モンゴメリ乗算（Montgomery Multiplication）&lt;/strong> と呼ばれる手法を採用することがあります。モンゴメリ乗算は、数値を特殊な「モンゴメリ空間」に写像することで、高コストなモジュロ演算（除算）を「ビットシフトと乗算のみ」に置き換える驚異的なアルゴリズムです。
これをミラー・ラビン判定の剰余乗算に組み込むことで、さらに実行速度を2倍から3倍程度引き上げることが可能です。これについては非常に奥深いテーマとなるため、別の記事で詳細に解説したいと思います。&lt;/p>
&lt;hr>
&lt;h1 id="7-まとめ">7. まとめ
&lt;/h1>&lt;p>本記事では、素数判定の基礎から発展的な内容までを一挙に解説しました。
ポイントを振り返りましょう。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>試し割り法&lt;/strong> は確実だが、計算量 $O(\sqrt{N})$ のため $N$ が $10^{12}$ を超えると実用性に欠ける。&lt;/li>
&lt;li>&lt;strong>フェルマーテスト&lt;/strong> は $O(\log N)$ と非常に高速だが、カーマイケル数などの絶対疑似素数に騙されるという致命的な弱点がある。&lt;/li>
&lt;li>&lt;strong>ミラー・ラビン素数判定法&lt;/strong> は、フェルマーテストの弱点を解消した実用的で最強のアルゴリズム。&lt;/li>
&lt;li>C++による実装では、&lt;code>__int128_t&lt;/code> を活用することで 64ビット整数の乗算オーバーフローを安全に処理できる。&lt;/li>
&lt;li>64ビット整数の範囲内（$N &lt; 2^{64}$）であれば、$7$ 個または $12$ 個の特定の素数を底として選ぶことで、確率的ではなく&lt;strong>決定論的（100%正確）に素数判定が可能&lt;/strong>。&lt;/li>
&lt;/ol>
&lt;p>高速な素数判定は、巨大な数を扱う計算において避けては通れない技術です。本記事で提供したC++のミラー・ラビンのソースコードは、そのまま実戦で活用できる堅牢なものになっています。ぜひ、ご自身のプロジェクトやアルゴリズム競技で活用してみてください。&lt;/p>
&lt;div class="mermaid">graph LR
TrialDivision["試し割り法 (O(√N))"] --> Fermat["フェルマーテスト (O(log N), 弱点あり)"]
Fermat --> MillerRabin["ミラー・ラビン法 (O(log N), 決定論的化可能)"]
MillerRabin --> Montgomery["+ モンゴメリ乗算 (定数倍高速化)"]
style MillerRabin fill:#f9f,stroke:#333,stroke-width:2px&lt;/div>
&lt;p>プログラミングと数学が交差するアルゴリズムの世界は非常に美しく、奥深いものです。今後の学習の一助となれば幸いです。&lt;/p>
&lt;hr>
&lt;p>&lt;em>Reference:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;em>Pomerance, C., Selfridge, J. L., &amp;amp; Wagstaff, S. S. (1980). The pseudoprimes to 25.10^9. Mathematics of Computation.&lt;/em>&lt;/li>
&lt;li>&lt;em>Sinclair, J. (2011). Deterministic Miller-Rabin primality testing.&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>【数学解説】RSA暗号の仕組みを高校生でもわかるように説明する</title><link>http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/</link><pubDate>Fri, 11 Sep 2026 13:00:00 +0900</pubDate><guid>http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/</guid><description>&lt;img src="http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/img/eyecatch.jpg" alt="Featured image of post 【数学解説】RSA暗号の仕組みを高校生でもわかるように説明する" />&lt;p>インターネット社会の安全を根底から支えている技術の一つが「RSA暗号」です。オンラインショッピングでのクレジットカード決済、友人とのSNSのやり取り、会社の機密情報の送受信など、私たちが毎日何気なく使っている通信の多くは、このRSA暗号やその後継技術によって守られています。&lt;/p>
&lt;p>しかし、「暗号」と聞くと、スパイ映画に出てくるような複雑な暗号機や、一部の天才しか理解できない超高度な数学を想像するかもしれません。確かに現代の暗号理論は高度な数学に基づいていますが、&lt;strong>RSA暗号の根本的な仕組みは、高校で習う数学（整数の性質、素数、合同式など）の知識があれば十分に理解できる&lt;/strong>ものです。&lt;/p>
&lt;p>この記事では、高校数学の知識を出発点として、RSA暗号がどのような数学的原理で動いているのか、なぜ解読が難しいのかを、ステップバイステップで徹底的に解説します。数学が少し苦手な方でも理解できるように、具体例を交えながら丁寧に説明していきます。&lt;/p>
&lt;hr>
&lt;h2 id="1-共通鍵暗号と公開鍵暗号">1. 共通鍵暗号と公開鍵暗号
&lt;/h2>&lt;p>RSA暗号の数学的な仕組みに入る前に、まず暗号の基本的な考え方について整理しておきましょう。暗号方式は大きく分けて「共通鍵暗号」と「公開鍵暗号」の2種類があります。&lt;/p>
&lt;h3 id="11-共通鍵暗号方式の限界">1.1 共通鍵暗号方式の限界
&lt;/h3>&lt;p>昔から使われている暗号の多くは「共通鍵暗号方式」と呼ばれるものです。これは、&lt;strong>「暗号化（メッセージを秘密の暗号文に変換すること）」と「復号（暗号文を元のメッセージに戻すこと）」に同じ鍵を使う&lt;/strong>方式です。&lt;/p>
&lt;p>例えば、アリスがボブに秘密の手紙を送るとします。アリスは南京錠（共通鍵）を使って箱に手紙を入れて鍵をかけます。ボブがその箱を開けるためには、アリスが使ったのと同じ鍵を持っている必要があります。&lt;/p>
&lt;p>この方式には大きな問題があります。「鍵の配送問題」です。遠く離れたアリスとボブが初めて通信する場合、どのようにして盗聴されることなく鍵を共有すればよいのでしょうか？もし鍵を郵送している途中で第三者に盗まれたら、その後の暗号通信はすべて筒抜けになってしまいます。&lt;/p>
&lt;h3 id="12-画期的な発明公開鍵暗号方式">1.2 画期的な発明「公開鍵暗号方式」
&lt;/h3>&lt;p>この鍵の配送問題を解決するために考案されたのが「公開鍵暗号方式」です。RSA暗号もこの一種です。&lt;/p>
&lt;p>公開鍵暗号方式では、&lt;strong>「暗号化するための鍵（公開鍵）」と「復号するための鍵（秘密鍵）」という2つの異なる鍵&lt;/strong>を使います。&lt;/p>
&lt;ol>
&lt;li>受信者のボブは、「公開鍵」と「秘密鍵」のペアを作成します。&lt;/li>
&lt;li>ボブは「公開鍵」を世界中に公開します（誰が手に入れても構いません）。&lt;/li>
&lt;li>送信者のアリスは、ボブの「公開鍵」を使ってメッセージを暗号化し、送信します。&lt;/li>
&lt;li>暗号化されたメッセージは、ボブだけが持っている「秘密鍵」でしか復号できません。&lt;/li>
&lt;/ol>
&lt;p>これを南京錠で例えると、ボブは「開いた状態の南京錠（公開鍵）」をたくさん作って世界中にばらまきます。アリスはボブ宛のメッセージを箱に入れ、拾ったボブの南京錠でカチャッと鍵をかけます。南京錠は一度閉まると、ボブが持っている「マスターキー（秘密鍵）」でしか開けられません。途中で誰かが箱を盗んでも、マスターキーがないので開けられないのです。&lt;/p>
&lt;div class="mermaid">graph TD
A["アリス (送信者)"] --> B["平文 (メッセージ)"]
B --> C["暗号化処理"]
D["ボブの公開鍵 (誰でも入手可能)"] --> C
C --> E["インターネット経由で送信: 暗号文"]
E --> F["復号処理"]
G["ボブの秘密鍵 (ボブだけが持つ)"] --> F
F --> H["復元された平文 (メッセージ)"]
H --> I["ボブ (受信者)"]&lt;/div>
&lt;p>この画期的なシステムを実現するためには、「公開鍵で簡単に暗号化できるが、秘密鍵がないと絶対に復号できない」という、ある種の**「一方向性関数（一方通行の数学的パズル）」**が必要になります。そのパズルの部品として目をつけられたのが、私たちがよく知る「素数」でした。&lt;/p>
&lt;hr>
&lt;h2 id="2-rsa暗号を支える数学的基礎1素数と素因数分解">2. RSA暗号を支える数学的基礎1：素数と素因数分解
&lt;/h2>&lt;p>RSA暗号の安全性は、**「巨大な数の素因数分解は非常に難しい」**という数学的事実に基づいています。&lt;/p>
&lt;h3 id="21-素数とは">2.1 素数とは
&lt;/h3>&lt;p>素数とは、「1と自分自身でしか割り切れない、1より大きい自然数」のことです。
例：$2, 3, 5, 7, 11, 13, 17, 19, 23...$&lt;/p>
&lt;p>素数は、すべての整数の「原子」のようなものです。どんな自然数も、素数の掛け算の形に分解することができます。これを&lt;strong>素因数分解&lt;/strong>と呼びます。例えば、$60 = 2^2 \times 3 \times 5$ のように、順番を無視すればただ一通りに素因数分解できることは「算術の基本定理」として知られています。&lt;/p>
&lt;h3 id="22-素因数分解の難しさ一方向性関数">2.2 素因数分解の難しさ（一方向性関数）
&lt;/h3>&lt;p>ここで重要なのは、**「掛け算は簡単だが、素因数分解は難しい」**という非対称性です。&lt;/p>
&lt;p>例えば、次の2つの素数の掛け算を暗算で計算してみてください。
$11 \times 13 = ?$
これは簡単ですね。答えは $143$ です。&lt;/p>
&lt;p>では、次の数はどうでしょう？
$323$ を素因数分解してください。
どうでしょうか？少し時間がかかるはずです。（答えは $17 \times 19$ です）。&lt;/p>
&lt;p>数が小さければ人間でも何とか計算できますが、数が大きくなるとコンピュータを使っても計算が爆発的に難しくなります。現在主流のRSA暗号では、2048ビット（10進数で約600桁）という途方もなく巨大な素数 $p$ と $q$ を掛け合わせた数 $N = p \times q$ を使います。&lt;/p>
&lt;p>2つの巨大な素数 $p$ と $q$ が与えられたとき、$N$ を計算するのはコンピュータにとって一瞬（ミリ秒以下）です。しかし、逆に $N$ だけを与えられて元の $p$ と $q$ を見つけ出すことは、現在の最速のスーパーコンピュータを何兆年回しても解けないほど時間がかかります。&lt;/p>
&lt;p>この**「計算の非対称性（片道は簡単、逆道は困難）」**が、公開鍵と秘密鍵の関係性を作り出す土台になります。&lt;/p>
&lt;hr>
&lt;h2 id="3-rsa暗号を支える数学的基礎2合同式モジュロ演算">3. RSA暗号を支える数学的基礎2：合同式（モジュロ演算）
&lt;/h2>&lt;p>RSA暗号の計算は、私たちが普段使っているような無限に数が大きくなる足し算や掛け算ではなく、ある数で割った「余り」の世界で行われます。これを**合同式（モジュロ演算）**と呼びます。&lt;/p>
&lt;h3 id="31-時計の数学">3.1 時計の数学
&lt;/h3>&lt;p>モジュロ演算はよく「時計の数学」に例えられます。現在10時だとして、そこから5時間後は何時でしょうか？ $10 + 5 = 15$ 時ですが、通常の12時間時計では「3時」と答えます。これは、15を12で割った余りが3だからです。&lt;/p>
&lt;p>数学の世界では、これを次のように書き表します。
&lt;/p>
$$ 15 \equiv 3 \pmod{12} $$
&lt;p>
「15と3は、12を法として合同である（12で割った余りが等しい）」と読みます。&lt;/p>
&lt;h3 id="32-合同式の基本的な性質">3.2 合同式の基本的な性質
&lt;/h3>&lt;p>合同式には、等式（$=$）とよく似た非常に便利な性質があります。法（割る数）を $N$ とします。
$a \equiv b \pmod N$ かつ $c \equiv d \pmod N$ のとき、以下が成り立ちます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>足し算:&lt;/strong> $a + c \equiv b + d \pmod N$&lt;/li>
&lt;li>&lt;strong>引き算:&lt;/strong> $a - c \equiv b - d \pmod N$&lt;/li>
&lt;li>&lt;strong>掛け算:&lt;/strong> $a \times c \equiv b \times d \pmod N$&lt;/li>
&lt;li>&lt;strong>べき乗:&lt;/strong> $a^k \equiv b^k \pmod N$ （$k$ は自然数）&lt;/li>
&lt;/ol>
&lt;p>特に重要なのは「べき乗」の性質です。これは**「余りの累乗は、累乗の余りに等しい」**ことを意味します。
例えば、$7^{100}$ を $5$ で割った余りを求めたいとします。真面目に $7$ を100回掛けてから $5$ で割るのは大変ですが、合同式の性質を使えば、$7 \equiv 2 \pmod 5$ なので、$7^{100} \equiv 2^{100} \pmod 5$ となり、計算を飛躍的に簡単にしていくことができます。暗号の世界では非常に大きな数の累乗を扱うため、この性質が不可欠です。&lt;/p>
&lt;hr>
&lt;h2 id="4-rsa暗号を支える数学的基礎3オイラー関数とオイラーの定理">4. RSA暗号を支える数学的基礎3：オイラー関数とオイラーの定理
&lt;/h2>&lt;p>ここからがRSA暗号の核心となる魔法の数学です。「フェルマーの小定理」の一般化である「オイラーの定理」が登場します。&lt;/p>
&lt;h3 id="41-オイラーのトーティエント関数-phin">4.1 オイラーのトーティエント関数 $\phi(N)$
&lt;/h3>&lt;p>オイラーのトーティエント関数（$\phi$ 関数）は、ある自然数 $N$ に対して、**「1から $N$ までの自然数のうち、$N$ と互いに素（最大公約数が1）である数の個数」**を返す関数です。&lt;/p>
&lt;p>いくつか例を見てみましょう。&lt;/p>
&lt;ul>
&lt;li>$\phi(5)$: 1, 2, 3, 4, 5 の中で 5 と互いに素なのは 1, 2, 3, 4 の4個。よって $\phi(5) = 4$。&lt;/li>
&lt;li>$\phi(6)$: 1, 2, 3, 4, 5, 6 の中で 6 と互いに素なのは 1, 5 の2個。よって $\phi(6) = 2$。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【素数の場合の特別な性質】&lt;/strong>
$p$ が素数の場合、1から $p-1$ までのすべての数が $p$ と互いに素になります。したがって、
&lt;/p>
$$ \phi(p) = p - 1 $$
&lt;p>
となります。&lt;/p>
&lt;p>&lt;strong>【素数の積の場合の特別な性質】&lt;/strong>
2つの異なる素数 $p$ と $q$ について、$N = p \times q$ とした場合、$\phi(N)$ は以下の計算で簡単に求めることができます。
&lt;/p>
$$ \phi(N) = \phi(p) \times \phi(q) = (p - 1)(q - 1) $$
&lt;p>
この性質が、RSA暗号の「秘密の裏口（トラップドア）」として機能します。$p$ と $q$ を知っている人（鍵の作成者）は $\phi(N)$ を一瞬で計算できますが、$N$ しか知らない第三者は、$N$ を素因数分解しない限り $\phi(N)$ を求めることができないのです。&lt;/p>
&lt;h3 id="42-オイラーの定理">4.2 オイラーの定理
&lt;/h3>&lt;p>レオンハルト・オイラーは、この $\phi(N)$ を使って次のような美しい定理を証明しました。&lt;/p>
&lt;p>&lt;strong>オイラーの定理：&lt;/strong>
整数 $a$ と $N$ が互いに素であるとき、以下の合同式が成り立つ。
&lt;/p>
$$ a^{\phi(N)} \equiv 1 \pmod N $$
&lt;p>これは、「ある数 $a$ を $\phi(N)$ 回掛け合わせて $N$ で割ると、余りが必ず $1$ になる」という驚くべき性質です。（$N$ が素数 $p$ の場合は $a^{p-1} \equiv 1 \pmod p$ となり、フェルマーの小定理と呼ばれます）。&lt;/p>
&lt;p>このオイラーの定理を変形してみましょう。両辺に $a$ をもう一回掛けます。
&lt;/p>
$$ a^{\phi(N) + 1} \equiv a \pmod N $$
&lt;p>さらに、任意の整数 $k$ に対して、$a^{k \cdot \phi(N)}$ も $1^k = 1$ になるため、次の式が成り立ちます。
&lt;/p>
$$ a^{k \cdot \phi(N) + 1} \equiv a \pmod N $$
&lt;p>この式こそが、RSA暗号の**「暗号化して復号すると元に戻る」**という魔法を成り立たせている根本原理なのです。&lt;/p>
&lt;hr>
&lt;h2 id="5-rsa暗号のアルゴリズム鍵生成暗号化復号のステップ">5. RSA暗号のアルゴリズム：鍵生成・暗号化・復号のステップ
&lt;/h2>&lt;p>基礎知識が揃ったところで、いよいよRSA暗号の具体的な手順を見ていきましょう。RSA暗号は大きく「1. 鍵の生成」「2. 暗号化」「3. 復号」の3つのフェーズに分かれます。&lt;/p>
&lt;div class="mermaid">flowchart TD
A1["1. 素数 p, q を選ぶ"] --> A2["N = p × q を計算"]
A1 --> A3["φ(N) = (p-1)(q-1) を計算"]
A3 --> A4["φ(N)と互いに素な e を選ぶ"]
A3 --> A5["e × d ≡ 1 (mod φ(N)) となる d を計算"]
A2 --> A6["公開鍵 (N, e)"]
A4 --> A6
A5 --> A7["秘密鍵 d"]
B1["2. 平文メッセージ M"] --> B2["C ≡ M^e (mod N) を計算"]
A6 -.-> B2
B2 --> B3["暗号文 C を送信"]
B3 --> C1["3. 受信した暗号文 C"]
C1 --> C2["M ≡ C^d (mod N) を計算"]
A7 -.-> C2
C2 --> C3["元の平文メッセージ M を取得"]&lt;/div>
&lt;h3 id="51-鍵生成key-generation">5.1 鍵生成（Key Generation）
&lt;/h3>&lt;p>受信者であるボブは、自分のための「公開鍵」と「秘密鍵」を生成します。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>素数の選択:&lt;/strong> 2つの大きな素数 $p$ と $q$ をランダムに選びます。&lt;/li>
&lt;li>&lt;strong>法 $N$ の計算:&lt;/strong> $N = p \times q$ を計算します。この $N$ は公開されます。&lt;/li>
&lt;li>&lt;strong>$\phi(N)$ の計算:&lt;/strong> オイラー関数 $\phi(N) = (p - 1)(q - 1)$ を計算します。これはボブだけの秘密の数です。&lt;/li>
&lt;li>&lt;strong>公開鍵 $e$ の選択:&lt;/strong> $1 &lt; e &lt; \phi(N)$ であり、かつ $\phi(N)$ と互いに素である整数 $e$ を選びます。&lt;/li>
&lt;li>&lt;strong>秘密鍵 $d$ の計算:&lt;/strong> 次の条件を満たす整数 $d$ を見つけます。
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
これはつまり、「$e \times d$ を $\phi(N)$ で割った余りが $1$ になるような数 $d$」です。&lt;/li>
&lt;/ol>
&lt;p>これで鍵の準備は完了です。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>公開鍵:&lt;/strong> $(N, e)$ のペア。全世界に公開します。&lt;/li>
&lt;li>&lt;strong>秘密鍵:&lt;/strong> $d$。絶対に誰にも教えません。&lt;/li>
&lt;/ul>
&lt;h3 id="52-暗号化encryption">5.2 暗号化（Encryption）
&lt;/h3>&lt;p>アリスはボブに秘密のメッセージ $M$ を送りたいとします。（$M$ は文字を数値化したもので、$0 \le M &lt; N$ とします）。アリスはボブの公開鍵 $(N, e)$ を使って次のように計算します。&lt;/p>
$$ C \equiv M^e \pmod N $$
&lt;p>「メッセージ $M$ を $e$ 乗して、$N$ で割った余り $C$」を計算します。この $C$ が暗号文です。&lt;/p>
&lt;h3 id="53-復号decryption">5.3 復号（Decryption）
&lt;/h3>&lt;p>ボブは暗号文 $C$ を受け取ります。ボブは秘密鍵 $d$ を使って次のように計算します。&lt;/p>
$$ M \equiv C^d \pmod N $$
&lt;p>「暗号文 $C$ を $d$ 乗して、$N$ で割った余り」を計算すると、なんと元のメッセージ $M$ が復元されるのです！&lt;/p>
&lt;hr>
&lt;h2 id="6-なぜ復号で元に戻るのか数学的証明">6. なぜ復号で元に戻るのか？（数学的証明）
&lt;/h2>&lt;p>「$C$ を $d$ 乗するだけで、どうして元の $M$ に戻るの？」と疑問に思うかもしれません。ここで、先ほどの「オイラーの定理」が威力を発揮します。&lt;/p>
&lt;p>復号の計算式 $C^d \pmod N$ に、暗号化の式 $C = M^e$ を代入してみましょう。
&lt;/p>
$$ C^d \equiv (M^e)^d \equiv M^{ed} \pmod N $$
&lt;p>ここで、鍵生成のステップ 5 を思い出してください。ボブは $d$ を作るときに、$e \times d \equiv 1 \pmod{\phi(N)}$ となるように選びました。これは「$ed$ は $\phi(N)$ の倍数に $1$ を足した数である」ということを意味します。整数 $k$ を使って次のように書けます。
&lt;/p>
$$ ed = k \cdot \phi(N) + 1 $$
&lt;p>これを指数部分に代入し、指数法則を使って分解します。
&lt;/p>
$$ M^{ed} = M^{k \cdot \phi(N) + 1} = M^{k \cdot \phi(N)} \times M^1 = (M^{\phi(N)})^k \times M $$
&lt;p>ここで、メッセージ $M$ と $N$ が互いに素であると仮定すると、&lt;strong>オイラーの定理&lt;/strong>より $M^{\phi(N)} \equiv 1 \pmod N$ となります。
&lt;/p>
$$ (M^{\phi(N)})^k \times M \equiv 1^k \times M \equiv M \pmod N $$
&lt;p>したがって、見事に以下の式が成り立ちます。
&lt;/p>
$$ C^d \equiv M \pmod N $$
&lt;p>アリスは $d$ を知らず、盗聴者も $d$ を知らないので、$C$ から $M$ を取り出せるのは $d$ を持っているボブだけなのです。&lt;/p>
&lt;hr>
&lt;h2 id="7-具体例小さな素数を使って手計算でrsaを体験してみよう">7. 具体例：小さな素数を使って手計算でRSAを体験してみよう
&lt;/h2>&lt;p>実際に小さな数字（素数）を使って、アリスからボブへ暗号通信をしてみましょう。&lt;/p>
&lt;p>&lt;strong>【ボブの鍵生成フェーズ】&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>2つの素数 $p=11$, $q=13$ を選びます。&lt;/li>
&lt;li>$N = 11 \times 13 = 143$ を計算します。&lt;/li>
&lt;li>$\phi(N) = (11 - 1) \times (13 - 1) = 10 \times 12 = 120$ を計算します。&lt;/li>
&lt;li>$\phi(N)=120$ と互いに素な公開鍵 $e$ を選びます。ここでは $e=7$ にします。&lt;/li>
&lt;li>秘密鍵 $d$ を求めます。$7 \times d \equiv 1 \pmod{120}$ となる $d$ を探します。
方程式 $7d = 120k + 1$ において、$k=6$ のとき $721$ となり、$721 \div 7 = 103$。
したがって、$d = 103$ となります。&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>公開鍵：$(N=143, e=7)$&lt;/li>
&lt;li>秘密鍵：$d=103$&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【アリスの暗号化フェーズ】&lt;/strong>
メッセージ $M = 9$ を送りたいとします。
式：$C \equiv 9^7 \pmod{143}$
$9^7 = 4,782,969$。これを 143 で割ると $33447$ 余り $48$。
暗号文 $C = 48$ となりました。&lt;/p>
&lt;p>&lt;strong>【ボブの復号フェーズ】&lt;/strong>
ボブは暗号文 $C = 48$ を受け取り、秘密鍵 $d = 103$ を使って復号します。
式：$M \equiv 48^{103} \pmod{143}$
計算機で &lt;code>(48 ** 103) % 143&lt;/code> を実行すると、見事に結果は「&lt;strong>9&lt;/strong>」となります！元のメッセージを無事に受け取ることができました。&lt;/p>
&lt;hr>
&lt;h2 id="8-秘密鍵-d-の求め方拡張ユークリッドの互除法">8. 秘密鍵 $d$ の求め方：拡張ユークリッドの互除法
&lt;/h2>&lt;p>手計算の例では勘で $k$ を探して $d=103$ を見つけましたが、数が何百桁にもなるとこの方法は不可能です。実際のプログラムでは**「拡張ユークリッドの互除法」**というアルゴリズムを使います。&lt;/p>
&lt;p>$7d \equiv 1 \pmod{120}$ を解くということは、$7d + 120y = 1$ を満たす整数 $d, y$ を見つけることと同じです。ユークリッドの互除法を逆算していくことで、これを機械的に求めることができます。&lt;/p>
&lt;ol>
&lt;li>$120 \div 7 = 17$ 余り $1$&lt;/li>
&lt;li>これを変形すると、$1 = 120 - 17 \times 7$&lt;/li>
&lt;li>つまり、$-17 \times 7 \equiv 1 \pmod{120}$&lt;/li>
&lt;/ol>
&lt;p>$-17$ は、法 $120$ の世界では $120 - 17 = 103$ と同じ意味になります。したがって、$d = 103$ が一瞬で求まるのです。この方法はどんなに巨大な数でも非常に高速に計算できます。&lt;/p>
&lt;hr>
&lt;h2 id="9-rsa暗号のもう一つの顔デジタル署名">9. RSA暗号のもう一つの顔：デジタル署名
&lt;/h2>&lt;p>RSA暗号の素晴らしい点は、公開鍵と秘密鍵の役割を逆にすることで**「デジタル署名」**としても使えることです。&lt;/p>
&lt;p>暗号化のときは「公開鍵で暗号化 $\Rightarrow$ 秘密鍵で復号」でしたが、
デジタル署名では「秘密鍵で暗号化 $\Rightarrow$ 公開鍵で復号」という手順を踏みます。&lt;/p>
&lt;div class="mermaid">flowchart TD
A1["1. アリスが秘密鍵で署名作成"] --> A2["S ≡ M^d (mod N)"]
A2 --> A3["メッセージ M と署名 S を送信"]
A3 --> B1["2. ボブが公開鍵で署名検証"]
B1 --> B2["M' ≡ S^e (mod N) を計算"]
B2 --> B3["M' と M が一致するか確認"]&lt;/div>
&lt;p>アリスが自身の秘密鍵 $d$ を使ってメッセージを変換し（これが署名 $S$）、ボブに送ります。ボブはアリスの公開鍵 $e$ を使って検証計算をします。もし計算結果が元のメッセージと一致すれば、「アリスの秘密鍵でしか作れないデータである」ことと、「メッセージが途中で改ざんされていないこと」が同時に証明されるのです。&lt;/p>
&lt;hr>
&lt;h2 id="10-プログラムで実感するrsa暗号">10. プログラムで実感するRSA暗号
&lt;/h2>&lt;p>手計算では大変な累乗計算も、Pythonを使えば非常に簡単に実装できます。以下はRSA暗号のコアとなるロジックを体験できるPythonコードです。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;最大公約数を求める&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;秘密鍵 d を求める（Python 3.8以降の組み込み機能を利用）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 鍵生成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">11&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">N&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">q&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">phi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;公開鍵: (N=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">N&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, e=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">), 秘密鍵: d=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 暗号化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">9&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ciphertext&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;暗号文: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ciphertext&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 復号&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">decrypted_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ciphertext&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;復号されたメッセージ: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">decrypted_message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Pythonの &lt;code>pow(base, exp, mod)&lt;/code> 関数は内部で「繰り返し二乗法」という高速アルゴリズムを使っているため、何百桁の数であっても一瞬で計算が終わります。&lt;/p>
&lt;hr>
&lt;h2 id="11-まとめと未来の暗号技術">11. まとめと未来の暗号技術
&lt;/h2>&lt;p>高校数学の知識をベースに、RSA暗号の仕組みを解き明かしてきました。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>素因数分解の困難性:&lt;/strong> $p \times q = N$ は簡単だが、$N$ から $p, q$ を見つけるのは非常に難しい。&lt;/li>
&lt;li>&lt;strong>合同式とオイラーの定理:&lt;/strong> $a^{\phi(N)} \equiv 1 \pmod N$ という法則により、「ある数で累乗すると元に戻る」魔法のトラップドアが完成する。&lt;/li>
&lt;li>&lt;strong>公開鍵と秘密鍵:&lt;/strong> 誰でも暗号化できるが、復号できるのは正当な受信者だけ。&lt;/li>
&lt;/ol>
&lt;p>現在使われているRSA暗号の $N$ は600桁以上あり、世界中のスーパーコンピュータを総動員しても素因数分解には宇宙の年齢以上の時間がかかります。しかし、近年研究が進んでいる「量子コンピュータ」が将来実用化されると、「ショアのアルゴリズム」によってこの素因数分解が一瞬で解かれてしまう可能性があります。そのため、現在は量子コンピュータでも解読できない「耐量子計算機暗号」の開発が世界中で急ピッチで進められています。&lt;/p>
&lt;p>「役に立たない」と思われがちな高度な数学が、実は私たちの日常生活を根底から守っている。RSA暗号は、そんな数学の奥深さと美しさを教えてくれる最高の教材です。この記事を通して、暗号と数学の面白さを少しでも感じていただけたなら幸いです。&lt;/p></description></item><item><title>完全準同型暗号（FHE）とは？次世代セキュリティの要を解説</title><link>http://kenji.blog/p/fully-homomorphic-encryption-fhe-explained/</link><pubDate>Fri, 11 Sep 2026 11:00:00 +0900</pubDate><guid>http://kenji.blog/p/fully-homomorphic-encryption-fhe-explained/</guid><description>&lt;img src="http://kenji.blog/p/fully-homomorphic-encryption-fhe-explained/img/eyecatch.jpg" alt="Featured image of post 完全準同型暗号（FHE）とは？次世代セキュリティの要を解説" />&lt;p>クラウドコンピューティングやAI技術が社会の基盤として定着する中、「データのプライバシー」と「データの利活用」のトレードオフは、最も重要な課題の一つとなっています。医療データ、金融情報、個人の生体情報など、機密性の高いデータをクラウド上でAIに解析させたいという需要は高まっていますが、セキュリティ上の懸念からデータの外部送信を躊躇する企業は少なくありません。&lt;/p>
&lt;p>従来の暗号化技術（AESやRSAなど）は、ストレージに保存されているデータ（Data at Rest）やネットワーク上を流れるデータ（Data in Transit）を保護することには長けています。しかし、サーバー側でデータに対して検索や機械学習などの&lt;strong>処理（計算）を行う際（Data in Use）には、一度暗号を復号して平文に戻す必要があります&lt;/strong>。もしこの復号されたタイミングでサーバーがハッキングされたり、内部の悪意ある管理者がデータを覗き見たりすれば、情報漏洩に直結します。&lt;/p>
&lt;p>この「処理時の復号」という根本的な弱点を克服する夢の技術が、**完全準同型暗号（Fully Homomorphic Encryption: FHE）**です。FHEを利用すれば、データを暗号化したまま一切復号せずに計算処理を行い、その結果の暗号文だけをクライアントに返すことが可能になります。&lt;/p>
&lt;p>本記事では、このFHEの概念から歴史、Craig Gentryによる画期的なブレイクスルー、数学的な基礎（Ring-LWEなど）、最大の課題である「ノイズ」とその解決策（ブートストラッピング）、そして最新の実装ライブラリに至るまで、次世代セキュリティの要であるFHEを徹底的に深く解説します。&lt;/p>
&lt;hr>
&lt;h2 id="1-準同型暗号とは何か基本的な概念">1. 準同型暗号とは何か？基本的な概念
&lt;/h2>&lt;p>「準同型（Homomorphic）」とは代数学の用語であり、ある構造を持つ集合間で、演算の構造を保ったまま写像できる性質を指します。暗号理論における「準同型性」とは、&lt;strong>平文空間での演算が、暗号文空間での演算に対応している&lt;/strong>という性質です。&lt;/p>
&lt;p>単純な数式で表すと、平文 $m_1$ と $m_2$ に対する暗号化関数を $E(\cdot)$ とし、復号関数を $D(\cdot)$ とします。平文上での演算（加算や乗算など）を $\circ$ 、暗号文上での演算を $\diamond$ としたとき、以下の関係が成り立ちます。&lt;/p>
$$ D(E(m_1) \diamond E(m_2)) = m_1 \circ m_2 $$
&lt;p>つまり、暗号文 $E(m_1)$ と $E(m_2)$ に対して何らかの演算 $\diamond$ を施した結果を復号すると、元の平文同士を演算 $\circ$ した結果と一致するということです。&lt;/p>
&lt;h3 id="クラウドコンピューティングにおけるデータフロー">クラウドコンピューティングにおけるデータフロー
&lt;/h3>&lt;p>FHEを用いたクラウド処理のアーキテクチャは、従来のものと全く異なります。以下の図は、FHEを活用したセキュアなデータ処理のフローを示しています。&lt;/p>
&lt;div class="mermaid">graph TD
A["クライアント (秘密鍵を保持)"] -->|1. 平文 x を暗号化: E(x)| B["クラウドサーバー (暗号化データのみ)"]
B -->|2. 暗号文のまま関数 f を適用: E(f(x))| B
B -->|3. 計算結果の暗号文 E(y)| A
A -->|4. 秘密鍵で復号: y = f(x)| A
style A fill:#d4edda,stroke:#28a745
style B fill:#f8d7da,stroke:#dc3545&lt;/div>
&lt;p>サーバーは暗号化されたデータ $E(x)$ を受け取りますが、秘密鍵を持たないためデータの中身を知ることは絶対にできません。しかし、FHEの性質を利用することで、暗号文に対して関数 $f$（例えば機械学習の推論モデル）を適用し、$E(f(x))$ を生成することができます。クライアントはこれを受け取り、自身の秘密鍵で復号することで、目的の結果 $y = f(x)$ を手に入れます。&lt;/p>
&lt;hr>
&lt;h2 id="2-準同型暗号の進化の歴史phe-she-fhe">2. 準同型暗号の進化の歴史：PHE, SHE, FHE
&lt;/h2>&lt;p>準同型暗号は、一度に現在の「完全」な形になったわけではありません。実現できる演算の種類や回数によって、大きく3つの段階に分類されます。&lt;/p>
&lt;h3 id="partially-homomorphic-encryption-phe-部分準同型暗号">Partially Homomorphic Encryption (PHE: 部分準同型暗号)
&lt;/h3>&lt;p>PHEは、加算か乗算の&lt;strong>どちらか一方のみ&lt;/strong>を無制限に行うことができる暗号方式です。実は、この性質を持つ暗号は古くから存在していました。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>RSA暗号（乗算に対する準同型性）&lt;/strong>
RSA暗号は意図せずして乗算の準同型性を持っていました。平文 $m_1, m_2$、公開鍵 $(e, N)$ とすると：
$$ E(m_1) = m_1^e \pmod N $$
$$ E(m_2) = m_2^e \pmod N $$
これらを掛け合わせると：
$$ E(m_1) \times E(m_2) = (m_1 \cdot m_2)^e \pmod N = E(m_1 \times m_2) $$
このように、暗号文同士の乗算が平文の乗算に対応します。&lt;/li>
&lt;li>&lt;strong>Paillier暗号（加算に対する準同型性）&lt;/strong>
1999年に考案されたPaillier暗号は、加算に対する準同型性を持ちます。電子投票（暗号化された票を集計し、最終結果だけを復号する）などで実用化されています。&lt;/li>
&lt;/ul>
&lt;h3 id="somewhat-homomorphic-encryption-she-有界準同型暗号">Somewhat Homomorphic Encryption (SHE: 有界準同型暗号)
&lt;/h3>&lt;p>加算と乗算の&lt;strong>両方&lt;/strong>を実行できますが、演算できる&lt;strong>回数（回路の深さ）に制限&lt;/strong>がある方式です。後述する「ノイズ」の蓄積により、ある一定回数以上の乗算を行うと復号が不可能になってしまいます。2005年のBGN (Boneh-Goh-Nissim) 暗号などがこれに該当しますが、実用的な複雑な計算（ディープラーニングなど）を行うには限界がありました。&lt;/p>
&lt;h3 id="fully-homomorphic-encryption-fhe-完全準同型暗号">Fully Homomorphic Encryption (FHE: 完全準同型暗号)
&lt;/h3>&lt;p>加算と乗算の両方を、&lt;strong>回数無制限&lt;/strong>で実行できる暗号方式です。情報理論におけるチューリング完全性と同様に、加算（XORに相当）と乗算（ANDに相当）を無限に組み合わせることができれば、原理上いかなる計算可能な関数・アルゴリズムも暗号化されたまま実行できることを意味します。&lt;/p>
&lt;p>FHEは長らく「暗号界の聖杯」と呼ばれ、実現不可能ではないかとも言われていました。しかし、2009年に当時スタンフォード大学の博士課程にいた &lt;strong>Craig Gentry&lt;/strong> が、理想格子（Ideal Lattices）を用いた最初のFHEスキームを提案し、世界に衝撃を与えました。&lt;/p>
&lt;hr>
&lt;h2 id="3-fheの数学的基盤lwe問題とring-lwe">3. FHEの数学的基盤：LWE問題とRing-LWE
&lt;/h2>&lt;p>現在の主流となっているFHEスキームの多くは、耐量子計算機暗号（Post-Quantum Cryptography）としても知られる「格子暗号（Lattice-based Cryptography）」の数学的難問である &lt;strong>LWE (Learning With Errors) 問題&lt;/strong>に基づいています。&lt;/p>
&lt;h3 id="lwe問題の直感的な理解">LWE問題の直感的な理解
&lt;/h3>&lt;p>連立一次方程式を解くことは、ガウスの消去法などを用いれば簡単です。&lt;/p>
$$ \begin{cases} 3s_1 + 4s_2 + 2s_3 \equiv 12 \pmod{17} \\ 1s_1 + 9s_2 + 5s_3 \equiv 8 \pmod{17} \\ \vdots \end{cases} $$
&lt;p>しかし、この方程式の結果に、ごく僅かな「ランダムな誤差（ノイズ）」$e$ を加えるとどうなるでしょうか。&lt;/p>
$$ \begin{cases} 3s_1 + 4s_2 + 2s_3 + e_1 \equiv 13 \pmod{17} \\ 1s_1 + 9s_2 + 5s_3 + e_2 \equiv 7 \pmod{17} \\ \vdots \end{cases} $$
&lt;p>この誤差 $e$ が加わるだけで、秘密の変数ベクトル $\vec{s}$ を見つけ出す問題は、現在のスーパーコンピュータや量子コンピュータを用いても解読が困難なNP困難な問題へと変貌します。これがLWE問題です。&lt;/p>
&lt;h3 id="ring-lwe問題rlwe">Ring-LWE問題（RLWE）
&lt;/h3>&lt;p>標準のLWE問題は行列演算を含むため、鍵のサイズが非常に大きく（ギガバイト単位になることも）、計算効率も悪いという問題がありました。これを解決するために導入されたのが、多項式環上の演算を用いる &lt;strong>Ring-LWE (RLWE) 問題&lt;/strong>です。&lt;/p>
&lt;p>RLWEでは、要素が多項式環 $R_q = \mathbb{Z}_q[x] / (x^N + 1)$ に属します（ここで $N$ は2の冪乗、$q$ は法となる素数）。
秘密鍵を多項式 $s(x)$ とし、ランダムな多項式 $a(x)$、小さなノイズ多項式 $e(x)$ とすると、公開鍵は以下のペアになります。&lt;/p>
$$ (a(x), b(x)) \quad \text{where} \quad b(x) = -a(x) \cdot s(x) + e(x) \pmod q $$
&lt;p>暗号化の際には、この多項式の性質を利用して平文 $m(x)$ をエンコードし、暗号文を生成します。&lt;/p>
&lt;hr>
&lt;h2 id="4-最大の障壁ノイズとgentryのブートストラッピング">4. 最大の障壁「ノイズ」とGentryのブートストラッピング
&lt;/h2>&lt;p>FHEの理解において最も重要な概念が**「ノイズの管理」**です。&lt;/p>
&lt;p>LWE/RLWEベースの暗号では、安全性を担保するために意図的に小さな「ノイズ（誤差）」を含めています。
平文 $m$ の暗号文 $c$ を復号するプロセスは、大雑把に言えば次のような数式で表されます。&lt;/p>
$$ D(c) = (c \cdot s) \pmod q = m + \text{noise} $$
&lt;p>復号時には、この &lt;code>noise&lt;/code> を丸め処理などで取り除くことで正しい平文 $m$ を得ます。しかし、暗号文同士で準同型演算（特に乗算）を行うと、このノイズが劇的に増幅してしまいます。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>準同型加算&lt;/strong>: ノイズは足し算的に増加します（$e_1 + e_2$）。これは比較的緩やかな増加です。&lt;/li>
&lt;li>&lt;strong>準同型加算による準同型性の数式表現&lt;/strong>:
$$ E(m_1) \oplus E(m_2) = E(m_1 + m_2) $$&lt;/li>
&lt;li>&lt;strong>準同型乗算&lt;/strong>: ノイズは掛け算的に爆発します（$e_1 \times e_2$ などを含むため）。数回乗算するだけでノイズがしきい値 $q/2$ を超えてしまい、正しい丸め処理ができず復号に失敗します。&lt;/li>
&lt;li>&lt;strong>準同型乗算による準同型性の数式表現&lt;/strong>:
$$ E(m_1) \otimes E(m_2) = E(m_1 \times m_2) $$&lt;/li>
&lt;/ul>
&lt;p>これが、長い間FHEが実現できず、SHE（回数制限付き）に留まっていた理由です。&lt;/p>
&lt;h3 id="ブートストラッピングbootstrappingの魔法">ブートストラッピング（Bootstrapping）の魔法
&lt;/h3>&lt;p>Craig Gentryの天才的な貢献は、**「ブートストラッピング」**と呼ばれるノイズ削減の手法を発明したことです。これは暗号学におけるパラダイムシフトでした。&lt;/p>
&lt;p>直感的には「暗号文がノイズまみれになって壊れる前に、暗号化されたままの状態で『復号』して綺麗にし、新しい暗号文に入れ直す」という操作です。&lt;/p>
&lt;ol>
&lt;li>ノイズが大きくなった暗号文 $C_{noisy}$ があるとします。&lt;/li>
&lt;li>クライアントは、秘密鍵 $sk$ を「公開鍵で暗号化したもの」$E_{pk}(sk)$（これをブートストラッピングキーと呼ぶ）をあらかじめサーバーに渡しておきます。&lt;/li>
&lt;li>サーバーは、$C_{noisy}$ に対して、準同型的に**復号回路（Decryption Circuit）**を実行します。&lt;/li>
&lt;li>具体的には、$E_{pk}(C_{noisy})$ に対して $E_{pk}(sk)$ を用いて「暗号化された空間内での復号」を行います。&lt;/li>
&lt;li>この復号回路自体も準同型演算であるため新たなノイズを生みますが、出力される新しい暗号文 $C_{fresh}$ のノイズは、一定の「固定レベル」にリセットされます。&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph LR
A["高ノイズ暗号文 C_noisy"] --> B["準同型復号回路 (Eval_Dec)"]
C["暗号化された秘密鍵 E(sk)"] --> B
B --> D["低ノイズ暗号文 C_fresh"]
style B fill:#ffeeba,stroke:#ffc107&lt;/div>
&lt;p>このブートストラッピングを計算の途中で定期的に実行することで、理論上は無限の深さの回路を計算可能（FHEの達成）にしました。しかし、初期のGentryの方式は、このブートストラッピング処理に1回あたり数十分から数時間かかるという、絶望的なほど計算コストが高いものでした。&lt;/p>
&lt;hr>
&lt;h2 id="5-fheの世代と主要なスキームの進化">5. FHEの世代と主要なスキームの進化
&lt;/h2>&lt;p>FHEの実用化に向けて、世界中の暗号学者が競うようにアルゴリズムを改良してきました。現在、FHEは主に4つの世代・ファミリーに分類されます。&lt;/p>
&lt;h3 id="第2世代整数の正確な演算-bgv-bfv">第2世代：整数の正確な演算 (BGV, BFV)
&lt;/h3>&lt;p>2011〜2012年にかけて登場した &lt;strong>BGV (Brakerski-Gentry-Vaikuntanathan)&lt;/strong> と &lt;strong>BFV (Brakerski/Fan-Vercauteren)&lt;/strong> スキームです。これらはRLWEを基盤としており、整数のモジュラ演算（正確な計算）に適しています。
SIMD (Single Instruction, Multiple Data) のようなバッチング（Batching）技術をサポートしており、1つの巨大な多項式の暗号文の中に、何千ものデータスロットを詰め込んで一度に並列計算できるのが特徴です。&lt;/p>
&lt;h3 id="第3世代ブートストラッピングの高速化-gsw-fhew-tfhe">第3世代：ブートストラッピングの高速化 (GSW, FHEW, TFHE)
&lt;/h3>&lt;p>2013年の &lt;strong>GSW (Gentry-Sahai-Waters)&lt;/strong> スキームは、FHEの構造をよりシンプルにしました。そしてこれを発展させたのが、現在の主流の一つである &lt;strong>TFHE (Fast Fully Homomorphic Encryption over the Torus)&lt;/strong> です。
TFHEの特徴は、ブートストラッピングが非常に高速（ミリ秒単位）であることです。ゲート単位（AND, XORなどの論理回路）での演算に強く、暗号文のサイズも比較的小さいため、任意の論理回路を高速に評価するのに適しています。&lt;/p>
&lt;h3 id="第4世代近似計算と機械学習への特化-ckks">第4世代：近似計算と機械学習への特化 (CKKS)
&lt;/h3>&lt;p>2017年にCheonらによって提案された &lt;strong>CKKS (Cheon-Kim-Kim-Song)&lt;/strong> スキームは、現在のAI・機械学習のプライバシー保護における決定版と言える技術です。
これまでのFHEが「正確な整数計算」にこだわっていたのに対し、CKKSは**「浮動小数点数の近似計算」**を暗号化されたままサポートします。ニューラルネットワークの学習や推論など、小さな誤差が許容される実数計算において、圧倒的なパフォーマンスを発揮します。&lt;/p>
&lt;p>以下の表に、目的別のスキームの選び方をまとめます。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">スキーム名&lt;/th>
&lt;th style="text-align:left">得意なデータ型&lt;/th>
&lt;th style="text-align:left">推奨されるユースケース&lt;/th>
&lt;th style="text-align:left">特徴&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>BFV / BGV&lt;/strong>&lt;/td>
&lt;td style="text-align:left">整数 (Integer)&lt;/td>
&lt;td style="text-align:left">正確な統計計算、金融データ集計、DB検索&lt;/td>
&lt;td style="text-align:left">SIMDバッチングによる高スループット&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>CKKS&lt;/strong>&lt;/td>
&lt;td style="text-align:left">実数 (Real/Complex)&lt;/td>
&lt;td style="text-align:left">機械学習 (DNN, ロジスティック回帰)、信号処理&lt;/td>
&lt;td style="text-align:left">近似計算による高速化、再スケーリング&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>TFHE&lt;/strong>&lt;/td>
&lt;td style="text-align:left">ブール値 (Boolean)&lt;/td>
&lt;td style="text-align:left">任意の論理回路、文字列検索、非線形関数の評価&lt;/td>
&lt;td style="text-align:left">超高速ブートストラッピング（ミリ秒台）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="6-実践fheライブラリと概念的なコード">6. 実践：FHEライブラリと概念的なコード
&lt;/h2>&lt;p>現在では、暗号学の深い知識がなくてもFHEを利用できるオープンソースのライブラリが多数提供されています。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Microsoft SEAL (Simple Encrypted Arithmetic Library)&lt;/strong>: BFV, BGV, CKKSをサポートするC++ライブラリ。業界標準の一つ。Pythonバインディングである &lt;strong>TenSEAL&lt;/strong> がAIエンジニアの間で人気です。&lt;/li>
&lt;li>&lt;strong>Zama (Concrete)&lt;/strong>: TFHEをベースにしたフレームワーク。Rust/Pythonで記述でき、既存のPyTorchモデルをコンパイルしてFHE上で動かす機能（Concrete ML）を提供しています。&lt;/li>
&lt;li>&lt;strong>OpenFHE&lt;/strong>: PALISADEの後継で、すべての主要なスキームをサポートする包括的なC++ライブラリ。&lt;/li>
&lt;/ul>
&lt;h3 id="python-tenseal-を用いたfheプログラミングの例">Python (TenSEAL) を用いたFHEプログラミングの例
&lt;/h3>&lt;p>ここでは、CKKSスキームを用いて、暗号化されたまま実数ベクトルを足し合わせ、掛け合わせる概念的なPythonコードの例を示します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tenseal&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">ts&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. コンテキストのセットアップ (鍵生成を含む)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># CKKSスキームを使用し、多項式の次数を8192に設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">SCHEME_TYPE&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CKKS&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">poly_modulus_degree&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">8192&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">coeff_mod_bit_sizes&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generate_galois_keys&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">global_scale&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="mi">40&lt;/span> &lt;span class="c1"># 実数のスケーリングファクタ&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. クライアント側：データの暗号化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">vector1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mf">1.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">2.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">3.5&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">vector2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mf">2.0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">3.0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">4.0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 平文ベクトルを暗号文に変換 (本来はクライアント側で実行)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enc_v1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ckks_vector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vector1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enc_v2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ckks_vector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vector2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. サーバー側：暗号化されたままの演算 (Data in Useの保護)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># サーバーは平文を知らないが、加算と乗算を実行できる&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enc_add&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_v1&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">enc_v2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enc_mul&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_v1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">enc_v2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. クライアント側：結果の復号&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 秘密鍵を持つクライアントだけが結果を見ることができる&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">res_add&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_add&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decrypt&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">res_mul&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_mul&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decrypt&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;復号された加算結果: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">res_add&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 出力例: [3.5000001, 5.5000001, 7.5000002] (近似計算のため微小な誤差が含まれる)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;復号された乗算結果: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">res_mul&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 出力例: [3.0000002, 7.5000005, 14.0000003]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上記のコードからわかるように、&lt;code>enc_v1 + enc_v2&lt;/code> のように通常のPythonの演算子をオーバーロードして直感的に暗号文同士の計算を記述することができます。サーバー側では、ベクトルの中身を知らずにベクトル演算を完了させています。&lt;/p>
&lt;hr>
&lt;h2 id="7-fheの課題パフォーマンスとハードウェアアクセラレーション">7. FHEの課題：パフォーマンスとハードウェアアクセラレーション
&lt;/h2>&lt;p>FHEは理論的に完璧なセキュリティを提供しますが、実用化における最大の課題は**「パフォーマンスのオーバーヘッド」**です。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>計算のオーバーヘッド&lt;/strong>: 平文での計算に比べ、暗号文での計算はCPU上で数千倍〜数万倍遅くなります。多項式の乗算やブートストラッピングには膨大なFFT（高速フーリエ変換）やNTT（数論変換）の計算が必要です。&lt;/li>
&lt;li>&lt;strong>データサイズの膨張 (Ciphertext Expansion)&lt;/strong>: 数バイトの平文が、暗号化されると数メガバイトになることがあります。これはメモリ帯域やネットワーク帯域を強く圧迫します。&lt;/li>
&lt;/ol>
&lt;h3 id="ハードウェアによる解決へのアプローチ">ハードウェアによる解決へのアプローチ
&lt;/h3>&lt;p>このオーバーヘッドを克服するため、世界中でFHE専用のハードウェアアクセラレータ（ASIC, FPGA, GPU対応）の開発が進められています。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>GPUアクセラレーション&lt;/strong>: NVIDIAなどの強力なGPUを用いて、NTT演算やブートストラッピングを並列化する取り組みが進んでおり、ソフトウェア実装に対して数十倍の高速化が報告されています（例：100x.ai, ZamaのTFHE-rs CUDA backend）。&lt;/li>
&lt;li>&lt;strong>DARPA DPRIVE プロジェクト&lt;/strong>: 米国防高等研究計画局（DARPA）は、FHEの計算速度を平文処理の実行速度と同等レベル（オーバーヘッド10倍以内）に引き上げるための専用ハードウェア開発プロジェクト「DPRIVE (Data Protection in Virtual Environments)」を推進しており、IntelやMicrosoft、Intellectual Venturesなどが参加しています。&lt;/li>
&lt;li>&lt;strong>FPU (FHE Processing Unit) の登場&lt;/strong>: CornamiやOptalysysといったスタートアップが、光コンピューティングや特殊なシリコンアーキテクチャを用いたFHE専用チップの開発に乗り出しています。&lt;/li>
&lt;/ul>
&lt;p>近い将来、AIにおけるNPU（Neural Processing Unit）のように、サーバーやクラウドインフラに「FPU」が標準搭載される時代が来るかもしれません。&lt;/p>
&lt;hr>
&lt;h2 id="8-期待されるユースケース">8. 期待されるユースケース
&lt;/h2>&lt;p>FHEが実用的な速度に到達しつつある今、以下のような分野での破壊的イノベーションが期待されています。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>医療・ゲノム解析のプライバシー保護&lt;/strong>:
複数の病院が持つ患者のカルテデータやDNAデータを、FHEで暗号化したままクラウドのAIに学習させることで、プライバシー法（HIPAAやGDPR）に違反することなく、高精度な癌診断モデルや新薬開発を行うことができます。&lt;/li>
&lt;li>&lt;strong>金融機関の不正検知・マネーロンダリング対策 (AML)&lt;/strong>:
競合する銀行同士が、顧客の口座情報や取引履歴を明かすことなく、暗号化された状態で互いのデータを照合し、巨大な不正送金ネットワークを検知するクロスバンク分析が可能になります。&lt;/li>
&lt;li>&lt;strong>セキュアなAI推論API (MaaS: Model as a Service)&lt;/strong>:
ユーザーは自身の音声や顔画像、プロンプトを暗号化してAIサービス（ChatGPTのようなLLMなど）に送信します。AIプロバイダーはユーザーの入力を一切知ることなく回答を生成し、暗号文として返します。これにより、「AIに個人情報を学習される・盗み見られる」という懸念が完全に払拭されます。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="9-結論暗号の未来は見えない計算へ">9. 結論：暗号の未来は「見えない計算」へ
&lt;/h2>&lt;p>1970年代に公開鍵暗号（RSA）が発明されてインターネット上の安全な通信（HTTPSなど）が可能になったように、Craig GentryによるFHEの発明は、暗号の歴史における最も重要なマイルストーンの一つです。&lt;/p>
&lt;p>現在、完全準同型暗号（FHE）は研究室の理論から飛び出し、Microsoft、IBM、Intel、Google、そして多くのスタートアップが実用化に向けてしのぎを削る段階に入っています。計算コストやデータサイズの課題は依然として存在しますが、アルゴリズムの洗練とハードウェアアクセラレータの進化により、ムーアの法則を超えるペースで性能向上が続いています。&lt;/p>
&lt;p>数年後、「データを暗号化したまま計算する」ことは特別なことではなく、クラウドサービスにおける標準的なデータ保護のベストプラクティスとなるでしょう。FHEは、データ駆動型社会における&lt;strong>究極のプライバシーとデータ利活用の両立&lt;/strong>を実現する、次世代セキュリティの要なのです。&lt;/p></description></item><item><title>楕円曲線暗号（ECC）の数学的基礎とC++での実装</title><link>http://kenji.blog/p/elliptic-curve-cryptography-math-cpp/</link><pubDate>Fri, 11 Sep 2026 10:00:00 +0900</pubDate><guid>http://kenji.blog/p/elliptic-curve-cryptography-math-cpp/</guid><description>&lt;img src="http://kenji.blog/p/elliptic-curve-cryptography-math-cpp/img/eyecatch.jpg" alt="Featured image of post 楕円曲線暗号（ECC）の数学的基礎とC++での実装" />&lt;h1 id="楕円曲線暗号eccの数学的基礎とcでの実装">楕円曲線暗号（ECC）の数学的基礎とC++での実装
&lt;/h1>&lt;p>現代の暗号技術において、**楕円曲線暗号（Elliptic Curve Cryptography: ECC）**は極めて重要な役割を果たしています。私たちの日常的なインターネット通信（HTTPS/TLS）から、スマートフォンのセキュアエンクレーブ、SSHによるサーバー認証、FIDOなどのパスワードレス認証、さらにはビットコインやイーサリアムなどの暗号資産に至るまで、現代のデジタル社会の信頼基盤はECCによって支えられていると言っても過言ではありません。&lt;/p>
&lt;p>本記事では、この楕円曲線暗号がいかにして機能しているのか、その背後にある美しくも難解な数学的理論（有限体上の代数幾何学）から出発し、実際のC++を用いた実装方法、さらにはサイドチャネル攻撃（タイミング攻撃）を防ぐためのセキュアなコーディング手法まで、圧倒的なボリュームで徹底的に解説します。&lt;/p>
&lt;hr>
&lt;h2 id="1-なぜ楕円曲線暗号なのかrsaとの比較">1. なぜ楕円曲線暗号なのか？（RSAとの比較）
&lt;/h2>&lt;p>公開鍵暗号方式の代名詞といえば、長らく&lt;strong>RSA暗号&lt;/strong>でした。RSA暗号は「巨大な合成数の素因数分解の困難性」を安全性の根拠としています。しかし、コンピューターの計算能力の向上に伴い、安全性を維持するためにはRSAの鍵長（モジュラスのビット数）を継続的に長くする必要が生じました。現在では、最低でも2048ビット、より安全を期すなら3072ビットや4096ビットの鍵長が推奨されています。&lt;/p>
&lt;p>これに対して、楕円曲線暗号（ECC）は**「楕円曲線上の離散対数問題（ECDLP）」**という別の数学的困難性を安全性の根拠としています。ECDLPを解くための効率的なアルゴリズム（準指数関数時間アルゴリズムなど）は現在に至るまで発見されておらず、既知の最も効率的な攻撃手法であっても指数関数的な時間を要します。&lt;/p>
&lt;p>この性質により、ECCは&lt;strong>非常に短い鍵長でRSAと同等のセキュリティ強度を実現できる&lt;/strong>という決定的な利点を持ちます。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">セキュリティ強度（ビット）&lt;/th>
&lt;th style="text-align:center">RSA暗号の鍵長（ビット）&lt;/th>
&lt;th style="text-align:center">楕円曲線暗号の鍵長（ビット）&lt;/th>
&lt;th style="text-align:center">鍵長の比率&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">80&lt;/td>
&lt;td style="text-align:center">1024&lt;/td>
&lt;td style="text-align:center">160&lt;/td>
&lt;td style="text-align:center">1:6&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">112&lt;/td>
&lt;td style="text-align:center">2048&lt;/td>
&lt;td style="text-align:center">224&lt;/td>
&lt;td style="text-align:center">1:9&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">128&lt;/td>
&lt;td style="text-align:center">3072&lt;/td>
&lt;td style="text-align:center">256&lt;/td>
&lt;td style="text-align:center">1:12&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">192&lt;/td>
&lt;td style="text-align:center">7680&lt;/td>
&lt;td style="text-align:center">384&lt;/td>
&lt;td style="text-align:center">1:20&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">256&lt;/td>
&lt;td style="text-align:center">15360&lt;/td>
&lt;td style="text-align:center">512&lt;/td>
&lt;td style="text-align:center">1:30&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>上記の表が示すように、128ビットのセキュリティ強度（現在標準的な強度）を得るために、RSAでは3072ビットの鍵が必要ですが、ECCであればわずか256ビットで済みます。これにより、計算量の削減、メモリ使用量の低減、ネットワーク帯域の節約が可能となり、特にリソースが限られたIoTデバイスやスマートカード環境において圧倒的な優位性を誇ります。&lt;/p>
&lt;hr>
&lt;h2 id="2-数学的準備群論と有限体の世界">2. 数学的準備：群論と有限体の世界
&lt;/h2>&lt;p>楕円曲線暗号を真に理解するためには、抽象代数学（群論および体論）の基本的な概念を押さえておく必要があります。ここでは、ECCを構成するための前提知識を簡潔にまとめます。&lt;/p>
&lt;h3 id="21-群groupとアーベル群">2.1. 群（Group）とアーベル群
&lt;/h3>&lt;p>**群（Group）**とは、ある集合 $G$ と、その集合上の二項演算（ここでは加法 $+$ とします）の組 $(G, +)$ であり、以下の4つの公理を満たすものです。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>閉性（Closure）&lt;/strong>: 任意の $a, b \in G$ について、$a + b \in G$ である。&lt;/li>
&lt;li>&lt;strong>結合法則（Associativity）&lt;/strong>: 任意の $a, b, c \in G$ について、$(a + b) + c = a + (b + c)$ が成り立つ。&lt;/li>
&lt;li>&lt;strong>単位元の存在（Identity element）&lt;/strong>: 任意の $a \in G$ について、$a + e = e + a = a$ となる要素 $e \in G$ が存在する。加法群の場合、この単位元を通常 $0$ または $\mathcal{O}$ と表記します。&lt;/li>
&lt;li>&lt;strong>逆元の存在（Inverse element）&lt;/strong>: 任意の $a \in G$ に対して、$a + b = b + a = e$ となる要素 $b \in G$ が存在する。この $b$ を $-a$ と表記します。&lt;/li>
&lt;/ol>
&lt;p>さらに、演算の順序を入れ替えても結果が変わらない、すなわち以下の条件を満たす群を**アーベル群（可換群）**と呼びます。&lt;/p>
&lt;ol start="5">
&lt;li>&lt;strong>交換法則（Commutativity）&lt;/strong>: 任意の $a, b \in G$ について、$a + b = b + a$ が成り立つ。&lt;/li>
&lt;/ol>
&lt;p>楕円曲線上の点の集合は、特定の加法ルールを定義することで、この&lt;strong>アーベル群&lt;/strong>を構成します。&lt;/p>
&lt;h3 id="22-有限体finite-field">2.2. 有限体（Finite Field）
&lt;/h3>&lt;p>暗号理論においては、実数や複素数のような連続的で無限の要素を持つ体ではなく、要素の数が有限である**有限体（Finite Field）**またはガロア体（Galois Field）を使用します。&lt;/p>
&lt;p>最も基本的な有限体は、素数 $p$ を用いた&lt;strong>素体 $\mathbb{F}_p$&lt;/strong> です。これは、$\{0, 1, 2, \dots, p-1\}$ の整数の集合に、モジュロ $p$ （$p$ で割った余り）での四則演算（加算、減算、乗算、除算）を定義したものです。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>加算&lt;/strong>: $(a + b) \pmod p$&lt;/li>
&lt;li>&lt;strong>減算&lt;/strong>: $(a - b) \pmod p$&lt;/li>
&lt;li>&lt;strong>乗算&lt;/strong>: $(a \times b) \pmod p$&lt;/li>
&lt;li>&lt;strong>除算&lt;/strong>: $a \times b^{-1} \pmod p$ （ここで $b^{-1}$ はモジュロ $p$ における $b$ の乗法逆元）&lt;/li>
&lt;/ul>
&lt;p>**乗法逆元（Modular Multiplicative Inverse）**の計算は暗号実装において非常に重要です。$b \times b^{-1} \equiv 1 \pmod p$ を満たす $b^{-1}$ を求めるには、主に以下の2つのアルゴリズムが用いられます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>拡張ユークリッドの互除法（Extended Euclidean Algorithm）&lt;/strong>: 高速ですが、実装によっては処理時間が入力値に依存するためタイミング攻撃のリスクがあります。&lt;/li>
&lt;li>&lt;strong>フェルマーの小定理（Fermat&amp;rsquo;s Little Theorem）&lt;/strong>: $p$ が素数で $b \neq 0$ のとき、$b^{p-1} \equiv 1 \pmod p$ が成り立ちます。両辺を $b$ で割ると、$b^{p-2} \equiv b^{-1} \pmod p$ となります。つまり、$b$ の $p-2$ 乗を計算することで逆元が求まります。べき乗演算は定数時間で実装しやすいため、暗号実装ではこちらが好まれます。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="3-楕円曲線の方程式と幾何学">3. 楕円曲線の方程式と幾何学
&lt;/h2>&lt;h3 id="31-ワイエルシュトラスの標準形">3.1. ワイエルシュトラスの標準形
&lt;/h3>&lt;p>**楕円曲線（Elliptic Curve）&lt;strong>は、一般に以下の&lt;/strong>ワイエルシュトラスの標準形（Weierstrass normal form）**と呼ばれる方程式で定義される平面曲線です。&lt;/p>
$$ y^2 = x^3 + ax + b $$
&lt;p>ここで、$a$ と $b$ は定数であり、曲線が特異点（自己交差や尖点）を持たない（滑らかな曲線である）ための条件として、以下の&lt;strong>判別式（Discriminant） $\Delta$&lt;/strong> がゼロでないことが求められます。&lt;/p>
$$ \Delta = -16(4a^3 + 27b^2) \neq 0 $$
&lt;p>特異点を持つ曲線は暗号学的な安全性を損なうため、必ずこの条件を満たす係数 $a, b$ が選ばれます。&lt;/p>
&lt;h3 id="32-無限遠点point-at-infinity">3.2. 無限遠点（Point at Infinity）
&lt;/h3>&lt;p>楕円曲線を数学的に完全な群にするために、平面上の点に加えて**「無限遠点（Point at Infinity）」**と呼ばれる仮想的な点を導入します。これを $\mathcal{O}$ （オー）と表記します。&lt;/p>
&lt;p>無限遠点 $\mathcal{O}$ は、すべての垂直線が無限の彼方で交わる点として定義されます。群論において、この無限遠点 $\mathcal{O}$ は&lt;strong>加法における単位元&lt;/strong>（ゼロ）として機能します。&lt;/p>
&lt;p>すなわち、曲線上の任意の点 $P$ に対して、以下が成り立ちます。
&lt;/p>
$$ P + \mathcal{O} = \mathcal{O} + P = P $$
&lt;p>また、点 $P = (x, y)$ の逆元 $-P$ は、x軸に対して対称な点 $(x, -y)$ と定義されます。したがって：
&lt;/p>
$$ P + (-P) = \mathcal{O} $$
&lt;p>
となります。&lt;/p>
&lt;hr>
&lt;h2 id="4-楕円曲線上の群演算点の加算と2倍算">4. 楕円曲線上の群演算（点の加算と2倍算）
&lt;/h2>&lt;p>楕円曲線暗号の根幹をなすのが、曲線上の点同士の**「加算（Addition）」**という演算です。これは通常の整数の足し算とは異なり、幾何学的な操作に基づいて定義されています。&lt;/p>
&lt;h3 id="41-幾何学的な加算tangent-and-chord-method">4.1. 幾何学的な加算（Tangent and Chord Method）
&lt;/h3>&lt;p>曲線上の異なる2点 $P$ と $Q$ を足して新しい点 $R$ ($R = P + Q$) を求める手順は以下の通りです。&lt;/p>
&lt;ol>
&lt;li>点 $P$ と点 $Q$ を通る直線（弦）を引きます。&lt;/li>
&lt;li>この直線は、楕円曲線と必ずもう一つの点（これを $-R$ とします）で交わります。（※代数幾何学の定理によります）&lt;/li>
&lt;li>交点 $-R$ をx軸に対して対称に折り返した点（y座標の符号を反転させた点）が、求める点 $R$ となります。&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph TD
Step1["P(x1, y1) と Q(x2, y2) を結ぶ直線を引く"] --> Step2["曲線との第3の交点 -R を見つける"]
Step2 --> Step3["-R をx軸で折り返し、R(x3, y3) を得る"]
Step3 -.-> Result["これが R = P + Q"]&lt;/div>
&lt;h3 id="42-点の2倍算point-doubling">4.2. 点の2倍算（Point Doubling）
&lt;/h3>&lt;p>点 $P$ に同じ点 $P$ を足す場合（$P + P = 2P$）、2点を通る直線を引くことができません。この場合は、**点 $P$ における曲線の接線（Tangent）**を引きます。&lt;/p>
&lt;ol>
&lt;li>点 $P$ における曲線の接線を引きます。&lt;/li>
&lt;li>この接線は曲線ともう一つの点 $-R$ で交わります。&lt;/li>
&lt;li>交点をx軸で対称に折り返した点が、求める点 $R = 2P$ となります。&lt;/li>
&lt;/ol>
&lt;h3 id="43-代数的な計算公式">4.3. 代数的な計算公式
&lt;/h3>&lt;p>幾何学的な操作を、コンピューターで計算できるように代数的な公式に落とし込みます。
演算はすべて**有限体 $\mathbb{F}_p$ 上（モジュロ $p$）**で行われます。&lt;/p>
&lt;p>点 $P = (x_1, y_1)$、点 $Q = (x_2, y_2)$ とします。
また、計算結果の点を $R = P + Q = (x_3, y_3)$ とします。&lt;/p>
&lt;p>直線の傾きを $\lambda$（ラムダ）とします。&lt;/p>
&lt;p>&lt;strong>【ケース1： $P \neq Q$ の場合（点の加算）】&lt;/strong>
傾き $\lambda$ は、2点間の変化の割合です。
&lt;/p>
$$ \lambda \equiv \frac{y_2 - y_1}{x_2 - x_1} \pmod p $$
$$ \lambda \equiv (y_2 - y_1) \cdot (x_2 - x_1)^{-1} \pmod p $$
&lt;p>この $\lambda$ を用いて、$x_3, y_3$ は次のように求まります。
&lt;/p>
$$ x_3 \equiv \lambda^2 - x_1 - x_2 \pmod p $$
$$ y_3 \equiv \lambda(x_1 - x_3) - y_1 \pmod p $$
&lt;p>&lt;strong>【ケース2： $P = Q$ の場合（点の2倍算）】&lt;/strong>
傾き $\lambda$ は、微分によって求められる接線の傾きになります。（$y^2 = x^3 + ax + b$ を暗黙に微分します）
&lt;/p>
$$ 2y \cdot y' = 3x^2 + a \implies y' = \frac{3x^2 + a}{2y} $$
&lt;p>
したがって、
&lt;/p>
$$ \lambda \equiv (3x_1^2 + a) \cdot (2y_1)^{-1} \pmod p $$
&lt;p>$x_3, y_3$ の式は加算と同じ形ですが、$x_2 = x_1$ であるため以下のようになります。
&lt;/p>
$$ x_3 \equiv \lambda^2 - 2x_1 \pmod p $$
$$ y_3 \equiv \lambda(x_1 - x_3) - y_1 \pmod p $$
&lt;blockquote>
&lt;p>[!IMPORTANT]
これらの公式には $(x_2 - x_1)^{-1}$ や $(2y_1)^{-1}$ といった**除算（モジュロ逆元の計算）&lt;strong>が含まれています。モジュロ逆元の計算は計算コストが非常に高いため、実際の実装では除算を遅延させる&lt;/strong>「ヤコビ座標系（Jacobian Coordinates）」**などの射影座標系が一般的に用いられます。&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="5-スカラー倍算と楕円曲線離散対数問題ecdlp">5. スカラー倍算と楕円曲線離散対数問題（ECDLP）
&lt;/h2>&lt;p>楕円曲線暗号において、最も計算量が多く、かつセキュリティの中核をなす演算が**スカラー倍算（Scalar Multiplication）**です。&lt;/p>
&lt;h3 id="51-スカラー倍算とは">5.1. スカラー倍算とは
&lt;/h3>&lt;p>ある点 $P$ を $k$ 回足し合わせる操作をスカラー倍算と呼び、$kP$ と表記します。
&lt;/p>
$$ kP = \underbrace{P + P + \dots + P}_{k\text{回}} $$
&lt;p>ここで、$k$ は非常に大きな整数（例えば256ビットの整数）です。&lt;/p>
&lt;h3 id="52-楕円曲線離散対数問題ecdlp">5.2. 楕円曲線離散対数問題（ECDLP）
&lt;/h3>&lt;p>楕円曲線暗号の安全性は、以下の問題の困難性に依存しています。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>楕円曲線離散対数問題 (Elliptic Curve Discrete Logarithm Problem: ECDLP)&lt;/strong>
既知の点 $P$（ベースポイント）と、計算結果の点 $Q$ が与えられたとき、$Q = kP$ を満たすスカラー $k$ を求めよ。&lt;/p>
&lt;/blockquote>
&lt;p>$k$ と $P$ から $Q$ を計算する（順方向）のは後述のアルゴリズムを用いれば簡単（多項式時間）ですが、$P$ と $Q$ から $k$ を逆算する（逆方向）ことは、総当たり的な探索以外に効率的な解法がなく、事実上不可能です（一方向性関数）。
暗号プロトコルにおいては、**$k$ が「秘密鍵」、$Q$ が「公開鍵」**に対応します。&lt;/p>
&lt;h3 id="53-double-and-add-アルゴリズム">5.3. Double-and-Add アルゴリズム
&lt;/h3>&lt;p>$k$ が巨大な数（例：$2^{256}$）の場合、$P$ を愚直に $k$ 回足し合わせることは宇宙の寿命が尽きても終わりません。そこで、高速にスカラー倍算を行うために**Double-and-Add法（バイナリ法）**が用いられます。&lt;/p>
&lt;p>これは、整数のべき乗を高速に計算する「繰り返し二乗法」の楕円曲線版です。スカラー $k$ を2進数表現し、上位ビットから順に処理します。&lt;/p>
&lt;ol>
&lt;li>結果を保持する点 $R$ を $\mathcal{O}$ で初期化する。&lt;/li>
&lt;li>$k$ の最上位ビットから最下位ビットまで以下を繰り返す：
&lt;ul>
&lt;li>$R$ を2倍する（Point Doubling: $R = 2R$）&lt;/li>
&lt;li>もし現在のビットが &lt;code>1&lt;/code> なら、$R$ に $P$ を足す（Point Addition: $R = R + P$）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>このアルゴリズムにより、計算量は $O(k)$ から $O(\log_2 k)$ に劇的に削減され、現実的な時間（ミリ秒単位）で計算が可能になります。&lt;/p>
&lt;hr>
&lt;h2 id="6-楕円曲線ディフィーヘルマンecdh鍵共有">6. 楕円曲線ディフィー・ヘルマン（ECDH）鍵共有
&lt;/h2>&lt;p>ここでは、ECCの最も代表的な応用例である&lt;strong>ECDH（Elliptic Curve Diffie-Hellman）鍵共有プロトコル&lt;/strong>の仕組みを解説します。ECDHは、盗聴可能な通信路上で、アリスとボブが安全に共通の秘密鍵（セッションキー）を生成・共有するための仕組みです（TLSハンドシェイクの中核です）。&lt;/p>
&lt;p>&lt;strong>【前提パラメータ（ドメインパラメータ）】&lt;/strong>
両者はあらかじめ、使用する楕円曲線 $E$、素数 $p$、およびベースポイント $G$ を共有しています。（例としてNIST P-256やsecp256k1など）&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant Alice as "Alice (アリス)"
participant Bob as "Bob (ボブ)"
Note over Alice,Bob: "公開パラメータ: 曲線 E, ベースポイント G, 素数 p"
Alice->>Alice: "秘密鍵 d_A (ランダムな整数) を生成"
Alice->>Alice: "公開鍵 Q_A = d_A * G を計算"
Bob->>Bob: "秘密鍵 d_B (ランダムな整数) を生成"
Bob->>Bob: "公開鍵 Q_B = d_B * G を計算"
Alice->>Bob: "公開鍵 Q_A を送信 (平文)"
Bob->>Alice: "公開鍵 Q_B を送信 (平文)"
Alice->>Alice: "共有シークレット S = d_A * Q_B を計算"
Bob->>Bob: "共有シークレット S = d_B * Q_A を計算"
Note over Alice,Bob: "S = d_A * (d_B * G) = d_B * (d_A * G) = (d_A * d_B) * G"
Note over Alice,Bob: "両者の計算結果 S は完全に一致する！"&lt;/div>
&lt;p>盗聴者（イヴ）は通信経路上を流れる $G$、$Q_A$、$Q_B$ を傍受できますが、ECDLPの困難性により、$Q_A = d_A \cdot G$ からアリスの秘密鍵 $d_A$ を割り出すことはできません。また、$Q_A$ と $Q_B$ を掛け合わせても共有鍵 $S$ にはならないため、盗聴者は $S$ を計算することができません。&lt;/p>
&lt;hr>
&lt;h2 id="7-実装の落とし穴サイドチャネル攻撃と対策">7. 実装の落とし穴：サイドチャネル攻撃と対策
&lt;/h2>&lt;p>理論的に完璧な暗号アルゴリズムであっても、それをプログラムとして実装する過程で脆弱性が生まれ得ます。それが**「サイドチャネル攻撃（Side-Channel Attack）」**です。&lt;/p>
&lt;h3 id="71-タイミング攻撃timing-attack">7.1. タイミング攻撃（Timing Attack）
&lt;/h3>&lt;p>前述の Double-and-Add アルゴリズムを振り返ってみましょう。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 脆弱な Double-and-Add の疑似コード
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">Point&lt;/span> &lt;span class="n">R&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Infinity&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">255&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">R&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PointDoubling&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 常に実行される
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">R&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PointAddition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// ビットが1のときだけ実行される！
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>この実装には致命的な欠陥があります。ビットが &lt;code>1&lt;/code> のときは Point Addition が実行されるため、ビットが &lt;code>0&lt;/code> のときよりも&lt;strong>計算時間がわずかに長く&lt;/strong>なります。また、プロセッサの分岐予測やキャッシュメモリの挙動も変化します。
攻撃者がこの計算時間（あるいは消費電力）の微小な差異を何千回も統計的に観測することで、&lt;strong>秘密鍵 $k$ のビット列を1ビットずつ完全に復元&lt;/strong>されてしまいます。これがタイミング攻撃です。&lt;/p>
&lt;h3 id="72-constant-time-実装montgomery-ladder">7.2. Constant-Time 実装：Montgomery Ladder
&lt;/h3>&lt;p>タイミング攻撃を防ぐためには、**秘密鍵のビット値にかかわらず、実行される命令のシーケンスと計算時間が常に一定（Constant-Time）**であるアルゴリズムを採用する必要があります。&lt;/p>
&lt;p>その代表例が**Montgomery Ladder（モンゴメリのラダー）**です。&lt;/p>
&lt;div class="mermaid">graph TD
Start["初期化: R0 = O, R1 = P"] --> LoopStart["各ビット i について (最上位から)"]
LoopStart --> Cond{"k_i の値は？"}
Cond -->|0| Branch0["R1 = R0 + R1&lt;br>R0 = 2 * R0"]
Cond -->|1| Branch1["R0 = R0 + R1&lt;br>R1 = 2 * R1"]
Branch0 --> LoopEnd["次のビットへ"]
Branch1 --> LoopEnd
LoopEnd --> LoopStart
LoopStart -.->|"全ビット完了"| End["終了: R0 が結果 (kP)"]&lt;/div>
&lt;p>Montgomery Ladderの美しい点は、ビットが &lt;code>0&lt;/code> であっても &lt;code>1&lt;/code> であっても、**「常に1回のPoint Additionと1回のPoint Doubling」**が実行されることです。これにより、計算時間のデータ依存性が完全に排除されます。&lt;/p>
&lt;p>ただし、分岐（&lt;code>if (k_i == 0)&lt;/code>）自体が存在すると、コンパイラの最適化やCPUの分岐予測によって実行時間が変動するリスクが残ります。そのため、実際のConstant-Time実装では、条件分岐（&lt;code>if&lt;/code>文）を排除し、**ビット演算を用いた条件付きスワップ（Conditional Swap）**を利用します。&lt;/p>
&lt;hr>
&lt;h2 id="8-cによる楕円曲線暗号の実装">8. C++による楕円曲線暗号の実装
&lt;/h2>&lt;p>ここからは、理論をC++のコードに落とし込んでいきます。実用的な暗号ライブラリ（OpenSSLやlibsodiumなど）は高度なアセンブリ最適化やヤコビ座標系を使用していますが、ここでは数学的理解を深めるために、&lt;strong>アフィン座標系を用いたわかりやすいConstant-Time実装&lt;/strong>の骨組みを示します。&lt;/p>
&lt;p>巨大な整数の演算には &lt;code>boost::multiprecision::cpp_int&lt;/code> を用いると仮定します。&lt;/p>
&lt;h3 id="81-モジュロ演算と逆元">8.1. モジュロ演算と逆元
&lt;/h3>&lt;p>まず、有限体上の演算ヘルパー関数を定義します。フェルマーの小定理による逆元計算を実装します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;vector&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;stdexcept&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;boost/multiprecision/cpp_int.hpp&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">boost&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">multiprecision&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 例として secp256k1 の素数 p とパラメータ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="nf">p&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">const&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">const&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 正の剰余を返すモジュロ演算
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="nf">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nl">m&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// べき乗剰余計算 (x^y mod m)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="nf">powerMod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">exp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">res&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exp&lt;/span> &lt;span class="o">/=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// フェルマーの小定理を用いたモジュロ逆元
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="nf">modInverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// mが素数である前提: n^(m-2) ≡ n^(-1) mod m
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">powerMod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="82-点の表現と群演算加算2倍算">8.2. 点の表現と群演算（加算・2倍算）
&lt;/h3>&lt;p>無限遠点をフラグで管理する &lt;code>Point&lt;/code> 構造体と、加算の公式を実装します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">Point&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="n">isInfinity&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 無限遠点の生成
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">isInfinity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">true&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 通常の点の生成
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">isInfinity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">false&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 楕円曲線上の点の加算 (R = P + Q)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">Point&lt;/span> &lt;span class="nf">pointAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">isInfinity&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">isInfinity&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// P + (-P) = 無限遠点
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">lambda&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Point Doubling (P = Q の場合)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// lambda = (3x^2 + a) / 2y
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">den&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">modInverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lambda&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">den&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Point Addition (P != Q の場合)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// lambda = (y2 - y1) / (x2 - x1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">den&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">modInverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lambda&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">den&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">x3&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lambda&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">lambda&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">y3&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lambda&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">x3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="83-constant-time-conditional-swap-の実装">8.3. Constant-Time Conditional Swap の実装
&lt;/h3>&lt;p>秘密鍵のビット値に基づいて変数の中身を入れ替える際、&lt;code>if&lt;/code> 文を使わずにビット演算（マスク）のみで入れ替えを行います。これにより実行経路が完全に一定になります。&lt;/p>
&lt;blockquote>
&lt;p>[!TIP]
実際の実装では、&lt;code>cpp_int&lt;/code> のような動的確保される多倍長整数クラスは Constant-Time 処理には適していません。メモリ割り当てや配列サイズの変動によってタイミング情報が漏洩するためです。実用ライブラリでは、固定長（例えば uint64_t × 4要素 の配列）で表現し、ビット単位でのマスク処理を実装します。以下は概念的な例です。&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 概念的な Constant-Time Swap (固定長整数の場合を想定)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// bit が 1 なら P1 と P2 を交換し、0 なら交換しない
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="nf">cswap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">bit&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// bit は 0 または 1。マスクは bit=1 なら全ビット1(0xFF..)、0なら全ビット0。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// (ここでは説明のため、固定長の BigInt クラスの各ワードを w と仮定)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="cm">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> uint64_t mask = 0 - (uint64_t)bit;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> for (int i = 0; i &amp;lt; NUM_WORDS; i++) {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> uint64_t dummy = mask &amp;amp; (P1.x.words[i] ^ P2.x.words[i]);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> P1.x.words[i] ^= dummy;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> P2.x.words[i] ^= dummy;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> // y座標や isInfinity フラグも同様に処理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ※ boost::multiprecision での完全な定数時間スワップは困難ですが、
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ここではロジックの理解のために分岐によるシミュレートにとどめます。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bit&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">P1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">P2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="84-montgomery-ladder-によるスカラー倍算">8.4. Montgomery Ladder によるスカラー倍算
&lt;/h3>&lt;p>前述の &lt;code>pointAdd&lt;/code> と &lt;code>cswap&lt;/code> を組み合わせて、セキュアなスカラー倍算を実装します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// スカラー倍算 k * P (Montgomery Ladder 方式)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">Point&lt;/span> &lt;span class="nf">scalarMultiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Point&lt;/span> &lt;span class="n">R0&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 無限遠点
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">Point&lt;/span> &lt;span class="n">R1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// k のビット長を取得 (secp256k1 なら 256 ビット)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">numBits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">256&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">numBits&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">--&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// iビット目の値を取得 (0 または 1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">bit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">static_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint8_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bit_test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// bit == 1 なら R0 と R1 をスワップ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cswap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bit&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 常に同じ演算を実行 (Point Addition と Point Doubling)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">R1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pointAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">R0&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pointAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// bit == 1 だった場合、状態を元に戻すために再度スワップ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cswap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bit&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">R0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>この実装ロジックにより、スカラー $k$ の各ビットが &lt;code>0&lt;/code> であっても &lt;code>1&lt;/code> であっても、各ループイテレーション内で実行される演算（&lt;code>cswap&lt;/code> $\to$ &lt;code>pointAdd&lt;/code> $\to$ &lt;code>pointAdd&lt;/code> $\to$ &lt;code>cswap&lt;/code>）は全く同じフローとなり、タイミングやキャッシュアクセスパターンの差異を通じた秘密情報の漏洩を強力に防ぐことができます。&lt;/p>
&lt;hr>
&lt;h2 id="9-まとめ">9. まとめ
&lt;/h2>&lt;p>楕円曲線暗号（ECC）は、初見では「なぜ直線を引いて交点を折り返すという幾何学的な操作が暗号になるのか？」と不思議に思えるかもしれません。しかし、有限体という離散的な世界にマッピングすることで、見事な一方向性関数（離散対数問題）を構築できるという、数学と暗号学の奇跡的な融合の産物です。&lt;/p>
&lt;p>本記事では以下の重要なポイントを解説しました。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>RSAに対する優位性&lt;/strong>: 非常に短い鍵長で強力なセキュリティを提供し、現代のモバイル・IoT時代に最適であること。&lt;/li>
&lt;li>&lt;strong>群論と有限体の基礎&lt;/strong>: ECCの土台となる数学的構造。&lt;/li>
&lt;li>&lt;strong>加算と2倍算の公式&lt;/strong>: ワイエルシュトラス方程式を用いた代数的な群演算の実装方法。&lt;/li>
&lt;li>&lt;strong>サイドチャネル攻撃の脅威&lt;/strong>: 秘密鍵のビットに依存した条件分岐が致命的な脆弱性を生むこと。&lt;/li>
&lt;li>&lt;strong>Constant-Time 実装&lt;/strong>: Montgomery Ladder と Conditional Swap を用いて、ハードウェアレベルの挙動を均一化し攻撃を防ぐC++コーディング技法。&lt;/li>
&lt;/ol>
&lt;p>実際にプロダクション環境で動作する暗号ライブラリを自作することは、セキュリティ上のリスクが極めて高いため非推奨（&amp;ldquo;Don&amp;rsquo;t roll your own crypto&amp;rdquo;）とされています。しかし、その内部で動いているアルゴリズムと数学的背景を深く理解することは、よりセキュアでパフォーマンスの高いシステムを設計・運用するエンジニアにとって、かけがえのない強力な武器となるはずです。&lt;/p>
&lt;p>次回の記事では、この楕円曲線を用いたデジタル署名アルゴリズムである &lt;strong>ECDSA (Elliptic Curve Digital Signature Algorithm)&lt;/strong> のメカニズムや、ビットコインで採用されている &lt;strong>Schnorr署名&lt;/strong> についてさらに深く掘り下げていきたいと思います。&lt;/p></description></item><item><title>GNFS（一般数体篩法）を超えるアルゴリズムは存在するのか？</title><link>http://kenji.blog/p/beyond-gnfs-integer-factorization-algorithms/</link><pubDate>Fri, 11 Sep 2026 09:00:00 +0900</pubDate><guid>http://kenji.blog/p/beyond-gnfs-integer-factorization-algorithms/</guid><description>&lt;img src="http://kenji.blog/p/beyond-gnfs-integer-factorization-algorithms/img/eyecatch.jpg" alt="Featured image of post GNFS（一般数体篩法）を超えるアルゴリズムは存在するのか？" />&lt;h2 id="1-はじめに素因数分解と現代暗号の根幹">1. はじめに：素因数分解と現代暗号の根幹
&lt;/h2>&lt;p>現代社会におけるインターネット通信の安全性は、公開鍵暗号であるRSA暗号の安全性に大きく依存しています。そしてRSA暗号の安全性は「巨大な合成数の素因数分解の困難さ」という数学的仮定に基づいています。もし、極めて効率的な素因数分解アルゴリズムが発見されれば、世界中の通信インフラは根底から崩れ去ることになります。&lt;/p>
&lt;p>現在、古典コンピュータを用いた巨大整数の素因数分解において、最も高速かつ最強のアルゴリズムとして君臨しているのが**一般数体篩法（GNFS: General Number Field Sieve）**です。GNFSは、1980年代後半に提唱された特殊数体篩法（SNFS）を拡張する形で誕生し、今日に至るまでRSA-768やRSA-250といった巨大な合成数の素因数分解記録を打ち立ててきました。&lt;/p>
&lt;p>しかし、暗号学者や数学者たちは常に次のような疑問を抱いています。「GNFSを超える古典的アルゴリズムは存在するのか？」「古典コンピュータの限界はどこにあるのか？」そして、「量子コンピュータはどのようにこの状況を打破するのか？」&lt;/p>
&lt;p>本記事では、GNFSの背後にある深淵なる数学的構造を徹底的に解剖し、多項式選択、篩処理、ブロック・ヴィーデマン法による線形代数ステップなどの詳細な技術的分析を行います。さらに、Coppersmithの改良などによるGNFSの拡張手法について考察し、古典的な準指数関数時間（Sub-exponential time）アルゴリズムと量子的多項式時間アルゴリズムの決定的な違いを数理的な観点から比較・解説します。&lt;/p>
&lt;hr>
&lt;h2 id="2-漸近的計算量とl記法l-notation">2. 漸近的計算量とL記法（L-notation）
&lt;/h2>&lt;p>素因数分解アルゴリズムの計算量を評価する際、標準的な多項式時間表記（$O(n^k)$など）ではなく、入力 $n$ の桁数に対する準指数関数時間を表現するために**L記法（L-notation）**が用いられます。L記法は次のように定義されます。&lt;/p>
$$
L_n[\alpha, c] = \exp \left( (c + o(1)) (\ln n)^\alpha (\ln \ln n)^{1-\alpha} \right)
$$
&lt;p>ここで、$n$ は素因数分解の対象となる整数、$\ln n$ は自然対数であり $n$ のビット長に比例します。&lt;/p>
&lt;ul>
&lt;li>$\alpha = 0$ の場合：$L_n[0, c] = \exp(c \ln \ln n) = (\ln n)^c$ となり、ビット長に対する**多項式時間（Polynomial time）**を表します。&lt;/li>
&lt;li>$\alpha = 1$ の場合：$L_n[1, c] = \exp(c \ln n) = n^c$ となり、ビット長に対する**指数関数時間（Exponential time）**を表します。&lt;/li>
&lt;li>$0 &lt; \alpha &lt; 1$ の場合：多項式時間と指数関数時間の中間に位置する**準指数関数時間（Sub-exponential time）**となります。&lt;/li>
&lt;/ul>
&lt;p>過去の素因数分解アルゴリズムの進化は、この $\alpha$ の値を徐々に小さくする歴史でもありました。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>連分数法（CFRAC）や複数多項式二次篩法（MPQS）&lt;/strong>：$\alpha = 1/2$ のクラスに属し、計算量は $L_n[1/2, 1]$ 程度です。&lt;/li>
&lt;li>&lt;strong>一般数体篩法（GNFS）&lt;/strong>：$\alpha = 1/3$ を達成し、現在知られている古典アルゴリズムの中で最速の $L_n[1/3, (64/9)^{1/3}]$ という計算量を誇ります。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="3-gnfs一般数体篩法のアルゴリズム全容と数学的構造">3. GNFS（一般数体篩法）のアルゴリズム全容と数学的構造
&lt;/h2>&lt;p>GNFSは非常に複雑で高度な数学的基盤を持っています。基本的なアイデアは、フェルマーの小定理や二次篩法（QS）の延長線上にあり、合同式 $X^2 \equiv Y^2 \pmod n$ を満たし、かつ $X \not\equiv \pm Y \pmod n$ となる非自明な組 $(X, Y)$ を見つけることで、$n$ の因数 $\gcd(X-Y, n)$ を導出するというものです。&lt;/p>
&lt;p>しかし、GNFSの真髄は、これを有理数体 $\mathbb{Q}$ のみで行うのではなく、代数体（Algebraic Number Field）と呼ばれる拡大体 $\mathbb{Q}(\alpha)$ と有理数体の両方で同時に「滑らかな数（Smooth numbers）」を探索し、準同型写像を通じて合同関係を構築する点にあります。&lt;/p>
&lt;p>GNFSのプロセスは大きく5つのフェーズに分かれます。&lt;/p>
&lt;div class="mermaid">graph TD
A["素因数分解問題 (入力 n)"] --> B["1. 多項式選択 (Polynomial Selection)"]
B --> C["2. 篩処理 (Sieving Phase)"]
C --> D["3. フィルタリング (Filtering Phase)"]
D --> E["4. 線形代数 (Linear Algebra Phase)"]
E --> F["5. 平方根 (Square Root Phase)"]
F --> G["素因数 p, q の出力"]&lt;/div>
&lt;h3 id="31-フェーズ1多項式選択-polynomial-selection">3.1 フェーズ1：多項式選択 (Polynomial Selection)
&lt;/h3>&lt;p>GNFSの成功は、適切な多項式の選択に大きく依存します。目標は、共通の根 $m$ を持つ2つの既約多項式 $f_1(x)$（有理側）と $f_2(x)$（代数側）を見つけることです。つまり、
$f_1(m) \equiv f_2(m) \equiv 0 \pmod n$
を満たします。&lt;/p>
&lt;p>通常、有理側の多項式は1次式 $f_1(x) = x - m$ を選び、代数側の多項式 $f_2(x)$ は次数 $d$（典型的には5や6）のモニック多項式を選びます。最も古典的なアプローチは&lt;strong>Base-$m$ メソッド&lt;/strong>です。
$n$ の $1/(d+1)$ 乗に近い整数 $m = \lfloor n^{1/(d+1)} \rfloor$ を選び、$n$ を $m$ 進数展開します。
$n = c_d m^d + c_{d-1} m^{d-1} + \dots + c_1 m + c_0$
これにより、多項式 $f_2(x) = c_d x^d + c_{d-1} x^{d-1} + \dots + c_0$ を得ます。明らかに $f_2(m) = n \equiv 0 \pmod n$ となります。&lt;/p>
&lt;p>しかし、現代の実装では &lt;strong>Kleinjungのアルゴリズム&lt;/strong> が用いられます。これは、多項式の係数が極端に大きくならないようにしつつ（Skewnessの最適化）、代数的な性質（Murphy&amp;rsquo;s $E$ value や $\alpha$-value）を最適化し、篩処理で滑らかな数が生成されやすい多項式を探索します。このステップだけでも多大な計算資源が投入されます。&lt;/p>
&lt;h3 id="32-フェーズ2篩処理-sieving-phase">3.2 フェーズ2：篩処理 (Sieving Phase)
&lt;/h3>&lt;p>多項式が決定すると、アルゴリズムの最も計算負荷が高い「篩（Sieving）」フェーズに入ります。ここでは、ペア $(a, b)$ を探索します。このペアは互いに素であり、以下の2つの値が同時に「滑らか（Smooth）」であることを求められます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>有理側のノルム&lt;/strong>: $F_1(a, b) = b \cdot f_1(a/b) = a - bm$&lt;/li>
&lt;li>&lt;strong>代数側のノルム&lt;/strong>: $F_2(a, b) = b^d \cdot f_2(a/b)$&lt;/li>
&lt;/ol>
&lt;p>「滑らか」とは、指定された上限（Sieve bound）以下の素数のみで素因数分解可能であることを意味します。有理側の素数ベース（Factor base）と代数側の素数ベースを用意し、巨大な探索空間上でエラトステネスの篩の要領で滑らかな数を効率的に見つけ出します。
現在では**格子篩（Lattice Sieving）**と呼ばれる手法が主流であり、特定の素数 $q$ を固定して、有理側・代数側の両方が $q$ の倍数となるような部分格子上の $(a, b)$ ペアのみを篩にかけることで、極めて高い効率を実現しています。&lt;/p>
&lt;h3 id="33-フェーズ3フィルタリング-filtering-phase">3.3 フェーズ3：フィルタリング (Filtering Phase)
&lt;/h3>&lt;p>篩処理で見つかった滑らかな関係式（リレーション）の数は数億、数十億に上ります。しかし、これらは無駄な情報も多く含んでいます。
フィルタリングの目的は、巨大な疎行列（Sparse Matrix）を構築しつつ、その次元を可能な限り小さくすることです。&lt;/p>
&lt;p>具体的には以下のような操作を行います。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Singleton removal&lt;/strong>: 1回しか現れない素因数を含むリレーションを削除する。&lt;/li>
&lt;li>&lt;strong>Clique removal / Merging&lt;/strong>: 2回以上現れる素因数を持つリレーション同士を掛け合わせて、変数を消去し、より密度の高い、しかし次元の小さい方程式系へと縮約する。&lt;/li>
&lt;/ul>
&lt;p>これにより、数十億行の行列が、数千万行レベルの巨大な疎行列 $\mathbf{A}$（要素は体 $\mathbb{F}_2$ 上の0と1）へと圧縮されます。&lt;/p>
&lt;h3 id="34-フェーズ4線形代数-linear-algebra-phase">3.4 フェーズ4：線形代数 (Linear Algebra Phase)
&lt;/h3>&lt;p>ここでは、方程式 $\mathbf{A} \mathbf{x} \equiv \mathbf{0} \pmod 2$ の非自明な解ベクトル $\mathbf{x}$ を見つけます。つまり、巨大な疎行列の左零空間（Left Nullspace）を求める問題です。&lt;/p>
&lt;p>行列のサイズが極端に大きいため、通常のガウスの消去法（$O(N^3)$）では到底計算不可能です。そこで、クリロフ部分空間法の一種である反復法が用いられます。歴史的には**ブロック・ランチョス法（Block Lanczos）&lt;strong>が使われてきましたが、現在の分散コンピューティング環境においては、通信オーバーヘッドを劇的に削減できる&lt;/strong>ブロック・ヴィーデマン法（Block Wiedemann Algorithm）**が主流です。&lt;/p>
&lt;p>ブロック・ヴィーデマン法は、行列 $\mathbf{A}$ とベクトル系列から最小多項式を計算し、ベルカンプ・マッシー（Berlekamp-Massey）アルゴリズムを用いて零空間の基底を構成します。このステップは並列化が非常に困難であり、スーパーコンピュータや大規模クラスターの密結合な通信ネットワークを要求する、GNFS最大のボトルネックの1つです。&lt;/p>
&lt;h3 id="35-フェーズ5平方根-square-root-phase">3.5 フェーズ5：平方根 (Square Root Phase)
&lt;/h3>&lt;p>線形代数の解から、有理側と代数側のそれぞれで「完全平方」となる積が構築されます。
有理側では $\prod (a-bm)$ がある整数 $X$ の平方 $X^2$ となり、代数側では対応するイデアルの積が代数体上で完全平方 $\gamma^2$ となります。
この $\gamma$ を代数体上で計算し、有理整数環への準同型写像 $\phi: \alpha \mapsto m \pmod n$ を適用することで、合同式：
$X^2 \equiv \phi(\gamma)^2 \equiv Y^2 \pmod n$
が得られます。&lt;/p>
&lt;p>代数体上の平方根の計算には、**Montgomeryの手法（Montgomery&amp;rsquo;s Method）**などの複雑なアルゴリズムが用いられ、代数的整数論の深い知識が要求されます。最終的に $\gcd(X-Y, n)$ を計算し、非自明な因数が得られれば素因数分解完了です。&lt;/p>
&lt;hr>
&lt;h2 id="4-gnfsを超える古典アルゴリズムは存在するのか">4. GNFSを超える古典アルゴリズムは存在するのか？
&lt;/h2>&lt;p>現在までのところ、一般の整数に対する素因数分解において、漸近的計算量が $L_n[1/3, c]$ を下回る古典アルゴリズムは発見されていません。しかし、理論的・実践的な限界を突破するためのいくつかの試みや派生アルゴリズムが存在します。&lt;/p>
&lt;h3 id="41-複数数体篩法-mnfs-multiple-number-field-sieve">4.1 複数数体篩法 (MNFS: Multiple Number Field Sieve)
&lt;/h3>&lt;p>GNFSを拡張したアプローチとして、D. Coppersmithによる**複数数体篩法（MNFS）**があります。GNFSでは2つの多項式（有理側と代数側）を用いますが、MNFSでは1つの有理側多項式に対して、複数の異なる代数側多項式を同時に使用します。&lt;/p>
$$ f_1(x), f_{2,1}(x), f_{2,2}(x), \dots, f_{2,V}(x) $$
&lt;p>複数の代数体を利用することで、各篩のステップにおいて「いずれかの代数体で滑らかになる」確率を飛躍的に高めることができます。Coppersmithは、このアプローチにより計算量 $L_n[1/3, c]$ の定数 $c$ をわずかに減少させることに成功しました。
具体的には、GNFSの定数が $c = (64/9)^{1/3} \approx 1.923$ であるのに対し、MNFSを最適化することで $c \approx 1.902$ 程度まで計算量を削減できることが理論的に示されています。
しかし、実践においては複数体の管理によるオーバーヘッドが大きく、実用規模でのRSAモジュラスに対する決定的なブレイクスルーには至っていません。&lt;/p>
&lt;h3 id="42-l_n14-クラスのアルゴリズムは可能か">4.2 $L_n[1/4]$ クラスのアルゴリズムは可能か？
&lt;/h3>&lt;p>素因数分解の古典的アルゴリズムの限界について、数学者たちの間で長年議論されてきたテーマが「指数 $\alpha = 1/4$ のアルゴリズムは存在するのか？」という問いです。
現在のGNFSやその派生は、篩による「滑らかさの探索」という枠組みに強く縛られており、このパラダイムの中では $\alpha = 1/3$ が限界であると広く信じられています。ディクマン関数（Dickman function）を用いた滑らかな整数の分布確率の解析からも、現在の代数体構築法と篩の組み合わせでは、どう最適化しても $O(L_n[1/3])$ の壁を越えることはできないと考えられています。&lt;/p>
&lt;p>もし $L_n[1/4]$ や、あるいは古典的な多項式時間アルゴリズムが存在するとすれば、それはGNFSのような「滑らかさベース」のアプローチとは全く異なる、現在人類が思いもよらない新しい数学的構造（例えば、楕円曲線暗号に対するSchoofのアルゴリズムのような、より高度な代数幾何学的アプローチなど）に依存するはずです。しかし、現状そのような兆候は見られません。&lt;/p>
&lt;hr>
&lt;h2 id="5-量子計算機によるブレイクスルーショアのアルゴリズム">5. 量子計算機によるブレイクスルー：ショアのアルゴリズム
&lt;/h2>&lt;p>古典コンピュータが $L_n[1/3]$ の壁に直面している一方で、計算モデル自体を根本から変えることでこの壁を粉砕したのが、1994年にPeter Shorによって発表された**ショアのアルゴリズム（Shor&amp;rsquo;s Algorithm）**です。&lt;/p>
&lt;h3 id="51-量子的多項式時間の衝撃">5.1 量子的多項式時間の衝撃
&lt;/h3>&lt;p>ショアのアルゴリズムは、素因数分解問題を「位数発見問題（Order Finding Problem）」に帰着させます。ある整数 $a$ に対して、関数 $f(x) = a^x \pmod n$ の周期（位数） $r$ を見つける問題です。
古典コンピュータではこの周期を見つけるために指数関数的な時間を要しますが、量子コンピュータ上の**量子位相推定（QPE: Quantum Phase Estimation）&lt;strong>と&lt;/strong>量子フーリエ変換（QFT: Quantum Fourier Transform）**を用いることで、全ての状態の重ね合わせ（Superposition）に対して並列に評価を行い、高い確率で周期 $r$ を抽出することが可能です。&lt;/p>
&lt;p>計算量的に見ると、ショアのアルゴリズムの実行時間は&lt;strong>量子的多項式時間&lt;/strong>、具体的には以下のようになります。
&lt;/p>
$$ O((\log n)^3) $$
&lt;p>
近年の最適化された回路実装を考慮すると、$O((\log n)^2 \log \log n)$ にまで削減可能とされています。&lt;/p>
&lt;div class="mermaid">graph LR
A["古典アルゴリズム (GNFS)"] -->|限界| B["準指数関数時間 L_n[1/3]"]
C["量子アルゴリズム (Shor)"] -->|ブレイクスルー| D["多項式時間 O((log n)^3)"]
B --> E["RSA暗号の継続利用 (鍵長増加)"]
D --> F["RSA暗号の完全崩壊"]&lt;/div>
&lt;h3 id="52-古典的準指数関数時間-vs-量子的多項式時間">5.2 古典的準指数関数時間 vs 量子的多項式時間
&lt;/h3>&lt;p>これら2つの計算量クラスの差は、現実世界の暗号セキュリティにおいて決定的な意味を持ちます。&lt;/p>
&lt;p>たとえば、RSA-2048（2048ビットの合成数）を因数分解する場合を考えます。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>GNFS (古典)&lt;/strong>: $n \approx 2^{2048}$ を $L_n[1/3, 1.923]$ に代入すると、およそ $2^{112}$ 回の演算が必要となります。これは現在の地球上の全計算資源を結集しても宇宙の寿命以上の時間を要する天文学的な計算量です。&lt;/li>
&lt;li>&lt;strong>Shor&amp;rsquo;s Algorithm (量子)&lt;/strong>: $O((\log n)^3)$ のアルゴリズムでは、$2048^3 \approx 8.5 \times 10^9$ 回程度の論理ゲート操作で済みます。これは適切なハードウェア（数百万の物理量子ビットと誤り訂正能力を備えた万能量子コンピュータ）が存在すれば、わずか数時間から数日で計算が完了することを意味します。&lt;/li>
&lt;/ul>
&lt;p>「指数 $\alpha=1/3$」の準指数関数から「多項式時間」へのパラダイムシフトは、鍵長を伸ばすことで安全性を担保するという従来の暗号学の戦略を無力化します。&lt;/p>
&lt;hr>
&lt;h2 id="6-まとめ次世代への展望">6. まとめ：次世代への展望
&lt;/h2>&lt;p>「GNFSを超える古典アルゴリズムは存在するのか？」という問いに対する現在の科学界のコンセンサスは以下の通りです。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>実践的な改良は続くが、漸近的な飛躍は無い&lt;/strong>: MNFSや多項式選択の最適化、ブロック・ヴィーデマン法の並列化など、GNFSの定数項 $c$ を改善する試みは続いています。しかし、$\alpha = 1/3$ を下回る古典的アルゴリズムが発見される可能性は極めて低いと考えられています。&lt;/li>
&lt;li>&lt;strong>古典計算機上でのRSAの安全性は依然として強固&lt;/strong>: GNFSの計算量は依然として莫大であり、RSA-2048やRSA-4096は古典コンピュータに対する攻撃に対しては今後数十年にわたり安全性を保ち続けます。&lt;/li>
&lt;li>&lt;strong>真の脅威は量子アルゴリズムである&lt;/strong>: 計算複雑性の壁を越えたのは量子力学の原理に基づくショアのアルゴリズムです。これにより、世界は耐量子計算機暗号（PQC: Post-Quantum Cryptography）への移行を余儀なくされています。格子暗号やハッシュベース暗号など、量子コンピュータでも解読が困難（多項式時間で解けない）とされる新しい数学的問題への移行が、現在の暗号学の最前線となっています。&lt;/li>
&lt;/ol>
&lt;p>一般数体篩法（GNFS）は、人類が古典数学とアルゴリズム設計の極限まで挑み、到達した「最高到達点」の一つです。GNFSの奥深い数学的構造を理解することは、単に暗号解読の歴史を学ぶだけでなく、計算複雑性理論や代数的整数論の美しさに触れる知的探求の旅でもあります。量子コンピュータが実用化されるその日まで、GNFSは最強の素因数分解アルゴリズムとしての王座を守り続けることでしょう。&lt;/p></description></item><item><title>ショアのアルゴリズムをPythonでシミュレーションしてみた</title><link>http://kenji.blog/p/shors-algorithm-simulation-python/</link><pubDate>Fri, 11 Sep 2026 08:00:00 +0900</pubDate><guid>http://kenji.blog/p/shors-algorithm-simulation-python/</guid><description>&lt;img src="http://kenji.blog/p/shors-algorithm-simulation-python/img/eyecatch.jpg" alt="Featured image of post ショアのアルゴリズムをPythonでシミュレーションしてみた" />&lt;h1 id="1-はじめに量子コンピュータがもたらす暗号の危機">1. はじめに：量子コンピュータがもたらす暗号の危機
&lt;/h1>&lt;p>現代のインターネット社会におけるセキュリティの大部分は、&lt;strong>公開鍵暗号方式&lt;/strong>（特にRSA暗号）に依存しています。私たちがオンラインショッピングでクレジットカード情報を送信する際や、機密性の高いデータをやり取りする際、その通信内容はRSA暗号によって強固に守られています。&lt;/p>
&lt;p>RSA暗号の安全性の根拠は、「&lt;strong>巨大な整数の素因数分解は、古典コンピュータ（私たちが普段使っているPCやスーパーコンピュータ）では極めて困難である&lt;/strong>」という数学的な事実に依存しています。しかし、1994年にピーター・ショア（Peter Shor）が発表した「&lt;strong>ショアのアルゴリズム（Shor&amp;rsquo;s Algorithm）&lt;/strong>」は、この前提を根底から覆すものでした。ショアのアルゴリズムを大規模な量子コンピュータ上で実行すれば、古典コンピュータでは宇宙の年齢以上の時間を要する素因数分解を、わずか数分から数時間で解くことができると数学的に証明されたのです。&lt;/p>
&lt;p>本記事では、このショアのアルゴリズムがいかにして素因数分解を高速に行うのか、その数学的な仕組みから、Pythonと量子計算フレームワークである&lt;strong>Qiskit&lt;/strong>を用いた具体的なシミュレーション実装までを、徹底的に詳細に解説していきます。&lt;/p>
&lt;hr>
&lt;h1 id="2-計算量の劇的な変化指数関数から多項式時間へ">2. 計算量の劇的な変化：指数関数から多項式時間へ
&lt;/h1>&lt;p>なぜ素因数分解が難しいのでしょうか？古典コンピュータにおける最良の素因数分解アルゴリズムとして知られる「一般数体ふるい法（General Number Field Sieve, GNFS）」を用いたとしても、その計算量は準指数関数的になります。&lt;/p>
&lt;p>桁数 $N$ の合成数を素因数分解するのにかかる時間計算量は、古典的な手法では以下のようになります。&lt;/p>
$$ O\left(\exp\left( c (\log N)^{1/3} (\log \log N)^{2/3} \right)\right) $$
&lt;p>このため、鍵長を長くする（例えば2048ビットや4096ビットにする）だけで、古典コンピュータでの解読には数千年、数万年といった現実的ではない時間がかかるようになります。&lt;/p>
&lt;p>しかし、量子コンピュータ上で&lt;strong>ショアのアルゴリズム&lt;/strong>を用いると、計算量は入力のビット数 $\log N$ に対して多項式時間へと劇的に削減されます。&lt;/p>
$$ O((\log N)^3) $$
&lt;p>これは、ビット数を2倍にした場合、古典コンピュータでは計算時間が天文学的に増大するのに対し、量子コンピュータでは計算時間がせいぜい8倍程度にしか増えないことを意味します。この**指数関数時間から多項式時間への計算量クラスの削減（BQPクラスへの包含）**こそが、ショアのアルゴリズムの真の凄みです。&lt;/p>
&lt;div class="mermaid">graph TD
A["入力サイズ(ビット数) Nの増加"] --> B{"アルゴリズムの選択"}
B -->|古典: 一般数体ふるい法| C["準指数関数的増加 O(exp(...))"]
B -->|量子: ショアのアルゴリズム| D["多項式時間 O((log N)^3)"]
C --> E["数千年〜数十億年 (解読不能)"]
D --> F["数分〜数時間 (現実的な時間で解読)"]&lt;/div>
&lt;hr>
&lt;h1 id="3-アルゴリズムの全体像と数学的背景">3. アルゴリズムの全体像と数学的背景
&lt;/h1>&lt;p>ショアのアルゴリズムは、実はすべてを量子コンピュータで行うわけではありません。古典コンピュータによる前処理・後処理と、量子コンピュータによる核心部分（周期発見アルゴリズム）の連携によって成り立っています。&lt;/p>
&lt;p>アルゴリズムの全体的な流れは以下のようになります。&lt;/p>
&lt;div class="mermaid">graph TD
A["入力: 素因数分解したい合成数 N"] --> B["a &lt; N なる乱数 a を選ぶ"]
B --> C{"gcd(a, N) > 1 ?"}
C -- "Yes" --> D["自明な因数 gcd(a, N) を出力して終了"]
C -- "No" --> E["量子アルゴリズムで f(x) = a^x mod N の周期 r を見つける"]
E --> F{"r が偶数 かつ a^(r/2) ≢ -1 mod N ?"}
F -- "No" --> B
F -- "Yes" --> G["因数 p = gcd(a^(r/2) - 1, N), q = gcd(a^(r/2) + 1, N) を計算"]
G --> H["出力: p, q"]&lt;/div>
&lt;h2 id="素因数分解から周期発見問題への帰着">素因数分解から周期発見問題への帰着
&lt;/h2>&lt;p>ショアの天才的なひらめきは、「&lt;strong>素因数分解問題&lt;/strong>」を「&lt;strong>周期発見問題（Order Finding Problem）&lt;/strong>」に変換したことにあります。&lt;/p>
&lt;p>整数 $N$（素因数分解したい数）と、互いに素な整数 $a$（$1 &lt; a &lt; N$）を考えます。次のようなモジュラー指数関数を定義します。&lt;/p>
$$ f(x) = a^x \bmod N $$
&lt;p>この関数は、ある周期 $r$ を持ちます。つまり、任意の $x$ に対して $f(x+r) = f(x)$ が成り立ちます。特に $x=0$ のとき、&lt;/p>
$$ a^r \equiv 1 \pmod N $$
&lt;p>となる最小の正の整数 $r$ を「$a$ の $N$ を法とする位数（Order）」と呼びます。この周期 $r$ を見つけることができれば、次のようにして素因数を導き出すことができます。&lt;/p>
&lt;p>式を変形すると、
&lt;/p>
$$ a^r - 1 \equiv 0 \pmod N $$
&lt;p>
もし $r$ が偶数であれば、平方差の公式を用いて因数分解できます。
&lt;/p>
$$ (a^{r/2} - 1)(a^{r/2} + 1) \equiv 0 \pmod N $$
&lt;p>これは、$N$ が $(a^{r/2} - 1)$ または $(a^{r/2} + 1)$ のいずれかと公約数を持つことを意味します（ただし $a^{r/2} \not\equiv -1 \pmod N$ という条件を満たす必要があります）。したがって、ユークリッドの互除法を用いて、&lt;/p>
$$ p = \gcd(a^{r/2} - 1, N) $$
$$ q = \gcd(a^{r/2} + 1, N) $$
&lt;p>を計算すれば、$N$ の非自明な素因数 $p, q$ を見つけることができるのです。この計算（最大公約数の計算や乱数の生成）は、古典コンピュータで非常に高速に行えます。問題は、&lt;strong>周期 $r$ をどのようにして高速に見つけるか&lt;/strong>という点に絞られます。古典コンピュータでは、この周期 $r$ を見つけること自体が指数関数的な時間を要してしまいます。ここで量子コンピュータの出番となります。&lt;/p>
&lt;hr>
&lt;h1 id="4-量子アルゴリズム部分周期発見の仕組み">4. 量子アルゴリズム部分：周期発見の仕組み
&lt;/h1>&lt;p>量子コンピュータを用いて周期 $r$ を見つけるためのサブルーチンは、以下の4つのステップで構成されます。&lt;/p>
&lt;div class="mermaid">graph LR
subgraph "量子状態の推移"
S1["|0⟩|0⟩ (初期化)"] --> S2["Hゲート: 重ね合わせ Σ|x⟩|0⟩"]
S2 --> S3["オラクル U: Σ|x⟩|a^x mod N⟩"]
S3 --> S4["QFT: 干渉による周期抽出"]
S4 --> S5["測定: 近似値 y の取得"]
end&lt;/div>
&lt;h2 id="ステップ1量子レジスタの初期化と重ね合わせ">ステップ1：量子レジスタの初期化と重ね合わせ
&lt;/h2>&lt;p>まず、2つの量子レジスタを用意します。第1レジスタは状態を入力するためのもので、第2レジスタは関数の計算結果を格納するためのものです。
初期状態は全て $|0\rangle$ です。&lt;/p>
$$ |\psi_0\rangle = |0\rangle_1 |0\rangle_2 $$
&lt;p>第1レジスタのすべての量子ビットにアダマールゲート（Hadamard Gate）を適用し、考え得るすべての入力 $x$ （$0$ から $Q-1$ まで、$Q=2^n$）の等確率な重ね合わせ状態を作り出します。&lt;/p>
$$ |\psi_1\rangle = \frac{1}{\sqrt{Q}} \sum_{x=0}^{Q-1} |x\rangle_1 |0\rangle_2 $$
&lt;p>これにより、量子コンピュータは一度の演算で $Q$ 個すべての入力に対する状態を同時に保持することになります。これが&lt;strong>量子並列性&lt;/strong>の強力な源泉です。&lt;/p>
&lt;h2 id="ステップ2オラクル関数モジュラーべき乗の適用">ステップ2：オラクル関数（モジュラーべき乗）の適用
&lt;/h2>&lt;p>次に、量子演算回路 $U_f$ を用いて、関数 $f(x) = a^x \bmod N$ を計算し、その結果を第2レジスタに格納します。&lt;/p>
$$ |\psi_2\rangle = \frac{1}{\sqrt{Q}} \sum_{x=0}^{Q-1} |x\rangle_1 |a^x \bmod N\rangle_2 $$
&lt;p>この時点で、第1レジスタと第2レジスタは**量子もつれ（エンタングルメント）**の状態にあります。もし（仮に）第2レジスタを観測して特定の値 $k = a^{x_0} \bmod N$ を得たとすると、第1レジスタの状態は、その値 $k$ を与えるような $x$ の重ね合わせ状態に崩壊します。関数の周期が $r$ であるため、残る状態は $x_0, x_0+r, x_0+2r, \dots$ という $r$ 飛ばしの値になります。&lt;/p>
$$ |\psi_3\rangle = \sqrt{\frac{r}{Q}} \sum_{j=0}^{M-1} |x_0 + j r\rangle_1 |k\rangle_2 $$
&lt;p>しかし、私たちは $x_0$ を知りたいわけではなく、周期 $r$ 自体を知りたいのです。この状態から $r$ を直接観測することは不可能です。そこで、量子フーリエ変換を用います。&lt;/p>
&lt;h2 id="ステップ3量子フーリエ変換qftによる位相干渉">ステップ3：量子フーリエ変換（QFT）による位相干渉
&lt;/h2>&lt;p>第1レジスタに対して**量子フーリエ変換（Quantum Fourier Transform, QFT）**を適用します。QFTは古典的な離散フーリエ変換の量子版であり、状態ベクトルの振幅を変換します。基底状態 $|x\rangle$ に対するQFTの作用は以下のように定義されます。&lt;/p>
$$ QFT |x\rangle = \frac{1}{\sqrt{Q}} \sum_{y=0}^{Q-1} \omega^{xy} |y\rangle $$
&lt;p>ここで、$\omega = e^{2\pi i / Q}$ です。&lt;/p>
&lt;p>QFTを適用すると、状態の振幅が干渉を起こします。数学的な詳細は省きますが、周期 $r$ を持つ状態に対してQFTを適用すると、波が**建設的干渉（Constructive Interference）&lt;strong>を起こすのは、$y$ が $Q/r$ の整数倍に極めて近い値のときだけになります。それ以外の状態は&lt;/strong>破壊的干渉（Destructive Interference）**により確率振幅が相殺され、ゼロに近づきます。&lt;/p>
&lt;h2 id="ステップ4測定と連分数展開">ステップ4：測定と連分数展開
&lt;/h2>&lt;p>最後に第1レジスタを測定します。測定によって得られる値 $y$ は、高い確率で以下の条件を満たします。&lt;/p>
$$ y \approx c \frac{Q}{r} \implies \frac{y}{Q} \approx \frac{c}{r} $$
&lt;p>（$c$ は $0 \le c &lt; r$ の未知の整数です）&lt;/p>
&lt;p>得られた有理数 $y/Q$ に対して古典アルゴリズムである**連分数展開（Continued Fraction Expansion）**を適用することで、近似分数 $c/r$ を計算し、分母から周期 $r$ を抽出します。&lt;/p>
&lt;hr>
&lt;h1 id="5-pythonとqiskitを用いたシミュレーション実装">5. PythonとQiskitを用いたシミュレーション実装
&lt;/h1>&lt;p>理論だけでは実感が湧かないため、実際にPythonとIBMの量子計算フレームワークである&lt;strong>Qiskit&lt;/strong>を用いて、ショアのアルゴリズムをシミュレーションしてみましょう。&lt;/p>
&lt;p>ここでは、最も古典的で有名な例である &lt;strong>「$N=15$ を $a=7$ を用いて素因数分解する」&lt;/strong> というシナリオを実装します。&lt;/p>
&lt;h2 id="実行環境の準備">実行環境の準備
&lt;/h2>&lt;p>あらかじめQiskitをインストールしておいてください。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install qiskit qiskit-aer numpy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="python実装コードの全体像">Python実装コードの全体像
&lt;/h2>&lt;p>以下のコードは、$N=15, a=7$ に特化したショアのアルゴリズムの実装例です。汎用的なモジュラーべき乗回路を組むのは現在のシミュレータでは計算コストが高すぎるため、特定の $a=7$ の場合のゲート動作をハードコーディングしています。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;span class="lnt">104
&lt;/span>&lt;span class="lnt">105
&lt;/span>&lt;span class="lnt">106
&lt;/span>&lt;span class="lnt">107
&lt;/span>&lt;span class="lnt">108
&lt;/span>&lt;span class="lnt">109
&lt;/span>&lt;span class="lnt">110
&lt;/span>&lt;span class="lnt">111
&lt;/span>&lt;span class="lnt">112
&lt;/span>&lt;span class="lnt">113
&lt;/span>&lt;span class="lnt">114
&lt;/span>&lt;span class="lnt">115
&lt;/span>&lt;span class="lnt">116
&lt;/span>&lt;span class="lnt">117
&lt;/span>&lt;span class="lnt">118
&lt;/span>&lt;span class="lnt">119
&lt;/span>&lt;span class="lnt">120
&lt;/span>&lt;span class="lnt">121
&lt;/span>&lt;span class="lnt">122
&lt;/span>&lt;span class="lnt">123
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">QuantumCircuit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit_aer&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AerSimulator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit.visualization&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">plot_histogram&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fractions&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Fraction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">math&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 逆量子フーリエ変換 (QFT†) を構築する関数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">qft_dagger&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;n量子ビットの逆量子フーリエ変換回路を生成する&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QuantumCircuit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 順序を反転するためのSWAPゲート&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">qubit&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">qubit&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">qubit&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 制御位相ゲートとHゲートの適用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pi&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nb">float&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">)),&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;QFT_dagger&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">qc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 7^x mod 15 の制御モジュラーべき乗演算を構築する関数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">c_amod15&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">power&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;特定のaとべき乗に対する制御Uゲートを生成する(N=15専用)&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QuantumCircuit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">power&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># a=7の場合の 7^x mod 15 のハードコーディング論理&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">13&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">11&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">11&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">13&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_gate&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">^&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">power&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> mod 15&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c_U&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">control&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">c_U&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. メインの量子回路構成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">shor_circuit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n_count&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># n_count: 制御レジスタのビット数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ターゲットレジスタは 0〜15 を表現するため 4ビット&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QuantumCircuit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n_count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 第1レジスタ（制御レジスタ）の初期化（重ね合わせの生成）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 第2レジスタ（ターゲットレジスタ）を |1&amp;gt; (0001) に初期化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">n_count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 制御モジュラーべき乗演算（オラクル）の適用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2^q 乗の演算を適用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c_amod15&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="n">n_count&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 第1レジスタに逆量子フーリエ変換を適用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">qft_dagger&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 第1レジスタを測定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">measure&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">qc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># --- 実行セクション ---&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">N&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">15&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="c1"># 制御レジスタに8量子ビットを使用 (Q=256)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;探索設定: N=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">N&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, a=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, 制御量子ビット数=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 回路の生成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">shor_circuit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n_count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># シミュレータでの実行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sim&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AerSimulator&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 最新のQiskitではtranspileを推奨&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">transpile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">compiled_circuit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">transpile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">qc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sim&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">job&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sim&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">compiled_circuit&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">shots&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1024&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">job&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_counts&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">測定結果(ビット列: 観測回数):&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">bitstring&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">counts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">bitstring&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">回&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 古典的後処理: 連分数展開による周期rの特定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">--- 周期の計算と素因数分解 ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">phases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">counts&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ビット列を10進数に変換&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">decimal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 位相 = 測定値 / 2^n_count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">phase&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">decimal&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">phases&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">phase&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 連分数展開によって近似分数を取得。分母の上限は N=15&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">frac&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Fraction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">phase&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">limit_denominator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">frac&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">denominator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;観測値: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">decimal&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">3d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> | 位相: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">phase&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.4f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> | 連分数: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">frac&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> | 推定周期 r = &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 周期 r が偶数かつ有効な結果をもたらすか確認&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">guess1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">guess2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">guess1&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">guess2&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; =&amp;gt; 成功！ &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">N&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> の素因数は &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">guess1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> と &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">guess2&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> です。&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; =&amp;gt; 自明な因数のみ。やり直し。&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; =&amp;gt; 周期が奇数のため失敗。&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="コードの解説と実行結果の解析">コードの解説と実行結果の解析
&lt;/h2>&lt;p>上記のコードを実行すると、制御レジスタの測定結果として、高い確率で特定のピーク（観測値）が得られます。&lt;code>n_count=8&lt;/code>（$Q=256$）の場合、理想的な量子コンピュータ（またはシミュレータ）であれば、観測値として &lt;code>0&lt;/code>, &lt;code>64&lt;/code>, &lt;code>128&lt;/code>, &lt;code>192&lt;/code> のような数値が圧倒的な確率で出現します。&lt;/p>
&lt;p>これらを $Q=256$ で割ると、位相 $y/Q$ はそれぞれ $0.0$, $0.25$, $0.5$, $0.75$ となります。
この位相を連分数展開すると：&lt;/p>
&lt;ul>
&lt;li>$0.25 \to 1/4$ （推定周期 $r=4$）&lt;/li>
&lt;li>$0.50 \to 1/2$ （推定周期 $r=2$）&lt;/li>
&lt;li>$0.75 \to 3/4$ （推定周期 $r=4$）&lt;/li>
&lt;/ul>
&lt;p>ここで得られた周期 $r=4$ を用いて、素因数を計算します。
$a=7, r=4$ なので、
$p = \gcd(7^2 - 1, 15) = \gcd(48, 15) = 3$
$q = \gcd(7^2 + 1, 15) = \gcd(50, 15) = 5$&lt;/p>
&lt;p>見事に、$15 = 3 \times 5$ の素因数分解に成功しました。&lt;/p>
&lt;blockquote>
&lt;p>[!TIP]
測定値として $y=128$（位相 $0.5$）が得られた場合、分母は $2$ となり、真の周期 $r=4$ ではなくその約数を得てしまいます。このような場合は、アルゴリズムを複数回実行するか、得られた $r$ の倍数を調べることで真の周期にたどり着くことができます。&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h1 id="6-実用化に向けた課題と-nisq-時代の限界">6. 実用化に向けた課題と NISQ 時代の限界
&lt;/h1>&lt;p>シミュレータ上で $N=15$ を素因数分解することは簡単にできましたが、実社会で使われているRSA-2048（617桁の10進数）を素因数分解するには、現実の量子コンピュータにはまだ数多くの壁が存在します。&lt;/p>
&lt;p>現在私たちが生きている時代は、&lt;strong>NISQ（Noisy Intermediate-Scale Quantum：ノイズあり中規模量子）時代&lt;/strong>と呼ばれています。量子ビットは外部環境のノイズに対して極めて脆弱であり、計算途中で「デコヒーレンス」を起こして状態が壊れてしまいます。&lt;/p>
&lt;p>ショアのアルゴリズムのような深い（ゲート数が多い）回路を正確に実行するためには、ノイズを訂正する**量子誤り訂正（Quantum Error Correction）**が不可欠です。一つのノイズのない「論理量子ビット」を作り出すために、数千個の「物理量子ビット」を表面符号（Surface Code）などでエンコードする必要があります。&lt;/p>
&lt;p>2048ビットのRSA暗号を破るためには、数千個の完璧な論理量子ビットが必要であり、それを実現するには&lt;strong>数百万から数千万個の物理量子ビット&lt;/strong>を搭載したフォールトトレラント（誤り耐性）量子コンピュータが必要になると見積もられています。現在の最先端の量子プロセッサでも数百〜数千物理量子ビット程度であるため、直ちに世界中の暗号が破られるわけではありません。&lt;/p>
&lt;blockquote>
&lt;p>[!WARNING]
しかし、「Store Now, Decrypt Later（今保存して、後で解読する）」という脅威モデルが存在します。攻撃者は現在暗号化されている機密通信を暗号データのまま大量に保存しておき、10〜20年後に強力な量子コンピュータが完成した瞬間に全てを解読するという戦略をとる可能性があります。&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h1 id="7-耐量子計算機暗号pqcへの移行">7. 耐量子計算機暗号（PQC）への移行
&lt;/h1>&lt;p>このような「Q-Day（量子コンピュータが暗号を破る日）」の到来に備え、アメリカ国立標準技術研究所（NIST）を筆頭に、世界中の暗号学者が**耐量子計算機暗号（Post-Quantum Cryptography, PQC）**の策定を進めています。&lt;/p>
&lt;p>PQCは、ショアのアルゴリズムを用いても（あるいはグローバーのアルゴリズムを用いても）効率的に解くことができないと数学的に考えられている新しい数学的問題（格子問題、多変数多項式問題、ハッシュ関数ベースなど）を基盤としています。すでに「CRYSTALS-Kyber」や「CRYSTALS-Dilithium」といったアルゴリズムが標準規格として選定され、AppleのiMessageや各種ウェブブラウザの通信プロトコルへの導入が徐々に始まっています。&lt;/p>
&lt;p>ITインフラを管理するエンジニアにとって、既存のRSAや楕円曲線暗号からPQCへの「クリプト・アジリティ（暗号の俊敏性：素早く暗号方式を切り替えられる設計）」をシステムに組み込むことが、今後の大きなミッションとなるでしょう。&lt;/p>
&lt;hr>
&lt;h1 id="8-おわりに">8. おわりに
&lt;/h1>&lt;p>本記事では、ショアのアルゴリズムの理論的な数学的背景から始まり、量子フーリエ変換を用いた周期抽出のメカニズム、そしてPythonとQiskitを用いた具体的なシミュレーションコードまで、1万文字規模のボリュームで徹底的に解説を行いました。&lt;/p>
&lt;p>量子力学というミクロな世界の物理法則が、マクロな情報科学の根幹である計算量理論や暗号理論を根本から覆してしまうという事実は、科学の歴史において最もエキサイティングなパラダイムシフトの一つです。現在進行形で発展し続ける量子コンピューティング技術と、それに立ち向かう新しい暗号技術の攻防戦から、今後も目が離せません。&lt;/p>
&lt;p>ぜひ、今回紹介したPythonコードをご自身の環境で実行し、量子状態の重ね合わせと干渉が生み出す「計算の魔法」を体感してみてください。&lt;/p>
&lt;hr>
&lt;p>&lt;strong>参考文献&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Shor, P. W. (1994). &amp;ldquo;Algorithms for quantum computation: discrete logarithms and factoring&amp;rdquo;. Proceedings 35th Annual Symposium on Foundations of Computer Science.&lt;/li>
&lt;li>Nielsen, M. A., &amp;amp; Chuang, I. L. (2010). &amp;ldquo;Quantum Computation and Quantum Information&amp;rdquo;. Cambridge University Press.&lt;/li>
&lt;li>Qiskit Documentation: &lt;a class="link" href="https://qiskit.org/documentation/" target="_blank" rel="noopener"
>https://qiskit.org/documentation/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>【図解PQC】耐量子計算機暗号の主要なアルゴリズム比較</title><link>http://kenji.blog/p/post-quantum-cryptography-algorithms-comparison/</link><pubDate>Fri, 11 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/p/post-quantum-cryptography-algorithms-comparison/</guid><description>&lt;img src="http://kenji.blog/p/post-quantum-cryptography-algorithms-comparison/img/eyecatch.jpg" alt="Featured image of post 【図解PQC】耐量子計算機暗号の主要なアルゴリズム比較" />&lt;h2 id="1-はじめに量子コンピュータがもたらす暗号の危機">1. はじめに：量子コンピュータがもたらす「暗号の危機」
&lt;/h2>&lt;p>現代のインターネット社会において、通信の機密性やデータの完全性を守るために公開鍵暗号技術はインフラとして必要不可欠です。現在広く利用されているRSA暗号や楕円曲線暗号（ECC）は、それぞれ「巨大な合成数の素因数分解の困難性」や「楕円曲線上の離散対数問題の困難性」という数学的な壁に依存して安全性を担保しています。古典的なコンピュータ（スーパーコンピュータを含む現在私たちが使っているコンピュータ）では、これらの数学的問題を解くためには宇宙の年齢よりも長い時間がかかると証明されており、それが安全性の根拠となってきました。&lt;/p>
&lt;p>しかし、この堅牢な前提は&lt;strong>量子コンピュータ&lt;/strong>の理論と実用化の進展によって根底から覆されようとしています。1994年に暗号学者ピーター・ショア（Peter Shor）が発表した「&lt;strong>Shorのアルゴリズム&lt;/strong>」は、十分な性能を持つ誤り耐性汎用量子コンピュータ（CRQC: Cryptographically Relevant Quantum Computer）上で実行することで、素因数分解問題や離散対数問題を「多項式時間」で解読できることを理論的に証明しました。これは、現在使われている公開鍵暗号がすべて無力化されることを意味します。&lt;/p>
&lt;div class="mermaid">graph TD
A["大規模な量子コンピュータ (CRQC)"] -->|実行| B["Shorのアルゴリズム"]
B -->|多項式時間で解読| C["素因数分解問題 (RSA)"]
B -->|多項式時間で解読| D["離散対数問題 (ECC / ECDSA)"]
C --> E["暗号通信の盗聴・データ改ざん・なりすまし"]
D --> E
F["Store Now, Decrypt Later (SNDL)"] --> E&lt;/div>
&lt;p>「量子コンピュータの本格的な完成はまだ数十年先だから問題ない」と考えるのは非常に危険です。なぜなら、**Store Now, Decrypt Later (SNDL：今すぐ保存し、後で復号する)**と呼ばれる攻撃手法がすでに現実の脅威となっているからです。これは、悪意のある国家やハッカー組織が、現在の暗号化された通信データ（TLSのトラフィックなど）を大量にストレージに保存しておき、将来強力な量子コンピュータが利用可能になった瞬間にそれらをすべて復号するという攻撃です。国家機密、インフラ情報、長期間保護すべき医療データなどは、すでにこの脅威に晒されています。&lt;/p>
&lt;p>また、対称鍵暗号（AESなど）やハッシュ関数（SHA-256など）に対しても、1996年に発見された&lt;strong>Groverのアルゴリズム&lt;/strong>が存在します。これにより、総当たり攻撃（ブルートフォース）の計算量が平方根に削減されます。つまり、AES-128のセキュリティレベルは実質的に2の64乗に半減するため、量子時代においてはAES-256やSHA-384といった、より長い鍵やハッシュ長を使用することが推奨されています。&lt;/p>
&lt;p>この未曾有の暗号危機に対抗するために誕生したのが、量子コンピュータを用いても解読が困難な新しい数学的問題に基づいた**耐量子計算機暗号（Post-Quantum Cryptography: PQC）**です。本記事では、米国国立標準技術研究所（NIST）が主導して進めてきたPQC標準化プロセスの結果に基づき、主要なPQCアルゴリズムについて、その数学的背景から仕組み、アーキテクチャの比較までを極めて詳細に解説します。&lt;/p>
&lt;hr>
&lt;h2 id="2-nistによるpqc標準化プロジェクトの全貌と歴史">2. NISTによるPQC標準化プロジェクトの全貌と歴史
&lt;/h2>&lt;p>暗号技術の移行には、プロトコルの再設計、システムのアップデート、ハードウェアの交換などを含め、数年から数十年の時間がかかります。そのため、世界中の暗号学者たちは早期からPQCの研究を進めてきました。その中心的な役割を担ってきたのが米国のNIST（国立標準技術研究所）です。NISTは2016年にPQCの標準化プロセスを公募し、全世界の暗号コミュニティから全く新しい暗号アルゴリズムの提案を受け付けました。&lt;/p>
&lt;p>標準化の対象となったのは以下の2つの主要カテゴリです。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>公開鍵暗号 / 鍵カプセル化メカニズム (KEM: Key Encapsulation Mechanism)&lt;/strong>: TLS接続などで、通信経路を暗号化するための共通鍵を安全に共有（配送）するための仕組み。&lt;/li>
&lt;li>&lt;strong>デジタル署名 (Digital Signatures)&lt;/strong>: ソフトウェアのアップデートや電子証明書において、データの改ざんがないことや、送信者のなりすましがないこと（真正性）を証明するための仕組み。&lt;/li>
&lt;/ol>
&lt;p>約6年間にわたる非常に激しい評価・解析と暗号解読の競争（Round 1〜Round 3）を経て、さらに一部のアルゴリズムについてはRound 4の追加評価が行われました。その結果、2024年に以下のアルゴリズムが正式な連邦情報処理標準（FIPS）として発行され、今後の世界の標準として確定しました。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FIPS 203 (ML-KEM)&lt;/strong>: CRYSTALS-Kyberに基づくKEM&lt;/li>
&lt;li>&lt;strong>FIPS 204 (ML-DSA)&lt;/strong>: CRYSTALS-Dilithiumに基づくデジタル署名&lt;/li>
&lt;li>&lt;strong>FIPS 205 (SLH-DSA)&lt;/strong>: SPHINCS+に基づくステートレスハッシュベース署名&lt;/li>
&lt;li>&lt;strong>（今後の策定予定）FN-DSA&lt;/strong>: FALCONに基づくデジタル署名&lt;/li>
&lt;/ul>
&lt;p>これらの選定されたアルゴリズムは、依拠する数学的な「困難性問題」がそれぞれ異なっており、ある一つのアルゴリズムに将来致命的な脆弱性が発見された場合でも、システム全体が崩壊しないように多様性（Crypto Agility）が確保されています。標準化プロセスでは、主に格子暗号（Lattice-based cryptography）が性能の面から主役となりましたが、ハッシュベース暗号や符号ベース暗号が強力なバックアップとして採用されています。&lt;/p>
&lt;hr>
&lt;h2 id="3-pqcの主要な数学的アプローチの分類">3. PQCの主要な数学的アプローチの分類
&lt;/h2>&lt;p>PQCアルゴリズムは、その安全性の根拠となる数学的な問題によって、主に以下の5つのカテゴリに大別されます。本記事では特に上位3つについて詳しく掘り下げます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>格子暗号 (Lattice-based Cryptography)&lt;/strong>:
多次元の格子空間における最短ベクトル問題（SVP）や最近接ベクトル問題（CVP）、およびそこから派生したLWE問題に基づきます。NIST標準化の中心であり、Kyber、Dilithium、FALCONが該当します。処理速度、公開鍵サイズ、暗号文サイズのバランスが最も優れており、汎用的な利用に適しています。&lt;/li>
&lt;li>&lt;strong>ハッシュベース暗号 (Hash-based Cryptography)&lt;/strong>:
暗号学的ハッシュ関数（SHA-2やSHAKEなど）の「衝突耐性」と「一方向性」のみに安全性の根拠を置きます。デジタル署名（SPHINCS+など）にのみ適用可能ですが、安全性の証明が最も強固であり、未知の数学的攻撃に対する耐性が極めて高いのが特徴です。&lt;/li>
&lt;li>&lt;strong>符号ベース暗号 (Code-based Cryptography)&lt;/strong>:
誤り訂正符号の理論に基づき、シンドローム復号問題（Syndrome Decoding Problem）の困難性に依存します。1970年代に提案されたClassic McElieceが代表的で、非常に長い歴史と実績のある安全性を持つ反面、公開鍵のサイズがメガバイト単位と極端に大きくなります。&lt;/li>
&lt;li>&lt;strong>多変数多項式暗号 (Multivariate Polynomial Cryptography)&lt;/strong>:
有限体上の多変数連立二次方程式の解を求めること（MQ問題）の困難性に基づきます。主にデジタル署名（Rainbowなど）として提案されましたが、NISTの最終ラウンドの最中にパソコン1台で数日で解読されるという強力な攻撃手法が発見され、多くのアルゴリズムが標準化から脱落しました。&lt;/li>
&lt;li>&lt;strong>同種写像暗号 (Isogeny-based Cryptography)&lt;/strong>:
楕円曲線の同種写像（Isogeny）グラフ上での経路探索問題に基づきます。鍵サイズが非常に小さく、ECCの正当な後継として期待されていましたが、最終候補であった「SIKE」が2022年に古典的な数学（Castryck-Decru攻撃など）を用いて通常のPCでわずか数時間で完全に解読されてしまい、PQC設計の難しさと恐ろしさを象徴する劇的な幕引きとなりました。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-格子暗号の深淵lwe問題とmodule-lweの数学的基礎">4. 格子暗号の深淵：LWE問題とModule-LWEの数学的基礎
&lt;/h2>&lt;p>現在最も有力視され、標準化の中心となった&lt;strong>格子暗号&lt;/strong>。その安全性の根底にあるのが**LWE問題 (Learning with Errors: 誤差付き学習問題)**です。2005年にOded Regevによって提唱され、この画期的な功績により彼はGödel賞を受賞しました。LWE問題の理解なくして現代のPQCを語ることはできません。&lt;/p>
&lt;h3 id="41-lwe問題-learning-with-errors-とは何か">4.1. LWE問題 (Learning with Errors) とは何か
&lt;/h3>&lt;p>まず、簡単な連立一次方程式を考えてみましょう。ある法 $q$ （モジュロ $q$）のもとで、既知のランダムな行列 $A$ と、未知の秘密のベクトル $\vec{s}$ があり、その積 $\vec{b}$ が与えられたとします。&lt;/p>
$$ \vec{b} = A\vec{s} \pmod q $$
&lt;p>このとき、公開情報である $A$ と $\vec{b}$ から、未知の $\vec{s}$ を求めるのは簡単です。古典的なアルゴリズムである「ガウスの消去法（掃き出し法）」を使えば、多項式時間で容易に $\vec{s}$ を計算できます。&lt;/p>
&lt;p>しかし、この式に「小さな意図的な誤差（ノイズ）」を加えると、問題の難易度が劇的に跳ね上がります。これが&lt;strong>LWE問題&lt;/strong>です。&lt;/p>
&lt;p>未知の秘密ベクトル $\vec{s} \in \mathbb{Z}_q^n$ と、ランダムに選ばれた行列 $A \in \mathbb{Z}_q^{m \times n}$ を用意します。さらに、正規分布や二項分布などに従って選ばれた「要素の値が十分に小さい」誤差ベクトル $\vec{e} \in \mathbb{Z}_q^m$ を用意し、次のように $\vec{b}$ を計算します。&lt;/p>
$$ \vec{b} = A\vec{s} + \vec{e} \pmod q $$
&lt;p>&lt;strong>Search LWE問題&lt;/strong>とは、「公開情報 $(A, \vec{b})$ から秘密情報 $\vec{s}$ を求めよ」という問題です。この誤差 $\vec{e}$ が存在することにより、ガウスの消去法などの代数的な解法を行おうとすると、方程式を足し引きする過程で誤差 $\vec{e}$ が雪だるま式に増幅されてしまい、最終的にランダムな値と区別がつかなくなり破綻してしまいます。&lt;/p>
&lt;p>LWE問題の凄さは、「格子上の最悪時計算量問題（Worst-case hardness）」であるGapSVP（判定最短ベクトル問題）やSIVP（最短独立ベクトル問題）を解くことができる量子アルゴリズムが存在しない限り、LWE問題も平均的（Average-case）に解くことができない、という強力な理論的証明（還元）が存在する点です。つまり、ランダムに生成された暗号鍵であっても、理論的な上限に裏打ちされた強固な安全性を持つことが保証されています。&lt;/p>
&lt;h3 id="42-ring-lwe-と-module-lwe-による劇的な効率化">4.2. Ring-LWE と Module-LWE による劇的な効率化
&lt;/h3>&lt;p>通常のLWE問題（Standard LWE）は安全性の根拠が非常に明確ですが、行列 $A$ のサイズが非常に大きくなり、鍵サイズがメガバイト級になるため実用的ではありません。そこで提案されたのが、多項式環（Polynomial Rings）を利用して代数的な構造を持たせるアプローチです。&lt;/p>
&lt;p>&lt;strong>Ring-LWE問題&lt;/strong>では、単なるベクトルや行列の代わりに、ある多項式環 $R_q$ の元（多項式）を使用します。NIST標準で一般的に用いられるのは以下のような円分多項式環です。&lt;/p>
$$ R_q = \mathbb{Z}_q[X]/(X^n + 1) $$
&lt;p>ここで、$n$ は 2 のべき乗（例：256）、$q$ は適当な素数です。この環の上で、要素 $a, s, e \in R_q$ を用いて $b = a \cdot s + e \pmod q$ を計算します。一つの多項式 $a$ が $n$ 個の係数を持つため、データを大幅に圧縮でき、さらに**NTT (Number Theoretic Transform: 数論変換)**という高速フーリエ変換（FFT）の有限体バージョンを用いることで、$O(n \log n)$ の計算量で超高速に多項式の乗算が可能になります。&lt;/p>
&lt;p>しかし、Ring-LWEには「環の特殊な代数構造に起因する未知の脆弱性が存在するかもしれない」という懸念がありました。また、セキュリティレベル（AES-128, 192, 256相当など）を変更する際に、多項式の次数 $n$ そのものを変える必要があり、それに伴いNTTのアルゴリズムなど実装全体を書き直さなければならないという工学的な課題がありました。&lt;/p>
&lt;p>そこで、標準化アルゴリズムであるKyberやDilithiumが採用したのが&lt;strong>Module-LWE (M-LWE) 問題&lt;/strong>です。Module-LWEは、無構造なStandard LWEと、構造を持ちすぎるRing-LWEのちょうど中間に位置する妥協案であり、多項式環 $R_q$ の要素を成分とする $k \times k$ の行列（モジュール）を使用します。&lt;/p>
$$ \vec{b} = A\vec{s} + \vec{e} \pmod{R_q} \quad (A \in R_q^{k \times k}, \vec{s}, \vec{e} \in R_q^k) $$
&lt;p>Module-LWEの最大の利点は、多項式の次数 $n$（NIST標準では $n=256$）を固定したまま、行列の次元 $k$ を変えるだけで容易にセキュリティレベルをスケールさせることができる点です。
例えばKyberの場合、以下のように次元 $k$ を調整します。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Kyber512 (Level 1)&lt;/strong>: $k = 2$ （AES-128相当）&lt;/li>
&lt;li>&lt;strong>Kyber768 (Level 3)&lt;/strong>: $k = 3$ （AES-192相当）&lt;/li>
&lt;li>&lt;strong>Kyber1024 (Level 5)&lt;/strong>: $k = 4$ （AES-256相当）&lt;/li>
&lt;/ul>
&lt;p>これにより、基盤となるNTTのコードや多項式演算のハードウェア回路を全セキュリティレベルで100%再利用することが可能になり、実装の安全性と効率性が劇的に向上しました。&lt;/p>
&lt;hr>
&lt;h2 id="5-crystals-kyber-ml-kem次世代の鍵カプセル化メカニズム">5. CRYSTALS-Kyber (ML-KEM)：次世代の鍵カプセル化メカニズム
&lt;/h2>&lt;p>&lt;strong>FIPS 203 (ML-KEM)&lt;/strong> として正式に標準化されたCRYSTALS-Kyberは、前述のModule-LWE問題に基づく鍵カプセル化メカニズム (KEM) です。今後、TLS 1.3やSSHなどでセッションキーを安全に共有するための事実上の世界標準となります。&lt;/p>
&lt;h3 id="51-kem-key-encapsulation-mechanism-のアーキテクチャ">5.1. KEM (Key Encapsulation Mechanism) のアーキテクチャ
&lt;/h3>&lt;p>PQCの時代では、RSAのように「クライアントが共通鍵を作ってサーバーの公開鍵で暗号化して送る」という直接的なアプローチではなく、KEMというカプセル化の枠組みが標準となります。&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant Client as "クライアント (Alice)"
participant Server as "サーバー (Bob)"
Note over Client: "ML-KEM KeyGen()"
Client->>Client: "秘密鍵 (sk) と公開鍵 (pk) を生成"
Client->>Server: "公開鍵 (pk) を送信"
Note over Server: "ML-KEM Encaps()"
Server->>Server: "ランダムな共通鍵 (K) を生成"
Server->>Server: "Kをpkでカプセル化し、暗号文 (c) を作成"
Server->>Client: "暗号文 (c) を送信"
Note over Client: "ML-KEM Decaps()"
Client->>Client: "秘密鍵 (sk) を使って暗号文 (c) を復号"
Client->>Client: "カプセル化を解除し、共通鍵 (K) を取り出す"
Note over Client, Server: "共有した共通鍵 (K) を用いてAESなどで暗号化通信を開始"&lt;/div>
&lt;h3 id="52-kyberの内部アルゴリズムの仕組みと藤崎岡本変換">5.2. Kyberの内部アルゴリズムの仕組みと藤崎・岡本変換
&lt;/h3>&lt;p>Kyberの設計は非常に洗練されています。まず、CPA（選択平文攻撃）に対してのみ安全な公開鍵暗号方式（Kyber.CPAPKE）を構築し、それに&lt;strong>藤崎・岡本変換 (Fujisaki-Okamoto Transform)&lt;/strong> と呼ばれる暗号学的に非常に強力な手法を適用することで、CCA（適応的選択暗号文攻撃）に対しても安全な完全なKEMへとアップグレードする設計を採用しています。&lt;/p>
&lt;p>CPAPKEの核となる暗号化と復号のメカニズムは以下の通りです。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>鍵生成 (Key Generation)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>ランダムなシード値から、NTTドメイン上の行列 $A \in R_q^{k \times k}$ を生成します。法 $q$ は $3329$ が使われます。&lt;/li>
&lt;li>中心二項分布（CBD）から、小さな係数を持つ秘密ベクトル $\vec{s}$ と誤差ベクトル $\vec{e}$ をサンプリングします。&lt;/li>
&lt;li>$\vec{t} = A\vec{s} + \vec{e}$ を計算します。公開鍵は $(A, \vec{t})$、秘密鍵は $\vec{s}$ です。（実際には $A$ はシード値として公開され、帯域を節約します）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>暗号化 (Encryption)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>共有したい32バイトのメッセージ（共通鍵の材料）$m$ を多項式にエンコードします。&lt;/li>
&lt;li>新たなランダムなベクトル $\vec{r}$ と、小さな誤差 $\vec{e_1}, e_2$ を生成します。&lt;/li>
&lt;li>$\vec{u} = A^T\vec{r} + \vec{e_1}$&lt;/li>
&lt;li>$v = \vec{t}^T\vec{r} + e_2 + \lfloor q/2 \rceil \cdot m$&lt;/li>
&lt;li>暗号文は $(\vec{u}, v)$ となります。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>復号 (Decryption)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>受信者は $v - \vec{s}^T\vec{u}$ を計算します。&lt;/li>
&lt;li>この式を数式展開すると以下のようになります。
$v - \vec{s}^T\vec{u} = (\vec{t}^T\vec{r} + e_2 + \lfloor q/2 \rceil \cdot m) - \vec{s}^T(A^T\vec{r} + \vec{e_1})$&lt;/li>
&lt;li>ここで $\vec{t} = A\vec{s} + \vec{e}$ を代入すると、主項である $\vec{s}^TA^T\vec{r}$ が相殺されます。&lt;/li>
&lt;li>残るのは $\lfloor q/2 \rceil \cdot m + (\vec{e}^T\vec{r} + e_2 - \vec{s}^T\vec{e_1})$ となります。&lt;/li>
&lt;li>括弧内の項は「小さな誤差同士の積や和」であるため、全体としても十分に小さな値（ノイズ）に留まります。したがって、各係数が $0$ に近いか $q/2$ に近いかを閾値判定することで、元のメッセージ $m$ のビット（0 または 1）を完全にエラーなく復元することができます。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Kyberの最大の強みは、その圧倒的な&lt;strong>処理速度&lt;/strong>と&lt;strong>適度な鍵サイズ&lt;/strong>です。Kyber768の場合、公開鍵サイズは1,184バイト、暗号文サイズは1,088バイトであり、RSA-3072（鍵サイズ約384バイト）などと比較すると大きいものの、現代のインターネット通信のMTU（Maximum Transmission Unit）内にパケット分割なしで収めることが可能であり、ネットワークのレイテンシにほとんど悪影響を与えません。&lt;/p>
&lt;hr>
&lt;h2 id="6-crystals-dilithium-ml-dsa格子ベースの汎用デジタル署名">6. CRYSTALS-Dilithium (ML-DSA)：格子ベースの汎用デジタル署名
&lt;/h2>&lt;p>デジタル署名の標準化においては、同じ格子暗号アプローチの中でも異なる設計思想を持つアルゴリズムが競い合いました。その中で汎用的なデジタル署名として&lt;strong>FIPS 204 (ML-DSA)&lt;/strong> に選定されたのが&lt;strong>CRYSTALS-Dilithium&lt;/strong>です。&lt;/p>
&lt;h3 id="61-fiat-shamir-with-aborts-パラダイム">6.1. Fiat-Shamir with Aborts パラダイム
&lt;/h3>&lt;p>DilithiumはKyberと同じくModule-LWE（およびModule-SIS問題）に基づくデジタル署名スキームです。設計のベースには「&lt;strong>Fiat-Shamir with Aborts (中途終了を伴うフィアット・シャミア変換)&lt;/strong>」という極めて重要なパラダイムが用いられています。&lt;/p>
&lt;p>Fiat-Shamir変換自体は、対話型のゼロ知識証明プロトコルを非対話型のデジタル署名へと変換するための標準的な手法です。証明者（署名者）がコミットメント $y$ を生成し、$w = Ay$ を計算してハッシュ関数に通すことで、ランダムなチャレンジ $c$ を得て、応答 $z = y + cs$ を計算します。&lt;/p>
&lt;p>しかし、格子暗号においてこれを単純に適用すると、応答 $z$ の分布が秘密鍵 $s$ の値に依存して歪んでしまい、多数の署名を観測した攻撃者に秘密鍵 $s$ の情報が少しずつ漏洩してしまうという致命的な問題（サイドチャネル的な数学的漏洩）がありました。&lt;/p>
&lt;p>Dilithiumの設計チーム（Lyubashevskyら）は、署名の計算結果 $z$ の係数が、あらかじめ設定された安全な閾値の範囲内に収まらなかった場合、その署名プロセス全体を破棄（Abort）して、新しい乱数 $y$ を使って最初から計算をやり直す「&lt;strong>棄却サンプリング (Rejection Sampling)&lt;/strong>」という手法を導入しました。&lt;/p>
&lt;p>これにより、最終的に出力される署名 $z$ は秘密鍵に一切依存しない完全な均一分布となり、情報漏洩を数学的に完全に防ぐことに成功しました。&lt;/p>
&lt;h3 id="62-dilithiumの利点と実装の容易さ">6.2. Dilithiumの利点と実装の容易さ
&lt;/h3>&lt;p>Dilithiumの設計上の大きな利点は、署名生成プロセスにおいて複雑な「ガウス分布からのサンプリング」や「浮動小数点演算」を&lt;strong>一切使用しない&lt;/strong>点です。一様分布からのサンプリングと、単純な整数モジュロ演算、NTT、およびハッシュ関数（SHAKE）のみで実装できるため、組み込みのマイクロコントローラからクラウドサーバーまで、幅広い環境で安全かつ定数時間（Constant-time）で実装することが容易です。これにより、タイミング攻撃などの物理的なサイドチャネル攻撃に対しても強固な耐性を持ちます。&lt;/p>
&lt;hr>
&lt;h2 id="7-falcon-fn-dsa究極にコンパクトな格子署名">7. FALCON (FN-DSA)：究極にコンパクトな格子署名
&lt;/h2>&lt;p>NISTは、Dilithiumとは異なる特性を持つもう一つの格子ベース署名として&lt;strong>FALCON (Fast-Fourier Lattice-based Compact Signatures over NTRU)&lt;/strong> を標準化候補として選定しました（現在FN-DSAとしてドラフト策定中）。&lt;/p>
&lt;h3 id="71-ntru格子とガウスサンプリング">7.1. NTRU格子とガウスサンプリング
&lt;/h3>&lt;p>FALCONの最大の特徴は、LWE問題ではなく、1996年から存在する歴史の古い&lt;strong>NTRU (N-th degree Truncated polynomial Ring Units) 格子&lt;/strong>を使用している点です。さらに、GPV (Gentry-Peikert-Vaikuntanathan) フレームワークに基づく「&lt;strong>ハッシュ・アンド・サイン (Hash-and-Sign)&lt;/strong>」パラダイムを採用しています。&lt;/p>
&lt;p>Hash-and-Signでは、メッセージのハッシュ値を空間内のターゲット点とし、その点に最も近い格子上の点（最近接ベクトル問題の近似解）を見つけることで署名とします。これには、秘密鍵である「質の良い短い基底」を用いて、離散ガウス分布に従って点をサンプリングする必要があります。&lt;/p>
&lt;p>FALCONは、この重い計算を「&lt;strong>高速フーリエ直交化 (Fast Fourier Orthogonalization: FFO)&lt;/strong>」という手法を用いて劇的に高速化しました。&lt;/p>
&lt;h3 id="72-falconのメリットとデメリット">7.2. FALCONのメリットとデメリット
&lt;/h3>&lt;p>FALCONの圧倒的なメリットは、その**署名サイズと公開鍵サイズが極めて小さい（コンパクトである）**ことです。Dilithium3の署名サイズが約3,309バイトであるのに対し、FALCON-512の署名サイズはわずか約666バイトです。公開鍵も897バイトと非常に小さく、通信帯域が極端に制限される環境や、IoTデバイス、特定のネットワークプロトコルにおいては救世主となります。&lt;/p>
&lt;p>しかし、重大なデメリットが存在します。署名生成時に複雑な**浮動小数点演算（64-bit IEEE 754）**を伴う離散ガウスサンプリングが必須となるため、タイミング漏洩を防ぐ定数時間実装（Constant-time implementation）が極めて難しく、コードも巨大化します。このため、FALCONは汎用的な利用（Dilithium）に対する、特定用途向けの強力な特化型アルゴリズムという位置付けになります。&lt;/p>
&lt;div class="mermaid">graph LR
A["デジタル署名の要件"] --> B{"最優先する制約は？"}
B -->|"実装のシンプルさ・汎用性・定数時間実装の容易さ"| C["Dilithium (ML-DSA)"]
B -->|"通信帯域の最小化・データサイズのコンパクトさ"| D["FALCON (FN-DSA)"]
C --> E["汎用的なTLS証明書、ソフトウェアの電子署名"]
D --> F["パケットサイズ制限が厳しいプロトコル、特殊環境"]&lt;/div>
&lt;hr>
&lt;h2 id="8-sphincs-slh-dsa最強の安全性を誇るハッシュベース署名">8. SPHINCS+ (SLH-DSA)：最強の安全性を誇るハッシュベース署名
&lt;/h2>&lt;p>格子暗号の安全性が、将来的に天才的な数学者のブレイクスルーによって破られるという最悪のシナリオ（万が一の事態）に備えて、NISTは格子暗号とは全く異なるアプローチを持つ標準として &lt;strong>FIPS 205 (SLH-DSA)&lt;/strong>、すなわち**SPHINCS+**を策定しました。&lt;/p>
&lt;p>SPHINCS+は&lt;strong>ハッシュベース署名&lt;/strong>に分類されます。その安全性の根拠は、「用いている暗号学的ハッシュ関数（SHA-2やSHAKE256など）が衝突耐性と一方向性を持つこと」という、ただ1点のみに依存しています。LWEや素因数分解のような、特定の代数構造を持つ数学的問題に依存していないため、将来的にどんな強力な量子アルゴリズムが登場しようとも、ハッシュ関数の出力長を単純に伸ばすだけで対抗できるという、極めて強固な安全性（最も保守的なセキュリティ）を誇ります。&lt;/p>
&lt;h3 id="81-wots-と-fors-によるステートレスアーキテクチャ">8.1. WOTS+ と FORS によるステートレスアーキテクチャ
&lt;/h3>&lt;p>ハッシュベース署名の歴史は古く、1970年代のLamport署名やWinternitzワンタイム署名（WOTS）に遡ります。これらは「1回だけ安全に署名できる」という使い捨ての鍵でした。これを複数回使えるようにするために、マークル木（Merkle Tree）を組み合わせて無数のワンタイム鍵を一つのルートハッシュで管理するXMSS（eXtended Merkle Signature Scheme）やLMSといったアルゴリズムが開発されました。&lt;/p>
&lt;p>しかし、XMSSやLMSには「&lt;strong>ステートフル（状態保持型）&lt;/strong>」であるという重大な欠点がありました。署名するたびに「何番目のワンタイム鍵を使用したか」というインデックス状態を不揮発性メモリに厳密に記録し続ける必要があり、もし仮想マシンのスナップショットの復元などで状態が巻き戻り、同じワンタイム鍵を二度使ってしまうと、秘密鍵が即座に漏洩してシステムが崩壊してしまいます。&lt;/p>
&lt;p>SPHINCS+は、この状態管理の煩わしさを解決した「&lt;strong>ステートレス（状態非保持型）&lt;/strong>」のハッシュベース署名です。
その中核技術は以下の組み合わせです。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>WOTS+ (Winternitz One-Time Signature Plus)&lt;/strong>: 基本的なワンタイム署名。&lt;/li>
&lt;li>&lt;strong>FORS (Forest of Random Subsets)&lt;/strong>: 少回数署名（Few-Time Signature）技術。同じ鍵を数回程度なら再利用しても安全性を保つ。&lt;/li>
&lt;li>&lt;strong>Hyper-Tree (巨大な木構造)&lt;/strong>: マークル木を多層的に重ね合わせた巨大な構造。&lt;/li>
&lt;/ol>
&lt;p>SPHINCS+では、署名を行う際、状態を管理する代わりに、Hyper-Treeの底辺にある膨大な数のFORS鍵の中から、擬似乱数を用いてランダムに一つを選び出して署名します。木の葉の数が天文学的に多いため、同じ鍵を偶然二度選んでしまう確率（コリジョン）が無視できるほど小さくなり、結果としてステートレスを実現しています。&lt;/p>
&lt;p>SPHINCS+の唯一にして最大の弱点は、&lt;strong>署名サイズが極めて大きい&lt;/strong>ことです。パラメータにもよりますが、署名サイズは17キロバイト〜49キロバイトにも達し、署名生成速度も格子暗号に比べて圧倒的に遅くなります。そのため、日常的なWebブラウジングでの利用よりも、ソフトウェアのアップデート署名やルート認証局（CA）の証明書など、頻繁に署名を行わず、かつ長期間の絶対的な安全性が強く求められる用途での利用が想定されています。&lt;/p>
&lt;hr>
&lt;h2 id="9-符号ベース暗号classic-mcelieceという古き良き巨人">9. 符号ベース暗号：Classic McElieceという古き良き巨人
&lt;/h2>&lt;p>NISTの標準化プロセスにおいて、Round 4の最終候補として現在も評価が継続されている重要なアプローチが、&lt;strong>符号ベース暗号&lt;/strong>の&lt;strong>Classic McEliece&lt;/strong>です。&lt;/p>
&lt;p>1978年にRobert McElieceによって提案されたこのアルゴリズムは、公開鍵暗号の歴史の中でもRSAと並んで最も古いものの一つです。「Goppa符号（ゴッパ符号）」と呼ばれる代数幾何符号を利用しており、メッセージに意図的にエラー（ノイズベクトル）を加えて暗号化し、秘密鍵としてGoppa符号のパリティ検査行列を持つ者だけが、強力な誤り訂正能力を使ってエラーを取り除き、元のメッセージを復号できる、という「&lt;strong>シンドローム復号問題 (Syndrome Decoding Problem)&lt;/strong>」に基づいています。&lt;/p>
$$ \vec{c} = \vec{m} G + \vec{e} $$
&lt;p>
（$G$ は公開鍵であるスクランブルされた生成行列、$\vec{e}$ は重み $t$ のエラーベクトル）&lt;/p>
&lt;p>Classic McElieceの驚くべき点は、&lt;strong>提案から40年以上経過し、世界中の暗号学者による激しい解読の研究に晒されてきたにもかかわらず、その本質的な脆弱性が一度も発見されていない&lt;/strong>という、圧倒的な実績にあります。PQCの中で最も「時間が証明した強固な安全性」を持っています。&lt;/p>
&lt;p>さらに、暗号文のサイズが非常に小さい（わずか100〜200バイト程度）というメリットがあります。しかし、&lt;strong>公開鍵のサイズがメガバイト（MB）単位になる&lt;/strong>という致命的な欠点が存在します。最も低いセキュリティレベル（AES-128相当）でも公開鍵が約250KB、高いレベルでは1MBを超えます。&lt;/p>
&lt;p>このため、TLSハンドシェイクのように通信のたびに公開鍵をネットワーク上で送信する用途には全く適用できません。しかし、VPNの事前共有鍵の交換、ファームウェアへの公開鍵のハードコード、あるいは衛星通信など、公開鍵をシステムに事前に配置できる特殊なユースケースにおいては、その強固な安全性から極めて有望な選択肢として検討が続けられています。&lt;/p>
&lt;hr>
&lt;h2 id="10-各pqcアルゴリズムの性能比較とトレードオフ">10. 各PQCアルゴリズムの性能比較とトレードオフ
&lt;/h2>&lt;p>ここまで解説してきた主要なアルゴリズムについて、一般的なセキュリティレベル（NIST Level 2〜3相当、AES-128〜192レベル）での性能特性を以下の表にまとめました。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">アルゴリズム (標準名)&lt;/th>
&lt;th style="text-align:left">カテゴリ&lt;/th>
&lt;th style="text-align:left">数学的基盤&lt;/th>
&lt;th style="text-align:left">公開鍵サイズ&lt;/th>
&lt;th style="text-align:left">秘密鍵サイズ&lt;/th>
&lt;th style="text-align:left">暗号文/署名サイズ&lt;/th>
&lt;th style="text-align:left">処理速度の傾向&lt;/th>
&lt;th style="text-align:left">主な特徴と用途&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Kyber768&lt;/strong>&lt;br>(ML-KEM)&lt;/td>
&lt;td style="text-align:left">KEM&lt;/td>
&lt;td style="text-align:left">Module-LWE&lt;/td>
&lt;td style="text-align:left">1,184 Bytes&lt;/td>
&lt;td style="text-align:left">2,400 Bytes&lt;/td>
&lt;td style="text-align:left">1,088 Bytes&lt;/td>
&lt;td style="text-align:left">非常に高速&lt;/td>
&lt;td style="text-align:left">鍵サイズ・速度のバランスが最高。TLS 1.3などの汎用KEM標準。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Dilithium3&lt;/strong>&lt;br>(ML-DSA)&lt;/td>
&lt;td style="text-align:left">署名&lt;/td>
&lt;td style="text-align:left">Module-LWE&lt;/td>
&lt;td style="text-align:left">1,952 Bytes&lt;/td>
&lt;td style="text-align:left">4,032 Bytes&lt;/td>
&lt;td style="text-align:left">3,309 Bytes&lt;/td>
&lt;td style="text-align:left">生成・検証ともに高速&lt;/td>
&lt;td style="text-align:left">実装がシンプル。汎用的なデジタル署名標準。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>FALCON-512&lt;/strong>&lt;br>(FN-DSA)&lt;/td>
&lt;td style="text-align:left">署名&lt;/td>
&lt;td style="text-align:left">NTRU格子&lt;/td>
&lt;td style="text-align:left">897 Bytes&lt;/td>
&lt;td style="text-align:left">1,281 Bytes&lt;/td>
&lt;td style="text-align:left">666 Bytes&lt;/td>
&lt;td style="text-align:left">署名生成は遅め、検証は超高速&lt;/td>
&lt;td style="text-align:left">署名サイズが極小。ただし浮動小数点演算が必要。組込・IoT向け。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>SPHINCS+&lt;/strong>&lt;br>(SLH-DSA)&lt;/td>
&lt;td style="text-align:left">署名&lt;/td>
&lt;td style="text-align:left">ハッシュ関数&lt;/td>
&lt;td style="text-align:left">32 Bytes&lt;/td>
&lt;td style="text-align:left">64 Bytes&lt;/td>
&lt;td style="text-align:left">約 17,000 Bytes&lt;/td>
&lt;td style="text-align:left">生成が非常に遅い&lt;/td>
&lt;td style="text-align:left">数学的な破綻リスクがほぼゼロ。ルート証明書など高セキュリティ用途。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Classic McEliece&lt;/strong>&lt;/td>
&lt;td style="text-align:left">KEM&lt;/td>
&lt;td style="text-align:left">Goppa符号&lt;/td>
&lt;td style="text-align:left">&lt;strong>約 1.04 MB&lt;/strong>&lt;/td>
&lt;td style="text-align:left">13,568 Bytes&lt;/td>
&lt;td style="text-align:left">&lt;strong>188 Bytes&lt;/strong>&lt;/td>
&lt;td style="text-align:left">カプセル化は高速&lt;/td>
&lt;td style="text-align:left">40年の安全性実績。公開鍵が巨大。ハードコード可能な環境向け。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="トレードオフの理解">トレードオフの理解
&lt;/h3>&lt;p>PQCの世界においては、「サイズが小さく、速度が速く、数学的保証も完璧」という魔法のような単一のアルゴリズムは存在しません。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>インターネットの標準 (Kyber / Dilithium)&lt;/strong>: パフォーマンスのバランスが最も良く、現在のRSA/ECCからのドロップイン・リプレイスメント（そのまま置き換えること）に最も適しています。&lt;/li>
&lt;li>&lt;strong>究極の保守性 (SPHINCS+)&lt;/strong>: データサイズや処理速度を犠牲にしてでも、将来的な数学的ブレイクスルーに対する絶対的な保険が欲しい場合に選択されます。&lt;/li>
&lt;li>&lt;strong>特殊環境向け (FALCON / Classic McEliece)&lt;/strong>: 通信帯域が極端に狭い、あるいは事前配布が可能など、環境の制約に合わせて選ばれる特化型の武器です。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="11-実用化に向けた課題とハイブリッド暗号という現実解">11. 実用化に向けた課題と「ハイブリッド暗号」という現実解
&lt;/h2>&lt;p>NISTによる標準化が完了し、FIPS規格が正式に発行されたことで、世界中のITインフラのPQC移行（&lt;strong>PQCマイグレーション&lt;/strong>）が本格的にスタートしました。GoogleのChromeブラウザ、AppleのiMessage（PQ3プロトコル）、Cloudflareなどのネットワークプロバイダは、すでにPQCのサポートをプロトコルに実装し、実運用を開始しています。&lt;/p>
&lt;p>しかし、新しい暗号アルゴリズムにいきなり完全に切り替えることには非常に高いリスクが伴います。仮にKyberなどの格子暗号に対して、数年後にある天才数学者が致命的な攻撃手法（古典コンピュータでも解けるような数学的欠陥）を発見した場合、それに依存しているシステム全体が一瞬にして丸裸になってしまいます。&lt;/p>
&lt;p>この不確実性リスクを軽減するための現実的かつ推奨されるアプローチが「&lt;strong>ハイブリッド暗号 (Hybrid Cryptography)&lt;/strong>」です。&lt;/p>
&lt;p>ハイブリッド暗号では、長年の実績がある現行の古典的暗号（例：X25519などの楕円曲線暗号）と、新しいPQC（例：Kyber768）の両方を同時に使用して鍵交換を行います。それぞれのアルゴリズムで個別に共通鍵成分を生成し、最後に安全な鍵導出関数（KDF）を用いて二つの成分を混ぜ合わせ、最終的なマスターシークレットを生成します。&lt;/p>
&lt;div class="mermaid">graph TD
A["クライアント"] -->|① X25519の公開鍵 + Kyberの公開鍵 を送信| B["サーバー"]
B -->|② X25519の共有鍵 + Kyberのカプセル化暗号文 を返信| A
A --> C{"マスターシークレットの導出 (KDF)"}
B --> C
C -->|入力: (X25519の共通鍵) || (Kyberの共通鍵)| D["セキュアな通信鍵 (AES-256 / ChaCha20)"]
D -->|"量子の脅威 ＆ 古典的な脆弱性 の両方に耐性"| E["安全なハイブリッド暗号通信 (TLS 1.3)"]&lt;/div>
&lt;p>これにより、「万が一量子コンピュータが実現してECCが破られてもKyberが通信を守り」、逆に「万が一Kyberに未知の数学的欠陥が見つかってもECCが通信を守る」という、堅牢な二段構えの安全性を実現できます。代表的な例として、IETFで標準化が進められている &lt;strong>X25519MLKEM768 (旧 X25519Kyber768)&lt;/strong> ドラフトがあり、現在のWebブラウザと最先端のサーバー間の通信は、まさにこのハイブリッド方式を用いて行われています。&lt;/p>
&lt;p>また、システム設計において「特定の暗号アルゴリズムに過度に依存せず、アルゴリズムが破綻した際に迅速に別のアルゴリズム（例：KyberからMcElieceへ、DilithiumからSPHINCS+へ）に切り替えられるアーキテクチャ」を構築する &lt;strong>Crypto Agility（暗号の俊敏性）&lt;/strong> の概念が、今後のシステム開発において必須の要件となります。&lt;/p>
&lt;hr>
&lt;h2 id="12-まとめ暗号技術の新たな地平線">12. まとめ：暗号技術の新たな地平線
&lt;/h2>&lt;p>量子コンピュータという人類の夢のテクノロジーは、皮肉にも私たちが長年信頼を寄せてきた「素因数分解」や「離散対数問題」という数学的防壁を打ち破る最大の脅威となりました。しかし、世界中の暗号学者たちはそれに屈することなく、格子理論、ハッシュ関数木、誤り訂正符号といったより複雑で深遠な多次元の数学的領域を開拓し、耐量子計算機暗号（PQC）という新たな防壁を築き上げました。&lt;/p>
&lt;p>NISTによるFIPS 203 (ML-KEM)、FIPS 204 (ML-DSA)、FIPS 205 (SLH-DSA) の標準化完了は、ゴールではありません。これから数十年続くPQCマイグレーションという壮大な旅の第一歩に過ぎません。ソフトウェアエンジニアやシステムアーキテクトにとって、これらの新アルゴリズムがもたらす「鍵サイズの増大」や「計算コストの変化」をネットワークプロトコルやシステムにどのように最適に適応させていくかが、これからの大きな技術的課題となるでしょう。&lt;/p>
&lt;p>量子コンピュータと暗号の戦いは、人類の数学的探求とテクノロジーの進化が最も激しく交差するエキサイティングな領域です。本記事を通じて、PQCの背後にある美しい数学の理論と、サイバーセキュリティの未来を形作る各アルゴリズムの驚くべきメカニズムについて、深く理解していただけたなら幸いです。&lt;/p>
&lt;hr>
&lt;p>&lt;em>References:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;em>NIST Post-Quantum Cryptography Standardization Program&lt;/em>&lt;/li>
&lt;li>&lt;em>FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard&lt;/em>&lt;/li>
&lt;li>&lt;em>FIPS 204: Module-Lattice-Based Digital Signature Standard&lt;/em>&lt;/li>
&lt;li>&lt;em>FIPS 205: Stateless Hash-Based Digital Signature Standard&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>量子コンピュータが実用化される日：2026年の現在地</title><link>http://kenji.blog/p/quantum-computing-2026-current-status/</link><pubDate>Fri, 11 Sep 2026 06:00:00 +0900</pubDate><guid>http://kenji.blog/p/quantum-computing-2026-current-status/</guid><description>&lt;img src="http://kenji.blog/p/quantum-computing-2026-current-status/img/eyecatch.jpg" alt="Featured image of post 量子コンピュータが実用化される日：2026年の現在地" />&lt;h2 id="1-はじめに2026年量子コンピュータはどこまで来たのか">1. はじめに：2026年、量子コンピュータはどこまで来たのか
&lt;/h2>&lt;p>2026年現在、量子コンピューティングはかつての「理論上の夢」から「工学的な現実」へと決定的なシフトを遂げました。数年前まで主流であった**NISQ（Noisy Intermediate-Scale Quantum）**デバイスの限界が明確になるにつれ、世界中の研究機関とテックジャイアントは「誤り耐性量子計算（FTQC: Fault-Tolerant Quantum Computing）」の実現へと舵を切りました。&lt;/p>
&lt;p>この記事では、2026年の最新のブレイクスルーを交えながら、量子コンピュータの現在地を深く掘り下げます。特に、量子エラー訂正（表面符号）、物理量子ビットと論理量子ビットの違い、トポロジカル量子計算の進展、そして超伝導・イオントラップ方式の最前線について詳解します。&lt;/p>
&lt;hr>
&lt;h2 id="2-量子状態の基礎とフィデリティ忠実度">2. 量子状態の基礎とフィデリティ（忠実度）
&lt;/h2>&lt;p>量子コンピュータの基本単位である量子ビット（Qubit）は、古典的なビット（0または1）とは異なり、0と1の重ね合わせ状態（Superposition）をとることができます。単一の量子ビットの状態は、ヒルベルト空間上のベクトルとして次のように表されます。&lt;/p>
$$
|\psi\rangle = \alpha|0\rangle + \beta|1\rangle
$$
&lt;p>ここで、$\alpha$ と $\beta$ は複素数の確率振幅であり、以下の規格化条件を満たします。&lt;/p>
$$
|\alpha|^2 + |\beta|^2 = 1
$$
&lt;p>量子計算の性能を測る上で極めて重要な指標が**フィデリティ（Fidelity）**です。理想的な量子状態 $|\psi\rangle$ と、ノイズによって劣化し混合状態となった実際の密度行列 $\rho$ との間のフィデリティ $F$ は次のように定義されます。&lt;/p>
$$
F(\rho, |\psi\rangle) = \langle \psi | \rho | \psi \rangle
$$
&lt;p>2026年の現在、2量子ビットゲート（例：CNOTゲートやCZゲート）のフィデリティは、超伝導方式で**99.99%**の壁（いわゆる「4ナイン」）を安定して超えるようになりました。これは表面符号によるエラー訂正の閾値（約99%）を大幅に上回る数値であり、実用化に向けた最大のブレイクスルーの一つです。&lt;/p>
&lt;hr>
&lt;h2 id="3-nisq時代の限界とftqcへのパラダイムシフト">3. NISQ時代の限界とFTQCへのパラダイムシフト
&lt;/h2>&lt;p>2010年代後半から2020年代前半にかけては、エラー訂正を持たない数十〜数百量子ビットのデバイスであるNISQ（Noisy Intermediate-Scale Quantum）の時代でした。しかし、NISQには明確な限界がありました。&lt;/p>
&lt;p>回路の深さ（Depth）が増すにつれてエラーが指数関数的に蓄積し、意味のある計算結果を得ることが不可能になります。回路の深さ $D$ における全体の成功確率 $P_{success}$ は、単一ゲートのフィデリティ $f$ とゲート数 $N$ に対して次のように減衰します。&lt;/p>
$$
P_{success} \approx f^N
$$
&lt;p>もし $f = 0.99$ で1000個のゲートを適用した場合、$0.99^{1000} \approx 4.3 \times 10^{-5}$ となり、結果はほぼランダムノイズに埋もれてしまいます。このため、2026年においては、NISQアルゴリズム（VQEやQAOAなど）の直接的なスケールアップよりも、**論理量子ビット（Logical Qubit）**の生成にリソースが集中しています。&lt;/p>
&lt;hr>
&lt;h2 id="4-量子エラー訂正と論理量子ビット表面符号の最前線">4. 量子エラー訂正と論理量子ビット：表面符号の最前線
&lt;/h2>&lt;p>量子エラー訂正（QEC: Quantum Error Correction）は、複数の「物理量子ビット」をエンコードして1つの「論理量子ビット」を作り出し、エラーを検知・訂正する技術です。現在最も有望視されているのが**表面符号（Surface Code）**です。&lt;/p>
&lt;h3 id="41-表面符号surface-codeの構造">4.1 表面符号（Surface Code）の構造
&lt;/h3>&lt;p>表面符号では、量子ビットを2次元の格子状に配置します。データ量子ビット（実際の情報を保持）と測定量子ビット（シンドローム測定用）が市松模様のように並びます。&lt;/p>
&lt;div class="mermaid">graph TD
A["Data Qubit (D1)"] --- B["Measure Qubit (M1)"]
B --- C["Data Qubit (D2)"]
C --- D["Measure Qubit (M2)"]
D --- E["Data Qubit (D3)"]
B --- F["Data Qubit (D4)"]
D --- G["Data Qubit (D5)"]
style A fill:#e1f5fe,stroke:#039be5
style C fill:#e1f5fe,stroke:#039be5
style E fill:#e1f5fe,stroke:#039be5
style F fill:#e1f5fe,stroke:#039be5
style G fill:#e1f5fe,stroke:#039be5
style B fill:#fff3e0,stroke:#fb8c00
style D fill:#fff3e0,stroke:#fb8c00&lt;/div>
&lt;p>スタビライザー演算子 $S_x$ と $S_z$ を用いて、ビット反転（Xエラー）と位相反転（Zエラー）を絶えず監視します。&lt;/p>
$$
S_x = \prod_{i \in \text{star}} X_i, \quad S_z = \prod_{j \in \text{plaquette}} Z_j
$$
&lt;p>2026年の重大な進展は、「ブレイクイーブン点（Break-even point）」を完全に突破したことです。すなわち、エラー訂正を行うための余分な回路が引き起こすノイズよりも、エラー訂正によって取り除かれるノイズの方が大きくなり、論理量子ビットの寿命が物理量子ビットの寿命を何桁も上回るようになりました。&lt;/p>
&lt;h3 id="42-量子エラー訂正のサイクル">4.2 量子エラー訂正のサイクル
&lt;/h3>&lt;p>エラー訂正は継続的なフィードバックループとして機能します。&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant D as "Data Qubits"
participant M as "Ancilla/Measure Qubits"
participant C as "Classical Controller"
loop "Syndrome Extraction Cycle (approx 1 microsec)"
D->>M: "Entangle (CNOT/CZ)"
M->>C: "Measure State (Syndrome)"
C->>C: "Decode Syndrome (e.g. Minimum Weight Perfect Matching)"
C-->>D: "Apply Pauli Correction (if necessary)"
end&lt;/div>
&lt;p>現在では、この古典的なデコード処理（シンドロームの解析）をFPGAや専用ASICでナノ秒単位で実行する技術が確立され、リアルタイムでのエラー訂正が実用段階に入っています。&lt;/p>
&lt;hr>
&lt;h2 id="5-ハードウェアアーキテクチャの進化2026年版">5. ハードウェアアーキテクチャの進化（2026年版）
&lt;/h2>&lt;p>2026年における量子ハードウェアは、主に「超伝導方式」「イオントラップ方式」「トポロジカル方式」の3つの軸で進化しています。&lt;/p>
&lt;h3 id="51-超伝導量子ビットの集積化">5.1 超伝導量子ビットの集積化
&lt;/h3>&lt;p>超伝導方式は、IBMやGoogleがリードする分野であり、ジョセフソン接合を用いたトランズモン（Transmon）量子ビットが主流です。2026年には、単一チップ上に数千から一万の物理量子ビットを集積するメガチップが実現しました。&lt;/p>
&lt;p>特筆すべきは、**モジュール間量子通信（Quantum Interconnects）**の確立です。マイクロ波光子を用いたチップ間の量子テレポーテーションが商用レベルで実装され、単一の希釈冷凍機のサイズ制限を回避できるようになりました。&lt;/p>
&lt;h3 id="52-イオントラップの2次元スケーリングと光インターコネクト">5.2 イオントラップの2次元スケーリングと光インターコネクト
&lt;/h3>&lt;p>イオントラップ方式（QuantinuumやIonQなどが主導）では、真空中に浮遊するイオンの内部エネルギー状態を量子ビットとして用います。超伝導方式と比較して、T1/T2コヒーレンス時間が極めて長く、全結合（All-to-All Connectivity）が可能という利点があります。&lt;/p>
&lt;p>2026年のブレイクスルーは、QCCD（Quantum Charge Coupled Device）アーキテクチャの2次元化と、フォトニックインターコネクトを用いたマルチトラップ間の高速エンタングルメント生成です。これにより、イオントラップの弱点であった「ゲート速度の遅さ」と「スケーラビリティ」が劇的に改善されました。&lt;/p>
&lt;h3 id="53-トポロジカル量子計算エニオンの制御">5.3 トポロジカル量子計算：エニオンの制御
&lt;/h3>&lt;p>長らく理論上の存在とされてきた&lt;strong>トポロジカル量子計算&lt;/strong>が、2026年に遂に実験的な実証のフェーズに入りました。Microsoftなどが推し進めるこの方式は、「マヨラナゼロモード（Majorana Zero Modes）」と呼ばれる非可換エニオン（Non-Abelian Anyons）を用います。&lt;/p>
&lt;p>エニオンの粒子の位置を入れ替える「ブレイディング（Braiding）」という操作によって量子ゲートを実行します。&lt;/p>
$$
|\psi_{final}\rangle = B_{ij} |\psi_{initial}\rangle
$$
&lt;p>ここで $B_{ij}$ はブレイディング演算子です。トポロジカル方式は、情報の保存が粒子の局所的な状態ではなく、全体の「結び目」のトポロジーに依存するため、環境ノイズに対して本質的に耐性（ハードウェアレベルでのエラー耐性）を持っています。2026年に世界で初めて高忠実度なトポロジカル論理量子ビットの生成が確認され、FTQCへの強力なショートカットとして注目を集めています。&lt;/p>
&lt;hr>
&lt;h2 id="6-実用化に向けたロードマップと展望">6. 実用化に向けたロードマップと展望
&lt;/h2>&lt;p>量子コンピュータが「化学計算」「材料科学」「金融モデリング」などで古典コンピュータ（スーパーコンピュータ）を圧倒する**量子超越性（Quantum Advantage）**を真に発揮するためには、数千の論理量子ビットが必要です。&lt;/p>
&lt;div class="mermaid">gantt
title "Quantum Computing Roadmap (Revised 2026)"
dateFormat YYYY
axisFormat %Y
section "NISQ Era"
"Noisy Qubits (&lt;1000)" :done, 2018, 2024
section "Early FTQC"
"Break-even Point Demonstration" :done, 2024, 2026
"Hundreds of Logical Qubits" :active, 2026, 2028
section "Full-Scale FTQC"
"1000+ Logical Qubits (Commercial App)" : 2028, 2030
"Universal Fault-Tolerant Quantum Computer" : 2030, 2035&lt;/div>
&lt;h3 id="61-現在直面している課題と未来">6.1 現在直面している課題と未来
&lt;/h3>&lt;p>2026年時点での最大の課題は、極低温を維持するための巨大なクライオスタット（希釈冷凍機）の冷却能力と、室温の制御装置と極低温の量子チップを繋ぐ配線（I/Oボトルネック）です。これに対して、極低温環境で動作するCMOS（Cryo-CMOS）コントローラチップの開発が急ピッチで進んでいます。&lt;/p>
&lt;h3 id="結論">結論
&lt;/h3>&lt;p>2026年は、量子コンピュータの歴史において「論理量子ビットのスケールアップ元年」として記録されるでしょう。エラー訂正アルゴリズムの実証、ハードウェアのモジュール化、そしてトポロジカルアプローチの急進展により、「実用化される日」はもはや遠い未来の話ではなく、今後数年以内の具体的なマイルストーンとして見据えることができるようになりました。量子アルゴリズムの開発者や企業にとって、今こそ量子ネイティブな問題解決へと本格的に投資すべきタイミングと言えます。&lt;/p>
&lt;hr>
&lt;p>&lt;em>この記事は2026年時点の最新の量子コンピューティング研究論文および業界動向に基づいて執筆されています。&lt;/em>&lt;/p></description></item></channel></rss>