@deserialize/multi-vm-wallet 1.4.2 → 1.5.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/.claude/settings.local.json +7 -1
- package/BUILD_OPTIMIZATION_PLAN.md +640 -0
- package/BUILD_RESULTS.md +282 -0
- package/BUN_MIGRATION.md +415 -0
- package/CHANGELOG_SECURITY.md +573 -0
- package/IMPLEMENTATION_SUMMARY.md +494 -0
- package/SECURITY_AUDIT.md +1124 -0
- package/bun.lock +553 -0
- package/dist/IChainWallet.js +0 -5
- package/dist/bip32Old.js +0 -885
- package/dist/bip32Small.js +0 -79
- package/dist/bipTest.js +0 -362
- package/dist/constant.js +0 -17
- package/dist/english.js +0 -1
- package/dist/evm/aa-service/index.d.ts +0 -5
- package/dist/evm/aa-service/index.js +0 -14
- package/dist/evm/aa-service/lib/account-adapter.d.ts +0 -22
- package/dist/evm/aa-service/lib/account-adapter.js +0 -24
- package/dist/evm/aa-service/lib/kernel-account.d.ts +0 -30
- package/dist/evm/aa-service/lib/kernel-account.js +2 -67
- package/dist/evm/aa-service/lib/kernel-modules.d.ts +0 -177
- package/dist/evm/aa-service/lib/kernel-modules.js +4 -202
- package/dist/evm/aa-service/lib/session-keys.d.ts +0 -118
- package/dist/evm/aa-service/lib/session-keys.js +7 -151
- package/dist/evm/aa-service/lib/type.d.ts +0 -55
- package/dist/evm/aa-service/lib/type.js +0 -10
- package/dist/evm/aa-service/services/account-abstraction.d.ts +0 -426
- package/dist/evm/aa-service/services/account-abstraction.js +0 -461
- package/dist/evm/aa-service/services/bundler.d.ts +0 -6
- package/dist/evm/aa-service/services/bundler.js +0 -54
- package/dist/evm/evm.d.ts +9 -51
- package/dist/evm/evm.js +338 -76
- package/dist/evm/index.js +0 -3
- package/dist/evm/script.js +3 -17
- package/dist/evm/smartWallet.d.ts +0 -173
- package/dist/evm/smartWallet.js +0 -206
- package/dist/evm/smartWallet.types.d.ts +0 -6
- package/dist/evm/smartWallet.types.js +0 -8
- package/dist/evm/transaction.utils.d.ts +0 -242
- package/dist/evm/transaction.utils.js +4 -320
- package/dist/evm/transactionParsing.d.ts +0 -11
- package/dist/evm/transactionParsing.js +28 -147
- package/dist/evm/utils.d.ts +0 -46
- package/dist/evm/utils.js +1 -57
- package/dist/helpers/index.d.ts +0 -4
- package/dist/helpers/index.js +8 -44
- package/dist/helpers/routeScan.js +0 -1
- package/dist/index.js +0 -1
- package/dist/old.js +0 -884
- package/dist/price.js +0 -1
- package/dist/price.types.js +0 -2
- package/dist/rate-limiter.d.ts +28 -0
- package/dist/rate-limiter.js +95 -0
- package/dist/retry-logic.d.ts +14 -0
- package/dist/retry-logic.js +120 -0
- package/dist/savings/index.js +0 -1
- package/dist/savings/saving-manager.d.ts +10 -11
- package/dist/savings/saving-manager.js +79 -22
- package/dist/savings/savings-operations.d.ts +39 -0
- package/dist/savings/savings-operations.js +141 -0
- package/dist/savings/smart-savings.d.ts +0 -63
- package/dist/savings/smart-savings.js +0 -78
- package/dist/savings/types.d.ts +0 -69
- package/dist/savings/types.js +0 -7
- package/dist/savings/validation.d.ts +9 -0
- package/dist/savings/validation.js +85 -0
- package/dist/svm/constant.js +0 -1
- package/dist/svm/index.js +0 -1
- package/dist/svm/svm.d.ts +11 -1
- package/dist/svm/svm.js +267 -27
- package/dist/svm/transactionParsing.d.ts +0 -7
- package/dist/svm/transactionParsing.js +3 -41
- package/dist/svm/transactionSender.js +0 -9
- package/dist/svm/utils.d.ts +0 -12
- package/dist/svm/utils.js +9 -60
- package/dist/test.d.ts +0 -4
- package/dist/test.js +6 -98
- package/dist/transaction-utils.d.ts +38 -0
- package/dist/transaction-utils.js +168 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.js +0 -1
- package/dist/utils.js +0 -1
- package/dist/vm-validation.d.ts +11 -0
- package/dist/vm-validation.js +151 -0
- package/dist/vm.d.ts +12 -2
- package/dist/vm.js +61 -16
- package/dist/walletBip32.js +15 -70
- package/package.json +9 -4
- package/test-discovery.ts +235 -0
- package/test-pocket-discovery.ts +84 -0
- package/tsconfig.json +18 -11
- package/tsconfig.prod.json +10 -0
- package/utils/evm/evm.ts +554 -8
- package/utils/rate-limiter.ts +179 -0
- package/utils/retry-logic.ts +271 -0
- package/utils/savings/EXAMPLES.md +883 -0
- package/utils/savings/SECURITY.md +731 -0
- package/utils/savings/saving-manager.ts +526 -16
- package/utils/savings/savings-operations.ts +509 -0
- package/utils/savings/validation.ts +187 -0
- package/utils/svm/svm.ts +476 -5
- package/utils/test.ts +2 -2
- package/utils/transaction-utils.ts +394 -0
- package/utils/types.ts +100 -0
- package/utils/vm-validation.ts +280 -0
- package/utils/vm.ts +197 -10
- package/utils/walletBip32.ts +39 -3
- package/dist/IChainWallet.js.map +0 -1
- package/dist/bip32.d.ts +0 -9
- package/dist/bip32.js +0 -172
- package/dist/bip32.js.map +0 -1
- package/dist/bip32Old.js.map +0 -1
- package/dist/bip32Small.js.map +0 -1
- package/dist/bipTest.js.map +0 -1
- package/dist/constant.js.map +0 -1
- package/dist/english.js.map +0 -1
- package/dist/evm/SMART_WALLET_EXAMPLES.d.ts +0 -20
- package/dist/evm/SMART_WALLET_EXAMPLES.js +0 -451
- package/dist/evm/SMART_WALLET_EXAMPLES.js.map +0 -1
- package/dist/evm/aa-service/index.js.map +0 -1
- package/dist/evm/aa-service/lib/account-adapter.js.map +0 -1
- package/dist/evm/aa-service/lib/kernel-account.js.map +0 -1
- package/dist/evm/aa-service/lib/kernel-modules.js.map +0 -1
- package/dist/evm/aa-service/lib/session-keys.js.map +0 -1
- package/dist/evm/aa-service/lib/type.js.map +0 -1
- package/dist/evm/aa-service/services/account-abstraction.js.map +0 -1
- package/dist/evm/aa-service/services/bundler.js.map +0 -1
- package/dist/evm/evm.js.map +0 -1
- package/dist/evm/index.js.map +0 -1
- package/dist/evm/script.js.map +0 -1
- package/dist/evm/smartWallet.js.map +0 -1
- package/dist/evm/smartWallet.types.js.map +0 -1
- package/dist/evm/transaction.utils.js.map +0 -1
- package/dist/evm/transactionParsing.js.map +0 -1
- package/dist/evm/utils.js.map +0 -1
- package/dist/helpers/index.js.map +0 -1
- package/dist/helpers/routeScan.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/old.js.map +0 -1
- package/dist/price.js.map +0 -1
- package/dist/price.types.js.map +0 -1
- package/dist/privacy/artifact-manager.d.ts +0 -117
- package/dist/privacy/artifact-manager.js +0 -251
- package/dist/privacy/artifact-manager.js.map +0 -1
- package/dist/privacy/broadcaster-client.d.ts +0 -166
- package/dist/privacy/broadcaster-client.js +0 -261
- package/dist/privacy/broadcaster-client.js.map +0 -1
- package/dist/privacy/index.d.ts +0 -34
- package/dist/privacy/index.js +0 -56
- package/dist/privacy/index.js.map +0 -1
- package/dist/privacy/network-config.d.ts +0 -57
- package/dist/privacy/network-config.js +0 -118
- package/dist/privacy/network-config.js.map +0 -1
- package/dist/privacy/poi-helper.d.ts +0 -161
- package/dist/privacy/poi-helper.js +0 -249
- package/dist/privacy/poi-helper.js.map +0 -1
- package/dist/privacy/railgun-engine.d.ts +0 -135
- package/dist/privacy/railgun-engine.js +0 -205
- package/dist/privacy/railgun-engine.js.map +0 -1
- package/dist/privacy/railgun-privacy-wallet.d.ts +0 -288
- package/dist/privacy/railgun-privacy-wallet.js +0 -539
- package/dist/privacy/railgun-privacy-wallet.js.map +0 -1
- package/dist/privacy/types.d.ts +0 -229
- package/dist/privacy/types.js +0 -26
- package/dist/privacy/types.js.map +0 -1
- package/dist/savings/index.js.map +0 -1
- package/dist/savings/saving-actions.d.ts +0 -0
- package/dist/savings/saving-actions.js +0 -78
- package/dist/savings/saving-actions.js.map +0 -1
- package/dist/savings/saving-manager.js.map +0 -1
- package/dist/savings/savings-manager.d.ts +0 -126
- package/dist/savings/savings-manager.js +0 -234
- package/dist/savings/savings-manager.js.map +0 -1
- package/dist/savings/smart-savings.js.map +0 -1
- package/dist/savings/types.js.map +0 -1
- package/dist/svm/constant.js.map +0 -1
- package/dist/svm/index.js.map +0 -1
- package/dist/svm/svm.js.map +0 -1
- package/dist/svm/transactionParsing.js.map +0 -1
- package/dist/svm/transactionSender.js.map +0 -1
- package/dist/svm/utils.js.map +0 -1
- package/dist/test.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/dist/vm.js.map +0 -1
- package/dist/walletBip32.js.map +0 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for VM operations
|
|
3
|
+
*
|
|
4
|
+
* Provides validation for wallet indices, derivation paths, seeds, and other
|
|
5
|
+
* cryptographic inputs to prevent security issues and crashes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* VM validation utilities
|
|
10
|
+
*/
|
|
11
|
+
export class VMValidation {
|
|
12
|
+
/**
|
|
13
|
+
* Validate wallet/account index
|
|
14
|
+
*
|
|
15
|
+
* @param index - Index to validate
|
|
16
|
+
* @param label - Label for error message
|
|
17
|
+
* @throws Error if index is invalid
|
|
18
|
+
*/
|
|
19
|
+
static validateIndex(index: number, label: string = 'Index'): void {
|
|
20
|
+
if (!Number.isInteger(index)) {
|
|
21
|
+
throw new Error(`${label} must be an integer, got: ${index}`);
|
|
22
|
+
}
|
|
23
|
+
if (index < 0) {
|
|
24
|
+
throw new Error(`${label} must be non-negative, got: ${index}`);
|
|
25
|
+
}
|
|
26
|
+
if (index > 0x7FFFFFFF) {
|
|
27
|
+
throw new Error(`${label} exceeds maximum (2147483647), got: ${index}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate hex seed string
|
|
33
|
+
*
|
|
34
|
+
* @param seed - Seed to validate
|
|
35
|
+
* @throws Error if seed is invalid
|
|
36
|
+
*/
|
|
37
|
+
static validateSeed(seed: string): void {
|
|
38
|
+
if (typeof seed !== 'string') {
|
|
39
|
+
throw new Error(`Seed must be a string, got: ${typeof seed}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (seed.length === 0) {
|
|
43
|
+
throw new Error('Seed cannot be empty');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!/^[0-9a-fA-F]+$/.test(seed)) {
|
|
47
|
+
throw new Error('Seed must be a hex string');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Typical seed length is 64 hex chars (32 bytes)
|
|
51
|
+
if (seed.length < 32) {
|
|
52
|
+
throw new Error(`Seed too short: ${seed.length} chars (minimum 32)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Validate mnemonic phrase
|
|
58
|
+
*
|
|
59
|
+
* @param mnemonic - Mnemonic to validate
|
|
60
|
+
* @throws Error if mnemonic is invalid
|
|
61
|
+
*/
|
|
62
|
+
static validateMnemonic(mnemonic: string): void {
|
|
63
|
+
if (typeof mnemonic !== 'string') {
|
|
64
|
+
throw new Error(`Mnemonic must be a string, got: ${typeof mnemonic}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const trimmed = mnemonic.trim();
|
|
68
|
+
if (trimmed.length === 0) {
|
|
69
|
+
throw new Error('Mnemonic cannot be empty');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const words = trimmed.split(/\s+/);
|
|
73
|
+
const validWordCounts = [12, 15, 18, 21, 24];
|
|
74
|
+
|
|
75
|
+
if (!validWordCounts.includes(words.length)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Mnemonic must have 12, 15, 18, 21, or 24 words (BIP-39 standard), got: ${words.length} words`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validate BIP-44 derivation path format
|
|
84
|
+
*
|
|
85
|
+
* @param path - Derivation path to validate
|
|
86
|
+
* @param vmType - VM type ('EVM' or 'SVM')
|
|
87
|
+
* @throws Error if path is invalid
|
|
88
|
+
*/
|
|
89
|
+
static validateDerivationPath(path: string, vmType?: 'EVM' | 'SVM'): void {
|
|
90
|
+
if (typeof path !== 'string') {
|
|
91
|
+
throw new Error(`Derivation path must be a string, got: ${typeof path}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Expected format: m/44'/cointype'/account'/change'/addressindex'
|
|
95
|
+
// Some paths may have all hardened segments (with ')
|
|
96
|
+
const pathRegex = /^m(\/\d+')+$/;
|
|
97
|
+
|
|
98
|
+
if (!pathRegex.test(path)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Invalid derivation path format: ${path}. ` +
|
|
101
|
+
`Expected format: m/44'/cointype'/account'/... (all segments hardened)`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Extract parts
|
|
106
|
+
const parts = path.split('/');
|
|
107
|
+
if (parts.length < 3) {
|
|
108
|
+
throw new Error(`Derivation path too short: ${path}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check purpose (should be 44' for BIP-44)
|
|
112
|
+
const purpose = parseInt(parts[1].replace("'", ""));
|
|
113
|
+
if (purpose !== 44) {
|
|
114
|
+
console.warn(`Warning: Non-BIP-44 purpose value: ${purpose}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Validate coin type if VM type specified
|
|
118
|
+
if (vmType && parts.length >= 3) {
|
|
119
|
+
const coinType = parseInt(parts[2].replace("'", ""));
|
|
120
|
+
|
|
121
|
+
if (vmType === 'EVM' && coinType !== 60) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Invalid coin type for EVM: ${coinType}. Expected 60 (Ethereum).`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (vmType === 'SVM' && coinType !== 501) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Invalid coin type for SVM: ${coinType}. Expected 501 (Solana).`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Validate all indices are within range
|
|
135
|
+
for (let i = 1; i < parts.length; i++) {
|
|
136
|
+
const indexStr = parts[i].replace("'", "");
|
|
137
|
+
const index = parseInt(indexStr);
|
|
138
|
+
|
|
139
|
+
if (isNaN(index)) {
|
|
140
|
+
throw new Error(`Invalid index in path segment ${i}: ${parts[i]}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check hardened index range (0x80000000 to 0xFFFFFFFF)
|
|
144
|
+
// Non-hardened range (0 to 0x7FFFFFFF)
|
|
145
|
+
if (parts[i].endsWith("'")) {
|
|
146
|
+
if (index > 0x7FFFFFFF) {
|
|
147
|
+
throw new Error(`Hardened index ${index} exceeds maximum`);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
if (index > 0x7FFFFFFF) {
|
|
151
|
+
throw new Error(`Non-hardened index ${index} exceeds maximum`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Validate password strength
|
|
159
|
+
*
|
|
160
|
+
* @param password - Password to validate
|
|
161
|
+
* @param minLength - Minimum password length (default: 8)
|
|
162
|
+
* @throws Error if password is weak
|
|
163
|
+
*/
|
|
164
|
+
static validatePassword(password: string, minLength: number = 8): void {
|
|
165
|
+
if (typeof password !== 'string') {
|
|
166
|
+
throw new Error(`Password must be a string, got: ${typeof password}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (password.length < minLength) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Password too short. Minimum length: ${minLength}, got: ${password.length}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for common weak passwords
|
|
176
|
+
const weakPasswords = ['password', '12345678', 'qwerty', 'abc123'];
|
|
177
|
+
if (weakPasswords.includes(password.toLowerCase())) {
|
|
178
|
+
throw new Error('Password is too weak. Choose a stronger password.');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validate amount (bigint)
|
|
184
|
+
*
|
|
185
|
+
* @param amount - Amount to validate
|
|
186
|
+
* @param label - Label for error message
|
|
187
|
+
* @throws Error if amount is invalid
|
|
188
|
+
*/
|
|
189
|
+
static validateAmount(amount: bigint, label: string = 'Amount'): void {
|
|
190
|
+
if (typeof amount !== 'bigint') {
|
|
191
|
+
throw new Error(`${label} must be a bigint, got: ${typeof amount}`);
|
|
192
|
+
}
|
|
193
|
+
if (amount <= 0n) {
|
|
194
|
+
throw new Error(`${label} must be positive, got: ${amount}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validate Ethereum address format
|
|
200
|
+
*
|
|
201
|
+
* @param address - Address to validate
|
|
202
|
+
* @param label - Label for error message
|
|
203
|
+
* @throws Error if address is invalid
|
|
204
|
+
*/
|
|
205
|
+
static validateEthereumAddress(address: string, label: string = 'Address'): void {
|
|
206
|
+
if (typeof address !== 'string') {
|
|
207
|
+
throw new Error(`${label} must be a string, got: ${typeof address}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`${label} has invalid format. Expected 0x followed by 40 hex characters, got: ${address}`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Sanitize error messages to remove sensitive data
|
|
220
|
+
*
|
|
221
|
+
* @param error - Error to sanitize
|
|
222
|
+
* @param additionalSensitiveFields - Additional field names to redact
|
|
223
|
+
* @returns Sanitized error
|
|
224
|
+
*/
|
|
225
|
+
export function sanitizeError(
|
|
226
|
+
error: any,
|
|
227
|
+
additionalSensitiveFields: string[] = []
|
|
228
|
+
): Error {
|
|
229
|
+
const message = error.message || error.toString();
|
|
230
|
+
|
|
231
|
+
// Remove common sensitive patterns
|
|
232
|
+
let sanitized = message
|
|
233
|
+
.replace(/seed:\s*[0-9a-fA-F]{32,}/gi, 'seed: [REDACTED]')
|
|
234
|
+
.replace(/password:\s*\S+/gi, 'password: [REDACTED]')
|
|
235
|
+
.replace(/mnemonic:\s*.+/gi, 'mnemonic: [REDACTED]')
|
|
236
|
+
.replace(/private\s*key:\s*[0-9a-fA-F]+/gi, 'privateKey: [REDACTED]')
|
|
237
|
+
.replace(/0x[0-9a-fA-F]{64,}/g, '[PRIVATE_KEY_REDACTED]')
|
|
238
|
+
.replace(/secret:\s*\S+/gi, 'secret: [REDACTED]');
|
|
239
|
+
|
|
240
|
+
// Remove custom sensitive fields
|
|
241
|
+
additionalSensitiveFields.forEach(field => {
|
|
242
|
+
const regex = new RegExp(`${field}:\\s*\\S+`, 'gi');
|
|
243
|
+
sanitized = sanitized.replace(regex, `${field}: [REDACTED]`);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const sanitizedError = new Error(sanitized);
|
|
247
|
+
sanitizedError.stack = error.stack; // Preserve stack trace
|
|
248
|
+
return sanitizedError;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Safe error logger that redacts sensitive information
|
|
253
|
+
*
|
|
254
|
+
* @param message - Error message
|
|
255
|
+
* @param error - Error object
|
|
256
|
+
* @param context - Additional context
|
|
257
|
+
*/
|
|
258
|
+
export function logSafeError(
|
|
259
|
+
message: string,
|
|
260
|
+
error: any,
|
|
261
|
+
context?: Record<string, any>
|
|
262
|
+
): void {
|
|
263
|
+
const sanitized = sanitizeError(error);
|
|
264
|
+
|
|
265
|
+
// Filter sensitive data from context
|
|
266
|
+
const safeContext = context ? { ...context } : {};
|
|
267
|
+
const sensitiveKeys = ['seed', 'mnemonic', 'privateKey', 'password', 'secret'];
|
|
268
|
+
|
|
269
|
+
sensitiveKeys.forEach(key => {
|
|
270
|
+
if (safeContext[key]) {
|
|
271
|
+
safeContext[key] = '[REDACTED]';
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
console.error(message, {
|
|
276
|
+
error: sanitized.message,
|
|
277
|
+
stack: sanitized.stack,
|
|
278
|
+
context: safeContext
|
|
279
|
+
});
|
|
280
|
+
}
|
package/utils/vm.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { EntropyToMnemonic, mnemonicToSeed } from "./walletBip32";
|
|
|
8
8
|
export abstract class VM<AddressType, PrivateKeyType, ConnectionType> {
|
|
9
9
|
protected seed: string;
|
|
10
10
|
type: vmTypes
|
|
11
|
+
private disposed: boolean = false;
|
|
11
12
|
|
|
12
13
|
constructor(seed: string, vm: vmTypes) {
|
|
13
14
|
this.type = vm;
|
|
@@ -15,51 +16,237 @@ export abstract class VM<AddressType, PrivateKeyType, ConnectionType> {
|
|
|
15
16
|
}
|
|
16
17
|
static mnemonicToSeed = mnemonicToSeed
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Clear sensitive data from memory
|
|
21
|
+
*
|
|
22
|
+
* IMPORTANT: After calling dispose(), the VM instance should not be used.
|
|
23
|
+
* JavaScript strings are immutable, so this only clears references.
|
|
24
|
+
* The actual memory will be cleared by garbage collection.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* Call this method when:
|
|
28
|
+
* - User locks the wallet
|
|
29
|
+
* - Application goes to background (mobile)
|
|
30
|
+
* - Extension popup closes (browser extension)
|
|
31
|
+
* - Session ends
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const vm = EVMVM.fromMnemonic(mnemonic);
|
|
36
|
+
* // ... use vm ...
|
|
37
|
+
* vm.dispose();
|
|
38
|
+
* vm = null; // Remove reference for garbage collection
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
dispose(): void {
|
|
42
|
+
if (this.disposed) {
|
|
43
|
+
return; // Already disposed
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Clear seed reference
|
|
47
|
+
(this as any).seed = '';
|
|
48
|
+
this.disposed = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if VM has been disposed
|
|
53
|
+
*
|
|
54
|
+
* @returns true if dispose() has been called
|
|
55
|
+
*/
|
|
56
|
+
isDisposed(): boolean {
|
|
57
|
+
return this.disposed || !this.seed || this.seed === '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Throw error if VM has been disposed
|
|
62
|
+
*
|
|
63
|
+
* @throws Error if VM is disposed
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
protected checkNotDisposed(): void {
|
|
67
|
+
if (this.isDisposed()) {
|
|
68
|
+
throw new Error('VM has been disposed. Create a new instance to perform operations.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
18
72
|
static generateSalt(): string {
|
|
19
73
|
return CryptoJS.lib.WordArray.random(16).toString(); // 128-bit salt
|
|
20
74
|
}
|
|
21
75
|
|
|
22
76
|
static getMnemonicFromEntropy = EntropyToMnemonic
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Derive encryption key using PBKDF2
|
|
80
|
+
*
|
|
81
|
+
* @param password - User password
|
|
82
|
+
* @param salt - Hex salt string
|
|
83
|
+
* @param iterations - PBKDF2 iterations (default: 600,000 - OWASP recommendation)
|
|
84
|
+
* @param keySize - Key size in 32-bit words (default: 8 = 256 bits)
|
|
85
|
+
* @returns Derived key as hex string
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* OWASP recommends at least 600,000 iterations for PBKDF2-SHA256.
|
|
89
|
+
* Using fewer iterations is a security risk.
|
|
90
|
+
*
|
|
91
|
+
* @security
|
|
92
|
+
* - 10,000 iterations (old default): INSECURE - deprecated
|
|
93
|
+
* - 100,000 iterations: Minimum acceptable
|
|
94
|
+
* - 600,000 iterations: Recommended
|
|
95
|
+
*/
|
|
23
96
|
static deriveKey(
|
|
24
97
|
password: string,
|
|
25
98
|
salt: string,
|
|
26
|
-
iterations =
|
|
99
|
+
iterations = 600000, // ✅ Updated to OWASP recommendation
|
|
27
100
|
keySize = 256 / 32
|
|
28
101
|
) {
|
|
102
|
+
// Validate inputs
|
|
103
|
+
if (!password || password.length < 8) {
|
|
104
|
+
throw new Error('Password must be at least 8 characters');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!salt) {
|
|
108
|
+
throw new Error('Salt is required');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Warn about weak iteration counts
|
|
112
|
+
if (iterations < 100000) {
|
|
113
|
+
console.warn(
|
|
114
|
+
`⚠️ WARNING: Using ${iterations} PBKDF2 iterations is insecure. ` +
|
|
115
|
+
`Minimum recommended: 100,000. Recommended: 600,000.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
29
119
|
return CryptoJS.PBKDF2(password, CryptoJS.enc.Hex.parse(salt), {
|
|
30
120
|
keySize: keySize,
|
|
31
121
|
iterations: iterations,
|
|
122
|
+
hasher: CryptoJS.algo.SHA256 // Explicitly specify hasher
|
|
32
123
|
}).toString();
|
|
33
124
|
}
|
|
34
|
-
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Encrypt seed phrase with strong encryption
|
|
128
|
+
*
|
|
129
|
+
* @param seedPhrase - Seed phrase to encrypt
|
|
130
|
+
* @param password - User password (min 8 characters)
|
|
131
|
+
* @param iterations - PBKDF2 iterations (default: 600,000)
|
|
132
|
+
* @returns Encrypted data, salt, and iteration count
|
|
133
|
+
*
|
|
134
|
+
* @throws Error if inputs are invalid
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const { encrypted, salt, iterations } = VM.encryptSeedPhrase(
|
|
139
|
+
* mnemonic,
|
|
140
|
+
* userPassword
|
|
141
|
+
* );
|
|
142
|
+
* // Store encrypted, salt, and iterations
|
|
143
|
+
* await storage.save({ encrypted, salt, iterations });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
static encryptSeedPhrase(seedPhrase: string, password: string, iterations: number = 600000) {
|
|
147
|
+
// Validate inputs
|
|
148
|
+
if (!seedPhrase || seedPhrase.trim().length === 0) {
|
|
149
|
+
throw new Error('Seed phrase cannot be empty');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!password || password.length < 8) {
|
|
153
|
+
throw new Error('Password must be at least 8 characters');
|
|
154
|
+
}
|
|
155
|
+
|
|
35
156
|
const salt = this.generateSalt(); // Generate a unique salt for this encryption
|
|
36
|
-
const key = this.deriveKey(password, salt); // Derive a key using PBKDF2
|
|
157
|
+
const key = this.deriveKey(password, salt, iterations); // Derive a key using PBKDF2
|
|
37
158
|
|
|
38
159
|
// Encrypt the seed phrase with AES using the derived key
|
|
39
160
|
const encrypted = CryptoJS.AES.encrypt(seedPhrase, key).toString();
|
|
40
161
|
|
|
41
|
-
// Return the encrypted data and
|
|
42
|
-
return { encrypted, salt };
|
|
162
|
+
// Return the encrypted data, salt, and iterations (needed for decryption)
|
|
163
|
+
return { encrypted, salt, iterations };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Legacy encryption method for backwards compatibility
|
|
168
|
+
*
|
|
169
|
+
* @deprecated Use encryptSeedPhrase() instead which returns iteration count
|
|
170
|
+
*/
|
|
171
|
+
static encryptSeedPhraseLegacy(seedPhrase: string, password: string) {
|
|
172
|
+
const result = this.encryptSeedPhrase(seedPhrase, password, 10000);
|
|
173
|
+
return { encrypted: result.encrypted, salt: result.salt };
|
|
43
174
|
}
|
|
44
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Decrypt seed phrase
|
|
178
|
+
*
|
|
179
|
+
* @param encryptedSeedPhrase - Encrypted seed phrase
|
|
180
|
+
* @param password - User password
|
|
181
|
+
* @param salt - Salt used for encryption
|
|
182
|
+
* @param iterations - PBKDF2 iterations (default: 600,000)
|
|
183
|
+
* @returns Decrypted seed phrase or null if failed
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* If you encrypted with the old default (10,000 iterations), pass iterations=10000.
|
|
187
|
+
* New encryptions use 600,000 iterations.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* // Decrypt with stored iteration count
|
|
192
|
+
* const seedPhrase = VM.decryptSeedPhrase(
|
|
193
|
+
* encrypted,
|
|
194
|
+
* userPassword,
|
|
195
|
+
* salt,
|
|
196
|
+
* storedIterations || 600000
|
|
197
|
+
* );
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
45
200
|
static decryptSeedPhrase(
|
|
46
201
|
encryptedSeedPhrase: string,
|
|
47
202
|
password: string,
|
|
48
|
-
salt: string
|
|
49
|
-
|
|
203
|
+
salt: string,
|
|
204
|
+
iterations: number = 600000
|
|
205
|
+
): string | null {
|
|
50
206
|
try {
|
|
51
|
-
|
|
207
|
+
// Validate inputs
|
|
208
|
+
if (!encryptedSeedPhrase) {
|
|
209
|
+
throw new Error('Encrypted seed phrase is required');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!password || password.length < 8) {
|
|
213
|
+
throw new Error('Password must be at least 8 characters');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!salt) {
|
|
217
|
+
throw new Error('Salt is required');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const key = this.deriveKey(password, salt, iterations); // Derive the key using the same salt
|
|
52
221
|
const bytes = CryptoJS.AES.decrypt(encryptedSeedPhrase, key);
|
|
53
222
|
const seedPhrase = bytes.toString(CryptoJS.enc.Utf8);
|
|
54
223
|
|
|
55
224
|
// Check if decryption was successful
|
|
56
|
-
if (!seedPhrase
|
|
225
|
+
if (!seedPhrase || seedPhrase.trim().length === 0) {
|
|
226
|
+
throw new Error("Decryption failed - invalid password or corrupted data");
|
|
227
|
+
}
|
|
228
|
+
|
|
57
229
|
return seedPhrase;
|
|
58
230
|
} catch (e: any) {
|
|
59
|
-
|
|
231
|
+
// Log sanitized error (no password in logs)
|
|
232
|
+
console.error("Decryption failed:", e.message);
|
|
60
233
|
return null;
|
|
61
234
|
}
|
|
62
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Legacy decryption method for backwards compatibility
|
|
239
|
+
*
|
|
240
|
+
* @deprecated Use decryptSeedPhrase() with explicit iterations parameter
|
|
241
|
+
*/
|
|
242
|
+
static decryptSeedPhraseLegacy(
|
|
243
|
+
encryptedSeedPhrase: string,
|
|
244
|
+
password: string,
|
|
245
|
+
salt: string
|
|
246
|
+
): string | null {
|
|
247
|
+
// Try with old default (10,000 iterations)
|
|
248
|
+
return this.decryptSeedPhrase(encryptedSeedPhrase, password, salt, 10000);
|
|
249
|
+
}
|
|
63
250
|
generateSalt = VM.generateSalt
|
|
64
251
|
deriveKey = VM.deriveKey
|
|
65
252
|
encryptSeedPhrase = VM.encryptSeedPhrase
|
package/utils/walletBip32.ts
CHANGED
|
@@ -76,6 +76,7 @@ if (typeof window !== 'undefined') {
|
|
|
76
76
|
import { wordlist } from "./english";
|
|
77
77
|
import { hmac } from "@noble/hashes/hmac";
|
|
78
78
|
import { sha512 } from "@noble/hashes/sha2";
|
|
79
|
+
import { VMValidation } from "./vm-validation";
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
|
|
@@ -121,21 +122,56 @@ export function GenerateSeed(_mnemonic?: string) {
|
|
|
121
122
|
return seedString;
|
|
122
123
|
}
|
|
123
124
|
//EVM
|
|
125
|
+
/**
|
|
126
|
+
* Derive EVM child private key using BIP-44
|
|
127
|
+
*
|
|
128
|
+
* @param seed - Hex seed string
|
|
129
|
+
* @param index - Wallet index
|
|
130
|
+
* @param derivationPath - BIP-44 derivation path (e.g., "m/44'/60'/0'/0/")
|
|
131
|
+
* @returns Private key and public key
|
|
132
|
+
* @throws Error if inputs are invalid
|
|
133
|
+
*/
|
|
124
134
|
export function EVMDeriveChildPrivateKey(seed: string, index: number, derivationPath: string) {
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
// Validate inputs
|
|
136
|
+
VMValidation.validateSeed(seed);
|
|
137
|
+
VMValidation.validateIndex(index, 'Wallet index');
|
|
138
|
+
VMValidation.validateDerivationPath(derivationPath + index + "'", 'EVM');
|
|
139
|
+
|
|
140
|
+
const path = `${derivationPath}${index}'`;
|
|
141
|
+
const scureNode = HDKey.fromMasterSeed(Buffer.from(seed, "hex"));
|
|
127
142
|
const child = scureNode.derive(path);
|
|
128
|
-
|
|
143
|
+
|
|
144
|
+
if (!child.privateKey) {
|
|
145
|
+
throw new Error(`Failed to derive private key at path: ${path}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const privateKey = Buffer.from(child.privateKey).toString("hex");
|
|
129
149
|
const publicKey = Buffer.from(child.publicKey!).toString("hex");
|
|
130
150
|
return { privateKey, publicKey };
|
|
131
151
|
}
|
|
132
152
|
|
|
133
153
|
//SVM
|
|
154
|
+
/**
|
|
155
|
+
* Derive SVM (Solana) child private key using BIP-44
|
|
156
|
+
*
|
|
157
|
+
* @param seed - Hex seed string
|
|
158
|
+
* @param index - Wallet index
|
|
159
|
+
* @param derivationPath - BIP-44 derivation path (e.g., "m/44'/501'/0'/")
|
|
160
|
+
* @returns Solana Keypair
|
|
161
|
+
* @throws Error if inputs are invalid
|
|
162
|
+
*/
|
|
134
163
|
export function SVMDeriveChildPrivateKey(seed: string, index: number, derivationPath: string) {
|
|
164
|
+
// Validate inputs
|
|
165
|
+
VMValidation.validateSeed(seed);
|
|
166
|
+
VMValidation.validateIndex(index, 'Wallet index');
|
|
167
|
+
VMValidation.validateDerivationPath(derivationPath + index + "'", 'SVM');
|
|
168
|
+
|
|
135
169
|
const path = `${derivationPath}${index}'`;
|
|
170
|
+
|
|
136
171
|
// Derive a seed from the given path
|
|
137
172
|
const derivedSeed = derivePathEclipticCurve(path, Buffer.from(seed, "hex")).key;
|
|
138
173
|
const derivedKeyPair = Keypair.fromSeed(derivedSeed);
|
|
174
|
+
|
|
139
175
|
return derivedKeyPair;
|
|
140
176
|
}
|
|
141
177
|
|
package/dist/IChainWallet.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"IChainWallet.js","sourceRoot":"","sources":["../utils/IChainWallet.ts"],"names":[],"mappings":";;;AAKA,MAAsB,WAAW;IACnB,UAAU,CAAiB;IACrC,MAAM,CAAoB;IAC1B,OAAO,CAAc;IACrB,KAAK,CAAoB;IACzB,UAAU,CAA4B;IAEtC,YAAY,MAAyB,EAAE,UAA0B,EAAE,KAAc;QAC7E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IAEvB,CAAC;IAYD,+GAA+G;IAC/G,mEAAmE;IACnE,mFAAmF;IACnF,oDAAoD;IAEpD,UAAU;QACN,OAAO,IAAI,CAAC,OAAO,CAAC;IACxB,CAAC;IACD,oBAAoB;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;CAMJ;AAzCD,kCAyCC"}
|
package/dist/bip32.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Keypair } from "@solana/web3.js";
|
|
2
|
-
export declare function GenerateNewMnemonic(): string;
|
|
3
|
-
export declare function ValidateMnemonic(mnemonic: string): true;
|
|
4
|
-
export declare function GenerateSeed(_mnemonic?: string): Uint8Array<ArrayBufferLike>;
|
|
5
|
-
export declare function EVMDeriveChildPrivateKey(seed: string, index: number, derivationPath: string): {
|
|
6
|
-
privateKey: string;
|
|
7
|
-
publicKey: string;
|
|
8
|
-
};
|
|
9
|
-
export declare function SVMDeriveChildPrivateKey(seed: string, index: number, derivationPath: string): Keypair;
|