Develop with pleasure!

福岡でCloudとかBlockchainとか。

PSBT Version 2を定義したBIP-370

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_LOCKTIMEPSBT_IN_REQUIRED_HEIGHT_LOCKTIMEをチェックすることで決定される。どのインプットにもPSBT_IN_REQUIRED_TIME_LOCKTIMEPSBT_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を設定する必要がある*2Creatorはまた、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_COUNTPSBT_GLOBAL_OUTPUT_COUNTがそれぞれインプットの数とアウトプットの数になるようインクリメントしなければならない。インプットを追加する際は、PSBT_IN_PREVIOUS_TXIDPSBT_IN_OUTPUT_INDEXを設定しなくてはならない。アウトプットを追加する際は、PSBT_OUT_VALUEPSBT_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

TBD

参照実装

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のような機能のバリデーションルールの一部である。後方互換性があり、これらの機能を無効にする方法は他にもあるため(シーケンス番号など)、トランザクションのバージョン番号をネゴシエートするよりも、トランザクションがこれらの機能をサポートできるようにする方が簡単。