on e2498e9 help sign in
bridge/src/lib/useMultikeyFileGenerator.ts
203 lines 5.2 kB history raw
1import * as need from 'lib/need';
2import saveAs from 'file-saver';
3import ob from 'urbit-ob';
4import { Just, Nothing } from 'folktale/maybe';
6import { useCallback, useEffect, useMemo, useState } from 'react';
7import { usePointCache } from 'store/pointCache';
8import { usePointCursor } from 'store/pointCursor';
9import { useWallet } from 'store/wallet';
10import { deriveNetworkKeys } from 'urbit-key-generation';
11import { convertToInt } from './convertToInt';
12import {
13 attemptNetworkSeedDerivation,
14 compileMultiKey,
15 keysMatchChain,
16} from './keys';
17import { generateCode } from './networkCode';
18import { L1Point } from './types/L1Point';
19import useCurrentPermissions from './useCurrentPermissions';
20import { stripSigPrefix } from 'form/formatters';
22interface useMultikeyFileGeneratorArgs {
23 seed?: string;
24 point?: number;
25 seedWallet?: any;
28/**
29 * defaults:
30 * - seed: derived from wallet, mnemonic, or token (if posisble)
31 * - point: the current pointCursor
32 */
33export default function useMultikeyFileGenerator({
34 seed,
35 point,
36 seedWallet,
37}: useMultikeyFileGeneratorArgs) {
38 const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
39 const { pointCursor }: any = usePointCursor();
40 const { syncDetails, getDetails }: any = usePointCache();
41 const { isOwner, isManagementProxy } = useCurrentPermissions();
43 const [notice, setNotice] = useState('Deriving networking keys...');
44 const [downloaded, setDownloaded] = useState(false);
45 const [generating, setGenerating] = useState(true);
46 const [keyfile, setKeyfile] = useState<boolean | string>(false);
47 const [code, setCode] = useState(false);
49 const _point = useMemo(() => point || need.point(pointCursor), [
50 point,
51 pointCursor,
52 ]);
53 const details = getDetails(_point);
54 const _details: L1Point | null = useMemo(
55 () =>
56 details.matchWith({
57 Just: d => need.details(d),
58 Nothing: () => null,
59 }),
60 [details]
61 );
63 const currentRevision: number = useMemo(
64 () =>
65 details.matchWith({
66 Just: ({ value }) => convertToInt(value.keyRevisionNumber, 10),
67 Nothing: () => 0,
68 }),
69 [details]
70 );
71 const nextRevision = useMemo(() => currentRevision + 1, [currentRevision]);
73 const pairFromRevision = useCallback(
74 async (revision: number) => {
75 if (!_details) {
76 return;
77 }
79 const networkSeed = seed
80 ? Just(seed)
81 : await attemptNetworkSeedDerivation({
82 urbitWallet: seedWallet || urbitWallet,
83 wallet: wallet,
84 authMnemonic,
85 details: _details,
86 authToken,
87 point: _point,
88 revision: revision,
89 });
91 if (Nothing.hasInstance(networkSeed)) {
92 setGenerating(false);
93 setNotice(
94 'Custom or nondeterministic networking keys cannot be re-downloaded.'
95 );
96 console.log(`seed is nondeterminable for revision ${revision}`);
97 return;
98 }
100 const _networkSeed = networkSeed.value;
102 return deriveNetworkKeys(_networkSeed);
103 },
104 [
105 _details,
106 _point,
107 authMnemonic,
108 authToken,
109 seed,
110 urbitWallet,
111 wallet,
112 seedWallet,
113 ]
114 );
116 const hasNetworkKeys = currentRevision > 0;
117 const available =
118 (isOwner || isManagementProxy) && hasNetworkKeys && !!keyfile;
120 const generate = useCallback(async () => {
121 if (!hasNetworkKeys) {
122 setGenerating(false);
123 setNotice('Network keys not yet set.');
124 console.log(
125 `no networking keys available for revision ${currentRevision}`
126 );
127 return;
128 }
130 const currentPair = await pairFromRevision(currentRevision);
132 if (!currentPair || !keysMatchChain(currentPair, _details || {})) {
133 setGenerating(false);
134 setNotice('Derived networking keys do not match on-chain details.');
135 console.log(`keys do not match details for revision ${currentRevision}`);
136 return;
137 }
139 const nextPair = await pairFromRevision(nextRevision);
141 setNotice('');
142 setCode(generateCode(currentPair));
143 setKeyfile(
144 compileMultiKey(_point, [
145 {
146 revision: currentRevision,
147 pair: currentPair,
148 },
149 {
150 revision: nextRevision,
151 pair: nextPair,
152 },
153 ])
154 );
155 setGenerating(false);
156 }, [
157 _details,
158 _point,
159 currentRevision,
160 hasNetworkKeys,
161 nextRevision,
162 pairFromRevision,
163 ]);
165 const filename = useMemo(() => {
166 return `${stripSigPrefix(ob.patp(_point))}-${currentRevision}.key`;
167 }, [_point, currentRevision]);
169 const download = useCallback(() => {
170 if (typeof keyfile !== 'string') {
171 return;
172 }
174 saveAs(
175 new Blob([keyfile], {
176 type: 'text/plain;charset=utf-8',
177 }),
178 filename
179 );
180 setDownloaded(true);
181 }, [filename, keyfile]);
183 useEffect(() => {
184 syncDetails(_point);
185 }, [_point, syncDetails]);
187 useEffect(() => {
188 generate();
189 }, [generate]);
191 const output = {
192 generating,
193 available,
194 downloaded,
195 download,
196 filename,
197 notice,
198 keyfile,
199 code,
200 };
202 return { ...output, bind: output };