on master help sign in
Merge pull request #849 from urbit/t/activate-key-generation
eb4d5a9tomholford
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+27−3
@@ -76,12 +76,36 @@ export const compileNetworkKey = (pair, point, revision) => {
76 noun.Atom.fromInt(point),
76 noun.Atom.fromInt(point),
77 noun.Atom.fromInt(revision),
77 noun.Atom.fromInt(revision),
78 noun.Atom.fromString(bnsec.toString()),
78 noun.Atom.fromString(bnsec.toString()),
79− noun.Atom.yes
79+ noun.Atom.fromInt(0)
80 );
80 );
81
81
82 return b64(jam(sed));
82 return b64(jam(sed));
83};
83};
84
84
85+/**
86+ * @param {number} point
87+ * @param {array<{revision: number, pair: object}>} keys
88+ * @return {string}
89+ */
90+export const compileMultiKey = (point, keys) => {
91+ const kyz = keys.map(k => {
92+ const bnsec = new BN(createRing(k.pair), 'hex');
93+ return noun.dwim(
94+ noun.Atom.fromInt(k.revision),
95+ noun.Atom.fromString(bnsec.toString())
96+ );
97+ });
98+ kyz.push(noun.Atom.fromInt(0));
99+
100+ const fed = noun.dwim(
101+ noun.dwim(noun.Atom.fromInt(1), noun.Atom.fromInt(0)), // version
102+ noun.Atom.fromInt(point), // ship
103+ noun.dwim(kyz) // keys
104+ );
105+
106+ return b64(jam(fed));
107+};
108+
85/**
109/**
86 * @param {object} urbitWallet
110 * @param {object} urbitWallet
87 * @param {number} revision
111 * @param {number} revision
@@ -198,8 +222,8 @@ export const attemptNetworkSeedDerivation = async ({
198
222
199/**
223/**
200 *
224 *
201− * @param {object} pair
225+ * @param {object} pair - type NetworkKeys
202− * @param {object} details
226+ * @param {object} details - type L1Point
203 * @return {boolean}
227 * @return {boolean}
204 */
228 */
205export const keysMatchChain = (pair, details) => {
229export const keysMatchChain = (pair, details) => {
src/lib/types/BridgeWallet.ts+1
@@ -2,4 +2,5 @@ import { BIP32Interface } from 'bitcoinjs-lib';
2
2
3export default interface BridgeWallet extends BIP32Interface {
3export default interface BridgeWallet extends BIP32Interface {
4 address: string;
4 address: string;
5+ passphrase?: string;
5}
6}
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/L2Transaction.ts+3−2
@@ -1,5 +1,6 @@
1import RollerRPCAPI, { EthAddress, Proxy, Ship } from '@urbit/roller-api';
1import RollerRPCAPI, { EthAddress, Proxy, Ship } from '@urbit/roller-api';
2import WalletConnect from '@walletconnect/client';
2import WalletConnect from '@walletconnect/client';
3+import BridgeWallet from './BridgeWallet';
3
4
4export type TransactionType =
5export type TransactionType =
5 | 'transferPoint'
6 | 'transferPoint'
@@ -43,6 +44,6 @@ export interface ReticketParams {
43 point: Ship;
44 point: Ship;
44 to: EthAddress;
45 to: EthAddress;
45 manager: EthAddress;
46 manager: EthAddress;
46− fromWallet: any; // TODO: wallet type
47+ fromWallet: BridgeWallet;
47− toWallet: any; // TODO: wallet type
48+ toWallet: UrbitWallet;
48}
49}
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/useKeyfileGenerator.js → src/lib/useKeyfileGenerator.tsrenamed+35−13
@@ -18,20 +18,39 @@ import {
18} from './keys';
18} from './keys';
19import useCurrentPermissions from './useCurrentPermissions';
19import useCurrentPermissions from './useCurrentPermissions';
20import { convertToInt } from './convertToInt';
20import { convertToInt } from './convertToInt';
21+import { stripSigPrefix } from 'form/formatters';
22+
23+// overrideable defaults:
24+// seed - derived from wallet, mnemonic, or token (if posisble)
25+// point - the current pointCursor
26+interface useKeyfileGeneratorArgs {
27+ seed?: string;
28+ point?: number;
29+}
21
30
31+/**
32+ * @deprecated
33+ * Use `useMultikeyFileGenerator` instead
34+ */
22−export default function useKeyfileGenerator(manualNetworkSeed) {
35+export default function useKeyfileGenerator({
36+ seed,
37+ point,
38+}: useKeyfileGeneratorArgs) {
23− const { urbitWallet, wallet, authMnemonic, authToken } = useWallet();
39+ const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet();
24− const { pointCursor } = usePointCursor();
40+ const { pointCursor }: any = usePointCursor();
25− const { getDetails } = usePointCache();
41+ const { syncDetails, getDetails }: any = usePointCache();
26
42
27 const [notice, setNotice] = useState('Deriving networking keys...');
43 const [notice, setNotice] = useState('Deriving networking keys...');
28 const [downloaded, setDownloaded] = useState(false);
44 const [downloaded, setDownloaded] = useState(false);
29 const [generating, setGenerating] = useState(true);
45 const [generating, setGenerating] = useState(true);
30− const [keyfile, setKeyfile] = useState(false);
46+ const [keyfile, setKeyfile] = useState<boolean | string>(false);
31−
32 const [code, setCode] = useState(false);
47 const [code, setCode] = useState(false);
33
48
34− const _point = need.point(pointCursor);
49+ const _point = point || need.point(pointCursor);
50+ useEffect(() => {
51+ syncDetails(_point);
52+ }, [_point, syncDetails]);
53+
35 const details = getDetails(_point);
54 const details = getDetails(_point);
36
55
37 const networkRevision = details.matchWith({
56 const networkRevision = details.matchWith({
@@ -56,8 +75,8 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
56
75
57 const _details = need.details(details);
76 const _details = need.details(details);
58
77
59− const networkSeed = manualNetworkSeed
78+ const networkSeed = seed
60− ? Just(manualNetworkSeed)
79+ ? Just(seed)
61 : await attemptNetworkSeedDerivation({
80 : await attemptNetworkSeedDerivation({
62 urbitWallet,
81 urbitWallet,
63 wallet,
82 wallet,
@@ -88,14 +107,14 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
88 return;
107 return;
89 }
108 }
90
109
91− setNotice();
110+ setNotice('');
92 setCode(generateCode(pair));
111 setCode(generateCode(pair));
93 setKeyfile(compileNetworkKey(pair, _point, networkRevision));
112 setKeyfile(compileNetworkKey(pair, _point, networkRevision));
94 setGenerating(false);
113 setGenerating(false);
95 }, [
114 }, [
96 networkRevision,
115 networkRevision,
97 hasNetworkKeys,
116 hasNetworkKeys,
98− manualNetworkSeed,
117+ seed,
99 urbitWallet,
118 urbitWallet,
100 wallet,
119 wallet,
101 authMnemonic,
120 authMnemonic,
@@ -106,11 +125,14 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
106 ]);
125 ]);
107
126
108 const filename = useMemo(() => {
127 const filename = useMemo(() => {
109− return `${ob.patp(_point).slice(1)}-${networkRevision}.key`;
128+ return `${stripSigPrefix(ob.patp(_point))}-${networkRevision}.key`;
110− // TODO: ^ unifiy "remove tilde" calls
111 }, [_point, networkRevision]);
129 }, [_point, networkRevision]);
112
130
113 const download = useCallback(() => {
131 const download = useCallback(() => {
132+ if (typeof keyfile !== 'string') {
133+ return;
134+ }
135+
114 saveAs(
136 saveAs(
115 new Blob([keyfile], {
137 new Blob([keyfile], {
116 type: 'text/plain;charset=utf-8',
138 type: 'text/plain;charset=utf-8',
src/lib/useMultikeyFileGenerator.tsnew file+203
@@ -0,0 +1,203 @@
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+ const currentPair = await pairFromRevision(currentRevision);
131+
132+ if (!currentPair || !keysMatchChain(currentPair, _details || {})) {
133+ setGenerating(false);
134+ setNotice('Derived networking keys do not match on-chain details.');
135+ console.log(`keys do not match details for revision ${currentRevision}`);
136+ return;
137+ }
138+
139+ const nextPair = await pairFromRevision(nextRevision);
140+
141+ setNotice('');
142+ setCode(generateCode(currentPair));
143+ setKeyfile(
144+ compileMultiKey(_point, [
145+ {
146+ revision: currentRevision,
147+ pair: currentPair,
148+ },
149+ {
150+ revision: nextRevision,
151+ pair: nextPair,
152+ },
153+ ])
154+ );
155+ setGenerating(false);
156+ }, [
157+ _details,
158+ _point,
159+ currentRevision,
160+ hasNetworkKeys,
161+ nextRevision,
162+ pairFromRevision,
163+ ]);
164+
165+ const filename = useMemo(() => {
166+ return `${stripSigPrefix(ob.patp(_point))}-${currentRevision}.key`;
167+ }, [_point, currentRevision]);
168+
169+ const download = useCallback(() => {
170+ if (typeof keyfile !== 'string') {
171+ return;
172+ }
173+
174+ saveAs(
175+ new Blob([keyfile], {
176+ type: 'text/plain;charset=utf-8',
177+ }),
178+ filename
179+ );
180+ setDownloaded(true);
181+ }, [filename, keyfile]);
182+
183+ useEffect(() => {
184+ syncDetails(_point);
185+ }, [_point, syncDetails]);
186+
187+ useEffect(() => {
188+ generate();
189+ }, [generate]);
190+
191+ const output = {
192+ generating,
193+ available,
194+ downloaded,
195+ download,
196+ filename,
197+ notice,
198+ keyfile,
199+ code,
200+ };
201+
202+ return { ...output, bind: output };
203+}
src/lib/useRoller.ts+7−7
@@ -22,7 +22,7 @@ import { usePointCache } from 'store/pointCache';
22
22
23import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
23import { getUpdatedPointMessage, isPlanet, toL1Details } from './utils/point';
24import { convertToInt } from './convertToInt';
24import { convertToInt } from './convertToInt';
25−import { isDevelopment, isRopsten } from './flags';
25+import { isDevelopment, isMainnet, isRopsten } from './flags';
26import {
26import {
27 generateInviteWallet,
27 generateInviteWallet,
28 getPendingSpawns,
28 getPendingSpawns,
@@ -113,13 +113,13 @@ export default function useRoller() {
113 const [config, setConfig] = useState<Config | null>(null);
113 const [config, setConfig] = useState<Config | null>(null);
114
114
115 const options: Options = useMemo(() => {
115 const options: Options = useMemo(() => {
116− const type = isRopsten || !isDevelopment ? 'https' : 'http';
116+ const type = isMainnet || isRopsten ? 'https' : 'http';
117+ const host = isMainnet
118+ ? ROLLER_HOSTS.MAINNET
117− const host = isRopsten
119+ : isRopsten
118 ? ROLLER_HOSTS.ROPSTEN
120 ? ROLLER_HOSTS.ROPSTEN
119− : isDevelopment
120− ? ROLLER_HOSTS.LOCAL
121+ : ROLLER_HOSTS.LOCAL;
121− : ROLLER_HOSTS.MAINNET;
122− const port = isDevelopment && !isRopsten ? 8080 : 443;
122+ const port = isMainnet || isRopsten ? 443 : 8080;
123 const path = '/v1/roller';
123 const path = '/v1/roller';
124
124
125 return {
125 return {
src/lib/wallet.ts+5−1
@@ -12,7 +12,11 @@ export function EthereumWallet(privateKey) {
12 this.address = publicToAddress(this.publicKey);
12 this.address = publicToAddress(this.publicKey);
13}
13}
14
14
15−export const urbitWalletFromTicket = async (ticket, point, passphrase) => {
15+export const urbitWalletFromTicket = async (
16+ ticket: string,
17+ point: number,
18+ passphrase?: string
19+) => {
16 return await kg.generateWallet({
20 return await kg.generateWallet({
17 ticket: ticket,
21 ticket: ticket,
18 ship: point,
22 ship: point,
src/urbit-key-generation.d.tsnew file+68
@@ -0,0 +1,68 @@
1+interface NetworkKeys {
2+ crypt: {
3+ private: string;
4+ public: string;
5+ };
6+ auth: {
7+ private: string;
8+ public: string;
9+ };
10+}
11+
12+interface WalletNodeKeys {
13+ public: string;
14+ private: string;
15+ chain: string;
16+ address: string;
17+}
18+
19+interface WalletNode {
20+ type: string;
21+ seed: string;
22+ keys: WalletNodeKeys;
23+ derivationPath: string;
24+}
25+
26+interface WalletConfig {
27+ ticket: string;
28+ ship: number;
29+ passphrase?: string;
30+ boot?: boolean;
31+}
32+
33+interface UrbitWallet {
34+ meta: {
35+ generator: {
36+ name: string;
37+ version: string;
38+ };
39+ spec: string;
40+ ship: string;
41+ patp: string;
42+ tier: string;
43+ passphrase: string;
44+ };
45+ masterSeed: string;
46+ ownership: WalletNode;
47+ management: WalletNode;
48+ transfer: WalletNode;
49+ network:
50+ | {
51+ type: string;
52+ seed: string;
53+ keys: string;
54+ }
55+ | {};
56+ voting?: WalletNode;
57+ spawn?: WalletNode;
58+}
59+
60+declare module 'urbit-key-generation' {
61+ function deriveNetworkKeys(hex: string): NetworkKeys;
62+ function generateWallet({
63+ ticket,
64+ ship,
65+ passphrase,
66+ boot,
67+ }: WalletConfig): UrbitWallet;
68+}
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/ActivateCodeForm.tsx+14−8
@@ -18,9 +18,8 @@ import { useActivateFlow } from './ActivateFlow';
18import WarningBox from 'components/WarningBox';
18import WarningBox from 'components/WarningBox';
19import useRoller from 'lib/useRoller';
19import useRoller from 'lib/useRoller';
20import useImpliedTicket from 'lib/useImpliedTicket';
20import useImpliedTicket from 'lib/useImpliedTicket';
21−import { generateOwnershipWallet } from 'lib/walletgen';
21+import { generateWallet } from 'lib/walletgen';
22−import { generateWallet } from 'lib/invite';
23−import { walletFromMnemonic } from 'lib/wallet';
22+import { urbitWalletFromTicket, walletFromMnemonic } from 'lib/wallet';
24import { DEFAULT_HD_PATH, POINT_PROXIES } from 'lib/constants';
23import { DEFAULT_HD_PATH, POINT_PROXIES } from 'lib/constants';
25import useBreakpoints from 'lib/useBreakpoints';
24import useBreakpoints from 'lib/useBreakpoints';
26import { Ship } from '@urbit/roller-api';
25import { Ship } from '@urbit/roller-api';
@@ -46,6 +45,7 @@ const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
46 setDerivedWallet,
45 setDerivedWallet,
47 setInviteWallet,
46 setInviteWallet,
48 setIsIn,
47 setIsIn,
48+ setSendWallet,
49 } = useActivateFlow();
49 } = useActivateFlow();
50
50
51 // this is a pretty naive way to detect if we're on a mobile device
51 // this is a pretty naive way to detect if we're on a mobile device
@@ -109,11 +109,17 @@ const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
109 const { ticket, point } = getTicketAndPoint(
109 const { ticket, point } = getTicketAndPoint(
110 impliedTicket || values.ticket
110 impliedTicket || values.ticket
111 );
111 );
112− const { seed } = await generateOwnershipWallet(point, ticket);
112+
113− const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
113+ const inviteWallet = Just(await urbitWalletFromTicket(ticket, point));
114 setInviteWallet(inviteWallet);
114 setInviteWallet(inviteWallet);
115− const _inviteWallet = need.wallet(inviteWallet);
115+ const _inviteWallet: UrbitWallet = need.wallet(inviteWallet);
116− const inviteAddress = _inviteWallet.address;
116+ const inviteAddress = _inviteWallet.ownership.keys.address;
117+
118+ const sendWallet = await walletFromMnemonic(
119+ _inviteWallet.ownership.seed,
120+ DEFAULT_HD_PATH
121+ );
122+ setSendWallet(sendWallet);
117
123
118 let incoming = await loadPoints(inviteAddress);
124 let incoming = await loadPoints(inviteAddress);
119
125
@@ -136,7 +142,7 @@ const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
136 setDerivedPatp(Just(ob.patp(point)));
142 setDerivedPatp(Just(ob.patp(point)));
137 setDerivedPoint(Just(point));
143 setDerivedPoint(Just(point));
138 setDerivedPointDominion(Just(rollerPoint.dominion));
144 setDerivedPointDominion(Just(rollerPoint.dominion));
139− setDerivedWallet(Just(await generateWallet(point, true)));
145+ setDerivedWallet(Just(await generateWallet(point, ticket, true)));
140 setIsIn(false);
146 setIsIn(false);
141 await timeout(100);
147 await timeout(100);
142 } else {
148 } else {
src/views/Activate/MasterKeyDownload.tsx+11−9
@@ -1,7 +1,6 @@
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3import * as need from 'lib/need';
3import * as need from 'lib/need';
4−import * as ob from 'urbit-ob';
5
4
6import { ActivateSteps } from './ActivateSteps';
5import { ActivateSteps } from './ActivateSteps';
7import { Box } from '@tlon/indigo-react';
6import { Box } from '@tlon/indigo-react';
@@ -14,17 +13,18 @@ import { FadeableMasterKeyPresenter as MasterKeyPresenter } from './MasterKeyPre
14import { useActivateFlow } from './ActivateFlow';
13import { useActivateFlow } from './ActivateFlow';
15import ActivateView from './ActivateView';
14import ActivateView from './ActivateView';
16import { useLocalRouter } from 'lib/LocalRouter';
15import { useLocalRouter } from 'lib/LocalRouter';
17−import { compileNetworkKey } from 'lib/keys';
18−import { downloadWallet } from 'lib/invite';
19import PaperBuilder from 'components/PaperBuilder';
16import PaperBuilder from 'components/PaperBuilder';
20import { DEFAULT_FADE_TIMEOUT, MASTER_KEY_DURATION } from 'lib/constants';
17import { DEFAULT_FADE_TIMEOUT, MASTER_KEY_DURATION } from 'lib/constants';
21import { timeout } from 'lib/timeout';
18import { timeout } from 'lib/timeout';
22import View from 'components/View';
19import View from 'components/View';
20+import useMultikeyFileGenerator from 'lib/useMultikeyFileGenerator';
21+import { downloadWallet } from 'lib/invite';
23
22
24const MasterKeyDownload = () => {
23const MasterKeyDownload = () => {
25 const {
24 const {
26 derivedPoint,
25 derivedPoint,
27 derivedWallet,
26 derivedWallet,
27+ inviteWallet,
28 isIn,
28 isIn,
29 setGenerated,
29 setGenerated,
30 setIsIn,
30 setIsIn,
@@ -46,12 +46,10 @@ const MasterKeyDownload = () => {
46 Just: p => p.value.toFixed(),
46 Just: p => p.value.toFixed(),
47 });
47 });
48
48
49− const download = useCallback(async () => {
49+ const { keyfile, filename } = useMultikeyFileGenerator({
50− const netkey = compileNetworkKey(wallet.network.keys, point, 1);
50+ point,
51− //TODO could be deduplicated with useKeyfileGenerator's logic
51+ seedWallet: inviteWallet,
52− const filename = ob.patp(point).slice(1) + '-1.key';
52+ });
53− await downloadWallet(paper.getOrElse([]), netkey, filename);
54− }, [paper, wallet, point]);
55
53
56 // sync paper value to activation state
54 // sync paper value to activation state
57 useEffect(
55 useEffect(
@@ -65,6 +63,10 @@ const MasterKeyDownload = () => {
65 [paper, setGenerated]
63 [paper, setGenerated]
66 );
64 );
67
65
66+ const download = useCallback(async () => {
67+ await downloadWallet(paper.getOrElse([]), keyfile, filename);
68+ }, [paper, keyfile, filename]);
69+
68 const onDownloadClick = useCallback(async () => {
70 const onDownloadClick = useCallback(async () => {
69 await download();
71 await download();
70 setIsIn(false);
72 setIsIn(false);
src/views/Activate/MasterKeyTransfer.tsx+3−8
@@ -20,7 +20,7 @@ const MasterKeyTransfer = () => {
20 derivedPatp,
20 derivedPatp,
21 derivedPoint,
21 derivedPoint,
22 derivedWallet,
22 derivedWallet,
23− inviteWallet,
23+ sendWallet,
24 } = useActivateFlow();
24 } = useActivateFlow();
25 const [error, setError] = useState();
25 const [error, setError] = useState();
26
26
@@ -33,18 +33,13 @@ const MasterKeyTransfer = () => {
33 to: derivedWallet.value.ownership.keys.address,
33 to: derivedWallet.value.ownership.keys.address,
34 manager: derivedWallet.value.management.keys.address,
34 manager: derivedWallet.value.management.keys.address,
35 toWallet: derivedWallet.value,
35 toWallet: derivedWallet.value,
36− fromWallet: inviteWallet.value,
36+ fromWallet: sendWallet.value,
37 });
37 });
38 } catch (e) {
38 } catch (e) {
39 console.error(e);
39 console.error(e);
40 setError(e);
40 setError(e);
41 }
41 }
42− }, [
43− derivedWallet,
44− performL2Reticket,
42+ }, [derivedWallet, performL2Reticket, derivedPoint.value, sendWallet.value]);
45− derivedPoint.value,
46− inviteWallet.value,
47− ]);
48
43
49 useFadeIn();
44 useFadeIn();
50
45
src/views/Activate/useActivateFlowState.js+25−3
@@ -4,13 +4,30 @@ import { Nothing } from 'folktale/maybe';
4export default function useActivateFlowState() {
4export default function useActivateFlowState() {
5 const [derivedPatp, setDerivedPatp] = useState(Nothing());
5 const [derivedPatp, setDerivedPatp] = useState(Nothing());
6 const [derivedPoint, setDerivedPoint] = useState(Nothing());
6 const [derivedPoint, setDerivedPoint] = useState(Nothing());
7− const [derivedPointDominion, setDerivedPointDominion] = useState(Nothing());
8− const [derivedWallet, setDerivedWallet] = useState(Nothing());
9 const [generated, setGenerated] = useState(false);
7 const [generated, setGenerated] = useState(false);
10 const [incomingPoints, setIncomingPoints] = useState(Nothing());
8 const [incomingPoints, setIncomingPoints] = useState(Nothing());
11− const [inviteWallet, setInviteWallet] = useState(Nothing());
9+ const [derivedPointDominion, setDerivedPointDominion] = useState(Nothing());
12 const [isIn, setIsIn] = useState(false);
10 const [isIn, setIsIn] = useState(false);
13
11
12+ // A new wallet generated after confirming that the invite ticket is valid
13+ // and the point is available for activation.
14+ // At the end of the Activate flow, the point will be transferred
15+ // from the invite wallet to the derived wallet. When the user confirms
16+ // the Master Key (ticket) and downloads the passport / mnemonic,
17+ // it is for the derived wallet
18+ // type UrbitWallet
19+ const [derivedWallet, setDerivedWallet] = useState(Nothing());
20+ // The wallet generated deterministically during the invite flow;
21+ // it is re-generated when the user starts the Activate flow. It is used
22+ // to derive and confirm a shareable ticket that a user can activate a point with.
23+ // type UrbitWallet
24+ const [inviteWallet, setInviteWallet] = useState(Nothing());
25+ // This wallet is generated from the invite wallet's seed phrase.
26+ // It is used during the transfer phase of the invite flow to send transactions.
27+ // useMultiKeyFileGenerator.
28+ // type bip32.BIP32Interface
29+ const [sendWallet, setSendWallet] = useState(Nothing());
30+
14 const reset = useCallback(() => {
31 const reset = useCallback(() => {
15 setDerivedPatp(Nothing());
32 setDerivedPatp(Nothing());
16 setDerivedPoint(Nothing());
33 setDerivedPoint(Nothing());
@@ -19,13 +36,16 @@ export default function useActivateFlowState() {
19 setGenerated(false);
36 setGenerated(false);
20 setIncomingPoints(Nothing());
37 setIncomingPoints(Nothing());
21 setInviteWallet(Nothing());
38 setInviteWallet(Nothing());
39+ setSendWallet(Nothing());
22 }, [
40 }, [
41+ setDerivedPatp,
23 setDerivedPoint,
42 setDerivedPoint,
24 setDerivedPointDominion,
43 setDerivedPointDominion,
25 setDerivedWallet,
44 setDerivedWallet,
26 setGenerated,
45 setGenerated,
27 setIncomingPoints,
46 setIncomingPoints,
28 setInviteWallet,
47 setInviteWallet,
48+ setSendWallet,
29 ]);
49 ]);
30
50
31 return {
51 return {
@@ -38,6 +58,7 @@ export default function useActivateFlowState() {
38 inviteWallet,
58 inviteWallet,
39 isIn,
59 isIn,
40 reset,
60 reset,
61+ sendWallet,
41 setDerivedPatp,
62 setDerivedPatp,
42 setDerivedPoint,
63 setDerivedPoint,
43 setDerivedPointDominion,
64 setDerivedPointDominion,
@@ -46,5 +67,6 @@ export default function useActivateFlowState() {
46 setIncomingPoints,
67 setIncomingPoints,
47 setInviteWallet,
68 setInviteWallet,
48 setIsIn,
69 setIsIn,
70+ setSendWallet,
49 };
71 };
50}
72}
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(manualNetworkSeed);
75+ } = useMultikeyFileGenerator({ seed: manualNetworkSeed });
76
76
77 const buildNetworkSeed = useCallback(
77 const buildNetworkSeed = useCallback(
78 async manualSeed => {
78 async manualSeed => {
src/worker/worker.ts+1−1
@@ -1,6 +1,6 @@
1import kg from 'urbit-key-generation';
1import kg from 'urbit-key-generation';
2
2
3−export async function generateWallet(data: string): Promise<string> {
3+export async function generateWallet(data: string): Promise<UrbitWallet> {
4 // Process the data without stalling the UI
4 // Process the data without stalling the UI
5 const config = JSON.parse(data);
5 const config = JSON.parse(data);
6 const wallet = await kg.generateWallet(config);
6 const wallet = await kg.generateWallet(config);