diff --git a/es/tx/builder/helpers.js b/es/tx/builder/helpers.js index e133fe2447..d710eca67f 100644 --- a/es/tx/builder/helpers.js +++ b/es/tx/builder/helpers.js @@ -36,6 +36,18 @@ export function buildContractId (ownerId, nonce) { return encode(b2bHash, 'ct') } +/** + * Build hash + * @function + * @alias module:@aeternity/aepp-sdk/es/tx/builder/helpers + * @param {String} prefix Transaction hash prefix + * @param {Buffer} data Rlp encoded transaction buffer + * @return {String} Transaction hash + */ +export function buildHash (prefix, data) { + return encode(hash(data), prefix) +} + /** * Build a oracle query id * @function @@ -206,5 +218,6 @@ export default { commitmentHash, formatSalt, oracleQueryId, - createSalt + createSalt, + buildHash } diff --git a/es/tx/builder/index.js b/es/tx/builder/index.js index b83a7b6e62..3f50936153 100644 --- a/es/tx/builder/index.js +++ b/es/tx/builder/index.js @@ -14,7 +14,7 @@ import { VALIDATION_MESSAGE, VSN } from './schema' -import { readInt, readId, readPointers, writeId, writeInt, buildPointers, encode, decode } from './helpers' +import { readInt, readId, readPointers, writeId, writeInt, buildPointers, encode, decode, buildHash } from './helpers' import { toBytes } from '../../utils/bytes' import * as mpt from '../../utils/mptree' @@ -362,4 +362,16 @@ export function unpackTx (encodedTx, fromRlpBinary = false) { return { txType: OBJECT_ID_TX_TYPE[objId], tx: unpackRawTx(binary, schema), rlpEncoded, binary } } -export default { calculateMinFee, calculateFee, unpackTx, unpackRawTx, buildTx, buildRawTx, validateParams } +/** + * Build a transaction hash + * @function + * @alias module:@aeternity/aepp-sdk/es/tx/builder/helpers + * @param {String | Buffer} rawTx base64 or rlp encoded transaction + * @return {String} Transaction hash + */ +export function buildTxHash (rawTx) { + if (typeof rawTx === 'string' && rawTx.indexOf('tx_') !== -1) return buildHash('th', unpackTx(rawTx).rlpEncoded) + return buildHash('th', rawTx) +} + +export default { calculateMinFee, calculateFee, unpackTx, unpackRawTx, buildTx, buildRawTx, validateParams, buildTxHash } diff --git a/es/utils/crypto.js b/es/utils/crypto.js index 779ccc43ac..0d8e94747c 100644 --- a/es/utils/crypto.js +++ b/es/utils/crypto.js @@ -106,7 +106,7 @@ export function addressFromDecimal (decimalAddress) { /** * Calculate 256bits Blake2b hash of `input` * @rtype (input: String) => hash: String - * @param {String} input - Data to hash + * @param {String|Buffer} input - Data to hash * @return {Buffer} Hash */ export function hash (input) { diff --git a/test/unit/crypto.js b/test/unit/crypto.js index 3018b6389c..9852c5c2bf 100644 --- a/test/unit/crypto.js +++ b/test/unit/crypto.js @@ -21,6 +21,7 @@ import { assert, expect } from 'chai' import * as Crypto from '../../es/utils/crypto' import { addressToHex, encodeBase58Check } from '../../es/utils/crypto' +import { buildTxHash, unpackTx } from '../../es/tx/builder' // These keys are fixations for the encryption lifecycle tests and will // not be used for signing @@ -34,6 +35,9 @@ const txBinary = Buffer.from(txBinaryAsArray) const signatureAsArray = [95, 146, 31, 37, 95, 194, 36, 76, 58, 49, 167, 156, 127, 131, 142, 248, 25, 121, 139, 109, 59, 243, 203, 205, 16, 172, 115, 143, 254, 236, 33, 4, 43, 46, 16, 190, 46, 46, 140, 166, 76, 39, 249, 54, 38, 27, 93, 159, 58, 148, 67, 198, 81, 206, 106, 237, 91, 131, 27, 14, 143, 178, 130, 2] const signature = Buffer.from(signatureAsArray) +const txRaw = `tx_+QTlCwH4QrhA4xEWFIGZUVn0NhnYl9TwGX30YJ9/Y6x6LHU6ALfiupJPORvjbiUowrNgLtKnvt7CvtweY0N/THhwn8WUlPJfDrkEnPkEmSoBoQFj/aG9TnbDDSLtstOaR3E1i0Tcexu1UutStbkmXRBdzAG5A/j5A/VGAqAmKh8Xm79E6zTwogrUezzUmpJlC5Cdjc1KXtWLnJIrbvkC+/kBKqBo8mdjOP9QiDmrpHdJ7/qL6H7yhPIH+z2ZmHAc1TiHxYRtYWluuMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD5AcuguclW8osxSan1mHqlBfPaGyIJzFc5I0AGK7bBvZ+fmeqEaW5pdLhgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////uQFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQD//////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////////////////////////////////+4zGIAAGRiAACEkYCAgFF/uclW8osxSan1mHqlBfPaGyIJzFc5I0AGK7bBvZ+fmeoUYgAAwFdQgFF/aPJnYzj/UIg5q6R3Se/6i+h+8oTyB/s9mZhwHNU4h8UUYgAAr1dQYAEZUQBbYAAZWWAgAZCBUmAgkANgA4FSkFlgAFFZUmAAUmAA81tgAIBSYADzW1lZYCABkIFSYCCQA2AAGVlgIAGQgVJgIJADYAOBUoFSkFZbYCABUVFZUICRUFCAkFCQVltQUIKRUFBiAACMVoUzLjIuMIMEAAGGWa0Z+ZAAAAQBgxgX+IQ7msoAuGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAILnJVvKLMUmp9Zh6pQXz2hsiCcxXOSNABiu2wb2fn5nqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB95HF6'` +const expectedHash = 'th_HZMNgTvEiyKeATpauJjjeWwZcyHapKG8bDgy2S1sCUEUQnbwK' + describe('crypto', () => { describe('generateKeyPair', () => { it('generates an account key pair', () => { @@ -142,4 +146,9 @@ describe('crypto', () => { const fromHexAddress = 'ak_' + encodeBase58Check(Buffer.from(hex.slice(2), 'hex')) fromHexAddress.should.be.equal(address) }) + it('Can produce tx hash', () => { + const rlpEncodedTx = unpackTx(txRaw).rlpEncoded + buildTxHash(txRaw).should.be.equal(expectedHash) + buildTxHash(rlpEncodedTx).should.be.equal(expectedHash) + }) })