@gjsify/crypto 0.1.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 (87) hide show
  1. package/README.md +27 -0
  2. package/lib/esm/asn1.js +504 -0
  3. package/lib/esm/bigint-math.js +34 -0
  4. package/lib/esm/cipher.js +1272 -0
  5. package/lib/esm/constants.js +15 -0
  6. package/lib/esm/crypto-utils.js +47 -0
  7. package/lib/esm/dh.js +411 -0
  8. package/lib/esm/ecdh.js +356 -0
  9. package/lib/esm/ecdsa.js +125 -0
  10. package/lib/esm/hash.js +100 -0
  11. package/lib/esm/hkdf.js +58 -0
  12. package/lib/esm/hmac.js +93 -0
  13. package/lib/esm/index.js +158 -0
  14. package/lib/esm/key-object.js +330 -0
  15. package/lib/esm/mgf1.js +27 -0
  16. package/lib/esm/pbkdf2.js +68 -0
  17. package/lib/esm/public-encrypt.js +175 -0
  18. package/lib/esm/random.js +138 -0
  19. package/lib/esm/rsa-oaep.js +95 -0
  20. package/lib/esm/rsa-pss.js +100 -0
  21. package/lib/esm/scrypt.js +134 -0
  22. package/lib/esm/sign.js +248 -0
  23. package/lib/esm/timing-safe-equal.js +13 -0
  24. package/lib/esm/x509.js +214 -0
  25. package/lib/types/asn1.d.ts +87 -0
  26. package/lib/types/bigint-math.d.ts +13 -0
  27. package/lib/types/cipher.d.ts +84 -0
  28. package/lib/types/constants.d.ts +10 -0
  29. package/lib/types/crypto-utils.d.ts +22 -0
  30. package/lib/types/dh.d.ts +79 -0
  31. package/lib/types/ecdh.d.ts +96 -0
  32. package/lib/types/ecdsa.d.ts +21 -0
  33. package/lib/types/hash.d.ts +25 -0
  34. package/lib/types/hkdf.d.ts +9 -0
  35. package/lib/types/hmac.d.ts +20 -0
  36. package/lib/types/index.d.ts +105 -0
  37. package/lib/types/key-object.d.ts +36 -0
  38. package/lib/types/mgf1.d.ts +5 -0
  39. package/lib/types/pbkdf2.d.ts +9 -0
  40. package/lib/types/public-encrypt.d.ts +42 -0
  41. package/lib/types/random.d.ts +22 -0
  42. package/lib/types/rsa-oaep.d.ts +8 -0
  43. package/lib/types/rsa-pss.d.ts +8 -0
  44. package/lib/types/scrypt.d.ts +11 -0
  45. package/lib/types/sign.d.ts +61 -0
  46. package/lib/types/timing-safe-equal.d.ts +6 -0
  47. package/lib/types/x509.d.ts +72 -0
  48. package/package.json +45 -0
  49. package/src/asn1.ts +797 -0
  50. package/src/bigint-math.ts +45 -0
  51. package/src/cipher.spec.ts +332 -0
  52. package/src/cipher.ts +952 -0
  53. package/src/constants.ts +16 -0
  54. package/src/crypto-utils.ts +64 -0
  55. package/src/dh.spec.ts +111 -0
  56. package/src/dh.ts +761 -0
  57. package/src/ecdh.spec.ts +116 -0
  58. package/src/ecdh.ts +624 -0
  59. package/src/ecdsa.ts +243 -0
  60. package/src/extended.spec.ts +444 -0
  61. package/src/gcm.spec.ts +141 -0
  62. package/src/hash.spec.ts +86 -0
  63. package/src/hash.ts +119 -0
  64. package/src/hkdf.ts +99 -0
  65. package/src/hmac.spec.ts +64 -0
  66. package/src/hmac.ts +123 -0
  67. package/src/index.ts +93 -0
  68. package/src/key-object.spec.ts +202 -0
  69. package/src/key-object.ts +401 -0
  70. package/src/mgf1.ts +37 -0
  71. package/src/pbkdf2.spec.ts +76 -0
  72. package/src/pbkdf2.ts +106 -0
  73. package/src/public-encrypt.ts +288 -0
  74. package/src/random.spec.ts +133 -0
  75. package/src/random.ts +183 -0
  76. package/src/rsa-oaep.ts +167 -0
  77. package/src/rsa-pss.ts +190 -0
  78. package/src/scrypt.spec.ts +90 -0
  79. package/src/scrypt.ts +191 -0
  80. package/src/sign.spec.ts +160 -0
  81. package/src/sign.ts +319 -0
  82. package/src/test.mts +19 -0
  83. package/src/timing-safe-equal.ts +21 -0
  84. package/src/x509.spec.ts +210 -0
  85. package/src/x509.ts +262 -0
  86. package/tsconfig.json +31 -0
  87. package/tsconfig.tsbuildinfo +1 -0
package/src/ecdsa.ts ADDED
@@ -0,0 +1,243 @@
1
+ // ECDSA (Elliptic Curve Digital Signature Algorithm) for GJS
2
+ // Implements RFC 6979 (deterministic k) + FIPS 186-4 signature generation/verification
3
+ // Reference: refs/node/lib/internal/crypto/sig.js
4
+ // Copyright (c) Node.js contributors. MIT license.
5
+ // Reimplemented for GJS using BigInt elliptic curve arithmetic from ecdh.ts
6
+
7
+ import { Buffer } from 'node:buffer';
8
+ import { Hash } from './hash.js';
9
+ import { Hmac } from './hmac.js';
10
+ import {
11
+ mod, modInverse, scalarMul, pointAdd,
12
+ CURVES, CURVE_ALIASES,
13
+ type ECPoint, type CurveParams,
14
+ } from './ecdh.js';
15
+ import { bigIntToBytes as bigintToBytes, bytesToBigInt as bytesToBigint } from './bigint-math.js';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Helpers
19
+ // ---------------------------------------------------------------------------
20
+
21
+ function getCurve(curveName: string): CurveParams {
22
+ const alias = CURVE_ALIASES[curveName.toLowerCase()];
23
+ if (!alias) throw new Error(`Unsupported curve: ${curveName}`);
24
+ return CURVES[alias];
25
+ }
26
+
27
+ /** Truncate hash to curve order bit length (FIPS 186-4 Section 6.4) */
28
+ function truncateHash(hash: Uint8Array, curve: CurveParams): bigint {
29
+ const orderBits = curve.n.toString(2).length;
30
+ let e = bytesToBigint(hash);
31
+ const hashBits = hash.length * 8;
32
+ if (hashBits > orderBits) {
33
+ e >>= BigInt(hashBits - orderBits);
34
+ }
35
+ return e;
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // RFC 6979 — Deterministic k generation via HMAC-DRBG
40
+ // ---------------------------------------------------------------------------
41
+
42
+ function hmacDigest(algo: string, key: Uint8Array, data: Uint8Array): Uint8Array {
43
+ const hmac = new Hmac(algo, key);
44
+ hmac.update(data);
45
+ return new Uint8Array(hmac.digest() as any);
46
+ }
47
+
48
+ /**
49
+ * Generate deterministic k per RFC 6979 Section 3.2.
50
+ * Uses HMAC-DRBG seeded with private key and message hash.
51
+ */
52
+ function rfc6979(
53
+ hashAlgo: string,
54
+ privKey: bigint,
55
+ msgHash: Uint8Array,
56
+ curve: CurveParams,
57
+ ): bigint {
58
+ const qLen = curve.byteLength;
59
+ const hLen = msgHash.length;
60
+
61
+ // Step a: h1 = H(m) — already provided as msgHash
62
+ // Step b: V = 0x01 * hLen
63
+ let V: any = new Uint8Array(hLen).fill(0x01);
64
+ // Step c: K = 0x00 * hLen
65
+ let K: any = new Uint8Array(hLen).fill(0x00);
66
+
67
+ // int2octets(x) — private key as fixed-length big-endian bytes
68
+ const x = bigintToBytes(privKey, qLen);
69
+ // bits2octets(h1) — truncate hash to curve order, then encode
70
+ const z = bigintToBytes(mod(truncateHash(msgHash, curve), curve.n), qLen);
71
+
72
+ // Step d: K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))
73
+ const concat0 = new Uint8Array(hLen + 1 + qLen + qLen);
74
+ concat0.set(V, 0);
75
+ concat0[hLen] = 0x00;
76
+ concat0.set(x, hLen + 1);
77
+ concat0.set(z, hLen + 1 + qLen);
78
+ K = hmacDigest(hashAlgo, K, concat0);
79
+
80
+ // Step e: V = HMAC_K(V)
81
+ V = hmacDigest(hashAlgo, K, V);
82
+
83
+ // Step f: K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1))
84
+ const concat1 = new Uint8Array(hLen + 1 + qLen + qLen);
85
+ concat1.set(V, 0);
86
+ concat1[hLen] = 0x01;
87
+ concat1.set(x, hLen + 1);
88
+ concat1.set(z, hLen + 1 + qLen);
89
+ K = hmacDigest(hashAlgo, K, concat1);
90
+
91
+ // Step g: V = HMAC_K(V)
92
+ V = hmacDigest(hashAlgo, K, V);
93
+
94
+ // Step h: Generate k candidates
95
+ for (let attempt = 0; attempt < 100; attempt++) {
96
+ // Step h.1–h.2: Generate T by concatenating HMAC_K(V) blocks
97
+ let T: any = new Uint8Array(0);
98
+ while (T.length < qLen) {
99
+ V = hmacDigest(hashAlgo, K, V);
100
+ const newT = new Uint8Array(T.length + V.length);
101
+ newT.set(T, 0);
102
+ newT.set(V, T.length);
103
+ T = newT;
104
+ }
105
+
106
+ // Step h.3: k = bits2int(T)
107
+ const k = truncateHash(T.slice(0, qLen), curve);
108
+
109
+ // Check: 1 <= k < n
110
+ if (k >= 1n && k < curve.n) {
111
+ return k;
112
+ }
113
+
114
+ // h.3 retry: K = HMAC_K(V || 0x00), V = HMAC_K(V)
115
+ const retryConcat = new Uint8Array(hLen + 1);
116
+ retryConcat.set(V, 0);
117
+ retryConcat[hLen] = 0x00;
118
+ K = hmacDigest(hashAlgo, K, retryConcat);
119
+ V = hmacDigest(hashAlgo, K, V);
120
+ }
121
+
122
+ throw new Error('RFC 6979: failed to generate valid k after 100 attempts');
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // ECDSA Sign
127
+ // ---------------------------------------------------------------------------
128
+
129
+ /**
130
+ * ECDSA signature generation per FIPS 186-4 Section 6.4.
131
+ *
132
+ * @param hashAlgo Hash algorithm name (e.g. 'sha256')
133
+ * @param privKeyBytes Private key as big-endian bytes
134
+ * @param data Data to sign (will be hashed)
135
+ * @param curveName Curve name (e.g. 'P-256')
136
+ * @returns Signature as r || s concatenated (each curve.byteLength bytes)
137
+ */
138
+ export function ecdsaSign(
139
+ hashAlgo: string,
140
+ privKeyBytes: Uint8Array,
141
+ data: Uint8Array,
142
+ curveName: string,
143
+ ): Uint8Array {
144
+ const curve = getCurve(curveName);
145
+ const G: ECPoint = { x: curve.Gx, y: curve.Gy };
146
+ const d = bytesToBigint(privKeyBytes);
147
+
148
+ // Hash the message
149
+ const hash = new Hash(hashAlgo);
150
+ hash.update(data);
151
+ const msgHash = new Uint8Array(hash.digest() as any);
152
+
153
+ // Truncate hash to curve order bit length
154
+ const e = truncateHash(msgHash, curve);
155
+
156
+ // Generate deterministic k via RFC 6979
157
+ const k = rfc6979(hashAlgo, d, msgHash, curve);
158
+
159
+ // R = k * G
160
+ const R = scalarMul(k, G, curve);
161
+ if (R === null) throw new Error('ECDSA: k * G is point at infinity');
162
+
163
+ // r = R.x mod n
164
+ const r = mod(R.x, curve.n);
165
+ if (r === 0n) throw new Error('ECDSA: r is zero');
166
+
167
+ // s = k^-1 * (e + r * d) mod n
168
+ const kInv = modInverse(k, curve.n);
169
+ const s = mod(kInv * (e + r * d), curve.n);
170
+ if (s === 0n) throw new Error('ECDSA: s is zero');
171
+
172
+ // Encode as r || s (fixed-length concatenation)
173
+ const sigLen = curve.byteLength;
174
+ const sig = new Uint8Array(sigLen * 2);
175
+ sig.set(bigintToBytes(r, sigLen), 0);
176
+ sig.set(bigintToBytes(s, sigLen), sigLen);
177
+ return sig;
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // ECDSA Verify
182
+ // ---------------------------------------------------------------------------
183
+
184
+ /**
185
+ * ECDSA signature verification per FIPS 186-4 Section 6.4.
186
+ *
187
+ * @param hashAlgo Hash algorithm name (e.g. 'sha256')
188
+ * @param pubKeyBytes Public key as uncompressed point (0x04 || x || y)
189
+ * @param signature Signature as r || s concatenated
190
+ * @param data Signed data (will be hashed)
191
+ * @param curveName Curve name (e.g. 'P-256')
192
+ * @returns true if signature is valid
193
+ */
194
+ export function ecdsaVerify(
195
+ hashAlgo: string,
196
+ pubKeyBytes: Uint8Array,
197
+ signature: Uint8Array,
198
+ data: Uint8Array,
199
+ curveName: string,
200
+ ): boolean {
201
+ const curve = getCurve(curveName);
202
+ const G: ECPoint = { x: curve.Gx, y: curve.Gy };
203
+ const sigLen = curve.byteLength;
204
+
205
+ // Parse public key (uncompressed: 0x04 || x || y)
206
+ if (pubKeyBytes[0] !== 0x04 || pubKeyBytes.length !== 1 + sigLen * 2) {
207
+ return false; // Only uncompressed point format supported
208
+ }
209
+ const Qx = bytesToBigint(pubKeyBytes.slice(1, 1 + sigLen));
210
+ const Qy = bytesToBigint(pubKeyBytes.slice(1 + sigLen));
211
+ const Q: ECPoint = { x: Qx, y: Qy };
212
+
213
+ // Parse signature (r || s)
214
+ if (signature.length !== sigLen * 2) return false;
215
+ const r = bytesToBigint(signature.slice(0, sigLen));
216
+ const s = bytesToBigint(signature.slice(sigLen));
217
+
218
+ // Check: 1 <= r < n, 1 <= s < n
219
+ if (r < 1n || r >= curve.n || s < 1n || s >= curve.n) return false;
220
+
221
+ // Hash the message
222
+ const hash = new Hash(hashAlgo);
223
+ hash.update(data);
224
+ const msgHash = new Uint8Array(hash.digest() as any);
225
+ const e = truncateHash(msgHash, curve);
226
+
227
+ // w = s^-1 mod n
228
+ const w = modInverse(s, curve.n);
229
+
230
+ // u1 = e * w mod n, u2 = r * w mod n
231
+ const u1 = mod(e * w, curve.n);
232
+ const u2 = mod(r * w, curve.n);
233
+
234
+ // R' = u1 * G + u2 * Q
235
+ const R1 = scalarMul(u1, G, curve);
236
+ const R2 = scalarMul(u2, Q, curve);
237
+ const R = pointAdd(R1, R2, curve);
238
+
239
+ if (R === null) return false;
240
+
241
+ // Check: r === R'.x mod n
242
+ return mod(R.x, curve.n) === r;
243
+ }
@@ -0,0 +1,444 @@
1
+ // Extended crypto tests — additional algorithms, edge cases, getHashes/getCiphers
2
+ // Ported from refs/node-test/parallel/test-crypto-*.js
3
+ // Original: MIT license, Node.js contributors
4
+
5
+ import { describe, it, expect } from '@gjsify/unit';
6
+ import * as crypto from 'node:crypto';
7
+
8
+ export default async () => {
9
+
10
+ // ===================== Module exports =====================
11
+ await describe('crypto module exports', async () => {
12
+ await it('should export createHash', async () => {
13
+ expect(typeof crypto.createHash).toBe('function');
14
+ });
15
+ await it('should export createHmac', async () => {
16
+ expect(typeof crypto.createHmac).toBe('function');
17
+ });
18
+ await it('should export randomBytes', async () => {
19
+ expect(typeof crypto.randomBytes).toBe('function');
20
+ });
21
+ await it('should export randomUUID', async () => {
22
+ expect(typeof crypto.randomUUID).toBe('function');
23
+ });
24
+ await it('should export randomInt', async () => {
25
+ expect(typeof crypto.randomInt).toBe('function');
26
+ });
27
+ await it('should export randomFillSync', async () => {
28
+ expect(typeof crypto.randomFillSync).toBe('function');
29
+ });
30
+ await it('should export pbkdf2Sync', async () => {
31
+ expect(typeof crypto.pbkdf2Sync).toBe('function');
32
+ });
33
+ await it('should export pbkdf2', async () => {
34
+ expect(typeof crypto.pbkdf2).toBe('function');
35
+ });
36
+ await it('should export scryptSync', async () => {
37
+ expect(typeof crypto.scryptSync).toBe('function');
38
+ });
39
+ await it('should export createCipheriv', async () => {
40
+ expect(typeof crypto.createCipheriv).toBe('function');
41
+ });
42
+ await it('should export createDecipheriv', async () => {
43
+ expect(typeof crypto.createDecipheriv).toBe('function');
44
+ });
45
+ await it('should export createSign', async () => {
46
+ expect(typeof crypto.createSign).toBe('function');
47
+ });
48
+ await it('should export createVerify', async () => {
49
+ expect(typeof crypto.createVerify).toBe('function');
50
+ });
51
+ await it('should export createDiffieHellman', async () => {
52
+ expect(typeof crypto.createDiffieHellman).toBe('function');
53
+ });
54
+ await it('should export createECDH', async () => {
55
+ expect(typeof crypto.createECDH).toBe('function');
56
+ });
57
+ await it('should export timingSafeEqual', async () => {
58
+ expect(typeof crypto.timingSafeEqual).toBe('function');
59
+ });
60
+ await it('should export constants', async () => {
61
+ expect(typeof crypto.constants).toBe('object');
62
+ });
63
+ await it('should export getHashes', async () => {
64
+ expect(typeof crypto.getHashes).toBe('function');
65
+ });
66
+ await it('should export getCiphers', async () => {
67
+ expect(typeof crypto.getCiphers).toBe('function');
68
+ });
69
+ await it('should export getCurves', async () => {
70
+ expect(typeof crypto.getCurves).toBe('function');
71
+ });
72
+ await it('should export createSecretKey', async () => {
73
+ expect(typeof crypto.createSecretKey).toBe('function');
74
+ });
75
+ });
76
+
77
+ // ===================== getHashes =====================
78
+ await describe('crypto.getHashes', async () => {
79
+ await it('should return an array', async () => {
80
+ const hashes = crypto.getHashes();
81
+ expect(Array.isArray(hashes)).toBe(true);
82
+ });
83
+
84
+ await it('should contain sha256', async () => {
85
+ const hashes = crypto.getHashes();
86
+ const lower = hashes.map(h => h.toLowerCase());
87
+ expect(lower.some(h => h === 'sha256')).toBe(true);
88
+ });
89
+
90
+ await it('should contain sha1', async () => {
91
+ const hashes = crypto.getHashes();
92
+ const lower = hashes.map(h => h.toLowerCase());
93
+ expect(lower.some(h => h === 'sha1')).toBe(true);
94
+ });
95
+
96
+ await it('should contain md5', async () => {
97
+ const hashes = crypto.getHashes();
98
+ const lower = hashes.map(h => h.toLowerCase());
99
+ expect(lower.some(h => h === 'md5')).toBe(true);
100
+ });
101
+
102
+ await it('should contain sha512', async () => {
103
+ const hashes = crypto.getHashes();
104
+ const lower = hashes.map(h => h.toLowerCase());
105
+ expect(lower.some(h => h === 'sha512')).toBe(true);
106
+ });
107
+
108
+ await it('all entries should be strings', async () => {
109
+ const hashes = crypto.getHashes();
110
+ for (const h of hashes) {
111
+ expect(typeof h).toBe('string');
112
+ }
113
+ });
114
+ });
115
+
116
+ // ===================== getCiphers =====================
117
+ await describe('crypto.getCiphers', async () => {
118
+ await it('should return an array', async () => {
119
+ const ciphers = crypto.getCiphers();
120
+ expect(Array.isArray(ciphers)).toBe(true);
121
+ });
122
+
123
+ await it('should contain aes ciphers', async () => {
124
+ const ciphers = crypto.getCiphers();
125
+ const lower = ciphers.map(c => c.toLowerCase());
126
+ expect(lower.some(c => c.includes('aes'))).toBe(true);
127
+ });
128
+
129
+ await it('all entries should be strings', async () => {
130
+ const ciphers = crypto.getCiphers();
131
+ for (const c of ciphers) {
132
+ expect(typeof c).toBe('string');
133
+ }
134
+ });
135
+ });
136
+
137
+ // ===================== getCurves =====================
138
+ await describe('crypto.getCurves', async () => {
139
+ await it('should return an array', async () => {
140
+ const curves = crypto.getCurves();
141
+ expect(Array.isArray(curves)).toBe(true);
142
+ });
143
+
144
+ await it('should contain common curves', async () => {
145
+ const curves = crypto.getCurves();
146
+ expect(curves).toContain('secp256k1');
147
+ expect(curves).toContain('prime256v1');
148
+ });
149
+
150
+ await it('all entries should be strings', async () => {
151
+ const curves = crypto.getCurves();
152
+ for (const c of curves) {
153
+ expect(typeof c).toBe('string');
154
+ }
155
+ });
156
+ });
157
+
158
+ // ===================== Hash extended =====================
159
+ await describe('crypto.createHash extended', async () => {
160
+ await it('sha384 should produce 48-byte digest', async () => {
161
+ const hash = crypto.createHash('sha384').update('test').digest();
162
+ expect(hash.length).toBe(48);
163
+ });
164
+
165
+ await it('sha512 should produce 64-byte digest', async () => {
166
+ const hash = crypto.createHash('sha512').update('test').digest();
167
+ expect(hash.length).toBe(64);
168
+ });
169
+
170
+ await it('md5 should produce 16-byte digest', async () => {
171
+ const hash = crypto.createHash('md5').update('test').digest();
172
+ expect(hash.length).toBe(16);
173
+ });
174
+
175
+ await it('sha1 should produce 20-byte digest', async () => {
176
+ const hash = crypto.createHash('sha1').update('test').digest();
177
+ expect(hash.length).toBe(20);
178
+ });
179
+
180
+ await it('sha256 should produce 32-byte digest', async () => {
181
+ const hash = crypto.createHash('sha256').update('test').digest();
182
+ expect(hash.length).toBe(32);
183
+ });
184
+
185
+ await it('should produce known SHA256 hex for empty string', async () => {
186
+ const hash = crypto.createHash('sha256').update('').digest('hex');
187
+ expect(hash).toBe('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
188
+ });
189
+
190
+ await it('should produce known MD5 hex for empty string', async () => {
191
+ const hash = crypto.createHash('md5').update('').digest('hex');
192
+ expect(hash).toBe('d41d8cd98f00b204e9800998ecf8427e');
193
+ });
194
+
195
+ await it('should produce known SHA1 hex for empty string', async () => {
196
+ const hash = crypto.createHash('sha1').update('').digest('hex');
197
+ expect(hash).toBe('da39a3ee5e6b4b0d3255bfef95601890afd80709');
198
+ });
199
+
200
+ await it('chained updates should produce correct hash', async () => {
201
+ const hash1 = crypto.createHash('sha256').update('hello').update(' world').digest('hex');
202
+ const hash2 = crypto.createHash('sha256').update('hello world').digest('hex');
203
+ expect(hash1).toBe(hash2);
204
+ });
205
+
206
+ await it('should accept Uint8Array input', async () => {
207
+ const arr = new TextEncoder().encode('test data');
208
+ const hash = crypto.createHash('sha256').update(arr).digest('hex');
209
+ expect(typeof hash).toBe('string');
210
+ expect(hash.length).toBe(64);
211
+ });
212
+
213
+ await it('different data should produce different hashes', async () => {
214
+ const h1 = crypto.createHash('sha256').update('abc').digest('hex');
215
+ const h2 = crypto.createHash('sha256').update('def').digest('hex');
216
+ expect(h1).not.toBe(h2);
217
+ });
218
+
219
+ await it('should support base64 encoding', async () => {
220
+ const hash = crypto.createHash('sha256').update('test').digest('base64');
221
+ expect(typeof hash).toBe('string');
222
+ expect(hash.length).toBeGreaterThan(0);
223
+ });
224
+
225
+ await it('should throw for unknown algorithm', async () => {
226
+ expect(() => crypto.createHash('nonexistent')).toThrow();
227
+ });
228
+ });
229
+
230
+ // ===================== HMAC extended =====================
231
+ await describe('crypto.createHmac extended', async () => {
232
+ await it('HMAC-SHA256 should produce 32-byte digest', async () => {
233
+ const hmac = crypto.createHmac('sha256', 'key').update('data').digest();
234
+ expect(hmac.length).toBe(32);
235
+ });
236
+
237
+ await it('HMAC-MD5 should produce 16-byte digest', async () => {
238
+ const hmac = crypto.createHmac('md5', 'key').update('data').digest();
239
+ expect(hmac.length).toBe(16);
240
+ });
241
+
242
+ await it('HMAC-SHA1 should produce 20-byte digest', async () => {
243
+ const hmac = crypto.createHmac('sha1', 'key').update('data').digest();
244
+ expect(hmac.length).toBe(20);
245
+ });
246
+
247
+ await it('HMAC-SHA512 should produce 64-byte digest', async () => {
248
+ const hmac = crypto.createHmac('sha512', 'key').update('data').digest();
249
+ expect(hmac.length).toBe(64);
250
+ });
251
+
252
+ await it('should accept Uint8Array key', async () => {
253
+ const key = new TextEncoder().encode('secret');
254
+ const hmac = crypto.createHmac('sha256', key).update('data').digest('hex');
255
+ expect(typeof hmac).toBe('string');
256
+ expect(hmac.length).toBe(64);
257
+ });
258
+
259
+ await it('different keys should produce different HMACs', async () => {
260
+ const h1 = crypto.createHmac('sha256', 'key1').update('data').digest('hex');
261
+ const h2 = crypto.createHmac('sha256', 'key2').update('data').digest('hex');
262
+ expect(h1).not.toBe(h2);
263
+ });
264
+
265
+ await it('different data should produce different HMACs', async () => {
266
+ const h1 = crypto.createHmac('sha256', 'key').update('data1').digest('hex');
267
+ const h2 = crypto.createHmac('sha256', 'key').update('data2').digest('hex');
268
+ expect(h1).not.toBe(h2);
269
+ });
270
+
271
+ await it('chained updates should match single update', async () => {
272
+ const h1 = crypto.createHmac('sha256', 'key').update('hello').update(' world').digest('hex');
273
+ const h2 = crypto.createHmac('sha256', 'key').update('hello world').digest('hex');
274
+ expect(h1).toBe(h2);
275
+ });
276
+
277
+ await it('empty data should produce valid HMAC', async () => {
278
+ const hmac = crypto.createHmac('sha256', 'key').update('').digest('hex');
279
+ expect(typeof hmac).toBe('string');
280
+ expect(hmac.length).toBe(64);
281
+ });
282
+
283
+ await it('empty key should produce valid HMAC', async () => {
284
+ const hmac = crypto.createHmac('sha256', '').update('data').digest('hex');
285
+ expect(typeof hmac).toBe('string');
286
+ expect(hmac.length).toBe(64);
287
+ });
288
+ });
289
+
290
+ // ===================== randomBytes extended =====================
291
+ await describe('crypto.randomBytes extended', async () => {
292
+ await it('should return Buffer of requested size', async () => {
293
+ const buf = crypto.randomBytes(32);
294
+ expect(buf.length).toBe(32);
295
+ });
296
+
297
+ await it('should return different values on each call', async () => {
298
+ const a = crypto.randomBytes(32);
299
+ const b = crypto.randomBytes(32);
300
+ // Extremely unlikely to be equal
301
+ expect(a.toString('hex')).not.toBe(b.toString('hex'));
302
+ });
303
+
304
+ await it('should handle size 0', async () => {
305
+ const buf = crypto.randomBytes(0);
306
+ expect(buf.length).toBe(0);
307
+ });
308
+
309
+ await it('should handle size 1', async () => {
310
+ const buf = crypto.randomBytes(1);
311
+ expect(buf.length).toBe(1);
312
+ });
313
+
314
+ await it('should handle large sizes', async () => {
315
+ const buf = crypto.randomBytes(1024);
316
+ expect(buf.length).toBe(1024);
317
+ });
318
+ });
319
+
320
+ // ===================== randomUUID extended =====================
321
+ await describe('crypto.randomUUID extended', async () => {
322
+ await it('should return a v4 UUID string', async () => {
323
+ const uuid = crypto.randomUUID();
324
+ expect(typeof uuid).toBe('string');
325
+ expect(uuid.length).toBe(36);
326
+ });
327
+
328
+ await it('should match UUID v4 format', async () => {
329
+ const uuid = crypto.randomUUID();
330
+ const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
331
+ expect(regex.test(uuid)).toBe(true);
332
+ });
333
+
334
+ await it('should generate unique UUIDs', async () => {
335
+ const uuids = new Set(Array.from({ length: 100 }, () => crypto.randomUUID()));
336
+ expect(uuids.size).toBe(100);
337
+ });
338
+
339
+ await it('version nibble should be 4', async () => {
340
+ const uuid = crypto.randomUUID();
341
+ expect(uuid[14]).toBe('4');
342
+ });
343
+
344
+ await it('variant nibble should be 8, 9, a, or b', async () => {
345
+ const uuid = crypto.randomUUID();
346
+ expect(['8', '9', 'a', 'b']).toContain(uuid[19]);
347
+ });
348
+ });
349
+
350
+ // ===================== randomInt extended =====================
351
+ await describe('crypto.randomInt extended', async () => {
352
+ await it('should return integer in [0, max)', async () => {
353
+ for (let i = 0; i < 20; i++) {
354
+ const val = crypto.randomInt(10);
355
+ expect(val).toBeGreaterThan(-1);
356
+ expect(val).toBeLessThan(10);
357
+ expect(Number.isInteger(val)).toBe(true);
358
+ }
359
+ });
360
+
361
+ await it('should return integer in [min, max)', async () => {
362
+ for (let i = 0; i < 20; i++) {
363
+ const val = crypto.randomInt(5, 15);
364
+ expect(val).toBeGreaterThan(4);
365
+ expect(val).toBeLessThan(15);
366
+ }
367
+ });
368
+
369
+ await it('should handle max=1 (always returns 0)', async () => {
370
+ for (let i = 0; i < 10; i++) {
371
+ expect(crypto.randomInt(1)).toBe(0);
372
+ }
373
+ });
374
+
375
+ await it('should handle min=0, max=2', async () => {
376
+ const results = new Set<number>();
377
+ for (let i = 0; i < 50; i++) {
378
+ results.add(crypto.randomInt(0, 2));
379
+ }
380
+ // Should produce both 0 and 1
381
+ expect(results.has(0)).toBe(true);
382
+ expect(results.has(1)).toBe(true);
383
+ expect(results.has(2)).toBe(false);
384
+ });
385
+ });
386
+
387
+ // ===================== timingSafeEqual extended =====================
388
+ await describe('crypto.timingSafeEqual extended', async () => {
389
+ await it('should return true for equal arrays', async () => {
390
+ const a = new Uint8Array([104, 101, 108, 108, 111]);
391
+ const b = new Uint8Array([104, 101, 108, 108, 111]);
392
+ expect(crypto.timingSafeEqual(a, b)).toBe(true);
393
+ });
394
+
395
+ await it('should return false for different arrays', async () => {
396
+ const a = new Uint8Array([104, 101, 108, 108, 111]);
397
+ const b = new Uint8Array([119, 111, 114, 108, 100]);
398
+ expect(crypto.timingSafeEqual(a, b)).toBe(false);
399
+ });
400
+
401
+ await it('should throw for different lengths', async () => {
402
+ const a = new Uint8Array([1, 2, 3, 4, 5]);
403
+ const b = new Uint8Array([1, 2]);
404
+ expect(() => crypto.timingSafeEqual(a, b)).toThrow();
405
+ });
406
+
407
+ await it('should work with Uint8Array', async () => {
408
+ const a = new Uint8Array([1, 2, 3]);
409
+ const b = new Uint8Array([1, 2, 3]);
410
+ expect(crypto.timingSafeEqual(a, b)).toBe(true);
411
+ });
412
+
413
+ await it('should return false for single byte difference', async () => {
414
+ const a = new Uint8Array([1, 2, 3, 4]);
415
+ const b = new Uint8Array([1, 2, 3, 5]);
416
+ expect(crypto.timingSafeEqual(a, b)).toBe(false);
417
+ });
418
+
419
+ await it('should work with empty arrays', async () => {
420
+ const a = new Uint8Array(0);
421
+ const b = new Uint8Array(0);
422
+ expect(crypto.timingSafeEqual(a, b)).toBe(true);
423
+ });
424
+ });
425
+
426
+ // ===================== constants =====================
427
+ await describe('crypto.constants', async () => {
428
+ await it('should be an object', async () => {
429
+ expect(typeof crypto.constants).toBe('object');
430
+ });
431
+
432
+ await it('should have RSA_PKCS1_PADDING', async () => {
433
+ expect(typeof crypto.constants.RSA_PKCS1_PADDING).toBe('number');
434
+ });
435
+
436
+ await it('should have RSA_PKCS1_OAEP_PADDING', async () => {
437
+ expect(typeof crypto.constants.RSA_PKCS1_OAEP_PADDING).toBe('number');
438
+ });
439
+
440
+ await it('should have DH_CHECK_P_NOT_PRIME', async () => {
441
+ expect(typeof crypto.constants.DH_CHECK_P_NOT_PRIME).toBe('number');
442
+ });
443
+ });
444
+ };