[WIP] trezor subprovider

This commit is contained in:
marcmartinez
2018-12-07 07:06:40 -08:00
parent cae0e02bc6
commit 65c60f5386
6 changed files with 199 additions and 2 deletions

View File

@@ -51,6 +51,7 @@
"json-rpc-error": "2.0.0",
"lodash": "^4.17.5",
"semaphore-async-await": "^1.5.1",
"trezor-connect": "^6.0.2",
"web3-provider-engine": "14.0.6"
},
"devDependencies": {

View File

@@ -22,3 +22,5 @@ declare module 'web3-provider-engine/subproviders/fixture' {
}
export = FixtureSubprovider;
}
declare module 'trezor-connect';

View File

@@ -29,6 +29,7 @@ export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet';
export { MnemonicWalletSubprovider } from './subproviders/mnemonic_wallet';
export { MetamaskSubprovider } from './subproviders/metamask_subprovider';
export { EthLightwalletSubprovider } from './subproviders/eth_lightwallet_subprovider';
export { TrezorSubprovider } from './subproviders/trezor';
export {
Callback,

View File

@@ -0,0 +1,150 @@
import { assert } from '@0x/assert';
import { addressUtils } from '@0x/utils';
import EthereumTx = require('ethereumjs-tx');
import * as _ from 'lodash';
import TrezorConnect from 'trezor-connect';
import {
PartialTxParams,
TrezorConnectResponse,
TrezorGetAddressResponsePayload,
TrezorResponseErrorPayload,
TrezorSignMssgResponsePayload,
TrezorSignTxResponsePayload,
WalletSubproviderErrors,
} from '../types';
import { BaseWalletSubprovider } from './base_wallet_subprovider';
const PRIVATE_KEY_PATH = `m/44'/60'/0'`;
export class TrezorSubprovider extends BaseWalletSubprovider {
private readonly _publicKeyPath: string;
private _cachedAccounts: string[];
/**
* Instantiates a TrezorSubprovider. Defaults to derivationPath set to `44'/60'/0'`.
* @return TrezorSubprovider instance
*/
constructor() {
super();
this._publicKeyPath = PRIVATE_KEY_PATH;
this._cachedAccounts = [];
}
/**
* Retrieve a users Trezor account. The accounts are derived private key path, This method
* is automatically called when issuing a `eth_accounts` JSON RPC request via your providerEngine
* instance.
* @return An array of accounts
*/
public async getAccountsAsync(): Promise<string[]> {
if (this._cachedAccounts.length) {
return this._cachedAccounts;
}
const accounts: string[] = [];
const response: TrezorConnectResponse = TrezorConnect.ethereumGetAddress({ path: this._publicKeyPath, showOnTrezor: true });
if (response.success) {
const payload: TrezorGetAddressResponsePayload = response.payload;
accounts.push(payload.address);
this._cachedAccounts = accounts;
} else {
const payload: TrezorResponseErrorPayload = response.payload;
throw new Error(payload.error);
}
return accounts;
}
/**
* Signs a transaction on the Trezor with the account specificed by the `from` field in txParams.
* If you've added the TrezorSubprovider to your app's provider, you can simply send an `eth_sendTransaction`
* JSON RPC request, and this method will be called auto-magically. If you are not using this via a ProviderEngine
* instance, you can call it directly.
* @param txParams Parameters of the transaction to sign
* @return Signed transaction hex string
*/
public async signTransactionAsync(txData: PartialTxParams): Promise<string> {
if (_.isUndefined(txData.from) || !addressUtils.isAddress(txData.from)) {
throw new Error(WalletSubproviderErrors.FromAddressMissingOrInvalid);
}
txData.value = txData.value ? txData.value : '0x0';
txData.data = txData.data ? txData.data : '0x';
txData.gas = txData.gas ? txData.gas : '0x0';
txData.gasPrice = txData.gasPrice ? txData.gasPrice : '0x0';
const accountIndex = this._cachedAccounts.indexOf(txData.from);
const response: TrezorConnectResponse = TrezorConnect.ethereumSignTransaction({
path: this._publicKeyPath + `${accountIndex}`,
transaction: {
to: txData.to,
value: txData.value,
data: txData.data,
chainId: 1,
nonce: txData.nonce,
gasLimit: txData.gas,
gasPrice: txData.gasPrice,
},
});
if (response.success) {
const payload: TrezorSignTxResponsePayload = response.payload;
const tx = new EthereumTx(txData);
// Set the EIP155 bits
const vIndex = 6;
tx.raw[vIndex] = Buffer.from([1]); // v
const rIndex = 7;
tx.raw[rIndex] = Buffer.from([]); // r
const sIndex = 8;
tx.raw[sIndex] = Buffer.from([]); // s
// slice off leading 0x
tx.v = Buffer.from(payload.v.slice(2), 'hex');
tx.r = Buffer.from(payload.r.slice(2), 'hex');
tx.s = Buffer.from(payload.s.slice(2), 'hex');
return `0x${tx.serialize().toString('hex')}`;
} else {
const payload: TrezorResponseErrorPayload = response.payload;
throw new Error(payload.error);
}
}
/**
* Sign a personal Ethereum signed message. The signing account will be the account
* associated with the provided address. If you've added the TrezorSubprovider to
* your app's provider, you can simply send an `eth_sign` or `personal_sign` JSON RPC
* request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param data Hex string message to sign
* @param address Address of the account to sign with
* @return Signature hex string (order: rsv)
*/
public async signPersonalMessageAsync(data: string, address: string): Promise<string> {
if (_.isUndefined(data)) {
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
}
assert.isHexString('data', data);
assert.isETHAddressHex('address', address);
const accountIndex = this._cachedAccounts.indexOf(address);
const response: TrezorConnectResponse = TrezorConnect.ethereumSignMessage({ path: this._publicKeyPath + `${accountIndex}`, message: data, hex: true });
if (response.success) {
const payload: TrezorSignMssgResponsePayload = response.payload;
return '0x' + payload.signature;
} else {
const payload: TrezorResponseErrorPayload = response.payload;
throw new Error(payload.error);
}
}
/**
* eth_signTypedData is currently not supported on Trezor devices.
* @param address Address of the account to sign with
* @param data the typed data object
* @return Signature hex string (order: rsv)
*/
// tslint:disable-next-line:prefer-function-over-method
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
throw new Error(WalletSubproviderErrors.MethodNotSupported);
}
}

View File

@@ -136,3 +136,34 @@ export type NextCallback = (callback?: OnNextCompleted) => void;
export interface JSONRPCRequestPayloadWithMethod extends JSONRPCRequestPayload {
method: string;
}
export interface TrezorGetAddressResponsePayload {
address: string;
path: {
0: number;
1: number;
2: number;
};
serializedPath: string;
}
export interface TrezorSignTxResponsePayload {
v: string;
r: string;
s: string;
}
export interface TrezorSignMssgResponsePayload {
address: string;
signature: string;
}
export interface TrezorResponseErrorPayload {
error: string;
}
export interface TrezorConnectResponse {
payload: any;
id: number;
success: boolean;
}

View File

@@ -6232,7 +6232,7 @@ eventemitter3@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
events@1.1.1, events@^1.0.0:
events@1.1.1, events@^1.0.0, events@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
@@ -7000,7 +7000,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep:
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
ethereumjs-util "^5.2.0"
ethereumjs-vm "2.3.5"
ethereumjs-wallet "~0.6.0"
ethereumjs-wallet "0.6.0"
fake-merkle-patricia-tree "~1.0.1"
heap "~0.2.6"
js-scrypt "^0.2.0"
@@ -15537,6 +15537,14 @@ treeify@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8"
trezor-connect@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/trezor-connect/-/trezor-connect-6.0.2.tgz#a4ca892cc4a167b34b97644e1404a56f6a110379"
dependencies:
babel-runtime "^6.26.0"
events "^1.1.1"
whatwg-fetch "^2.0.4"
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -16842,6 +16850,10 @@ whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
whatwg-fetch@^2.0.4:
version "2.0.4"
resolved "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
whatwg-mimetype@^2.1.0:
version "2.2.0"
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"