MyCrypto/common/libs/wallet/deterministic/ledger.ts

145 lines
4.1 KiB
TypeScript

import ledger from 'ledgerco';
import EthTx, { TxObj } from 'ethereumjs-tx';
import { addHexPrefix, toBuffer } from 'ethereumjs-util';
import { HardwareWallet, ChainCodeResponse } from './hardware';
import { getTransactionFields } from 'libs/transaction';
import { IFullWallet } from '../IWallet';
import { translateRaw } from 'translations';
import EnclaveAPI, { WalletTypes } from 'shared/enclave/client';
export class LedgerWallet extends HardwareWallet implements IFullWallet {
public static async getChainCode(dpath: string): Promise<ChainCodeResponse> {
if (process.env.BUILD_ELECTRON) {
return EnclaveAPI.getChainCode({
walletType: WalletTypes.LEDGER,
dpath
});
}
return makeApp()
.then(app => app.getAddress_async(dpath, false, true))
.then(res => {
return {
publicKey: res.publicKey,
chainCode: res.chainCode
};
})
.catch((err: any) => {
throw new Error(ledgerErrToMessage(err));
});
}
private ethApp: ledger.eth;
constructor(address: string, dPath: string, index: number) {
super(address, dPath, index);
makeApp().then(app => (this.ethApp = app));
}
// modeled after
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/uiFuncs.js#L58
public async signRawTransaction(t: EthTx): Promise<Buffer> {
const txFields = getTransactionFields(t);
if (process.env.BUILD_ELECTRON) {
const res = await EnclaveAPI.signTransaction({
walletType: WalletTypes.LEDGER,
transaction: txFields,
path: this.getPath()
});
return new EthTx(res.signedTransaction).serialize();
}
t.v = Buffer.from([t._chainId]);
t.r = toBuffer(0);
t.s = toBuffer(0);
try {
const result = await this.ethApp.signTransaction_async(
this.getPath(),
t.serialize().toString('hex')
);
const txToSerialize: TxObj = {
...txFields,
v: addHexPrefix(result.v),
r: addHexPrefix(result.r),
s: addHexPrefix(result.s)
};
return new EthTx(txToSerialize).serialize();
} catch (err) {
throw Error(err + '. Check to make sure contract data is on');
}
}
// modeled after
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/controllers/signMsgCtrl.js#L53
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.LEDGER,
message: msg,
path: this.getPath()
});
return res.signedMessage;
}
const msgHex = Buffer.from(msg).toString('hex');
try {
const signed = await this.ethApp.signPersonalMessage_async(this.getPath(), msgHex);
const combined = addHexPrefix(signed.r + signed.s + signed.v.toString(16));
return combined;
} catch (error) {
throw (this.ethApp as any).getError(error);
}
}
public displayAddress() {
const path = this.dPath + '/' + this.index;
if (process.env.BUILD_ELECTRON) {
return EnclaveAPI.displayAddress({
walletType: WalletTypes.LEDGER,
path
})
.then(res => res.success)
.catch(() => false);
}
return this.ethApp
.getAddress_async(path, true, false)
.then(() => true)
.catch(() => false);
}
public getWalletType(): string {
return translateRaw('X_LEDGER');
}
}
function makeApp(): Promise<ledger.eth> {
return ledger.comm_u2f.create_async().then((comm: any) => new ledger.eth(comm));
}
function ledgerErrToMessage(err: any) {
// Timeout
if (err && err.metaData && err.metaData.code === 5) {
return translateRaw('LEDGER_TIMEOUT');
}
// Wrong app logged into
if (err && err.includes && err.includes('6804')) {
return translateRaw('LEDGER_WRONG_APP');
}
// Ledger locked
if (err && err.includes && err.includes('6801')) {
return translateRaw('LEDGER_LOCKED');
}
// Other
return err && err.metaData ? err.metaData.type : err.toString();
}