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
31 changes: 31 additions & 0 deletions src/common/hashes/hashprefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* 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".
*/

// transaction plus signature to give transaction ID
export const HASH_TX_ID = 0x54584E00 // 'TXN'
Stormtv marked this conversation as resolved.
Show resolved Hide resolved
// transaction plus metadata
export const HASH_TX_NODE = 0x534E4400 // 'TND'
// inner node in tree
export const HASH_INNER_NODE = 0x4D494E00 // 'MIN'
// inner node version 2
export const HASH_INNER_NODE_V2 = 0x494E5200 // 'INR'
// leaf node in tree
export const HASH_LEAF_NODE = 0x4D4C4E00 // 'MLN'
// inner transaction to sign
export const HASH_TX_SIGN = 0x53545800 // 'STX'
// inner transaction to sign (TESTNET)
export const HASH_TX_SIGN_TESTNET = 0x73747800 // 'stx'
// inner transaction to multisign
export const HASH_TX_MULTISIGN = 0x534D5400 // 'SMT'

export const HASH_LEDGER = 0x4C575200
Stormtv marked this conversation as resolved.
Show resolved Hide resolved
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 hash from './sha512hash'
import {HASH_TX_ID, HASH_TX_SIGN, HASH_LEDGER} from './hashprefix'
import {SHAMap, TYPE_TRANSACTION_MD, TYPE_ACCOUNT_STATE} 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 = HASH_TX_ID.toString(16).toUpperCase()
return hash(prefix + txBlobHex)
}

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

export const computeBinaryTransactionSigningHash = (txBlobHex: string): string => {
const prefix = HASH_TX_SIGN.toString(16).toUpperCase()
return hash(prefix + txBlobHex)
}

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

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

export const computeSignerListHash = (address: string): string => {
return hash(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 hash(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 hash(prefix + lowAddressHex + highAddressHex +
currencyToHex(currency))
}

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

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

return shamap.hash
}

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

entries.forEach((ledgerEntry) => {
const data = encode(ledgerEntry)
shamap.add_item(ledgerEntry.index, data, TYPE_ACCOUNT_STATE)
})

return shamap.hash
}

// see rippled Ledger::updateHash()
export const computeLedgerHash = (ledgerHeader): string => {
const prefix = HASH_LEDGER.toString(16).toUpperCase()
return hash(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 hash(ledgerSpaceHex('escrow') + addressToHex(address) +
intToHex(sequence, 4))
}

export const computePaymentChannelHash = (address, dstAddress, sequence): string => {
return hash(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'
}
4 changes: 4 additions & 0 deletions src/common/hashes/sha512hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {createHash} from 'crypto'
export default (hex: string): string => {
Stormtv marked this conversation as resolved.
Show resolved Hide resolved
return createHash('sha512').update(Buffer.from(hex, 'hex')).digest('hex').toUpperCase().slice(0, 64)
}
Loading