Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ripple hashes integration #1039

Merged
merged 12 commits into from
Oct 2, 2019
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"lodash": "^4.17.4",
"ripple-address-codec": "^3.0.4",
"ripple-binary-codec": "^0.2.4",
"ripple-hashes": "^0.3.4",
"ripple-keypairs": "^0.10.1",
"ripple-lib-transactionparser": "0.8.0",
"ws": "^3.3.1"
Expand Down
40 changes: 40 additions & 0 deletions src/common/hashes/hash-prefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Prefix for hashing functions.
*
* These prefixes are inserted before the source material used to
* generate various hashes. This is done to put each hash in its own
* "space." This way, two different types of objects with the
* same binary data will produce different hashes.
*
* Each prefix is a 4-byte value with the last byte set to zero
* and the first three bytes formed from the ASCII equivalent of
* some arbitrary string. For example "TXN".
*/

enum HashPrefix {
// transaction plus signature to give transaction ID
TRANSACTION_ID = 0x54584E00, // 'TXN'

// transaction plus metadata
TRANSACTION_NODE = 0x534E4400, // 'TND'

// inner node in tree
INNER_NODE = 0x4D494E00, // 'MIN'

// leaf node in tree
LEAF_NODE = 0x4D4C4E00, // 'MLN'

// inner transaction to sign
TRANSACTION_SIGN = 0x53545800, // 'STX'

// inner transaction to sign (TESTNET)
TRANSACTION_SIGN_TESTNET = 0x73747800, // 'stx'

// inner transaction to multisign
TRANSACTION_MULTISIGN = 0x534D5400, // 'SMT'

// ledger
LEDGER = 0x4C575200 // 'LWR'
}

export default HashPrefix
155 changes: 155 additions & 0 deletions src/common/hashes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import BigNumber from 'bignumber.js'
import {decodeAddress} from 'ripple-address-codec'
import sha512Half from './sha512Half'
import HashPrefix from './hash-prefix'
import {SHAMap, NodeType} from './shamap'
import {encode} from 'ripple-binary-codec'
import ledgerspaces from './ledgerspaces'

const padLeftZero = (string: string, length: number): string => {
return Array(length - string.length + 1).join('0') + string
}

const intToHex = (integer: number, byteLength: number): string => {
return padLeftZero(Number(integer).toString(16), byteLength * 2)
}

const bytesToHex = (bytes: number[]): string => {
return Buffer.from(bytes).toString('hex')
}

const bigintToHex = (integerString: string | number | BigNumber, byteLength: number): string => {
const hex = (new BigNumber(integerString)).toString(16)
return padLeftZero(hex, byteLength * 2)
}

const ledgerSpaceHex = (name: string): string => {
return intToHex(ledgerspaces[name].charCodeAt(0), 2)
}

const addressToHex = (address: string): string => {
return (Buffer.from(decodeAddress(address))).toString('hex')
}

const currencyToHex = (currency: string): string => {
if (currency.length === 3) {
let bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
bytes[12] = currency.charCodeAt(0) & 0xff
bytes[13] = currency.charCodeAt(1) & 0xff
bytes[14] = currency.charCodeAt(2) & 0xff
return bytesToHex(bytes)
}
return currency
}

const addLengthPrefix = (hex: string): string => {
const length = hex.length / 2
if (length <= 192) {
return bytesToHex([length]) + hex
} else if (length <= 12480) {
const x = length - 193
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex
} else if (length <= 918744) {
const x = length - 12481
return bytesToHex([241 + (x >>> 16), x >>> 8 & 0xff, x & 0xff]) + hex
}
throw new Error('Variable integer overflow.')
}

export const computeBinaryTransactionHash = (txBlobHex: string): string => {
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
return sha512Half(prefix + txBlobHex)
}

export const computeTransactionHash = (txJSON: any): string => {
return computeBinaryTransactionHash(encode(txJSON))
}

export const computeBinaryTransactionSigningHash = (txBlobHex: string): string => {
const prefix = HashPrefix.TRANSACTION_SIGN.toString(16).toUpperCase()
return sha512Half(prefix + txBlobHex)
}

export const computeTransactionSigningHash = (txJSON: any): string => {
return computeBinaryTransactionSigningHash(encode(txJSON))
}

export const computeAccountHash = (address: string): string => {
return sha512Half(ledgerSpaceHex('account') + addressToHex(address))
}

export const computeSignerListHash = (address: string): string => {
return sha512Half(ledgerSpaceHex('signerList') +
addressToHex(address) +
'00000000' /* uint32(0) signer list index */)
}

export const computeOrderHash = (address: string, sequence: number): string => {
const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1)
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
}

export const computeTrustlineHash = (address1: string, address2: string, currency: string): string => {
const address1Hex = addressToHex(address1)
const address2Hex = addressToHex(address2)

const swap = (new BigNumber(address1Hex, 16)).greaterThan(
new BigNumber(address2Hex, 16))
const lowAddressHex = swap ? address2Hex : address1Hex
const highAddressHex = swap ? address1Hex : address2Hex

const prefix = ledgerSpaceHex('rippleState')
return sha512Half(prefix + lowAddressHex + highAddressHex +
currencyToHex(currency))
}

export const computeTransactionTreeHash = (transactions: any[]): string => {
const shamap = new SHAMap()

transactions.forEach((txJSON) => {
const txBlobHex = encode(txJSON)
const metaHex = encode(txJSON.metaData)
const txHash = computeBinaryTransactionHash(txBlobHex)
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex)
shamap.addItem(txHash, data, NodeType.TRANSACTION_METADATA)
})

return shamap.hash
}

export const computeStateTreeHash = (entries: any[]): string => {
const shamap = new SHAMap()

entries.forEach((ledgerEntry) => {
const data = encode(ledgerEntry)
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
})

return shamap.hash
}

// see rippled Ledger::updateHash()
export const computeLedgerHash = (ledgerHeader): string => {
const prefix = HashPrefix.LEDGER.toString(16).toUpperCase()
return sha512Half(prefix +
intToHex(ledgerHeader.ledger_index, 4) +
bigintToHex(ledgerHeader.total_coins, 8) +
ledgerHeader.parent_hash +
ledgerHeader.transaction_hash +
ledgerHeader.account_hash +
intToHex(ledgerHeader.parent_close_time, 4) +
intToHex(ledgerHeader.close_time, 4) +
intToHex(ledgerHeader.close_time_resolution, 1) +
intToHex(ledgerHeader.close_flags, 1)
)
}

export const computeEscrowHash = (address, sequence): string => {
return sha512Half(ledgerSpaceHex('escrow') + addressToHex(address) +
intToHex(sequence, 4))
}

export const computePaymentChannelHash = (address, dstAddress, sequence): string => {
return sha512Half(ledgerSpaceHex('paychan') + addressToHex(address) +
addressToHex(dstAddress) + intToHex(sequence, 4))
}
25 changes: 25 additions & 0 deletions src/common/hashes/ledgerspaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

/**
* Ripple ledger namespace prefixes.
*
* The Ripple 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.
*/
export default {
account : 'a',
dirNode : 'd',
generatorMap : 'g',
rippleState : 'r',
offer : 'o', // Entry for an offer.
ownerDir : 'O', // Directory of things owned by an account.
bookDir : 'B', // Directory of order books.
contract : 'c',
skipList : 's',
amendment : 'f',
feeSettings : 'e',
signerList : 'S',
escrow : 'u',
paychan : 'x'
}
7 changes: 7 additions & 0 deletions src/common/hashes/sha512Half.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {createHash} from 'crypto'

const sha512Half = (hex: string): string => {
return createHash('sha512').update(Buffer.from(hex, 'hex')).digest('hex').toUpperCase().slice(0, 64)
}

export default sha512Half
Loading