on 555fac9 help sign in
bridge/src/views/Admin/AdminNetworkingKeys.js
401 lines 11 kB history raw
1import React, { useCallback, useMemo, useRef, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
3import { Grid, Text, H5, Flex, ToggleInput, CheckboxInput } 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';
12import { useLocalRouter } from 'lib/LocalRouter';
13import * as need from 'lib/need';
14import {
15 segmentNetworkKey,
16 attemptNetworkSeedDerivation,
17 deriveNetworkKeys,
18 CRYPTO_SUITE_VERSION,
19 CURVE_ZERO_ADDR,
20} from 'lib/keys';
21import { formatDotsWithTime } from 'lib/dateFormat';
22import useEthereumTransaction from 'lib/useEthereumTransaction';
23import { GAS_LIMITS } from 'lib/constants';
24import { addHexPrefix } from 'lib/wallet';
25import useKeyfileGenerator from 'lib/useKeyfileGenerator';
27import ViewHeader from 'components/ViewHeader';
28import { BootArvoButton } from 'components/Buttons';
29import FooterButton from 'components/FooterButton';
30import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
31import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
32import NoticeBox from 'components/NoticeBox';
34import { HexInput } from 'form/Inputs';
35import {
36 composeValidator,
37 buildCheckboxValidator,
38 buildHexValidator,
39} from 'form/validators';
40import BridgeForm from 'form/BridgeForm';
41import Condition from 'form/Condition';
42import FormError from 'form/FormError';
43import convertToInt from 'lib/convertToInt';
45const chainKeyProp = name => d =>
46 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
48const renderNetworkKey = key => {
49 const segments = segmentNetworkKey(key);
51 if (!segments) {
52 return 'Unset';
53 }
55 return segments.join('\n');
56};
58function useSetKeys() {
59 const { urbitWallet, wallet, authMnemonic } = useWallet();
60 const { pointCursor } = usePointCursor();
61 const { syncDetails, syncRekeyDate, getDetails } = usePointCache();
62 const { contracts } = useNetwork();
64 const _point = need.point(pointCursor);
65 const _contracts = need.contracts(contracts);
66 const _details = need.details(getDetails(_point));
68 const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
69 const randomSeed = useRef();
71 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
72 const [ndNetworkSeed, setNdNetworkSeed] = useState();
74 const {
75 available: keyfileAvailable,
76 bind: keyfileBind,
77 } = useKeyfileGenerator(ndNetworkSeed);
79 const buildNetworkSeed = useCallback(
80 async manualSeed => {
81 if (manualSeed !== undefined) {
82 setNdNetworkSeed(manualSeed);
83 return manualSeed;
84 } else {
85 const newNetworkRevision = networkRevision + 1;
86 console.log(`deriving seed with revision ${newNetworkRevision}`);
88 const networkSeed = await attemptNetworkSeedDerivation({
89 urbitWallet,
90 wallet,
91 authMnemonic,
92 details: _details,
93 revision: newNetworkRevision,
94 });
96 if (Just.hasInstance(networkSeed)) {
97 return networkSeed.value;
98 }
100 randomSeed.current = randomSeed.current || randomHex(64);
101 setNdNetworkSeed(randomSeed.current);
103 return randomSeed.current;
104 }
105 },
106 [_details, authMnemonic, networkRevision, urbitWallet, wallet]
107 );
109 const { completed: _completed, ...rest } = useEthereumTransaction(
110 useCallback(
111 async (manualSeed, isDiscontinuity) => {
112 const seed = await buildNetworkSeed(manualSeed);
113 const pair = deriveNetworkKeys(seed);
115 return azimuth.ecliptic.configureKeys(
116 _contracts,
117 _point,
118 addHexPrefix(pair.crypt.public),
119 addHexPrefix(pair.auth.public),
120 CRYPTO_SUITE_VERSION,
121 isDiscontinuity
122 );
123 },
124 [_contracts, _point, buildNetworkSeed]
125 ),
126 useCallback(
127 () => Promise.all([syncDetails(_point), syncRekeyDate(_point)]),
128 [_point, syncDetails, syncRekeyDate]
129 ),
130 GAS_LIMITS.CONFIGURE_KEYS
131 );
133 // only treat the transaction as completed once we also have keys to download
134 const completed = _completed && keyfileAvailable;
136 return {
137 completed,
138 ndNetworkSeed,
139 keyfileBind,
140 ...rest,
141 };
144export default function AdminNetworkingKeys() {
145 const { push, names, pop } = useLocalRouter();
146 const { pointCursor } = usePointCursor();
147 const { getDetails, getRekeyDate } = usePointCache();
149 const point = need.point(pointCursor);
150 const details = getDetails(point);
152 const hasKeys = details.matchWith({
153 Nothing: () => false,
154 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
155 // we actually don't mind the default NaN behavior of parseInt here,
156 // since NaN > 0 === false and that's a reasonable result
157 });
159 const {
160 isDefaultState,
161 construct,
162 unconstruct,
163 broadcasting,
164 completed,
165 inputsLocked,
166 bind,
167 keyfileBind,
168 } = useSetKeys();
170 const validateForm = useCallback((values, errors) => {
171 if (values.useNetworkSeed && errors.networkSeed) {
172 return errors;
173 }
174 }, []);
176 const validate = useMemo(
177 () =>
178 composeValidator(
179 {
180 useNetworkSeed: buildCheckboxValidator(),
181 networkSeed: buildHexValidator(32),
182 useDiscontinuity: buildCheckboxValidator(),
183 },
184 validateForm
185 ),
186 [validateForm]
187 );
189 const onValues = useCallback(
190 ({ valid, values, form }) => {
191 if (valid) {
192 construct(
193 values.useNetworkSeed ? values.networkSeed : undefined,
194 values.useDiscontinuity
195 );
196 } else {
197 unconstruct();
198 }
200 if (!values.useNetworkSeed && values.networkSeed) {
201 form.change('networkSeed', '');
202 }
203 },
204 [construct, unconstruct]
205 );
207 const initialValues = useMemo(
208 () => ({
209 useNetworkSeed: false,
210 useDiscontinuity: false,
211 }),
212 []
213 );
215 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
217 const renderTitle = () => {
218 if (completed) {
219 return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
220 }
222 if (broadcasting) {
223 return 'Setting Network Keys...';
224 }
226 if (!hasKeys) {
227 return 'Networking keys have not yet been set.';
228 }
230 return 'Networking';
231 };
233 const renderNetworkKeySection = (title, key) => (
234 <>
235 <Grid.Item full as={H5} className="mt3 gray4">
236 {title}
237 </Grid.Item>
238 {key.matchWith({
239 Nothing: () => (
240 <Grid.Item full as="code" className="f5 mono gray4">
241 Unset
242 </Grid.Item>
243 ),
244 Just: ({ value: key }) => (
245 <>
246 <Grid.Item full as="code" className="f5 mono wrap">
247 0x
248 </Grid.Item>
249 <Grid.Item full as="code" className="f5 mono wrap">
250 {renderNetworkKey(key)}
251 </Grid.Item>
252 </>
253 ),
254 })}
255 </>
256 );
258 const renderDetail = (title, value) => (
259 <Flex.Item as={Flex} col>
260 <Flex.Item as={H5} className="gray4">
261 {title}
262 </Flex.Item>
263 {value.matchWith({
264 Nothing: () => (
265 <Flex.Item as={Text} className="f5 gray4">
266 Unset
267 </Flex.Item>
268 ),
269 Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
270 })}
271 </Flex.Item>
272 );
274 const renderDetails = () => {
275 return (
276 <>
277 {renderNetworkKeySection(
278 'Authentication',
279 details.chain(chainKeyProp('authenticationKey'))
280 )}
281 {renderNetworkKeySection(
282 'Encryption',
283 details.chain(chainKeyProp('encryptionKey'))
284 )}
285 <Grid.Item full as={Flex} row justify="between" className="mt3">
286 {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
287 {renderDetail('Continuity Era', details.map(d => d.continuityNumber))}
288 {renderDetail(
289 'Crypto Suite Ver.',
290 details.map(d => d.cryptoSuiteVersion)
291 )}
292 </Grid.Item>
293 {hasKeys && (
294 <Grid.Item full as={Flex} row justify="between" className="mt3">
295 {renderDetail(
296 'Last Set',
297 getRekeyDate(point).map(date =>
298 date.matchWith({
299 Ok: r => formatDotsWithTime(r.value),
300 Error: () => Nothing(),
301 })
302 )
303 )}
304 </Grid.Item>
305 )}
306 </>
307 );
308 };
310 return (
311 <>
312 <Grid>
313 <Grid.Item full as={ViewHeader}>
314 {renderTitle()}
315 </Grid.Item>
317 <Grid.Item full as={Text} className="mb3">
318 {hasKeys
319 ? 'Here are your public keys that authenticate your Arvo.'
320 : 'Set your networking keys to authenticate with Arvo.'}
321 </Grid.Item>
323 {completed && (
324 <>
325 <Grid.Item full as={NoticeBox} className="mb3">
326 You need this keyfile to authenticate with Arvo. If you have
327 already booted Arvo, run <code>|rekey '0wkeyfile.contents'</code>
328 </Grid.Item>
329 <Grid.Item full as={DownloadKeyfileButton} solid {...keyfileBind} />
330 </>
331 )}
333 {!completed && (
334 <BridgeForm
335 validate={validate}
336 onValues={onValues}
337 initialValues={initialValues}>
338 {() => (
339 <>
340 <Grid.Item
341 full
342 as={ToggleInput}
343 name="useNetworkSeed"
344 label="Use Custom Network Seed"
345 inverseLabel="Back to Derived Network Seed"
346 disabled={inputsLocked}
347 />
348 <Condition when="useNetworkSeed" is={true}>
349 <Grid.Item full as={NoticeBox} className="mb2">
350 When using a custom network seed, you'll need to download
351 your Arvo keyfile immediately after this transaction is
352 Bridge does not store your seed.
353 </Grid.Item>
354 <Grid.Item
355 full
356 as={HexInput}
357 name="networkSeed"
358 label="Network Seed (64 bytes)"
359 disabled={inputsLocked}
360 />
361 </Condition>
362 <Grid.Item
363 full
364 as={CheckboxInput}
365 name="useDiscontinuity"
366 label="Trigger New Continuity Era"
367 disabled={inputsLocked}
368 />
370 <Grid.Item full as={FormError} />
372 <Grid.Item
373 full
374 as={InlineEthereumTransaction}
375 {...bind}
376 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
377 onReturn={() => pop()}
378 />
379 </>
380 )}
381 </BridgeForm>
382 )}
384 {isDefaultState && renderDetails()}
386 {completed && (
387 <>
388 <Grid.Item full as={BootArvoButton} disabled />
389 <Grid.Divider />
390 </>
391 )}
392 </Grid>
394 {isDefaultState && (
395 <FooterButton onClick={goRelocate} disabled>
396 Relocate
397 </FooterButton>
398 )}
399 </>
400 );