去年発表されたDiscreet Log Contractsについて↓
https://adiabat.github.io/dlc.pdf techmedia-think.hatenablog.com
オラクルが作成するのがSchnorr署名だったので、最初読んだとき現状のBitcoinでは実装できないと勘違いしてたんだけど、オラクルが公開する署名はブロックチェーン上に出てくる必要はなく、公開方法は著者曰く
The oracle's signatures don't need to show up on the blockchain, and can just be on a website.
— Tadge Dryja (@tdryja) 2018年4月5日
てことだったので、オラクルが管理するWebサイトなんかで公開されればOKで、それを利用したコントラクトやシークレットの復元は既存のBitcoinのECDSAで対応できる。
ということで実際にRubyでDLCを実現するコードを書いてみた(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
を使用する。
オラクルは事前にV
とR
を公開しておく。
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のアウトプットをインプットとしたクロージングトランザクションを作成する方が良い。