「
ある数 x が素数 p を法として平方数であるかないか。言い換えれば、x と p が与えられたとして
y2 ≡ x (mod p) …… ①
を満たす y が存在するかどうか。
この問題は理論的に重要であるだけなく、応用上も、ソロベイ・ストラッセンの素数性判定に結びつく。
さらに楕円曲線暗号においても、ℤp上の楕円曲線
y2 ≡ (x の3次式) (mod p)
の点を求めるには、右辺が平方数であるかどうかという問題に帰着する。
いま定数pを3以上の素数とする。以下、すべてpを法として考える。 (まわりくどい言葉遣いはしたくないので、整数とそれが属する剰余類を同一視する。) ある整数xが与えられたとして、xが平方数であるかどうか知りたいとする。 言い換えれば、①を満たすyがあるかどうか知りたいとする。 pが小さければ、yに1, 2, 3, ... を代入して≡x になるケースがあるかどうか片っ端から調べるのでもいいが、 我々のpは何十桁あるいは100桁以上もあるような巨大素数なので、そんな悠長なことはやっていられない。 実際に解を求める泥臭い計算をせずに、透明に、ただ解があるかないかという、それだけを知りたい。
さて、フェルマーの小定理の合同式の肩の数字を半分にした形:
x(p-1)/2 ……②
は、≡1 または ≡-1 となる。
フェルマーの小定理より (p-1) 乗すれば≡1は分かっているので、その平方根にあたる②は±1になる。
法が素数のとき、2乗して1になる数は±1に限られるからだ。
(ただし、x は p の倍数ではないとする。
x が p の倍数なら x≡0だから、②は、もちろん0になるが、このような場合には興味がない。
なぜなら我々の p は実際には巨大な素数であって、そのさらに倍数であるような x など興味なく、
p 未満の数だけで世界は完成するからだ。もちろん p 未満には p の倍数はない。)
もし② が ≡ 1 なら、① を満たす y が存在する。②が ≡ -1 なら①を満たす y は存在しない。 平方数かどうか?を判定するオイラーの規準だ。
①を成り立たせるyが存在したとしよう。
つまり、x ≡ y2 と書けたとする。
これを使って②を書き直すと、
(y2)(p-1)/2 ≡ yp-1
となるが、フェルマーの小定理からこれは≡1だ。
逆に、②が≡1になったとしよう。
このとき、適当に選んだ原始根 b を使って x ≡ bα と書いたとすると、②は
(bα)(p-1)/2 ≡ b(α(p-1)/2)
となるが、これが≡1になると仮定している以上、指数 (α(p-1)/2) は (p-1) の倍数でなければならない。
b は原始根で、(p-1)乗して初めて≡1になるからだ。
もし指数 (α(p-1)/2) が (p-1) の倍数でなければ、
(α(p-1)/2)を (p-1)で割った0でない余りをβとでも書くとして、
bβが≡1になってしまう。しかもβはp-1 より小さい。
これでは b として原始根を選んだ、という仮定と矛盾してしまう。
指数 (α(p-1)/2) が (p-1) の倍数であるということは、要するにαが2の倍数(偶数)であるというだけの話だ。
もし α が奇数だったら、上記の指数は (p-1)の1.5倍または2.5倍または3.5倍……というふうに0.5の端数がついてしまって、
きっかり倍数にならないからだ。よって、α/2 は整数であり、
b(α/2)
は意味を持つ —— それは「二乗すると≡bαになる整数」であるが、
x ≡ bα なのだから、結局、二乗すると≡x になる数が存在したことになって、
①は解を持つ。
まとめると、①が成り立つなら②は≡1となるし、逆に②が≡1となるなら①は成り立つ。 ①が成立する・しないは、②が≡1になる・ならないと連動している。 ところで、 ②は≡1か≡-1かのどっちかになるのだから、 ①が成立しない場合 —— 言い換えれば②が≡1にならない場合 —— には、②は≡-1にならざるを得ない。 そんなわけで、①に解があるかどうかを知るには②を計算して結果が+1になるか-1になるかを見れば良いのである。
素数9973について。
850MHz程度のクロックのPC上でJavaScriptを使った場合、法pが100万くらい(つまり7桁くらい)までは、片っ端から可能性を試すことで、 平方数であるかどうか簡単に判定できる。しかしpが8桁、9桁……となると1桁増えるごとに計算量が10倍になるので、力ずくでは無理がある。 オイラーの規準の出番だ。次のリストでは、8桁の素数 12345643 を法として、y2 ≡ 7 が整数解を持たないことを示す。
// 強引な方法 // あれば平方根を返し、平方根がなければ0を返す function QuadraticResidue( x , p ) { for( var y=1; y < p/2; y++ ) { if( y*y % p === x ) { return y; } } return 0; } // オイラーによる判定法 // 平方剰余ならtrue、そうでなければfalseを返す Bigint.prototype.isQuadraticResidue = function( modulus ) { var oModulus = Bigint.serio( modulus ); var oResult = Bigint.powmod( this, oModulus.minus(1).half(), oModulus ); return ( oResult.isEqualTo( 1 ) ); } ver p = 12345643; ver x = 7; _Debug( "Brute" , QuadraticResidue( x , p ) ); _Debug( "Euler" , Bigint.Number( x ).isQuadraticResidue( p ) );
この例の場合、 オイラーの規準を使った方法では、0.1秒ほどで判定できる。 一方、強引な方法の場合、p2 が約47ビットの長さなので JavaScriptの組み込み整数型(53ビットまで)でまかなえるものの、 それでも手元の環境では10秒以上かかった。 8桁でこれなのだから、我々が問題にする数十桁以上の素数では話にならない。
最初の例で、素数9973を法とするとき、3は平方剰余で5は平方非剰余であることを示した。 このことを「対数的」に考えてみよう。すなわち……
まず原始根をひとつ求める。フェルマーの小定理によって、9973-1=9972乗すれば必ず≡1になるが、
原始根であるためには、それ以前に≡1になってはいけない。9972を素因数分解すると
9972 = 22 × 32 × 277
であるから、(9972を2、3、277で割って)4986乗、3324乗、36乗のいずれでも≡1にならなければ、9972乗より手前で1になることはないだろう。
そのような数を探すと11が見つかる。
11(9972/2) ≡ -1 (mod 9973)
11(9972/3) ≡ 1569 (mod 9973)
11(9972/277) ≡ 6437 (mod 9973)
次に、平方剰余である3が、11の何乗として表せるか考えてみる。
11x ≡ 3 (mod 9973)
この方程式の計算は、離散対数と呼ばれるものの一種だ。
実数上の普通の指数・対数関数と違って増え方が一様でなく、xが1ずつ増加するにつれて、(剰余類の代表として0~9972をとった場合)大きくなったり小さくなったり、予想が難しい挙動をする。計算も面倒くさい。
11が原始根である以上、xに1, 2, 3,...を代入すればどこかで≡3になるのは確かなのだが。
ともあれ、この例では、119300 という約9700桁のばかでかい数が ≡ 3 となる。
つまり、
log113 ≡ 9300 (mod 9973)
だ。この数が平方数であることは、具体的に、
119300/2
の二乗が≡119300であることから明らかだ。
119300/2≡3291の平方が3と合同になることはすでに見た。
同様に、平方非剰余である5が、11の何乗として表せるか考えてみる。
11x ≡ 5
答は x=3093 だ。
つまり、
log115 = 3093
もしこの対数値が偶数であったなら、その半分をべきとする数の2乗が≡5になれたはずである。
それは5が平方数でない(平方非剰余)という事実と矛盾する。
つまりこの場合、対数値は奇数でなければならない。
平方剰余か非剰余か? という区別は、離散対数をとった場合に偶数か奇数か?という区別と同じことだ。 離散対数は計算が大変なので、この方法は平方剰余・非剰余の判定法として実用的ではない。
余談だが、 11を底とする離散対数の表を作るには、a=11, b≡a*11≡112, c≡b*11≡113, ... のように次々と底を掛け算しながら先に進んでゆけば良く、 アルゴリズムは単純だが、例えば法が10桁あるとすると見出しが10億以上あるテーブルを作らなければならない。 時間的計算量のみならず、空間的計算量もばくだいだ。 法が数十桁以上の場合、こんなふうに離散対数でマスクされると、単純に逆変換表を作ってクラックするのは困難だ。