From be3c1c23a2611ccb0828cc82018f9565e2397096 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 7 Nov 2018 16:44:38 -0800 Subject: [PATCH 01/19] Start tx serialization docs --- .../transaction-formats/txserialization.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 content/references/rippled-api/transaction-formats/txserialization.md diff --git a/content/references/rippled-api/transaction-formats/txserialization.md b/content/references/rippled-api/transaction-formats/txserialization.md new file mode 100644 index 0000000000..7fdc3da4d2 --- /dev/null +++ b/content/references/rippled-api/transaction-formats/txserialization.md @@ -0,0 +1,58 @@ +# Transaction Serialization Format (notes) +[[Source]
](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 "Source") + +XRP Ledger transactions have a canonical binary format, which is necessary to create and verify digital signatures of those transactions' contents. (JSON is sometimes used as a serialization format, but the many equivalent ways to format and represent the same data in JSON makes it unsuitable for being digitally signed.) + +The process of serializing a transaction from JSON or any other representation into their canonical binary format can be summarized with these steps: + +1. Convert each field's data into its "internal" binary format. +2. Sort the fields in canonical order. +3. Concatenate the fields in their sorted order. ***TODO: seems there must be some sort of wrapping/control data to indicate which fields are present for cases with optional fields.*** + +When serializing transaction instructions to be signed, you must also: + +- Make sure all required fields are provided, including any required but "auto-fillable" fields. +- Add the `SigningPubKey` field ***TODO: at what point in the above process? Probably before sorting, if it's a signing field?*** + + +The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. The hard work is the details of each of those steps. + +***Notes: some useful links for signing:*** + +- Actual core of the signing code in rippled: https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 +- Serialization code in ripple-lib depends on `ripple-binary-codec`. These definitions have the canonical types and sort codes for all fields: https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json + +## Internal Format + +Each field has an "internal" binary format used in the `rippled` source code to represent that field when signing (and in most other cases). The internal formats for all fields are defined in the source code of [`SField.cpp`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/impl/SField.cpp). (This file also includes fields other than transaction fields. The [Transaction Format Reference](transaction-formats.html) also lists the internal formats for all transaction fields. + +For example, the `Flags` [common transaction field](transaction-common-fields.html) becomes a UInt32 (32-bit unsigned integer). + +## Canonical Field Order + +All fields in a transaction are sorted in a specific order based on the field's type first, then the field itself second. (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) + +### Type Codes + +Each field type has an arbitrary sort code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). + +For example, [UInt32 has sort order 2](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L59), so all UInt32 fields come before all [Amount fields with order 6](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L63). + +### Field Codes + +Each field also has a sort code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). + +For example, the `Account` field of a [Payment transaction][] [has sort code 1](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L219), so it comes before the `Destination` field which [has sort code 3](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L221). + + +### Array Fields + +Some transaction fields, such as `SignerEntries` (in [SignerListSet transactions][]) and [`Memos`](transaction-common-fields.html#memos-field), are arrays. ***TODO: describe how they're serialized.*** + +### Object Fields + +Some fields, such as `SignerEntry` (in [SignerListSet transactions][]), and `Memo` (in `Memos` arrays) are objects. ***TODO: describe how objects are serialized.*** + +### Amount Fields + +The "AMOUNT" type is a special field type that represents an amount of currency, either XRP or an issued currency. ***TODO: details on how both are serialized in transactions.*** From ce931fa89b02e1d290e9b5e22b011a2f9034fe05 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 14 Nov 2018 19:50:25 -0800 Subject: [PATCH 02/19] Serialization - sample code in progress, clarifications --- .../_code-samples/tx-serialization/address.py | 8 + .../tx-serialization/base58/LICENSE | 19 + .../tx-serialization/base58/base58.py | 177 ++ .../tx-serialization/definitions.json | 1663 +++++++++++++++++ .../tx-serialization/field_ordering.py | 180 ++ .../tx-serialization/serialize.py | 154 ++ .../transaction-formats/txserialization.md | 33 +- 7 files changed, 2223 insertions(+), 11 deletions(-) create mode 100644 content/_code-samples/tx-serialization/address.py create mode 100644 content/_code-samples/tx-serialization/base58/LICENSE create mode 100644 content/_code-samples/tx-serialization/base58/base58.py create mode 100644 content/_code-samples/tx-serialization/definitions.json create mode 100644 content/_code-samples/tx-serialization/field_ordering.py create mode 100755 content/_code-samples/tx-serialization/serialize.py diff --git a/content/_code-samples/tx-serialization/address.py b/content/_code-samples/tx-serialization/address.py new file mode 100644 index 0000000000..e500930307 --- /dev/null +++ b/content/_code-samples/tx-serialization/address.py @@ -0,0 +1,8 @@ +import base58.base58 as base58 + +def decode_address(address): + decoded = base58.b58decode_check(address) + if decoded[0] == 0 and len(decoded) == 21: # is an address + return decoded[1:] + else: + raise ValueError("Not an AccountID!") diff --git a/content/_code-samples/tx-serialization/base58/LICENSE b/content/_code-samples/tx-serialization/base58/LICENSE new file mode 100644 index 0000000000..ca15a778d8 --- /dev/null +++ b/content/_code-samples/tx-serialization/base58/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 David Keijser + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/content/_code-samples/tx-serialization/base58/base58.py b/content/_code-samples/tx-serialization/base58/base58.py new file mode 100644 index 0000000000..9767194b5f --- /dev/null +++ b/content/_code-samples/tx-serialization/base58/base58.py @@ -0,0 +1,177 @@ +'''Base58 encoding + +Implementations of Base58 and Base58Check endcodings that are compatible +with the XRP Ledger. +''' + +# This This code is adapted from the module by David Keijser at +# . - rome@ripple.com +# His notes are preserved below: + +# This module is based upon base58 snippets found scattered over many bitcoin +# tools written in python. From what I gather the original source is from a +# forum post by Gavin Andresen, so direct your praise to him. +# This module adds shiny packaging and support for python3. + +from hashlib import sha256 + +__version__ = '1.0.3-xrp' + +# 58 character alphabet used +# alphabet = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' # Bitcoin +alphabet = b'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz' # XRP Ledger + + +if bytes == str: # python2 + iseq, bseq, buffer = ( + lambda s: map(ord, s), + lambda s: ''.join(map(chr, s)), + lambda s: s, + ) +else: # python3 + iseq, bseq, buffer = ( + lambda s: s, + bytes, + lambda s: s.buffer, + ) + + +def scrub_input(v): + if isinstance(v, str) and not isinstance(v, bytes): + v = v.encode('ascii') + + if not isinstance(v, bytes): + raise TypeError( + "a bytes-like object is required (also str), not '%s'" % + type(v).__name__) + + return v + + +def b58encode_int(i, default_one=True): + '''Encode an integer using Base58''' + if not i and default_one: + return alphabet[0:1] + string = b"" + while i: + i, idx = divmod(i, 58) + string = alphabet[idx:idx+1] + string + return string + + +def b58encode(v): + '''Encode a string using Base58''' + + v = scrub_input(v) + + nPad = len(v) + v = v.lstrip(b'\0') + nPad -= len(v) + + p, acc = 1, 0 + for c in iseq(reversed(v)): + acc += p * c + p = p << 8 + + result = b58encode_int(acc, default_one=False) + + return (alphabet[0:1] * nPad + result) + + +def b58decode_int(v): + '''Decode a Base58 encoded string as an integer''' + + v = scrub_input(v) + + decimal = 0 + for char in v: + decimal = decimal * 58 + alphabet.index(char) + return decimal + + +def b58decode(v): + '''Decode a Base58 encoded string''' + + v = scrub_input(v) + + origlen = len(v) + v = v.lstrip(alphabet[0:1]) + newlen = len(v) + + acc = b58decode_int(v) + + result = [] + while acc > 0: + acc, mod = divmod(acc, 256) + result.append(mod) + + return (b'\0' * (origlen - newlen) + bseq(reversed(result))) + + +def b58encode_check(v): + '''Encode a string using Base58 with a 4 character checksum''' + + digest = sha256(sha256(v).digest()).digest() + return b58encode(v + digest[:4]) + + +def b58decode_check(v): + '''Decode and verify the checksum of a Base58 encoded string''' + + result = b58decode(v) + result, check = result[:-4], result[-4:] + digest = sha256(sha256(result).digest()).digest() + + if check != digest[:4]: + raise ValueError("Invalid checksum") + + return result + + +def main(): + '''Base58 encode or decode FILE, or standard input, to standard output.''' + + import sys + import argparse + + stdout = buffer(sys.stdout) + + parser = argparse.ArgumentParser(description=main.__doc__) + parser.add_argument( + 'file', + metavar='FILE', + nargs='?', + type=argparse.FileType('r'), + default='-') + parser.add_argument( + '-d', '--decode', + action='store_true', + help='decode data') + parser.add_argument( + '-c', '--check', + action='store_true', + help='append a checksum before encoding') + + args = parser.parse_args() + fun = { + (False, False): b58encode, + (False, True): b58encode_check, + (True, False): b58decode, + (True, True): b58decode_check + }[(args.decode, args.check)] + + data = buffer(args.file).read() + + try: + result = fun(data) + except Exception as e: + sys.exit(e) + + if not isinstance(result, bytes): + result = result.encode('ascii') + + stdout.write(result) + + +if __name__ == '__main__': + main() diff --git a/content/_code-samples/tx-serialization/definitions.json b/content/_code-samples/tx-serialization/definitions.json new file mode 100644 index 0000000000..cf07fbb033 --- /dev/null +++ b/content/_code-samples/tx-serialization/definitions.json @@ -0,0 +1,1663 @@ +{ + "TYPES": { + "Validation": 10003, + "Done": -1, + "Hash128": 4, + "Blob": 7, + "AccountID": 8, + "Amount": 6, + "Hash256": 5, + "UInt8": 16, + "Vector256": 19, + "STObject": 14, + "Unknown": -2, + "Transaction": 10001, + "Hash160": 17, + "PathSet": 18, + "LedgerEntry": 10002, + "UInt16": 1, + "NotPresent": 0, + "UInt64": 3, + "UInt32": 2, + "STArray": 15 + }, + "LEDGER_ENTRY_TYPES": { + "Any": -3, + "Child": -2, + "Invalid": -1, + "AccountRoot": 97, + "DirectoryNode": 100, + "RippleState": 114, + "Ticket": 84, + "SignerList": 83, + "Offer": 111, + "LedgerHashes": 104, + "Amendments": 102, + "FeeSettings": 115, + "Escrow": 117, + "PayChannel": 120, + "DepositPreauth": 112, + "Check": 67, + "Nickname": 110, + "Contract": 99, + "GeneratorMap": 103 + }, + "FIELDS": [ + [ + "Generic", + { + "nth": 0, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Unknown" + } + ], + [ + "Invalid", + { + "nth": -1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Unknown" + } + ], + [ + "LedgerEntryType", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "TransactionType", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "SignerWeight", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "Flags", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SourceTag", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "Sequence", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "PreviousTxnLgrSeq", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LedgerSequence", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "CloseTime", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ParentCloseTime", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SigningTime", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "Expiration", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TransferRate", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "WalletSize", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OwnerCount", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "DestinationTag", + { + "nth": 14, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HighQualityIn", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HighQualityOut", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LowQualityIn", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LowQualityOut", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "QualityIn", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "QualityOut", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "StampEscrow", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "BondAmount", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LoadFee", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OfferSequence", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FirstLedgerSequence", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LastLedgerSequence", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TransactionIndex", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OperationLimit", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReferenceFeeUnits", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReserveBase", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReserveIncrement", + { + "nth": 32, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SetFlag", + { + "nth": 33, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ClearFlag", + { + "nth": 34, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SignerQuorum", + { + "nth": 35, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "CancelAfter", + { + "nth": 36, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FinishAfter", + { + "nth": 37, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "IndexNext", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "IndexPrevious", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "BookNode", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "OwnerNode", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "BaseFee", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "ExchangeRate", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "LowNode", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HighNode", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "EmailHash", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash128" + } + ], + [ + "LedgerHash", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "ParentHash", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "TransactionHash", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "AccountHash", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "PreviousTxnID", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "LedgerIndex", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "WalletLocator", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "RootIndex", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "AccountTxnID", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "BookDirectory", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "InvoiceID", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Nickname", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Amendment", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "TicketID", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Digest", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "hash", + { + "nth": 257, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Hash256" + } + ], + [ + "index", + { + "nth": 258, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Hash256" + } + ], + [ + "Amount", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Balance", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LimitAmount", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "TakerPays", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "TakerGets", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LowLimit", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "HighLimit", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Fee", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "SendMax", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "DeliverMin", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "MinimumOffer", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "RippleEscrow", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "DeliveredAmount", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "taker_gets_funded", + { + "nth": 258, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Amount" + } + ], + [ + "taker_pays_funded", + { + "nth": 259, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Amount" + } + ], + [ + "PublicKey", + { + "nth": 1, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MessageKey", + { + "nth": 2, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "SigningPubKey", + { + "nth": 3, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "TxnSignature", + { + "nth": 4, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": false, + "type": "Blob" + } + ], + [ + "Generator", + { + "nth": 5, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Signature", + { + "nth": 6, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": false, + "type": "Blob" + } + ], + [ + "Domain", + { + "nth": 7, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "FundCode", + { + "nth": 8, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "RemoveCode", + { + "nth": 9, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "ExpireCode", + { + "nth": 10, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "CreateCode", + { + "nth": 11, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MemoType", + { + "nth": 12, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MemoData", + { + "nth": 13, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MemoFormat", + { + "nth": 14, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Fulfillment", + { + "nth": 16, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Condition", + { + "nth": 17, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MasterSignature", + { + "nth": 18, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": false, + "type": "Blob" + } + ], + [ + "Account", + { + "nth": 1, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Owner", + { + "nth": 2, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Destination", + { + "nth": 3, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Issuer", + { + "nth": 4, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Authorize", + { + "nth": 5, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Unauthorize", + { + "nth": 6, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Target", + { + "nth": 7, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "RegularKey", + { + "nth": 8, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "ObjectEndMarker", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "TransactionMetaData", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "CreatedNode", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "DeletedNode", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "ModifiedNode", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "PreviousFields", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "FinalFields", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "NewFields", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "TemplateEntry", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Memo", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "SignerEntry", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Signer", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Majority", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "ArrayEndMarker", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Signers", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": false, + "type": "STArray" + } + ], + [ + "SignerEntries", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Template", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Necessary", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Sufficient", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "AffectedNodes", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Memos", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Majorities", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "CloseResolution", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "Method", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "TransactionResult", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "TakerPaysCurrency", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerPaysIssuer", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerGetsCurrency", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerGetsIssuer", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "Paths", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "PathSet" + } + ], + [ + "Indexes", + { + "nth": 1, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "Hashes", + { + "nth": 2, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "Amendments", + { + "nth": 3, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "Transaction", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Transaction" + } + ], + [ + "LedgerEntry", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "LedgerEntry" + } + ], + [ + "Validation", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Validation" + } + ], + [ + "SignerListID", + { + "nth": 38, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SettleDelay", + { + "nth": 39, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "Channel", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "ConsensusHash", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "CheckID", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "TickSize", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "DestinationNode", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ] + ], + "TRANSACTION_RESULTS": { + "telNO_DST_PARTIAL": -393, + "temBAD_SRC_ACCOUNT": -281, + "tefPAST_SEQ": -189, + "terNO_ACCOUNT": -96, + "temREDUNDANT": -275, + "tefCREATED": -194, + "temDST_IS_SRC": -279, + "terRETRY": -99, + "temINVALID_FLAG": -276, + "temBAD_SEND_XRP_LIMIT": -288, + "terNO_LINE": -94, + "tefBAD_AUTH": -196, + "temBAD_EXPIRATION": -295, + "temBAD_SEND_XRP_NO_DIRECT": -286, + "temBAD_SEND_XRP_PATHS": -284, + "tefBAD_LEDGER": -195, + "tefNO_AUTH_REQUIRED": -190, + "terOWNERS": -93, + "terLAST": -91, + "terNO_RIPPLE": -90, + "temBAD_FEE": -294, + "terPRE_SEQ": -92, + "tefMASTER_DISABLED": -187, + "temBAD_CURRENCY": -296, + "tefDST_TAG_NEEDED": -193, + "temBAD_SIGNATURE": -282, + "tefFAILURE": -199, + "telBAD_PATH_COUNT": -397, + "temBAD_TRANSFER_RATE": -280, + "tefWRONG_PRIOR": -188, + "telBAD_DOMAIN": -398, + "temBAD_AMOUNT": -298, + "temBAD_AUTH_MASTER": -297, + "temBAD_LIMIT": -292, + "temBAD_ISSUER": -293, + "telBAD_PUBLIC_KEY": -396, + "tefBAD_ADD_AUTH": -197, + "temBAD_OFFER": -291, + "temBAD_SEND_XRP_PARTIAL": -285, + "temDST_NEEDED": -278, + "tefALREADY": -198, + "temUNCERTAIN": -272, + "telLOCAL_ERROR": -399, + "temREDUNDANT_SEND_MAX": -274, + "tefINTERNAL": -191, + "temBAD_PATH_LOOP": -289, + "tefEXCEPTION": -192, + "temRIPPLE_EMPTY": -273, + "telINSUF_FEE_P": -394, + "temBAD_SEQUENCE": -283, + "tefMAX_LEDGER": -186, + "terFUNDS_SPENT": -98, + "temBAD_SEND_XRP_MAX": -287, + "telFAILED_PROCESSING": -395, + "terINSUF_FEE_B": -97, + "tesSUCCESS": 0, + "temBAD_PATH": -290, + "temMALFORMED": -299, + "temUNKNOWN": -271, + "temINVALID": -277, + "terNO_AUTH": -95, + "temBAD_TICK_SIZE": -270, + + "tecCLAIM": 100, + "tecPATH_PARTIAL": 101, + "tecUNFUNDED_ADD": 102, + "tecUNFUNDED_OFFER": 103, + "tecUNFUNDED_PAYMENT": 104, + "tecFAILED_PROCESSING": 105, + "tecDIR_FULL": 121, + "tecINSUF_RESERVE_LINE": 122, + "tecINSUF_RESERVE_OFFER": 123, + "tecNO_DST": 124, + "tecNO_DST_INSUF_XRP": 125, + "tecNO_LINE_INSUF_RESERVE": 126, + "tecNO_LINE_REDUNDANT": 127, + "tecPATH_DRY": 128, + "tecUNFUNDED": 129, + "tecNO_ALTERNATIVE_KEY": 130, + "tecNO_REGULAR_KEY": 131, + "tecOWNERS": 132, + "tecNO_ISSUER": 133, + "tecNO_AUTH": 134, + "tecNO_LINE": 135, + "tecINSUFF_FEE": 136, + "tecFROZEN": 137, + "tecNO_TARGET": 138, + "tecNO_PERMISSION": 139, + "tecNO_ENTRY": 140, + "tecINSUFFICIENT_RESERVE": 141, + "tecNEED_MASTER_KEY": 142, + "tecDST_TAG_NEEDED": 143, + "tecINTERNAL": 144, + "tecOVERSIZE": 145, + "tecCRYPTOCONDITION_ERROR": 146, + "tecINVARIANT_FAILED": 147, + "tecEXPIRED": 148, + "tecDUPLICATE": 149 + }, + "TRANSACTION_TYPES": { + "Invalid": -1, + "Payment": 0, + "EscrowCreate": 1, + "EscrowFinish": 2, + "AccountSet": 3, + "EscrowCancel": 4, + "SetRegularKey": 5, + "NickNameSet": 6, + "OfferCreate": 7, + "OfferCancel": 8, + "Contract": 9, + "TicketCreate": 10, + "TicketCancel": 11, + "SignerListSet": 12, + "PaymentChannelCreate": 13, + "PaymentChannelFund": 14, + "PaymentChannelClaim": 15, + "CheckCreate": 16, + "CheckCash": 17, + "CheckCancel": 18, + "DepositPreauth": 19, + "TrustSet": 20, + "EnableAmendment": 100, + "SetFee": 101 + } +} diff --git a/content/_code-samples/tx-serialization/field_ordering.py b/content/_code-samples/tx-serialization/field_ordering.py new file mode 100644 index 0000000000..2d6d111e37 --- /dev/null +++ b/content/_code-samples/tx-serialization/field_ordering.py @@ -0,0 +1,180 @@ +# Canonical ordering (of transaction signing fields only) +# for serializing and signing transactions + +TYPES = { + # Type name: canonical order for this type + "UInt16": 1, + "UInt32": 2, + "UInt64": 3, + "Hash128": 4, + "Hash256": 5, + "Amount": 6, + "VL": 7, # VariableLength + "Account": 8, + # 9-13 are reserved + "Object": 14, + "Array": 15, + "UInt8": 16, + "Hash160": 17, + "PathSet": 18, # "Paths" field of cross-currency payments only + "Vector256": 19, +} + +FIELDS = { + # Type name: Canonical order for this field (among fields of its type) + + # UInt16 types + "LedgerEntryType": 1, + "TransactionType": 2, + "SignerWeight": 3, + # UInt32 types + "Flags": 2, + "SourceTag": 3, + "Sequence": 4, + "PreviousTxnLgrSeq": 5, + "LedgerSequence": 6, + "CloseTime": 7, + "ParentCloseTime": 8, + "SigningTime": 9, + "Expiration": 10, + "TransferRate": 11, + "WalletSize": 12, + "OwnerCount": 13, + "DestinationTag": 14, + "HighQualityIn": 16, + "HighQualityOut": 17, + "LowQualityIn": 18, + "LowQualityOut": 19, + "QualityIn": 20, + "QualityOut": 21, + "StampEscrow": 22, + "BondAmount": 23, + "LoadFee": 24, + "OfferSequence": 25, + "FirstLedgerSequence": 26, + "LastLedgerSequence": 27, + "TransactionIndex": 28, + "OperationLimit": 29, + "ReferenceFeeUnits": 30, + "ReserveBase": 31, + "ReserveIncrement": 32, + "SetFlag": 33, + "ClearFlag": 34, + "SignerQuorum": 35, + "CancelAfter": 36, + "FinishAfter": 37, + "SignerListID": 38, + "SettleDelay": 39, + # UInt64 types + "IndexNext": 1, + "IndexPrevious": 2, + "BookNode": 3, + "OwnerNode": 4, + "BaseFee": 5, + "ExchangeRate": 6, + "LowNode": 7, + "HighNode": 8, + "DestinationNode": 9, + "Cookie": 10, + # Hash128 types + "EmailHash": 1, + # Hash256 types + "LedgerHash": 1, + "ParentHash": 2, + "TransactionHash": 3, + "AccountHash": 4, + "PreviousTxnID": 5, + "LedgerIndex": 6, + "WalletLocator": 7, + "RootIndex": 8, + "AccountTxnID": 9, + "BookDirectory": 16, + "InvoiceID": 17, + "Nickname": 18, + "Amendment": 19, + "TicketID": 20, + "Digest": 21, + "Channel": 22, + "ConsensusHash": 23, + "CheckID": 24, + "hash": 257, + "index": 258, + # Amount types + "Amount": 1, + "Balance": 2, + "LimitAmount": 3, + "TakerPays": 4, + "TakerGets": 5, + "LowLimit": 6, + "HighLimit": 7, + "Fee": 8, + "SendMax": 9, + "DeliverMin": 10, + "MinimumOffer": 16, + "RippleEscrow": 17, + "DeliveredAmount": 18, + # VL types + "PublicKey": 1, + "MessageKey": 2, + "SigningPubKey": 3, + "TxnSignature": 4, + "Signature": 6, + "Domain": 7, + "FundCode": 8, + "RemoveCode": 9, + "ExpireCode": 10, + "CreateCode": 11, + "MemoType": 12, + "MemoData": 13, + "MemoFormat": 14, + "Fulfillment": 16, + "Condition": 17, + "MasterSignature": 18, + # Account types + "Account": 1, + "Owner": 2, + "Destination": 3, + "Issuer": 4, + "Authorize": 5, + "Unauthorize": 6, + "Target": 7, + "RegularKey": 8, + # Object types + "TransactionMetaData": 2, + "CreatedNode": 3, + "DeletedNode": 4, + "ModifiedNode": 5, + "PreviousFields": 6, + "FinalFields": 7, + "NewFields": 8, + "TemplateEntry": 9, + "Memo": 10, + "SignerEntry": 11, + "Signer": 16, + "Majority": 18, + # Array types + "Signers": 3, + "SignerEntries": 4, + "Template": 5, + "Necessary": 6, + "Sufficient": 7, + "AffectedNodes": 8, + "Memos": 9, + "Majorities": 16, + # UInt8 types + "CloseResolution": 1, + "Method": 2, + "TransactionResult": 3, + "TickSize": 16, + # Hash160 types + "TakerPaysCurrency": 1, + "TakerPaysIssuer": 2, + "TakerGetsCurrency": 3, + "TakerGetsIssuer": 4, + # PathSet types + "Paths": 1, + # Vector256 types + "Indexes": 1, + "Hashes": 2, + "Amendments": 3, +} diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py new file mode 100755 index 0000000000..c76a32c227 --- /dev/null +++ b/content/_code-samples/tx-serialization/serialize.py @@ -0,0 +1,154 @@ +#!/bin/env python3 + +# Transaction Serialization Sample Code (Python3 version) +# Author: rome@ripple.com +# Copyright Ripple 2018 + +import json +import logging + +from address import decode_address + +logger = logging.getLogger(__name__) +logger.addHandler(logging.StreamHandler()) + +def load_defs(fname="definitions.json"): + with open(fname) as definitions_file: + definitions = json.load(definitions_file) + return { + "TYPES": definitions["TYPES"], + # type_name str: type_sort_key int + "FIELDS": {k:v for (k,v) in definitions["FIELDS"]}, # convert list of tuples to dict + # field_name str: { + # nth: field_sort_key int, + # isVLEncoded: bool, + # isSerialized: bool, + # isSigningField: bool, + # type: type_name str + # } + "LEDGER_ENTRY_TYPES": definitions["LEDGER_ENTRY_TYPES"], + "TRANSACTION_RESULTS": definitions["TRANSACTION_RESULTS"], + "TRANSACTION_TYPES": definitions["TRANSACTION_TYPES"], + } + + +def field_sort_key(field_name): + """Return a tuple sort key for a given field name""" + field_type_name = DEFINITIONS["FIELDS"][field_name]["type"] + return (DEFINITIONS["TYPES"][field_type_name], DEFINITIONS["FIELDS"][field_name]["nth"]) + +def field_id(field_name): + field_type_name = DEFINITIONS["FIELDS"][field_name]["type"] + type_code = DEFINITIONS["TYPES"][field_type_name] + field_code = DEFINITIONS["FIELDS"][field_name]["nth"] + + if type_code < 16 and field_code < 16: + # first 4 bits is the type_code, next 4 bits is the field code + combined_code = (type_code << 4) | field_code + return combined_code.to_bytes(1, byteorder="big", signed=False) + else: + # TODO: need more than 1 byte to encode this field id + raise ValueError("field_id not yet implemented for types/fields > 16") + +def bytes_from_uint(i, bits): + if bits % 8: + raise ValueError("bytes_from_uint: bits must be divisible by 8") + return i.to_bytes(bits // 8, byteorder="big", signed=False) + +def amount_to_bytes(a): + if type(a) == str: + # is XRP + xrp_amt = int(a) + if (xrp_amt >= 0): + # set the "is positive" bit -- this is backwards from usual two's complement! + xrp_amt = xrp_amt | 0x4000000000000000 + else: + # convert to absolute value, leaving the "is positive" bit unset + xrp_amt = -xrp_amt + return xrp_amt.to_bytes(8, byteorder="big", signed=False) + elif type(a) == dict: + if sorted(a.keys()) != ["currency", "issuer", "value"]: + raise ValueError("amount must have currency, value, issuer only (actually had: %s)" % + sorted(a.keys())) + #TODO: canonicalize mantissa/exponent/etc. of issued currency amount + # https://developers.ripple.com/currency-formats.html#issued-currency-math + # temporarily returning all zeroes + issued_amt = bytes(8) + # TODO: calculate 160-bit currency ID + currency_code = bytes(20) + return issued_amt + currency_code + decode_address(a["issuer"]) + else: + raise ValueError("amount must be XRP string or {currency, value, issuer}") + +def tx_type_to_bytes(txtype): + type_uint = DEFINITIONS["TRANSACTION_TYPES"][txtype] + return type_uint.to_bytes(2, byteorder="big", signed=False) + +def field_to_bytes(field_name, field_val): + field_type = DEFINITIONS["FIELDS"][field_name]["type"] + logger.debug("Serializing field {f} of type {t}".format(f=field_name, t=field_type)) + + id_prefix = field_id(field_name) + logger.debug("id_prefix is: %s" % id_prefix.hex()) + + if field_name == "TransactionType": + # Special case: convert from string to UInt16 + return b''.join( (id_prefix, tx_type_to_bytes(field_val)) ) + + dispatch = { + # TypeName: function(field): bytes object + "UInt64": lambda x:bytes_from_uint(x, 64), + "UInt32": lambda x:bytes_from_uint(x, 32), + "UInt16": lambda x:bytes_from_uint(x, 16), + "UInt8" : lambda x:bytes_from_uint(x, 8), + "AccountID": decode_address, + "Amount": amount_to_bytes + } + field_binary = dispatch[field_type](field_val) + return b''.join( (id_prefix, field_binary) ) + +def serialize_tx(tx): + field_order = sorted(tx.keys(), key=field_sort_key) + logger.debug("Canonical field order: %s" % field_order) + + fields_as_bytes = [] + for field_name in field_order: + field_val = tx[field_name] + fields_as_bytes.append(field_to_bytes(field_name, field_val)) + + all_serial = b''.join(fields_as_bytes) + logger.info(all_serial.hex().upper()) + return all_serial + + + +################################################################################ + +if __name__ == "__main__": + logger.setLevel(logging.DEBUG) + DEFINITIONS = load_defs() + + example_tx = { + "TransactionType" : "Payment", + "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Destination" : "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "Amount" : { + "currency" : "USD", + "value" : "1", + "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" + }, + "Fee": "12", + "Flags": 2147483648, + "Sequence": 2 + } + + serialize_tx(example_tx) + + # example rippled signature: + # ./rippled sign masterpassphrase (the above JSON) + # where "masterpassphrase" is the key behind rHb9... + # snoPBrXtMeMyMHUVTgbuqAfg1SUTb in base58 + # "tx_blob" : "1200002280000000240000000261D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA968400000000000000C73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD0207446304402201FE0A74FC1BDB509C8F42B861EF747C43B92917706BB623F0A0D621891933AF402205206FBA8B0BF6733DB5B03AD76B5A76A2D46DF9093916A3BEC78897E58A3DF148114B5F762798A53D543A014CAF8B297CFF8F2F937E883143E9D4A2B8AA0780F682D136F7A56D6724EF53754", + # "SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + # "TxnSignature" : "304402201FE0A74FC1BDB509C8F42B861EF747C43B92917706BB623F0A0D621891933AF402205206FBA8B0BF6733DB5B03AD76B5A76A2D46DF9093916A3BEC78897E58A3DF14", + # "hash" : "8BA1509E4FB80CCF76CD9DE924B8B71597637C775BA2DC515F90C333DA534BF3" diff --git a/content/references/rippled-api/transaction-formats/txserialization.md b/content/references/rippled-api/transaction-formats/txserialization.md index 7fdc3da4d2..761537df44 100644 --- a/content/references/rippled-api/transaction-formats/txserialization.md +++ b/content/references/rippled-api/transaction-formats/txserialization.md @@ -5,17 +5,16 @@ XRP Ledger transactions have a canonical binary format, which is necessary to cr The process of serializing a transaction from JSON or any other representation into their canonical binary format can be summarized with these steps: -1. Convert each field's data into its "internal" binary format. -2. Sort the fields in canonical order. -3. Concatenate the fields in their sorted order. ***TODO: seems there must be some sort of wrapping/control data to indicate which fields are present for cases with optional fields.*** +1. Make sure all required fields are provided, including any required but "auto-fillable" fields. + **Note:** The `SigningPubKey` must also be provided at this step. When signing, you can derive this key from the secret key that is provided for signing. +2. Convert each field's data into its "internal" binary format. +3. Sort the fields in canonical order. +4. Prefix each field with an identifier. +5. Concatenate the fields (including prefixes) in their sorted order. -When serializing transaction instructions to be signed, you must also: +The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. After signing, you must attach the signature to the transaction, calculate the transaction's identifying hash, then re-serialize the transaction with the additional fields. -- Make sure all required fields are provided, including any required but "auto-fillable" fields. -- Add the `SigningPubKey` field ***TODO: at what point in the above process? Probably before sorting, if it's a signing field?*** - - -The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. The hard work is the details of each of those steps. +The hard work is the details of each of those steps. ***Notes: some useful links for signing:*** @@ -32,6 +31,8 @@ For example, the `Flags` [common transaction field](transaction-common-fields.ht All fields in a transaction are sorted in a specific order based on the field's type first, then the field itself second. (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) +When you combine the type code and sort code, you get the field's identifier, which is prefixed before the field in the final serialized blob. If both the type code and the field code + ### Type Codes Each field type has an arbitrary sort code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). @@ -40,7 +41,7 @@ For example, [UInt32 has sort order 2](https://github.com/ripple/rippled/blob/72 ### Field Codes -Each field also has a sort code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). +Each field also has a field code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). For example, the `Account` field of a [Payment transaction][] [has sort code 1](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L219), so it comes before the `Destination` field which [has sort code 3](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L221). @@ -55,4 +56,14 @@ Some fields, such as `SignerEntry` (in [SignerListSet transactions][]), and `Mem ### Amount Fields -The "AMOUNT" type is a special field type that represents an amount of currency, either XRP or an issued currency. ***TODO: details on how both are serialized in transactions.*** +The "Amount" type is a special field type that represents an amount of currency, either XRP or an issued currency. This type consists of two sub-types: + +- **XRP** + XRP is serialized as a 64-bit unsigned integer (big-endian order), except that the second-most-significant bit is `1` to indicate that it is positive. In other words, take a standard UInt64 and calculate the bitwise-OR of that with `0x4000000000000000` to get the serialized format. +- **Issued Currencies** + Issued currencies consist of three segments in order: + 1. 64 bits indicating the amount in the [internal currency format](currency-formats.html#issued-currency-math). The first bit is `1` to indicate that this is not XRP. + 2. 160 bits indicating the [currency code](https://developers.ripple.com/currency-formats.html#currency-codes). The standard API converts 3-character codes such as "USD" into 160-bit codes using the [standard currency code format](currency-formats.html#standard-currency-codes), but custom 160-bit codes are also possible. + 3. 160 bits indicating the issuer's Account ID. (See also: [Account Address Encoding](accounts.html#address-encoding)) + +You can tell which of the two sub-types it is based on the first bit: `0` for XRP; `1` for issued currency. From 04f40e66f41b6b562b5d9c93e3a6a01145490bfa Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Fri, 16 Nov 2018 18:39:47 -0800 Subject: [PATCH 03/19] Serialization: field IDs, VL encoding --- .../tx-serialization/serialize.py | 119 +++++++++++++---- .../test-cases/tx1-binary.txt | 1 + .../tx-serialization/test-cases/tx1-full.json | 125 ++++++++++++++++++ .../test-cases/tx1-nometa.json | 18 +++ .../transaction-common-fields.md | 2 +- .../transaction-formats/txserialization.md | 39 +++++- 6 files changed, 278 insertions(+), 26 deletions(-) create mode 100644 content/_code-samples/tx-serialization/test-cases/tx1-binary.txt create mode 100644 content/_code-samples/tx-serialization/test-cases/tx1-full.json create mode 100644 content/_code-samples/tx-serialization/test-cases/tx1-nometa.json diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py index c76a32c227..869aa3fc46 100755 --- a/content/_code-samples/tx-serialization/serialize.py +++ b/content/_code-samples/tx-serialization/serialize.py @@ -6,6 +6,7 @@ import json import logging +import re from address import decode_address @@ -43,12 +44,31 @@ def field_id(field_name): field_code = DEFINITIONS["FIELDS"][field_name]["nth"] if type_code < 16 and field_code < 16: - # first 4 bits is the type_code, next 4 bits is the field code + # high 4 bits is the type_code + # low 4 bits is the field code combined_code = (type_code << 4) | field_code - return combined_code.to_bytes(1, byteorder="big", signed=False) - else: - # TODO: need more than 1 byte to encode this field id - raise ValueError("field_id not yet implemented for types/fields > 16") + return bytes_from_uint(combined_code, 8) + elif type_code >= 16 and field_code < 16: + # first 4 bits are zeroes + # next 4 bits is field code + # next byte is type code + byte1 = bytes_from_uint(field_code, 8) + byte2 = bytes_from_uint(type_code, 8) + return b''.join( (byte1, byte2) ) + elif type_code < 16 and field_code >= 16: + # first 4 bits is type code + # next 4 bits are zeroes + # next byte is field code + byte1 = bytes_from_uint(type_code << 4, 8) + byte2 = bytes_from_uint(field_code, 8) + return b''.join( (byte1, byte2) ) + else: # both are >= 16 + # first byte is all zeroes + # second byte is type + # third byte is field code + byte2 = bytes_from_uint(type_code, 8) + byte3 = bytes_from_uint(field_code, 8) + return b''.join( (bytes(1), byte2, byte3) ) def bytes_from_uint(i, bits): if bits % 8: @@ -74,8 +94,7 @@ def amount_to_bytes(a): # https://developers.ripple.com/currency-formats.html#issued-currency-math # temporarily returning all zeroes issued_amt = bytes(8) - # TODO: calculate 160-bit currency ID - currency_code = bytes(20) + currency_code = currency_code_to_bytes(a["currency"]) return issued_amt + currency_code + decode_address(a["issuer"]) else: raise ValueError("amount must be XRP string or {currency, value, issuer}") @@ -84,6 +103,51 @@ def tx_type_to_bytes(txtype): type_uint = DEFINITIONS["TRANSACTION_TYPES"][txtype] return type_uint.to_bytes(2, byteorder="big", signed=False) +def currency_code_to_bytes(code_string): + if re.match(r"^[A-Za-z0-9?!@#$%^&*<>(){}\[\]|]{3}$", code_string): + # ISO 4217-like code + if code_string == "XRP": + raise ValueError("issued currency can't be XRP") + code_ascii = code_string.encode("ASCII") + # standard currency codes: https://developers.ripple.com/currency-formats.html#standard-currency-codes + # 8 bits type code (0x00) + # 96 bits reserved (0's) + # 24 bits ASCII + # 8 bits version (0x00) + # 24 bits reserved (0's) + return b''.join( ( bytes(13), code_ascii, bytes(4) ) ) + elif re.match(r"^[0-9a-fA-F]{40}$", code_string): + # raw hex code + return bytes.fromhex(code_string) # requires Python 3.5+ + else: + raise ValueError("invalid currency code") + +def vl_encode(vl_contents): + vl_len = len(vl_contents) + if vl_len <= 192: + len_byte = vl_len.to_bytes(1, byteorder="big", signed=False) + return b''.join( (len_byte, vl_contents) ) + elif vl_len <= 12480: + vl_len -= 193 + byte1 = ((vl_len >> 8) + 193).to_bytes(1, byteorder="big", signed=False) + byte2 = (vl_len & 0xff).to_bytes(1, byteorder="big", signed=False) + return b''.join( (byte1, byte2, vl_contents) ) + elif vl_len <= 918744: + vl_len -= 12481 + byte1 = (241 + (vl_len >> 16)).to_bytes(1, byteorder="big", signed=False) + byte2 = ((vl_len >> 8) & 0xff).to_bytes(1, byteorder="big", signed=False) + byte3 = (vl_len & 0xff).to_bytes(1, byteorder="big", signed=False) + return b''.join( (byte1, byte2, byte3, vl_contents) ) + + raise ValueError("VariableLength field must be <= 918744 bytes long") + +def vl_to_bytes(field_val): + vl_contents = bytes.fromhex(field_val) + return vl_encode(vl_contents) + +def accountid_to_bytes(address): + return vl_encode(decode_address(address)) + def field_to_bytes(field_name, field_val): field_type = DEFINITIONS["FIELDS"][field_name]["type"] logger.debug("Serializing field {f} of type {t}".format(f=field_name, t=field_type)) @@ -101,8 +165,9 @@ def field_to_bytes(field_name, field_val): "UInt32": lambda x:bytes_from_uint(x, 32), "UInt16": lambda x:bytes_from_uint(x, 16), "UInt8" : lambda x:bytes_from_uint(x, 8), - "AccountID": decode_address, - "Amount": amount_to_bytes + "AccountID": accountid_to_bytes, + "Amount": amount_to_bytes, + "Blob": vl_to_bytes } field_binary = dispatch[field_type](field_val) return b''.join( (id_prefix, field_binary) ) @@ -113,8 +178,11 @@ def serialize_tx(tx): fields_as_bytes = [] for field_name in field_order: - field_val = tx[field_name] - fields_as_bytes.append(field_to_bytes(field_name, field_val)) + if (DEFINITIONS["FIELDS"][field_name]["isSerialized"]): + field_val = tx[field_name] + field_bytes = field_to_bytes(field_name, field_val) + logger.debug("{n}: {h}".format(n=field_name, h=field_bytes.hex())) + fields_as_bytes.append(field_bytes) all_serial = b''.join(fields_as_bytes) logger.info(all_serial.hex().upper()) @@ -128,19 +196,22 @@ if __name__ == "__main__": logger.setLevel(logging.DEBUG) DEFINITIONS = load_defs() - example_tx = { - "TransactionType" : "Payment", - "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", - "Destination" : "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", - "Amount" : { - "currency" : "USD", - "value" : "1", - "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" - }, - "Fee": "12", - "Flags": 2147483648, - "Sequence": 2 - } + # example_tx = { + # "TransactionType" : "Payment", + # "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + # "Destination" : "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + # "Amount" : { + # "currency" : "USD", + # "value" : "1", + # "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" + # }, + # "Fee": "12", + # "Flags": 2147483648, + # "Sequence": 2 + # } + + with open("test-cases/tx1-nometa.json") as f: + example_tx = json.load(f) serialize_tx(example_tx) diff --git a/content/_code-samples/tx-serialization/test-cases/tx1-binary.txt b/content/_code-samples/tx-serialization/test-cases/tx1-binary.txt new file mode 100644 index 0000000000..4b66a67703 --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx1-binary.txt @@ -0,0 +1 @@ +120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A732103EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE3744630440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C8114DD76483FACDEE26E60D8A586BB58D09F27045C46 diff --git a/content/_code-samples/tx-serialization/test-cases/tx1-full.json b/content/_code-samples/tx-serialization/test-cases/tx1-full.json new file mode 100644 index 0000000000..fc02fcfae3 --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx1-full.json @@ -0,0 +1,125 @@ +{ + "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", + "metaData": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Flags": 0, + "IndexNext": "0000000000000000", + "IndexPrevious": "0000000000000000", + "Owner": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + "RootIndex": "10AF5737F535F47CA9E8B6F82C4B7F4D998B1B7C44185C6078A22C751FD9FB7D" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "10AF5737F535F47CA9E8B6F82C4B7F4D998B1B7C44185C6078A22C751FD9FB7D" + } + }, + { + "DeletedNode": { + "FinalFields": { + "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + "BookDirectory": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10BFD011CB2800", + "BookNode": "0000000000000000", + "Expiration": 595640096, + "Flags": 131072, + "OwnerNode": "0000000000000000", + "PreviousTxnID": "50C2CBD3BEF831D80C2950D3001E67F1D257665569A9D77B1F0E0B8B4D178CEB", + "PreviousTxnLgrSeq": 43010795, + "Sequence": 1752791, + "TakerGets": "15000000000", + "TakerPays": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "7071.75" + } + }, + "LedgerEntryType": "Offer", + "LedgerIndex": "233E9A034C083E895EF7B4F6A643291FEF1608D55C8C5783F71E9C5F82D3E7FB" + } + }, + { + "ModifiedNode": { + "FinalFields": { + "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + "Balance": "37180946255", + "Flags": 0, + "OwnerCount": 6, + "Sequence": 1752793 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "9EB65374048F2AED1995A6725D4234545432083B0C5728627E06443A8E1F4C98", + "PreviousFields": { + "Balance": "37180946265", + "Sequence": 1752792 + }, + "PreviousTxnID": "50C2CBD3BEF831D80C2950D3001E67F1D257665569A9D77B1F0E0B8B4D178CEB", + "PreviousTxnLgrSeq": 43010795 + } + }, + { + "DeletedNode": { + "FinalFields": { + "ExchangeRate": "4E10BFD011CB2800", + "Flags": 0, + "RootIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10BFD011CB2800", + "TakerGetsCurrency": "0000000000000000000000000000000000000000", + "TakerGetsIssuer": "0000000000000000000000000000000000000000", + "TakerPaysCurrency": "0000000000000000000000005553440000000000", + "TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1" + }, + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10BFD011CB2800" + } + }, + { + "CreatedNode": { + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10C0730D0B8000", + "NewFields": { + "ExchangeRate": "4E10C0730D0B8000", + "RootIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10C0730D0B8000", + "TakerPaysCurrency": "0000000000000000000000005553440000000000", + "TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1" + } + } + }, + { + "CreatedNode": { + "LedgerEntryType": "Offer", + "LedgerIndex": "FD6C2E2D72319FB0C22FC50B0AF993B2AF26717927EAEB1E8857971EE2C3CADD", + "NewFields": { + "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + "BookDirectory": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10C0730D0B8000", + "Expiration": 595640108, + "Flags": 131072, + "Sequence": 1752792, + "TakerGets": "15000000000", + "TakerPays": { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "value": "7072.8" + } + } + } + } + ], + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS" + } +} diff --git a/content/_code-samples/tx-serialization/test-cases/tx1-nometa.json b/content/_code-samples/tx-serialization/test-cases/tx1-nometa.json new file mode 100644 index 0000000000..987dd58aa7 --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx1-nometa.json @@ -0,0 +1,18 @@ +{ + "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" +} diff --git a/content/references/rippled-api/transaction-formats/transaction-common-fields.md b/content/references/rippled-api/transaction-formats/transaction-common-fields.md index 5e12440224..91f08f34e4 100644 --- a/content/references/rippled-api/transaction-formats/transaction-common-fields.md +++ b/content/references/rippled-api/transaction-formats/transaction-common-fields.md @@ -14,7 +14,7 @@ Every transaction has the same set of common fields, plus additional fields base | [Memos][] | Array of Objects | Array | _(Optional)_ Additional arbitrary information used to identify this transaction. | | [Signers][] | Array | Array | _(Optional)_ Array of objects that represent a [multi-signature](multi-signing.html) which authorizes this transaction. | | SourceTag | Unsigned Integer | UInt32 | _(Optional)_ Arbitrary integer used to identify the reason for this payment, or a sender on whose behalf this transaction is made. Conventionally, a refund should specify the initial payment's `SourceTag` as the refund payment's `DestinationTag`. | -| SigningPubKey | String | PubKey | _(Automatically added when signing)_ Hex representation of the public key that corresponds to the private key used to sign this transaction. If an empty string, indicates a multi-signature is present in the `Signers` field instead. | +| SigningPubKey | String | VariableLength | _(Automatically added when signing)_ Hex representation of the public key that corresponds to the private key used to sign this transaction. If an empty string, indicates a multi-signature is present in the `Signers` field instead. | | TxnSignature | String | VariableLength | _(Automatically added when signing)_ The signature that verifies this transaction as originating from the account it says it is from. | [auto-fillable]: #auto-fillable-fields diff --git a/content/references/rippled-api/transaction-formats/txserialization.md b/content/references/rippled-api/transaction-formats/txserialization.md index 761537df44..b63c2b0a8b 100644 --- a/content/references/rippled-api/transaction-formats/txserialization.md +++ b/content/references/rippled-api/transaction-formats/txserialization.md @@ -31,7 +31,25 @@ For example, the `Flags` [common transaction field](transaction-common-fields.ht All fields in a transaction are sorted in a specific order based on the field's type first, then the field itself second. (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) -When you combine the type code and sort code, you get the field's identifier, which is prefixed before the field in the final serialized blob. If both the type code and the field code +### Field IDs + +[[Source - Encoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L117-L148 "Source") +[[Source - Decoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L484-L509 "Source") + + +When you combine the type code and sort code, you get the field's identifier, which is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and field codes it combines. See the following table: + +| | Type Code < 16 | Type Code >= 16 | +|:-----------------|:------------------------------------------------------------------------------|:--| +| **Field Code < 16** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | +| **Field Code >= 16** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | + +When decoding, you can tell how many bytes the field ID is by which bits **of the first byte** are zeroes. This corresponds to the cases in the above table: + +| | High 4 bits are nonzero | High 4 bits are zero | +|:-----------------|:------------------------------------------------------------------------------|:--| +| **Low 4 bits are nonzero** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | +| **Low 4 bits are zero** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | ### Type Codes @@ -67,3 +85,22 @@ The "Amount" type is a special field type that represents an amount of currency, 3. 160 bits indicating the issuer's Account ID. (See also: [Account Address Encoding](accounts.html#address-encoding)) You can tell which of the two sub-types it is based on the first bit: `0` for XRP; `1` for issued currency. + +### VariableLength Fields + +The "VariableLength" type represents binary data of arbitrary length. The most important such fields are `SigningPubKey`, which every signed transaction includes to indicate the key pair used to sign the transaction, and `TxnSignature`, which contains the actual signature for any signed transaction. (Other uses of variable length fields include account `Domain` settings and the `Condition` and `Fulfillment` of conditional escrows.) + +VariableLength fields are encoded with one to three bytes indicating the length of the field immediately after the type prefix and before the contents. + +- If the VariableLength field contains 0 to 192 bytes of data, the first byte defines the length of the VariableLength data, then that many bytes of data follow immediately after the length byte. +- If the VariableLength field contains 193 to 12480 bytes of data, the first two bytes indicate the length of the field with the following formula: + 193 + ((byte1 - 193) * 256) + byte2 +- If the VariableLength field contains 12481 to 918744 bytes of data, the first three bytes indicate the length of the field with the following formula: + 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3; +- A VariableLength field cannot contain more than 918744 bytes of data. + +When decoding, you can tell from the value of the first length byte whether there are 0, 1, or 2 more length bytes: + +- If the first length byte has a value of 192 or less, then that's the only length byte and it contains the exact length of the field contents in bytes. +- If the first length byte has a value of 193 to 240, then there are two length bytes. +- If the first length byte has a value of 241 to 254, then there are three length bytes. From 74190e435ee5cd98db14e7f892a9830017a71d8a Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 19 Nov 2018 17:17:25 -0800 Subject: [PATCH 04/19] Tx serialization: docs on many types w/ diagrams --- content/_img-sources/serialization-array.uxf | 151 +++++++++++ content/_img-sources/serialization-object.uxf | 253 ++++++++++++++++++ .../api-conventions/serialization.md | 188 +++++++++++++ .../transaction-formats/txserialization.md | 106 -------- dactyl-config.yml | 10 + img/serialization-array.png | Bin 0 -> 7898 bytes img/serialization-object.png | Bin 0 -> 11967 bytes 7 files changed, 602 insertions(+), 106 deletions(-) create mode 100644 content/_img-sources/serialization-array.uxf create mode 100644 content/_img-sources/serialization-object.uxf create mode 100644 content/references/rippled-api/api-conventions/serialization.md delete mode 100644 content/references/rippled-api/transaction-formats/txserialization.md create mode 100644 img/serialization-array.png create mode 100644 img/serialization-object.png diff --git a/content/_img-sources/serialization-array.uxf b/content/_img-sources/serialization-array.uxf new file mode 100644 index 0000000000..92f81f9e78 --- /dev/null +++ b/content/_img-sources/serialization-array.uxf @@ -0,0 +1,151 @@ + + + 10 + + UMLClass + + 130 + 110 + 600 + 50 + + + + + + UMLClass + + 140 + 120 + 40 + 30 + + 0xf4 + + + + Relation + + 150 + 140 + 30 + 80 + + lt=<<- + 10.0;10.0;10.0;60.0 + + + Text + + 80 + 200 + 110 + 50 + + SignerEntries +Field ID + + + + UMLClass + + 190 + 120 + 50 + 30 + + 0xeb + + + + UMLClass + + 670 + 120 + 50 + 30 + + 0xf1 + + + + Relation + + 680 + 140 + 30 + 80 + + lt=<<- + 10.0;10.0;10.0;60.0 + + + Text + + 640 + 200 + 100 + 60 + + Array end +Field ID; +no contents + + + + UMLClass + + 240 + 120 + 180 + 30 + + (SignerEntry Contents) + + + + Relation + + 210 + 140 + 100 + 80 + + lt=<<- + 10.0;10.0;10.0;60.0;80.0;60.0 + + + Text + + 300 + 180 + 110 + 50 + + SignerEntry +Field ID + + + + UMLClass + + 430 + 120 + 50 + 30 + + 0xeb + + + + UMLClass + + 480 + 120 + 180 + 30 + + (SignerEntry Contents) + + + diff --git a/content/_img-sources/serialization-object.uxf b/content/_img-sources/serialization-object.uxf new file mode 100644 index 0000000000..4924e729c6 --- /dev/null +++ b/content/_img-sources/serialization-object.uxf @@ -0,0 +1,253 @@ + + + 10 + + UMLClass + + 80 + 160 + 600 + 50 + + + + + + UMLClass + + 90 + 170 + 40 + 30 + + 0xea + + + + Relation + + 100 + 190 + 30 + 60 + + lt=<<- + 10.0;10.0;10.0;40.0 + + + Text + + 70 + 230 + 80 + 50 + + Memo +Field ID + + + + UMLClass + + 140 + 170 + 50 + 30 + + 0x7c + + + + UMLClass + + 620 + 170 + 50 + 30 + + 0xe1 + + + + Relation + + 630 + 190 + 30 + 80 + + lt=<<- + 10.0;10.0;10.0;60.0 + + + Text + + 590 + 250 + 100 + 60 + + "Object end" +Field ID; +no contents + + + + UMLClass + + 220 + 170 + 150 + 30 + + MemoType contents + + + + Relation + + 160 + 190 + 50 + 80 + + lt=<<- + 10.0;10.0;10.0;60.0;30.0;60.0 + + + Text + + 220 + 120 + 190 + 50 + + Length prefix for +variable-length Blob fields + + + + UMLClass + + 380 + 170 + 50 + 30 + + 0x7d + + + + UMLClass + + 190 + 170 + 30 + 30 + + + + + + Relation + + 190 + 130 + 50 + 60 + + lt=<<- + 10.0;40.0;10.0;10.0;30.0;10.0 + + + Text + + 190 + 230 + 110 + 50 + + MemoType +Field ID + + + + UMLClass + + 460 + 170 + 150 + 30 + + MemoData contents + + + + UMLClass + + 430 + 170 + 30 + 30 + + + + + + Relation + + 400 + 130 + 70 + 60 + + lt=<<- + 50.0;40.0;50.0;10.0;10.0;10.0 + + + Relation + + 130 + 80 + 490 + 90 + + lt=. + 10.0;60.0;10.0;20.0;220.0;20.0;220.0;10.0;220.0;20.0;470.0;20.0;470.0;70.0 + + + Text + + 290 + 40 + 170 + 50 + + Object contents in canonical order +style=wordwrap + + + + Text + + 340 + 230 + 110 + 50 + + MemoData +Field ID + + + + Relation + + 390 + 190 + 30 + 60 + + lt=<<- + 10.0;10.0;10.0;40.0 + + diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md new file mode 100644 index 0000000000..7696de01ee --- /dev/null +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -0,0 +1,188 @@ +# Serialization Format +[[Source]
](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 "Source") + +This page describes the XRP Ledger's canonical binary format for transactions and other data. This binary format is necessary to create and verify digital signatures of those transactions' contents, and is also used in other places. The [rippled APIs](rippled-apis.html) typically use JSON to communicate with client applications. However, JSON is unsuitable as a format for serializing transactions for being digitally signed, because JSON can represent the same data in many different but equivalent ways. + +The process of serializing a transaction from JSON or any other representation into their canonical binary format can be summarized with these steps: + +1. Make sure all required fields are provided, including any required but "auto-fillable" fields. + + **Note:** The `SigningPubKey` must also be provided at this step. When signing, you can derive this key from the secret key that is provided for signing. + +2. Convert each field's data into its "internal" binary format. +3. Sort the fields in canonical order. +4. Prefix each field with a Field ID. +5. Concatenate the fields (including prefixes) in their sorted order. + +The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. After signing, you must attach the signature to the transaction, calculate the transaction's identifying hash, then re-serialize the transaction with the additional fields. + +**Note:** The XRP Ledger uses the same serialization format to represent other types of data, such as [ledger objects](ledger-object-types.html) and processed transactions. However, only certain fields are appropriate for including in a transaction that gets signed. (For example, the `TxnSignature` field, containing the signature itself, should not be present in the binary blob that you sign.) Thus, some fields are designated as "Signing" fields, which are included in objects when those objects are signed, and "non-signing" fields, which are not. + +The hard work is the details of each of those steps. + +***Notes: some useful links for signing:*** + +- Actual core of the signing code in rippled: https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 +- Serialization code in ripple-lib depends on `ripple-binary-codec`. These definitions have the canonical types and sort codes for all fields: https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json + +## Internal Format + +Each field has an "internal" binary format used in the `rippled` source code to represent that field when signing (and in most other cases). The internal formats for all fields are defined in the source code of [`SField.cpp`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/impl/SField.cpp). (This file also includes fields other than transaction fields. The [Transaction Format Reference](transaction-formats.html) also lists the internal formats for all transaction fields. + +For example, the `Flags` [common transaction field](transaction-common-fields.html) becomes a UInt32 (32-bit unsigned integer). + +## Canonical Field Order + +All fields in a transaction are sorted in a specific order based on the field's type first, then the field itself second. (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) + +### Field IDs + +[[Source - Encoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L117-L148 "Source") +[[Source - Decoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L484-L509 "Source") + + +When you combine the type code and sort code, you get the field's identifier, which is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and field codes it combines. See the following table: + +| | Type Code < 16 | Type Code >= 16 | +|:-----------------|:------------------------------------------------------------------------------|:--| +| **Field Code < 16** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | +| **Field Code >= 16** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | + +When decoding, you can tell how many bytes the field ID is by which bits **of the first byte** are zeroes. This corresponds to the cases in the above table: + +| | High 4 bits are nonzero | High 4 bits are zero | +|:-----------------|:------------------------------------------------------------------------------|:--| +| **Low 4 bits are nonzero** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | +| **Low 4 bits are zero** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | + +### Type Codes + +Each field type has an arbitrary sort code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). + +For example, [UInt32 has sort order 2](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L59), so all UInt32 fields come before all [Amount fields with order 6](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L63). + +### Field Codes + +Each field also has a field code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). + +For example, the `Account` field of a [Payment transaction][] [has sort code 1](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L219), so it comes before the `Destination` field which [has sort code 3](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L221). + +The field code is combined with the type code to make a field's [Field ID](#field-ids). + +### Variable-Length Encoding + +Some types of fields are Variable-Length encoding, which means they are not always the same byte length and are prefixed with a length indicator to indicate how much data they contain. `Blob` fields (containing arbitrary binary data) are one such variable-length encoded type. For a list of which types are variable-length encoded, see the [Internal Formats](#internal-formats) table. + +**Note:** Some types that are not variable-length encoded nonetheless vary in length. These types have different ways of indicating how long they are. + +Variable-length fields are encoded with one to three bytes indicating the length of the field immediately after the type prefix and before the contents. + +- If the field contains 0 to 192 bytes of data, the first byte defines the length of the VariableLength data, then that many bytes of data follow immediately after the length byte. +- If the field contains 193 to 12480 bytes of data, the first two bytes indicate the length of the field with the following formula: + 193 + ((byte1 - 193) * 256) + byte2 +- If the field contains 12481 to 918744 bytes of data, the first three bytes indicate the length of the field with the following formula: + 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3; +- A variable-length field cannot contain more than 918744 bytes of data. + +When decoding, you can tell from the value of the first length byte whether there are 0, 1, or 2 more length bytes: + +- If the first length byte has a value of 192 or less, then that's the only length byte and it contains the exact length of the field contents in bytes. +- If the first length byte has a value of 193 to 240, then there are two length bytes. +- If the first length byte has a value of 241 to 254, then there are three length bytes. + + +## Type List + +Transaction instructions may contain fields of any of the following types: + +| Type Name | Type Code | Variable-Length? | Description | +|:--------------|:----------|:-----------------|:------------------------------| +| [AccountID][] | 8 | Yes | The unique identifier for an [account](accounts.html). This field is variable-length encoded, but always exactly 20 bytes. | +| [Amount][] | 6 | No | An amount of XRP or issued currency. The length of the field is 64 bits for XRP or | +| [Blob][] | 7 | Yes | Arbitrary binary data. One important such field is `TxnSignature`, the signature that authorizes a transaction. | +| [Hash128][] | 4 | No | A 128-bit arbitrary binary value. The only such field is `EmailHash`, which is intended to store the MD-5 hash of an account owner's email for purposes of fetching a [Gravatar](https://www.gravatar.com/). | +| [Hash160][] | 17 | No | A 160-bit arbitrary binary value. This may define a currency code or issuer. | +| [Hash256][] | 5 | No | A 256-bit arbitrary binary value. This usually represents the "SHA-512Half" hash of a transaction, ledger version, or ledger data object. | +| [PathSet][] | 18 | No | A set of possible [payment paths](paths.html) for a [cross-currency payment](cross-currency-payments.html). | +| [STArray][] | 15 | No | An array containing a variable number of members, which can be different types depending on the field. Two cases of this include [memos](transaction-common-fields.html#memos-field) and lists of signers used in [multi-signing](multi-signing.html). | +| [STObject][] | 14 | No | An object containing one or more nested fields. | +| [UInt8][] | 16 | No | An 8-bit unsigned integer. | +| [UInt16][] | 1 | No | A 16-bit unsigned integer. The `TransactionType` is a special case of this type, with specific strings mapping to integer values. | +| [UInt32][] | 2 | No | A 32-bit unsigned integer. The `Flags` and `Sequence` fields on all transactions are examples of this type. | + +In addition to all of the above field types, the following types may appear in other contexts, such as [ledger objects](ledger-object-types.html) and [transaction metadata](transaction-metadata.html): + +| Type Name | Type Code | Variable-Length? | Description | +|:--------------|:----------|:-----------------|:------------------------------| +| Transaction | 10001 | No | A "high-level" type containing an entire [transaction](transaction-formats.html). | +| LedgerEntry | 10002 | No | A "high-level" type containing an entire [ledger object](ledger-object-types.html). | +| Validation | 10003 | No | A "high-level" type used in peer-to-peer communications to represent a validation vote in the [consensus process](consensus.html). | +| Metadata | 10004 | No | A "high-level" type containing [metadata for one transaction](transaction-metadata.html). | +| [UInt64][] | 3 | No | A 64-bit unsigned integer. This type does not appear in transaction instructions, but several use fields of this type. | +| [Vector256][] | 19 | Yes | This type does not appear in transaction instructions, but the [Amendments ledger object](amendments-object.html)'s `Amendments` field uses this to represent which [amendments](amendments.html) are currently enabled. | + + +### AccountID Fields +[AccountID]: #accountid-fields + +Fields of this type contain the 160-bit identifier for an XRP Ledger [account](accounts.html). In JSON, these fields are represented as base58 XRP Ledger "addresses", with additional checksum data so that typos are unlikely to result in valid addresses. (This encoding, sometimes called "Base58Check", prevents accidentally sending money to the wrong address.) The binary format for these fields does not contain any checksum data. (However, since the binary format is used mostly for signed transactions, a typo or other error in transcribing a signed transaction would invalidate the signature, preventing it from sending money.) + +These fields are [variable-length encoded](#variable-length-encoding) despite being a fixed 160 bits in length. As a result, the length indicator for these fields is always the byte `0x14`. + + +### Amount Fields +[Amount]: #amount-fields + +The "Amount" type is a special field type that represents an amount of currency, either XRP or an issued currency. This type consists of two sub-types: + +- **XRP** + XRP is serialized as a 64-bit unsigned integer (big-endian order), except that the second-most-significant bit is `1` to indicate that it is positive. In other words, take a standard UInt64 and calculate the bitwise-OR of that with `0x4000000000000000` to get the serialized format. +- **Issued Currencies** + Issued currencies consist of three segments in order: + 1. 64 bits indicating the amount in the [internal currency format](currency-formats.html#issued-currency-math). The first bit is `1` to indicate that this is not XRP. + 2. 160 bits indicating the [currency code](https://developers.ripple.com/currency-formats.html#currency-codes). The standard API converts 3-character codes such as "USD" into 160-bit codes using the [standard currency code format](currency-formats.html#standard-currency-codes), but custom 160-bit codes are also possible. + 3. 160 bits indicating the issuer's Account ID. (See also: [Account Address Encoding](accounts.html#address-encoding)) + +You can tell which of the two sub-types it is based on the first bit: `0` for XRP; `1` for issued currency. + + +### Array Fields +[STArray]: #array-fields + +Some transaction fields, such as `SignerEntries` (in [SignerListSet transactions][]) and [`Memos`](transaction-common-fields.html#memos-field), are arrays. + +Arrays contain several other fields in their native binary format in a specific order. Each of these fields has its normal Field ID prefix and contents. To mark the end of an array, append an item with a "Field ID" of `0xf1` (the type code for array with field code of 1) and no contents. + +The following example shows the serialization format for an array (the `SignerEntries` field): + +![Array field ID, followed by the Field ID and contents of each array element, followed by the "Array end" field ID](img/serialization-array.png) + + +### Blob Fields +[Blob]: #blob-fields + +The Blob type is a [variable-length encoded](#variable-length-encoding) field with arbitrary data. Two common fields that use this type are `SigningPubKey` and `TxnSignature`, which contain (respectively) the public key and signature that authorize a transaction to be executed. + +Blob fields have no further structure to their contents, so they consist of exactly the amount of bytes indicated in the variable-length encoding, after the Field ID and length prefixes. + + +### Object Fields +[STObject]: #object-fields + +Some fields, such as `SignerEntry` (in [SignerListSet transactions][]), and `Memo` (in `Memos` arrays) are objects. The serialization of objects is very similar to that of arrays, with one difference: **object members must be placed in canonical order** within the object field, where array fields have an explicit order already. + +The [canonical field order](#canonical-field-order) of object fields is the same as the canonical field order for all top-level fields, but the members of the object must be sorted within the object. After the last member, there is an "Object end" Field ID of `0xe1` with no contents. + +The following example shows the serialization format for an object (a single `Memo` object in the `Memos` array). + +![Object field ID, followed by the Object ID and contents of each object member in canonical order, followed by the "Object end" field ID](img/serialization-object.png) + +### UInt Fields +[UInt8]: #uint-fields +[UInt16]: #uint-fields +[UInt32]: #uint-fields +[UInt64]: #uint-fields + +The XRP Ledger has several unsigned integer types: UInt8, UInt16, UInt32, and UInt64. All of these are standard big-endian binary unsigned integers with the specified number of bits. + +When representing these fields in JSON objects, most are represented as JSON numbers by default. The exception is UInt64, which is represented as a string because some JSON decoders may try to represent these integers as 64-bit "double precision" floating point numbers, which cannot represent all distinct UInt64 values with full precision. diff --git a/content/references/rippled-api/transaction-formats/txserialization.md b/content/references/rippled-api/transaction-formats/txserialization.md deleted file mode 100644 index b63c2b0a8b..0000000000 --- a/content/references/rippled-api/transaction-formats/txserialization.md +++ /dev/null @@ -1,106 +0,0 @@ -# Transaction Serialization Format (notes) -[[Source]
](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 "Source") - -XRP Ledger transactions have a canonical binary format, which is necessary to create and verify digital signatures of those transactions' contents. (JSON is sometimes used as a serialization format, but the many equivalent ways to format and represent the same data in JSON makes it unsuitable for being digitally signed.) - -The process of serializing a transaction from JSON or any other representation into their canonical binary format can be summarized with these steps: - -1. Make sure all required fields are provided, including any required but "auto-fillable" fields. - **Note:** The `SigningPubKey` must also be provided at this step. When signing, you can derive this key from the secret key that is provided for signing. -2. Convert each field's data into its "internal" binary format. -3. Sort the fields in canonical order. -4. Prefix each field with an identifier. -5. Concatenate the fields (including prefixes) in their sorted order. - -The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. After signing, you must attach the signature to the transaction, calculate the transaction's identifying hash, then re-serialize the transaction with the additional fields. - -The hard work is the details of each of those steps. - -***Notes: some useful links for signing:*** - -- Actual core of the signing code in rippled: https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 -- Serialization code in ripple-lib depends on `ripple-binary-codec`. These definitions have the canonical types and sort codes for all fields: https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json - -## Internal Format - -Each field has an "internal" binary format used in the `rippled` source code to represent that field when signing (and in most other cases). The internal formats for all fields are defined in the source code of [`SField.cpp`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/impl/SField.cpp). (This file also includes fields other than transaction fields. The [Transaction Format Reference](transaction-formats.html) also lists the internal formats for all transaction fields. - -For example, the `Flags` [common transaction field](transaction-common-fields.html) becomes a UInt32 (32-bit unsigned integer). - -## Canonical Field Order - -All fields in a transaction are sorted in a specific order based on the field's type first, then the field itself second. (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) - -### Field IDs - -[[Source - Encoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L117-L148 "Source") -[[Source - Decoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L484-L509 "Source") - - -When you combine the type code and sort code, you get the field's identifier, which is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and field codes it combines. See the following table: - -| | Type Code < 16 | Type Code >= 16 | -|:-----------------|:------------------------------------------------------------------------------|:--| -| **Field Code < 16** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | -| **Field Code >= 16** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | - -When decoding, you can tell how many bytes the field ID is by which bits **of the first byte** are zeroes. This corresponds to the cases in the above table: - -| | High 4 bits are nonzero | High 4 bits are zero | -|:-----------------|:------------------------------------------------------------------------------|:--| -| **Low 4 bits are nonzero** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | -| **Low 4 bits are zero** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | - -### Type Codes - -Each field type has an arbitrary sort code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). - -For example, [UInt32 has sort order 2](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L59), so all UInt32 fields come before all [Amount fields with order 6](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L63). - -### Field Codes - -Each field also has a field code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). - -For example, the `Account` field of a [Payment transaction][] [has sort code 1](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L219), so it comes before the `Destination` field which [has sort code 3](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L221). - - -### Array Fields - -Some transaction fields, such as `SignerEntries` (in [SignerListSet transactions][]) and [`Memos`](transaction-common-fields.html#memos-field), are arrays. ***TODO: describe how they're serialized.*** - -### Object Fields - -Some fields, such as `SignerEntry` (in [SignerListSet transactions][]), and `Memo` (in `Memos` arrays) are objects. ***TODO: describe how objects are serialized.*** - -### Amount Fields - -The "Amount" type is a special field type that represents an amount of currency, either XRP or an issued currency. This type consists of two sub-types: - -- **XRP** - XRP is serialized as a 64-bit unsigned integer (big-endian order), except that the second-most-significant bit is `1` to indicate that it is positive. In other words, take a standard UInt64 and calculate the bitwise-OR of that with `0x4000000000000000` to get the serialized format. -- **Issued Currencies** - Issued currencies consist of three segments in order: - 1. 64 bits indicating the amount in the [internal currency format](currency-formats.html#issued-currency-math). The first bit is `1` to indicate that this is not XRP. - 2. 160 bits indicating the [currency code](https://developers.ripple.com/currency-formats.html#currency-codes). The standard API converts 3-character codes such as "USD" into 160-bit codes using the [standard currency code format](currency-formats.html#standard-currency-codes), but custom 160-bit codes are also possible. - 3. 160 bits indicating the issuer's Account ID. (See also: [Account Address Encoding](accounts.html#address-encoding)) - -You can tell which of the two sub-types it is based on the first bit: `0` for XRP; `1` for issued currency. - -### VariableLength Fields - -The "VariableLength" type represents binary data of arbitrary length. The most important such fields are `SigningPubKey`, which every signed transaction includes to indicate the key pair used to sign the transaction, and `TxnSignature`, which contains the actual signature for any signed transaction. (Other uses of variable length fields include account `Domain` settings and the `Condition` and `Fulfillment` of conditional escrows.) - -VariableLength fields are encoded with one to three bytes indicating the length of the field immediately after the type prefix and before the contents. - -- If the VariableLength field contains 0 to 192 bytes of data, the first byte defines the length of the VariableLength data, then that many bytes of data follow immediately after the length byte. -- If the VariableLength field contains 193 to 12480 bytes of data, the first two bytes indicate the length of the field with the following formula: - 193 + ((byte1 - 193) * 256) + byte2 -- If the VariableLength field contains 12481 to 918744 bytes of data, the first three bytes indicate the length of the field with the following formula: - 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3; -- A VariableLength field cannot contain more than 918744 bytes of data. - -When decoding, you can tell from the value of the first length byte whether there are 0, 1, or 2 more length bytes: - -- If the first length byte has a value of 192 or less, then that's the only length byte and it contains the exact length of the field contents in bytes. -- If the first length byte has a value of 193 to 240, then there are two length bytes. -- If the first length byte has a value of 241 to 254, then there are three length bytes. diff --git a/dactyl-config.yml b/dactyl-config.yml index 90101924eb..d24ceaa43d 100644 --- a/dactyl-config.yml +++ b/dactyl-config.yml @@ -1251,6 +1251,16 @@ pages: targets: - local + - md: references/rippled-api/api-conventions/serialization.md + html: serialization.html + funnel: Docs + doc_type: References + supercategory: rippled API + category: API Conventions + blurb: Conversion between JSON and canonical binary format for XRP Ledger transactions and other objects. + targets: + - local + # rippled Public Methods - md: references/rippled-api/public-rippled-methods/public-rippled-methods.md diff --git a/img/serialization-array.png b/img/serialization-array.png new file mode 100644 index 0000000000000000000000000000000000000000..ed0eb008ebe50ae69abb363e43b800a6813c104b GIT binary patch literal 7898 zcma)BXIPUVUsue9K$@uzJ@WcN8gH6Qd)LnF z5Zc<8mw%yo$)!uglHONOuDYx_9_%e2YznRQ&uHA4P=d{;_OPmlhW#>j{W{Cw9`dxF@$K%BVy=Fj?oOl6@}y6WBfd~bsF_ghmj z8;4m9j#L~pGCojWKh(j_X>xMc<-sqAYIG86M>lLq%4Ip)X~+Bc0Flxlb+l`uyVrr< z!S*wSx7ix$UmhxToa5Ioez>={G1b7u#r5*#OEMqn*w~mDuJ?tg5Y#c@SI|+cF05x- z{UXOhc@-7A2r_Z6-ABc;Ns=g0=f&x@HL2%6zX??Beyq2)j_}hh4ejn$cXj>6k@4`) z-Hn!o$!lr#t)YLF9}x~(7?|!nd(mm!gZW|aeGnHA5a6`uI)TEuZ<+7yjg~s@FHMv@ zpL#(;8pzDc&yPM>!I4ulkCtIDr`xQtY@Tc55|=L-KTjX0 z;(i!sUlo!}Sjb3HYk?BhK3Rr?Tpma z)KXH_ds}lY8r@fm+$kw3H%U-lUKJG;p`oF@!@bk~v63i{x%LE;r%#>U<)q;mm|C08 zJ}J~&Fc^A@$Kw~XOc|^WBQg$0K-DU@yYW@_*uqtleTaw6iz z-PNrk(`Z7;s#1G%T_^ zOny8kNdLyZYHzms2rnSgOF=~y)}1U~^`y@4B`LYpz}{BUu>lubE(1)jUz|Q=(^^d} zBpmzd)v3&L4}uxh$Z-`R%bK*bw433}?+VI`OB&nbMI65e(pzbiJ1wx;c51U><+RZs z2-AmqTkn}A6qS`DaGjTREiC4MF}1a|6%QmNBwW|VZQJ}E92|yA9W(9;6WDJh;NzIf zkvjTXTG!Hq1O!ywvnnbKT#hbYSNS6sVZ?Q-?EwqQ)G{FV@fWr$SMHu0o_=$con2Pj zVQaQ+R;aZ*{WJPnw~9~g6>{MMKM<0+dPP|!C6C<~ffIDIrz0wFCen8VT4438FR;P~ zA0XW}r{5lW?5;n-({PDi(CI^%A*;69#kzac>B3Qih1F^AuU%=0M&dg=U(Gh%d>J@- zc<8kvDPrFGN71vhFu1(uyVi+`>UuesZ*ORnqWHDEC7k9zR5<@y9lNd^d)4`HAHS3? zr@dn(!YSgh{b{p=@w|Lj*JWK><0!VgWfNuer!OWAR;qI_6#3}%J|C^_iq*x77iVra zcZ?L${e97U=j5KKL<-xwI~qBT60Q68qXEsOb0ugMw_e>zssq@jM|W zDIl>O?Cmv$d|^azyWEiHFq(t*Fp}}sf z44Z zE~_CB0t8W$?}54cIG2TPISGPUX0<-yp)6Ibj-jFLjX$m~>gmOGfpk!-=Oyae5X`hz zu@#(V;ppf%Qz9+GWI7Ow}4rR zE|@FBv4|GqeSEaIV8C;-+rp0@KSl~5@(wFN&8exWVJQh9hDLsqn;l-^b*C{oM*3ht zP`}uw>!>L3tS%P`2=&lw4M7aTPrp&j3@;a-h4LYd?%(&z$jC_56Pf1b<7;bb(yc~U zik3uBH84w|a}4fqI2YH;73btU!r(=IRfOHBiY{`{94QQHCY`k)yPnZpmSa7SGJ08pU~Ie z>v-0i|57zclFvHw0=xe?X=&*K6D9iFd<70Rtj?Wvbtx7diJ&kPuxDCCJ2$^R@)zVT zxR0RCoDf*dLA)FL{G@JlbhOa>qxrtvT8=I@bw*1|OLXKOj3ddg_yt_+oR^vWAxI6H zi5@%4F^r^pySvdxZ)5RLY#~<;;;qwuo?$r~8(V6IDb%_@N9{}uBUw?Kl!UWEx(R#wpm{>2n zM#6TZ>)YF2yXzPx+cUo(?PUFagth2c?aLkj1#lUuF=!G;QggFV=QIbr@OX!tpMM9B z=i}j_I(zmI0JtY~Bel#UXCc8DCY~D@3<0O}@L?;!hco`54Cf1;va+_e^%gr>!-TlT z){dzArs+f#DoM&a?~s@;K%6j4Q0xqvL!O;o(SJz^T`8?h9bji?2Tq+Qxw-yBRzM*C z=g+5cW2a6g8U3DwK0cVBW|17-uKo0h^S0bD?Bs;`HLv|#MvRJziU6h(b({@c(71W? zW}J|1LG63{_qJq^24gDP=+4gNl|DW6;S@_lLqi-Yib2@UX?d`V>{vNkqL?1S=o6%A zEF&xS>C>n6&CO7|;_X-OCHLnme~qWK0c4+g>LQF+uh?ejt1p$aQL@2`!}_H3@L7l( z6ZZ{Ft@zt_d3kyL@ux!#EZL7UhKN_x3VTh~VEP`{KjTsZxeKnMlkl1hk_^_FNBi+Y zZdjiO1F!;dURjb5DWDWYQbGZB;syld-(CQekm%cBjubeSIaG#pOf`-K z@)~dr2t?D^{1TY=;><&2@&P6E-CnvOI$8SjGSBc-Y9~Ym=>`HlX{p!`$M2Qh#-(4prD{Q zVf)pd4Al&k1Q}UbP=EU5Og9#KGF(?jML0O_OTi!ORd+Q}Dv00D9;@`k;c%`fRApI~ zWly?tjG(U}?TO87uuZAsX-aeoq{A_NHpmYwrmIrkM~5j7-m7b9n3Y?~`e3nJ{m=$X$>f!7RA3u&3=Zr~7PQIJI&i){nGvJPh$jWHx7TAJvqGhLUbyo~o zN}sltmV!rPON&M#pMXHrvcu1BlrCDojY(g?_k6yS74VwXv2xW|I2RAk5)qnVq|*A=Ks*Fj`iA z{GI0?h2Y5RCeG?ouB-99&NI!lSdkj!Q9m`*e864^9{v5*n>TNu3__Ra=#)$f=hRN( z1oMNusjbb~!9hKb^7GLWB{r;#*X+h0Lbk)(i!!oaUS8_zp=e}dd;2*`N?iar)_>G3 zApX8>O&uKo6y5-`Bj&o=+uI9jrJtW4cN9|vy!5=_ojZ?%2U=QMdb+y<^F%(wGc8O_ z-8?*)CB1f=!mg*Kr2$mAG>~s!-}wPt91~Nnk+P)_TvJoCq^wMFeh6zpEyndbzRwOt zilO$m0Uh$hFM{p8diBZ_7Wvp12FuFMHZ%IQzD}GjBl8u|(#w2$z0;}uF3W>7pmIC9 z7#k-uu1-{YPpBhrl%4M$8>?)>PgJ-&4t{=OXJ@yVWDr|v^`t*Xw|g@yi`zy0ud~@^ zefe_p>Qz%fL3g?*2L~VHD=2=)vPEN+9te?Pfa!h6shgXdfm=h#>Q+W@D`_+B?d^cO zrd}lZGwhEswl3oNPi6ahdwC7ZdI5(30SsK5mz%pF;3(px7Jl(OMNDoG$Xz5*OkR`K z?C829Y?K7zZd+d-U_J%1-MRX0d!~+fku618pB;qYr?pjpz*klzta?AGO72Yq_PI?3 z5~_YG`u@@D(#NGr)+?Qc%1J4$jNXO|vHIH3t;XVR+}@V2S(ItR!mR zX|b0J4u=a14{VzWwt>{(v^qM}(P4jdxVPzsmGQ|+O}#hnTkQ&VhisyWV|xDL{axgZ zk+`M)Tq-Im9&YZeygYtR&h?!gNr&k-T`iG(`W%@*Pw1eEO8-&Pg1N6>)Wn3^)Ah+( zCny9Z=%)Qk)zevI`Cq2$r8==n~}Ycl<_P!NPae*6Fo zJdDkAWw;2S5x^VzT+Z=(8zW!HCR#qSQuJRF5fc*=5oyW?*ljq`*15bN6#O6}j+8#PKi0lh zHBnr|xCSCDjH^N&0IZZ5P6xrITmSMUPWN0@ImizxLbR-DQ>a>%3i)&EcPK zSmv~MxYc1|ViF~Qu!GYfc`7Vx5uc4;3NlsT`}L&=3#!KeL~>HCj#rM1jFeYZ85#Bg zsU!Vi%+u|DQ60sFhmQRSu^9@Cba9B&`&5#ZemDbcnjF9MqqkQxW#qil)zNZSd*D() zA^>z?`@?2&sn+@Fn77J$1&CGChL`FrmFrz@~Pf2si3-6Bc%c*v021M}=;N|7zo+rfI zYy)I}b89ESsem?-CC*Q&B&x*#D-5xnZH)<)FW7VleFKoZbi5oYE(> z$LFS?@U|kVa&-PpXY&1;z`(OZ_5e^FPB(>hcX!9f$K#g<7%Crr_ba%i6ojnx{h(e9 z&Z57)I_3$SE>vb7HmSvM^XWHGuxH5MQaL<6&X6KBsrw4^?dmx@J2Qzo?as#8ha;}Z zHhN(WD{M!K6D2%h37Y-Y>doEo-W=-;#;b6OrUw_8QrpSM$;(Sh%2I7&E0|Oo0|p2Q55HtRO9>VNuz1t}?M6tAws@ewK|O2HmL(t)}<*)#WKWM1LD9WuXZ({mdc839-bga%pDc<$un?18eo>1b+d>fuq= zrN4yWGP8tP^%rRbUxaBoAg|H(w6!U}HMZKQ1cj@8pswI{5mU0Aa)Ll&&}BFL>7#jomB zYz4$xr>`*=FxP(uy0NjLprFwInv7sytEHxphfjXl0 zT8LB&)89(-6-XpT23CD_byYd*WuB&tpRibK;ESV`Uwky*#K)E zBq&M(vE}0DSMfaU^Rqu!Uw!=e=vTEcJ?tJDU8VlxnuYxE2ZehKFeCJ_>Lt_5VVX}e zp^<+Fvb2mW6cG&otLftC-uDfA0!YLX$}EZ)sBHJMB#I5o0D?uLI=yK$Sh2Y8k$U~ zf^1ty_$*9_CW<)DM!19yc@Hi9Yq7moT2BF)Ec)tY%JOHi-Df4~+*eVzKc}g! z?E(tiix)5E=jVrq^>$bwZeuc}TOpNxWGnpxr7a~pLZRB@8V1Kg-vBrq_4@;GnAQh7 zFqqS-U|v=0zUR9o*Y1|JHO`xE>&D>iEAYW~FQ>^G65YN!e`>_9&Sb(V%gLIT=^@H< z{8P;>vCgU+9AaD&5{BPN4yYj&c9`18Id&NzttOS@ljqN^MKeJqT9$&;#E{Lhi%~)M zUFip0LL+`%!p%B$eqfay^ag#J8ZT1|j5K&@apB6+8du(gLU)e=1towYv+U((p#sip zCmYn?zZeCQYtvt;u6qeAeHZyTVegGKEN3M(D)O(HaRdt($a8=qzI*$2s0!tYLP>~G z1NdQMCE48C8W=VR5O_XM1#Drx4#ea-c4@qMF1Vm2Z*4@tR)g) zbse1nP{GR#g}*x{mte5ixVVu~qVDcc^MV9GMNsA20N9ga z0<`mmxj)(88aGfj+&w%%S=D#9s((jILPFx}>pOw^We9Y$haY0*<>#{M$fVUrk z5&~odeuI)n-p>rT3XrK zv~+YA0eeM0K}Hi>9!sq;x8_=7*rj^=`KkBgebo z@J7Yav9YbKts?rTtPBauLbyQ%}v?5<_MlX1VAXDdu6)!1AG!3 zGpa@#qO3rI1+jNOz?*;{*euA_$%jegVN23n;ZP`mT{?D3tnmp63GwmM1Ifu(mUOxY z2k)Q{-9SqO&;cOl7Y?E50mp_koP{ndETm)CW?G_x%n7HdE)(l6a>H@1J#&Bsxxj0; zz@#keQ{d3V(l0#XbS;$O%$YOV`9@5jeY8-!JWRBCp*Wm#LV)qkv%9D)Aah1mB@Px@ zM|Dk!1}wGy`klk?^Gp<4Y5>&QrFbHJssK!#d|S4zWi8)y8Kg>(-sR-vfNq#Z#p;dE zM!0)=dhZNb0KFH1!bj}L$YTSRDpFG4fglDTEvv))A2|ySeGS<7?c29RY5Tl}fx+_9 z5|1@em*{N9`~Y0U!C-Z2s%JXM;AjU|4Lx(l0hE0~(>FV+{B8VsTfw2+eo%(l+1YeL zvk5>aPo6v}2nF)&HKoW=z*P1l463}`2l5PQXlOv6?=k{wt}H7{O6<1|o&g5?-@3BI zNB>t>mbkz_b!GpxPkX{a{w5>;KKQdCoK2wi*nnNL67$~y??M#hRPN=;7{B^Y>9Z)* literal 0 HcmV?d00001 diff --git a/img/serialization-object.png b/img/serialization-object.png new file mode 100644 index 0000000000000000000000000000000000000000..faae809cc9aee45451c031b13ce5a127c6ff9d29 GIT binary patch literal 11967 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≧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 literal 0 HcmV?d00001 From 8240872c564c60b75fa668fbc38739f97ed4640e Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 21 Nov 2018 16:32:44 -0800 Subject: [PATCH 05/19] Serialization: objects, arrays, hashes --- .../tx-serialization/serialize.py | 72 ++++++++++++-- .../test-cases/tx2-binary.txt | 1 + .../tx-serialization/test-cases/tx2-full.json | 94 +++++++++++++++++++ .../test-cases/tx2-nometa.json | 18 ++++ .../api-conventions/serialization.md | 55 +++++++++-- 5 files changed, 225 insertions(+), 15 deletions(-) create mode 100644 content/_code-samples/tx-serialization/test-cases/tx2-binary.txt create mode 100644 content/_code-samples/tx-serialization/test-cases/tx2-full.json create mode 100644 content/_code-samples/tx-serialization/test-cases/tx2-nometa.json diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py index 869aa3fc46..9b8bf15467 100755 --- a/content/_code-samples/tx-serialization/serialize.py +++ b/content/_code-samples/tx-serialization/serialize.py @@ -39,6 +39,11 @@ def field_sort_key(field_name): return (DEFINITIONS["TYPES"][field_type_name], DEFINITIONS["FIELDS"][field_name]["nth"]) def field_id(field_name): + """ + Returns the unique field ID for a given field name. + This field ID consists of the type code and field code, in 1 to 3 bytes + depending on whether those values are "common" (<16) or uncommon (>=16) + """ field_type_name = DEFINITIONS["FIELDS"][field_name]["type"] type_code = DEFINITIONS["TYPES"][field_type_name] field_code = DEFINITIONS["FIELDS"][field_name]["nth"] @@ -145,10 +150,59 @@ def vl_to_bytes(field_val): vl_contents = bytes.fromhex(field_val) return vl_encode(vl_contents) +def hash_to_bytes(contents): + return bytes.fromhex(field_val) + def accountid_to_bytes(address): return vl_encode(decode_address(address)) +def array_to_bytes(array): + """ + Serialize an array of objects. + Each member object must have a type wrapper and an inner object. + """ + members_as_bytes = [] + for el in array: + wrapper_key = list(el.keys())[0] + inner_obj = el[wrapper_key] + members_as_bytes.append(field_to_bytes(field_name=wrapper_key, field_val=el)) + members_as_bytes.append(field_id("ArrayEndMarker")) + return b''.join(members_as_bytes) + +def object_to_bytes(obj): + """ + Serialize an object, assuming a type wrapper, for example: + + { + "SignerEntry": { + "Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v", + "SignerWeight": 1 + } + } + + Puts the child fields (e.g. Account, SignerWeight) in canonical order + and appends an object end marker. + """ + wrapper_key = list(obj.keys())[0] + inner_obj = obj[wrapper_key] + child_order = sorted(inner_obj.keys(), key=field_sort_key) + fields_as_bytes = [] + for field_name in child_order: + if (DEFINITIONS["FIELDS"][field_name]["isSerialized"]): + field_val = inner_obj[field_name] + field_bytes = field_to_bytes(field_name, field_val) + logger.debug("{n}: {h}".format(n=field_name, h=field_bytes.hex())) + fields_as_bytes.append(field_bytes) + + fields_as_bytes.append(field_id("ObjectEndMarker")) + return b''.join(fields_as_bytes) + + def field_to_bytes(field_name, field_val): + """ + Returns a bytes object containing the serialized version of a field + including its field ID prefix. + """ field_type = DEFINITIONS["FIELDS"][field_name]["type"] logger.debug("Serializing field {f} of type {t}".format(f=field_name, t=field_type)) @@ -161,13 +215,19 @@ def field_to_bytes(field_name, field_val): dispatch = { # TypeName: function(field): bytes object - "UInt64": lambda x:bytes_from_uint(x, 64), - "UInt32": lambda x:bytes_from_uint(x, 32), - "UInt16": lambda x:bytes_from_uint(x, 16), - "UInt8" : lambda x:bytes_from_uint(x, 8), "AccountID": accountid_to_bytes, "Amount": amount_to_bytes, - "Blob": vl_to_bytes + "Blob": vl_to_bytes, + "Hash128": hash_to_bytes, + "Hash160": hash_to_bytes, + "Hash256": hash_to_bytes, + # TODO: PathSet + "STArray": array_to_bytes, + "STObject": object_to_bytes, + "UInt8" : lambda x:bytes_from_uint(x, 8), + "UInt16": lambda x:bytes_from_uint(x, 16), + "UInt32": lambda x:bytes_from_uint(x, 32), + "UInt64": lambda x:bytes_from_uint(x, 64), } field_binary = dispatch[field_type](field_val) return b''.join( (id_prefix, field_binary) ) @@ -210,7 +270,7 @@ if __name__ == "__main__": # "Sequence": 2 # } - with open("test-cases/tx1-nometa.json") as f: + with open("test-cases/tx2-nometa.json") as f: example_tx = json.load(f) serialize_tx(example_tx) diff --git a/content/_code-samples/tx-serialization/test-cases/tx2-binary.txt b/content/_code-samples/tx-serialization/test-cases/tx2-binary.txt new file mode 100644 index 0000000000..d2dbb5f3ac --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx2-binary.txt @@ -0,0 +1 @@ +1200022280000000240000000120190000000B68400000000000277573210268D79CD579D077750740FA18A2370B7C2018B2714ECE70BA65C38D223E79BC9C74473045022100F06FB54049D6D50142E5CF2E2AC21946AF305A13E2A2D4BA881B36484DD01A540220311557EC8BEF536D729605A4CB4D4DC51B1E37C06C93434DD5B7651E1E2E28BF811452C7F01AD13B3CA9C1D133FA8F3482D2EF08FA7D82145A380FBD236B6A1CD14B939AD21101E5B6B6FFA2F9EA7D0F04C4D46544659A2D58525043686174E1F1 diff --git a/content/_code-samples/tx-serialization/test-cases/tx2-full.json b/content/_code-samples/tx-serialization/test-cases/tx2-full.json new file mode 100644 index 0000000000..492396a070 --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx2-full.json @@ -0,0 +1,94 @@ +{ + "hash": "C0B450C8601E19CB0BDED71C4B523B2A4AAB77415B08E7923D8DA3F831631702", + "ledger_index": 36259236, + "date": "2018-02-01T09:45:32+00:00", + "tx": { + "TransactionType": "EscrowFinish", + "Flags": 2147483648, + "Sequence": 1, + "OfferSequence": 11, + "Fee": "10101", + "SigningPubKey": "0268D79CD579D077750740FA18A2370B7C2018B2714ECE70BA65C38D223E79BC9C", + "TxnSignature": "3045022100F06FB54049D6D50142E5CF2E2AC21946AF305A13E2A2D4BA881B36484DD01A540220311557EC8BEF536D729605A4CB4D4DC51B1E37C06C93434DD5B7651E1E2E28BF", + "Account": "r3Y6vCE8XqfZmYBRngy22uFYkmz3y9eCRA", + "Owner": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3", + "Memos": [ + { + "Memo": { + "MemoData": "04C4D46544659A2D58525043686174" + } + } + ] + }, + "meta": { + "TransactionIndex": 35, + "AffectedNodes": [ + { + "DeletedNode": { + "LedgerEntryType": "Escrow", + "LedgerIndex": "983EBDF89C1C30CECDB105B593E7DEBE602AF89012EDB4DD76D24ACEF92C89EF", + "FinalFields": { + "Flags": 0, + "PreviousTxnLgrSeq": 35059511, + "FinishAfter": 570758400, + "OwnerNode": "0000000000000000", + "PreviousTxnID": "6F54E04D7B205CBE2FCAEF1C249E62A9759721C7FE1F6992FD800266C8E4814C", + "Amount": "1000000000000000", + "Account": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3", + "Destination": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3" + } + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "C05DC35EBDCA5D8697190FF41950EC5AFCBF7F61C1177DC73000CC17C2149886", + "FinalFields": { + "Flags": 0, + "RootIndex": "C05DC35EBDCA5D8697190FF41950EC5AFCBF7F61C1177DC73000CC17C2149886", + "Owner": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3" + } + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "PreviousTxnLgrSeq": 36230933, + "PreviousTxnID": "9366F75CD350ACE0EEFC0A392ECBD5AC2B84C06E5DEC2DE895B76FFC7BD55553", + "LedgerIndex": "C180CA555CE8820D8F1086CDCA756FF8D5813FEB2AE3FF002B9F48F870CA08A0", + "PreviousFields": { + "Sequence": 1, + "Balance": "26000000" + }, + "FinalFields": { + "Flags": 0, + "Sequence": 2, + "OwnerCount": 0, + "Balance": "25989899", + "Account": "r3Y6vCE8XqfZmYBRngy22uFYkmz3y9eCRA" + } + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "PreviousTxnLgrSeq": 36253527, + "PreviousTxnID": "F2AA5584005847C19F59A9C87E7BF3108F97F2567C5083159EEC40B08ED90F46", + "LedgerIndex": "DCED5AAFE87AA8D00E651DBBFBA2F992927BC3DC5FFD905EF49019ED02824B3A", + "PreviousFields": { + "OwnerCount": 14, + "Balance": "200923000" + }, + "FinalFields": { + "Flags": 1048576, + "Sequence": 18, + "OwnerCount": 13, + "Balance": "1000000200923000", + "Account": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3" + } + } + } + ], + "TransactionResult": "tesSUCCESS" + } +} diff --git a/content/_code-samples/tx-serialization/test-cases/tx2-nometa.json b/content/_code-samples/tx-serialization/test-cases/tx2-nometa.json new file mode 100644 index 0000000000..605ebdaea7 --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx2-nometa.json @@ -0,0 +1,18 @@ +{ + "TransactionType": "EscrowFinish", + "Flags": 2147483648, + "Sequence": 1, + "OfferSequence": 11, + "Fee": "10101", + "SigningPubKey": "0268D79CD579D077750740FA18A2370B7C2018B2714ECE70BA65C38D223E79BC9C", + "TxnSignature": "3045022100F06FB54049D6D50142E5CF2E2AC21946AF305A13E2A2D4BA881B36484DD01A540220311557EC8BEF536D729605A4CB4D4DC51B1E37C06C93434DD5B7651E1E2E28BF", + "Account": "r3Y6vCE8XqfZmYBRngy22uFYkmz3y9eCRA", + "Owner": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3", + "Memos": [ + { + "Memo": { + "MemoData": "04C4D46544659A2D58525043686174" + } + } + ] +} diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index 7696de01ee..e6865cd513 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -18,12 +18,6 @@ The result is a single binary blob that can be signed using well-known signature **Note:** The XRP Ledger uses the same serialization format to represent other types of data, such as [ledger objects](ledger-object-types.html) and processed transactions. However, only certain fields are appropriate for including in a transaction that gets signed. (For example, the `TxnSignature` field, containing the signature itself, should not be present in the binary blob that you sign.) Thus, some fields are designated as "Signing" fields, which are included in objects when those objects are signed, and "non-signing" fields, which are not. -The hard work is the details of each of those steps. - -***Notes: some useful links for signing:*** - -- Actual core of the signing code in rippled: https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 -- Serialization code in ripple-lib depends on `ripple-binary-codec`. These definitions have the canonical types and sort codes for all fields: https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json ## Internal Format @@ -31,6 +25,36 @@ Each field has an "internal" binary format used in the `rippled` source code to For example, the `Flags` [common transaction field](transaction-common-fields.html) becomes a UInt32 (32-bit unsigned integer). +### Definitions File + +The following JSON file defines the important constants you need for serializing XRP Ledger data to its binary format and deserializing it from binary: + +**** + +The following table defines the top-level fields from the definitions file: + +| Field | Contents | +|:----------------------|:-----------------------------------------------------| +| `TYPES` | Map of data types to their "type code" for constructing field IDs and sorting fields in canonical order. Codes below 1 should not appear in actual data; codes above 10000 represent special "high-level" object types such as "Transaction" that cannot be serialized inside other objects. | +| `LEDGER_ENTRY_TYPES` | Map of [ledger objects](ledger-object-types.html) to their data type. These appear in ledger state data, and in the "affected nodes" section of processed transactions' [metadata](transaction-metadata.html). | +| `FIELDS` | A sorted array of tuples representing all fields that may appear in transactions, ledger objects, or other data. The first member of each tuple is the string name of the field and the second member is an object with that field's properties. (See the "Field properties" table below for definitions of those fields.) | +| `TRANSACTION_RESULTS` | Map of [transaction result codes](transaction-results.html) to their numeric values. Result types not included in ledgers have negative values;`tesSUCCESS` has numeric value 0; [`tec`-class codes](tec-codes.html) represent failures that are included in ledgers. | +| `TRANSACTION_TYPES` | Map of all [transaction types](transaction-types.html) to their numeric values. | + +For purposes of serializing transactions for signing and submitting, only `FIELDS` and `TYPES` are necessary. + +The field definition objects in the `FIELDS` array have the following fields: + +| Field | Type | Contents | +|:-----------------|:--------|:------------------------------------------------| +| `nth` | Number | The [field code](#field-codes) of this field, for use in constructing its [Field ID](#field-ids) and ordering it with other fields of the same data type. | +| `isVLEncoded` | Boolean | If `true`, this field is [variable-length encoded](#variable-length-encoding). | +| `isSerialized` | Boolean | If `true`, this field should be encoded into serialized binary data. When this field is `false`, the field is typically reconstructed on demand rather than stored. | +| `isSigningField` | Boolean | If `true` this field should be serialized when preparing a transaction for signing. If `false`, this field should be omitted from the data to be signed. (It may not be part of transactions at all.) | +| `type` | String | The internal data type of this field. This maps to a key in the `TYPES` map, which gives the [type code](#type-codes) for this field. | + + + ## Canonical Field Order All fields in a transaction are sorted in a specific order based on the field's type first, then the field itself second. (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) @@ -149,9 +173,11 @@ You can tell which of the two sub-types it is based on the first bit: `0` for XR ### Array Fields [STArray]: #array-fields -Some transaction fields, such as `SignerEntries` (in [SignerListSet transactions][]) and [`Memos`](transaction-common-fields.html#memos-field), are arrays. +Some transaction fields, such as `SignerEntries` (in [SignerListSet transactions][]) and [`Memos`](transaction-common-fields.html#memos-field), are arrays of objects (called the "STArray" type). -Arrays contain several other fields in their native binary format in a specific order. Each of these fields has its normal Field ID prefix and contents. To mark the end of an array, append an item with a "Field ID" of `0xf1` (the type code for array with field code of 1) and no contents. +Arrays contain several [object fields](#object-fields) in their native binary format in a specific order. In JSON, each array member is a JSON "wrapper" object with a single field, which is the name of the member object field. The value of that field is the ("inner") object itself. + +In the binary format, each member of the array has a Field ID prefix (based on the single key of the wrapper object) and contents (comprising the inner object, [serialized as an object](#object-fields)). To mark the end of an array, append an item with a "Field ID" of `0xf1` (the type code for array with field code of 1) and no contents. The following example shows the serialization format for an array (the `SignerEntries` field): @@ -166,10 +192,20 @@ The Blob type is a [variable-length encoded](#variable-length-encoding) field wi Blob fields have no further structure to their contents, so they consist of exactly the amount of bytes indicated in the variable-length encoding, after the Field ID and length prefixes. +### Hash Fields +[Hash128]: #hash-fields +[Hash160]: #hash-fields +[Hash256]: #hash-fields + +The XRP Ledger has several "hash" types: Hash128, Hash160, and Hash256. These fields contain arbitrary binary data of the given number of bits, which may or may not represent the result of a hash operation. + +All such fields are serialized as the specific number of bits, with no length indicator, in big-endian byte order. + + ### Object Fields [STObject]: #object-fields -Some fields, such as `SignerEntry` (in [SignerListSet transactions][]), and `Memo` (in `Memos` arrays) are objects. The serialization of objects is very similar to that of arrays, with one difference: **object members must be placed in canonical order** within the object field, where array fields have an explicit order already. +Some fields, such as `SignerEntry` (in [SignerListSet transactions][]), and `Memo` (in `Memos` arrays) are objects (called the "STObject" type). The serialization of objects is very similar to that of arrays, with one difference: **object members must be placed in canonical order** within the object field, where array fields have an explicit order already. The [canonical field order](#canonical-field-order) of object fields is the same as the canonical field order for all top-level fields, but the members of the object must be sorted within the object. After the last member, there is an "Object end" Field ID of `0xe1` with no contents. @@ -177,6 +213,7 @@ The following example shows the serialization format for an object (a single `Me ![Object field ID, followed by the Object ID and contents of each object member in canonical order, followed by the "Object end" field ID](img/serialization-object.png) + ### UInt Fields [UInt8]: #uint-fields [UInt16]: #uint-fields From e500134da612a6d90439425806c704f7938cbbe5 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 21 Nov 2018 16:34:14 -0800 Subject: [PATCH 06/19] Serialization sample code: remove unused field_ordering.py file --- .../tx-serialization/field_ordering.py | 180 ------------------ 1 file changed, 180 deletions(-) delete mode 100644 content/_code-samples/tx-serialization/field_ordering.py diff --git a/content/_code-samples/tx-serialization/field_ordering.py b/content/_code-samples/tx-serialization/field_ordering.py deleted file mode 100644 index 2d6d111e37..0000000000 --- a/content/_code-samples/tx-serialization/field_ordering.py +++ /dev/null @@ -1,180 +0,0 @@ -# Canonical ordering (of transaction signing fields only) -# for serializing and signing transactions - -TYPES = { - # Type name: canonical order for this type - "UInt16": 1, - "UInt32": 2, - "UInt64": 3, - "Hash128": 4, - "Hash256": 5, - "Amount": 6, - "VL": 7, # VariableLength - "Account": 8, - # 9-13 are reserved - "Object": 14, - "Array": 15, - "UInt8": 16, - "Hash160": 17, - "PathSet": 18, # "Paths" field of cross-currency payments only - "Vector256": 19, -} - -FIELDS = { - # Type name: Canonical order for this field (among fields of its type) - - # UInt16 types - "LedgerEntryType": 1, - "TransactionType": 2, - "SignerWeight": 3, - # UInt32 types - "Flags": 2, - "SourceTag": 3, - "Sequence": 4, - "PreviousTxnLgrSeq": 5, - "LedgerSequence": 6, - "CloseTime": 7, - "ParentCloseTime": 8, - "SigningTime": 9, - "Expiration": 10, - "TransferRate": 11, - "WalletSize": 12, - "OwnerCount": 13, - "DestinationTag": 14, - "HighQualityIn": 16, - "HighQualityOut": 17, - "LowQualityIn": 18, - "LowQualityOut": 19, - "QualityIn": 20, - "QualityOut": 21, - "StampEscrow": 22, - "BondAmount": 23, - "LoadFee": 24, - "OfferSequence": 25, - "FirstLedgerSequence": 26, - "LastLedgerSequence": 27, - "TransactionIndex": 28, - "OperationLimit": 29, - "ReferenceFeeUnits": 30, - "ReserveBase": 31, - "ReserveIncrement": 32, - "SetFlag": 33, - "ClearFlag": 34, - "SignerQuorum": 35, - "CancelAfter": 36, - "FinishAfter": 37, - "SignerListID": 38, - "SettleDelay": 39, - # UInt64 types - "IndexNext": 1, - "IndexPrevious": 2, - "BookNode": 3, - "OwnerNode": 4, - "BaseFee": 5, - "ExchangeRate": 6, - "LowNode": 7, - "HighNode": 8, - "DestinationNode": 9, - "Cookie": 10, - # Hash128 types - "EmailHash": 1, - # Hash256 types - "LedgerHash": 1, - "ParentHash": 2, - "TransactionHash": 3, - "AccountHash": 4, - "PreviousTxnID": 5, - "LedgerIndex": 6, - "WalletLocator": 7, - "RootIndex": 8, - "AccountTxnID": 9, - "BookDirectory": 16, - "InvoiceID": 17, - "Nickname": 18, - "Amendment": 19, - "TicketID": 20, - "Digest": 21, - "Channel": 22, - "ConsensusHash": 23, - "CheckID": 24, - "hash": 257, - "index": 258, - # Amount types - "Amount": 1, - "Balance": 2, - "LimitAmount": 3, - "TakerPays": 4, - "TakerGets": 5, - "LowLimit": 6, - "HighLimit": 7, - "Fee": 8, - "SendMax": 9, - "DeliverMin": 10, - "MinimumOffer": 16, - "RippleEscrow": 17, - "DeliveredAmount": 18, - # VL types - "PublicKey": 1, - "MessageKey": 2, - "SigningPubKey": 3, - "TxnSignature": 4, - "Signature": 6, - "Domain": 7, - "FundCode": 8, - "RemoveCode": 9, - "ExpireCode": 10, - "CreateCode": 11, - "MemoType": 12, - "MemoData": 13, - "MemoFormat": 14, - "Fulfillment": 16, - "Condition": 17, - "MasterSignature": 18, - # Account types - "Account": 1, - "Owner": 2, - "Destination": 3, - "Issuer": 4, - "Authorize": 5, - "Unauthorize": 6, - "Target": 7, - "RegularKey": 8, - # Object types - "TransactionMetaData": 2, - "CreatedNode": 3, - "DeletedNode": 4, - "ModifiedNode": 5, - "PreviousFields": 6, - "FinalFields": 7, - "NewFields": 8, - "TemplateEntry": 9, - "Memo": 10, - "SignerEntry": 11, - "Signer": 16, - "Majority": 18, - # Array types - "Signers": 3, - "SignerEntries": 4, - "Template": 5, - "Necessary": 6, - "Sufficient": 7, - "AffectedNodes": 8, - "Memos": 9, - "Majorities": 16, - # UInt8 types - "CloseResolution": 1, - "Method": 2, - "TransactionResult": 3, - "TickSize": 16, - # Hash160 types - "TakerPaysCurrency": 1, - "TakerPaysIssuer": 2, - "TakerGetsCurrency": 3, - "TakerGetsIssuer": 4, - # PathSet types - "Paths": 1, - # Vector256 types - "Indexes": 1, - "Hashes": 2, - "Amendments": 3, -} From 6bcf83ee1b0503d43efbed5d8853617a1a277c56 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 21 Nov 2018 16:40:29 -0800 Subject: [PATCH 07/19] Serialization: clarify TransactionType special case --- .../references/rippled-api/api-conventions/serialization.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index e6865cd513..ff9ecc4e10 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -41,7 +41,7 @@ The following table defines the top-level fields from the definitions file: | `TRANSACTION_RESULTS` | Map of [transaction result codes](transaction-results.html) to their numeric values. Result types not included in ledgers have negative values;`tesSUCCESS` has numeric value 0; [`tec`-class codes](tec-codes.html) represent failures that are included in ledgers. | | `TRANSACTION_TYPES` | Map of all [transaction types](transaction-types.html) to their numeric values. | -For purposes of serializing transactions for signing and submitting, only `FIELDS` and `TYPES` are necessary. +For purposes of serializing transactions for signing and submitting, the `FIELDS`, `TYPES`, and `TRANSACTION_TYPES` fields are necessary. The field definition objects in the `FIELDS` array have the following fields: @@ -222,4 +222,6 @@ The following example shows the serialization format for an object (a single `Me The XRP Ledger has several unsigned integer types: UInt8, UInt16, UInt32, and UInt64. All of these are standard big-endian binary unsigned integers with the specified number of bits. -When representing these fields in JSON objects, most are represented as JSON numbers by default. The exception is UInt64, which is represented as a string because some JSON decoders may try to represent these integers as 64-bit "double precision" floating point numbers, which cannot represent all distinct UInt64 values with full precision. +When representing these fields in JSON objects, most are represented as JSON numbers by default. One exception is UInt64, which is represented as a string because some JSON decoders may try to represent these integers as 64-bit "double precision" floating point numbers, which cannot represent all distinct UInt64 values with full precision. + +Another special case is the `TransactionType` field. In JSON, this field is conventionally represented as a string with the name of the transaction type, but in binary, this field is a UInt16. The `TRANSACTION_TYPES` object in the [definitions file](#definitions-file) maps these strings to specific numeric values. From 3894149859f0430f15f30391bfd161516bcbb51d Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 26 Nov 2018 20:10:37 -0800 Subject: [PATCH 08/19] Serialization: implement issued currency amounts, pathsets --- .../tx-serialization/serialize.py | 88 ++++++++++++++++--- .../test-cases/tx3-binary.txt | 1 + .../test-cases/tx3-nometa.json | 57 ++++++++++++ .../tx-serialization/xrpl_num.py | 65 ++++++++++++++ 4 files changed, 201 insertions(+), 10 deletions(-) create mode 100644 content/_code-samples/tx-serialization/test-cases/tx3-binary.txt create mode 100644 content/_code-samples/tx-serialization/test-cases/tx3-nometa.json create mode 100644 content/_code-samples/tx-serialization/xrpl_num.py diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py index 9b8bf15467..e72e92c63f 100755 --- a/content/_code-samples/tx-serialization/serialize.py +++ b/content/_code-samples/tx-serialization/serialize.py @@ -9,6 +9,7 @@ import logging import re from address import decode_address +from xrpl_num import IssuedAmount logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) @@ -81,6 +82,11 @@ def bytes_from_uint(i, bits): return i.to_bytes(bits // 8, byteorder="big", signed=False) def amount_to_bytes(a): + """ + Serializes an "Amount" type, which can be either XRP or an issued currency: + - XRP: 64 bits; 0, followed by 1 ("is positive"), followed by 62 bit UInt amount + - Issued Currency: 64 bits of amount, followed by + """ if type(a) == str: # is XRP xrp_amt = int(a) @@ -95,32 +101,42 @@ def amount_to_bytes(a): if sorted(a.keys()) != ["currency", "issuer", "value"]: raise ValueError("amount must have currency, value, issuer only (actually had: %s)" % sorted(a.keys())) - #TODO: canonicalize mantissa/exponent/etc. of issued currency amount - # https://developers.ripple.com/currency-formats.html#issued-currency-math - # temporarily returning all zeroes - issued_amt = bytes(8) + + issued_amt = IssuedAmount(a["value"]).to_bytes() + logger.debug("Issued amount: %s"%issued_amt.hex()) currency_code = currency_code_to_bytes(a["currency"]) return issued_amt + currency_code + decode_address(a["issuer"]) else: raise ValueError("amount must be XRP string or {currency, value, issuer}") +def issued_amount_as_bytes(strnum): + num = Decimal(strnum) + + def tx_type_to_bytes(txtype): type_uint = DEFINITIONS["TRANSACTION_TYPES"][txtype] return type_uint.to_bytes(2, byteorder="big", signed=False) -def currency_code_to_bytes(code_string): +def currency_code_to_bytes(code_string, xrp_ok=False): if re.match(r"^[A-Za-z0-9?!@#$%^&*<>(){}\[\]|]{3}$", code_string): # ISO 4217-like code if code_string == "XRP": + if xrp_ok: + # Rare, but when the currency code "XRP" is serialized, it's + # a special-case all zeroes. + logger.debug("Currency code(XRP): "+("0"*40)) + return bytes(20) raise ValueError("issued currency can't be XRP") + code_ascii = code_string.encode("ASCII") + logger.debug("Currency code ASCII: %s"%code_ascii.hex()) # standard currency codes: https://developers.ripple.com/currency-formats.html#standard-currency-codes # 8 bits type code (0x00) - # 96 bits reserved (0's) + # 88 bits reserved (0's) # 24 bits ASCII - # 8 bits version (0x00) + # 16 bits version (0x00) # 24 bits reserved (0's) - return b''.join( ( bytes(13), code_ascii, bytes(4) ) ) + return b''.join( ( bytes(12), code_ascii, bytes(5) ) ) elif re.match(r"^[0-9a-fA-F]{40}$", code_string): # raw hex code return bytes.fromhex(code_string) # requires Python 3.5+ @@ -197,6 +213,58 @@ def object_to_bytes(obj): fields_as_bytes.append(field_id("ObjectEndMarker")) return b''.join(fields_as_bytes) +def pathset_to_bytes(pathset): + """ + Serialize a PathSet, which is an array of arrays, + where each inner array represents one possible payment path. + A path consists of "path step" objects in sequence, each with one or + more of "account", "currency", and "issuer" fields, plus (ignored) "type" + and "type_hex" fields which indicate which fields are present. + (We re-create the type field for serialization based on which of the core + 3 fields are present.) + """ + + if not len(pathset): + raise ValueError("PathSet type must not be empty") + + paths_as_bytes = [] + for n in range(len(pathset)): + path = path_as_bytes(pathset[n]) + logger.debug("Path %d: %s"%(n, path.hex())) + paths_as_bytes.append(path) + if n + 1 == len(pathset): # last path; add an end byte + paths_as_bytes.append(bytes.fromhex("00")) + else: # add a path separator byte + paths_as_bytes.append(bytes.fromhex("ff")) + + return b''.join(paths_as_bytes) + +def path_as_bytes(path): + """ + Helper function for representing one member of a pathset as a bytes object + """ + + if not len(path): + raise ValueError("Path must not be empty") + + path_contents = [] + for step in path: + step_data = [] + type_byte = 0 + if "account" in step.keys(): + type_byte |= 0x01 + step_data.append(decode_address(step["account"])) + if "currency" in step.keys(): + type_byte |= 0x10 + step_data.append(currency_code_to_bytes(step["currency"], xrp_ok=True)) + if "issuer" in step.keys(): + type_byte |= 0x20 + step_data.append(decode_address(step["issuer"])) + step_data = [bytes_from_uint(type_byte, 8)] + step_data + path_contents += step_data + + return b''.join(path_contents) + def field_to_bytes(field_name, field_val): """ @@ -221,7 +289,7 @@ def field_to_bytes(field_name, field_val): "Hash128": hash_to_bytes, "Hash160": hash_to_bytes, "Hash256": hash_to_bytes, - # TODO: PathSet + "PathSet": pathset_to_bytes, "STArray": array_to_bytes, "STObject": object_to_bytes, "UInt8" : lambda x:bytes_from_uint(x, 8), @@ -270,7 +338,7 @@ if __name__ == "__main__": # "Sequence": 2 # } - with open("test-cases/tx2-nometa.json") as f: + with open("test-cases/tx3-nometa.json") as f: example_tx = json.load(f) serialize_tx(example_tx) diff --git a/content/_code-samples/tx-serialization/test-cases/tx3-binary.txt b/content/_code-samples/tx-serialization/test-cases/tx3-binary.txt new file mode 100644 index 0000000000..d1bc34834c --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx3-binary.txt @@ -0,0 +1 @@ +1200002200000000240000034A201B009717BE61400000000098968068400000000000000C69D4564B964A845AC0000000000000000000000000555344000000000069D33B18D53385F8A3185516C2EDA5DEDB8AC5C673210379F17CFA0FFD7518181594BE69FE9A10471D6DE1F4055C6D2746AFD6CF89889E74473045022100D55ED1953F860ADC1BC5CD993ABB927F48156ACA31C64737865F4F4FF6D015A80220630704D2BD09C8E99F26090C25F11B28F5D96A1350454402C2CED92B39FFDBAF811469D33B18D53385F8A3185516C2EDA5DEDB8AC5C6831469D33B18D53385F8A3185516C2EDA5DEDB8AC5C6F9EA7C06636C69656E747D077274312E312E31E1F1011201F3B1997562FD742B54D4EBDEA1D6AEA3D4906B8F100000000000000000000000000000000000000000FF014B4E9C06F24296074F7BC48F92A97916C6DC5EA901DD39C650A96EDA48334E70CC4A85B8B2E8502CD310000000000000000000000000000000000000000000 diff --git a/content/_code-samples/tx-serialization/test-cases/tx3-nometa.json b/content/_code-samples/tx-serialization/test-cases/tx3-nometa.json new file mode 100644 index 0000000000..71078f7da9 --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/tx3-nometa.json @@ -0,0 +1,57 @@ +{ + "Account": "rweYz56rfmQ98cAdRaeTxQS9wVMGnrdsFp", + "Amount": "10000000", + "Destination": "rweYz56rfmQ98cAdRaeTxQS9wVMGnrdsFp", + "Fee": "12", + "Flags": 0, + "LastLedgerSequence": 9902014, + "Memos": [ + { + "Memo": { + "MemoData": "7274312E312E31", + "MemoType": "636C69656E74" + } + } + ], + "Paths": [ + [ + { + "account": "rPDXxSZcuVL3ZWoyU82bcde3zwvmShkRyF", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "XRP", + "type": 16, + "type_hex": "0000000000000010" + } + ], + [ + { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "XRP", + "type": 16, + "type_hex": "0000000000000010" + } + ] + ], + "SendMax": { + "currency": "USD", + "issuer": "rweYz56rfmQ98cAdRaeTxQS9wVMGnrdsFp", + "value": "0.6275558355" + }, + "Sequence": 842, + "SigningPubKey": "0379F17CFA0FFD7518181594BE69FE9A10471D6DE1F4055C6D2746AFD6CF89889E", + "TransactionType": "Payment", + "TxnSignature": "3045022100D55ED1953F860ADC1BC5CD993ABB927F48156ACA31C64737865F4F4FF6D015A80220630704D2BD09C8E99F26090C25F11B28F5D96A1350454402C2CED92B39FFDBAF", + "hash": "B521424226FC100A2A802FE20476A5F8426FD3F720176DC5CCCE0D75738CC208" +} diff --git a/content/_code-samples/tx-serialization/xrpl_num.py b/content/_code-samples/tx-serialization/xrpl_num.py new file mode 100644 index 0000000000..ad82690496 --- /dev/null +++ b/content/_code-samples/tx-serialization/xrpl_num.py @@ -0,0 +1,65 @@ +# Serializes issued currency amounts from string number representations, +# matching the precision of the XRP Ledger. + +from decimal import getcontext, Decimal + +class IssuedAmount: + MIN_MANTISSA = 10**15 + MAX_MANTISSA = 10**16 - 1 + MIN_EXP = -96 + MAX_EXP = 80 + def __init__(self, strnum): + self.context = getcontext() + self.context.prec = 15 + self.context.Emin = self.MIN_EXP + self.context.Emax = self.MAX_EXP + + self.dec = Decimal(strnum) + + def to_bytes(self): + if self.dec.is_zero(): + return self.canonical_zero_serial() + + # Convert components to integers --------------------------------------- + sign, digits, exp = self.dec.as_tuple() + mantissa = int("".join([str(d) for d in digits])) + + # Canonicalize to expected range --------------------------------------- + while mantissa < self.MIN_MANTISSA and exp > self.MIN_EXP: + mantissa *= 10 + exp -= 1 + + while mantissa > self.MAX_MANTISSA: + if exp >= self.MAX_EXP: + raise ValueError("amount overflow") + mantissa //= 10 + exp += 1 + + if exp < self.MIN_EXP or mantissa < self.MIN_MANTISSA: + # Round to zero + return self.canonical_zero_serial() + + if exp > self.MAX_EXP or mantissa > self.MAX_MANTISSA: + raise ValueError("amount overflow") + + # Convert to bytes ----------------------------------------------------- + serial = 0x8000000000000000 # "Not XRP" bit set + if sign == 0: + serial |= 0x4000000000000000 # "Is positive" bit set + serial |= ((exp+97) << 54) # next 8 bits are exponent + serial |= mantissa # last 54 bits are mantissa + + return serial.to_bytes(8, byteorder="big", signed=False) + + + def canonical_zero_serial(self): + """ + Returns canonical format for zero: + - "Not XRP" bit = 1 + - "Sign bit" = 1 (for positive !!) + - exponent ??? + """ + # Mantissa is all zeroes. Must be positive zero. + #bitval = 0b1100000001000000000000000000000000000000000000000000000000000000 + #return bitval.to_bytes(8, byteorder="big", signed=False) + return (0x8000000000000000).to_bytes(8, byteorder="big", signed=False) From d5d48d092d5e5ba511859453bd0027ae1bfa619b Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 28 Nov 2018 18:02:02 -0800 Subject: [PATCH 09/19] Serialization: document PathSet fields --- .../api-conventions/serialization.md | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index ff9ecc4e10..1c58b75068 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -151,7 +151,7 @@ In addition to all of the above field types, the following types may appear in o Fields of this type contain the 160-bit identifier for an XRP Ledger [account](accounts.html). In JSON, these fields are represented as base58 XRP Ledger "addresses", with additional checksum data so that typos are unlikely to result in valid addresses. (This encoding, sometimes called "Base58Check", prevents accidentally sending money to the wrong address.) The binary format for these fields does not contain any checksum data. (However, since the binary format is used mostly for signed transactions, a typo or other error in transcribing a signed transaction would invalidate the signature, preventing it from sending money.) -These fields are [variable-length encoded](#variable-length-encoding) despite being a fixed 160 bits in length. As a result, the length indicator for these fields is always the byte `0x14`. +AccountIDs that appear as stand-alone fields (such as `Account` and `Destination`) are [variable-length encoded](#variable-length-encoding) despite being a fixed 160 bits in length. As a result, the length indicator for these fields is always the byte `0x14`. AccountIDs that appear as children of special fields ([Amount `issuer`][Amount] and [PathSet `account`][PathSet]) are _not_ variable-length encoded. ### Amount Fields @@ -164,7 +164,7 @@ The "Amount" type is a special field type that represents an amount of currency, - **Issued Currencies** Issued currencies consist of three segments in order: 1. 64 bits indicating the amount in the [internal currency format](currency-formats.html#issued-currency-math). The first bit is `1` to indicate that this is not XRP. - 2. 160 bits indicating the [currency code](https://developers.ripple.com/currency-formats.html#currency-codes). The standard API converts 3-character codes such as "USD" into 160-bit codes using the [standard currency code format](currency-formats.html#standard-currency-codes), but custom 160-bit codes are also possible. + 2. 160 bits indicating the [currency code](currency-formats.html#currency-codes). The standard API converts 3-character codes such as "USD" into 160-bit codes using the [standard currency code format](currency-formats.html#standard-currency-codes), but custom 160-bit codes are also possible. 3. 160 bits indicating the issuer's Account ID. (See also: [Account Address Encoding](accounts.html#address-encoding)) You can tell which of the two sub-types it is based on the first bit: `0` for XRP; `1` for issued currency. @@ -214,6 +214,35 @@ The following example shows the serialization format for an object (a single `Me ![Object field ID, followed by the Object ID and contents of each object member in canonical order, followed by the "Object end" field ID](img/serialization-object.png) +### PathSet Fields +[PathSet]: #pathset-fields + +The `Paths` field of a cross-currency [Payment transaction][] is a "PathSet", represented in JSON as an array of arrays. For more information on what paths are used for, see [Paths](paths.html). + +A PathSet is serialized as each individual path in sequence. Each complete path is followed by a byte that indicates what comes next: + +- `0xff` indicates another path follows +- `0x00` indicates the end of the PathSet + +Each path consists of a set of path steps in order. Each step starts with a **type** byte, followed by one or more fields describing the path step. The type indicates which fields are present in that path step through bitwise flags. (For example, the value `0x11` indicates changing both currency and issuer.) If more than one field is present, the fields are always placed in a specific order. + +The following table describes the possible fields and the bitwise flags to set in the type byte to indicate them: + +| Type Flag | Field Present | Field Type | Bit Size | Order | +|:----------|:--------------|:------------------|:---------|:------| +| `0x01` | `account` | [AccountID][] | 160 bits | 1st | +| `0x10` | `currency` | [Currency Code][] | 160 bits | 2nd | +| `0x20` | `issuer` | [AccountID][] | 160 bits | 3rd | + +[Currency Code]: currency-formats.html#standard-currency-codes + +Some combinations are invalid; see [Path Specifications](paths.html#path-specifications) for details. + +The AccountIDs in the `account` and `issuer` fields are presented _without_ a variable-length encoding prefix. When the `currency` is XRP, the currency code is represented as 160 bits of zeroes. + +Each step is followed directly by the next step of the path. As described above, last step of a path is followed by either `0xff` (if another path follows) or `0x00` (if this ends the last path). + + ### UInt Fields [UInt8]: #uint-fields [UInt16]: #uint-fields From a1bfc274d55ce4a7c28027cde7f78663fd5a12ad Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 28 Nov 2018 19:35:50 -0800 Subject: [PATCH 10/19] Serialization: Add PathSet diagram, fix typos --- .../_img-sources/serialization-pathset.uxf | 323 ++++++++++++++++++ .../api-conventions/serialization.md | 29 +- img/serialization-pathset.png | Bin 0 -> 16722 bytes 3 files changed, 342 insertions(+), 10 deletions(-) create mode 100644 content/_img-sources/serialization-pathset.uxf create mode 100644 img/serialization-pathset.png diff --git a/content/_img-sources/serialization-pathset.uxf b/content/_img-sources/serialization-pathset.uxf new file mode 100644 index 0000000000..84eb14431a --- /dev/null +++ b/content/_img-sources/serialization-pathset.uxf @@ -0,0 +1,323 @@ + + + 10 + + UMLPackage + + 40 + 40 + 650 + 70 + + PathSet + + + + UMLClass + + 50 + 210 + 40 + 30 + + 0x30 + + + + UMLClass + + 200 + 70 + 40 + 30 + + 0xff + + + + UMLClass + + 50 + 70 + 150 + 30 + + First path + + + + Relation + + 430 + 230 + 60 + 60 + + lt=<<- + 10.0;10.0;10.0;40.0;40.0;40.0 + + + Text + + 470 + 260 + 260 + 30 + + Path step's type byte + + + + UMLClass + + 250 + 70 + 150 + 30 + + Second path + + + + UMLClass + + 400 + 70 + 40 + 30 + + 0xff + + + + Text + + 450 + 70 + 40 + 30 + + ... + + + + UMLClass + + 490 + 70 + 150 + 30 + + Last path + + + + UMLClass + + 640 + 70 + 40 + 30 + + 0x00 + + + + UMLPackage + + 40 + 180 + 650 + 70 + + Path + + + + Relation + + 30 + 90 + 40 + 110 + + lt=.. + 10.0;90.0;20.0;10.0 + + + Relation + + 190 + 90 + 520 + 130 + + lt=.. + 500.0;110.0;10.0;10.0 + + + UMLClass + + 90 + 210 + 160 + 30 + + Currency (160 bits) + + + + UMLClass + + 250 + 210 + 160 + 30 + + Issuer (160 bits) + + + + UMLClass + + 420 + 210 + 40 + 30 + + 0x01 + + + + UMLClass + + 460 + 210 + 160 + 30 + + Account (160 bits) + + + + Text + + 640 + 210 + 40 + 30 + + ... + + + + Relation + + 40 + 250 + 390 + 50 + + lt=. + 10.0;10.0;10.0;30.0;370.0;30.0;370.0;10.0 + + + Text + + 80 + 290 + 260 + 30 + + Path step + + + + Relation + + 450 + 90 + 60 + 60 + + lt=<<- + 10.0;10.0;10.0;40.0;40.0;40.0 + + + Text + + 490 + 120 + 180 + 30 + + More paths + + + + Text + + 490 + 300 + 130 + 30 + + More path steps + + + + Relation + + 610 + 230 + 60 + 100 + + lt=<<- + 40.0;10.0;40.0;80.0;10.0;80.0 + + + Text + + 460 + 20 + 180 + 30 + + "Continue" byte + + + + Relation + + 410 + 20 + 60 + 70 + + lt=<<- + 10.0;50.0;10.0;10.0;40.0;10.0 + + + Text + + 640 + 20 + 100 + 30 + + "End" byte + + + + Relation + + 670 + 40 + 60 + 60 + + lt=<<- + 10.0;40.0;40.0;40.0;40.0;10.0 + + diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index 1c58b75068..9fe463a432 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -136,14 +136,14 @@ Transaction instructions may contain fields of any of the following types: In addition to all of the above field types, the following types may appear in other contexts, such as [ledger objects](ledger-object-types.html) and [transaction metadata](transaction-metadata.html): -| Type Name | Type Code | Variable-Length? | Description | -|:--------------|:----------|:-----------------|:------------------------------| -| Transaction | 10001 | No | A "high-level" type containing an entire [transaction](transaction-formats.html). | -| LedgerEntry | 10002 | No | A "high-level" type containing an entire [ledger object](ledger-object-types.html). | -| Validation | 10003 | No | A "high-level" type used in peer-to-peer communications to represent a validation vote in the [consensus process](consensus.html). | -| Metadata | 10004 | No | A "high-level" type containing [metadata for one transaction](transaction-metadata.html). | -| [UInt64][] | 3 | No | A 64-bit unsigned integer. This type does not appear in transaction instructions, but several use fields of this type. | -| [Vector256][] | 19 | Yes | This type does not appear in transaction instructions, but the [Amendments ledger object](amendments-object.html)'s `Amendments` field uses this to represent which [amendments](amendments.html) are currently enabled. | +| Type Name | Type Code | Variable-Length? | Description | +|:------------|:----------|:-----------------|:--------------------------------| +| Transaction | 10001 | No | A "high-level" type containing an entire [transaction](transaction-formats.html). | +| LedgerEntry | 10002 | No | A "high-level" type containing an entire [ledger object](ledger-object-types.html). | +| Validation | 10003 | No | A "high-level" type used in peer-to-peer communications to represent a validation vote in the [consensus process](consensus.html). | +| Metadata | 10004 | No | A "high-level" type containing [metadata for one transaction](transaction-metadata.html). | +| [UInt64][] | 3 | No | A 64-bit unsigned integer. This type does not appear in transaction instructions, but several ledger objects use fields of this type. | +| Vector256 | 19 | Yes | This type does not appear in transaction instructions, but the [Amendments ledger object](amendments-object.html)'s `Amendments` field uses this to represent which [amendments](amendments.html) are currently enabled. | ### AccountID Fields @@ -219,12 +219,12 @@ The following example shows the serialization format for an object (a single `Me The `Paths` field of a cross-currency [Payment transaction][] is a "PathSet", represented in JSON as an array of arrays. For more information on what paths are used for, see [Paths](paths.html). -A PathSet is serialized as each individual path in sequence. Each complete path is followed by a byte that indicates what comes next: +A PathSet is serialized as **one or more** individual paths in sequence. Each complete path is followed by a byte that indicates what comes next: - `0xff` indicates another path follows - `0x00` indicates the end of the PathSet -Each path consists of a set of path steps in order. Each step starts with a **type** byte, followed by one or more fields describing the path step. The type indicates which fields are present in that path step through bitwise flags. (For example, the value `0x11` indicates changing both currency and issuer.) If more than one field is present, the fields are always placed in a specific order. +Each path consists of **one or more** path steps in order. Each step starts with a **type** byte, followed by one or more fields describing the path step. The type indicates which fields are present in that path step through bitwise flags. (For example, the value `0x30` indicates changing both currency and issuer.) If more than one field is present, the fields are always placed in a specific order. The following table describes the possible fields and the bitwise flags to set in the type byte to indicate them: @@ -242,6 +242,10 @@ The AccountIDs in the `account` and `issuer` fields are presented _without_ a va Each step is followed directly by the next step of the path. As described above, last step of a path is followed by either `0xff` (if another path follows) or `0x00` (if this ends the last path). +The following example shows the serialization format for a PathSet: + +![PathSet is several paths each followed by a continue or end byte; each path is several path steps consisting of a type byte and one or more 160-bit fields based on the type byte](img/serialization-pathset.png) + ### UInt Fields [UInt8]: #uint-fields @@ -254,3 +258,8 @@ The XRP Ledger has several unsigned integer types: UInt8, UInt16, UInt32, and UI When representing these fields in JSON objects, most are represented as JSON numbers by default. One exception is UInt64, which is represented as a string because some JSON decoders may try to represent these integers as 64-bit "double precision" floating point numbers, which cannot represent all distinct UInt64 values with full precision. Another special case is the `TransactionType` field. In JSON, this field is conventionally represented as a string with the name of the transaction type, but in binary, this field is a UInt16. The `TRANSACTION_TYPES` object in the [definitions file](#definitions-file) maps these strings to specific numeric values. + + +{% include '_snippets/rippled-api-links.md' %} +{% include '_snippets/tx-type-links.md' %} +{% include '_snippets/rippled_versions.md' %} diff --git a/img/serialization-pathset.png b/img/serialization-pathset.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1d528f0f4c2a8ed2c9b386444e37c0ef939b10 GIT binary patch literal 16722 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 Sn>fO4%1SEUD!gI*=>Gtg;~ Date: Wed, 28 Nov 2018 20:15:03 -0800 Subject: [PATCH 11/19] Serialization: fix typos, make tx internal types consistent w/ serialization docs --- content/_snippets/rippled-api-links.md | 2 +- .../api-conventions/serialization.md | 17 +++++++---- .../transaction-common-fields.md | 12 ++++---- .../transaction-types/accountset.md | 28 +++++++++++-------- .../transaction-types/checkcash.md | 10 +++---- .../transaction-types/checkcreate.md | 4 +-- .../transaction-types/escrowcancel.md | 8 +++--- .../transaction-types/escrowcreate.md | 2 +- .../transaction-types/escrowfinish.md | 12 ++++---- .../transaction-types/offercancel.md | 6 ++-- .../transaction-types/offercreate.md | 14 ++++++---- .../transaction-types/payment.md | 2 +- .../transaction-types/paymentchannelclaim.md | 4 +-- .../transaction-types/paymentchannelcreate.md | 2 +- .../transaction-types/trustset.md | 16 +++++------ 15 files changed, 76 insertions(+), 63 deletions(-) diff --git a/content/_snippets/rippled-api-links.md b/content/_snippets/rippled-api-links.md index e6a2f8f15f..b6f589cd73 100644 --- a/content/_snippets/rippled-api-links.md +++ b/content/_snippets/rippled-api-links.md @@ -6,6 +6,7 @@ [Currency Code]: currency-formats.html#currency-codes [drops of XRP]: basic-data-types.html#specifying-currency-amounts [Hash]: basic-data-types.html#hashes +[Internal Type]: serialization.html [Ledger Index]: basic-data-types.html#ledger-index [Marker]: markers-and-pagination.html [result code]: transaction-results.html @@ -27,7 +28,6 @@ [crypto-conditions]: https://tools.ietf.org/html/draft-thomas-crypto-conditions-03 [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal [Interledger Protocol]: https://interledger.org/ -[Internal Type]: https://github.com/ripple/rippled/blob/master/src/ripple/protocol/impl/SField.cpp [RFC-1751]: https://tools.ietf.org/html/rfc1751 [ripple-lib]: https://github.com/ripple/ripple-lib diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index 9fe463a432..d029e4cfff 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -21,7 +21,7 @@ The result is a single binary blob that can be signed using well-known signature ## Internal Format -Each field has an "internal" binary format used in the `rippled` source code to represent that field when signing (and in most other cases). The internal formats for all fields are defined in the source code of [`SField.cpp`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/impl/SField.cpp). (This file also includes fields other than transaction fields. The [Transaction Format Reference](transaction-formats.html) also lists the internal formats for all transaction fields. +Each field has an "internal" binary format used in the `rippled` source code to represent that field when signing (and in most other cases). The internal formats for all fields are defined in the source code of [`SField.cpp`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/impl/SField.cpp). (This file also includes fields other than transaction fields.) The [Transaction Format Reference](transaction-formats.html) also lists the internal formats for all transaction fields. For example, the `Flags` [common transaction field](transaction-common-fields.html) becomes a UInt32 (32-bit unsigned integer). @@ -102,13 +102,18 @@ Some types of fields are Variable-Length encoding, which means they are not alwa Variable-length fields are encoded with one to three bytes indicating the length of the field immediately after the type prefix and before the contents. - If the field contains 0 to 192 bytes of data, the first byte defines the length of the VariableLength data, then that many bytes of data follow immediately after the length byte. + - If the field contains 193 to 12480 bytes of data, the first two bytes indicate the length of the field with the following formula: + 193 + ((byte1 - 193) * 256) + byte2 + - If the field contains 12481 to 918744 bytes of data, the first three bytes indicate the length of the field with the following formula: - 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3; + + 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3 + - A variable-length field cannot contain more than 918744 bytes of data. -When decoding, you can tell from the value of the first length byte whether there are 0, 1, or 2 more length bytes: +When decoding, you can tell from the value of the first length byte whether there are 0, 1, or 2 additional length bytes: - If the first length byte has a value of 192 or less, then that's the only length byte and it contains the exact length of the field contents in bytes. - If the first length byte has a value of 193 to 240, then there are two length bytes. @@ -122,7 +127,7 @@ Transaction instructions may contain fields of any of the following types: | Type Name | Type Code | Variable-Length? | Description | |:--------------|:----------|:-----------------|:------------------------------| | [AccountID][] | 8 | Yes | The unique identifier for an [account](accounts.html). This field is variable-length encoded, but always exactly 20 bytes. | -| [Amount][] | 6 | No | An amount of XRP or issued currency. The length of the field is 64 bits for XRP or | +| [Amount][] | 6 | No | An amount of XRP or issued currency. The length of the field is 64 bits for XRP or 384 bits (64+160+160) for issued currencies. | | [Blob][] | 7 | Yes | Arbitrary binary data. One important such field is `TxnSignature`, the signature that authorizes a transaction. | | [Hash128][] | 4 | No | A 128-bit arbitrary binary value. The only such field is `EmailHash`, which is intended to store the MD-5 hash of an account owner's email for purposes of fetching a [Gravatar](https://www.gravatar.com/). | | [Hash160][] | 17 | No | A 160-bit arbitrary binary value. This may define a currency code or issuer. | @@ -219,12 +224,12 @@ The following example shows the serialization format for an object (a single `Me The `Paths` field of a cross-currency [Payment transaction][] is a "PathSet", represented in JSON as an array of arrays. For more information on what paths are used for, see [Paths](paths.html). -A PathSet is serialized as **one or more** individual paths in sequence. Each complete path is followed by a byte that indicates what comes next: +A PathSet is serialized as **1 to 6** individual paths in sequence[[Source]](https://github.com/ripple/rippled/blob/4cff94f7a4a05302bdf1a248515379da99c5bcd4/src/ripple/app/tx/impl/Payment.h#L35-L36 "Source"). Each complete path is followed by a byte that indicates what comes next: - `0xff` indicates another path follows - `0x00` indicates the end of the PathSet -Each path consists of **one or more** path steps in order. Each step starts with a **type** byte, followed by one or more fields describing the path step. The type indicates which fields are present in that path step through bitwise flags. (For example, the value `0x30` indicates changing both currency and issuer.) If more than one field is present, the fields are always placed in a specific order. +Each path consists of **1 to 8** path steps in order[[Source]](https://github.com/ripple/rippled/blob/4cff94f7a4a05302bdf1a248515379da99c5bcd4/src/ripple/app/tx/impl/Payment.h#L38-L39 "Source"). Each step starts with a **type** byte, followed by one or more fields describing the path step. The type indicates which fields are present in that path step through bitwise flags. (For example, the value `0x30` indicates changing both currency and issuer.) If more than one field is present, the fields are always placed in a specific order. The following table describes the possible fields and the bitwise flags to set in the type byte to indicate them: diff --git a/content/references/rippled-api/transaction-formats/transaction-common-fields.md b/content/references/rippled-api/transaction-formats/transaction-common-fields.md index 91f08f34e4..9682f3dd7f 100644 --- a/content/references/rippled-api/transaction-formats/transaction-common-fields.md +++ b/content/references/rippled-api/transaction-formats/transaction-common-fields.md @@ -14,8 +14,8 @@ Every transaction has the same set of common fields, plus additional fields base | [Memos][] | Array of Objects | Array | _(Optional)_ Additional arbitrary information used to identify this transaction. | | [Signers][] | Array | Array | _(Optional)_ Array of objects that represent a [multi-signature](multi-signing.html) which authorizes this transaction. | | SourceTag | Unsigned Integer | UInt32 | _(Optional)_ Arbitrary integer used to identify the reason for this payment, or a sender on whose behalf this transaction is made. Conventionally, a refund should specify the initial payment's `SourceTag` as the refund payment's `DestinationTag`. | -| SigningPubKey | String | VariableLength | _(Automatically added when signing)_ Hex representation of the public key that corresponds to the private key used to sign this transaction. If an empty string, indicates a multi-signature is present in the `Signers` field instead. | -| TxnSignature | String | VariableLength | _(Automatically added when signing)_ The signature that verifies this transaction as originating from the account it says it is from. | +| SigningPubKey | String | Blob | _(Automatically added when signing)_ Hex representation of the public key that corresponds to the private key used to sign this transaction. If an empty string, indicates a multi-signature is present in the `Signers` field instead. | +| TxnSignature | String | Blob | _(Automatically added when signing)_ The signature that verifies this transaction as originating from the account it says it is from. | [auto-fillable]: #auto-fillable-fields [AccountTxnID]: #accounttxnid @@ -89,9 +89,9 @@ The `Memos` field includes arbitrary messaging data with the transaction. It is | Field | Type | [Internal Type][] | Description | |:-----------|:-------|:------------------|:-----------------------------------| -| MemoData | String | VariableLength | Arbitrary hex value, conventionally containing the content of the memo. | -| MemoFormat | String | VariableLength | Hex value representing characters allowed in URLs. Conventionally containing information on how the memo is encoded, for example as a [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml). | -| MemoType | String | VariableLength | Hex value representing characters allowed in URLs. Conventionally, a unique relation (according to [RFC 5988](http://tools.ietf.org/html/rfc5988#section-4)) that defines the format of this memo. | +| MemoData | String | Blob | Arbitrary hex value, conventionally containing the content of the memo. | +| MemoFormat | String | Blob | Hex value representing characters allowed in URLs. Conventionally containing information on how the memo is encoded, for example as a [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml). | +| MemoType | String | Blob | Hex value representing characters allowed in URLs. Conventionally, a unique relation (according to [RFC 5988](http://tools.ietf.org/html/rfc5988#section-4)) that defines the format of this memo. | The MemoType and MemoFormat fields should only consist of the following characters: `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%` @@ -125,7 +125,7 @@ The `Signers` field contains a [multi-signature](multi-signing.html), which has |:--------------|:-------|:------------------|:--------------------------------| | Account | String | AccountID | The address associated with this signature, as it appears in the SignerList. | | TxnSignature | String | Blob | A signature for this transaction, verifiable using the `SigningPubKey`. | -| SigningPubKey | String | PubKey | The public key used to create this signature. | +| SigningPubKey | String | Blob | The public key used to create this signature. | The `SigningPubKey` must be a key that is associated with the `Account` address. If the referenced `Account` is a funded account in the ledger, then the SigningPubKey can be that account's current Regular Key if one is set. It could also be that account's Master Key, unless the [lsfDisableMaster](accountroot.html#accountroot-flags) flag is enabled. If the referenced `Account` address is not a funded account in the ledger, then the `SigningPubKey` must be the master key associated with that address. diff --git a/content/references/rippled-api/transaction-formats/transaction-types/accountset.md b/content/references/rippled-api/transaction-formats/transaction-types/accountset.md index 81ae646d2d..56eeb8b86d 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/accountset.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/accountset.md @@ -22,17 +22,23 @@ An AccountSet transaction modifies the properties of an [account in the XRP Ledg -| Field | JSON Type | [Internal Type][] | Description | -|:-------------------------------|:-----------------|:------------------|:-----| -| [ClearFlag](#accountset-flags) | Unsigned Integer | UInt32 | _(Optional)_ Unique identifier of a flag to disable for this account. | -| [Domain](#domain) | String | VariableLength | _(Optional)_ The domain that owns this account, as a string of hex representing the ASCII for the domain in lowercase. | -| EmailHash | String | Hash128 | _(Optional)_ Hash of an email address to be used for generating an avatar image. Conventionally, clients use [Gravatar](http://en.gravatar.com/site/implement/hash/) to display this image. | -| MessageKey | String | PubKey | _(Optional)_ Public key for sending encrypted messages to this account. | -| [SetFlag](#accountset-flags) | Unsigned Integer | UInt32 | _(Optional)_ Integer flag to enable for this account. | -| [TransferRate](accountset.html#transferrate) | Unsigned Integer | UInt32 | _(Optional)_ The fee to charge when users transfer this account's issued currencies, represented as billionths of a unit. Cannot be more than `2000000000` or less than `1000000000`, except for the special case `0` meaning no fee. | -| [TickSize](ticksize.html) | Unsigned Integer | UInt8 | _(Optional)_ Tick size to use for offers involving a currency issued by this address. The exchange rates of those offers is rounded to this many significant digits. Valid values are `3` to `15` inclusive, or `0` to disable. _(Requires the [TickSize amendment](known-amendments.html#ticksize).)_ | -| WalletLocator | String | Hash256 | _(Optional)_ Not used. | -| WalletSize | Unsigned Integer | UInt32 | _(Optional)_ Not used. | +| Field | JSON Type | [Internal Type][] | Description | +|:-----------------|:-----------------|:------------------|:-------------------| +| [ClearFlag][] | Number | UInt32 | _(Optional)_ Unique identifier of a flag to disable for this account. | +| [Domain][] | String | Blob | _(Optional)_ The domain that owns this account, as a string of hex representing the ASCII for the domain in lowercase. | +| EmailHash | String | Hash128 | _(Optional)_ Hash of an email address to be used for generating an avatar image. Conventionally, clients use [Gravatar](http://en.gravatar.com/site/implement/hash/) to display this image. | +| MessageKey | String | Blob | _(Optional)_ Public key for sending encrypted messages to this account. | +| [SetFlag][] | Number | UInt32 | _(Optional)_ Integer flag to enable for this account. | +| [TransferRate][] | Unsigned Integer | UInt32 | _(Optional)_ The fee to charge when users transfer this account's issued currencies, represented as billionths of a unit. Cannot be more than `2000000000` or less than `1000000000`, except for the special case `0` meaning no fee. | +| [TickSize][] | Unsigned Integer | UInt8 | _(Optional)_ Tick size to use for offers involving a currency issued by this address. The exchange rates of those offers is rounded to this many significant digits. Valid values are `3` to `15` inclusive, or `0` to disable. _(Requires the [TickSize amendment](known-amendments.html#ticksize).)_ | +| WalletLocator | String | Hash256 | _(Optional)_ Not used. | +| WalletSize | Number | UInt32 | _(Optional)_ Not used. | + +[ClearFlag]: #accountset-flags +[Domain]: #domain +[SetFlag]: #accountset-flags +[TickSize]: ticksize.html +[TransferRate]: accountset.html#transferrate If none of these options are provided, then the AccountSet transaction has no effect (beyond destroying the transaction cost). See [Cancel or Skip a Transaction](cancel-or-skip-a-transaction.html) for more details. diff --git a/content/references/rippled-api/transaction-formats/transaction-types/checkcash.md b/content/references/rippled-api/transaction-formats/transaction-types/checkcash.md index 5bd7b7905b..df2b1f896d 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/checkcash.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/checkcash.md @@ -22,11 +22,11 @@ Since the funds for a check are not guaranteed, redeeming a Check can fail becau {% include '_snippets/tx-fields-intro.md' %} -| Field | JSON Type | [Internal Type][] | Description | -|:------------|:----------|:------------------|:-------------------------------| -| `CheckID` | String | Hash256 | The ID of the [Check ledger object](check.html) to cash, as a 64-character hexadecimal string. | -| `Amount` | [Currency Amount][] | Amount | _(Optional)_ Redeem the Check for exactly this amount, if possible. The currency must match that of the `SendMax` of the corresponding CheckCreate transaction. You must provide either this field or `DeliverMin`. | -| `DeliverMin` | [Currency Amount][] | Amount | _(Optional)_ Redeem the Check for at least this amount and for as much as possible. The currency must match that of the `SendMax` of the corresponding CheckCreate transaction. You must provide either this field or `Amount`. | +| Field | JSON Type | [Internal Type][] | Description | +|:-------------|:--------------------|:------------------|:--------------------| +| `CheckID` | String | Hash256 | The ID of the [Check ledger object](check.html) to cash, as a 64-character hexadecimal string. | +| `Amount` | [Currency Amount][] | Amount | _(Optional)_ Redeem the Check for exactly this amount, if possible. The currency must match that of the `SendMax` of the corresponding CheckCreate transaction. You must provide either this field or `DeliverMin`. | +| `DeliverMin` | [Currency Amount][] | Amount | _(Optional)_ Redeem the Check for at least this amount and for as much as possible. The currency must match that of the `SendMax` of the corresponding CheckCreate transaction. You must provide either this field or `Amount`. | The transaction ***must*** include either `Amount` or `DeliverMin`, but not both. diff --git a/content/references/rippled-api/transaction-formats/transaction-types/checkcreate.md b/content/references/rippled-api/transaction-formats/transaction-types/checkcreate.md index 78587efc7e..232d863170 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/checkcreate.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/checkcreate.md @@ -27,8 +27,8 @@ Create a Check object in the ledger, which is a deferred payment that can be cas |:-----------------|:--------------------|:------------------|:----------------| | `Destination` | String | Account | The unique address of the [account](accounts.html) that can cash the Check. | | `SendMax` | [Currency Amount][] | Amount | Maximum amount of source currency the Check is allowed to debit the sender, including [transfer fees](transfer-fees.html) on non-XRP currencies. The Check can only credit the destination with the same currency (from the same issuer, for non-XRP currencies). For non-XRP amounts, the nested field names MUST be lower-case. | -| `DestinationTag` | Unsigned Integer | UInt32 | _(Optional)_ Arbitrary tag that identifies the reason for the Check, or a hosted recipient to pay. | -| `Expiration` | Unsigned Integer | UInt32 | _(Optional)_ Time after which the Check is no longer valid, in [seconds since the Ripple Epoch][]. | +| `DestinationTag` | Number | UInt32 | _(Optional)_ Arbitrary tag that identifies the reason for the Check, or a hosted recipient to pay. | +| `Expiration` | Number | UInt32 | _(Optional)_ Time after which the Check is no longer valid, in [seconds since the Ripple Epoch][]. | | `InvoiceID` | String | Hash256 | _(Optional)_ Arbitrary 256-bit hash representing a specific reason or identifier for this Check. | ## Error Cases diff --git a/content/references/rippled-api/transaction-formats/transaction-types/escrowcancel.md b/content/references/rippled-api/transaction-formats/transaction-types/escrowcancel.md index e420243aca..213cd93434 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/escrowcancel.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/escrowcancel.md @@ -21,10 +21,10 @@ Return escrowed XRP to the sender. -| Field | JSON Type | [Internal Type][] | Description | -|:----------------|:-----------------|:------------------|:--------------------------| -| `Owner` | String | AccountID | Address of the source account that funded the escrow payment. -| `OfferSequence` | Unsigned Integer | UInt32 | Transaction sequence of [EscrowCreate transaction][] that created the escrow to cancel. +| Field | JSON Type | [Internal Type][] | Description | +|:----------------|:----------|:------------------|:---------------------------| +| `Owner` | String | AccountID | Address of the source account that funded the escrow payment. | +| `OfferSequence` | Number | UInt32 | Transaction sequence of [EscrowCreate transaction][] that created the escrow to cancel. | Any account may submit an EscrowCancel transaction. diff --git a/content/references/rippled-api/transaction-formats/transaction-types/escrowcreate.md b/content/references/rippled-api/transaction-formats/transaction-types/escrowcreate.md index 88c1119d2e..9440c352d2 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/escrowcreate.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/escrowcreate.md @@ -32,7 +32,7 @@ Sequester XRP until the escrow process either finishes or is canceled. | `Destination` | String | AccountID | Address to receive escrowed XRP. | | `CancelAfter` | Number | UInt32 | _(Optional)_ The time, in [seconds since the Ripple Epoch][], when this escrow expires. This value is immutable; the funds can only be returned the sender after this time. | | `FinishAfter` | Number | UInt32 | _(Optional)_ The time, in [seconds since the Ripple Epoch][], when the escrowed XRP can be released to the recipient. This value is immutable; the funds cannot move until this time is reached. | -| `Condition` | String | VariableLength | _(Optional)_ Hex value representing a [PREIMAGE-SHA-256 crypto-condition](https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-8.1). The funds can only be delivered to the recipient if this condition is fulfilled. | +| `Condition` | String | Blob | _(Optional)_ Hex value representing a [PREIMAGE-SHA-256 crypto-condition](https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-8.1). The funds can only be delivered to the recipient if this condition is fulfilled. | | `DestinationTag` | Number | UInt32 | _(Optional)_ Arbitrary tag to further specify the destination for this escrowed payment, such as a hosted recipient at the destination address. | Either `CancelAfter` or `FinishAfter` must be specified. If both are included, the `FinishAfter` time must be before the `CancelAfter` time. diff --git a/content/references/rippled-api/transaction-formats/transaction-types/escrowfinish.md b/content/references/rippled-api/transaction-formats/transaction-types/escrowfinish.md index a7565594d0..aa9fff382c 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/escrowfinish.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/escrowfinish.md @@ -23,12 +23,12 @@ Deliver XRP from a held payment to the recipient. -| Field | JSON Type | [Internal Type][] | Description | -|:----------------|:-----------------|:------------------|:--------------------------| -| `Owner` | String | AccountID | Address of the source account that funded the held payment. -| `OfferSequence` | Unsigned Integer | UInt32 | Transaction sequence of [EscrowCreate transaction][] that created the held payment to finish. -| `Condition` | String | VariableLength | _(Optional)_ Hex value matching the previously-supplied [PREIMAGE-SHA-256 crypto-condition](https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-8.1) of the held payment. | -| `Fulfillment` | String | VariableLength | _(Optional)_ Hex value of the [PREIMAGE-SHA-256 crypto-condition fulfillment](https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-8.1.4) matching the held payment's `Condition`. | +| Field | JSON Type | [Internal Type][] | Description | +|:----------------|:-----------------|:------------------|:--------------------| +| `Owner` | String | AccountID | Address of the source account that funded the held payment. | +| `OfferSequence` | Unsigned Integer | UInt32 | Transaction sequence of [EscrowCreate transaction][] that created the held payment to finish. | +| `Condition` | String | Blob | _(Optional)_ Hex value matching the previously-supplied [PREIMAGE-SHA-256 crypto-condition](https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-8.1) of the held payment. | +| `Fulfillment` | String | Blob | _(Optional)_ Hex value of the [PREIMAGE-SHA-256 crypto-condition fulfillment](https://tools.ietf.org/html/draft-thomas-crypto-conditions-02#section-8.1.4) matching the held payment's `Condition`. | Any account may submit an EscrowFinish transaction. diff --git a/content/references/rippled-api/transaction-formats/transaction-types/offercancel.md b/content/references/rippled-api/transaction-formats/transaction-types/offercancel.md index f6c0d9985c..9db61ad087 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/offercancel.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/offercancel.md @@ -22,9 +22,9 @@ An OfferCancel transaction removes an Offer object from the XRP Ledger. -| Field | JSON Type | [Internal Type][] | Description | -|:--------------|:-----------------|:------------------|:----------------------| -| OfferSequence | Unsigned Integer | UInt32 | The sequence number of a previous OfferCreate transaction. If specified, cancel any offer object in the ledger that was created by that transaction. It is not considered an error if the offer specified does not exist. | +| Field | JSON Type | [Internal Type][] | Description | +|:--------------|:----------|:------------------|:-----------------------------| +| OfferSequence | Number | UInt32 | The sequence number of a previous OfferCreate transaction. If specified, cancel any offer object in the ledger that was created by that transaction. It is not considered an error if the offer specified does not exist. | *Tip:* To remove an old offer and replace it with a new one, you can use an [OfferCreate transaction][] with an `OfferSequence` parameter, instead of using OfferCancel and another OfferCreate. diff --git a/content/references/rippled-api/transaction-formats/transaction-types/offercreate.md b/content/references/rippled-api/transaction-formats/transaction-types/offercreate.md index 9d5fc17af4..49cb4d0a63 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/offercreate.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/offercreate.md @@ -29,12 +29,14 @@ For more information about how Offers work, see [Offers](offers.html). -| Field | JSON Type | [Internal Type][] | Description | -|:--------------------------|:--------------------|:------------------|:-------| -| [Expiration](offers.html#offer-expiration) | Unsigned Integer | UInt32 | _(Optional)_ Time after which the offer is no longer active, in [seconds since the Ripple Epoch][]. | -| OfferSequence | Unsigned Integer | UInt32 | _(Optional)_ An offer to delete first, specified in the same way as [OfferCancel][]. | -| TakerGets | [Currency Amount][] | Amount | The amount and type of currency being provided by the offer creator. | -| TakerPays | [Currency Amount][] | Amount | The amount and type of currency being requested by the offer creator. | +| Field | JSON Type | [Internal Type][] | Description | +|:---------------|:--------------------|:------------------|:------------------| +| [Expiration][] | Number | UInt32 | _(Optional)_ Time after which the offer is no longer active, in [seconds since the Ripple Epoch][]. | +| OfferSequence | Number | UInt32 | _(Optional)_ An offer to delete first, specified in the same way as [OfferCancel][]. | +| TakerGets | [Currency Amount][] | Amount | The amount and type of currency being provided by the offer creator. | +| TakerPays | [Currency Amount][] | Amount | The amount and type of currency being requested by the offer creator. | + +[Expiration]: offers.html#offer-expiration ## OfferCreate Flags diff --git a/content/references/rippled-api/transaction-formats/transaction-types/payment.md b/content/references/rippled-api/transaction-formats/transaction-types/payment.md index 6ba3da6866..df109b8379 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/payment.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/payment.md @@ -31,7 +31,7 @@ Payments are also the only way to [create accounts](#creating-accounts). |:---------------|:---------------------|:------------------|:-----------------| | Amount | [Currency Amount][] | Amount | The amount of currency to deliver. For non-XRP amounts, the nested field names MUST be lower-case. If the [**tfPartialPayment** flag](#payment-flags) is set, deliver _up to_ this amount instead. | | Destination | String | Account | The unique address of the account receiving the payment. | -| DestinationTag | Unsigned Integer | UInt32 | _(Optional)_ Arbitrary tag that identifies the reason for the payment to the destination, or a hosted recipient to pay. | +| DestinationTag | Number | UInt32 | _(Optional)_ Arbitrary tag that identifies the reason for the payment to the destination, or a hosted recipient to pay. | | InvoiceID | String | Hash256 | _(Optional)_ Arbitrary 256-bit hash representing a specific reason or identifier for this payment. | | Paths | Array of path arrays | PathSet | (Optional, auto-fillable) Array of [payment paths](paths.html) to be used for this transaction. Must be omitted for XRP-to-XRP transactions. | | SendMax | [Currency Amount][] | Amount | _(Optional)_ Highest amount of source currency this transaction is allowed to cost, including [transfer fees](transfer-fees.html), exchange rates, and [slippage](http://en.wikipedia.org/wiki/Slippage_%28finance%29). Does not include the [XRP destroyed as a cost for submitting the transaction](transaction-cost.html). For non-XRP amounts, the nested field names MUST be lower-case. Must be supplied for cross-currency/cross-issue payments. Must be omitted for XRP-to-XRP payments. | diff --git a/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelclaim.md b/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelclaim.md index 3112eb1d8a..18cd8371af 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelclaim.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelclaim.md @@ -44,8 +44,8 @@ The **destination address** of a channel can: | `Channel` | String | Hash256 | The unique ID of the channel, as a 64-character hexadecimal string. | | `Balance` | String | Amount | _(Optional)_ Total amount of [XRP, in drops][Currency Amount], delivered by this channel after processing this claim. Required to deliver XRP. Must be more than the total amount delivered by the channel so far, but not greater than the `Amount` of the signed claim. Must be provided except when closing the channel. | | `Amount` | String | Amount | _(Optional)_ The amount of [XRP, in drops][Currency Amount], authorized by the `Signature`. This must match the amount in the signed message. This is the cumulative amount of XRP that can be dispensed by the channel, including XRP previously redeemed. | -| `Signature` | String | VariableLength | _(Optional)_ The signature of this claim, as hexadecimal. The signed message contains the channel ID and the amount of the claim. Required unless the sender of the transaction is the source address of the channel. | -| `PublicKey` | String | PubKey | _(Optional)_ The public key used for the signature, as hexadecimal. This must match the `PublicKey` stored in the ledger for the channel. Required unless the sender of the transaction is the source address of the channel and the `Signature` field is omitted. (The transaction includes the PubKey so that `rippled` can check the validity of the signature before trying to apply the transaction to the ledger.) | +| `Signature` | String | Blob | _(Optional)_ The signature of this claim, as hexadecimal. The signed message contains the channel ID and the amount of the claim. Required unless the sender of the transaction is the source address of the channel. | +| `PublicKey` | String | Blob | _(Optional)_ The public key used for the signature, as hexadecimal. This must match the `PublicKey` stored in the ledger for the channel. Required unless the sender of the transaction is the source address of the channel and the `Signature` field is omitted. (The transaction includes the PubKey so that `rippled` can check the validity of the signature before trying to apply the transaction to the ledger.) | ## PaymentChannelClaim Flags diff --git a/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelcreate.md b/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelcreate.md index 6ed7f642d3..3103c0d7d3 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelcreate.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/paymentchannelcreate.md @@ -30,7 +30,7 @@ Create a unidirectional channel and fund it with XRP. The address sending this t | `Amount` | String | Amount | Amount of [XRP, in drops][Currency Amount], to deduct from the sender's balance and set aside in this channel. While the channel is open, the XRP can only go to the `Destination` address. When the channel closes, any unclaimed XRP is returned to the source address's balance. | | `Destination` | String | AccountID | Address to receive XRP claims against this channel. This is also known as the "destination address" for the channel. Cannot be the same as the sender (`Account`). | | `SettleDelay` | Number | UInt32 | Amount of time the source address must wait before closing the channel if it has unclaimed XRP. | -| `PublicKey` | String | PubKey | The public key of the key pair the source will use to sign claims against this channel, in hexadecimal. This can be any secp256k1 or Ed25519 public key. | +| `PublicKey` | String | Blob | The public key of the key pair the source will use to sign claims against this channel, in hexadecimal. This can be any secp256k1 or Ed25519 public key. | | `CancelAfter` | Number | UInt32 | _(Optional)_ The time, in [seconds since the Ripple Epoch][], when this channel expires. Any transaction that would modify the channel after this time closes the channel without otherwise affecting it. This value is immutable; the channel can be closed earlier than this time but cannot remain open after this time. | | `DestinationTag` | Number | UInt32 | _(Optional)_ Arbitrary tag to further specify the destination for this payment channel, such as a hosted recipient at the destination address. | diff --git a/content/references/rippled-api/transaction-formats/transaction-types/trustset.md b/content/references/rippled-api/transaction-formats/transaction-types/trustset.md index 658e5a31a9..a31ba56dbb 100644 --- a/content/references/rippled-api/transaction-formats/transaction-types/trustset.md +++ b/content/references/rippled-api/transaction-formats/transaction-types/trustset.md @@ -25,14 +25,14 @@ Create or modify a trust line linking two accounts. {% include '_snippets/tx-fields-intro.md' %} -| Field | JSON Type | [Internal Type][] | Description | -|:-------------------------|:-----------------|:------------------|:-----------| -| `LimitAmount` | Object | Amount | Object defining the trust line to create or modify, in the format of a [Currency Amount][]. | -| `LimitAmount`.`currency` | String | (Amount.currency) | The currency to this trust line applies to, as a three-letter [ISO 4217 Currency Code](http://www.xe.com/iso4217.php) or a 160-bit hex value according to [currency format](currency-formats.html). "XRP" is invalid. | -| `LimitAmount`.`value` | String | (Amount.value) | Quoted decimal representation of the limit to set on this trust line. | -| `LimitAmount`.`issuer` | String | (Amount.issuer) | The address of the account to extend trust to. | -| `QualityIn` | Unsigned Integer | UInt32 | _(Optional)_ Value incoming balances on this trust line at the ratio of this number per 1,000,000,000 units. A value of `0` is shorthand for treating balances at face value. | -| `QualityOut` | Unsigned Integer | UInt32 | _(Optional)_ Value outgoing balances on this trust line at the ratio of this number per 1,000,000,000 units. A value of `0` is shorthand for treating balances at face value. | +| Field | JSON Type | [Internal Type][] | Description | +|:-------------------------|:----------|:------------------|:------------------| +| `LimitAmount` | Object | Amount | Object defining the trust line to create or modify, in the format of a [Currency Amount][]. | +| `LimitAmount`.`currency` | String | (Amount.currency) | The currency to this trust line applies to, as a three-letter [ISO 4217 Currency Code](http://www.xe.com/iso4217.php) or a 160-bit hex value according to [currency format](currency-formats.html). "XRP" is invalid. | +| `LimitAmount`.`value` | String | (Amount.value) | Quoted decimal representation of the limit to set on this trust line. | +| `LimitAmount`.`issuer` | String | (Amount.issuer) | The address of the account to extend trust to. | +| `QualityIn` | Number | UInt32 | _(Optional)_ Value incoming balances on this trust line at the ratio of this number per 1,000,000,000 units. A value of `0` is shorthand for treating balances at face value. | +| `QualityOut` | Number | UInt32 | _(Optional)_ Value outgoing balances on this trust line at the ratio of this number per 1,000,000,000 units. A value of `0` is shorthand for treating balances at face value. | ## TrustSet Flags From 1904ce1de923f25b0bb8d620c3e1e3b4a8e513fd Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Thu, 29 Nov 2018 15:33:25 -0800 Subject: [PATCH 12/19] Amount diagram; standardize diagrams; link example code --- content/_img-sources/serialization-amount.uxf | 341 ++++++++++++++++++ content/_img-sources/serialization-array.uxf | 22 +- content/_img-sources/serialization-object.uxf | 119 +++--- .../api-conventions/serialization.md | 19 +- img/serialization-amount.png | Bin 0 -> 34377 bytes img/serialization-array.png | Bin 7898 -> 9203 bytes img/serialization-object.png | Bin 11967 -> 11960 bytes 7 files changed, 422 insertions(+), 79 deletions(-) create mode 100644 content/_img-sources/serialization-amount.uxf create mode 100644 img/serialization-amount.png diff --git a/content/_img-sources/serialization-amount.uxf b/content/_img-sources/serialization-amount.uxf new file mode 100644 index 0000000000..a8818ce839 --- /dev/null +++ b/content/_img-sources/serialization-amount.uxf @@ -0,0 +1,341 @@ + + + 10 + + UMLPackage + + 30 + 240 + 850 + 70 + + Issued Currency Amount Format + + + + UMLClass + + 40 + 270 + 20 + 30 + + 1 + + + + UMLClass + + 70 + 270 + 20 + 30 + + + + + + UMLClass + + 100 + 270 + 150 + 30 + + exponent (8 bits) + + + + UMLClass + + 260 + 270 + 190 + 30 + + mantissa (54 bits) + + + + Relation + + 70 + 290 + 50 + 60 + + lt=<<- + 10.0;10.0;10.0;40.0;30.0;40.0 + + + Text + + 100 + 320 + 260 + 30 + + Sign bit (0=negative, 1=positive) +style=wordwrap + + + + Text + + 80 + 350 + 160 + 60 + + "Not XRP" bit +(0=XRP, 1=not XRP) +style=wordwrap + + + + Relation + + 40 + 290 + 50 + 90 + + lt=<<- + 10.0;10.0;10.0;70.0;30.0;70.0 + + + UMLClass + + 460 + 270 + 200 + 30 + + currency code (160 bits) + + + + UMLClass + + 670 + 270 + 200 + 30 + + issuer AccountID (160 bits) + + + + UMLClass + + 40 + 510 + 20 + 30 + + 00 + + + + Relation + + 40 + 520 + 60 + 70 + + lt=<<- + 10.0;10.0;10.0;50.0;40.0;50.0 + + + Text + + 80 + 560 + 300 + 40 + + Type code (8 bits) +0x00 for ISO 4217/pseudo-ISO currency + + + + UMLClass + + 70 + 510 + 220 + 30 + + Reserved (88 bits) + + + + UMLClass + + 460 + 510 + 180 + 30 + + Reserved (40 bits) + + + + UMLClass + + 300 + 510 + 150 + 30 + + ISO code (24 bits) + + + + UMLPackage + + 30 + 480 + 620 + 70 + + Issued Currency Code Format + + + + Relation + + 310 + 530 + 40 + 50 + + lt=<<- + 10.0;10.0;10.0;30.0;20.0;30.0 + + + Text + + 330 + 550 + 130 + 30 + + 3 chars of ASCII + + + + Relation + + 20 + 290 + 460 + 210 + + lt=.. + 10.0;190.0;440.0;10.0 + + + Relation + + 640 + 290 + 40 + 230 + + lt=.. + 10.0;210.0;20.0;10.0 + + + UMLPackage + + 30 + 30 + 620 + 70 + + XRP Amount Format + + + + UMLClass + + 40 + 60 + 20 + 30 + + 0 + + + + UMLClass + + 70 + 60 + 20 + 30 + + 1 + + + + Text + + 100 + 110 + 260 + 30 + + Sign bit (always 1 for positive) +style=wordwrap + + + + Relation + + 70 + 80 + 50 + 60 + + lt=<<- + 10.0;10.0;10.0;40.0;30.0;40.0 + + + Text + + 80 + 140 + 160 + 60 + + "Not XRP" bit +(0=XRP, 1=not XRP) +style=wordwrap + + + + Relation + + 40 + 80 + 50 + 90 + + lt=<<- + 10.0;10.0;10.0;70.0;30.0;70.0 + + + UMLClass + + 100 + 60 + 540 + 30 + + integer drops (62 bits) + + + diff --git a/content/_img-sources/serialization-array.uxf b/content/_img-sources/serialization-array.uxf index 92f81f9e78..f5b443c100 100644 --- a/content/_img-sources/serialization-array.uxf +++ b/content/_img-sources/serialization-array.uxf @@ -1,17 +1,6 @@ 10 - - UMLClass - - 130 - 110 - 600 - 50 - - - - UMLClass @@ -148,4 +137,15 @@ Field ID (SignerEntry Contents) + + UMLPackage + + 130 + 90 + 600 + 70 + + SignerEntries field + + diff --git a/content/_img-sources/serialization-object.uxf b/content/_img-sources/serialization-object.uxf index 4924e729c6..37ee31ac98 100644 --- a/content/_img-sources/serialization-object.uxf +++ b/content/_img-sources/serialization-object.uxf @@ -1,17 +1,6 @@ 10 - - UMLClass - - 80 - 160 - 600 - 50 - - - - UMLClass @@ -73,17 +62,17 @@ Field ID 630 190 - 30 - 80 + 80 + 60 lt=<<- - 10.0;10.0;10.0;60.0 + 10.0;10.0;10.0;30.0;60.0;30.0;60.0;40.0 Text - 590 - 250 + 640 + 230 100 60 @@ -109,22 +98,10 @@ no contents 160 190 50 - 80 + 60 lt=<<- - 10.0;10.0;10.0;60.0;30.0;60.0 - - - Text - - 220 - 120 - 190 - 50 - - Length prefix for -variable-length Blob fields - + 10.0;10.0;10.0;40.0;30.0;40.0 UMLClass @@ -148,22 +125,11 @@ variable-length Blob fields - - Relation - - 190 - 130 - 50 - 60 - - lt=<<- - 10.0;40.0;10.0;10.0;30.0;10.0 - Text 190 - 230 + 210 110 50 @@ -193,33 +159,22 @@ Field ID - - Relation - - 400 - 130 - 70 - 60 - - lt=<<- - 50.0;40.0;50.0;10.0;10.0;10.0 - Relation 130 - 80 - 490 + 210 + 500 90 lt=. - 10.0;60.0;10.0;20.0;220.0;20.0;220.0;10.0;220.0;20.0;470.0;20.0;470.0;70.0 + 10.0;10.0;10.0;70.0;480.0;70.0;480.0;10.0 Text - 290 - 40 + 300 + 280 170 50 @@ -230,24 +185,56 @@ style=wordwrap Text - 340 + 290 230 - 110 - 50 + 150 + 30 - MemoData -Field ID + MemoData Field ID Relation - 390 + 350 190 - 30 + 70 60 lt=<<- - 10.0;10.0;10.0;40.0 + 50.0;10.0;50.0;30.0;10.0;30.0;10.0;40.0 + + + UMLPackage + + 80 + 140 + 600 + 70 + + Memos field + + + + Relation + + 430 + 190 + 50 + 50 + + lt=<<- + 10.0;10.0;10.0;30.0;30.0;30.0 + + + Text + + 460 + 210 + 140 + 40 + + Length prefix + diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index d029e4cfff..1b832200f9 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -1,5 +1,5 @@ # Serialization Format -[[Source]
](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L697-L718 "Source") +[[Source]
](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L696-L718 "Source") This page describes the XRP Ledger's canonical binary format for transactions and other data. This binary format is necessary to create and verify digital signatures of those transactions' contents, and is also used in other places. The [rippled APIs](rippled-apis.html) typically use JSON to communicate with client applications. However, JSON is unsuitable as a format for serializing transactions for being digitally signed, because JSON can represent the same data in many different but equivalent ways. @@ -18,6 +18,17 @@ The result is a single binary blob that can be signed using well-known signature **Note:** The XRP Ledger uses the same serialization format to represent other types of data, such as [ledger objects](ledger-object-types.html) and processed transactions. However, only certain fields are appropriate for including in a transaction that gets signed. (For example, the `TxnSignature` field, containing the signature itself, should not be present in the binary blob that you sign.) Thus, some fields are designated as "Signing" fields, which are included in objects when those objects are signed, and "non-signing" fields, which are not. +## Sample Code + +The serialization processes described here are implemented in multiple places and programming languages: + +- In C++ [in the `rippled` code base](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp). +- In JavaScript in the [`ripple-binary-codec`](https://github.com/ripple/ripple-binary-codec/) package. +- In Python 3 [this repository's code samples section]({{target.github_forkurl}}/blob/{{target.github_branch}}/content/_code-samples/tx-serialization/serialize.py). + +These implementations are all provided with permissive open-source licenses, so you can import, use, or adapt the code for your needs in addition to using it alongside these documents for learning purposes. + + ## Internal Format @@ -165,7 +176,7 @@ AccountIDs that appear as stand-alone fields (such as `Account` and `Destination The "Amount" type is a special field type that represents an amount of currency, either XRP or an issued currency. This type consists of two sub-types: - **XRP** - XRP is serialized as a 64-bit unsigned integer (big-endian order), except that the second-most-significant bit is `1` to indicate that it is positive. In other words, take a standard UInt64 and calculate the bitwise-OR of that with `0x4000000000000000` to get the serialized format. + XRP is serialized as a 64-bit unsigned integer (big-endian order), except that the most significant bit is always 0 to indicate that it's XRP, and the second-most-significant bit is `1` to indicate that it is positive. Since the maximum amount of XRP (1017 drops) only requires 57 bits, you can calculate XRP serialized format by taking standard 64-bit unsigned integer and performing a bitwise-OR with `0x4000000000000000`. - **Issued Currencies** Issued currencies consist of three segments in order: 1. 64 bits indicating the amount in the [internal currency format](currency-formats.html#issued-currency-math). The first bit is `1` to indicate that this is not XRP. @@ -174,6 +185,10 @@ The "Amount" type is a special field type that represents an amount of currency, You can tell which of the two sub-types it is based on the first bit: `0` for XRP; `1` for issued currency. +The following diagram shows the serialization formats for both XRP amounts and issued currency amounts: + +![XRP amounts have a "not XRP" bit, a sign bit, and 62 bits of precision. Issued currency amounts consist of a "not XRP" bit, a sign bit, an exponent (8 bits), mantissa (54 bits), currency code (160 bits), and issuer (160 bits).](img/serialization-amount.png) + ### Array Fields [STArray]: #array-fields diff --git a/img/serialization-amount.png b/img/serialization-amount.png new file mode 100644 index 0000000000000000000000000000000000000000..6f0bd314f3c7c6e4b5b2c7052812139c2c333571 GIT binary patch literal 34377 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

mXcv9S+S=)X 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%0Dd1em;M>(ccJ%y&DysSr-?yvo&_+~Vg3Fc zX||>chcGVrp5i|0{Za?BX2wb{uDi-c|JL)gQ7V-uHl8!dw<)*$ov;!#vn?^&WU|{Y z2l(+Xr+bBm~_w;v-Lwqd;~~0uAcW zeC(#;19w%o$ix|=$t1kzu)k^dpHEHtI!}7>K_Ph*%)I20J4v5lK&&1{-n(~lVq)#i z0KBW{3!gNb07AnO5(=ee;$Kv#{{o&)jz=J~S94$m%upkWz*Zf1*$kfKLU zQ~>c!Fb6~$9)sBHh&`CDK_A-gqt^`TLx+MIPb)nETV{BvhCH@-Zvh_e&d!9 z^s>grj*?;B7Upmo@14R}qf!`s9LHSGf{wH=aCrOYHU?9m@6*8rkmJkJpT7MID=QyC z9>Z3z+<49~oEqQ?A`E(&M1S~CCxNyCxft;~T1ZCTfBjmC-gf?RoQHGSD}njrPhei0 z1r-dbRtEOQGPN;+cff0S5pmkx_aE>9P{_~#q3@8DZNXDO>NJ1_;yraq&8R2$@I{eU zq*~K8qEHo2ooH4|vl#$01_N3K1_m%6$+_a>u;G^0nkhI10m@*!e~)5q`-F=BsN

DS8p}*Q)4#&p9`&S z4}FP%uq+y7!S3LM2RN|N!j9nMDFQlFjKN_hgtrxr?kT|bioY-YSRd}FGQyu2uqaI= z3aX~q_`#8{PUm={Y|bz{Q2j%y(W3)u&K?OiOY0Y4-|u_VKG0d2oUHAv2E$~_4(h#o zv#$?dKLdv?syfr&_~6SiY*?0tUcD?4M*{u)ZtWM#7D19^s^_-LvI}?>h7t(qkxfrE z@PW+EGhl6P4dehOHHlv3wirWx+16BBQBPmL1)xqr9URB30}Uf@aMF3w8m%1)8tFYf z7%N=I5?{pcamgax*E}A(0NEJ2<^-)*jq{1!o~=C>oGxceP`9P{7H zEwvb-ju=lBr~=IwZ0FD)K;eD3_5L2#!hduyQtjxa>(|W=b{9llaRl&GNEM~OU>^i1 zqZcQCb7x!>(c!3P4n;C&#?mx0(j)~!i4xzu2>EJFldOcn{^R{%HZad)5XP>Db!qGeCY1t3SlXM0T;c9x?5s`c^%6DdKlL`ou=r5r|h3%FOL&V&60^H z?%Zsu@OpWn{s;z4<<(fAZegm+g2H292(w)F3Ns>%w9lQ&>EUxN2ck(;IGe z!(lpdqKGMKa|0&082wsAo@>pHmd?@7&3wT?oVg-i!n2=)jiSBVnCCnO;@-r7 zdCFbf)GP@sj+m5gZ1}Dtv*X28lnI2ut>Tida+^2r+gNgp81TdV`7n?U!ke*TAoe>{K!pR{_*|$O)}_09GQUb#WO?6d;rO-t0+%7MkIN!Av>kqiI0cW zURP>e!&dtG_uK~U9nu|}|FJxtZ2ssNzk<7vvnYp&qwk_%40~e)OIcpsK+FdS-(6u- z>}8D%ka_S4LFTYfvp$NPc%cp=;`HLFv#yPd06+uZK(q5~}G=OYf2X3da7T8gA3KGa5tKACLv);+InVX&y2QcAmxdw##)e-+?KvkU_0_S zA*0oV!yw1i4UU!#C#7vGvmBJ0&k%S*=I#{n&RMt;zUb&MVcAJJm`=I|@LnGia6xL? z{X^<~np$kiMm^xsTvXAjQ{QY%n)!5t0&3$>GF1HsToAlN9fHiOmlVl7t*!KG5MwaJTYy(LsnV}890e#Tu>y7-#lCe0 zgza_BE1N*Q`P!ML*#+&-IM!|B$B{_Of65}gg44^iD+?L21DHjym|~={Q@7Is=0((u z)_ww2QS>t5nAv$dGF(<7^UK$-I64w!z~!S8z-dqQ7x{`02O21Uc^r(;Q2+y3(qw>K zXe$;Jqa12E(5~w#bSJgAaD!WEcLxJ=#$G?#NKi#2h^$xYLLMqIwCUtkRm;&V{Gv=8 zIgG?Afp+xym`aDK{iS$-;F<%UM0)N(RM5}+{(gsOGb^R`zkG;tkvp;0j~x&4M?Kqg zwHR9fxQb4{4u!Iqld~;oSH{DtTZ ziSQg`if0urxdmU93~4*Vh3rl~f2%qFZBkO8Qu;ECe`B>ea{ib0k@6kdD}x9NSR6o| zmzA-fF-H%P5Uq=0%uj{p#M0)mqq`PevrzZb*w`4b=vN3gMn*;^J_1?$QtasoS3K~f zuO{v*1jGx*N9@|Sgusw>d!H}YqDA_}8MB}SnIwj8xJ)JZ^xhD>CtsYoKKpxF$aQ1o zT2p>B7ynjZS^eF0O`DQVuR4q;T$#@M2{MWaUi%WPOA;2x9D36Q^Mu=F4Jkk zM3FdmHyUPD=Rjxe`2z K>#)L#z!;6V#B|(ifcJRcYw+_t3ik78YYS5=>pAH7n#0 zVD8@K#B#sC4ca96Kr0FRg1ahcIwU2%2PG+ej%pFU+`d5lC){ODowCh!U^kf?6-%UM z1PgX9sRz7?KLYI`b`H?TKe^l7OTYvEKVBm6%6~JyIdU!WTYuhm?xnf6{qu>3e$J9+ YM6Y#88=4!6>=hsul{lWL% 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 literal 7898 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 diff --git a/img/serialization-object.png b/img/serialization-object.png index faae809cc9aee45451c031b13ce5a127c6ff9d29..d62f99922d8b888f232c735b2eaa3ab04baff1d6 100644 GIT binary patch literal 11960 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 z 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 Vi#3dwufZfqWF!>s<%;P)|38?N3C{ok literal 11967 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≧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 From 8342ddc16a37e626b5e4d2bb6f61b41617800763 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 3 Dec 2018 18:12:59 -0800 Subject: [PATCH 13/19] Serialization: clean up sample code, add CLI usage --- .../tx-serialization/serialize.py | 297 ++++++++++++------ .../tx-serialization/test-cases/README.md | 52 +++ .../test-cases/meld-example.png | Bin 0 -> 9584 bytes .../tx-serialization/test-cases/tx1-full.json | 125 -------- .../test-cases/{tx1-nometa.json => tx1.json} | 0 .../tx-serialization/test-cases/tx2-full.json | 94 ------ .../test-cases/{tx2-nometa.json => tx2.json} | 0 .../test-cases/{tx3-nometa.json => tx3.json} | 0 8 files changed, 254 insertions(+), 314 deletions(-) create mode 100644 content/_code-samples/tx-serialization/test-cases/README.md create mode 100644 content/_code-samples/tx-serialization/test-cases/meld-example.png delete mode 100644 content/_code-samples/tx-serialization/test-cases/tx1-full.json rename content/_code-samples/tx-serialization/test-cases/{tx1-nometa.json => tx1.json} (100%) delete mode 100644 content/_code-samples/tx-serialization/test-cases/tx2-full.json rename content/_code-samples/tx-serialization/test-cases/{tx2-nometa.json => tx2.json} (100%) rename content/_code-samples/tx-serialization/test-cases/{tx3-nometa.json => tx3.json} (100%) diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py index e72e92c63f..53d343ab20 100755 --- a/content/_code-samples/tx-serialization/serialize.py +++ b/content/_code-samples/tx-serialization/serialize.py @@ -4,9 +4,11 @@ # Author: rome@ripple.com # Copyright Ripple 2018 +import argparse import json import logging import re +import sys from address import decode_address from xrpl_num import IssuedAmount @@ -15,6 +17,12 @@ logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) def load_defs(fname="definitions.json"): + """ + Loads JSON from the definitions file and converts it to a preferred format. + + (The definitions file should be drop-in compatible with the one from the + ripple-binary-codec JavaScript package.) + """ with open(fname) as definitions_file: definitions = json.load(definitions_file) return { @@ -53,33 +61,71 @@ def field_id(field_name): # high 4 bits is the type_code # low 4 bits is the field code combined_code = (type_code << 4) | field_code - return bytes_from_uint(combined_code, 8) + return uint8_to_bytes(combined_code) elif type_code >= 16 and field_code < 16: # first 4 bits are zeroes # next 4 bits is field code # next byte is type code - byte1 = bytes_from_uint(field_code, 8) - byte2 = bytes_from_uint(type_code, 8) + byte1 = uint8_to_bytes(field_code) + byte2 = uint8_to_bytes(type_code) return b''.join( (byte1, byte2) ) elif type_code < 16 and field_code >= 16: # first 4 bits is type code # next 4 bits are zeroes # next byte is field code - byte1 = bytes_from_uint(type_code << 4, 8) - byte2 = bytes_from_uint(field_code, 8) + byte1 = uint8_to_bytes(type_code << 4) + byte2 = uint8_to_bytes(field_code) return b''.join( (byte1, byte2) ) else: # both are >= 16 # first byte is all zeroes # second byte is type # third byte is field code - byte2 = bytes_from_uint(type_code, 8) - byte3 = bytes_from_uint(field_code, 8) + byte2 = uint8_to_bytes(type_code) + byte3 = uint8_to_bytes(field_code) return b''.join( (bytes(1), byte2, byte3) ) -def bytes_from_uint(i, bits): - if bits % 8: - raise ValueError("bytes_from_uint: bits must be divisible by 8") - return i.to_bytes(bits // 8, byteorder="big", signed=False) +def vl_encode(vl_contents): + """ + Helper function for variable-length fields including Blob types + and some AccountID types. + + Encodes arbitrary binary data with a length prefix. The length of the prefix + is 1-3 bytes depending on the length of the contents: + + Content length <= 192 bytes: prefix is 1 byte + 192 bytes < Content length <= 12480 bytes: prefix is 2 bytes + 12480 bytes < Content length <= 918744 bytes: prefix is 3 bytes + """ + + vl_len = len(vl_contents) + if vl_len <= 192: + len_byte = vl_len.to_bytes(1, byteorder="big", signed=False) + return b''.join( (len_byte, vl_contents) ) + elif vl_len <= 12480: + vl_len -= 193 + byte1 = ((vl_len >> 8) + 193).to_bytes(1, byteorder="big", signed=False) + byte2 = (vl_len & 0xff).to_bytes(1, byteorder="big", signed=False) + return b''.join( (byte1, byte2, vl_contents) ) + elif vl_len <= 918744: + vl_len -= 12481 + byte1 = (241 + (vl_len >> 16)).to_bytes(1, byteorder="big", signed=False) + byte2 = ((vl_len >> 8) & 0xff).to_bytes(1, byteorder="big", signed=False) + byte3 = (vl_len & 0xff).to_bytes(1, byteorder="big", signed=False) + return b''.join( (byte1, byte2, byte3, vl_contents) ) + + raise ValueError("VariableLength field must be <= 918744 bytes long") + + +# Individual field type serialization routines --------------------------------- + +def accountid_to_bytes(address): + """ + Serialize an AccountID field type. These are variable-length encoded. + + Some fields contain nested non-VL-encoded AccountIDs directly; those call + decode_address() instead of this function. + """ + return vl_encode(decode_address(address)) def amount_to_bytes(a): """ @@ -109,13 +155,38 @@ def amount_to_bytes(a): else: raise ValueError("amount must be XRP string or {currency, value, issuer}") -def issued_amount_as_bytes(strnum): - num = Decimal(strnum) +def array_to_bytes(array): + """ + Serialize an array of objects from decoded JSON. + Each member object must have a type wrapper and an inner object. + For example: + [ + { + // wrapper object + "Memo": { + // inner object + "MemoType": "687474703a2f2f6578616d706c652e636f6d2f6d656d6f2f67656e65726963", + "MemoData": "72656e74" + } + } + ] + """ + members_as_bytes = [] + for el in array: + wrapper_key = list(el.keys())[0] + inner_obj = el[wrapper_key] + members_as_bytes.append(field_to_bytes(field_name=wrapper_key, field_val=el)) + members_as_bytes.append(field_id("ArrayEndMarker")) + return b''.join(members_as_bytes) - -def tx_type_to_bytes(txtype): - type_uint = DEFINITIONS["TRANSACTION_TYPES"][txtype] - return type_uint.to_bytes(2, byteorder="big", signed=False) +def blob_to_bytes(field_val): + """ + Serializes a string of hex as binary data with a variable-length encoded + length prefix. (The prefix is 1-3 bytes depending on the length of the + contents.) + """ + vl_contents = bytes.fromhex(field_val) + return vl_encode(vl_contents) def currency_code_to_bytes(code_string, xrp_ok=False): if re.match(r"^[A-Za-z0-9?!@#$%^&*<>(){}\[\]|]{3}$", code_string): @@ -143,54 +214,43 @@ def currency_code_to_bytes(code_string, xrp_ok=False): else: raise ValueError("invalid currency code") -def vl_encode(vl_contents): - vl_len = len(vl_contents) - if vl_len <= 192: - len_byte = vl_len.to_bytes(1, byteorder="big", signed=False) - return b''.join( (len_byte, vl_contents) ) - elif vl_len <= 12480: - vl_len -= 193 - byte1 = ((vl_len >> 8) + 193).to_bytes(1, byteorder="big", signed=False) - byte2 = (vl_len & 0xff).to_bytes(1, byteorder="big", signed=False) - return b''.join( (byte1, byte2, vl_contents) ) - elif vl_len <= 918744: - vl_len -= 12481 - byte1 = (241 + (vl_len >> 16)).to_bytes(1, byteorder="big", signed=False) - byte2 = ((vl_len >> 8) & 0xff).to_bytes(1, byteorder="big", signed=False) - byte3 = (vl_len & 0xff).to_bytes(1, byteorder="big", signed=False) - return b''.join( (byte1, byte2, byte3, vl_contents) ) +def hash128_to_bytes(contents): + """ + Serializes a hexadecimal string as binary and confirms that it's 128 bits + """ + b = hash_to_bytes(contents) + if len(b) != 16: # 16 bytes = 128 bits + raise ValueError("Hash128 is not 128 bits long") + return b - raise ValueError("VariableLength field must be <= 918744 bytes long") +def hash160_to_bytes(contents): + b = hash_to_bytes(contents) + if len(b) != 20: # 20 bytes = 160 bits + raise ValueError("Hash160 is not 160 bits long") + return b -def vl_to_bytes(field_val): - vl_contents = bytes.fromhex(field_val) - return vl_encode(vl_contents) +def hash256_to_bytes(contents): + b = hash_to_bytes(contents) + if len(b) != 32: # 32 bytes = 256 bits + raise ValueError("Hash256 is not 256 bits long") + return b def hash_to_bytes(contents): + """ + Helper function; serializes a hash value from a hexadecimal string + of any length. + """ return bytes.fromhex(field_val) -def accountid_to_bytes(address): - return vl_encode(decode_address(address)) - -def array_to_bytes(array): - """ - Serialize an array of objects. - Each member object must have a type wrapper and an inner object. - """ - members_as_bytes = [] - for el in array: - wrapper_key = list(el.keys())[0] - inner_obj = el[wrapper_key] - members_as_bytes.append(field_to_bytes(field_name=wrapper_key, field_val=el)) - members_as_bytes.append(field_id("ArrayEndMarker")) - return b''.join(members_as_bytes) - def object_to_bytes(obj): """ - Serialize an object, assuming a type wrapper, for example: + Serialize an object from decoded JSON. + Each object must have a type wrapper and an inner object. For example: { + // type wrapper "SignerEntry": { + // inner object "Account": "rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v", "SignerWeight": 1 } @@ -260,11 +320,31 @@ def path_as_bytes(path): if "issuer" in step.keys(): type_byte |= 0x20 step_data.append(decode_address(step["issuer"])) - step_data = [bytes_from_uint(type_byte, 8)] + step_data + step_data = [uint8_to_bytes(type_byte)] + step_data path_contents += step_data return b''.join(path_contents) +def tx_type_to_bytes(txtype): + """ + TransactionType field is a special case that is written in JSON + as a string name but in binary as a UInt16. + """ + type_uint = DEFINITIONS["TRANSACTION_TYPES"][txtype] + return uint16_to_bytes(type_uint) + + +def uint8_to_bytes(i): + return i.to_bytes(1, byteorder="big", signed=False) + +def uint16_to_bytes(i): + return i.to_bytes(2, byteorder="big", signed=False) + +def uint32_to_bytes(i): + return i.to_bytes(4, byteorder="big", signed=False) + + +# Core serialization logic ----------------------------------------------------- def field_to_bytes(field_name, field_val): """ @@ -285,22 +365,51 @@ def field_to_bytes(field_name, field_val): # TypeName: function(field): bytes object "AccountID": accountid_to_bytes, "Amount": amount_to_bytes, - "Blob": vl_to_bytes, - "Hash128": hash_to_bytes, - "Hash160": hash_to_bytes, - "Hash256": hash_to_bytes, + "Blob": blob_to_bytes, + "Hash128": hash128_to_bytes, + "Hash160": hash160_to_bytes, + "Hash256": hash256_to_bytes, "PathSet": pathset_to_bytes, "STArray": array_to_bytes, "STObject": object_to_bytes, - "UInt8" : lambda x:bytes_from_uint(x, 8), - "UInt16": lambda x:bytes_from_uint(x, 16), - "UInt32": lambda x:bytes_from_uint(x, 32), - "UInt64": lambda x:bytes_from_uint(x, 64), + "UInt8" : uint8_to_bytes, + "UInt16": uint16_to_bytes, + "UInt32": uint32_to_bytes, } field_binary = dispatch[field_type](field_val) return b''.join( (id_prefix, field_binary) ) -def serialize_tx(tx): +def serialize_tx(tx, for_signing=False): + """ + Takes a transaction as decoded JSON and returns a bytes object representing + the transaction in binary format. + + The input format should omit transaction metadata and the transaction + should be formatted with the transaction instructions at the top level. + ("hash" can be included, but will be ignored) + + If for_signing=True, then only signing fields are serialized, so you can use + the output to sign the transaction. + + SigningPubKey and TxnSignature are optional, but the transaction can't + be submitted without them. + + For example: + + { + "TransactionType" : "Payment", + "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Destination" : "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "Amount" : { + "currency" : "USD", + "value" : "1", + "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" + }, + "Fee": "12", + "Flags": 2147483648, + "Sequence": 2 + } + """ field_order = sorted(tx.keys(), key=field_sort_key) logger.debug("Canonical field order: %s" % field_order) @@ -313,41 +422,39 @@ def serialize_tx(tx): fields_as_bytes.append(field_bytes) all_serial = b''.join(fields_as_bytes) - logger.info(all_serial.hex().upper()) + logger.debug(all_serial.hex().upper()) return all_serial +# Startup stuff ---------------------------------------------------------------- +logger.setLevel(logging.WARNING) +DEFINITIONS = load_defs() - -################################################################################ - +# Commandline utility ---------------------------------------------------------- +# parses JSON from a file or commandline argument and prints the serialized +# form of the transaction as hex if __name__ == "__main__": - logger.setLevel(logging.DEBUG) - DEFINITIONS = load_defs() + p = argparse.ArgumentParser() + txsource = p.add_mutually_exclusive_group() + txsource.add_argument("-f", "--filename", default="test-cases/tx1.json", + help="Read input transaction from a JSON file. (Uses test-cases/tx1.json by default)") + txsource.add_argument("-j", "--json", + help="Read input transaction JSON from the command line") + txsource.add_argument("--stdin", action="store_true", default=False, + help="Read input transaction JSON from standard input (stdin)") + p.add_argument("-v", "--verbose", action="store_true", default=False, + help="Display debug messages (such as individual field serializations)") + args = p.parse_args() - # example_tx = { - # "TransactionType" : "Payment", - # "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", - # "Destination" : "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", - # "Amount" : { - # "currency" : "USD", - # "value" : "1", - # "issuer" : "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" - # }, - # "Fee": "12", - # "Flags": 2147483648, - # "Sequence": 2 - # } + if args.verbose: + logger.setLevel(logging.DEBUG) - with open("test-cases/tx3-nometa.json") as f: - example_tx = json.load(f) + # Determine source of JSON transaction: + if args.json: + example_tx = json.loads(args.json) + elif args.stdin: + example_tx = json.load(sys.stdin) + else: + with open(args.filename) as f: + example_tx = json.load(f) - serialize_tx(example_tx) - - # example rippled signature: - # ./rippled sign masterpassphrase (the above JSON) - # where "masterpassphrase" is the key behind rHb9... - # snoPBrXtMeMyMHUVTgbuqAfg1SUTb in base58 - # "tx_blob" : "1200002280000000240000000261D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA968400000000000000C73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD0207446304402201FE0A74FC1BDB509C8F42B861EF747C43B92917706BB623F0A0D621891933AF402205206FBA8B0BF6733DB5B03AD76B5A76A2D46DF9093916A3BEC78897E58A3DF148114B5F762798A53D543A014CAF8B297CFF8F2F937E883143E9D4A2B8AA0780F682D136F7A56D6724EF53754", - # "SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", - # "TxnSignature" : "304402201FE0A74FC1BDB509C8F42B861EF747C43B92917706BB623F0A0D621891933AF402205206FBA8B0BF6733DB5B03AD76B5A76A2D46DF9093916A3BEC78897E58A3DF14", - # "hash" : "8BA1509E4FB80CCF76CD9DE924B8B71597637C775BA2DC515F90C333DA534BF3" + print(serialize_tx(example_tx).hex().upper()) diff --git a/content/_code-samples/tx-serialization/test-cases/README.md b/content/_code-samples/tx-serialization/test-cases/README.md new file mode 100644 index 0000000000..0cfa290ed1 --- /dev/null +++ b/content/_code-samples/tx-serialization/test-cases/README.md @@ -0,0 +1,52 @@ +# Transaction Serialization Test Cases + +This folder contains several transactions in their JSON and binary forms, which +you can use to verify the behavior of transaction serialization code. + +For example (starting from the `tx-serialization/` dir above this one): + +```bash +$ python3 serialize.py -f test-cases/tx2.json | \ + diff - test-cases/tx2-binary.txt +``` + +The expected result is no output because the output of `serialize.py` matches +the contents of `test-cases/tx2-binary.txt` exactly. + +For an example of how the output is different if you change the `Fee` parameter of sample transaction 1, we can pipe a modified version of the file into the serializer: + +```bash +$ cat test-cases/tx1.json | \ + sed -e 's/"Fee": "10"/"Fee": "100"/' | \ + python3 serialize.py --stdin | \ + diff - test-cases/tx1-binary.txt --color +``` + +The output shows that the two versions of the transaction binary are different (but because they're all on one line, it's not super clear _where_ within the line the difference is): + +```text +1c1 +< 120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC93914000000000000000 +00000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E +11D600684000000000000064732103EE83BB432547885C219634A1BC407A9DB0474145D69737D09C +CDC63E1DEE7FE3744630440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED +282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C81 +14DD76483FACDEE26E60D8A586BB58D09F27045C46 +--- +> 120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC93914000000000000000 +00000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E +11D60068400000000000000A732103EE83BB432547885C219634A1BC407A9DB0474145D69737D09C +CDC63E1DEE7FE3744630440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED +282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C81 +14DD76483FACDEE26E60D8A586BB58D09F27045C46 +``` + +(If you're curious, the difference appears in the third line of each blob in this example. The modified version of the transaction serializes the `Fee` amount ending in `64` (hex for 100) while the original version ended in `0A` (hex for 10).) + +For a friendlier display, you could pipe the output of the serializer to a file and use a visual tool like [Meld](http://meldmerge.org/) that shows intra-line differences: + +```bash +$ cat test-cases/tx1.json | sed -e 's/"Fee": "10"/"Fee": "100"/' | python3 serialize.py --stdin > /tmp/tx1-modified.txt && meld /tmp/tx1-modified.txt test-cases/tx1-binary.txt +``` + +![Meld screenshot showing the `0A` / `64` difference](meld-example.png) diff --git a/content/_code-samples/tx-serialization/test-cases/meld-example.png b/content/_code-samples/tx-serialization/test-cases/meld-example.png new file mode 100644 index 0000000000000000000000000000000000000000..3c7e4f505901b8ed8acfd84e1b93a02f818c647f GIT binary patch literal 9584 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 literal 0 HcmV?d00001 diff --git a/content/_code-samples/tx-serialization/test-cases/tx1-full.json b/content/_code-samples/tx-serialization/test-cases/tx1-full.json deleted file mode 100644 index fc02fcfae3..0000000000 --- a/content/_code-samples/tx-serialization/test-cases/tx1-full.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "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", - "metaData": { - "AffectedNodes": [ - { - "ModifiedNode": { - "FinalFields": { - "Flags": 0, - "IndexNext": "0000000000000000", - "IndexPrevious": "0000000000000000", - "Owner": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", - "RootIndex": "10AF5737F535F47CA9E8B6F82C4B7F4D998B1B7C44185C6078A22C751FD9FB7D" - }, - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "10AF5737F535F47CA9E8B6F82C4B7F4D998B1B7C44185C6078A22C751FD9FB7D" - } - }, - { - "DeletedNode": { - "FinalFields": { - "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", - "BookDirectory": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10BFD011CB2800", - "BookNode": "0000000000000000", - "Expiration": 595640096, - "Flags": 131072, - "OwnerNode": "0000000000000000", - "PreviousTxnID": "50C2CBD3BEF831D80C2950D3001E67F1D257665569A9D77B1F0E0B8B4D178CEB", - "PreviousTxnLgrSeq": 43010795, - "Sequence": 1752791, - "TakerGets": "15000000000", - "TakerPays": { - "currency": "USD", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", - "value": "7071.75" - } - }, - "LedgerEntryType": "Offer", - "LedgerIndex": "233E9A034C083E895EF7B4F6A643291FEF1608D55C8C5783F71E9C5F82D3E7FB" - } - }, - { - "ModifiedNode": { - "FinalFields": { - "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", - "Balance": "37180946255", - "Flags": 0, - "OwnerCount": 6, - "Sequence": 1752793 - }, - "LedgerEntryType": "AccountRoot", - "LedgerIndex": "9EB65374048F2AED1995A6725D4234545432083B0C5728627E06443A8E1F4C98", - "PreviousFields": { - "Balance": "37180946265", - "Sequence": 1752792 - }, - "PreviousTxnID": "50C2CBD3BEF831D80C2950D3001E67F1D257665569A9D77B1F0E0B8B4D178CEB", - "PreviousTxnLgrSeq": 43010795 - } - }, - { - "DeletedNode": { - "FinalFields": { - "ExchangeRate": "4E10BFD011CB2800", - "Flags": 0, - "RootIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10BFD011CB2800", - "TakerGetsCurrency": "0000000000000000000000000000000000000000", - "TakerGetsIssuer": "0000000000000000000000000000000000000000", - "TakerPaysCurrency": "0000000000000000000000005553440000000000", - "TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1" - }, - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10BFD011CB2800" - } - }, - { - "CreatedNode": { - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10C0730D0B8000", - "NewFields": { - "ExchangeRate": "4E10C0730D0B8000", - "RootIndex": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10C0730D0B8000", - "TakerPaysCurrency": "0000000000000000000000005553440000000000", - "TakerPaysIssuer": "0A20B3C85F482532A9578DBB3950B85CA06594D1" - } - } - }, - { - "CreatedNode": { - "LedgerEntryType": "Offer", - "LedgerIndex": "FD6C2E2D72319FB0C22FC50B0AF993B2AF26717927EAEB1E8857971EE2C3CADD", - "NewFields": { - "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", - "BookDirectory": "DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4E10C0730D0B8000", - "Expiration": 595640108, - "Flags": 131072, - "Sequence": 1752792, - "TakerGets": "15000000000", - "TakerPays": { - "currency": "USD", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", - "value": "7072.8" - } - } - } - } - ], - "TransactionIndex": 0, - "TransactionResult": "tesSUCCESS" - } -} diff --git a/content/_code-samples/tx-serialization/test-cases/tx1-nometa.json b/content/_code-samples/tx-serialization/test-cases/tx1.json similarity index 100% rename from content/_code-samples/tx-serialization/test-cases/tx1-nometa.json rename to content/_code-samples/tx-serialization/test-cases/tx1.json diff --git a/content/_code-samples/tx-serialization/test-cases/tx2-full.json b/content/_code-samples/tx-serialization/test-cases/tx2-full.json deleted file mode 100644 index 492396a070..0000000000 --- a/content/_code-samples/tx-serialization/test-cases/tx2-full.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "hash": "C0B450C8601E19CB0BDED71C4B523B2A4AAB77415B08E7923D8DA3F831631702", - "ledger_index": 36259236, - "date": "2018-02-01T09:45:32+00:00", - "tx": { - "TransactionType": "EscrowFinish", - "Flags": 2147483648, - "Sequence": 1, - "OfferSequence": 11, - "Fee": "10101", - "SigningPubKey": "0268D79CD579D077750740FA18A2370B7C2018B2714ECE70BA65C38D223E79BC9C", - "TxnSignature": "3045022100F06FB54049D6D50142E5CF2E2AC21946AF305A13E2A2D4BA881B36484DD01A540220311557EC8BEF536D729605A4CB4D4DC51B1E37C06C93434DD5B7651E1E2E28BF", - "Account": "r3Y6vCE8XqfZmYBRngy22uFYkmz3y9eCRA", - "Owner": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3", - "Memos": [ - { - "Memo": { - "MemoData": "04C4D46544659A2D58525043686174" - } - } - ] - }, - "meta": { - "TransactionIndex": 35, - "AffectedNodes": [ - { - "DeletedNode": { - "LedgerEntryType": "Escrow", - "LedgerIndex": "983EBDF89C1C30CECDB105B593E7DEBE602AF89012EDB4DD76D24ACEF92C89EF", - "FinalFields": { - "Flags": 0, - "PreviousTxnLgrSeq": 35059511, - "FinishAfter": 570758400, - "OwnerNode": "0000000000000000", - "PreviousTxnID": "6F54E04D7B205CBE2FCAEF1C249E62A9759721C7FE1F6992FD800266C8E4814C", - "Amount": "1000000000000000", - "Account": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3", - "Destination": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3" - } - } - }, - { - "ModifiedNode": { - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "C05DC35EBDCA5D8697190FF41950EC5AFCBF7F61C1177DC73000CC17C2149886", - "FinalFields": { - "Flags": 0, - "RootIndex": "C05DC35EBDCA5D8697190FF41950EC5AFCBF7F61C1177DC73000CC17C2149886", - "Owner": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3" - } - } - }, - { - "ModifiedNode": { - "LedgerEntryType": "AccountRoot", - "PreviousTxnLgrSeq": 36230933, - "PreviousTxnID": "9366F75CD350ACE0EEFC0A392ECBD5AC2B84C06E5DEC2DE895B76FFC7BD55553", - "LedgerIndex": "C180CA555CE8820D8F1086CDCA756FF8D5813FEB2AE3FF002B9F48F870CA08A0", - "PreviousFields": { - "Sequence": 1, - "Balance": "26000000" - }, - "FinalFields": { - "Flags": 0, - "Sequence": 2, - "OwnerCount": 0, - "Balance": "25989899", - "Account": "r3Y6vCE8XqfZmYBRngy22uFYkmz3y9eCRA" - } - } - }, - { - "ModifiedNode": { - "LedgerEntryType": "AccountRoot", - "PreviousTxnLgrSeq": 36253527, - "PreviousTxnID": "F2AA5584005847C19F59A9C87E7BF3108F97F2567C5083159EEC40B08ED90F46", - "LedgerIndex": "DCED5AAFE87AA8D00E651DBBFBA2F992927BC3DC5FFD905EF49019ED02824B3A", - "PreviousFields": { - "OwnerCount": 14, - "Balance": "200923000" - }, - "FinalFields": { - "Flags": 1048576, - "Sequence": 18, - "OwnerCount": 13, - "Balance": "1000000200923000", - "Account": "r9NpyVfLfUG8hatuCCHKzosyDtKnBdsEN3" - } - } - } - ], - "TransactionResult": "tesSUCCESS" - } -} diff --git a/content/_code-samples/tx-serialization/test-cases/tx2-nometa.json b/content/_code-samples/tx-serialization/test-cases/tx2.json similarity index 100% rename from content/_code-samples/tx-serialization/test-cases/tx2-nometa.json rename to content/_code-samples/tx-serialization/test-cases/tx2.json diff --git a/content/_code-samples/tx-serialization/test-cases/tx3-nometa.json b/content/_code-samples/tx-serialization/test-cases/tx3.json similarity index 100% rename from content/_code-samples/tx-serialization/test-cases/tx3-nometa.json rename to content/_code-samples/tx-serialization/test-cases/tx3.json From 61c401658690d9f666d404658890a2a71b16596f Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 3 Dec 2018 18:28:03 -0800 Subject: [PATCH 14/19] Fix serialization broken links --- .../references/rippled-api/api-conventions/serialization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index 1b832200f9..3ba2a4b057 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -1,7 +1,7 @@ # Serialization Format [[Source]
](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp#L696-L718 "Source") -This page describes the XRP Ledger's canonical binary format for transactions and other data. This binary format is necessary to create and verify digital signatures of those transactions' contents, and is also used in other places. The [rippled APIs](rippled-apis.html) typically use JSON to communicate with client applications. However, JSON is unsuitable as a format for serializing transactions for being digitally signed, because JSON can represent the same data in many different but equivalent ways. +This page describes the XRP Ledger's canonical binary format for transactions and other data. This binary format is necessary to create and verify digital signatures of those transactions' contents, and is also used in other places. The [rippled APIs](rippled-api.html) typically use JSON to communicate with client applications. However, JSON is unsuitable as a format for serializing transactions for being digitally signed, because JSON can represent the same data in many different but equivalent ways. The process of serializing a transaction from JSON or any other representation into their canonical binary format can be summarized with these steps: @@ -106,7 +106,7 @@ The field code is combined with the type code to make a field's [Field ID](#fiel ### Variable-Length Encoding -Some types of fields are Variable-Length encoding, which means they are not always the same byte length and are prefixed with a length indicator to indicate how much data they contain. `Blob` fields (containing arbitrary binary data) are one such variable-length encoded type. For a list of which types are variable-length encoded, see the [Internal Formats](#internal-formats) table. +Some types of fields are Variable-Length encoding, which means they are not always the same byte length and are prefixed with a length indicator to indicate how much data they contain. `Blob` fields (containing arbitrary binary data) are one such variable-length encoded type. For a list of which types are variable-length encoded, see the [Type List](#type-list) table. **Note:** Some types that are not variable-length encoded nonetheless vary in length. These types have different ways of indicating how long they are. From 4e31639385f7dd3b33d39f25c707a66915d8dac9 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 3 Dec 2018 19:09:12 -0800 Subject: [PATCH 15/19] Serialization code: implement for_signing option --- content/_code-samples/tx-serialization/serialize.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py index 53d343ab20..837be3cae2 100755 --- a/content/_code-samples/tx-serialization/serialize.py +++ b/content/_code-samples/tx-serialization/serialize.py @@ -3,6 +3,7 @@ # Transaction Serialization Sample Code (Python3 version) # Author: rome@ripple.com # Copyright Ripple 2018 +# Requires Python 3.5+ because of bytes.hex() import argparse import json @@ -416,6 +417,9 @@ def serialize_tx(tx, for_signing=False): fields_as_bytes = [] for field_name in field_order: if (DEFINITIONS["FIELDS"][field_name]["isSerialized"]): + if for_signing and not DEFINITIONS["FIELDS"][field_name]["isSigningField"]: + # Skip non-signing fields in for_signing mode. + continue field_val = tx[field_name] field_bytes = field_to_bytes(field_name, field_val) logger.debug("{n}: {h}".format(n=field_name, h=field_bytes.hex())) From 7bd10aaaa16e14cb3614bc5abdbb12096a0a5998 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Thu, 6 Dec 2018 17:11:43 -0800 Subject: [PATCH 16/19] Serialization: updates per reviews --- .../tx-serialization/serialize.py | 12 +- .../tx-serialization/xrpl_num.py | 10 +- content/_img-sources/serialization-amount.uxf | 103 +++++++++----- content/_img-sources/serialization-array.uxf | 69 +++++---- content/_img-sources/serialization-object.uxf | 134 ++++++++++-------- .../_img-sources/serialization-pathset.uxf | 74 ++++++---- .../api-conventions/serialization.md | 108 ++++++++------ img/serialization-amount.png | Bin 34377 -> 34352 bytes img/serialization-array.png | Bin 9203 -> 9703 bytes img/serialization-object.png | Bin 11960 -> 12384 bytes img/serialization-pathset.png | Bin 16722 -> 16556 bytes 11 files changed, 310 insertions(+), 200 deletions(-) diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py index 837be3cae2..695c1308f8 100755 --- a/content/_code-samples/tx-serialization/serialize.py +++ b/content/_code-samples/tx-serialization/serialize.py @@ -87,7 +87,7 @@ def field_id(field_name): def vl_encode(vl_contents): """ - Helper function for variable-length fields including Blob types + Helper function for length-prefixed fields including Blob types and some AccountID types. Encodes arbitrary binary data with a length prefix. The length of the prefix @@ -121,10 +121,10 @@ def vl_encode(vl_contents): def accountid_to_bytes(address): """ - Serialize an AccountID field type. These are variable-length encoded. + Serialize an AccountID field type. These are length-prefixed. - Some fields contain nested non-VL-encoded AccountIDs directly; those call - decode_address() instead of this function. + Some fields contain nested non-length-prefixed AccountIDs directly; those + call decode_address() instead of this function. """ return vl_encode(decode_address(address)) @@ -182,9 +182,7 @@ def array_to_bytes(array): def blob_to_bytes(field_val): """ - Serializes a string of hex as binary data with a variable-length encoded - length prefix. (The prefix is 1-3 bytes depending on the length of the - contents.) + Serializes a string of hex as binary data with a length prefix. """ vl_contents = bytes.fromhex(field_val) return vl_encode(vl_contents) diff --git a/content/_code-samples/tx-serialization/xrpl_num.py b/content/_code-samples/tx-serialization/xrpl_num.py index ad82690496..6d0c25c79d 100644 --- a/content/_code-samples/tx-serialization/xrpl_num.py +++ b/content/_code-samples/tx-serialization/xrpl_num.py @@ -54,12 +54,10 @@ class IssuedAmount: def canonical_zero_serial(self): """ - Returns canonical format for zero: + Returns canonical format for zero (a special case): - "Not XRP" bit = 1 - - "Sign bit" = 1 (for positive !!) - - exponent ??? + - Everything else is zeroes + - Arguably this means it's canonically written as "negative zero" + because the encoding usually uses 1 for positive. """ - # Mantissa is all zeroes. Must be positive zero. - #bitval = 0b1100000001000000000000000000000000000000000000000000000000000000 - #return bitval.to_bytes(8, byteorder="big", signed=False) return (0x8000000000000000).to_bytes(8, byteorder="big", signed=False) diff --git a/content/_img-sources/serialization-amount.uxf b/content/_img-sources/serialization-amount.uxf index a8818ce839..f30fd7c59a 100644 --- a/content/_img-sources/serialization-amount.uxf +++ b/content/_img-sources/serialization-amount.uxf @@ -1,17 +1,6 @@ 10 - - UMLPackage - - 30 - 240 - 850 - 70 - - Issued Currency Amount Format - - UMLClass @@ -192,17 +181,6 @@ style=wordwrap ISO code (24 bits) - - UMLPackage - - 30 - 480 - 620 - 70 - - Issued Currency Code Format - - Relation @@ -231,10 +209,10 @@ style=wordwrap 20 290 460 - 210 + 230 lt=.. - 10.0;190.0;440.0;10.0 + 10.0;210.0;440.0;10.0 Relation @@ -247,17 +225,6 @@ style=wordwrap lt=.. 10.0;210.0;20.0;10.0 - - UMLPackage - - 30 - 30 - 620 - 70 - - XRP Amount Format - - UMLClass @@ -338,4 +305,70 @@ style=wordwrap integer drops (62 bits) + + UMLClass + + 30 + 50 + 620 + 50 + + + + + + Text + + 30 + 20 + 160 + 30 + + XRP Amount Format + + + + Text + + 30 + 230 + 240 + 30 + + Issued Currency Amount Format + + + + UMLClass + + 30 + 260 + 850 + 50 + + + + + + Text + + 90 + 470 + 240 + 30 + + Issued Currency Code Format + + + + UMLClass + + 30 + 500 + 620 + 50 + + + + diff --git a/content/_img-sources/serialization-array.uxf b/content/_img-sources/serialization-array.uxf index f5b443c100..8b389748c8 100644 --- a/content/_img-sources/serialization-array.uxf +++ b/content/_img-sources/serialization-array.uxf @@ -4,8 +4,8 @@ UMLClass - 140 - 120 + 90 + 80 40 30 @@ -15,8 +15,8 @@ Relation - 150 - 140 + 100 + 100 30 80 @@ -26,8 +26,8 @@ Text - 80 - 200 + 30 + 160 110 50 @@ -38,8 +38,8 @@ Field ID UMLClass - 190 - 120 + 140 + 80 50 30 @@ -49,8 +49,8 @@ Field ID UMLClass - 670 - 120 + 620 + 80 50 30 @@ -60,8 +60,8 @@ Field ID Relation - 680 - 140 + 630 + 100 30 80 @@ -71,8 +71,8 @@ Field ID Text - 640 - 200 + 590 + 160 100 60 @@ -84,8 +84,8 @@ no contents UMLClass - 240 - 120 + 190 + 80 180 30 @@ -95,8 +95,8 @@ no contents Relation - 210 - 140 + 160 + 100 100 80 @@ -106,8 +106,8 @@ no contents Text - 300 - 180 + 250 + 140 110 50 @@ -118,8 +118,8 @@ Field ID UMLClass - 430 - 120 + 380 + 80 50 30 @@ -129,8 +129,8 @@ Field ID UMLClass - 480 - 120 + 430 + 80 180 30 @@ -138,14 +138,25 @@ Field ID - UMLPackage + UMLClass - 130 - 90 + 80 + 70 600 - 70 + 50 - SignerEntries field + + + + + Text + + 80 + 40 + 210 + 30 + + SignerEntries (array) field diff --git a/content/_img-sources/serialization-object.uxf b/content/_img-sources/serialization-object.uxf index 37ee31ac98..50bc2f0950 100644 --- a/content/_img-sources/serialization-object.uxf +++ b/content/_img-sources/serialization-object.uxf @@ -4,8 +4,8 @@ UMLClass - 90 - 170 + 50 + 80 40 30 @@ -15,8 +15,8 @@ Relation - 100 - 190 + 60 + 100 30 60 @@ -26,8 +26,8 @@ Text - 70 - 230 + 30 + 140 80 50 @@ -38,8 +38,8 @@ Field ID UMLClass - 140 - 170 + 100 + 80 50 30 @@ -49,8 +49,8 @@ Field ID UMLClass - 620 - 170 + 580 + 80 50 30 @@ -60,8 +60,8 @@ Field ID Relation - 630 - 190 + 590 + 100 80 60 @@ -71,8 +71,8 @@ Field ID Text - 640 - 230 + 600 + 140 100 60 @@ -84,8 +84,8 @@ no contents UMLClass - 220 - 170 + 180 + 80 150 30 @@ -95,19 +95,19 @@ no contents Relation - 160 - 190 + 120 + 100 50 - 60 + 80 lt=<<- - 10.0;10.0;10.0;40.0;30.0;40.0 + 10.0;10.0;10.0;60.0;30.0;60.0 UMLClass - 380 - 170 + 340 + 80 50 30 @@ -117,8 +117,8 @@ no contents UMLClass - 190 - 170 + 150 + 80 30 30 @@ -128,8 +128,8 @@ no contents Text - 190 - 210 + 150 + 140 110 50 @@ -140,8 +140,8 @@ Field ID UMLClass - 460 - 170 + 420 + 80 150 30 @@ -151,8 +151,8 @@ Field ID UMLClass - 430 - 170 + 390 + 80 30 30 @@ -162,8 +162,8 @@ Field ID Relation - 130 - 210 + 90 + 120 500 90 @@ -173,8 +173,8 @@ Field ID Text - 300 - 280 + 260 + 190 170 50 @@ -185,8 +185,8 @@ style=wordwrap Text - 290 - 230 + 370 + 30 150 30 @@ -197,29 +197,18 @@ style=wordwrap Relation 350 - 190 - 70 - 60 - - lt=<<- - 50.0;10.0;50.0;30.0;10.0;30.0;10.0;40.0 - - - UMLPackage - - 80 - 140 - 600 + 30 + 40 70 - Memos field - + lt=<<- + 10.0;50.0;10.0;10.0;20.0;10.0 Relation - 430 - 190 + 390 + 100 50 50 @@ -229,12 +218,45 @@ style=wordwrap Text - 460 - 210 + 420 + 120 140 40 - Length prefix + Length prefixes + + UMLClass + + 40 + 70 + 600 + 50 + + + + + + Text + + 40 + 40 + 160 + 30 + + Memo (object) field + + + + Relation + + 150 + 100 + 290 + 50 + + lt=<<- + 10.0;10.0;10.0;30.0;270.0;30.0 + diff --git a/content/_img-sources/serialization-pathset.uxf b/content/_img-sources/serialization-pathset.uxf index 84eb14431a..b38268357a 100644 --- a/content/_img-sources/serialization-pathset.uxf +++ b/content/_img-sources/serialization-pathset.uxf @@ -1,17 +1,6 @@ 10 - - UMLPackage - - 40 - 40 - 650 - 70 - - PathSet - - UMLClass @@ -122,27 +111,16 @@ 0x00 - - UMLPackage - - 40 - 180 - 650 - 70 - - Path - - Relation 30 90 40 - 110 + 130 lt=.. - 10.0;90.0;20.0;10.0 + 10.0;110.0;20.0;10.0 Relation @@ -292,11 +270,11 @@ 410 20 - 60 + 70 70 lt=<<- - 10.0;50.0;10.0;10.0;40.0;10.0 + 10.0;50.0;10.0;10.0;50.0;10.0 Text @@ -320,4 +298,48 @@ lt=<<- 10.0;40.0;40.0;40.0;40.0;10.0 + + UMLClass + + 40 + 60 + 650 + 50 + + + + + + Text + + 40 + 30 + 100 + 30 + + PathSet + + + + Text + + 50 + 170 + 100 + 30 + + Path + + + + UMLClass + + 40 + 200 + 650 + 50 + + + + diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index 3ba2a4b057..9ff6a05ddf 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -10,14 +10,33 @@ The process of serializing a transaction from JSON or any other representation i **Note:** The `SigningPubKey` must also be provided at this step. When signing, you can derive this key from the secret key that is provided for signing. 2. Convert each field's data into its "internal" binary format. + 3. Sort the fields in canonical order. + 4. Prefix each field with a Field ID. + 5. Concatenate the fields (including prefixes) in their sorted order. -The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. After signing, you must attach the signature to the transaction, calculate the transaction's identifying hash, then re-serialize the transaction with the additional fields. +The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. After signing, you must re-serialize the transaction with the `TxnSignature` field included. **Note:** The XRP Ledger uses the same serialization format to represent other types of data, such as [ledger objects](ledger-object-types.html) and processed transactions. However, only certain fields are appropriate for including in a transaction that gets signed. (For example, the `TxnSignature` field, containing the signature itself, should not be present in the binary blob that you sign.) Thus, some fields are designated as "Signing" fields, which are included in objects when those objects are signed, and "non-signing" fields, which are not. +### Examples + +Both signed and unsigned transactions can be represented in both JSON and binary formats. The following samples show the same signed transaction in its JSON and binary formats: + +**JSON:** + +```json +{% include '_code-samples/tx-serialization/test-cases/tx1.json' %} +``` + +**Binary (represented as hexadecimal):** + +```text +{% include '_code-samples/tx-serialization/test-cases/tx1-binary.txt' %} +``` + ## Sample Code The serialization processes described here are implemented in multiple places and programming languages: @@ -49,7 +68,7 @@ The following table defines the top-level fields from the definitions file: | `TYPES` | Map of data types to their "type code" for constructing field IDs and sorting fields in canonical order. Codes below 1 should not appear in actual data; codes above 10000 represent special "high-level" object types such as "Transaction" that cannot be serialized inside other objects. | | `LEDGER_ENTRY_TYPES` | Map of [ledger objects](ledger-object-types.html) to their data type. These appear in ledger state data, and in the "affected nodes" section of processed transactions' [metadata](transaction-metadata.html). | | `FIELDS` | A sorted array of tuples representing all fields that may appear in transactions, ledger objects, or other data. The first member of each tuple is the string name of the field and the second member is an object with that field's properties. (See the "Field properties" table below for definitions of those fields.) | -| `TRANSACTION_RESULTS` | Map of [transaction result codes](transaction-results.html) to their numeric values. Result types not included in ledgers have negative values;`tesSUCCESS` has numeric value 0; [`tec`-class codes](tec-codes.html) represent failures that are included in ledgers. | +| `TRANSACTION_RESULTS` | Map of [transaction result codes](transaction-results.html) to their numeric values. Result types not included in ledgers have negative values; `tesSUCCESS` has numeric value 0; [`tec`-class codes](tec-codes.html) represent failures that are included in ledgers. | | `TRANSACTION_TYPES` | Map of all [transaction types](transaction-types.html) to their numeric values. | For purposes of serializing transactions for signing and submitting, the `FIELDS`, `TYPES`, and `TRANSACTION_TYPES` fields are necessary. @@ -59,7 +78,7 @@ The field definition objects in the `FIELDS` array have the following fields: | Field | Type | Contents | |:-----------------|:--------|:------------------------------------------------| | `nth` | Number | The [field code](#field-codes) of this field, for use in constructing its [Field ID](#field-ids) and ordering it with other fields of the same data type. | -| `isVLEncoded` | Boolean | If `true`, this field is [variable-length encoded](#variable-length-encoding). | +| `isVLEncoded` | Boolean | If `true`, this field is [length-prefixed](#length-prefixing). | | `isSerialized` | Boolean | If `true`, this field should be encoded into serialized binary data. When this field is `false`, the field is typically reconstructed on demand rather than stored. | | `isSigningField` | Boolean | If `true` this field should be serialized when preparing a transaction for signing. If `false`, this field should be omitted from the data to be signed. (It may not be part of transactions at all.) | | `type` | String | The internal data type of this field. This maps to a key in the `TYPES` map, which gives the [type code](#type-codes) for this field. | @@ -68,15 +87,14 @@ The field definition objects in the `FIELDS` array have the following fields: ## Canonical Field Order -All fields in a transaction are sorted in a specific order based on the field's type first, then the field itself second. (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) +All fields in a transaction are sorted in a specific order based first on the field's type, then on the field itself (a "field code"). (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) ### Field IDs [[Source - Encoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L117-L148 "Source") [[Source - Decoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L484-L509 "Source") - -When you combine the type code and sort code, you get the field's identifier, which is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and field codes it combines. See the following table: +When you combine the type code and field code, you get the field's unique identifier, which is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and field codes it combines. See the following table: | | Type Code < 16 | Type Code >= 16 | |:-----------------|:------------------------------------------------------------------------------|:--| @@ -90,29 +108,31 @@ When decoding, you can tell how many bytes the field ID is by which bits **of th | **Low 4 bits are nonzero** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | | **Low 4 bits are zero** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | +**Caution:** Even though the Field ID consists of the two elements that are used to sort fields, you should not sort by the serialized Field ID itself, because the byte structure of the Field ID changes the sort order. + ### Type Codes -Each field type has an arbitrary sort code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). +Each field type has an arbitrary type code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). For example, [UInt32 has sort order 2](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L59), so all UInt32 fields come before all [Amount fields with order 6](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L63). ### Field Codes -Each field also has a field code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). +Each field has a field code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). For example, the `Account` field of a [Payment transaction][] [has sort code 1](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L219), so it comes before the `Destination` field which [has sort code 3](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L221). -The field code is combined with the type code to make a field's [Field ID](#field-ids). +Field codes are reused for fields of different field types, but fields of the same type never have the same field code. When you combine the type code with the field code, you get the field's unique [Field ID](#field-ids). -### Variable-Length Encoding +### Length Prefixing -Some types of fields are Variable-Length encoding, which means they are not always the same byte length and are prefixed with a length indicator to indicate how much data they contain. `Blob` fields (containing arbitrary binary data) are one such variable-length encoded type. For a list of which types are variable-length encoded, see the [Type List](#type-list) table. +Some types of variable-length fields are prefixed with a length indicator. `Blob` fields (containing arbitrary binary data) are one such type. For a list of which types are length-prefixed, see the [Type List](#type-list) table. -**Note:** Some types that are not variable-length encoded nonetheless vary in length. These types have different ways of indicating how long they are. +**Note:** Some types of fields that vary in length are not length-prefixed. Those types have other ways of indicating the end of their contents. -Variable-length fields are encoded with one to three bytes indicating the length of the field immediately after the type prefix and before the contents. +The length prefix consists of one to three bytes indicating the length of the field immediately after the type prefix and before the contents. -- If the field contains 0 to 192 bytes of data, the first byte defines the length of the VariableLength data, then that many bytes of data follow immediately after the length byte. +- If the field contains 0 to 192 bytes of data, the first byte defines the length of the contents, then that many bytes of data follow immediately after the length byte. - If the field contains 193 to 12480 bytes of data, the first two bytes indicate the length of the field with the following formula: @@ -122,7 +142,7 @@ Variable-length fields are encoded with one to three bytes indicating the length 12481 + ((byte1 - 241) * 65536) + (byte2 * 256) + byte3 -- A variable-length field cannot contain more than 918744 bytes of data. +- A length-prefixed field cannot contain more than 918744 bytes of data. When decoding, you can tell from the value of the first length byte whether there are 0, 1, or 2 additional length bytes: @@ -135,39 +155,41 @@ When decoding, you can tell from the value of the first length byte whether ther Transaction instructions may contain fields of any of the following types: -| Type Name | Type Code | Variable-Length? | Description | -|:--------------|:----------|:-----------------|:------------------------------| -| [AccountID][] | 8 | Yes | The unique identifier for an [account](accounts.html). This field is variable-length encoded, but always exactly 20 bytes. | -| [Amount][] | 6 | No | An amount of XRP or issued currency. The length of the field is 64 bits for XRP or 384 bits (64+160+160) for issued currencies. | -| [Blob][] | 7 | Yes | Arbitrary binary data. One important such field is `TxnSignature`, the signature that authorizes a transaction. | -| [Hash128][] | 4 | No | A 128-bit arbitrary binary value. The only such field is `EmailHash`, which is intended to store the MD-5 hash of an account owner's email for purposes of fetching a [Gravatar](https://www.gravatar.com/). | -| [Hash160][] | 17 | No | A 160-bit arbitrary binary value. This may define a currency code or issuer. | -| [Hash256][] | 5 | No | A 256-bit arbitrary binary value. This usually represents the "SHA-512Half" hash of a transaction, ledger version, or ledger data object. | -| [PathSet][] | 18 | No | A set of possible [payment paths](paths.html) for a [cross-currency payment](cross-currency-payments.html). | -| [STArray][] | 15 | No | An array containing a variable number of members, which can be different types depending on the field. Two cases of this include [memos](transaction-common-fields.html#memos-field) and lists of signers used in [multi-signing](multi-signing.html). | -| [STObject][] | 14 | No | An object containing one or more nested fields. | -| [UInt8][] | 16 | No | An 8-bit unsigned integer. | -| [UInt16][] | 1 | No | A 16-bit unsigned integer. The `TransactionType` is a special case of this type, with specific strings mapping to integer values. | -| [UInt32][] | 2 | No | A 32-bit unsigned integer. The `Flags` and `Sequence` fields on all transactions are examples of this type. | +| Type Name | Type Code | Bit Length | [Length-prefixed]? | Description | +|:--------------|:----------|:-----------|:-------------------|----------------| +| [AccountID][] | 8 | 160 | Yes | The unique identifier for an [account](accounts.html). | +| [Amount][] | 6 | 64 or 384 | No | An amount of XRP or issued currency. The length of the field is 64 bits for XRP or 384 bits (64+160+160) for issued currencies. | +| [Blob][] | 7 | Variable | Yes | Arbitrary binary data. One important such field is `TxnSignature`, the signature that authorizes a transaction. | +| [Hash128][] | 4 | 128 | No | A 128-bit arbitrary binary value. The only such field is `EmailHash`, which is intended to store the MD-5 hash of an account owner's email for purposes of fetching a [Gravatar](https://www.gravatar.com/). | +| [Hash160][] | 17 | 160 | No | A 160-bit arbitrary binary value. This may define a currency code or issuer. | +| [Hash256][] | 5 | 256 | No | A 256-bit arbitrary binary value. This usually represents the "SHA-512Half" hash of a transaction, ledger version, or ledger data object. | +| [PathSet][] | 18 | Variable | No | A set of possible [payment paths](paths.html) for a [cross-currency payment](cross-currency-payments.html). | +| [STArray][] | 15 | Variable | No | An array containing a variable number of members, which can be different types depending on the field. Two cases of this include [memos](transaction-common-fields.html#memos-field) and lists of signers used in [multi-signing](multi-signing.html). | +| [STObject][] | 14 | Variable | No | An object containing one or more nested fields. | +| [UInt8][] | 16 | 8 | No | An 8-bit unsigned integer. | +| [UInt16][] | 1 | 16 | No | A 16-bit unsigned integer. The `TransactionType` is a special case of this type, with specific strings mapping to integer values. | +| [UInt32][] | 2 | 32 | No | A 32-bit unsigned integer. The `Flags` and `Sequence` fields on all transactions are examples of this type. | + +[Length-prefixed]: #length-prefixing In addition to all of the above field types, the following types may appear in other contexts, such as [ledger objects](ledger-object-types.html) and [transaction metadata](transaction-metadata.html): -| Type Name | Type Code | Variable-Length? | Description | -|:------------|:----------|:-----------------|:--------------------------------| -| Transaction | 10001 | No | A "high-level" type containing an entire [transaction](transaction-formats.html). | -| LedgerEntry | 10002 | No | A "high-level" type containing an entire [ledger object](ledger-object-types.html). | -| Validation | 10003 | No | A "high-level" type used in peer-to-peer communications to represent a validation vote in the [consensus process](consensus.html). | -| Metadata | 10004 | No | A "high-level" type containing [metadata for one transaction](transaction-metadata.html). | -| [UInt64][] | 3 | No | A 64-bit unsigned integer. This type does not appear in transaction instructions, but several ledger objects use fields of this type. | -| Vector256 | 19 | Yes | This type does not appear in transaction instructions, but the [Amendments ledger object](amendments-object.html)'s `Amendments` field uses this to represent which [amendments](amendments.html) are currently enabled. | +| Type Name | Type Code | [Length-prefixed]? | Description | +|:------------|:----------|:-------------------|:------------------------------| +| Transaction | 10001 | No | A "high-level" type containing an entire [transaction](transaction-formats.html). | +| LedgerEntry | 10002 | No | A "high-level" type containing an entire [ledger object](ledger-object-types.html). | +| Validation | 10003 | No | A "high-level" type used in peer-to-peer communications to represent a validation vote in the [consensus process](consensus.html). | +| Metadata | 10004 | No | A "high-level" type containing [metadata for one transaction](transaction-metadata.html). | +| [UInt64][] | 3 | No | A 64-bit unsigned integer. This type does not appear in transaction instructions, but several ledger objects use fields of this type. | +| Vector256 | 19 | Yes | This type does not appear in transaction instructions, but the [Amendments ledger object](amendments-object.html)'s `Amendments` field uses this to represent which [amendments](amendments.html) are currently enabled. | ### AccountID Fields [AccountID]: #accountid-fields -Fields of this type contain the 160-bit identifier for an XRP Ledger [account](accounts.html). In JSON, these fields are represented as base58 XRP Ledger "addresses", with additional checksum data so that typos are unlikely to result in valid addresses. (This encoding, sometimes called "Base58Check", prevents accidentally sending money to the wrong address.) The binary format for these fields does not contain any checksum data. (However, since the binary format is used mostly for signed transactions, a typo or other error in transcribing a signed transaction would invalidate the signature, preventing it from sending money.) +Fields of this type contain the 160-bit identifier for an XRP Ledger [account](accounts.html). In JSON, these fields are represented as base58 XRP Ledger "addresses", with additional checksum data so that typos are unlikely to result in valid addresses. (This encoding, sometimes called "Base58Check", prevents accidentally sending money to the wrong address.) The binary format for these fields does not contain any checksum data nor does it include the `0x00` "type prefix" used in [address base58 encoding](accounts.html#address-encoding). (However, since the binary format is used mostly for signed transactions, a typo or other error in transcribing a signed transaction would invalidate the signature, preventing it from sending money.) -AccountIDs that appear as stand-alone fields (such as `Account` and `Destination`) are [variable-length encoded](#variable-length-encoding) despite being a fixed 160 bits in length. As a result, the length indicator for these fields is always the byte `0x14`. AccountIDs that appear as children of special fields ([Amount `issuer`][Amount] and [PathSet `account`][PathSet]) are _not_ variable-length encoded. +AccountIDs that appear as stand-alone fields (such as `Account` and `Destination`) are [length-prefixed](#length-prefixing) despite being a fixed 160 bits in length. As a result, the length indicator for these fields is always the byte `0x14`. AccountIDs that appear as children of special fields ([Amount `issuer`][Amount] and [PathSet `account`][PathSet]) are _not_ length-prefixed. ### Amount Fields @@ -176,9 +198,13 @@ AccountIDs that appear as stand-alone fields (such as `Account` and `Destination The "Amount" type is a special field type that represents an amount of currency, either XRP or an issued currency. This type consists of two sub-types: - **XRP** + XRP is serialized as a 64-bit unsigned integer (big-endian order), except that the most significant bit is always 0 to indicate that it's XRP, and the second-most-significant bit is `1` to indicate that it is positive. Since the maximum amount of XRP (1017 drops) only requires 57 bits, you can calculate XRP serialized format by taking standard 64-bit unsigned integer and performing a bitwise-OR with `0x4000000000000000`. + - **Issued Currencies** + Issued currencies consist of three segments in order: + 1. 64 bits indicating the amount in the [internal currency format](currency-formats.html#issued-currency-math). The first bit is `1` to indicate that this is not XRP. 2. 160 bits indicating the [currency code](currency-formats.html#currency-codes). The standard API converts 3-character codes such as "USD" into 160-bit codes using the [standard currency code format](currency-formats.html#standard-currency-codes), but custom 160-bit codes are also possible. 3. 160 bits indicating the issuer's Account ID. (See also: [Account Address Encoding](accounts.html#address-encoding)) @@ -207,7 +233,7 @@ The following example shows the serialization format for an array (the `SignerEn ### Blob Fields [Blob]: #blob-fields -The Blob type is a [variable-length encoded](#variable-length-encoding) field with arbitrary data. Two common fields that use this type are `SigningPubKey` and `TxnSignature`, which contain (respectively) the public key and signature that authorize a transaction to be executed. +The Blob type is a [length-prefixed](#length-prefixing) field with arbitrary data. Two common fields that use this type are `SigningPubKey` and `TxnSignature`, which contain (respectively) the public key and signature that authorize a transaction to be executed. Blob fields have no further structure to their contents, so they consist of exactly the amount of bytes indicated in the variable-length encoding, after the Field ID and length prefixes. @@ -258,7 +284,7 @@ The following table describes the possible fields and the bitwise flags to set i Some combinations are invalid; see [Path Specifications](paths.html#path-specifications) for details. -The AccountIDs in the `account` and `issuer` fields are presented _without_ a variable-length encoding prefix. When the `currency` is XRP, the currency code is represented as 160 bits of zeroes. +The AccountIDs in the `account` and `issuer` fields are presented _without_ a length prefix. When the `currency` is XRP, the currency code is represented as 160 bits of zeroes. Each step is followed directly by the next step of the path. As described above, last step of a path is followed by either `0xff` (if another path follows) or `0x00` (if this ends the last path). diff --git a/img/serialization-amount.png b/img/serialization-amount.png index 6f0bd314f3c7c6e4b5b2c7052812139c2c333571..70446e114838f3a53c21b9f32c1f8830238050e6 100644 GIT binary patch literal 34352 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

mXcv9S+S=)X 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%0Dd1em;M>(ccJ%y&DysSr-?yvo&_+~Vg3Fc zX||>chcGVrp5i|0{Za?BX2wb{uDi-c|JL)gQ7V-uHl8!dw<)*$ov;!#vn?^&WU|{Y z2l(+Xr+bBm~_w;v-Lwqd;~~0uAcW zeC(#;19w%o$ix|=$t1kzu)k^dpHEHtI!}7>K_Ph*%)I20J4v5lK&&1{-n(~lVq)#i z0KBW{3!gNb07AnO5(=ee;$Kv#{{o&)jz=J~S94$m%upkWz*Zf1*$kfKLU zQ~>c!Fb6~$9)sBHh&`CDK_A-gqt^`TLx+MIPb)nETV{BvhCH@-Zvh_e&d!9 z^s>grj*?;B7Upmo@14R}qf!`s9LHSGf{wH=aCrOYHU?9m@6*8rkmJkJpT7MID=QyC z9>Z3z+<49~oEqQ?A`E(&M1S~CCxNyCxft;~T1ZCTfBjmC-gf?RoQHGSD}njrPhei0 z1r-dbRtEOQGPN;+cff0S5pmkx_aE>9P{_~#q3@8DZNXDO>NJ1_;yraq&8R2$@I{eU zq*~K8qEHo2ooH4|vl#$01_N3K1_m%6$+_a>u;G^0nkhI10m@*!e~)5q`-F=BsN

DS8p}*Q)4#&p9`&S z4}FP%uq+y7!S3LM2RN|N!j9nMDFQlFjKN_hgtrxr?kT|bioY-YSRd}FGQyu2uqaI= z3aX~q_`#8{PUm={Y|bz{Q2j%y(W3)u&K?OiOY0Y4-|u_VKG0d2oUHAv2E$~_4(h#o zv#$?dKLdv?syfr&_~6SiY*?0tUcD?4M*{u)ZtWM#7D19^s^_-LvI}?>h7t(qkxfrE z@PW+EGhl6P4dehOHHlv3wirWx+16BBQBPmL1)xqr9URB30}Uf@aMF3w8m%1)8tFYf z7%N=I5?{pcamgax*E}A(0NEJ2<^-)*jq{1!o~=C>oGxceP`9P{7H zEwvb-ju=lBr~=IwZ0FD)K;eD3_5L2#!hduyQtjxa>(|W=b{9llaRl&GNEM~OU>^i1 zqZcQCb7x!>(c!3P4n;C&#?mx0(j)~!i4xzu2>EJFldOcn{^R{%HZad)5XP>Db!qGeCY1t3SlXM0T;c9x?5s`c^%6DdKlL`ou=r5r|h3%FOL&V&60^H z?%Zsu@OpWn{s;z4<<(fAZegm+g2H292(w)F3Ns>%w9lQ&>EUxN2ck(;IGe z!(lpdqKGMKa|0&082wsAo@>pHmd?@7&3wT?oVg-i!n2=)jiSBVnCCnO;@-r7 zdCFbf)GP@sj+m5gZ1}Dtv*X28lnI2ut>Tida+^2r+gNgp81TdV`7n?U!ke*TAoe>{K!pR{_*|$O)}_09GQUb#WO?6d;rO-t0+%7MkIN!Av>kqiI0cW zURP>e!&dtG_uK~U9nu|}|FJxtZ2ssNzk<7vvnYp&qwk_%40~e)OIcpsK+FdS-(6u- z>}8D%ka_S4LFTYfvp$NPc%cp=;`HLFv#yPd06+uZK(q5~}G=OYf2X3da7T8gA3KGa5tKACLv);+InVX&y2QcAmxdw##)e-+?KvkU_0_S zA*0oV!yw1i4UU!#C#7vGvmBJ0&k%S*=I#{n&RMt;zUb&MVcAJJm`=I|@LnGia6xL? z{X^<~np$kiMm^xsTvXAjQ{QY%n)!5t0&3$>GF1HsToAlN9fHiOmlVl7t*!KG5MwaJTYy(LsnV}890e#Tu>y7-#lCe0 zgza_BE1N*Q`P!ML*#+&-IM!|B$B{_Of65}gg44^iD+?L21DHjym|~={Q@7Is=0((u z)_ww2QS>t5nAv$dGF(<7^UK$-I64w!z~!S8z-dqQ7x{`02O21Uc^r(;Q2+y3(qw>K zXe$;Jqa12E(5~w#bSJgAaD!WEcLxJ=#$G?#NKi#2h^$xYLLMqIwCUtkRm;&V{Gv=8 zIgG?Afp+xym`aDK{iS$-;F<%UM0)N(RM5}+{(gsOGb^R`zkG;tkvp;0j~x&4M?Kqg zwHR9fxQb4{4u!Iqld~;oSH{DtTZ ziSQg`if0urxdmU93~4*Vh3rl~f2%qFZBkO8Qu;ECe`B>ea{ib0k@6kdD}x9NSR6o| zmzA-fF-H%P5Uq=0%uj{p#M0)mqq`PevrzZb*w`4b=vN3gMn*;^J_1?$QtasoS3K~f zuO{v*1jGx*N9@|Sgusw>d!H}YqDA_}8MB}SnIwj8xJ)JZ^xhD>CtsYoKKpxF$aQ1o zT2p>B7ynjZS^eF0O`DQVuR4q;T$#@M2{MWaUi%WPOA;2x9D36Q^Mu=F4Jkk zM3FdmHyUPD=Rjxe`2z K>#)L#z!;6V#B|(ifcJRcYw+_t3ik78YYS5=>pAH7n#0 zVD8@K#B#sC4ca96Kr0FRg1ahcIwU2%2PG+ej%pFU+`d5lC){ODowCh!U^kf?6-%UM z1PgX9sRz7?KLYI`b`H?TKe^l7OTYvEKVBm6%6~JyIdU!WTYuhm?xnf6{qu>3e$J9+ YXG&!M&NbiIe=^d#O1Vp8` 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 R7DNM4yrp`xNY3bw{{z2b?%)6b literal 9203 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 diff --git a/img/serialization-object.png b/img/serialization-object.png index d62f99922d8b888f232c735b2eaa3ab04baff1d6..d47128ebc3a7e722c23bc8b83893f73b3a4a2a41 100644 GIT binary patch literal 12384 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 z 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 Vi#3dwufZfqWF!>s<%;P)|38?N3C{ok diff --git a/img/serialization-pathset.png b/img/serialization-pathset.png index 2f1d528f0f4c2a8ed2c9b386444e37c0ef939b10..09217c839c076c6e0f338335b76daadd3c144147 100644 GIT binary patch literal 16556 zcmbWe2Urx@7B$+)hyejnL6V|GX+X5epdd(25}KSuXmUnCGKwSxiA@$za+aKffz*IV z&Y+T`ihzQIw_9fJ%-s9G|9#){bEK!Js;)X`pS{;!Yn@m)3vn3#hqH)`hk%>58O?8ceuOdHtAleg7l*yWo7Xhe4taJO-jRgG3hpGy4?& zUnC*? z6{=HiNumAqNc>HYtva{wNqmjU)CgZw)0PlwtHJ!9#rrm+Wo$^KD*Mx?Ph&ZC2a60F zpIs2%+1^%6;92a9=T;QNy$e1Y%c0$S5^20X&r@v4Bj9HCEL6~Gu57!pZfn#^QH7ed zlM79RNNvu`OYz=eyL9Q}bJxjs94BB)CO{!=DSnu#wrvyX-Vh=?57&M z1~o%VqTn7L9v(jydKfQXevz3eHApXDe`^q}u5O`=mWq>U-?|bSNM3J4QB>_56lguL=9qc+Yb^ zOy}%0JfI}wRsEWc@7<#Ea=1yFbQEcfA%s*ain9aDt~oQlzmX*xzl%;7QZ@42o{b$% z2oFD9FdD8SozOfgU259(BqXFv3jah&xHgLi4<4A9nl25gr%8uZ%pJph3Lw3ZTaVrQ zwY{A)yRcv`c&B`KF)L+jWkhY0ohr#w?V0TtsxWL%`a3-l5x4bpwJaq=pS^M&Lw$YD z6O*Y%jekGPY)9(zinFT_q=n8v@eb9 z*A)e82(v@{4XC5GAMEWo%chJ~+F8*<0OyDyr|)k2!>UB~l|)BJYgN5Y&t>+)#*6yw z4;LGk>qz@N&fG*AujvvYxot*y)Q=ie*^4RNuFEACRVJ;p|0Euzo+k8pt}D^W(Q(5k z^L0D_?837G6w7y&<`9?jCFFnK{`2F&z(m@uJlkttn**Ao372mNEKJ=+u`-u*S5;L7 zov{OGC6B}DwUCqqO%CCeuWvjKZG$8L$>oos9*3es!kI&`j*H1eq zUi#o)>}+ml6zd%((}ZtCQT6lZVcBOZJpCT#v-@^x>HwD6_WrLg?Ck7MpFC;U+tSTZ z>A&?Drk{w2$kfbi`F>3(EkDhvU%af$FNGPULauOf+Jxgge|`!S6BDbht`56+b7!;2 zr$7{CJW=DEoSYm;Mju1uE++Om`KAY-&8P`xAUhxeCo*tNb?+Jdw#suMXGdq}z1{EK z$9v% zyO0E4in#A=EIy%N_w@8s5DBpw@eEP3bLK3krg)3nS?hV zEIQ))W%2?Y9Y@yX8pafypsqGY_j6&_(K3t4$;pXY*RZfK{x~i(2u(P8m7TqB@OON! zA&|!KL~q}wO?rB+&jhDer(1R>-%NY?a)Z9b<;||;cX3ocJe=3ghOCUt6)vvIU5e>r zO`SO*!?Pur4kodQ@o@u>RbGpcwgmS(?G{-UM{Z$K2dKDBLpCWm!woA6QMt4Qxw&!) z7xZggD8j6+Vk0V=Q^owQ>Q!ATM82=`-rKn&vi%2^;1=d%GebuG)2AH+d782(*E?RI zrHUVX`|RuMJ6iK;C$}RiN%tZ6^^R1^E_Rbr68m(8&u`u&g z`yk-!tG|B#{(WO(yaYomE9Na&g)<+ob>$Kg5)u%os;SW_xIV&eeB;-PHo2ifeMUyc zO*6g0SJ&7s*Q4+yX!u9@fjC-9K{EQKZl&!`SOq(hoqep_%5@e;L5wtZ+a%Y z#fx0^8@(Q1>RF|7Zj#u!sft66#u6aVnFPo*l3yezPwF&yR?}Sa+S}P|k79+Snnz7| zbTf-=4&5@&5hHrATY5Y+pZeNeU%GoR;N_c&azQvqOHdqnZec=Fl704f-EQ5wrJ$ey zL8z{-uBfOONP3}+x?%akR$dW`rsS+(Ol|G2QnPN3JAYp)i#bV5oPoe~bg0>J8PsJT zm@yM^pVG-ey=n0JHCkb#tD|EH&pN$Fh_9~M3WVs)+}vEKKEc7k7z{?U$bjU;i5^Yr zX=)B!Gh~UDnm9C}-|SbQ|UhfgiJf|A29%TSrdgjv-EJ z|Nhk#6M`xn6cz-U$T#m4od5UnKd=7xuLCbZ>4TWH$xnq`At@=DPKAHjySfrmQftG- zZ`b)gefk82DV+lH-B0>ch!5gF2UJv4j0%tMyrtj&`1!NAI02O}Zg`K32w}Dmms42i z4ausnuTPfQ*EByt1(3lU6Bk$a)YOzoYsi)NRSwfK4DfXDXo)Q*tqKkf#j&xPwzgLn z34L)erFZ+{R^o!6Jb4lp#_E7jriM3MLHIq3|32USbbR5ykSauu7zWWqdTk+&TEz3` z{@yQH%ESBBiY>@iQ>IbeIpo*#|oYzeM5R;NAD9ClUc2z*`3T z`{xj>XNSDa?TltqqvX^vu`=Ve?9W!u(-2%@)=^XAwwtV5o2cy|?M@N3`$~x|?pzru zWv-@U`D)Eqs7yW9THMjj63|ucG#@}lfAP$j$cTuZcMIYe>nnnDo$y<{3E%m1>2V!at_NV{meRe!SRh_$&2gO+w7Dgy`ts@ z1_lovR5dpGSXx>-J3IHK5o3!vu@*7QQS$x1c8z-#5S5OhH$8vaL)9+v{PgB%N9|~h z^YU_0qwQKjK~CpPd(<%!8Ya;tNEuLgmWPTCW}@yWQwN5zqXQgEg1?OiQB{1U=1jp>>htGW11l7<5o%E4a<}++c+iSW2i-pVeM3X8Zv#jy_n@c>xvczb z72W?fb+9*8PCr&;DC$>Pk%_`$vAI95Z7)L5XDCGU#ia1re{6#BSo8s$=wc!6ywq>8 z@Zm)+@mIj0-@bi|h{tTheTd4C2i}xzIe|qt(m~ zAz(>sGr4SWwI=_BrjvAW>+cEq#2zE6+FI|jnlSUGaeujS>Ap}$);yJ(aT+QHooO>x z!N$gxk&&^kVE_fpWo=yBz#us#B_%E{TS`gW)}~EqCKkuc`R)GJigtBVdIk6J++@8+ zPfyS5*ROMbbemYcZEl|Z_}Y(|nR#Sn1Q5Y7Qo2`S*oc#3Z{JS4HvW2EAw?q#FE@az zBE2BA3*f;EitFxhTboR|_xHK3#-GhxWkpnVPBKHJGzB#N5!Nt+}d5x;&h&irV<}R0Hy(fph%x-=E z&OI`2H&poiXH~U4+OWy2D5a zA(C--|3_o2_)JEuf%}*GAL%poZr?KpwHcPVxw$vHOg9#KN-j0mM1T4GZs%9TYO{XB z?z+59=s6*shD4bEMIq;#Pamz2P^S zOB0Ede5w*m%*??wEUc`wr=rC$N!gmFBPEy}#kYWue=H4HZLiNlS&Noy#>wA*{pjeZ zu7!OYS?sOoSy*^?l5c2_u0f=zlXKPbx(2f_F`3)Vo|?U}Dvp`ii8yJ{c+GtltC>E# zvG-#tG0)gq5A!(1XJ7lVl%r9TFTv_qdCn-z0FV0W47&l zekJZJ*^XCTX6HS7=H9(~&}AA6wlbT}=&Bl-;}Lm#5^mV|$KTI%J*x z^>$*zGqbb)@FyUEW>=OPIn4n`E=b`jy1HdBUz^+f!=-Ay48D@NGx}BbFTOD&Yfm0O z9zL3vnwkm)KQlA)b|5LMQc`yLJ%PloT!s3HWS@gyQyGhwu=V?r=B~)J%hfConHm?M z8$EwjFZ_01__bSJCf7u6$p$yzI#WaqmyhzqPA}<>dR^&28izeJD|PV4QOyKZUqNZh z@D(O`ZY)Gprw!{1js#KJ?vSt$s;a5g*VTQVo-V)KlpPY48zOSAid`c=-2dy>uh6G1 z3oFg8$N0Z_BQ8r2fhjlO9UoA)th?=@LhYRExIe19y*hfAhb<)iYCN>mtSO$ouxJX- zk9w<14OiNwl%1ECmmkn)vY@55OA{HbbuHSLbM{-zD{AN+JVlL?I3S8@r;9Z$^vrYM zk6U3ltR7w*5)tjSZ8ZYF)o1$Hi%BXt-=H4L~!wisYSq zaUmyUpYai_r6C^)Wp8n4%AAFs^RP{N{g{BZU-xGEU1_A6wl-T99&53gG$Waqh)C(O ziQXMw9xMPT8!a_3-5Qp0f$dYVMIXy%541=hS6BDC_@|&V%<&cP#7@gJ!BR>nKb3$OEH1QKvb~(7Mn zMtTt40B|!RA|g8k$OizI=|(pne7$R7Bv0qL(Ibr>cuZ#xQsqj+8MIlkz^~o15HiAI+}rrk8ca`f{x)gSUb8S z4bVv2)qM-tx>ix9?RD59U2Th_{KF! zJm>RVbd>mb0=m31Cd2Z#h1)aaV{r~D)W0<1HQo}L*;p1haoWi6!_K`SLApfOxk(j_i{yE-qSFSd=KMsEAqm z_Nk)L5)8f-hiy)=I%~; z{(RIqUz|WJ?l_dm$I*-`Kv@()1EP9RQFDJ$b+dBwWx))uz%_Dm^8NjN2pFcLsVJo^ z)W_kO`T6DhLfS#0q3VvM-h1xa+8q;D>@PVwI(m9}!Gu40^a$3K4C{wp;Qs)#yI7`bslznIM?BY!w-5=IL?!Ai0U`Uh^ zsJp`HnVF>YBI6G-Bslg-vP0CePZijhpWfcyZf|cdv7hUt9xzJlTkQ9x^*w)Y42Fbd z%(z!ct@tDiA~bdgfs1zan;L))+a2$m8XFkEG98(#a5;^=9$1zo?79|0bGtM=F){Dr z)?~TW(9%F2Mr;%pqUlS?`MR#Ij^qAG7G`E3DV{w(VsC;aFjwjA>*GeDESF771YK6* zEU)CvHxVSf2fEAn-aQWMRH;9>YlxvaGDk`}x4mhl_sO3zg?Y|>8&Hi%bqJz#UFvU7 z5b*?VZNCfAWTegl=#PQnQF`Fue1DFbWy_~mudX@kE&=(od;StnIThyf351y%HDb&N z+F3b_RMq*d?(0t(AUg@aFy=kTgSsd#@i2#u7p&E)apc zcA8)%;mMPe#qpyxqK#-ZviX^rmEq#F7+n?o3BkDg%Y%vurA#*kPC+HiQhFZg57Zol z1G4&)VGEv&)WQ4oEF~A?EBqb@- z5Ni4H#hXB-iSEkIYJhCudp;9|_+Tz4FAuG_zV1*hH6^8n?vQTU{xU7x7oCay=!W*q zRMXcl$Xx8`?oJVOGPRlLV&kQDaK6Aqjx@Fkq2aY0$W>1#6nKPt91xI8{tWSQw3n+-HFSu;`v1k$SbI@(u|ya`k*I+!DbgcB1J^rGJRcw%__=g&4dG?8-rU}{ZG0<8j~AVeuk zjmT-?!)4wDJbeg3cPuSI9-~8Qj3vjoO^5Pttxe<&(usO|0#1TtJ&2w=g!~X5g@uKX zowDwv4E_jq1E3stJxxCLs*#ZqG=Nh;Uo?8}L4P4|*gC}TK3vl6%ewPpYiq41O&C4P!|z*w>5JSYFEjVZ zD?21QL?PnX-42;jE&cS*Jh}6xtE1&h`{!KKtfes~VmIyr)&^ob>4sZL-2i$H;_x03 zp5ty{&=xf9r0Qag;ndBH;nJ^R(yhpY?kWS2_j-lhR0E5;DIS`SL7?DbEMGlah)(jY ztgPIrv0tB&bwWmkho{!-mznnffnf3p`d7e7Uk37Gcj>m*fER7Uyt2Ro*Q4^5jG)Ot zMqXacu@5@omoHzA)?B3%vNou~B?-GK{lReMip*YAY$ob6C2-t6`0}^ zLH>XI@kbDn;@mk1{ek}e`wJgFf$%ejmhu@c%0%HzO;SKFqUJFV z7YOCe`s3>xJe%bU_#$`23gWZB>A17gNgXxJV@(6M=GIne%){3tnMDmda=&olmv3*_ zU9%8TF)&!0n=|d&SZ?8ulMpa@@#>Xwl2oGM^ylWM>l$kDhj`fvEtT@&!v_j>jqskk zwMa%qp`C03IzpR-hTdLP-l$Netg{_Cn1qHwRcfzO0eisB0n;u2%zC`4=k9P(!<~HH z%J+SJ-@kr(8%8gB^~#lD7~f3AM1|ZCnyp_T|M0G8T4z?Q34R6og_VUR28-3NcUSM% zFvPq|kqV)D;ruaPEgtySg@uKVSgyRBJTEV=S)MlYSNd4UESVCV!K5F*%c0)ut?7q@+_x&yw$&(9Cj0nN$q@bIr6UlP&r!09L@@^y`k>5emt zh!}Vn2yt_(dFjts{MuRt_B8<)BuYZd*Eunv??E+|j%zD~tRf6hm7kwqFfdGHN5-R< zwuw@m2$A#zDkzZ8?Qe3_ve1DrGR6&dwSFV32<*1u`uFd$4hYutvNE*(KENi+6(1?7 zfSoBjTcI=*!X*u*8dJZsl$S1mI1a$c@|C@+YVXeGveokb%_r48kdYHLfS4qPq7d8# zrOYJl;df`SUBP&dms;C}Z> z32%LWX?x=Y9to*ZZ?gKA-=IATzm#B?Q%pGuq-Ibk!ShzoQ7&^9+1gR*E z^|?ellm-;AKxor=qZEPV0(#nJ!q78C*!5X7Q1}SM%9-o5yuwo;j`kl<+}-s|-XXe$ z*%#dnq(cmozvQxHp>?3yoSy{xMO$0Dd_44sy$%)z1lUoAYJw&uC`uta(jcgsWoBh{ zyeT$rg=dweMwkKVia5q7D5%}82q0?7Ifjqciw{AXhjM)V{g?vcV|Y;O6~A~Mi%1%+ zvycaq9h0Zn;&_WN|+*@(0v(2i?@C%_(|_gSN4%1Mj%4xcDKJTiPi(8LwAS9k(|vTwD&uK zXfhaS^l5ZIgG3_boV~df=|RgBS5b+u1eyJoe0WMqQSzD4kP!6jD!|Brcj-57o&+V? z9_STV1QCM>M1ho!PC_J=kB<-Uv2V~-Exv*V1ZEEV^XE_eTD^SvvSk-Mw=Cr?Og;lE zt91DZ1ma_}DqxPaTe8LZrX>+aF@gim;S#>MINOugB>CZOUjp{!o>WyPqZg^GSR>=M+_%6X2T_l`&QTALts+3IL_oH}T!UuCa_JL56yw%s z11Lp$0W1!!sTqdHyF>_kgrk$wfbYZ22J2z1oO6mP!-#z@~`QC#LJ~vQmMv4<~Kytfv)1W#FJG=ZI*>U*gXIPiz z&fWgm@zyXC42+Be(y;#cvQQA(H$q4d2+Gr5D%}sxQ&KiS0x5Ss`y(SWGZJ<8EV2*x zCq|oLNBHVT0qvNSq^Ik8OwM`u-1+l)jd}=k8?5Y|JC!As_JCY%mdruk1tJYZV9<3q zeK3b~HZ*|@ zRzb7i-<07^Yinyq2m95lYd{W}a3g#PotFC9HpAlReRjXSxE&b1mJQtq)&hA(b}($4 zHw#7cV{PK;vuC8Fq=U)h+#`Quo5+pOS436GJ6cOek(ebGWo@gp-cCd~Z8E-T6x z_-;`7<9H&$y|6sr+6S84EU^DTcz&350gQW*J*X1gdcFT3fl5 z=^*+--n@AORLsC&m-zu+V6RrWBl9rr_uAnOhw>3Hx-!*h=!~ZX`r;}*@N$v*!F@=5 zADKY*+I-~-uzJVI&P~0v8Ks!AmxEW%c9yxo3zRhrxz93N(ZnQusajTEzAw4+=+15e7jutav=GC7B9nv@bW-rJt>-Hg zy#D^TC8#Kz^pgH9LgEEcJQajE6am3Ng_kDb zF)%XtA;c?jfB(oe#pifDZIr-VA7@97ppmP`kltdKzJQ!wL4Fr632$I%wY3Rvd_SI$ zjwV2Q<1dG{j`}4jUa&m;ocekG{0r1Mp{8Fcqmr4n2$xt_#cmOH_Ap9Z!<>42Kn#+y;hta@?FDC$ULv{n z#~Sw%SI?c>oU<;KFy@Y7(vUJ%6DNmP%2$p5Qv*CR4rOw@!X}Z!u*&}RdUA8ok1tbP zL(r!L>&bvAikg~waHVTUT-xR0>C<1|3x5FuS{;oJy3wBOv)y4R0wUhp`cKe+R4sQ` z?R@$MSH`QIChOcHQA9^SfWj_udJ3EgfWFvV!SLY(^&-GD%Ku2)&Kb37aOvrnnudag zx#>2%&aS*KCZ@rOwg4yqYE^8Y^DN`J?TGzIU9%8xURlFFE_WW=uV1={z0Xc%;nxj zA0MacR6|68UbSlPE~u$2Pbr8`SiQ>FH4&s1tiO2Eor#I5zrTN>GDG6rhFNbXFa%)| zQsB;Vc6D_X8~~}*T+|c%At2&;Jp@bNyUynY{!Q0nzqF9|R@GU>tmN^3L4T$)NvZBP zH~(w=Sb`H&FieRxE~`6+2b+e*Eg{;)_x$&zTh7;3W8VN%F+Dv!D;;doCJ5!cX7V0R zPtbV%jrEqfbxUNrSkLf+vIIssen(xZ@D3SS*wo&d>!^E=J?BTJE5i2|z793+s}}}- z{P+=6M8D@jD?nA@E=CXOP9|>pD?zJKMiChq*=ciE8w12cVq_$P@O5u?nd`_#W?|vl zy1HE`o{rdyf{qfu3VC`+FW%5nRb`-k;O_pUC_4;t61%^Wj?O5w&pxmsz! zh2qGIL0AL@Y`*EK(J?Q-nQpS=ixDu@V#NW;wv$Eo#URI15h!yR z+0Rwg)w0BQdL>5f!B_%)%M#;uaY-e9x%p>@w4W`tub@2gR$L659iS-GsJ z<#$9zCv;R-_KK*eXa~{>Tq+|6z@eDe@oTfu%gXMFqg!TzwFPvY9vWSqB4J4H`E!v{ zo22;vjBg50_b7<6Gq*m=jZ5as=$<`u<|QWdDE7Mc`~EzX zVpxkQ*WXK+M+y@e6_xQ}Xi`H%qhTG#-W%`}>UXFsLq!9yP!`5}+uc4Drgj>XLa|(i zFxvz?FZ&C>%SOyrcg+deWz8qUNGKHMj1HVbTj& zBn`k&nqDUiW=Zd?ji>6=QLLt)thsWf`x^}v@`DdW^PEkW2OPt!kvfD zL-w8*&r4Lc0x<}nd!cv%w0UGU_d}RBAyYoHr&Zz?A~nd#ezw;X6ZsSkF&o%79}8&c zS&xQS%WL`=7X+zHp&FbE-kShX2{BH@oFG>Dbz`v)zd(Ap-PTR^h}0m3(DrcF*?)RN zloKNK*qW`YO3aT4%ty=-OOzGsJ$^A%?1$N~G zCA`89479E&xQJ}dX#sfT^8WS|N}2b1JF6pg-156*W$AhkD`l9L;%h7X92_ftWFjBF z^p~q*(9*>`65yF#(glYY?>-I$5<~CpS>R|sy$wwK#R*#HT3=Qe@6qsRwVjNxBr^c| z1+rQl6K_;gsqPbZ6n_5vx%0Wbxp_9r8hiJ&VBtM1zIf7vSg+sX*F2IMf=#UeDc7oKz(e3Ms6t&t~*37vY0+Db>2J9D!D zeQwwlmo8PGy(SUtXrm!i{bWXNzst^>u>-aQOn?Z$+`(LxfxG^|x^-rfMr9$0wOz6o zMBWi>84-(8($Cn&T}dyd*w8X27|pr)`SY~2?4e}K>QcP6kAN7gRQ=F4G`++V*2V{sIaQM2o33J>+X(yzWc-H zptAbugx(2!W&pYtNERy55SyzsoYCAw=S_(v|Ai5MPCpvF3u$VnIZ$?OvYx$JY*~SS zGNkQ`POVEp)O^wNIeo^GA~*E_Rey8q808tFvw+CT1bUA(Z%CUc5pwjxkXN5PSk>0bGzWf7l0P-^Mp7_P5s8*Wu}4Q-$nF`=5UuWME)`6$sAUe;nlbAN|#0C$Lizf~7Ut z;y~dwHXZ;)9U=<+q`Z<6OK{U=cu5O;cLLC3{*ro2*uf?GTkDk$6@vb&X$^LgKufRc z7B_Z@jm-k;6w|rWa8raYX9+m=0Cf@5W*5hU&dyNa3&VjxSQA?us6J$uqP4lvd{$PAXJXwyMOG-|M|sV2TnWR6-MGkc~yFZb^3U!Dztdm z%ftjt&pJrtm{KoFVA{m-p4%Y&)527KrUr^Tbs>O!2#5y;1`ZAm;Iii4*N4`ssByohw$=l>aaa`$ zV{zkSP(mwh#{S&YLwdL!CV{duk>3uK3h>W6Y8)CQTad<{=cZHgeLetW({j1Ol4Wx# zFb=qOkmQ;)Jsu613bn@DGfzmjbVR3-C2B)6ziW$ji&)+`z$B2f7;% zUZtlq2@8+)^{M4r`VnGdVrqV?d!==CHvk-7zkZ#S6|yCt_3)*@sJ*rr5Du}(1K1C= zbVYUI$#Wo3w7K3hQPe%e#R)(0w+(|z1v_0pR@JM=yMCzNLf6Ad-!m>tlNZu{Q%m3< z4`ojE?0Som8ufLG9F|(=KNA345Wm-OhC!qtJ}vwcPHzBf2_Lnzht%-Bp5^)gm7?_kLtt zj6|t>$M^;6eIe@9ucL^0j-AcGJ))yWISm`?^79o24Z%vhKGO~Zj#w~LzQ@8-S z+;V|!T56dJ*9AVnOtps5PvvQH2|ixIyJT5@xPa4DP@`5zPfxGE2gISgXC-%EAF+`q z=h|KXpWh%|a|v$u>svC%Du63EM;VzQzMAH7a?YgmVi-9yr(?VVHPLPlv8o!0Xn4~0YRpL{&gF!i3Zs!O?eBS&E zl?{2oc#oty@wqAc^_A{xy#q@SnST=N`&2TIU07ySAin# zKdp0}kUi|4?>8}LA${n|!B!2s8=Us0Fl#|`O1)Ybj-=9dKbn+eY%4>u3U$e8gmU}= zGk88>cbDbJ{~H6TUxWj%vNbs6fX{Goaw0x?a%Uketlzl3#pA3CxWb-Y)op2M0b8@- z1q|#a!*4U#+M?_Ok3GNlhtYABQ`g)+{D}=nD0(H{y&+djh8foqF`M>FxN+0!ndFT$ zl*wg;vh6Sr=N>Bpn)P^oI|HkWJu_m#X6OKpKM*()bmr?M5wB4&`@(<~bQ{fmNw zW4}#Y|M{W(TPpeM#J|qJOb*%izlprxSp}b?@$4SnD+Xu${UiQ-SGG!;MuCo`5FE#2 z13ss@P75_@SQx)H7C|jnHZZW=W`})7dL<^ZPB|gpCh<=JUlmW3jo)pEd6ApD1W*OU zqR`M#aIXD+)D?w)C)~*dpimUzu6-IT&>h{f*#L)yI`ZsaPVwp4S&H-Lf2@pfPa9)Q zOzQS`mef8Ke;(9-td6i|8N6hhc>v9q57WA#BU2tMX{izrvGEW z9vB#~3~e_6GkPq?14?gq_Zp7{J#p5aVb z6EG6MY89uV@TAw|`vhKg6sq?wYo*;(%m;j0BpIY#DR}wv&D*z(AW=_QgFC9=84BNS z?ZN2m?d~2|!%t$8KBmm55n3SN0=U@Oqc(@Y1O#@7%1T$*4KP3iPUNyw6cn1#G0CTt zit(uCkGEi)V99wxPdszFBoEGjVg+2;jMnOsy@P{_^cUDuBZPZ&l(>vjvTh~~Qsdbi z{D=vZ<3)~1iul~X zZ9RMqSiQZ#;$`)CD5I-jhJnNkaRReJ@3j%(9zFWN?hQQQKo6$l(1DPb*7=|9bN6&$ zKLQK=HWA_#SKPG=K;>4A5+L^HFB7>9z~0tbIqmJW+ApL1IW!^U*7rWmg`W)tRsk?J z;(ay>sA`D)(LMz1;Q}mDat1E{z1*8f(NfW~q5PA>U{}_0f-a7Lkf<~HChR@||5HnA z>#v=inwpw`fPiEXFP+B6kew!BHz>F-&rnjD+KxI*JGPH=%nWwN+s-~-Eawz7tNE5( zaPC=*U$_*k+9VYWp6f2AF2|ElP=p3G<6(CO-X+1#z6`k>Mm8#8ABu+#FF32lqNC4k zo_kbn(Hg3rIjP;Y{G59t;Y6R6uH*arP98+~%%!7?c}D~aRiR;{ftJ?7(vtg+HGM@4 zXoBy4d-Ye@p`;}IA;T+*Hx*eMjXD@(i%lsVJDf6gxsBXysr!r31wwvP_-LyLAq4gouD0+CsXb9JUF~Gsx+M6{Qdo9GisY^ z!9K+`Jg7xHAV4zK0rdrQVrl*4e_S|5_{SH#CP?h&H>Y6t?#}_PFoX2~T6lJd2+><_ z=$%VTOPz%#0058#>@EFzj32()rWuYIk_7GvIjjYIUDxXGuY1M$-?oZ(o5QI|M{K{PT~G_+5+e=eLaYkB^6E{66|W mC;z_1|Mj~6zh6>z@qi3r?{;y3$M5%3$x13o6pG({^#1_5+QR|> literal 16722 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 Sn>fO4%1SEUD!gI*=>Gtg;~ Date: Mon, 10 Dec 2018 15:54:21 -0800 Subject: [PATCH 17/19] Serialization: code tweaks per @seelabs review --- content/_code-samples/tx-serialization/serialize.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/content/_code-samples/tx-serialization/serialize.py b/content/_code-samples/tx-serialization/serialize.py index 695c1308f8..ecc51fb6c2 100755 --- a/content/_code-samples/tx-serialization/serialize.py +++ b/content/_code-samples/tx-serialization/serialize.py @@ -1,4 +1,4 @@ -#!/bin/env python3 +#!/usr/bin/env python3 # Transaction Serialization Sample Code (Python3 version) # Author: rome@ripple.com @@ -52,12 +52,16 @@ def field_id(field_name): """ Returns the unique field ID for a given field name. This field ID consists of the type code and field code, in 1 to 3 bytes - depending on whether those values are "common" (<16) or uncommon (>=16) + depending on whether those values are "common" (<16) or "uncommon" (>=16) """ field_type_name = DEFINITIONS["FIELDS"][field_name]["type"] type_code = DEFINITIONS["TYPES"][field_type_name] field_code = DEFINITIONS["FIELDS"][field_name]["nth"] + # Codes must be nonzero and fit in 1 byte + assert 0 < field_code <= 255 + assert 0 < type_code <= 255 + if type_code < 16 and field_code < 16: # high 4 bits is the type_code # low 4 bits is the field code @@ -132,15 +136,18 @@ def amount_to_bytes(a): """ Serializes an "Amount" type, which can be either XRP or an issued currency: - XRP: 64 bits; 0, followed by 1 ("is positive"), followed by 62 bit UInt amount - - Issued Currency: 64 bits of amount, followed by + - Issued Currency: 64 bits of amount, followed by 160 bit currency code and + 160 bit issuer AccountID. """ if type(a) == str: # is XRP xrp_amt = int(a) if (xrp_amt >= 0): + assert xrp_amt <= 10**17 # set the "is positive" bit -- this is backwards from usual two's complement! xrp_amt = xrp_amt | 0x4000000000000000 else: + assert xrp_amt >= -(10**17) # convert to absolute value, leaving the "is positive" bit unset xrp_amt = -xrp_amt return xrp_amt.to_bytes(8, byteorder="big", signed=False) From 682c090ba4f4c7dfa17985d6648090e3ae2c4d09 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 10 Dec 2018 17:06:00 -0800 Subject: [PATCH 18/19] Serialization: move some subsections around for better hierarchy & add links --- .../api-conventions/serialization.md | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index 9ff6a05ddf..91e379b583 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -5,19 +5,21 @@ This page describes the XRP Ledger's canonical binary format for transactions an The process of serializing a transaction from JSON or any other representation into their canonical binary format can be summarized with these steps: -1. Make sure all required fields are provided, including any required but "auto-fillable" fields. +1. Make sure all required fields are provided, including any required but ["auto-fillable" fields](transaction-common-fields.html#auto-fillable-fields). + + The [Transaction Formats Reference](transaction-formats.html) defines the required and optional fields for XRP Ledger transactions. **Note:** The `SigningPubKey` must also be provided at this step. When signing, you can derive this key from the secret key that is provided for signing. -2. Convert each field's data into its "internal" binary format. +2. Convert each field's data into its ["internal" binary format](#internal-format). -3. Sort the fields in canonical order. +3. Sort the fields in [canonical order](#canonical-field-order). -4. Prefix each field with a Field ID. +4. Prefix each field with a [Field ID](#field-ids). 5. Concatenate the fields (including prefixes) in their sorted order. -The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. After signing, you must re-serialize the transaction with the `TxnSignature` field included. +The result is a single binary blob that can be signed using well-known signature algorithms such as ECDSA (with the secp256k1 elliptic curve) and Ed25519. For purposes of the XRP Ledger, you must also [hash][Hash] the data with the appropriate prefix (`0x53545800` if single-signing, or `0x534D5400` if multi-signing). After signing, you must re-serialize the transaction with the `TxnSignature` field included. **Note:** The XRP Ledger uses the same serialization format to represent other types of data, such as [ledger objects](ledger-object-types.html) and processed transactions. However, only certain fields are appropriate for including in a transaction that gets signed. (For example, the `TxnSignature` field, containing the signature itself, should not be present in the binary blob that you sign.) Thus, some fields are designated as "Signing" fields, which are included in objects when those objects are signed, and "non-signing" fields, which are not. @@ -65,7 +67,7 @@ The following table defines the top-level fields from the definitions file: | Field | Contents | |:----------------------|:-----------------------------------------------------| -| `TYPES` | Map of data types to their "type code" for constructing field IDs and sorting fields in canonical order. Codes below 1 should not appear in actual data; codes above 10000 represent special "high-level" object types such as "Transaction" that cannot be serialized inside other objects. | +| `TYPES` | Map of data types to their ["type code"](#type-codes) for constructing field IDs and sorting fields in canonical order. Codes below 1 should not appear in actual data; codes above 10000 represent special "high-level" object types such as "Transaction" that cannot be serialized inside other objects. See the [Type List](#type-list) for details of how to serialize each type. | | `LEDGER_ENTRY_TYPES` | Map of [ledger objects](ledger-object-types.html) to their data type. These appear in ledger state data, and in the "affected nodes" section of processed transactions' [metadata](transaction-metadata.html). | | `FIELDS` | A sorted array of tuples representing all fields that may appear in transactions, ledger objects, or other data. The first member of each tuple is the string name of the field and the second member is an object with that field's properties. (See the "Field properties" table below for definitions of those fields.) | | `TRANSACTION_RESULTS` | Map of [transaction result codes](transaction-results.html) to their numeric values. Result types not included in ledgers have negative values; `tesSUCCESS` has numeric value 0; [`tec`-class codes](tec-codes.html) represent failures that are included in ledgers. | @@ -83,18 +85,12 @@ The field definition objects in the `FIELDS` array have the following fields: | `isSigningField` | Boolean | If `true` this field should be serialized when preparing a transaction for signing. If `false`, this field should be omitted from the data to be signed. (It may not be part of transactions at all.) | | `type` | String | The internal data type of this field. This maps to a key in the `TYPES` map, which gives the [type code](#type-codes) for this field. | - - -## Canonical Field Order - -All fields in a transaction are sorted in a specific order based first on the field's type, then on the field itself (a "field code"). (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) - ### Field IDs [[Source - Encoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L117-L148 "Source") [[Source - Decoding]](https://github.com/seelabs/rippled/blob/cecc0ad75849a1d50cc573188ad301ca65519a5b/src/ripple/protocol/impl/Serializer.cpp#L484-L509 "Source") -When you combine the type code and field code, you get the field's unique identifier, which is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and field codes it combines. See the following table: +When you combine a field's type code and field code, you get the field's unique identifier, which is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and field codes it combines. See the following table: | | Type Code < 16 | Type Code >= 16 | |:-----------------|:------------------------------------------------------------------------------|:--| @@ -110,20 +106,6 @@ When decoding, you can tell how many bytes the field ID is by which bits **of th **Caution:** Even though the Field ID consists of the two elements that are used to sort fields, you should not sort by the serialized Field ID itself, because the byte structure of the Field ID changes the sort order. -### Type Codes - -Each field type has an arbitrary type code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). - -For example, [UInt32 has sort order 2](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L59), so all UInt32 fields come before all [Amount fields with order 6](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L63). - -### Field Codes - -Each field has a field code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). - -For example, the `Account` field of a [Payment transaction][] [has sort code 1](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L219), so it comes before the `Destination` field which [has sort code 3](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L221). - -Field codes are reused for fields of different field types, but fields of the same type never have the same field code. When you combine the type code with the field code, you get the field's unique [Field ID](#field-ids). - ### Length Prefixing Some types of variable-length fields are prefixed with a length indicator. `Blob` fields (containing arbitrary binary data) are one such type. For a list of which types are length-prefixed, see the [Type List](#type-list) table. @@ -151,6 +133,28 @@ When decoding, you can tell from the value of the first length byte whether ther - If the first length byte has a value of 241 to 254, then there are three length bytes. +## Canonical Field Order + +All fields in a transaction are sorted in a specific order based first on the field's type (specifically, a numeric "type code" assigned to each type), then on the field itself (a "field code"). (Think of it as sorting by family name, then given name, where the family name is the field's type and the given name is the field itself.) + +### Type Codes + +Each field type has an arbitrary type code, with lower codes sorting first. These codes are defined in [`SField.h`](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/SField.h#L57-L74). + +For example, [UInt32 has type code 2](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L59), so all UInt32 fields come before all [Amount fields, which have type code 6](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/SField.h#L63). + +The [definitions file](#definitions-file) lists the type codes for each type in the `TYPES` map. + +### Field Codes + +Each field has a field code, which is used to sort fields that have the same type as one another, with lower codes sorting first. These fields are defined in [`SField.cpp`](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266). + +For example, the `Account` field of a [Payment transaction][] [has sort code 1](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L219), so it comes before the `Destination` field which [has sort code 3](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L221). + +Field codes are reused for fields of different field types, but fields of the same type never have the same field code. When you combine the type code with the field code, you get the field's unique [Field ID](#field-ids). + + + ## Type List Transaction instructions may contain fields of any of the following types: From a9fa0dcb8c2c4858051aa1e578fef3d408b3dbfd Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 10 Dec 2018 18:37:34 -0800 Subject: [PATCH 19/19] Serialization: Field ID diagrams, etc. --- content/_img-sources/field-id-diagrams.uxf | 1092 +++++++++++++++++ .../api-conventions/serialization.md | 6 +- img/field-id-common-type-common-field.png | Bin 0 -> 7210 bytes img/field-id-common-type-uncommon-field.png | Bin 0 -> 8739 bytes img/field-id-uncommon-type-common-field.png | Bin 0 -> 8418 bytes img/field-id-uncommon-type-uncommon-field.png | Bin 0 -> 8836 bytes 6 files changed, 1095 insertions(+), 3 deletions(-) create mode 100644 content/_img-sources/field-id-diagrams.uxf create mode 100644 img/field-id-common-type-common-field.png create mode 100644 img/field-id-common-type-uncommon-field.png create mode 100644 img/field-id-uncommon-type-common-field.png create mode 100644 img/field-id-uncommon-type-uncommon-field.png diff --git a/content/_img-sources/field-id-diagrams.uxf b/content/_img-sources/field-id-diagrams.uxf new file mode 100644 index 0000000000..fb0532a4d2 --- /dev/null +++ b/content/_img-sources/field-id-diagrams.uxf @@ -0,0 +1,1092 @@ + + + 10 + + UMLClass + + 240 + 210 + 20 + 30 + + 0 + + + + Relation + + 60 + 150 + 1220 + 30 + + lt=- + 10.0;10.0;1200.0;10.0 + + + Text + + 240 + 120 + 160 + 30 + + Type code < 16 + + + + Text + + 720 + 120 + 160 + 30 + + Type code >= 16 + + + + UMLClass + + 260 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 280 + 210 + 20 + 30 + + 1 + + + + UMLClass + + 300 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 330 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 350 + 210 + 20 + 30 + + 1 + + + + UMLClass + + 370 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 390 + 210 + 20 + 30 + + 0 + + + + Relation + + 230 + 240 + 110 + 100 + + lt=. + + +type code +(4 bits, +nonzero) + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + Relation + + 320 + 240 + 110 + 100 + + lt=. + + +field code +(4 bits, +nonzero) + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + Relation + + 700 + 110 + 30 + 530 + + lt=- + 10.0;10.0;10.0;510.0 + + + Relation + + 60 + 330 + 1220 + 30 + + lt=- + 10.0;10.0;1200.0;10.0 + + + Text + + 80 + 230 + 130 + 30 + + Field code < 16 + + + + Text + + 240 + 170 + 160 + 30 + + 1 byte: + + + + Relation + + 220 + 120 + 30 + 520 + + lt=- + 10.0;10.0;10.0;500.0 + + + Text + + 80 + 420 + 140 + 30 + + Field code >= 16 + + + + UMLClass + + 810 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 830 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 850 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 870 + 210 + 20 + 30 + + 1 + + + + Relation + + 800 + 240 + 110 + 100 + + lt=. + + +field code +(4 bits, +nonzero) + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + UMLClass + + 720 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 740 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 760 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 780 + 210 + 20 + 30 + + 0 + + + + Relation + + 710 + 240 + 110 + 90 + + lt=. + + +high 4 bits +are 0 + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + Text + + 720 + 170 + 160 + 30 + + 2 bytes: + + + + UMLClass + + 910 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 930 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 950 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 970 + 210 + 20 + 30 + + 1 + + + + UMLClass + + 1010 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 990 + 210 + 20 + 30 + + 0 + + + + UMLClass + + 1030 + 210 + 20 + 30 + + 1 + + + + UMLClass + + 1050 + 210 + 20 + 30 + + 1 + + + + Relation + + 900 + 240 + 190 + 90 + + lt=. + + +type code +(8 bits) + 10.0;10.0;10.0;30.0;170.0;30.0;170.0;10.0 + + + Text + + 240 + 350 + 160 + 30 + + 2 bytes: + + + + Relation + + 230 + 420 + 110 + 100 + + lt=. + + +type code +(4 bits, +nonzero) + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + UMLClass + + 240 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 260 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 280 + 390 + 20 + 30 + + 1 + + + + UMLClass + + 300 + 390 + 20 + 30 + + 0 + + + + Relation + + 320 + 420 + 110 + 90 + + lt=. + + +next 4 bits +are 0 + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + UMLClass + + 390 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 370 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 350 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 330 + 390 + 20 + 30 + + 0 + + + + Relation + + 420 + 420 + 190 + 90 + + lt=. + + +field code +(8 bits) + 10.0;10.0;10.0;30.0;170.0;30.0;170.0;10.0 + + + UMLClass + + 430 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 450 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 470 + 390 + 20 + 30 + + 1 + + + + UMLClass + + 490 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 510 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 530 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 550 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 570 + 390 + 20 + 30 + + 1 + + + + Text + + 720 + 350 + 160 + 30 + + 3 bytes: + + + + UMLClass + + 810 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 830 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 850 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 870 + 390 + 20 + 30 + + 0 + + + + Relation + + 800 + 420 + 110 + 90 + + lt=. + + +next 4 bits +are 0 + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + UMLClass + + 780 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 760 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 740 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 720 + 390 + 20 + 30 + + 0 + + + + Relation + + 710 + 420 + 110 + 90 + + lt=. + + +high 4 bits +are 0 + 10.0;10.0;10.0;30.0;90.0;30.0;90.0;10.0 + + + Relation + + 900 + 420 + 190 + 90 + + lt=. + + +type code +(8 bits) + 10.0;10.0;10.0;30.0;170.0;30.0;170.0;10.0 + + + UMLClass + + 910 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 930 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 950 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 970 + 390 + 20 + 30 + + 1 + + + + UMLClass + + 990 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1010 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1030 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1050 + 390 + 20 + 30 + + 0 + + + + UMLNote + + 440 + 200 + 170 + 80 + + Example: +"Sequence" (UInt32) + Type code 2 + Field code 4 + +bg=#FED74C +transparency=0 + + + + UMLNote + + 440 + 530 + 170 + 80 + + Example: +"SetFlag" (UInt32) + Type code 2 + Field code 33 + +bg=#FED74C +transparency=0 + + + + UMLNote + + 1090 + 210 + 170 + 80 + + Example: +"Paths" (PathSet) + Type code 18 + Field code 1 + +bg=#FED74C +transparency=0 + + + + Relation + + 1080 + 420 + 190 + 90 + + lt=. + + +field code +(8 bits) + 10.0;10.0;10.0;30.0;170.0;30.0;170.0;10.0 + + + UMLClass + + 1090 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1110 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1130 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1150 + 390 + 20 + 30 + + 1 + + + + UMLClass + + 1170 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1190 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1210 + 390 + 20 + 30 + + 0 + + + + UMLClass + + 1230 + 390 + 20 + 30 + + 0 + + + + UMLNote + + 1090 + 530 + 170 + 80 + + Example: +"TickSize" (UInt8) + Type code 16 + Field code 16 + +bg=#FED74C +transparency=0 + + + diff --git a/content/references/rippled-api/api-conventions/serialization.md b/content/references/rippled-api/api-conventions/serialization.md index 91e379b583..d997e3a279 100644 --- a/content/references/rippled-api/api-conventions/serialization.md +++ b/content/references/rippled-api/api-conventions/serialization.md @@ -45,7 +45,7 @@ The serialization processes described here are implemented in multiple places an - In C++ [in the `rippled` code base](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/STObject.cpp). - In JavaScript in the [`ripple-binary-codec`](https://github.com/ripple/ripple-binary-codec/) package. -- In Python 3 [this repository's code samples section]({{target.github_forkurl}}/blob/{{target.github_branch}}/content/_code-samples/tx-serialization/serialize.py). +- In Python 3 in [this repository's code samples section]({{target.github_forkurl}}/blob/{{target.github_branch}}/content/_code-samples/tx-serialization/serialize.py). These implementations are all provided with permissive open-source licenses, so you can import, use, or adapt the code for your needs in addition to using it alongside these documents for learning purposes. @@ -94,8 +94,8 @@ When you combine a field's type code and field code, you get the field's unique | | Type Code < 16 | Type Code >= 16 | |:-----------------|:------------------------------------------------------------------------------|:--| -| **Field Code < 16** | 1 byte: high 4 bits define type; low 4 bits define field. | 2 bytes: low 4 bits of the first byte define field; next byte defines type | -| **Field Code >= 16** | 2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field | 3 bytes: first byte is 0x00, second byte defines type; third byte defines field | +| **Field Code < 16** | ![1 byte: high 4 bits define type; low 4 bits define field.](img/field-id-common-type-common-field.png) | ![2 bytes: low 4 bits of the first byte define field; next byte defines type.](img/field-id-uncommon-type-common-field.png) | +| **Field Code >= 16** | ![2 bytes: high 4 bits of the first byte define type; low 4 bits of first byte are 0; next byte defines field](img/field-id-common-type-uncommon-field.png) | ![3 bytes: first byte is 0x00, second byte defines type; third byte defines field](img/field-id-uncommon-type-uncommon-field.png) | When decoding, you can tell how many bytes the field ID is by which bits **of the first byte** are zeroes. This corresponds to the cases in the above table: diff --git a/img/field-id-common-type-common-field.png b/img/field-id-common-type-common-field.png new file mode 100644 index 0000000000000000000000000000000000000000..386799beb2e64e4ba2ce94ed6de498ab26908afd GIT binary patch literal 7210 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 literal 0 HcmV?d00001 diff --git a/img/field-id-common-type-uncommon-field.png b/img/field-id-common-type-uncommon-field.png new file mode 100644 index 0000000000000000000000000000000000000000..c88a45551c0a8a6daaf99398a38f0199d0b5a3fa GIT binary patch literal 8739 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 NszFst?>zYPe*mg}Lhk?o literal 0 HcmV?d00001 diff --git a/img/field-id-uncommon-type-common-field.png b/img/field-id-uncommon-type-common-field.png new file mode 100644 index 0000000000000000000000000000000000000000..9f945b40c3456ff96ff6302c67b6bbc70a0c20f8 GIT binary patch literal 8418 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 F{{=5*XP*E7 literal 0 HcmV?d00001