on 4c8c607 help sign in
bridge/src/views/Login/Ledger.js
230 lines 7.1 kB history raw
1import React, { useCallback, useMemo } from 'react';
2import cn from 'classnames';
3import { Just } from 'folktale/maybe';
4import { P, Text, Grid, H5, CheckboxInput, SelectInput } from 'indigo-react';
5import { times } from 'lodash';
6import * as bip32 from 'bip32';
7import Transport from '@ledgerhq/hw-transport-u2f';
8import Eth from '@ledgerhq/hw-app-eth';
9import * as secp256k1 from 'secp256k1';
11import { useWallet } from 'store/wallet';
13import {
14 LEDGER_LIVE_PATH,
15 LEDGER_LEGACY_PATH,
16 chopHdPrefix,
17 addHdPrefix,
18} from 'lib/ledger';
19import { WALLET_TYPES } from 'lib/wallet';
20import useLoginView from 'lib/useLoginView';
21import useBreakpoints from 'lib/useBreakpoints';
23import {
24 composeValidator,
25 buildCheckboxValidator,
26 buildHdPathValidator,
27 buildSelectValidator,
28} from 'form/validators';
29import BridgeForm from 'form/BridgeForm';
30import FormError from 'form/FormError';
31import SubmitButton from 'form/SubmitButton';
32import Condition from 'form/Condition';
33import { FORM_ERROR } from 'final-form';
34import { HdPathInput } from 'form/Inputs';
35import { getAuthToken } from 'lib/authToken';
37const PATH_OPTIONS = [
38 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
39 { text: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
40];
42const ACCOUNT_OPTIONS = times(20, i => ({
43 text: `Account #${i + 1}`,
44 value: i,
45}));
47export default function Ledger({ className, goHome }) {
48 useLoginView(WALLET_TYPES.LEDGER);
50 const { setWallet, setWalletHdPath, setAuthToken } = useWallet();
52 const validate = useMemo(
53 () =>
54 composeValidator({
55 useCustomPath: buildCheckboxValidator(),
56 derivationpath: buildSelectValidator(PATH_OPTIONS),
57 account: buildSelectValidator(ACCOUNT_OPTIONS),
58 hdpath: buildHdPathValidator(),
59 }),
60 []
61 );
63 const onSubmit = useCallback(
64 async values => {
65 const transport = await Transport.create();
66 const eth = new Eth(transport);
67 const path = chopHdPrefix(values.hdpath);
69 try {
70 const info = await eth.getAddress(path, false, true);
71 const publicKey = Buffer.from(info.publicKey, 'hex');
72 const chainCode = Buffer.from(info.chainCode, 'hex');
73 const pub = secp256k1.publicKeyConvert(publicKey, true);
74 const hd = bip32.fromPublicKey(pub, chainCode);
75 const authToken = await getAuthToken({
76 wallet: hd,
77 walletType: WALLET_TYPES.LEDGER,
78 walletHdPath: addHdPrefix(values.hdpath),
79 });
81 setAuthToken(Just(authToken));
82 setWallet(Just(hd));
83 setWalletHdPath(addHdPrefix(values.hdpath));
84 } catch (error) {
85 console.error(error);
86 return { [FORM_ERROR]: error.message };
87 }
88 },
89 [setAuthToken, setWallet, setWalletHdPath]
90 );
92 const onValues = useCallback(({ valid, values, form }) => {
93 if (!valid) {
94 return;
95 }
97 // when the base path pattern or the account index changes
98 // update the hd path in our input
99 if (!values.useCustomPath) {
100 form.change(
101 'hdpath',
102 values.derivationpath.replace(/x/g, values.account)
103 );
104 }
105 }, []);
107 const initialValues = useMemo(
108 () => ({
109 useCustomPath: false,
110 hdpath: PATH_OPTIONS[0].value.replace(/x/g, ACCOUNT_OPTIONS[0].value),
111 derivationpath: PATH_OPTIONS[0].value,
112 account: ACCOUNT_OPTIONS[0].value,
113 }),
114 []
115 );
117 const full = useBreakpoints([true, true, false]);
118 const half = useBreakpoints([false, false, true]);
119 const isHTTPS = document.location.protocol === 'https:';
121 // when not on https, tell user how to get there
122 const renderHTTP = () => (
123 <>
124 <Grid.Item full as={H5}>
125 Running on HTTP?
126 </Grid.Item>
128 <Grid.Item full as={P}>
129 To authenticate and sign transactions with a Ledger, Bridge must be
130 serving over HTTPS on localhost. You can do this via the following:
131 </Grid.Item>
133 <Grid.Item full as="ol">
134 <li>
135 Install{' '}
136 <a
137 target="_blank"
138 href="https://github.com/FiloSottile/mkcert"
139 rel="noopener noreferrer">
140 mkcert
141 </a>
142 </li>
143 <li className="mt3">
144 Install a local certificate authority via <code>mkcert -install</code>
145 </li>
146 <li className="mt3">
147 In your <code>bridge</code> directory, generate a certificate valid
148 for localhost via <code>mkcert localhost</code>. This will produce two
149 files: <code>localhost.pem</code>, the local certificate, and
150 <code>localhost-key.pem</code> , its corresponding private key.
151 </li>
152 <li className="mt3">
153 Run <code>python bridge-https.py</code>
154 </li>
155 <li className="mt3">Return to this page.</li>
156 </Grid.Item>
157 </>
158 );
160 const renderHTTPS = () => (
161 <>
162 <Grid.Item full as={Text} className="f6 gray4 mb3">
163 Connect and authenticate to your Ledger, and then open the "Ethereum"
164 application. If you're running on older firmware, make sure the "browser
165 support" option is turned on. To sign transactions, you'll also need to
166 enable the "contract data" option. You will be prompted to sign a
167 message. This allows Bridge to operate correctly. Never sign this
168 message outside of Bridge.
169 </Grid.Item>
171 <BridgeForm
172 validate={validate}
173 onValues={onValues}
174 onSubmit={onSubmit}
175 afterSubmit={goHome}
176 initialValues={initialValues}>
177 {({ handleSubmit, submitting }) => (
178 <>
179 <Condition when="useCustomPath" is={true}>
180 <Grid.Item full as={HdPathInput} name="hdpath" label="HD Path" />
181 </Condition>
183 <Condition when="useCustomPath" is={false}>
184 <Grid.Item
185 full={full}
186 half={half && 1}
187 as={SelectInput}
188 className={cn({ pr1: half })}
189 name="derivationpath"
190 label="Derivation Path"
191 placeholder="Choose path pattern..."
192 options={PATH_OPTIONS}
193 />
195 <Grid.Item
196 full={full}
197 half={half && 2}
198 as={SelectInput}
199 className={cn({ pl1: half })}
200 name="account"
201 label="Account"
202 placeholder="Choose account..."
203 options={ACCOUNT_OPTIONS}
204 />
205 </Condition>
207 <Grid.Item
208 full
209 as={CheckboxInput}
210 className="mv3"
211 name="useCustomPath"
212 label="Custom HD Path"
213 />
215 <Grid.Item full as={FormError} />
217 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
218 {!submitting && 'Authenticate'}
219 {submitting && 'Please check your device'}
220 </Grid.Item>
221 </>
222 )}
223 </BridgeForm>
224 </>
225 );
227 return (
228 <Grid className={className}>{isHTTPS ? renderHTTPS() : renderHTTP()}</Grid>
229 );