btc: BIP-341が難しい (2)
2025/01/15
はじめに
前回 P2TR の key path で署名できたので気が抜けている。
勢いで script path も!と思ったが script を merkle tree に分解するという手間があったような気がして気が乗らない。。。
とりあえずサイトの更新をしよう。
witness program と scriptPubKey
P2WPKH/P2WSH から witness program という考え方?が追加された。
- 先頭 1byte は witness バージョン(0x00, 0x51-0x60)
- 次の 1byte はデータ長(0x02-0x28)
- 最後にデータ
witness バージョンは、値としては 0-16。
スクリプトの一部なので値表現になって 0x00, 0x51-0x60 となる。
0 は 0x00 として単独で成り立つが、1 はスタックするデータ長とデータで 0x01 0x01 と冗長になるのを嫌ったのか専用命令がある。
冗長な表現を使うと bitcoind
のチェックで拒否されるかも?
ともかく witness バージョンはそういう値である。
P2WPKH/P2WSH はバージョン0、P2TR はバージョン1だ。
送金先としてアドレスを表現するときは文字列だがトランザクションデータでは scriptPubKey というデータになっている。
witness な送金先については scriptPubKey に witness program を使うようになっている。
スクリプトは複数のオペコードとオペランから構成されるが、最初はスクリプト全体の長さを表す命令から始まる。
scriptPubKey もバイナリデータとしてはスクリプト長から始まる。
実装していて、”scriptPubKey” といったときに先頭のスクリプト長も含んでいるのかどうかということが気になってしまった。
vin_count などは CompactSize型 なので 0x01 などと値を書き、スクリプト形式の表現にはなっていない。
同じスクリプト系の scriptSig もそうだと思うが最初のデータはスクリプト形式のデータ長だ。
まあ、スクリプトとして処理するから最初は全体を載せないと、という考え方もあると思うがそれなら CompactSize型でもよかったはずだ。
なのでスクリプトの先頭にあるデータもスクリプトの一部だと考えていた。
しかし、説明文に「scriptPubKey には witness program を使う」と書いてあったりする。
witness program は定義に全体のデータ長は入っていない。
また、scriptPubKey を使ったハッシュ計算をするときの説明に “serialized as script inside CTxOut
” と書いてある。
CTxOut.scriptPubKey の
CScript のところを見てもよくわからなかったが、なんとなく入っていない気がする(わざわざ serialized as script と書いているくらいだし)。
ならば最初のデータ長はデータに含めないという考え方が一般的なんだろうか。。。
そういうことをもごもご考えてしまったのだ。
どうせ全体のデータ長はどこかで持っているのだからデータとして持つ必要がない、と言われてしまえばそれだけなんだけどね。
C言語 でバッファを持つ
C++ だと std::vector
などがあるが、C言語でやりたかったら自分で実装しないといけない。
ライブラリも探せばあるのだろうが、そんなに大したことをしたいわけではない。
- バッファの先頭アドレス
- バッファサイズ
sigMsg
のようにデータをどんどん連結させる操作をするなら write インデックスがあるとよいか。
リングバッファなら read と write の両方を用意するが、今回は読込みは 1回だけだからなぁ。
いや、そういう考え方がよくないのかもしれない。
ネットワーク経由で受信しながら処理するのであれば必要な分だけ読み込んで処理したいだろうし、
送信するならエンコードしながら送信したくなるだろう。
そういうときは write だけでなく read も管理したくなるか。
そう考えていくと stream になりそうだ。
FILE
をメモリに割り当てて使うことはできるんだっけ?
できたとしても、やりたいことに対して重たすぎる気がするのだが、どうなんだろう。
ccan はライブラリ集みたいなもので、ライセンスはそれぞれに違うので仕事で使うには注意が必要かもしれない。
しかし一部だけ切りだして使えるので条件に合えば魅力的だ。
今回で言えば membuf になるのかな。
test.c を見ると、最初に確保したサイズより大きいデータを add したりしていそうだ。
他にも tal でツリー構造的な malloc … というと面倒そうだが、関連して malloc したメモリを一気に free したりできた気がする(弱気)。
Bitcoin の他の勉強
script path をやらないとしたら何をやるか。
逃避がてら考えよう。
マイニング
ほどほどに調べて JavaScript でコードを書いて動作確認中。
regtest では動くのだけど、ちゃんと複数ノードで動作するのかが分かっていない。
もしかしたら testnet3 であれば難易度が低くて成功するかも?と思ったがそんなに甘くない。
だったらもう mainnet でやっても変わらんね、ということで動かしている。
もちろん成功したことはないというか、nonce が見つかったことが無いので正しいかどうかの判定ができていない。
C言語とかで書き直せば計算効率は上がるだろうけど、何億分の 1かの確率が何億分の 2とかになる程度だと思う(よくても)。
よってこれは放置だ。
まじめにウォレットアプリを実装
P2WPKH と P2TR key path が動くなら、あとは HDウォレットがあればウォレットアプリくらいは作ることができるだろう。
作ったからといって私はたぶん使わないし、他の人も使わないだろう。win win だ(?)。
ただ、ウォレットアプリを自分で作ろうと思えば作ることができるというのは Bitcoin のよいところである。
「私のお金は私が動かす!」という感じがするしね。
他のウォレットだとそういうのできない。
ドコモのアプリとか WiFi 使ってるとログインできなくて何考えてるんだ!と思ってもどうしようもない。
ハードウェアなウォレット
アプリのウォレットは amount を集めて見せてくれたりするのでウォレットって感じがする。
ハードウェアウォレットは、HDウォレットの鍵管理と署名・検証をするのがメインだから「サイナー(signer)」って気持ちになるね。
そのハードウェアウォレットだが、まじめにセキュリティの保護をしていくと値段が高くなっていく。
有名どころの Trezor を仕事で使っていたけど、
PC と接続するドライバをネットで検索したら一番上に出てきたのが偽サイトだったことがあるし、
偽物を買ってしまうということもあるし、
物理的なハッキングという目にあったこともある(私があったのは偽サイトだけだが)。
そういうのをすっぱり諦め、物理的に奪われたらダメだけどネットにつながないことでソフトウェア的に鍵情報を奪われることだけは避けたい、
というのであれば HDウォレットや楕円曲線の secp256k1 やシュノア署名などができれば組み込み機器でも十分動かせる。
Blockstream 社の Jade なんかは ESP32 にも対応している(売っているのは M5 Stick FIRE ベースだったか)。
機能としては動かせるけど、画面に表示したり USB 制御したり、カメラが無いと入力が面倒だったりと、計算機能よりは UI で苦労すると思う。
ただまあ、課題としては悪くないかもしれない。大変だけど。
ネットワークにつながっていない分、署名しろと言われたデータが本当に署名して良いデータかどうかを判定することはできない。
なんかこう、知られていないような手法で鍵を分析されてしまったりするかもしれない。
そこまで考えるとハードウェアウォレットを動かすアプリも自分で作らないとなあ、大変さがさらに増すなあ。
Bitcoinスクリプトのデバッグ
Bitcoin スクリプトを自作すると、そのデバッグが大変だ。
デバッガがあるのは知っているが、使い方がどうも分からずにいつも力業でデバッグしていた。
すなわち bitcoind
にログを追加して regtest などで実際にスクリプトを処理させてスタックにどういう値が載ったかを見るのだ。
もっとスマートにデバッグしたいという思いはあるので、これは候補に挙げておこう。
思いつかない
そのくらいだ。
そう簡単には思いつかないのだよ。
特に Bitcoin なんてどうやってもお金を払う方向がないと成り立たないので苦手としているのだ。
testnet などだとお金がかからないので気楽なんだけど、そもそもお金を使うのに慣れていない。
そういうわけで、詳細やライブラリなどに思考が行ってしまうのだった。
・・・潔く script path をやろう。
BIP-341 再び
script path も BIP-341 が大きく関わる。
BIP-342 も関係しそうな気がするが、まずは BIP-341 だけでよかろう。
この辺りか。
サンプルのスクリプトが無いとわかりづらそうなのでここから持ってこよう。
HASH160 <revokehash> EQUAL
IF
<Bob's pubkey>
ELSE
"24h" CHECKSEQUENCEVERIFY DROP
<Alice's pubkey>
ENDIF
CHECKSIG
スクリプトの分解
初っぱなで英文に詰まってしまった。 “decide between A and B”=AかBのどちらかを選ぶ、なのでスクリプトを分けるか分けないかの話のはずだ。
When deciding between scripts with conditionals (
OP_IF
etc.) and splitting them up into multiple scripts (each corresponding to one execution path through the original script), it is generally preferable to pick the latter.
ChatGPT氏にお願いしよう。
スクリプトで条件文(OP_IFなど)を使用する場合と、元のスクリプトの各実行パスに対応する複数のスクリプトに分割する場合を比較すると、一般的には後者(複数のスクリプトに分割する方法)を選ぶ方が望ましいです。
だいたいそうだけど、分けてどうするのか。
それと、分けるにしてもどう構成したら良いのか。。。
merkle tree を作るのだから、分岐ごとに tree も分岐させて leaf にスクリプトを突っ込むのか。
OP_IF
が 1つのサンプルはいくつか見つかったけど 2つあるサンプルが見つけられないのだ。