on 8282b79 help sign in
bridge/src/views/Admin/AdminNetworkingKeys.js
398 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();
63 const _point = need.point(pointCursor);
64 const _contracts = need.contracts(contracts);
65 const _details = need.details(getDetails(_point));
67 const networkRevision = parseInt(_details.keyRevisionNumber, 10);
68 const randomSeed = useRef();
70 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
71 const [ndNetworkSeed, setNdNetworkSeed] = useState();
73 const {
74 available: keyfileAvailable,
75 bind: keyfileBind,
76 } = useKeyfileGenerator(ndNetworkSeed);
78 const {
79 isDefaultState,
80 construct: _construct,
81 unconstruct,
82 broadcasting,
83 confirmed: _confirmed,
84 inputsLocked,
85 bind,
86 } = useEthereumTransaction(GAS_LIMITS.CONFIGURE_KEYS);
88 // only treat the transaction as confirmed once we also have keys to download
89 const confirmed = _confirmed && keyfileAvailable;
91 // sync point details after success
92 useEffect(() => {
93 if (confirmed) {
94 syncOwnedPoint(_point);
95 }
96 }, [_point, confirmed, syncOwnedPoint]);
98 const buildNetworkSeed = useCallback(
99 async manualSeed => {
100 if (manualSeed !== undefined) {
101 setNdNetworkSeed(manualSeed);
102 return manualSeed;
103 } else {
104 const newNetworkRevision = networkRevision + 1;
105 console.log(`deriving seed with revision ${newNetworkRevision}`);
107 const networkSeed = await attemptNetworkSeedDerivation({
108 urbitWallet,
109 wallet,
110 authMnemonic,
111 details: _details,
112 revision: newNetworkRevision,
113 });
115 if (Just.hasInstance(networkSeed)) {
116 return networkSeed.value;
117 }
119 randomSeed.current = randomSeed.current || randomHex(64);
120 setNdNetworkSeed(randomSeed.current);
122 return randomSeed.current;
123 }
124 },
125 [_details, authMnemonic, networkRevision, urbitWallet, wallet]
126 );
128 const construct = useCallback(
129 async (manualSeed, isDiscontinuity) => {
130 const seed = await buildNetworkSeed(manualSeed);
131 const pair = deriveNetworkKeys(seed);
133 const txn = azimuth.ecliptic.configureKeys(
134 _contracts,
135 _point,
136 addHexPrefix(pair.crypt.public),
137 addHexPrefix(pair.auth.public),
138 CRYPTO_SUITE_VERSION,
139 isDiscontinuity
140 );
142 _construct(txn);
143 },
144 [_construct, _contracts, _point, buildNetworkSeed]
145 );
147 return {
148 isDefaultState,
149 construct,
150 unconstruct,
151 broadcasting,
152 confirmed,
153 ndNetworkSeed,
154 inputsLocked,
155 bind,
156 keyfileBind,
157 };
160export default function AdminNetworkingKeys() {
161 const { push, names, pop } = useLocalRouter();
162 const { pointCursor } = usePointCursor();
163 const { getDetails, getRekeyDate } = usePointCache();
165 const point = need.point(pointCursor);
166 const details = getDetails(point);
168 const hasKeys = details.matchWith({
169 Nothing: () => false,
170 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
171 });
173 const {
174 isDefaultState,
175 construct,
176 unconstruct,
177 broadcasting,
178 confirmed,
179 inputsLocked,
180 bind,
181 keyfileBind,
182 } = useSetKeys();
184 const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
185 name: 'shownetworkseed',
186 label: 'Use Custom Network Seed',
187 inverseLabel: 'Back to Derived Network Seed',
188 initialValue: false,
189 disabled: inputsLocked,
190 });
192 const [
193 networkSeedInput,
194 { pass: validNetworkSeed, data: networkSeed },
195 { reset: resetNetworkSeed },
196 ] = useHexInput({
197 name: 'networkseed',
198 label: 'Network Seed (64 bytes)',
199 length: 32, // 64 bytes
200 disabled: inputsLocked,
201 });
203 const [
204 discontinuityInput,
205 { pass: validDiscontinuity, data: isDiscontinuity },
206 ] = useCheckboxInput({
207 name: 'discontinuity',
208 label: 'Trigger New Continuity Era',
209 initialValue: false,
210 disabled: inputsLocked,
211 });
213 useEffect(() => {
214 const nothingOrValidSeed =
215 !showNetworkSeed || (showNetworkSeed && validNetworkSeed);
216 if (nothingOrValidSeed && validDiscontinuity) {
217 construct(networkSeed, isDiscontinuity);
218 } else {
219 unconstruct();
220 }
221 }, [
222 construct,
223 unconstruct,
224 isDiscontinuity,
225 networkSeed,
226 showNetworkSeed,
227 validDiscontinuity,
228 validNetworkSeed,
229 ]);
231 useEffect(() => {
232 if (!showNetworkSeed) {
233 resetNetworkSeed();
234 }
235 }, [resetNetworkSeed, showNetworkSeed]);
237 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
239 const renderTitle = () => {
240 if (confirmed) {
241 return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
242 }
244 if (broadcasting) {
245 return 'Setting Network Keys...';
246 }
248 if (!hasKeys) {
249 return 'Networking keys have not yet been set.';
250 }
252 return 'Networking';
253 };
255 const renderNetworkKeySection = (title, key) => (
256 <>
257 <Grid.Item full as={H5} className="mt3 gray4">
258 {title}
259 </Grid.Item>
260 {key.matchWith({
261 Nothing: () => (
262 <Grid.Item full as="code" className="f5 gray4">
263 Unset
264 </Grid.Item>
265 ),
266 Just: ({ value: key }) => (
267 <>
268 <Grid.Item full as="code" className="f5 wrap">
269 0x
270 </Grid.Item>
271 <Grid.Item full as="code" className="f5 wrap">
272 {renderNetworkKey(key)}
273 </Grid.Item>
274 </>
275 ),
276 })}
277 </>
278 );
280 const renderDetail = (title, value) => (
281 <Flex.Item as={Flex} col>
282 <Flex.Item as={H5} className="gray4">
283 {title}
284 </Flex.Item>
285 {value.matchWith({
286 Nothing: () => (
287 <Flex.Item as={Text} className="f5 gray4">
288 Unset
289 </Flex.Item>
290 ),
291 Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
292 })}
293 </Flex.Item>
294 );
296 const renderDetails = () => {
297 return (
298 <>
299 {renderNetworkKeySection(
300 'Authentication',
301 details.chain(chainKeyProp('authenticationKey'))
302 )}
303 {renderNetworkKeySection(
304 'Encryption',
305 details.chain(chainKeyProp('encryptionKey'))
306 )}
307 <Grid.Item full as={Flex} row justify="between" className="mt3">
308 {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
309 {renderDetail('Continuity Era', details.map(d => d.continuityNumber))}
310 {renderDetail(
311 'Crypto Suite Ver.',
312 details.map(d => d.cryptoSuiteVersion)
313 )}
314 </Grid.Item>
315 {hasKeys && (
316 <Grid.Item full as={Flex} row justify="between" className="mt3">
317 {renderDetail(
318 'Last Set',
319 getRekeyDate(point).map(date =>
320 date.matchWith({
321 Ok: r => formatDotsWithTime(r.value),
322 Error: () => Nothing(),
323 })
324 )
325 )}
326 </Grid.Item>
327 )}
328 </>
329 );
330 };
332 return (
333 <>
334 <Grid>
335 <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
337 <Grid.Item full as={ViewHeader}>
338 {renderTitle()}
339 </Grid.Item>
341 <Grid.Item full as={Text} className="mb3">
342 {hasKeys
343 ? 'Here are your public keys that authenticate your Arvo.'
344 : 'Set your networking keys to authenticate with Arvo.'}
345 </Grid.Item>
347 {confirmed && (
348 <>
349 <Grid.Item full as={NoticeBox} className="mb3">
350 You need this keyfile to authenticate with Arvo.
351 </Grid.Item>
352 <Grid.Item full as={DownloadKeyfileButton} solid {...keyfileBind} />
353 </>
354 )}
356 {!confirmed && (
357 <>
358 <Grid.Item full as={ToggleInput} {...showNetworkSeedInput} />
359 {showNetworkSeed && (
360 <>
361 <Grid.Item full as={NoticeBox} className="mb2">
362 When using a custom network seed, you'll need to download your
363 Arvo keyfile immediately after this transaction is
364 confirmed—Multipass does not store your seed.
365 </Grid.Item>
366 <Grid.Item full as={Input} {...networkSeedInput} />
367 </>
368 )}
369 <Grid.Item full as={CheckboxInput} {...discontinuityInput} />
370 </>
371 )}
373 <Grid.Item
374 full
375 as={InlineEthereumTransaction}
376 {...bind}
377 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
378 onReturn={() => pop()}
379 />
381 {isDefaultState && renderDetails()}
383 {confirmed && (
384 <>
385 <Grid.Item full as={BootArvoButton} disabled />
386 <Grid.Divider />
387 </>
388 )}
389 </Grid>
391 {isDefaultState && (
392 <FooterButton onClick={goRelocate} disabled>
393 Relocate
394 </FooterButton>
395 )}
396 </>
397 );