@swapkit/wallet-keystore 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "author": "swapkit-oss-team",
3
+ "dependencies": {
4
+ "blakejs": "1.2.1",
5
+ "uuid": "9.0.0",
6
+ "@swapkit/types": "1.0.0-rc.0"
7
+ },
8
+ "description": "SwapKit Lib keystore",
9
+ "devDependencies": {
10
+ "@scure/bip39": "1.2.1",
11
+ "@types/long": "4.0.2",
12
+ "@types/uniqid": "5.3.2",
13
+ "@types/uuid": "9.0.3",
14
+ "@vitest/coverage-istanbul": "0.34.4",
15
+ "bitcoinjs-lib": "5.2.0",
16
+ "ethers": "6.7.1",
17
+ "vite": "4.4.9",
18
+ "vitest": "0.34.4",
19
+ "@internal/config": "0.0.0-internal.0",
20
+ "@swapkit/helpers": "1.0.0-rc.0",
21
+ "@swapkit/toolbox-cosmos": "1.0.0-rc.0",
22
+ "@swapkit/toolbox-evm": "1.0.0-rc.0",
23
+ "@swapkit/toolbox-utxo": "1.0.0-rc.0"
24
+ },
25
+ "eslintConfig": {
26
+ "extends": "../../../internal/eslint-config"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "import": "./dist/index.es.js",
31
+ "require": "./dist/index.cjs",
32
+ "types": "./dist/index.d.ts"
33
+ }
34
+ },
35
+ "files": [
36
+ "src/",
37
+ "dist/"
38
+ ],
39
+ "homepage": "https://github.com/thorswap/SwapKit",
40
+ "license": "Apache-2.0",
41
+ "main": "./dist/index.cjs",
42
+ "module": "./dist/index.es.js",
43
+ "name": "@swapkit/wallet-keystore",
44
+ "peerDependencies": {
45
+ "@scure/bip39": "1.2.1",
46
+ "ethers": "^6.7.1",
47
+ "bitcoinjs-lib": "^5.2.0",
48
+ "@swapkit/helpers": "1.0.0-rc.0",
49
+ "@swapkit/toolbox-cosmos": "1.0.0-rc.0",
50
+ "@swapkit/toolbox-evm": "1.0.0-rc.0",
51
+ "@swapkit/toolbox-utxo": "1.0.0-rc.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "react-native": "./src/index.ts",
57
+ "repository": "https://github.com/thorswap/SwapKit.git",
58
+ "type": "module",
59
+ "types": "./dist/index.d.ts",
60
+ "version": "1.0.0-rc.0",
61
+ "scripts": {
62
+ "build": "vite build",
63
+ "clean": "rm -rf dist vite.config.ts.* .turbo node_modules",
64
+ "lint": "eslint ./ --ext .ts,.tsx --fix; tsc --noEmit",
65
+ "test": "echo 'vitest --run'",
66
+ "test:coverage": "echo 'vitest run --coverage'"
67
+ }
68
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,156 @@
1
+ import { entropyToMnemonic, generateMnemonic } from '@scure/bip39';
2
+ import { wordlist } from '@scure/bip39/wordlists/english';
3
+ import { blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs';
4
+ import crypto from 'crypto';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+
7
+ const cipher = 'aes-128-ctr';
8
+ const kdf = 'pbkdf2';
9
+ const prf = 'hmac-sha256';
10
+ const dklen = 32;
11
+ const c = 262144;
12
+ const hashFunction = 'sha256';
13
+ const meta = 'xchain-keystore';
14
+
15
+ export type Keystore = {
16
+ crypto: {
17
+ cipher: string;
18
+ ciphertext: string;
19
+ cipherparams: {
20
+ iv: string;
21
+ };
22
+ kdf: string;
23
+ kdfparams: {
24
+ prf: string;
25
+ dklen: number;
26
+ salt: string;
27
+ c: number;
28
+ };
29
+ mac: string;
30
+ };
31
+ id: string;
32
+ version: number;
33
+ meta: string;
34
+ };
35
+
36
+ /**
37
+ * taken from `foundry-primitives` and modified
38
+ */
39
+ const toHexByte = (byte: number) => (byte < 0x10 ? `0${byte.toString(16)}` : byte.toString(16));
40
+ const toHex = (buffer: Buffer | Uint8Array) => Array.from(buffer).map(toHexByte).join('');
41
+
42
+ /**
43
+ * Gets data's 256 bit blake hash.
44
+ * @param data buffer or hexadecimal string
45
+ * @returns 32 byte hexadecimal string
46
+ */
47
+ export const blake256 = (data: Buffer | string): string => {
48
+ if (!(data instanceof Buffer)) {
49
+ data = Buffer.from(data, 'hex');
50
+ }
51
+ const context = blake2bInit(32);
52
+ blake2bUpdate(context, data);
53
+ return toHex(blake2bFinal(context));
54
+ };
55
+
56
+ const pbkdf2Async = async (
57
+ passphrase: string | Buffer | NodeJS.TypedArray | DataView,
58
+ salt: string | Buffer | NodeJS.TypedArray | DataView,
59
+ iterations: number,
60
+ keylen: number,
61
+ digest: string,
62
+ ) => {
63
+ return new Promise<Buffer>((resolve, reject) => {
64
+ crypto.pbkdf2(passphrase, salt, iterations, keylen, digest, (err, drived) => {
65
+ if (err) {
66
+ reject(err);
67
+ } else {
68
+ resolve(drived);
69
+ }
70
+ });
71
+ });
72
+ };
73
+
74
+ const _isNode = () => {
75
+ return typeof window === 'undefined';
76
+ };
77
+
78
+ export const encryptToKeyStore = async (phrase: string, password: string) => {
79
+ const ID = _isNode() ? require('uuid').v4() : uuidv4();
80
+ const salt = crypto.randomBytes(32);
81
+ const iv = crypto.randomBytes(16);
82
+ const kdfParams = {
83
+ prf,
84
+ dklen,
85
+ salt: salt.toString('hex'),
86
+ c,
87
+ };
88
+ const cipherParams = {
89
+ iv: iv.toString('hex'),
90
+ };
91
+
92
+ const derivedKey = await pbkdf2Async(
93
+ Buffer.from(password),
94
+ salt,
95
+ kdfParams.c,
96
+ kdfParams.dklen,
97
+ hashFunction,
98
+ );
99
+ const cipherIV = crypto.createCipheriv(cipher, derivedKey.slice(0, 16), iv);
100
+ const cipherText = Buffer.concat([
101
+ cipherIV.update(Buffer.from(phrase, 'utf8')),
102
+ cipherIV.final(),
103
+ ]);
104
+ const mac = blake256(Buffer.concat([derivedKey.slice(16, 32), Buffer.from(cipherText)]));
105
+
106
+ const cryptoStruct = {
107
+ cipher: cipher,
108
+ ciphertext: cipherText.toString('hex'),
109
+ cipherparams: cipherParams,
110
+ kdf: kdf,
111
+ kdfparams: kdfParams,
112
+ mac: mac,
113
+ };
114
+
115
+ const keystore = {
116
+ crypto: cryptoStruct,
117
+ id: ID,
118
+ version: 1,
119
+ meta: meta,
120
+ };
121
+
122
+ return keystore;
123
+ };
124
+
125
+ export const generatePhrase = (size = 12) => {
126
+ const entropy = size === 12 ? 128 : 256;
127
+ if (_isNode()) {
128
+ return entropyToMnemonic(crypto.randomBytes(entropy / 8), wordlist);
129
+ } else {
130
+ return generateMnemonic(wordlist, entropy);
131
+ }
132
+ };
133
+
134
+ export const decryptFromKeystore = async (keystore: Keystore, password: string) => {
135
+ const kdfparams = keystore.crypto.kdfparams;
136
+ const derivedKey = await pbkdf2Async(
137
+ Buffer.from(password),
138
+ Buffer.from(kdfparams.salt, 'hex'),
139
+ kdfparams.c,
140
+ kdfparams.dklen,
141
+ hashFunction,
142
+ );
143
+
144
+ const ciphertext = Buffer.from(keystore.crypto.ciphertext, 'hex');
145
+ const mac = blake256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
146
+
147
+ if (mac !== keystore.crypto.mac) throw new Error('Invalid password');
148
+ const decipher = crypto.createDecipheriv(
149
+ keystore.crypto.cipher,
150
+ derivedKey.slice(0, 16),
151
+ Buffer.from(keystore.crypto.cipherparams.iv, 'hex'),
152
+ );
153
+
154
+ const phrase = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
155
+ return phrase.toString('utf8');
156
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './helpers.ts';
2
+ export { keystoreWallet } from './keystore.ts';
@@ -0,0 +1,249 @@
1
+ import type { DepositParam, TransferParams } from '@swapkit/toolbox-cosmos';
2
+ import type {
3
+ TransactionType,
4
+ UTXOTransferParams,
5
+ UTXOWalletTransferParams,
6
+ } from '@swapkit/toolbox-utxo';
7
+ import type { ConnectWalletParams, Witness } from '@swapkit/types';
8
+ import { Chain, DerivationPath, WalletOption } from '@swapkit/types';
9
+ import type { Psbt } from 'bitcoinjs-lib';
10
+
11
+ type KeystoreOptions = {
12
+ ethplorerApiKey?: string;
13
+ utxoApiKey?: string;
14
+ covalentApiKey?: string;
15
+ stagenet?: boolean;
16
+ };
17
+
18
+ type Params = KeystoreOptions & {
19
+ api?: any;
20
+ rpcUrl?: string;
21
+ chain: Chain;
22
+ phrase: string;
23
+ index: number;
24
+ };
25
+
26
+ const getWalletMethodsForChain = async ({
27
+ api,
28
+ rpcUrl,
29
+ chain,
30
+ phrase,
31
+ ethplorerApiKey,
32
+ covalentApiKey,
33
+ utxoApiKey,
34
+ index,
35
+ stagenet,
36
+ }: Params) => {
37
+ const derivationPath = `${DerivationPath[chain]}/${index}`;
38
+
39
+ switch (chain) {
40
+ case Chain.BinanceSmartChain:
41
+ case Chain.Avalanche:
42
+ case Chain.Ethereum: {
43
+ if (chain === Chain.Ethereum && !ethplorerApiKey) {
44
+ throw new Error('Ethplorer API key not found');
45
+ } else if (!covalentApiKey) {
46
+ throw new Error('Covalent API key not found');
47
+ }
48
+
49
+ const { HDNodeWallet } = await import('ethers');
50
+ const { getProvider, ETHToolbox, AVAXToolbox, BSCToolbox } = await import(
51
+ '@swapkit/toolbox-evm'
52
+ );
53
+
54
+ const provider = getProvider(chain, rpcUrl);
55
+ const wallet = HDNodeWallet.fromPhrase(phrase).connect(provider);
56
+ const params = { api, provider, signer: wallet };
57
+
58
+ const toolbox =
59
+ chain === Chain.Ethereum
60
+ ? ETHToolbox({ ...params, ethplorerApiKey: ethplorerApiKey! })
61
+ : chain === Chain.Avalanche
62
+ ? AVAXToolbox({ ...params, covalentApiKey: covalentApiKey! })
63
+ : BSCToolbox({ ...params, covalentApiKey: covalentApiKey! });
64
+
65
+ return {
66
+ address: wallet.address,
67
+ walletMethods: {
68
+ ...toolbox,
69
+ getAddress: () => wallet.address,
70
+ },
71
+ };
72
+ }
73
+
74
+ case Chain.BitcoinCash: {
75
+ if (!utxoApiKey) throw new Error('UTXO API key not found');
76
+ const { BCHToolbox } = await import('@swapkit/toolbox-utxo');
77
+ const toolbox = BCHToolbox({ rpcUrl, apiKey: utxoApiKey, apiClient: api });
78
+ const keys = await toolbox.createKeysForPath({ phrase, derivationPath });
79
+ const address = toolbox.getAddressFromKeys(keys);
80
+
81
+ const signTransaction = async ({
82
+ builder,
83
+ utxos,
84
+ }: Awaited<ReturnType<typeof toolbox.buildBCHTx>>) => {
85
+ utxos.forEach((utxo, index) => {
86
+ builder.sign(index, keys, undefined, 0x41, (utxo.witnessUtxo as Witness).value);
87
+ });
88
+
89
+ return builder.build();
90
+ };
91
+
92
+ const walletMethods = {
93
+ ...toolbox,
94
+ getAddress: () => address,
95
+ transfer: (
96
+ params: UTXOWalletTransferParams<
97
+ Awaited<ReturnType<typeof toolbox.buildBCHTx>>,
98
+ TransactionType
99
+ >,
100
+ ) => toolbox.transfer({ ...params, from: address, signTransaction }),
101
+ };
102
+
103
+ return { address, walletMethods };
104
+ }
105
+
106
+ case Chain.Bitcoin:
107
+ case Chain.Dogecoin:
108
+ case Chain.Litecoin: {
109
+ const params = { rpcUrl, apiKey: utxoApiKey, apiClient: api };
110
+
111
+ const { BTCToolbox, LTCToolbox, DOGEToolbox } = await import('@swapkit/toolbox-utxo');
112
+
113
+ const toolbox =
114
+ chain === Chain.Bitcoin
115
+ ? BTCToolbox(params)
116
+ : chain === Chain.Litecoin
117
+ ? LTCToolbox(params)
118
+ : DOGEToolbox(params);
119
+
120
+ const keys = await toolbox.createKeysForPath({ phrase, derivationPath });
121
+ const address = toolbox.getAddressFromKeys(keys);
122
+
123
+ const signTransaction = async (psbt: Psbt) => {
124
+ psbt.signAllInputs(keys);
125
+
126
+ return psbt;
127
+ };
128
+
129
+ return {
130
+ address,
131
+ walletMethods: {
132
+ ...toolbox,
133
+ getAddress: () => address,
134
+ transfer: (params: UTXOTransferParams) =>
135
+ toolbox.transfer({ ...params, from: address, signTransaction }),
136
+ },
137
+ };
138
+ }
139
+
140
+ case Chain.Binance: {
141
+ const { BinanceToolbox } = await import('@swapkit/toolbox-cosmos');
142
+ const toolbox = BinanceToolbox();
143
+ const privkey = await toolbox.createKeyPair(phrase);
144
+ const address = await toolbox.getAddressFromMnemonic(phrase);
145
+
146
+ const transfer = ({ assetValue, recipient, memo }: TransferParams) =>
147
+ toolbox.transfer({
148
+ from: address,
149
+ recipient,
150
+ assetValue,
151
+ privkey,
152
+ memo,
153
+ });
154
+
155
+ return {
156
+ address,
157
+ walletMethods: { ...toolbox, transfer, getAddress: () => address },
158
+ };
159
+ }
160
+
161
+ case Chain.Cosmos: {
162
+ const { GaiaToolbox } = await import('@swapkit/toolbox-cosmos');
163
+ const toolbox = GaiaToolbox({ server: api });
164
+ const signer = await toolbox.getSigner(phrase);
165
+ const address = await toolbox.getAddressFromMnemonic(phrase);
166
+
167
+ const transfer = ({ assetValue, recipient, memo }: TransferParams) =>
168
+ toolbox.transfer({
169
+ from: address,
170
+ recipient,
171
+ signer,
172
+ assetValue,
173
+ memo,
174
+ });
175
+
176
+ return {
177
+ address,
178
+ walletMethods: { ...toolbox, transfer, getAddress: () => address },
179
+ };
180
+ }
181
+
182
+ case Chain.Maya:
183
+ case Chain.THORChain: {
184
+ const { MayaToolbox, ThorchainToolbox } = await import('@swapkit/toolbox-cosmos');
185
+ const toolbox =
186
+ chain === Chain.THORChain ? ThorchainToolbox({ stagenet }) : MayaToolbox({ stagenet });
187
+ const address = await toolbox.getAddressFromMnemonic(phrase);
188
+ const signer = await toolbox.getSigner(phrase);
189
+
190
+ const transfer = async ({ assetValue, recipient, memo }: TransferParams) =>
191
+ toolbox.transfer({
192
+ from: address,
193
+ recipient,
194
+ signer,
195
+ assetValue,
196
+ memo,
197
+ });
198
+
199
+ const deposit = async ({ assetValue, memo }: DepositParam) => {
200
+ return toolbox.deposit({ assetValue, memo, from: address, signer });
201
+ };
202
+
203
+ const walletMethods = { ...toolbox, deposit, transfer, getAddress: () => address };
204
+
205
+ return { address, walletMethods };
206
+ }
207
+
208
+ default:
209
+ throw new Error(`Unsupported chain ${chain}`);
210
+ }
211
+ };
212
+
213
+ const connectKeystore =
214
+ ({
215
+ addChain,
216
+ apis,
217
+ rpcUrls,
218
+ config: { covalentApiKey, ethplorerApiKey, utxoApiKey, stagenet },
219
+ }: ConnectWalletParams) =>
220
+ async (chains: Chain[], phrase: string, index: number = 0) => {
221
+ const promises = chains.map(async (chain) => {
222
+ const { address, walletMethods } = await getWalletMethodsForChain({
223
+ index,
224
+ chain,
225
+ api: apis[chain as Chain.Avalanche],
226
+ rpcUrl: rpcUrls[chain],
227
+ covalentApiKey,
228
+ ethplorerApiKey,
229
+ phrase,
230
+ utxoApiKey,
231
+ stagenet,
232
+ });
233
+
234
+ addChain({
235
+ chain,
236
+ walletMethods,
237
+ wallet: { address, balance: [], walletType: WalletOption.KEYSTORE },
238
+ });
239
+ });
240
+
241
+ await Promise.all(promises);
242
+
243
+ return true;
244
+ };
245
+
246
+ export const keystoreWallet = {
247
+ connectMethodName: 'connectKeystore' as const,
248
+ connect: connectKeystore,
249
+ };