ビットコイン・インスティテュート

BIP 65 — OP_CHECKLOCKTIMEVERIFY

BIP: 65
  Layer: Consensus (soft fork)
  Title: OP_CHECKLOCKTIMEVERIFY
  Authors: Peter Todd <pete@petertodd.org>
  Status: Deployed
  Type: Specification
  Assigned: 2014-10-01
  License: PD

概要

本 BIP は、ビットコインのスクリプトシステムに新しいオペコード(OP_CHECKLOCKTIMEVERIFY)を追加する。このオペコードを使うと、トランザクション出力を将来のある時点まで使用不能にできる。

要約

CHECKLOCKTIMEVERIFY は、既存の NOP2 オペコードを再定義する。実行時、以下の条件のいずれかが真であれば、スクリプトインタープリターはエラーで終了する。

  • スタックが空である
  • スタック最上位の項目が 0 未満である
  • スタック最上位の項目のロックタイム種別(ブロック高か時刻か)と nLockTime フィールドの種別が一致しない
  • スタック最上位の項目がトランザクションの nLockTime フィールドより大きい
  • txin の nSequence フィールドが 0xffffffff である

これらのいずれにも該当しなければ、NOP が実行されたのと同様にスクリプトの実行を継続する。

トランザクションの nLockTime フィールドは、特定のブロック高またはブロック時刻に達するまで、そのトランザクションが採掘されるのを防ぐ。CHECKLOCKTIMEVERIFY に渡す引数を nLockTime フィールドと比較することで、目的のブロック高またはブロック時刻に達したかどうかを間接的に検証する。そのブロック高またはブロック時刻に達するまで、トランザクション出力は使用不能のままとなる。

動機

トランザクションの nLockTime フィールドは、nLockTime を設定してその出力を使う有効なトランザクションを構築することで、将来その出力を使うことが可能であると証明するために使える。

しかし nLockTime フィールドでは、ある時点までその出力を使うことが不可能であると証明することはできない。同じ出力を使う別のトランザクションに対して、既に有効な署名が作られていないという保証がないからだ。

エスクロー

アリスとボブが共同で事業を営んでいるなら、資金はすべて、両者の協力がなければ使えない 2-of-2 マルチシグのトランザクション出力に置いておきたいと考えるだろう。しかし、どちらか一方が「事故に遭う」といった不測の事態も想定し、資金を取り戻すための予備手段が要ることも認識している。そこで二人は、弁護士のレニーを第三者として任命する。

通常の 2-of-3 CHECKMULTISIG では、レニーがいつでもアリスかボブのどちらかと結託し、不正に資金を奪える。またレニーとしても、秘密鍵を力ずくで奪おうとする悪意ある者を思いとどまらせるため、資金に即座にアクセスできない状態を望むかもしれない。

しかし CHECKLOCKTIMEVERIFY を使えば、資金を次の形の scriptPubKey に保管できる。

IF
    <now + 3 months> CHECKLOCKTIMEVERIFY DROP
    <Lenny's pubkey> CHECKSIGVERIFY
    1
ELSE
    2
ENDIF
<Alice's pubkey> <Bob's pubkey> 2 CHECKMULTISIG

いつでも、次の scriptSig で資金を使える。

0 <Alice's signature> <Bob's signature> 0

3 か月が経過した後は、レニーとアリス・ボブのいずれか一方が、次の scriptSig で資金を使える。

0 <Alice/Bob's signature> <Lenny's signature> 1

非インタラクティブなタイムロック払い戻し

出力を使うために両者の協力を必要とするトランザクション出力を作成するプロトコルは数多く存在する。一方が離脱しても資金が失われないよう、nLockTime を使った払い戻しトランザクションがあらかじめ用意される。この払い戻しトランザクションはインタラクティブに作成する必要があり、さらに現状ではトランザクション展性への耐性もない。CHECKLOCKTIMEVERIFY を使えば、こうしたプロトコルにおいてインタラクティブな設定を非インタラクティブな設定に置き換えられるうえ、トランザクション展性も問題にならなくなる。

二要素ウォレット

GreenAddress のようなサービスは、一方の鍵ペアをユーザーが、もう一方の鍵ペアをサービス側が管理する 2-of-2 マルチシグの scriptPubKey でビットコインを保管する。資金を使うとき、ユーザーはローカルにインストールしたウォレットソフトウェアで必要な署名の一方を生成し、続いて第二要素認証を用いてサービス側を認可し、将来のある時点までロックされた 2 つ目の SIGHASH_NONE 署名を作成させ、その署名をユーザーへ送って保管させる。ユーザーが資金を使う必要があるのにサービスが利用できない場合は、nLockTime が失効するまで待てばよい。

問題は、ユーザーが自分のトランザクション出力の一部または全部について有効な署名を持たない場面が数多く存在することだ。CHECKLOCKTIMEVERIFY を使えば、その場で払い戻し署名を作る代わりに、次の形の scriptPubKey を用いることができる。

IF
    <service pubkey> CHECKSIGVERIFY
ELSE
    <expiry time> CHECKLOCKTIMEVERIFY DROP
ENDIF
<user pubkey> CHECKSIG

これにより、ユーザーはサービス側の協力なしに、失効時刻の到来を待つだけで、いつでも自分の資金を使えるようになる。

ペイメントチャンネル

ジェレミー・スピルマン方式のペイメントチャンネルでは、まず 2-of-2 マルチシグで管理される預け入れトランザクション tx1 を用意し、その後、tx1 の出力を支払人と受取人へ分配する 2 つ目のトランザクション tx2 を調整していく。tx1 を公開する前には、受取人が消えてしまっても支払人が預け入れ資金を取り戻せるよう、払い戻しトランザクション tx3 が作成される。この払い戻しトランザクションを作成する過程は現状ではトランザクション展性攻撃に対して脆弱であり、さらに支払人がその払い戻しを保管しておく必要もある。二要素ウォレットの例と同じ形の scriptPubKey を用いれば、これら両方の問題を解決できる。

データ公開のための信頼不要な支払い

PayPub プロトコルは、暗号化されたファイルが目的のデータを含んでいることをまず証明し、次に、使用すると暗号鍵が明らかになるような scriptPubKey を支払いに用いることで、信頼を要さない形での情報への対価支払いを可能にする。しかし既存の実装には重大な欠陥がある。公開者が鍵の公開をいつまでも遅らせられてしまうのだ。

この問題は払い戻しトランザクションの技法を使えばインタラクティブに解決できる。CHECKLOCKTIMEVERIFY を使えば、次の形の scriptPubKey により非インタラクティブに解決できる。

IF
    HASH160 <Hash160(encryption key)> EQUALVERIFY
    <publisher pubkey> CHECKSIG
ELSE
    <expiry time> CHECKLOCKTIMEVERIFY DROP
    <buyer pubkey> CHECKSIG
ENDIF

こうすることで、データの買い手は失効時刻付きの安全な申し出をできるようになる。公開者が失効時刻までに申し出を受け入れなければ、買い手はその出力を使うことで申し出を取り消せる。

マイナー手数料への犠牲の証明

何らかの限られた資源を犠牲にしたことを証明する手法は、さまざまな暗号プロトコルで広く使われている。コインをマイニング手数料として犠牲にする方法は、コインを単に破棄するのではなく、犠牲を向けられる普遍的な公共財として提案されてきた。しかしこれを実現するのは容易ではなく、既存の最良の手法であるアナウンス・コミット型の犠牲でさえ、マイニングの中央集権化を助長しかねない。CHECKLOCKTIMEVERIFY を使えば、誰でも使えることが証明可能な出力(つまりマイナーが最適かつ合理的にふるまえばマイニング手数料に回される出力)を、大手マイナーが犠牲分を割安で売って利益を得られないほど十分先の時刻になるまで使用不能にした形で作成できる。

資金の凍結

コールドストレージ、ハードウェアウォレット、P2SH マルチシグ出力による資金管理に加え、ブロックチェーン上の UTXO に直接資金を凍結することも可能になる。次の scriptPubKey を使えば、指定した失効時刻に達するまで、誰もその出力を使えなくなる。資金を確実に凍結できるこの機能は、強要や没収のリスクを減らしたい場面で役立つ可能性がある。

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

nLockTime フィールドの完全な置き換え

余談だが、もし SignatureHash() アルゴリズムが scriptSig の一部を任意でカバーできるなら、署名は scriptSig に CHECKLOCKTIMEVERIFY オペコードを含めることを要求し、さらにそれが実行されることも要求できる(ビットコイン v0.1 の CODESEPARATOR オペコードは、これを可能にする一歩手前まで来ていた)。この署名単位の機能により、トランザクション単位の nLockTime フィールドを完全に置き換えられる可能性がある。有効な署名そのものが、トランザクション出力を使用できることの証明になるからだ。

詳細仕様

正確な意味論とその詳細な根拠については、以下に再掲するリファレンス実装を参照のこと。

case OP_NOP2:
{
    // CHECKLOCKTIMEVERIFY
    //
    // (nLockTime -- nLockTime )

    if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY))
        break; // not enabled; treat as a NOP

    if (stack.size() < 1)
        return false;

    // Note that elsewhere numeric opcodes are limited to
    // operands in the range -2**31+1 to 2**31-1, however it is
    // legal for opcodes to produce results exceeding that
    // range. This limitation is implemented by CScriptNum's
    // default 4-byte limit.
    //
    // If we kept to that limit we'd have a year 2038 problem,
    // even though the nLockTime field in transactions
    // themselves is uint32 which only becomes meaningless
    // after the year 2106.
    //
    // Thus as a special case we tell CScriptNum to accept up
    // to 5-byte bignums, which are good until 2**32-1, the
    // same limit as the nLockTime field itself.
    const CScriptNum nLockTime(stacktop(-1), 5);

    // In the rare event that the argument may be < 0 due to
    // some arithmetic being done first, you can always use
    // 0 MAX CHECKLOCKTIMEVERIFY.
    if (nLockTime < 0)
        return false;

    // There are two types of nLockTime: lock-by-blockheight
    // and lock-by-blocktime, distinguished by whether
    // nLockTime < LOCKTIME_THRESHOLD.
    //
    // We want to compare apples to apples, so fail the script
    // unless the type of nLockTime being tested is the same as
    // the nLockTime in the transaction.
    if (!(
          (txTo.nLockTime <  LOCKTIME_THRESHOLD && nLockTime <  LOCKTIME_THRESHOLD) ||
          (txTo.nLockTime >= LOCKTIME_THRESHOLD && nLockTime >= LOCKTIME_THRESHOLD)
         ))
        return false;

    // Now that we know we're comparing apples-to-apples, the
    // comparison is a simple numeric one.
    if (nLockTime > (int64_t)txTo.nLockTime)
        return false;

    // Finally the nLockTime feature can be disabled and thus
    // CHECKLOCKTIMEVERIFY bypassed if every txin has been
    // finalized by setting nSequence to maxint. The
    // transaction would be allowed into the blockchain, making
    // the opcode ineffective.
    //
    // Testing if this vin is not final is sufficient to
    // prevent this condition. Alternatively we could test all
    // inputs, but testing just this input minimizes the data
    // required to prove correct CHECKLOCKTIMEVERIFY execution.
    if (txTo.vin[nIn].IsFinal())
        return false;

    break;

}

https://github.com/petertodd/bitcoin/commit/ab0f54f38e08ee1e50ff72f801680ee84d0f1bf4

展開

本 BIP では、BIP66 で用いられた二重しきい値の IsSuperMajority() 切り替え機構を、同じしきい値のまま nVersion = 4 に対して再利用する。新しいルールは、nVersion = 4 であるブロック(高さ H)について、その直前の 1000 ブロック(高さ H-1000 から H-1)のうち少なくとも 750 ブロックも nVersion >= 4 である場合に有効となる。さらに、あるブロックの直前 1000 ブロックのうち 950 ブロックが nVersion >= 4 になると、nVersion < 4 のブロックは無効となり、以降のすべてのブロックが新しいルールを強制する。

なお BIP9 は上位ビットを恒久的に 1 に設定するものであり、これにより nVersion は従来のすべての IsSuperMajority() ソフトフォークの条件も満たすことになるため、nVersion のいかなるビットも恒久的に失われることはない。

SPV クライアント

SPV クライアントは(現状では)ブロックを一般的に検証できず、検証をマイナーに委ねているが、ブロックヘッダーは検証できるため、展開ルールの一部については検証が可能である。直前 1000 ブロックのうち 950 ブロックが nVersion >= 4 であれば、SPV クライアントは nVersion < 4 のブロックを拒否すべきである。これにより、95% のしきい値に達した時点で残る 5% の未アップグレードマイナーによる誤った承認を防げる。

謝辞

この引数を現在のブロック高・時刻ではなく、トランザクション単位の nLockTime と比較するよう提案してくれたグレゴリー・マックスウェルに感謝する。

参考文献

PayPub

  • https://github.com/unsystem/paypub

Jeremy Spilman Payment Channels

  • https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2013-April/002433.html

実装

Python / python-bitcoinlib

  • https://github.com/petertodd/checklocktimeverify-demos

JavaScript / Node.js / bitcore

  • https://github.com/mruddy/bip65-demos

著作権

本文書はパブリックドメインに置かれる。