Skip to content

hsm signing service, refactor for TransactionSender signing functions. #218

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

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions federator/config/config.sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
50 changes: 50 additions & 0 deletions federator/src/lib/HSM.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const net = require('net');

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;
}

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 = hsmPayloadBuilder(`sign`, `m/44'/60'/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) {
this.logger.error(`HSM (connectSendAndReceive)`, err);
throw err;
}
}
}
40 changes: 32 additions & 8 deletions federator/src/lib/TransactionSender.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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
}, this.logger);
}

async getNonce(address) {
Expand Down Expand Up @@ -126,12 +130,32 @@ 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('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.r = Buffer.from(r, 'hex');
tx.s = Buffer.from(s, 'hex');
tx.v = Buffer.from((this.getChainId() * 2 + 8).toString());
}
return tx;
}


async getAddress(privateKey) {
let address = null;
if (privateKey && privateKey.length) {
Expand Down Expand Up @@ -170,15 +194,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;
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) {
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;
Expand Down
3 changes: 2 additions & 1 deletion federator/src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ async function evm_mine(iterations, web3Instance = null) {
};
};


module.exports = {
asyncMine,
evm_mine,
Expand All @@ -193,5 +194,5 @@ module.exports = {
zeroHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
retry,
retry3Times,
getHeartbeatPollingInterval
getHeartbeatPollingInterval,
}
38 changes: 37 additions & 1 deletion federator/test/TransactionSender.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,40 @@ describe('TransactionSender module tests', () => {
expect(result).toEqual(expectedAddr.toLocaleLowerCase());
});

});
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);
});

});