用語集 / 開発者向けリファレンス
CompactSize
別名はCompact size unsigned integer、VarInt、コンパクトサイズです。
- CompactSizeとは?
- CompactSizeは、ビットコインがシリアライズされたデータ内の個数や長さの前に付ける可変長整数で、253未満の値では1バイト、それ以外では最大9バイトを要します。
ビットコインのバイナリ形式にはフィールド名がないため、パーサーには、入力がいくつ続くのか、各スクリプトがどれだけの長さなのかを伝える必要があります。CompactSizeは最小で1バイトでそれに応えます。0から252まではその1バイトだけで、より大きな値にはマーカーと、さらに2バイト、4バイト、8バイトが付きます。送信するすべての支払いには、少なくとも4つのCompactSizeが含まれます。
仕組み
CompactSizeは自身の最初の1バイトを読んで、残りの長さを決めます。
最初の1バイトが0xfd未満であれば、それが値そのものです。したがって0から252までのカウントは1バイトだけを占め、それ以上は何も占めません。最初の1バイトが0xfdなら値は続く2バイトに入り、0xfeなら続く4バイト、0xffなら続く8バイトに入ります。これらの後続の数値はすべてリトルエンディアンです。最長のエンコーディングは9バイトで、1,800京を超える値を表せますが、実際の構造体がそこまでの値を必要とすることはまずありません。
エンコーディングは最小でなければなりません。数値5を0xfd 0x05 0x00と書くのは、0x05と書くのと算術的には同一ですが、Bitcoin Coreはこれを拒否し、デシリアライズ時に非正規のエラーを発生させます。この規則がなければ、同じトランザクションを複数の方法で書くことができ、それぞれが異なる識別子を持つことになります。これは体裁の問題ではなく、トランザクション展性の問題です。
算術上の上限よりはるかに低い実用的な上限があります。Bitcoin Coreは、0x02000000バイト(約33.5メガバイト)より大きいと主張するデシリアライズ対象にメモリを割り当てることを拒否し、ピアプロトコルは別途、1つのメッセージを4,000,000バイトに制限しています。したがって、悪意あるピアが、100京の入力を持つトランザクションを通知しても、お使いのノードにギガバイト単位のメモリを確保させることはできません。
命名上の落とし穴が1つあるので指摘しておきます。プロトコル仕様のドキュメントはこれをvar_intと呼び、ウォレットライブラリは通常VarIntと呼びますが、Bitcoin Coreのソースコード自体では、VarIntという名前を、データベースファイルとundoファイルにのみ現れる、まったく別の無関係なエンコーディングに使っています。コードを読むときは、目の前にあるのがどちらなのかを確認してください。
どこで見られるか
CompactSizeは、ビットコインの中でユーザー向けの名前をまったく持たない構造体としては、最も頻繁に遭遇するものです。
生のトランザクションでは、4バイトのバージョンの直後、またはSegWitのマーカーとフラグが存在する場合はその直後に現れ、入力数を保持します。各スクリプトの前にもその長さを示すために再び現れ、出力数の前にもまた現れます。シリアライズされたブロックではトランザクション数を保持します。ピアプロトコルでは、inv、getdata、addr、headersの各メッセージのペイロードの先頭に現れます。
headersメッセージは、このエンコーディングが最も奇妙な形で現れる例です。各80バイトのブロックヘッダーの後には、ゼロのCompactSizeトランザクション数が続くため、1つのエントリは80バイトではなく81バイトを占め、2,000個のヘッダーの完全なバッチはネットワーク上で162,000バイトになります。headersメッセージは決してトランザクションを運ばないため、この数は常にゼロです。このフィールドが存在するのは、同じ解析コードが、ブロック本体が続くかどうかに関係なくヘッダーを読めるようにするためです。
生のトランザクションを手作業でデコードしていて、途中でフィールドの意味が分からなくなった場合、その原因はほとんどの場合CompactSizeの読み間違いです。1バイト読み足りないと、それ以降がすべてずれます。