@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.
Files changed (110) hide show
  1. package/README.md +214 -122
  2. package/abstract/bls.d.ts +299 -16
  3. package/abstract/bls.d.ts.map +1 -1
  4. package/abstract/bls.js +82 -22
  5. package/abstract/bls.js.map +1 -1
  6. package/abstract/curve.d.ts +274 -27
  7. package/abstract/curve.d.ts.map +1 -1
  8. package/abstract/curve.js +177 -23
  9. package/abstract/curve.js.map +1 -1
  10. package/abstract/edwards.d.ts +166 -30
  11. package/abstract/edwards.d.ts.map +1 -1
  12. package/abstract/edwards.js +221 -86
  13. package/abstract/edwards.js.map +1 -1
  14. package/abstract/fft.d.ts +322 -10
  15. package/abstract/fft.d.ts.map +1 -1
  16. package/abstract/fft.js +154 -12
  17. package/abstract/fft.js.map +1 -1
  18. package/abstract/frost.d.ts +293 -0
  19. package/abstract/frost.d.ts.map +1 -0
  20. package/abstract/frost.js +704 -0
  21. package/abstract/frost.js.map +1 -0
  22. package/abstract/hash-to-curve.d.ts +173 -24
  23. package/abstract/hash-to-curve.d.ts.map +1 -1
  24. package/abstract/hash-to-curve.js +170 -31
  25. package/abstract/hash-to-curve.js.map +1 -1
  26. package/abstract/modular.d.ts +429 -37
  27. package/abstract/modular.d.ts.map +1 -1
  28. package/abstract/modular.js +414 -119
  29. package/abstract/modular.js.map +1 -1
  30. package/abstract/montgomery.d.ts +83 -12
  31. package/abstract/montgomery.d.ts.map +1 -1
  32. package/abstract/montgomery.js +32 -7
  33. package/abstract/montgomery.js.map +1 -1
  34. package/abstract/oprf.d.ts +164 -91
  35. package/abstract/oprf.d.ts.map +1 -1
  36. package/abstract/oprf.js +88 -29
  37. package/abstract/oprf.js.map +1 -1
  38. package/abstract/poseidon.d.ts +138 -7
  39. package/abstract/poseidon.d.ts.map +1 -1
  40. package/abstract/poseidon.js +178 -15
  41. package/abstract/poseidon.js.map +1 -1
  42. package/abstract/tower.d.ts +122 -3
  43. package/abstract/tower.d.ts.map +1 -1
  44. package/abstract/tower.js +323 -139
  45. package/abstract/tower.js.map +1 -1
  46. package/abstract/weierstrass.d.ts +339 -76
  47. package/abstract/weierstrass.d.ts.map +1 -1
  48. package/abstract/weierstrass.js +395 -205
  49. package/abstract/weierstrass.js.map +1 -1
  50. package/bls12-381.d.ts +16 -2
  51. package/bls12-381.d.ts.map +1 -1
  52. package/bls12-381.js +199 -209
  53. package/bls12-381.js.map +1 -1
  54. package/bn254.d.ts +11 -2
  55. package/bn254.d.ts.map +1 -1
  56. package/bn254.js +93 -38
  57. package/bn254.js.map +1 -1
  58. package/ed25519.d.ts +125 -14
  59. package/ed25519.d.ts.map +1 -1
  60. package/ed25519.js +202 -40
  61. package/ed25519.js.map +1 -1
  62. package/ed448.d.ts +108 -14
  63. package/ed448.d.ts.map +1 -1
  64. package/ed448.js +194 -42
  65. package/ed448.js.map +1 -1
  66. package/index.js +7 -1
  67. package/index.js.map +1 -1
  68. package/misc.d.ts +106 -7
  69. package/misc.d.ts.map +1 -1
  70. package/misc.js +141 -32
  71. package/misc.js.map +1 -1
  72. package/nist.d.ts +112 -11
  73. package/nist.d.ts.map +1 -1
  74. package/nist.js +139 -17
  75. package/nist.js.map +1 -1
  76. package/package.json +11 -6
  77. package/secp256k1.d.ts +92 -15
  78. package/secp256k1.d.ts.map +1 -1
  79. package/secp256k1.js +211 -28
  80. package/secp256k1.js.map +1 -1
  81. package/src/abstract/bls.ts +350 -67
  82. package/src/abstract/curve.ts +327 -44
  83. package/src/abstract/edwards.ts +367 -143
  84. package/src/abstract/fft.ts +369 -36
  85. package/src/abstract/frost.ts +1092 -0
  86. package/src/abstract/hash-to-curve.ts +255 -56
  87. package/src/abstract/modular.ts +591 -144
  88. package/src/abstract/montgomery.ts +114 -30
  89. package/src/abstract/oprf.ts +383 -194
  90. package/src/abstract/poseidon.ts +235 -35
  91. package/src/abstract/tower.ts +428 -159
  92. package/src/abstract/weierstrass.ts +710 -312
  93. package/src/bls12-381.ts +239 -236
  94. package/src/bn254.ts +107 -46
  95. package/src/ed25519.ts +227 -55
  96. package/src/ed448.ts +227 -57
  97. package/src/index.ts +7 -1
  98. package/src/misc.ts +154 -35
  99. package/src/nist.ts +143 -20
  100. package/src/secp256k1.ts +284 -41
  101. package/src/utils.ts +583 -81
  102. package/src/webcrypto.ts +302 -73
  103. package/utils.d.ts +457 -24
  104. package/utils.d.ts.map +1 -1
  105. package/utils.js +410 -53
  106. package/utils.js.map +1 -1
  107. package/webcrypto.d.ts +167 -25
  108. package/webcrypto.d.ts.map +1 -1
  109. package/webcrypto.js +165 -58
  110. 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 jwk and then create it manually, since jwk secret key is priv+pub.
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(randomSecretKey: any, getPublicKey: any) {
67
- return async function keygenA(_seed?: Uint8Array) {
68
- const secretKey = await randomSecretKey();
69
- return { secretKey, publicKey: await getPublicKey(secretKey) };
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
- function hexToBytesUns(hex: string): Uint8Array {
74
- return Uint8Array.from(hex.match(/(\w\w)/g)!, (b) => Number.parseInt(b, 16));
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, format?: WebCryptoFormat): Promise<CryptoKey>;
94
- export(key: CryptoKey, format?: WebCryptoFormat): Promise<Key>;
95
- convert(key: Key, inFormat?: WebCryptoFormat, outFormat?: WebCryptoFormat): Promise<Key>;
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, format: WebCryptoFormat) =>
107
- format === TYPE_JWK ? res : new Uint8Array(res as unknown as ArrayBuffer);
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, format: WebCryptoFormat): Promise<CryptoKey> {
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(key: Key, inFormat: WebCryptoFormat, outFormat: WebCryptoFormat): Promise<Key> {
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, format: WebCryptoFormat): Promise<CryptoKey> {
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 = hexToBytesUns(pkcs8header);
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
- // Fix import of ECDSA keys into ECDH, other formats are ok
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(key: Key, inFormat: WebCryptoFormat, outFormat: WebCryptoFormat): Promise<Key> {
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(secretKey: Key, opts: WebCryptoOpts = {}): Promise<Key> {
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(keys: ReturnType<typeof createKeyUtils>, algo: SigAlgo): WebCryptoSigner {
269
+ function createSigner(
270
+ keys: ReturnType<typeof createKeyUtils>,
271
+ algo: SigAlgo
272
+ ): TRet<WebCryptoSigner> {
222
273
  return {
223
- async sign(msgHash: Uint8Array, secretKey: Key, opts: WebCryptoOpts = {}): Promise<Uint8Array> {
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(secretKeyA, opts.formatSec || dfsec);
253
- const pubKey = await keys.pub.import(publicKeyB, opts.formatPub || dfpub);
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, opts?: WebCryptoOpts): Promise<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
- sign(message: Uint8Array, secretKey: Key, opts?: WebCryptoOpts): Promise<Uint8Array>;
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
- getSharedSecret(secA: Uint8Array, pubB: Uint8Array, opts?: WebCryptoOpts): Promise<Uint8Array>;
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: keys.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
- /** Friendly wrapper over built-in WebCrypto NIST P-256 (secp256r1). */
354
- export const p256: WebCryptoECDSA = /* @__PURE__ */ wrapECDSA(
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
- /** Friendly wrapper over built-in WebCrypto NIST P-384 (secp384r1). */
362
- export const p384: WebCryptoECDSA = /* @__PURE__ */ wrapECDSA(
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
- /** Friendly wrapper over built-in WebCrypto NIST P-521 (secp521r1). */
370
- export const p521: WebCryptoECDSA = /* @__PURE__ */ wrapECDSA(
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
- /** Friendly wrapper over built-in WebCrypto ed25519. */
378
- export const ed25519: WebCryptoEdDSA = /* @__PURE__ */ wrapEdDSA(
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
- /** Friendly wrapper over built-in WebCrypto ed448. */
385
- export const ed448: WebCryptoEdDSA = /* @__PURE__ */ wrapEdDSA(
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
- /** Friendly wrapper over built-in WebCrypto x25519 (ECDH over Curve25519). */
392
- export const x25519: WebCryptoMontgomery = /* @__PURE__ */ wrapMontgomery(
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
- /** Friendly wrapper over built-in WebCrypto x448 (ECDH over Curve448). */
399
- export const x448: WebCryptoMontgomery = /* @__PURE__ */ wrapMontgomery(
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'