on c3c8a37 help sign in
bridge/src/views/UrbitOS/NetworkingKeys.js
349 lines 10 kB history raw
1import React, { useCallback, useMemo, useRef, useState } from 'react';
2import { Just } from 'folktale/maybe';
3import { Grid, ToggleInput, CheckboxInput, Button } from 'indigo-react';
4import * as azimuth from 'azimuth-js';
5import { randomHex } from 'web3-utils';
7import { usePointCursor } from 'store/pointCursor';
8import { usePointCache } from 'store/pointCache';
9import { useNetwork } from 'store/network';
10import { useWallet } from 'store/wallet';
11import { useRollerStore } from 'store/roller';
13import useRoller from 'lib/useRoller';
14import { useLocalRouter } from 'lib/LocalRouter';
15import * as need from 'lib/need';
16import {
17 attemptNetworkSeedDerivation,
18 deriveNetworkKeys,
19 CRYPTO_SUITE_VERSION,
20} from 'lib/keys';
21import useEthereumTransaction from 'lib/useEthereumTransaction';
22import { GAS_LIMITS } from 'lib/constants';
23import { addHexPrefix } from 'lib/utils/address';
24import useKeyfileGenerator from 'lib/useKeyfileGenerator';
26import ViewHeader from 'components/ViewHeader';
27import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
28import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
29import NoticeBox from 'components/NoticeBox';
30import AlertBox from 'components/AlertBox';
32import { HexInput } from 'form/Inputs';
33import {
34 composeValidator,
35 buildCheckboxValidator,
36 buildHexValidator,
37} from 'form/validators';
38import BridgeForm from 'form/BridgeForm';
39import Condition from 'form/Condition';
40import FormError from 'form/FormError';
41import { convertToInt } from 'lib/convertToInt';
43function useSetKeys(manualNetworkSeed, setManualNetworkSeed) {
44 const { urbitWallet, wallet, authMnemonic, authToken } = useWallet();
45 const { pointCursor } = usePointCursor();
46 const { syncDetails, syncRekeyDate, getDetails } = usePointCache();
47 const { contracts } = useNetwork();
49 const _point = need.point(pointCursor);
50 const _contracts = need.contracts(contracts);
51 const _details = need.details(getDetails(_point));
53 const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
54 const randomSeed = useRef();
56 const {
57 available: keyfileAvailable,
58 generating: keyfileGenerating,
59 filename,
60 bind: keyfileBind,
61 } = useKeyfileGenerator(manualNetworkSeed);
63 const buildNetworkSeed = useCallback(
64 async manualSeed => {
65 if (manualSeed !== undefined) {
66 setManualNetworkSeed(manualSeed);
67 return manualSeed;
68 } else {
69 const newNetworkRevision = networkRevision + 1;
70 console.log(`deriving seed with revision ${newNetworkRevision}`);
72 const networkSeed = await attemptNetworkSeedDerivation({
73 urbitWallet,
74 wallet,
75 authMnemonic,
76 details: _details,
77 point: _point,
78 authToken,
79 revision: newNetworkRevision,
80 });
82 if (Just.hasInstance(networkSeed)) {
83 return networkSeed.value;
84 }
86 randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes
87 setManualNetworkSeed(randomSeed.current);
89 return randomSeed.current;
90 }
91 },
92 [
93 _details,
94 authMnemonic,
95 setManualNetworkSeed,
96 networkRevision,
97 urbitWallet,
98 wallet,
99 _point,
100 authToken,
101 ]
102 );
104 const { completed: _completed, ...rest } = useEthereumTransaction(
105 useCallback(
106 async (manualSeed, isDiscontinuity) => {
107 const seed = await buildNetworkSeed(manualSeed);
108 const pair = deriveNetworkKeys(seed);
110 return azimuth.ecliptic.configureKeys(
111 _contracts,
112 _point,
113 addHexPrefix(pair.crypt.public),
114 addHexPrefix(pair.auth.public),
115 CRYPTO_SUITE_VERSION,
116 isDiscontinuity
117 );
118 },
119 [_contracts, _point, buildNetworkSeed]
120 ),
121 useCallback(
122 () => Promise.all([syncDetails(_point), syncRekeyDate(_point)]),
123 [_point, syncDetails, syncRekeyDate]
124 ),
125 GAS_LIMITS.CONFIGURE_KEYS
126 );
128 // only treat the transaction as completed once we also have keys to download
129 const completed = _completed && keyfileAvailable && !keyfileGenerating;
131 return {
132 completed,
133 filename,
134 keyfileBind,
135 ...rest,
136 };
139export default function UrbitOSNetworkingKeys({
140 manualNetworkSeed,
141 setManualNetworkSeed,
142}) {
143 const { pop } = useLocalRouter();
144 const { pointCursor } = usePointCursor();
145 const { getDetails } = usePointCache();
146 const { currentL2, currentL2Spawn } = useRollerStore();
147 const { configureNetworkingKeys, getPendingTransactions } = useRoller();
148 const [breach, setBreach] = useState(false);
150 const point = need.point(pointCursor);
151 const details = getDetails(point);
153 const hasKeys = details.matchWith({
154 Nothing: () => false,
155 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
156 // we actually don't mind the default NaN behavior of parseInt here,
157 // since NaN > 0 === false and that's a reasonable result
158 });
160 const {
161 construct,
162 unconstruct,
163 completed,
164 inputsLocked,
165 bind,
166 keyfileBind,
167 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
169 const validateForm = useCallback((values, errors) => {
170 if (values.useNetworkSeed && errors.networkSeed) {
171 return errors;
172 }
173 }, []);
175 const validate = useMemo(
176 () =>
177 composeValidator(
178 {
179 useNetworkSeed: buildCheckboxValidator(),
180 networkSeed: buildHexValidator(64), // 64 chars
181 useDiscontinuity: buildCheckboxValidator(),
182 },
183 validateForm
184 ),
185 [validateForm]
186 );
188 const onValues = useCallback(
189 ({ valid, values, form }) => {
190 if (valid) {
191 construct(
192 values.useNetworkSeed ? values.networkSeed : undefined,
193 values.useDiscontinuity
194 );
195 setBreach(values.useDiscontinuity);
196 setManualNetworkSeed(values.useNetworkSeed);
197 } else {
198 unconstruct();
199 }
201 if (!values.useNetworkSeed && values.networkSeed) {
202 form.change('networkSeed', '');
203 setBreach(false);
204 setManualNetworkSeed('');
205 }
206 },
207 [construct, unconstruct, setManualNetworkSeed, setBreach]
208 );
210 const setNetworkingKeys = useCallback(async () => {
211 // setLoading(true);
212 console.log(breach, manualNetworkSeed);
213 try {
214 const txHash = await configureNetworkingKeys({
215 breach,
216 manualNetworkSeed,
217 });
218 // TODO: just use the tx hash instead?
219 getPendingTransactions(point);
220 // TODO: this is just so we get visual feedback that the tx has gone through
221 // but this should be addressed with the new UI flow (-> download keyfile)
222 //
223 // A question here is how to deal with the modals/messages about the keys not being
224 // set since they are in pending in the Roller...
225 // we could inspect if there's a changeKeys in the local list of pending txs,...
226 //
227 pop();
228 } catch (error) {
229 // setError(error);
230 } finally {
231 // setLoading(false);
232 }
233 }, [
234 breach,
235 manualNetworkSeed,
236 getPendingTransactions,
237 point,
238 pop,
239 configureNetworkingKeys,
240 ]);
242 const initialValues = useMemo(
243 () => ({
244 useNetworkSeed: false,
245 useDiscontinuity: false,
246 }),
247 []
248 );
250 const viewTitle = completed
251 ? 'Network Keys are now set'
252 : 'Set Networking Keys';
254 const usageMessage =
255 'You need this keyfile to authenticate with your OS. Please Download.';
257 return (
258 <>
259 <Grid>
260 <Grid.Item full as={ViewHeader}>
261 {viewTitle}
262 </Grid.Item>
263 {!completed && (
264 <Grid.Item className="gray4 f5" full>
265 Network Keys authenticate Urbit ID with Urbit OS.
266 </Grid.Item>
267 )}
268 {completed && (
269 <>
270 <Grid.Item
271 full
272 className="mb3"
273 as={DownloadKeyfileButton}
274 solid
275 {...keyfileBind}
276 />
277 <Grid.Item full as={AlertBox} className="mb8">
278 {usageMessage}
279 </Grid.Item>
280 </>
281 )}
282 <BridgeForm
283 validate={validate}
284 onValues={onValues}
285 initialValues={initialValues}>
286 {() => (
287 <>
288 {!completed && (
289 <>
290 <Grid.Item
291 full
292 as={ToggleInput}
293 name="useNetworkSeed"
294 label="Use Custom Network Seed"
295 inverseLabel="Back to Derived Network Seed"
296 disabled={inputsLocked}
297 />
298 <Condition when="useNetworkSeed" is={true}>
299 <Grid.Item full as={NoticeBox} className="mb2">
300 When using a custom network seed, you'll need to download
301 your Arvo keyfile immediately after this transaction as
302 Bridge does not store your seed.
303 </Grid.Item>
304 <Grid.Item
305 full
306 as={HexInput}
307 name="networkSeed"
308 label="Network Seed (32 bytes)"
309 disabled={inputsLocked}
310 />
311 </Condition>
312 <Grid.Item
313 full
314 as={CheckboxInput}
315 name="useDiscontinuity"
316 label="Breach Continuity"
317 disabled={inputsLocked}
318 />
320 <Grid.Item full as={FormError} />
321 </>
322 )}
324 {currentL2 && !currentL2Spawn ? (
325 <Grid.Item
326 as={Button}
327 full
328 className=""
329 center
330 solid
331 onClick={setNetworkingKeys}>
332 {'Sign Transaction'}
333 </Grid.Item>
334 ) : (
335 <Grid.Item
336 full
337 as={InlineEthereumTransaction}
338 {...bind}
339 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
340 onReturn={() => pop()}
341 />
342 )}
343 </>
344 )}
345 </BridgeForm>
346 </Grid>
347 </>
348 );