on 04bea4c help sign in
bridge/src/lib/useMultikeyFileGenerator.ts
214 lines 5.8 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';
21import { useRollerStore } from 'store/rollerStore';
23interface useMultikeyFileGeneratorArgs {
24 seed?: string;
25 point?: number;
26 seedWallet?: any;
27 inviteMasterTicketWallet?: any;
30/**
31 * defaults:
32 * - seed: derived from wallet, mnemonic, or token (if posisble)
33 * - point: the current pointCursor
34 */
35export default function useMultikeyFileGenerator({
36 seed,
37 point,
38 seedWallet,
39 inviteMasterTicketWallet,
40}: useMultikeyFileGeneratorArgs) {
41 const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
42 const { pointCursor }: any = usePointCursor();
43 const { syncDetails, getDetails }: any = usePointCache();
44 const { isOwner, isManagementProxy } = useCurrentPermissions();
45 const rollerStore = useRollerStore();
47 const [notice, setNotice] = useState('Deriving networking keys...');
48 const [downloaded, setDownloaded] = useState(false);
49 const [generating, setGenerating] = useState(true);
50 const [keyfile, setKeyfile] = useState<boolean | string>(false);
51 const [code, setCode] = useState(false);
53 const _point = useMemo(() => point || need.point(pointCursor), [
54 point,
55 pointCursor,
56 ]);
57 const details = getDetails(_point);
58 const _details: L1Point | null = useMemo(
59 () =>
60 rollerStore?.point?.isL2
61 ? rollerStore.point
62 : details.matchWith({
63 Just: (d: any) => need.details(d),
64 Nothing: () => null,
65 }),
66 [details, rollerStore.point]
67 );
69 const currentRevision: number = useMemo(
70 () => convertToInt(_details?.keyRevisionNumber || 0, 10),
71 [_details]
72 );
73 const nextRevision = useMemo(() => currentRevision + 1, [currentRevision]);
75 const pairFromRevision = useCallback(
76 async (revision: number, useInviteMasterTicketWallet = false) => {
77 if (!_details) {
78 return;
79 }
81 // During the activation flow, we pass in the
82 // inviteMasterTicketWallet to derive the multikey
83 const derivationWallet =
84 useInviteMasterTicketWallet && inviteMasterTicketWallet
85 ? inviteMasterTicketWallet
86 : seedWallet || urbitWallet;
88 const networkSeed = seed
89 ? Just(seed)
90 : await attemptNetworkSeedDerivation({
91 urbitWallet: derivationWallet,
92 wallet,
93 authMnemonic,
94 details: _details,
95 authToken,
96 point: _point,
97 revision: revision,
98 });
100 if (Nothing.hasInstance(networkSeed)) {
101 setGenerating(false);
102 setNotice(
103 'Custom or nondeterministic networking keys cannot be re-downloaded.'
104 );
105 console.log(`seed is nondeterminable for revision ${revision}`);
106 return;
107 }
109 const _networkSeed = networkSeed.value;
111 return deriveNetworkKeys(_networkSeed);
112 },
113 [
114 _details,
115 inviteMasterTicketWallet,
116 seedWallet,
117 urbitWallet,
118 seed,
119 wallet,
120 authMnemonic,
121 authToken,
122 _point,
123 ]
124 );
126 const hasNetworkKeys = currentRevision > 0;
127 const available =
128 (isOwner || isManagementProxy) && hasNetworkKeys && !!keyfile;
130 const generate = useCallback(async () => {
131 if (!hasNetworkKeys) {
132 setGenerating(false);
133 setNotice('Network keys not yet set.');
134 console.log(
135 `no networking keys available for revision ${currentRevision}`
136 );
137 return;
138 }
140 const currentPair = await pairFromRevision(currentRevision);
142 if (!currentPair || !keysMatchChain(currentPair, _details || {})) {
143 setGenerating(false);
144 setNotice('Derived networking keys do not match on-chain details.');
145 console.log(`keys do not match details for revision ${currentRevision}`);
146 return;
147 }
149 // During the activation flow, we want to use the invite wallet for the next pair
150 const nextPair = await pairFromRevision(nextRevision, true);
152 setNotice('');
153 setCode(generateCode(currentPair));
154 setKeyfile(
155 compileMultiKey(_point, [
156 {
157 revision: currentRevision,
158 pair: currentPair,
159 },
160 {
161 revision: nextRevision,
162 pair: nextPair,
163 },
164 ])
165 );
166 setGenerating(false);
167 }, [
168 _details,
169 _point,
170 currentRevision,
171 hasNetworkKeys,
172 nextRevision,
173 pairFromRevision,
174 ]);
176 const filename = useMemo(() => {
177 return `${stripSigPrefix(ob.patp(_point))}-${currentRevision}.key`;
178 }, [_point, currentRevision]);
180 const download = useCallback(() => {
181 if (typeof keyfile !== 'string') {
182 return;
183 }
185 saveAs(
186 new Blob([keyfile], {
187 type: 'text/plain;charset=utf-8',
188 }),
189 filename
190 );
191 setDownloaded(true);
192 }, [filename, keyfile]);
194 useEffect(() => {
195 syncDetails(_point);
196 }, [_point, syncDetails]);
198 useEffect(() => {
199 generate();
200 }, [generate]);
202 const output = {
203 generating,
204 available,
205 downloaded,
206 download,
207 filename,
208 notice,
209 keyfile,
210 code,
211 };
213 return { ...output, bind: output };