@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.
Files changed (186) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/BUILD_OPTIMIZATION_PLAN.md +640 -0
  3. package/BUILD_RESULTS.md +282 -0
  4. package/BUN_MIGRATION.md +415 -0
  5. package/CHANGELOG_SECURITY.md +573 -0
  6. package/IMPLEMENTATION_SUMMARY.md +494 -0
  7. package/SECURITY_AUDIT.md +1124 -0
  8. package/bun.lock +553 -0
  9. package/dist/IChainWallet.js +0 -5
  10. package/dist/bip32Old.js +0 -885
  11. package/dist/bip32Small.js +0 -79
  12. package/dist/bipTest.js +0 -362
  13. package/dist/constant.js +0 -17
  14. package/dist/english.js +0 -1
  15. package/dist/evm/aa-service/index.d.ts +0 -5
  16. package/dist/evm/aa-service/index.js +0 -14
  17. package/dist/evm/aa-service/lib/account-adapter.d.ts +0 -22
  18. package/dist/evm/aa-service/lib/account-adapter.js +0 -24
  19. package/dist/evm/aa-service/lib/kernel-account.d.ts +0 -30
  20. package/dist/evm/aa-service/lib/kernel-account.js +2 -67
  21. package/dist/evm/aa-service/lib/kernel-modules.d.ts +0 -177
  22. package/dist/evm/aa-service/lib/kernel-modules.js +4 -202
  23. package/dist/evm/aa-service/lib/session-keys.d.ts +0 -118
  24. package/dist/evm/aa-service/lib/session-keys.js +7 -151
  25. package/dist/evm/aa-service/lib/type.d.ts +0 -55
  26. package/dist/evm/aa-service/lib/type.js +0 -10
  27. package/dist/evm/aa-service/services/account-abstraction.d.ts +0 -426
  28. package/dist/evm/aa-service/services/account-abstraction.js +0 -461
  29. package/dist/evm/aa-service/services/bundler.d.ts +0 -6
  30. package/dist/evm/aa-service/services/bundler.js +0 -54
  31. package/dist/evm/evm.d.ts +9 -51
  32. package/dist/evm/evm.js +338 -76
  33. package/dist/evm/index.js +0 -3
  34. package/dist/evm/script.js +3 -17
  35. package/dist/evm/smartWallet.d.ts +0 -173
  36. package/dist/evm/smartWallet.js +0 -206
  37. package/dist/evm/smartWallet.types.d.ts +0 -6
  38. package/dist/evm/smartWallet.types.js +0 -8
  39. package/dist/evm/transaction.utils.d.ts +0 -242
  40. package/dist/evm/transaction.utils.js +4 -320
  41. package/dist/evm/transactionParsing.d.ts +0 -11
  42. package/dist/evm/transactionParsing.js +28 -147
  43. package/dist/evm/utils.d.ts +0 -46
  44. package/dist/evm/utils.js +1 -57
  45. package/dist/helpers/index.d.ts +0 -4
  46. package/dist/helpers/index.js +8 -44
  47. package/dist/helpers/routeScan.js +0 -1
  48. package/dist/index.js +0 -1
  49. package/dist/old.js +0 -884
  50. package/dist/price.js +0 -1
  51. package/dist/price.types.js +0 -2
  52. package/dist/rate-limiter.d.ts +28 -0
  53. package/dist/rate-limiter.js +95 -0
  54. package/dist/retry-logic.d.ts +14 -0
  55. package/dist/retry-logic.js +120 -0
  56. package/dist/savings/index.js +0 -1
  57. package/dist/savings/saving-manager.d.ts +10 -11
  58. package/dist/savings/saving-manager.js +79 -22
  59. package/dist/savings/savings-operations.d.ts +39 -0
  60. package/dist/savings/savings-operations.js +141 -0
  61. package/dist/savings/smart-savings.d.ts +0 -63
  62. package/dist/savings/smart-savings.js +0 -78
  63. package/dist/savings/types.d.ts +0 -69
  64. package/dist/savings/types.js +0 -7
  65. package/dist/savings/validation.d.ts +9 -0
  66. package/dist/savings/validation.js +85 -0
  67. package/dist/svm/constant.js +0 -1
  68. package/dist/svm/index.js +0 -1
  69. package/dist/svm/svm.d.ts +11 -1
  70. package/dist/svm/svm.js +267 -27
  71. package/dist/svm/transactionParsing.d.ts +0 -7
  72. package/dist/svm/transactionParsing.js +3 -41
  73. package/dist/svm/transactionSender.js +0 -9
  74. package/dist/svm/utils.d.ts +0 -12
  75. package/dist/svm/utils.js +9 -60
  76. package/dist/test.d.ts +0 -4
  77. package/dist/test.js +6 -98
  78. package/dist/transaction-utils.d.ts +38 -0
  79. package/dist/transaction-utils.js +168 -0
  80. package/dist/types.d.ts +36 -0
  81. package/dist/types.js +0 -1
  82. package/dist/utils.js +0 -1
  83. package/dist/vm-validation.d.ts +11 -0
  84. package/dist/vm-validation.js +151 -0
  85. package/dist/vm.d.ts +12 -2
  86. package/dist/vm.js +61 -16
  87. package/dist/walletBip32.js +15 -70
  88. package/package.json +9 -4
  89. package/test-discovery.ts +235 -0
  90. package/test-pocket-discovery.ts +84 -0
  91. package/tsconfig.json +18 -11
  92. package/tsconfig.prod.json +10 -0
  93. package/utils/evm/evm.ts +554 -8
  94. package/utils/rate-limiter.ts +179 -0
  95. package/utils/retry-logic.ts +271 -0
  96. package/utils/savings/EXAMPLES.md +883 -0
  97. package/utils/savings/SECURITY.md +731 -0
  98. package/utils/savings/saving-manager.ts +526 -16
  99. package/utils/savings/savings-operations.ts +509 -0
  100. package/utils/savings/validation.ts +187 -0
  101. package/utils/svm/svm.ts +476 -5
  102. package/utils/test.ts +2 -2
  103. package/utils/transaction-utils.ts +394 -0
  104. package/utils/types.ts +100 -0
  105. package/utils/vm-validation.ts +280 -0
  106. package/utils/vm.ts +197 -10
  107. package/utils/walletBip32.ts +39 -3
  108. package/dist/IChainWallet.js.map +0 -1
  109. package/dist/bip32.d.ts +0 -9
  110. package/dist/bip32.js +0 -172
  111. package/dist/bip32.js.map +0 -1
  112. package/dist/bip32Old.js.map +0 -1
  113. package/dist/bip32Small.js.map +0 -1
  114. package/dist/bipTest.js.map +0 -1
  115. package/dist/constant.js.map +0 -1
  116. package/dist/english.js.map +0 -1
  117. package/dist/evm/SMART_WALLET_EXAMPLES.d.ts +0 -20
  118. package/dist/evm/SMART_WALLET_EXAMPLES.js +0 -451
  119. package/dist/evm/SMART_WALLET_EXAMPLES.js.map +0 -1
  120. package/dist/evm/aa-service/index.js.map +0 -1
  121. package/dist/evm/aa-service/lib/account-adapter.js.map +0 -1
  122. package/dist/evm/aa-service/lib/kernel-account.js.map +0 -1
  123. package/dist/evm/aa-service/lib/kernel-modules.js.map +0 -1
  124. package/dist/evm/aa-service/lib/session-keys.js.map +0 -1
  125. package/dist/evm/aa-service/lib/type.js.map +0 -1
  126. package/dist/evm/aa-service/services/account-abstraction.js.map +0 -1
  127. package/dist/evm/aa-service/services/bundler.js.map +0 -1
  128. package/dist/evm/evm.js.map +0 -1
  129. package/dist/evm/index.js.map +0 -1
  130. package/dist/evm/script.js.map +0 -1
  131. package/dist/evm/smartWallet.js.map +0 -1
  132. package/dist/evm/smartWallet.types.js.map +0 -1
  133. package/dist/evm/transaction.utils.js.map +0 -1
  134. package/dist/evm/transactionParsing.js.map +0 -1
  135. package/dist/evm/utils.js.map +0 -1
  136. package/dist/helpers/index.js.map +0 -1
  137. package/dist/helpers/routeScan.js.map +0 -1
  138. package/dist/index.js.map +0 -1
  139. package/dist/old.js.map +0 -1
  140. package/dist/price.js.map +0 -1
  141. package/dist/price.types.js.map +0 -1
  142. package/dist/privacy/artifact-manager.d.ts +0 -117
  143. package/dist/privacy/artifact-manager.js +0 -251
  144. package/dist/privacy/artifact-manager.js.map +0 -1
  145. package/dist/privacy/broadcaster-client.d.ts +0 -166
  146. package/dist/privacy/broadcaster-client.js +0 -261
  147. package/dist/privacy/broadcaster-client.js.map +0 -1
  148. package/dist/privacy/index.d.ts +0 -34
  149. package/dist/privacy/index.js +0 -56
  150. package/dist/privacy/index.js.map +0 -1
  151. package/dist/privacy/network-config.d.ts +0 -57
  152. package/dist/privacy/network-config.js +0 -118
  153. package/dist/privacy/network-config.js.map +0 -1
  154. package/dist/privacy/poi-helper.d.ts +0 -161
  155. package/dist/privacy/poi-helper.js +0 -249
  156. package/dist/privacy/poi-helper.js.map +0 -1
  157. package/dist/privacy/railgun-engine.d.ts +0 -135
  158. package/dist/privacy/railgun-engine.js +0 -205
  159. package/dist/privacy/railgun-engine.js.map +0 -1
  160. package/dist/privacy/railgun-privacy-wallet.d.ts +0 -288
  161. package/dist/privacy/railgun-privacy-wallet.js +0 -539
  162. package/dist/privacy/railgun-privacy-wallet.js.map +0 -1
  163. package/dist/privacy/types.d.ts +0 -229
  164. package/dist/privacy/types.js +0 -26
  165. package/dist/privacy/types.js.map +0 -1
  166. package/dist/savings/index.js.map +0 -1
  167. package/dist/savings/saving-actions.d.ts +0 -0
  168. package/dist/savings/saving-actions.js +0 -78
  169. package/dist/savings/saving-actions.js.map +0 -1
  170. package/dist/savings/saving-manager.js.map +0 -1
  171. package/dist/savings/savings-manager.d.ts +0 -126
  172. package/dist/savings/savings-manager.js +0 -234
  173. package/dist/savings/savings-manager.js.map +0 -1
  174. package/dist/savings/smart-savings.js.map +0 -1
  175. package/dist/savings/types.js.map +0 -1
  176. package/dist/svm/constant.js.map +0 -1
  177. package/dist/svm/index.js.map +0 -1
  178. package/dist/svm/svm.js.map +0 -1
  179. package/dist/svm/transactionParsing.js.map +0 -1
  180. package/dist/svm/transactionSender.js.map +0 -1
  181. package/dist/svm/utils.js.map +0 -1
  182. package/dist/test.js.map +0 -1
  183. package/dist/types.js.map +0 -1
  184. package/dist/utils.js.map +0 -1
  185. package/dist/vm.js.map +0 -1
  186. 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 = 10000,
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
- static encryptSeedPhrase(seedPhrase: string, password: string) {
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 the salt (needed for decryption)
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
- const key = this.deriveKey(password, salt); // Derive the key using the same salt
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) throw new Error("Decryption failed.");
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
- console.error("Invalid password or corrupted data:", e.message);
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
@@ -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
- const path = `${derivationPath}${index}'`
126
- const scureNode = HDKey.fromMasterSeed(Buffer.from(seed, "hex"))
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
- const privateKey = Buffer.from(child.privateKey!).toString("hex");
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
 
@@ -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;