on 36aafee help sign in
bridge/src/views/UrbitOS/NetworkingKeys.js
357 lines 11 kB history raw
1import React, { useCallback, useMemo, useRef, useState } from 'react';
2import { Just } from 'folktale/maybe';
3import { Grid, ToggleInput, CheckboxInput, Button } 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';
11import { useRollerStore } from 'store/roller';
13import useRoller from 'lib/useRoller';
14import { useLocalRouter } from 'lib/LocalRouter';
15import * as need from 'lib/need';
16import {
17 attemptNetworkSeedDerivation,
18 deriveNetworkKeys,
19 CRYPTO_SUITE_VERSION,
20} from 'lib/keys';
21import useEthereumTransaction from 'lib/useEthereumTransaction';
22import { GAS_LIMITS } from 'lib/constants';
23import { addHexPrefix } from 'lib/utils/address';
24import useKeyfileGenerator from 'lib/useKeyfileGenerator';
26import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
27import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
28import NoticeBox from 'components/NoticeBox';
29import AlertBox from 'components/AlertBox';
31import { HexInput } from 'form/Inputs';
32import {
33 composeValidator,
34 buildCheckboxValidator,
35 buildHexValidator,
36} from 'form/validators';
37import BridgeForm from 'form/BridgeForm';
38import Condition from 'form/Condition';
39import FormError from 'form/FormError';
40import { convertToInt } from 'lib/convertToInt';
41import Window from 'components/L2/Window/Window';
42import HeaderPane from 'components/L2/Window/HeaderPane';
43import BodyPane from 'components/L2/Window/BodyPane';
44import { Box, Row } from '@tlon/indigo-react';
46function useSetKeys(manualNetworkSeed, setManualNetworkSeed) {
47 const { urbitWallet, wallet, authMnemonic, authToken } = useWallet();
48 const { pointCursor } = usePointCursor();
49 const { syncDetails, syncRekeyDate, getDetails } = usePointCache();
50 const { contracts } = useNetwork();
52 const _point = need.point(pointCursor);
53 const _contracts = need.contracts(contracts);
54 const _details = need.details(getDetails(_point));
56 const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
57 const randomSeed = useRef();
59 const {
60 available: keyfileAvailable,
61 generating: keyfileGenerating,
62 filename,
63 bind: keyfileBind,
64 } = useKeyfileGenerator(manualNetworkSeed);
66 const buildNetworkSeed = useCallback(
67 async manualSeed => {
68 if (manualSeed !== undefined) {
69 setManualNetworkSeed(manualSeed);
70 return manualSeed;
71 } else {
72 const newNetworkRevision = networkRevision + 1;
73 console.log(`deriving seed with revision ${newNetworkRevision}`);
75 const networkSeed = await attemptNetworkSeedDerivation({
76 urbitWallet,
77 wallet,
78 authMnemonic,
79 details: _details,
80 point: _point,
81 authToken,
82 revision: newNetworkRevision,
83 });
85 if (Just.hasInstance(networkSeed)) {
86 return networkSeed.value;
87 }
89 randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes
90 setManualNetworkSeed(randomSeed.current);
92 return randomSeed.current;
93 }
94 },
95 [
96 _details,
97 authMnemonic,
98 setManualNetworkSeed,
99 networkRevision,
100 urbitWallet,
101 wallet,
102 _point,
103 authToken,
104 ]
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 && !keyfileGenerating;
134 return {
135 completed,
136 filename,
137 keyfileBind,
138 ...rest,
139 };
142export default function UrbitOSNetworkingKeys({
143 manualNetworkSeed,
144 setManualNetworkSeed,
145}) {
146 const { pop } = useLocalRouter();
147 const { pointCursor } = usePointCursor();
148 const { getDetails } = usePointCache();
149 const { currentL2, currentL2Spawn } = useRollerStore();
150 const { configureNetworkingKeys, getPendingTransactions } = useRoller();
151 const [breach, setBreach] = useState(false);
153 const point = need.point(pointCursor);
154 const details = getDetails(point);
156 const hasKeys = details.matchWith({
157 Nothing: () => false,
158 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
159 // we actually don't mind the default NaN behavior of parseInt here,
160 // since NaN > 0 === false and that's a reasonable result
161 });
163 const {
164 construct,
165 unconstruct,
166 completed,
167 inputsLocked,
168 bind,
169 keyfileBind,
170 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
172 const validateForm = useCallback((values, errors) => {
173 if (values.useNetworkSeed && errors.networkSeed) {
174 return errors;
175 }
176 }, []);
178 const validate = useMemo(
179 () =>
180 composeValidator(
181 {
182 useNetworkSeed: buildCheckboxValidator(),
183 networkSeed: buildHexValidator(64), // 64 chars
184 useDiscontinuity: buildCheckboxValidator(),
185 },
186 validateForm
187 ),
188 [validateForm]
189 );
191 const onValues = useCallback(
192 ({ valid, values, form }) => {
193 if (valid) {
194 construct(
195 values.useNetworkSeed ? values.networkSeed : undefined,
196 values.useDiscontinuity
197 );
198 setBreach(values.useDiscontinuity);
199 setManualNetworkSeed(values.useNetworkSeed);
200 } else {
201 unconstruct();
202 }
204 if (!values.useNetworkSeed && values.networkSeed) {
205 form.change('networkSeed', '');
206 setBreach(false);
207 setManualNetworkSeed('');
208 }
209 },
210 [construct, unconstruct, setManualNetworkSeed, setBreach]
211 );
213 const setNetworkingKeys = useCallback(async () => {
214 // setLoading(true);
215 console.log(breach, manualNetworkSeed);
216 try {
217 const txHash = await configureNetworkingKeys({
218 breach,
219 manualNetworkSeed,
220 });
221 // TODO: just use the tx hash instead?
222 getPendingTransactions(point);
223 // TODO: this is just so we get visual feedback that the tx has gone through
224 // but this should be addressed with the new UI flow (-> download keyfile)
225 //
226 // A question here is how to deal with the modals/messages about the keys not being
227 // set since they are in pending in the Roller...
228 // we could inspect if there's a changeKeys in the local list of pending txs,...
229 //
230 pop();
231 } catch (error) {
232 // setError(error);
233 } finally {
234 // setLoading(false);
235 }
236 }, [
237 breach,
238 manualNetworkSeed,
239 getPendingTransactions,
240 point,
241 pop,
242 configureNetworkingKeys,
243 ]);
245 const initialValues = useMemo(
246 () => ({
247 useNetworkSeed: false,
248 useDiscontinuity: false,
249 }),
250 []
251 );
253 const viewTitle = completed
254 ? 'Network Keys are now set'
255 : 'Set Networking Keys';
257 const usageMessage =
258 'You need this keyfile to authenticate with your OS. Please Download.';
260 return (
261 <Window>
262 <HeaderPane>
263 <Row className="header-row">
264 <h5>{viewTitle}</h5>
265 </Row>
266 </HeaderPane>
267 <BodyPane>
268 {!completed && (
269 <Grid.Item className="gray4 f5 mv2 align-self-start" full>
270 Network Keys authenticate Urbit ID with Urbit OS.
271 </Grid.Item>
272 )}
273 {completed && (
274 <>
275 <Grid.Item
276 full
277 className="mb3"
278 as={DownloadKeyfileButton}
279 solid
280 left
281 {...keyfileBind}
282 />
283 <Grid.Item full as={AlertBox} className="mb8">
284 {usageMessage}
285 </Grid.Item>
286 </>
287 )}
288 <BridgeForm
289 validate={validate}
290 onValues={onValues}
291 initialValues={initialValues}>
292 {() => (
293 <Box width="100%">
294 {!completed && (
295 <>
296 <Grid.Item
297 full
298 as={ToggleInput}
299 name="useNetworkSeed"
300 label="Use Custom Network Seed"
301 inverseLabel="Back to Derived Network Seed"
302 disabled={inputsLocked}
303 />
304 <Condition when="useNetworkSeed" is={true}>
305 <Grid.Item full as={NoticeBox} className="mb2">
306 When using a custom network seed, you'll need to download
307 your Arvo keyfile immediately after this transaction as
308 Bridge does not store your seed.
309 </Grid.Item>
310 <Grid.Item
311 full
312 as={HexInput}
313 name="networkSeed"
314 label="Network Seed (32 bytes)"
315 disabled={inputsLocked}
316 className="mb6"
317 />
318 </Condition>
319 <Grid.Item
320 full
321 as={CheckboxInput}
322 name="useDiscontinuity"
323 label="Breach Continuity"
324 disabled={inputsLocked}
325 className="mb6"
326 />
328 <Grid.Item full as={FormError} />
329 </>
330 )}
332 {currentL2 && !currentL2Spawn ? (
333 <Grid.Item
334 as={Button}
335 full
336 className=""
337 center
338 solid
339 onClick={setNetworkingKeys}>
340 {'Sign Transaction'}
341 </Grid.Item>
342 ) : (
343 <Grid.Item
344 full
345 as={InlineEthereumTransaction}
346 {...bind}
347 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
348 onReturn={() => pop()}
349 />
350 )}
351 </Box>
352 )}
353 </BridgeForm>
354 </BodyPane>
355 </Window>
356 );