Reading text-only. View on full site →

鍵があっても金庫は見つからない

2026-10-14 · articles · ja

シードフレーズが復元するのは鍵だけだ。その鍵からどのアドレスを作るかは、出力ディスクリプタという一行が決める。同じ鍵三つから最初のアドレスが五通りに分かれる実験、ディスクリプタの読み方と八文字のチェックサム、マルチシグで必要数のシードが揃っても金庫を開けられない場合、そしてバックアップの方法までを整理する。


シードフレーズが三つある。一つはあらゆるビットコインウォレットがテストベクトルとして使う abandon で始まるフレーズで、残りの二つも BIP-39 のテストベクトルに載っている公開フレーズだ。それぞれからマルチシグ用の拡張公開鍵を取り出し、二つの署名があれば使える 2-of-3 の金庫を作る。鍵は同じ三つだ。ところがこの金庫の最初の受取アドレスは一つではない。

組み立て方最初のアドレス
ネイティブ SegWit、鍵を整列bc1qm43n7nnev58aj3nrznz2xscgv98t7gxycq5pmp20a5vzfp5t0q2s7r6twa
ネイティブ SegWit、鍵の順序 A・B・Cbc1qp38kn2cpud9u5z8px3ga9vtuwdp3n0cc6xly6td45u5g6nsgcl9sagut92
ネイティブ SegWit、鍵の順序 C・B・Abc1qlklm7s3d48twp5rj4q39shf74ccfty96nyqc5gvm9y2fzxrdrdcs8t36am
ラップド SegWit、鍵を整列3HTVa2FKu5zq9e95qEoJX8Cy5vBo5y9Ljc
レガシー P2SH、鍵を整列39tmmWUjbK9SeTK4L6vjZi7kEkPx4DjcJb

五つのアドレスは Bitcoin Core 31.1 と別のライブラリでそれぞれ計算し、同じ値を得た。変わったのは鍵ではなく組み立て方だ。スクリプトをどの形式で包んだか、鍵を整列したか並べた順に入れたか。鍵の順序だけでも六通りある。

ここで、この金庫の鍵の一つを預かる人が火事でシードを失ったとしよう。残るシードは二つ。2-of-3 だから署名は足りる。ところが三人目の公開鍵がなければ、上のどのアドレスも計算し直せない。アドレスは三つの公開鍵をすべて入れたスクリプトのハッシュだからだ。コインを使える署名は手元にあるのに、コインがどこにあるかわからない。わかったとしても、使うときに示さなければならないスクリプトを作れない。

この空白を埋めるのが出力ディスクリプタだ。どの鍵を、どの経路で、どのスクリプトに入れてアドレスを作るかを書いた一行の文字列である。

単語が復元するのは鍵だけだ

シードフレーズは秘密鍵の木の根を収めている。その根から鍵をいくらでも育て直せるが、それらの鍵でどんなアドレスを作ってきたかは単語のどこにも書かれていない。

ディスクリプタを定めた BIP-380 の動機の節は、まさにこの問題から始まる。ウォレットのバックアップは従来秘密鍵だけで、今は主に BIP-39 ニーモニックの形をとる。しかし SegWit が新しい出力の種類を加えてからは、それだけでは足りなくなった。秘密鍵だけを渡された復元ウォレットは、どの種類のスクリプトとアドレスを作ればよいかを知りえず、それがバックアップの復元や監視専用ウォレットへの書き出しでウォレット間の非互換を生んだ、というのだ。

導出パスが問題をさらに大きくする。BIP-44、49、84 は出力の種類ごとに標準パスを定めたが、すべてのウォレットがそのパスに対応し、使っているわけではない。同じ12単語を別のウォレットに入れたら残高が0と表示される、という出来事はたいていここから生じる。鍵は合っているのに、ウォレットが別のパス、別のアドレス形式を探したのだ。

この空白を最初から意識していたウォレットもある。Electrum はシードフレーズにバージョン番号を入れる。フレーズのハッシュ値の先頭部分が、どの導出方式に従うかを示す仕組みだ。Electrum の文書は BIP-39 をこう批判する。BIP-39 のシードにはバージョン番号がないので、ソフトウェアは鍵とアドレスの作り方を常に知っていなければならない。いくつもの導出方式を一つずつ試すのはきわめて非効率で、将来のウォレットが過去の方式をすべてサポートするという前提に頼っている、と。

もう一つの回り道は、拡張公開鍵の頭の部分を変えることだった。xpub の代わりに ypub、zpub と書いて、ラップド SegWit かネイティブ SegWit かを示す方式だ。BIP-380 はこれをレイヤー違反と呼ぶ。鍵の導出とスクリプトの種類の意味は分けられるべきなのに両者を一つに束ね、しかも特定のパスと特定のスクリプト形式にしか通用しない、という理由である。

ディスクリプタを一行読む

ディスクリプタはこの情報を人が読める短い言語で書く。abandon テストフレーズの BIP-84 アカウントはこう書く。

wpkh([73c5da0a/84h/0h/0h]xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V/0/*)#afwvtk2s

外側の wpkh(...) はスクリプト式だ。公開鍵一つのハッシュでロックするネイティブ SegWit の出力を作れ、という意味である。角括弧の中の 73c5da0a はこの鍵の出発点となったマスター鍵の八桁のフィンガープリントで、続く 84h/0h/0h はそこからこの xpub まで降りてきたパスだ。h は強化導出の印で、古い表記ではアポストロフィを使う。xpub の後ろの /0/* はこの xpub の0番の枝の子をすべて順に使えという意味で、末尾の #afwvtk2s がチェックサムである。

このディスクリプタの最初のアドレスは bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu で、BIP-84 の文書のテストベクトルと一致する。外側の関数だけを替えると、同じ xpub からまったく別のアドレスが出てくる。

スクリプト式最初のアドレス
pkh(...)1JaUQDVNRdhfNsVncGkXedaPSM5Gc54Hso
sh(wpkh(...))3GtVZYzsKF6Feikdjd4bDyPdAiyeHANY9b
wpkh(...)bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu
tr(...)bc1p8knh0enfv47gmpuf66528zd4jtkgjq4sv5w5l2gqwgk8exu2ynns9g8c9m

アドレス形式の違いが鍵の違いではなく、この外側の関数の違いであることが一目でわかる。ディスクリプタはアドレス一つではなくアドレスの一覧全体を記述し、その一覧を作るのに必要なものを漏れなく含む。だから秘密鍵を含まない公開情報だけのディスクリプタを取り込めば、残高と履歴を見る監視専用ウォレットになる。

ウォレットは受取アドレスとお釣りアドレスを別の枝で作る。慣例では0番が受取、1番がお釣りだ。以前はディスクリプタを二行書く必要があったが、BIP-389 は /<0;1>/* のように二つの枝を一行で書く表記を提案した。文書はまだ草案の段階だが、Bitcoin Core は29.0からこの表記を受け付け、受取用とお釣り用の二つのディスクリプタに展開して保存する。

マルチシグで欠かせないもの

マルチシグのディスクリプタは、この構造に鍵を複数入れたものだ。冒頭の 2-of-3 の金庫はこうなる。

wsh(sortedmulti(2,[73c5da0a/48h/0h/0h/2h]xpub6DkF.../<0;1>/*,[b8688df1/48h/0h/0h/2h]xpub6FQy.../<0;1>/*,[28645006/48h/0h/0h/2h]xpub6DnE.../<0;1>/*))

省略した部分はそれぞれ111文字の xpub だ。wsh はネイティブ SegWit のスクリプトハッシュ、2 は必要な署名の数、その後ろが三人の署名者の鍵である。BIP-383 によれば、xpub が複数あると三つの鍵は同じ子番号で並んで導出される。5番のアドレスは三つの xpub それぞれの5番の子公開鍵から作られる。

sortedmulti と multi の違いが、最初の表でアドレスを分けた。multi はディスクリプタに書かれた順に鍵をスクリプトに入れ、sortedmulti は導出した公開鍵を辞書順に整列してから入れる。整列の方式は2015年に BIP-67 が P2SH マルチシグのアドレスを決定的にするために定めたものだ。整列すれば鍵をどの順で入力したかを覚えておく必要がなく、整列しなければ順序までバックアップしなければならない。スクリプト式によって入れられる鍵の数も違う。sh の中ではスクリプト要素一つの520バイト制限のため圧縮公開鍵15個まで、wsh の中では20個までだ。

冒頭の問いに戻ろう。シードを失った人がいても、ディスクリプタが残っていれば残りの二人は金庫を開けられる。ディスクリプタに三つ目の xpub が入っているのでアドレスをすべて作り直せ、残る二つのシードで署名すればよい。ディスクリプタがなければ三つ目の公開鍵を取り戻す手段はない。すでに一度お金を引き出したアドレスなら、ブロックチェーンにそのアドレスのスクリプトが残っていて三つの公開鍵が見える。だがそれはそのアドレス一つの子鍵にすぎず、ほかのアドレスの鍵ではない。

逆に三つのシードがすべて残っていれば、ディスクリプタがなくてもたいてい取り戻せる。BIP-48 のような標準パス、よくあるスクリプト形式、整列の有無を組み合わせて順に試せばよい。非効率だが可能だ。ディスクリプタが本当に決定的になるのは、必要数だけしか残らなかったときである。マルチシグが備えようとした、まさにその状況だ。

八文字のチェックサム

ディスクリプタ末尾の # の後ろの八文字は、bech32 アドレスと同じ文字集合を使う誤り検出符号だ。BIP-380 はその性質をこう示す。記号一つの誤りは必ず検出する。記号二つか三つの誤りも、49,154文字以下のディスクリプタなら必ず検出する。ランダムに壊れたときに検出をすり抜ける確率は2の40乗分の1である。

チェックサムは鍵とパスの数字まで守る。先のディスクリプタでアカウントパスの 0h を一つだけ 1h に変えて Bitcoin Core に入れると、こう拒否される。

Provided checksum 'afwvtk2s' does not match computed checksum 'mu5cevdr'

手で書き写したディスクリプタに誤字があれば、ウォレットが受け入れる前に知らせてくれるということだ。裏を返せば、チェックサムは文字をそのまま見る。同じパスを 84'/0'/0' と書けばチェックサムは wc3n3van になり、84h/0h/0h と書けば afwvtk2s になる。Bitcoin Core は26.0でアポストロフィの代わりに h を出力するよう変えたとき、この変更がチェックサムを変えるとリリースノートに書いた。ディスクリプタは解釈して書き写さず、ウォレットが書き出した文字列をそのまま保管する。

0.17から30.0まで

ディスクリプタは標準文書より実装が先だった。2018年10月3日に出た Bitcoin Core 0.17.0 で、Pieter Wuille が作ったディスクリプタが scantxoutset コマンドの引数として初めて使われ、言語を説明する参照文書も一緒に入った。2021年1月14日の0.21.0は、ディスクリプタで出力を管理する新しいウォレット形式を実験機能として加えた。BIP 番号380から386が Wuille と Ava Chow の名前で割り当てられたのはその年の6月27日で、実装から3年近く後のことだ。

2022年4月25日の23.0からは新しく作るウォレットが標準でディスクリプタウォレットになり、Taproot の単一署名アドレス用の tr() ディスクリプタが自動で入るようになった。2025年10月10日の30.0は最後の段階を踏んだ。旧形式のレガシーウォレットはもう作ることも開くこともできず、migratewallet でディスクリプタウォレットに移す道だけが残った。Bitcoin Core の中で鍵の束がウォレットだった時代は終わり、ウォレットは今やディスクリプタの一覧である。

ハードウェアウォレットは何を確かめるか

ディスクリプタが偽造されたら何が起きるか。マルチシグの設定手順を標準化した BIP-129 は危険を二つに分ける。設定を書き換えられる攻撃者は、ユーザーをだまして誤ったアドレスに送金させ、資金を盗むか人質に取ることができる。書き換えられず覗き見るだけの攻撃者は、ウォレットの取引を監視でき、プライバシーが崩れる。

一つ目の危険はハードウェアウォレットの問題だ。コンピュータが感染していれば、コンピュータが表示するマルチシグのアドレスを信じる理由はない。三つ目の鍵を攻撃者のものにすり替えたディスクリプタでも、金庫は相変わらず 2-of-3 に見えるが、攻撃者はほかの署名者を一人だますだけで開けられる。BIP-388 はこれをウォレットポリシーの登録で解く。初めて使うとき、ハードウェアウォレットが自分の画面にポリシーとすべての鍵を表示し、ユーザーは印刷したバックアップのような信頼できる原本と照らし合わせて承認する。それ以降、機器は登録されたポリシーでのみアドレスを作り、お釣りを判別する。PSBT で機器がお釣りの出力を自分のものと確認する仕組みがマルチシグで成り立つには、この登録が先になければならない。

BIP-388 は xpub を @0、@1 のような仮の記号に置き換えて、画面に表示する量を減らす。文書が挙げる実装は、Ledger のビットコインアプリ2.1.0、BitBox02 ファームウェア9.15.0、Blockstream Jade 1.0.24 以降だ。

時間の入ったディスクリプタ

ディスクリプタ言語は拡張できるように設計されている。その大きな枝がミニスクリプトだ。BIP-379 としてまとめられつつあるこの言語は、ビットコインスクリプトの部分集合を構造的に書き、ウォレットがどんな条件でコインを使えるかを分析して署名できるようにする。Bitcoin Core は25.0で P2WSH のミニスクリプト出力を使えるようにし、26.0で Taproot の中に広げた。

この組み合わせで、ディスクリプタ一行に時間の条件が入る。普段は自分の鍵一つで使い、コインが一定期間動かなければ家族の鍵でも使える、といった形だ。Wizardsardine が2022年末に最初に出した Liana ウォレットがこの構造を使う。ミニスクリプトの older()、つまり BIP-112 の相対タイムロックで復旧用の鍵をロックし、時計はコインごとにそのコインを受け取ったブロックから動き出す。だから決められたブロック数の内に一度ずつコインを動かして時計を戻さなければならない。

こうしたウォレットでは、ディスクリプタはバックアップの付属物ではなく本体だ。時間の条件と相続人の鍵がディスクリプタにしか書かれていないからである。Liana の利用文書も、スクリプトのテンプレートであるディスクリプタと秘密鍵の両方をバックアップする必要があり、ディスクリプタがなければバックアップから復旧できないと書いている。

バックアップはどうするか

ディスクリプタは秘密ではないが、公開してもいけない情報だ。秘密鍵が入っていないので、ディスクリプタだけではコインを使えない。しかし xpub がすべて入っているので、ディスクリプタを見た人は金庫のすべてのアドレスと残高と履歴を見る。シードほど厳重に隠す必要はないが、どこにでも置いてよいものでもない。

Sparrow ウォレットは、必要署名数が鍵の総数より小さいマルチシグを保存するとき警告を出す。このウォレットを復元するには少なくとも必要数のシードとすべての xpub が要るという内容で、xpub のためにウォレットファイルか出力ディスクリプタをバックアップするよう勧める。その画面からディスクリプタを QR コード入りの PDF として保存できる。

マルチシグなら、ディスクリプタの写しを鍵ごとに一つずつ置く。シードフレーズを保管した場所ごとにディスクリプタも置いておけば、どの鍵が失われても、残った鍵のそばに地図がある。ディスクリプタを一か所にしか置かなければ、そこが新たな単一障害点になる。マルチシグを使う理由を考えれば筋が通らない。

保管したディスクリプタは一度検証しておく。別のウォレットソフトに監視専用として読み込み、最初の受取アドレスが使っているウォレットの最初のアドレスと同じかを確かめる。チェックサムは書き写すときの誤字を捕まえてくれるが、最初から間違っていたディスクリプタは捕まえられない。アドレスが同じなら、その地図は正しい。BIP-129 が定めたマルチシグ設定の記録が、四行目にウォレットの最初のアドレスを書かせるのも同じ理由だ。

相続計画でも同じである。相続人に残すのがシードフレーズだけなら、相続人は鍵を受け取っても、どのウォレットでどのパスとどの形式でアドレスを探せばよいかを突き止めなければならない。単一署名ならディスクリプタ一行でその手間が消え、マルチシグならその一行があるかどうかに金庫がかかっている。

鍵と地図

シードフレーズは鍵だ。鍵は錠を開けるが、錠がどこにあるかは教えない。単一署名のウォレットでは、標準パスをいくつか手探りすればたいてい錠が見つかるので、この違いが見えにくかった。マルチシグや時間の条件が入ったウォレットでは、手探りできる道が多すぎるか、まったくない。

ディスクリプタはその道を一行で書いた地図だ。冒頭の五つのアドレスは、同じ三つの鍵で作れる金庫のほんの一部にすぎない。どの金庫にコインを入れたかは、ディスクリプタだけが知っている。

Read on the full site: https://learn.txid.uk/ja/articles/bitcoin-output-descriptors/