hiro99ma blog

何か技術的なこと

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 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 版。
意外と短め?

scriptTreeoutput というのが 7つあるのでスクリプトだけで 7ルートあるのか?
ただ鍵情報を持つのが leafScript だけなので、このルートしか redeem できない。

script path でもマークルートを計算したら最後は internal public key と TapTweak 計算するのだが payment.p2tr() でやるのだろうか。

サンプルコードで 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.tapScriptSigPsbtInputUpdate.tapScriptSig?: TapScriptSig[] なのだけどどういう配列なのか分からない。
第1引数の inputIndexfinalizeInput() の第1引数と同じだろうからここで使うのは違うだろう。

おわりに

簡単なスクリプトだったが script path での支払いを bitcoinjs-lib で行うことができた。

< Top page