From 8648d3bacf3c3a923eb9102402e5cf82727df0f0 Mon Sep 17 00:00:00 2001 From: Alex Speller Date: Mon, 26 Sep 2022 15:56:40 +0100 Subject: [PATCH] Alternate signing implementation --- trezor-wallet-provider.js | 142 +++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/trezor-wallet-provider.js b/trezor-wallet-provider.js index 5c71ee2..dcb333a 100644 --- a/trezor-wallet-provider.js +++ b/trezor-wallet-provider.js @@ -3,12 +3,12 @@ const Web3 = require("web3"); const HookedWalletSubprovider = require("@trufflesuite/web3-provider-engine/subproviders/hooked-wallet.js"); // there is a bug in web3's BN that is not calculating hex correctly, so we use this instead const numberToBN = require("number-to-bn"); -const tmp = require("tmp"); const { toChecksumAddress } = Web3.utils; -const fs = require("fs"); - const ETHEREUM_PATH = "m/44'/60'/0'/0"; +const fs = require("fs").promises; +const os = require("os"); +const path = require("path"); function trezorCtl(args) { let stdout = execSync(`trezorctl ${args}`, { @@ -64,6 +64,41 @@ class Trezor { return this.cachedAccounts; } + async signTypedMessage({ from, data }, cb) { + from = toChecksumAddress(from); + + const defaultDerivationPath = this.validateFromAndGetDerivationPath(from); + const path = this.opts.derivationPath || defaultDerivationPath; + + let response; + + try { + await withTempFile(async (file) => { + await fs.writeFile(file, data); + response = trezorCtl( + `ethereum sign-typed-data --address "${path}" ${file}` + ); + const match = response.match(/signature: (0x[0-9A-Fa-f]+)/); + if (!match || !match[1]) { + cb(`Could not find signature in trezor response: ${response}`); + } + cb(null, match[1]); + }); + } catch (e) { + cb(e.message); + } + } + + validateFromAndGetDerivationPath(from) { + let addresses = this.getAccountsSync(); + let idx = addresses.findIndex((i) => i === from); + if (idx === -1) { + throw new Error( + `tried to sign transaction with a 'from' address that is not an address on this trezor ${from}. Possible addresses are ${addresses.join()}. Use 'numberOfAccounts' constructor option to increase available addresses.` + ); + } + } + signTransaction(txn, cb) { let { to = "", // according to trezorctl docs, empty string designates contract creation @@ -79,72 +114,36 @@ class Trezor { gasPrice = numberToBN(gasPrice).toString(); value = numberToBN(value).toString(); nonce = numberToBN(nonce).toString(); - let addresses = this.getAccountsSync(); - let idx = addresses.findIndex((i) => i === from); - if (idx === -1) { - cb( - `tried to sign transaction with a 'from' address that is not an address on this trezor ${from}. Possible addresses are ${addresses.join()}. Use 'numberOfAccounts' constructor option to increase available addresses.` + + let defaultDerivationPath; + try { + defaultDerivationPath = this.validateFromAndGetDerivationPath(from); + } catch (e) { + cb(e.message); + } + + const path = this.opts.derivationPath || defaultDerivationPath; + let response; + try { + // --gas-limit is an integer + // --gas-price is a string integer (wei) + // --nonce is an integer + // --data is a string hex: "0x1234" + // --chain-id is an integer + // "to" is a string hex: "0x1234" (or empty string for contract creation) + // "value" is a string integer (wei) + console.log("\u0007"); + response = trezorCtl( + `ethereum sign-tx --chain-id ${this.opts.chainId} --address "${path}" --nonce ${nonce} --gas-limit ${gasLimit} --gas-price "${gasPrice}" --data "${txn.data}" "${to}" "${value}"` ); + } catch (e) { + cb(e.message); } - if (idx > -1) { - const path = this.opts.derivationPath - ? this.opts.derivationPath - : `${this.opts.derivationPathPrefix}/${idx}`; - let response; - try { - // --gas-limit is an integer - // --gas-price is a string integer (wei) - // --nonce is an integer - // --data is a string hex: "0x1234" - // --chain-id is an integer - // "to" is a string hex: "0x1234" (or empty string for contract creation) - // "value" is a string integer (wei) - response = trezorCtl( - `ethereum sign-tx --chain-id ${this.opts.chainId} --address "${path}" --nonce ${nonce} --gas-limit ${gasLimit} --gas-price "${gasPrice}" --data "${txn.data}" "${to}" "${value}"` - ); - } catch (e) { - cb(e.message); - } - if (response) { - let signedTxn = response.slice(response.indexOf("0x")).trim(); - cb(null, signedTxn); - } + if (response) { + let signedTxn = response.slice(response.indexOf("0x")).trim(); + cb(null, signedTxn); } } - - signTypedMessage(txn, cb) { - let { from, data } = txn; - let addresses = this.getAccountsSync(); - let idx = addresses.findIndex((i) => i === from); - const path = this.opts.derivationPath - ? this.opts.derivationPath - : `${this.opts.derivationPathPrefix}/${idx}`; - // tmp file created because `ethereum sign-typed-data` takes file path as input. - tmp.file( - { postfix: ".json" }, - function _tempFileCreated(err, filePath, fd, cleanupCallback) { - if (err) throw err; - let response; - fs.writeFileSync(filePath, JSON.stringify(data), function (err) { - if (err) throw err; - }); - try { - let command = `ethereum sign-typed-data --address "${path}" ${filePath}`; - response = trezorCtl(command); - } catch (e) { - console.log(e); - } - cleanupCallback(); - if (response) { - let keyword = "signature: 0x"; - let signedTxn = response.slice( - response.indexOf(keyword) + keyword.length - ); - cb(null, signedTxn); - } - } - ); - } } module.exports = class TrezorWalletProvider extends HookedWalletSubprovider { @@ -157,3 +156,16 @@ module.exports = class TrezorWalletProvider extends HookedWalletSubprovider { }); } }; + +// https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/ + +const withTempFile = (fn) => withTempDir((dir) => fn(path.join(dir, "file"))); + +const withTempDir = async (fn) => { + const dir = await fs.mkdtemp((await fs.realpath(os.tmpdir())) + path.sep); + try { + return await fn(dir); + } finally { + fs.rmdir(dir, { recursive: true }); + } +};