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