Andrew Poelstraが発表したScriptless Scriptsとその応用であるAdaptor Signatureについて実際に計算部分をRubyで計算してみた。
前提となるSchnorr署名
Andrew PoelstraのScriptless ScriptsはSchnorr署名が前提となるので、まずその公式について。
秘密鍵をx
、署名対象のメッセージをm
、署名時に選択するランダム値をr
とする。x
とr
の公開鍵をそれぞれP = xG
、R = rG
とする。
sG = R + H(P || R || m)P
を満たす、(s, R)
が有効な署名。ECDSAに比べてシンプルね。
2-of-2のAdaptor Signature
続いて、Schnorrを使ったAdaptor Signatureについて。
アリスが鍵ペアPA = xa G
とランダムなナンスRA = ra G
、ボブが鍵ペアPB = ba G
とランダムなナンスRB = rb G
を持っている場合、Adaptor Signatureは以下のようにして作成する。
- アリスはランダム値
t
を選択し、T = tG
を計算する。 - アリスとボブはそれぞれPA、PB、RA、RBを共有し、アリスはボブにTを送る。
- アリスとボブはそれぞれ
e = H(J(A, B) || RA + RB + T || m)
を計算する。 - アリスはAdaptor Signature
s' = ra + e * xa
を計算し、ボブに送る。 - ボブはアリスから受け取った
s'
についてs'G == RA + ePA
が成立するか検証する。 - 5が成立する場合、ボブは
sB = rb + e * xb
を計算し、アリスに送る。 - アリスは
sA = ra + t + e * xa
を計算し、s = sA + sB
を計算しブロードキャストする。 - ボブは
s - sB - s' = (ra + t + e * xa) - (ra + e * xa) = t
を計算して、t
の値を知る。
※ J(A, B)
はアリスとボブの鍵を集約したもの。
これをRubyで(bitcoinrb使って)書くと↓な感じになる。シンプルに書いたので省略してるけど、実際に使用する場合は曲線の位数を使って剰余をとる必要がある。
require 'bitcoin' # 署名対象のメッセージ m = 'message' # [Alice]アリスの鍵 alice_key = Bitcoin::Key.generate PA = alice_key.to_point ra = 1 + SecureRandom.random_number(ECDSA::Group::Secp256k1.order - 1) RA = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(ra) # [Bob]ボブの鍵 bob_key = Bitcoin::Key.generate PB = bob_key.to_point rb = 1 + SecureRandom.random_number(ECDSA::Group::Secp256k1.order - 1) RB = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(rb) # PA,RA,PB,RB,mは共有 # [Alice]STEP1, アリスはランダム値tから T=tGを計算 t = 1 + SecureRandom.random_number(ECDSA::Group::Secp256k1.order - 1) T = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(t) # [Alice]STEP2, アリスはボブにTを送る。 # [Alice][Bob]STEP3, アリスとボブ共にeを計算 J = ECDSA::Format::PointOctetString.encode(PA + PB, compression: true) e = Bitcoin.sha256((J + ECDSA::Format::PointOctetString.encode(RA + RB + T, compression: true) + m).htb).to_i(16) # [Alice]STEP4, アリスはAdaptor Signature s' = ra + e * xa を計算してボブに送る s_dash = ra + e * alice_key.priv_key.to_i(16) # [Bob]STEP5, ボブはs'G == RA + ePAを検証 verify = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(s_dash) == (RA + PA.multiply_by_scalar(e)) puts verify # [Bob]STEP6, sB = rb + e * xbを計算してアリスに送る。 sB = rb + e * bob_key.priv_key.to_i(16) # [Alice]STEP7, アリスは s = sA + sBを計算し、ブロードキャストする。 sA = ra + t + e * alice_key.priv_key.to_i(16) s = sA + sB # [Bob]STEP8, t = s -sB - s' = (ra + t + e * xa) - (ra + e * xa)を求める。 puts t == s - sB -s_dash # アリスが最初に作ったtと一致する。
これをクロスチェーンのAtomic Swapなんかに利用する場合、各チェーン毎にキーペア(PA1、PB1、PA2、PB2)があり、トランザクションもそれぞれ異なるので、m
もm1
、m2
の2つになる。またアリスが生成するAdaptor Signatureもチェーン毎に2つ必要で、上記のステップ3〜6を両方のチェーン分で行うことになる。ステップ7のアリスがブロードキャストするのは片方のチェーンのみで、そのチェーンの署名データs
を見て、ボブはステップ8のt
の計算をする。
そのt
と予めアリスからもらっていたAdaptor Signatureを使って自分が貰う方のコインのチェーンの償還トランザクションのアリス分の署名を作成し、自分の署名と組み合わせて署名データs
を作成する。
大まかな流れは以前書いた↓参照。