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