on fdeb086 help sign in
bridge/src/views/Admin/AdminNetworkingKeys.js
393 lines 11 kB history raw
1import React, { useCallback, useMemo, useRef, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
3import { Grid, Text, H5, Flex, ToggleInput, CheckboxInput } from 'indigo-react';
4import * as azimuth from 'azimuth-js';
5import { randomHex } from 'web3-utils';
7import { usePointCursor } from 'store/pointCursor';
8import { usePointCache } from 'store/pointCache';
9import { useNetwork } from 'store/network';
10import { useWallet } from 'store/wallet';
12import { useLocalRouter } from 'lib/LocalRouter';
13import * as need from 'lib/need';
14import {
15 segmentNetworkKey,
16 attemptNetworkSeedDerivation,
17 deriveNetworkKeys,
18 CRYPTO_SUITE_VERSION,
19 CURVE_ZERO_ADDR,
20} from 'lib/keys';
21import { formatDotsWithTime } from 'lib/dateFormat';
22import useEthereumTransaction from 'lib/useEthereumTransaction';
23import { GAS_LIMITS } from 'lib/constants';
24import { addHexPrefix } from 'lib/wallet';
25import useKeyfileGenerator from 'lib/useKeyfileGenerator';
27import ViewHeader from 'components/ViewHeader';
28import { BootArvoButton } from 'components/Buttons';
29import FooterButton from 'components/FooterButton';
30import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
31import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
32import NoticeBox from 'components/NoticeBox';
34import {
35 composeValidator,
36 buildCheckboxValidator,
37 buildHexValidator,
38 HexInput,
39} from 'form/Inputs';
40import BridgeForm from 'form/BridgeForm';
41import Condition from 'form/Condition';
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 validateForm = useCallback((values, errors) => {
167 if (values.useNetworkSeed && errors.networkSeed) {
168 return errors;
169 }
171 return {};
172 }, []);
174 const validate = useMemo(
175 () =>
176 composeValidator(
177 {
178 useNetworkSeed: buildCheckboxValidator(),
179 networkSeed: buildHexValidator(32),
180 useDiscontinuity: buildCheckboxValidator(),
181 },
182 validateForm
183 ),
184 [validateForm]
185 );
187 const onValues = useCallback(
188 ({ valid, values, form }) => {
189 if (valid) {
190 construct(
191 values.useNetworkSeed ? values.networkSeed : undefined,
192 values.useDiscontinuity
193 );
194 } else {
195 unconstruct();
196 }
198 if (!values.useNetworkSeed && values.networkSeed) {
199 form.change('networkSeed', '');
200 }
201 },
202 [construct, unconstruct]
203 );
205 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
207 const renderTitle = () => {
208 if (completed) {
209 return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
210 }
212 if (broadcasting) {
213 return 'Setting Network Keys...';
214 }
216 if (!hasKeys) {
217 return 'Networking keys have not yet been set.';
218 }
220 return 'Networking';
221 };
223 const renderNetworkKeySection = (title, key) => (
224 <>
225 <Grid.Item full as={H5} className="mt3 gray4">
226 {title}
227 </Grid.Item>
228 {key.matchWith({
229 Nothing: () => (
230 <Grid.Item full as="code" className="f5 gray4">
231 Unset
232 </Grid.Item>
233 ),
234 Just: ({ value: key }) => (
235 <>
236 <Grid.Item full as="code" className="f5 wrap">
237 0x
238 </Grid.Item>
239 <Grid.Item full as="code" className="f5 wrap">
240 {renderNetworkKey(key)}
241 </Grid.Item>
242 </>
243 ),
244 })}
245 </>
246 );
248 const renderDetail = (title, value) => (
249 <Flex.Item as={Flex} col>
250 <Flex.Item as={H5} className="gray4">
251 {title}
252 </Flex.Item>
253 {value.matchWith({
254 Nothing: () => (
255 <Flex.Item as={Text} className="f5 gray4">
256 Unset
257 </Flex.Item>
258 ),
259 Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
260 })}
261 </Flex.Item>
262 );
264 const renderDetails = () => {
265 return (
266 <>
267 {renderNetworkKeySection(
268 'Authentication',
269 details.chain(chainKeyProp('authenticationKey'))
270 )}
271 {renderNetworkKeySection(
272 'Encryption',
273 details.chain(chainKeyProp('encryptionKey'))
274 )}
275 <Grid.Item full as={Flex} row justify="between" className="mt3">
276 {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
277 {renderDetail('Continuity Era', details.map(d => d.continuityNumber))}
278 {renderDetail(
279 'Crypto Suite Ver.',
280 details.map(d => d.cryptoSuiteVersion)
281 )}
282 </Grid.Item>
283 {hasKeys && (
284 <Grid.Item full as={Flex} row justify="between" className="mt3">
285 {renderDetail(
286 'Last Set',
287 getRekeyDate(point).map(date =>
288 date.matchWith({
289 Ok: r => formatDotsWithTime(r.value),
290 Error: () => Nothing(),
291 })
292 )
293 )}
294 </Grid.Item>
295 )}
296 </>
297 );
298 };
300 return (
301 <>
302 <Grid>
303 <Grid.Item full as={ViewHeader}>
304 {renderTitle()}
305 </Grid.Item>
307 <Grid.Item full as={Text} className="mb3">
308 {hasKeys
309 ? 'Here are your public keys that authenticate your Arvo.'
310 : 'Set your networking keys to authenticate with Arvo.'}
311 </Grid.Item>
313 {completed && (
314 <>
315 <Grid.Item full as={NoticeBox} className="mb3">
316 You need this keyfile to authenticate with Arvo. If you have
317 already booted Arvo, run <code>|rekey '0wkeyfile.contents'</code>
318 </Grid.Item>
319 <Grid.Item full as={DownloadKeyfileButton} solid {...keyfileBind} />
320 </>
321 )}
323 {!completed && (
324 <BridgeForm
325 validate={validate}
326 onValues={onValues}
327 onSubmit={() => {}}
328 initialValues={{
329 useNetworkSeed: false,
330 useDiscontinuity: false,
331 }}>
332 {({ handleSubmit }) => (
333 <>
334 <Grid.Item
335 full
336 as={ToggleInput}
337 name="useNetworkSeed"
338 label="Use Custom Network Seed"
339 inverseLabel="Back to Derived Network Seed"
340 disabled={inputsLocked}
341 />
342 <Condition when="useNetworkSeed" is={true}>
343 <Grid.Item full as={NoticeBox} className="mb2">
344 When using a custom network seed, you'll need to download
345 your Arvo keyfile immediately after this transaction is
346 completed—Multipass does not store your seed.
347 </Grid.Item>
348 <Grid.Item
349 full
350 as={HexInput}
351 name="networkSeed"
352 label="Network Seed (64 bytes)"
353 disabled={inputsLocked}
354 />
355 </Condition>
356 <Grid.Item
357 full
358 as={CheckboxInput}
359 name="useDiscontinuity"
360 label="Trigger New Continuity Era"
361 disabled={inputsLocked}
362 />
363 </>
364 )}
365 </BridgeForm>
366 )}
368 <Grid.Item
369 full
370 as={InlineEthereumTransaction}
371 {...bind}
372 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
373 onReturn={() => pop()}
374 />
376 {isDefaultState && renderDetails()}
378 {completed && (
379 <>
380 <Grid.Item full as={BootArvoButton} disabled />
381 <Grid.Divider />
382 </>
383 )}
384 </Grid>
386 {isDefaultState && (
387 <FooterButton onClick={goRelocate} disabled>
388 Relocate
389 </FooterButton>
390 )}
391 </>
392 );