Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Electron TREZOR Support #1946

Merged
merged 7 commits into from
Jun 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion common/libs/wallet/deterministic/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { makeEnclaveWallet } from './enclave';
import { WalletTypes } from 'shared/enclave/client';
import { LedgerWallet as LedgerWalletWeb } from './ledger';
import { TrezorWallet as TrezorWalletWeb } from './trezor';

function enclaveOrWallet<T>(type: WalletTypes, lib: T) {
return process.env.BUILD_ELECTRON ? makeEnclaveWallet(type) : lib;
}

export * from './mnemonic';
export * from './trezor';
export * from './hardware';
export const LedgerWallet = enclaveOrWallet(WalletTypes.LEDGER, LedgerWalletWeb);
export const TrezorWallet = enclaveOrWallet(WalletTypes.TREZOR, TrezorWalletWeb);
23 changes: 7 additions & 16 deletions common/libs/wallet/deterministic/trezor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,20 @@ import BN from 'bn.js';
import EthTx, { TxObj } from 'ethereumjs-tx';
import { addHexPrefix } from 'ethereumjs-util';
import { stripHexPrefixAndLower, padLeftEven } from 'libs/values';
import TC from 'vendor/trezor-connect';
import TrezorConnect from 'vendor/trezor-connect';
import { HardwareWallet, ChainCodeResponse } from './hardware';
import { getTransactionFields } from 'libs/transaction';
import mapValues from 'lodash/mapValues';
import { translateRaw } from 'translations';
import EnclaveAPI, { WalletTypes } from 'shared/enclave/client';

export const TREZOR_MINIMUM_FIRMWARE = '1.5.2';
const TrezorConnect = TC as any;

export class TrezorWallet extends HardwareWallet {
public static getChainCode(dpath: string): Promise<ChainCodeResponse> {
if (process.env.BUILD_ELECTRON) {
return EnclaveAPI.getChainCode({
walletType: WalletTypes.TREZOR,
dpath
});
}

return new Promise(resolve => {
TrezorConnect.getXPubKey(
dpath,
(res: any) => {
res => {
if (res.success) {
resolve({
publicKey: res.publicKey,
Expand Down Expand Up @@ -56,7 +47,7 @@ export class TrezorWallet extends HardwareWallet {
cleanedTx.data,
chainId,
// Callback
(result: any) => {
result => {
if (!result.success) {
return reject(Error(result.error));
}
Expand All @@ -66,7 +57,7 @@ export class TrezorWallet extends HardwareWallet {
const txToSerialize: TxObj = {
...strTx,
v: addHexPrefix(new BN(result.v).toString(16)),
r: addHexPrefix(result.r),
r: addHexPrefix(result.r.toString()),
s: addHexPrefix(result.s)
};
const eTx = new EthTx(txToSerialize);
Expand All @@ -81,11 +72,11 @@ export class TrezorWallet extends HardwareWallet {
return Promise.reject(new Error('Signing via Trezor not yet supported.'));
}

public displayAddress(): Promise<any> {
public displayAddress(): Promise<boolean> {
return new Promise(resolve => {
TrezorConnect.ethereumGetAddress(
this.dPath + '/' + this.index,
(res: any) => {
`${this.dPath}/${this.index}`,
res => {
if (res.error) {
resolve(false);
} else {
Expand Down
5 changes: 1 addition & 4 deletions common/selectors/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,7 @@ export function getDisabledWallets(state: AppState): DisabledWallets {

// Some wallets are disabled on certain platforms
if (process.env.BUILD_ELECTRON) {
addReason(
[SecureWalletName.WEB3, SecureWalletName.TREZOR],
'This wallet is not supported in the MyCrypto app'
);
addReason([SecureWalletName.WEB3], 'This wallet is not supported in the MyCrypto app');
}

// Dedupe and sort for consistency
Expand Down
69 changes: 69 additions & 0 deletions common/typescript/trezor-connect.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
declare module 'vendor/trezor-connect' {
type Path = number[] | string;

interface TxSignature {
r: number;
s: string;
v: string;
}

interface MessageSignature {
signature: string;
address: string;
}

interface PublicKey {
xpubkey: string;
path: string;
serializedPath: string;
chainCode: string;
publicKey: string;
}

interface ErrorResponse {
success: false;
error: string;
}
type SuccessResponse<T> = {
success: true;
error: undefined;
} & T;
type Response<T> = ErrorResponse | SuccessResponse<T>;

namespace TrezorConnect {
export function getXPubKey(
path: Path,
cb: (res: Response<PublicKey>) => void,
minFirmware?: string
): void;

export function ethereumSignTx(
path: Path,
nonce: string,
gasPrice: string,
gasLimit: string,
to: string,
value: string,
data: string | null,
chainId: number | null,
cb: (signature: Response<TxSignature>) => void,
minFirmware?: string
): void;

export function signMessage(
path: Path,
message: string,
cb: (res: Response<MessageSignature>) => void,
coin?: string,
minFirmware?: string
): void;

export function ethereumGetAddress(
path: Path,
cb: (res: Response<{ address: string }>) => void,
minFirmware?: string
): void;
}

export default TrezorConnect;
}
191 changes: 191 additions & 0 deletions common/typescript/trezor-js.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/* tslint:disable max-classes-per-file */
// Types are only based off of what's mentioned in the API
// https://github.com/trezor/trezor.js/blob/master/API.md

declare module 'mycrypto-trezor.js' {
import { EventEmitter } from 'events';
import { Transport, TrezorDeviceInfoWithSession as DeviceDescriptor } from 'trezor-link';

/***************/
/* Device List */
/***************/

export interface DeviceListOptions {
debug?: boolean;
debugInfo?: boolean;
transport?: Transport;
nodeTransport?: Transport;
configUrl?: string;
config?: string;
bridgeVersionUrl?: string;
clearSession?: boolean;
clearSessionTime?: number;
rememberDevicePasshprase?: boolean;
// Unsure of these options or our need for them
// getPassphraseHash?(device: Device): number[] | undefined;
// xpubDerive?: (xpub: string, network: bitcoin.Network, index: number) => Promise<string>;
}

export class DeviceList extends EventEmitter {
public transport: Transport | undefined;
public devices: { [k: string]: Device };
public unacquiredDevices: { [k: string]: UnacquiredDevice };
constructor(opts?: DeviceListOptions);
public acquireFirstDevice(
rejectOnEmpty?: boolean
): Promise<{ device: Device; session: Session }>;
}

/**********/
/* Device */
/**********/

export interface CoinType {
coin_name: string;
coin_shortcut: string;
address_type: number;
maxfee_kb: number;
address_type_p2sh: number;
}

export interface Features {
vendor: string;
major_version: number;
minor_version: number;
patch_version: number;
bootloader_mode: boolean;
device_id: string;
pin_protection: boolean;
passphrase_protection: boolean;
language: string;
label: string;
coins: CoinType[];
initialized: boolean;
revision: string;
bootloader_hash: string;
imported: boolean;
pin_cached: boolean;
passphrase_cached: boolean;
needs_backup?: boolean;
firmware_present?: boolean;
flags?: number;
model?: string;
unfinished_backup?: boolean;
}

export interface RunOptions {
aggressive?: boolean;
skipFinalReload?: boolean;
waiting?: boolean;
onlyOneActivity?: boolean;
}

export class Device extends EventEmitter {
public path: string;
public features: Features;

constructor(
transport: Transport,
descriptor: DeviceDescriptor,
features: Features,
deviceList: DeviceList
);

public isBootloader(): boolean;
public isInitialized(): boolean;
public getVersion(): string;
public atLeast(v: string): boolean;
public isUsed(): boolean;
public isUsedHere(): boolean;
public isUsedElsewhere(): boolean;

public run<T>(fn: (session: Session) => Promise<T> | T, options?: RunOptions): Promise<T>;
public waitForSessionAndRun<T>(
fn: (session: Session) => Promise<T> | T,
options?: RunOptions
): Promise<T>;
public steal(): Promise<boolean>;
}

/*********************/
/* Unacquired Device */
/*********************/

export class UnacquiredDevice extends EventEmitter {
public path: string;
constructor(transport: Transport, descriptor: DeviceDescriptor, deviceList: DeviceList);
public steal(): Promise<boolean>;
}

/***********/
/* Session */
/***********/

export interface MessageResponse<T> {
type: string;
message: T;
}

export interface EthereumSignature {
v: number;
r: string;
s: string;
}

export interface HDPubNode {
depth: number;
fingerprint: number;
child_num: number;
chain_code: string;
public_key: string;
}

export interface PublicKey {
node: HDPubNode;
xpub: string;
}

export type DefaultMessageResponse = MessageResponse<object>;

export class Session extends EventEmitter {
public typedCall<T>(type: string, resType: string, message: T): Promise<T>;
public getEntropy(size: number): Promise<MessageResponse<{ bytes: string }>>;
public ethereumGetAddress(
path: number[],
display?: boolean
): Promise<MessageResponse<{ address: string; path: number[] }>>;
public clearSession(): Promise<boolean>;
public signEthMessage(
path: number[],
message: string
): Promise<MessageResponse<{ address: string; signature: string }>>;
public verifyEthMessage(address: string, signature: string, message: string): Promise<boolean>;
public signEthTx(
path: number[],
nonce: string,
gasPrice: string,
gasLimit: string,
to: string,
value: string,
data?: string,
chainId?: number
): Promise<EthereumSignature>;
public getPublicKey(
path: number[],
coin?: string | CoinType
): Promise<MessageResponse<PublicKey>>;

/* Unused functions, either Bitcoin-centric or just things we wouldn't want to touch */
// public getAddress(path: number[], coin: string | CoinType, display: boolean): Promise<MessageResponse<{ address: string }>>;
// public verifyAddress(path: number[], refAddress: string, coin: string | CoinType): Promise<boolean>;
// public getHDNode(path: number[], coin: string | CoinType): Promise<HDNode>;
// public wipeDevice(): Promise<boolean>;
// public resetDevice(...): Promise<boolean>;
// public loadDevice(...): Promise<boolean>;
// public recoverDevice(...): Promise<boolean>;
// public updateFirmware(payload: string): Promise<boolean>;
// public signMessage(path: number[], message: string, coin: string | CoinType, segwit: boolean): Promise<object>;
// public verifyMessage(...): Promise<boolean>;
// public signIdentity(...): any;
}
}
39 changes: 39 additions & 0 deletions common/typescript/trezor-link.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
declare module 'trezor-link' {
export interface TrezorDeviceInfo {
path: string;
}

export interface TrezorDeviceInfoWithSession extends TrezorDeviceInfo {
session?: string;
}

export interface AcquireInput {
path: string;
previous?: string;
checkPrevious: boolean;
}

export interface MessageFromTrezor {
type: string;
message: any;
}

export interface Transport {
configured: boolean;
version: string;
name: string;
requestNeeded: boolean;
isOutdated: boolean;

enumerate(): Promise<TrezorDeviceInfoWithSession[]>;
listen(old?: TrezorDeviceInfoWithSession[]): Promise<TrezorDeviceInfoWithSession[]>;
acquire(input: AcquireInput): Promise<string>;
release(session: string, onclose: boolean): Promise<void>;
configure(signedData: string): Promise<void>;
call(session: string, name: string, data: any): Promise<MessageFromTrezor>;
init(debug?: boolean): Promise<void>;
stop(): void;
requestDevice(): Promise<void>;
setBridgeLatestUrl(url: string): void;
}
}
Loading