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