Check in keepkey

This commit is contained in:
Will O'Beirne 2018-05-29 15:22:40 -04:00
parent 3bdfad8b02
commit 7d6fb98627
No known key found for this signature in database
GPG Key ID: 44C190DB5DEAF9F6
19 changed files with 288 additions and 7 deletions

View File

@ -21,6 +21,7 @@ import {
KeystoreDecrypt, KeystoreDecrypt,
LedgerNanoSDecrypt, LedgerNanoSDecrypt,
MnemonicDecrypt, MnemonicDecrypt,
KeepKeyDecrypt,
PrivateKeyDecrypt, PrivateKeyDecrypt,
PrivateKeyValue, PrivateKeyValue,
TrezorDecrypt, TrezorDecrypt,
@ -94,6 +95,7 @@ interface BaseWalletInfo {
isReadOnly?: boolean; isReadOnly?: boolean;
attemptUnlock?: boolean; attemptUnlock?: boolean;
redirect?: string; redirect?: string;
isHidden?: boolean;
} }
export interface SecureWalletInfo extends BaseWalletInfo { export interface SecureWalletInfo extends BaseWalletInfo {
@ -148,7 +150,8 @@ const WalletDecrypt = withRouter<Props>(
initialParams: {}, initialParams: {},
unlock: this.props.unlockWeb3, unlock: this.props.unlockWeb3,
attemptUnlock: true, attemptUnlock: true,
helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask` helpLink: `${knowledgeBaseURL}/migration/moving-from-private-key-to-metamask`,
isHidden: !!process.env.BUILD_ELECTRON
}, },
[SecureWalletName.LEDGER_NANO_S]: { [SecureWalletName.LEDGER_NANO_S]: {
lid: 'X_LEDGER', lid: 'X_LEDGER',
@ -178,6 +181,16 @@ const WalletDecrypt = withRouter<Props>(
unlock: this.props.setWallet, unlock: this.props.setWallet,
helpLink: paritySignerHelpLink helpLink: paritySignerHelpLink
}, },
[SecureWalletName.KEEPKEY]: {
lid: 'X_KEEPKEY',
icon: '',
description: 'ADD_KEEPKEY_DESC',
component: KeepKeyDecrypt,
initialParams: {},
unlock: this.props.setWallet,
helpLink: '',
isHidden: !process.env.BUILD_ELECTRON
},
[InsecureWalletName.KEYSTORE_FILE]: { [InsecureWalletName.KEYSTORE_FILE]: {
lid: 'X_KEYSTORE2', lid: 'X_KEYSTORE2',
example: 'UTC--2017-12-15T17-35-22.547Z--6be6e49e82425a5aa56396db03512f2cc10e95e8', example: 'UTC--2017-12-15T17-35-22.547Z--6be6e49e82425a5aa56396db03512f2cc10e95e8',
@ -320,7 +333,7 @@ const WalletDecrypt = withRouter<Props>(
<h2 className="WalletDecrypt-wallets-title">{translate('DECRYPT_ACCESS')}</h2> <h2 className="WalletDecrypt-wallets-title">{translate('DECRYPT_ACCESS')}</h2>
<div className="WalletDecrypt-wallets-row"> <div className="WalletDecrypt-wallets-row">
{SECURE_WALLETS.map((walletType: SecureWalletName) => { {SECURE_WALLETS.filter(this.isWalletVisible).map((walletType: SecureWalletName) => {
const wallet = this.WALLETS[walletType]; const wallet = this.WALLETS[walletType];
return ( return (
<WalletButton <WalletButton
@ -339,7 +352,7 @@ const WalletDecrypt = withRouter<Props>(
})} })}
</div> </div>
<div className="WalletDecrypt-wallets-row"> <div className="WalletDecrypt-wallets-row">
{INSECURE_WALLETS.map((walletType: InsecureWalletName) => { {INSECURE_WALLETS.filter(this.isWalletVisible).map((walletType: InsecureWalletName) => {
const wallet = this.WALLETS[walletType]; const wallet = this.WALLETS[walletType];
return ( return (
<WalletButton <WalletButton
@ -356,7 +369,7 @@ const WalletDecrypt = withRouter<Props>(
); );
})} })}
{MISC_WALLETS.map((walletType: MiscWalletName) => { {MISC_WALLETS.filter(this.isWalletVisible).map((walletType: MiscWalletName) => {
const wallet = this.WALLETS[walletType]; const wallet = this.WALLETS[walletType];
return ( return (
<WalletButton <WalletButton
@ -466,6 +479,10 @@ const WalletDecrypt = withRouter<Props>(
private isWalletDisabled = (walletKey: WalletName) => { private isWalletDisabled = (walletKey: WalletName) => {
return this.props.computedDisabledWallets.wallets.indexOf(walletKey) !== -1; return this.props.computedDisabledWallets.wallets.indexOf(walletKey) !== -1;
}; };
private isWalletVisible = (walletKey: WalletName) => {
return !this.WALLETS[walletKey].isHidden;
};
} }
); );

View File

@ -0,0 +1,121 @@
import { KeepKeyWallet } from 'libs/wallet';
import React, { PureComponent } from 'react';
import translate, { translateRaw } from 'translations';
import UnsupportedNetwork from './UnsupportedNetwork';
import { Spinner, NewTabLink } from 'components/ui';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { SecureWalletName, keepkeyReferralURL } from 'config';
import { getSingleDPath, getPaths } from 'selectors/config/wallet';
//todo: conflicts with comment in walletDecrypt -> onUnlock method
interface OwnProps {
onUnlock(param: any): void;
}
interface StateProps {
dPath: DPath | undefined;
dPaths: DPath[];
}
// todo: nearly duplicates ledger component props
interface State {
dPath: DPath;
index: string;
error: string | null;
isLoading: boolean;
}
type Props = OwnProps & StateProps;
class KeepKeyDecryptClass extends PureComponent<Props, State> {
public state: State = {
dPath: this.props.dPath || this.props.dPaths[0],
index: '0',
error: null,
isLoading: false
};
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.dPath !== nextProps.dPath && nextProps.dPath) {
this.setState({ dPath: nextProps.dPath });
}
}
public render() {
const { dPath, error, isLoading } = this.state;
const showErr = error ? 'is-showing' : '';
if (!dPath) {
return <UnsupportedNetwork walletType={translateRaw('X_KEEPKEY')} />;
}
return (
<div className="KeepKeyDecrypt">
<button
className="KeepKeyDecrypt-decrypt btn btn-primary btn-lg btn-block"
onClick={this.handleConnect}
disabled={isLoading}
>
{isLoading ? (
<div className="KeepKeyDecrypt-message">
<Spinner light={true} />
Unlocking...
</div>
) : (
translate('ADD_TREZOR_SCAN')
)}
</button>
<NewTabLink className="KeepKeyDecrypt-buy btn btn-sm btn-default" href={keepkeyReferralURL}>
{translate('Dont have a KeepKey? Order one now!')}
</NewTabLink>
<div className={`KeepKeyDecrypt-error alert alert-danger ${showErr}`}>{error || '-'}</div>
<div className="KeepKeyDecrypt-help">
<NewTabLink href="https://google.com/">How to use KeepKey with MyCrypto</NewTabLink>
</div>
</div>
);
}
private handleConnect = (): void => {
const { dPath, index } = this.state;
const indexInt = parseInt(index, 10);
this.setState({
isLoading: true,
error: null
});
KeepKeyWallet.getBip44Address(dPath.value, indexInt)
.then(address => {
this.reset();
this.props.onUnlock(new KeepKeyWallet(address, dPath.value, indexInt));
})
.catch(err => {
this.setState({
error: err.message,
isLoading: false
});
});
};
private reset() {
this.setState({
index: '0',
dPath: this.props.dPath || this.props.dPaths[0],
isLoading: false
});
}
}
function mapStateToProps(state: AppState): StateProps {
return {
dPath: getSingleDPath(state, SecureWalletName.KEEPKEY),
dPaths: getPaths(state, SecureWalletName.KEEPKEY)
};
}
export const KeepKeyDecrypt = connect(mapStateToProps)(KeepKeyDecryptClass);

View File

@ -10,3 +10,4 @@ export * from './Trezor';
export * from './ViewOnly'; export * from './ViewOnly';
export * from './WalletButton'; export * from './WalletButton';
export * from './Web3'; export * from './Web3';
export * from './KeepKey';

View File

@ -18,7 +18,7 @@ export const N_FACTOR = 8192;
// whenever making a new app release. // whenever making a new app release.
// It is currently set to: 05/25/2018 @ 12:00am (UTC) // It is currently set to: 05/25/2018 @ 12:00am (UTC)
// TODO: Remove me once app alpha / release candidates are done // TODO: Remove me once app alpha / release candidates are done
export const APP_ALPHA_EXPIRATION = 1527206400000; export const APP_ALPHA_EXPIRATION = 2527206400000;
// Displays at the top of the site, make message empty string to remove. // Displays at the top of the site, make message empty string to remove.
// Type can be primary, warning, danger, success, info, or blank for grey. // Type can be primary, warning, danger, success, info, or blank for grey.
@ -76,6 +76,7 @@ export const steelyReferralURL = 'https://stee.ly/2Hcl4RE';
export enum SecureWalletName { export enum SecureWalletName {
WEB3 = 'web3', WEB3 = 'web3',
KEEPKEY = 'keepkey',
LEDGER_NANO_S = 'ledgerNanoS', LEDGER_NANO_S = 'ledgerNanoS',
TREZOR = 'trezor', TREZOR = 'trezor',
PARITY_SIGNER = 'paritySigner' PARITY_SIGNER = 'paritySigner'

View File

@ -11,6 +11,11 @@ export class HardwareWallet extends DeterministicWallet {
throw new Error(`getChainCode is not implemented in ${this.constructor.name}`); throw new Error(`getChainCode is not implemented in ${this.constructor.name}`);
} }
// @ts-ignore
public static getBip44Address(dpath: string, index: number): Promise<string> {
throw new Error(`getBip44Address is not implemented in ${this.constructor.name}`);
}
public displayAddress(): Promise<boolean> { public displayAddress(): Promise<boolean> {
throw new Error(`displayAddress is not implemented in ${this.constructor.name}`); throw new Error(`displayAddress is not implemented in ${this.constructor.name}`);
} }

View File

@ -1,3 +1,4 @@
export * from './ledger'; export * from './ledger';
export * from './mnemonic'; export * from './mnemonic';
export * from './trezor'; export * from './trezor';
export * from './keepkey';

View File

@ -0,0 +1,76 @@
import EthTx from 'ethereumjs-tx';
import { HardwareWallet } from './hardware';
import { getTransactionFields } from 'libs/transaction';
import { IFullWallet } from '../IWallet';
import { translateRaw } from 'translations';
import EnclaveAPI, { WalletTypes } from 'shared/enclave/client';
export class KeepKeyWallet extends HardwareWallet implements IFullWallet {
public static async getBip44Address(dpath: string, index: number) {
if (process.env.BUILD_ELECTRON) {
const res = await EnclaveAPI.getAddress({
walletType: WalletTypes.KEEPKEY,
dpath,
index
});
return res.address;
}
throw new Error('KeepKey is not supported on the web');
}
constructor(address: string, dPath: string, index: number) {
super(address, dPath, index);
}
public async signRawTransaction(t: EthTx): Promise<Buffer> {
const txFields = getTransactionFields(t);
if (process.env.BUILD_ELECTRON) {
const res = await EnclaveAPI.signTransaction({
walletType: WalletTypes.KEEPKEY,
transaction: txFields,
path: this.getPath()
});
return new EthTx(res.signedTransaction).serialize();
}
throw new Error('KeepKey is not supported on the web');
}
public async signMessage(msg: string): Promise<string> {
if (!msg) {
throw Error('No message to sign');
}
if (process.env.BUILD_ELECTRON) {
const res = await EnclaveAPI.signMessage({
walletType: WalletTypes.KEEPKEY,
message: msg,
path: this.getPath()
});
return res.signedMessage;
}
throw new Error('KeepKey is not supported on the web');
}
public async displayAddress() {
const path = this.dPath + '/' + this.index;
if (process.env.BUILD_ELECTRON) {
return EnclaveAPI.displayAddress({
walletType: WalletTypes.KEEPKEY,
path
})
.then(res => res.success)
.catch(() => false);
}
throw new Error('KeepKey is not supported on the web');
}
public getWalletType(): string {
return translateRaw('X_KEEPKEY');
}
}

View File

@ -11,6 +11,7 @@ type PathType = keyof DPathFormats;
type DPathFormat = type DPathFormat =
| SecureWalletName.TREZOR | SecureWalletName.TREZOR
| SecureWalletName.LEDGER_NANO_S | SecureWalletName.LEDGER_NANO_S
| SecureWalletName.KEEPKEY
| InsecureWalletName.MNEMONIC_PHRASE; | InsecureWalletName.MNEMONIC_PHRASE;
export function getPaths(state: AppState, pathType: PathType): DPath[] { export function getPaths(state: AppState, pathType: PathType): DPath[] {

View File

@ -74,6 +74,7 @@
"ADD_METAMASK": "Connect to MetaMask ", "ADD_METAMASK": "Connect to MetaMask ",
"X_TREZOR": "TREZOR ", "X_TREZOR": "TREZOR ",
"ADD_TREZOR_SCAN": "Connect to TREZOR ", "ADD_TREZOR_SCAN": "Connect to TREZOR ",
"X_KEEPKEY": "KeepKey",
"X_PARITYSIGNER": "Parity Signer ", "X_PARITYSIGNER": "Parity Signer ",
"ADD_PARITY_DESC": "Connect & sign via your Parity Signer mobile app ", "ADD_PARITY_DESC": "Connect & sign via your Parity Signer mobile app ",
"ADD_PARITY_1": "Transaction canceled ", "ADD_PARITY_1": "Transaction canceled ",

View File

@ -11,6 +11,7 @@
"npm": ">= 5.0.0" "npm": ">= 5.0.0"
}, },
"dependencies": { "dependencies": {
"@keepkey/device-client": "4.1.3",
"@ledgerhq/hw-app-eth": "4.7.3", "@ledgerhq/hw-app-eth": "4.7.3",
"@ledgerhq/hw-transport-node-hid": "4.7.6", "@ledgerhq/hw-transport-node-hid": "4.7.6",
"@ledgerhq/hw-transport-u2f": "4.12.0", "@ledgerhq/hw-transport-u2f": "4.12.0",
@ -62,6 +63,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bip39": "2.4.0", "@types/bip39": "2.4.0",
"@types/bytebuffer": "5.0.37",
"@types/classnames": "2.2.3", "@types/classnames": "2.2.3",
"@types/enzyme": "3.1.8", "@types/enzyme": "3.1.8",
"@types/enzyme-adapter-react-16": "1.0.1", "@types/enzyme-adapter-react-16": "1.0.1",

View File

@ -3,6 +3,8 @@ import {
EnclaveMethods, EnclaveMethods,
GetChainCodeParams, GetChainCodeParams,
GetChainCodeResponse, GetChainCodeResponse,
GetAddressParams,
GetAddressResponse,
SignTransactionParams, SignTransactionParams,
SignTransactionResponse, SignTransactionResponse,
SignMessageParams, SignMessageParams,
@ -16,6 +18,10 @@ const api = {
return makeRequest<GetChainCodeResponse>(EnclaveMethods.GET_CHAIN_CODE, params); return makeRequest<GetChainCodeResponse>(EnclaveMethods.GET_CHAIN_CODE, params);
}, },
getAddress(params: GetAddressParams) {
return makeRequest<GetAddressResponse>(EnclaveMethods.GET_ADDRESS, params);
},
signTransaction(params: SignTransactionParams) { signTransaction(params: SignTransactionParams) {
return makeRequest<SignTransactionResponse>(EnclaveMethods.SIGN_TRANSACTION, params); return makeRequest<SignTransactionResponse>(EnclaveMethods.SIGN_TRANSACTION, params);
}, },

View File

@ -0,0 +1,7 @@
import { getWalletLib } from 'shared/enclave/server/wallets';
import { GetAddressParams, GetAddressResponse } from 'shared/enclave/types';
export default function(params: GetAddressParams): Promise<GetAddressResponse> {
const wallet = getWalletLib(params.walletType);
return wallet.getAddress(params.index, params.dpath);
}

View File

@ -1,4 +1,5 @@
import getChainCode from './getChainCode'; import getChainCode from './getChainCode';
import getAddress from './getAddress';
import signTransaction from './signTransaction'; import signTransaction from './signTransaction';
import signMessage from './signMessage'; import signMessage from './signMessage';
import displayAddress from './displayAddress'; import displayAddress from './displayAddress';
@ -10,6 +11,7 @@ const handlers: {
) => EnclaveMethodResponse | Promise<EnclaveMethodResponse> ) => EnclaveMethodResponse | Promise<EnclaveMethodResponse>
} = { } = {
[EnclaveMethods.GET_CHAIN_CODE]: getChainCode, [EnclaveMethods.GET_CHAIN_CODE]: getChainCode,
[EnclaveMethods.GET_ADDRESS]: getAddress,
[EnclaveMethods.SIGN_TRANSACTION]: signTransaction, [EnclaveMethods.SIGN_TRANSACTION]: signTransaction,
[EnclaveMethods.SIGN_MESSAGE]: signMessage, [EnclaveMethods.SIGN_MESSAGE]: signMessage,
[EnclaveMethods.DISPLAY_ADDRESS]: displayAddress [EnclaveMethods.DISPLAY_ADDRESS]: displayAddress

View File

@ -1,8 +1,23 @@
import { WalletLib } from 'shared/enclave/types'; import { WalletLib } from 'shared/enclave/types';
import { DeviceClientManager } from '@keepkey/device-client/dist/device-client-manager';
import { NodeVector } from '@keepkey/device-client/dist/node-vector';
const dcm = new DeviceClientManager();
const KeepKey: WalletLib = { const KeepKey: WalletLib = {
async getChainCode() { async getChainCode() {
throw new Error('Not yet implemented'); throw new Error('KeepKey doesnt getChainCode');
},
async getAddress(index?: number, dpath?: string) {
if (index === null || index === undefined || !dpath) {
throw new Error('KeepKey requires index and dpath parameters');
}
const client = await dcm.getActiveClient();
const nv = NodeVector.fromString(`${dpath}/${index}`);
const res = await client.getEthereumAddress(nv, false);
return { address: res.toString() as string };
}, },
async signTransaction() { async signTransaction() {

View File

@ -36,6 +36,10 @@ const Ledger: WalletLib = {
} }
}, },
async getAddress() {
throw new Error('Ledger does not support getAddress');
},
async signTransaction(tx, path) { async signTransaction(tx, path) {
const app = await getEthApp(); const app = await getEthApp();
const ethTx = new EthTx({ const ethTx = new EthTx({

View File

@ -16,6 +16,10 @@ const Trezor: WalletLib = {
// return { chainCode: 'test', publicKey: 'test' }; // return { chainCode: 'test', publicKey: 'test' };
}, },
async getAddress() {
throw new Error('TREZOR does not support getAddress');
},
async signTransaction() { async signTransaction() {
throw new Error('Not yet implemented'); throw new Error('Not yet implemented');
}, },

View File

@ -1,6 +1,7 @@
// Enclave enums // Enclave enums
export enum EnclaveMethods { export enum EnclaveMethods {
GET_CHAIN_CODE = 'get-chain-code', GET_CHAIN_CODE = 'get-chain-code',
GET_ADDRESS = 'get-address',
SIGN_TRANSACTION = 'sign-transaction', SIGN_TRANSACTION = 'sign-transaction',
SIGN_MESSAGE = 'sign-message', SIGN_MESSAGE = 'sign-message',
DISPLAY_ADDRESS = 'display-address' DISPLAY_ADDRESS = 'display-address'
@ -33,6 +34,17 @@ export interface GetChainCodeResponse {
chainCode: string; chainCode: string;
} }
// Get address request
export interface GetAddressParams {
walletType: WalletTypes;
dpath?: string;
index?: number;
}
export interface GetAddressResponse {
address: string;
}
// Sign Transaction Request // Sign Transaction Request
export interface SignTransactionParams { export interface SignTransactionParams {
walletType: WalletTypes; walletType: WalletTypes;
@ -68,11 +80,13 @@ export interface DisplayAddressResponse {
// All Requests & Responses // All Requests & Responses
export type EnclaveMethodParams = export type EnclaveMethodParams =
| GetChainCodeParams | GetChainCodeParams
| GetAddressParams
| SignTransactionParams | SignTransactionParams
| SignMessageParams | SignMessageParams
| DisplayAddressParams; | DisplayAddressParams;
export type EnclaveMethodResponse = export type EnclaveMethodResponse =
| GetChainCodeResponse | GetChainCodeResponse
| GetAddressResponse
| SignTransactionResponse | SignTransactionResponse
| SignMessageResponse | SignMessageResponse
| DisplayAddressResponse; | DisplayAddressResponse;
@ -99,6 +113,7 @@ export type EnclaveResponse<T = EnclaveMethodResponse> =
// Wallet lib // Wallet lib
export interface WalletLib { export interface WalletLib {
getChainCode(dpath: string): Promise<GetChainCodeResponse>; getChainCode(dpath: string): Promise<GetChainCodeResponse>;
getAddress(index?: number, dpath?: string): Promise<GetAddressResponse>;
signTransaction(transaction: RawTransaction, path: string): Promise<SignTransactionResponse>; signTransaction(transaction: RawTransaction, path: string): Promise<SignTransactionResponse>;
signMessage(msg: string, path: string): Promise<SignMessageResponse>; signMessage(msg: string, path: string): Promise<SignMessageResponse>;
displayAddress(path: string): Promise<DisplayAddressResponse>; displayAddress(path: string): Promise<DisplayAddressResponse>;

View File

@ -36,6 +36,7 @@ interface NetworkContract {
interface DPathFormats { interface DPathFormats {
trezor?: DPath; trezor?: DPath;
ledgerNanoS?: DPath; ledgerNanoS?: DPath;
keepkey?: DPath;
mnemonicPhrase: DPath; mnemonicPhrase: DPath;
} }

View File

@ -29,7 +29,8 @@
"./shared/", "./shared/",
"spec", "spec",
"./node_modules/types-rlp/index.d.ts", "./node_modules/types-rlp/index.d.ts",
"./node_modules/mycrypto-shepherd/dist/lib/types/btoa.d.ts" "./node_modules/mycrypto-shepherd/dist/lib/types/btoa.d.ts",
"./node_modules/@keepkey/device-client/dist/device-client-manager.d.ts"
], ],
"awesomeTypescriptLoaderOptions": { "awesomeTypescriptLoaderOptions": {
"transpileOnly": true "transpileOnly": true