Develop with pleasure!

福岡でCloudとかBlockchainとか。

DLCでオラクルが公開した署名からシークレットを生成するコードを書いてみた

去年発表されたDiscreet Log Contractsについて↓

https://adiabat.github.io/dlc.pdf techmedia-think.hatenablog.com

オラクルが作成するのがSchnorr署名だったので、最初読んだとき現状のBitcoinでは実装できないと勘違いしてたんだけど、オラクルが公開する署名はブロックチェーン上に出てくる必要はなく、公開方法は著者曰く

てことだったので、オラクルが管理するWebサイトなんかで公開されればOKで、それを利用したコントラクトやシークレットの復元は既存のBitcoinのECDSAで対応できる。

ということで実際にRubyDLCを実現するコードを書いてみた(DLCの仕組みについてはここでは記載しないので、↑のブログかホワイトペーパー参照)。

オラクルのセットアップ

事前にgem bitcoinrbをインストールしておく。

オラクルはまず自身の公開鍵を共有する。ECDSAの鍵で良いので

require 'bitcoin'

o_key = Bitcoin::Key.generate

とかして生成する。

この時ランダムな秘密鍵v、対応する公開鍵(=楕円曲線状の点)をV = vGとする。

V = o_key.to_point
v = o_key.priv_key.to_i(16)

↑のVはオラクルを識別するための公開鍵に当たり、再利用可能。

Vとは別に、DLCコントラクト毎に鍵ペアを生成する。R = kG

tmp_key = Bitcoin::Key.generate
R = tmp_key.to_point
k = tmp_key.priv_key.to_i(16)

○月△日のマーケットの終わり値を公開するオラクルとかだとRはある日の終わり値を公開するのに使われる。別の日は別の鍵ペアから生成したRを使用する。

オラクルは事前にVRを公開しておく。

DLC参加者のセットアップ

DLC参加者のアリスとボブは資金を両者のマルチシグにロックするFunding Txを作成しブローキャストする。

Funding TxでロックされたマルチシグアウトプットをインプットにしたDLCのContract Execution Transactionsを大量に作成する。このトランザクションは基本的に2つのアウトプットを持ち、1つは通常のP2PKHで、もう1つは以下のような条件のスクリプトになる。

OP_IF
  <PubAi>
OP_ELSE
  <ロックタイム> OP_CSV OP_DROP <PubB>
OP_ENDIF
OP_CHECKSIG

このコインをアンロックできる条件は以下のいずれか

  • このトランザクションがブロックに入ってからロックタイムで指定された期間が経過した後に、ボブの公開鍵に対して有効な署名を作成する。
  • PubAiに対して有効な署名を作成する。

一見タイムロックのコントラクトだが、ELSE分岐のPubBは通常のボブの公開鍵に対して、タイムロックが設定されていないIF分岐のPubAiの方は通常のアリスの公開鍵ではなく、アリスの公開鍵 + siGとなる公開鍵になる。アリスが資金を償還する際は自身の秘密鍵に加えてsiの値を知らないと有効な署名を作成できない。アリスはこの時点でsiの値は知らないが、siGについてはsiG = R - h(i, R)Vで計算できる。iの部分は署名のメッセージで、DLCではオラクルが公開する値。マーケットの終値が100だったら、オラクルが公開するのはi == 100のデータ。

このPubAiを計算するのが↓のコード
h(i, R)で使うハッシュ関数や計算ルールは詳細に記載されていないので適当)

# アリスが鍵ペアを作成
alice_key = Bitcoin::Key.new.gerate

# 考えられるスコープでi分のsiGを計算する。
# とりあえずi == 100の場合、h(100, R)は
R_str = ECDSA::Format::PointOctetString.encode(R, compression: true)
R_hash_value = Bitcoin.sha256((100.to_s(16) + R_str).htb).to_i(16)

# siG = R - h(100, R)V
s100_G = R + V.multiply_by_scalar(R_hash_value).negate 

# Alice's pubkey + siGは
alice_100_pubkey = alice_key.to_point + s100_G

同様のことを条件を逆にしてボブも行い、作成したそれぞれの公開鍵を使ってContract Execution Transactionsを作成し、自分の署名を付与して、相手とそのトランザクションを交換する。

作成したiのデータが異なる大量のContract Execution Transactionsのいずれかが、チェーンにブロードキャストされるトランザクションになる。実際にブロードキャストされるトランザクションは不正をしない限り、オラクルが公開したiに対応するトランザクションである(不正をした場合は、LNのペイメントチャネルと同様、不正をした側の資金が全部没収される)。

オラクルによる署名の公開

マーケットが閉じたらオラクルは、その終値をもとに署名データを作成する。署名に使用するRは事前に公開してあるので、必要なのはsの値。具体的にはオラクルが持つ秘密鍵を使ってs = k - h(i, R)vを計算する。ここではi == 100だったと仮定する。

# h(100, R)vを計算
hash_value = R_hash_value * v % ECDSA::Group::Secp256k1.order

# s = k - h(100, R)vを計算
s100 = (k - hash_value) % ECDSA::Group::Secp256k1.order

と生成したs100を公開する。

公開された署名から必要な秘密鍵を計算

アリスとボブは、オラクルが公開したsの値を使って、アンロックに必要な秘密鍵を計算する。

alice_unlock_key = Bitcoin::Key.new(priv_key: ((alice_key.priv_key.to_i(16) + s100) % ECDSA::Group::Secp256k1.order).to_s(16))

この計算結果がPubA100秘密鍵になる。この秘密鍵を使ってブロードキャストされたContract Execution Transactionのアウトプットをアンロックできる。 scriptPubkeyのアンロック自体は計算した秘密鍵を元にECDSAで行われるので、Schnorrの署名検証がブロックチェーンスクリプトとして実行される必要はない。

※実際は手数料を考えると二人が協力し、Contract Execution Transactionはブロードキャストせず、Funding Txのアウトプットをインプットとしたクロージングトランザクションを作成する方が良い。