on ee32cc5 help sign in
bridge/src/views/Admin/AdminNetworkingKeys.js
380 lines 10 kB history raw
1import React, { useCallback, useEffect, 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} from 'lib/keys';
27import { formatDotsWithTime } from 'lib/dateFormat';
28import useEthereumTransaction from 'lib/useEthereumTransaction';
29import { GAS_LIMITS, ZERO_KEY, CRYPTO_SUITE_VERSION } from 'lib/constants';
30import { addHexPrefix } from 'lib/wallet';
32import ViewHeader from 'components/ViewHeader';
33import MiniBackButton from 'components/MiniBackButton';
34import { BootArvoButton } from 'components/Buttons';
35import FooterButton from 'components/FooterButton';
36import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
37import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
38import NoticeBox from 'components/NoticeBox';
39import { useHexInput, useCheckboxInput } from 'lib/useInputs';
41const chainKeyProp = name => d =>
42 d[name] === ZERO_KEY ? Nothing() : Just(d[name]);
44const renderNetworkKey = key => {
45 const segments = segmentNetworkKey(key);
47 if (!segments) {
48 return 'Unset';
49 }
51 return segments.join('\n');
52};
54function useSetKeys() {
55 const { urbitWallet, wallet, authMnemonic } = useWallet();
56 const { pointCursor } = usePointCursor();
57 const { syncOwnedPoint, getDetails } = usePointCache();
58 const { contracts } = useNetwork();
59 const _point = need.point(pointCursor);
60 const _contracts = need.contracts(contracts);
62 const [ndNetworkSeed, setNdNetworkSeed] = useState();
64 const {
65 isDefaultState,
66 construct: _construct,
67 broadcasting,
68 confirmed,
69 inputsLocked,
70 bind,
71 } = useEthereumTransaction(GAS_LIMITS.CONFIGURE_KEYS);
73 // sync point details after success
74 useEffect(() => {
75 if (confirmed) {
76 syncOwnedPoint(_point);
77 }
78 }, [_point, confirmed, syncOwnedPoint]);
80 const getSeed = useCallback(
81 async manualSeed => {
82 const details = need.details(getDetails(_point));
83 const networkRevision = parseInt(details.keyRevisionNumber, 10);
85 if (manualSeed !== undefined) {
86 setNdNetworkSeed(manualSeed);
87 return manualSeed;
88 } else {
89 const networkSeed = await attemptNetworkSeedDerivation({
90 urbitWallet,
91 wallet,
92 authMnemonic,
93 details,
94 revision: networkRevision + 1,
95 });
97 return networkSeed.matchWith({
98 Nothing: () => {
99 const ndSeed = randomHex(64);
100 setNdNetworkSeed(ndSeed);
101 return ndSeed;
102 },
103 Just: p => p.value,
104 });
105 }
106 },
107 [_point, authMnemonic, getDetails, urbitWallet, wallet]
108 );
110 const construct = useCallback(
111 async (manualSeed, isDiscontinuity) => {
112 const seed = await getSeed(manualSeed);
113 const pair = deriveNetworkKeys(seed);
115 const txn = azimuth.ecliptic.configureKeys(
116 _contracts,
117 _point,
118 addHexPrefix(pair.crypt.public),
119 addHexPrefix(pair.auth.public),
120 CRYPTO_SUITE_VERSION,
121 isDiscontinuity ? 1 : 0
122 );
124 _construct(txn);
125 },
126 [_construct, _contracts, _point, getSeed]
127 );
129 return {
130 isDefaultState,
131 construct,
132 broadcasting,
133 confirmed,
134 ndNetworkSeed,
135 inputsLocked,
136 bind,
137 };
140export default function AdminNetworkingKeys() {
141 const { push, names, pop } = useLocalRouter();
142 const { pointCursor } = usePointCursor();
143 const { getDetails, getRekeyDate } = usePointCache();
145 const point = need.point(pointCursor);
146 const details = getDetails(point);
148 const hasKeys = details.matchWith({
149 Nothing: () => false,
150 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
151 });
153 const {
154 isDefaultState,
155 construct,
156 broadcasting,
157 confirmed,
158 inputsLocked,
159 ndNetworkSeed,
160 bind,
161 } = useSetKeys();
163 const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
164 name: 'shownetworkseed',
165 label: 'Use Custom Network Seed',
166 inverseLabel: 'Back to Derived Network Seed',
167 initialValue: false,
168 disabled: inputsLocked,
169 });
171 const [
172 networkSeedInput,
173 { pass: validNetworkSeed, data: networkSeed },
174 { setValue: setNetworkSeed },
175 ] = useHexInput({
176 name: 'networkseed',
177 label: 'Network Seed (64 bytes)',
178 length: 32, // 64 bytes
179 disabled: inputsLocked,
180 });
182 const [
183 discontinuityInput,
184 { pass: validDiscontinuity, data: isDiscontinuity },
185 ] = useCheckboxInput({
186 name: 'discontinuity',
187 label: 'Trigger New Continuity Era',
188 initialValue: false,
189 disabled: inputsLocked,
190 });
192 useEffect(() => {
193 if (
194 (!showNetworkSeed || (showNetworkSeed && validNetworkSeed)) &&
195 validDiscontinuity
196 ) {
197 construct(networkSeed, isDiscontinuity);
198 }
199 }, [
200 construct,
201 isDiscontinuity,
202 networkSeed,
203 showNetworkSeed,
204 validDiscontinuity,
205 validNetworkSeed,
206 ]);
208 useEffect(() => {
209 if (!showNetworkSeed) {
210 setNetworkSeed('');
211 }
212 }, [setNetworkSeed, showNetworkSeed]);
214 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
216 const renderTitle = () => {
217 if (confirmed) {
218 return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
219 }
221 if (broadcasting) {
222 return 'Setting Network Keys...';
223 }
225 if (!hasKeys) {
226 return 'Networking keys have not yet been set.';
227 }
229 return 'Networking';
230 };
232 const renderNetworkKeySection = (title, key) => (
233 <>
234 <Grid.Item full as={H5} className="mt3 gray4">
235 {title}
236 </Grid.Item>
237 {key.matchWith({
238 Nothing: () => (
239 <Grid.Item full as="code" className="f5 gray4">
240 Unset
241 </Grid.Item>
242 ),
243 Just: ({ value: key }) => (
244 <>
245 <Grid.Item full as="code" className="f5 wrap">
246 0x
247 </Grid.Item>
248 <Grid.Item full as="code" className="f5 wrap">
249 {renderNetworkKey(key)}
250 </Grid.Item>
251 </>
252 ),
253 })}
254 </>
255 );
257 const renderDetail = (title, value) => (
258 <Flex.Item as={Flex} col>
259 <Flex.Item as={H5} className="gray4">
260 {title}
261 </Flex.Item>
262 {value.matchWith({
263 Nothing: () => (
264 <Flex.Item as={Text} className="f5 gray4">
265 Unset
266 </Flex.Item>
267 ),
268 Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
269 })}
270 </Flex.Item>
271 );
273 const renderDetails = () => {
274 return (
275 <>
276 {renderNetworkKeySection(
277 'Authentication',
278 details.chain(chainKeyProp('authenticationKey'))
279 )}
280 {renderNetworkKeySection(
281 'Encryption',
282 details.chain(chainKeyProp('encryptionKey'))
283 )}
284 <Grid.Item full as={Flex} row justify="between" className="mt3">
285 {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
286 {renderDetail('Continuity Era', details.map(d => d.continuityNumber))}
287 {renderDetail(
288 'Crypto Suite Ver.',
289 details.map(d => d.cryptoSuiteVersion)
290 )}
291 </Grid.Item>
292 {hasKeys && (
293 <Grid.Item full as={Flex} row justify="between" className="mt3">
294 {renderDetail(
295 'Last Set',
296 getRekeyDate(point).map(date =>
297 date.matchWith({
298 Ok: r => formatDotsWithTime(r.value),
299 Error: () => Nothing(),
300 })
301 )
302 )}
303 </Grid.Item>
304 )}
305 </>
306 );
307 };
309 return (
310 <>
311 <Grid>
312 <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
314 <Grid.Item full as={ViewHeader}>
315 {renderTitle()}
316 </Grid.Item>
318 <Grid.Item full as={Text} className="mb3">
319 {hasKeys
320 ? 'Here are your public keys that authenticate your Arvo.'
321 : 'Set your networking keys to authenticate with Arvo.'}
322 </Grid.Item>
324 {confirmed && (
325 <>
326 <Grid.Item full as={NoticeBox} className="mb3">
327 You need this keyfile to authenticate with Arvo.
328 </Grid.Item>
329 <Grid.Item
330 full
331 as={DownloadKeyfileButton}
332 solid
333 networkSeed={ndNetworkSeed}
334 />
335 </>
336 )}
338 {!confirmed && (
339 <>
340 <Grid.Item full as={ToggleInput} {...showNetworkSeedInput} />
341 {showNetworkSeed && (
342 <>
343 <Grid.Item full as={NoticeBox} className="mb2">
344 When using a custom network seed, you'll need to download your
345 Arvo keyfile immediately after this transaction is
346 confirmed—Multipass does not store your seed.
347 </Grid.Item>
348 <Grid.Item full as={Input} {...networkSeedInput} />
349 </>
350 )}
351 <Grid.Item full as={CheckboxInput} {...discontinuityInput} />
352 </>
353 )}
355 <Grid.Item
356 full
357 as={InlineEthereumTransaction}
358 {...bind}
359 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
360 onReturn={() => pop()}
361 />
363 {isDefaultState && renderDetails()}
365 {confirmed && (
366 <>
367 <Grid.Item full as={BootArvoButton} disabled />
368 <Grid.Divider />
369 </>
370 )}
371 </Grid>
373 {isDefaultState && (
374 <FooterButton onClick={goRelocate} disabled>
375 Relocate
376 </FooterButton>
377 )}
378 </>
379 );