Skip to content

Commit 9f48c83

Browse files
authored
Merge pull request #55 from BitGo/CT-698-eos
CT-698 Add EOS signing logic
2 parents 4b2623c + d7855fb commit 9f48c83

File tree

6 files changed

+118
-1
lines changed

6 files changed

+118
-1
lines changed

app/sign.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const utxoNetworks = {
2727
const coinDecimals = {
2828
btc: 8,
2929
eth: 18,
30+
eos: 4,
3031
xrp: 6,
3132
bch: 8,
3233
bsv: 8,
@@ -36,6 +37,7 @@ const coinDecimals = {
3637
xlm: 7,
3738
tbtc: 8,
3839
teth: 18,
40+
teos: 4,
3941
txrp: 6,
4042
tltc: 8,
4143
txlm: 7,
@@ -48,6 +50,9 @@ const coinDecimals = {
4850
const BCH_COINS = ['bch', 'tbch', 'bsv', 'tbsv'];
4951
const TEN = new BN(10);
5052

53+
const EOS_MAINNET_CHAIN_ID = 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906';
54+
const EOS_TESTNET_CHAIN_ID = 'e70aaab8997e1dfce58fbfac80cbbb8fecec7b99cf982a9444273cbc64c41473';
55+
5156
const confirmRecovery = function(backupKey, outputs, customMessage, skipConfirm) {
5257
console.log('Sign Recovery Transaction');
5358
console.log('=========================');
@@ -199,6 +204,49 @@ const handleSignEthereum = function(recoveryRequest, key, skipConfirm) {
199204
return transaction.serialize().toString('hex');
200205
};
201206

207+
const handleSignEos = function(recoveryRequest, key, skipConfirm) {
208+
const EosJs = require('eosjs');
209+
const ecc = require('eosjs-ecc');
210+
let chainId;
211+
if (recoveryRequest.coin === 'eos') {
212+
chainId = EOS_MAINNET_CHAIN_ID;
213+
} else {
214+
chainId = EOS_TESTNET_CHAIN_ID;
215+
}
216+
217+
const sendableTxJsonString = getTransactionHexFromRequest(recoveryRequest);
218+
const eosTx = JSON.parse(sendableTxJsonString);
219+
const packed_trx = eosTx.packed_trx;
220+
221+
const { recipient, amount } = utils.deserializeEOSTransaction(EosJs, packed_trx);
222+
223+
const customMessage = recoveryRequest.custom ? recoveryRequest.custom.message : 'None';
224+
225+
const outputs = [{
226+
address: recipient,
227+
amount: new BN(amount)
228+
}];
229+
230+
confirmRecovery(recoveryRequest.backupKey, outputs, customMessage, skipConfirm);
231+
232+
if (!key) {
233+
console.log('Please enter the xprv of the wallet for signing: ');
234+
key = prompt();
235+
}
236+
237+
const backupKeyNode = getHDNodeAndVerify(key, recoveryRequest.backupKey);
238+
239+
const dataToSign = utils.getEOSSignatureData(packed_trx, chainId);
240+
const signBuffer = Buffer.from(dataToSign, 'hex');
241+
const privateKeyBuffer = backupKeyNode.keyPair.getPrivateKeyBuffer();
242+
const signature = ecc.Signature.sign(signBuffer, privateKeyBuffer).toString();
243+
244+
eosTx.signatures.push(signature);
245+
246+
// EOS txHex is a stringified JSON containing the signatures array
247+
return JSON.stringify(eosTx);
248+
};
249+
202250
const handleSignXrp = function(recoveryRequest, key, skipConfirm) {
203251
const rippleLib = require('ripple-lib');
204252
const rippleApi = new rippleLib.RippleAPI();
@@ -403,6 +451,9 @@ const handleSign = function(args) {
403451
}
404452
}
405453
break;
454+
case 'eos':
455+
txHex = handleSignEos(recoveryRequest, key, args.confirm);
456+
break;
406457
case 'xrp':
407458
txHex = handleSignXrp(recoveryRequest, key, args.confirm);
408459
break;
@@ -438,4 +489,4 @@ const handleSign = function(args) {
438489
return finalRecovery;
439490
};
440491

441-
module.exports = { handleSign, handleSignUtxo, handleSignEthereum, handleSignXrp, handleSignXlm, handleSignErc20, parseKey };
492+
module.exports = { handleSign, handleSignUtxo, handleSignEthereum, handleSignXrp, handleSignXlm, handleSignErc20, handleSignEos, parseKey };

app/utils.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,51 @@ exports.halfSignEthTransaction = function(basecoin, recoveryRequest, key) {
193193
}
194194
return outFile;
195195
}
196+
197+
198+
/**
199+
* Deserialize an EOS transaction
200+
* @param {ObjectConstructor} EosJs The EosJs class
201+
* @param {String} serializedTransaction The EOS transaction returned from `serializeTransaction` above as hex string
202+
* @return {Object} Deserialized transaction, including recipient and amount
203+
*/
204+
exports.deserializeEOSTransaction = function(EosJs, serializedTransaction) {
205+
const eosClient = new EosJs({ });
206+
const eosTxStruct = eosClient.fc.structs.transaction;
207+
const serializedBuffer = Buffer.from(serializedTransaction, 'hex');
208+
209+
const transaction = EosJs.modules.Fcbuffer.fromBuffer(eosTxStruct, serializedBuffer);
210+
211+
// Get transfer action values
212+
// Only support transactions with one action: transfer
213+
if (transaction.actions.length !== 1) {
214+
throw new Error(`invalid number of actions: ${transaction.actions.length}`);
215+
}
216+
const txAction = transaction.actions[0];
217+
if (!txAction) {
218+
throw new Error('missing transaction action');
219+
}
220+
if (txAction.name !== 'transfer') {
221+
throw new Error(`invalid action: ${txAction.name}`);
222+
}
223+
const transferStruct = eosClient.fc.abiCache.abi('eosio.token').structs.transfer;
224+
const serializedTransferDataBuffer = Buffer.from(txAction.data, 'hex');
225+
const transferActionData = EosJs.modules.Fcbuffer.fromBuffer(transferStruct, serializedTransferDataBuffer);
226+
transaction.address = transferActionData.to;
227+
transaction.amount = transferActionData.quantity.split(' ')[0];;
228+
return { recipient: transaction.address, amount: transaction.amount };
229+
};
230+
231+
/**
232+
* Get the data that actually has to be signed for the tx
233+
* @param {Object} tx The serialized EOS transaction to get signature data for
234+
* @return {String} The data that needs to be signed for this tx
235+
*/
236+
exports.getEOSSignatureData = function(tx, chainId) {
237+
return Buffer.concat([
238+
Buffer.from(chainId, 'hex'), // The ChainID representing the chain that we are on
239+
Buffer.from(tx, 'hex'), // The serialized unsigned tx
240+
Buffer.from(new Uint8Array(32)), // Some padding
241+
]).toString('hex');
242+
};
243+

config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ module.exports = {
66
tbtc: 'xpub',
77
eth: 'xpub',
88
teth: 'xpub',
9+
eos: 'xpub',
10+
teos: 'xpub',
911
erc: 'xpub',
1012
terc: 'xpub',
1113
ltc: 'xpub',

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
"bitgo-utxo-lib": "^1.2.1",
2323
"body-parser": "^1.18.3",
2424
"dotenv": "^6.1.0",
25+
"eosjs": "^16.0.9",
26+
"eosjs-ecc": "^4.0.4",
2527
"ethereumjs-tx": "^1.3.7",
2628
"express": "^4.16.3",
2729
"jsrender": "^0.9.90",

test/sign.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/transactions/teos.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"id": "27b5fe65c239efc9674c6ade77edbd00e5dc9c4d0f059866a7e388b072b57d1d",
3+
"tx": "{\"compression\":\"none\",\"packed_trx\":\"d3ca235dbed650bc3a64000000000100a6823403ea3055000000572d3ccdcd01507551997772734c00000000a8ed323221507551997772734c9052d3d42ec9b071a08601000000000004454f53000000000000\",\"signatures\":[\"SIG_K1_JzpSv5pkpzuzg5FqYUAVsrEtPooVCDx5Ls3qkpyuSpsZozj6cMCxgz2jYuuqBoLBTRPet62QFoaVVJ6Rrh1YDp64yRMBBy\"]}",
4+
"backupKey": "xpub661MyMwAqRbcEvM8SrtkWnXB9knYvwZP3FsSL8a5P1KPknFj4in2ne2arrzHAG3sUD7VxTDUfaxuPb78vnGVzaUBJMoo4pt2oYez6cgr46Y",
5+
"coin": "teos"
6+
}

0 commit comments

Comments
 (0)