btc: bitcoinjs-lib を使う (4)
2025/01/24
はじめに
P2TR を扱うのに node.js で使える bitcoinjs-lib を使ってみよう。 前回は key path だったので、今回は script path を扱う。
server=1
txindex=1
regtest=1
rpcuser=user
rpcpassword=pass
fallbackfee=0.00001
- 手順
rm -rf ~/.bitcoin/regtest/
bitcoind -regtest -daemon
sleep 5
bitcoin-cli -regtest -named createwallet wallet_name=test load_on_startup=true
addr=`bitcoin-cli -regtest getnewaddress`
bitcoin-cli -regtest generatetoaddress 110 $addr
- npm install
npm install bip32@v4 bitcoinjs-lib@v6 ecpair@v2 ky@v1 tiny-secp256k1@v2
npm install -D @types/node eslint @eslint/js typescript@v5 typescript-eslint
script path しか使えないようにする場合
BIP-341 にこういう記述がある。
In order to avoid leaking the information that key path spending is not possible it is recommended to pick a fresh integer r in the range 0…n-1 uniformly at random and use H + rG as internal key.
H + rG って r を秘密鍵にするんだから、それを知っている人は key path で解けてしまうんじゃないの?と思ったのだ。
もしかしたらこの人なら教えてくれるのではと ChatGPT 氏に訊いてみた。
うーーん・・・力強く説明してくれるのだが納得できていない。。。
彼/彼女 は調子が良いので、こちらから具体的な返信をすると「そうそう、そうなんです!」みたいなことをいってくるので心配になるのよね。
無料版を使っているからだろうか。
Q = P + tG なので H + rG もその部分の話と混同していたが、”as internal key” なので p の話だ。
単純に、internal private key を作らずにいきなり internal public key のデータだけ作りましょう、ということだ。
rG は private key r の public key だが、 H + rG は少なくとも r の public key ではない。
何らかの private key に対応するかもしれないが、それは「public key から private key は復元できない」ということになっているのと同じことだ。
そういう解釈だろう。
複数人で管理するスクリプトで、internal public key に H + rG を使ったように見せかけて rG を使うようなあくどいことをされるかもしれない。
internal public key を決めるときはみんなで決めようね。
script path サンプル
bitcoinjs-lib の script path サンプルというかテストというかがこちらだ。
いくつかあるのだが、こちらは OP_CHECKSIG 版。
意外と短め?
scriptTree に output というのが 7つあるのでスクリプトだけで 7ルートあるのか?
ただ鍵情報を持つのが leafScript だけなので、このルートしか redeem できない。
script path でもマークルートを計算したら最後は internal public key と TapTweak 計算するのだが payment.p2tr() でやるのだろうか。
Taptreeを作るpayments.Paymentを作る- 送金してもらう場合
payments.Payment.addressのアドレスに送金してもらう
- 解く場合
- 2番目と同じ内容に
redeemを追加してpayments.Paymentを作る- 2番目を作るときに予め
redeemを設定しておいても良い
- 2番目を作るときに予め
- PSBT でトランザクションを作る
signInput()でスクリプトに署名するfinalizeInput()でトランザクションを確定させる
- 2番目と同じ内容に
サンプルコードで PSBT の addInput() と updateInput() を分けているのが気になる。
addInput() で設定できるので少なくともここにあるコードであればまとめてしまいそうなものだが。
そしてやはり気になるのは署名の部分だ。
signInput() に鍵を渡すだけの場合と finalizeInput() でカスタマイズする場合がある。
Eunovoのサンプル は両方使っている。
<PUBKEY> OP_CHECKSIG の場合は鍵だけで済んだ。
最初に試したのは Eunovoのサンプル の最初にあった OP_SHA256 <hash> OP_EQUALVERIFY <PUBKEY> OP_CHECKSIG で試していたので sendrawtransaction でエラーが返ってきていた。
おそらく、終わりが <PUBKEY> OP_CHECKSIG で、それより前は何もないか OP_CSV のように自動的に処理してスタックから消えていくようなスクリプトであればカスタマイズは不要なのだろう。
bitcoinjs v6.1.7 の finalizeInput() で戻り値は FinalTaprootScriptsFunc の方だろう。
type FinalTaprootScriptsFunc = (
inputIndex: number, // Which input is it?
input: PsbtInput, // The PSBT input contents
tapLeafHashToFinalize?: Buffer, // Only finalize this specific leaf
) => {
finalScriptWitness: Buffer | undefined;
};
カスタムするとこんな感じ。
input.tapScriptSig は PsbtInputUpdate.tapScriptSig?: TapScriptSig[] なのだけどどういう配列なのか分からない。
第1引数の inputIndex は finalizeInput() の第1引数と同じだろうからここで使うのは違うだろう。
おわりに
簡単なスクリプトだったが script path での支払いを bitcoinjs-lib で行うことができた。