PSBTは、マルチパーティでBitcoinのトランザクションを構築したり、トランザクションの署名にハードウェアウォレットと連携したりする際に、共通のデータフォーマットを定義した仕様で、2017年に定義され、これまでいくつか修正が加えられてきた↓
techmedia-think.hatenablog.com
そして、先日PSBTのVersion 2がBIP-370として定義された↓
https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki
BIP-174のPSBTは、グローバルタイプで未署名のトランザクションを定義(PSBT_GLOBAL_UNSIGNED_TX
)していたので、後からそのトランザクションにインプットやアウトプットを追加することができなかった。この問題に対応するため、BIP-370で新しいPSBT Version 2を定義し、PSBT_GLOBAL_UNSIGNED_TX
を廃止し、各インプットとアウトプットの情報をマップ形式で保持するようPSBTのフォーマットを変更している。この変更のため、BIP-174とBIP-370は互換性はないが、BIP-370→BIP-174への変換は可能になっている。
もともとBIP-174はPSBT Version 0で本来ならVersion 1になるべきが、どうもBIP-174をVersion 1と呼び、BIP-370がVersion 2と呼称されてることから、混乱を避けるためVersion 2にしたらしい。まぁこういう仕様はバージョン1から定義した方がいいね。
以下、BIP-370の意訳↓
イントロダクション
概要
このドキュメントはBIP 174で定義されているPartially Signed Bitcoin Transactionフォーマットの2つめのバージョンを提案するもので、PSBTの作成後にインプットやアウトプットの追加を可能にする。
Copyright
このBIPは2-clause BSD licenseが適用される。
動機
BIP 174で定義されているPartially Signed Bitcoin Transaction Version 0では、新しいインプットとアウトプットをトランザクションに追加することができない。固定のGlobal Unsigned Transactionは変更できないので、これによりインプットやアウトプットが追加できなくなっている。PSBT Version 2は、この問題を修正することを目的としている。
さらに良い副作用として、あるインプットやアウトプットのすべての情報が、<input-map>
および<output-map>
で提供されるようになる。Version 0では、インプットやアウトプットのすべての情報を取得するのに、<input-map>
/<output-map>
とGlobal Unsigned Transactionの2つの異なる場所から探す必要があった。PSBT Version 2では関連数すべての情報が1つの場所に移動している。
仕様
PSBT Version 2(PSBTv2)は、新しいフィールドとフィールドの包含/除外要件のみを定義する。
PSBT_GLOBAL_UNSIGNED_TX
はPSBTv2では除外されなければならない。PSBTv2には、PSBT_GLOBAL_VERSION
が含まれ
バージョン番号は2*1をセットする必要がある。
PSBT Version 2の新しいグローバルタイプは以下の通り:
名称 | キータイプ | キーデータ | キーデータの定義 | 値データ | 値データの定義 | 含める必要のあるバージョン | 除外する必要のあるバージョン | 含めることができるバージョン |
---|---|---|---|---|---|---|---|---|
トランザクションバージョン | PSBT_GLOBAL_TX_VERSION = 0x02 |
None | キーデータ無し | 32-bit uint | 作成されるトランザクションのバージョン番号を表す32bitのリトルエンディアンの符号付き整数。PSBT_GLOBAL_VERSION フィールドで指定されるPSBTバージョン番号と異なることに注意すること。 |
2 | 0 | 2 |
フォールバックロックタイム | PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03 |
None | キーデータ無し | 32-bit uint | インプットがlocktimeを指定しない場合に使用するトランザクションのlocktimeを表す32bitのリトルエンディアンの符号なし整数 | 0 | 2 | |
インプットの数 | PSBT_GLOBAL_INPUT_COUNT = 0x04 |
None | キーデータ無し | compact size uint | このPSBTのインプットの数を表すCompact sizeの符号なし整数 | 2 | 0 | 2 |
アウトプットの数 | PSBT_GLOBAL_OUTPUT_COUNT = 0x05 |
None | キーデータ無し | compact size uint | このPSBTのアウトプットの数を表すCompact sizeの符号なし整数 | 2 | 0 | 2 |
トランザクションの変更可能フラグ | PSBT_GLOBAL_TX_MODIFIABLE = 0x06 |
None | キーデータ無し | 8-bit uint | さまざまなトランザクションの変更フラグ用のビットフィールドとして使用する8bitのリトルエンディアンの符号なし整数。bit 0はInputs Modifiableフラグで、インプットが変更可能かどうかを示す。bit 1は、Outputs Modifiableフラグで、アウトプットが変更可能化どうかを示す。bit 2は、Has SIGHASH_SINGLEフラグで、トランザクションがSIGHASH_SINGLE署名を持ち、インプットとアウトプットのペアを保持する必要があるかどうかを示す。bit 2は基本的に、Constructorがインプットをチェックして、インプットを追加するかどうか、追加する方法を決定する必要があることを示す。 | 0 | 2 |
PSBT Version 2の新しいインプット毎のタイプは以下の通り:
名称 | キータイプ | キーデータ | キーデータの定義 | 値データ | 値データの定義 | 含める必要のあるバージョン | 除外する必要のあるバージョン | 含めることができるバージョン |
---|---|---|---|---|---|---|---|---|
前のTXID | PSBT_IN_PREVIOUS_TXID = 0x0e |
Nonee | キーデータ無し | txid | 前のトランザクションの32バイトのtixdで、そのPSBT_IN_OUTPUT_INDEX のアウトプットが使用される。 |
2 | 0 | 2 |
使用するアウトプットインデックス | PSBT_IN_OUTPUT_INDEX = 0x0f |
None | キーデータ無し | 32-bit uint | PSBT_IN_PREVIOUS_TXID のtxidを持つトランザクションの使用するアウトプットのインデックスを表す32bitのリトルエンディアン整数。 |
2 | 0 | 2 |
シーケンス番号 | PSBT_IN_SEQUENCE = 0x10 |
None | キーデータ無し | 32-bit uint | このインプットのシーケンス番号である32bitの符号なしリトルエンディアン整数。省略された場合、シーケンス番号は最終シーケンス番号(0xffffffff)とみなされる。 | 0 | 2 | |
時間ベースのLocktime | PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11 |
None | キーデータ無し | 32bit uint | このトランザクションのlocktimeとして設定する必要がある最小のUnixタイムスタンプを表す500000000以上の32bit符号なしリトルエンディアン整数。 | 0 | 2 | |
高さベースのLocktime | PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12 |
None | キーデータ無し | 32-bit uint | このトランザクションのlocktimeとして設定する必要がる最小のブロック高を表す、500000000未満の32bit符号なしリトルエンディアン整数。 | 0 | 2 |
PSBT Version 2の新しいアウトプット毎のタイプは以下の通り:
名称 | キータイプ | キーデータ | キーデータの定義 | 値データ | 値データの定義 | 含める必要のあるバージョン | 除外する必要のあるバージョン | 含めることができるバージョン |
---|---|---|---|---|---|---|---|---|
アウトプットの量 | PSBT_OUT_AMOUNT = 0x03 |
None | キーデータ無し | 64-bit int | satoshi単位のアウトプットの量を表す64bitの符号付きリトルエンディアン整数。 | 2 | 0 | 2 |
アウトプットスクリプト | PSBT_OUT_SCRIPT = 0x03 |
None | キーデータ無し | Script | scriptPubKeyとして知られるアウトプットのScript。PSBTv0では省略され、PSBTv2では提供されなければならない。 |
Locktimeの決定
トランザクションのnLockTimeフィールドは、PSBT_GLOBAL_PREFERRED_LOCKTIME
および各インプットのPSBT_IN_REQUIRED_TIME_LOCKTIME
、PSBT_IN_REQUIRED_HEIGHT_LOCKTIME
をチェックすることで決定される。どのインプットにもPSBT_IN_REQUIRED_TIME_LOCKTIME
、PSBT_IN_REQUIRED_HEIGHT_LOCKTIME
が内場合は、PSBT_GLOBAL_PREFERRED_LOCKTIME
を使用しなければならない。PSBT_GLOBAL_PREFERRED_LOCKTIME
が提供されない場合、0とみなされる。
1つ以上のインプットがPSBT_IN_REQUIRED_TIME_LOCKTIME
もしくはPSBT_IN_REQUIRED_HEIGHT_LOCKTIME
を持つ場合、選択されたフィールドはすべてのインプットでサポートされるフィールドである。これは、これらのフィールドのいずれかでlocktimeを指定するすべてのインプットをチェックし、それらのすべてのインプットに存在するフィールドを選択することで決定できる。locktimeを指定しないインプットは、両方を指定するインプットと同様に、両方のタイプのlocktimeを取れる。選択されるlocktimeは、選択されたloctimeタイプの最大値になる。
一意な識別
PSBTv2は、PSBTに記載されている情報を元に未署名のトランザクションを構築し、そのトランザクションのトランザクションIDを計算することで一意に識別することができる。PSBT_IN_SEQUENCE
はUpdaterやCombinerによって変更される可能性があるため、この未署名のトランザクションのシーケンス番号は(finalでもなくPSBT_IN_SEQUENCE
のシーケンスでもなく)0に設定する必要がある。この未署名のトランザクションのlocktimeは前述のように計算されなければならない。
ロール
PSBTv2は新しいロールを導入し、既存のロールを一部変更する。
Creator
PSBTv2では、Creatorは0個のインプットと0個のアウトプットを持つPSBTを初期化する。PSBTのバージョン番号は2が設定される。トランザクションのバージョン番号は少なくとも2を設定する必要がある*2。Creatorはまた、PSBT_GLOBAL_PREFERRED_LOCKTIME
を設定する必要がある。CreatorがConstructorではなく、PSBTを他のユーザーに渡してインプットやアウトプットを追加する場合は、PSBT_GLOBAL_TX_MODIFIABLE
フィールドが存在し、Inputs Modifiable
およびOutputs Modifiable
フラグが適切に設定されている必要がある。CreatorがConstructorで、他のエンティティによってインプットとアウトプットが追加されない場合、PSBT_GLOBAL_TX_MODIFIABLE
は省略可能。
Constructor
Constructorは、PSBTv2にのみ登場する。CreatorがPSBTを初期化すると、Constructorはインプットとアウトプットを追加する。これらを追加する前に、ConstructorはPSBT_GLOBAL_TX_MODIFIABLE
フィールドをチェックする必要がある。nputs Modifiable
フラグがtrueの場合のみインプットを追加でき、Outputs Modifiable
フラグがtrueの場合のみアウトプットを追加できる。
インプットとアウトプットを追加したら、PSBTのPSBT_GLOBAL_INPUT_COUNT
とPSBT_GLOBAL_OUTPUT_COUNT
がそれぞれインプットの数とアウトプットの数になるようインクリメントしなければならない。インプットを追加する際は、PSBT_IN_PREVIOUS_TXID
とPSBT_IN_OUTPUT_INDEX
を設定しなくてはならない。アウトプットを追加する際は、PSBT_OUT_VALUE
とPSBT_OUT_OUTPUT_SCRIPT
を設定しなくてはならない。もしインプットがタイムロックを必要とする場合は、Constructorsはtimelockフィールドを設定しなければならない。インプットが時間ベースのタイムロックを持つ場合は、PSBT_IN_REQUIRED_TIME_TIMELOCK
を、ブロック高ベースのタイムロックを持つ場合はPSBT_IN_REQUIRED_HEIGHT_TIMELOCK
を設定しなければならない。インプットが両方のタイプのタイムロックを持つ場合、両方設定しなければならない。場合によっては、両方のタイプを許可するインプットがあるが、1つのタイプのタイムロックのみをサポートする特定の分岐が選択されるため、使用されるタイムロックのタイプは1つのみになる。
追加するインプットで必要案タイムロックが指定されている場合、Constructorは既存のすべてのインプットをチェックし、タイムロックのタイプに互換性があることを確認する必要がある。さらに、このチェック中に、インプットに署名があることが分かった場合、新しく追加されたインプットによってトランザクションのlocktimeが変更されないようにする必要がある。新しく追加されたインプットに互換性のないタイムロックがある場合、追加してはならない。既存の署名がある場合、トランザクションのlocktimeを変更するような追加をしてはならない。
Has SIGHASH_SINGLE
フラグがtrueの場合、Constructorはすべてのインプットチェックし、SIGHASH_SINGLE
を使用した署名を持っているインプットを探す。インプットと対応するアウトプットの前に、同じ数のインプットとアウトプットを加える必要がある。
Constructorは、PSBT_GLOBAL_TX_MODIFIABLE
のbool値をfalseにするか、フィールドを完全に削除して、トランザクションにインプットとアウトプットを追加できないよう宣言することを選択できる。
単一のエンティティがCreatorとConstructorの両方になる可能性がある。
Updater
PSBTv2では、Updaterはシーケンス番号を設定できる。
Signer
PSBTv2sでは、Signerはインプットに署名後、PSBT_GLOBAL_TX_MODIFIABLE
フィールドを更新し、PSBT状態を正確に反映させる必要がある。SignerがSIGHASH_ANYONECANPAY
を使用しない署名を追加した場合、Input Modifiable
フラグをfalseに設定しなければならない。SignerがSIGHASH_NONE
を使用しない署名を追加した場合、Outputs Modifiable
をfalseに設定しなければならない。Signerが、SIGHASH_SINGLE
を使用する署名を追加した場合、Has SIGHASH_SINGLE
フラグをtrueに設定しなければならない。
Transaction Extractor
PSBTv2sでは、トランザクションはPSBTv2の各フィールドを使って構築される。このトランザクションのlocktimeは、Locktimeの決定セクションで指定された方法で決定される。Extractorは、すべての入力が完了した場合、完全に有効なネットワークシリアライゼーションされたトランザクションを生成する必要がある。
後方互換性
PSBTv2は、BIP 174で定義されているPSBTv0と同じgemeric形式を共有している。PSBTv0用のパーサーは新しいフィールドをサポートするための変更のみでPSBTv2をデシリアライズすることができるはずである。
ただし、PSBT_GLOBAL_VERSION
を使用しているため、PSBTv2はPSBTv0とは互換性がなく、その逆も同様。この非互換性は、PSBT_GLOBAL_UNSIGNED_TX
を削除するために意図的なものだ。しかし、PSBTv2のフィールドから未署名のトランザクションを作成することで、PSBTv2をPSBTv0に変換することは可能。
Test Vector
参照実装
BSBTフォーマットの参照実装は、https://github.com/achow101/bitcoin/tree/psbt2から入手可能。
*1:バージョン番号1はどうなったの?PSBT Version 0が俗にバージョン1と呼ばれているため、Version 1はスキップしている。本来このBIPはVersion 1の予定だったが、設計中に俗にVersion 2と呼ばれていたため、混乱がないようにバージョン番号を2に変更することにした。
*2:トランザクションのバージョン番号はなぜ2以上なのか?トランザクションのバージョン番号はOP_CHECKSEQUENCEVERIFYのような機能のバリデーションルールの一部である。後方互換性があり、これらの機能を無効にする方法は他にもあるため(シーケンス番号など)、トランザクションのバージョン番号をネゴシエートするよりも、トランザクションがこれらの機能をサポートできるようにする方が簡単。