hd-wallet-wasm 0.2.0 → 0.2.8

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/README.md CHANGED
@@ -7,9 +7,10 @@ A comprehensive HD (Hierarchical Deterministic) wallet implementation compiled t
7
7
  - **BIP-32/39/44/49/84** - Complete HD wallet derivation standards
8
8
  - **Multi-curve support** - secp256k1, Ed25519, P-256, P-384, X25519
9
9
  - **Multi-chain** - Bitcoin, Ethereum, Solana, Cosmos, Polkadot
10
+ - **AES-256-GCM** - Authenticated encryption via WASM (Crypto++/OpenSSL)
10
11
  - **Hardware wallet ready** - Trezor, Ledger, KeepKey abstraction layer
11
12
  - **Secure** - Crypto++ backend, secure memory handling
12
- - **Fast** - WebAssembly performance
13
+ - **Fast** - WebAssembly performance, synchronous cryptographic operations
13
14
  - **TypeScript** - Full type definitions included
14
15
 
15
16
  ## Installation
@@ -224,6 +225,40 @@ const pbkdf2 = wallet.utils.pbkdf2(password, salt, 100000, 32);
224
225
  wallet.utils.secureWipe(sensitiveData);
225
226
  ```
226
227
 
228
+ ### AES-GCM Encryption
229
+
230
+ ```javascript
231
+ // Generate key and IV
232
+ const key = wallet.utils.generateAesKey(256); // 32 bytes for AES-256
233
+ const iv = wallet.utils.generateIv(); // 12 bytes
234
+
235
+ // Encrypt
236
+ const plaintext = new TextEncoder().encode('Secret data');
237
+ const { ciphertext, tag } = wallet.utils.aesGcm.encrypt(key, plaintext, iv);
238
+
239
+ // Decrypt
240
+ const decrypted = wallet.utils.aesGcm.decrypt(key, ciphertext, tag, iv);
241
+
242
+ // With additional authenticated data (AAD)
243
+ const aad = new TextEncoder().encode('context');
244
+ const enc = wallet.utils.aesGcm.encrypt(key, plaintext, iv, aad);
245
+ const dec = wallet.utils.aesGcm.decrypt(key, enc.ciphertext, enc.tag, iv, aad);
246
+ ```
247
+
248
+ ### Random Number Generation
249
+
250
+ ```javascript
251
+ // Generate cryptographically secure random bytes
252
+ const randomBytes = wallet.utils.getRandomBytes(32);
253
+
254
+ // Generate random IV for AES-GCM (12 bytes)
255
+ const iv = wallet.utils.generateIv();
256
+
257
+ // Generate random AES key (128, 192, or 256 bits)
258
+ const aes128Key = wallet.utils.generateAesKey(128);
259
+ const aes256Key = wallet.utils.generateAesKey(256);
260
+ ```
261
+
227
262
  ## Coin Types (SLIP-44)
228
263
 
229
264
  ```javascript
package/dist/hd-wallet.js CHANGED
Binary file
Binary file
package/dist/index.d.ts CHANGED
@@ -24,6 +24,14 @@ export interface HDWalletModule {
24
24
  getSupportedCoins(): string[];
25
25
  getSupportedCurves(): string[];
26
26
 
27
+ // OpenSSL FIPS support (when built with HD_WALLET_USE_OPENSSL)
28
+ /** Initialize OpenSSL in FIPS mode. Returns true if FIPS mode activated, false if using fallback. */
29
+ initFips(): boolean;
30
+ /** Check if OpenSSL backend is being used for FIPS algorithms */
31
+ isOpenSSL(): boolean;
32
+ /** Check if running in FIPS mode (OpenSSL FIPS provider active) */
33
+ isOpenSSLFips(): boolean;
34
+
27
35
  // WASI bridge
28
36
  wasiHasFeature(feature: WasiFeature): boolean;
29
37
  wasiGetWarning(feature: WasiFeature): WasiWarning;
@@ -579,28 +587,21 @@ export interface UtilsAPI {
579
587
  blake2b(data: Uint8Array, outputLength?: number): Uint8Array;
580
588
  blake2s(data: Uint8Array, outputLength?: number): Uint8Array;
581
589
 
582
- // Key derivation (sync - Crypto++)
590
+ // Key derivation
583
591
  hkdf(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Uint8Array;
584
592
  pbkdf2(password: Uint8Array, salt: Uint8Array, iterations: number, length: number): Uint8Array;
585
593
  scrypt(password: Uint8Array, salt: Uint8Array, n: number, r: number, p: number, length: number): Uint8Array;
586
594
 
587
- // Key derivation (async - WebCrypto, hardware accelerated)
588
- hkdfSha256Async(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Promise<Uint8Array>;
589
- hkdfSha384Async(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Promise<Uint8Array>;
590
-
591
- // AES-GCM encryption/decryption (async - WebCrypto, hardware accelerated)
595
+ // AES-GCM encryption/decryption (WASM/OpenSSL)
592
596
  aesGcm: {
593
- encrypt(key: Uint8Array, plaintext: Uint8Array, iv: Uint8Array, aad?: Uint8Array): Promise<{ ciphertext: Uint8Array; tag: Uint8Array }>;
594
- decrypt(key: Uint8Array, ciphertext: Uint8Array, tag: Uint8Array, iv: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
595
- generateIv(): Uint8Array;
596
- generateKey(bits?: number): Uint8Array;
597
+ encrypt(key: Uint8Array, plaintext: Uint8Array, iv: Uint8Array, aad?: Uint8Array): { ciphertext: Uint8Array; tag: Uint8Array };
598
+ decrypt(key: Uint8Array, ciphertext: Uint8Array, tag: Uint8Array, iv: Uint8Array, aad?: Uint8Array): Uint8Array;
597
599
  };
598
600
 
599
- // WebCrypto availability
600
- isWebCryptoAvailable(): boolean;
601
-
602
601
  // Random number generation
603
602
  getRandomBytes(length: number): Uint8Array;
603
+ generateIv(): Uint8Array;
604
+ generateAesKey(bits?: number): Uint8Array;
604
605
 
605
606
  // Encoding
606
607
  encodeBase58(data: Uint8Array): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-wallet-wasm",
3
- "version": "0.2.0",
3
+ "version": "0.2.8",
4
4
  "description": "Comprehensive HD Wallet implementation in WebAssembly - BIP-32/39/44, multi-curve, multi-chain support",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
@@ -25,7 +25,6 @@
25
25
  "files": [
26
26
  "src/index.mjs",
27
27
  "src/index.d.ts",
28
- "src/webcrypto.mjs",
29
28
  "src/aligned.mjs",
30
29
  "src/aligned.d.ts",
31
30
  "src/generated/",
@@ -43,7 +42,7 @@
43
42
  "test:bip32": "node test/test_bip32.mjs",
44
43
  "test:vectors": "node test/test_vectors.mjs",
45
44
  "test:aligned": "node test/test_aligned.mjs",
46
- "prepublishOnly": "npm run build && npm test"
45
+ "prepublishOnly": "echo 'Build handled by CI'"
47
46
  },
48
47
  "repository": {
49
48
  "type": "git",
package/src/index.d.ts CHANGED
@@ -24,6 +24,14 @@ export interface HDWalletModule {
24
24
  getSupportedCoins(): string[];
25
25
  getSupportedCurves(): string[];
26
26
 
27
+ // OpenSSL FIPS support (when built with HD_WALLET_USE_OPENSSL)
28
+ /** Initialize OpenSSL in FIPS mode. Returns true if FIPS mode activated, false if using fallback. */
29
+ initFips(): boolean;
30
+ /** Check if OpenSSL backend is being used for FIPS algorithms */
31
+ isOpenSSL(): boolean;
32
+ /** Check if running in FIPS mode (OpenSSL FIPS provider active) */
33
+ isOpenSSLFips(): boolean;
34
+
27
35
  // WASI bridge
28
36
  wasiHasFeature(feature: WasiFeature): boolean;
29
37
  wasiGetWarning(feature: WasiFeature): WasiWarning;
@@ -579,28 +587,21 @@ export interface UtilsAPI {
579
587
  blake2b(data: Uint8Array, outputLength?: number): Uint8Array;
580
588
  blake2s(data: Uint8Array, outputLength?: number): Uint8Array;
581
589
 
582
- // Key derivation (sync - Crypto++)
590
+ // Key derivation
583
591
  hkdf(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Uint8Array;
584
592
  pbkdf2(password: Uint8Array, salt: Uint8Array, iterations: number, length: number): Uint8Array;
585
593
  scrypt(password: Uint8Array, salt: Uint8Array, n: number, r: number, p: number, length: number): Uint8Array;
586
594
 
587
- // Key derivation (async - WebCrypto, hardware accelerated)
588
- hkdfSha256Async(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Promise<Uint8Array>;
589
- hkdfSha384Async(ikm: Uint8Array, salt: Uint8Array, info: Uint8Array, length: number): Promise<Uint8Array>;
590
-
591
- // AES-GCM encryption/decryption (async - WebCrypto, hardware accelerated)
595
+ // AES-GCM encryption/decryption (WASM/OpenSSL)
592
596
  aesGcm: {
593
- encrypt(key: Uint8Array, plaintext: Uint8Array, iv: Uint8Array, aad?: Uint8Array): Promise<{ ciphertext: Uint8Array; tag: Uint8Array }>;
594
- decrypt(key: Uint8Array, ciphertext: Uint8Array, tag: Uint8Array, iv: Uint8Array, aad?: Uint8Array): Promise<Uint8Array>;
595
- generateIv(): Uint8Array;
596
- generateKey(bits?: number): Uint8Array;
597
+ encrypt(key: Uint8Array, plaintext: Uint8Array, iv: Uint8Array, aad?: Uint8Array): { ciphertext: Uint8Array; tag: Uint8Array };
598
+ decrypt(key: Uint8Array, ciphertext: Uint8Array, tag: Uint8Array, iv: Uint8Array, aad?: Uint8Array): Uint8Array;
597
599
  };
598
600
 
599
- // WebCrypto availability
600
- isWebCryptoAvailable(): boolean;
601
-
602
601
  // Random number generation
603
602
  getRandomBytes(length: number): Uint8Array;
603
+ generateIv(): Uint8Array;
604
+ generateAesKey(bits?: number): Uint8Array;
604
605
 
605
606
  // Encoding
606
607
  encodeBase58(data: Uint8Array): string;
package/src/index.mjs CHANGED
@@ -9,15 +9,12 @@
9
9
  * - Transaction building and signing
10
10
  *
11
11
  * @module hd-wallet-wasm
12
- * @version 0.1.0
12
+ * @version 0.2.0
13
13
  */
14
14
 
15
15
  // Import aligned API for batch operations
16
16
  import { AlignedAPI } from './aligned.mjs';
17
17
 
18
- // Import WebCrypto bridge for hardware-accelerated async operations
19
- import * as WebCrypto from './webcrypto.mjs';
20
-
21
18
  // =============================================================================
22
19
  // Enums (matching TypeScript definitions)
23
20
  // =============================================================================
@@ -2241,45 +2238,125 @@ function createModule(wasm) {
2241
2238
  }
2242
2239
  },
2243
2240
 
2244
- // Key derivation (async - uses WebCrypto for hardware acceleration)
2245
- hkdfSha256Async: WebCrypto.hkdfSha256,
2246
- hkdfSha384Async: WebCrypto.hkdfSha384,
2247
-
2248
- // AES-GCM encryption/decryption (async - uses WebCrypto)
2241
+ // AES-GCM encryption/decryption (uses WASM/Crypto++ or OpenSSL)
2249
2242
  aesGcm: {
2250
2243
  /**
2251
- * Encrypt data with AES-GCM (WebCrypto)
2252
- * @param {Uint8Array} key - AES key (16, 24, or 32 bytes)
2244
+ * Encrypt data with AES-GCM (synchronous WASM)
2245
+ * @param {Uint8Array} key - AES-256 key (32 bytes)
2253
2246
  * @param {Uint8Array} plaintext - Data to encrypt
2254
- * @param {Uint8Array} iv - Initialization vector (12 bytes recommended)
2247
+ * @param {Uint8Array} iv - Initialization vector (12 bytes)
2255
2248
  * @param {Uint8Array} [aad] - Additional authenticated data
2256
- * @returns {Promise<{ciphertext: Uint8Array, tag: Uint8Array}>}
2249
+ * @returns {{ciphertext: Uint8Array, tag: Uint8Array}}
2257
2250
  */
2258
- encrypt: WebCrypto.aesGcmEncrypt,
2251
+ encrypt(key, plaintext, iv, aad = new Uint8Array(0)) {
2252
+ if (key.length !== 32) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Key must be 32 bytes');
2253
+ if (iv.length !== 12) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'IV must be 12 bytes');
2254
+
2255
+ const keyPtr = allocAndCopy(wasm, key);
2256
+ const ptPtr = allocAndCopy(wasm, plaintext);
2257
+ const ivPtr = allocAndCopy(wasm, iv);
2258
+ const aadPtr = aad.length > 0 ? allocAndCopy(wasm, aad) : 0;
2259
+ const ctPtr = wasm._hd_alloc(plaintext.length);
2260
+ const tagPtr = wasm._hd_alloc(16);
2261
+
2262
+ try {
2263
+ const result = wasm._hd_aes_gcm_encrypt(
2264
+ keyPtr, key.length,
2265
+ ptPtr, plaintext.length,
2266
+ ivPtr, iv.length,
2267
+ aadPtr, aad.length,
2268
+ ctPtr, tagPtr
2269
+ );
2270
+ if (result < 0) throw new HDWalletError(-result);
2271
+
2272
+ return {
2273
+ ciphertext: readBytes(wasm, ctPtr, plaintext.length),
2274
+ tag: readBytes(wasm, tagPtr, 16)
2275
+ };
2276
+ } finally {
2277
+ wasm._hd_secure_wipe(keyPtr, key.length);
2278
+ wasm._hd_dealloc(keyPtr);
2279
+ wasm._hd_dealloc(ptPtr);
2280
+ wasm._hd_dealloc(ivPtr);
2281
+ if (aadPtr) wasm._hd_dealloc(aadPtr);
2282
+ wasm._hd_dealloc(ctPtr);
2283
+ wasm._hd_dealloc(tagPtr);
2284
+ }
2285
+ },
2259
2286
 
2260
2287
  /**
2261
- * Decrypt data with AES-GCM (WebCrypto)
2262
- * @param {Uint8Array} key - AES key
2288
+ * Decrypt data with AES-GCM (synchronous WASM)
2289
+ * @param {Uint8Array} key - AES-256 key (32 bytes)
2263
2290
  * @param {Uint8Array} ciphertext - Encrypted data
2264
2291
  * @param {Uint8Array} tag - Authentication tag (16 bytes)
2265
- * @param {Uint8Array} iv - Initialization vector
2292
+ * @param {Uint8Array} iv - Initialization vector (12 bytes)
2266
2293
  * @param {Uint8Array} [aad] - Additional authenticated data
2267
- * @returns {Promise<Uint8Array>} Decrypted plaintext
2294
+ * @returns {Uint8Array} Decrypted plaintext
2295
+ * @throws {HDWalletError} If authentication fails
2268
2296
  */
2269
- decrypt: WebCrypto.aesGcmDecrypt,
2297
+ decrypt(key, ciphertext, tag, iv, aad = new Uint8Array(0)) {
2298
+ if (key.length !== 32) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Key must be 32 bytes');
2299
+ if (iv.length !== 12) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'IV must be 12 bytes');
2300
+ if (tag.length !== 16) throw new HDWalletError(ErrorCode.INVALID_ARGUMENT, 'Tag must be 16 bytes');
2301
+
2302
+ const keyPtr = allocAndCopy(wasm, key);
2303
+ const ctPtr = allocAndCopy(wasm, ciphertext);
2304
+ const ivPtr = allocAndCopy(wasm, iv);
2305
+ const tagPtr = allocAndCopy(wasm, tag);
2306
+ const aadPtr = aad.length > 0 ? allocAndCopy(wasm, aad) : 0;
2307
+ const ptPtr = wasm._hd_alloc(ciphertext.length);
2270
2308
 
2271
- /** Generate a random IV (12 bytes) */
2272
- generateIv: WebCrypto.generateIv,
2309
+ try {
2310
+ const result = wasm._hd_aes_gcm_decrypt(
2311
+ keyPtr, key.length,
2312
+ ctPtr, ciphertext.length,
2313
+ ivPtr, iv.length,
2314
+ aadPtr, aad.length,
2315
+ tagPtr, ptPtr
2316
+ );
2317
+ if (result < 0) {
2318
+ throw new HDWalletError(
2319
+ result === -2 ? ErrorCode.VERIFICATION_FAILED : -result,
2320
+ result === -2 ? 'AES-GCM authentication failed' : undefined
2321
+ );
2322
+ }
2273
2323
 
2274
- /** Generate a random AES key (default 256 bits) */
2275
- generateKey: WebCrypto.generateAesKey
2324
+ return readBytes(wasm, ptPtr, ciphertext.length);
2325
+ } finally {
2326
+ wasm._hd_secure_wipe(keyPtr, key.length);
2327
+ wasm._hd_dealloc(keyPtr);
2328
+ wasm._hd_dealloc(ctPtr);
2329
+ wasm._hd_dealloc(ivPtr);
2330
+ wasm._hd_dealloc(tagPtr);
2331
+ if (aadPtr) wasm._hd_dealloc(aadPtr);
2332
+ wasm._hd_dealloc(ptPtr);
2333
+ }
2334
+ }
2276
2335
  },
2277
2336
 
2278
- /** Check if WebCrypto is available */
2279
- isWebCryptoAvailable: WebCrypto.isWebCryptoAvailable,
2280
-
2281
2337
  /** Generate cryptographically secure random bytes */
2282
- getRandomBytes: WebCrypto.getRandomBytes,
2338
+ getRandomBytes(length) {
2339
+ const bytes = new Uint8Array(length);
2340
+ if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues) {
2341
+ globalThis.crypto.getRandomValues(bytes);
2342
+ } else {
2343
+ throw new Error('No cryptographic random source available');
2344
+ }
2345
+ return bytes;
2346
+ },
2347
+
2348
+ /** Generate a random IV for AES-GCM (12 bytes) */
2349
+ generateIv() {
2350
+ return this.getRandomBytes(12);
2351
+ },
2352
+
2353
+ /** Generate a random AES key (default 256 bits) */
2354
+ generateAesKey(bits = 256) {
2355
+ if (![128, 192, 256].includes(bits)) {
2356
+ throw new Error('AES key size must be 128, 192, or 256 bits');
2357
+ }
2358
+ return this.getRandomBytes(bits / 8);
2359
+ },
2283
2360
 
2284
2361
  pbkdf2(password, salt, iterations, length) {
2285
2362
  const pwdPtr = allocAndCopy(wasm, password);
@@ -2525,6 +2602,50 @@ function createModule(wasm) {
2525
2602
  return wasm._hd_get_entropy_status();
2526
2603
  },
2527
2604
 
2605
+ // OpenSSL FIPS support
2606
+ /**
2607
+ * Initialize OpenSSL in FIPS mode
2608
+ * @returns {boolean} True if FIPS mode activated, false if using default provider
2609
+ */
2610
+ initFips() {
2611
+ // Check if the function exists (only when built with HD_WALLET_USE_OPENSSL)
2612
+ if (typeof wasm._hd_openssl_init_fips !== 'function') {
2613
+ console.warn('[HD Wallet] OpenSSL not compiled in, skipping FIPS init');
2614
+ return false;
2615
+ }
2616
+ const result = wasm._hd_openssl_init_fips();
2617
+ if (result === 1) {
2618
+ console.log('[HD Wallet] OpenSSL FIPS mode activated');
2619
+ return true;
2620
+ } else if (result === -1) {
2621
+ console.warn('[HD Wallet] FIPS provider not available, using default OpenSSL');
2622
+ wasm._hd_openssl_init_default();
2623
+ return false;
2624
+ } else {
2625
+ console.error('[HD Wallet] Failed to initialize OpenSSL');
2626
+ return false;
2627
+ }
2628
+ },
2629
+
2630
+ /**
2631
+ * Check if OpenSSL backend is being used
2632
+ * @returns {boolean}
2633
+ */
2634
+ isOpenSSL() {
2635
+ return typeof wasm._hd_openssl_init_fips === 'function';
2636
+ },
2637
+
2638
+ /**
2639
+ * Check if running in FIPS mode (OpenSSL FIPS provider active)
2640
+ * @returns {boolean}
2641
+ */
2642
+ isOpenSSLFips() {
2643
+ if (typeof wasm._hd_openssl_is_fips !== 'function') {
2644
+ return false;
2645
+ }
2646
+ return wasm._hd_openssl_is_fips() !== 0;
2647
+ },
2648
+
2528
2649
  // APIs
2529
2650
  mnemonic,
2530
2651
  hdkey,
package/src/webcrypto.mjs DELETED
@@ -1,216 +0,0 @@
1
- /**
2
- * WebCrypto Bridge - Hardware-accelerated cryptographic operations
3
- *
4
- * These functions use the browser's native WebCrypto API for
5
- * hardware-accelerated, constant-time cryptographic operations.
6
- *
7
- * All functions are async because WebCrypto is Promise-based.
8
- */
9
-
10
- /**
11
- * Check if WebCrypto is available
12
- * @returns {boolean}
13
- */
14
- export function isWebCryptoAvailable() {
15
- return typeof globalThis.crypto !== 'undefined' &&
16
- typeof globalThis.crypto.subtle !== 'undefined';
17
- }
18
-
19
- /**
20
- * AES-GCM Encryption using WebCrypto
21
- * @param {Uint8Array} key - AES key (128, 192, or 256 bits = 16, 24, or 32 bytes)
22
- * @param {Uint8Array} plaintext - Data to encrypt
23
- * @param {Uint8Array} iv - Initialization vector (12 bytes recommended for GCM)
24
- * @param {Uint8Array} [aad] - Additional authenticated data (optional)
25
- * @returns {Promise<{ciphertext: Uint8Array, tag: Uint8Array}>}
26
- */
27
- export async function aesGcmEncrypt(key, plaintext, iv, aad = null) {
28
- if (!isWebCryptoAvailable()) {
29
- throw new Error('WebCrypto not available');
30
- }
31
-
32
- const cryptoKey = await crypto.subtle.importKey(
33
- 'raw',
34
- key,
35
- { name: 'AES-GCM' },
36
- false,
37
- ['encrypt']
38
- );
39
-
40
- const algorithmParams = {
41
- name: 'AES-GCM',
42
- iv: iv,
43
- tagLength: 128 // 16 bytes
44
- };
45
-
46
- if (aad && aad.length > 0) {
47
- algorithmParams.additionalData = aad;
48
- }
49
-
50
- const encrypted = await crypto.subtle.encrypt(
51
- algorithmParams,
52
- cryptoKey,
53
- plaintext
54
- );
55
-
56
- // WebCrypto returns ciphertext + tag combined
57
- const fullResult = new Uint8Array(encrypted);
58
- const ciphertext = fullResult.slice(0, fullResult.length - 16);
59
- const tag = fullResult.slice(fullResult.length - 16);
60
-
61
- return { ciphertext, tag };
62
- }
63
-
64
- /**
65
- * AES-GCM Decryption using WebCrypto
66
- * @param {Uint8Array} key - AES key (128, 192, or 256 bits)
67
- * @param {Uint8Array} ciphertext - Encrypted data
68
- * @param {Uint8Array} tag - Authentication tag (16 bytes)
69
- * @param {Uint8Array} iv - Initialization vector used during encryption
70
- * @param {Uint8Array} [aad] - Additional authenticated data (must match encryption)
71
- * @returns {Promise<Uint8Array>} Decrypted plaintext
72
- * @throws {Error} If decryption fails (invalid tag, wrong key, etc.)
73
- */
74
- export async function aesGcmDecrypt(key, ciphertext, tag, iv, aad = null) {
75
- if (!isWebCryptoAvailable()) {
76
- throw new Error('WebCrypto not available');
77
- }
78
-
79
- const cryptoKey = await crypto.subtle.importKey(
80
- 'raw',
81
- key,
82
- { name: 'AES-GCM' },
83
- false,
84
- ['decrypt']
85
- );
86
-
87
- // Reconstruct the full ciphertext + tag format WebCrypto expects
88
- const encrypted = new Uint8Array(ciphertext.length + tag.length);
89
- encrypted.set(ciphertext, 0);
90
- encrypted.set(tag, ciphertext.length);
91
-
92
- const algorithmParams = {
93
- name: 'AES-GCM',
94
- iv: iv,
95
- tagLength: 128
96
- };
97
-
98
- if (aad && aad.length > 0) {
99
- algorithmParams.additionalData = aad;
100
- }
101
-
102
- try {
103
- const decrypted = await crypto.subtle.decrypt(
104
- algorithmParams,
105
- cryptoKey,
106
- encrypted
107
- );
108
- return new Uint8Array(decrypted);
109
- } catch (err) {
110
- throw new Error('AES-GCM decryption failed: authentication tag mismatch or invalid data');
111
- }
112
- }
113
-
114
- /**
115
- * HKDF using WebCrypto (SHA-256)
116
- * @param {Uint8Array} ikm - Input keying material
117
- * @param {Uint8Array} salt - Salt value (can be empty Uint8Array)
118
- * @param {Uint8Array} info - Context/application-specific info (can be empty)
119
- * @param {number} length - Desired output length in bytes
120
- * @returns {Promise<Uint8Array>} Derived key material
121
- */
122
- export async function hkdfSha256(ikm, salt, info, length) {
123
- if (!isWebCryptoAvailable()) {
124
- throw new Error('WebCrypto not available');
125
- }
126
-
127
- const keyMaterial = await crypto.subtle.importKey(
128
- 'raw',
129
- ikm,
130
- { name: 'HKDF' },
131
- false,
132
- ['deriveBits']
133
- );
134
-
135
- const derivedBits = await crypto.subtle.deriveBits(
136
- {
137
- name: 'HKDF',
138
- hash: 'SHA-256',
139
- salt: salt.length > 0 ? salt : new Uint8Array(32), // WebCrypto requires non-empty salt
140
- info: info
141
- },
142
- keyMaterial,
143
- length * 8
144
- );
145
-
146
- return new Uint8Array(derivedBits);
147
- }
148
-
149
- /**
150
- * HKDF using WebCrypto (SHA-384)
151
- * @param {Uint8Array} ikm - Input keying material
152
- * @param {Uint8Array} salt - Salt value
153
- * @param {Uint8Array} info - Context/application-specific info
154
- * @param {number} length - Desired output length in bytes
155
- * @returns {Promise<Uint8Array>} Derived key material
156
- */
157
- export async function hkdfSha384(ikm, salt, info, length) {
158
- if (!isWebCryptoAvailable()) {
159
- throw new Error('WebCrypto not available');
160
- }
161
-
162
- const keyMaterial = await crypto.subtle.importKey(
163
- 'raw',
164
- ikm,
165
- { name: 'HKDF' },
166
- false,
167
- ['deriveBits']
168
- );
169
-
170
- const derivedBits = await crypto.subtle.deriveBits(
171
- {
172
- name: 'HKDF',
173
- hash: 'SHA-384',
174
- salt: salt.length > 0 ? salt : new Uint8Array(48),
175
- info: info
176
- },
177
- keyMaterial,
178
- length * 8
179
- );
180
-
181
- return new Uint8Array(derivedBits);
182
- }
183
-
184
- /**
185
- * Generate cryptographically secure random bytes
186
- * @param {number} length - Number of random bytes to generate
187
- * @returns {Uint8Array} Random bytes
188
- */
189
- export function getRandomBytes(length) {
190
- if (!isWebCryptoAvailable()) {
191
- throw new Error('WebCrypto not available');
192
- }
193
- const bytes = new Uint8Array(length);
194
- crypto.getRandomValues(bytes);
195
- return bytes;
196
- }
197
-
198
- /**
199
- * Generate a random AES-GCM IV (12 bytes recommended)
200
- * @returns {Uint8Array} 12-byte IV
201
- */
202
- export function generateIv() {
203
- return getRandomBytes(12);
204
- }
205
-
206
- /**
207
- * Generate a random AES key
208
- * @param {number} [bits=256] - Key size in bits (128, 192, or 256)
209
- * @returns {Uint8Array} Random key
210
- */
211
- export function generateAesKey(bits = 256) {
212
- if (![128, 192, 256].includes(bits)) {
213
- throw new Error('AES key size must be 128, 192, or 256 bits');
214
- }
215
- return getRandomBytes(bits / 8);
216
- }