@@ -1,49 +1,66 @@
11import { useCallback, useMemo, useEffect, useState } from 'react';
2−import { Just, Nothing } from 'folktale/maybe';
2+import { Just } from 'folktale/maybe';
33import saveAs from 'file-saver';
4−
54import { useWallet } from 'store/wallet';
6−import { useRollerStore } from 'store/rollerStore';
7−
5+import { EMPTY_POINT, useRollerStore } from 'store/rollerStore';
86import { generateCode } from 'lib/networkCode';
9−
107import {
118 attemptNetworkSeedDerivation,
9+ deriveNetworkSeedFromUrbitWallet,
1210 keysMatchChain,
1311 deriveNetworkKeys,
14− compileNetworkKey,
12+ compileMultiKey,
1513} from './keys';
1614import { stripSigPrefix } from 'form/formatters';
15+import useRoller from './useRoller';
16+import Point from './types/Point';
17+import { toL1Details } from './utils/point';
18+import { ETH_ZERO_ADDR } from './constants';
1719
18−// overrideable defaults:
19−// seed - derived from wallet, mnemonic, or token (if posisble)
20−// point - the current pointCursor
2120interface useKeyfileGeneratorArgs {
22− seed?: string;
23− point?: number;
21+ point?: Point;
22+ seeds: string[];
2423}
2524
2625/**
27− * @deprecated
28− * Use `useMultikeyFileGenerator` instead
26+ * The default Keyfile Generator. Can be used by itself, or composed with
27+ * other hooks (see `useActivationKeyfileGenerator` and
28+ * `useSingleKeyfileGenerator`).
29+ *
30+ * @param point Point - (optional) the Point for which to generate keys
31+ * @param seeds string[] - (required) one or more seeds consumed by deriveNetworkKeys
32+ *
2933 */
30−export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
31− const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
32− const { point } = useRollerStore();
33−
34+function useKeyfileGenerator({ point, seeds }: useKeyfileGeneratorArgs) {
35+ // exported keyfile generator properties
3436 const [notice, setNotice] = useState('Deriving networking keys...');
3537 const [downloaded, setDownloaded] = useState(false);
3638 const [generating, setGenerating] = useState(true);
3739 const [keyfile, setKeyfile] = useState<boolean | string>(false);
3840 const [code, setCode] = useState(false);
3941
40− const networkRevision = Number(point.keyRevisionNumber);
41− const canManage = point.isOwner || point.isManagementProxy;
42+ // resolved point
43+ const { point: storedPoint } = useRollerStore();
44+ const resolvedPoint = useMemo(() => {
45+ return point || storedPoint;
46+ }, [point, storedPoint]);
47+
48+ const networkRevision = useMemo(() => {
49+ return Number(resolvedPoint.keyRevisionNumber);
50+ }, [resolvedPoint]);
51+ const canManage = useMemo(() => {
52+ return resolvedPoint.isOwner || resolvedPoint.isManagementProxy;
53+ }, [resolvedPoint]);
4254
4355 const hasNetworkKeys = networkRevision > 0;
4456 const available = canManage && hasNetworkKeys && !!keyfile;
4557
4658 const generate = useCallback(async () => {
59+ // Point still loading ...
60+ if (resolvedPoint.value === EMPTY_POINT.value) {
61+ return;
62+ }
63+
4764 if (!hasNetworkKeys) {
4865 setGenerating(false);
4966 setNotice('Network keys not yet set.');
@@ -53,19 +70,9 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
5370 return;
5471 }
5572
56− const networkSeed = seed
57− ? Just(seed)
58− : await attemptNetworkSeedDerivation({
59− urbitWallet,
60− wallet,
61− authMnemonic,
62− details: point,
63− authToken,
64− point: point.value,
65− revision: networkRevision,
66− });
67−
68− if (Nothing.hasInstance(networkSeed)) {
73+ // seeds were either generated non-deterministically,
74+ // or still being derived ...
75+ if (seeds.length === 0) {
6976 setGenerating(false);
7077 setNotice(
7178 'Custom or nondeterministic networking keys cannot be re-downloaded.'
@@ -74,11 +81,10 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
7481 return;
7582 }
7683
77− const _networkSeed = networkSeed.value;
78−
79− const pair = deriveNetworkKeys(_networkSeed);
84+ const pairs = seeds.map(seed => deriveNetworkKeys(seed));
85+ const firstPair = pairs[0];
8086
81− if (!keysMatchChain(pair, point)) {
87+ if (!keysMatchChain(firstPair, resolvedPoint)) {
8288 setGenerating(false);
8389 setNotice('Derived networking keys do not match on-chain details.');
8490 console.log(`keys do not match details for revision ${networkRevision}`);
@@ -86,24 +92,21 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
8692 }
8793
8894 setNotice('');
89− setCode(generateCode(pair));
90− setKeyfile(compileNetworkKey(pair, point.value, networkRevision));
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+ );
91104 setGenerating(false);
92− }, [
93− networkRevision,
94− hasNetworkKeys,
95− seed,
96− urbitWallet,
97− wallet,
98− authMnemonic,
99− setCode,
100− point,
101− authToken,
102− ]);
105+ }, [hasNetworkKeys, seeds, resolvedPoint, networkRevision]);
103106
104107 const filename = useMemo(() => {
105− return `${stripSigPrefix(point.patp)}-${networkRevision}.key`;
106− }, [point, networkRevision]);
108+ return `${stripSigPrefix(resolvedPoint.patp)}-${networkRevision}.key`;
109+ }, [resolvedPoint, networkRevision]);
107110
108111 const download = useCallback(() => {
109112 if (typeof keyfile !== 'string') {
@@ -136,3 +139,117 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
136139
137140 return { ...values, bind: values };
138141}
142+
143+interface ActivationKeyfileGeneratorArgs {
144+ pointId: number;
145+ wallets: UrbitWallet[];
146+}
147+
148+/**
149+ * This Activation Keyfile Generator is used to derive a multikey during the
150+ * Activation Flow. At the time of generation, there is no Point yet set in
151+ * the Roller Store, so we need to look it up by Point ID (to retreive the
152+ * current key revision via API).
153+ *
154+ * @param pointId number - (required) the point ID for which to generate keys
155+ * @param wallets UrbitWallet[] - (required) for each wallet,
156+ * a seed + revision pair is generated and consumed by `compileMultiKey`.
157+ * It should be an array of [deterministic invite wallet, non-deterministic
158+ * master ticket wallet] (the sending and receiving wallets)
159+ */
160+export const useActivationKeyfileGenerator = ({
161+ pointId,
162+ wallets,
163+}: ActivationKeyfileGeneratorArgs) => {
164+ // resolve point
165+ const { api } = useRoller();
166+ const [point, setPoint] = useState<Point>(EMPTY_POINT);
167+
168+ const resolvePoint = useCallback(async () => {
169+ const resolved = new Point({
170+ value: pointId,
171+ details: toL1Details(await api.getPoint(pointId)),
172+ address: ETH_ZERO_ADDR,
173+ });
174+
175+ setPoint(resolved);
176+ }, [api, pointId]);
177+
178+ useEffect(() => {
179+ resolvePoint();
180+ }, [resolvePoint]);
181+
182+ // derive seeds from wallets
183+ const [seeds, setSeeds] = useState<string[]>([]);
184+ const deriveSeeds = useCallback(async () => {
185+ if (!('keyRevisionNumber' in point)) {
186+ return;
187+ }
188+
189+ const derivedSeeds = await Promise.all(
190+ wallets.map(async (w, i) => {
191+ return deriveNetworkSeedFromUrbitWallet(w, point.keyRevisionNumber + i);
192+ })
193+ );
194+
195+ setSeeds(
196+ derivedSeeds
197+ .map(maybeSeed => maybeSeed.getOrElse(null))
198+ .filter(s => s !== null)
199+ );
200+ }, [point, wallets]);
201+
202+ useEffect(() => {
203+ deriveSeeds();
204+ // eslint-disable-next-line react-hooks/exhaustive-deps
205+ }, [point]);
206+
207+ return useKeyfileGenerator({ point, seeds });
208+};
209+
210+interface SingleKeyfileGeneratorArgs {
211+ seed?: string;
212+}
213+
214+/**
215+ * This Single Keyfile Generator is used to derive network keys in multiple
216+ * views in the Bridge dashboard (Set Network Keys, Home > ID, Home > OS).
217+ * By default, it uses the logged in wallet's seed phrase.
218+ *
219+ * * @param seed string - (optional) default: provided by the logged in wallet
220+ */
221+export const useSingleKeyfileGenerator = ({
222+ seed,
223+}: SingleKeyfileGeneratorArgs) => {
224+ // use provided seed, or fallback to deriving from logged in wallet
225+ const { point } = useRollerStore();
226+ const { authMnemonic, authToken, urbitWallet, wallet }: any = useWallet();
227+ const [seeds, setSeeds] = useState<string[]>([]);
228+
229+ const deriveSeeds = useCallback(async () => {
230+ if (seed) {
231+ setSeeds([seed]);
232+ return;
233+ }
234+
235+ const derivedSeed = await attemptNetworkSeedDerivation({
236+ urbitWallet,
237+ wallet,
238+ authMnemonic,
239+ details: point,
240+ authToken,
241+ point: point.value,
242+ revision: point.keyRevisionNumber,
243+ });
244+
245+ if (Just.hasInstance(derivedSeed)) {
246+ setSeeds([derivedSeed.value]);
247+ }
248+ }, [authMnemonic, authToken, point, seed, urbitWallet, wallet]);
249+
250+ useEffect(() => {
251+ deriveSeeds();
252+ }, [deriveSeeds]);
253+
254+ return useKeyfileGenerator({ seeds });
255+};