on 1352bb6 help sign in
bridge/src/views/UrbitOS/NetworkingKeys.js
294 lines 8.4 kB history raw
1import React, { useCallback, useMemo, useRef, useState } from 'react';
2import { Just } from 'folktale/maybe';
3import { Grid, 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 attemptNetworkSeedDerivation,
16 deriveNetworkKeys,
17 CRYPTO_SUITE_VERSION,
18} from 'lib/keys';
19import useEthereumTransaction from 'lib/useEthereumTransaction';
20import { GAS_LIMITS } from 'lib/constants';
21import { addHexPrefix } from 'lib/wallet';
22import useKeyfileGenerator from 'lib/useKeyfileGenerator';
24import ViewHeader from 'components/ViewHeader';
25import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
26import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
27import NoticeBox from 'components/NoticeBox';
29import { HexInput } from 'form/Inputs';
30import {
31 composeValidator,
32 buildCheckboxValidator,
33 buildHexValidator,
34} from 'form/validators';
35import BridgeForm from 'form/BridgeForm';
36import Condition from 'form/Condition';
37import FormError from 'form/FormError';
38import convertToInt from 'lib/convertToInt';
40function useSetKeys() {
41 const { urbitWallet, wallet, authMnemonic } = useWallet();
42 const { pointCursor } = usePointCursor();
43 const { syncDetails, syncRekeyDate, getDetails } = usePointCache();
44 const { contracts } = useNetwork();
46 const _point = need.point(pointCursor);
47 const _contracts = need.contracts(contracts);
48 const _details = need.details(getDetails(_point));
50 const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
51 const randomSeed = useRef();
53 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
54 const [ndNetworkSeed, setNdNetworkSeed] = useState();
56 const {
57 available: keyfileAvailable,
58 filename,
59 bind: keyfileBind,
60 } = useKeyfileGenerator(ndNetworkSeed);
62 const buildNetworkSeed = useCallback(
63 async manualSeed => {
64 if (manualSeed !== undefined) {
65 setNdNetworkSeed(manualSeed);
66 return manualSeed;
67 } else {
68 const newNetworkRevision = networkRevision + 1;
69 console.log(`deriving seed with revision ${newNetworkRevision}`);
71 const networkSeed = await attemptNetworkSeedDerivation({
72 urbitWallet,
73 wallet,
74 authMnemonic,
75 details: _details,
76 revision: newNetworkRevision,
77 });
79 if (Just.hasInstance(networkSeed)) {
80 return networkSeed.value;
81 }
83 randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes
84 setNdNetworkSeed(randomSeed.current);
86 return randomSeed.current;
87 }
88 },
89 [_details, authMnemonic, networkRevision, urbitWallet, wallet]
90 );
92 const { completed: _completed, ...rest } = useEthereumTransaction(
93 useCallback(
94 async (manualSeed, isDiscontinuity) => {
95 const seed = await buildNetworkSeed(manualSeed);
96 const pair = deriveNetworkKeys(seed);
98 return azimuth.ecliptic.configureKeys(
99 _contracts,
100 _point,
101 addHexPrefix(pair.crypt.public),
102 addHexPrefix(pair.auth.public),
103 CRYPTO_SUITE_VERSION,
104 isDiscontinuity
105 );
106 },
107 [_contracts, _point, buildNetworkSeed]
108 ),
109 useCallback(
110 () => Promise.all([syncDetails(_point), syncRekeyDate(_point)]),
111 [_point, syncDetails, syncRekeyDate]
112 ),
113 GAS_LIMITS.CONFIGURE_KEYS
114 );
116 // only treat the transaction as completed once we also have keys to download
117 const completed = _completed && keyfileAvailable;
119 return {
120 completed,
121 ndNetworkSeed,
122 filename,
123 keyfileBind,
124 ...rest,
125 };
128export default function UrbitOSNetworkingKeys() {
129 const { push, names, pop } = useLocalRouter();
130 const { pointCursor } = usePointCursor();
131 const { getDetails } = usePointCache();
133 const point = need.point(pointCursor);
134 const details = getDetails(point);
136 const hasKeys = details.matchWith({
137 Nothing: () => false,
138 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
139 // we actually don't mind the default NaN behavior of parseInt here,
140 // since NaN > 0 === false and that's a reasonable result
141 });
143 const {
144 isDefaultState,
145 construct,
146 unconstruct,
147 broadcasting,
148 completed,
149 inputsLocked,
150 bind,
151 keyfileBind,
152 } = useSetKeys();
154 const validateForm = useCallback((values, errors) => {
155 if (values.useNetworkSeed && errors.networkSeed) {
156 return errors;
157 }
158 }, []);
160 const validate = useMemo(
161 () =>
162 composeValidator(
163 {
164 useNetworkSeed: buildCheckboxValidator(),
165 networkSeed: buildHexValidator(64), // 64 chars
166 useDiscontinuity: buildCheckboxValidator(),
167 },
168 validateForm
169 ),
170 [validateForm]
171 );
173 const onValues = useCallback(
174 ({ valid, values, form }) => {
175 if (valid) {
176 construct(
177 values.useNetworkSeed ? values.networkSeed : undefined,
178 values.useDiscontinuity
179 );
180 } else {
181 unconstruct();
182 }
184 if (!values.useNetworkSeed && values.networkSeed) {
185 form.change('networkSeed', '');
186 }
187 },
188 [construct, unconstruct]
189 );
191 const initialValues = useMemo(
192 () => ({
193 useNetworkSeed: false,
194 useDiscontinuity: false,
195 }),
196 []
197 );
199 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
201 const usageMessage = 'You need this to authenticate with arvo';
203 const renderTitle = () => {
204 if (completed) {
205 return 'Networking keys are now set.';
206 }
208 if (broadcasting) {
209 return 'Setting Network Keys...';
210 }
212 if (!hasKeys) {
213 return 'Networking keys have not yet been set.';
214 }
216 return 'Networking';
217 };
219 return (
220 <>
221 <Grid>
222 <Grid.Item full as={ViewHeader}>
223 {renderTitle()}
224 </Grid.Item>
225 {completed && (
226 <>
227 <Grid.Item
228 full
229 className="mb3"
230 as={DownloadKeyfileButton}
231 solid
232 {...keyfileBind}
233 />
234 <Grid.Item full className="mb3 bg-red3 white t-center p3">
235 {usageMessage}
236 </Grid.Item>
237 </>
238 )}
239 <BridgeForm
240 validate={validate}
241 onValues={onValues}
242 initialValues={initialValues}>
243 {() => (
244 <>
245 {!completed && (
246 <>
247 <Grid.Item
248 full
249 as={ToggleInput}
250 name="useNetworkSeed"
251 label="Use Custom Network Seed"
252 inverseLabel="Back to Derived Network Seed"
253 disabled={inputsLocked}
254 />
255 <Condition when="useNetworkSeed" is={true}>
256 <Grid.Item full as={NoticeBox} className="mb2">
257 When using a custom network seed, you'll need to download
258 your Arvo keyfile immediately after this transaction is
259 Bridge does not store your seed.
260 </Grid.Item>
261 <Grid.Item
262 full
263 as={HexInput}
264 name="networkSeed"
265 label="Network Seed (32 bytes)"
266 disabled={inputsLocked}
267 />
268 </Condition>
269 <Grid.Item
270 full
271 as={CheckboxInput}
272 name="useDiscontinuity"
273 label="Breach Continuity"
274 disabled={inputsLocked}
275 />
277 <Grid.Item full as={FormError} />
278 </>
279 )}
281 <Grid.Item
282 full
283 as={InlineEthereumTransaction}
284 {...bind}
285 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
286 onReturn={() => pop()}
287 />
288 </>
289 )}
290 </BridgeForm>
291 </Grid>
292 </>
293 );