モジュラーべき乗とは、法を用いて行うべき乗演算のことです。これはコンピュータ科学、特に公開鍵暗号の分野で有用であり、ディフィー・ヘルマン鍵交換とRSA公開鍵/秘密鍵の両方で使用されています。
モジュラーべき乗とは、整数b (底) をe乗(指数) し、正の整数m (法)で割ったときの余りcのことです。つまり、 c = b e mod mです。除算の定義から、 0 ≤ c < mが成り立ちます。
例えば、b = 5、e = 3、m = 13の場合、5 ÷ 3 = 125を13で割ると、余りはc = 8になります。
bとmが互いに素である場合、 bの法mにおける乗法逆元dを求めることで(例えば拡張ユークリッド互除法を用いるなど)、指数eを負にすることもできる。より正確には:
モジュラーべき乗は、非常に大きな整数に対しても効率的に計算できます。一方、モジュラー離散対数、つまりb、c、mが与えられたときに指数eを求めることは難しいと考えられています。このような一方向関数的な性質から、モジュラーべき乗は暗号アルゴリズムへの応用候補として注目されています。
モジュラー指数を計算する最も直接的な方法は、 b e を直接計算し、次にこの数をmで割った余りを取ることです。b = 4 、 e = 13、m = 497の場合にcを計算してみましょう。
電卓を使って 4 ÷ 13を計算すると、67,108,864 になります。この値を 497 で割った余りを求めると、答えcは 445 となります。
bは1 桁、eは 2 桁ですが、 b e の値は8 桁になります。
強力な暗号化では、bは多くの場合、少なくとも 1024ビットです。[ 1 ] b = 5 × 10 76およびe = 17 を考えてみましょう。どちらも完全に妥当な値です。この例では、bは 77 桁、eは 2 桁ですが、値b eは 1,304 桁になります。このような計算は現代のコンピュータで可能ですが、このような数値の大きさが計算速度を大幅に低下させます。セキュリティを向上させるためにbとeをさらに大きくすると、値b eは扱いにくくなります。
べき乗演算の実行に必要な時間は、動作環境とプロセッサによって異なります。上記の方法では、完了までにΘ ( e )回の乗算が必要です。
数値を小さく保つには追加のモジュラ削減操作が必要になりますが、サイズが小さくなることで各操作が高速化され、全体として時間(およびメモリ)を節約できます。
このアルゴリズムは恒等式を利用します
修正されたアルゴリズムは以下のとおりです。
ループの各反復の最後に、式c ≡ b e′ (mod m )が成り立つことに注意してください。アルゴリズムは、ループがe回実行されると終了します。その時点で、c にはb e mod mの結果が格納されます。
要約すると、このアルゴリズムは、 e′ がeと等しくなるまでe′ を1 ずつ増やします。各ステップで、前の反復の結果cにbを掛け、その結果の積に対して剰余演算を実行することで、結果として得られるc を小さな整数に保ちます。
b = 4、e = 13、m = 497の例を再度示します。このアルゴリズムは、反復処理を 13 回実行します。
したがって、 cの最終的な答えは、直接法と同様に 445 です。
最初の方法と同様に、この方法でも計算にはO( e )回の乗算が必要です。ただし、これらの計算で使用される数値は最初のアルゴリズムの計算で使用される数値よりもはるかに小さいため、この方法では計算時間が少なくともO( e )倍短縮されます。
擬似コードでは、このメソッドは次のように実行できます。
関数module_pow(base, exponent, modulus)は、 modulus = 1の場合、 0を返します。 c := 1 e_prime = 0からexponent-1まで 、c := (c * base) mod modulus を 実行し、 cを返す。
3つ目の方法は、モジュラべき乗を実行するための演算回数を大幅に削減しつつ、前述の方法と同じメモリ使用量を維持します。これは、前述の方法と、二乗によるべき乗(バイナリべき乗とも呼ばれる)と呼ばれるより一般的な原理を組み合わせたものです。
まず、指数eを2進数表記に変換する必要があります。つまり、eは次のように表すことができます。
このような表記では、eの長さはnビットです。a iは、0 ≤ i < nを満たす任意のiに対して、0 または 1 の値を取ることができます。定義により、a n − 1 = 1 です。
すると、 b e の値は次のように表すことができます。
したがって、解cは次のようになります。
以下は、 Bruce Schneier著『Applied Cryptography』に基づく擬似コードの例です。[ 2 ] 入力base、exponent、modulus は、上記の式におけるb、e、mに対応します。
関数module_pow(base, exponent, modulus)は、 modulus = 1の場合、 0 を 返します。Assert :: (modulus - 1) * (modulus - 1) は base をオーバーフローしません。 結果 := 1 base := base mod modulus while exponent > 0 do if (exponent mod 2 == 1) then result := (result * base) mod modulus 指数 := 指数 >> 1 base := (base * base) mod modulus return result
ループに初めて入ったとき、コード変数baseはbと等しくなります。しかし、コードの 3 行目で繰り返し二乗することで、ループが完了するたびに変数baseはb 2 i mod mと等しくなります。ここでiはループが繰り返された回数です。(これにより、 i はバイナリ指数exponentの次の有効ビットになります。ここで最下位ビットは指数0です。)
最初のコード行は単純に乗算を実行します。aがゼロの場合、実行中の合計が実質的に1倍されるため、コードは実行されません。一方、 aが1の場合、変数base (元の基数のb 2 i mod mの値を含む)が単純に乗算されます。
この例では、基数bを指数e = 13で累乗します。指数はバイナリで 1101 です。バイナリの桁は 4 個あるため、ループは 4 回実行され、値はa 0 = 1、a 1 = 0、a 2 = 1、a 3 = 1となります。
まず、結果を初期化します。1 に戻し、変数xにbの値を保存します。
完了しました: Rは現在。
以下は上記の計算であり、b = 4 のe = 13乗を 497 で割った余りを計算しています。
初期化:
完了しました: Rは現在これは、以前のアルゴリズムで得られた結果と同じです。
このアルゴリズムの実行時間はO(log指数)です。指数が大きい場合、このアルゴリズムは、実行時間がO(指数)である以前の 2 つのアルゴリズムよりも大幅に高速化されます。たとえば、指数が 2 20 = 1048576 の場合、このアルゴリズムは 1048576 ステップではなく 20 ステップで済みます。
function modPow(b, e, m) if m == 1 then return 0 end local r = 1 b = b % m e > 0の間、 e % 2 == 1ならば r = (r*b) % m 終わり b = (b*b) % m e = e >> 1 -- Lua 5.2 以前のバージョンでは 'e = math.floor(e / 2)' を使用してください end return r end
指数部のビットを左から右の順で使用することもできます。実際には、通常は結果を何らかの法mで割った余りを求めます。その場合、処理を進める前に各乗算結果(mod m )を減算します。ここでは簡略化のため、法の計算は省略します。この例では、計算方法を示します。左から右への二進数べき乗を使用します。指数は二進数で1101です。ビット数は4なので、4回の反復が必要です。
結果を1に初期化します。。
『コンピュータプログラミングの技法』第2巻「半数値アルゴリズム」463ページで、ドナルド・クヌースは、一部の主張とは異なり、この方法が常に最小の乗算回数を与えるとは限らないと指摘している。最も小さな反例は15のべき乗で、2進法では6回の乗算が必要となる。代わりに、 2回の乗算でx 3を作り、次にx 3を2乗してx 6を作り、次にx 6を2乗してx 12を作り、最後にx 12とx 3を掛けてx 15を作ることで、わずか5回の乗算で目的の結果を得ることができる。しかし、その後の多くのページでは、このような数列を一般的にどのように考案できるかが説明されている。
各項がk個の前の項の線形関数である定数再帰数列(フィボナッチ数列やペリン数列など)のm番目の項は、対応するk × kのコンパニオン行列Aを用いてA m mod nを計算することで、効率的にnを法として計算できます。上記の方法は、この用途にも容易に適用できます。これは、例えば大きな数nの素数判定などに利用できます。
ModExp(A, b, c)= A b mod cの再帰アルゴリズム。ここでAは正方行列である。
関数Matrix_ModExp(Matrix A, int b, int c)は 、 b == 0 の場合、 Iを返します// 単位行列 if (b mod 2 == 1) then return (A * Matrix_ModExp(A, b - 1, c)) mod c 行列 D := Matrix_ModExp(A, b / 2, c) return (D * D) mod c
Diffie–Hellman鍵交換では、有限巡回群のべき乗演算が使用されます。上記のモジュラー行列のべき乗演算の方法は、明らかにこの文脈にも適用できます。モジュラー行列の乗算C ≡ AB (mod n )は、群の乗算c = abに単純に置き換えられます。
量子コンピューティングでは、モジュラべき乗がショアのアルゴリズムのボトルネックとして現れ、可逆ゲートで構成される回路で計算する必要があり、さらに特定の物理デバイスに適した量子ゲートに分解することができます。さらに、ショアのアルゴリズムでは、各呼び出しでべき乗の底とモジュラスを知ることができ、さまざまな回路最適化が可能になります。[ 3 ]
モジュラーべき乗はコンピュータサイエンスにおいて重要な演算であり、単純にべき乗して余りを取るよりもはるかに高速な効率的なアルゴリズム(上記参照)が存在するため、多くのプログラミング言語や任意精度整数ライブラリには、モジュラーべき乗を実行するための専用関数が用意されています。
pow()(べき乗)関数オプションの3番目の引数としてモジュラスを受け取りますBigIntegerがあります。ModPow()java.math.BigIntegerクラスには、modPow()モジュラべき乗を実行するメソッドがあります。powermodSymbolic Math Toolbox の関数Math::BigIntモジュールにはbmodpow()メソッドがありますモジュラーべき乗を実行するexpmod。big.Int型にはExp()(べき乗)メソッドが含まれていますその3番目のパラメータがnilでない場合、それはモジュラスですbcpowmod()関数がありますモジュラーべき乗を実行するmpz_powm()関数が含まれていますモジュラーべき乗を実行する@PowerMod() Pro用カスタム関数(1024ビットRSA暗号化の例付き)opensslパッケージにはOpenSSL::BN#mod_expメソッドがありますモジュラーべき乗演算を実行する。