wative-core 1.0.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/src/account.ts ADDED
@@ -0,0 +1,582 @@
1
+ import * as fs from "fs"
2
+ import * as path from "path"
3
+ import Web3 from 'web3';
4
+ import * as nacl from "tweetnacl";
5
+ import * as bs58 from 'bs58';
6
+ import * as bip39 from 'bip39'
7
+ import { Keypair, Connection } from '@solana/web3.js';
8
+ import { Wallet } from "@coral-xyz/anchor"
9
+ import * as CryptoJS from "crypto-js";
10
+ import { EvmGasUtil, Result, checkAccountLabelsExistence, encryptMessage, decryptMessage, excutePromiseFunction, checkFileExistence, getJsonParams, setJsonParams, hexToUint8Array } from "./utils";
11
+ import { checkMnemonic } from "./mnemonic";
12
+ import { hdkey as evmHdKey } from 'ethereumjs-wallet';
13
+ import { HDKey as solanaHdKey } from 'micro-ed25519-hdkey';
14
+
15
+
16
+ const web3 = new Web3(Web3.givenProvider);
17
+
18
+ export class Account {
19
+ private setting_path: string;
20
+ private account_labels: string[];
21
+ private account_passwords: any = {};
22
+ private account_ciphertexts: any = {};
23
+
24
+ constructor(setting_path: string, account_labels: string[], account_passwords?: string[]) {
25
+ this.setting_path = setting_path;
26
+ this.account_labels = account_labels;
27
+
28
+ if (account_labels.length === 0) {
29
+ return;
30
+ }
31
+
32
+ if (account_passwords) {
33
+ if (account_labels.length !== account_passwords.length) {
34
+ throw new Error("account_labels should have same length for account_passwords");
35
+ }
36
+
37
+ for (let i = 0; i < account_labels.length; i++) {
38
+ if (!this.checkPassword(account_labels[i], account_passwords[i].trim())) {
39
+ throw new Error("account_passwords is not correct");
40
+ };
41
+ this.account_passwords[account_labels[i]] = account_passwords[i].trim();
42
+ }
43
+ }
44
+
45
+ this.reload();
46
+ }
47
+
48
+ public isLogin(account_label: string) {
49
+ return account_label in this.account_passwords;
50
+ }
51
+
52
+ public reloadAccount() {
53
+ this.reload();
54
+ }
55
+
56
+ public getAccountLabel(account_address: string) {
57
+ account_address = this.getAccountChecksumAddress(account_address);
58
+
59
+ if (!this.checkAccountIsExist(account_address)) {
60
+ return null;
61
+ }
62
+
63
+ return this.account_ciphertexts[account_address].account_labels;
64
+ }
65
+
66
+ public login(account_label: string, password: string) {
67
+ if (!this.checkPassword(account_label, password)) {
68
+ return {
69
+ status: false,
70
+ output: "Password is not correct"
71
+ }
72
+ };
73
+ this.account_passwords[account_label] = password;
74
+ this.account_labels.push(account_label);
75
+ this.reload();
76
+ return {
77
+ status: true,
78
+ output: "Login success"
79
+ }
80
+ }
81
+
82
+ public checkAccountLabelIsExist(account_label: string) {
83
+ return checkAccountLabelsExistence(this.setting_path, [account_label]);
84
+ }
85
+
86
+ public checkAccountIsExist(account_address: string) {
87
+ account_address = this.getAccountChecksumAddress(account_address);
88
+ if (account_address in this.account_ciphertexts) {
89
+ return true;
90
+ }
91
+
92
+ this.reload();
93
+ return account_address in this.account_ciphertexts;
94
+ }
95
+
96
+ public checkPassword(account_label: string, password: string) {
97
+ let json_data_path = path.join(this.setting_path, `accounts/${account_label}.json`);
98
+
99
+ if (!checkFileExistence(json_data_path)) {
100
+ return false;
101
+ }
102
+
103
+ let account_label_data = getJsonParams(json_data_path);
104
+
105
+ let message: any;
106
+ if (account_label_data.account_type === "PK") {
107
+ message = {
108
+ salt: password,
109
+ ...account_label_data.ciphertexts
110
+ };
111
+
112
+ } else {
113
+ message = {
114
+ slat: password,
115
+ data: account_label_data.pp_ciphertexts
116
+ };
117
+ }
118
+
119
+ let signature = CryptoJS.SHA256(JSON.stringify(message)).toString();
120
+
121
+ return account_label_data.signature === signature;
122
+ }
123
+
124
+ public showPrivateKey(account_address: string) {
125
+ let account_is_exist = this.checkAccountIsExist(account_address);
126
+ if (!account_is_exist) {
127
+ throw new Error("Account is not exist");
128
+ }
129
+
130
+ account_address = this.getAccountChecksumAddress(account_address);
131
+ let account_data = this.account_ciphertexts[account_address];
132
+ let password = this.account_passwords[account_data.account_labels];
133
+ return decryptMessage(account_data.data, password);
134
+ }
135
+
136
+ public getAccountChainType(account_address: string): string {
137
+ if (account_address.startsWith("0x") && account_address.length === 42) {
138
+ return "evm";
139
+ }
140
+
141
+ if (/^[A-HJ-NP-Za-km-z1-9]*$/.test(account_address)) {
142
+ return "solana";
143
+ }
144
+ return "unknown";
145
+ }
146
+
147
+ public generatePKAccount(account_label: string, pk: string, password: string, chain_id: string) {
148
+ if (this.checkAccountLabelIsExist(account_label)) {
149
+ throw new Error("Account labels already exist");
150
+ };
151
+
152
+ let ciphertext_data = encryptMessage(pk, password);
153
+ let ciphertexts = {
154
+ address: this.getAccountAddressByPrivateKey(pk, chain_id),
155
+ data: ciphertext_data
156
+ };
157
+ let message = {
158
+ salt: password,
159
+ ...ciphertexts
160
+ };
161
+ let signature = CryptoJS.SHA256(JSON.stringify(message)).toString();
162
+
163
+ let account = {
164
+ signature,
165
+ account_type: "PK",
166
+ tag: "",
167
+ ciphertexts
168
+ }
169
+
170
+ this.account_passwords[account_label] = password;
171
+ this.account_labels.push(account_label);
172
+ return {
173
+ status: true,
174
+ output: account
175
+ };
176
+ }
177
+
178
+ public generatePPAccount(account_label: string, pp: string, password: string) {
179
+ if (this.checkAccountLabelIsExist(account_label)) {
180
+ return {
181
+ status: false,
182
+ output: "Account labels already exist"
183
+ }
184
+ };
185
+
186
+ let pp_ciphertext_data = encryptMessage(pp, password);
187
+ let message = {
188
+ slat: password,
189
+ data: pp_ciphertext_data
190
+ }
191
+ let signature = CryptoJS.SHA256(JSON.stringify(message)).toString();
192
+ let evm_account_data = this.getSubAccount("evm", pp, 0);
193
+ if (evm_account_data.status === false) {
194
+ return {
195
+ status: false,
196
+ output: evm_account_data.output
197
+ }
198
+ }
199
+
200
+ let solana_account_data = this.getSubAccount("solana", pp, 0);
201
+ if (solana_account_data.status === false) {
202
+ return {
203
+ status: false,
204
+ output: solana_account_data.output
205
+ }
206
+ }
207
+
208
+ let account = {
209
+ signature,
210
+ account_type: "PP",
211
+ pp_ciphertexts: pp_ciphertext_data,
212
+ tag: "",
213
+ data: [{
214
+ ciphertexts: {
215
+ evm: {
216
+ address: evm_account_data.output.address,
217
+ data: encryptMessage(evm_account_data.output.pk, password)
218
+ },
219
+ solana: {
220
+ address: solana_account_data.output.address,
221
+ data: encryptMessage(solana_account_data.output.pk, password)
222
+ }
223
+ }
224
+ }]
225
+ }
226
+
227
+ this.account_passwords[account_label] = password;
228
+ this.account_labels.push(account_label);
229
+ return {
230
+ status: true,
231
+ output: account
232
+ };
233
+ }
234
+
235
+ public generateSubAccount(account_label: string, account_index: number) {
236
+ if (!this.checkAccountLabelIsExist(account_label)) {
237
+ return {
238
+ status: false,
239
+ output: "Account labels not exist"
240
+ }
241
+ };
242
+
243
+ let json_data_path = path.join(this.setting_path, `accounts/${account_label}.json`);
244
+ let account_label_data = getJsonParams(json_data_path);
245
+ let pp_ciphertexts = account_label_data.pp_ciphertexts;
246
+
247
+ let pp = decryptMessage(pp_ciphertexts, this.account_passwords[account_label]);
248
+
249
+ let evm_account_data = this.getSubAccount("evm", pp, account_index);
250
+ if (evm_account_data.status === false) {
251
+ return {
252
+ status: false,
253
+ output: evm_account_data.output
254
+ }
255
+ }
256
+
257
+ let solana_account_data = this.getSubAccount("solana", pp, account_index);
258
+ if (solana_account_data.status === false) {
259
+ return {
260
+ status: false,
261
+ output: solana_account_data.output
262
+ }
263
+ }
264
+
265
+ let sub_account = {
266
+ evm: {
267
+ address: evm_account_data.output.address,
268
+ data: encryptMessage(evm_account_data.output.pk, this.account_passwords[account_label])
269
+ },
270
+ solana: {
271
+ address: solana_account_data.output.address,
272
+ data: encryptMessage(solana_account_data.output.pk, this.account_passwords[account_label])
273
+ }
274
+ }
275
+
276
+ return {
277
+ status: true,
278
+ output: sub_account
279
+ };
280
+ }
281
+
282
+ public resetPassword(account_label: string, new_password: string) {
283
+ if (!this.checkAccountLabelIsExist(account_label)) {
284
+ return {
285
+ status: false,
286
+ output: "Account labels not exist"
287
+ }
288
+ };
289
+
290
+ if (!this.checkPassword(account_label, this.account_passwords[account_label])) {
291
+ return {
292
+ status: false,
293
+ output: "account passwords is not correct"
294
+ }
295
+ };
296
+
297
+ let json_data_path = path.join(this.setting_path, `accounts/${account_label}.json`);
298
+ let account_label_data = getJsonParams(json_data_path);
299
+
300
+ if (account_label_data.account_type === "PK") {
301
+ let old_ciphertexts_data = account_label_data.ciphertexts.data;
302
+ let pk = decryptMessage(old_ciphertexts_data, this.account_passwords[account_label]);
303
+ account_label_data.ciphertexts.data = encryptMessage(pk, new_password);
304
+ let message = {
305
+ salt: new_password,
306
+ ...account_label_data.ciphertexts
307
+ };
308
+ account_label_data.signature = CryptoJS.SHA256(JSON.stringify(message)).toString();
309
+ setJsonParams(account_label_data, json_data_path);
310
+ delete this.account_passwords[account_label];
311
+
312
+ return {
313
+ status: true,
314
+ output: "Password reset successfully"
315
+ };
316
+ }
317
+
318
+ let pp_ciphertext_data = decryptMessage(account_label_data.pp_ciphertexts, this.account_passwords[account_label]);
319
+ account_label_data.pp_ciphertexts = encryptMessage(pp_ciphertext_data, new_password);
320
+ let message = {
321
+ slat: new_password,
322
+ data: account_label_data.pp_ciphertexts
323
+ };
324
+ account_label_data.signature = CryptoJS.SHA256(JSON.stringify(message)).toString();
325
+
326
+ let accounts = account_label_data.data;
327
+ for (let i = 0; i < accounts.length; i++) {
328
+ let ppk = decryptMessage(accounts[i].ciphertexts.evm.data, this.account_passwords[account_label]);
329
+ accounts[i].ciphertexts.evm.data = encryptMessage(ppk, new_password);
330
+ let ppp = decryptMessage(accounts[i].ciphertexts.solana.data, this.account_passwords[account_label]);
331
+ accounts[i].ciphertexts.solana.data = encryptMessage(ppp, new_password);
332
+ }
333
+
334
+ setJsonParams(account_label_data, json_data_path);
335
+ delete this.account_passwords[account_label];
336
+
337
+ return {
338
+ status: true,
339
+ output: "Password reset successfully"
340
+ }
341
+ }
342
+
343
+ public signTransaction(account_address: string, transaction: any, rpc_url: string) {
344
+ account_address = this.getAccountChecksumAddress(account_address);
345
+ if (this.getAccountChainType(account_address) === "unknown") {
346
+ return {
347
+ status: false,
348
+ output: "Chain type unknown"
349
+ }
350
+ }
351
+ if (this.getAccountChainType(account_address) === "evm") {
352
+ return this.signEvmTransaction(transaction, rpc_url);
353
+ }
354
+
355
+ return this.signSolanaTransaction(account_address, rpc_url, transaction);
356
+ }
357
+
358
+ public sendSignedTransaction(account_address: string, signed_transaction: any, rpc_url: string) {
359
+ account_address = this.getAccountChecksumAddress(account_address);
360
+ if (this.getAccountChainType(account_address) === "unknown") {
361
+ return {
362
+ status: false,
363
+ output: "Chain type unknown"
364
+ }
365
+ }
366
+ if (this.getAccountChainType(account_address) === "evm") {
367
+ return this.sendEvmTransaction(signed_transaction, rpc_url);
368
+ }
369
+ return this.sendSolanaTransaction(signed_transaction, rpc_url);
370
+ }
371
+
372
+ public signMessage(account_address: string, message: string) {
373
+ account_address = this.getAccountChecksumAddress(account_address);
374
+ if (this.getAccountChainType(account_address) === "unknown") {
375
+ return {
376
+ status: false,
377
+ output: "Chain type unknown"
378
+ }
379
+ }
380
+ if (this.getAccountChainType(account_address) === "evm") {
381
+ return {
382
+ status: true,
383
+ output: this.signEvmMessage(message, account_address)
384
+ };
385
+ }
386
+ return {
387
+ status: true,
388
+ output: this.signSolanaMessage(message, account_address)
389
+ };
390
+ }
391
+
392
+ public async simulateTransaction(account_address: string, rpc_url: string, transaction: any) {
393
+ const connection = new Connection(rpc_url);
394
+ const private_key = this.showPrivateKey(account_address);
395
+
396
+ const signer = Keypair.fromSecretKey(bs58.decode(private_key));
397
+ const simulate_transaction_result = await excutePromiseFunction(connection.simulateTransaction.bind(connection), [transaction, [signer]]);
398
+ return simulate_transaction_result;
399
+ }
400
+
401
+ private async signEvmTransaction(tx_params: any, rpc_url: string): Promise<Result> {
402
+ if (!this.account_passwords) {
403
+ return {
404
+ status: false,
405
+ output: "Please set password first"
406
+ }
407
+ }
408
+
409
+ let account = tx_params.from;
410
+ if (!tx_params.gas) {
411
+ let evm_gas_util = new EvmGasUtil(rpc_url);
412
+ let result = await evm_gas_util.getEvmGasLimit(tx_params, rpc_url);
413
+ if (!result.status) {
414
+ return result;
415
+ }
416
+ tx_params.gas = result.output;
417
+ }
418
+ if (!tx_params.gasPrice) {
419
+ let evm_gas_util = new EvmGasUtil(rpc_url);
420
+ let result = await evm_gas_util.getEvmGasPrice();
421
+ if (!result.status) {
422
+ return result;
423
+ }
424
+ tx_params.gasPrice = result.output;
425
+ }
426
+ const private_key = this.showPrivateKey(account);
427
+ const current_web3 = new Web3(rpc_url);
428
+ let excute_result = await excutePromiseFunction(current_web3.eth.accounts.signTransaction.bind(current_web3.eth.accounts), [tx_params, private_key]);
429
+ return excute_result;
430
+ }
431
+
432
+ private async sendEvmTransaction(signed_transaction: any, rpc_url: string) {
433
+ const current_web3 = new Web3(rpc_url);
434
+ let excute_result = await excutePromiseFunction(current_web3.eth.sendSignedTransaction.bind(current_web3.eth), [signed_transaction]);
435
+ return excute_result;
436
+ }
437
+
438
+ private async signSolanaTransaction(account_address: string, rpc_url: string, transaction: any) {
439
+ const private_key = this.showPrivateKey(account_address);
440
+ const owner = Keypair.fromSecretKey(bs58.decode(private_key));
441
+ const wallet = new Wallet(owner);
442
+ if (!("recentBlockhash" in transaction) || !transaction.recentBlockhash) {
443
+ const connection = new Connection(rpc_url);
444
+ let current_blockhash_result = await excutePromiseFunction(connection.getLatestBlockhash.bind(connection), []);
445
+ if (!current_blockhash_result.status) {
446
+ return {
447
+ status: false,
448
+ output: "Failed to get latest blockhash"
449
+ }
450
+ }
451
+ transaction.recentBlockhash = current_blockhash_result.output.blockhash;
452
+ }
453
+ transaction.feePayer = wallet.publicKey;
454
+ let excute_result = await excutePromiseFunction(wallet.signTransaction.bind(wallet), [transaction]);
455
+ return excute_result;
456
+ }
457
+
458
+ private async sendSolanaTransaction(signed_transaction: any, rpc_url: string) {
459
+ const connection = new Connection(rpc_url);
460
+ let excute_result = await excutePromiseFunction(connection.sendRawTransaction.bind(connection), [signed_transaction.serialize()]);
461
+ return excute_result;
462
+ }
463
+
464
+ private signEvmMessage(message: any, account_address: string) {
465
+ const private_key = this.showPrivateKey(account_address);
466
+ const signedData = web3.eth.accounts.sign(message, private_key);
467
+ return signedData.signature;
468
+ }
469
+
470
+ private signSolanaMessage(message: any, account_address: string) {
471
+ const private_key = this.showPrivateKey(account_address);
472
+ const signature = nacl.sign.detached(
473
+ hexToUint8Array(message),
474
+ bs58.decode(private_key)
475
+ );
476
+ return bs58.encode(signature);
477
+ }
478
+
479
+ private reload() {
480
+ if (!checkAccountLabelsExistence(this.setting_path, this.account_labels)) {
481
+ throw new Error("Account labels not exist");
482
+ }
483
+
484
+ let account_labels_size = this.account_labels.length;
485
+ for (let i = 0; i < account_labels_size; i++) {
486
+ let account_path = path.join(this.setting_path, `accounts/${this.account_labels[i]}.json`);
487
+ let account_data: any = JSON.parse(fs.readFileSync(account_path, 'utf8'));
488
+
489
+ if (account_data.account_type === "PK") {
490
+ this.account_ciphertexts[account_data.ciphertexts.address] = {
491
+ account_labels: this.account_labels[i],
492
+ data: account_data.ciphertexts.data
493
+ };
494
+ continue;
495
+ }
496
+
497
+ let account_data_len = account_data.data.length;
498
+
499
+ for (let j = 0; j < account_data_len; j++) {
500
+ this.account_ciphertexts[account_data.data[j].ciphertexts.evm.address] = {
501
+ account_labels: this.account_labels[i],
502
+ data: account_data.data[j].ciphertexts.evm.data
503
+ };
504
+ this.account_ciphertexts[account_data.data[j].ciphertexts.solana.address] = {
505
+ account_labels: this.account_labels[i],
506
+ data: account_data.data[j].ciphertexts.solana.data
507
+ };
508
+ }
509
+ }
510
+ }
511
+
512
+ private getAccountChecksumAddress(account_address: string) {
513
+ let account_type = this.getAccountChainType(account_address);
514
+ if (account_type === "evm") {
515
+ account_address = web3.utils.toChecksumAddress(account_address);
516
+ }
517
+ return account_address;
518
+ }
519
+
520
+ private getAccountAddressByPrivateKey(private_key: string, chain_id: string) {
521
+ let network_type = this.getNetworkTypeByChainId(chain_id);
522
+
523
+ if (network_type === "evm") {
524
+ return web3.eth.accounts.privateKeyToAccount(private_key).address;
525
+ }
526
+
527
+ return Keypair.fromSecretKey(bs58.decode(private_key)).publicKey.toBase58();
528
+ }
529
+
530
+ private getNetworkTypeByChainId(chain_id: string) {
531
+ if (chain_id === "901" || chain_id === "902" || chain_id === "903") {
532
+ return "solana";
533
+ }
534
+ return "evm";
535
+ }
536
+
537
+ private getSubAccount(chain_type: string, pp: string, account_index: number): {
538
+ status: boolean,
539
+ output: any
540
+ } {
541
+ if (!checkMnemonic(pp)) {
542
+ return {
543
+ status: false,
544
+ output: "Please set correct mnemonic"
545
+ }
546
+ };
547
+
548
+ if (chain_type === "solana") {
549
+ const seed = bip39.mnemonicToSeedSync(pp);
550
+ let hdWallet = solanaHdKey.fromMasterSeed(seed);
551
+ let keypair = Keypair.fromSeed(hdWallet.derive(`m/44'/501'/${account_index}'/0'`).privateKey);
552
+ return {
553
+ status: true,
554
+ output: {
555
+ address: keypair.publicKey.toBase58(),
556
+ pk: bs58.encode(keypair.secretKey),
557
+ }
558
+ }
559
+ }
560
+
561
+ if (chain_type === "evm") {
562
+ const seed = bip39.mnemonicToSeedSync(pp);
563
+ const hdWallet = evmHdKey.fromMasterSeed(seed);
564
+ const hdRoot = hdWallet.derivePath("m/44'/60'/0'/0");
565
+ const child = hdRoot.deriveChild(account_index);
566
+ const wallet: any = child.getWallet();
567
+
568
+ return {
569
+ status: true,
570
+ output: {
571
+ address: web3.utils.toChecksumAddress(wallet.getAddressString()),
572
+ pk: wallet.getPrivateKeyString()
573
+ }
574
+ }
575
+ }
576
+
577
+ return {
578
+ status: false,
579
+ output: "Unknown chain type"
580
+ }
581
+ }
582
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Account } from "./account";
2
+ const packageJson = require('../package.json');
3
+
4
+ export class WativeCore {
5
+ public static version = packageJson.version;
6
+ public account: Account;
7
+
8
+ public constructor(setting_path: string, account_labels: string[], account_passwords?: string[]) {
9
+ this.account = new Account(setting_path, account_labels, account_passwords);
10
+ }
11
+ }