Skip to content
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);
});

});