[WIP] trezor subprovider
This commit is contained in:
@@ -51,6 +51,7 @@
|
|||||||
"json-rpc-error": "2.0.0",
|
"json-rpc-error": "2.0.0",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
"semaphore-async-await": "^1.5.1",
|
"semaphore-async-await": "^1.5.1",
|
||||||
|
"trezor-connect": "^6.0.2",
|
||||||
"web3-provider-engine": "14.0.6"
|
"web3-provider-engine": "14.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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;
|
export = FixtureSubprovider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'trezor-connect';
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet';
|
|||||||
export { MnemonicWalletSubprovider } from './subproviders/mnemonic_wallet';
|
export { MnemonicWalletSubprovider } from './subproviders/mnemonic_wallet';
|
||||||
export { MetamaskSubprovider } from './subproviders/metamask_subprovider';
|
export { MetamaskSubprovider } from './subproviders/metamask_subprovider';
|
||||||
export { EthLightwalletSubprovider } from './subproviders/eth_lightwallet_subprovider';
|
export { EthLightwalletSubprovider } from './subproviders/eth_lightwallet_subprovider';
|
||||||
|
export { TrezorSubprovider } from './subproviders/trezor';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Callback,
|
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 {
|
export interface JSONRPCRequestPayloadWithMethod extends JSONRPCRequestPayload {
|
||||||
method: string;
|
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"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
|
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"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
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-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
|
||||||
ethereumjs-util "^5.2.0"
|
ethereumjs-util "^5.2.0"
|
||||||
ethereumjs-vm "2.3.5"
|
ethereumjs-vm "2.3.5"
|
||||||
ethereumjs-wallet "~0.6.0"
|
ethereumjs-wallet "0.6.0"
|
||||||
fake-merkle-patricia-tree "~1.0.1"
|
fake-merkle-patricia-tree "~1.0.1"
|
||||||
heap "~0.2.6"
|
heap "~0.2.6"
|
||||||
js-scrypt "^0.2.0"
|
js-scrypt "^0.2.0"
|
||||||
@@ -15537,6 +15537,14 @@ treeify@^1.0.1:
|
|||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8"
|
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:
|
trim-newlines@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
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"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
|
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:
|
whatwg-mimetype@^2.1.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"
|
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"
|
||||||
|
|||||||
Reference in New Issue
Block a user