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