@sidhujag/sysweb3-keyring 1.0.545 → 1.0.547
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/coverage/clover.xml +2875 -0
- package/coverage/coverage-final.json +29468 -0
- package/coverage/lcov-report/base.css +354 -0
- package/coverage/lcov-report/block-navigation.js +85 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +320 -0
- package/coverage/lcov-report/prettify.css +101 -0
- package/coverage/lcov-report/prettify.js +1008 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +191 -0
- package/coverage/lcov-report/src/index.html +276 -0
- package/coverage/lcov-report/src/index.ts.html +114 -0
- package/coverage/lcov-report/src/initial-state.ts.html +558 -0
- package/coverage/lcov-report/src/keyring-manager.ts.html +6279 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.html +178 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/index.ts.html +144 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/appClient.ts.html +1560 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/bip32.ts.html +276 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/buffertools.ts.html +495 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/clientCommands.ts.html +1138 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/index.html +363 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts.html +289 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkle.ts.html +486 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/merkleMap.ts.html +240 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/policy.ts.html +342 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/psbtv2.ts.html +2388 -0
- package/coverage/lcov-report/src/ledger/bitcoin_client/lib/varint.ts.html +453 -0
- package/coverage/lcov-report/src/ledger/consts.ts.html +177 -0
- package/coverage/lcov-report/src/ledger/index.html +216 -0
- package/coverage/lcov-report/src/ledger/index.ts.html +1371 -0
- package/coverage/lcov-report/src/ledger/utils.ts.html +102 -0
- package/coverage/lcov-report/src/signers.ts.html +591 -0
- package/coverage/lcov-report/src/storage.ts.html +198 -0
- package/coverage/lcov-report/src/transactions/ethereum.ts.html +5826 -0
- package/coverage/lcov-report/src/transactions/index.html +216 -0
- package/coverage/lcov-report/src/transactions/index.ts.html +93 -0
- package/coverage/lcov-report/src/transactions/syscoin.ts.html +1521 -0
- package/coverage/lcov-report/src/trezor/index.html +176 -0
- package/coverage/lcov-report/src/trezor/index.ts.html +2655 -0
- package/coverage/lcov-report/src/types.ts.html +1443 -0
- package/coverage/lcov-report/src/utils/derivation-paths.ts.html +486 -0
- package/coverage/lcov-report/src/utils/index.html +196 -0
- package/coverage/lcov-report/src/utils/psbt.ts.html +159 -0
- package/coverage/lcov-report/test/helpers/constants.ts.html +627 -0
- package/coverage/lcov-report/test/helpers/index.html +176 -0
- package/coverage/lcov.info +4832 -0
- package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/appClient.js +1 -124
- package/dist/cjs/ledger/bitcoin_client/lib/appClient.js.map +1 -0
- package/{cjs → dist/cjs}/transactions/ethereum.js +6 -2
- package/dist/cjs/transactions/ethereum.js.map +1 -0
- package/dist/package.json +50 -0
- package/{types → dist/types}/ledger/bitcoin_client/lib/appClient.d.ts +0 -6
- package/examples/basic-usage.js +140 -0
- package/jest.config.js +32 -0
- package/package.json +31 -13
- package/readme.md +201 -0
- package/src/declare.d.ts +7 -0
- package/src/errorUtils.ts +83 -0
- package/src/hardware-wallet-manager.ts +655 -0
- package/src/index.ts +12 -0
- package/src/initial-state.ts +108 -0
- package/src/keyring-manager.ts +2698 -0
- package/src/ledger/bitcoin_client/index.ts +19 -0
- package/src/ledger/bitcoin_client/lib/appClient.ts +405 -0
- package/src/ledger/bitcoin_client/lib/bip32.ts +61 -0
- package/src/ledger/bitcoin_client/lib/buffertools.ts +134 -0
- package/src/ledger/bitcoin_client/lib/clientCommands.ts +356 -0
- package/src/ledger/bitcoin_client/lib/constants.ts +12 -0
- package/src/ledger/bitcoin_client/lib/merkelizedPsbt.ts +65 -0
- package/src/ledger/bitcoin_client/lib/merkle.ts +136 -0
- package/src/ledger/bitcoin_client/lib/merkleMap.ts +49 -0
- package/src/ledger/bitcoin_client/lib/policy.ts +91 -0
- package/src/ledger/bitcoin_client/lib/psbtv2.ts +768 -0
- package/src/ledger/bitcoin_client/lib/varint.ts +120 -0
- package/src/ledger/consts.ts +3 -0
- package/src/ledger/index.ts +685 -0
- package/src/ledger/types.ts +74 -0
- package/src/network-utils.ts +99 -0
- package/src/providers.ts +345 -0
- package/src/signers.ts +158 -0
- package/src/storage.ts +63 -0
- package/src/transactions/__tests__/integration.test.ts +303 -0
- package/src/transactions/__tests__/syscoin.test.ts +409 -0
- package/src/transactions/ethereum.ts +2503 -0
- package/src/transactions/index.ts +2 -0
- package/src/transactions/syscoin.ts +542 -0
- package/src/trezor/index.ts +1050 -0
- package/src/types.ts +366 -0
- package/src/utils/derivation-paths.ts +133 -0
- package/src/utils/psbt.ts +24 -0
- package/src/utils.ts +191 -0
- package/test/README.md +158 -0
- package/test/__mocks__/ledger-mock.js +20 -0
- package/test/__mocks__/trezor-mock.js +75 -0
- package/test/cleanup-summary.md +167 -0
- package/test/helpers/README.md +78 -0
- package/test/helpers/constants.ts +79 -0
- package/test/helpers/setup.ts +714 -0
- package/test/integration/import-validation.spec.ts +588 -0
- package/test/unit/hardware/ledger.spec.ts +869 -0
- package/test/unit/hardware/trezor.spec.ts +828 -0
- package/test/unit/keyring-manager/account-management.spec.ts +970 -0
- package/test/unit/keyring-manager/import-watchonly.spec.ts +181 -0
- package/test/unit/keyring-manager/import-wif.spec.ts +126 -0
- package/test/unit/keyring-manager/initialization.spec.ts +782 -0
- package/test/unit/keyring-manager/key-derivation.spec.ts +996 -0
- package/test/unit/keyring-manager/security.spec.ts +505 -0
- package/test/unit/keyring-manager/state-management.spec.ts +375 -0
- package/test/unit/network/network-management.spec.ts +372 -0
- package/test/unit/transactions/ethereum-transactions.spec.ts +382 -0
- package/test/unit/transactions/syscoin-transactions.spec.ts +615 -0
- package/tsconfig.json +14 -0
- package/cjs/ledger/bitcoin_client/lib/appClient.js.map +0 -1
- package/cjs/transactions/ethereum.js.map +0 -1
- /package/{README.md → dist/README.md} +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js +0 -0
- /package/{cjs → dist/cjs}/errorUtils.js.map +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js +0 -0
- /package/{cjs → dist/cjs}/hardware-wallet-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/index.js +0 -0
- /package/{cjs → dist/cjs}/index.js.map +0 -0
- /package/{cjs → dist/cjs}/initial-state.js +0 -0
- /package/{cjs → dist/cjs}/initial-state.js.map +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js +0 -0
- /package/{cjs → dist/cjs}/keyring-manager.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/bip32.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/buffertools.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/clientCommands.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/constants.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkelizedPsbt.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkle.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/merkleMap.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/policy.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/psbtv2.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js +0 -0
- /package/{cjs → dist/cjs}/ledger/bitcoin_client/lib/varint.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js +0 -0
- /package/{cjs → dist/cjs}/ledger/consts.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js +0 -0
- /package/{cjs → dist/cjs}/ledger/index.js.map +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js +0 -0
- /package/{cjs → dist/cjs}/ledger/types.js.map +0 -0
- /package/{cjs → dist/cjs}/network-utils.js +0 -0
- /package/{cjs → dist/cjs}/network-utils.js.map +0 -0
- /package/{cjs → dist/cjs}/providers.js +0 -0
- /package/{cjs → dist/cjs}/providers.js.map +0 -0
- /package/{cjs → dist/cjs}/signers.js +0 -0
- /package/{cjs → dist/cjs}/signers.js.map +0 -0
- /package/{cjs → dist/cjs}/storage.js +0 -0
- /package/{cjs → dist/cjs}/storage.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/integration.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js +0 -0
- /package/{cjs → dist/cjs}/transactions/__tests__/syscoin.test.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js +0 -0
- /package/{cjs → dist/cjs}/transactions/index.js.map +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js +0 -0
- /package/{cjs → dist/cjs}/transactions/syscoin.js.map +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js +0 -0
- /package/{cjs → dist/cjs}/trezor/index.js.map +0 -0
- /package/{cjs → dist/cjs}/types.js +0 -0
- /package/{cjs → dist/cjs}/types.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js +0 -0
- /package/{cjs → dist/cjs}/utils/derivation-paths.js.map +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js +0 -0
- /package/{cjs → dist/cjs}/utils/psbt.js.map +0 -0
- /package/{cjs → dist/cjs}/utils.js +0 -0
- /package/{cjs → dist/cjs}/utils.js.map +0 -0
- /package/{types → dist/types}/errorUtils.d.ts +0 -0
- /package/{types → dist/types}/hardware-wallet-manager.d.ts +0 -0
- /package/{types → dist/types}/index.d.ts +0 -0
- /package/{types → dist/types}/initial-state.d.ts +0 -0
- /package/{types → dist/types}/keyring-manager.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/bip32.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/buffertools.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/clientCommands.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/constants.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkelizedPsbt.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkle.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/merkleMap.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/policy.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/psbtv2.d.ts +0 -0
- /package/{types → dist/types}/ledger/bitcoin_client/lib/varint.d.ts +0 -0
- /package/{types → dist/types}/ledger/consts.d.ts +0 -0
- /package/{types → dist/types}/ledger/index.d.ts +0 -0
- /package/{types → dist/types}/ledger/types.d.ts +0 -0
- /package/{types → dist/types}/network-utils.d.ts +0 -0
- /package/{types → dist/types}/providers.d.ts +0 -0
- /package/{types → dist/types}/signers.d.ts +0 -0
- /package/{types → dist/types}/storage.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/integration.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/__tests__/syscoin.test.d.ts +0 -0
- /package/{types → dist/types}/transactions/ethereum.d.ts +0 -0
- /package/{types → dist/types}/transactions/index.d.ts +0 -0
- /package/{types → dist/types}/transactions/syscoin.d.ts +0 -0
- /package/{types → dist/types}/trezor/index.d.ts +0 -0
- /package/{types → dist/types}/types.d.ts +0 -0
- /package/{types → dist/types}/utils/derivation-paths.d.ts +0 -0
- /package/{types → dist/types}/utils/psbt.d.ts +0 -0
- /package/{types → dist/types}/utils.d.ts +0 -0
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
import { Wallet } from '@ethersproject/wallet';
|
|
2
|
+
import { INetworkType } from '@sidhujag/sysweb3-network';
|
|
3
|
+
|
|
4
|
+
import { KeyringManager, KeyringAccountType } from '../../../src';
|
|
5
|
+
import { FAKE_PASSWORD, PEACE_SEED_PHRASE } from '../../helpers/constants';
|
|
6
|
+
import { setupMocks } from '../../helpers/setup';
|
|
7
|
+
|
|
8
|
+
describe('KeyringManager - Key Derivation', () => {
|
|
9
|
+
let keyringManager: KeyringManager;
|
|
10
|
+
let mockVaultStateGetter: jest.Mock;
|
|
11
|
+
let currentVaultState: any;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
setupMocks();
|
|
15
|
+
// Set up vault-keys that would normally be created by Pali's MainController
|
|
16
|
+
await setupTestVault(FAKE_PASSWORD);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('EVM Key Derivation', () => {
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
// Set up EVM vault state
|
|
22
|
+
currentVaultState = createMockVaultState({
|
|
23
|
+
activeAccountId: 0,
|
|
24
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
25
|
+
networkType: INetworkType.Ethereum,
|
|
26
|
+
chainId: 1,
|
|
27
|
+
});
|
|
28
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
29
|
+
|
|
30
|
+
keyringManager = await KeyringManager.createInitialized(
|
|
31
|
+
PEACE_SEED_PHRASE,
|
|
32
|
+
FAKE_PASSWORD,
|
|
33
|
+
mockVaultStateGetter
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should derive deterministic EVM addresses', async () => {
|
|
38
|
+
// Get the actual derived addresses for PEACE_SEED_PHRASE (standard test phrase)
|
|
39
|
+
const account0 = keyringManager.getActiveAccount().activeAccount;
|
|
40
|
+
const address0 = account0.address;
|
|
41
|
+
|
|
42
|
+
// Add and check more accounts - verify they are deterministic
|
|
43
|
+
const account1 = await keyringManager.addNewAccount();
|
|
44
|
+
const address1 = account1.address;
|
|
45
|
+
|
|
46
|
+
// Update vault state with new account
|
|
47
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
|
|
48
|
+
id: 1,
|
|
49
|
+
label: 'Account 2',
|
|
50
|
+
address: account1.address,
|
|
51
|
+
xpub: account1.xpub,
|
|
52
|
+
xprv: '',
|
|
53
|
+
isImported: false,
|
|
54
|
+
isTrezorWallet: false,
|
|
55
|
+
isLedgerWallet: false,
|
|
56
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
57
|
+
assets: { syscoin: [], ethereum: [] },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const account2 = await keyringManager.addNewAccount();
|
|
61
|
+
const address2 = account2.address;
|
|
62
|
+
|
|
63
|
+
// Update vault state with new account
|
|
64
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][2] = {
|
|
65
|
+
id: 2,
|
|
66
|
+
label: 'Account 3',
|
|
67
|
+
address: account2.address,
|
|
68
|
+
xpub: account2.xpub,
|
|
69
|
+
xprv: '',
|
|
70
|
+
isImported: false,
|
|
71
|
+
isTrezorWallet: false,
|
|
72
|
+
isLedgerWallet: false,
|
|
73
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
74
|
+
assets: { syscoin: [], ethereum: [] },
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Verify addresses are valid Ethereum addresses
|
|
78
|
+
expect(address0.startsWith('0x')).toBe(true);
|
|
79
|
+
expect(address0).toHaveLength(42);
|
|
80
|
+
expect(address1.startsWith('0x')).toBe(true);
|
|
81
|
+
expect(address1).toHaveLength(42);
|
|
82
|
+
expect(address2.startsWith('0x')).toBe(true);
|
|
83
|
+
expect(address2).toHaveLength(42);
|
|
84
|
+
|
|
85
|
+
// All addresses should be unique
|
|
86
|
+
expect(address0).not.toBe(address1);
|
|
87
|
+
expect(address1).not.toBe(address2);
|
|
88
|
+
expect(address2).not.toBe(address0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should derive correct private keys for EVM accounts', async () => {
|
|
92
|
+
// The vault state now properly includes encrypted xprv values
|
|
93
|
+
// No manual updates needed - just test the functionality
|
|
94
|
+
|
|
95
|
+
// Get private key for account 0
|
|
96
|
+
const privateKey0 = await keyringManager.getPrivateKeyByAccountId(
|
|
97
|
+
0,
|
|
98
|
+
KeyringAccountType.HDAccount,
|
|
99
|
+
FAKE_PASSWORD
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Verify it derives the correct address
|
|
103
|
+
const wallet = new Wallet(privateKey0);
|
|
104
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
105
|
+
expect(wallet.address.toLowerCase()).toBe(account.address.toLowerCase());
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should use standard EVM derivation path', async () => {
|
|
109
|
+
// EVM uses m/44'/60'/0'/0/index
|
|
110
|
+
// This is tested indirectly by verifying known addresses
|
|
111
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
112
|
+
expect(account.address).toBeDefined();
|
|
113
|
+
expect(account.address.startsWith('0x')).toBe(true);
|
|
114
|
+
expect(account.address).toHaveLength(42);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle multiple accounts with consistent derivation', async () => {
|
|
118
|
+
// Add multiple accounts
|
|
119
|
+
const accounts = [keyringManager.getActiveAccount().activeAccount];
|
|
120
|
+
for (let i = 0; i < 5; i++) {
|
|
121
|
+
const account = await keyringManager.addNewAccount();
|
|
122
|
+
accounts.push(account);
|
|
123
|
+
|
|
124
|
+
// Update vault state with new account
|
|
125
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][account.id] = {
|
|
126
|
+
id: account.id,
|
|
127
|
+
label: `Account ${account.id + 1}`,
|
|
128
|
+
address: account.address,
|
|
129
|
+
xpub: account.xpub,
|
|
130
|
+
xprv: '',
|
|
131
|
+
isImported: false,
|
|
132
|
+
isTrezorWallet: false,
|
|
133
|
+
isLedgerWallet: false,
|
|
134
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
135
|
+
assets: { syscoin: [], ethereum: [] },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Verify all addresses are unique
|
|
140
|
+
const addresses = accounts.map((a) => a.address);
|
|
141
|
+
const uniqueAddresses = new Set(addresses);
|
|
142
|
+
expect(uniqueAddresses.size).toBe(addresses.length);
|
|
143
|
+
|
|
144
|
+
// Verify sequential IDs
|
|
145
|
+
accounts.forEach((account, index) => {
|
|
146
|
+
expect(account.id).toBe(index);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should derive same addresses from same seed across instances', async () => {
|
|
151
|
+
// Set up second EVM vault state
|
|
152
|
+
const vault2State = createMockVaultState({
|
|
153
|
+
activeAccountId: 0,
|
|
154
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
155
|
+
networkType: INetworkType.Ethereum,
|
|
156
|
+
chainId: 1,
|
|
157
|
+
});
|
|
158
|
+
const vault2StateGetter = jest.fn(() => vault2State);
|
|
159
|
+
|
|
160
|
+
// Create second keyring with same seed
|
|
161
|
+
const keyring2 = await KeyringManager.createInitialized(
|
|
162
|
+
PEACE_SEED_PHRASE,
|
|
163
|
+
FAKE_PASSWORD,
|
|
164
|
+
vault2StateGetter
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Add accounts to both
|
|
168
|
+
const accounts1 = [keyringManager.getActiveAccount().activeAccount];
|
|
169
|
+
const accounts2 = [keyring2.getActiveAccount().activeAccount];
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < 3; i++) {
|
|
172
|
+
const account1 = await keyringManager.addNewAccount();
|
|
173
|
+
const account2 = await keyring2.addNewAccount();
|
|
174
|
+
accounts1.push(account1);
|
|
175
|
+
accounts2.push(account2);
|
|
176
|
+
|
|
177
|
+
// Update vault states with new accounts
|
|
178
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][account1.id] =
|
|
179
|
+
{
|
|
180
|
+
id: account1.id,
|
|
181
|
+
label: `Account ${account1.id + 1}`,
|
|
182
|
+
address: account1.address,
|
|
183
|
+
xpub: account1.xpub,
|
|
184
|
+
xprv: '',
|
|
185
|
+
isImported: false,
|
|
186
|
+
isTrezorWallet: false,
|
|
187
|
+
isLedgerWallet: false,
|
|
188
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
189
|
+
assets: { syscoin: [], ethereum: [] },
|
|
190
|
+
};
|
|
191
|
+
vault2State.accounts[KeyringAccountType.HDAccount][account2.id] = {
|
|
192
|
+
id: account2.id,
|
|
193
|
+
label: `Account ${account2.id + 1}`,
|
|
194
|
+
address: account2.address,
|
|
195
|
+
xpub: account2.xpub,
|
|
196
|
+
xprv: '',
|
|
197
|
+
isImported: false,
|
|
198
|
+
isTrezorWallet: false,
|
|
199
|
+
isLedgerWallet: false,
|
|
200
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
201
|
+
assets: { syscoin: [], ethereum: [] },
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Verify addresses match
|
|
206
|
+
for (let i = 0; i < accounts1.length; i++) {
|
|
207
|
+
expect(accounts1[i].address).toBe(accounts2[i].address);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('UTXO Key Derivation', () => {
|
|
213
|
+
beforeEach(async () => {
|
|
214
|
+
// Set up UTXO vault state
|
|
215
|
+
currentVaultState = createMockVaultState({
|
|
216
|
+
activeAccountId: 0,
|
|
217
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
218
|
+
networkType: INetworkType.Syscoin,
|
|
219
|
+
chainId: 57,
|
|
220
|
+
});
|
|
221
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
222
|
+
|
|
223
|
+
keyringManager = await KeyringManager.createInitialized(
|
|
224
|
+
PEACE_SEED_PHRASE,
|
|
225
|
+
FAKE_PASSWORD,
|
|
226
|
+
mockVaultStateGetter
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should derive deterministic UTXO addresses', async () => {
|
|
231
|
+
// Check initial account has Syscoin address format
|
|
232
|
+
const account0 = keyringManager.getActiveAccount().activeAccount;
|
|
233
|
+
expect(account0.address.match(/^(sys1|tsys1)/)).toBeTruthy();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should use BIP84 derivation for UTXO', async () => {
|
|
237
|
+
// The vault state now properly includes encrypted xprv values
|
|
238
|
+
// No manual updates needed - just test the functionality
|
|
239
|
+
|
|
240
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
241
|
+
// BIP84 (native segwit) addresses start with 'sys1' or 'tsys1' for Syscoin
|
|
242
|
+
expect(account.address.match(/^(sys1|tsys1)/)).toBeTruthy();
|
|
243
|
+
expect(account.xpub).toBeDefined();
|
|
244
|
+
expect(
|
|
245
|
+
account.xpub.startsWith('zpub') || account.xpub.startsWith('xpub')
|
|
246
|
+
).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should derive different addresses for different account indices', async () => {
|
|
250
|
+
const addresses = [
|
|
251
|
+
keyringManager.getActiveAccount().activeAccount.address,
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
// Add more accounts
|
|
255
|
+
for (let i = 0; i < 3; i++) {
|
|
256
|
+
const newAccount = await keyringManager.addNewAccount();
|
|
257
|
+
addresses.push(newAccount.address);
|
|
258
|
+
|
|
259
|
+
// Update vault state with new account
|
|
260
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][
|
|
261
|
+
newAccount.id
|
|
262
|
+
] = {
|
|
263
|
+
id: newAccount.id,
|
|
264
|
+
label: `Account ${newAccount.id + 1}`,
|
|
265
|
+
address: newAccount.address,
|
|
266
|
+
xpub: newAccount.xpub,
|
|
267
|
+
xprv: '',
|
|
268
|
+
isImported: false,
|
|
269
|
+
isTrezorWallet: false,
|
|
270
|
+
isLedgerWallet: false,
|
|
271
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
272
|
+
assets: { syscoin: [], ethereum: [] },
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// All addresses should be unique
|
|
277
|
+
const uniqueAddresses = new Set(addresses);
|
|
278
|
+
expect(uniqueAddresses.size).toBe(addresses.length);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should handle testnet derivation correctly', async () => {
|
|
282
|
+
// Set up testnet vault state
|
|
283
|
+
const testnetVaultState = createMockVaultState({
|
|
284
|
+
activeAccountId: 0,
|
|
285
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
286
|
+
networkType: INetworkType.Syscoin,
|
|
287
|
+
chainId: 5700,
|
|
288
|
+
});
|
|
289
|
+
const testnetVaultStateGetter = jest.fn(() => testnetVaultState);
|
|
290
|
+
|
|
291
|
+
// Create testnet keyring
|
|
292
|
+
const testnetKeyring = await KeyringManager.createInitialized(
|
|
293
|
+
PEACE_SEED_PHRASE,
|
|
294
|
+
FAKE_PASSWORD,
|
|
295
|
+
testnetVaultStateGetter
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const account = testnetKeyring.getActiveAccount().activeAccount;
|
|
299
|
+
// Testnet addresses should start with 'tsys1' or use default format
|
|
300
|
+
expect(account.address.match(/^(sys1|tsys1)/)).toBeTruthy();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('Cross-Chain Derivation Consistency', () => {
|
|
305
|
+
it('should derive different addresses for EVM vs UTXO from same seed', async () => {
|
|
306
|
+
// Set up EVM vault state
|
|
307
|
+
const evmVaultState = createMockVaultState({
|
|
308
|
+
activeAccountId: 0,
|
|
309
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
310
|
+
networkType: INetworkType.Ethereum,
|
|
311
|
+
chainId: 1,
|
|
312
|
+
});
|
|
313
|
+
const evmVaultStateGetter = jest.fn(() => evmVaultState);
|
|
314
|
+
|
|
315
|
+
// EVM keyring
|
|
316
|
+
const evmKeyring = await KeyringManager.createInitialized(
|
|
317
|
+
PEACE_SEED_PHRASE,
|
|
318
|
+
FAKE_PASSWORD,
|
|
319
|
+
evmVaultStateGetter
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// Set up UTXO vault state
|
|
323
|
+
const utxoVaultState = createMockVaultState({
|
|
324
|
+
activeAccountId: 0,
|
|
325
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
326
|
+
networkType: INetworkType.Syscoin,
|
|
327
|
+
chainId: 57,
|
|
328
|
+
});
|
|
329
|
+
const utxoVaultStateGetter = jest.fn(() => utxoVaultState);
|
|
330
|
+
|
|
331
|
+
// UTXO keyring
|
|
332
|
+
const utxoKeyring = await KeyringManager.createInitialized(
|
|
333
|
+
PEACE_SEED_PHRASE,
|
|
334
|
+
FAKE_PASSWORD,
|
|
335
|
+
utxoVaultStateGetter
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const evmAccount = evmKeyring.getActiveAccount().activeAccount;
|
|
339
|
+
const utxoAccount = utxoKeyring.getActiveAccount().activeAccount;
|
|
340
|
+
|
|
341
|
+
// Addresses should be completely different
|
|
342
|
+
expect(evmAccount.address).not.toBe(utxoAccount.address);
|
|
343
|
+
expect(evmAccount.address.startsWith('0x')).toBe(true);
|
|
344
|
+
expect(utxoAccount.address.match(/^(sys1|tsys1)/)).toBeTruthy();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should maintain deterministic derivation after re-encryption', async () => {
|
|
348
|
+
// Store original addresses
|
|
349
|
+
const originalAddresses = [
|
|
350
|
+
keyringManager.getActiveAccount().activeAccount.address,
|
|
351
|
+
];
|
|
352
|
+
for (let i = 0; i < 2; i++) {
|
|
353
|
+
const account = await keyringManager.addNewAccount();
|
|
354
|
+
originalAddresses.push(account.address);
|
|
355
|
+
|
|
356
|
+
// Update vault state with new account
|
|
357
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][account.id] = {
|
|
358
|
+
id: account.id,
|
|
359
|
+
label: `Account ${account.id + 1}`,
|
|
360
|
+
address: account.address,
|
|
361
|
+
xpub: account.xpub,
|
|
362
|
+
xprv: '',
|
|
363
|
+
isImported: false,
|
|
364
|
+
isTrezorWallet: false,
|
|
365
|
+
isLedgerWallet: false,
|
|
366
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
367
|
+
assets: { syscoin: [], ethereum: [] },
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Lock and unlock (simulating re-encryption scenario)
|
|
372
|
+
keyringManager.lockWallet();
|
|
373
|
+
await keyringManager.unlock(FAKE_PASSWORD);
|
|
374
|
+
|
|
375
|
+
// Verify addresses remain the same
|
|
376
|
+
const account0 = keyringManager.getAccountById(
|
|
377
|
+
0,
|
|
378
|
+
KeyringAccountType.HDAccount
|
|
379
|
+
);
|
|
380
|
+
expect(account0.address).toBe(originalAddresses[0]);
|
|
381
|
+
|
|
382
|
+
const account1 = keyringManager.getAccountById(
|
|
383
|
+
1,
|
|
384
|
+
KeyringAccountType.HDAccount
|
|
385
|
+
);
|
|
386
|
+
expect(account1.address).toBe(originalAddresses[1]);
|
|
387
|
+
|
|
388
|
+
const account2 = keyringManager.getAccountById(
|
|
389
|
+
2,
|
|
390
|
+
KeyringAccountType.HDAccount
|
|
391
|
+
);
|
|
392
|
+
expect(account2.address).toBe(originalAddresses[2]);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe('Imported Key Handling', () => {
|
|
397
|
+
it('should not derive keys for imported EVM accounts', async () => {
|
|
398
|
+
// Set up EVM vault state
|
|
399
|
+
currentVaultState = createMockVaultState({
|
|
400
|
+
activeAccountId: 0,
|
|
401
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
402
|
+
networkType: INetworkType.Ethereum,
|
|
403
|
+
chainId: 1,
|
|
404
|
+
});
|
|
405
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
406
|
+
|
|
407
|
+
keyringManager = await KeyringManager.createInitialized(
|
|
408
|
+
PEACE_SEED_PHRASE,
|
|
409
|
+
FAKE_PASSWORD,
|
|
410
|
+
mockVaultStateGetter
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// Import a specific private key
|
|
414
|
+
const privateKey =
|
|
415
|
+
'0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
|
|
416
|
+
const imported = await keyringManager.importAccount(privateKey);
|
|
417
|
+
|
|
418
|
+
// Update vault state with imported account
|
|
419
|
+
currentVaultState.accounts[KeyringAccountType.Imported][imported.id] = {
|
|
420
|
+
id: imported.id,
|
|
421
|
+
label: 'Imported 1',
|
|
422
|
+
address: imported.address,
|
|
423
|
+
xpub: imported.xpub,
|
|
424
|
+
xprv: imported.xprv,
|
|
425
|
+
isImported: true,
|
|
426
|
+
isTrezorWallet: false,
|
|
427
|
+
isLedgerWallet: false,
|
|
428
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
429
|
+
assets: { syscoin: [], ethereum: [] },
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Verify it uses the exact key, not derived
|
|
433
|
+
const retrievedKey = await keyringManager.getPrivateKeyByAccountId(
|
|
434
|
+
imported.id,
|
|
435
|
+
KeyringAccountType.Imported,
|
|
436
|
+
FAKE_PASSWORD
|
|
437
|
+
);
|
|
438
|
+
expect(retrievedKey).toBe(privateKey);
|
|
439
|
+
|
|
440
|
+
// Verify address matches the private key
|
|
441
|
+
const wallet = new Wallet(privateKey);
|
|
442
|
+
expect(imported.address.toLowerCase()).toBe(wallet.address.toLowerCase());
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should handle imported zprv for UTXO', async () => {
|
|
446
|
+
// Set up UTXO vault state
|
|
447
|
+
currentVaultState = createMockVaultState({
|
|
448
|
+
activeAccountId: 0,
|
|
449
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
450
|
+
networkType: INetworkType.Syscoin,
|
|
451
|
+
chainId: 57,
|
|
452
|
+
});
|
|
453
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
454
|
+
|
|
455
|
+
keyringManager = await KeyringManager.createInitialized(
|
|
456
|
+
PEACE_SEED_PHRASE,
|
|
457
|
+
FAKE_PASSWORD,
|
|
458
|
+
mockVaultStateGetter
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
// Import a zprv
|
|
462
|
+
const zprv =
|
|
463
|
+
'zprvAdGDwa3WySqQoVwVSbYRMKxDhSXpK2wW6wDjekCMdm7TaQ3igf52xRRjYghTvnFurtMm6CMgQivEDJs5ixGSnTtv8usFmkAoTe6XCF5hnpR';
|
|
464
|
+
const imported = await keyringManager.importAccount(zprv);
|
|
465
|
+
|
|
466
|
+
// Update vault state with imported account
|
|
467
|
+
currentVaultState.accounts[KeyringAccountType.Imported][imported.id] = {
|
|
468
|
+
id: imported.id,
|
|
469
|
+
label: 'Imported 1',
|
|
470
|
+
address: imported.address,
|
|
471
|
+
xpub: imported.xpub,
|
|
472
|
+
xprv: imported.xprv,
|
|
473
|
+
isImported: true,
|
|
474
|
+
isTrezorWallet: false,
|
|
475
|
+
isLedgerWallet: false,
|
|
476
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
477
|
+
assets: { syscoin: [], ethereum: [] },
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// Verify it's treated as imported, not derived from main seed
|
|
481
|
+
expect(imported.isImported).toBe(true);
|
|
482
|
+
|
|
483
|
+
// Retrieve and verify the zprv
|
|
484
|
+
const retrievedKey = await keyringManager.getPrivateKeyByAccountId(
|
|
485
|
+
imported.id,
|
|
486
|
+
KeyringAccountType.Imported,
|
|
487
|
+
FAKE_PASSWORD
|
|
488
|
+
);
|
|
489
|
+
expect(retrievedKey).toBe(zprv);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
describe('Edge Cases', () => {
|
|
494
|
+
it('should handle account creation when no HD signer exists', async () => {
|
|
495
|
+
keyringManager = new KeyringManager();
|
|
496
|
+
|
|
497
|
+
// Set up mock vault state getter for initializeWalletSecurely
|
|
498
|
+
currentVaultState = createMockVaultState({
|
|
499
|
+
activeAccountId: 0,
|
|
500
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
501
|
+
networkType: INetworkType.Ethereum,
|
|
502
|
+
chainId: 1,
|
|
503
|
+
});
|
|
504
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
505
|
+
keyringManager.setVaultStateGetter(mockVaultStateGetter);
|
|
506
|
+
|
|
507
|
+
await keyringManager.initializeWalletSecurely(
|
|
508
|
+
PEACE_SEED_PHRASE,
|
|
509
|
+
FAKE_PASSWORD
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
// Should create HD signer on first account access
|
|
513
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
514
|
+
expect(account).toBeDefined();
|
|
515
|
+
expect(account.address).toBeDefined();
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('should maintain key derivation consistency with special characters in labels', async () => {
|
|
519
|
+
// Set up EVM vault state
|
|
520
|
+
currentVaultState = createMockVaultState({
|
|
521
|
+
activeAccountId: 0,
|
|
522
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
523
|
+
networkType: INetworkType.Ethereum,
|
|
524
|
+
chainId: 1,
|
|
525
|
+
});
|
|
526
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
527
|
+
|
|
528
|
+
keyringManager = await KeyringManager.createInitialized(
|
|
529
|
+
PEACE_SEED_PHRASE,
|
|
530
|
+
FAKE_PASSWORD,
|
|
531
|
+
mockVaultStateGetter
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// Labels should not affect derivation
|
|
535
|
+
const account1 = await keyringManager.addNewAccount(
|
|
536
|
+
'Account with 特殊文字 and émojis 🚀'
|
|
537
|
+
);
|
|
538
|
+
const address1 = account1.address;
|
|
539
|
+
|
|
540
|
+
// Update vault state
|
|
541
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][account1.id] = {
|
|
542
|
+
id: account1.id,
|
|
543
|
+
label: 'Account with 特殊文字 and émojis 🚀',
|
|
544
|
+
address: account1.address,
|
|
545
|
+
xpub: account1.xpub,
|
|
546
|
+
xprv: '',
|
|
547
|
+
isImported: false,
|
|
548
|
+
isTrezorWallet: false,
|
|
549
|
+
isLedgerWallet: false,
|
|
550
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
551
|
+
assets: { syscoin: [], ethereum: [] },
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// Set up second EVM vault state
|
|
555
|
+
const vault2State = createMockVaultState({
|
|
556
|
+
activeAccountId: 0,
|
|
557
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
558
|
+
networkType: INetworkType.Ethereum,
|
|
559
|
+
chainId: 1,
|
|
560
|
+
});
|
|
561
|
+
const vault2StateGetter = jest.fn(() => vault2State);
|
|
562
|
+
|
|
563
|
+
// Create new keyring and add account with different label
|
|
564
|
+
const keyring2 = await KeyringManager.createInitialized(
|
|
565
|
+
PEACE_SEED_PHRASE,
|
|
566
|
+
FAKE_PASSWORD,
|
|
567
|
+
vault2StateGetter
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
const account2 = await keyring2.addNewAccount('Different Label');
|
|
571
|
+
|
|
572
|
+
// Same index should give same address regardless of label
|
|
573
|
+
expect(account2.address).toBe(address1);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('should handle maximum safe integer account indices', () => {
|
|
577
|
+
// This is more of a sanity check - we shouldn't realistically reach this
|
|
578
|
+
const accounts = {};
|
|
579
|
+
const maxSafeIndex = Number.MAX_SAFE_INTEGER;
|
|
580
|
+
|
|
581
|
+
// Simulate account with the maximum safe index
|
|
582
|
+
accounts[maxSafeIndex] = { id: maxSafeIndex };
|
|
583
|
+
|
|
584
|
+
// getNextAccountId should handle this gracefully
|
|
585
|
+
// (testing the concept, not the actual implementation)
|
|
586
|
+
expect(() => {
|
|
587
|
+
const nextId = maxSafeIndex + 1;
|
|
588
|
+
if (nextId > Number.MAX_SAFE_INTEGER) {
|
|
589
|
+
throw new Error('Account index overflow');
|
|
590
|
+
}
|
|
591
|
+
}).toThrow('Account index overflow');
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
describe('Address Public Key and BIP32 Path Methods', () => {
|
|
596
|
+
let mockFetchBackendAccount: jest.Mock;
|
|
597
|
+
|
|
598
|
+
beforeEach(async () => {
|
|
599
|
+
// Set up UTXO vault state for testing these methods
|
|
600
|
+
currentVaultState = createMockVaultState({
|
|
601
|
+
activeAccountId: 0,
|
|
602
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
603
|
+
networkType: INetworkType.Syscoin,
|
|
604
|
+
chainId: 57,
|
|
605
|
+
});
|
|
606
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
607
|
+
|
|
608
|
+
keyringManager = await KeyringManager.createInitialized(
|
|
609
|
+
PEACE_SEED_PHRASE,
|
|
610
|
+
FAKE_PASSWORD,
|
|
611
|
+
mockVaultStateGetter
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
// Mock the fetchBackendAccount to return specific tokens with paths
|
|
615
|
+
const syscoinjs = require('syscoinjs-lib');
|
|
616
|
+
mockFetchBackendAccount = syscoinjs.utils
|
|
617
|
+
.fetchBackendAccount as jest.Mock;
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
describe('getCurrentAddressPubkey', () => {
|
|
621
|
+
it('should return public key for receiving address', async () => {
|
|
622
|
+
// Mock backend response with tokens
|
|
623
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
624
|
+
balance: 100000000,
|
|
625
|
+
tokens: [
|
|
626
|
+
{
|
|
627
|
+
path: "m/84'/57'/0'/0/0",
|
|
628
|
+
transfers: '1',
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
path: "m/84'/57'/0'/0/1",
|
|
632
|
+
transfers: '1',
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
path: "m/84'/57'/0'/1/0",
|
|
636
|
+
transfers: '1',
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
642
|
+
const pubkey = await keyringManager.getCurrentAddressPubkey(
|
|
643
|
+
account.xpub,
|
|
644
|
+
false // receiving address
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
// Verify the public key is a valid hex string
|
|
648
|
+
expect(pubkey).toMatch(/^[0-9a-fA-F]{66}$/); // 33 bytes = 66 hex chars
|
|
649
|
+
expect(pubkey.length).toBe(66); // Compressed public key
|
|
650
|
+
|
|
651
|
+
// Verify fetchBackendAccount was called with correct params
|
|
652
|
+
expect(mockFetchBackendAccount).toHaveBeenCalledWith(
|
|
653
|
+
expect.any(String), // blockbook URL
|
|
654
|
+
account.xpub,
|
|
655
|
+
'tokens=used&details=tokens',
|
|
656
|
+
true,
|
|
657
|
+
undefined
|
|
658
|
+
);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('should return public key for change address', async () => {
|
|
662
|
+
// Mock backend response with tokens
|
|
663
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
664
|
+
balance: 100000000,
|
|
665
|
+
tokens: [
|
|
666
|
+
{
|
|
667
|
+
path: "m/84'/57'/0'/0/0",
|
|
668
|
+
transfers: '1',
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
path: "m/84'/57'/0'/1/0",
|
|
672
|
+
transfers: '1',
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
path: "m/84'/57'/0'/1/1",
|
|
676
|
+
transfers: '1',
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
682
|
+
const pubkey = await keyringManager.getCurrentAddressPubkey(
|
|
683
|
+
account.xpub,
|
|
684
|
+
true // change address
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
// Verify the public key is a valid hex string
|
|
688
|
+
expect(pubkey).toMatch(/^[0-9a-fA-F]{66}$/);
|
|
689
|
+
expect(pubkey.length).toBe(66);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it('should use correct index based on token paths', async () => {
|
|
693
|
+
// Mock backend response with specific token paths
|
|
694
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
695
|
+
balance: 100000000,
|
|
696
|
+
tokens: [
|
|
697
|
+
{
|
|
698
|
+
path: "m/84'/57'/0'/0/5", // receiving index 5
|
|
699
|
+
transfers: '1',
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
path: "m/84'/57'/0'/1/3", // change index 3
|
|
703
|
+
transfers: '1',
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
709
|
+
|
|
710
|
+
// Get public key for receiving address - should use index 6 (5 + 1)
|
|
711
|
+
const receivingPubkey = await keyringManager.getCurrentAddressPubkey(
|
|
712
|
+
account.xpub,
|
|
713
|
+
false
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
// Get public key for change address - should use index 4 (3 + 1)
|
|
717
|
+
const changePubkey = await keyringManager.getCurrentAddressPubkey(
|
|
718
|
+
account.xpub,
|
|
719
|
+
true
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
// They should be different
|
|
723
|
+
expect(receivingPubkey).not.toBe(changePubkey);
|
|
724
|
+
expect(receivingPubkey).toMatch(/^[0-9a-fA-F]{66}$/);
|
|
725
|
+
expect(changePubkey).toMatch(/^[0-9a-fA-F]{66}$/);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
describe('getCurrentAddressBip32Path', () => {
|
|
730
|
+
it('should return correct BIP32 path for receiving address', async () => {
|
|
731
|
+
// Mock backend response with tokens
|
|
732
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
733
|
+
balance: 100000000,
|
|
734
|
+
tokens: [
|
|
735
|
+
{
|
|
736
|
+
path: "m/84'/57'/0'/0/2",
|
|
737
|
+
transfers: '1',
|
|
738
|
+
},
|
|
739
|
+
],
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
743
|
+
const path = await keyringManager.getCurrentAddressBip32Path(
|
|
744
|
+
account.xpub,
|
|
745
|
+
false // receiving address
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
// Should return path for index 3 (2 + 1)
|
|
749
|
+
expect(path).toBe("m/84'/57'/0'/0/3");
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('should return correct BIP32 path for change address', async () => {
|
|
753
|
+
// Mock backend response with tokens
|
|
754
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
755
|
+
balance: 100000000,
|
|
756
|
+
tokens: [
|
|
757
|
+
{
|
|
758
|
+
path: "m/84'/57'/0'/1/4",
|
|
759
|
+
transfers: '1',
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
765
|
+
const path = await keyringManager.getCurrentAddressBip32Path(
|
|
766
|
+
account.xpub,
|
|
767
|
+
true // change address
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// Should return path for index 5 (4 + 1)
|
|
771
|
+
expect(path).toBe("m/84'/57'/0'/1/5");
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('should use account ID from active account', async () => {
|
|
775
|
+
// Add a new account
|
|
776
|
+
const newAccount = await keyringManager.addNewAccount();
|
|
777
|
+
|
|
778
|
+
// Update vault state with new account
|
|
779
|
+
currentVaultState.accounts[KeyringAccountType.HDAccount][1] = {
|
|
780
|
+
id: 1,
|
|
781
|
+
label: 'Account 2',
|
|
782
|
+
address: newAccount.address,
|
|
783
|
+
xpub: newAccount.xpub,
|
|
784
|
+
xprv: '',
|
|
785
|
+
isImported: false,
|
|
786
|
+
isTrezorWallet: false,
|
|
787
|
+
isLedgerWallet: false,
|
|
788
|
+
balances: { syscoin: 0, ethereum: 0 },
|
|
789
|
+
assets: { syscoin: [], ethereum: [] },
|
|
790
|
+
};
|
|
791
|
+
currentVaultState.activeAccount = {
|
|
792
|
+
id: 1,
|
|
793
|
+
type: KeyringAccountType.HDAccount,
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// Mock backend response
|
|
797
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
798
|
+
balance: 100000000,
|
|
799
|
+
tokens: [],
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
const path = await keyringManager.getCurrentAddressBip32Path(
|
|
803
|
+
newAccount.xpub,
|
|
804
|
+
false
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
// Should use account ID 1
|
|
808
|
+
expect(path).toBe("m/84'/57'/1'/0/0");
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('should handle empty token list', async () => {
|
|
812
|
+
// Mock backend response with no tokens
|
|
813
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
814
|
+
balance: 0,
|
|
815
|
+
tokens: [],
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
819
|
+
|
|
820
|
+
// Should use index 0 for both
|
|
821
|
+
const receivingPath = await keyringManager.getCurrentAddressBip32Path(
|
|
822
|
+
account.xpub,
|
|
823
|
+
false
|
|
824
|
+
);
|
|
825
|
+
const changePath = await keyringManager.getCurrentAddressBip32Path(
|
|
826
|
+
account.xpub,
|
|
827
|
+
true
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
expect(receivingPath).toBe("m/84'/57'/0'/0/0");
|
|
831
|
+
expect(changePath).toBe("m/84'/57'/0'/1/0");
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it('should handle Bitcoin network paths', async () => {
|
|
835
|
+
// Set up Bitcoin vault state
|
|
836
|
+
const btcVaultState = createMockVaultState({
|
|
837
|
+
activeAccountId: 0,
|
|
838
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
839
|
+
networkType: INetworkType.Syscoin, // UTXO type
|
|
840
|
+
chainId: 57, // Use default Syscoin chainId for now
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// Override the active network to be Bitcoin
|
|
844
|
+
btcVaultState.activeNetwork = {
|
|
845
|
+
chainId: 0,
|
|
846
|
+
currency: 'BTC',
|
|
847
|
+
label: 'Bitcoin',
|
|
848
|
+
url: 'https://blockstream.info',
|
|
849
|
+
kind: INetworkType.Syscoin,
|
|
850
|
+
slip44: 0, // Bitcoin's slip44
|
|
851
|
+
explorer: 'https://blockstream.info',
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
const btcVaultStateGetter = jest.fn(() => btcVaultState);
|
|
855
|
+
|
|
856
|
+
// Create Bitcoin keyring
|
|
857
|
+
const btcKeyring = await KeyringManager.createInitialized(
|
|
858
|
+
PEACE_SEED_PHRASE,
|
|
859
|
+
FAKE_PASSWORD,
|
|
860
|
+
btcVaultStateGetter
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// Mock backend response
|
|
864
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
865
|
+
balance: 100000000,
|
|
866
|
+
tokens: [],
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
const account = btcKeyring.getActiveAccount().activeAccount;
|
|
870
|
+
const path = await btcKeyring.getCurrentAddressBip32Path(
|
|
871
|
+
account.xpub,
|
|
872
|
+
false
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
// Should use Bitcoin's slip44 (0)
|
|
876
|
+
expect(path).toBe("m/84'/0'/0'/0/0");
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
describe('Integration between methods', () => {
|
|
881
|
+
it('getCurrentAddressPubkey and getAddress should derive from same index', async () => {
|
|
882
|
+
// Mock backend response
|
|
883
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
884
|
+
balance: 100000000,
|
|
885
|
+
tokens: [
|
|
886
|
+
{
|
|
887
|
+
path: "m/84'/57'/0'/0/2",
|
|
888
|
+
transfers: '1',
|
|
889
|
+
},
|
|
890
|
+
],
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
894
|
+
|
|
895
|
+
// Get address and public key
|
|
896
|
+
const address = await keyringManager.getAddress(account.xpub, false);
|
|
897
|
+
const pubkey = await keyringManager.getCurrentAddressPubkey(
|
|
898
|
+
account.xpub,
|
|
899
|
+
false
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
// Both should be valid and non-empty
|
|
903
|
+
expect(address).toBeTruthy();
|
|
904
|
+
expect(address).toMatch(/^(sys1|tsys1)/);
|
|
905
|
+
expect(pubkey).toMatch(/^[0-9a-fA-F]{66}$/);
|
|
906
|
+
|
|
907
|
+
// They should have been called with the same backend data
|
|
908
|
+
expect(mockFetchBackendAccount).toHaveBeenCalledTimes(2);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
describe('Hardware Wallet Support', () => {
|
|
914
|
+
it('should work with all read-only methods', async () => {
|
|
915
|
+
// Set up UTXO vault state with a regular HD account for testing
|
|
916
|
+
// The key point is that our methods work with just xpub, regardless of account type
|
|
917
|
+
currentVaultState = createMockVaultState({
|
|
918
|
+
activeAccountId: 0,
|
|
919
|
+
activeAccountType: KeyringAccountType.HDAccount,
|
|
920
|
+
networkType: INetworkType.Syscoin,
|
|
921
|
+
chainId: 57,
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
mockVaultStateGetter = jest.fn(() => currentVaultState);
|
|
925
|
+
|
|
926
|
+
keyringManager = await KeyringManager.createInitialized(
|
|
927
|
+
PEACE_SEED_PHRASE,
|
|
928
|
+
FAKE_PASSWORD,
|
|
929
|
+
mockVaultStateGetter
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
// Mock backend response
|
|
933
|
+
const syscoinjs = require('syscoinjs-lib');
|
|
934
|
+
const mockFetchBackendAccount = syscoinjs.utils
|
|
935
|
+
.fetchBackendAccount as jest.Mock;
|
|
936
|
+
mockFetchBackendAccount.mockResolvedValue({
|
|
937
|
+
balance: 100000000,
|
|
938
|
+
tokens: [
|
|
939
|
+
{
|
|
940
|
+
path: "m/84'/57'/0'/0/2",
|
|
941
|
+
transfers: '1',
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
path: "m/84'/57'/0'/1/1",
|
|
945
|
+
transfers: '1',
|
|
946
|
+
},
|
|
947
|
+
],
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
// Get the xpub from a regular account to test with
|
|
951
|
+
const account = keyringManager.getActiveAccount().activeAccount;
|
|
952
|
+
const xpub = account.xpub;
|
|
953
|
+
|
|
954
|
+
// Test all read-only methods that hardware wallets can use
|
|
955
|
+
// These methods only need xpub, not private keys
|
|
956
|
+
|
|
957
|
+
// 1. Test getCurrentAddressPubkey
|
|
958
|
+
const receivingPubkey = await keyringManager.getCurrentAddressPubkey(
|
|
959
|
+
xpub,
|
|
960
|
+
false
|
|
961
|
+
);
|
|
962
|
+
const changePubkey = await keyringManager.getCurrentAddressPubkey(
|
|
963
|
+
xpub,
|
|
964
|
+
true
|
|
965
|
+
);
|
|
966
|
+
expect(receivingPubkey).toMatch(/^[0-9a-fA-F]{66}$/);
|
|
967
|
+
expect(changePubkey).toMatch(/^[0-9a-fA-F]{66}$/);
|
|
968
|
+
expect(receivingPubkey).not.toBe(changePubkey);
|
|
969
|
+
|
|
970
|
+
// 2. Test getBip32Path
|
|
971
|
+
const receivingPath = await keyringManager.getCurrentAddressBip32Path(
|
|
972
|
+
xpub,
|
|
973
|
+
false
|
|
974
|
+
);
|
|
975
|
+
const changePath = await keyringManager.getCurrentAddressBip32Path(
|
|
976
|
+
xpub,
|
|
977
|
+
true
|
|
978
|
+
);
|
|
979
|
+
expect(receivingPath).toBe("m/84'/57'/0'/0/3"); // index 2 + 1
|
|
980
|
+
expect(changePath).toBe("m/84'/57'/0'/1/2"); // index 1 + 1
|
|
981
|
+
|
|
982
|
+
// 3. Test getAddress (used by getChangeAddress)
|
|
983
|
+
const receivingAddress = await keyringManager.getAddress(xpub, false);
|
|
984
|
+
const changeAddress = await keyringManager.getAddress(xpub, true);
|
|
985
|
+
expect(receivingAddress).toMatch(/^(sys1|tsys1)/);
|
|
986
|
+
expect(changeAddress).toMatch(/^(sys1|tsys1)/);
|
|
987
|
+
expect(receivingAddress).not.toBe(changeAddress);
|
|
988
|
+
|
|
989
|
+
// 4. Test getChangeAddress (calls getAddress internally)
|
|
990
|
+
const changeAddr = await keyringManager.getChangeAddress(0);
|
|
991
|
+
expect(changeAddr).toMatch(/^(sys1|tsys1)/);
|
|
992
|
+
|
|
993
|
+
// All these methods work with just xpub, making them hardware wallet compatible
|
|
994
|
+
});
|
|
995
|
+
});
|
|
996
|
+
});
|