前置き
本記事は Nostr Advent Calendar 第一会場 12月3日 分の記事として書きました。
序論
Schnorr 署名にかかわる演算のほとんどの部分は整数座標上で定義された楕円曲線上の演算で実行されます。
その楕円曲線上の演算を更に要素に分けていくと、結局のところ、整数の演算の組み合わせとなります。
幸いなことに、Schnorr 署名で使われる範囲の演算ならば、そこさえ見通せば署名のアルゴリズムを概観するのには十分…というのが私の所感です。
具体的には以下を把握できれば、楕円曲線上の計算を具体的なプログラムとして実装できると考えます。
- 整数の表現を把握する
- 四則演算を定義
- 拡張ユークリッド互除法による割り算(乗法逆元)の計算
- 巨大な指数による累乗計算(バイナリ法)
- 平方剰余の計算
以下、一つ一つ見ていきます。
整数表現
素数を法とする合同式による表現
楕円曲線に関わる計算は、ほぼ全てが、特定の素数を法として合同な整数上で行われます。
上記の定義は、ちょっとわかりにくいかもしれないので、例として、素数
- …
となります。
ここで、
ところで、この記事を見るのはプログラマーの方やソフトウェア開発に興味のある方が多いと思われます。
そのようなバックグラウンドのある方にとって、
つまり、
と、計算結果が13以上にあふれた場合は、ゼロに戻るということです。
引き算に関しても同じで、0を下回るとアンダーフローし最大値の12に飛んでいくイメージです。
必然的に、
整数のサイズ
Schnorr 署名では、ほとんどの計算にて 64バイトの整数が用いられます。
使われている法としての素数のサイズも64バイトです。
例えば、ビットコインやNostrで署名として使われる scpe256k1 という体系では、
という巨大な素数が使われています。
この巨大な素数を法として用いることで、公開鍵から秘密鍵を探索することを難しくしています。
足し算/引き算/掛け算
先ほど述べた通り、剰余が同じ整数は、同じものとみなします。
よって、整数による足し算、引き算、掛け算を行った後、法とする整数で割った余りをもちいることで、合同式上の加減算・乗算を実現できます。
は、計算機上では、
とすればOKです。
この時注意すべきは、
それを考慮に入れた計算を実装するか、多倍長整数の計算ライブラリを使う必用があります。
割り算と拡張ユークリッド互除法による乗法逆元の計算
楕円曲線上で用いる合同式の四則演算の中で、割り算だけが特殊です。
通常、プログラミング言語で標準的に実装されいている整数の割り算は、計算結果の小数点以下を切り捨てることで実現します。
ですが、ここでの割り算
つまり、
乗法逆元の求め方 - 拡張ユークリッド互除法による解放
ここでは、拡張ユークリッド互除法による乗法逆元の求め方を紹介します。
拡張ユークリッド互除法は、
を満たす
ここで
この場合、
です。
ここで、両辺の素数pを法とした合同式を考えると、
が得られます。よって、
拡張ユークリッド互除法のアルゴリズム
拡張ユークリッド互除法は以下の手順によって、方程式をより小さい数によるものに帰着していくものです。
巨大な数を使ったとしても、場合でもそれほど多くない繰り返しで解にたどり着くことが知られています。
また冠している名前「ユークリッド」から推察できるように、古代から知られ、隅から隅まで研究しつくされている解法でもあります。 具体的な手順以下の通りです。
手順を見ることで「方程式をより小さい数を使ったものに帰着している」の感じがつかめるのではないかと思います。
, とする を で割った商を , 余りを とする。- 書き換えて、
- 新たな変数
, を導入し、逐次的により小さい問題に変換することを考える。 , , , , とし、新たな方程式、 を得る。- このより小さい係数からなる新しい方程式が解けた場合、与式より、
, として求められる。 - これを、最終的に
となるまで繰り返せばよい。このとき なので、 , が最終的に到達する点であり、 , と定めれば、一連の計算を逆にたどることで解にたどり着けることが分かる。
一連のステップは行列として書くことができて、繰り返し計算に還元するにはこちらを使うのが簡単です。
つまり、
と書けることから、
です。行列の積の性質
このようにして乗法逆元を求め、それをもとに割り算を実行しています。
巨大な数を法とする合同式上の割り算は、拡張ユークリッド互除法等で高速に計算できるのが実用に供することのできる一つのポイントになっています。
巨大な指数による累乗計算(バイナリ法)
累乗は要素としては掛け算の範疇ではありますが、大事なポイントのひとつとして、巨大な指数による整数の累乗計算の高速計算方法を紹介します。
巨大な指数による累乗計算は、合同式上の平方剰余の計算を高速に行う際に必要になります。
この方法のエッセンスは、途中の計算過程をうまく利用することにより、本来なら
この方法は、高速化の良く見るパターンのため、もしこれに馴染みがあまり無い場合には、学習しておく価値があると信じます。
以下概要です。
今ある整数
ナイーブな計算方法は、
高速化のためまず、指数の表現を書き換えます。
と書くこととします。こちらを使ってあらためて累乗の式を書き直すと、
となり、N回の剰積の中に
例えば、64バイトの整数なら
です。
ここで、工夫するの
i から N までの反復中、すでに、
として計算できます。つまり、
// 疑似コード
x // 累乗したい数
d(0 to N) // 指数の二進数表現
S = 1 // 計算結果
X = 1 // xの累乗途中結果保存
for int i = 0 to Length(d)
if d(i) = 1 then
S = S*X
end if
X = X * x^2
Next i
と、途中の乗積結果を再利用する形で計算すれば、たかだか
平方剰余の計算
平方剰余とはいわゆる合同式上の平方根のことです。 実数の場合とはことなり、どんな場合でも平方根に相当するものが存在するわけではありません。
ある数
このとき、y を x の平方剰余と呼びます。例として、
1 | 1 |
2 | 4 |
3 | 2 (9 mod 7) |
4 | 2 (16 mod 7) |
5 | 4 (25 mod 7) |
6 | 1 (36 mod 7) |
このように、1,2,4 にはそれぞれ 1 or 6, 3 or 4, 2 or 5 が平方剰余として定義できる半面、3,5, には平方剰余、つまり二乗して 3, 5 になる数 は存在しないことになります。
実数の計算と異なり、平方剰余では、実数でのニュートン法のような収束による高速計算が存在しないことが知られています。
今回は法とする整数
また、secp256k1 に用いられている素数はまさに、
フェルマーの小定理
フェルマーの小定理は、素数
平方剰余の計算
両辺に
ここで
となり、
累乗計算に、先述のバイナリ法にを用いることで、平方剰余が比較的低い計算コストで求めることができます。
結論
以上です。
ここまで、Schnorr 署名に必用な楕円曲線上の計算に必要な要素に関して、初等整数論に属している(と私が考える部分)に関して述べました。
計算の枠組みとしての合同式は、日頃から整数のデータ形式に馴染んでいるプログラマーの方々には、特に馴染みやすい概念なのではないかと思います。
拡張ユークリッド互除法、バイナリ法は、それぞれ逐次計算、計算過程で得られる途中結果を上手く利用することで、ナイーブなアルゴリズムから劇的に計算方法を抑えることができる例でした。
フーリエ変換や畳み込み計算の高速計算方法へとつながる知識でもあります。このようなシンプルなアルゴリズムに立ち戻ることによって、これらのもう少し複雑な例の理解の役に立つと考えます。
上記例は、歴史も長く、標準の整数計算ライブラリには組み込まれていることもあると思います。
ですが、もし、多倍長計算で速度に問題を感じた場合、上記のような高速計算が適用されているか、確認してみるとよいのではないかと思います。
次回で、署名に必要な楕円曲線上の計算のに進みたいと思います。