identitiesUtils.ts 10.4 KB
Newer Older
Hanwen Cheng's avatar
Hanwen Cheng committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity.  If not, see <http://www.gnu.org/licenses/>.

17
18
19
20
import { pathsRegex } from './regex';
import { decryptData } from './native';
import { parseSURI } from './suri';
import { generateAccountId } from './account';
Hanwen Cheng's avatar
Hanwen Cheng committed
21
22
23

import {
	NETWORK_LIST,
24
	SUBSTRATE_NETWORK_LIST,
25
	SubstrateNetworkKeys,
Hanwen Cheng's avatar
Hanwen Cheng committed
26
	UnknownNetworkKeys
27
28
29
30
31
32
33
34
35
36
37
} from 'constants/networkSpecs';
import {
	Account,
	FoundAccount,
	FoundLegacyAccount,
	Identity,
	LockedAccount,
	PathGroup,
	SerializedIdentity,
	UnlockedAccount
} from 'types/identityTypes';
38
39
40
41
42
43
import {
	defaultMetaData,
	kusamaMetadata,
	substrateDevMetadata,
	westendMetadata
} from 'constants/networkMetadata';
Hanwen Cheng's avatar
Hanwen Cheng committed
44
45

//walk around to fix the regular expression support for positive look behind;
46
export const removeSlash = (str: string): string => str.replace(/\//g, '');
Hanwen Cheng's avatar
Hanwen Cheng committed
47

48
49
50
51
52
53
54
export function isLegacyFoundAccount(
	foundAccount: FoundAccount
): foundAccount is FoundLegacyAccount {
	return foundAccount.isLegacy;
}

const extractPathId = (path: string): string | null => {
Hanwen Cheng's avatar
Hanwen Cheng committed
55
56
57
58
59
	const matchNetworkPath = path.match(pathsRegex.networkPath);
	if (!matchNetworkPath) return null;
	return removeSlash(matchNetworkPath[0]);
};

60
export const extractSubPathName = (path: string): string => {
Hanwen Cheng's avatar
Hanwen Cheng committed
61
	const pathFragments = path.match(pathsRegex.allPath);
62
63
	if (!pathFragments || pathFragments.length === 0) return '';
	if (pathFragments.length === 1) return removeSlash(pathFragments[0]);
Hanwen Cheng's avatar
Hanwen Cheng committed
64
65
66
	return removeSlash(pathFragments.slice(1).join(''));
};

67
export const isSubstratePath = (path: string): boolean =>
68
	path.match(pathsRegex.allPath) !== null || path === '';
Hanwen Cheng's avatar
Hanwen Cheng committed
69

70
71
export const isEthereumAccountId = (v: string): boolean =>
	v.indexOf('ethereum:') === 0;
72

73
export const extractAddressFromAccountId = (id: string): string => {
74
75
76
77
78
79
80
81
	const withoutNetwork = id.split(':')[1];
	const address = withoutNetwork.split('@')[0];
	if (address.indexOf('0x') !== -1) {
		return address.slice(2);
	}
	return address;
};

82
export const getAddressKeyByPath = (address: string, path: string): string =>
83
84
85
86
	isSubstratePath(path)
		? address
		: generateAccountId({ address, networkKey: getNetworkKeyByPath(path) });

87
export function emptyIdentity(): Identity {
Hanwen Cheng's avatar
Hanwen Cheng committed
88
	return {
89
		addresses: new Map(),
Hanwen Cheng's avatar
Hanwen Cheng committed
90
		derivationPassword: '',
91
		encryptedSeed: '',
Hanwen Cheng's avatar
Hanwen Cheng committed
92
93
94
95
96
		meta: new Map(),
		name: ''
	};
}

97
98
99
export const serializeIdentity = (identity: Identity): SerializedIdentity =>
	Object.entries(identity).reduce((newIdentity: any, entry: [string, any]) => {
		const [key, value] = entry;
Hanwen Cheng's avatar
Hanwen Cheng committed
100
101
102
103
104
105
106
107
		if (value instanceof Map) {
			newIdentity[key] = Array.from(value.entries());
		} else {
			newIdentity[key] = value;
		}
		return newIdentity;
	}, {});

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
export const deserializeIdentity = (
	identityJSON: SerializedIdentity
): Identity =>
	Object.entries(identityJSON).reduce(
		(newIdentity: any, entry: [string, any]) => {
			const [key, value] = entry;
			if (value instanceof Array) {
				newIdentity[key] = new Map(value);
			} else {
				newIdentity[key] = value;
			}
			return newIdentity;
		},
		{}
	);
Hanwen Cheng's avatar
Hanwen Cheng committed
123

124
export const serializeIdentities = (identities: Identity[]): string => {
Hanwen Cheng's avatar
Hanwen Cheng committed
125
126
127
128
	const identitiesWithObject = identities.map(serializeIdentity);
	return JSON.stringify(identitiesWithObject);
};

129
export const deserializeIdentities = (identitiesJSON: string): Identity[] => {
Hanwen Cheng's avatar
Hanwen Cheng committed
130
131
132
133
	const identitiesWithObject = JSON.parse(identitiesJSON);
	return identitiesWithObject.map(deserializeIdentity);
};

134
export const deepCopyIdentities = (identities: Identity[]): Identity[] =>
Hanwen Cheng's avatar
Hanwen Cheng committed
135
	deserializeIdentities(serializeIdentities(identities));
136
137

export const deepCopyIdentity = (identity: Identity): Identity =>
Hanwen Cheng's avatar
Hanwen Cheng committed
138
139
	deserializeIdentity(serializeIdentity(identity));

140
141
142
143
export const getPathsWithSubstrateNetworkKey = (
	paths: string[],
	networkKey: string
): string[] => {
144
145
146
147
	if (networkKey === UnknownNetworkKeys.UNKNOWN) {
		const pathIdList = Object.values(SUBSTRATE_NETWORK_LIST).map(
			networkParams => networkParams.pathId
		);
148
149
150
151
152
		return paths.filter(path => {
			const pathId = extractPathId(path);
			if (!isSubstratePath(path)) return false;
			return !pathId || !pathIdList.includes(pathId);
		});
153
154
	}
	return paths.filter(
155
		path => extractPathId(path) === SUBSTRATE_NETWORK_LIST[networkKey].pathId
156
157
	);
};
Hanwen Cheng's avatar
Hanwen Cheng committed
158

159
160
const getNetworkKeyByPathId = (pathId: string): string => {
	const networkKeyIndex = Object.values(SUBSTRATE_NETWORK_LIST).findIndex(
161
162
		networkParams => networkParams.pathId === pathId
	);
163
164
	if (networkKeyIndex !== -1)
		return Object.keys(SUBSTRATE_NETWORK_LIST)[networkKeyIndex];
165
166
167
	return UnknownNetworkKeys.UNKNOWN;
};

168
export const getNetworkKey = (path: string, identity: Identity): string => {
169
	if (identity.meta.has(path)) {
170
		const networkPathId = identity.meta.get(path)!.networkPathId;
171
172
173
174
175
		if (networkPathId) return getNetworkKeyByPathId(networkPathId);
	}
	return getNetworkKeyByPath(path);
};

176
export const getNetworkKeyByPath = (path: string): string => {
Hanwen Cheng's avatar
Hanwen Cheng committed
177
178
179
180
181
182
	if (!isSubstratePath(path) && NETWORK_LIST.hasOwnProperty(path)) {
		return path;
	}
	const pathId = extractPathId(path);
	if (!pathId) return UnknownNetworkKeys.UNKNOWN;

183
	return getNetworkKeyByPathId(pathId);
Hanwen Cheng's avatar
Hanwen Cheng committed
184
185
};

186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
export const parseFoundLegacyAccount = (
	legacyAccount: Account,
	accountId: string
): FoundLegacyAccount => {
	const returnAccount: FoundLegacyAccount = {
		accountId,
		address: legacyAccount.address,
		createdAt: legacyAccount.createdAt,
		isLegacy: true,
		name: legacyAccount.name,
		networkKey: legacyAccount.networkKey,
		updatedAt: legacyAccount.updatedAt,
		validBip39Seed: legacyAccount.validBip39Seed
	};
	if (legacyAccount.hasOwnProperty('encryptedSeed')) {
		returnAccount.encryptedSeed = (legacyAccount as LockedAccount).encryptedSeed;
	}
	if (legacyAccount.hasOwnProperty('derivationPath')) {
		returnAccount.path = (legacyAccount as UnlockedAccount).derivationPath;
	}
	return returnAccount;
};

export const getIdentityFromSender = (
	sender: FoundAccount,
	identities: Identity[]
): Identity | undefined =>
Hanwen Cheng's avatar
Hanwen Cheng committed
213
214
	identities.find(i => i.encryptedSeed === sender.encryptedSeed);

215
216
217
218
export const getAddressWithPath = (
	path: string,
	identity: Identity
): string => {
Hanwen Cheng's avatar
Hanwen Cheng committed
219
	const pathMeta = identity.meta.get(path);
220
221
222
223
224
	if (!pathMeta) return '';
	const { address } = pathMeta;
	return isEthereumAccountId(address)
		? extractAddressFromAccountId(address)
		: address;
Hanwen Cheng's avatar
Hanwen Cheng committed
225
226
};

227
228
229
230
export const unlockIdentitySeed = async (
	pin: string,
	identity: Identity
): Promise<string> => {
Hanwen Cheng's avatar
Hanwen Cheng committed
231
232
233
234
235
236
	const { encryptedSeed } = identity;
	const seed = await decryptData(encryptedSeed, pin);
	const { phrase } = parseSURI(seed);
	return phrase;
};

237
export const getExistedNetworkKeys = (identity: Identity): string[] => {
238
239
	const pathsList = Array.from(identity.addresses.values());
	const networkKeysSet = pathsList.reduce((networksSet, path) => {
Hanwen Cheng's avatar
Hanwen Cheng committed
240
241
242
243
244
245
246
247
248
249
250
		let networkKey;
		if (isSubstratePath(path)) {
			networkKey = getNetworkKeyByPath(path);
		} else {
			networkKey = path;
		}
		return { ...networksSet, [networkKey]: true };
	}, {});
	return Object.keys(networkKeysSet);
};

251
export const validateDerivedPath = (derivedPath: string): boolean =>
Hanwen Cheng's avatar
Hanwen Cheng committed
252
253
	pathsRegex.validateDerivedPath.test(derivedPath);

254
255
256
257
export const getIdentityName = (
	identity: Identity,
	identities: Identity[]
): string => {
Hanwen Cheng's avatar
Hanwen Cheng committed
258
259
260
261
262
263
264
	if (identity.name) return identity.name;
	const identityIndex = identities.findIndex(
		i => i.encryptedSeed === identity.encryptedSeed
	);
	return `Identity_${identityIndex}`;
};

265
export const getPathName = (path: string, lookUpIdentity: Identity): string => {
Hanwen Cheng's avatar
Hanwen Cheng committed
266
267
268
	if (
		lookUpIdentity &&
		lookUpIdentity.meta.has(path) &&
269
		lookUpIdentity.meta.get(path)!.name !== ''
Hanwen Cheng's avatar
Hanwen Cheng committed
270
	) {
271
		return lookUpIdentity.meta.get(path)!.name;
Hanwen Cheng's avatar
Hanwen Cheng committed
272
	}
273
274
	if (!isSubstratePath(path)) return 'No name';
	if (path === '') return 'Identity root';
Hanwen Cheng's avatar
Hanwen Cheng committed
275
276
277
	return extractSubPathName(path);
};

278
279
280
281
282
283
/**
 * This function decides how to group the list of derivation paths in the display based on the following rules.
 * If the network is unknown: group by the first subpath, e.g. '/random' of '/random//derivation/1'
 * If the network is known: group by the second subpath, e.g. '//staking' of '//kusama//staking/0'
 * Please refer to identitiesUtils.spec.js for more examples.
 **/
284
285
286
287
288
289
290
291
export const groupPaths = (paths: string[]): PathGroup[] => {
	const insertPathIntoGroup = (
		matchingPath: string,
		fullPath: string,
		pathGroup: PathGroup[]
	): void => {
		const matchResult = matchingPath.match(pathsRegex.firstPath);
		const groupName = matchResult ? matchResult[0] : '-';
292
293
294
295
296
297
298
299
300
301

		const existedItem = pathGroup.find(p => p.title === groupName);
		if (existedItem) {
			existedItem.paths.push(fullPath);
			existedItem.paths.sort();
		} else {
			pathGroup.push({ paths: [fullPath], title: groupName });
		}
	};

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
	const groupedPaths = paths.reduce(
		(groupedPath: PathGroup[], path: string) => {
			if (path === '') {
				groupedPath.push({ paths: [''], title: 'Identity root' });
				return groupedPath;
			}

			const rootPath = path.match(pathsRegex.firstPath)?.[0];
			if (rootPath === undefined) return groupedPath;

			const networkParams = Object.values(SUBSTRATE_NETWORK_LIST).find(
				v => `//${v.pathId}` === rootPath
			);
			if (networkParams === undefined) {
				insertPathIntoGroup(path, path, groupedPath);
				return groupedPath;
			}

			const isRootPath = path === rootPath;
			if (isRootPath) {
				groupedPath.push({
					paths: [path],
					title: `${networkParams.title} root`
				});
				return groupedPath;
			}

			const subPath = path.slice(rootPath.length);
			insertPathIntoGroup(subPath, path, groupedPath);
331

332
			return groupedPath;
333
334
335
		},
		[]
	);
336
337
338
339
340
341
	return groupedPaths.sort((a, b) => {
		if (a.paths.length === 1 && b.paths.length === 1) {
			return a.paths[0].length - b.paths[0].length;
		}
		return a.paths.length - b.paths.length;
	});
Hanwen Cheng's avatar
Hanwen Cheng committed
342
};
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357

export const getMetadata = (networkKey: string): string => {
	switch (networkKey) {
		case SubstrateNetworkKeys.KUSAMA:
		case SubstrateNetworkKeys.KUSAMA_CC2:
		case SubstrateNetworkKeys.KUSAMA_DEV:
			return kusamaMetadata;
		case SubstrateNetworkKeys.WESTEND:
			return westendMetadata;
		case SubstrateNetworkKeys.SUBSTRATE_DEV:
			return substrateDevMetadata;
		default:
			return defaultMetaData;
	}
};