@noble/curves 2.0.1 → 2.2.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 +214 -122
- package/abstract/bls.d.ts +299 -16
- package/abstract/bls.d.ts.map +1 -1
- package/abstract/bls.js +82 -22
- package/abstract/bls.js.map +1 -1
- package/abstract/curve.d.ts +274 -27
- package/abstract/curve.d.ts.map +1 -1
- package/abstract/curve.js +177 -23
- package/abstract/curve.js.map +1 -1
- package/abstract/edwards.d.ts +166 -30
- package/abstract/edwards.d.ts.map +1 -1
- package/abstract/edwards.js +221 -86
- package/abstract/edwards.js.map +1 -1
- package/abstract/fft.d.ts +322 -10
- package/abstract/fft.d.ts.map +1 -1
- package/abstract/fft.js +154 -12
- package/abstract/fft.js.map +1 -1
- package/abstract/frost.d.ts +293 -0
- package/abstract/frost.d.ts.map +1 -0
- package/abstract/frost.js +704 -0
- package/abstract/frost.js.map +1 -0
- package/abstract/hash-to-curve.d.ts +173 -24
- package/abstract/hash-to-curve.d.ts.map +1 -1
- package/abstract/hash-to-curve.js +170 -31
- package/abstract/hash-to-curve.js.map +1 -1
- package/abstract/modular.d.ts +429 -37
- package/abstract/modular.d.ts.map +1 -1
- package/abstract/modular.js +414 -119
- package/abstract/modular.js.map +1 -1
- package/abstract/montgomery.d.ts +83 -12
- package/abstract/montgomery.d.ts.map +1 -1
- package/abstract/montgomery.js +32 -7
- package/abstract/montgomery.js.map +1 -1
- package/abstract/oprf.d.ts +164 -91
- package/abstract/oprf.d.ts.map +1 -1
- package/abstract/oprf.js +88 -29
- package/abstract/oprf.js.map +1 -1
- package/abstract/poseidon.d.ts +138 -7
- package/abstract/poseidon.d.ts.map +1 -1
- package/abstract/poseidon.js +178 -15
- package/abstract/poseidon.js.map +1 -1
- package/abstract/tower.d.ts +122 -3
- package/abstract/tower.d.ts.map +1 -1
- package/abstract/tower.js +323 -139
- package/abstract/tower.js.map +1 -1
- package/abstract/weierstrass.d.ts +339 -76
- package/abstract/weierstrass.d.ts.map +1 -1
- package/abstract/weierstrass.js +395 -205
- package/abstract/weierstrass.js.map +1 -1
- package/bls12-381.d.ts +16 -2
- package/bls12-381.d.ts.map +1 -1
- package/bls12-381.js +199 -209
- package/bls12-381.js.map +1 -1
- package/bn254.d.ts +11 -2
- package/bn254.d.ts.map +1 -1
- package/bn254.js +93 -38
- package/bn254.js.map +1 -1
- package/ed25519.d.ts +125 -14
- package/ed25519.d.ts.map +1 -1
- package/ed25519.js +202 -40
- package/ed25519.js.map +1 -1
- package/ed448.d.ts +108 -14
- package/ed448.d.ts.map +1 -1
- package/ed448.js +194 -42
- package/ed448.js.map +1 -1
- package/index.js +7 -1
- package/index.js.map +1 -1
- package/misc.d.ts +106 -7
- package/misc.d.ts.map +1 -1
- package/misc.js +141 -32
- package/misc.js.map +1 -1
- package/nist.d.ts +112 -11
- package/nist.d.ts.map +1 -1
- package/nist.js +139 -17
- package/nist.js.map +1 -1
- package/package.json +11 -6
- package/secp256k1.d.ts +92 -15
- package/secp256k1.d.ts.map +1 -1
- package/secp256k1.js +211 -28
- package/secp256k1.js.map +1 -1
- package/src/abstract/bls.ts +350 -67
- package/src/abstract/curve.ts +327 -44
- package/src/abstract/edwards.ts +367 -143
- package/src/abstract/fft.ts +369 -36
- package/src/abstract/frost.ts +1092 -0
- package/src/abstract/hash-to-curve.ts +255 -56
- package/src/abstract/modular.ts +591 -144
- package/src/abstract/montgomery.ts +114 -30
- package/src/abstract/oprf.ts +383 -194
- package/src/abstract/poseidon.ts +235 -35
- package/src/abstract/tower.ts +428 -159
- package/src/abstract/weierstrass.ts +710 -312
- package/src/bls12-381.ts +239 -236
- package/src/bn254.ts +107 -46
- package/src/ed25519.ts +227 -55
- package/src/ed448.ts +227 -57
- package/src/index.ts +7 -1
- package/src/misc.ts +154 -35
- package/src/nist.ts +143 -20
- package/src/secp256k1.ts +284 -41
- package/src/utils.ts +583 -81
- package/src/webcrypto.ts +302 -73
- package/utils.d.ts +457 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +410 -53
- package/utils.js.map +1 -1
- package/webcrypto.d.ts +167 -25
- package/webcrypto.d.ts.map +1 -1
- package/webcrypto.js +165 -58
- package/webcrypto.js.map +1 -1
package/src/webcrypto.ts
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
then throw a SyntaxError."
|
|
16
16
|
- SPKI (Simple public-key infrastructure) is public-key-only
|
|
17
17
|
- PKCS8 is secret-key-only
|
|
18
|
-
- No way to get public key from secret key, but we convert to
|
|
18
|
+
- No way to get public key from secret key, but we convert to JWK and then
|
|
19
|
+
create it manually, since a JWK secret key includes both private and public
|
|
20
|
+
parts.
|
|
19
21
|
- Noble supports generating keys for both sign, verify & getSharedSecret,
|
|
20
22
|
but JWK key includes usage, which forces us to patch it (non-JWK is ok)
|
|
21
23
|
- We have import/export for 'raw', but it doesn't work in Firefox / Safari
|
|
@@ -26,7 +28,9 @@
|
|
|
26
28
|
but this is implementation specific and not much we can do there.
|
|
27
29
|
- `getSharedSecret` differs for p256, p384, p521:
|
|
28
30
|
Noble returns 33-byte output (y-parity + x coordinate),
|
|
29
|
-
while in WebCrypto returns 32-byte output (x coordinate)
|
|
31
|
+
while in WebCrypto returns 32-byte output (x coordinate).
|
|
32
|
+
This is intentional: noble keeps the full encoded shared point, and x-only
|
|
33
|
+
callers can slice it down themselves.
|
|
30
34
|
- `getSharedSecret` identical for X25519, X448
|
|
31
35
|
|
|
32
36
|
## Availability
|
|
@@ -37,12 +41,14 @@ There seems no reasonable way to check for availability, other than actually cal
|
|
|
37
41
|
* @module
|
|
38
42
|
*/
|
|
39
43
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
44
|
+
import type { TArg, TRet } from './utils.ts';
|
|
40
45
|
|
|
41
46
|
/** Raw type */
|
|
42
47
|
const TYPE_RAW = 'raw';
|
|
43
48
|
const TYPE_JWK = 'jwk';
|
|
44
49
|
const TYPE_SPKI = 'spki';
|
|
45
50
|
const TYPE_PKCS = 'pkcs8';
|
|
51
|
+
/** Key serialization formats supported by the WebCrypto wrappers. */
|
|
46
52
|
export type WebCryptoFormat =
|
|
47
53
|
| typeof TYPE_RAW
|
|
48
54
|
| typeof TYPE_JWK
|
|
@@ -50,7 +56,9 @@ export type WebCryptoFormat =
|
|
|
50
56
|
| typeof TYPE_PKCS;
|
|
51
57
|
/** WebCrypto keys can be in raw, jwk, pkcs8/spki formats. Raw is internal and fragile. */
|
|
52
58
|
export type WebCryptoOpts = {
|
|
59
|
+
/** Preferred secret-key serialization format. */
|
|
53
60
|
formatSec?: WebCryptoFormat;
|
|
61
|
+
/** Preferred public-key serialization format. */
|
|
54
62
|
formatPub?: WebCryptoFormat;
|
|
55
63
|
};
|
|
56
64
|
// default formats
|
|
@@ -63,17 +71,29 @@ function getSubtle(): any {
|
|
|
63
71
|
throw new Error('crypto.subtle must be defined');
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
function createKeygenA(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
function createKeygenA(
|
|
75
|
+
randomSecretKey: any,
|
|
76
|
+
getPublicKey: any
|
|
77
|
+
): TRet<(seed?: Uint8Array) => Promise<{ secretKey: Uint8Array; publicKey: Uint8Array }>> {
|
|
78
|
+
// Runtime accepts an accidental `keygen(seed)` argument for parity with other wrappers, but the
|
|
79
|
+
// seed is intentionally ignored because WebCrypto keygen here always goes through fresh keygen.
|
|
80
|
+
return async function keygenA(_seed?: TArg<Uint8Array>) {
|
|
81
|
+
const secretKey = (await randomSecretKey()) as TRet<Uint8Array>;
|
|
82
|
+
return { secretKey, publicKey: (await getPublicKey(secretKey)) as TRet<Uint8Array> };
|
|
70
83
|
};
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
// Internal helper only: strict hex parser for the local hardcoded PKCS8 header constants.
|
|
87
|
+
function hexToBytesLocal(hex: string): TRet<Uint8Array> {
|
|
88
|
+
const pairs = hex.match(/[0-9a-f]{2}/gi);
|
|
89
|
+
if (!pairs || pairs.length * 2 !== hex.length) throw new Error('invalid hex');
|
|
90
|
+
return Uint8Array.from(pairs, (b) => Number.parseInt(b, 16)) as TRet<Uint8Array>;
|
|
75
91
|
}
|
|
76
92
|
|
|
93
|
+
export const __TEST: { hexToBytesLocal: typeof hexToBytesLocal } = /* @__PURE__ */ Object.freeze({
|
|
94
|
+
hexToBytesLocal,
|
|
95
|
+
});
|
|
96
|
+
|
|
77
97
|
// Trying to do generics here creates hell on conversion and usage
|
|
78
98
|
type JsonWebKey = {
|
|
79
99
|
crv?: string;
|
|
@@ -90,12 +110,18 @@ type Algo = string | { name: string; namedCurve: string };
|
|
|
90
110
|
type SigAlgo = string | { name: string; hash?: { name: string } };
|
|
91
111
|
|
|
92
112
|
type KeyUtils = {
|
|
93
|
-
import(key: Key
|
|
94
|
-
export(key: CryptoKey, format?: WebCryptoFormat): Promise<Key
|
|
95
|
-
convert(
|
|
113
|
+
import(key: TArg<Key>, format?: WebCryptoFormat): Promise<CryptoKey>;
|
|
114
|
+
export(key: CryptoKey, format?: WebCryptoFormat): TRet<Promise<Key>>;
|
|
115
|
+
convert(
|
|
116
|
+
key: TArg<Key>,
|
|
117
|
+
inFormat?: WebCryptoFormat,
|
|
118
|
+
outFormat?: WebCryptoFormat
|
|
119
|
+
): TRet<Promise<Key>>;
|
|
96
120
|
};
|
|
97
121
|
|
|
98
122
|
function assertType(type: 'private' | 'public', key: any) {
|
|
123
|
+
// Callers are expected to pass a non-null key-like object; `null` / `undefined` still fail first
|
|
124
|
+
// via property access before reaching the explicit wrapper error.
|
|
99
125
|
if (key.type !== type) throw new Error(`invalid key type, expected ${type}`);
|
|
100
126
|
}
|
|
101
127
|
|
|
@@ -103,25 +129,33 @@ function createKeyUtils(algo: Algo, derive: boolean, keyLen: number, pkcs8header
|
|
|
103
129
|
const secUsage: KeyUsage[] = derive ? ['deriveBits'] : ['sign'];
|
|
104
130
|
const pubUsage: KeyUsage[] = derive ? [] : ['verify'];
|
|
105
131
|
// Return Uint8Array instead of ArrayBuffer
|
|
106
|
-
const arrBufToU8 = (res: Key
|
|
107
|
-
format === TYPE_JWK
|
|
132
|
+
const arrBufToU8 = (res: TArg<Key>, format: WebCryptoFormat): TRet<Key> =>
|
|
133
|
+
(format === TYPE_JWK
|
|
134
|
+
? (res as JsonWebKey)
|
|
135
|
+
: new Uint8Array(res as unknown as ArrayBuffer)) as TRet<Key>;
|
|
108
136
|
const pub: KeyUtils = {
|
|
109
|
-
async import(key: Key
|
|
137
|
+
async import(key: TArg<Key>, format: WebCryptoFormat): Promise<CryptoKey> {
|
|
138
|
+
// For sign/verify wrappers we pass caller-provided JWK metadata through unchanged and let
|
|
139
|
+
// WebCrypto enforce mismatched `key_ops` / extractability instead of normalizing it here.
|
|
110
140
|
const keyi: CryptoKey = await getSubtle().importKey(format, key, algo, true, pubUsage);
|
|
111
141
|
assertType('public', keyi);
|
|
112
142
|
return keyi;
|
|
113
143
|
},
|
|
114
|
-
async export(key: CryptoKey, format: WebCryptoFormat): Promise<Key
|
|
144
|
+
async export(key: CryptoKey, format: WebCryptoFormat): Promise<TRet<Key>> {
|
|
115
145
|
assertType('public', key);
|
|
116
146
|
const keyi = await getSubtle().exportKey(format, key);
|
|
117
147
|
return arrBufToU8(keyi, format);
|
|
118
148
|
},
|
|
119
|
-
async convert(
|
|
149
|
+
async convert(
|
|
150
|
+
key: TArg<Key>,
|
|
151
|
+
inFormat: WebCryptoFormat,
|
|
152
|
+
outFormat: WebCryptoFormat
|
|
153
|
+
): Promise<TRet<Key>> {
|
|
120
154
|
return pub.export(await pub.import(key, inFormat), outFormat);
|
|
121
155
|
},
|
|
122
156
|
};
|
|
123
157
|
const priv: KeyUtils = {
|
|
124
|
-
async import(key: Key
|
|
158
|
+
async import(key: TArg<Key>, format: WebCryptoFormat): Promise<CryptoKey> {
|
|
125
159
|
const crypto = getSubtle();
|
|
126
160
|
let keyi: CryptoKey;
|
|
127
161
|
if (format === TYPE_RAW) {
|
|
@@ -129,21 +163,24 @@ function createKeyUtils(algo: Algo, derive: boolean, keyLen: number, pkcs8header
|
|
|
129
163
|
// Safari, Firefox: Data provided to an operation does not meet requirements
|
|
130
164
|
// This is the best one can do. JWK can't be used: it contains public key component inside.
|
|
131
165
|
const k = key as Uint8Array;
|
|
132
|
-
const head =
|
|
166
|
+
const head = hexToBytesLocal(pkcs8header);
|
|
133
167
|
const all = new Uint8Array(head.length + k.length);
|
|
134
168
|
all.set(head, 0);
|
|
135
169
|
all.set(k, head.length);
|
|
136
170
|
|
|
137
171
|
keyi = await crypto.importKey(TYPE_PKCS, all, algo, true, secUsage);
|
|
138
172
|
} else {
|
|
139
|
-
//
|
|
173
|
+
// Sign/verify wrappers keep caller JWK metadata as-is and assume the supplied `key_ops`
|
|
174
|
+
// already match the requested operation. ECDH is different: noble treats the same key
|
|
175
|
+
// material as usable for both sign and derive, so JWK imported through the derive path
|
|
176
|
+
// must rewrite `key_ops` or WebCrypto refuses otherwise-correct keys exported by keygen.
|
|
140
177
|
if (derive && format === TYPE_JWK) key = { ...key, key_ops: secUsage };
|
|
141
178
|
keyi = await crypto.importKey(format, key, algo, true, secUsage);
|
|
142
179
|
}
|
|
143
180
|
assertType('private', keyi);
|
|
144
181
|
return keyi;
|
|
145
182
|
},
|
|
146
|
-
async export(key: CryptoKey, format: WebCryptoFormat): Promise<Key
|
|
183
|
+
async export(key: CryptoKey, format: WebCryptoFormat): Promise<TRet<Key>> {
|
|
147
184
|
const crypto = getSubtle();
|
|
148
185
|
assertType('private', key);
|
|
149
186
|
if (format === TYPE_RAW) {
|
|
@@ -158,16 +195,23 @@ function createKeyUtils(algo: Algo, derive: boolean, keyLen: number, pkcs8header
|
|
|
158
195
|
// Pad key to key len because Bun strips leading zero for P-521 only
|
|
159
196
|
const res = new Uint8Array(keyLen);
|
|
160
197
|
res.set(raw, keyLen - raw.length);
|
|
161
|
-
return res as Key
|
|
198
|
+
return res as TRet<Key>;
|
|
162
199
|
}
|
|
163
200
|
const keyi = await crypto.exportKey(format, key);
|
|
164
201
|
return arrBufToU8(keyi, format);
|
|
165
202
|
},
|
|
166
|
-
async convert(
|
|
203
|
+
async convert(
|
|
204
|
+
key: TArg<Key>,
|
|
205
|
+
inFormat: WebCryptoFormat,
|
|
206
|
+
outFormat: WebCryptoFormat
|
|
207
|
+
): Promise<TRet<Key>> {
|
|
167
208
|
return priv.export(await priv.import(key, inFormat), outFormat);
|
|
168
209
|
},
|
|
169
210
|
};
|
|
170
|
-
async function getPublicKey(
|
|
211
|
+
async function getPublicKey(
|
|
212
|
+
secretKey: TArg<Key>,
|
|
213
|
+
opts: TArg<WebCryptoOpts> = {}
|
|
214
|
+
): Promise<TRet<Key>> {
|
|
171
215
|
const fsec = opts.formatSec ?? dfsec;
|
|
172
216
|
const fpub = opts.formatPub ?? dfpub;
|
|
173
217
|
// Export to jwk, remove private scalar and then convert to format
|
|
@@ -176,10 +220,10 @@ function createKeyUtils(algo: Algo, derive: boolean, keyLen: number, pkcs8header
|
|
|
176
220
|
) as JsonWebKey;
|
|
177
221
|
delete jwk.d;
|
|
178
222
|
jwk.key_ops = pubUsage;
|
|
179
|
-
if (fpub === TYPE_JWK) return jwk
|
|
223
|
+
if (fpub === TYPE_JWK) return jwk as TRet<Key>;
|
|
180
224
|
return pub.convert(jwk, TYPE_JWK, fpub);
|
|
181
225
|
}
|
|
182
|
-
async function randomSecretKey(format: WebCryptoFormat = dfsec): Promise<Key
|
|
226
|
+
async function randomSecretKey(format: WebCryptoFormat = dfsec): Promise<TRet<Key>> {
|
|
183
227
|
const keyPair = await getSubtle().generateKey(algo, true, secUsage);
|
|
184
228
|
return priv.export(keyPair.privateKey, format);
|
|
185
229
|
}
|
|
@@ -210,26 +254,41 @@ function createKeyUtils(algo: Algo, derive: boolean, keyLen: number, pkcs8header
|
|
|
210
254
|
},
|
|
211
255
|
getPublicKey,
|
|
212
256
|
keygen: createKeygenA(randomSecretKey, getPublicKey),
|
|
213
|
-
utils: {
|
|
257
|
+
utils: Object.freeze({
|
|
214
258
|
randomSecretKey,
|
|
259
|
+
// Runtime expects both formats explicitly here; omitted formats just flow into
|
|
260
|
+
// `subtle.importKey(...)`, and JWK conversion also assumes extractable keys (`ext !== false`).
|
|
215
261
|
convertPublicKey: pub.convert as KeyUtils['convert'],
|
|
262
|
+
// Runtime expects both formats explicitly here; omitted formats just flow into
|
|
263
|
+
// `subtle.importKey(...)`, and JWK conversion also assumes extractable keys (`ext !== false`).
|
|
216
264
|
convertSecretKey: priv.convert as KeyUtils['convert'],
|
|
217
|
-
},
|
|
265
|
+
}),
|
|
218
266
|
};
|
|
219
267
|
}
|
|
220
268
|
|
|
221
|
-
function createSigner(
|
|
269
|
+
function createSigner(
|
|
270
|
+
keys: ReturnType<typeof createKeyUtils>,
|
|
271
|
+
algo: SigAlgo
|
|
272
|
+
): TRet<WebCryptoSigner> {
|
|
222
273
|
return {
|
|
223
|
-
|
|
274
|
+
// Historical param name: wrappers pass message bytes here, while WebCrypto performs the
|
|
275
|
+
// algorithm-specific hashing itself for ECDSA. We also return provider signatures verbatim:
|
|
276
|
+
// this wrapper is intentionally "raw WebCrypto", so it does not parse scalars or normalize
|
|
277
|
+
// high-S ECDSA outputs into software noble's low-S convention.
|
|
278
|
+
async sign(
|
|
279
|
+
msgHash: TArg<Uint8Array>,
|
|
280
|
+
secretKey: TArg<Key>,
|
|
281
|
+
opts: TArg<WebCryptoOpts> = {}
|
|
282
|
+
): Promise<TRet<Uint8Array>> {
|
|
224
283
|
const key = await keys.priv.import(secretKey, opts.formatSec ?? dfsec);
|
|
225
284
|
const sig = await getSubtle().sign(algo, key, msgHash);
|
|
226
|
-
return new Uint8Array(sig)
|
|
285
|
+
return new Uint8Array(sig) as TRet<Uint8Array>;
|
|
227
286
|
},
|
|
228
287
|
async verify(
|
|
229
|
-
signature: Uint8Array
|
|
230
|
-
msgHash: Uint8Array
|
|
231
|
-
publicKey: Key
|
|
232
|
-
opts: WebCryptoOpts = {}
|
|
288
|
+
signature: TArg<Uint8Array>,
|
|
289
|
+
msgHash: TArg<Uint8Array>,
|
|
290
|
+
publicKey: TArg<Key>,
|
|
291
|
+
opts: TArg<WebCryptoOpts> = {}
|
|
233
292
|
): Promise<boolean> {
|
|
234
293
|
const key = await keys.pub.import(publicKey, opts.formatPub ?? dfpub);
|
|
235
294
|
return await getSubtle().verify(algo, key, signature, msgHash);
|
|
@@ -241,22 +300,30 @@ function createECDH(
|
|
|
241
300
|
keys: ReturnType<typeof createKeyUtils>,
|
|
242
301
|
algo: Algo,
|
|
243
302
|
keyLen: number
|
|
244
|
-
): WebCryptoECDH {
|
|
303
|
+
): TRet<WebCryptoECDH> {
|
|
245
304
|
return {
|
|
305
|
+
// Runtime accepts the alternate key formats supported by `keys.import(...)`; the public type is
|
|
306
|
+
// still narrower than that accepted surface.
|
|
246
307
|
async getSharedSecret(
|
|
247
|
-
secretKeyA: Uint8Array
|
|
248
|
-
publicKeyB: Uint8Array
|
|
249
|
-
opts: WebCryptoOpts = {}
|
|
250
|
-
): Promise<Uint8Array
|
|
308
|
+
secretKeyA: TArg<Uint8Array>,
|
|
309
|
+
publicKeyB: TArg<Uint8Array>,
|
|
310
|
+
opts: TArg<WebCryptoOpts> = {}
|
|
311
|
+
): Promise<TRet<Uint8Array>> {
|
|
251
312
|
// if (_isCompressed !== true) throw new Error('WebCrypto only supports compressed keys');
|
|
252
|
-
const secKey = await keys.priv.import(
|
|
253
|
-
|
|
313
|
+
const secKey = await keys.priv.import(
|
|
314
|
+
secretKeyA,
|
|
315
|
+
opts.formatSec === undefined ? dfsec : opts.formatSec
|
|
316
|
+
);
|
|
317
|
+
const pubKey = await keys.pub.import(
|
|
318
|
+
publicKeyB,
|
|
319
|
+
opts.formatPub === undefined ? dfpub : opts.formatPub
|
|
320
|
+
);
|
|
254
321
|
const shared = await getSubtle().deriveBits(
|
|
255
322
|
{ name: typeof algo === 'string' ? algo : algo.name, public: pubKey },
|
|
256
323
|
secKey,
|
|
257
324
|
8 * keyLen
|
|
258
325
|
);
|
|
259
|
-
return new Uint8Array(shared)
|
|
326
|
+
return new Uint8Array(shared) as TRet<Uint8Array>;
|
|
260
327
|
},
|
|
261
328
|
};
|
|
262
329
|
}
|
|
@@ -264,38 +331,76 @@ function createECDH(
|
|
|
264
331
|
type WebCryptoBaseCurve = {
|
|
265
332
|
name: string;
|
|
266
333
|
isSupported(): Promise<boolean>;
|
|
267
|
-
keygen(): Promise<{ secretKey: Uint8Array; publicKey: Uint8Array }
|
|
268
|
-
getPublicKey(secretKey: Key
|
|
334
|
+
keygen(): TRet<Promise<{ secretKey: Uint8Array; publicKey: Uint8Array }>>;
|
|
335
|
+
getPublicKey(secretKey: TArg<Key>, opts?: TArg<WebCryptoOpts>): TRet<Promise<Key>>;
|
|
269
336
|
utils: {
|
|
270
|
-
randomSecretKey: (format?: WebCryptoFormat) => Promise<Key
|
|
337
|
+
randomSecretKey: (format?: WebCryptoFormat) => TRet<Promise<Key>>;
|
|
271
338
|
convertSecretKey: (
|
|
272
|
-
key: Key
|
|
339
|
+
key: TArg<Key>,
|
|
273
340
|
inFormat?: WebCryptoFormat,
|
|
274
341
|
outFormat?: WebCryptoFormat
|
|
275
|
-
) => Promise<Key
|
|
342
|
+
) => TRet<Promise<Key>>;
|
|
276
343
|
convertPublicKey: (
|
|
277
|
-
key: Key
|
|
344
|
+
key: TArg<Key>,
|
|
278
345
|
inFormat?: WebCryptoFormat,
|
|
279
346
|
outFormat?: WebCryptoFormat
|
|
280
|
-
) => Promise<Key
|
|
347
|
+
) => TRet<Promise<Key>>;
|
|
281
348
|
};
|
|
282
349
|
};
|
|
283
350
|
|
|
284
351
|
// Specific per-curve methods - no reason to export them; we can't "add" a new curve
|
|
352
|
+
/** WebCrypto signing interface shared by ECDSA and EdDSA helpers. */
|
|
285
353
|
export type WebCryptoSigner = {
|
|
286
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Sign one message with a WebCrypto-backed private key.
|
|
356
|
+
* @param message - Message bytes to sign.
|
|
357
|
+
* @param secretKey - Secret key in one supported format.
|
|
358
|
+
* @param opts - Optional key-format overrides. See {@link WebCryptoOpts}.
|
|
359
|
+
* @returns Signature bytes.
|
|
360
|
+
*/
|
|
361
|
+
sign(
|
|
362
|
+
message: TArg<Uint8Array>,
|
|
363
|
+
secretKey: TArg<Key>,
|
|
364
|
+
opts?: TArg<WebCryptoOpts>
|
|
365
|
+
): TRet<Promise<Uint8Array>>;
|
|
366
|
+
/**
|
|
367
|
+
* Verify one signature with a WebCrypto-backed public key.
|
|
368
|
+
* @param signature - Signature bytes.
|
|
369
|
+
* @param message - Signed message bytes.
|
|
370
|
+
* @param publicKey - Public key in one supported format.
|
|
371
|
+
* @param opts - Optional key-format overrides. See {@link WebCryptoOpts}.
|
|
372
|
+
* @returns `true` when the signature is valid.
|
|
373
|
+
*/
|
|
287
374
|
verify(
|
|
288
|
-
signature: Uint8Array
|
|
289
|
-
message: Uint8Array
|
|
290
|
-
publicKey: Key
|
|
291
|
-
opts?: WebCryptoOpts
|
|
375
|
+
signature: TArg<Uint8Array>,
|
|
376
|
+
message: TArg<Uint8Array>,
|
|
377
|
+
publicKey: TArg<Key>,
|
|
378
|
+
opts?: TArg<WebCryptoOpts>
|
|
292
379
|
): Promise<boolean>;
|
|
293
380
|
};
|
|
381
|
+
/** WebCrypto ECDH interface for shared-secret derivation. */
|
|
294
382
|
export type WebCryptoECDH = {
|
|
295
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Derive one shared secret from a local secret key and peer public key.
|
|
385
|
+
* Short-Weierstrass wrappers return the raw x-coordinate here, not noble's parity-prefixed
|
|
386
|
+
* shared-point encoding. Runtime also accepts alternate key formats through `opts`, even though
|
|
387
|
+
* this public type is still narrowed to byte arrays.
|
|
388
|
+
* @param secA - Local secret key in one supported format.
|
|
389
|
+
* @param pubB - Peer public key in one supported format.
|
|
390
|
+
* @param opts - Optional key-format overrides. See {@link WebCryptoOpts}.
|
|
391
|
+
* @returns Shared secret bytes.
|
|
392
|
+
*/
|
|
393
|
+
getSharedSecret(
|
|
394
|
+
secA: TArg<Uint8Array>,
|
|
395
|
+
pubB: TArg<Uint8Array>,
|
|
396
|
+
opts?: TArg<WebCryptoOpts>
|
|
397
|
+
): TRet<Promise<Uint8Array>>;
|
|
296
398
|
};
|
|
399
|
+
/** WebCrypto ECDSA interface with keygen, signing, and ECDH helpers. */
|
|
297
400
|
export type WebCryptoECDSA = WebCryptoBaseCurve & WebCryptoSigner & WebCryptoECDH;
|
|
401
|
+
/** WebCrypto EdDSA interface with keygen and signing helpers. */
|
|
298
402
|
export type WebCryptoEdDSA = WebCryptoBaseCurve & WebCryptoSigner;
|
|
403
|
+
/** WebCrypto Montgomery interface with keygen and ECDH helpers. */
|
|
299
404
|
export type WebCryptoMontgomery = WebCryptoBaseCurve & WebCryptoECDH;
|
|
300
405
|
|
|
301
406
|
function wrapECDSA(
|
|
@@ -303,18 +408,38 @@ function wrapECDSA(
|
|
|
303
408
|
hash: string,
|
|
304
409
|
keyLen: number,
|
|
305
410
|
pkcs8header: string
|
|
306
|
-
): WebCryptoECDSA {
|
|
411
|
+
): TRet<WebCryptoECDSA> {
|
|
307
412
|
const ECDH_ALGO = { name: 'ECDH', namedCurve: curve };
|
|
308
413
|
const keys = createKeyUtils({ name: 'ECDSA', namedCurve: curve }, false, keyLen, pkcs8header);
|
|
309
414
|
const keysEcdh = createKeyUtils(ECDH_ALGO, true, keyLen, pkcs8header);
|
|
310
415
|
return Object.freeze({
|
|
311
416
|
name: curve,
|
|
417
|
+
// Support probing comes from the sign-side wrapper only; ECDH availability is not checked
|
|
418
|
+
// independently here even though the public wrapper also exposes `getSharedSecret(...)`.
|
|
312
419
|
isSupported: keys.isSupported,
|
|
313
420
|
getPublicKey: keys.getPublicKey,
|
|
314
421
|
keygen: createKeygenA(keys.utils.randomSecretKey, keys.getPublicKey),
|
|
315
422
|
...createSigner(keys, { name: 'ECDSA', hash: { name: hash } }),
|
|
316
423
|
...createECDH(keysEcdh, ECDH_ALGO, keyLen),
|
|
317
|
-
utils:
|
|
424
|
+
utils: Object.freeze({
|
|
425
|
+
...keys.utils,
|
|
426
|
+
async convertSecretKey(
|
|
427
|
+
key: TArg<Key>,
|
|
428
|
+
inFormat?: WebCryptoFormat,
|
|
429
|
+
outFormat?: WebCryptoFormat
|
|
430
|
+
): Promise<TRet<Key>> {
|
|
431
|
+
const jwk = inFormat === TYPE_JWK ? (key as JsonWebKey) : undefined;
|
|
432
|
+
// `wrapECDSA(...)` exposes the same key material for both sign and derive, so an ECDH-flavored
|
|
433
|
+
// JWK secret key from `getSharedSecret(...)` should still round-trip through `utils`.
|
|
434
|
+
if (
|
|
435
|
+
Array.isArray(jwk?.key_ops) &&
|
|
436
|
+
jwk.key_ops.length === 1 &&
|
|
437
|
+
jwk.key_ops[0] === 'deriveBits'
|
|
438
|
+
)
|
|
439
|
+
return keysEcdh.utils.convertSecretKey(key, inFormat, outFormat);
|
|
440
|
+
return keys.utils.convertSecretKey(key, inFormat, outFormat);
|
|
441
|
+
},
|
|
442
|
+
}),
|
|
318
443
|
});
|
|
319
444
|
}
|
|
320
445
|
|
|
@@ -322,11 +447,13 @@ function wrapEdDSA(
|
|
|
322
447
|
curve: 'Ed25519' | 'Ed448',
|
|
323
448
|
keyLen: number,
|
|
324
449
|
pkcs8header: string
|
|
325
|
-
): WebCryptoEdDSA {
|
|
450
|
+
): TRet<WebCryptoEdDSA> {
|
|
326
451
|
const keys = createKeyUtils(curve, false, keyLen, pkcs8header);
|
|
327
452
|
return Object.freeze({
|
|
328
453
|
name: curve,
|
|
329
454
|
isSupported: keys.isSupported,
|
|
455
|
+
// This wrapper intentionally re-exports the generic WebCrypto key-conversion/signing behavior
|
|
456
|
+
// without adding extra JWK-metadata or extractability guardrails of its own.
|
|
330
457
|
getPublicKey: keys.getPublicKey,
|
|
331
458
|
keygen: createKeygenA(keys.utils.randomSecretKey, keys.getPublicKey),
|
|
332
459
|
...createSigner(keys, { name: curve }),
|
|
@@ -338,11 +465,13 @@ function wrapMontgomery(
|
|
|
338
465
|
curve: 'X25519' | 'X448',
|
|
339
466
|
keyLen: number,
|
|
340
467
|
pkcs8header: string
|
|
341
|
-
): WebCryptoMontgomery {
|
|
468
|
+
): TRet<WebCryptoMontgomery> {
|
|
342
469
|
const keys = createKeyUtils(curve, true, keyLen, pkcs8header);
|
|
343
470
|
return Object.freeze({
|
|
344
471
|
name: curve,
|
|
345
472
|
isSupported: keys.isSupported,
|
|
473
|
+
// This wrapper intentionally re-exports the generic ECDH key-format behavior without widening
|
|
474
|
+
// the narrow public `Uint8Array` key types.
|
|
346
475
|
getPublicKey: keys.getPublicKey,
|
|
347
476
|
keygen: createKeygenA(keys.utils.randomSecretKey, keys.getPublicKey),
|
|
348
477
|
...createECDH(keys, curve, keyLen),
|
|
@@ -350,53 +479,153 @@ function wrapMontgomery(
|
|
|
350
479
|
});
|
|
351
480
|
}
|
|
352
481
|
|
|
353
|
-
/**
|
|
354
|
-
|
|
482
|
+
/**
|
|
483
|
+
* Friendly wrapper over built-in WebCrypto NIST P-256 (secp256r1).
|
|
484
|
+
* Inherits the generic WebCrypto ECDSA caveats: `isSupported()` only probes the sign-side API, and
|
|
485
|
+
* the conversion/signing helpers keep the shared `createKeyUtils(...)` / `createSigner(...)` quirks,
|
|
486
|
+
* including raw WebCrypto ECDSA signatures without low-S normalization.
|
|
487
|
+
* @example
|
|
488
|
+
* Check support, then sign and verify once with WebCrypto P-256.
|
|
489
|
+
*
|
|
490
|
+
* ```ts
|
|
491
|
+
* if (await p256.isSupported()) {
|
|
492
|
+
* const { secretKey, publicKey } = await p256.keygen();
|
|
493
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
494
|
+
* const sig = await p256.sign(msg, secretKey);
|
|
495
|
+
* const isValid = await p256.verify(sig, msg, publicKey);
|
|
496
|
+
* }
|
|
497
|
+
* ```
|
|
498
|
+
*/
|
|
499
|
+
export const p256: TRet<WebCryptoECDSA> = /* @__PURE__ */ wrapECDSA(
|
|
355
500
|
'P-256',
|
|
356
501
|
'SHA-256',
|
|
357
502
|
32,
|
|
358
503
|
'3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420'
|
|
359
504
|
);
|
|
360
505
|
|
|
361
|
-
/**
|
|
362
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Friendly wrapper over built-in WebCrypto NIST P-384 (secp384r1).
|
|
508
|
+
* Inherits the generic WebCrypto ECDSA caveats around support probing and key/signing conversion.
|
|
509
|
+
* @example
|
|
510
|
+
* Check support, then sign and verify once with WebCrypto P-384.
|
|
511
|
+
*
|
|
512
|
+
* ```ts
|
|
513
|
+
* if (await p384.isSupported()) {
|
|
514
|
+
* const { secretKey, publicKey } = await p384.keygen();
|
|
515
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
516
|
+
* const sig = await p384.sign(msg, secretKey);
|
|
517
|
+
* const isValid = await p384.verify(sig, msg, publicKey);
|
|
518
|
+
* }
|
|
519
|
+
* ```
|
|
520
|
+
*/
|
|
521
|
+
export const p384: TRet<WebCryptoECDSA> = /* @__PURE__ */ wrapECDSA(
|
|
363
522
|
'P-384',
|
|
364
523
|
'SHA-384',
|
|
365
524
|
48,
|
|
366
525
|
'304e020100301006072a8648ce3d020106052b81040022043730350201010430'
|
|
367
526
|
);
|
|
368
527
|
|
|
369
|
-
/**
|
|
370
|
-
|
|
528
|
+
/**
|
|
529
|
+
* Friendly wrapper over built-in WebCrypto NIST P-521 (secp521r1).
|
|
530
|
+
* Inherits the generic WebCrypto ECDSA caveats around support probing and key/signing conversion.
|
|
531
|
+
* @example
|
|
532
|
+
* Check support, then sign and verify once with WebCrypto P-521.
|
|
533
|
+
*
|
|
534
|
+
* ```ts
|
|
535
|
+
* if (await p521.isSupported()) {
|
|
536
|
+
* const { secretKey, publicKey } = await p521.keygen();
|
|
537
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
538
|
+
* const sig = await p521.sign(msg, secretKey);
|
|
539
|
+
* const isValid = await p521.verify(sig, msg, publicKey);
|
|
540
|
+
* }
|
|
541
|
+
* ```
|
|
542
|
+
*/
|
|
543
|
+
export const p521: TRet<WebCryptoECDSA> = /* @__PURE__ */ wrapECDSA(
|
|
371
544
|
'P-521',
|
|
372
545
|
'SHA-512',
|
|
373
546
|
66,
|
|
374
547
|
'3060020100301006072a8648ce3d020106052b81040023044930470201010442'
|
|
375
548
|
);
|
|
376
549
|
|
|
377
|
-
/**
|
|
378
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Friendly wrapper over built-in WebCrypto ed25519.
|
|
552
|
+
* Inherits the generic WebCrypto EdDSA caveats around JWK conversion metadata and extractability.
|
|
553
|
+
* @example
|
|
554
|
+
* Check support, then sign and verify once with WebCrypto Ed25519.
|
|
555
|
+
*
|
|
556
|
+
* ```ts
|
|
557
|
+
* if (await ed25519.isSupported()) {
|
|
558
|
+
* const { secretKey, publicKey } = await ed25519.keygen();
|
|
559
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
560
|
+
* const sig = await ed25519.sign(msg, secretKey);
|
|
561
|
+
* const isValid = await ed25519.verify(sig, msg, publicKey);
|
|
562
|
+
* }
|
|
563
|
+
* ```
|
|
564
|
+
*/
|
|
565
|
+
export const ed25519: TRet<WebCryptoEdDSA> = /* @__PURE__ */ wrapEdDSA(
|
|
379
566
|
'Ed25519',
|
|
380
567
|
32,
|
|
381
568
|
'302e020100300506032b657004220420'
|
|
382
569
|
);
|
|
383
570
|
|
|
384
|
-
/**
|
|
385
|
-
|
|
571
|
+
/**
|
|
572
|
+
* Friendly wrapper over built-in WebCrypto ed448.
|
|
573
|
+
* Inherits the generic WebCrypto EdDSA caveats around JWK conversion metadata and extractability.
|
|
574
|
+
* @example
|
|
575
|
+
* Check support, then sign and verify once with WebCrypto Ed448.
|
|
576
|
+
*
|
|
577
|
+
* ```ts
|
|
578
|
+
* if (await ed448.isSupported()) {
|
|
579
|
+
* const { secretKey, publicKey } = await ed448.keygen();
|
|
580
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
581
|
+
* const sig = await ed448.sign(msg, secretKey);
|
|
582
|
+
* const isValid = await ed448.verify(sig, msg, publicKey);
|
|
583
|
+
* }
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
export const ed448: TRet<WebCryptoEdDSA> = /* @__PURE__ */ wrapEdDSA(
|
|
386
587
|
'Ed448',
|
|
387
588
|
57,
|
|
388
589
|
'3047020100300506032b6571043b0439'
|
|
389
590
|
);
|
|
390
591
|
|
|
391
|
-
/**
|
|
392
|
-
|
|
592
|
+
/**
|
|
593
|
+
* Friendly wrapper over built-in WebCrypto x25519 (ECDH over Curve25519).
|
|
594
|
+
* Inherits the generic WebCrypto Montgomery caveat that runtime accepts more key formats than the
|
|
595
|
+
* narrow public `Uint8Array` argument types suggest.
|
|
596
|
+
* @example
|
|
597
|
+
* Check support, then derive one shared secret with WebCrypto X25519.
|
|
598
|
+
*
|
|
599
|
+
* ```ts
|
|
600
|
+
* if (await x25519.isSupported()) {
|
|
601
|
+
* const alice = await x25519.keygen();
|
|
602
|
+
* const bob = await x25519.keygen();
|
|
603
|
+
* const shared = await x25519.getSharedSecret(alice.secretKey, bob.publicKey);
|
|
604
|
+
* }
|
|
605
|
+
* ```
|
|
606
|
+
*/
|
|
607
|
+
export const x25519: TRet<WebCryptoMontgomery> = /* @__PURE__ */ wrapMontgomery(
|
|
393
608
|
'X25519',
|
|
394
609
|
32,
|
|
395
610
|
'302e020100300506032b656e04220420'
|
|
396
611
|
);
|
|
397
612
|
|
|
398
|
-
/**
|
|
399
|
-
|
|
613
|
+
/**
|
|
614
|
+
* Friendly wrapper over built-in WebCrypto x448 (ECDH over Curve448).
|
|
615
|
+
* Inherits the generic WebCrypto Montgomery caveat that runtime accepts more key formats than the
|
|
616
|
+
* narrow public `Uint8Array` argument types suggest.
|
|
617
|
+
* @example
|
|
618
|
+
* Check support, then derive one shared secret with WebCrypto X448.
|
|
619
|
+
*
|
|
620
|
+
* ```ts
|
|
621
|
+
* if (await x448.isSupported()) {
|
|
622
|
+
* const alice = await x448.keygen();
|
|
623
|
+
* const bob = await x448.keygen();
|
|
624
|
+
* const shared = await x448.getSharedSecret(alice.secretKey, bob.publicKey);
|
|
625
|
+
* }
|
|
626
|
+
* ```
|
|
627
|
+
*/
|
|
628
|
+
export const x448: TRet<WebCryptoMontgomery> = /* @__PURE__ */ wrapMontgomery(
|
|
400
629
|
'X448',
|
|
401
630
|
56,
|
|
402
631
|
'3046020100300506032b656f043a0438'
|