hiro99ma blog

rust: rust-bitcoin と BDK (6)

2025/10/02

BDK でトランザクションを作る

前回は BDK の example を動かしただけだった。
そのコードを改造して rust-bitcoin で作ったように署名したトランザクションを作ろうとしている。
今のところ、署名するコードを呼び出して失敗するところまでやってきた。

Cookbook の頃とメジャーバージョンが違うせいか、Transaction Builder のような書き方ではなく、 build_tx() を別にするようだ。
TxBuilder in bdk_wallet - Rust の例を見ていると 絶対に別にしないとダメということではないようだが、まだ私には理屈が分からん。

ダミーUTXOを追加したい

今のコードを実行すると “UTXO not found in the internal database for txid” とエラーになる。 add_utxo() なのだが、 unspent判定 でエラーになってるんじゃなかろうか。
まあ、オールゼロだしねぇ。

しかし Coinbaseトランザクションもあるので、指定できないこともないが、 “wallet” という意味では対応しなくてもおかしくないのか。 rust-bitcoin でやればよいだけだし。

add_unspendable() は 失敗はしないが finish() のときに “Insufficient funds” になる。
やっぱり “wallet” として処理しているので UTXO であることが大切なのだろう。

もう1つ add_foreign_utxo() もあるが こちらは psbt.Input を引数に取るなど UTXO の存在が明らかになっていないといけないようだ。
unit test で作りたいときはこっちがよいのかな?
いや、通常は add_utxo() でやりたいだろうからそうではないな。。。

困ったときの DeepWiki である。

なるほど、それなりに Input を作る方法はあるそうだ。
今回はこのように した。

これで sign() は成功したのだが、なぜか vout に指定していない 818 sats の出力がある。

......
    output: [
        TxOut {
            value: 818 SAT,
            script_pubkey: Script(OP_PUSHNUM_1 OP_PUSHBYTES_32 882d74e5d0572d5a816cef0041a96b6c1de832f6f9676d9605c44d5e9a97d3dc),
        },
        TxOut {
            value: 14999000 SAT,
            script_pubkey: Script(OP_PUSHNUM_1 OP_PUSHBYTES_32 882d74e5d0572d5a816cef0041a96b6c1de832f6f9676d9605c44d5e9a97d3dc),
        },
        TxOut {
            value: 5000000 SAT,
            script_pubkey: Script(OP_PUSHNUM_1 OP_PUSHBYTES_32 7b40f5890a1f8efbfdabbce9b18a7b09a06c3e5c3512a7a5d278a2e018d6d242),
        },
......

手数料を指定

全 INPUT が 20_000_000、送金額が 5_000_000、お釣りが 14_999_000
手数料は 1_000 になって終わりのつもりだったが、これは手数料を 182 sats にしたからか? と思ったが、vsize() は 197 だった。
vout が増えたせいだと +15 では済まないから違うか。

署名したせいかと思ったがコメントアウトしても vout は 3つ。
となると怪しいのは add_foreign_utxo() だが、UTXO を追加したからといって vout が増えるのもおかしかろう。

困ったときの DeepWiki である(2回目)。

なるほど、手数料設定が可能なのか。
ならばお釣りアドレスをわざわざ用意しなくても wallet 側で用意してくれるはずだ。
つまりこの 818 sats は wallet が用意したお釣りだったのだろう。

今回のサンプルでは手数料を 1,000 sats 固定にしたいので .fee_absolute() で指定するとよい。
そうすると vout は 2つになって追加された方は 14_999_000 になった。

手数料率を指定

固定の手数料を使うことは少ないだろう。
だいたいは手数料率の相場を確認しつつ設定するだろう。

.fee_absolute(1000)(実際は型変換しているが) を削除すると 14_999_861、つまり手数料は 139 sats になった。
vsize() が 154 なので 154 sats になるかと思ったのだが、なんでだろう?
FeeRate::from_sat_per_vb_unchecked(1) などとしても同じだ。
他のツールでトランザクションで確認しても vsize が 154 なのは間違いないのだ。

署名をする前に設定しているからかと考えた。
.sign() の前にトランザクションを確認すると 137 vbyte になったが、構造が非 witness になっていたので参考にならない。

あ、+15 は vsize ベースなので 4分の1 くらいと考えると 60 ほどだ。 つまり、署名が入っていないのと、非witness になっているのとが合わさってこの値になったのかもしれない。
ダミーの UTXO にしているのも影響がありそうだ。

まず UTXO から。
これは script_pubkey が P2TR であればよい。
is_p2tr()true だったので OK。

では、署名が考慮されていないということ? と訊いてみたが、create_tx()の段階で計算されるらしい。
これは内部の話のようで外側から create_tx() を呼べるというわけでもなさそう。

では署名後の処理に何かあるのかと思ったが Cookbook 的には sign() して extract_tx() でよさそう。 ただメジャーバージョンが変わっているからそのまま信用してはいかん。

あまり考えず、やりたいことをそのまま質問しよう。

「satisfaction_weight: 署名時の重み」って、witness だから追加分とかそういうやつだろうと思ったけどそのまま使われるの?
テストコードを見ると wallet から取得しているようだ。
そんな重要パラメータだったのね。。。

まねして weight を取得して使うとお釣りが 14,999,845 sats、つまり手数料は 155 sats になった。
vsize が 154 なので妥当なところだ。

writer: hiro99ma

 < Top page

コメント(Google Formへ飛びます)

GitHub

X/Twitter

Homepage

About me