ビットコイン技術セキュリティ中級

時計のないビットコインは、どうやって1年後を約束するのか

普通の送金にすでに入っているnLockTimeから、時計を見ないCLTVとCSVの二段構えの設計、マイナーの時刻操作を防ぐ中央値時間まで。ライトニングネットワークも相続設計も、すべてこの上に立っている。

·10分

ブロックエクスプローラーで適当な取引を開いてみよう。下の方にlocktimeというフィールドがあり、値が0でないことが多い。現代のウォレットが作る普通の送金のかなりの割合がそうなっている。何も設定した覚えがないのに、取引にはすでに時間の条件が入っているのだ。その理由は記事の途中で明らかになる。

タイムロックは、ビットコインを特定の時点より前には使えないように施錠する仕組みだ。相続設計、エスクロー、そしてライトニングネットワークの全体が、この仕組みの上に立っている。ところが実装を覗くと奇妙な点が一つある。時間を強制する機能なのに、どこにも時計を読むコードがないのだ。

ビットコインには時計がない

分散システムにおいて「いま何時か」は見かけより難しい問いだ。ノードごとに時計は少しずつずれており、ブロックに刻まれるタイムスタンプはマイナーが書き込む値であって、コンセンサスルールは緩くしか制約しない。前のブロックより早い時刻を書くこともできるし、ある程度未来に引き寄せて書くこともできる。

問題は、お金に関する約束が時間を必要とすることだ。「この資金は1年後から使える」という条件を強制するには、システムのどこかに全員が合意できる時間がなければならない。ビットコインの答えは壁掛け時計ではなくチェーンそのものだ。ブロック高は誰にも否定できない形で単調に増えていくし、タイムスタンプも後で見る安全装置を通せば使い物になる時間になる。タイムロックの設計全体がこの前提の上にある。

nLockTime: この取引はまだ有効ではない

最も古いタイムロックは取引そのものに付く。すべてのビットコイン取引にはnLockTimeという4バイトのフィールドがあり、これが設定された取引はその時点が来るまでブロックに入れない。値の解釈は大きさで分かれる。500,000,000未満ならブロック高として読み、それ以上ならUnixタイムスタンプとして読む。5億番目のブロックが来るのは数千年先なので、二つの解釈が衝突することはない。

ただし但し書きが一つある。すべての入力のnSequenceが最大値(0xFFFFFFFF)なら、nLockTimeは無視される。これはサトシ・ナカモトの当初の設計が残した痕跡だ。初期の実装には、未確定の取引をより高いnSequenceを持つ新しい版で置き換える差し替えメカニズムがあり、nLockTimeはその交渉が終わる前に取引が確定してしまうのを防ぐ締め切りだった。このメカニズムは手数料なしで無限に差し替えを繰り返すDoS攻撃に使えたため早々に無効化されたが、フィールドとルールは残り、今日のRBFとタイムロックへつながった。

冒頭の謎もここで解ける。Bitcoin Coreをはじめ複数のウォレットは、普通の送金にもnLockTimeを現在のブロック高で埋める。狙いは手数料スナイピング、つまりチェーンの再編成が起きたとき、マイナーが他人の確定済み取引を掘り直した過去のブロックに引き込み、手数料をもう一度さらう行動を防ぐことだ。作ったばかりの取引は現在の高さより前のブロックには入れないので、その取引を狙ってチェーンを巻き戻す動機が消える。あなたの送金に刻まれていたlocktimeの値は、その対策の痕跡だ。

nLockTimeの限界は、施錠する対象がコインではなく取引だという点にある。相続設計で使われる方法のように、1年後にようやく有効になる取引をあらかじめ署名しておくことはできる。しかしコイン自体は自由なままなので、気が変わればその前に別の取引で同じコインを使ってしまえばよい。更新可能な約束としては利点だが、コインそのものに条件を縛り付けるには、道具がもう一つ必要になる。

スクリプトは時計を見ない: CLTV

その道具が、2014年にピーター・トッド(Peter Todd)が提案したBIP-65、OP_CHECKLOCKTIMEVERIFYだ。略してCLTVと呼ばれる。このオペコードをスクリプトに入れると、条件は取引ではなくコインに付く。その出力を受け取った瞬間から、指定した時点より前には誰も使えない。

<期限> OP_CHECKLOCKTIMEVERIFY OP_DROP <公開鍵> OP_CHECKSIG

興味深いのは検証の方式だ。CLTVは現在のブロック高や時刻を確認しない。代わりに、このコインを使おうとする取引のnLockTimeフィールドが、スクリプトに書かれた期限以上かどうかを確認する。スタックが空のとき、値が負のとき、片方がブロック高でもう片方がタイムスタンプという単位の食い違いがあるとき、nLockTimeが期限に届かないとき、その入力のnSequenceが最大値でnLockTime自体が切られているとき、スクリプトは失敗する。

つまり施錠は二段階で強制される。スクリプトは「使う取引が期限以上のnLockTimeを掲げているか」だけを見て、そのnLockTimeが実際に過ぎたかどうかはコンセンサスルールが別途確認する。一見回り道に見えるが、理由のある設計だ。スクリプトの検証結果は、どのブロックで実行しても同じでなければならない。もしスクリプトがチェーンの現在の状態を直接読んだら、今日有効だった取引が再編成の後に無効になりうるし、その瞬間に検証の決定性が壊れる。時間の確認を取引のフィールドで一度包むことで、スクリプトは決定的なまま保ち、時間との照合はコンセンサス層に委ねたのだ。

CLTVには配置の妙もある。新しいオペコードを作ったのではなく、何もしない予約オペコードOP_NOP2に意味を与えた。アップグレードしていない古いノードはこの場所を相変わらず「何もしない」と読んで通すので、新しいルールは古いルールの部分集合となり、ソフトフォークとして配備できた。2015年末に有効化された。

確定からどれだけ経ったか: BIP-68とCSV

絶対的な時点だけでは足りない場面がすぐに現れた。ペイメントチャネルに必要な条件は「2027年3月1日から」ではなく「この出力が確定してから144ブロック後から」だ。チャネルがいつ閉じるかは事前に分からないのだから、基準点はカレンダーではなくその出力の確定時点でなければならない。これが相対タイムロックだ。

2016年に同時に有効化された三つのBIPがこれを作った。BIP-68は、差し替えメカニズムの無効化以来遊んでいたnSequenceフィールドを再利用する。バージョン2以上の取引でこのフィールドの最上位ビット(無効化フラグ)が消えていれば、下位16ビットが相対ロック期間になる。22番ビットが単位を決める。消えていればブロック数、立っていれば512秒刻みの時間だ。512は2の9乗なのでビット演算が安く、平均ブロック間隔の600秒に近いため二つの単位のスケールが揃う。16ビットで最大65,535ブロック、およそ1.25年まで施錠できる。

BIP-112のOP_CHECKSEQUENCEVERIFY、略してCSVはそのスクリプト側の相棒だ。CLTVと完全に同じ間接構造を使う。現在のチェーンを見る代わりに、使う取引の該当入力に書かれたnSequenceがスクリプトの要求値以上かを確認し、そのnSequenceが実際に満たされたかどうかはBIP-68のコンセンサスルールが確認する。今回再定義された予約オペコードはOP_NOP3だ。

マイナーが時刻を偽ったら: BIP-113

時間単位のタイムロックには穴が一つ残っていた。期限の判定にブロックのタイムスタンプを使うなら、タイムスタンプを書くのがマイナーだという事実が問題になる。まだ期限前の取引が手数料を付けてメモリープールに積まれているとき、マイナーにはブロックの時刻を前倒しに書いてそれらを先にさらい取る動機がある。BIP-113の言葉を借りれば、ブロックの時刻について嘘をつく歪んだ動機だ。

BIP-113は期限比較の基準を、ブロック自身のタイムスタンプから直前11ブロックのタイムスタンプの中央値、いわゆる中央値時間(median time-past)に変えた。中央値を動かすには一つではなく多数のブロックの時刻を継続的に操作しなければならないので、個々のマイナーの嘘は力を失い、この値は常に単調に増加する。副作用は些細だ。中央値は実際の時刻からおよそ1時間ほど遅れるため、時間基準のタイムロックは壁の時計よりその分だけ遅く解ける。

この部品で動いているもの

ライトニングネットワークはタイムロックの最大の消費者だ。支払いをホップごとにつなぐHTLCはハッシュ条件とCLTVの期限を組み合わせ、相手が沈黙したら中継者が期限後に資金を回収できるようにする。チャネルを一方的に閉じたとき、閉じた側の取り分が一定期間ロックされるのはCSVだ。その遅延期間が、古いチャネル状態で不正をしていないか相手が検査し異議を申し立てる時間を与える。数十万のチャネルが開いていても、正直に閉じられる限りタイムロックは発動すらしない。普段は見えず、紛争の瞬間にだけ働く安全網だ。

相続設計では二つの方式がそれぞれ違う役割を果たす。nLockTimeで事前署名した取引は定期的に更新する遺言状になり、CLTVを入れたスクリプトは「私が5万ブロックの間コインを動かさなければ、相続人の鍵でも開くようになる」という条件をコインそのものに刻む。コベナンツの議論に出てくるボールトの設計も、出金に時間遅延を強制するためにタイムロックを使う。鍵が盗まれても、ロック期間の間に復旧鍵で資金を引き戻す機会が生まれる。

タイムロックにできないこと

向きに注意する必要がある。タイムロックは常に一方向にしか施錠しない。「この時点より前には使えない」は可能だが、「この時点より後には使えない」、つまりコインの失効は不可能だ。偶然ではなく意図された制約である。一度有効になった支出条件が時間の経過で無効に変わりうるなら、再編成の際にすでに確定していた取引が遡って無効になる事態が起きる。検証が単調であること、つまり有効なものは有効であり続けることが、チェーンを安全に組み直せる条件なのだ。

だから失効が必要な場面では、錠の代わりに分かれ道を作る。期限前はAの鍵だけで開き、期限後はBの鍵でも開く二股のスクリプトだ。Aの権利は消えず、Bの経路が追加されるだけなので単調性は守られる。エスクローの返金経路も、先ほどの相続スクリプトも、HTLCの回収経路も、すべてこのパターンだ。

時計も管理者もないシステムが、時間のかかる約束を強制する。取引に付くnLockTime、コインに付くCLTVとCSV、マイナーの嘘を濾し取る中央値時間。部品はこの四つで全部であり、一つ一つはフィールドを一つ比較する程度に単純だ。その単純な部品を、決定性を壊さないように慎重に配置した結果が、ライトニングから相続まで、時間の上に建てられた応用のすべてである。

関連記事