@opendatalabs/vana-sdk 2.2.0 → 2.2.2
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/dist/config/features.cjs +1 -24
- package/dist/config/features.cjs.map +1 -1
- package/dist/config/features.d.ts +13 -44
- package/dist/config/features.js +1 -24
- package/dist/config/features.js.map +1 -1
- package/dist/crypto/ecies/__tests__/constants.test.d.ts +1 -1
- package/dist/crypto/ecies/__tests__/serialization.test.d.ts +8 -0
- package/dist/crypto/ecies/base.cjs +43 -20
- package/dist/crypto/ecies/base.cjs.map +1 -1
- package/dist/crypto/ecies/base.js +43 -20
- package/dist/crypto/ecies/base.js.map +1 -1
- package/dist/crypto/ecies/constants.cjs +2 -10
- package/dist/crypto/ecies/constants.cjs.map +1 -1
- package/dist/crypto/ecies/constants.d.ts +0 -9
- package/dist/crypto/ecies/constants.js +1 -8
- package/dist/crypto/ecies/constants.js.map +1 -1
- package/dist/crypto/ecies/index.cjs.map +1 -1
- package/dist/crypto/ecies/index.d.ts +10 -2
- package/dist/crypto/ecies/index.js.map +1 -1
- package/dist/crypto/ecies/interface.cjs +19 -2
- package/dist/crypto/ecies/interface.cjs.map +1 -1
- package/dist/crypto/ecies/interface.js +19 -2
- package/dist/crypto/ecies/interface.js.map +1 -1
- package/dist/index.browser.d.ts +2 -0
- package/dist/index.browser.js +10 -0
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +12 -0
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.d.ts +3 -0
- package/dist/index.node.js +12 -0
- package/dist/index.node.js.map +1 -1
- package/dist/platform/browser.cjs +40 -119
- package/dist/platform/browser.cjs.map +1 -1
- package/dist/platform/browser.d.ts +7 -7
- package/dist/platform/browser.js +40 -119
- package/dist/platform/browser.js.map +1 -1
- package/dist/platform/node.cjs +51 -129
- package/dist/platform/node.cjs.map +1 -1
- package/dist/platform/node.d.ts +5 -5
- package/dist/platform/node.js +51 -129
- package/dist/platform/node.js.map +1 -1
- package/package.json +1 -2
- package/dist/types/eccrypto-js.d.cjs +0 -2
- package/dist/types/eccrypto-js.d.cjs.map +0 -1
- package/dist/types/eccrypto-js.d.js +0 -1
- package/dist/types/eccrypto-js.d.js.map +0 -1
package/dist/config/features.cjs
CHANGED
|
@@ -21,30 +21,7 @@ __export(features_exports, {
|
|
|
21
21
|
features: () => features
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(features_exports);
|
|
24
|
-
const features = {
|
|
25
|
-
/**
|
|
26
|
-
* Use custom ECIES implementation instead of eccrypto
|
|
27
|
-
*
|
|
28
|
-
* When false (default): Uses the original eccrypto/eccrypto-js libraries for stability
|
|
29
|
-
* When true: Uses the custom platform-specific ECIES implementation
|
|
30
|
-
*
|
|
31
|
-
* Enable by setting environment variable: VANA_USE_CUSTOM_ECIES=true
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```bash
|
|
35
|
-
* # Use eccrypto-js (default)
|
|
36
|
-
* VANA_USE_CUSTOM_ECIES=false npm run your-app
|
|
37
|
-
*
|
|
38
|
-
* # Use custom ECIES implementation
|
|
39
|
-
* VANA_USE_CUSTOM_ECIES=true npm run your-app
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* @returns Whether to use custom ECIES implementation
|
|
43
|
-
*/
|
|
44
|
-
get useCustomECIES() {
|
|
45
|
-
return process.env.VANA_USE_CUSTOM_ECIES === "true";
|
|
46
|
-
}
|
|
47
|
-
};
|
|
24
|
+
const features = {};
|
|
48
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
49
26
|
0 && (module.exports = {
|
|
50
27
|
features
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/features.ts"],"sourcesContent":["/**\n * Feature flags for the Vana SDK\n *\n * @remarks\n * This module controls feature toggles that allow switching between different\n * implementations or enabling experimental features.\n *\n * The getter pattern is used to allow dynamic evaluation of environment variables,\n * which is necessary for tests to override the default before modules are loaded.\n *\n * ##
|
|
1
|
+
{"version":3,"sources":["../../src/config/features.ts"],"sourcesContent":["/**\n * Feature flags for the Vana SDK\n *\n * @remarks\n * This module controls feature toggles that allow switching between different\n * implementations or enabling experimental features.\n *\n * The getter pattern is used to allow dynamic evaluation of environment variables,\n * which is necessary for tests to override the default before modules are loaded.\n *\n * ## ECIES Encryption\n *\n * The SDK uses a custom ECIES implementation:\n * - **Node.js**: Uses native `secp256k1` module for optimal performance\n * - **Browser**: Uses `@noble/secp256k1` (pure JavaScript)\n * - Fully compatible with eccrypto format for backward compatibility\n *\n * ### Architecture Notes\n * - Browser builds: Use `@noble/secp256k1` (no native dependencies)\n * - Node builds: Use native `secp256k1` when available for better performance\n * - `secp256k1`: Remains an `optionalDependency` so browser-only users don't face build issues\n * - All encrypted data is compatible with the eccrypto format specification\n */\n\n/**\n * Feature flag configuration\n *\n * @remarks\n * Currently empty as all feature flags have been graduated to default behavior.\n * This module is kept for future feature flag additions.\n */\nexport const features = {};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BO,MAAM,WAAW,CAAC;","names":[]}
|
|
@@ -8,55 +8,24 @@
|
|
|
8
8
|
* The getter pattern is used to allow dynamic evaluation of environment variables,
|
|
9
9
|
* which is necessary for tests to override the default before modules are loaded.
|
|
10
10
|
*
|
|
11
|
-
* ##
|
|
11
|
+
* ## ECIES Encryption
|
|
12
12
|
*
|
|
13
|
-
* The SDK
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - Works everywhere (Node.js and browsers)
|
|
18
|
-
* - No native dependencies
|
|
19
|
-
* - Good performance for most use cases
|
|
20
|
-
*
|
|
21
|
-
* 2. **Custom ECIES** (Opt-in)
|
|
22
|
-
* - Uses native `secp256k1` module in Node.js for optimal performance
|
|
23
|
-
* - Uses `@noble/secp256k1` in browsers (pure JS)
|
|
24
|
-
* - Slightly faster in Node.js environments
|
|
25
|
-
* - Currently used by tests to ensure compatibility
|
|
13
|
+
* The SDK uses a custom ECIES implementation:
|
|
14
|
+
* - **Node.js**: Uses native `secp256k1` module for optimal performance
|
|
15
|
+
* - **Browser**: Uses `@noble/secp256k1` (pure JavaScript)
|
|
16
|
+
* - Fully compatible with eccrypto format for backward compatibility
|
|
26
17
|
*
|
|
27
18
|
* ### Architecture Notes
|
|
28
|
-
* - Browser builds:
|
|
29
|
-
* - Node builds:
|
|
19
|
+
* - Browser builds: Use `@noble/secp256k1` (no native dependencies)
|
|
20
|
+
* - Node builds: Use native `secp256k1` when available for better performance
|
|
30
21
|
* - `secp256k1`: Remains an `optionalDependency` so browser-only users don't face build issues
|
|
31
|
-
*
|
|
32
|
-
* ### Future Plans
|
|
33
|
-
* Once the custom ECIES implementation is battle-tested:
|
|
34
|
-
* 1. Make custom ECIES the default
|
|
35
|
-
* 2. Eventually remove eccrypto-js dependency
|
|
36
|
-
* 3. Keep the same architecture (native for Node, @noble for browser)
|
|
22
|
+
* - All encrypted data is compatible with the eccrypto format specification
|
|
37
23
|
*/
|
|
38
24
|
/**
|
|
39
25
|
* Feature flag configuration
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Currently empty as all feature flags have been graduated to default behavior.
|
|
29
|
+
* This module is kept for future feature flag additions.
|
|
40
30
|
*/
|
|
41
|
-
export declare const features: {
|
|
42
|
-
/**
|
|
43
|
-
* Use custom ECIES implementation instead of eccrypto
|
|
44
|
-
*
|
|
45
|
-
* When false (default): Uses the original eccrypto/eccrypto-js libraries for stability
|
|
46
|
-
* When true: Uses the custom platform-specific ECIES implementation
|
|
47
|
-
*
|
|
48
|
-
* Enable by setting environment variable: VANA_USE_CUSTOM_ECIES=true
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```bash
|
|
52
|
-
* # Use eccrypto-js (default)
|
|
53
|
-
* VANA_USE_CUSTOM_ECIES=false npm run your-app
|
|
54
|
-
*
|
|
55
|
-
* # Use custom ECIES implementation
|
|
56
|
-
* VANA_USE_CUSTOM_ECIES=true npm run your-app
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @returns Whether to use custom ECIES implementation
|
|
60
|
-
*/
|
|
61
|
-
readonly useCustomECIES: boolean;
|
|
62
|
-
};
|
|
31
|
+
export declare const features: {};
|
package/dist/config/features.js
CHANGED
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
const features = {
|
|
2
|
-
/**
|
|
3
|
-
* Use custom ECIES implementation instead of eccrypto
|
|
4
|
-
*
|
|
5
|
-
* When false (default): Uses the original eccrypto/eccrypto-js libraries for stability
|
|
6
|
-
* When true: Uses the custom platform-specific ECIES implementation
|
|
7
|
-
*
|
|
8
|
-
* Enable by setting environment variable: VANA_USE_CUSTOM_ECIES=true
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```bash
|
|
12
|
-
* # Use eccrypto-js (default)
|
|
13
|
-
* VANA_USE_CUSTOM_ECIES=false npm run your-app
|
|
14
|
-
*
|
|
15
|
-
* # Use custom ECIES implementation
|
|
16
|
-
* VANA_USE_CUSTOM_ECIES=true npm run your-app
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* @returns Whether to use custom ECIES implementation
|
|
20
|
-
*/
|
|
21
|
-
get useCustomECIES() {
|
|
22
|
-
return process.env.VANA_USE_CUSTOM_ECIES === "true";
|
|
23
|
-
}
|
|
24
|
-
};
|
|
1
|
+
const features = {};
|
|
25
2
|
export {
|
|
26
3
|
features
|
|
27
4
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/features.ts"],"sourcesContent":["/**\n * Feature flags for the Vana SDK\n *\n * @remarks\n * This module controls feature toggles that allow switching between different\n * implementations or enabling experimental features.\n *\n * The getter pattern is used to allow dynamic evaluation of environment variables,\n * which is necessary for tests to override the default before modules are loaded.\n *\n * ##
|
|
1
|
+
{"version":3,"sources":["../../src/config/features.ts"],"sourcesContent":["/**\n * Feature flags for the Vana SDK\n *\n * @remarks\n * This module controls feature toggles that allow switching between different\n * implementations or enabling experimental features.\n *\n * The getter pattern is used to allow dynamic evaluation of environment variables,\n * which is necessary for tests to override the default before modules are loaded.\n *\n * ## ECIES Encryption\n *\n * The SDK uses a custom ECIES implementation:\n * - **Node.js**: Uses native `secp256k1` module for optimal performance\n * - **Browser**: Uses `@noble/secp256k1` (pure JavaScript)\n * - Fully compatible with eccrypto format for backward compatibility\n *\n * ### Architecture Notes\n * - Browser builds: Use `@noble/secp256k1` (no native dependencies)\n * - Node builds: Use native `secp256k1` when available for better performance\n * - `secp256k1`: Remains an `optionalDependency` so browser-only users don't face build issues\n * - All encrypted data is compatible with the eccrypto format specification\n */\n\n/**\n * Feature flag configuration\n *\n * @remarks\n * Currently empty as all feature flags have been graduated to default behavior.\n * This module is kept for future feature flag additions.\n */\nexport const features = {};\n"],"mappings":"AA+BO,MAAM,WAAW,CAAC;","names":[]}
|
|
@@ -36,7 +36,7 @@ class BaseECIESUint8 {
|
|
|
36
36
|
* @throws {ECIESError} If key format is invalid.
|
|
37
37
|
*/
|
|
38
38
|
normalizePublicKey(publicKey) {
|
|
39
|
-
if (BaseECIESUint8.validatedKeys.
|
|
39
|
+
if (BaseECIESUint8.validatedKeys.get(publicKey)) {
|
|
40
40
|
return publicKey;
|
|
41
41
|
}
|
|
42
42
|
if (publicKey.length === import_constants.CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
|
|
@@ -53,12 +53,21 @@ class BaseECIESUint8 {
|
|
|
53
53
|
return publicKey;
|
|
54
54
|
}
|
|
55
55
|
if (publicKey.length === import_constants.CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (publicKey[0] === import_constants.CURVE.PREFIX.COMPRESSED_EVEN || publicKey[0] === import_constants.CURVE.PREFIX.COMPRESSED_ODD) {
|
|
57
|
+
const decompressed = this.decompressPublicKey(publicKey);
|
|
58
|
+
if (!decompressed) {
|
|
59
|
+
throw new import_interface.ECIESError(
|
|
60
|
+
"Failed to decompress public key",
|
|
61
|
+
"INVALID_KEY"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
BaseECIESUint8.validatedKeys.set(decompressed, true);
|
|
65
|
+
return decompressed;
|
|
59
66
|
}
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
throw new import_interface.ECIESError(
|
|
68
|
+
`Invalid compressed public key prefix: expected 0x02 or 0x03, got 0x${publicKey[0].toString(16).padStart(2, "0")}`,
|
|
69
|
+
"INVALID_KEY"
|
|
70
|
+
);
|
|
62
71
|
}
|
|
63
72
|
throw new import_interface.ECIESError(
|
|
64
73
|
`Invalid public key length: ${publicKey.length}`,
|
|
@@ -73,6 +82,11 @@ class BaseECIESUint8 {
|
|
|
73
82
|
* @returns Promise resolving to encrypted data structure
|
|
74
83
|
*/
|
|
75
84
|
async encrypt(publicKey, message) {
|
|
85
|
+
let ephemeralPrivateKey;
|
|
86
|
+
let sharedSecret;
|
|
87
|
+
let kdf;
|
|
88
|
+
let encryptionKey;
|
|
89
|
+
let macKey;
|
|
76
90
|
try {
|
|
77
91
|
if (!(publicKey instanceof Uint8Array)) {
|
|
78
92
|
throw new import_interface.ECIESError("Public key must be a Uint8Array", "INVALID_KEY");
|
|
@@ -87,7 +101,6 @@ class BaseECIESUint8 {
|
|
|
87
101
|
throw new import_interface.ECIESError("Public key cannot be empty", "INVALID_KEY");
|
|
88
102
|
}
|
|
89
103
|
const pubKey = this.normalizePublicKey(publicKey);
|
|
90
|
-
let ephemeralPrivateKey;
|
|
91
104
|
do {
|
|
92
105
|
ephemeralPrivateKey = this.generateRandomBytes(
|
|
93
106
|
import_constants.CURVE.PRIVATE_KEY_LENGTH
|
|
@@ -103,13 +116,13 @@ class BaseECIESUint8 {
|
|
|
103
116
|
"ENCRYPTION_FAILED"
|
|
104
117
|
);
|
|
105
118
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
sharedSecret = this.performECDH(pubKey, ephemeralPrivateKey);
|
|
120
|
+
kdf = this.sha512(sharedSecret);
|
|
121
|
+
encryptionKey = kdf.slice(
|
|
109
122
|
import_constants.KDF.ENCRYPTION_KEY_OFFSET,
|
|
110
123
|
import_constants.KDF.ENCRYPTION_KEY_OFFSET + import_constants.KDF.ENCRYPTION_KEY_LENGTH
|
|
111
124
|
);
|
|
112
|
-
|
|
125
|
+
macKey = kdf.slice(
|
|
113
126
|
import_constants.KDF.MAC_KEY_OFFSET,
|
|
114
127
|
import_constants.KDF.MAC_KEY_OFFSET + import_constants.KDF.MAC_KEY_LENGTH
|
|
115
128
|
);
|
|
@@ -117,9 +130,6 @@ class BaseECIESUint8 {
|
|
|
117
130
|
const ciphertext = await this.aesEncrypt(encryptionKey, iv, message);
|
|
118
131
|
const macData = (0, import_viem.concat)([iv, ephemeralPublicKey, ciphertext]);
|
|
119
132
|
const mac = this.hmacSha256(macKey, macData);
|
|
120
|
-
this.clearBuffer(ephemeralPrivateKey);
|
|
121
|
-
this.clearBuffer(sharedSecret);
|
|
122
|
-
this.clearBuffer(kdf);
|
|
123
133
|
return {
|
|
124
134
|
iv,
|
|
125
135
|
ephemPublicKey: ephemeralPublicKey,
|
|
@@ -133,6 +143,12 @@ class BaseECIESUint8 {
|
|
|
133
143
|
"ENCRYPTION_FAILED",
|
|
134
144
|
error instanceof Error ? error : void 0
|
|
135
145
|
);
|
|
146
|
+
} finally {
|
|
147
|
+
if (ephemeralPrivateKey) this.clearBuffer(ephemeralPrivateKey);
|
|
148
|
+
if (sharedSecret) this.clearBuffer(sharedSecret);
|
|
149
|
+
if (kdf) this.clearBuffer(kdf);
|
|
150
|
+
if (encryptionKey) this.clearBuffer(encryptionKey);
|
|
151
|
+
if (macKey) this.clearBuffer(macKey);
|
|
136
152
|
}
|
|
137
153
|
}
|
|
138
154
|
/**
|
|
@@ -143,6 +159,10 @@ class BaseECIESUint8 {
|
|
|
143
159
|
* @returns Promise resolving to the original plaintext
|
|
144
160
|
*/
|
|
145
161
|
async decrypt(privateKey, encrypted) {
|
|
162
|
+
let sharedSecret;
|
|
163
|
+
let kdf;
|
|
164
|
+
let encryptionKey;
|
|
165
|
+
let macKey;
|
|
146
166
|
try {
|
|
147
167
|
if (!(privateKey instanceof Uint8Array)) {
|
|
148
168
|
throw new import_interface.ECIESError("Private key must be a Uint8Array", "INVALID_KEY");
|
|
@@ -178,13 +198,13 @@ class BaseECIESUint8 {
|
|
|
178
198
|
throw new import_interface.ECIESError("Invalid ephemeral public key", "INVALID_KEY");
|
|
179
199
|
}
|
|
180
200
|
const ephemeralPublicKey = encrypted.ephemPublicKey;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
201
|
+
sharedSecret = this.performECDH(ephemeralPublicKey, privateKey);
|
|
202
|
+
kdf = this.sha512(sharedSecret);
|
|
203
|
+
encryptionKey = kdf.slice(
|
|
184
204
|
import_constants.KDF.ENCRYPTION_KEY_OFFSET,
|
|
185
205
|
import_constants.KDF.ENCRYPTION_KEY_OFFSET + import_constants.KDF.ENCRYPTION_KEY_LENGTH
|
|
186
206
|
);
|
|
187
|
-
|
|
207
|
+
macKey = kdf.slice(
|
|
188
208
|
import_constants.KDF.MAC_KEY_OFFSET,
|
|
189
209
|
import_constants.KDF.MAC_KEY_OFFSET + import_constants.KDF.MAC_KEY_LENGTH
|
|
190
210
|
);
|
|
@@ -202,8 +222,6 @@ class BaseECIESUint8 {
|
|
|
202
222
|
encrypted.iv,
|
|
203
223
|
encrypted.ciphertext
|
|
204
224
|
);
|
|
205
|
-
this.clearBuffer(sharedSecret);
|
|
206
|
-
this.clearBuffer(kdf);
|
|
207
225
|
return decrypted;
|
|
208
226
|
} catch (error) {
|
|
209
227
|
if (error instanceof import_interface.ECIESError) throw error;
|
|
@@ -212,6 +230,11 @@ class BaseECIESUint8 {
|
|
|
212
230
|
"DECRYPTION_FAILED",
|
|
213
231
|
error instanceof Error ? error : void 0
|
|
214
232
|
);
|
|
233
|
+
} finally {
|
|
234
|
+
if (sharedSecret) this.clearBuffer(sharedSecret);
|
|
235
|
+
if (kdf) this.clearBuffer(kdf);
|
|
236
|
+
if (encryptionKey) this.clearBuffer(encryptionKey);
|
|
237
|
+
if (macKey) this.clearBuffer(macKey);
|
|
215
238
|
}
|
|
216
239
|
}
|
|
217
240
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/crypto/ecies/base.ts"],"sourcesContent":["import type { ECIESProvider, ECIESEncrypted } from \"./interface\";\nimport { ECIESError, isECIESEncrypted } from \"./interface\";\nimport { CURVE, CIPHER, KDF } from \"./constants\";\nimport { constantTimeEqual } from \"./utils\";\nimport { concat } from \"viem\";\n\n/**\n * Provides shared ECIES encryption logic across platforms using Uint8Array.\n *\n * @remarks\n * Platform implementations extend this class and provide crypto primitives.\n * The base class handles the ECIES protocol flow while maintaining\n * compatibility with the eccrypto data format.\n *\n * **Implementation details:**\n * - KDF: SHA-512(shared_secret) → encKey (32B) || macKey (32B)\n * - Cipher: AES-256-CBC with random 16-byte IV\n * - MAC: HMAC-SHA256(macKey, iv || ephemPublicKey || ciphertext)\n *\n * @category Cryptography\n */\nexport abstract class BaseECIESUint8 implements ECIESProvider {\n // Cache for validated public keys to avoid repeated validation\n private static readonly validatedKeys = new WeakMap<Uint8Array, boolean>();\n\n /**\n * Generates cryptographically secure random bytes.\n *\n * @param length - Number of random bytes to generate.\n * @returns Random bytes array.\n */\n protected abstract generateRandomBytes(length: number): Uint8Array;\n\n /**\n * Verifies a private key is valid for secp256k1.\n *\n * @param privateKey - Private key to verify (32 bytes).\n * @returns `true` if valid private key.\n */\n protected abstract verifyPrivateKey(privateKey: Uint8Array): boolean;\n\n /**\n * Creates a public key from a private key.\n *\n * @param privateKey - Source private key (32 bytes).\n * @param compressed - Generate compressed (33B) or uncompressed (65B) format.\n * @returns Public key or `null` if creation failed.\n */\n protected abstract createPublicKey(\n privateKey: Uint8Array,\n compressed: boolean,\n ): Uint8Array | null;\n\n /**\n * Validates a public key on the secp256k1 curve.\n *\n * @param publicKey - Public key to validate.\n * @returns `true` if valid public key.\n */\n protected abstract validatePublicKey(publicKey: Uint8Array): boolean;\n\n /**\n * Decompresses a compressed public key.\n *\n * @param publicKey - Compressed public key (33 bytes).\n * @returns Uncompressed public key (65 bytes) or `null` if decompression failed.\n */\n protected abstract decompressPublicKey(\n publicKey: Uint8Array,\n ): Uint8Array | null;\n\n /**\n * Performs ECDH key agreement.\n *\n * @param publicKey - Other party's public key.\n * @param privateKey - Your private key.\n * @returns Raw X coordinate of shared point (32 bytes).\n */\n protected abstract performECDH(\n publicKey: Uint8Array,\n privateKey: Uint8Array,\n ): Uint8Array;\n\n /**\n * Computes SHA-512 hash.\n *\n * @param data - Data to hash.\n * @returns SHA-512 hash (64 bytes).\n */\n protected abstract sha512(data: Uint8Array): Uint8Array;\n\n /**\n * Computes HMAC-SHA256 authentication tag.\n *\n * @param key - HMAC key.\n * @param data - Data to authenticate.\n * @returns HMAC-SHA256 (32 bytes).\n */\n protected abstract hmacSha256(key: Uint8Array, data: Uint8Array): Uint8Array;\n\n /**\n * Encrypts data using AES-256-CBC.\n *\n * @param key - Encryption key (32 bytes).\n * @param iv - Initialization vector (16 bytes).\n * @param plaintext - Data to encrypt.\n * @returns Ciphertext with PKCS#7 padding.\n */\n protected abstract aesEncrypt(\n key: Uint8Array,\n iv: Uint8Array,\n plaintext: Uint8Array,\n ): Promise<Uint8Array>;\n\n /**\n * Decrypts data using AES-256-CBC.\n *\n * @param key - Decryption key (32 bytes).\n * @param iv - Initialization vector (16 bytes).\n * @param ciphertext - Data to decrypt.\n * @returns Plaintext with padding removed.\n */\n protected abstract aesDecrypt(\n key: Uint8Array,\n iv: Uint8Array,\n ciphertext: Uint8Array,\n ): Promise<Uint8Array>;\n\n /**\n * Normalizes a public key to uncompressed format.\n *\n * @param publicKey - Public key in any format.\n * @returns Uncompressed public key (65 bytes).\n * @throws {ECIESError} If key format is invalid.\n */\n protected normalizePublicKey(publicKey: Uint8Array): Uint8Array {\n // Check cache first\n if (BaseECIESUint8.validatedKeys.has(publicKey)) {\n return publicKey;\n }\n\n if (publicKey.length === CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {\n if (publicKey[0] !== CURVE.PREFIX.UNCOMPRESSED) {\n throw new ECIESError(\n \"Invalid uncompressed public key prefix\",\n \"INVALID_KEY\",\n );\n }\n // Validate and cache\n if (!this.validatePublicKey(publicKey)) {\n throw new ECIESError(\"Invalid public key\", \"INVALID_KEY\");\n }\n BaseECIESUint8.validatedKeys.set(publicKey, true);\n return publicKey;\n }\n\n if (publicKey.length === CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) {\n const decompressed = this.decompressPublicKey(publicKey);\n if (!decompressed) {\n throw new ECIESError(\"Failed to decompress public key\", \"INVALID_KEY\");\n }\n // Cache the decompressed key\n BaseECIESUint8.validatedKeys.set(decompressed, true);\n return decompressed;\n }\n\n throw new ECIESError(\n `Invalid public key length: ${publicKey.length}`,\n \"INVALID_KEY\",\n );\n }\n\n /**\n * Normalizes a public key to uncompressed format (65 bytes with 0x04 prefix).\n * Must be implemented by derived classes to handle platform-specific operations.\n *\n * @param publicKey - The public key to normalize\n * @returns The normalized uncompressed public key\n */\n public abstract normalizeToUncompressed(publicKey: Uint8Array): Uint8Array;\n\n /**\n * Encrypts data using ECIES.\n *\n * @param publicKey - The recipient's public key (compressed or uncompressed)\n * @param message - The data to encrypt\n * @returns Promise resolving to encrypted data structure\n */\n async encrypt(\n publicKey: Uint8Array,\n message: Uint8Array,\n ): Promise<ECIESEncrypted> {\n try {\n // Validate inputs\n if (!(publicKey instanceof Uint8Array)) {\n throw new ECIESError(\"Public key must be a Uint8Array\", \"INVALID_KEY\");\n }\n if (!(message instanceof Uint8Array)) {\n throw new ECIESError(\n \"Message must be a Uint8Array\",\n \"ENCRYPTION_FAILED\",\n );\n }\n if (publicKey.length === 0) {\n throw new ECIESError(\"Public key cannot be empty\", \"INVALID_KEY\");\n }\n\n // Normalize public key to uncompressed format\n const pubKey = this.normalizePublicKey(publicKey);\n\n // Generate ephemeral key pair\n let ephemeralPrivateKey: Uint8Array;\n do {\n ephemeralPrivateKey = this.generateRandomBytes(\n CURVE.PRIVATE_KEY_LENGTH,\n );\n } while (!this.verifyPrivateKey(ephemeralPrivateKey));\n\n const ephemeralPublicKey = this.createPublicKey(\n ephemeralPrivateKey,\n false,\n );\n if (!ephemeralPublicKey) {\n throw new ECIESError(\n \"Failed to generate ephemeral public key\",\n \"ENCRYPTION_FAILED\",\n );\n }\n\n // Perform ECDH to get shared secret (raw X coordinate)\n const sharedSecret = this.performECDH(pubKey, ephemeralPrivateKey);\n\n // Derive keys using SHA-512 (eccrypto-compatible KDF)\n const kdf = this.sha512(sharedSecret);\n const encryptionKey = kdf.slice(\n KDF.ENCRYPTION_KEY_OFFSET,\n KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH,\n );\n const macKey = kdf.slice(\n KDF.MAC_KEY_OFFSET,\n KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH,\n );\n\n // Generate random IV and encrypt\n const iv = this.generateRandomBytes(CIPHER.IV_LENGTH);\n const ciphertext = await this.aesEncrypt(encryptionKey, iv, message);\n\n // Calculate MAC (Encrypt-then-MAC)\n const macData = concat([iv, ephemeralPublicKey, ciphertext]);\n const mac = this.hmacSha256(macKey, macData);\n\n // Clear sensitive data\n this.clearBuffer(ephemeralPrivateKey);\n this.clearBuffer(sharedSecret);\n this.clearBuffer(kdf);\n\n return {\n iv,\n ephemPublicKey: ephemeralPublicKey,\n ciphertext,\n mac,\n };\n } catch (error) {\n if (error instanceof ECIESError) throw error;\n throw new ECIESError(\n `Encryption failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n \"ENCRYPTION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Decrypts ECIES encrypted data.\n *\n * @param privateKey - The recipient's private key (32 bytes)\n * @param encrypted - The encrypted data structure from encrypt()\n * @returns Promise resolving to the original plaintext\n */\n async decrypt(\n privateKey: Uint8Array,\n encrypted: ECIESEncrypted,\n ): Promise<Uint8Array> {\n try {\n // Validate inputs\n if (!(privateKey instanceof Uint8Array)) {\n throw new ECIESError(\"Private key must be a Uint8Array\", \"INVALID_KEY\");\n }\n if (!isECIESEncrypted(encrypted)) {\n throw new ECIESError(\n \"Invalid encrypted data structure\",\n \"DECRYPTION_FAILED\",\n );\n }\n if (privateKey.length !== CURVE.PRIVATE_KEY_LENGTH) {\n throw new ECIESError(\n `Invalid private key length: ${privateKey.length}`,\n \"INVALID_KEY\",\n );\n }\n if (!this.verifyPrivateKey(privateKey)) {\n throw new ECIESError(\"Invalid private key\", \"INVALID_KEY\");\n }\n\n // Strict validation: ephemeral keys must be 65-byte uncompressed (eccrypto standard)\n if (\n encrypted.ephemPublicKey.length !== CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH\n ) {\n throw new ECIESError(\n `Invalid ephemeral public key: expected ${CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH} bytes (uncompressed), got ${encrypted.ephemPublicKey.length} bytes`,\n \"INVALID_KEY\",\n );\n }\n if (encrypted.ephemPublicKey[0] !== CURVE.PREFIX.UNCOMPRESSED) {\n throw new ECIESError(\n \"Invalid ephemeral public key: must be uncompressed format with 0x04 prefix (eccrypto standard)\",\n \"INVALID_KEY\",\n );\n }\n if (!this.validatePublicKey(encrypted.ephemPublicKey)) {\n throw new ECIESError(\"Invalid ephemeral public key\", \"INVALID_KEY\");\n }\n const ephemeralPublicKey = encrypted.ephemPublicKey;\n\n // Perform ECDH to recover shared secret\n const sharedSecret = this.performECDH(ephemeralPublicKey, privateKey);\n\n // Derive keys using SHA-512 (eccrypto-compatible KDF)\n const kdf = this.sha512(sharedSecret);\n const encryptionKey = kdf.slice(\n KDF.ENCRYPTION_KEY_OFFSET,\n KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH,\n );\n const macKey = kdf.slice(\n KDF.MAC_KEY_OFFSET,\n KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH,\n );\n\n // Verify MAC before decryption (Encrypt-then-MAC)\n const macData = concat([\n encrypted.iv,\n encrypted.ephemPublicKey,\n encrypted.ciphertext,\n ]);\n const expectedMac = this.hmacSha256(macKey, macData);\n\n if (!constantTimeEqual(encrypted.mac, expectedMac)) {\n throw new ECIESError(\"MAC verification failed\", \"MAC_MISMATCH\");\n }\n\n // Decrypt the ciphertext\n const decrypted = await this.aesDecrypt(\n encryptionKey,\n encrypted.iv,\n encrypted.ciphertext,\n );\n\n // Clear sensitive data\n this.clearBuffer(sharedSecret);\n this.clearBuffer(kdf);\n\n return decrypted;\n } catch (error) {\n if (error instanceof ECIESError) throw error;\n throw new ECIESError(\n `Decryption failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n \"DECRYPTION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n }\n }\n\n /**\n * Clears sensitive data from memory using multi-pass overwrite.\n *\n * @remarks\n * Uses multiple passes with different patterns to make it harder\n * for JIT compilers to optimize away the operation. While not\n * guaranteed in JavaScript, this is a best-effort approach to\n * clear sensitive data from memory.\n *\n * @param buffer - The buffer to clear\n */\n protected clearBuffer(buffer: Uint8Array): void {\n if (buffer && buffer.length > 0) {\n // Multi-pass overwrite to resist compiler optimization\n buffer.fill(0x00); // Fill with zeros\n buffer.fill(0xff); // Fill with ones\n buffer.fill(0xaa); // Fill with alternating pattern\n buffer.fill(0x00); // Final zero fill\n\n // Additional pattern write to further discourage optimization\n for (let i = 0; i < buffer.length; i++) {\n buffer[i] = (i & 0xff) ^ 0x5a; // XOR with pattern\n }\n buffer.fill(0x00); // Final clear\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/crypto/ecies/base.ts"],"sourcesContent":["import type { ECIESProvider, ECIESEncrypted } from \"./interface\";\nimport { ECIESError, isECIESEncrypted } from \"./interface\";\nimport { CURVE, CIPHER, KDF } from \"./constants\";\nimport { constantTimeEqual } from \"./utils\";\nimport { concat } from \"viem\";\n\n/**\n * Provides shared ECIES encryption logic across platforms using Uint8Array.\n *\n * @remarks\n * Platform implementations extend this class and provide crypto primitives.\n * The base class handles the ECIES protocol flow while maintaining\n * compatibility with the eccrypto data format.\n *\n * **Implementation details:**\n * - KDF: SHA-512(shared_secret) → encKey (32B) || macKey (32B)\n * - Cipher: AES-256-CBC with random 16-byte IV\n * - MAC: HMAC-SHA256(macKey, iv || ephemPublicKey || ciphertext)\n *\n * @category Cryptography\n */\nexport abstract class BaseECIESUint8 implements ECIESProvider {\n // Cache for validated public keys to avoid repeated validation\n private static readonly validatedKeys = new WeakMap<Uint8Array, boolean>();\n\n /**\n * Generates cryptographically secure random bytes.\n *\n * @param length - Number of random bytes to generate.\n * @returns Random bytes array.\n */\n protected abstract generateRandomBytes(length: number): Uint8Array;\n\n /**\n * Verifies a private key is valid for secp256k1.\n *\n * @param privateKey - Private key to verify (32 bytes).\n * @returns `true` if valid private key.\n */\n protected abstract verifyPrivateKey(privateKey: Uint8Array): boolean;\n\n /**\n * Creates a public key from a private key.\n *\n * @param privateKey - Source private key (32 bytes).\n * @param compressed - Generate compressed (33B) or uncompressed (65B) format.\n * @returns Public key or `null` if creation failed.\n */\n protected abstract createPublicKey(\n privateKey: Uint8Array,\n compressed: boolean,\n ): Uint8Array | null;\n\n /**\n * Validates a public key on the secp256k1 curve.\n *\n * @param publicKey - Public key to validate.\n * @returns `true` if valid public key.\n */\n protected abstract validatePublicKey(publicKey: Uint8Array): boolean;\n\n /**\n * Decompresses a compressed public key.\n *\n * @param publicKey - Compressed public key (33 bytes).\n * @returns Uncompressed public key (65 bytes) or `null` if decompression failed.\n */\n protected abstract decompressPublicKey(\n publicKey: Uint8Array,\n ): Uint8Array | null;\n\n /**\n * Performs ECDH key agreement.\n *\n * @param publicKey - Other party's public key.\n * @param privateKey - Your private key.\n * @returns Raw X coordinate of shared point (32 bytes).\n */\n protected abstract performECDH(\n publicKey: Uint8Array,\n privateKey: Uint8Array,\n ): Uint8Array;\n\n /**\n * Computes SHA-512 hash.\n *\n * @param data - Data to hash.\n * @returns SHA-512 hash (64 bytes).\n */\n protected abstract sha512(data: Uint8Array): Uint8Array;\n\n /**\n * Computes HMAC-SHA256 authentication tag.\n *\n * @param key - HMAC key.\n * @param data - Data to authenticate.\n * @returns HMAC-SHA256 (32 bytes).\n */\n protected abstract hmacSha256(key: Uint8Array, data: Uint8Array): Uint8Array;\n\n /**\n * Encrypts data using AES-256-CBC.\n *\n * @param key - Encryption key (32 bytes).\n * @param iv - Initialization vector (16 bytes).\n * @param plaintext - Data to encrypt.\n * @returns Ciphertext with PKCS#7 padding.\n */\n protected abstract aesEncrypt(\n key: Uint8Array,\n iv: Uint8Array,\n plaintext: Uint8Array,\n ): Promise<Uint8Array>;\n\n /**\n * Decrypts data using AES-256-CBC.\n *\n * @param key - Decryption key (32 bytes).\n * @param iv - Initialization vector (16 bytes).\n * @param ciphertext - Data to decrypt.\n * @returns Plaintext with padding removed.\n */\n protected abstract aesDecrypt(\n key: Uint8Array,\n iv: Uint8Array,\n ciphertext: Uint8Array,\n ): Promise<Uint8Array>;\n\n /**\n * Normalizes a public key to uncompressed format.\n *\n * @param publicKey - Public key in any format.\n * @returns Uncompressed public key (65 bytes).\n * @throws {ECIESError} If key format is invalid.\n */\n protected normalizePublicKey(publicKey: Uint8Array): Uint8Array {\n // Check cache first\n if (BaseECIESUint8.validatedKeys.get(publicKey)) {\n return publicKey;\n }\n\n if (publicKey.length === CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {\n if (publicKey[0] !== CURVE.PREFIX.UNCOMPRESSED) {\n throw new ECIESError(\n \"Invalid uncompressed public key prefix\",\n \"INVALID_KEY\",\n );\n }\n // Validate and cache\n if (!this.validatePublicKey(publicKey)) {\n throw new ECIESError(\"Invalid public key\", \"INVALID_KEY\");\n }\n BaseECIESUint8.validatedKeys.set(publicKey, true);\n return publicKey;\n }\n\n if (publicKey.length === CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) {\n if (\n publicKey[0] === CURVE.PREFIX.COMPRESSED_EVEN ||\n publicKey[0] === CURVE.PREFIX.COMPRESSED_ODD\n ) {\n const decompressed = this.decompressPublicKey(publicKey);\n if (!decompressed) {\n throw new ECIESError(\n \"Failed to decompress public key\",\n \"INVALID_KEY\",\n );\n }\n // Cache the decompressed key\n BaseECIESUint8.validatedKeys.set(decompressed, true);\n return decompressed;\n }\n throw new ECIESError(\n `Invalid compressed public key prefix: expected 0x02 or 0x03, got 0x${publicKey[0].toString(16).padStart(2, \"0\")}`,\n \"INVALID_KEY\",\n );\n }\n\n throw new ECIESError(\n `Invalid public key length: ${publicKey.length}`,\n \"INVALID_KEY\",\n );\n }\n\n /**\n * Normalizes a public key to uncompressed format (65 bytes with 0x04 prefix).\n * Must be implemented by derived classes to handle platform-specific operations.\n *\n * @param publicKey - The public key to normalize\n * @returns The normalized uncompressed public key\n */\n public abstract normalizeToUncompressed(publicKey: Uint8Array): Uint8Array;\n\n /**\n * Encrypts data using ECIES.\n *\n * @param publicKey - The recipient's public key (compressed or uncompressed)\n * @param message - The data to encrypt\n * @returns Promise resolving to encrypted data structure\n */\n async encrypt(\n publicKey: Uint8Array,\n message: Uint8Array,\n ): Promise<ECIESEncrypted> {\n // Declare sensitive variables outside try so finally can access them\n let ephemeralPrivateKey: Uint8Array | undefined;\n let sharedSecret: Uint8Array | undefined;\n let kdf: Uint8Array | undefined;\n let encryptionKey: Uint8Array | undefined;\n let macKey: Uint8Array | undefined;\n\n try {\n // Validate inputs\n if (!(publicKey instanceof Uint8Array)) {\n throw new ECIESError(\"Public key must be a Uint8Array\", \"INVALID_KEY\");\n }\n if (!(message instanceof Uint8Array)) {\n throw new ECIESError(\n \"Message must be a Uint8Array\",\n \"ENCRYPTION_FAILED\",\n );\n }\n if (publicKey.length === 0) {\n throw new ECIESError(\"Public key cannot be empty\", \"INVALID_KEY\");\n }\n\n // Normalize public key to uncompressed format\n const pubKey = this.normalizePublicKey(publicKey);\n\n // Generate ephemeral key pair\n do {\n ephemeralPrivateKey = this.generateRandomBytes(\n CURVE.PRIVATE_KEY_LENGTH,\n );\n } while (!this.verifyPrivateKey(ephemeralPrivateKey));\n\n const ephemeralPublicKey = this.createPublicKey(\n ephemeralPrivateKey,\n false,\n );\n if (!ephemeralPublicKey) {\n throw new ECIESError(\n \"Failed to generate ephemeral public key\",\n \"ENCRYPTION_FAILED\",\n );\n }\n\n // Perform ECDH to get shared secret (raw X coordinate)\n sharedSecret = this.performECDH(pubKey, ephemeralPrivateKey);\n\n // Derive keys using SHA-512 (eccrypto-compatible KDF)\n kdf = this.sha512(sharedSecret);\n encryptionKey = kdf.slice(\n KDF.ENCRYPTION_KEY_OFFSET,\n KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH,\n );\n macKey = kdf.slice(\n KDF.MAC_KEY_OFFSET,\n KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH,\n );\n\n // Generate random IV and encrypt\n const iv = this.generateRandomBytes(CIPHER.IV_LENGTH);\n const ciphertext = await this.aesEncrypt(encryptionKey, iv, message);\n\n // Calculate MAC (Encrypt-then-MAC)\n const macData = concat([iv, ephemeralPublicKey, ciphertext]);\n const mac = this.hmacSha256(macKey, macData);\n\n return {\n iv,\n ephemPublicKey: ephemeralPublicKey,\n ciphertext,\n mac,\n };\n } catch (error) {\n if (error instanceof ECIESError) throw error;\n throw new ECIESError(\n `Encryption failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n \"ENCRYPTION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n } finally {\n // Clear sensitive data on all code paths (success, error, throw)\n if (ephemeralPrivateKey) this.clearBuffer(ephemeralPrivateKey);\n if (sharedSecret) this.clearBuffer(sharedSecret);\n if (kdf) this.clearBuffer(kdf);\n if (encryptionKey) this.clearBuffer(encryptionKey);\n if (macKey) this.clearBuffer(macKey);\n }\n }\n\n /**\n * Decrypts ECIES encrypted data.\n *\n * @param privateKey - The recipient's private key (32 bytes)\n * @param encrypted - The encrypted data structure from encrypt()\n * @returns Promise resolving to the original plaintext\n */\n async decrypt(\n privateKey: Uint8Array,\n encrypted: ECIESEncrypted,\n ): Promise<Uint8Array> {\n // Declare sensitive variables outside try so finally can access them\n let sharedSecret: Uint8Array | undefined;\n let kdf: Uint8Array | undefined;\n let encryptionKey: Uint8Array | undefined;\n let macKey: Uint8Array | undefined;\n\n try {\n // Validate inputs\n if (!(privateKey instanceof Uint8Array)) {\n throw new ECIESError(\"Private key must be a Uint8Array\", \"INVALID_KEY\");\n }\n if (!isECIESEncrypted(encrypted)) {\n throw new ECIESError(\n \"Invalid encrypted data structure\",\n \"DECRYPTION_FAILED\",\n );\n }\n if (privateKey.length !== CURVE.PRIVATE_KEY_LENGTH) {\n throw new ECIESError(\n `Invalid private key length: ${privateKey.length}`,\n \"INVALID_KEY\",\n );\n }\n if (!this.verifyPrivateKey(privateKey)) {\n throw new ECIESError(\"Invalid private key\", \"INVALID_KEY\");\n }\n\n // Strict validation: ephemeral keys must be 65-byte uncompressed (eccrypto standard)\n if (\n encrypted.ephemPublicKey.length !== CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH\n ) {\n throw new ECIESError(\n `Invalid ephemeral public key: expected ${CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH} bytes (uncompressed), got ${encrypted.ephemPublicKey.length} bytes`,\n \"INVALID_KEY\",\n );\n }\n if (encrypted.ephemPublicKey[0] !== CURVE.PREFIX.UNCOMPRESSED) {\n throw new ECIESError(\n \"Invalid ephemeral public key: must be uncompressed format with 0x04 prefix (eccrypto standard)\",\n \"INVALID_KEY\",\n );\n }\n if (!this.validatePublicKey(encrypted.ephemPublicKey)) {\n throw new ECIESError(\"Invalid ephemeral public key\", \"INVALID_KEY\");\n }\n const ephemeralPublicKey = encrypted.ephemPublicKey;\n\n // Perform ECDH to recover shared secret\n sharedSecret = this.performECDH(ephemeralPublicKey, privateKey);\n\n // Derive keys using SHA-512 (eccrypto-compatible KDF)\n kdf = this.sha512(sharedSecret);\n encryptionKey = kdf.slice(\n KDF.ENCRYPTION_KEY_OFFSET,\n KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH,\n );\n macKey = kdf.slice(\n KDF.MAC_KEY_OFFSET,\n KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH,\n );\n\n // Verify MAC before decryption (Encrypt-then-MAC)\n const macData = concat([\n encrypted.iv,\n encrypted.ephemPublicKey,\n encrypted.ciphertext,\n ]);\n const expectedMac = this.hmacSha256(macKey, macData);\n\n if (!constantTimeEqual(encrypted.mac, expectedMac)) {\n throw new ECIESError(\"MAC verification failed\", \"MAC_MISMATCH\");\n }\n\n // Decrypt the ciphertext\n const decrypted = await this.aesDecrypt(\n encryptionKey,\n encrypted.iv,\n encrypted.ciphertext,\n );\n\n return decrypted;\n } catch (error) {\n if (error instanceof ECIESError) throw error;\n throw new ECIESError(\n `Decryption failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n \"DECRYPTION_FAILED\",\n error instanceof Error ? error : undefined,\n );\n } finally {\n // Clear sensitive data on all code paths (success, error, throw)\n if (sharedSecret) this.clearBuffer(sharedSecret);\n if (kdf) this.clearBuffer(kdf);\n if (encryptionKey) this.clearBuffer(encryptionKey);\n if (macKey) this.clearBuffer(macKey);\n }\n }\n\n /**\n * Clears sensitive data from memory using multi-pass overwrite.\n *\n * @remarks\n * Uses multiple passes with different patterns to make it harder\n * for JIT compilers to optimize away the operation. While not\n * guaranteed in JavaScript, this is a best-effort approach to\n * clear sensitive data from memory.\n *\n * @param buffer - The buffer to clear\n */\n protected clearBuffer(buffer: Uint8Array): void {\n if (buffer && buffer.length > 0) {\n // Multi-pass overwrite to resist compiler optimization\n buffer.fill(0x00); // Fill with zeros\n buffer.fill(0xff); // Fill with ones\n buffer.fill(0xaa); // Fill with alternating pattern\n buffer.fill(0x00); // Final zero fill\n\n // Additional pattern write to further discourage optimization\n for (let i = 0; i < buffer.length; i++) {\n buffer[i] = (i & 0xff) ^ 0x5a; // XOR with pattern\n }\n buffer.fill(0x00); // Final clear\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -13,7 +13,7 @@ class BaseECIESUint8 {
|
|
|
13
13
|
* @throws {ECIESError} If key format is invalid.
|
|
14
14
|
*/
|
|
15
15
|
normalizePublicKey(publicKey) {
|
|
16
|
-
if (BaseECIESUint8.validatedKeys.
|
|
16
|
+
if (BaseECIESUint8.validatedKeys.get(publicKey)) {
|
|
17
17
|
return publicKey;
|
|
18
18
|
}
|
|
19
19
|
if (publicKey.length === CURVE.UNCOMPRESSED_PUBLIC_KEY_LENGTH) {
|
|
@@ -30,12 +30,21 @@ class BaseECIESUint8 {
|
|
|
30
30
|
return publicKey;
|
|
31
31
|
}
|
|
32
32
|
if (publicKey.length === CURVE.COMPRESSED_PUBLIC_KEY_LENGTH) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (publicKey[0] === CURVE.PREFIX.COMPRESSED_EVEN || publicKey[0] === CURVE.PREFIX.COMPRESSED_ODD) {
|
|
34
|
+
const decompressed = this.decompressPublicKey(publicKey);
|
|
35
|
+
if (!decompressed) {
|
|
36
|
+
throw new ECIESError(
|
|
37
|
+
"Failed to decompress public key",
|
|
38
|
+
"INVALID_KEY"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
BaseECIESUint8.validatedKeys.set(decompressed, true);
|
|
42
|
+
return decompressed;
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
throw new ECIESError(
|
|
45
|
+
`Invalid compressed public key prefix: expected 0x02 or 0x03, got 0x${publicKey[0].toString(16).padStart(2, "0")}`,
|
|
46
|
+
"INVALID_KEY"
|
|
47
|
+
);
|
|
39
48
|
}
|
|
40
49
|
throw new ECIESError(
|
|
41
50
|
`Invalid public key length: ${publicKey.length}`,
|
|
@@ -50,6 +59,11 @@ class BaseECIESUint8 {
|
|
|
50
59
|
* @returns Promise resolving to encrypted data structure
|
|
51
60
|
*/
|
|
52
61
|
async encrypt(publicKey, message) {
|
|
62
|
+
let ephemeralPrivateKey;
|
|
63
|
+
let sharedSecret;
|
|
64
|
+
let kdf;
|
|
65
|
+
let encryptionKey;
|
|
66
|
+
let macKey;
|
|
53
67
|
try {
|
|
54
68
|
if (!(publicKey instanceof Uint8Array)) {
|
|
55
69
|
throw new ECIESError("Public key must be a Uint8Array", "INVALID_KEY");
|
|
@@ -64,7 +78,6 @@ class BaseECIESUint8 {
|
|
|
64
78
|
throw new ECIESError("Public key cannot be empty", "INVALID_KEY");
|
|
65
79
|
}
|
|
66
80
|
const pubKey = this.normalizePublicKey(publicKey);
|
|
67
|
-
let ephemeralPrivateKey;
|
|
68
81
|
do {
|
|
69
82
|
ephemeralPrivateKey = this.generateRandomBytes(
|
|
70
83
|
CURVE.PRIVATE_KEY_LENGTH
|
|
@@ -80,13 +93,13 @@ class BaseECIESUint8 {
|
|
|
80
93
|
"ENCRYPTION_FAILED"
|
|
81
94
|
);
|
|
82
95
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
sharedSecret = this.performECDH(pubKey, ephemeralPrivateKey);
|
|
97
|
+
kdf = this.sha512(sharedSecret);
|
|
98
|
+
encryptionKey = kdf.slice(
|
|
86
99
|
KDF.ENCRYPTION_KEY_OFFSET,
|
|
87
100
|
KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH
|
|
88
101
|
);
|
|
89
|
-
|
|
102
|
+
macKey = kdf.slice(
|
|
90
103
|
KDF.MAC_KEY_OFFSET,
|
|
91
104
|
KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH
|
|
92
105
|
);
|
|
@@ -94,9 +107,6 @@ class BaseECIESUint8 {
|
|
|
94
107
|
const ciphertext = await this.aesEncrypt(encryptionKey, iv, message);
|
|
95
108
|
const macData = concat([iv, ephemeralPublicKey, ciphertext]);
|
|
96
109
|
const mac = this.hmacSha256(macKey, macData);
|
|
97
|
-
this.clearBuffer(ephemeralPrivateKey);
|
|
98
|
-
this.clearBuffer(sharedSecret);
|
|
99
|
-
this.clearBuffer(kdf);
|
|
100
110
|
return {
|
|
101
111
|
iv,
|
|
102
112
|
ephemPublicKey: ephemeralPublicKey,
|
|
@@ -110,6 +120,12 @@ class BaseECIESUint8 {
|
|
|
110
120
|
"ENCRYPTION_FAILED",
|
|
111
121
|
error instanceof Error ? error : void 0
|
|
112
122
|
);
|
|
123
|
+
} finally {
|
|
124
|
+
if (ephemeralPrivateKey) this.clearBuffer(ephemeralPrivateKey);
|
|
125
|
+
if (sharedSecret) this.clearBuffer(sharedSecret);
|
|
126
|
+
if (kdf) this.clearBuffer(kdf);
|
|
127
|
+
if (encryptionKey) this.clearBuffer(encryptionKey);
|
|
128
|
+
if (macKey) this.clearBuffer(macKey);
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
/**
|
|
@@ -120,6 +136,10 @@ class BaseECIESUint8 {
|
|
|
120
136
|
* @returns Promise resolving to the original plaintext
|
|
121
137
|
*/
|
|
122
138
|
async decrypt(privateKey, encrypted) {
|
|
139
|
+
let sharedSecret;
|
|
140
|
+
let kdf;
|
|
141
|
+
let encryptionKey;
|
|
142
|
+
let macKey;
|
|
123
143
|
try {
|
|
124
144
|
if (!(privateKey instanceof Uint8Array)) {
|
|
125
145
|
throw new ECIESError("Private key must be a Uint8Array", "INVALID_KEY");
|
|
@@ -155,13 +175,13 @@ class BaseECIESUint8 {
|
|
|
155
175
|
throw new ECIESError("Invalid ephemeral public key", "INVALID_KEY");
|
|
156
176
|
}
|
|
157
177
|
const ephemeralPublicKey = encrypted.ephemPublicKey;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
sharedSecret = this.performECDH(ephemeralPublicKey, privateKey);
|
|
179
|
+
kdf = this.sha512(sharedSecret);
|
|
180
|
+
encryptionKey = kdf.slice(
|
|
161
181
|
KDF.ENCRYPTION_KEY_OFFSET,
|
|
162
182
|
KDF.ENCRYPTION_KEY_OFFSET + KDF.ENCRYPTION_KEY_LENGTH
|
|
163
183
|
);
|
|
164
|
-
|
|
184
|
+
macKey = kdf.slice(
|
|
165
185
|
KDF.MAC_KEY_OFFSET,
|
|
166
186
|
KDF.MAC_KEY_OFFSET + KDF.MAC_KEY_LENGTH
|
|
167
187
|
);
|
|
@@ -179,8 +199,6 @@ class BaseECIESUint8 {
|
|
|
179
199
|
encrypted.iv,
|
|
180
200
|
encrypted.ciphertext
|
|
181
201
|
);
|
|
182
|
-
this.clearBuffer(sharedSecret);
|
|
183
|
-
this.clearBuffer(kdf);
|
|
184
202
|
return decrypted;
|
|
185
203
|
} catch (error) {
|
|
186
204
|
if (error instanceof ECIESError) throw error;
|
|
@@ -189,6 +207,11 @@ class BaseECIESUint8 {
|
|
|
189
207
|
"DECRYPTION_FAILED",
|
|
190
208
|
error instanceof Error ? error : void 0
|
|
191
209
|
);
|
|
210
|
+
} finally {
|
|
211
|
+
if (sharedSecret) this.clearBuffer(sharedSecret);
|
|
212
|
+
if (kdf) this.clearBuffer(kdf);
|
|
213
|
+
if (encryptionKey) this.clearBuffer(encryptionKey);
|
|
214
|
+
if (macKey) this.clearBuffer(macKey);
|
|
192
215
|
}
|
|
193
216
|
}
|
|
194
217
|
/**
|