on master help sign in
keys: use Multikey for Activate, Single in Home

A previous refactor unified all keyfile generation to a single hook, useMultiKeyfileGenerator. This turned out to cause issues as described in #895. So, this commit resolves #895 by restoring single keyfile generation for Home view. Tested on Ropsten, keyfiles generated in both Activation and Home views

commit1226972e63ea5952268e358d1e1f070c62f2104d authortomholford <tomholford@users.noreply.github.com> dateThu 3 Feb 2022, 21:43 (5 years ago) parent04bea4c
11 files changed+205−238
src/components/DownloadKeyfileButton.js+1−1 src/lib/useKeyfileGenerator.ts+10−8 src/lib/useMultiKeyfileGenerator.ts+180 src/lib/useMultikeyFileGenerator.ts−214 src/views/Activate/MasterKeyDownload.tsx+3−3 src/views/Activate/useActivateFlowState.js+1−2 src/views/UrbitID/DownloadKeys.js+2−2 src/views/UrbitID/Home.tsx+2−2 src/views/UrbitOS/AccessCode.test.tsx+2−2 src/views/UrbitOS/Home.tsx+2−2 src/views/UrbitOS/NetworkKeys.tsx+2−2
src/components/DownloadKeyfileButton.js+1−1
@@ -4,7 +4,7 @@ import { B } from 'indigo-react';
44import { DownloadButton } from 'components/Buttons';
55
66export default function DownloadKeyfileButton({
7− // useMultikeyFileGenerator.bind
7+ // useKeyfileGenerator.bind
88 generating,
99 available,
1010 downloaded,
src/lib/useKeyfileGenerator.ts+10−8
@@ -11,21 +11,16 @@ import {
1111 attemptNetworkSeedDerivation,
1212 keysMatchChain,
1313 deriveNetworkKeys,
14− compileNetworkKey,
14+ compileMultiKey,
1515} from './keys';
1616import { stripSigPrefix } from 'form/formatters';
1717
18−// overrideable defaults:
19−// seed - derived from wallet, mnemonic, or token (if posisble)
20−// point - the current pointCursor
2118interface useKeyfileGeneratorArgs {
2219 seed?: string;
23− point?: number;
2420}
2521
2622/**
27− * @deprecated
28− * Use `useMultikeyFileGenerator` instead
23+ * seed - (optional) derived from wallet, mnemonic, or token by default (if possible)
2924 */
3025export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
3126 const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
@@ -87,7 +82,14 @@ export default function useKeyfileGenerator({ seed }: useKeyfileGeneratorArgs) {
8782
8883 setNotice('');
8984 setCode(generateCode(pair));
90− setKeyfile(compileNetworkKey(pair, point.value, networkRevision));
85+ setKeyfile(
86+ compileMultiKey(point.value, [
87+ {
88+ revision: networkRevision,
89+ pair: pair,
90+ },
91+ ])
92+ );
9193 setGenerating(false);
9294 }, [
9395 networkRevision,
src/lib/useMultiKeyfileGenerator.tsnew file+180
@@ -0,0 +1,180 @@
1+import saveAs from 'file-saver';
2+import ob from 'urbit-ob';
3+import { Nothing } from 'folktale/maybe';
4+
5+import { useCallback, useEffect, useMemo, useState } from 'react';
6+import { deriveNetworkKeys } from 'urbit-key-generation';
7+import {
8+ compileMultiKey,
9+ deriveNetworkSeedFromUrbitWallet,
10+ keysMatchChain,
11+} from './keys';
12+import { generateCode } from './networkCode';
13+import { stripSigPrefix } from 'form/formatters';
14+import useRoller from './useRoller';
15+import Point from './types/Point';
16+
17+interface useMultiKeyfileGeneratorArgs {
18+ point: number;
19+ inviteWallet: UrbitWallet;
20+ inviteMasterTicketWallet: UrbitWallet;
21+}
22+
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+ */
36+export 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();
48+
49+ const fetchPoint = useCallback(async () => {
50+ setPointDetails(await initPoint(point));
51+ }, [initPoint, point]);
52+
53+ useEffect(() => {
54+ fetchPoint();
55+ }, [fetchPoint]);
56+
57+ const currentRevision = useMemo(
58+ () => (pointDetails ? pointDetails.keyRevisionNumber : 0),
59+ [pointDetails]
60+ );
61+ const nextRevision = useMemo(() => currentRevision + 1, [currentRevision]);
62+
63+ const pairFromRevision = useCallback(
64+ async (revision: number, derivationWallet) => {
65+ if (!pointDetails) {
66+ return;
67+ }
68+
69+ const networkSeed = await deriveNetworkSeedFromUrbitWallet({
70+ urbitWallet: derivationWallet,
71+ revision,
72+ });
73+
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+ }
82+
83+ const _networkSeed = networkSeed.value;
84+
85+ return deriveNetworkKeys(_networkSeed);
86+ },
87+ [pointDetails]
88+ );
89+
90+ const hasNetworkKeys = currentRevision > 0;
91+
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+ }
101+
102+ const currentPair = await pairFromRevision(currentRevision, inviteWallet);
103+
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+ }
110+
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+ );
118+
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+ ]);
144+
145+ const filename = useMemo(() => {
146+ return `${stripSigPrefix(
147+ ob.patp(point)
148+ )}-${currentRevision}-${nextRevision}.key`;
149+ }, [point, currentRevision, nextRevision]);
150+
151+ const download = useCallback(() => {
152+ if (typeof keyfile !== 'string') {
153+ return;
154+ }
155+
156+ saveAs(
157+ new Blob([keyfile], {
158+ type: 'text/plain;charset=utf-8',
159+ }),
160+ filename
161+ );
162+ setDownloaded(true);
163+ }, [filename, keyfile]);
164+
165+ useEffect(() => {
166+ generate();
167+ }, [generate]);
168+
169+ const output = {
170+ generating,
171+ downloaded,
172+ download,
173+ filename,
174+ notice,
175+ keyfile,
176+ code,
177+ };
178+
179+ return { ...output, bind: output };
180+}
src/lib/useMultikeyFileGenerator.tsdeleted−214
@@ -1,214 +0,0 @@
1−import * as need from 'lib/need';
2−import saveAs from 'file-saver';
3−import ob from 'urbit-ob';
4−import { Just, Nothing } from 'folktale/maybe';
5−
6−import { useCallback, useEffect, useMemo, useState } from 'react';
7−import { usePointCache } from 'store/pointCache';
8−import { usePointCursor } from 'store/pointCursor';
9−import { useWallet } from 'store/wallet';
10−import { deriveNetworkKeys } from 'urbit-key-generation';
11−import { convertToInt } from './convertToInt';
12−import {
13− attemptNetworkSeedDerivation,
14− compileMultiKey,
15− keysMatchChain,
16−} from './keys';
17−import { generateCode } from './networkCode';
18−import { L1Point } from './types/L1Point';
19−import useCurrentPermissions from './useCurrentPermissions';
20−import { stripSigPrefix } from 'form/formatters';
21−import { useRollerStore } from 'store/rollerStore';
22−
23−interface useMultikeyFileGeneratorArgs {
24− seed?: string;
25− point?: number;
26− seedWallet?: any;
27− inviteMasterTicketWallet?: any;
28−}
29−
30−/**
31− * defaults:
32− * - seed: derived from wallet, mnemonic, or token (if posisble)
33− * - point: the current pointCursor
34− */
35−export 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();
46−
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);
52−
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− );
68−
69− const currentRevision: number = useMemo(
70− () => convertToInt(_details?.keyRevisionNumber || 0, 10),
71− [_details]
72− );
73− const nextRevision = useMemo(() => currentRevision + 1, [currentRevision]);
74−
75− const pairFromRevision = useCallback(
76− async (revision: number, useInviteMasterTicketWallet = false) => {
77− if (!_details) {
78− return;
79− }
80−
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;
87−
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− });
99−
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− }
108−
109− const _networkSeed = networkSeed.value;
110−
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− );
125−
126− const hasNetworkKeys = currentRevision > 0;
127− const available =
128− (isOwner || isManagementProxy) && hasNetworkKeys && !!keyfile;
129−
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− }
139−
140− const currentPair = await pairFromRevision(currentRevision);
141−
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− }
148−
149− // During the activation flow, we want to use the invite wallet for the next pair
150− const nextPair = await pairFromRevision(nextRevision, true);
151−
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− ]);
175−
176− const filename = useMemo(() => {
177− return `${stripSigPrefix(ob.patp(_point))}-${currentRevision}.key`;
178− }, [_point, currentRevision]);
179−
180− const download = useCallback(() => {
181− if (typeof keyfile !== 'string') {
182− return;
183− }
184−
185− saveAs(
186− new Blob([keyfile], {
187− type: 'text/plain;charset=utf-8',
188− }),
189− filename
190− );
191− setDownloaded(true);
192− }, [filename, keyfile]);
193−
194− useEffect(() => {
195− syncDetails(_point);
196− }, [_point, syncDetails]);
197−
198− useEffect(() => {
199− generate();
200− }, [generate]);
201−
202− const output = {
203− generating,
204− available,
205− downloaded,
206− download,
207− filename,
208− notice,
209− keyfile,
210− code,
211− };
212−
213− return { ...output, bind: output };
214−}
src/views/Activate/MasterKeyDownload.tsx+3−3
@@ -16,9 +16,9 @@ import { useLocalRouter } from 'lib/LocalRouter';
1616import PaperBuilder from 'components/PaperBuilder';
1717import { DEFAULT_FADE_TIMEOUT, MASTER_KEY_DURATION } from 'lib/constants';
1818import { timeout } from 'lib/timeout';
19−import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
2019import { downloadWallet } from 'lib/invite';
2120import View from 'components/View';
21+import useMultiKeyfileGenerator from 'lib/useMultiKeyfileGenerator';
2222
2323const MasterKeyDownload = () => {
2424 const {
@@ -46,9 +46,9 @@ const MasterKeyDownload = () => {
4646 Just: (p: any) => p.value.toFixed(),
4747 });
4848
49− const { keyfile, filename } = useMultikeyFileGenerator({
49+ const { keyfile, filename } = useMultiKeyfileGenerator({
5050 point,
51− seedWallet: inviteWallet,
51+ inviteWallet,
5252 inviteMasterTicketWallet,
5353 });
5454
src/views/Activate/useActivateFlowState.js+1−2
@@ -17,14 +17,13 @@ export default function useActivateFlowState() {
1717 // it is for this wallet
1818 // type UrbitWallet
1919 const [inviteMasterTicketWallet, setInviteMasterTicketWallet] = useState(Nothing());
20− // The wallet generated deterministically during the invite flow;
20+ // The wallet generated deterministically during the invite creation flow;
2121 // it is re-generated when the user starts the Activate flow. It is used
2222 // to derive and confirm a shareable ticket that a user can activate a point with.
2323 // type UrbitWallet
2424 const [inviteWallet, setInviteWallet] = useState(Nothing());
2525 // This wallet is generated from the invite wallet's seed phrase.
2626 // It is used during the transfer phase of the invite flow to send transactions.
27− // useMultiKeyFileGenerator.
2827 // type bip32.BIP32Interface
2928 const [sendWallet, setSendWallet] = useState(Nothing());
3029
src/views/UrbitID/DownloadKeys.js+2−2
@@ -9,7 +9,7 @@ import { DownloadButton, RestartButton } from 'components/Buttons';
99import PaperBuilder from 'components/PaperBuilder';
1010
1111import { downloadWallet } from 'lib/invite';
12−import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
12+import useKeyfileGenerator from 'lib/useKeyfileGenerator';
1313
1414import { useWallet } from 'store/wallet';
1515import { Grid, P } from 'indigo-react';
@@ -26,7 +26,7 @@ export default function AdminRedownload() {
2626 const _urbitWallet = need.wallet(urbitWallet);
2727 const point = need.point(pointCursor);
2828
29− const { keyfile, filename } = useMultikeyFileGenerator({});
29+ const { keyfile, filename } = useKeyfileGenerator({});
3030
3131 const [paper, setPaper] = useState(Nothing());
3232 const [downloaded, setDownloaded] = useState(false);
src/views/UrbitID/Home.tsx+2−2
@@ -13,7 +13,7 @@ import {
1313} from 'lib/constants';
1414import { abbreviateAddress } from 'lib/utils/address';
1515import { downloadWallet } from 'lib/invite';
16−import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
16+import useKeyfileGenerator from 'lib/useKeyfileGenerator';
1717
1818import { useWallet } from 'store/wallet';
1919import { useRollerStore } from 'store/rollerStore';
@@ -43,7 +43,7 @@ export default function UrbitIDHome() {
4343 return null;
4444 }
4545 }, [urbitWallet]);
46− const { keyfile, filename } = useMultikeyFileGenerator({});
46+ const { keyfile, filename } = useKeyfileGenerator({});
4747 const [paper, setPaper] = useState(Nothing());
4848
4949 const downloadKeys = useCallback(() => {
src/views/UrbitOS/AccessCode.test.tsx+2−2
@@ -4,7 +4,7 @@ import AccessCode from './AccessCode';
44
55describe('AccessCode', () => {
66 describe('when code is not available', () => {
7− // Value is initialized as `false` in `useMultikeyFileGenerator`
7+ // Value is initialized as `false` in `useKeyfileGenerator`
88 const testCode = false;
99
1010 it('does not render the component', () => {
@@ -15,7 +15,7 @@ describe('AccessCode', () => {
1515 });
1616
1717 describe('when code is available', () => {
18− // Value has been loaded from `useMultikeyFileGenerator`
18+ // Value has been loaded from `useKeyfileGenerator`
1919 const testCode = 'foo-bar';
2020
2121 it('renders the component', () => {
src/views/UrbitOS/Home.tsx+2−2
@@ -9,7 +9,7 @@ import BodyPane from 'components/L2/Window/BodyPane';
99import { ReactComponent as KeyfileIcon } from 'assets/keyfile.svg';
1010
1111import { useLocalRouter } from 'lib/LocalRouter';
12−import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
12+import useKeyfileGenerator from 'lib/useKeyfileGenerator';
1313import { Box, Button, Row } from '@tlon/indigo-react';
1414import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
1515
@@ -42,7 +42,7 @@ export default function UrbitOSHome() {
4242 names,
4343 ]);
4444
45− const { code, download } = useMultikeyFileGenerator({});
45+ const { code, download } = useKeyfileGenerator({});
4646
4747 return (
4848 <Window className="os-home">
src/views/UrbitOS/NetworkKeys.tsx+2−2
@@ -27,7 +27,7 @@ import {
2727import useEthereumTransaction from 'lib/useEthereumTransaction';
2828import { GAS_LIMITS, ONE_SECOND } from 'lib/constants';
2929import { addHexPrefix } from 'lib/utils/address';
30−import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
30+import useKeyfileGenerator from 'lib/useKeyfileGenerator';
3131
3232import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
3333import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
@@ -66,7 +66,7 @@ function useSetKeys(
6666 generating: keyfileGenerating,
6767 filename,
6868 bind: keyfileBind,
69− } = useMultikeyFileGenerator({ seed: manualNetworkSeed });
69+ } = useKeyfileGenerator({ seed: manualNetworkSeed });
7070
7171 const buildNetworkSeed = useCallback(
7272 async manualSeed => {