Develop with pleasure!

福岡でCloudとかBlockchainとか。

Adaptor Signatureの計算をRubyで書いてみた

Andrew Poelstraが発表したScriptless Scriptsとその応用であるAdaptor Signatureについて実際に計算部分をRubyで計算してみた。

前提となるSchnorr署名

Andrew PoelstraのScriptless ScriptsはSchnorr署名が前提となるので、まずその公式について。

秘密鍵x、署名対象のメッセージをm、署名時に選択するランダム値をrとする。xrの公開鍵をそれぞれP = xGR = 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は以下のようにして作成する。

  1. アリスはランダム値tを選択し、T = tGを計算する。
  2. アリスとボブはそれぞれPA、PB、RA、RBを共有し、アリスはボブにTを送る。
  3. アリスとボブはそれぞれe = H(J(A, B) || RA + RB + T || m)を計算する。
  4. アリスはAdaptor Signature s' = ra + e * xaを計算し、ボブに送る。
  5. ボブはアリスから受け取ったs'についてs'G == RA + ePAが成立するか検証する。
  6. 5が成立する場合、ボブはsB = rb + e * xbを計算し、アリスに送る。
  7. アリスはsA = ra + t + e * xaを計算し、s = sA + sBを計算しブロードキャストする。
  8. ボブは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)があり、トランザクションもそれぞれ異なるので、mm1m2の2つになる。またアリスが生成するAdaptor Signatureもチェーン毎に2つ必要で、上記のステップ3〜6を両方のチェーン分で行うことになる。ステップ7のアリスがブロードキャストするのは片方のチェーンのみで、そのチェーンの署名データsを見て、ボブはステップ8のtの計算をする。

そのtと予めアリスからもらっていたAdaptor Signatureを使って自分が貰う方のコインのチェーンの償還トランザクションのアリス分の署名を作成し、自分の署名と組み合わせて署名データsを作成する。

大まかな流れは以前書いた↓参照。

techmedia-think.hatenablog.com