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