前回の投稿では、楕円曲線のスカラー倍算のアルゴリズムとしてバイナリ法やWindow法のアルゴリズムについて、また射影座標を用いた高速化手法について解説した↓
techmedia-think.hatenablog.com
今回は、もともとスカラー倍算の高速化について調査するきっかけとなったGLV法を用いた方法について。
GLVという名前は、発明者のRobert Gallant、Robert Lambert、Scott A. Vanstoneの3名の名前から来ており、自己準同型写像を持つ楕円曲線のスカラー倍算を高速化する。このアルゴリズムは特許が取られていたけど、それが2020年9月に有効期限切れになったため、Bitcoin Coreでもデフォルトで有効化された↓
検証自体は、故Hal Finneyにより2011年に行われ署名検証が約25%高速化すると報告されている。また、Pieter Wuille によりlibsecp256k1にも実装されたが、↑の特許のためデフォルトで無効化されていた。
GLV法
具体的に、GLV法ではどうやってスカラー倍算を高速化するのか見ていこう。この方法の解説は、Hal Finneyのbitcointalkへの投稿が分かりやすい(Hal Finneyが参考にしたのはScott A. Vanstoneが執筆したこの本)。
ここではスカラー値をk、乗算する点をQとする。つまりk*Q
を計算する。
自己準同型の性質
secp256k1曲線は、楕円曲線上の任意の点Q(x, y)について、以下を満たす自己準同型の性質を持つらしい。
ここで、pは楕円曲線のパラメータの1つでベースとなる有限体の素数、は、それぞれ
実際に↑の性質があるかは↓のように検証できる。
require 'ecdsa' require 'bitcoin' lambda = 0x5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72 beta = 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee # 任意の点Qを作成 key = Bitcoin::Key.generate Q = key.to_point # 検証用にλQを計算 lambda_q = Q.multiply_by_scalar(lambda) # beta * x mod p を計算 field = ECDSA::Group::Secp256k1.field x = field.mod(beta * Q.x) # λQが(beta * x \mod p, y)と同じであるかチェック puts lambda_q.x == x && lambda_q.y == Q.y
結果、任意の点に対してこの性質が成立してるのが確認できる。
高速化
続いて、↑の性質を利用して、スカラー値kをλを使って以下を満たすnの約半分のビット長のに分割する(nは曲線の位数)。
つまりk*Q
は、
となる。ここでを先に計算しておく、つまり。このQ'の計算はを単純にスカラー倍算するのではなく、↑の特性を利用してに対してを計算することで、つまりmod pの乗算を1回するだけで、効率的に計算することができる。
の分割方法
kをそれぞれ半分のビット長のの分解するアルゴリズムは↑の書籍の、Algorithm 3.74 Balanced length-two representation of a multiplieで紹介されている。Hal Finneyの投稿では、以下の手順で計算している。
- a1 = 0x3086d221a7d46bcde86c90e49284eb15
- b1 = -0xe4437ed6010e88286f547fa90abfe4c3
- a2 = 0x114ca50f7a8e2f3f657c1108d9d44cfd8
- b2 = 0x3086d221a7d46bcde86c90e49284eb15
とし(a1=b2)、
- c1 = (b2 * k / n.to_f).round
- c2 = (-b1 * k / n.to_f).round
- k1 = (k - c1 * a1 - c2 * a2) % n
- k2 = (-c1 * b1 - c2 * b2) % n
のように分割する。後は、を計算する。ここで、は、それぞれ半分のビット長であるため、計算する際のビット長が約半分になり、計算速度が向上すると。この計算量の削減によりスカラー倍算を高速化するというのがGLV法の仕組みっぽい。
require 'ecdsa' require 'bitcoin' beta = 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee n = ECDSA::Group::Secp256k1.order q = Bitcoin::Key.generate.to_point # beta * x mod n を計算 field = ECDSA::Group::Secp256k1.field x = field.mod(beta * q.x) # Q' = λQ lambda_q = ECDSA::Point.new(ECDSA::Group::Secp256k1, x, q.y) # k k = 1 + SecureRandom.random_number(n - 1) a1 = 0x3086d221a7d46bcde86c90e49284eb15 b1 = -0xe4437ed6010e88286f547fa90abfe4c3 a2 = 0x114ca50f7a8e2f3f657c1108d9d44cfd8 b2 = 0x3086d221a7d46bcde86c90e49284eb15 # kを分割 c1 = (b2 * k / n.to_f).round c2 = (-b1 * k / n.to_f).round k1 = (k - c1 * a1 - c2 * a2) % n k2 = (-c1 * b1 - c2 * b2) % n # k1Q + k2Q'を計算 glv = q.multiply_by_scalar(k1) + lambda_q.multiply_by_scalar(k2)
※ 厳密にはこのコードでは、最後のk1Q + k2Q'のmultiply_by_scalar
を個別にやってるので速度は向上しない。速度向上させるためには、multiply_by_scalar
の計算を1回のループで同時にやるよう修正する必要がある。