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

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

著作権

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