@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.
- package/README.md +27 -0
- package/lib/esm/asn1.js +504 -0
- package/lib/esm/bigint-math.js +34 -0
- package/lib/esm/cipher.js +1272 -0
- package/lib/esm/constants.js +15 -0
- package/lib/esm/crypto-utils.js +47 -0
- package/lib/esm/dh.js +411 -0
- package/lib/esm/ecdh.js +356 -0
- package/lib/esm/ecdsa.js +125 -0
- package/lib/esm/hash.js +100 -0
- package/lib/esm/hkdf.js +58 -0
- package/lib/esm/hmac.js +93 -0
- package/lib/esm/index.js +158 -0
- package/lib/esm/key-object.js +330 -0
- package/lib/esm/mgf1.js +27 -0
- package/lib/esm/pbkdf2.js +68 -0
- package/lib/esm/public-encrypt.js +175 -0
- package/lib/esm/random.js +138 -0
- package/lib/esm/rsa-oaep.js +95 -0
- package/lib/esm/rsa-pss.js +100 -0
- package/lib/esm/scrypt.js +134 -0
- package/lib/esm/sign.js +248 -0
- package/lib/esm/timing-safe-equal.js +13 -0
- package/lib/esm/x509.js +214 -0
- package/lib/types/asn1.d.ts +87 -0
- package/lib/types/bigint-math.d.ts +13 -0
- package/lib/types/cipher.d.ts +84 -0
- package/lib/types/constants.d.ts +10 -0
- package/lib/types/crypto-utils.d.ts +22 -0
- package/lib/types/dh.d.ts +79 -0
- package/lib/types/ecdh.d.ts +96 -0
- package/lib/types/ecdsa.d.ts +21 -0
- package/lib/types/hash.d.ts +25 -0
- package/lib/types/hkdf.d.ts +9 -0
- package/lib/types/hmac.d.ts +20 -0
- package/lib/types/index.d.ts +105 -0
- package/lib/types/key-object.d.ts +36 -0
- package/lib/types/mgf1.d.ts +5 -0
- package/lib/types/pbkdf2.d.ts +9 -0
- package/lib/types/public-encrypt.d.ts +42 -0
- package/lib/types/random.d.ts +22 -0
- package/lib/types/rsa-oaep.d.ts +8 -0
- package/lib/types/rsa-pss.d.ts +8 -0
- package/lib/types/scrypt.d.ts +11 -0
- package/lib/types/sign.d.ts +61 -0
- package/lib/types/timing-safe-equal.d.ts +6 -0
- package/lib/types/x509.d.ts +72 -0
- package/package.json +45 -0
- package/src/asn1.ts +797 -0
- package/src/bigint-math.ts +45 -0
- package/src/cipher.spec.ts +332 -0
- package/src/cipher.ts +952 -0
- package/src/constants.ts +16 -0
- package/src/crypto-utils.ts +64 -0
- package/src/dh.spec.ts +111 -0
- package/src/dh.ts +761 -0
- package/src/ecdh.spec.ts +116 -0
- package/src/ecdh.ts +624 -0
- package/src/ecdsa.ts +243 -0
- package/src/extended.spec.ts +444 -0
- package/src/gcm.spec.ts +141 -0
- package/src/hash.spec.ts +86 -0
- package/src/hash.ts +119 -0
- package/src/hkdf.ts +99 -0
- package/src/hmac.spec.ts +64 -0
- package/src/hmac.ts +123 -0
- package/src/index.ts +93 -0
- package/src/key-object.spec.ts +202 -0
- package/src/key-object.ts +401 -0
- package/src/mgf1.ts +37 -0
- package/src/pbkdf2.spec.ts +76 -0
- package/src/pbkdf2.ts +106 -0
- package/src/public-encrypt.ts +288 -0
- package/src/random.spec.ts +133 -0
- package/src/random.ts +183 -0
- package/src/rsa-oaep.ts +167 -0
- package/src/rsa-pss.ts +190 -0
- package/src/scrypt.spec.ts +90 -0
- package/src/scrypt.ts +191 -0
- package/src/sign.spec.ts +160 -0
- package/src/sign.ts +319 -0
- package/src/test.mts +19 -0
- package/src/timing-safe-equal.ts +21 -0
- package/src/x509.spec.ts +210 -0
- package/src/x509.ts +262 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/ecdh.ts
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
// ECDH (Elliptic Curve Diffie-Hellman) for GJS
|
|
2
|
+
// Reference: refs/create-ecdh/browser.js, Node.js lib/internal/crypto/diffiehellman.js
|
|
3
|
+
// Reimplemented for GJS using native BigInt (ES2024) elliptic curve arithmetic
|
|
4
|
+
// Curve parameters from NIST FIPS 186-4 and SEC 2
|
|
5
|
+
|
|
6
|
+
import { Buffer } from 'node:buffer';
|
|
7
|
+
import { randomBytes } from './random.js';
|
|
8
|
+
import { modPow } from './bigint-math.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Elliptic curve types
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** A point on an elliptic curve, or null for the point at infinity. */
|
|
15
|
+
type ECPoint = { x: bigint; y: bigint } | null;
|
|
16
|
+
|
|
17
|
+
interface CurveParams {
|
|
18
|
+
/** Field prime */
|
|
19
|
+
p: bigint;
|
|
20
|
+
/** Curve coefficient a */
|
|
21
|
+
a: bigint;
|
|
22
|
+
/** Curve coefficient b */
|
|
23
|
+
b: bigint;
|
|
24
|
+
/** Generator x-coordinate */
|
|
25
|
+
Gx: bigint;
|
|
26
|
+
/** Generator y-coordinate */
|
|
27
|
+
Gy: bigint;
|
|
28
|
+
/** Order of the generator */
|
|
29
|
+
n: bigint;
|
|
30
|
+
/** Byte length of a field element */
|
|
31
|
+
byteLength: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Curve parameter definitions (NIST FIPS 186-4 / SEC 2)
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
const CURVES: Record<string, CurveParams> = {
|
|
39
|
+
secp256k1: {
|
|
40
|
+
p: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn,
|
|
41
|
+
a: 0n,
|
|
42
|
+
b: 7n,
|
|
43
|
+
Gx: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n,
|
|
44
|
+
Gy: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n,
|
|
45
|
+
n: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n,
|
|
46
|
+
byteLength: 32,
|
|
47
|
+
},
|
|
48
|
+
prime256v1: {
|
|
49
|
+
p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFn,
|
|
50
|
+
a: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFn - 3n,
|
|
51
|
+
b: 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604Bn,
|
|
52
|
+
Gx: 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296n,
|
|
53
|
+
Gy: 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5n,
|
|
54
|
+
n: 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551n,
|
|
55
|
+
byteLength: 32,
|
|
56
|
+
},
|
|
57
|
+
secp384r1: {
|
|
58
|
+
p: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFFn,
|
|
59
|
+
a: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFFn - 3n,
|
|
60
|
+
b: 0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEFn,
|
|
61
|
+
Gx: 0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7n,
|
|
62
|
+
Gy: 0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5Fn,
|
|
63
|
+
n: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973n,
|
|
64
|
+
byteLength: 48,
|
|
65
|
+
},
|
|
66
|
+
secp521r1: {
|
|
67
|
+
p: 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn,
|
|
68
|
+
a: 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn - 3n,
|
|
69
|
+
b: 0x0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00n,
|
|
70
|
+
Gx: 0x00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66n,
|
|
71
|
+
Gy: 0x011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650n,
|
|
72
|
+
n: 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409n,
|
|
73
|
+
byteLength: 66,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Curve name aliases — Node.js accepts multiple names for the same curve
|
|
78
|
+
const CURVE_ALIASES: Record<string, string> = {
|
|
79
|
+
'secp256k1': 'secp256k1',
|
|
80
|
+
'prime256v1': 'prime256v1',
|
|
81
|
+
'secp256r1': 'prime256v1',
|
|
82
|
+
'p-256': 'prime256v1',
|
|
83
|
+
'p256': 'prime256v1',
|
|
84
|
+
'secp384r1': 'secp384r1',
|
|
85
|
+
'p-384': 'secp384r1',
|
|
86
|
+
'p384': 'secp384r1',
|
|
87
|
+
'secp521r1': 'secp521r1',
|
|
88
|
+
'p-521': 'secp521r1',
|
|
89
|
+
'p521': 'secp521r1',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// BigInt modular arithmetic
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Non-negative modulus: always returns a value in [0, mod).
|
|
98
|
+
*/
|
|
99
|
+
function mod(a: bigint, m: bigint): bigint {
|
|
100
|
+
const r = a % m;
|
|
101
|
+
return r < 0n ? r + m : r;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// modPow imported from shared bigint-math module
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Modular multiplicative inverse using extended Euclidean algorithm.
|
|
108
|
+
* Returns x such that (a * x) mod m = 1.
|
|
109
|
+
* Throws if a and m are not coprime.
|
|
110
|
+
*/
|
|
111
|
+
function modInverse(a: bigint, m: bigint): bigint {
|
|
112
|
+
a = mod(a, m);
|
|
113
|
+
if (a === 0n) {
|
|
114
|
+
throw new Error('No modular inverse for zero');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let [old_r, r] = [a, m];
|
|
118
|
+
let [old_s, s] = [1n, 0n];
|
|
119
|
+
|
|
120
|
+
while (r !== 0n) {
|
|
121
|
+
const q = old_r / r;
|
|
122
|
+
[old_r, r] = [r, old_r - q * r];
|
|
123
|
+
[old_s, s] = [s, old_s - q * s];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (old_r !== 1n) {
|
|
127
|
+
throw new Error('Modular inverse does not exist');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return mod(old_s, m);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Elliptic curve point operations
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Add two points on the curve.
|
|
139
|
+
* Returns the point at infinity (null) when appropriate.
|
|
140
|
+
*/
|
|
141
|
+
function pointAdd(P: ECPoint, Q: ECPoint, curve: CurveParams): ECPoint {
|
|
142
|
+
if (P === null) return Q;
|
|
143
|
+
if (Q === null) return P;
|
|
144
|
+
|
|
145
|
+
const { p } = curve;
|
|
146
|
+
|
|
147
|
+
if (P.x === Q.x) {
|
|
148
|
+
if (mod(P.y + Q.y, p) === 0n) {
|
|
149
|
+
// P + (-P) = O (point at infinity)
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
// P === Q, use point doubling
|
|
153
|
+
return pointDouble(P, curve);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Standard addition formula:
|
|
157
|
+
// lambda = (Qy - Py) / (Qx - Px) mod p
|
|
158
|
+
const dx = mod(Q.x - P.x, p);
|
|
159
|
+
const dy = mod(Q.y - P.y, p);
|
|
160
|
+
const lambda = mod(dy * modInverse(dx, p), p);
|
|
161
|
+
|
|
162
|
+
const x3 = mod(lambda * lambda - P.x - Q.x, p);
|
|
163
|
+
const y3 = mod(lambda * (P.x - x3) - P.y, p);
|
|
164
|
+
|
|
165
|
+
return { x: x3, y: y3 };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Double a point on the curve.
|
|
170
|
+
*/
|
|
171
|
+
function pointDouble(P: ECPoint, curve: CurveParams): ECPoint {
|
|
172
|
+
if (P === null) return null;
|
|
173
|
+
|
|
174
|
+
const { a, p } = curve;
|
|
175
|
+
|
|
176
|
+
// If y === 0, the tangent is vertical → point at infinity
|
|
177
|
+
if (P.y === 0n) return null;
|
|
178
|
+
|
|
179
|
+
// lambda = (3 * x^2 + a) / (2 * y) mod p
|
|
180
|
+
const num = mod(3n * P.x * P.x + a, p);
|
|
181
|
+
const den = mod(2n * P.y, p);
|
|
182
|
+
const lambda = mod(num * modInverse(den, p), p);
|
|
183
|
+
|
|
184
|
+
const x3 = mod(lambda * lambda - 2n * P.x, p);
|
|
185
|
+
const y3 = mod(lambda * (P.x - x3) - P.y, p);
|
|
186
|
+
|
|
187
|
+
return { x: x3, y: y3 };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Scalar multiplication using the double-and-add method (constant-time-ish
|
|
192
|
+
* with respect to the bit length of the scalar, but not fully
|
|
193
|
+
* constant-time — acceptable since we are not a production crypto library
|
|
194
|
+
* and match the behaviour of refs/create-ecdh which uses elliptic.js).
|
|
195
|
+
*
|
|
196
|
+
* Uses a fixed-window approach of width 1 (Montgomery ladder variant) to
|
|
197
|
+
* avoid the most trivial timing leaks.
|
|
198
|
+
*/
|
|
199
|
+
function scalarMul(k: bigint, P: ECPoint, curve: CurveParams): ECPoint {
|
|
200
|
+
if (P === null) return null;
|
|
201
|
+
if (k === 0n) return null;
|
|
202
|
+
|
|
203
|
+
const { n } = curve;
|
|
204
|
+
// Reduce k mod n
|
|
205
|
+
k = mod(k, n);
|
|
206
|
+
if (k === 0n) return null;
|
|
207
|
+
|
|
208
|
+
// Montgomery ladder
|
|
209
|
+
let R0: ECPoint = null; // identity
|
|
210
|
+
let R1: ECPoint = P;
|
|
211
|
+
|
|
212
|
+
const bits = k.toString(2);
|
|
213
|
+
for (let i = 0; i < bits.length; i++) {
|
|
214
|
+
if (bits[i] === '1') {
|
|
215
|
+
R0 = pointAdd(R0, R1, curve);
|
|
216
|
+
R1 = pointDouble(R1, curve);
|
|
217
|
+
} else {
|
|
218
|
+
R1 = pointAdd(R0, R1, curve);
|
|
219
|
+
R0 = pointDouble(R0, curve);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return R0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Encoding helpers
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Convert a BigInt to a Buffer of exactly `len` bytes (big-endian,
|
|
232
|
+
* zero-padded on the left).
|
|
233
|
+
*/
|
|
234
|
+
function bigintToBuffer(n: bigint, len: number): Buffer {
|
|
235
|
+
const hex = n.toString(16).padStart(len * 2, '0');
|
|
236
|
+
return Buffer.from(hex, 'hex');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Convert a Buffer (big-endian unsigned) to a BigInt.
|
|
241
|
+
*/
|
|
242
|
+
function bufferToBigint(buf: Buffer | Uint8Array): bigint {
|
|
243
|
+
const hex = Buffer.from(buf).toString('hex');
|
|
244
|
+
if (hex.length === 0) return 0n;
|
|
245
|
+
return BigInt('0x' + hex);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Encode a public key point to a Buffer.
|
|
250
|
+
*
|
|
251
|
+
* Formats:
|
|
252
|
+
* 'uncompressed' (default) — 0x04 || x || y
|
|
253
|
+
* 'compressed' — 0x02 (even y) or 0x03 (odd y) || x
|
|
254
|
+
* 'hybrid' — 0x06 (even y) or 0x07 (odd y) || x || y
|
|
255
|
+
*/
|
|
256
|
+
function encodePublicKey(
|
|
257
|
+
point: ECPoint,
|
|
258
|
+
byteLength: number,
|
|
259
|
+
format: 'uncompressed' | 'compressed' | 'hybrid' = 'uncompressed',
|
|
260
|
+
): Buffer {
|
|
261
|
+
if (point === null) {
|
|
262
|
+
throw new Error('Cannot encode the point at infinity');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const xBuf = bigintToBuffer(point.x, byteLength);
|
|
266
|
+
|
|
267
|
+
if (format === 'compressed') {
|
|
268
|
+
const prefix = point.y % 2n === 0n ? 0x02 : 0x03;
|
|
269
|
+
return Buffer.concat([Buffer.from([prefix]), xBuf]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const yBuf = bigintToBuffer(point.y, byteLength);
|
|
273
|
+
|
|
274
|
+
if (format === 'hybrid') {
|
|
275
|
+
const prefix = point.y % 2n === 0n ? 0x06 : 0x07;
|
|
276
|
+
return Buffer.concat([Buffer.from([prefix]), xBuf, yBuf]);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// uncompressed
|
|
280
|
+
return Buffer.concat([Buffer.from([0x04]), xBuf, yBuf]);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Decode a public key from a Buffer to an ECPoint.
|
|
285
|
+
*
|
|
286
|
+
* Supports uncompressed (0x04), compressed (0x02/0x03), and hybrid (0x06/0x07).
|
|
287
|
+
*/
|
|
288
|
+
function decodePublicKey(buf: Buffer | Uint8Array, curve: CurveParams): ECPoint {
|
|
289
|
+
const data = Buffer.from(buf);
|
|
290
|
+
if (data.length === 0) {
|
|
291
|
+
throw new Error('Invalid public key: empty buffer');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const prefix = data[0];
|
|
295
|
+
const { p, a, b, byteLength } = curve;
|
|
296
|
+
|
|
297
|
+
if (prefix === 0x04 || prefix === 0x06 || prefix === 0x07) {
|
|
298
|
+
// Uncompressed or hybrid: prefix || x || y
|
|
299
|
+
if (data.length !== 1 + 2 * byteLength) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Invalid public key length: expected ${1 + 2 * byteLength} bytes, got ${data.length}`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const x = bufferToBigint(data.subarray(1, 1 + byteLength));
|
|
305
|
+
const y = bufferToBigint(data.subarray(1 + byteLength));
|
|
306
|
+
|
|
307
|
+
// Validate that the point is on the curve: y^2 ≡ x^3 + ax + b (mod p)
|
|
308
|
+
const lhs = mod(y * y, p);
|
|
309
|
+
const rhs = mod(x * x * x + a * x + b, p);
|
|
310
|
+
if (lhs !== rhs) {
|
|
311
|
+
throw new Error('Invalid public key: point is not on the curve');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return { x, y };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (prefix === 0x02 || prefix === 0x03) {
|
|
318
|
+
// Compressed: prefix || x
|
|
319
|
+
if (data.length !== 1 + byteLength) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`Invalid public key length: expected ${1 + byteLength} bytes, got ${data.length}`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
const x = bufferToBigint(data.subarray(1, 1 + byteLength));
|
|
325
|
+
|
|
326
|
+
// Recover y from x: y^2 = x^3 + ax + b (mod p)
|
|
327
|
+
const ySquared = mod(x * x * x + a * x + b, p);
|
|
328
|
+
const y = sqrtMod(ySquared, p);
|
|
329
|
+
if (y === null) {
|
|
330
|
+
throw new Error('Invalid public key: no valid y coordinate for the given x');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Choose the correct root based on the prefix parity
|
|
334
|
+
const isOdd = prefix === 0x03;
|
|
335
|
+
const finalY = (y % 2n !== 0n) === isOdd ? y : mod(p - y, p);
|
|
336
|
+
|
|
337
|
+
return { x, y: finalY };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
throw new Error(`Invalid public key prefix: 0x${prefix.toString(16).padStart(2, '0')}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Compute the modular square root of a modulo p (Tonelli-Shanks algorithm).
|
|
345
|
+
*
|
|
346
|
+
* For the special case p ≡ 3 (mod 4), uses the simpler formula: a^((p+1)/4) mod p.
|
|
347
|
+
* All four supported NIST curves have p ≡ 3 (mod 4), but we include
|
|
348
|
+
* Tonelli-Shanks for completeness.
|
|
349
|
+
*
|
|
350
|
+
* Returns null if a is not a quadratic residue mod p.
|
|
351
|
+
*/
|
|
352
|
+
function sqrtMod(a: bigint, p: bigint): bigint | null {
|
|
353
|
+
a = mod(a, p);
|
|
354
|
+
if (a === 0n) return 0n;
|
|
355
|
+
|
|
356
|
+
// Euler's criterion: a is a QR iff a^((p-1)/2) ≡ 1 (mod p)
|
|
357
|
+
if (modPow(a, (p - 1n) / 2n, p) !== 1n) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Special case: p ≡ 3 (mod 4) — applies to all our supported curves
|
|
362
|
+
if (mod(p, 4n) === 3n) {
|
|
363
|
+
return modPow(a, (p + 1n) / 4n, p);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// General case: Tonelli-Shanks
|
|
367
|
+
// Factor out powers of 2 from p - 1: p - 1 = Q * 2^S
|
|
368
|
+
let S = 0n;
|
|
369
|
+
let Q = p - 1n;
|
|
370
|
+
while (mod(Q, 2n) === 0n) {
|
|
371
|
+
Q /= 2n;
|
|
372
|
+
S++;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Find a quadratic non-residue z
|
|
376
|
+
let z = 2n;
|
|
377
|
+
while (modPow(z, (p - 1n) / 2n, p) !== p - 1n) {
|
|
378
|
+
z++;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let M = S;
|
|
382
|
+
let c = modPow(z, Q, p);
|
|
383
|
+
let t = modPow(a, Q, p);
|
|
384
|
+
let R = modPow(a, (Q + 1n) / 2n, p);
|
|
385
|
+
|
|
386
|
+
while (true) {
|
|
387
|
+
if (t === 0n) return 0n;
|
|
388
|
+
if (t === 1n) return R;
|
|
389
|
+
|
|
390
|
+
// Find the least i such that t^(2^i) ≡ 1 (mod p)
|
|
391
|
+
let i = 1n;
|
|
392
|
+
let temp = mod(t * t, p);
|
|
393
|
+
while (temp !== 1n) {
|
|
394
|
+
temp = mod(temp * temp, p);
|
|
395
|
+
i++;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Update
|
|
399
|
+
const b = modPow(c, modPow(2n, M - i - 1n, p - 1n), p);
|
|
400
|
+
M = i;
|
|
401
|
+
c = mod(b * b, p);
|
|
402
|
+
t = mod(t * c, p);
|
|
403
|
+
R = mod(R * b, p);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Coerce an input value to a Buffer. Accepts Buffer, Uint8Array, or a string
|
|
409
|
+
* with an optional encoding.
|
|
410
|
+
*/
|
|
411
|
+
function toBuffer(
|
|
412
|
+
value: string | Buffer | Uint8Array | ArrayBufferView,
|
|
413
|
+
encoding?: BufferEncoding,
|
|
414
|
+
): Buffer {
|
|
415
|
+
if (Buffer.isBuffer(value)) return value;
|
|
416
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
417
|
+
if (typeof value === 'string') {
|
|
418
|
+
return Buffer.from(value, encoding || 'utf8');
|
|
419
|
+
}
|
|
420
|
+
if (ArrayBuffer.isView(value)) {
|
|
421
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
422
|
+
}
|
|
423
|
+
throw new TypeError(
|
|
424
|
+
'The "key" argument must be of type string or an instance of Buffer, TypedArray, or DataView.',
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Optionally encode a Buffer to a string. If encoding is null/undefined,
|
|
430
|
+
* return the raw Buffer.
|
|
431
|
+
*/
|
|
432
|
+
function formatReturnValue(buf: Buffer, encoding?: BufferEncoding | null): Buffer | string {
|
|
433
|
+
if (encoding) {
|
|
434
|
+
return buf.toString(encoding);
|
|
435
|
+
}
|
|
436
|
+
return buf;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ---------------------------------------------------------------------------
|
|
440
|
+
// Resolve curve name through aliases
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
function resolveCurve(curveName: string): { canonical: string; params: CurveParams } {
|
|
444
|
+
const lower = curveName.toLowerCase();
|
|
445
|
+
const canonical = CURVE_ALIASES[lower];
|
|
446
|
+
if (!canonical) {
|
|
447
|
+
throw new Error(`Unsupported curve: ${curveName}`);
|
|
448
|
+
}
|
|
449
|
+
const params = CURVES[canonical];
|
|
450
|
+
if (!params) {
|
|
451
|
+
throw new Error(`Unsupported curve: ${curveName}`);
|
|
452
|
+
}
|
|
453
|
+
return { canonical, params };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ---------------------------------------------------------------------------
|
|
457
|
+
// ECDH class
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
|
|
460
|
+
class ECDH {
|
|
461
|
+
private _curve: CurveParams;
|
|
462
|
+
private _curveName: string;
|
|
463
|
+
private _privateKey: bigint | null = null;
|
|
464
|
+
private _publicKey: ECPoint = null;
|
|
465
|
+
|
|
466
|
+
constructor(curveName: string) {
|
|
467
|
+
const resolved = resolveCurve(curveName);
|
|
468
|
+
this._curve = resolved.params;
|
|
469
|
+
this._curveName = resolved.canonical;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Generate a random private key and derive the corresponding public key.
|
|
474
|
+
* Returns the public key.
|
|
475
|
+
*/
|
|
476
|
+
generateKeys(): Buffer;
|
|
477
|
+
generateKeys(encoding: BufferEncoding, format?: 'uncompressed' | 'compressed' | 'hybrid'): string;
|
|
478
|
+
generateKeys(
|
|
479
|
+
encoding?: BufferEncoding,
|
|
480
|
+
format?: 'uncompressed' | 'compressed' | 'hybrid',
|
|
481
|
+
): Buffer | string {
|
|
482
|
+
const { n, byteLength } = this._curve;
|
|
483
|
+
|
|
484
|
+
// Generate a random private key in [1, n-1]
|
|
485
|
+
let k: bigint;
|
|
486
|
+
do {
|
|
487
|
+
const bytes = randomBytes(byteLength);
|
|
488
|
+
k = bufferToBigint(bytes);
|
|
489
|
+
} while (k === 0n || k >= n);
|
|
490
|
+
|
|
491
|
+
this._privateKey = k;
|
|
492
|
+
|
|
493
|
+
// Compute the public key: Q = k * G
|
|
494
|
+
const G: ECPoint = { x: this._curve.Gx, y: this._curve.Gy };
|
|
495
|
+
this._publicKey = scalarMul(k, G, this._curve);
|
|
496
|
+
|
|
497
|
+
return this.getPublicKey(encoding as any, format);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Compute the shared secret using the other party's public key.
|
|
502
|
+
*/
|
|
503
|
+
computeSecret(
|
|
504
|
+
otherPublicKey: string | Buffer | Uint8Array | ArrayBufferView,
|
|
505
|
+
inputEncoding?: BufferEncoding,
|
|
506
|
+
outputEncoding?: BufferEncoding,
|
|
507
|
+
): Buffer | string {
|
|
508
|
+
if (this._privateKey === null) {
|
|
509
|
+
throw new Error('ECDH key not generated. Call generateKeys() or setPrivateKey() first.');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const pubBuf = toBuffer(otherPublicKey, inputEncoding);
|
|
513
|
+
const otherPoint = decodePublicKey(pubBuf, this._curve);
|
|
514
|
+
|
|
515
|
+
if (otherPoint === null) {
|
|
516
|
+
throw new Error('Invalid public key: point at infinity');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Shared secret = x-coordinate of (privateKey * otherPublicKey)
|
|
520
|
+
const sharedPoint = scalarMul(this._privateKey, otherPoint, this._curve);
|
|
521
|
+
if (sharedPoint === null) {
|
|
522
|
+
throw new Error('Shared secret computation resulted in the point at infinity');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const secret = bigintToBuffer(sharedPoint.x, this._curve.byteLength);
|
|
526
|
+
return formatReturnValue(secret, outputEncoding) as Buffer | string;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Return the public key. Optionally encode as a string.
|
|
531
|
+
*/
|
|
532
|
+
getPublicKey(): Buffer;
|
|
533
|
+
getPublicKey(encoding: BufferEncoding, format?: 'uncompressed' | 'compressed' | 'hybrid'): string;
|
|
534
|
+
getPublicKey(
|
|
535
|
+
encoding?: BufferEncoding | null,
|
|
536
|
+
format?: 'uncompressed' | 'compressed' | 'hybrid',
|
|
537
|
+
): Buffer | string {
|
|
538
|
+
if (this._publicKey === null) {
|
|
539
|
+
throw new Error('ECDH key not generated. Call generateKeys() or setPrivateKey() first.');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const buf = encodePublicKey(this._publicKey, this._curve.byteLength, format || 'uncompressed');
|
|
543
|
+
return formatReturnValue(buf, encoding) as Buffer | string;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Return the private key. Optionally encode as a string.
|
|
548
|
+
*/
|
|
549
|
+
getPrivateKey(): Buffer;
|
|
550
|
+
getPrivateKey(encoding: BufferEncoding): string;
|
|
551
|
+
getPrivateKey(encoding?: BufferEncoding): Buffer | string {
|
|
552
|
+
if (this._privateKey === null) {
|
|
553
|
+
throw new Error('ECDH key not generated. Call generateKeys() or setPrivateKey() first.');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const buf = bigintToBuffer(this._privateKey, this._curve.byteLength);
|
|
557
|
+
return formatReturnValue(buf, encoding) as Buffer | string;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Set the public key. The key can be in uncompressed, compressed, or hybrid format.
|
|
562
|
+
*/
|
|
563
|
+
setPublicKey(
|
|
564
|
+
key: string | Buffer | Uint8Array | ArrayBufferView,
|
|
565
|
+
encoding?: BufferEncoding,
|
|
566
|
+
): void {
|
|
567
|
+
const buf = toBuffer(key, encoding);
|
|
568
|
+
this._publicKey = decodePublicKey(buf, this._curve);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Set the private key and derive the corresponding public key.
|
|
573
|
+
*/
|
|
574
|
+
setPrivateKey(
|
|
575
|
+
key: string | Buffer | Uint8Array | ArrayBufferView,
|
|
576
|
+
encoding?: BufferEncoding,
|
|
577
|
+
): void {
|
|
578
|
+
const buf = toBuffer(key, encoding);
|
|
579
|
+
const k = bufferToBigint(buf);
|
|
580
|
+
|
|
581
|
+
if (k === 0n || k >= this._curve.n) {
|
|
582
|
+
throw new Error('Private key is out of range [1, n-1]');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
this._privateKey = k;
|
|
586
|
+
|
|
587
|
+
// Derive the public key from the private key
|
|
588
|
+
const G: ECPoint = { x: this._curve.Gx, y: this._curve.Gy };
|
|
589
|
+
this._publicKey = scalarMul(k, G, this._curve);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
// Public API
|
|
595
|
+
// ---------------------------------------------------------------------------
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Create an ECDH key exchange object for the specified curve.
|
|
599
|
+
*
|
|
600
|
+
* Supported curves: secp256k1, prime256v1 (P-256), secp384r1 (P-384), secp521r1 (P-521).
|
|
601
|
+
*/
|
|
602
|
+
// ---------------------------------------------------------------------------
|
|
603
|
+
// Exported EC math primitives (used by ECDSA)
|
|
604
|
+
// ---------------------------------------------------------------------------
|
|
605
|
+
|
|
606
|
+
export { mod, modInverse, scalarMul, pointAdd, CURVES, CURVE_ALIASES };
|
|
607
|
+
export type { ECPoint, CurveParams };
|
|
608
|
+
|
|
609
|
+
export function createECDH(curveName: string): ECDH {
|
|
610
|
+
return new ECDH(curveName);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Return a list of supported elliptic curve names.
|
|
615
|
+
*/
|
|
616
|
+
export function getCurves(): string[] {
|
|
617
|
+
return [
|
|
618
|
+
'secp256k1',
|
|
619
|
+
'prime256v1',
|
|
620
|
+
'secp256r1',
|
|
621
|
+
'secp384r1',
|
|
622
|
+
'secp521r1',
|
|
623
|
+
];
|
|
624
|
+
}
|