Skip to content

Commit

Permalink
feat(Account): Build signature from transaction hash (#1025)
Browse files Browse the repository at this point in the history
* feat(Validator): Add tx-hash signature validation

* feat(Validator): Add ability to sign transaction hash instead of full transaction. Add `signHash=true`

* chore(Linter): Remove unused import

* feat(Validator): Add ability to build encoded or raw transaction to `buildTxHash` helper.
Add test's for validating transaction with signature from txHash or RlpBinary

* chore(Linter): Fix linter errors in Validator test
  • Loading branch information
nduchak committed Jun 18, 2020
1 parent be6171c commit 2cb8cc2
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 16 deletions.
6 changes: 3 additions & 3 deletions es/account/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import stampit from '@stamp/it'
import { required } from '@stamp/required'

import { hash, personalMessageToBinary, decodeBase64Check, assertedType, verifyPersonalMessage } from '../utils/crypto'
import { buildTx } from '../tx/builder'
import { buildTx, buildTxHash } from '../tx/builder'
import { decode } from '../tx/builder/helpers'
import { TX_TYPE } from '../tx/builder/schema'
import { getNetworkId } from '../node'
Expand All @@ -40,11 +40,11 @@ import { getNetworkId } from '../node'
* @param {Object} opt - Options
* @return {String} Signed transaction
*/
async function signTransaction (tx, opt = {}) {
async function signTransaction (tx, opt = { signHash: true }) {
const networkId = this.getNetworkId(opt)
const rlpBinaryTx = decodeBase64Check(assertedType(tx, 'tx'))
// Prepend `NETWORK_ID` to begin of data binary
const txWithNetworkId = Buffer.concat([Buffer.from(networkId), rlpBinaryTx])
const txWithNetworkId = Buffer.concat([Buffer.from(networkId), opt.signHash ? buildTxHash(rlpBinaryTx, { raw: true }) : rlpBinaryTx])

const signatures = [await this.sign(txWithNetworkId, opt)]
return buildTx({ encodedTx: rlpBinaryTx, signatures }, TX_TYPE.signed).tx
Expand Down
5 changes: 3 additions & 2 deletions es/tx/builder/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ export function buildContractId (ownerId, nonce) {
* @alias module:@aeternity/aepp-sdk/es/tx/builder/helpers
* @param {String} prefix Transaction hash prefix
* @param {Buffer} data Rlp encoded transaction buffer
* @param {{ raw: boolean = false }} options Options
* @return {String} Transaction hash
*/
export function buildHash (prefix, data) {
return encode(hash(data), prefix)
export function buildHash (prefix, data, options = { raw: false }) {
return options.raw ? hash(data) : encode(hash(data), prefix)
}

/**
Expand Down
7 changes: 4 additions & 3 deletions es/tx/builder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,12 @@ export function unpackTx (encodedTx, fromRlpBinary = false, prefix = 'tx') {
* @function
* @alias module:@aeternity/aepp-sdk/es/tx/builder
* @param {String | Buffer} rawTx base64 or rlp encoded transaction
* @param {{ raw: boolean = false }} options Options
* @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 function buildTxHash (rawTx, options = { raw: false }) {
if (typeof rawTx === 'string' && rawTx.indexOf('tx_') !== -1) return buildHash('th', unpackTx(rawTx).rlpEncoded, options)
return buildHash('th', rawTx, options)
}

export default { calculateMinFee, calculateFee, unpackTx, unpackRawTx, buildTx, buildRawTx, validateParams, buildTxHash }
6 changes: 4 additions & 2 deletions es/tx/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
SIGNATURE_VERIFICATION_SCHEMA,
TX_TYPE
} from './builder/schema'
import { calculateFee, unpackTx } from './builder'
import { buildTxHash, calculateFee, unpackTx } from './builder'
import { NodePool } from '../node-pool'

/**
Expand All @@ -31,7 +31,9 @@ const VALIDATORS = {
// VALIDATE SIGNATURE
signature ({ rlpEncoded, signature, ownerPublicKey, networkId = 'ae_mainnet' }) {
const txWithNetworkId = Buffer.concat([Buffer.from(networkId), rlpEncoded])
return verify(txWithNetworkId, signature, decodeBase58Check(assertedType(ownerPublicKey, 'ak')))
const txHashWithNetworkId = Buffer.concat([Buffer.from(networkId), buildTxHash(rlpEncoded, { raw: true })])
const decodedPub = decodeBase58Check(assertedType(ownerPublicKey, 'ak'))
return verify(txWithNetworkId, signature, decodedPub) || verify(txHashWithNetworkId, signature, decodedPub)
},
// VALIDATE IF ENOUGH FEE
insufficientFee ({ minFee, fee }) {
Expand Down
17 changes: 11 additions & 6 deletions test/integration/txVerification.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@ describe('Verify Transaction', function () {

await client.addAccount(MemoryAccount({ keypair: generateKeyPair() }), { select: true })
// Sign using another account
const signedTx = await client.signTransaction(spendTx)
const signedTxHash = await client.signTransaction(spendTx)
const signedTxFull = await client.signTransaction(spendTx, { signHash: false })

const { validation } = await client.unpackAndVerify(signedTx)
const error = validation
.filter(({ type, txKey }) => type === 'error') // exclude contract vm/abi, has separated test for it
.map(({ txKey }) => txKey)
const checkErrors = async (signedTx) => {
const { validation } = await client.unpackAndVerify(signedTx)
const error = validation
.filter(({ type }) => type === 'error') // exclude contract vm/abi, has separated test for it
.map(({ txKey }) => txKey)

JSON.stringify(ERRORS.filter(e => e !== 'gasPrice' && e !== 'ctVersion')).should.be.equals(JSON.stringify(error))
JSON.stringify(ERRORS.filter(e => e !== 'gasPrice' && e !== 'ctVersion')).should.be.equals(JSON.stringify(error))
}
await checkErrors(signedTxHash)
await checkErrors(signedTxFull)
})
it('verify transaction before broadcast', async () => {
client = await ready(this)
Expand Down

0 comments on commit 2cb8cc2

Please sign in to comment.