on master help sign in
network-keys: useMultikeyFileGenerator
87fa1a8tomholford
src/components/DownloadKeyfileButton.js+1−1
@@ -4,7 +4,7 @@ import { B } from 'indigo-react';
4import { DownloadButton } from 'components/Buttons';
4import { DownloadButton } from 'components/Buttons';
5
5
6export default function DownloadKeyfileButton({
6export default function DownloadKeyfileButton({
7− // useKeyfileGenerator.bind
7+ // useMultikeyFileGenerator.bind
8 generating,
8 generating,
9 available,
9 available,
10 downloaded,
10 downloaded,
src/lib/keys.js+29−2
@@ -198,8 +198,8 @@ export const attemptNetworkSeedDerivation = async ({
198
198
199/**
199/**
200 *
200 *
201− * @param {object} pair
201+ * @param {object} pair - type NetworkKeys
202− * @param {object} details
202+ * @param {object} details - type L1Point
203 * @return {boolean}
203 * @return {boolean}
204 */
204 */
205export const keysMatchChain = (pair, details) => {
205export const keysMatchChain = (pair, details) => {
@@ -222,3 +222,30 @@ export const segmentNetworkKey = hex => {
222
222
223 return [rowFrom(2), rowFrom(18), rowFrom(34), rowFrom(50)];
223 return [rowFrom(2), rowFrom(18), rowFrom(34), rowFrom(50)];
224};
224};
225+
226+// ctsy @yosoyubik
227+/**
228+ * @param {object} pair
229+ * @param {number} point
230+ * @param {number} revision
231+ * @return {string}
232+ */
233+export const compileMultikey = (point, pair1, pair2, revision1, revision2) => {
234+ const bnsec1 = new BN(createRing(pair1), 'hex');
235+ const bnsec2 = new BN(createRing(pair2), 'hex');
236+
237+ const tag = noun.dwim(noun.Atom.fromString('1'), noun.Atom.yes);
238+ const ship = noun.Atom.fromInt(point);
239+ const key1 = noun.dwim(
240+ noun.Atom.fromInt(revision1),
241+ noun.Atom.fromString(bnsec1.toString())
242+ );
243+ const key2 = noun.dwim(
244+ noun.Atom.fromInt(revision2),
245+ noun.Atom.fromString(bnsec2.toString())
246+ );
247+
248+ const sed = noun.dwim(tag, ship, key1, key2, noun.Atom.yes);
249+
250+ return b64(jam(sed));
251+};
src/lib/types/Keypair.tsnew file+7
@@ -0,0 +1,7 @@
1+// returned from urbit/key-generation#deriveNodes
2+export interface Keypair {
3+ public: string;
4+ private: string;
5+ chain: string;
6+ address: string;
7+}
src/lib/types/NetworkKeys.tsnew file+11
@@ -0,0 +1,11 @@
1+// returned from urbit/key-generation#deriveNetworkKeys
2+export interface NetworkKeys {
3+ crypt: {
4+ private: string;
5+ public: string;
6+ };
7+ auth: {
8+ private: string;
9+ public: string;
10+ };
11+}
src/lib/useMultikeyFileGenerator.tsnew file+201
@@ -0,0 +1,201 @@
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+
22+interface useMultikeyFileGeneratorArgs {
23+ seed?: string;
24+ point?: number;
25+ seedWallet?: any;
26+}
27+
28+/**
29+ * defaults:
30+ * - seed: derived from wallet, mnemonic, or token (if posisble)
31+ * - point: the current pointCursor
32+ */
33+export default function useMultikeyFileGenerator({
34+ seed,
35+ point,
36+ seedWallet,
37+}: useMultikeyFileGeneratorArgs) {
38+ const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
39+ const { pointCursor }: any = usePointCursor();
40+ const { syncDetails, getDetails }: any = usePointCache();
41+ const { isOwner, isManagementProxy } = useCurrentPermissions();
42+
43+ const [notice, setNotice] = useState('Deriving networking keys...');
44+ const [downloaded, setDownloaded] = useState(false);
45+ const [generating, setGenerating] = useState(true);
46+ const [keyfile, setKeyfile] = useState<boolean | string>(false);
47+ const [code, setCode] = useState(false);
48+
49+ const _point = useMemo(() => point || need.point(pointCursor), [
50+ point,
51+ pointCursor,
52+ ]);
53+ const details = getDetails(_point);
54+ const _details: L1Point | null = useMemo(
55+ () =>
56+ details.matchWith({
57+ Just: d => need.details(d),
58+ Nothing: () => null,
59+ }),
60+ [details]
61+ );
62+
63+ const currentRevision: number = useMemo(
64+ () =>
65+ details.matchWith({
66+ Just: ({ value }) => convertToInt(value.keyRevisionNumber, 10),
67+ Nothing: () => 0,
68+ }),
69+ [details]
70+ );
71+ const nextRevision = useMemo(() => currentRevision + 1, [currentRevision]);
72+
73+ const pairFromRevision = useCallback(
74+ async (revision: number) => {
75+ if (!_details) {
76+ return;
77+ }
78+
79+ const networkSeed = seed
80+ ? Just(seed)
81+ : await attemptNetworkSeedDerivation({
82+ urbitWallet: seedWallet || urbitWallet,
83+ wallet,
84+ authMnemonic,
85+ details: _details,
86+ authToken,
87+ point: _point,
88+ revision: revision,
89+ });
90+
91+ if (Nothing.hasInstance(networkSeed)) {
92+ setGenerating(false);
93+ setNotice(
94+ 'Custom or nondeterministic networking keys cannot be re-downloaded.'
95+ );
96+ console.log(`seed is nondeterminable for revision ${revision}`);
97+ return;
98+ }
99+
100+ const _networkSeed = networkSeed.value;
101+
102+ return deriveNetworkKeys(_networkSeed);
103+ },
104+ [
105+ _details,
106+ _point,
107+ authMnemonic,
108+ authToken,
109+ seed,
110+ urbitWallet,
111+ wallet,
112+ seedWallet,
113+ ]
114+ );
115+
116+ const hasNetworkKeys = currentRevision > 0;
117+ const available =
118+ (isOwner || isManagementProxy) && hasNetworkKeys && !!keyfile;
119+
120+ const generate = useCallback(async () => {
121+ if (!hasNetworkKeys) {
122+ setGenerating(false);
123+ setNotice('Network keys not yet set.');
124+ console.log(
125+ `no networking keys available for revision ${currentRevision}`
126+ );
127+ return;
128+ }
129+
130+ // TODO: Do we still need this? The legacy implementation in useKeyfileGenerator
131+ // performed this guard statement
132+
133+ // const currentPair = await pairFromRevision(currentRevision);
134+
135+ // if (!currentPair || !keysMatchChain(currentPair, _details)) {
136+ // setGenerating(false);
137+ // setNotice('Derived networking keys do not match on-chain details.');
138+ // console.log(`keys do not match details for revision ${currentRevision}`);
139+ // return;
140+ // }
141+
142+ // const nextPair = await pairFromRevision(nextRevision);
143+
144+ const [currentPair, nextPair] = await Promise.all([
145+ pairFromRevision(currentRevision),
146+ pairFromRevision(nextRevision),
147+ ]);
148+
149+ setNotice('');
150+ setCode(generateCode(currentPair));
151+ setKeyfile(
152+ compileMultikey(
153+ _point,
154+ currentPair,
155+ nextPair,
156+ currentRevision,
157+ nextRevision
158+ )
159+ );
160+ setGenerating(false);
161+ }, [_point, currentRevision, hasNetworkKeys, nextRevision, pairFromRevision]);
162+
163+ const filename = useMemo(() => {
164+ return `${stripSigPrefix(ob.patp(_point))}-${currentRevision}.key`;
165+ }, [_point, currentRevision]);
166+
167+ const download = useCallback(() => {
168+ if (typeof keyfile !== 'string') {
169+ return;
170+ }
171+
172+ saveAs(
173+ new Blob([keyfile], {
174+ type: 'text/plain;charset=utf-8',
175+ }),
176+ filename
177+ );
178+ setDownloaded(true);
179+ }, [filename, keyfile]);
180+
181+ useEffect(() => {
182+ syncDetails(_point);
183+ }, [_point, syncDetails]);
184+
185+ useEffect(() => {
186+ generate();
187+ }, [generate]);
188+
189+ const output = {
190+ generating,
191+ available,
192+ downloaded,
193+ download,
194+ filename,
195+ notice,
196+ keyfile,
197+ code,
198+ };
199+
200+ return { ...output, bind: output };
201+}
src/urbit-key-generation.d.tsnew file+14
@@ -0,0 +1,14 @@
1+interface NetworkKeys {
2+ crypt: {
3+ private: string;
4+ public: string;
5+ };
6+ auth: {
7+ private: string;
8+ public: string;
9+ };
10+}
11+
12+declare module 'urbit-key-generation' {
13+ function deriveNetworkKeys(hex: string): NetworkKeys;
14+}
src/urbit-ob.d.ts−1
@@ -1,2 +1 @@
1−// azimuth-js.d.ts
2declare module 'urbit-ob';
1declare module 'urbit-ob';
src/views/Activate/MasterKeyDownload.tsx+5−2
@@ -18,7 +18,7 @@ import PaperBuilder from 'components/PaperBuilder';
18import { DEFAULT_FADE_TIMEOUT, MASTER_KEY_DURATION } from 'lib/constants';
18import { DEFAULT_FADE_TIMEOUT, MASTER_KEY_DURATION } from 'lib/constants';
19import { timeout } from 'lib/timeout';
19import { timeout } from 'lib/timeout';
20import View from 'components/View';
20import View from 'components/View';
21−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
21+import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
22
22
23const MasterKeyDownload = () => {
23const MasterKeyDownload = () => {
24 const {
24 const {
@@ -45,7 +45,10 @@ const MasterKeyDownload = () => {
45 Just: p => p.value.toFixed(),
45 Just: p => p.value.toFixed(),
46 });
46 });
47
47
48− const { download } = useKeyfileGenerator({ point });
48+ const { download } = useMultikeyFileGenerator({
49+ point,
50+ seedWallet: derivedWallet,
51+ });
49
52
50 // sync paper value to activation state
53 // sync paper value to activation state
51 useEffect(
54 useEffect(
src/views/UrbitID/DownloadKeys.js+2−2
@@ -9,7 +9,7 @@ import { DownloadButton, RestartButton } from 'components/Buttons';
9import PaperBuilder from 'components/PaperBuilder';
9import PaperBuilder from 'components/PaperBuilder';
10
10
11import { downloadWallet } from 'lib/invite';
11import { downloadWallet } from 'lib/invite';
12−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
12+import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
13
13
14import { useWallet } from 'store/wallet';
14import { useWallet } from 'store/wallet';
15import { Grid, P } from 'indigo-react';
15import { Grid, P } from 'indigo-react';
@@ -26,7 +26,7 @@ export default function AdminRedownload() {
26 const _urbitWallet = need.wallet(urbitWallet);
26 const _urbitWallet = need.wallet(urbitWallet);
27 const point = need.point(pointCursor);
27 const point = need.point(pointCursor);
28
28
29− const { keyfile, filename } = useKeyfileGenerator({});
29+ const { keyfile, filename } = useMultikeyFileGenerator({});
30
30
31 const [paper, setPaper] = useState(Nothing());
31 const [paper, setPaper] = useState(Nothing());
32 const [downloaded, setDownloaded] = useState(false);
32 const [downloaded, setDownloaded] = useState(false);
src/views/UrbitID/Home.tsx+2−2
@@ -13,7 +13,7 @@ import {
13} from 'lib/constants';
13} from 'lib/constants';
14import { abbreviateAddress } from 'lib/utils/address';
14import { abbreviateAddress } from 'lib/utils/address';
15import { downloadWallet } from 'lib/invite';
15import { downloadWallet } from 'lib/invite';
16−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
16+import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
17
17
18import { useWallet } from 'store/wallet';
18import { useWallet } from 'store/wallet';
19import { useRollerStore } from 'store/rollerStore';
19import { useRollerStore } from 'store/rollerStore';
@@ -43,7 +43,7 @@ export default function UrbitIDHome() {
43 return null;
43 return null;
44 }
44 }
45 }, [urbitWallet]);
45 }, [urbitWallet]);
46− const { keyfile, filename } = useKeyfileGenerator({});
46+ const { keyfile, filename } = useMultikeyFileGenerator({});
47 const [paper, setPaper] = useState(Nothing());
47 const [paper, setPaper] = useState(Nothing());
48
48
49 const downloadKeys = useCallback(() => {
49 const downloadKeys = useCallback(() => {
src/views/UrbitOS/AccessCode.test.tsx+2−2
@@ -4,7 +4,7 @@ import AccessCode from './AccessCode';
4
4
5describe('AccessCode', () => {
5describe('AccessCode', () => {
6 describe('when code is not available', () => {
6 describe('when code is not available', () => {
7− // Value is initialized as `false` in `useKeyfileGenerator`
7+ // Value is initialized as `false` in `useMultikeyFileGenerator`
8 const testCode = false;
8 const testCode = false;
9
9
10 it('does not render the component', () => {
10 it('does not render the component', () => {
@@ -15,7 +15,7 @@ describe('AccessCode', () => {
15 });
15 });
16
16
17 describe('when code is available', () => {
17 describe('when code is available', () => {
18− // Value has been loaded from `useKeyfileGenerator`
18+ // Value has been loaded from `useMultikeyFileGenerator`
19 const testCode = 'foo-bar';
19 const testCode = 'foo-bar';
20
20
21 it('renders the component', () => {
21 it('renders the component', () => {
src/views/UrbitOS/Home.tsx+3−3
@@ -9,7 +9,7 @@ import BodyPane from 'components/L2/Window/BodyPane';
9import { ReactComponent as KeyfileIcon } from 'assets/keyfile.svg';
9import { ReactComponent as KeyfileIcon } from 'assets/keyfile.svg';
10
10
11import { useLocalRouter } from 'lib/LocalRouter';
11import { useLocalRouter } from 'lib/LocalRouter';
12−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
12+import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
13import { Box, Button, Row } from '@tlon/indigo-react';
13import { Box, Button, Row } from '@tlon/indigo-react';
14import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
14import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
15
15
@@ -42,7 +42,7 @@ export default function UrbitOSHome() {
42 names,
42 names,
43 ]);
43 ]);
44
44
45− const { bind: keyBind, code } = useKeyfileGenerator({});
45+ const { code, download } = useMultikeyFileGenerator({});
46
46
47 return (
47 return (
48 <Window className="os-home">
48 <Window className="os-home">
@@ -53,7 +53,7 @@ export default function UrbitOSHome() {
53 <Button
53 <Button
54 className="header-button keyfile"
54 className="header-button keyfile"
55 disabled={!hasSetNetworkKeys}
55 disabled={!hasSetNetworkKeys}
56− onClick={keyBind.download}>
56+ onClick={download}>
57 <KeyfileIcon />
57 <KeyfileIcon />
58 Download Keyfile
58 Download Keyfile
59 </Button>
59 </Button>
src/views/UrbitOS/NetworkKeys.tsx+2−2
@@ -28,7 +28,7 @@ import {
28import useEthereumTransaction from 'lib/useEthereumTransaction';
28import useEthereumTransaction from 'lib/useEthereumTransaction';
29import { GAS_LIMITS } from 'lib/constants';
29import { GAS_LIMITS } from 'lib/constants';
30import { addHexPrefix } from 'lib/utils/address';
30import { addHexPrefix } from 'lib/utils/address';
31−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
31+import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
32
32
33import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
33import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
34import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
34import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
@@ -72,7 +72,7 @@ function useSetKeys(
72 generating: keyfileGenerating,
72 generating: keyfileGenerating,
73 filename,
73 filename,
74 bind: keyfileBind,
74 bind: keyfileBind,
75− } = useKeyfileGenerator({ seed: manualNetworkSeed });
75+ } = useMultikeyFileGenerator({ seed: manualNetworkSeed });
76
76
77 const buildNetworkSeed = useCallback(
77 const buildNetworkSeed = useCallback(
78 async manualSeed => {
78 async manualSeed => {