@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/lib/esm/ecdh.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { randomBytes } from "./random.js";
|
|
3
|
+
import { modPow } from "./bigint-math.js";
|
|
4
|
+
const CURVES = {
|
|
5
|
+
secp256k1: {
|
|
6
|
+
p: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn,
|
|
7
|
+
a: 0n,
|
|
8
|
+
b: 7n,
|
|
9
|
+
Gx: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n,
|
|
10
|
+
Gy: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n,
|
|
11
|
+
n: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n,
|
|
12
|
+
byteLength: 32
|
|
13
|
+
},
|
|
14
|
+
prime256v1: {
|
|
15
|
+
p: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFn,
|
|
16
|
+
a: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFn - 3n,
|
|
17
|
+
b: 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604Bn,
|
|
18
|
+
Gx: 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296n,
|
|
19
|
+
Gy: 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5n,
|
|
20
|
+
n: 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551n,
|
|
21
|
+
byteLength: 32
|
|
22
|
+
},
|
|
23
|
+
secp384r1: {
|
|
24
|
+
p: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFFn,
|
|
25
|
+
a: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFFn - 3n,
|
|
26
|
+
b: 0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEFn,
|
|
27
|
+
Gx: 0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7n,
|
|
28
|
+
Gy: 0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5Fn,
|
|
29
|
+
n: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973n,
|
|
30
|
+
byteLength: 48
|
|
31
|
+
},
|
|
32
|
+
secp521r1: {
|
|
33
|
+
p: 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn,
|
|
34
|
+
a: 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn - 3n,
|
|
35
|
+
b: 0x0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00n,
|
|
36
|
+
Gx: 0x00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66n,
|
|
37
|
+
Gy: 0x011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650n,
|
|
38
|
+
n: 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409n,
|
|
39
|
+
byteLength: 66
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const CURVE_ALIASES = {
|
|
43
|
+
"secp256k1": "secp256k1",
|
|
44
|
+
"prime256v1": "prime256v1",
|
|
45
|
+
"secp256r1": "prime256v1",
|
|
46
|
+
"p-256": "prime256v1",
|
|
47
|
+
"p256": "prime256v1",
|
|
48
|
+
"secp384r1": "secp384r1",
|
|
49
|
+
"p-384": "secp384r1",
|
|
50
|
+
"p384": "secp384r1",
|
|
51
|
+
"secp521r1": "secp521r1",
|
|
52
|
+
"p-521": "secp521r1",
|
|
53
|
+
"p521": "secp521r1"
|
|
54
|
+
};
|
|
55
|
+
function mod(a, m) {
|
|
56
|
+
const r = a % m;
|
|
57
|
+
return r < 0n ? r + m : r;
|
|
58
|
+
}
|
|
59
|
+
function modInverse(a, m) {
|
|
60
|
+
a = mod(a, m);
|
|
61
|
+
if (a === 0n) {
|
|
62
|
+
throw new Error("No modular inverse for zero");
|
|
63
|
+
}
|
|
64
|
+
let [old_r, r] = [a, m];
|
|
65
|
+
let [old_s, s] = [1n, 0n];
|
|
66
|
+
while (r !== 0n) {
|
|
67
|
+
const q = old_r / r;
|
|
68
|
+
[old_r, r] = [r, old_r - q * r];
|
|
69
|
+
[old_s, s] = [s, old_s - q * s];
|
|
70
|
+
}
|
|
71
|
+
if (old_r !== 1n) {
|
|
72
|
+
throw new Error("Modular inverse does not exist");
|
|
73
|
+
}
|
|
74
|
+
return mod(old_s, m);
|
|
75
|
+
}
|
|
76
|
+
function pointAdd(P, Q, curve) {
|
|
77
|
+
if (P === null) return Q;
|
|
78
|
+
if (Q === null) return P;
|
|
79
|
+
const { p } = curve;
|
|
80
|
+
if (P.x === Q.x) {
|
|
81
|
+
if (mod(P.y + Q.y, p) === 0n) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return pointDouble(P, curve);
|
|
85
|
+
}
|
|
86
|
+
const dx = mod(Q.x - P.x, p);
|
|
87
|
+
const dy = mod(Q.y - P.y, p);
|
|
88
|
+
const lambda = mod(dy * modInverse(dx, p), p);
|
|
89
|
+
const x3 = mod(lambda * lambda - P.x - Q.x, p);
|
|
90
|
+
const y3 = mod(lambda * (P.x - x3) - P.y, p);
|
|
91
|
+
return { x: x3, y: y3 };
|
|
92
|
+
}
|
|
93
|
+
function pointDouble(P, curve) {
|
|
94
|
+
if (P === null) return null;
|
|
95
|
+
const { a, p } = curve;
|
|
96
|
+
if (P.y === 0n) return null;
|
|
97
|
+
const num = mod(3n * P.x * P.x + a, p);
|
|
98
|
+
const den = mod(2n * P.y, p);
|
|
99
|
+
const lambda = mod(num * modInverse(den, p), p);
|
|
100
|
+
const x3 = mod(lambda * lambda - 2n * P.x, p);
|
|
101
|
+
const y3 = mod(lambda * (P.x - x3) - P.y, p);
|
|
102
|
+
return { x: x3, y: y3 };
|
|
103
|
+
}
|
|
104
|
+
function scalarMul(k, P, curve) {
|
|
105
|
+
if (P === null) return null;
|
|
106
|
+
if (k === 0n) return null;
|
|
107
|
+
const { n } = curve;
|
|
108
|
+
k = mod(k, n);
|
|
109
|
+
if (k === 0n) return null;
|
|
110
|
+
let R0 = null;
|
|
111
|
+
let R1 = P;
|
|
112
|
+
const bits = k.toString(2);
|
|
113
|
+
for (let i = 0; i < bits.length; i++) {
|
|
114
|
+
if (bits[i] === "1") {
|
|
115
|
+
R0 = pointAdd(R0, R1, curve);
|
|
116
|
+
R1 = pointDouble(R1, curve);
|
|
117
|
+
} else {
|
|
118
|
+
R1 = pointAdd(R0, R1, curve);
|
|
119
|
+
R0 = pointDouble(R0, curve);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return R0;
|
|
123
|
+
}
|
|
124
|
+
function bigintToBuffer(n, len) {
|
|
125
|
+
const hex = n.toString(16).padStart(len * 2, "0");
|
|
126
|
+
return Buffer.from(hex, "hex");
|
|
127
|
+
}
|
|
128
|
+
function bufferToBigint(buf) {
|
|
129
|
+
const hex = Buffer.from(buf).toString("hex");
|
|
130
|
+
if (hex.length === 0) return 0n;
|
|
131
|
+
return BigInt("0x" + hex);
|
|
132
|
+
}
|
|
133
|
+
function encodePublicKey(point, byteLength, format = "uncompressed") {
|
|
134
|
+
if (point === null) {
|
|
135
|
+
throw new Error("Cannot encode the point at infinity");
|
|
136
|
+
}
|
|
137
|
+
const xBuf = bigintToBuffer(point.x, byteLength);
|
|
138
|
+
if (format === "compressed") {
|
|
139
|
+
const prefix = point.y % 2n === 0n ? 2 : 3;
|
|
140
|
+
return Buffer.concat([Buffer.from([prefix]), xBuf]);
|
|
141
|
+
}
|
|
142
|
+
const yBuf = bigintToBuffer(point.y, byteLength);
|
|
143
|
+
if (format === "hybrid") {
|
|
144
|
+
const prefix = point.y % 2n === 0n ? 6 : 7;
|
|
145
|
+
return Buffer.concat([Buffer.from([prefix]), xBuf, yBuf]);
|
|
146
|
+
}
|
|
147
|
+
return Buffer.concat([Buffer.from([4]), xBuf, yBuf]);
|
|
148
|
+
}
|
|
149
|
+
function decodePublicKey(buf, curve) {
|
|
150
|
+
const data = Buffer.from(buf);
|
|
151
|
+
if (data.length === 0) {
|
|
152
|
+
throw new Error("Invalid public key: empty buffer");
|
|
153
|
+
}
|
|
154
|
+
const prefix = data[0];
|
|
155
|
+
const { p, a, b, byteLength } = curve;
|
|
156
|
+
if (prefix === 4 || prefix === 6 || prefix === 7) {
|
|
157
|
+
if (data.length !== 1 + 2 * byteLength) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Invalid public key length: expected ${1 + 2 * byteLength} bytes, got ${data.length}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const x = bufferToBigint(data.subarray(1, 1 + byteLength));
|
|
163
|
+
const y = bufferToBigint(data.subarray(1 + byteLength));
|
|
164
|
+
const lhs = mod(y * y, p);
|
|
165
|
+
const rhs = mod(x * x * x + a * x + b, p);
|
|
166
|
+
if (lhs !== rhs) {
|
|
167
|
+
throw new Error("Invalid public key: point is not on the curve");
|
|
168
|
+
}
|
|
169
|
+
return { x, y };
|
|
170
|
+
}
|
|
171
|
+
if (prefix === 2 || prefix === 3) {
|
|
172
|
+
if (data.length !== 1 + byteLength) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Invalid public key length: expected ${1 + byteLength} bytes, got ${data.length}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const x = bufferToBigint(data.subarray(1, 1 + byteLength));
|
|
178
|
+
const ySquared = mod(x * x * x + a * x + b, p);
|
|
179
|
+
const y = sqrtMod(ySquared, p);
|
|
180
|
+
if (y === null) {
|
|
181
|
+
throw new Error("Invalid public key: no valid y coordinate for the given x");
|
|
182
|
+
}
|
|
183
|
+
const isOdd = prefix === 3;
|
|
184
|
+
const finalY = y % 2n !== 0n === isOdd ? y : mod(p - y, p);
|
|
185
|
+
return { x, y: finalY };
|
|
186
|
+
}
|
|
187
|
+
throw new Error(`Invalid public key prefix: 0x${prefix.toString(16).padStart(2, "0")}`);
|
|
188
|
+
}
|
|
189
|
+
function sqrtMod(a, p) {
|
|
190
|
+
a = mod(a, p);
|
|
191
|
+
if (a === 0n) return 0n;
|
|
192
|
+
if (modPow(a, (p - 1n) / 2n, p) !== 1n) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
if (mod(p, 4n) === 3n) {
|
|
196
|
+
return modPow(a, (p + 1n) / 4n, p);
|
|
197
|
+
}
|
|
198
|
+
let S = 0n;
|
|
199
|
+
let Q = p - 1n;
|
|
200
|
+
while (mod(Q, 2n) === 0n) {
|
|
201
|
+
Q /= 2n;
|
|
202
|
+
S++;
|
|
203
|
+
}
|
|
204
|
+
let z = 2n;
|
|
205
|
+
while (modPow(z, (p - 1n) / 2n, p) !== p - 1n) {
|
|
206
|
+
z++;
|
|
207
|
+
}
|
|
208
|
+
let M = S;
|
|
209
|
+
let c = modPow(z, Q, p);
|
|
210
|
+
let t = modPow(a, Q, p);
|
|
211
|
+
let R = modPow(a, (Q + 1n) / 2n, p);
|
|
212
|
+
while (true) {
|
|
213
|
+
if (t === 0n) return 0n;
|
|
214
|
+
if (t === 1n) return R;
|
|
215
|
+
let i = 1n;
|
|
216
|
+
let temp = mod(t * t, p);
|
|
217
|
+
while (temp !== 1n) {
|
|
218
|
+
temp = mod(temp * temp, p);
|
|
219
|
+
i++;
|
|
220
|
+
}
|
|
221
|
+
const b = modPow(c, modPow(2n, M - i - 1n, p - 1n), p);
|
|
222
|
+
M = i;
|
|
223
|
+
c = mod(b * b, p);
|
|
224
|
+
t = mod(t * c, p);
|
|
225
|
+
R = mod(R * b, p);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function toBuffer(value, encoding) {
|
|
229
|
+
if (Buffer.isBuffer(value)) return value;
|
|
230
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
231
|
+
if (typeof value === "string") {
|
|
232
|
+
return Buffer.from(value, encoding || "utf8");
|
|
233
|
+
}
|
|
234
|
+
if (ArrayBuffer.isView(value)) {
|
|
235
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
236
|
+
}
|
|
237
|
+
throw new TypeError(
|
|
238
|
+
'The "key" argument must be of type string or an instance of Buffer, TypedArray, or DataView.'
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
function formatReturnValue(buf, encoding) {
|
|
242
|
+
if (encoding) {
|
|
243
|
+
return buf.toString(encoding);
|
|
244
|
+
}
|
|
245
|
+
return buf;
|
|
246
|
+
}
|
|
247
|
+
function resolveCurve(curveName) {
|
|
248
|
+
const lower = curveName.toLowerCase();
|
|
249
|
+
const canonical = CURVE_ALIASES[lower];
|
|
250
|
+
if (!canonical) {
|
|
251
|
+
throw new Error(`Unsupported curve: ${curveName}`);
|
|
252
|
+
}
|
|
253
|
+
const params = CURVES[canonical];
|
|
254
|
+
if (!params) {
|
|
255
|
+
throw new Error(`Unsupported curve: ${curveName}`);
|
|
256
|
+
}
|
|
257
|
+
return { canonical, params };
|
|
258
|
+
}
|
|
259
|
+
class ECDH {
|
|
260
|
+
_curve;
|
|
261
|
+
_curveName;
|
|
262
|
+
_privateKey = null;
|
|
263
|
+
_publicKey = null;
|
|
264
|
+
constructor(curveName) {
|
|
265
|
+
const resolved = resolveCurve(curveName);
|
|
266
|
+
this._curve = resolved.params;
|
|
267
|
+
this._curveName = resolved.canonical;
|
|
268
|
+
}
|
|
269
|
+
generateKeys(encoding, format) {
|
|
270
|
+
const { n, byteLength } = this._curve;
|
|
271
|
+
let k;
|
|
272
|
+
do {
|
|
273
|
+
const bytes = randomBytes(byteLength);
|
|
274
|
+
k = bufferToBigint(bytes);
|
|
275
|
+
} while (k === 0n || k >= n);
|
|
276
|
+
this._privateKey = k;
|
|
277
|
+
const G = { x: this._curve.Gx, y: this._curve.Gy };
|
|
278
|
+
this._publicKey = scalarMul(k, G, this._curve);
|
|
279
|
+
return this.getPublicKey(encoding, format);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Compute the shared secret using the other party's public key.
|
|
283
|
+
*/
|
|
284
|
+
computeSecret(otherPublicKey, inputEncoding, outputEncoding) {
|
|
285
|
+
if (this._privateKey === null) {
|
|
286
|
+
throw new Error("ECDH key not generated. Call generateKeys() or setPrivateKey() first.");
|
|
287
|
+
}
|
|
288
|
+
const pubBuf = toBuffer(otherPublicKey, inputEncoding);
|
|
289
|
+
const otherPoint = decodePublicKey(pubBuf, this._curve);
|
|
290
|
+
if (otherPoint === null) {
|
|
291
|
+
throw new Error("Invalid public key: point at infinity");
|
|
292
|
+
}
|
|
293
|
+
const sharedPoint = scalarMul(this._privateKey, otherPoint, this._curve);
|
|
294
|
+
if (sharedPoint === null) {
|
|
295
|
+
throw new Error("Shared secret computation resulted in the point at infinity");
|
|
296
|
+
}
|
|
297
|
+
const secret = bigintToBuffer(sharedPoint.x, this._curve.byteLength);
|
|
298
|
+
return formatReturnValue(secret, outputEncoding);
|
|
299
|
+
}
|
|
300
|
+
getPublicKey(encoding, format) {
|
|
301
|
+
if (this._publicKey === null) {
|
|
302
|
+
throw new Error("ECDH key not generated. Call generateKeys() or setPrivateKey() first.");
|
|
303
|
+
}
|
|
304
|
+
const buf = encodePublicKey(this._publicKey, this._curve.byteLength, format || "uncompressed");
|
|
305
|
+
return formatReturnValue(buf, encoding);
|
|
306
|
+
}
|
|
307
|
+
getPrivateKey(encoding) {
|
|
308
|
+
if (this._privateKey === null) {
|
|
309
|
+
throw new Error("ECDH key not generated. Call generateKeys() or setPrivateKey() first.");
|
|
310
|
+
}
|
|
311
|
+
const buf = bigintToBuffer(this._privateKey, this._curve.byteLength);
|
|
312
|
+
return formatReturnValue(buf, encoding);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Set the public key. The key can be in uncompressed, compressed, or hybrid format.
|
|
316
|
+
*/
|
|
317
|
+
setPublicKey(key, encoding) {
|
|
318
|
+
const buf = toBuffer(key, encoding);
|
|
319
|
+
this._publicKey = decodePublicKey(buf, this._curve);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Set the private key and derive the corresponding public key.
|
|
323
|
+
*/
|
|
324
|
+
setPrivateKey(key, encoding) {
|
|
325
|
+
const buf = toBuffer(key, encoding);
|
|
326
|
+
const k = bufferToBigint(buf);
|
|
327
|
+
if (k === 0n || k >= this._curve.n) {
|
|
328
|
+
throw new Error("Private key is out of range [1, n-1]");
|
|
329
|
+
}
|
|
330
|
+
this._privateKey = k;
|
|
331
|
+
const G = { x: this._curve.Gx, y: this._curve.Gy };
|
|
332
|
+
this._publicKey = scalarMul(k, G, this._curve);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function createECDH(curveName) {
|
|
336
|
+
return new ECDH(curveName);
|
|
337
|
+
}
|
|
338
|
+
function getCurves() {
|
|
339
|
+
return [
|
|
340
|
+
"secp256k1",
|
|
341
|
+
"prime256v1",
|
|
342
|
+
"secp256r1",
|
|
343
|
+
"secp384r1",
|
|
344
|
+
"secp521r1"
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
export {
|
|
348
|
+
CURVES,
|
|
349
|
+
CURVE_ALIASES,
|
|
350
|
+
createECDH,
|
|
351
|
+
getCurves,
|
|
352
|
+
mod,
|
|
353
|
+
modInverse,
|
|
354
|
+
pointAdd,
|
|
355
|
+
scalarMul
|
|
356
|
+
};
|
package/lib/esm/ecdsa.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Hash } from "./hash.js";
|
|
2
|
+
import { Hmac } from "./hmac.js";
|
|
3
|
+
import {
|
|
4
|
+
mod,
|
|
5
|
+
modInverse,
|
|
6
|
+
scalarMul,
|
|
7
|
+
pointAdd,
|
|
8
|
+
CURVES,
|
|
9
|
+
CURVE_ALIASES
|
|
10
|
+
} from "./ecdh.js";
|
|
11
|
+
import { bigIntToBytes as bigintToBytes, bytesToBigInt as bytesToBigint } from "./bigint-math.js";
|
|
12
|
+
function getCurve(curveName) {
|
|
13
|
+
const alias = CURVE_ALIASES[curveName.toLowerCase()];
|
|
14
|
+
if (!alias) throw new Error(`Unsupported curve: ${curveName}`);
|
|
15
|
+
return CURVES[alias];
|
|
16
|
+
}
|
|
17
|
+
function truncateHash(hash, curve) {
|
|
18
|
+
const orderBits = curve.n.toString(2).length;
|
|
19
|
+
let e = bytesToBigint(hash);
|
|
20
|
+
const hashBits = hash.length * 8;
|
|
21
|
+
if (hashBits > orderBits) {
|
|
22
|
+
e >>= BigInt(hashBits - orderBits);
|
|
23
|
+
}
|
|
24
|
+
return e;
|
|
25
|
+
}
|
|
26
|
+
function hmacDigest(algo, key, data) {
|
|
27
|
+
const hmac = new Hmac(algo, key);
|
|
28
|
+
hmac.update(data);
|
|
29
|
+
return new Uint8Array(hmac.digest());
|
|
30
|
+
}
|
|
31
|
+
function rfc6979(hashAlgo, privKey, msgHash, curve) {
|
|
32
|
+
const qLen = curve.byteLength;
|
|
33
|
+
const hLen = msgHash.length;
|
|
34
|
+
let V = new Uint8Array(hLen).fill(1);
|
|
35
|
+
let K = new Uint8Array(hLen).fill(0);
|
|
36
|
+
const x = bigintToBytes(privKey, qLen);
|
|
37
|
+
const z = bigintToBytes(mod(truncateHash(msgHash, curve), curve.n), qLen);
|
|
38
|
+
const concat0 = new Uint8Array(hLen + 1 + qLen + qLen);
|
|
39
|
+
concat0.set(V, 0);
|
|
40
|
+
concat0[hLen] = 0;
|
|
41
|
+
concat0.set(x, hLen + 1);
|
|
42
|
+
concat0.set(z, hLen + 1 + qLen);
|
|
43
|
+
K = hmacDigest(hashAlgo, K, concat0);
|
|
44
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
45
|
+
const concat1 = new Uint8Array(hLen + 1 + qLen + qLen);
|
|
46
|
+
concat1.set(V, 0);
|
|
47
|
+
concat1[hLen] = 1;
|
|
48
|
+
concat1.set(x, hLen + 1);
|
|
49
|
+
concat1.set(z, hLen + 1 + qLen);
|
|
50
|
+
K = hmacDigest(hashAlgo, K, concat1);
|
|
51
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
52
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
53
|
+
let T = new Uint8Array(0);
|
|
54
|
+
while (T.length < qLen) {
|
|
55
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
56
|
+
const newT = new Uint8Array(T.length + V.length);
|
|
57
|
+
newT.set(T, 0);
|
|
58
|
+
newT.set(V, T.length);
|
|
59
|
+
T = newT;
|
|
60
|
+
}
|
|
61
|
+
const k = truncateHash(T.slice(0, qLen), curve);
|
|
62
|
+
if (k >= 1n && k < curve.n) {
|
|
63
|
+
return k;
|
|
64
|
+
}
|
|
65
|
+
const retryConcat = new Uint8Array(hLen + 1);
|
|
66
|
+
retryConcat.set(V, 0);
|
|
67
|
+
retryConcat[hLen] = 0;
|
|
68
|
+
K = hmacDigest(hashAlgo, K, retryConcat);
|
|
69
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
70
|
+
}
|
|
71
|
+
throw new Error("RFC 6979: failed to generate valid k after 100 attempts");
|
|
72
|
+
}
|
|
73
|
+
function ecdsaSign(hashAlgo, privKeyBytes, data, curveName) {
|
|
74
|
+
const curve = getCurve(curveName);
|
|
75
|
+
const G = { x: curve.Gx, y: curve.Gy };
|
|
76
|
+
const d = bytesToBigint(privKeyBytes);
|
|
77
|
+
const hash = new Hash(hashAlgo);
|
|
78
|
+
hash.update(data);
|
|
79
|
+
const msgHash = new Uint8Array(hash.digest());
|
|
80
|
+
const e = truncateHash(msgHash, curve);
|
|
81
|
+
const k = rfc6979(hashAlgo, d, msgHash, curve);
|
|
82
|
+
const R = scalarMul(k, G, curve);
|
|
83
|
+
if (R === null) throw new Error("ECDSA: k * G is point at infinity");
|
|
84
|
+
const r = mod(R.x, curve.n);
|
|
85
|
+
if (r === 0n) throw new Error("ECDSA: r is zero");
|
|
86
|
+
const kInv = modInverse(k, curve.n);
|
|
87
|
+
const s = mod(kInv * (e + r * d), curve.n);
|
|
88
|
+
if (s === 0n) throw new Error("ECDSA: s is zero");
|
|
89
|
+
const sigLen = curve.byteLength;
|
|
90
|
+
const sig = new Uint8Array(sigLen * 2);
|
|
91
|
+
sig.set(bigintToBytes(r, sigLen), 0);
|
|
92
|
+
sig.set(bigintToBytes(s, sigLen), sigLen);
|
|
93
|
+
return sig;
|
|
94
|
+
}
|
|
95
|
+
function ecdsaVerify(hashAlgo, pubKeyBytes, signature, data, curveName) {
|
|
96
|
+
const curve = getCurve(curveName);
|
|
97
|
+
const G = { x: curve.Gx, y: curve.Gy };
|
|
98
|
+
const sigLen = curve.byteLength;
|
|
99
|
+
if (pubKeyBytes[0] !== 4 || pubKeyBytes.length !== 1 + sigLen * 2) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const Qx = bytesToBigint(pubKeyBytes.slice(1, 1 + sigLen));
|
|
103
|
+
const Qy = bytesToBigint(pubKeyBytes.slice(1 + sigLen));
|
|
104
|
+
const Q = { x: Qx, y: Qy };
|
|
105
|
+
if (signature.length !== sigLen * 2) return false;
|
|
106
|
+
const r = bytesToBigint(signature.slice(0, sigLen));
|
|
107
|
+
const s = bytesToBigint(signature.slice(sigLen));
|
|
108
|
+
if (r < 1n || r >= curve.n || s < 1n || s >= curve.n) return false;
|
|
109
|
+
const hash = new Hash(hashAlgo);
|
|
110
|
+
hash.update(data);
|
|
111
|
+
const msgHash = new Uint8Array(hash.digest());
|
|
112
|
+
const e = truncateHash(msgHash, curve);
|
|
113
|
+
const w = modInverse(s, curve.n);
|
|
114
|
+
const u1 = mod(e * w, curve.n);
|
|
115
|
+
const u2 = mod(r * w, curve.n);
|
|
116
|
+
const R1 = scalarMul(u1, G, curve);
|
|
117
|
+
const R2 = scalarMul(u2, Q, curve);
|
|
118
|
+
const R = pointAdd(R1, R2, curve);
|
|
119
|
+
if (R === null) return false;
|
|
120
|
+
return mod(R.x, curve.n) === r;
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
ecdsaSign,
|
|
124
|
+
ecdsaVerify
|
|
125
|
+
};
|
package/lib/esm/hash.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import GLib from "@girs/glib-2.0";
|
|
2
|
+
import { Transform } from "node:stream";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
import { normalizeEncoding } from "@gjsify/utils";
|
|
5
|
+
import { normalizeAlgorithm } from "./crypto-utils.js";
|
|
6
|
+
const CHECKSUM_TYPES = {
|
|
7
|
+
md5: GLib.ChecksumType.MD5,
|
|
8
|
+
sha1: GLib.ChecksumType.SHA1,
|
|
9
|
+
sha256: GLib.ChecksumType.SHA256,
|
|
10
|
+
sha384: GLib.ChecksumType.SHA384,
|
|
11
|
+
sha512: GLib.ChecksumType.SHA512
|
|
12
|
+
};
|
|
13
|
+
function getChecksumType(algorithm) {
|
|
14
|
+
const normalized = normalizeAlgorithm(algorithm);
|
|
15
|
+
const type = CHECKSUM_TYPES[normalized];
|
|
16
|
+
if (type === void 0) {
|
|
17
|
+
const err = new Error(`Unknown message digest: ${algorithm}`);
|
|
18
|
+
err.code = "ERR_CRYPTO_HASH_UNKNOWN";
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
return type;
|
|
22
|
+
}
|
|
23
|
+
class Hash extends Transform {
|
|
24
|
+
_algorithm;
|
|
25
|
+
_checksum;
|
|
26
|
+
_finalized = false;
|
|
27
|
+
constructor(algorithm) {
|
|
28
|
+
super();
|
|
29
|
+
const normalized = normalizeAlgorithm(algorithm);
|
|
30
|
+
const type = getChecksumType(normalized);
|
|
31
|
+
this._algorithm = normalized;
|
|
32
|
+
this._checksum = new GLib.Checksum(type);
|
|
33
|
+
}
|
|
34
|
+
/** Update the hash with data. */
|
|
35
|
+
update(data, inputEncoding) {
|
|
36
|
+
if (this._finalized) {
|
|
37
|
+
throw new Error("Digest already called");
|
|
38
|
+
}
|
|
39
|
+
let bytes;
|
|
40
|
+
if (typeof data === "string") {
|
|
41
|
+
const enc = normalizeEncoding(inputEncoding);
|
|
42
|
+
bytes = Buffer.from(data, enc);
|
|
43
|
+
} else {
|
|
44
|
+
bytes = data instanceof Uint8Array ? data : Buffer.from(data);
|
|
45
|
+
}
|
|
46
|
+
this._checksum.update(bytes);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
/** Calculate the digest of all data passed to update(). */
|
|
50
|
+
digest(encoding) {
|
|
51
|
+
if (this._finalized) {
|
|
52
|
+
throw new Error("Digest already called");
|
|
53
|
+
}
|
|
54
|
+
this._finalized = true;
|
|
55
|
+
const hexStr = this._checksum.get_string();
|
|
56
|
+
const buf = Buffer.from(hexStr, "hex");
|
|
57
|
+
if (encoding) return buf.toString(encoding);
|
|
58
|
+
return buf;
|
|
59
|
+
}
|
|
60
|
+
/** Copy this hash to a new Hash object. */
|
|
61
|
+
copy() {
|
|
62
|
+
if (this._finalized) {
|
|
63
|
+
throw new Error("Digest already called");
|
|
64
|
+
}
|
|
65
|
+
const copy = Object.create(Hash.prototype);
|
|
66
|
+
Transform.call(copy);
|
|
67
|
+
copy._algorithm = this._algorithm;
|
|
68
|
+
copy._finalized = false;
|
|
69
|
+
copy._checksum = this._checksum.copy();
|
|
70
|
+
return copy;
|
|
71
|
+
}
|
|
72
|
+
// Transform stream interface
|
|
73
|
+
_transform(chunk, encoding, callback) {
|
|
74
|
+
try {
|
|
75
|
+
this.update(chunk, encoding);
|
|
76
|
+
callback();
|
|
77
|
+
} catch (err) {
|
|
78
|
+
callback(err);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
_flush(callback) {
|
|
82
|
+
try {
|
|
83
|
+
this.push(this.digest());
|
|
84
|
+
callback();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
callback(err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function getHashes() {
|
|
91
|
+
return ["md5", "sha1", "sha256", "sha384", "sha512"];
|
|
92
|
+
}
|
|
93
|
+
function hash(algorithm, data, encoding) {
|
|
94
|
+
return new Hash(algorithm).update(data).digest(encoding);
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
Hash,
|
|
98
|
+
getHashes,
|
|
99
|
+
hash
|
|
100
|
+
};
|
package/lib/esm/hkdf.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { Hmac } from "./hmac.js";
|
|
3
|
+
import { normalizeAlgorithm, DIGEST_SIZES, SUPPORTED_ALGORITHMS, toBuffer } from "./crypto-utils.js";
|
|
4
|
+
function hmacDigest(algo, key, data) {
|
|
5
|
+
const hmac = new Hmac(algo, key);
|
|
6
|
+
hmac.update(data);
|
|
7
|
+
return hmac.digest();
|
|
8
|
+
}
|
|
9
|
+
function hkdfExtract(algo, ikm, salt) {
|
|
10
|
+
return hmacDigest(algo, salt, ikm);
|
|
11
|
+
}
|
|
12
|
+
function hkdfExpand(algo, prk, info, length, hashLen) {
|
|
13
|
+
const n = Math.ceil(length / hashLen);
|
|
14
|
+
if (n > 255) {
|
|
15
|
+
throw new Error("HKDF cannot generate more than 255 * HashLen bytes");
|
|
16
|
+
}
|
|
17
|
+
const okm = Buffer.allocUnsafe(n * hashLen);
|
|
18
|
+
let prev = Buffer.alloc(0);
|
|
19
|
+
for (let i = 1; i <= n; i++) {
|
|
20
|
+
const input = Buffer.concat([prev, info, Buffer.from([i])]);
|
|
21
|
+
prev = hmacDigest(algo, prk, input);
|
|
22
|
+
prev.copy(okm, (i - 1) * hashLen);
|
|
23
|
+
}
|
|
24
|
+
return Buffer.from(okm.buffer, okm.byteOffset, length);
|
|
25
|
+
}
|
|
26
|
+
function hkdfSync(digest, ikm, salt, info, keylen) {
|
|
27
|
+
const algo = normalizeAlgorithm(digest);
|
|
28
|
+
const hashLen = DIGEST_SIZES[algo];
|
|
29
|
+
if (!SUPPORTED_ALGORITHMS.has(algo) || hashLen === void 0) {
|
|
30
|
+
throw new TypeError(`Unknown message digest: ${digest}`);
|
|
31
|
+
}
|
|
32
|
+
if (typeof keylen !== "number" || keylen < 0) {
|
|
33
|
+
throw new TypeError("keylen must be a non-negative number");
|
|
34
|
+
}
|
|
35
|
+
const ikmBuf = toBuffer(ikm);
|
|
36
|
+
const saltBuf = toBuffer(salt);
|
|
37
|
+
const infoBuf = toBuffer(info);
|
|
38
|
+
const effectiveSalt = saltBuf.length === 0 ? Buffer.alloc(hashLen, 0) : saltBuf;
|
|
39
|
+
const prk = hkdfExtract(algo, ikmBuf, effectiveSalt);
|
|
40
|
+
const okm = hkdfExpand(algo, prk, infoBuf, keylen, hashLen);
|
|
41
|
+
const result = new ArrayBuffer(okm.length);
|
|
42
|
+
new Uint8Array(result).set(okm);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
function hkdf(digest, ikm, salt, info, keylen, callback) {
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
try {
|
|
48
|
+
const result = hkdfSync(digest, ikm, salt, info, keylen);
|
|
49
|
+
callback(null, result);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
52
|
+
}
|
|
53
|
+
}, 0);
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
hkdf,
|
|
57
|
+
hkdfSync
|
|
58
|
+
};
|