on 4dfcb60 help sign in
bridge/src/views/Admin/AdminNetworkingKeys.js
379 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';
33import { useHexInput, useCheckboxInput } from 'lib/useInputs';
34import useKeyfileGenerator from 'lib/useKeyfileGenerator';
36import ViewHeader from 'components/ViewHeader';
37import { BootArvoButton } from 'components/Buttons';
38import FooterButton from 'components/FooterButton';
39import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
40import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
41import NoticeBox from 'components/NoticeBox';
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 { syncDetails, syncRekeyDate, getDetails } = usePointCache();
60 const { contracts } = useNetwork();
62 const _point = need.point(pointCursor);
63 const _contracts = need.contracts(contracts);
64 const _details = need.details(getDetails(_point));
66 const networkRevision = parseInt(_details.keyRevisionNumber, 10);
67 const randomSeed = useRef();
69 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
70 const [ndNetworkSeed, setNdNetworkSeed] = useState();
72 const {
73 available: keyfileAvailable,
74 bind: keyfileBind,
75 } = useKeyfileGenerator(ndNetworkSeed);
77 const buildNetworkSeed = useCallback(
78 async manualSeed => {
79 if (manualSeed !== undefined) {
80 setNdNetworkSeed(manualSeed);
81 return manualSeed;
82 } else {
83 const newNetworkRevision = networkRevision + 1;
84 console.log(`deriving seed with revision ${newNetworkRevision}`);
86 const networkSeed = await attemptNetworkSeedDerivation({
87 urbitWallet,
88 wallet,
89 authMnemonic,
90 details: _details,
91 revision: newNetworkRevision,
92 });
94 if (Just.hasInstance(networkSeed)) {
95 return networkSeed.value;
96 }
98 randomSeed.current = randomSeed.current || randomHex(64);
99 setNdNetworkSeed(randomSeed.current);
101 return randomSeed.current;
102 }
103 },
104 [_details, authMnemonic, networkRevision, urbitWallet, wallet]
105 );
107 const { completed: _completed, ...rest } = useEthereumTransaction(
108 useCallback(
109 async (manualSeed, isDiscontinuity) => {
110 const seed = await buildNetworkSeed(manualSeed);
111 const pair = deriveNetworkKeys(seed);
113 return azimuth.ecliptic.configureKeys(
114 _contracts,
115 _point,
116 addHexPrefix(pair.crypt.public),
117 addHexPrefix(pair.auth.public),
118 CRYPTO_SUITE_VERSION,
119 isDiscontinuity
120 );
121 },
122 [_contracts, _point, buildNetworkSeed]
123 ),
124 useCallback(
125 () => Promise.all([syncDetails(_point), syncRekeyDate(_point)]),
126 [_point, syncDetails, syncRekeyDate]
127 ),
128 GAS_LIMITS.CONFIGURE_KEYS
129 );
131 // only treat the transaction as completed once we also have keys to download
132 const completed = _completed && keyfileAvailable;
134 return {
135 completed,
136 ndNetworkSeed,
137 keyfileBind,
138 ...rest,
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 unconstruct,
159 broadcasting,
160 completed,
161 inputsLocked,
162 bind,
163 keyfileBind,
164 } = useSetKeys();
166 const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
167 name: 'shownetworkseed',
168 label: 'Use Custom Network Seed',
169 inverseLabel: 'Back to Derived Network Seed',
170 initialValue: false,
171 disabled: inputsLocked,
172 });
174 const [
175 networkSeedInput,
176 { pass: validNetworkSeed, data: networkSeed },
177 { reset: resetNetworkSeed },
178 ] = useHexInput({
179 name: 'networkseed',
180 label: 'Network Seed (64 bytes)',
181 length: 32, // 64 bytes
182 disabled: inputsLocked,
183 });
185 const [
186 discontinuityInput,
187 { pass: validDiscontinuity, data: isDiscontinuity },
188 ] = useCheckboxInput({
189 name: 'discontinuity',
190 label: 'Trigger New Continuity Era',
191 initialValue: false,
192 disabled: inputsLocked,
193 });
195 useEffect(() => {
196 const nothingOrValidSeed =
197 !showNetworkSeed || (showNetworkSeed && validNetworkSeed);
198 if (nothingOrValidSeed && validDiscontinuity) {
199 construct(networkSeed, isDiscontinuity);
200 } else {
201 unconstruct();
202 }
203 }, [
204 construct,
205 unconstruct,
206 isDiscontinuity,
207 networkSeed,
208 showNetworkSeed,
209 validDiscontinuity,
210 validNetworkSeed,
211 ]);
213 useEffect(() => {
214 if (!showNetworkSeed) {
215 resetNetworkSeed();
216 }
217 }, [resetNetworkSeed, showNetworkSeed]);
219 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
221 const renderTitle = () => {
222 if (completed) {
223 return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
224 }
226 if (broadcasting) {
227 return 'Setting Network Keys...';
228 }
230 if (!hasKeys) {
231 return 'Networking keys have not yet been set.';
232 }
234 return 'Networking';
235 };
237 const renderNetworkKeySection = (title, key) => (
238 <>
239 <Grid.Item full as={H5} className="mt3 gray4">
240 {title}
241 </Grid.Item>
242 {key.matchWith({
243 Nothing: () => (
244 <Grid.Item full as="code" className="f5 gray4">
245 Unset
246 </Grid.Item>
247 ),
248 Just: ({ value: key }) => (
249 <>
250 <Grid.Item full as="code" className="f5 wrap">
251 0x
252 </Grid.Item>
253 <Grid.Item full as="code" className="f5 wrap">
254 {renderNetworkKey(key)}
255 </Grid.Item>
256 </>
257 ),
258 })}
259 </>
260 );
262 const renderDetail = (title, value) => (
263 <Flex.Item as={Flex} col>
264 <Flex.Item as={H5} className="gray4">
265 {title}
266 </Flex.Item>
267 {value.matchWith({
268 Nothing: () => (
269 <Flex.Item as={Text} className="f5 gray4">
270 Unset
271 </Flex.Item>
272 ),
273 Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
274 })}
275 </Flex.Item>
276 );
278 const renderDetails = () => {
279 return (
280 <>
281 {renderNetworkKeySection(
282 'Authentication',
283 details.chain(chainKeyProp('authenticationKey'))
284 )}
285 {renderNetworkKeySection(
286 'Encryption',
287 details.chain(chainKeyProp('encryptionKey'))
288 )}
289 <Grid.Item full as={Flex} row justify="between" className="mt3">
290 {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
291 {renderDetail('Continuity Era', details.map(d => d.continuityNumber))}
292 {renderDetail(
293 'Crypto Suite Ver.',
294 details.map(d => d.cryptoSuiteVersion)
295 )}
296 </Grid.Item>
297 {hasKeys && (
298 <Grid.Item full as={Flex} row justify="between" className="mt3">
299 {renderDetail(
300 'Last Set',
301 getRekeyDate(point).map(date =>
302 date.matchWith({
303 Ok: r => formatDotsWithTime(r.value),
304 Error: () => Nothing(),
305 })
306 )
307 )}
308 </Grid.Item>
309 )}
310 </>
311 );
312 };
314 return (
315 <>
316 <Grid>
317 <Grid.Item full as={ViewHeader}>
318 {renderTitle()}
319 </Grid.Item>
321 <Grid.Item full as={Text} className="mb3">
322 {hasKeys
323 ? 'Here are your public keys that authenticate your Arvo.'
324 : 'Set your networking keys to authenticate with Arvo.'}
325 </Grid.Item>
327 {completed && (
328 <>
329 <Grid.Item full as={NoticeBox} className="mb3">
330 You need this keyfile to authenticate with Arvo. If you have
331 already booted Arvo, run <code>|rekey '0wkeyfile.contents'</code>
332 </Grid.Item>
333 <Grid.Item full as={DownloadKeyfileButton} solid {...keyfileBind} />
334 </>
335 )}
337 {!completed && (
338 <>
339 <Grid.Item full as={ToggleInput} {...showNetworkSeedInput} />
340 {showNetworkSeed && (
341 <>
342 <Grid.Item full as={NoticeBox} className="mb2">
343 When using a custom network seed, you'll need to download your
344 Arvo keyfile immediately after this transaction is completed —
345 Bridge does not store your seed.
346 </Grid.Item>
347 <Grid.Item full as={Input} {...networkSeedInput} />
348 </>
349 )}
350 <Grid.Item full as={CheckboxInput} {...discontinuityInput} />
351 </>
352 )}
354 <Grid.Item
355 full
356 as={InlineEthereumTransaction}
357 {...bind}
358 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
359 onReturn={() => pop()}
360 />
362 {isDefaultState && renderDetails()}
364 {completed && (
365 <>
366 <Grid.Item full as={BootArvoButton} disabled />
367 <Grid.Divider />
368 </>
369 )}
370 </Grid>
372 {isDefaultState && (
373 <FooterButton onClick={goRelocate} disabled>
374 Relocate
375 </FooterButton>
376 )}
377 </>
378 );