[WIP] trezor subprovider
This commit is contained in:
@@ -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": {
|
||||
|
||||
2
packages/subproviders/src/globals.d.ts
vendored
2
packages/subproviders/src/globals.d.ts
vendored
@@ -22,3 +22,5 @@ declare module 'web3-provider-engine/subproviders/fixture' {
|
||||
}
|
||||
export = FixtureSubprovider;
|
||||
}
|
||||
|
||||
declare module 'trezor-connect';
|
||||
|
||||
@@ -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,
|
||||
|
||||
150
packages/subproviders/src/subproviders/trezor.ts
Normal file
150
packages/subproviders/src/subproviders/trezor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user