シリアル化フォーマット

[ソース]

このページでは、XRP Ledgerのトランザクションとその他のデータの正規バイナリフォーマットについて説明します。このバイナリフォーマットは、トランザクションの内容のデジタル署名を作成および検証するために必要であり、また他の用途にも使用されます。通常、rippled APIはJSONを使用してクライアントアプリケーションと通信します。ただしJSONは、同じデータをさまざまな同等の方法で表現できるため、デジタル署名を付与するトランザクションをシリアル化するのに適したフォーマットではありません。

トランザクションをJSONまたはその他の表現から正規バイナリフォーマットへシリアル化するプロセスのステップを、以下にまとめます。

  1. すべての必須フィールドが指定されていること(必須の「自動入力可能」フィールドを含む)を確認します。

    トランザクションフォーマットリファレンスに、XRP Ledgerトランザクションの必須フィールドと省略可能なフィールドが定義されています。

    注記:SigningPubKeyもこのステップで指定する必要があります。署名の際に、署名用に指定されたシークレットキーからこのキーを生成できます。

  2. 各フィールドのデータを「内部」バイナリフォーマットに変換します。

  3. フィールドを正規順序でソートします。

  4. 各フィールドの前にフィールドIDを付加します。

  5. フィールド(プレフィクスを含む)をソート順に連結します。

その結果、ECDSA(secp256k1楕円曲線を使用)やEd25519などの既知の署名アルゴリズムを使用して署名できるバイナリブロブが1つ作成されます。XRP Ledgerのために、適切なプレフィクス(シングル署名の場合は0x53545800、マルチ署名の場合は0x534D5400)を使用してデータをハッシュ化する必要があります。署名後に、指定されているTxnSignatureフィールドを使用してトランザクションを再度シリアル化する必要があります。

注記: XRP Ledgerでは、レジャーオブジェクトや処理済みのトランザクションなど他のタイプのデータを表す場合にも同じシリアル化フォーマットが使用されます。ただし、署名されるトランザクションに追加するのに適切なフィールドは限られています。(たとえば署名自体が指定されているTxnSignatureフィールドは、署名するバイナリブロブに含まれていてはなりません。)このように、「署名」フィールドとされてオブジェクトに署名するときにオブジェクトに含まれるフィールドもあれば、「非署名」とされてオブジェクトに含まれないフィールドもあります。

署名済みトランザクションと未署名のトランザクションはいずれも、JSONフォーマットとバイナリフォーマットの両方で表すことができます。同じ署名済みトランザクションのJSONフォーマットとバイナリフォーマットの例を以下に示します。

JSON:

{
  "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys",
  "Expiration": 595640108,
  "Fee": "10",
  "Flags": 524288,
  "OfferSequence": 1752791,
  "Sequence": 1752792,
  "SigningPubKey": "03EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE3",
  "TakerGets": "15000000000",
  "TakerPays": {
    "currency": "USD",
    "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
    "value": "7072.8"
  },
  "TransactionType": "OfferCreate",
  "TxnSignature": "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C",
  "hash": "73734B611DDA23D3F5F62E20A173B78AB8406AC5015094DA53F53D39B9EDB06C"
}

バイナリ(16進数として表現):

120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A732103EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE3744630440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C8114DD76483FACDEE26E60D8A586BB58D09F27045C46

サンプルコード

ここで説明するシリアル化プロセスは複数の場所にさまざまなプログラミング言語で実装されています。

これらのすべての実装には、一般利用が可能なオープンソースライセンスが提供されているので、学習のためにドキュメントと合わせて使用するだけでなく、必要に応じてコードをインポート、使用、または変更することができます。

内部フォーマット

各フィールドには「内部」バイナリフォーマットがあります。このフォーマットは、rippledソースコードで署名時に(およびその他のほとんどの場合に)そのフィールドを表示するのに使用されます。すべてのフィールドの内部フォーマットは、SField.cpp のソースコードに定義されています。(このフィールドには、トランザクションフィールド以外のフィールドも含まれています。)トランザクションフォーマットリファレンスにも、すべてのトランザクションフィールドの内部フォーマットが記載されています。

たとえばFlags 共通トランザクションフィールドはUInt32(32ビット符号なし整数)になります。

定義ファイル

以下のJSONファイルには、XRP Ledgerデータをそのバイナリフォーマットにシリアル化し、バイナリからシリアル化解除するのに必要な重要な定数が定義されています。

https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json

この定義ファイルの最上位フィールドの定義を以下の表に示します。

フィールド 内容
TYPES フィールドIDの作成と正規順序でのフィールドのソートのためのデータタイプからその「タイプコード」へのマップ。1未満のコードは実際のデータには含まれません。10000を超えるコードは、他のオブジェクト内部ではシリアル化できない「トランザクション」などの特殊な「上位」オブジェクトタイプを表します。各タイプのシリアル化方法についての詳細は、タイプリストを参照してください。
LEDGER_ENTRY_TYPES レジャーオブジェクトから対応するデータタイプへのマップ。これはレジャー状態データと、処理されたトランザクションのメタデータの「affected nodes」セクションに含まれます。
FIELDS トランザクション、レジャーオブジェクト、あるいはその他のデータに含まれる可能性があるすべてのフィールドを表すタプルからなるソート済み配列。各タプルの1番目のメンバーはフィールドの文字列名であり、2番目のメンバーはそのフィールドのプロパティーが含まれているオブジェクトです。(これらのフィールドの定義については、以下の「フィールドプロパティー」の表を参照してください。)
TRANSACTION_RESULTS トランザクション結果コードから対応する数値へのマップ。レジャーに含まれない結果タイプにはマイナスの値が含まれています。tesSUCCESSに数値0が含まれています。tecクラスコードは、レジャーに含まれている失敗を示しています。
TRANSACTION_TYPES トランザクションのタイプから対応する数値へのマップ。

署名と送信のためにトランザクションをシリアル化するという目的から、FIELDSTYPES、およびTRANSACTION_TYPESフィールドが必要です。

FIELDS配列のフィールド定義オブジェクトには以下のフィールドが含まれています。

フィールド 内容
nth 数値 このフィールドのフィールドコード。このコードは、フィールドIDの作成時と、同一データタイプの他のフィールドとのソート時に使用されます。
isVLEncoded ブール値 trueの場合、このフィールドには長さプレフィクスが付加されています
isSerialized ブール値 trueの場合、このフィールドはシリアル化バイナリデータにエンコードされる必要があります。このフィールドがfalseの場合、一般にフィールドは保管されず、オンデマンドで再作成されます。
isSigningField ブール値 trueの場合、署名のためにトランザクションを準備する際にこのフィールドをシリアル化する必要があります。falseの場合、このフィールドは署名対象データから省略する必要があります。(これはトランザクションに含まれていない可能性があります。)
type 文字列 このフィールドの内部データタイプ。これは、このフィールドのタイプコードを示すTYPESマップのキーにマップします。

フィールドID

[ソース - エンコード] [ソース - デコード]

フィールドのタイプコードとフィールドコードを結合すると、フィールドの一意のIDになります。このIDは、最終的なシリアル化ブロブでこのフィールドの前に付加されます。フィールドIDのサイズは、タイプコードとその結合対象のフィールドコードに応じて1~3バイトとなります。以下の表を参照してください。

タイプコード < 16 タイプコード >= 16
フィールドコード < 16 1バイト: 上位4ビットがタイプを定義し、下位4ビットがフィールドを定義します。 2バイト: 1番目のバイトの下位4ビットがフィールドを定義し、次のバイトがタイプを定義します。
フィールドコード >= 16 2バイト: 1番目のバイトの上位4ビットがタイプを定義し、1番目のバイトの下位4ビットは0になります。次のバイトがフィールドを定義します。 3バイト: 1番目のバイトは0x00、2番目のバイトはタイプを定義します。3番目のバイトはフィールドを定義します。

デコードの際には、1番目のバイトのどのビットがゼロであるかによって、フィールドIDのバイト数を把握できます。これは、上記の表の例に対応しています。

上位4ビットがゼロ以外である 上位4ビットがゼロである
下位4ビットがゼロ以外である 1バイト: 上位4ビットがタイプを定義し、下位4ビットがフィールドを定義します。 2バイト: 1番目のバイトの下位4ビットがフィールドを定義し、次のバイトがタイプを定義します。
下位4ビットがゼロである 2バイト: 1番目のバイトの上位4ビットがタイプを定義し、1番目のバイトの下位4ビットは0になります。次のバイトがフィールドを定義します。 3バイト: 1番目のバイトは0x00、2番目のバイトはタイプを定義します。3番目のバイトはフィールドを定義します。

注意: フィールドIDは、フィールドのソートに使用される2つの要素で構成されますが、シリアル化されたフィールドID自体に基づいてソートを実行しないでください。これは、フィールドIDのバイト構造によってソート順序が変わるためです。

長さプレフィクスを付加する

一部の可変長フィールドの前には、長さインディケーターが付加されています。Blobフィールド(任意のバイナリデータを含む)がこれに該当します。長さプレフィクスが付加されているタイプのリストについては、タイプリストの表を参照してください。

注記: 一部のタイプの可変長フィールドには、長さプレフィクスが付加されません。このようなタイプでは、他の方法で内容の終わりが示されます。

長さプレフィクスはフィールドの長さを示す1~3バイトで構成され、タイププレフィクスと内容の間に挿入されます。

  • フィールドに0~192バイトのデータが含まれている場合、1番目のバイトは内容の長さを示し、長さバイトの直後にそのバイト数のデータが続きます。

  • フィールドに193~12480バイトのデータが含まれている場合、最初の2バイトは以下の式で算出されるフィールドの長さを示します。

    193 + ((byte1 - 193) * 256) + byte2
    
  • フィールドに12481~918744バイトのデータが含まれている場合、最初の3バイトは以下の式で算出されるフィールドの長さを示します。

    12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3
    
  • 長さプレフィクスが付加されているフィールドに格納できる最大データは918744バイトです。

デコード時に、1番目の長さバイトの値から、追加の長さバイト(0、1、または2)が存在するかどうかを把握できます。

  • 1番目の長さバイトの値が192以下の場合、これは唯一の長さバイトであり、フィールドの内容の長さはこのバイトが示すバイト数です。
  • 1番目の長さバイトの値が193~240の場合、2つの長さバイトがあります。
  • 1番目の長さバイトの値が241~254の場合、3つの長さバイトがあります。

フィールドの正規順序

トランザクションのすべてのフィールドは、まずフィールドのタイプ(特に各タイプに割り当てられている数値の「タイプコード」)に基づいて特定の順序でソートされ、次にフィールド自体(「フィールドコード」)に基づいてソートされます。(たとえば、姓がフィールドのタイプ、名前がフィールド自体とすると、姓で最初にソートし、次に名でソートすることになります。)

タイプコード

各フィールドタイプには任意のタイプコードが含まれており、番号が小さいコードから最初にソートされます。これらのコードはSField.h で定義されています。

たとえば UInt32のタイプコードが2である ので、すべてのUInt32フィールドは、すべてのAmountフィールド(タイプコード6) よりも前に位置します。

定義ファイルには、TYPESマップの各タイプのタイプコードがリストされています。

フィールドコード

各フィールドにはフィールドコードが含まれています。フィールドコードは、同じタイプのフィールドをソートするときに使用され、番号が小さいコードが最初になるようにソートされます。これらのフィールドはSField.cpp で定義されています。

たとえばPaymentトランザクションAccountフィールドのソートコードが1である 場合、このフィールドはDestinationフィールド(ソートコードが3である フィールド)よりも前に位置します。

フィールドコードは異なるフィールドタイプのフィールドで再利用されますが、同じタイプのフィールドに同じフィールドコードが含まれることはありません。タイプコードとフィールドコードを組み合わせると、フィールドの一意のフィールドIDになります。

タイプリスト

トランザクションの指示には、以下のタイプのフィールドを指定できます。

タイプ名 タイプコード ビット長 [長さプレフィクスが付加されるか]? 説明
AccountID 8 160 はい アカウントの一意のID。
Amount 6 64または384 いいえ XRPまたは発行済み通貨の額。フィールドの長さは、XRPの場合は64ビット、発行済み通貨の場合は384ビット(64+160+160)です。
Blob 7 可変 はい 任意のバイナリデータ。このようなフィールドの中で重要なフィールドとして、TxnSignature(トランザクションを承認する署名)があります。
Hash128 4 128 いいえ 128ビットの任意のバイナリ値。該当する唯一のフィールドはEmailHashです。これは、Gravatar を取得する目的でアカウント所有者のメールのMD-5ハッシュを保管するフィールドです。
Hash160 17 160 いいえ 160ビットの任意のバイナリ値。これにより通貨コードまたはイシュアーが定義されます。
Hash256 5 256 いいえ 256ビットの任意のバイナリ値。これは通常、トランザクション、レジャーバージョン、またはレジャーデータオブジェクトの「SHA-512Half」ハッシュを表します。
PathSet 18 可変 いいえ 複数通貨間ペイメントの有効なペイメントパスのセット。
STArray 15 可変 いいえ 可変数のメンバーからなる配列。フィールドによってタイプが異なる場合があります。この例として、memosマルチ署名で使用される署名者のリストがあります。
STObject 14 可変 いいえ 1つ以上のネストされたフィールドを含むオブジェクト。
UInt8 16 8 いいえ 8ビットの符号なし整数。
UInt16 1 16 いいえ 16ビットの符号なし整数。TransactionTypeは、このタイプの特殊なフィールドで、特定の文字列から整数値へのマッピングを含みます。
UInt32 2 32 いいえ 32ビットの符号なし整数。このタイプの例として、すべてのトランザクションのFlagsフィールドとSequenceフィールドがあります。

上記のフィールドタイプの他に、レジャーオブジェクトトランザクションメタデータなどのコンテキストでは以下のタイプが含まれることがあります。

タイプ名 タイプコード 長さプレフィクスを付加する? 説明
Transaction 10001 いいえ トランザクション全体を含む「上位」タイプ。
LedgerEntry 10002 いいえ レジャーオブジェクト全体を含む「上位」タイプ。
Validation 10003 いいえ ピアツーピア通信でコンセンサスプロセスの検証投票を表すために使用される「上位」タイプ。
Metadata 10004 いいえ 1つのトランザクションのメタデータを含む「上位」タイプ。
UInt64 3 いいえ 64ビットの符号なし整数。このタイプはトランザクションの指示には含まれませんが、さまざまなレジャーオブジェクトでこのタイプのフィールドが使用されます。
Vector256 19 はい このタイプはトランザクションの指示には含まれませんが、AmendmentsレジャーオブジェクトAmendmentsフィールドでは、現在有効なAmendmentを示すためにこのタイプが使用されます。

AccountIDフィールド

このタイプのフィールドには、XRP Ledgerアカウントの160ビットのIDが含まれています。JSONではこれらのフィールドはbase58 XRP Ledger「アドレス」および追加のチェックサムデータとして表示されます。このため、スペルミスが有効なアドレスとなることがありません。(このエンコードは「Base58Check」とも呼ばれ、誤ったアドレスへの送金を防止します。)これらのフィールドのバイナリフォーマットにはチェックサムデータは含まれておらず、またアドレスのbase58エンコードで使用される0x00「タイププレフィクス」も含まれていません。(ただし、バイナリフォーマットは主に署名済みトランザクションに使用されるため、署名済みトランザクションを転記する際にスペルミスなどのエラーが発生すると署名が無効となり、送金できなくなります。)

スタンドアロンフィールドとして表示されるAccountID(AccountDestinationなど)の長さは固定長の160ビットですが、長さプレフィクスが付加されます。その結果、これらのフィールドの長さインディケーターは常に0x14バイトになります。特殊フィールドの子として示されるAccountID(Amount issuerPathSet accountなど)では長さプレフィクスは付加 されません

Amountフィールド

「Amount」タイプは、通貨(XRPまたは発行済み通貨)の額を表す特殊なフィールドタイプです。このタイプは2つのサブタイプで構成されます。

  • XRP

    XRPは64ビット符号なし整数(ビッグエンディアンオーダー)としてシリアル化されます。ただし、XRPであることを示すため最上位ビットが常に0であり、プラスの値であることを示す最上位から2番目のビットは1となります。XRPの最大額(1017 drop)には57ビットが必要であるため、XRPのシリアル化フォーマットを計算するには、標準の64ビット符号なし整数をとり、0x4000000000000000のビットOR演算を行います。

  • 発行済み通貨

    発行済み通貨は以下の3つのセグメントで構成され、セグメントの順序は以下のとおりです。

    1. 内部通貨フォーマットの額を示す64ビット。1番目のビットは、これがXRPではないことを示す1です。
    2. 通貨コードを示す160ビット。標準APIでは、標準通貨コードフォーマットを使用して「USD」などの3文字のコードが160ビットのコードに変換されますが、160ビットのカスタムコードも使用できます。
    3. イシュアーのアカウントIDを示す160ビット。(関連項目: アカウントアドレスエンコード)

1番目のビットに基づいて2つのサブタイプのいずれに該当するかを確認できます。0の場合はXRP、1の場合は発行済み通貨です。

以下の図に、XRPの額と発行済み通貨の額のシリアル化フォーマットを示します。

「非XRP」ビット、符号ビット、および62ビットの精度で構成されるXRPの額。「非XRP」ビット、符号ビット、指数(8ビット)、仮数(54ビット)、通貨コード(160ビット)、イシュアー(160ビット)で構成される発行済み通貨の額。

配列フィールド

一部のトランザクションフィールド(SignerListSetトランザクションSignerEntriesMemosなど)はオブジェクトの配列です(「STArray」タイプと呼ばれます)。

配列には、さまざまなオブジェクトフィールドがそのネイティブバイナリフォーマットで特定の順序で含まれています。JSONでは、各配列メンバーが1つのフィールド(メンバーオブジェクトフィールドの名前)を含むJSON「ラッパー」オブジェクトです。そのフィールドの値は(「内部」)オブジェクト自体です。

バイナリフォーマットでは、配列の各メンバーにはフィールドIDプレフィクス(ラッパーオブジェクトの単一キーに基づく)と内容(オブジェクトとしてシリアル化された内部オブジェクトからなる)が含まれています。配列の終わりをマークするため、アイテムにフィールドID 0xf1(配列のタイプコードとフィールドコード1)を付加し、内容は指定しません。

以下の例は、配列のシリアル化フォーマットを示します(SignerEntriesフィールド)。

配列フィールドID、各配列要素のフィールドIDと内容、および「配列の終わり」を示すフィールドID

ブロブフィールド

ブロブタイプは、任意のデータを持つ長さプレフィクスが付加されているフィールドです。このタイプを使用する2種類の一般的なフィールドとして、SigningPubKeyTxnSignatureがあります。これらのフィールドにはそれぞれ、トランザクションの実行を承認する公開鍵と署名が含まれています。

両方のフィールドにはこれ以上の内容構造がないため、フィールドIDと長さプレフィクスの後に可変長エンコードで示される正確なバイト数で構成されます。

ハッシュフィールド

XRP LedgerのハッシュタイプにはHash128、Hash160、Hash256があります。これらのフィールドには特定のビット数のバイナリデータが含まれており、それらのデータはハッシュ演算の結果を表す場合とそうでない場合があります。

これらのフィールドは、長さインディケーターを使用せずに、ビッグエンディアンバイトオーダーで特定数のビットとしてシリアル化されます。

オブジェクトフィールド

トランザクションフィールドの一部(SignerListSetトランザクションSignerEntryMemos配列のMemoなど)はオブジェクトです(「STObject」タイプと呼ばれます)。オブジェクトのシリアル化は配列のシリアル化に似ていますが、唯一異なる点としてオブジェクトフィールド内ではオブジェクトのメンバーを正規順序に従って配置する必要がある点があげられます。配列フィールドではすでに順序が明示的に設定されています。

オブジェクトフィールドの正規フィールド順序は、すべての最上位フィールドの正規フィールド順序と同じですが、オブジェクトのメンバーはオブジェクト内でソートする必要があります。最終メンバーの後には、「オブジェクトの終わり」を示すフィールドID(0xe1)があり、このフィールドには内容がありません。

以下の例は、オブジェクトのシリアル化フォーマットを示します(Memos配列内の1つのMemoオブジェクト)。

オブジェクトフィールドID、各オブジェクトメンバーのオブジェクトIDと内容(正規順序)、および「オブジェクトの終わり」を示すフィールドID

PathSetフィールド

複数通貨間PaymentトランザクションPathsフィールドは、JSONで配列からなる配列として表される「PathSet」です。使用されるパスについての詳細は、パスを参照してください。

PathSetは、1~6の個別パスとして順序どおりにシリアル化されます[ソース] 。それぞれの完全なパスの後には、パスの後に続く内容を示すバイトが配置されます。

  • 0xff は別のパスが続くことを示します。
  • 0x00 はPathSetの終わりを示します。

各パスには1~8のパスステップがこの順序で含まれています[ソース] 。各ステップはタイプを示すバイトで始まり、その後にパスステップを記述する1つ以上のフィールドが続きます。タイプは、ビット単位のフラグを使用してそのパスステップに含まれるフィールドを示します。(たとえば値が0x30の場合、通貨とイシュアーの両方が変更されます。)複数のフィールドが含まれている場合、フィールドは常に特定の順序で配置されます。

以下の表に、有効なフィールドと、タイプバイトでフィールドを示すために設定されるビット単位のフラグを示します。

タイプフラグ 含まれるフィールド フィールドタイプ ビットサイズ 順序
0x01 account AccountID 160ビット 1番目
0x10 currency 通貨コード 160ビット 2番目
0x20 issuer AccountID 160ビット 3番目

いくつかの組み合わせは無効です。詳細は、パスの仕様を参照してください。

accountフィールドとissuerフィールドのAccountIDには、長さプレフィクスは付加 されていませんcurrencyがXRPの場合、通貨コードは160ビットのゼロとして表されます。

各ステップの直後にはパス上の次のステップが続きます。前述したように、パスの最終ステップの後には0xff(別のパスが続く場合)または0x00(これが最終パスの終わりの場合)が続きます。

以下の例は、PathSetのシリアル化フォーマットを示します。

PathSetは複数のパスからなり、各パスの後に継続または終了を示すバイトが続きます。各パスは複数のパスステップからなり、各パスステップはタイプバイトと、タイプバイトに基づく1つ以上の160ビットフィールドで構成されます。

UIntフィールド

XRP Ledgerには符号なし整数タイプUInt8、UInt16、UInt32、UInt64があります。これらのタイプはすべて、指定されたビット数の標準ビッグエンディアンバイナリー符号なし整数です。

JSONオブジェクトにこれらのフィールドが含まれている場合、ほとんどはデフォルトでJSONの数値として表されます。例外として、UInt64は文字列として表されます。これは、一部のJSONデコーダーがこれらの整数を64ビットの「倍精度」浮動小数点数として表現しようとするためです。64ビットの「倍精度」浮動小数点数では、すべてのUInt64値を完全な精度で表現することができません。

もう1つの特殊なケースとしてTransactionTypeフィールドがあります。JSONではこのフィールドは便宜上、トランザクションタイプの名前の文字列として表現されますが、バイナリではこのフィールドはUInt16です。定義ファイル内のTRANSACTION_TYPESオブジェクトにより、これらの文字列が特定の数値にマップされます。