Skip to content

Commit

Permalink
Wallet Decrypt - Ledger (#238)
Browse files Browse the repository at this point in the history
* add static vendor js libraries

* add ledger config

* add ledger components

* add ledger wallet

* bugfix: trezor, set dPath on change

* add rlp type package, update types

* change address to public

* update tslint script to exclude all files in directory

* revert to private address and use getAddress()

* remove unnecessary eslint line out of library files

* remove IWallet import

* Fix ts errors

* Remove version controlled vendor files from DLL
  • Loading branch information
skubakdj authored and dternyak committed Oct 5, 2017
1 parent 81c8176 commit aac0176
Show file tree
Hide file tree
Showing 16 changed files with 1,477 additions and 36 deletions.
26 changes: 26 additions & 0 deletions common/components/WalletDecrypt/LedgerNano.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.LedgerDecrypt {
text-align: center;
padding-top: 30px;

&-decrypt {
width: 100%;
}

&-help {
margin-top: 10px;
font-size: 13px;
}

&-error {
opacity: 0;
transition: none;

&.is-showing {
opacity: 1;
}
}

&-buy {
margin-top: 10px;
}
}
151 changes: 136 additions & 15 deletions common/components/WalletDecrypt/LedgerNano.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,150 @@
import './LedgerNano.scss';
import React, { Component } from 'react';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import DeterministicWalletsModal from './DeterministicWalletsModal';
import LedgerWallet from 'libs/wallet/ledger';
import Ledger3 from 'vendor/ledger3';
import LedgerEth from 'vendor/ledger-eth';
import DPATHS from 'config/dpaths';

const DEFAULT_PATH = DPATHS.LEDGER[0].value;

interface Props {
onUnlock(param: any): void;
}

interface State {
publicKey: string;
chainCode: string;
dPath: string;
error: string | null;
isLoading: boolean;
}

export default class LedgerNanoSDecrypt extends Component<Props, State> {
public state: State = {
publicKey: '',
chainCode: '',
dPath: DEFAULT_PATH,
error: null,
isLoading: false
};

export default class LedgerNanoSDecrypt extends Component {
public render() {
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>
{translate('ADD_Radio_2_alt')}
</h4>
const { dPath, publicKey, chainCode, error, isLoading } = this.state;
const showErr = error ? 'is-showing' : '';

<div className="form-group">
<input type="file" id="fselector" />
return (
<section className="LedgerDecrypt col-md-4 col-sm-6">
<button
className="LedgerDecrypt-decrypt btn btn-primary btn-lg"
onClick={this.handleNullConnect}
disabled={isLoading}
>
{isLoading ? 'Unlocking...' : translate('ADD_Ledger_scan')}
</button>

<div className="LedgerDecrypt-help">
Guides:
<div>
<a
href="http://support.ledgerwallet.com/knowledge_base/topics/how-to-use-myetherwallet-with-ledger"
target="_blank"
rel="noopener"
>
How to use MyEtherWallet with your Nano S
</a>
</div>
<div>
<a
className="btn-file marg-v-sm"
id="aria1"
tabIndex={0}
role="button"
href="https://ledger.groovehq.com/knowledge_base/topics/how-to-secure-your-eth-tokens-augur-rep-dot-dot-dot-with-your-nano-s"
target="_blank"
rel="noopener"
>
{translate('ADD_Radio_2_short')}
How to secure your tokens with your Nano S
</a>
</div>
</div>

<div className={`LedgerDecrypt-error alert alert-danger ${showErr}`}>
{error || '-'}
</div>

<a
className="LedgerDecrypt-buy btn btn-sm btn-default"
href="https://www.ledgerwallet.com/r/fa4b?path=/products/"
target="_blank"
rel="noopener"
>
{translate('Don’t have a Ledger? Order one now!')}
</a>

<DeterministicWalletsModal
isOpen={!!publicKey && !!chainCode}
publicKey={publicKey}
chainCode={chainCode}
dPath={dPath}
dPaths={DPATHS.LEDGER}
onCancel={this.handleCancel}
onConfirmAddress={this.handleUnlock}
onPathChange={this.handlePathChange}
walletType={translateRaw('x_Ledger')}
/>
</section>
);
}

private handlePathChange = (dPath: string) => {
this.handleConnect(dPath);
};

private handleConnect = (dPath: string = this.state.dPath) => {
this.setState({
isLoading: true,
error: null
});

const ledger = new Ledger3('w0w');
const ethApp = new LedgerEth(ledger);

ethApp.getAddress(
dPath,
(res, err) => {
if (err) {
err = ethApp.getError(err);
}

if (res) {
this.setState({
publicKey: res.publicKey,
chainCode: res.chainCode,
isLoading: false
});
} else {
this.setState({
error: err,
isLoading: false
});
}
},
false,
true
);
};

private handleCancel = () => {
this.setState({
publicKey: '',
chainCode: '',
dPath: DEFAULT_PATH
});
};

private handleUnlock = (address: string, index: number) => {
this.props.onUnlock(new LedgerWallet(address, this.state.dPath, index));
};

private handleNullConnect = (): void => {
return this.handleConnect();
};
}
1 change: 1 addition & 0 deletions common/components/WalletDecrypt/Trezor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default class TrezorDecrypt extends Component<Props, State> {
}

private handlePathChange = (dPath: string) => {
this.setState({ dPath });
this.handleConnect(dPath);
};

Expand Down
21 changes: 9 additions & 12 deletions common/components/WalletDecrypt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ const WALLETS = {
'ledger-nano-s': {
lid: 'x_Ledger',
component: LedgerNanoSDecrypt,
disabled: true
initialParams: {},
unlock: setWallet,
disabled: false
},
trezor: {
lid: 'x_Trezor',
Expand Down Expand Up @@ -120,9 +122,7 @@ export class WalletDecrypt extends Component<Props, State> {
onChange={this.handleDecryptionChoiceChange}
disabled={wallet.disabled}
/>
<span id={`${key}-label`}>
{translate(wallet.lid)}
</span>
<span id={`${key}-label`}>{translate(wallet.lid)}</span>
</label>
);
});
Expand All @@ -149,19 +149,15 @@ export class WalletDecrypt extends Component<Props, State> {
return (
<article className="Tab-content-pane row">
<section className="col-md-4 col-sm-6">
<h4>
{translate('decrypt_Access')}
</h4>
<h4>{translate('decrypt_Access')}</h4>

{this.buildWalletOptions()}
</section>

{decryptionComponent}
{!!(this.state.value as PrivateKeyValue).valid &&
{!!(this.state.value as PrivateKeyValue).valid && (
<section className="col-md-4 col-sm-6">
<h4 id="uploadbtntxt-wallet">
{translate('ADD_Label_6')}
</h4>
<h4 id="uploadbtntxt-wallet">{translate('ADD_Label_6')}</h4>
<div className="form-group">
<a
tabIndex={0}
Expand All @@ -172,7 +168,8 @@ export class WalletDecrypt extends Component<Props, State> {
{translate('ADD_Label_6_short')}
</a>
</div>
</section>}
</section>
)}
</article>
);
}
Expand Down
5 changes: 4 additions & 1 deletion common/config/dpaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ const MNEMONIC = [
EXPANSE
];

const LEDGER = [ETH_LEDGER, ETC_LEDGER, TESTNET];

export default {
TREZOR,
MNEMONIC
MNEMONIC,
LEDGER
};
2 changes: 0 additions & 2 deletions common/libs/wallet/deterministic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { IWallet } from './IWallet';

export default class DeterministicWallet {
private address: string;
private dPath: string;
Expand Down
1 change: 1 addition & 0 deletions common/libs/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { default as PresaleWallet } from './presale';
export { default as MewV1Wallet } from './mewv1';
export { default as UtcWallet } from './utc';
export { default as MnemonicWallet } from './mnemonic';
export { default as LedgerWallet } from './ledger';
91 changes: 91 additions & 0 deletions common/libs/wallet/ledger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Ledger3 from 'vendor/ledger3';
import LedgerEth from 'vendor/ledger-eth';
import EthTx from 'ethereumjs-tx';
import { addHexPrefix, rlp } from 'ethereumjs-util';
import DeterministicWallet from './deterministic';
import { IWallet } from './IWallet';
import { RawTransaction } from 'libs/transaction';

export default class LedgerWallet extends DeterministicWallet
implements IWallet {
private ledger: any;
private ethApp: any;

constructor(address: string, dPath: string, index: number) {
super(address, dPath, index);
this.ledger = new Ledger3('w0w');
this.ethApp = new LedgerEth(this.ledger);
}

// modeled after
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/uiFuncs.js#L58
public signRawTransaction(rawTx: RawTransaction): Promise<string> {
return new Promise((resolve, reject) => {
const eTx = new EthTx({
...rawTx,
v: Buffer.from([rawTx.chainId]),
r: 0,
s: 0
});

this.ethApp.signTransaction(
this.getPath(),
rlp.encode(eTx.raw).toString('hex'),
(result, error) => {
if (error) {
return reject(this.ethApp.getError(error));
}

const txToSerialize = {
...rawTx,
v: addHexPrefix(result.v),
r: addHexPrefix(result.r),
s: addHexPrefix(result.s)
};

const serializedTx = new EthTx(txToSerialize)
.serialize()
.toString('hex');

resolve(addHexPrefix(serializedTx));
}
);
});
}

// modeled after
// https://github.com/kvhnuke/etherwallet/blob/3f7ff809e5d02d7ea47db559adaca1c930025e24/app/scripts/controllers/signMsgCtrl.js#L53
public signMessage(msg: string): Promise<string> {
return new Promise((resolve, reject) => {
const msgHex = Buffer.from(msg).toString('hex');

this.ethApp.signPersonalMessage_async(
this.getPath(),
msgHex,
async (signed, error) => {
if (error) {
return reject(this.ethApp.getError(error));
}

try {
const combined = signed.r + signed.s + signed.v;
const combinedHex = combined.toString('hex');
const signedMsg = JSON.stringify(
{
address: await this.getAddress(),
msg,
sig: addHexPrefix(combinedHex),
version: '2'
},
null,
2
);
resolve(signedMsg);
} catch (err) {
reject(err);
}
}
);
});
}
}
2 changes: 2 additions & 0 deletions common/typescript/ethereumjs-tx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ declare module 'ethereumjs-tx' {

export = ITx;
class ITx {
public raw: Buffer;

constructor(data: Data);
/**
* If the tx's `to` is to the creation address
Expand Down
1 change: 1 addition & 0 deletions common/typescript/ethereumjs-util.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
declare module 'ethereumjs-util' {
import { Buffer } from 'buffer';
import BN = require('bn.js');
export import rlp = require('rlp');

interface Signature {
v: number;
Expand Down
Loading

0 comments on commit aac0176

Please sign in to comment.