on 1226972 help sign in
bridge/src/lib/useMultiKeyfileGenerator.ts
180 lines 5.0 kB history raw
1import saveAs from 'file-saver';
2import ob from 'urbit-ob';
3import { Nothing } from 'folktale/maybe';
5import { useCallback, useEffect, useMemo, useState } from 'react';
6import { deriveNetworkKeys } from 'urbit-key-generation';
7import {
8 compileMultiKey,
9 deriveNetworkSeedFromUrbitWallet,
10 keysMatchChain,
11} from './keys';
12import { generateCode } from './networkCode';
13import { stripSigPrefix } from 'form/formatters';
14import useRoller from './useRoller';
15import Point from './types/Point';
17interface useMultiKeyfileGeneratorArgs {
18 point: number;
19 inviteWallet: UrbitWallet;
20 inviteMasterTicketWallet: UrbitWallet;
23/**
24 * defaults:
25 * - point: the activating point
26 * - inviteWallet: determinstic invite wallet (generated during invite creation)
27 * - inviteMasterTicketWallet: the non-deterministic wallet (generated during invite acceptance);
28 * it is the wallet that receives the point transfer during Activation (and accessed by Master Ticket)
29 *
30 * This should only be used during the Activation flow,
31 * because the point will be "in between" key revisions.
32 * (0 --> 1 is when the invite is generated, 1 --> 2 is upon invite acceptance).
33 *
34 * For other keyfile use cases, see: useKeyfileGenerator
35 */
36export default function useMultiKeyfileGenerator({
37 point,
38 inviteWallet,
39 inviteMasterTicketWallet,
40}: useMultiKeyfileGeneratorArgs) {
41 const [notice, setNotice] = useState('Deriving networking keys...');
42 const [downloaded, setDownloaded] = useState(false);
43 const [generating, setGenerating] = useState(true);
44 const [keyfile, setKeyfile] = useState<boolean | string>(false);
45 const [code, setCode] = useState(false);
46 const [pointDetails, setPointDetails] = useState<Point | null>(null);
47 const { initPoint } = useRoller();
49 const fetchPoint = useCallback(async () => {
50 setPointDetails(await initPoint(point));
51 }, [initPoint, point]);
53 useEffect(() => {
54 fetchPoint();
55 }, [fetchPoint]);
57 const currentRevision = useMemo(
58 () => (pointDetails ? pointDetails.keyRevisionNumber : 0),
59 [pointDetails]
60 );
61 const nextRevision = useMemo(() => currentRevision + 1, [currentRevision]);
63 const pairFromRevision = useCallback(
64 async (revision: number, derivationWallet) => {
65 if (!pointDetails) {
66 return;
67 }
69 const networkSeed = await deriveNetworkSeedFromUrbitWallet({
70 urbitWallet: derivationWallet,
71 revision,
72 });
74 if (Nothing.hasInstance(networkSeed)) {
75 setGenerating(false);
76 setNotice(
77 'Custom or nondeterministic networking keys cannot be re-downloaded.'
78 );
79 console.log(`seed is nondeterminable for revision ${revision}`);
80 return;
81 }
83 const _networkSeed = networkSeed.value;
85 return deriveNetworkKeys(_networkSeed);
86 },
87 [pointDetails]
88 );
90 const hasNetworkKeys = currentRevision > 0;
92 const generate = useCallback(async () => {
93 if (!hasNetworkKeys) {
94 setGenerating(false);
95 setNotice('Network keys not yet set.');
96 console.log(
97 `no networking keys available for revision ${currentRevision}`
98 );
99 return;
100 }
102 const currentPair = await pairFromRevision(currentRevision, inviteWallet);
104 if (!currentPair || !keysMatchChain(currentPair, pointDetails || {})) {
105 setGenerating(false);
106 setNotice('Derived networking keys do not match on-chain details.');
107 console.log(`keys do not match details for revision ${currentRevision}`);
108 return;
109 }
111 // During the activation flow, we pass in the
112 // inviteMasterTicketWallet to derive the multikey
113 // During the activation flow, we want to use the invite wallet for the next pair
114 const nextPair = await pairFromRevision(
115 nextRevision,
116 inviteMasterTicketWallet
117 );
119 setNotice('');
120 setCode(generateCode(currentPair));
121 setKeyfile(
122 compileMultiKey(point, [
123 {
124 revision: currentRevision,
125 pair: currentPair,
126 },
127 {
128 revision: nextRevision,
129 pair: nextPair,
130 },
131 ])
132 );
133 setGenerating(false);
134 }, [
135 hasNetworkKeys,
136 pairFromRevision,
137 currentRevision,
138 inviteWallet,
139 pointDetails,
140 nextRevision,
141 inviteMasterTicketWallet,
142 point,
143 ]);
145 const filename = useMemo(() => {
146 return `${stripSigPrefix(
147 ob.patp(point)
148 )}-${currentRevision}-${nextRevision}.key`;
149 }, [point, currentRevision, nextRevision]);
151 const download = useCallback(() => {
152 if (typeof keyfile !== 'string') {
153 return;
154 }
156 saveAs(
157 new Blob([keyfile], {
158 type: 'text/plain;charset=utf-8',
159 }),
160 filename
161 );
162 setDownloaded(true);
163 }, [filename, keyfile]);
165 useEffect(() => {
166 generate();
167 }, [generate]);
169 const output = {
170 generating,
171 downloaded,
172 download,
173 filename,
174 notice,
175 keyfile,
176 code,
177 };
179 return { ...output, bind: output };