on 08176cf help sign in
bridge/src/views/UrbitOS/NetworkingKeys.js
296 lines 8.5 kB history raw
1import React, { useCallback, useMemo, useRef } 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/utils/address';
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';
28import AlertBox from 'components/AlertBox';
30import { HexInput } from 'form/Inputs';
31import {
32 composeValidator,
33 buildCheckboxValidator,
34 buildHexValidator,
35} from 'form/validators';
36import BridgeForm from 'form/BridgeForm';
37import Condition from 'form/Condition';
38import FormError from 'form/FormError';
39import { convertToInt } from 'lib/convertToInt';
41function useSetKeys(manualNetworkSeed, setManualNetworkSeed) {
42 const { urbitWallet, wallet, authMnemonic, authToken } = useWallet();
43 const { pointCursor } = usePointCursor();
44 const { syncDetails, syncRekeyDate, getDetails } = usePointCache();
45 const { contracts } = useNetwork();
47 const _point = need.point(pointCursor);
48 const _contracts = need.contracts(contracts);
49 const _details = need.details(getDetails(_point));
51 const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
52 const randomSeed = useRef();
54 const {
55 available: keyfileAvailable,
56 generating: keyfileGenerating,
57 filename,
58 bind: keyfileBind,
59 } = useKeyfileGenerator(manualNetworkSeed);
61 const buildNetworkSeed = useCallback(
62 async manualSeed => {
63 if (manualSeed !== undefined) {
64 setManualNetworkSeed(manualSeed);
65 return manualSeed;
66 } else {
67 const newNetworkRevision = networkRevision + 1;
68 console.log(`deriving seed with revision ${newNetworkRevision}`);
70 const networkSeed = await attemptNetworkSeedDerivation({
71 urbitWallet,
72 wallet,
73 authMnemonic,
74 details: _details,
75 point: _point,
76 authToken,
77 revision: newNetworkRevision,
78 });
80 if (Just.hasInstance(networkSeed)) {
81 return networkSeed.value;
82 }
84 randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes
85 setManualNetworkSeed(randomSeed.current);
87 return randomSeed.current;
88 }
89 },
90 [
91 _details,
92 authMnemonic,
93 setManualNetworkSeed,
94 networkRevision,
95 urbitWallet,
96 wallet,
97 _point,
98 authToken,
99 ]
100 );
102 const { completed: _completed, ...rest } = useEthereumTransaction(
103 useCallback(
104 async (manualSeed, isDiscontinuity) => {
105 const seed = await buildNetworkSeed(manualSeed);
106 const pair = deriveNetworkKeys(seed);
108 return azimuth.ecliptic.configureKeys(
109 _contracts,
110 _point,
111 addHexPrefix(pair.crypt.public),
112 addHexPrefix(pair.auth.public),
113 CRYPTO_SUITE_VERSION,
114 isDiscontinuity
115 );
116 },
117 [_contracts, _point, buildNetworkSeed]
118 ),
119 useCallback(
120 () => Promise.all([syncDetails(_point), syncRekeyDate(_point)]),
121 [_point, syncDetails, syncRekeyDate]
122 ),
123 GAS_LIMITS.CONFIGURE_KEYS
124 );
126 // only treat the transaction as completed once we also have keys to download
127 const completed = _completed && keyfileAvailable && !keyfileGenerating;
129 return {
130 completed,
131 filename,
132 keyfileBind,
133 ...rest,
134 };
137export default function UrbitOSNetworkingKeys({
138 manualNetworkSeed,
139 setManualNetworkSeed,
140}) {
141 const { pop } = useLocalRouter();
142 const { pointCursor } = usePointCursor();
143 const { getDetails } = usePointCache();
145 const point = need.point(pointCursor);
146 const details = getDetails(point);
148 const hasKeys = details.matchWith({
149 Nothing: () => false,
150 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
151 // we actually don't mind the default NaN behavior of parseInt here,
152 // since NaN > 0 === false and that's a reasonable result
153 });
155 const {
156 construct,
157 unconstruct,
158 completed,
159 inputsLocked,
160 bind,
161 keyfileBind,
162 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
164 const validateForm = useCallback((values, errors) => {
165 if (values.useNetworkSeed && errors.networkSeed) {
166 return errors;
167 }
168 }, []);
170 const validate = useMemo(
171 () =>
172 composeValidator(
173 {
174 useNetworkSeed: buildCheckboxValidator(),
175 networkSeed: buildHexValidator(64), // 64 chars
176 useDiscontinuity: buildCheckboxValidator(),
177 },
178 validateForm
179 ),
180 [validateForm]
181 );
183 const onValues = useCallback(
184 ({ valid, values, form }) => {
185 if (valid) {
186 construct(
187 values.useNetworkSeed ? values.networkSeed : undefined,
188 values.useDiscontinuity
189 );
190 } else {
191 unconstruct();
192 }
194 if (!values.useNetworkSeed && values.networkSeed) {
195 form.change('networkSeed', '');
196 }
197 },
198 [construct, unconstruct]
199 );
201 const initialValues = useMemo(
202 () => ({
203 useNetworkSeed: false,
204 useDiscontinuity: false,
205 }),
206 []
207 );
209 const viewTitle = completed
210 ? 'Network Keys are now set'
211 : 'Set Networking Keys';
213 const usageMessage =
214 'You need this keyfile to authenticate with your OS. Please Download.';
216 return (
217 <>
218 <Grid>
219 <Grid.Item full as={ViewHeader}>
220 {viewTitle}
221 </Grid.Item>
222 {!completed && (
223 <Grid.Item className="gray4 f5" full>
224 Network Keys authenticate Urbit ID with Urbit OS.
225 </Grid.Item>
226 )}
227 {completed && (
228 <>
229 <Grid.Item
230 full
231 className="mb3"
232 as={DownloadKeyfileButton}
233 solid
234 {...keyfileBind}
235 />
236 <Grid.Item full as={AlertBox} className="mb8">
237 {usageMessage}
238 </Grid.Item>
239 </>
240 )}
241 <BridgeForm
242 validate={validate}
243 onValues={onValues}
244 initialValues={initialValues}>
245 {() => (
246 <>
247 {!completed && (
248 <>
249 <Grid.Item
250 full
251 as={ToggleInput}
252 name="useNetworkSeed"
253 label="Use Custom Network Seed"
254 inverseLabel="Back to Derived Network Seed"
255 disabled={inputsLocked}
256 />
257 <Condition when="useNetworkSeed" is={true}>
258 <Grid.Item full as={NoticeBox} className="mb2">
259 When using a custom network seed, you'll need to download
260 your Arvo keyfile immediately after this transaction as
261 Bridge does not store your seed.
262 </Grid.Item>
263 <Grid.Item
264 full
265 as={HexInput}
266 name="networkSeed"
267 label="Network Seed (32 bytes)"
268 disabled={inputsLocked}
269 />
270 </Condition>
271 <Grid.Item
272 full
273 as={CheckboxInput}
274 name="useDiscontinuity"
275 label="Breach Continuity"
276 disabled={inputsLocked}
277 />
279 <Grid.Item full as={FormError} />
280 </>
281 )}
283 <Grid.Item
284 full
285 as={InlineEthereumTransaction}
286 {...bind}
287 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
288 onReturn={() => pop()}
289 />
290 </>
291 )}
292 </BridgeForm>
293 </Grid>
294 </>
295 );