読者です 読者をやめる 読者になる 読者になる

Develop with pleasure!

福岡でCloudとかBlockchainとか。

CHECKLOCKTIMEVERIFY使った有効期限付きトランザクション(BIP-65)

BIP-65で定義されているOP_CHECKLOCKTIMEVERIFYは、トランザクションの出力を将来のある時点までunspendableのままにする(要は指定された時間まで使えないUTXO)命令コードについて定義している。

bips/bip-0065.mediawiki at master · bitcoin/bips · GitHub

概要

CHECKLOCKTIMEVERIFYは既存のNOP2 opcodeを再定義する。CHECKLOCKTIMEVERIFYが実行された際、以下のいずれかの条件に当てはまるとスクリプトインタプリタはエラーで終了する。

  • スタックが空
  • スタックのトップアイテムが0より小さい
  • スタックのトップアイテムのlock-timeタイプ(ブロック高 or タイムスタンプ)とnLockTimeフィールドのタイプが同じでは無い
  • スタックのトップアイテムがトランザクションのnLockTimeより大きい
  • トランザクションの入力のnSequenceの値が0xffffffff

上記以外の場合はスクリプトは継続して実行される。

トランザクションのnLockTimeフィールドは、指定したブロック高もしくはブロックタイムになるまでマイニングされてブロックに含まれないようロックする。nLockTimeに対してCHECKLOCKTIMEVERIFYの引数を比較することで、指定されたブロック高もしくはブロックタイムに達しているか間接的に検証する。その時間に達するまで、そのトランザクションの出力はunspendableのまま。

動機

トランザクションのnLockTimeフィールドは、トランザクションの出力が将来使用可能であることの証明に使うことができる。 しかしnLockTimeフィールドはトランザクションの出力が将来使用不可能であることを証明することはできない。その出力を使用する有効な署名がされた別のトランザクションが作成されていた場合、それを知るすべが無いため。

エスクロー

アリスとボブが共同で事業を行う際、全ての資金を(利用するのに両者の協力が必要な)2-of-2のマルチシグトランザクションの出力に保持することができる。しかし、どちらかがバスにひかれるようなイレギュラーのケースを考慮すると、資金を取得するためのバックアッププランが必要になる。そこで彼らは第三者となる弁護士のレニーを任命する。

標準的な2-of-3のCHECKMULTISIGでは、レニーがアリスかボブのどちらかと共謀すればいつでも資金を盗むことができる。同様にレニーは、力によって悪意ある人間から彼の秘密鍵を取られて困らないよう、資金へすぐにアクセスできない方が良いと思うかもしれない。そういったユースケースでは、CHECKLOCKTIMEVERIFYを↓のように使える。

CHECKLOCKTIMEVERIFYでは、以下の形式で資金をscriptPubKeyに保存する。

IF
    <now + 3 months> CHECKLOCKTIMEVERIFY DROP
    <Lenny's pubkey> CHECKSIGVERIFY
    1
ELSE
    2
ENDIF
<アリスの公開鍵> <ボブの公開鍵> 2 CHECKMULTISIG

こうすると、もともとの2-of-2のマルチシグの制約と同様、アリストボブが合意すれば以下のscriptSigでいつでも資金を使用することができる。

0 <アリスの署名> <ボブの署名> 0

3ヶ月経つと、レニーと、アリスとボブのいずれかが以下のscriptSigで資金を使用することができるようになる。

0 <アリスかボブの署名> <レニーの署名> 1

つまり↑のscriptPubKeyによって、ステークホルダーであるアリスとボブのいずれかが不幸な事故でいなくなったとしても、どちらかが不在であるため永遠に資金が取り出せないような事態にならないよう、一定の有効期限の後に第三者であるレニーの協力の元、資金を改修することができるようになる。

非対話型のtime-lockedな払い戻し

UTXOを使用するのに当事者間の合意を必要とするトランザクションを作成するには、いくつかのプロトコルがある。ある当事者のミスによって資金がロストすることが無いよう、払い戻しのトランザクションがnLockTimeを使って事前にセットアップされる。この払い戻しのトランザクションは対話的に作成する必要があり、さらにトランザクションのmalleability*1に対して脆弱である。このプロトコルに対してCHECKLOCKTIMEVERIFYを使うことで、対話的なセットアップを非対話的に置き換え、トランザクションのmalleabilityに関する問題も発生しなくなる。

Two-factor wallets

GreenAddress*2のようなサービスでは、Bitcoinを2-of-2のマルチシグを使って保存しており、1つのキーペアをユーザが、もう1つのキーペアをサービス側が管理している。ユーザがウォレット内の資金を使う際は、ローカルにインストールしてあるウォレットアプリで必要な署名の1つを作成し、続いて2段階認証を使ってサービス側の認証を受け、将来のある時点までロックされているSIGHASH_NONE*3の署名をユーザに送る。サービスが利用できない状況でユーザが資金を使いたい場合は、nLockTimeの期限が切れるのを待つことになる。

この場合、ユーザが自分のUTXOに対する有効な署名を持っていない機会が多いという問題がある。CHECKLOCKTIMEVERIFYでは、必要に応じて以下の形式のscriptPubKeyを使用することで、ユーザは有効期限になれば、いつでもサービス側と連携することなく自分の資金を使用することができる。

IF
    <サービス側の公開鍵> CHECKSIGVERIFY
ELSE
    <有効期限> CHECKLOCKTIMEVERIFY DROP
ENDIF
<ユーザの公開鍵> CHECKSIG
ペイメントチャネル

Jeremy Spilmanスタイルのペイメントチャネルでは、始めに2-of-2のマルチシグにデポジットし(tx1)、続いて2つ目のトランザクションtx2で、t1のUTXOを支払人と受取人に送る。tx1を公開する前に払い戻し用のトランザクションtx3を作成し、支払人が消えてもデポジットした自分の預金を取り戻すことができるようにしておく。この払い戻しトランザクションを作成するプロセスは、トランザクションのmalleabilityを狙った攻撃に対し脆弱で、払い戻しを受け取る支払人が必要になる。Two-factor walletsの例と同様のscriptPubKeyを作成すればこういった問題は解決する。

データ公開に対するトラストレスな支払い

PayPubプロトコルではトラストレスな方法でデータに対する支払いを可能にする。まず最初に(データの一部を明らかにすることで)暗号化されたファイルに欲しいデータがあることを証明し、続いて支払いに使用するscriptPubKey(これを使えばデータの暗号化キーが分かる)を作成する。ただ現在の実装には重大な欠陥があって、発行者は鍵のリリースを無期限に遅らせることができる。

この問題は払い戻しトランザクションのテクニックを使って対話的に解決することができるが、CHECKLOCKTIMEVERIFYでは以下のような形式のscriptPubKeyを使って非対話的に解決することが可能。

IF
    HASH160 <Hash160(データを暗号化した際の鍵)> EQUALVERIFY
    <発行者の公開鍵> CHECKSIG
ELSE
    <有効期限> CHECKLOCKTIMEVERIFY DROP
    <購入者の公開鍵> CHECKSIG
ENDIF

データの購入者は有効期限を使って安全なオファーが作れる。有効期限前に発行者がオファーの受け入れに失敗した場合は、購入者は出力を使用することでオファーをキャンセルできる。

Proving sacrifice to miners' fees

announce-commit sacrificesあたりがまだよく分かってないのでひとまずスルー

資金の凍結

コールドストレージを使う以外に、ハードウェアウォレットやP2SHのマルチシグ出力を使うことでブロックチェーン上の資金をすぐに凍結することができる。CHECKLOCKTIMEVERIFYでは次のscriptPubKeyを使えば、有効期限を迎えるまで誰も資金を使えないようにすることができる。

<expiry time> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <pubKeyHash> EQUALVERIFY CHECKSIG

nLockTimeフィールドの置き換え

余談として、SignatureHash()アルゴリズムがscriptSigの署名の一部をカバーする際は、scriptSigにCHECKLOCKTIMEVERIFYの命令コードを含みそれらが実行されている必要があることに注意する必要がある。この署名の機能は、有効な署名がトランザクションが使用されたことの証明となるため、トランザクションのnLockTimeフィールドを置き換えることができる。

詳細な実装

↓が参照実装。

Replace NOP2 with CHECKLOCKTIMEVERIFY · petertodd/bitcoin@ab0f54f · GitHub

実装のサンプル

↓の2つがデモ用のサンプルとして公開されている。

github.com

github.com

まとめ&所感

  • CHECKLOCKTIMEVERIFYを使うと、トランザクションはconfirmされているけど、ある日まではそのトランザクションUTXOはunspendableのままというトランザクションを作ることができる。
  • nLockTimeは指定された時刻までトランザクションがブロックに含められないが、その後はブロックに含まれ使用可能となる。しかし、例えば同じ入力を使いそれより短いロックタイムが設定されたトランザクションが作成されていない場合に、最初のトランザクションは指定時刻を越えてもそのトランザクションの出力が使用不可能であることを知ることができない。
    CHECKLOCKTIMEVERIFYの場合、nLockTimeと同様にある日までトランザクションをロックすることができるけど、nLockTimeと違ってconfirmされているので、ある日をすぎれば必ず使用することができる。
  • トランザクションがブロックに入るのを制限するものではないので、トランザクションのmalleabilityを狙った攻撃の心配をしなくていい。
  • CHECKLOCKTIMEVERIFYを使うことで↓のようなトランザクションを作れる。
  • BIP-68もそうだけどnLockTimeが要らない子になりつつある。
  • Bitcoin Coreでは0.11.2でマージされたみたい。
  • CHECKLOCKTIMEVERIFY使ったscriptPubKeyの作り方はデモ用のサンプル見れば分かる?
  • BIPとは直接関係ないけど、データへの支払いをトラストレスに行うPayPubプロトコル面白そう。

*1:トランザクションの入力と出力は変更せずにトランザクションハッシュを変更しトランザクションの二重払いを行う攻撃

*2:Bitcoinのウォレットサービスアプリ。greenaddress.it

*3:トランザクショの入力には署名するが出力には署名しない