@@ -1,44 +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,
1412 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
1820interface useKeyfileGeneratorArgs {
19− seed?: string;
21+ point?: Point;
22+ seeds: string[];
2023}
2124
2225/**
23− * seed - (optional) derived from wallet, mnemonic, or token by default (if possible)
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+ *
2432 */
25−export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
26− const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
27− const { point } = useRollerStore();
28−
33+function useKeyfileGenerator({ point, seeds }: useKeyfileGeneratorArgs) {
34+ // exported keyfile generator properties
2935 const [notice, setNotice] = useState('Deriving networking keys...');
3036 const [downloaded, setDownloaded] = useState(false);
3137 const [generating, setGenerating] = useState(true);
3238 const [keyfile, setKeyfile] = useState<boolean | string>(false);
3339 const [code, setCode] = useState(false);
3440
35− const networkRevision = Number(point.keyRevisionNumber);
36− const canManage = point.isOwner || point.isManagementProxy;
41+ // resolved point
42+ const { point: storedPoint } = useRollerStore();
43+ const resolvedPoint = useMemo(() => {
44+ return point || storedPoint;
45+ }, [point, storedPoint]);
46+
47+ const networkRevision = useMemo(() => {
48+ return Number(resolvedPoint.keyRevisionNumber);
49+ }, [resolvedPoint]);
50+ const canManage = useMemo(() => {
51+ return resolvedPoint.isOwner || resolvedPoint.isManagementProxy;
52+ }, [resolvedPoint]);
3753
3854 const hasNetworkKeys = networkRevision > 0;
3955 const available = canManage && hasNetworkKeys && !!keyfile;
4056
4157 const generate = useCallback(async () => {
58+ // Point still loading ...
59+ if (resolvedPoint.value === EMPTY_POINT.value) {
60+ console.log('Loading point data ...');
61+ return;
62+ }
63+
4264 if (!hasNetworkKeys) {
4365 setGenerating(false);
4466 setNotice('Network keys not yet set.');
@@ -48,19 +70,9 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
4870 return;
4971 }
5072
51− const networkSeed = seed
52− ? Just(seed)
53− : await attemptNetworkSeedDerivation({
54− urbitWallet,
55− wallet,
56− authMnemonic,
57− details: point,
58− authToken,
59− point: point.value,
60− revision: networkRevision,
61− });
62−
63− if (Nothing.hasInstance(networkSeed)) {
73+ // seeds were either generated non-deterministically,
74+ // or still being derived ...
75+ if (seeds.length === 0) {
6476 setGenerating(false);
6577 setNotice(
6678 'Custom or nondeterministic networking keys cannot be re-downloaded.'
@@ -69,11 +81,10 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
6981 return;
7082 }
7183
72− const _networkSeed = networkSeed.value;
73−
74− const pair = deriveNetworkKeys(_networkSeed);
84+ const pairs = seeds.map(seed => deriveNetworkKeys(seed));
85+ const firstPair = pairs[0];
7586
76− if (!keysMatchChain(pair, point)) {
87+ if (!keysMatchChain(firstPair, resolvedPoint)) {
7788 setGenerating(false);
7889 setNotice('Derived networking keys do not match on-chain details.');
7990 console.log(`keys do not match details for revision ${networkRevision}`);
@@ -81,31 +92,22 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
8192 }
8293
8394 setNotice('');
84− setCode(generateCode(pair));
95+ setCode(generateCode(firstPair));
8596 setKeyfile(
86− compileMultiKey(point.value, [
87− {
88− revision: networkRevision,
89− pair: pair,
90− },
91− ])
97+ compileMultiKey(
98+ resolvedPoint.value,
99+ pairs.map((pair, k) => {
100+ return { revision: networkRevision + k, pair };
101+ })
102+ )
92103 );
104+ console.log(`keyfile generated for revision ${networkRevision}`);
93105 setGenerating(false);
94− }, [
95− networkRevision,
96− hasNetworkKeys,
97− seed,
98− urbitWallet,
99− wallet,
100− authMnemonic,
101− setCode,
102− point,
103− authToken,
104− ]);
106+ }, [hasNetworkKeys, seeds, resolvedPoint, networkRevision]);
105107
106108 const filename = useMemo(() => {
107− return `${stripSigPrefix(point.patp)}-${networkRevision}.key`;
108− }, [point, networkRevision]);
109+ return `${stripSigPrefix(resolvedPoint.patp)}-${networkRevision}.key`;
110+ }, [resolvedPoint, networkRevision]);
109111
110112 const download = useCallback(() => {
111113 if (typeof keyfile !== 'string') {
@@ -138,3 +140,114 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
138140
139141 return { ...values, bind: values };
140142}
143+
144+interface ActivationKeyfileGeneratorArgs {
145+ pointId: number;
146+ wallets: UrbitWallet[];
147+}
148+
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+ */
161+export const useActivationKeyfileGenerator = ({
162+ pointId,
163+ wallets,
164+}: ActivationKeyfileGeneratorArgs) => {
165+ // resolve point
166+ const { api } = useRoller();
167+ const [point, setPoint] = useState<Point>(EMPTY_POINT);
168+
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+ });
175+
176+ setPoint(resolved);
177+ }, [api, pointId]);
178+
179+ useEffect(() => {
180+ resolvePoint();
181+ }, [resolvePoint]);
182+
183+ // derive seeds from wallets
184+ const [seeds, setSeeds] = useState<string[]>([]);
185+ const deriveSeeds = useCallback(async () => {
186+ if (!('keyRevisionNumber' in point)) {
187+ return;
188+ }
189+
190+ const derivedSeeds = await Promise.all(
191+ wallets.map(async (w, i) => {
192+ return deriveNetworkSeedFromUrbitWallet(w, point.keyRevisionNumber + i);
193+ })
194+ );
195+
196+ setSeeds(
197+ derivedSeeds
198+ .map(maybeSeed => maybeSeed.getOrElse(null))
199+ .filter(s => s !== null)
200+ );
201+ }, [point, wallets]);
202+
203+ useEffect(() => {
204+ deriveSeeds();
205+ // eslint-disable-next-line react-hooks/exhaustive-deps
206+ }, [point]);
207+
208+ return useKeyfileGenerator({ point, seeds });
209+};
210+
211+interface HomeKeyfileGeneratorArgs {
212+ seed?: string;
213+}
214+
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+ */
221+export 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[]>([]);
226+
227+ const deriveSeeds = useCallback(async () => {
228+ if (seed) {
229+ setSeeds([seed]);
230+ return;
231+ }
232+
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+ });
242+
243+ if (Just.hasInstance(derivedSeed)) {
244+ setSeeds([derivedSeed.value]);
245+ }
246+ }, [authMnemonic, authToken, point, seed, urbitWallet, wallet]);
247+
248+ useEffect(() => {
249+ deriveSeeds();
250+ }, [deriveSeeds]);
251+
252+ return useKeyfileGenerator({ seeds });
253+};