on ef914ee help sign in
bridge/src/views/Admin/AdminNetworkingKeys.js
395 lines 11 kB history raw
1import React, { useCallback, useEffect, useRef, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
3import {
4 Grid,
5 Text,
6 H5,
7 Flex,
8 ToggleInput,
9 Input,
10 CheckboxInput,
11} from 'indigo-react';
12import * as azimuth from 'azimuth-js';
13import { randomHex } from 'web3-utils';
15import { usePointCursor } from 'store/pointCursor';
16import { usePointCache } from 'store/pointCache';
17import { useNetwork } from 'store/network';
18import { useWallet } from 'store/wallet';
20import { useLocalRouter } from 'lib/LocalRouter';
21import * as need from 'lib/need';
22import {
23 segmentNetworkKey,
24 attemptNetworkSeedDerivation,
25 deriveNetworkKeys,
26 CRYPTO_SUITE_VERSION,
27 CURVE_ZERO_ADDR,
28} from 'lib/keys';
29import { formatDotsWithTime } from 'lib/dateFormat';
30import useEthereumTransaction from 'lib/useEthereumTransaction';
31import { GAS_LIMITS } from 'lib/constants';
32import { addHexPrefix } from 'lib/wallet';
34import ViewHeader from 'components/ViewHeader';
35import MiniBackButton from 'components/MiniBackButton';
36import { BootArvoButton } from 'components/Buttons';
37import FooterButton from 'components/FooterButton';
38import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
39import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
40import NoticeBox from 'components/NoticeBox';
41import { useHexInput, useCheckboxInput } from 'lib/useInputs';
42import useKeyfileGenerator from 'lib/useKeyfileGenerator';
44const chainKeyProp = name => d =>
45 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
47const renderNetworkKey = key => {
48 const segments = segmentNetworkKey(key);
50 if (!segments) {
51 return 'Unset';
52 }
54 return segments.join('\n');
55};
57function useSetKeys() {
58 const { urbitWallet, wallet, authMnemonic } = useWallet();
59 const { pointCursor } = usePointCursor();
60 const { syncOwnedPoint, getDetails } = usePointCache();
61 const { contracts } = useNetwork();
62 const _point = need.point(pointCursor);
63 const _contracts = need.contracts(contracts);
65 const randomSeed = useRef();
67 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
68 const [ndNetworkSeed, setNdNetworkSeed] = useState();
70 const {
71 available: keyfileAvailable,
72 bind: keyfileBind,
73 } = useKeyfileGenerator(ndNetworkSeed);
75 const {
76 isDefaultState,
77 construct: _construct,
78 unconstruct,
79 broadcasting,
80 confirmed: _confirmed,
81 inputsLocked,
82 bind,
83 } = useEthereumTransaction(GAS_LIMITS.CONFIGURE_KEYS);
85 // only treat the transaction as confirmed once we also have keys to download
86 const confirmed = _confirmed && keyfileAvailable;
88 // sync point details after success
89 useEffect(() => {
90 if (confirmed) {
91 syncOwnedPoint(_point);
92 }
93 }, [_point, confirmed, syncOwnedPoint]);
95 const getSeed = useCallback(
96 async manualSeed => {
97 const details = need.details(getDetails(_point));
98 const networkRevision = parseInt(details.keyRevisionNumber, 10);
100 if (manualSeed !== undefined) {
101 setNdNetworkSeed(manualSeed);
102 return manualSeed;
103 } else {
104 const networkSeed = await attemptNetworkSeedDerivation({
105 urbitWallet,
106 wallet,
107 authMnemonic,
108 details,
109 revision: networkRevision + 1,
110 });
112 if (Just.hasInstance(networkSeed)) {
113 return networkSeed.value;
114 }
116 randomSeed.current = randomSeed.current || randomHex(64);
117 setNdNetworkSeed(randomSeed.current);
119 return randomSeed.current;
120 }
121 },
122 [_point, authMnemonic, getDetails, urbitWallet, wallet]
123 );
125 const construct = useCallback(
126 async (manualSeed, isDiscontinuity) => {
127 const seed = await getSeed(manualSeed);
128 const pair = deriveNetworkKeys(seed);
130 const txn = azimuth.ecliptic.configureKeys(
131 _contracts,
132 _point,
133 addHexPrefix(pair.crypt.public),
134 addHexPrefix(pair.auth.public),
135 CRYPTO_SUITE_VERSION,
136 isDiscontinuity
137 );
139 _construct(txn);
140 },
141 [_construct, _contracts, _point, getSeed]
142 );
144 return {
145 isDefaultState,
146 construct,
147 unconstruct,
148 broadcasting,
149 confirmed,
150 ndNetworkSeed,
151 inputsLocked,
152 bind,
153 keyfileBind,
154 };
157export default function AdminNetworkingKeys() {
158 const { push, names, pop } = useLocalRouter();
159 const { pointCursor } = usePointCursor();
160 const { getDetails, getRekeyDate } = usePointCache();
162 const point = need.point(pointCursor);
163 const details = getDetails(point);
165 const hasKeys = details.matchWith({
166 Nothing: () => false,
167 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
168 });
170 const {
171 isDefaultState,
172 construct,
173 unconstruct,
174 broadcasting,
175 confirmed,
176 inputsLocked,
177 bind,
178 keyfileBind,
179 } = useSetKeys();
181 const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
182 name: 'shownetworkseed',
183 label: 'Use Custom Network Seed',
184 inverseLabel: 'Back to Derived Network Seed',
185 initialValue: false,
186 disabled: inputsLocked,
187 });
189 const [
190 networkSeedInput,
191 { pass: validNetworkSeed, data: networkSeed },
192 { reset: resetNetworkSeed },
193 ] = useHexInput({
194 name: 'networkseed',
195 label: 'Network Seed (64 bytes)',
196 length: 32, // 64 bytes
197 disabled: inputsLocked,
198 });
200 const [
201 discontinuityInput,
202 { pass: validDiscontinuity, data: isDiscontinuity },
203 ] = useCheckboxInput({
204 name: 'discontinuity',
205 label: 'Trigger New Continuity Era',
206 initialValue: false,
207 disabled: inputsLocked,
208 });
210 useEffect(() => {
211 const nothingOrValidSeed =
212 !showNetworkSeed || (showNetworkSeed && validNetworkSeed);
213 if (nothingOrValidSeed && validDiscontinuity) {
214 construct(networkSeed, isDiscontinuity);
215 } else {
216 unconstruct();
217 }
218 }, [
219 construct,
220 unconstruct,
221 isDiscontinuity,
222 networkSeed,
223 showNetworkSeed,
224 validDiscontinuity,
225 validNetworkSeed,
226 ]);
228 useEffect(() => {
229 if (!showNetworkSeed) {
230 resetNetworkSeed();
231 }
232 }, [resetNetworkSeed, showNetworkSeed]);
234 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
236 const renderTitle = () => {
237 if (confirmed) {
238 return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
239 }
241 if (broadcasting) {
242 return 'Setting Network Keys...';
243 }
245 if (!hasKeys) {
246 return 'Networking keys have not yet been set.';
247 }
249 return 'Networking';
250 };
252 const renderNetworkKeySection = (title, key) => (
253 <>
254 <Grid.Item full as={H5} className="mt3 gray4">
255 {title}
256 </Grid.Item>
257 {key.matchWith({
258 Nothing: () => (
259 <Grid.Item full as="code" className="f5 gray4">
260 Unset
261 </Grid.Item>
262 ),
263 Just: ({ value: key }) => (
264 <>
265 <Grid.Item full as="code" className="f5 wrap">
266 0x
267 </Grid.Item>
268 <Grid.Item full as="code" className="f5 wrap">
269 {renderNetworkKey(key)}
270 </Grid.Item>
271 </>
272 ),
273 })}
274 </>
275 );
277 const renderDetail = (title, value) => (
278 <Flex.Item as={Flex} col>
279 <Flex.Item as={H5} className="gray4">
280 {title}
281 </Flex.Item>
282 {value.matchWith({
283 Nothing: () => (
284 <Flex.Item as={Text} className="f5 gray4">
285 Unset
286 </Flex.Item>
287 ),
288 Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
289 })}
290 </Flex.Item>
291 );
293 const renderDetails = () => {
294 return (
295 <>
296 {renderNetworkKeySection(
297 'Authentication',
298 details.chain(chainKeyProp('authenticationKey'))
299 )}
300 {renderNetworkKeySection(
301 'Encryption',
302 details.chain(chainKeyProp('encryptionKey'))
303 )}
304 <Grid.Item full as={Flex} row justify="between" className="mt3">
305 {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
306 {renderDetail('Continuity Era', details.map(d => d.continuityNumber))}
307 {renderDetail(
308 'Crypto Suite Ver.',
309 details.map(d => d.cryptoSuiteVersion)
310 )}
311 </Grid.Item>
312 {hasKeys && (
313 <Grid.Item full as={Flex} row justify="between" className="mt3">
314 {renderDetail(
315 'Last Set',
316 getRekeyDate(point).map(date =>
317 date.matchWith({
318 Ok: r => formatDotsWithTime(r.value),
319 Error: () => Nothing(),
320 })
321 )
322 )}
323 </Grid.Item>
324 )}
325 </>
326 );
327 };
329 return (
330 <>
331 <Grid>
332 <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
334 <Grid.Item full as={ViewHeader}>
335 {renderTitle()}
336 </Grid.Item>
338 <Grid.Item full as={Text} className="mb3">
339 {hasKeys
340 ? 'Here are your public keys that authenticate your Arvo.'
341 : 'Set your networking keys to authenticate with Arvo.'}
342 </Grid.Item>
344 {confirmed && (
345 <>
346 <Grid.Item full as={NoticeBox} className="mb3">
347 You need this keyfile to authenticate with Arvo.
348 </Grid.Item>
349 <Grid.Item full as={DownloadKeyfileButton} solid {...keyfileBind} />
350 </>
351 )}
353 {!confirmed && (
354 <>
355 <Grid.Item full as={ToggleInput} {...showNetworkSeedInput} />
356 {showNetworkSeed && (
357 <>
358 <Grid.Item full as={NoticeBox} className="mb2">
359 When using a custom network seed, you'll need to download your
360 Arvo keyfile immediately after this transaction is
361 confirmed—Multipass does not store your seed.
362 </Grid.Item>
363 <Grid.Item full as={Input} {...networkSeedInput} />
364 </>
365 )}
366 <Grid.Item full as={CheckboxInput} {...discontinuityInput} />
367 </>
368 )}
370 <Grid.Item
371 full
372 as={InlineEthereumTransaction}
373 {...bind}
374 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
375 onReturn={() => pop()}
376 />
378 {isDefaultState && renderDetails()}
380 {confirmed && (
381 <>
382 <Grid.Item full as={BootArvoButton} disabled />
383 <Grid.Divider />
384 </>
385 )}
386 </Grid>
388 {isDefaultState && (
389 <FooterButton onClick={goRelocate} disabled>
390 Relocate
391 </FooterButton>
392 )}
393 </>
394 );