@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/dh.ts ADDED
@@ -0,0 +1,761 @@
1
+ // Diffie-Hellman key exchange for GJS
2
+ // Reference: Node.js lib/internal/crypto/diffiehellman.js, refs/diffie-hellman/lib/dh.js
3
+ // Reimplemented for GJS using native BigInt (ES2024)
4
+ // Predefined groups from RFC 2409 (modp1, modp2) and RFC 3526 (modp5, modp14-modp18)
5
+
6
+ import { Buffer } from 'node:buffer';
7
+ import { randomBytes } from './random.js';
8
+ import { modPow } from './bigint-math.js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Predefined MODP groups (RFC 2409 Section 6.1-6.2, RFC 3526 Sections 2-7)
12
+ // All generators are 2.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ const PREDEFINED_GROUPS: Record<string, { gen: string; prime: string }> = {
16
+ // RFC 2409 Section 6.1 — 768-bit MODP Group
17
+ modp1: {
18
+ gen: '02',
19
+ prime:
20
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
21
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
22
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
23
+ 'e485b576625e7ec6f44c42e9a63a36' +
24
+ '20ffffffffffffffff',
25
+ },
26
+ // RFC 2409 Section 6.2 — 1024-bit MODP Group
27
+ modp2: {
28
+ gen: '02',
29
+ prime:
30
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
31
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
32
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
33
+ 'e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed' +
34
+ 'ee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381' +
35
+ 'ffffffffffffffff',
36
+ },
37
+ // RFC 3526 Section 2 — 1536-bit MODP Group
38
+ modp5: {
39
+ gen: '02',
40
+ prime:
41
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
42
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
43
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
44
+ 'e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed' +
45
+ 'ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d' +
46
+ 'c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f' +
47
+ '83655d23dca3ad961c62f356208552bb9ed529077096966d' +
48
+ '670c354e4abc9804f1746c08ca237327' +
49
+ 'ffffffffffffffff',
50
+ },
51
+ // RFC 3526 Section 3 — 2048-bit MODP Group
52
+ modp14: {
53
+ gen: '02',
54
+ prime:
55
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
56
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
57
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
58
+ 'e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed' +
59
+ 'ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d' +
60
+ 'c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f' +
61
+ '83655d23dca3ad961c62f356208552bb9ed529077096966d' +
62
+ '670c354e4abc9804f1746c08ca18217c32905e462e36ce3b' +
63
+ 'e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9' +
64
+ 'de2bcbf6955817183995497cea956ae515d2261898fa05101' +
65
+ '5728e5a8aacaa68ffffffffffffffff',
66
+ },
67
+ // RFC 3526 Section 4 — 3072-bit MODP Group
68
+ modp15: {
69
+ gen: '02',
70
+ prime:
71
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
72
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
73
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
74
+ 'e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed' +
75
+ 'ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d' +
76
+ 'c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f' +
77
+ '83655d23dca3ad961c62f356208552bb9ed529077096966d' +
78
+ '670c354e4abc9804f1746c08ca18217c32905e462e36ce3b' +
79
+ 'e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9' +
80
+ 'de2bcbf6955817183995497cea956ae515d2261898fa05101' +
81
+ '5728e5a8aaac42dad33170d04507a33a85521abdf1cba64e' +
82
+ 'cfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7a' +
83
+ 'bf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf' +
84
+ '12ffa06d98a0864d87602733ec86a64521f2b18177b200cb' +
85
+ 'be117577a615d6c770988c0bad946e208e24fa074e5ab314' +
86
+ '3db5bfce0fd108e4b82d120a93ad2caffffffffffffffff',
87
+ },
88
+ // RFC 3526 Section 5 — 4096-bit MODP Group
89
+ modp16: {
90
+ gen: '02',
91
+ prime:
92
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
93
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
94
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
95
+ 'e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed' +
96
+ 'ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d' +
97
+ 'c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f' +
98
+ '83655d23dca3ad961c62f356208552bb9ed529077096966d' +
99
+ '670c354e4abc9804f1746c08ca18217c32905e462e36ce3b' +
100
+ 'e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9' +
101
+ 'de2bcbf6955817183995497cea956ae515d2261898fa05101' +
102
+ '5728e5a8aaac42dad33170d04507a33a85521abdf1cba64e' +
103
+ 'cfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7a' +
104
+ 'bf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf' +
105
+ '12ffa06d98a0864d87602733ec86a64521f2b18177b200cb' +
106
+ 'be117577a615d6c770988c0bad946e208e24fa074e5ab314' +
107
+ '3db5bfce0fd108e4b82d120a92108011a723c12a787e6d78' +
108
+ '8719a10bdba5b2699c327186af4e23c1a946834b6150bda2' +
109
+ '583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa62' +
110
+ '87c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1' +
111
+ 'f612970cee2d7afb81bdd762170481cd0069127d5b05aa99' +
112
+ '3b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199' +
113
+ 'ffffffffffffffff',
114
+ },
115
+ // RFC 3526 Section 6 — 6144-bit MODP Group
116
+ modp17: {
117
+ gen: '02',
118
+ prime:
119
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
120
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
121
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
122
+ 'e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed' +
123
+ 'ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d' +
124
+ 'c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f' +
125
+ '83655d23dca3ad961c62f356208552bb9ed529077096966d' +
126
+ '670c354e4abc9804f1746c08ca18217c32905e462e36ce3b' +
127
+ 'e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9' +
128
+ 'de2bcbf6955817183995497cea956ae515d2261898fa05101' +
129
+ '5728e5a8aaac42dad33170d04507a33a85521abdf1cba64e' +
130
+ 'cfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7a' +
131
+ 'bf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf' +
132
+ '12ffa06d98a0864d87602733ec86a64521f2b18177b200cb' +
133
+ 'be117577a615d6c770988c0bad946e208e24fa074e5ab314' +
134
+ '3db5bfce0fd108e4b82d120a92108011a723c12a787e6d78' +
135
+ '8719a10bdba5b2699c327186af4e23c1a946834b6150bda2' +
136
+ '583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa62' +
137
+ '87c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1' +
138
+ 'f612970cee2d7afb81bdd762170481cd0069127d5b05aa99' +
139
+ '3b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff',
140
+ },
141
+ // RFC 3526 Section 7 — 8192-bit MODP Group
142
+ modp18: {
143
+ gen: '02',
144
+ prime:
145
+ 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1' +
146
+ '29024e088a67cc74020bbea63b139b22514a08798e3404dd' +
147
+ 'ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245' +
148
+ 'e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed' +
149
+ 'ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d' +
150
+ 'c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f' +
151
+ '83655d23dca3ad961c62f356208552bb9ed529077096966d' +
152
+ '670c354e4abc9804f1746c08ca18217c32905e462e36ce3b' +
153
+ 'e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9' +
154
+ 'de2bcbf6955817183995497cea956ae515d2261898fa05101' +
155
+ '5728e5a8aaac42dad33170d04507a33a85521abdf1cba64e' +
156
+ 'cfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7a' +
157
+ 'bf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf' +
158
+ '12ffa06d98a0864d87602733ec86a64521f2b18177b200cb' +
159
+ 'be117577a615d6c770988c0bad946e208e24fa074e5ab314' +
160
+ '3db5bfce0fd108e4b82d120a92108011a723c12a787e6d78' +
161
+ '8719a10bdba5b2699c327186af4e23c1a946834b6150bda2' +
162
+ '583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa62' +
163
+ '87c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1' +
164
+ 'f612970cee2d7afb81bdd762170481cd0069127d5b05aa99' +
165
+ '3b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff',
166
+ },
167
+ };
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // BigInt utility helpers
171
+ // ---------------------------------------------------------------------------
172
+
173
+ /** Convert a hex string to BigInt. */
174
+ function hexToBigInt(hex: string): bigint {
175
+ if (hex.length === 0) return 0n;
176
+ return BigInt('0x' + hex);
177
+ }
178
+
179
+ /** Convert a BigInt to a Buffer (big-endian, unsigned). */
180
+ function bigIntToBuffer(n: bigint): Buffer {
181
+ if (n === 0n) return Buffer.from([0]);
182
+
183
+ let hex = n.toString(16);
184
+ // Ensure even number of hex characters
185
+ if (hex.length % 2 !== 0) {
186
+ hex = '0' + hex;
187
+ }
188
+ return Buffer.from(hex, 'hex');
189
+ }
190
+
191
+ /** Convert a Buffer (big-endian, unsigned) to BigInt. */
192
+ function bufferToBigInt(buf: Buffer | Uint8Array): bigint {
193
+ if (buf.length === 0) return 0n;
194
+ const hex = Buffer.from(buf).toString('hex');
195
+ if (hex.length === 0) return 0n;
196
+ return BigInt('0x' + hex);
197
+ }
198
+
199
+ // modPow imported from shared bigint-math module
200
+
201
+ /**
202
+ * Count the bit length of a BigInt.
203
+ */
204
+ function bitLength(n: bigint): number {
205
+ if (n === 0n) return 0;
206
+ return n.toString(2).length;
207
+ }
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Primality testing (for verifyError)
211
+ // ---------------------------------------------------------------------------
212
+
213
+ // Small primes for sieve test
214
+ const SMALL_PRIMES: number[] = [];
215
+ {
216
+ // Generate primes up to ~1000 for the sieve
217
+ const limit = 1000;
218
+ const sieve = new Uint8Array(limit + 1);
219
+ for (let i = 2; i * i <= limit; i++) {
220
+ if (!sieve[i]) {
221
+ for (let j = i * i; j <= limit; j += i) {
222
+ sieve[j] = 1;
223
+ }
224
+ }
225
+ }
226
+ for (let i = 2; i <= limit; i++) {
227
+ if (!sieve[i]) SMALL_PRIMES.push(i);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Simple sieve: check if n is divisible by any small prime.
233
+ * Returns true if n passes the sieve (might be prime), false if composite.
234
+ */
235
+ function simpleSieve(n: bigint): boolean {
236
+ for (const p of SMALL_PRIMES) {
237
+ const bp = BigInt(p);
238
+ if (n % bp === 0n) {
239
+ return n === bp;
240
+ }
241
+ }
242
+ return true;
243
+ }
244
+
245
+ /**
246
+ * Fermat primality test: 2^(n-1) mod n === 1
247
+ */
248
+ function fermatTest(n: bigint): boolean {
249
+ return modPow(2n, n - 1n, n) === 1n;
250
+ }
251
+
252
+ /**
253
+ * Miller-Rabin primality test with a few fixed witnesses.
254
+ * Returns true if n is probably prime, false if definitely composite.
255
+ */
256
+ function millerRabinTest(n: bigint): boolean {
257
+ if (n < 2n) return false;
258
+ if (n === 2n || n === 3n) return true;
259
+ if (n % 2n === 0n) return false;
260
+
261
+ // Write n - 1 as 2^r * d
262
+ let d = n - 1n;
263
+ let r = 0;
264
+ while (d % 2n === 0n) {
265
+ d /= 2n;
266
+ r++;
267
+ }
268
+
269
+ // Test with a few witnesses
270
+ const witnesses = [2n, 3n, 5n, 7n, 11n, 13n];
271
+ for (const a of witnesses) {
272
+ if (a >= n) continue;
273
+
274
+ let x = modPow(a, d, n);
275
+ if (x === 1n || x === n - 1n) continue;
276
+
277
+ let composite = true;
278
+ for (let i = 0; i < r - 1; i++) {
279
+ x = modPow(x, 2n, n);
280
+ if (x === n - 1n) {
281
+ composite = false;
282
+ break;
283
+ }
284
+ }
285
+
286
+ if (composite) return false;
287
+ }
288
+
289
+ return true;
290
+ }
291
+
292
+ // DH_CHECK error flags (from OpenSSL / Node.js)
293
+ const DH_CHECK_P_NOT_PRIME = 0x01;
294
+ const DH_CHECK_P_NOT_SAFE_PRIME = 0x02;
295
+ const DH_NOT_SUITABLE_GENERATOR = 0x08;
296
+ const DH_UNABLE_TO_CHECK_GENERATOR = 0x04;
297
+
298
+ /**
299
+ * Check the prime and generator for errors, matching OpenSSL/Node.js behavior.
300
+ * Returns a bitmask of DH_CHECK_* flags.
301
+ */
302
+ function checkPrime(prime: bigint, generatorBuf: Buffer): number {
303
+ const genHex = generatorBuf.toString('hex');
304
+
305
+ let error = 0;
306
+
307
+ // Check if prime is even or fails primality tests
308
+ if (prime % 2n === 0n || !simpleSieve(prime) || !fermatTest(prime) || !millerRabinTest(prime)) {
309
+ // Not a prime: +1
310
+ error += DH_CHECK_P_NOT_PRIME;
311
+
312
+ if (genHex === '02' || genHex === '05') {
313
+ // We'd be able to check the generator, it would fail: +8
314
+ error += DH_NOT_SUITABLE_GENERATOR;
315
+ } else {
316
+ // We can't test the generator: +4
317
+ error += DH_UNABLE_TO_CHECK_GENERATOR;
318
+ }
319
+ return error;
320
+ }
321
+
322
+ // Check if (prime - 1) / 2 is also prime (safe prime test)
323
+ const halfPrime = prime >> 1n;
324
+ if (!millerRabinTest(halfPrime)) {
325
+ // Not a safe prime: +2
326
+ error += DH_CHECK_P_NOT_SAFE_PRIME;
327
+ }
328
+
329
+ // Check generator suitability
330
+ switch (genHex) {
331
+ case '02':
332
+ // For generator 2, prime mod 24 must equal 11
333
+ if (prime % 24n !== 11n) {
334
+ error += DH_NOT_SUITABLE_GENERATOR;
335
+ }
336
+ break;
337
+ case '05':
338
+ // For generator 5, prime mod 10 must equal 3 or 7
339
+ {
340
+ const rem = prime % 10n;
341
+ if (rem !== 3n && rem !== 7n) {
342
+ error += DH_NOT_SUITABLE_GENERATOR;
343
+ }
344
+ }
345
+ break;
346
+ default:
347
+ // Can't test this generator: +4
348
+ error += DH_UNABLE_TO_CHECK_GENERATOR;
349
+ }
350
+
351
+ return error;
352
+ }
353
+
354
+ // Cache for prime checks
355
+ const primeCheckCache: Record<string, number> = {};
356
+
357
+ function getCachedCheckPrime(prime: bigint, generatorBuf: Buffer): number {
358
+ const key = generatorBuf.toString('hex') + '_' + prime.toString(16);
359
+ if (key in primeCheckCache) {
360
+ return primeCheckCache[key];
361
+ }
362
+ const result = checkPrime(prime, generatorBuf);
363
+ primeCheckCache[key] = result;
364
+ return result;
365
+ }
366
+
367
+ // ---------------------------------------------------------------------------
368
+ // DiffieHellman class
369
+ // ---------------------------------------------------------------------------
370
+
371
+ /**
372
+ * Parse an input value to a Buffer, handling encoding parameters.
373
+ */
374
+ function toBuffer(
375
+ value: string | Buffer | Uint8Array | ArrayBuffer | number,
376
+ encoding?: BufferEncoding,
377
+ ): Buffer {
378
+ if (Buffer.isBuffer(value)) return value;
379
+ if (value instanceof Uint8Array) return Buffer.from(value);
380
+ if (value instanceof ArrayBuffer) return Buffer.from(value);
381
+ if (typeof value === 'string') return Buffer.from(value, encoding || 'utf8');
382
+ if (typeof value === 'number') {
383
+ // Single byte number — treat as generator value
384
+ const buf = Buffer.alloc(1);
385
+ buf[0] = value;
386
+ return buf;
387
+ }
388
+ throw new TypeError('Invalid input type');
389
+ }
390
+
391
+ /**
392
+ * Format a BigInt return value as Buffer or encoded string.
393
+ */
394
+ function formatReturnValue(n: bigint, encoding?: BufferEncoding): Buffer | string {
395
+ const buf = bigIntToBuffer(n);
396
+ if (!encoding) {
397
+ return buf;
398
+ }
399
+ return buf.toString(encoding);
400
+ }
401
+
402
+ export class DiffieHellman {
403
+ private _prime: bigint;
404
+ private _generator: bigint;
405
+ private _generatorBuf: Buffer;
406
+ private _primeByteLength: number;
407
+ private _pubKey: bigint | undefined;
408
+ private _privKey: bigint | undefined;
409
+ private _primeCode: number | undefined;
410
+ private _malleable: boolean;
411
+
412
+ constructor(prime: Buffer, generator: Buffer, malleable: boolean = false) {
413
+ this._prime = bufferToBigInt(prime);
414
+ this._generatorBuf = generator;
415
+ this._generator = bufferToBigInt(generator);
416
+ this._primeByteLength = prime.length;
417
+ this._pubKey = undefined;
418
+ this._privKey = undefined;
419
+ this._primeCode = undefined;
420
+ this._malleable = malleable;
421
+
422
+ if (!malleable) {
423
+ // For predefined groups (getDiffieHellman), verifyError is 0
424
+ // and setPublicKey/setPrivateKey are not available (undefined),
425
+ // matching Node.js behavior.
426
+ this._primeCode = 0;
427
+ // Remove setPublicKey/setPrivateKey from this instance so that
428
+ // typeof dh.setPublicKey === 'undefined', matching Node.js.
429
+ // Setting instance properties to undefined shadows the prototype methods.
430
+ this.setPublicKey = undefined as unknown as DiffieHellman['setPublicKey'];
431
+ this.setPrivateKey = undefined as unknown as DiffieHellman['setPrivateKey'];
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Error code from prime/generator validation.
437
+ * Lazily computed on first access.
438
+ */
439
+ get verifyError(): number {
440
+ if (typeof this._primeCode !== 'number') {
441
+ this._primeCode = getCachedCheckPrime(this._prime, this._generatorBuf);
442
+ }
443
+ return this._primeCode;
444
+ }
445
+
446
+ /**
447
+ * Generate a random private key and compute the corresponding public key.
448
+ * Returns the public key as a Buffer (or encoded string).
449
+ */
450
+ generateKeys(): Buffer;
451
+ generateKeys(encoding: BufferEncoding): string;
452
+ generateKeys(encoding?: BufferEncoding): Buffer | string {
453
+ if (!this._privKey) {
454
+ // Generate random bytes equal to the prime byte length
455
+ const randBuf = randomBytes(this._primeByteLength);
456
+ this._privKey = bufferToBigInt(randBuf);
457
+ }
458
+
459
+ // publicKey = generator ^ privateKey mod prime
460
+ this._pubKey = modPow(this._generator, this._privKey, this._prime);
461
+
462
+ return this.getPublicKey(encoding as BufferEncoding);
463
+ }
464
+
465
+ /**
466
+ * Compute the shared secret using the other party's public key.
467
+ */
468
+ computeSecret(otherPublicKey: Buffer | Uint8Array | string, inputEncoding?: BufferEncoding): Buffer;
469
+ computeSecret(otherPublicKey: Buffer | Uint8Array | string, inputEncoding: BufferEncoding, outputEncoding: BufferEncoding): string;
470
+ computeSecret(
471
+ otherPublicKey: Buffer | Uint8Array | string,
472
+ inputEncoding?: BufferEncoding,
473
+ outputEncoding?: BufferEncoding,
474
+ ): Buffer | string {
475
+ let otherBuf: Buffer;
476
+ if (typeof otherPublicKey === 'string') {
477
+ otherBuf = Buffer.from(otherPublicKey, inputEncoding || 'utf8');
478
+ } else {
479
+ otherBuf = Buffer.from(otherPublicKey);
480
+ }
481
+
482
+ const other = bufferToBigInt(otherBuf);
483
+
484
+ if (!this._privKey) {
485
+ throw new Error('You must generate keys before computing a secret');
486
+ }
487
+
488
+ if (other <= 0n || other >= this._prime) {
489
+ throw new Error('Supplied key is too large');
490
+ }
491
+
492
+ // secret = otherPublicKey ^ privateKey mod prime
493
+ const secret = modPow(other, this._privKey, this._prime);
494
+ let out = bigIntToBuffer(secret);
495
+
496
+ // Pad the output to the prime length (matching Node.js behavior)
497
+ const primeLen = this._primeByteLength;
498
+ if (out.length < primeLen) {
499
+ const padded = Buffer.alloc(primeLen);
500
+ out.copy(padded, primeLen - out.length);
501
+ out = padded;
502
+ }
503
+
504
+ if (outputEncoding) {
505
+ return out.toString(outputEncoding);
506
+ }
507
+ return out;
508
+ }
509
+
510
+ /**
511
+ * Get the prime as a Buffer or encoded string.
512
+ */
513
+ getPrime(): Buffer;
514
+ getPrime(encoding: BufferEncoding): string;
515
+ getPrime(encoding?: BufferEncoding): Buffer | string {
516
+ return formatReturnValue(this._prime, encoding as BufferEncoding);
517
+ }
518
+
519
+ /**
520
+ * Get the generator as a Buffer or encoded string.
521
+ */
522
+ getGenerator(): Buffer;
523
+ getGenerator(encoding: BufferEncoding): string;
524
+ getGenerator(encoding?: BufferEncoding): Buffer | string {
525
+ return formatReturnValue(this._generator, encoding as BufferEncoding);
526
+ }
527
+
528
+ /**
529
+ * Get the public key as a Buffer or encoded string.
530
+ */
531
+ getPublicKey(): Buffer;
532
+ getPublicKey(encoding: BufferEncoding): string;
533
+ getPublicKey(encoding?: BufferEncoding): Buffer | string {
534
+ if (!this._pubKey) {
535
+ throw new Error('No public key - call generateKeys() first');
536
+ }
537
+ return formatReturnValue(this._pubKey, encoding as BufferEncoding);
538
+ }
539
+
540
+ /**
541
+ * Get the private key as a Buffer or encoded string.
542
+ */
543
+ getPrivateKey(): Buffer;
544
+ getPrivateKey(encoding: BufferEncoding): string;
545
+ getPrivateKey(encoding?: BufferEncoding): Buffer | string {
546
+ if (!this._privKey) {
547
+ throw new Error('No private key - call generateKeys() first');
548
+ }
549
+ return formatReturnValue(this._privKey, encoding as BufferEncoding);
550
+ }
551
+
552
+ /**
553
+ * Set the public key. Only available for malleable DH instances
554
+ * (created via createDiffieHellman, not getDiffieHellman).
555
+ */
556
+ setPublicKey(publicKey: Buffer | Uint8Array | string, encoding?: BufferEncoding): void {
557
+ if (!this._malleable) {
558
+ throw new Error('setPublicKey is not available for predefined DH groups');
559
+ }
560
+ const buf = typeof publicKey === 'string'
561
+ ? Buffer.from(publicKey, encoding || 'utf8')
562
+ : Buffer.from(publicKey);
563
+ this._pubKey = bufferToBigInt(buf);
564
+ }
565
+
566
+ /**
567
+ * Set the private key. Only available for malleable DH instances
568
+ * (created via createDiffieHellman, not getDiffieHellman).
569
+ */
570
+ setPrivateKey(privateKey: Buffer | Uint8Array | string, encoding?: BufferEncoding): void {
571
+ if (!this._malleable) {
572
+ throw new Error('setPrivateKey is not available for predefined DH groups');
573
+ }
574
+ const buf = typeof privateKey === 'string'
575
+ ? Buffer.from(privateKey, encoding || 'utf8')
576
+ : Buffer.from(privateKey);
577
+ this._privKey = bufferToBigInt(buf);
578
+ }
579
+ }
580
+
581
+ // ---------------------------------------------------------------------------
582
+ // Prime generation for createDiffieHellman(primeLength)
583
+ // ---------------------------------------------------------------------------
584
+
585
+ /**
586
+ * Generate a random prime of the specified bit length suitable for DH.
587
+ * This matches OpenSSL's behavior: for generators 2 and 5 the prime
588
+ * must satisfy specific congruence conditions.
589
+ */
590
+ function generatePrime(bits: number, generator: Buffer): Buffer {
591
+ const gen = bufferToBigInt(generator);
592
+
593
+ if (bits < 16) {
594
+ // Match OpenSSL behavior for very small bit counts
595
+ if (gen === 2n || gen === 5n) {
596
+ return Buffer.from([0x8c, 0x7b]);
597
+ } else {
598
+ return Buffer.from([0x8c, 0x27]);
599
+ }
600
+ }
601
+
602
+ const byteLen = Math.ceil(bits / 8);
603
+ const ONE = 1n;
604
+ const TWO = 2n;
605
+ const FOUR = 4n;
606
+ const TWENTYFOUR = 24n;
607
+ const ELEVEN = 11n;
608
+ const TEN = 10n;
609
+ const THREE = 3n;
610
+
611
+ while (true) {
612
+ const randBuf = randomBytes(byteLen);
613
+ let num = bufferToBigInt(randBuf);
614
+
615
+ // Trim to desired bit length
616
+ while (bitLength(num) > bits) {
617
+ num >>= 1n;
618
+ }
619
+
620
+ // Make odd
621
+ if (num % TWO === 0n) {
622
+ num += ONE;
623
+ }
624
+
625
+ // Ensure bit 1 is set (num must be 3 mod 4 for safe primes)
626
+ if (!(num & TWO)) {
627
+ num += TWO;
628
+ }
629
+
630
+ // Adjust for generator congruence requirements
631
+ if (gen === TWO) {
632
+ // For generator 2: prime mod 24 must equal 11
633
+ while (num % TWENTYFOUR !== ELEVEN) {
634
+ num += FOUR;
635
+ }
636
+ } else if (gen === 5n) {
637
+ // For generator 5: prime mod 10 must equal 3
638
+ while (num % TEN !== THREE) {
639
+ num += FOUR;
640
+ }
641
+ }
642
+
643
+ // Check that (num - 1) / 2 is also prime (safe prime)
644
+ const n2 = num >> 1n;
645
+
646
+ if (
647
+ simpleSieve(n2) && simpleSieve(num) &&
648
+ fermatTest(n2) && fermatTest(num) &&
649
+ millerRabinTest(n2) && millerRabinTest(num)
650
+ ) {
651
+ return bigIntToBuffer(num);
652
+ }
653
+ }
654
+ }
655
+
656
+ // ---------------------------------------------------------------------------
657
+ // Public API
658
+ // ---------------------------------------------------------------------------
659
+
660
+ const VALID_ENCODINGS: Record<string, boolean> = {
661
+ binary: true,
662
+ hex: true,
663
+ base64: true,
664
+ latin1: true,
665
+ utf8: true,
666
+ 'utf-8': true,
667
+ base64url: true,
668
+ ascii: true,
669
+ ucs2: true,
670
+ 'ucs-2': true,
671
+ utf16le: true,
672
+ 'utf-16le': true,
673
+ };
674
+
675
+ /**
676
+ * Create a DiffieHellman key exchange object.
677
+ *
678
+ * Usage:
679
+ * createDiffieHellman(primeLength)
680
+ * createDiffieHellman(primeLength, generator)
681
+ * createDiffieHellman(prime, primeEncoding, generator, generatorEncoding)
682
+ * createDiffieHellman(prime, generator)
683
+ */
684
+ export function createDiffieHellman(
685
+ prime: number | string | Buffer | Uint8Array,
686
+ primeEncoding?: BufferEncoding | number | string | Buffer | Uint8Array,
687
+ generator?: number | string | Buffer | Uint8Array,
688
+ generatorEncoding?: BufferEncoding,
689
+ ): DiffieHellman {
690
+ // If primeEncoding is a Buffer or not a valid encoding string, treat it as
691
+ // the generator argument (shift arguments right)
692
+ if (
693
+ primeEncoding !== undefined &&
694
+ typeof primeEncoding !== 'number' &&
695
+ (Buffer.isBuffer(primeEncoding) ||
696
+ primeEncoding instanceof Uint8Array ||
697
+ (typeof primeEncoding === 'string' && !(primeEncoding in VALID_ENCODINGS)))
698
+ ) {
699
+ return createDiffieHellman(
700
+ prime,
701
+ 'binary' as BufferEncoding,
702
+ primeEncoding as unknown as string | Buffer | Uint8Array,
703
+ generator as unknown as BufferEncoding,
704
+ );
705
+ }
706
+
707
+ const enc = (primeEncoding as BufferEncoding) || ('binary' as BufferEncoding);
708
+ const genc = generatorEncoding || ('binary' as BufferEncoding);
709
+ let genBuf: Buffer = Buffer.from([2]);
710
+
711
+ if (generator !== undefined) {
712
+ if (typeof generator === 'number') {
713
+ genBuf = Buffer.alloc(1);
714
+ genBuf[0] = generator;
715
+ } else if (!Buffer.isBuffer(generator)) {
716
+ genBuf = Buffer.from(generator as string, genc);
717
+ } else {
718
+ genBuf = generator;
719
+ }
720
+ }
721
+
722
+ // If prime is a number, generate a random prime of that bit length
723
+ if (typeof prime === 'number') {
724
+ const primeBuf = generatePrime(prime, genBuf);
725
+ return new DiffieHellman(primeBuf, genBuf, true);
726
+ }
727
+
728
+ // Otherwise parse the prime
729
+ let primeBuf: Buffer;
730
+ if (Buffer.isBuffer(prime)) {
731
+ primeBuf = prime;
732
+ } else if (prime instanceof Uint8Array) {
733
+ primeBuf = Buffer.from(prime);
734
+ } else {
735
+ primeBuf = Buffer.from(prime, enc);
736
+ }
737
+
738
+ return new DiffieHellman(primeBuf, genBuf, true);
739
+ }
740
+
741
+ /**
742
+ * Create a DiffieHellman key exchange object using a predefined MODP group.
743
+ *
744
+ * Note: Instances from getDiffieHellman do NOT have setPublicKey/setPrivateKey,
745
+ * matching Node.js behavior where predefined group instances are not malleable.
746
+ */
747
+ export function getDiffieHellman(groupName: string): DiffieHellman {
748
+ const group = PREDEFINED_GROUPS[groupName];
749
+ if (!group) {
750
+ throw new Error(`Unknown group: ${groupName}. Supported groups: ${Object.keys(PREDEFINED_GROUPS).join(', ')}`);
751
+ }
752
+ const primeBuf = Buffer.from(group.prime, 'hex');
753
+ const genBuf = Buffer.from(group.gen, 'hex');
754
+ return new DiffieHellman(primeBuf, genBuf, false);
755
+ }
756
+
757
+ /** Alias for getDiffieHellman. */
758
+ export const createDiffieHellmanGroup = getDiffieHellman;
759
+
760
+ /** Alias for getDiffieHellman. */
761
+ export const DiffieHellmanGroup = getDiffieHellman;