rust: rust-bitcoin と BDK (8)
2025/10/08
BDK をメインで使うことに決めたので、ちまちまと気付いたことを書いていこう。
あとでまとめページを作ることを前提にしているので、メモ程度で良いのだ。
正直なところ DeepWiki 先生がいればだいたいのことは尋ねるとわかる。
わかるだろうけど、こういうサービスも急に使えなくなるかもしれないので、記録しておくとよいこともあるだろう。
The Book of BDK の v2.2.0 対応
Cookbook がまだ v1 系だからなあ、と思っていたら更新されていた。
ウォレットの新規作成
ウォレットを作るとなると、BIP-32 としては master private key を作ることになる。
今は descriptor というかハードコーディングした xprv を使ってウォレットの復元をしているので
ストレージなりなんなりに保存しなくてはならん。
ウォレットの DBファイルがあれば Wallet::load() の経路が成功するので、
失敗したときのルートで行えば良かろう。
bip32::Xpriv::generate(()) が最も簡単ということでお任せした。
一応 RNG は使われているそうなので最低ラインはクリアしているだろう。
心配ならカスタマイズすることもできるそうだ。
永続化
ウォレットは作ることができて、ファイルも生成された。
しかし、新しくアドレスを振り出しても次回開くとそのアドレスを忘れてしまっている。
これは .persist() して更新的なことをしないといけなかった。
mut じゃないとできないメソッドを呼び出したら .persist() する感じかな。
ブレークポイントに止まらない(vscode)
ブレークポイントに止まらない。。。
どうやら、シンボリックリンクのパスが挟まっているとダメらしい。
回避案がこちらにあって、やったらうちでも止まった。
シンボリックリンクのディレクトリの中にあるディレクトリをさらにシンボリックリンクして。。。とかだと正式名称?がわからなくなってくる。
readlink -f <ディレクトリ名> などで正式名称が取得できるので "sourceMap": {"<readlink -f . したパス>": "pwd したパス"} などでよいのかな。
cargo の後ろにオプションが付けられる
“spend” というコマンドをテストしたかったので cargo run spend .... のような感じで実行するので launch.json もそう書いた。
しかしなぜか “spend” という名前までわかっているのに失敗する。
理由は、コマンドラインオプションで “spend” のときに取る引数の数をチェックして不一致だと失敗させているのだが、
vscode のデバッガが最後の方に --message-format=json とか引数を追加していたのだ。
どうしようもないらしく、cargo の方ではなく普通に program として実行するしかなさそうだ。
署名できない
ブレークポイントを設定したかったのは、署名に失敗したからだ。
エラーではなく .sign() で false が戻ってくる。
PSBT の finalize の過程で失敗しているところまではわかった。
デバッグが始まらない(vscode)
vscode でブレークポイントに止まらない現象は sourceMap を書くことで対応できた。
その後、別の PC で引き続き作業をしようとしたのだが、そちらではデバッグが始まらない。
デバッグ開始のボタンを押すと、エラーにもならずプログレスバーっぽい青いマークが流れ続けるだけになる。

わからない。。。
翌日、動いている環境と並べて比較しよう!とやったのだが・・・動いた・・・。
CodeLLDB の再インストールがよかったのだろうか。
まだ署名できない
ブレークポイントに止まるようになったので、状況を確認できるようになった。
それについて DeepWiki に質問しながら進めている。
いろいろ変更して、builder.only_witness_utxo() や SignOptions.trust_witness_utxo = true を追加している。
しかし、まだ署名できない。
今は desc.satisfy() で L.2054 の Err の方に流れていて false が戻っている。
せめて Err(_) の中身が分かればよいのだが、確認できるんだろうか?
Arm 系なら r4 だろうけど、あれは C/C++ だけ?  そもそも戻り値といってもアドレスになりそうだ。
たどり着いたのが、bitcoin-miniscript の get_satisfaction()。
ここで Error::CouldNotSatisfy を返している。
if let Witness::Stack(stack) = satisfaction.stack { って、Rust は if の中に代入文を書けるのか。
何も考えず DeepWiki に finalize_psbt() で CouldNotSatisfy エラーになる件を質問。
ああ、頭を使っていない。。。
今回は署名する経路で呼ばれた finalize_psbt() なのであまり当てはまらない。
それに try_finalize は元々 true だ。
疑うなら witness_utxo の内容なのだが、これは正しくなっていると思いたい。
最終的には get_satisfaction() でのエラーだ。
雰囲気としては satisfaction.stack が witness stack で、ここに署名が入っているべきなのが入っていないからエラーか。
であれば、CouldNotSatisfy は最終チェックでのエラーというだけで、原因はそれより前の署名失敗にあると考えるのが自然か。
この行 が signer.sign_transaction() なので署名実行っぽい。
ここにブレークポイントを設定したのだが・・・止まっていない!
wallet に signer がいないと署名できない
wallet.get_signers(KeychainKind::External).signers().len() を出力すると 0 だった。
wallet.peek_address() でエラーになっていないから問題ないと思っていたが、wallet が最初からおかしいのか?
DeepWiki の Wallet Loading を見ていたのだが、
ウォレットの load 時は .descriptor() で xprv のような拡張秘密鍵を指定しないとダメなのだろうか。
確かにサンプルコードにはあったのだけど、なくてもアドレス生成ができていたので外していたのだ。
アドレス生成できたということは、拡張公開鍵だけ持っているウォレットということになるのか?
試したところ、アドレス生成は最初しかできないように見える。.persist() を呼び出しているのに毎回同じ鍵が作られる。
しかし next_unused_address() だからウォレットが “unused” と見なせば同じアドレスを返してもおかしくはないのか。
next_unused_address() では判定できないとしよう。
Wallet::load_wallet() でも .descriptor() を与えると、先ほどの .signers().len() は 1 になった。
そして .sign() すると成功した。
それだけのことか。。。



