diff --git a/src/api.ts b/src/api.ts index 8fb89398..4987230f 100644 --- a/src/api.ts +++ b/src/api.ts @@ -89,6 +89,19 @@ import {clamp, renameCounterpartyToIssuer} from './ledger/utils' import {TransactionJSON, Instructions, Prepare} from './transaction/types' import {ConnectionUserOptions} from './common/connection' import {isValidXAddress, isValidClassicAddress} from 'ripple-address-codec' +import { + computeBinaryTransactionHash, + computeTransactionHash, + computeBinaryTransactionSigningHash, + computeAccountLedgerObjectID, + computeSignerListLedgerObjectID, + computeOrderID, + computeTrustlineHash, + computeTransactionTreeHash, + computeStateTreeHash, + computeEscrowHash, + computePaymentChannelHash +} from './common/hashes' export interface APIOptions extends ConnectionUserOptions { server?: string @@ -402,6 +415,31 @@ class RippleAPI extends EventEmitter { static isValidXAddress = isValidXAddress static isValidClassicAddress = isValidClassicAddress + /** + * Static methods that replace functionality from the now-deprecated ripple-hashes library + */ + // Compute the hash of a binary transaction blob. + static computeBinaryTransactionHash = computeBinaryTransactionHash // (txBlobHex: string): string + // Compute the hash of a transaction in txJSON format. + static computeTransactionHash = computeTransactionHash // (txJSON: any): string + static computeBinaryTransactionSigningHash = computeBinaryTransactionSigningHash // (txBlobHex: string): string + // Compute the hash of an account, given the account's classic address (starting with `r`). + static computeAccountLedgerObjectID = computeAccountLedgerObjectID // (address: string): string + // Compute the hash (ID) of an account's SignerList. + static computeSignerListLedgerObjectID = computeSignerListLedgerObjectID // (address: string): string + // Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction. + static computeOrderID = computeOrderID // (address: string, sequence: number): string + // Compute the hash of a trustline, given the two parties' classic addresses (starting with `r`) and the currency code. + static computeTrustlineHash = computeTrustlineHash // (address1: string, address2: string, currency: string): string + static computeTransactionTreeHash = computeTransactionTreeHash // (transactions: any[]): string + static computeStateTreeHash = computeStateTreeHash // (entries: any[]): string + // Compute the hash of a ledger. + static computeLedgerHash = computeLedgerHash // (ledgerHeader): string + // Compute the hash of an escrow, given the owner's classic address (starting with `r`) and the account sequence number of the `EscrowCreate` escrow transaction. + static computeEscrowHash = computeEscrowHash // (address, sequence): string + // Compute the hash of a payment channel, given the owner's classic address (starting with `r`), the classic address of the destination, and the account sequence number of the `PaymentChannelCreate` payment channel transaction. + static computePaymentChannelHash = computePaymentChannelHash // (address, dstAddress, sequence): string + xrpToDrops = xrpToDrops dropsToXrp = dropsToXrp rippleTimeToISO8601 = rippleTimeToISO8601 diff --git a/src/common/hashes/README.md b/src/common/hashes/README.md index 8b4a27e6..008f724e 100644 --- a/src/common/hashes/README.md +++ b/src/common/hashes/README.md @@ -2,7 +2,7 @@ Methods to hash XRP Ledger objects -## Methods +## Computing a transaction hash (ID) ### computeBinaryTransactionHash = (txBlobHex: string): string @@ -12,19 +12,35 @@ Compute the hash of a binary transaction blob. Compute the hash of a transaction in txJSON format. +## [Hash Prefixes](https://xrpl.org/basic-data-types.html#hash-prefixes) + +In many cases, the XRP Ledger prefixes an object's binary data with a 4-byte code before calculating its hash, so that objects of different types have different hashes even if the binary data is the same. The existing 4-byte codes are structured as 3 alphabetic characters, encoded as ASCII, followed by a zero byte. + +Some types of hashes appear in API requests and responses. Others are only calculated as the first step of signing a certain type of data, or calculating a higher-level hash. Some of following methods internally use some of the 4-byte hash prefixes in order to calculate the appropriate hash. + ### computeBinaryTransactionSigningHash = (txBlobHex: string): string -### computeTransactionSigningHash = (txJSON: any): string +In order to single-sign a transaction, you must perform these steps: -### computeAccountHash = (address: string): string +1. Assuming the transaction is in JSON format (txJSON), `encode` the transaction in the XRP Ledger's binary format. +2. Hash the data with the appropriate prefix (`0x53545800` if single-signing, or `0x534D5400` if multi-signing). +3. After signing, you must re-serialize the transaction with the `TxnSignature` field included. + +The `computeBinaryTransactionSigningHash` helps with step 2, automatically using the `0x53545800` prefix needed for single-signing a transaction. + +For details, see [Serialization Format](https://xrpl.org/serialization.html). + +_Removed:_ `computeTransactionSigningHash`, which took txJSON as a parameter. It was part of the deprecated ripple-hashes library. If you have txJSON, `encode` it with [ripple-binary-codec](https://github.com/ripple/ripple-binary-codec) first. Example: `return computeBinaryTransactionSigningHash(encode(txJSON))` + +### computeAccountLedgerObjectID = (address: string): string Compute the hash of an account, given the account's classic address (starting with `r`). -### computeSignerListHash = (address: string): string +### computeSignerListLedgerObjectID = (address: string): string Compute the hash of an account's SignerList. -### computeOrderHash = (address: string, sequence: number): string +### computeOrderID = (address: string, sequence: number): string Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction. diff --git a/src/common/hashes/index.ts b/src/common/hashes/index.ts index b18a7000..2f493045 100644 --- a/src/common/hashes/index.ts +++ b/src/common/hashes/index.ts @@ -71,6 +71,14 @@ export const computeTransactionHash = (txJSON: any): string => { return computeBinaryTransactionHash(encode(txJSON)) } +/** + * Hash the given binary transaction data with the single-signing prefix. + * + * See [Serialization Format](https://xrpl.org/serialization.html) + * + * @param txBlobHex The binary transaction blob as a hexadecimal string + * @returns {string} The hash to sign + */ export const computeBinaryTransactionSigningHash = ( txBlobHex: string ): string => { @@ -78,21 +86,56 @@ export const computeBinaryTransactionSigningHash = ( return sha512Half(prefix + txBlobHex) } -export const computeTransactionSigningHash = (txJSON: any): string => { - return computeBinaryTransactionSigningHash(encode(txJSON)) -} - -export const computeAccountHash = (address: string): string => { +/** + * Compute Account Ledger Object ID + * + * All objects in a ledger's state tree have a unique ID. + * The Account Ledger Object ID is derived by hashing the + * address with a namespace identifier. This ensures every + * ID is unique. + * + * See [Ledger Object IDs](https://xrpl.org/ledger-object-ids.html) + * + * @param address The classic account address + * @returns {string} The Ledger Object ID for the account + */ +export const computeAccountLedgerObjectID = (address: string): string => { return sha512Half(ledgerSpaceHex('account') + addressToHex(address)) } -export const computeSignerListHash = (address: string): string => { +/** + * [SignerList ID Format](https://xrpl.org/signerlist.html#signerlist-id-format) + * + * The ID of a SignerList object is the SHA-512Half of the following values, concatenated in order: + * * The RippleState space key (0x0053) + * * The AccountID of the owner of the SignerList + * * The SignerListID (currently always 0) + * + * This method computes a SignerList Ledger Object ID. + * + * @param address The classic account address of the SignerList owner (starting with r) + * @return {string} The ID of the account's SignerList object + */ +export const computeSignerListLedgerObjectID = (address: string): string => { return sha512Half( ledgerSpaceHex('signerList') + addressToHex(address) + '00000000' ) // uint32(0) signer list index } -export const computeOrderHash = (address: string, sequence: number): string => { +/** + * [Offer ID Format](https://xrpl.org/offer.html#offer-id-format) + * + * The ID of a Offer object is the SHA-512Half of the following values, concatenated in order: + * * The Offer space key (0x006F) + * * The AccountID of the account placing the offer + * * The Sequence number of the OfferCreate transaction that created the offer + * + * This method computes an Offer ID (aka Order ID). + * + * @param address The classic account address of the SignerList owner (starting with r) + * @returns {string} The ID of the account's Offer object + */ +export const computeOrderID = (address: string, sequence: number): string => { const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1) return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4)) } diff --git a/src/common/hashes/ledgerspaces.ts b/src/common/hashes/ledgerspaces.ts index 0959b3ae..8777d213 100644 --- a/src/common/hashes/ledgerspaces.ts +++ b/src/common/hashes/ledgerspaces.ts @@ -1,10 +1,12 @@ /** - * Ripple ledger namespace prefixes. + * XRP Ledger namespace prefixes. * - * The Ripple ledger is a key-value store. In order to avoid name collisions, + * The XRP Ledger is a key-value store. In order to avoid name collisions, * names are partitioned into namespaces. * * Each namespace is just a single character prefix. + * + * See [LedgerNameSpace enum](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/LedgerFormats.h#L100) */ export default { account: 'a', @@ -16,9 +18,12 @@ export default { bookDir: 'B', // Directory of order books. contract: 'c', skipList: 's', + escrow: 'u', amendment: 'f', feeSettings: 'e', + ticket: 'T', signerList: 'S', - escrow: 'u', - paychan: 'x' + paychan: 'x', + check: 'C', + depositPreauth: 'p' } diff --git a/test/hashes-test.ts b/test/hashes-test.ts index 5bfcd0c5..6972d9d8 100644 --- a/test/hashes-test.ts +++ b/test/hashes-test.ts @@ -47,7 +47,7 @@ describe('Ledger', function() { var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh' var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8' - var actualEntryHash = hashes.computeAccountHash(account) + var actualEntryHash = hashes.computeAccountLedgerObjectID(account) assert.equal(actualEntryHash, expectedEntryHash) }) @@ -105,18 +105,18 @@ describe('Ledger', function() { var sequence = 137 var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3' - var actualEntryHash = hashes.computeOrderHash(account, sequence) + var actualEntryHash = hashes.computeOrderID(account, sequence) assert.equal(actualEntryHash, expectedEntryHash) }) }) - describe('computeSignerListHash', function() { + describe('computeSignerListLedgerObjectID', function() { it('will calculate the SignerList index for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw', function() { var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh' var expectedEntryHash = '778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF' - var actualEntryHash = hashes.computeSignerListHash(account) + var actualEntryHash = hashes.computeSignerListLedgerObjectID(account) assert.equal(actualEntryHash, expectedEntryHash) }) })