From 4d6e9d5ebd5a4fef49801fa52e8b6f0ef6a1c397 Mon Sep 17 00:00:00 2001 From: Mauri Date: Mon, 7 Jun 2021 10:26:03 -0300 Subject: [PATCH 1/3] hsm signing service, refactor for TransactionSender signing functions, etc. --- federator/config/config.sample.js | 2 + federator/src/lib/HSM.js | 52 ++++++++++++++++++++++++++ federator/src/lib/TransactionSender.js | 40 +++++++++++++++++--- federator/src/lib/utils.js | 6 ++- 4 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 federator/src/lib/HSM.js diff --git a/federator/config/config.sample.js b/federator/config/config.sample.js index 0a6e3f169..df803105d 100644 --- a/federator/config/config.sample.js +++ b/federator/config/config.sample.js @@ -9,4 +9,6 @@ module.exports = { etherscanApiKey: '', runHeartbeatEvery: 1, // In hours endpointsPort: 5000, // Server port + hsmPort: 6000, // [HSM] signing service port + hsmHost: '127.0.0.1' // [HSM] signing service host } diff --git a/federator/src/lib/HSM.js b/federator/src/lib/HSM.js new file mode 100644 index 000000000..6fad6b701 --- /dev/null +++ b/federator/src/lib/HSM.js @@ -0,0 +1,52 @@ +const net = require('net'); +const utils = require('./utils'); + +const payloadBuilder = + ( + command, + keyId, + txnHash + ) => `{"command":"${command}","keyId":"${keyId}","message":{"hash":"${txnHash}"},"version":2}`; + +module.exports = class HSM { + constructor({ + host = '127.0.0.1', + port = 6000, + }) { + this.host = host; + this.port = port; + this.client = null; + } + + async receive() { + return new Promise((resolve, reject) => { + this.client.on('data', (data) => { + resolve(data.toString()); + this.client.end(); + }) + + this.client.on('end', () => { + resolve(`connection to ${this.host}:${this.port} closed`) + }) + + this.client.on('error', (err) => { + reject(`Error: ${err.message}`) + }) + }) + } + + send(msgToSign = '') { + const payload = utils.hsmPayloadBuilder(`sign`, `m/44'/137'/0'/0/0`, msgToSign); + return this.client.write(`${payload}\n`); + } + + async connectSendAndReceive(msgToSign) { + try { + this.client = net.connect(this.port, this.host); + this.send(msgToSign); + return this.receive(); + } catch(err) { + console.log(`HSM (connectSendAndReceive)`, err); + } + } +} \ No newline at end of file diff --git a/federator/src/lib/TransactionSender.js b/federator/src/lib/TransactionSender.js index 6fdb7c94d..de0e20c17 100644 --- a/federator/src/lib/TransactionSender.js +++ b/federator/src/lib/TransactionSender.js @@ -1,9 +1,9 @@ - const Tx = require('ethereumjs-tx'); const ethUtils = require('ethereumjs-util'); const utils = require('./utils'); const fs = require('fs'); const axios = require('axios'); +const HSM = require('./HSM'); module.exports = class TransactionSender { constructor(client, logger, config) { @@ -13,6 +13,10 @@ module.exports = class TransactionSender { this.manuallyCheck = `${config.storagePath || __dirname}/manuallyCheck.txt`; this.etherscanApiKey = config.etherscanApiKey; this.debuggingMode = false; + this.hsm = new HSM({ + port: config.hsmPort, + host: config.hsmHost + }) } async getNonce(address) { @@ -126,12 +130,36 @@ module.exports = class TransactionSender { return rawTx; } - signRawTransaction(rawTx, privateKey) { + async signRawTransaction(rawTx, privateKey, useHSM) { let tx = new Tx(rawTx); - tx.sign(utils.hexStringToBuffer(privateKey)); + if(!useHSM) { + tx.sign(utils.hexStringToBuffer(privateKey)); + } else { + const txHash = tx.hash(false).toString(); + const { + errorcode, + signature: { + r, + s + } + } = JSON.parse(await this.hsm.connectSendAndReceive(txHash)); + + if(errorcode != 0) { + throw new Error(`error while signing txn with HSM`) + } + + tx = { + ...tx, + r, + s, + v: this.getChainId() * 2 + 8 + } + + } return tx; } + async getAddress(privateKey) { let address = null; if (privateKey && privateKey.length) { @@ -170,15 +198,15 @@ module.exports = class TransactionSender { return response.data; } - async sendTransaction(to, data, value, privateKey) { + async sendTransaction(to, data, value, privateKey, useHSM = false) { const chainId = await this.getChainId(); let txHash; let receipt; try { let from = await this.getAddress(privateKey); let rawTx = await this.createRawTransaction(from, to, data, value); - if (privateKey && privateKey.length) { - let signedTx = this.signRawTransaction(rawTx, privateKey); + if (privateKey && privateKey.length || useHSM) { + let signedTx = await this.signRawTransaction(rawTx, privateKey, useHSM); const serializedTx = ethUtils.bufferToHex(signedTx.serialize()); receipt = await this.client.eth.sendSignedTransaction(serializedTx).once('transactionHash', async (hash) => { txHash = hash; diff --git a/federator/src/lib/utils.js b/federator/src/lib/utils.js index 0041926b4..bb432b323 100644 --- a/federator/src/lib/utils.js +++ b/federator/src/lib/utils.js @@ -176,6 +176,9 @@ async function evm_mine(iterations, web3Instance = null) { await asyncMine(web3Instance); }; }; +function hsmPayloadBuilder(command, keyId, txnHash) { + return `{"command":"${command}","keyId":"${keyId}","message":{"hash":"${txnHash}"},"version":2}`; +} module.exports = { asyncMine, @@ -193,5 +196,6 @@ module.exports = { zeroHash: '0x0000000000000000000000000000000000000000000000000000000000000000', retry, retry3Times, - getHeartbeatPollingInterval + getHeartbeatPollingInterval, + hsmPayloadBuilder } From 49baeb4240220fde1d61ff42b971981d859e2efa Mon Sep 17 00:00:00 2001 From: Mauri Date: Mon, 7 Jun 2021 18:24:54 -0300 Subject: [PATCH 2/3] better r,s & v handling when using hsm for signing --- federator/src/lib/HSM.js | 18 +++++------ federator/src/lib/TransactionSender.js | 20 +++++-------- federator/src/lib/utils.js | 5 +--- federator/test/TransactionSender.test.js | 38 +++++++++++++++++++++++- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/federator/src/lib/HSM.js b/federator/src/lib/HSM.js index 6fad6b701..8e678a24a 100644 --- a/federator/src/lib/HSM.js +++ b/federator/src/lib/HSM.js @@ -1,20 +1,17 @@ const net = require('net'); -const utils = require('./utils'); -const payloadBuilder = - ( - command, - keyId, - txnHash - ) => `{"command":"${command}","keyId":"${keyId}","message":{"hash":"${txnHash}"},"version":2}`; +function hsmPayloadBuilder(command, keyId, txnHash) { + return `{"command":"${command}","keyId":"${keyId}","message":{"hash":"${txnHash}"},"version":2}`; +} module.exports = class HSM { constructor({ host = '127.0.0.1', port = 6000, - }) { + }, logger) { this.host = host; this.port = port; + this.logger = logger; this.client = null; } @@ -36,7 +33,7 @@ module.exports = class HSM { } send(msgToSign = '') { - const payload = utils.hsmPayloadBuilder(`sign`, `m/44'/137'/0'/0/0`, msgToSign); + const payload = hsmPayloadBuilder(`sign`, `m/44'/137'/0'/0/0`, msgToSign); return this.client.write(`${payload}\n`); } @@ -46,7 +43,8 @@ module.exports = class HSM { this.send(msgToSign); return this.receive(); } catch(err) { - console.log(`HSM (connectSendAndReceive)`, err); + this.logger.error(`HSM (connectSendAndReceive)`, err); + throw err; } } } \ No newline at end of file diff --git a/federator/src/lib/TransactionSender.js b/federator/src/lib/TransactionSender.js index de0e20c17..269bb374e 100644 --- a/federator/src/lib/TransactionSender.js +++ b/federator/src/lib/TransactionSender.js @@ -16,7 +16,7 @@ module.exports = class TransactionSender { this.hsm = new HSM({ port: config.hsmPort, host: config.hsmHost - }) + }, this.logger); } async getNonce(address) { @@ -135,26 +135,22 @@ module.exports = class TransactionSender { if(!useHSM) { tx.sign(utils.hexStringToBuffer(privateKey)); } else { - const txHash = tx.hash(false).toString(); - const { + const txHash = tx.hash(false).toString('hex'); + const { errorcode, signature: { r, s - } + } = { r: '0x0', s: '0x0' } } = JSON.parse(await this.hsm.connectSendAndReceive(txHash)); if(errorcode != 0) { throw new Error(`error while signing txn with HSM`) } - - tx = { - ...tx, - r, - s, - v: this.getChainId() * 2 + 8 - } - + + tx.r = Buffer.from(r, 'hex'); + tx.s = Buffer.from(s, 'hex'); + tx.v = Buffer.from((this.getChainId() * 2 + 8).toString()); } return tx; } diff --git a/federator/src/lib/utils.js b/federator/src/lib/utils.js index bb432b323..872f3f4ec 100644 --- a/federator/src/lib/utils.js +++ b/federator/src/lib/utils.js @@ -176,9 +176,7 @@ async function evm_mine(iterations, web3Instance = null) { await asyncMine(web3Instance); }; }; -function hsmPayloadBuilder(command, keyId, txnHash) { - return `{"command":"${command}","keyId":"${keyId}","message":{"hash":"${txnHash}"},"version":2}`; -} + module.exports = { asyncMine, @@ -197,5 +195,4 @@ module.exports = { retry, retry3Times, getHeartbeatPollingInterval, - hsmPayloadBuilder } diff --git a/federator/test/TransactionSender.test.js b/federator/test/TransactionSender.test.js index d1214a3fa..000a9c95e 100644 --- a/federator/test/TransactionSender.test.js +++ b/federator/test/TransactionSender.test.js @@ -74,4 +74,40 @@ describe('TransactionSender module tests', () => { expect(result).toEqual(expectedAddr.toLocaleLowerCase()); }); -}); \ No newline at end of file + it('should sign the same with HSM and web3', async () => { + const rawTx = { + chainId: 5777, + gasPrice: '0x6fc23ac00', + value: '0x0', + to: '0x557b77f7B280006f7732dCc123C3A966F5Fe1372', + data: '0x7ff4657e000000000000000000000000de451f57d061b915525736937d0f5d24c551edd1000000000000000000000000000000000000000000000000000000000000004000000000000000000000000013263f73dcbe9b123a9ea32c13040b2becfe1e5c00000000000000000000000013263f73dcbe9b123a9ea32c13040b2becfe1e5c000000000000000000000000000000000000000000000000125195019f840000157f354383710432cdc131e73815a179a4e858ea304e4916b0f4d1db6553a7a70612db9f2ee9b8d7078e8f00338f600080ebc2a1e27f39376f54f0f6d7fb73750000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000044d41494e00000000000000000000000000000000000000000000000000000000', + from: '0x57093C0C2aFEACaF7D677356c3eAC3E99933F7C0', + nonce: '0x49', + r: 0, + s: 0, + gas: '0x284d8' + } + + const pk = `3f28f888373e9ad1651a1227a5efdc0d7ea55bce6de3b5448de56c8588c6bd4d`; + const pk2 = `dfac7a2bfe2cd7f7fc8caffd65995300eb0e1a652502147da8d7a9e5bce16ac2`; + const from = `0x3444f14CbC7081ADEd7203E32E65304D17fe3bdA`; + const sender = new TransactionSender(web3Mock, logger, { + hsmPort: 6000, + hsmHost: '127.0.0.1' + }); + + const signedRawTransaction = await sender.signRawTransaction(rawTx, pk2, false); + const r = signedRawTransaction.r.toString('hex'); + const s = signedRawTransaction.s.toString('hex'); + const v = signedRawTransaction.v.toString('hex'); + + const signedHsmRawTransaction = await sender.signRawTransaction(rawTx, pk2, true); + const rHSM = signedHsmRawTransaction.r.toString('hex'); + const sHSM = signedHsmRawTransaction.s.toString('hex'); + const vHSM = signedHsmRawTransaction.v.toString('hex'); + + expect(rHSM).toEqual(r); + expect(sHSM).toEqual(s); + }); + +}); From 16dcd5ff9dec76c4e1e7c2b2b3409d8d5c10e49c Mon Sep 17 00:00:00 2001 From: Mauri Date: Wed, 9 Jun 2021 16:17:19 -0300 Subject: [PATCH 3/3] derivation path changed and minor refactor for rawTx in transaction sender --- federator/src/lib/HSM.js | 2 +- federator/src/lib/TransactionSender.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/federator/src/lib/HSM.js b/federator/src/lib/HSM.js index 8e678a24a..30190a03d 100644 --- a/federator/src/lib/HSM.js +++ b/federator/src/lib/HSM.js @@ -33,7 +33,7 @@ module.exports = class HSM { } send(msgToSign = '') { - const payload = hsmPayloadBuilder(`sign`, `m/44'/137'/0'/0/0`, msgToSign); + const payload = hsmPayloadBuilder(`sign`, `m/44'/60'/0'/0/0`, msgToSign); return this.client.write(`${payload}\n`); } diff --git a/federator/src/lib/TransactionSender.js b/federator/src/lib/TransactionSender.js index 269bb374e..670c715d6 100644 --- a/federator/src/lib/TransactionSender.js +++ b/federator/src/lib/TransactionSender.js @@ -198,9 +198,9 @@ module.exports = class TransactionSender { const chainId = await this.getChainId(); let txHash; let receipt; + let from = await this.getAddress(privateKey); + let rawTx = await this.createRawTransaction(from, to, data, value); try { - let from = await this.getAddress(privateKey); - let rawTx = await this.createRawTransaction(from, to, data, value); if (privateKey && privateKey.length || useHSM) { let signedTx = await this.signRawTransaction(rawTx, privateKey, useHSM); const serializedTx = ethUtils.bufferToHex(signedTx.serialize());