on 63bd524 help sign in
bridge/src/lib/useKeyfileGenerator.ts
253 lines 7.3 kB history raw
1import { useCallback, useMemo, useEffect, useState } from 'react';
2import { Just } from 'folktale/maybe';
3import saveAs from 'file-saver';
4import { useWallet } from 'store/wallet';
5import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
6import { generateCode } from 'lib/networkCode';
7import {
8 attemptNetworkSeedDerivation,
9 deriveNetworkSeedFromUrbitWallet,
10 keysMatchChain,
11 deriveNetworkKeys,
12 compileMultiKey,
13} from './keys';
14import { stripSigPrefix } from 'form/formatters';
15import useRoller from './useRoller';
16import Point from './types/Point';
17import { toL1Details } from './utils/point';
18import { ETH_ZERO_ADDR } from './constants';
20interface useKeyfileGeneratorArgs {
21 point?: Point;
22 seeds: string[];
25/**
26 * The default Keyfile Generator. Can be used by itself, or composed with
27 * other hooks (see `useActivationKeyfileGenerator` and `useHomeKeyfileGenerator`).
28 *
29 * @param point Point - (optional) the Point for which to generate keys
30 * @param seeds string[] - (required) one or more seeds consumed by deriveNetworkKeys
31 *
32 */
33function useKeyfileGenerator({ point, seeds }: useKeyfileGeneratorArgs) {
34 // exported keyfile generator properties
35 const [notice, setNotice] = useState('Deriving networking keys...');
36 const [downloaded, setDownloaded] = useState(false);
37 const [generating, setGenerating] = useState(true);
38 const [keyfile, setKeyfile] = useState<boolean | string>(false);
39 const [code, setCode] = useState(false);
41 // resolved point
42 const { point: storedPoint } = useRollerStore();
43 const resolvedPoint = useMemo(() => {
44 return point || storedPoint;
45 }, [point, storedPoint]);
47 const networkRevision = useMemo(() => {
48 return Number(resolvedPoint.keyRevisionNumber);
49 }, [resolvedPoint]);
50 const canManage = useMemo(() => {
51 return resolvedPoint.isOwner || resolvedPoint.isManagementProxy;
52 }, [resolvedPoint]);
54 const hasNetworkKeys = networkRevision > 0;
55 const available = canManage && hasNetworkKeys && !!keyfile;
57 const generate = useCallback(async () => {
58 // Point still loading ...
59 if (resolvedPoint.value === EMPTY_POINT.value) {
60 console.log('Loading point data ...');
61 return;
62 }
64 if (!hasNetworkKeys) {
65 setGenerating(false);
66 setNotice('Network keys not yet set.');
67 console.log(
68 `no networking keys available for revision ${networkRevision}`
69 );
70 return;
71 }
73 // seeds were either generated non-deterministically,
74 // or still being derived ...
75 if (seeds.length === 0) {
76 setGenerating(false);
77 setNotice(
78 'Custom or nondeterministic networking keys cannot be re-downloaded.'
79 );
80 console.log(`seed is nondeterminable for revision ${networkRevision}`);
81 return;
82 }
84 const pairs = seeds.map(seed => deriveNetworkKeys(seed));
85 const firstPair = pairs[0];
87 if (!keysMatchChain(firstPair, resolvedPoint)) {
88 setGenerating(false);
89 setNotice('Derived networking keys do not match on-chain details.');
90 console.log(`keys do not match details for revision ${networkRevision}`);
91 return;
92 }
94 setNotice('');
95 setCode(generateCode(firstPair));
96 setKeyfile(
97 compileMultiKey(
98 resolvedPoint.value,
99 pairs.map((pair, k) => {
100 return { revision: networkRevision + k, pair };
101 })
102 )
103 );
104 console.log(`keyfile generated for revision ${networkRevision}`);
105 setGenerating(false);
106 }, [hasNetworkKeys, seeds, resolvedPoint, networkRevision]);
108 const filename = useMemo(() => {
109 return `${stripSigPrefix(resolvedPoint.patp)}-${networkRevision}.key`;
110 }, [resolvedPoint, networkRevision]);
112 const download = useCallback(() => {
113 if (typeof keyfile !== 'string') {
114 return;
115 }
117 saveAs(
118 new Blob([keyfile], {
119 type: 'text/plain;charset=utf-8',
120 }),
121 filename
122 );
123 setDownloaded(true);
124 }, [filename, keyfile]);
126 useEffect(() => {
127 generate();
128 }, [generate]);
130 const values = {
131 generating,
132 available,
133 downloaded,
134 download,
135 filename,
136 notice,
137 keyfile,
138 code,
139 };
141 return { ...values, bind: values };
144interface ActivationKeyfileGeneratorArgs {
145 pointId: number;
146 wallets: UrbitWallet[];
149/**
150 * This Activation Keyfile Generator is used to derive a multikey during the
151 * Activation Flow. At the time of generation, there is no Point yet set in
152 * the Roller Store, so we need to look it up by Point ID (to retreive the
153 * current key revision via API).
154 *
155 * @param pointId number - (required) the point ID for which to generate keys
156 * @param wallets UrbitWallet[] - (required) for each wallet,
157 * a seed + revision pair is generated and consumed by `compileMultiKey`.
158 * It should be an array of [deterministic invite wallet, non-deterministic
159 * master ticket wallet] (the sending and receiving wallets)
160 */
161export const useActivationKeyfileGenerator = ({
162 pointId,
163 wallets,
164}: ActivationKeyfileGeneratorArgs) => {
165 // resolve point
166 const { api } = useRoller();
167 const [point, setPoint] = useState<Point>(EMPTY_POINT);
169 const resolvePoint = useCallback(async () => {
170 const resolved = new Point({
171 value: pointId,
172 details: toL1Details(await api.getPoint(pointId)),
173 address: ETH_ZERO_ADDR,
174 });
176 setPoint(resolved);
177 }, [api, pointId]);
179 useEffect(() => {
180 resolvePoint();
181 }, [resolvePoint]);
183 // derive seeds from wallets
184 const [seeds, setSeeds] = useState<string[]>([]);
185 const deriveSeeds = useCallback(async () => {
186 if (!('keyRevisionNumber' in point)) {
187 return;
188 }
190 const derivedSeeds = await Promise.all(
191 wallets.map(async (w, i) => {
192 return deriveNetworkSeedFromUrbitWallet(w, point.keyRevisionNumber + i);
193 })
194 );
196 setSeeds(
197 derivedSeeds
198 .map(maybeSeed => maybeSeed.getOrElse(null))
199 .filter(s => s !== null)
200 );
201 }, [point, wallets]);
203 useEffect(() => {
204 deriveSeeds();
205 // eslint-disable-next-line react-hooks/exhaustive-deps
206 }, [point]);
208 return useKeyfileGenerator({ point, seeds });
209};
211interface HomeKeyfileGeneratorArgs {
212 seed?: string;
215/**
216 * This Home Keyfile Generator is used to derive a multikey in multiple views
217 * in the Bridge dashboard (Set Network Keys, Home > ID, Home > OS views).
218 *
219 * * @param seed string - (optional) default: provided by the logged in wallet
220 */
221export const useHomeKeyfileGenerator = ({ seed }: HomeKeyfileGeneratorArgs) => {
222 // use provided seed, or fallback to deriving from logged in wallet
223 const { point } = useRollerStore();
224 const { authMnemonic, authToken, urbitWallet, wallet }: any = useWallet();
225 const [seeds, setSeeds] = useState<string[]>([]);
227 const deriveSeeds = useCallback(async () => {
228 if (seed) {
229 setSeeds([seed]);
230 return;
231 }
233 const derivedSeed = await attemptNetworkSeedDerivation({
234 urbitWallet,
235 wallet,
236 authMnemonic,
237 details: point,
238 authToken,
239 point: point.value,
240 revision: point.keyRevisionNumber,
241 });
243 if (Just.hasInstance(derivedSeed)) {
244 setSeeds([derivedSeed.value]);
245 }
246 }, [authMnemonic, authToken, point, seed, urbitWallet, wallet]);
248 useEffect(() => {
249 deriveSeeds();
250 }, [deriveSeeds]);
252 return useKeyfileGenerator({ seeds });
253};