on fc65a96 help sign in
bridge/src/views/UrbitOS/NetworkingKeys.js
289 lines 8.3 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';
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(manualNetworkSeed, setManualNetworkSeed) {
41 const { urbitWallet, wallet, authMnemonic, authToken } = 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 const {
54 available: keyfileAvailable,
55 generating: keyfileGenerating,
56 filename,
57 bind: keyfileBind,
58 } = useKeyfileGenerator(manualNetworkSeed);
60 const buildNetworkSeed = useCallback(
61 async manualSeed => {
62 if (manualSeed !== undefined) {
63 setManualNetworkSeed(manualSeed);
64 return manualSeed;
65 } else {
66 const newNetworkRevision = networkRevision + 1;
67 console.log(`deriving seed with revision ${newNetworkRevision}`);
69 const networkSeed = await attemptNetworkSeedDerivation({
70 urbitWallet,
71 wallet,
72 authMnemonic,
73 details: _details,
74 point: _point,
75 authToken,
76 revision: newNetworkRevision,
77 });
79 if (Just.hasInstance(networkSeed)) {
80 return networkSeed.value;
81 }
83 randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes
84 setManualNetworkSeed(randomSeed.current);
86 return randomSeed.current;
87 }
88 },
89 [
90 _details,
91 authMnemonic,
92 setManualNetworkSeed,
93 networkRevision,
94 urbitWallet,
95 wallet,
96 _point,
97 authToken,
98 ]
99 );
101 const { completed: _completed, ...rest } = useEthereumTransaction(
102 useCallback(
103 async (manualSeed, isDiscontinuity) => {
104 const seed = await buildNetworkSeed(manualSeed);
105 const pair = deriveNetworkKeys(seed);
107 return azimuth.ecliptic.configureKeys(
108 _contracts,
109 _point,
110 addHexPrefix(pair.crypt.public),
111 addHexPrefix(pair.auth.public),
112 CRYPTO_SUITE_VERSION,
113 isDiscontinuity
114 );
115 },
116 [_contracts, _point, buildNetworkSeed]
117 ),
118 useCallback(
119 () => Promise.all([syncDetails(_point), syncRekeyDate(_point)]),
120 [_point, syncDetails, syncRekeyDate]
121 ),
122 GAS_LIMITS.CONFIGURE_KEYS
123 );
125 // only treat the transaction as completed once we also have keys to download
126 const completed = _completed && keyfileAvailable && !keyfileGenerating;
128 return {
129 completed,
130 filename,
131 keyfileBind,
132 ...rest,
133 };
136export default function UrbitOSNetworkingKeys({
137 manualNetworkSeed,
138 setManualNetworkSeed,
139}) {
140 const { pop } = useLocalRouter();
141 const { pointCursor } = usePointCursor();
142 const { getDetails } = usePointCache();
144 const point = need.point(pointCursor);
145 const details = getDetails(point);
147 const hasKeys = details.matchWith({
148 Nothing: () => false,
149 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
150 // we actually don't mind the default NaN behavior of parseInt here,
151 // since NaN > 0 === false and that's a reasonable result
152 });
154 const {
155 construct,
156 unconstruct,
157 completed,
158 inputsLocked,
159 bind,
160 keyfileBind,
161 } = useSetKeys(manualNetworkSeed, setManualNetworkSeed);
163 const validateForm = useCallback((values, errors) => {
164 if (values.useNetworkSeed && errors.networkSeed) {
165 return errors;
166 }
167 }, []);
169 const validate = useMemo(
170 () =>
171 composeValidator(
172 {
173 useNetworkSeed: buildCheckboxValidator(),
174 networkSeed: buildHexValidator(64), // 64 chars
175 useDiscontinuity: buildCheckboxValidator(),
176 },
177 validateForm
178 ),
179 [validateForm]
180 );
182 const onValues = useCallback(
183 ({ valid, values, form }) => {
184 if (valid) {
185 construct(
186 values.useNetworkSeed ? values.networkSeed : undefined,
187 values.useDiscontinuity
188 );
189 } else {
190 unconstruct();
191 }
193 if (!values.useNetworkSeed && values.networkSeed) {
194 form.change('networkSeed', '');
195 }
196 },
197 [construct, unconstruct]
198 );
200 const initialValues = useMemo(
201 () => ({
202 useNetworkSeed: false,
203 useDiscontinuity: false,
204 }),
205 []
206 );
208 const usageMessage =
209 'You need this to authenticate with Arvo. Please download';
211 return (
212 <>
213 <Grid>
214 <Grid.Item full as={ViewHeader}>
215 Set Networking Keys
216 </Grid.Item>
217 <Grid.Item className="gray4 f6" full>
218 Network Keys solidify the handshake between ID and OS
219 </Grid.Item>
220 {completed && (
221 <>
222 <Grid.Item
223 full
224 className="mv3"
225 as={DownloadKeyfileButton}
226 solid
227 {...keyfileBind}
228 />
229 <Grid.Item full className="mb3 bg-red3 white t-center p3">
230 {usageMessage}
231 </Grid.Item>
232 </>
233 )}
234 <BridgeForm
235 validate={validate}
236 onValues={onValues}
237 initialValues={initialValues}>
238 {() => (
239 <>
240 {!completed && (
241 <>
242 <Grid.Item
243 full
244 as={ToggleInput}
245 name="useNetworkSeed"
246 label="Use Custom Network Seed"
247 inverseLabel="Back to Derived Network Seed"
248 disabled={inputsLocked}
249 />
250 <Condition when="useNetworkSeed" is={true}>
251 <Grid.Item full as={NoticeBox} className="mb2">
252 When using a custom network seed, you'll need to download
253 your Arvo keyfile immediately after this transaction as
254 Bridge does not store your seed.
255 </Grid.Item>
256 <Grid.Item
257 full
258 as={HexInput}
259 name="networkSeed"
260 label="Network Seed (32 bytes)"
261 disabled={inputsLocked}
262 />
263 </Condition>
264 <Grid.Item
265 full
266 as={CheckboxInput}
267 name="useDiscontinuity"
268 label="Breach Continuity"
269 disabled={inputsLocked}
270 />
272 <Grid.Item full as={FormError} />
273 </>
274 )}
276 <Grid.Item
277 full
278 as={InlineEthereumTransaction}
279 {...bind}
280 label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
281 onReturn={() => pop()}
282 />
283 </>
284 )}
285 </BridgeForm>
286 </Grid>
287 </>
288 );