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