@reallyme/crypto 0.1.8 → 0.3.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 (61) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +148 -18
  3. package/dist/aead.d.ts +4 -0
  4. package/dist/aead.js +40 -16
  5. package/dist/aesKw.d.ts +8 -2
  6. package/dist/aesKw.js +69 -12
  7. package/dist/algorithms.d.ts +12 -4
  8. package/dist/algorithms.js +8 -2
  9. package/dist/argon2id.d.ts +3 -0
  10. package/dist/argon2id.js +18 -3
  11. package/dist/cryptoFacade.d.ts +66 -5
  12. package/dist/cryptoFacade.js +99 -38
  13. package/dist/errors.d.ts +1 -1
  14. package/dist/hkdf.d.ts +1 -0
  15. package/dist/hkdf.js +5 -1
  16. package/dist/hmac.d.ts +3 -0
  17. package/dist/hmac.js +24 -3
  18. package/dist/hpke.d.ts +4 -1
  19. package/dist/hpke.js +45 -14
  20. package/dist/index.d.ts +14 -13
  21. package/dist/index.js +12 -10
  22. package/dist/jwaConcatKdf.js +8 -4
  23. package/dist/jwk.d.ts +2 -1
  24. package/dist/jwk.js +158 -122
  25. package/dist/kmac.d.ts +11 -0
  26. package/dist/kmac.js +49 -0
  27. package/dist/memory.d.ts +8 -0
  28. package/dist/memory.js +13 -0
  29. package/dist/mlDsa.d.ts +5 -0
  30. package/dist/mlDsa.js +43 -9
  31. package/dist/mlKem.d.ts +5 -2
  32. package/dist/mlKem.js +37 -26
  33. package/dist/operationResponse.d.ts +16 -0
  34. package/dist/operationResponse.js +46 -0
  35. package/dist/p256Ecdh.js +5 -1
  36. package/dist/p384Ecdh.js +5 -1
  37. package/dist/p521Ecdh.js +5 -1
  38. package/dist/pbkdf2.d.ts +6 -8
  39. package/dist/pbkdf2.js +12 -12
  40. package/dist/proto/generated/reallyme/crypto/v1/crypto_pb.d.ts +2859 -128
  41. package/dist/proto/generated/reallyme/crypto/v1/crypto_pb.js +992 -132
  42. package/dist/proto.d.ts +99 -7
  43. package/dist/proto.js +741 -16
  44. package/dist/rsa.d.ts +4 -1
  45. package/dist/rsa.js +23 -16
  46. package/dist/slhDsa.d.ts +5 -0
  47. package/dist/slhDsa.js +40 -9
  48. package/dist/validateBytes.d.ts +7 -1
  49. package/dist/validateBytes.js +63 -3
  50. package/dist/wasm/reallyme_crypto_wasm.js +77 -365
  51. package/dist/wasm/reallyme_crypto_wasm_bg.wasm +0 -0
  52. package/dist/wasmModuleTypes.d.ts +9 -21
  53. package/dist/wasmProvider.d.ts +12 -29
  54. package/dist/wasmProvider.js +25 -43
  55. package/dist/xWing.d.ts +5 -4
  56. package/dist/xWing.js +40 -40
  57. package/package.json +20 -2
  58. package/dist/codecs.d.ts +0 -35
  59. package/dist/codecs.js +0 -216
  60. package/dist/proto/generated/reallyme/codec/v1/codec_pb.d.ts +0 -253
  61. package/dist/proto/generated/reallyme/codec/v1/codec_pb.js +0 -160
package/dist/jwk.js CHANGED
@@ -3,9 +3,22 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { p256 } from "@noble/curves/nist.js";
5
5
  import { secp256k1 } from "@noble/curves/secp256k1.js";
6
+ import { base64urlDecode as codecBase64urlDecodeValue, base64urlEncode as codecBase64urlEncodeValue, canonicalizeJson as codecCanonicalizeJson, ReallyMeCodecError, } from "@reallyme/codec";
6
7
  import { ReallyMeCryptoError } from "./errors.js";
7
- const BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
8
- const DECODE_TABLE = new Map([...BASE64URL_ALPHABET].map((char, index) => [char, index]));
8
+ // JSON and protobuf JWKS ingress routes share this limit so neither boundary
9
+ // can amplify a small public-key record into unbounded curve validation work.
10
+ export const MAX_JWKS_KEYS = 1_024;
11
+ const withJwkBoundaryErrors = (operation) => {
12
+ try {
13
+ return operation();
14
+ }
15
+ catch (error) {
16
+ if (error instanceof ReallyMeCryptoError) {
17
+ throw error;
18
+ }
19
+ throw new ReallyMeCryptoError("invalid-input");
20
+ }
21
+ };
9
22
  const jwkSpec = (algorithm) => {
10
23
  switch (algorithm) {
11
24
  case "Ed25519":
@@ -32,8 +45,6 @@ const jwkSpec = (algorithm) => {
32
45
  return { alg: "SLH-DSA-SHA2-128s", kty: "AKP", publicKeyLength: 32, use: "sig" };
33
46
  case "X-Wing-768":
34
47
  return { alg: "X-Wing-768", kty: "AKP", publicKeyLength: 1_216, use: "enc" };
35
- case "X-Wing-1024":
36
- return { alg: "X-Wing-1024", kty: "AKP", publicKeyLength: 1_600, use: "enc" };
37
48
  }
38
49
  };
39
50
  const ensureLength = (bytes, expectedLength) => {
@@ -41,79 +52,58 @@ const ensureLength = (bytes, expectedLength) => {
41
52
  throw new ReallyMeCryptoError("invalid-input");
42
53
  }
43
54
  };
44
- const byteAt = (bytes, index) => {
45
- const value = bytes[index];
46
- if (value === undefined) {
55
+ const readString = (value, name) => {
56
+ if (typeof value !== "object" || value === null) {
47
57
  throw new ReallyMeCryptoError("invalid-input");
48
58
  }
49
- return value;
50
- };
51
- const base64urlEncode = (bytes) => {
52
- let encoded = "";
53
- let index = 0;
54
- while (index < bytes.length) {
55
- const first = byteAt(bytes, index);
56
- const second = index + 1 < bytes.length ? byteAt(bytes, index + 1) : 0;
57
- const third = index + 2 < bytes.length ? byteAt(bytes, index + 2) : 0;
58
- const triple = (first << 16) | (second << 8) | third;
59
- encoded += BASE64URL_ALPHABET[(triple >> 18) & 0x3f];
60
- encoded += BASE64URL_ALPHABET[(triple >> 12) & 0x3f];
61
- if (index + 1 < bytes.length) {
62
- encoded += BASE64URL_ALPHABET[(triple >> 6) & 0x3f];
63
- }
64
- if (index + 2 < bytes.length) {
65
- encoded += BASE64URL_ALPHABET[triple & 0x3f];
66
- }
67
- index += 3;
68
- }
69
- return encoded;
70
- };
71
- const decodeChar = (char) => {
72
- const value = DECODE_TABLE.get(char);
73
- if (value === undefined) {
59
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
60
+ if (descriptor === undefined ||
61
+ !("value" in descriptor) ||
62
+ typeof descriptor.value !== "string") {
74
63
  throw new ReallyMeCryptoError("invalid-input");
75
64
  }
76
- return value;
65
+ return descriptor.value;
77
66
  };
78
- const base64urlDecode = (encoded) => {
79
- if (encoded.length % 4 === 1) {
67
+ const ensureExactMembers = (value, allowedNames) => {
68
+ if (typeof value !== "object" || value === null) {
80
69
  throw new ReallyMeCryptoError("invalid-input");
81
70
  }
82
- const outputLength = Math.floor((encoded.length * 6) / 8);
83
- const output = new Uint8Array(outputLength);
84
- let buffer = 0;
85
- let bits = 0;
86
- let offset = 0;
87
- for (const char of encoded) {
88
- buffer = (buffer << 6) | decodeChar(char);
89
- bits += 6;
90
- if (bits >= 8) {
91
- bits -= 8;
92
- output[offset] = (buffer >> bits) & 0xff;
93
- offset += 1;
71
+ for (const name of Reflect.ownKeys(value)) {
72
+ if (typeof name !== "string" || !allowedNames.includes(name)) {
73
+ throw new ReallyMeCryptoError("invalid-input");
94
74
  }
95
75
  }
96
- if (offset !== output.length) {
97
- throw new ReallyMeCryptoError("invalid-input");
98
- }
99
- return output;
100
76
  };
101
- const readString = (value, name) => {
77
+ const privateJwkMemberNames = [
78
+ "d",
79
+ "p",
80
+ "q",
81
+ "dp",
82
+ "dq",
83
+ "qi",
84
+ "oth",
85
+ "k",
86
+ "priv",
87
+ "privateKey",
88
+ "secretKey",
89
+ ];
90
+ const rejectPrivateKeyMaterial = (value) => {
102
91
  if (typeof value !== "object" || value === null) {
103
92
  throw new ReallyMeCryptoError("invalid-input");
104
93
  }
105
- const property = Reflect.get(value, name);
106
- if (typeof property !== "string") {
107
- throw new ReallyMeCryptoError("invalid-input");
94
+ for (const name of privateJwkMemberNames) {
95
+ if (Reflect.has(value, name)) {
96
+ throw new ReallyMeCryptoError("invalid-input");
97
+ }
108
98
  }
109
- return property;
110
99
  };
111
100
  const optionalStringMatches = (value, name, expected) => {
112
101
  if (typeof value !== "object" || value === null) {
113
102
  throw new ReallyMeCryptoError("invalid-input");
114
103
  }
115
- const property = Reflect.get(value, name);
116
- if (property !== undefined && property !== expected) {
104
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
105
+ if (descriptor !== undefined &&
106
+ (!("value" in descriptor) || descriptor.value !== expected)) {
117
107
  throw new ReallyMeCryptoError("invalid-input");
118
108
  }
119
109
  };
@@ -138,7 +128,6 @@ const specFromAlgorithm = (algorithm) => {
138
128
  case "ML-KEM-1024":
139
129
  case "SLH-DSA-SHA2-128s":
140
130
  case "X-Wing-768":
141
- case "X-Wing-1024":
142
131
  return jwkSpec(algorithm);
143
132
  default:
144
133
  throw new ReallyMeCryptoError("unsupported-algorithm");
@@ -172,14 +161,43 @@ const compressEc = (algorithm, x, y) => {
172
161
  throw new ReallyMeCryptoError("invalid-input");
173
162
  }
174
163
  };
164
+ const mapCodecError = (error) => {
165
+ if (error instanceof ReallyMeCodecError && error.code === "provider-failure") {
166
+ return new ReallyMeCryptoError("provider-failure");
167
+ }
168
+ return new ReallyMeCryptoError("invalid-input");
169
+ };
170
+ const codecBase64urlEncode = (bytes) => {
171
+ try {
172
+ return codecBase64urlEncodeValue(bytes);
173
+ }
174
+ catch (error) {
175
+ throw mapCodecError(error);
176
+ }
177
+ };
178
+ const codecBase64urlDecode = (encoded) => {
179
+ try {
180
+ return codecBase64urlDecodeValue(encoded);
181
+ }
182
+ catch (error) {
183
+ throw mapCodecError(error);
184
+ }
185
+ };
186
+ const codecBase64urlDecodeCanonical = (encoded) => {
187
+ const bytes = codecBase64urlDecode(encoded);
188
+ if (codecBase64urlEncode(bytes) !== encoded) {
189
+ throw new ReallyMeCryptoError("invalid-input");
190
+ }
191
+ return bytes;
192
+ };
175
193
  const toJcs = (jwk) => {
176
- if (jwk.kty === "EC") {
177
- return `{"alg":${JSON.stringify(jwk.alg)},"crv":${JSON.stringify(jwk.crv)},"kty":"EC","use":"sig","x":${JSON.stringify(jwk.x)},"y":${JSON.stringify(jwk.y)}}`;
194
+ const key = ReallyMeJwk.fromJwk(jwk);
195
+ try {
196
+ return codecCanonicalizeJson(key.jwk);
178
197
  }
179
- if (jwk.kty === "AKP") {
180
- return `{"alg":${JSON.stringify(jwk.alg)},"kty":"AKP","pub":${JSON.stringify(jwk.pub)},"use":${JSON.stringify(jwk.use)}}`;
198
+ catch (error) {
199
+ throw mapCodecError(error);
181
200
  }
182
- return `{"alg":${JSON.stringify(jwk.alg)},"crv":${JSON.stringify(jwk.crv)},"kty":"OKP","use":${JSON.stringify(jwk.use)},"x":${JSON.stringify(jwk.x)}}`;
183
201
  };
184
202
  export const ReallyMeJwk = {
185
203
  toJwk(algorithm, publicKey) {
@@ -193,15 +211,15 @@ export const ReallyMeJwk = {
193
211
  crv: ecAlgorithm,
194
212
  kty: "EC",
195
213
  use: "sig",
196
- x: base64urlEncode(uncompressed.slice(1, 33)),
197
- y: base64urlEncode(uncompressed.slice(33, 65)),
214
+ x: codecBase64urlEncode(uncompressed.slice(1, 33)),
215
+ y: codecBase64urlEncode(uncompressed.slice(33, 65)),
198
216
  };
199
217
  }
200
218
  if (spec.kty === "AKP") {
201
219
  return {
202
220
  alg: spec.alg,
203
221
  kty: "AKP",
204
- pub: base64urlEncode(publicKey),
222
+ pub: codecBase64urlEncode(publicKey),
205
223
  use: spec.use,
206
224
  };
207
225
  }
@@ -214,71 +232,89 @@ export const ReallyMeJwk = {
214
232
  crv,
215
233
  kty: "OKP",
216
234
  use: spec.use,
217
- x: base64urlEncode(publicKey),
235
+ x: codecBase64urlEncode(publicKey),
218
236
  };
219
237
  },
220
238
  fromJwk(value) {
221
- const kty = readString(value, "kty");
222
- const spec = kty === "AKP"
223
- ? specFromAlgorithm(readString(value, "alg"))
224
- : specFromCurve(readString(value, "crv"));
225
- if (kty !== spec.kty) {
226
- throw new ReallyMeCryptoError("invalid-input");
227
- }
228
- optionalStringMatches(value, "alg", spec.alg);
229
- optionalStringMatches(value, "use", spec.use);
230
- if (spec.kty === "EC") {
231
- const ecAlgorithm = spec.crv === "P-256" ? "P-256" : "secp256k1";
232
- const x = base64urlDecode(readString(value, "x"));
233
- const y = base64urlDecode(readString(value, "y"));
234
- const publicKey = compressEc(ecAlgorithm, x, y);
235
- ensureLength(publicKey, spec.publicKeyLength);
236
- const jwk = ReallyMeJwk.toJwk(ecAlgorithm, publicKey);
237
- return { algorithm: ecAlgorithm, publicKey, jwk };
238
- }
239
- if (spec.kty === "OKP") {
240
- const crv = spec.crv;
241
- if (crv !== "Ed25519" && crv !== "X25519") {
242
- throw new ReallyMeCryptoError("unsupported-algorithm");
239
+ return withJwkBoundaryErrors(() => {
240
+ rejectPrivateKeyMaterial(value);
241
+ const kty = readString(value, "kty");
242
+ const spec = kty === "AKP"
243
+ ? specFromAlgorithm(readString(value, "alg"))
244
+ : specFromCurve(readString(value, "crv"));
245
+ if (kty !== spec.kty) {
246
+ throw new ReallyMeCryptoError("invalid-input");
243
247
  }
244
- const publicKey = base64urlDecode(readString(value, "x"));
245
- ensureLength(publicKey, spec.publicKeyLength);
246
- const jwk = ReallyMeJwk.toJwk(crv, publicKey);
247
- return { algorithm: crv, publicKey, jwk };
248
- }
249
- const algorithm = readString(value, "alg");
250
- const akpSpec = specFromAlgorithm(algorithm);
251
- const publicKey = base64urlDecode(readString(value, "pub"));
252
- ensureLength(publicKey, akpSpec.publicKeyLength);
253
- switch (algorithm) {
254
- case "ML-DSA-44":
255
- case "ML-DSA-65":
256
- case "ML-DSA-87":
257
- case "ML-KEM-512":
258
- case "ML-KEM-768":
259
- case "ML-KEM-1024":
260
- case "SLH-DSA-SHA2-128s":
261
- case "X-Wing-768":
262
- case "X-Wing-1024": {
263
- const jwk = ReallyMeJwk.toJwk(algorithm, publicKey);
264
- return { algorithm, publicKey, jwk };
248
+ if (spec.kty === "EC") {
249
+ ensureExactMembers(value, ["alg", "crv", "kty", "use", "x", "y"]);
250
+ if (readString(value, "alg") !== spec.alg ||
251
+ readString(value, "use") !== spec.use) {
252
+ throw new ReallyMeCryptoError("invalid-input");
253
+ }
254
+ const ecAlgorithm = spec.crv === "P-256" ? "P-256" : "secp256k1";
255
+ const x = codecBase64urlDecodeCanonical(readString(value, "x"));
256
+ const y = codecBase64urlDecodeCanonical(readString(value, "y"));
257
+ const publicKey = compressEc(ecAlgorithm, x, y);
258
+ ensureLength(publicKey, spec.publicKeyLength);
259
+ const jwk = ReallyMeJwk.toJwk(ecAlgorithm, publicKey);
260
+ return { algorithm: ecAlgorithm, publicKey, jwk };
265
261
  }
266
- default:
267
- throw new ReallyMeCryptoError("unsupported-algorithm");
268
- }
262
+ if (spec.kty === "OKP") {
263
+ ensureExactMembers(value, ["alg", "crv", "kty", "use", "x"]);
264
+ optionalStringMatches(value, "alg", spec.alg);
265
+ optionalStringMatches(value, "use", spec.use);
266
+ const crv = spec.crv;
267
+ if (crv !== "Ed25519" && crv !== "X25519") {
268
+ throw new ReallyMeCryptoError("unsupported-algorithm");
269
+ }
270
+ const publicKey = codecBase64urlDecodeCanonical(readString(value, "x"));
271
+ ensureLength(publicKey, spec.publicKeyLength);
272
+ const jwk = ReallyMeJwk.toJwk(crv, publicKey);
273
+ return { algorithm: crv, publicKey, jwk };
274
+ }
275
+ const algorithm = readString(value, "alg");
276
+ ensureExactMembers(value, ["alg", "kty", "pub", "use"]);
277
+ if (readString(value, "use") !== spec.use) {
278
+ throw new ReallyMeCryptoError("invalid-input");
279
+ }
280
+ const akpSpec = specFromAlgorithm(algorithm);
281
+ const publicKey = codecBase64urlDecodeCanonical(readString(value, "pub"));
282
+ ensureLength(publicKey, akpSpec.publicKeyLength);
283
+ switch (algorithm) {
284
+ case "ML-DSA-44":
285
+ case "ML-DSA-65":
286
+ case "ML-DSA-87":
287
+ case "ML-KEM-512":
288
+ case "ML-KEM-768":
289
+ case "ML-KEM-1024":
290
+ case "SLH-DSA-SHA2-128s":
291
+ case "X-Wing-768": {
292
+ const jwk = ReallyMeJwk.toJwk(algorithm, publicKey);
293
+ return { algorithm, publicKey, jwk };
294
+ }
295
+ default:
296
+ throw new ReallyMeCryptoError("unsupported-algorithm");
297
+ }
298
+ });
269
299
  },
270
300
  toJwks(keys) {
271
301
  return { keys: [...keys] };
272
302
  },
273
303
  fromJwks(value) {
274
- if (typeof value !== "object" || value === null) {
275
- throw new ReallyMeCryptoError("invalid-input");
276
- }
277
- const keys = Reflect.get(value, "keys");
278
- if (!Array.isArray(keys)) {
279
- throw new ReallyMeCryptoError("invalid-input");
280
- }
281
- return { keys: keys.map((key) => ReallyMeJwk.fromJwk(key)) };
304
+ return withJwkBoundaryErrors(() => {
305
+ ensureExactMembers(value, ["keys"]);
306
+ if (typeof value !== "object" || value === null) {
307
+ throw new ReallyMeCryptoError("invalid-input");
308
+ }
309
+ const descriptor = Object.getOwnPropertyDescriptor(value, "keys");
310
+ const keys = descriptor !== undefined && "value" in descriptor
311
+ ? descriptor.value
312
+ : undefined;
313
+ if (!Array.isArray(keys) || keys.length > MAX_JWKS_KEYS) {
314
+ throw new ReallyMeCryptoError("invalid-input");
315
+ }
316
+ return { keys: keys.map((key) => ReallyMeJwk.fromJwk(key)) };
317
+ });
282
318
  },
283
319
  publicKeyBytes(jwk) {
284
320
  return ReallyMeJwk.fromJwk(jwk).publicKey;
package/dist/kmac.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { ReallyMeWasmProvider } from "./wasmProvider.js";
2
+ export declare const KMAC256_MIN_KEY_LENGTH = 32;
3
+ export declare const KMAC256_MAX_KEY_LENGTH = 4096;
4
+ export declare const KMAC256_MAX_CONTEXT_LENGTH = 65536;
5
+ export declare const KMAC256_MAX_CUSTOMIZATION_LENGTH = 4096;
6
+ export declare const KMAC256_MIN_OUTPUT_LENGTH = 1;
7
+ export declare const KMAC256_MAX_OUTPUT_LENGTH = 65536;
8
+ export declare const ReallyMeKmac: {
9
+ readonly deriveKmac256: (key: Uint8Array, context: Uint8Array, customization: Uint8Array, outputLength: number) => Uint8Array;
10
+ readonly deriveKmac256WithProvider: (provider: ReallyMeWasmProvider, key: Uint8Array, context: Uint8Array, customization: Uint8Array, outputLength: number) => Uint8Array;
11
+ };
package/dist/kmac.js ADDED
@@ -0,0 +1,49 @@
1
+ // SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { ReallyMeCryptoError } from "./errors.js";
5
+ import { ensureByteArray, ensureIndependentByteArray, } from "./validateBytes.js";
6
+ import { requireReallyMeWasmProvider } from "./wasmProvider.js";
7
+ export const KMAC256_MIN_KEY_LENGTH = 32;
8
+ export const KMAC256_MAX_KEY_LENGTH = 4_096;
9
+ export const KMAC256_MAX_CONTEXT_LENGTH = 65_536;
10
+ export const KMAC256_MAX_CUSTOMIZATION_LENGTH = 4_096;
11
+ export const KMAC256_MIN_OUTPUT_LENGTH = 1;
12
+ export const KMAC256_MAX_OUTPUT_LENGTH = 65_536;
13
+ const validate = (key, context, customization, outputLength) => {
14
+ ensureByteArray(key);
15
+ ensureByteArray(context);
16
+ ensureByteArray(customization);
17
+ if (key.length < KMAC256_MIN_KEY_LENGTH ||
18
+ key.length > KMAC256_MAX_KEY_LENGTH ||
19
+ context.length > KMAC256_MAX_CONTEXT_LENGTH ||
20
+ customization.length > KMAC256_MAX_CUSTOMIZATION_LENGTH ||
21
+ !Number.isInteger(outputLength) ||
22
+ outputLength < KMAC256_MIN_OUTPUT_LENGTH ||
23
+ outputLength > KMAC256_MAX_OUTPUT_LENGTH) {
24
+ throw new ReallyMeCryptoError("invalid-input");
25
+ }
26
+ };
27
+ const requireDerivedKey = (value, outputLength, inputs) => {
28
+ if (!(value instanceof Uint8Array)) {
29
+ throw new ReallyMeCryptoError("provider-failure");
30
+ }
31
+ // A provider result must have independent ownership. Check overlap before
32
+ // clearing malformed output so validation cannot erase caller-owned input.
33
+ ensureIndependentByteArray(value, inputs);
34
+ if (value.length !== outputLength) {
35
+ value.fill(0);
36
+ throw new ReallyMeCryptoError("provider-failure");
37
+ }
38
+ return value;
39
+ };
40
+ export const ReallyMeKmac = {
41
+ deriveKmac256(key, context, customization, outputLength) {
42
+ validate(key, context, customization, outputLength);
43
+ return requireDerivedKey(requireReallyMeWasmProvider().kmac256Derive(key, context, customization, outputLength), outputLength, [key, context, customization]);
44
+ },
45
+ deriveKmac256WithProvider(provider, key, context, customization, outputLength) {
46
+ validate(key, context, customization, outputLength);
47
+ return requireDerivedKey(provider.kmac256Derive(key, context, customization, outputLength), outputLength, [key, context, customization]);
48
+ },
49
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Best-effort cleanup for caller-owned managed-runtime byte arrays.
3
+ *
4
+ * This overwrites the supplied `Uint8Array` view in place. It does not and
5
+ * cannot clear copies already made by JavaScript engines, WebAssembly
6
+ * marshalling, snapshots, browser developer tools, or native providers.
7
+ */
8
+ export declare const bestEffortClear: (bytes: Uint8Array) => void;
package/dist/memory.js ADDED
@@ -0,0 +1,13 @@
1
+ // SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ /**
5
+ * Best-effort cleanup for caller-owned managed-runtime byte arrays.
6
+ *
7
+ * This overwrites the supplied `Uint8Array` view in place. It does not and
8
+ * cannot clear copies already made by JavaScript engines, WebAssembly
9
+ * marshalling, snapshots, browser developer tools, or native providers.
10
+ */
11
+ export const bestEffortClear = (bytes) => {
12
+ bytes.fill(0);
13
+ };
package/dist/mlDsa.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ReallyMeSignatureAlgorithm } from "./algorithms.js";
2
+ import type { ReallyMeWasmProvider } from "./wasmProvider.js";
2
3
  export declare const ML_DSA_44_PUBLIC_KEY_LENGTH = 1312;
3
4
  export declare const ML_DSA_44_SIGNATURE_LENGTH = 2420;
4
5
  export declare const ML_DSA_65_PUBLIC_KEY_LENGTH = 1952;
@@ -12,7 +13,11 @@ export type ReallyMeMlDsaKeyPair = Readonly<{
12
13
  }>;
13
14
  export declare const ReallyMeMlDsa: {
14
15
  readonly generateKeyPair: (algorithm: ReallyMeSignatureAlgorithm) => ReallyMeMlDsaKeyPair;
16
+ readonly generateKeyPairWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeSignatureAlgorithm) => ReallyMeMlDsaKeyPair;
15
17
  readonly deriveKeyPair: (algorithm: ReallyMeSignatureAlgorithm, secretKey: Uint8Array) => ReallyMeMlDsaKeyPair;
18
+ readonly deriveKeyPairWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeSignatureAlgorithm, secretKey: Uint8Array) => ReallyMeMlDsaKeyPair;
16
19
  readonly sign: (algorithm: ReallyMeSignatureAlgorithm, message: Uint8Array, secretKey: Uint8Array) => Uint8Array;
20
+ readonly signWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeSignatureAlgorithm, message: Uint8Array, secretKey: Uint8Array) => Uint8Array;
17
21
  readonly verify: (algorithm: ReallyMeSignatureAlgorithm, signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array) => void;
22
+ readonly verifyWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeSignatureAlgorithm, signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array) => void;
18
23
  };
package/dist/mlDsa.js CHANGED
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { ReallyMeCryptoError } from "./errors.js";
5
- import { ensureBytes, readByteArrayProperty } from "./validateBytes.js";
5
+ import { ensureByteArrayAtMost, ensureBytes, ensureIndependentByteArray, MAX_CRYPTO_INPUT_LENGTH, readIndependentByteArrayProperty, } from "./validateBytes.js";
6
6
  import { requireReallyMeWasmProvider } from "./wasmProvider.js";
7
7
  export const ML_DSA_44_PUBLIC_KEY_LENGTH = 1_312;
8
8
  export const ML_DSA_44_SIGNATURE_LENGTH = 2_420;
@@ -13,6 +13,9 @@ export const ML_DSA_87_SIGNATURE_LENGTH = 4_627;
13
13
  export const ML_DSA_SECRET_KEY_LENGTH = 32;
14
14
  const mlDsaSuite = (algorithm) => {
15
15
  const provider = requireReallyMeWasmProvider();
16
+ return mlDsaSuiteWithProvider(algorithm, provider);
17
+ };
18
+ const mlDsaSuiteWithProvider = (algorithm, provider) => {
16
19
  switch (algorithm) {
17
20
  case "ML-DSA-44":
18
21
  return {
@@ -45,15 +48,22 @@ const mlDsaSuite = (algorithm) => {
45
48
  throw new ReallyMeCryptoError("unsupported-algorithm");
46
49
  }
47
50
  };
48
- const readKeyPair = (value, suite) => ({
49
- publicKey: readByteArrayProperty(value, "publicKey", suite.publicKeyLength),
50
- secretKey: readByteArrayProperty(value, "secretKey", ML_DSA_SECRET_KEY_LENGTH),
51
- });
52
- const readSignature = (value, suite) => {
51
+ const readKeyPair = (value, suite, inputs = []) => {
52
+ const publicKey = readIndependentByteArrayProperty(value, "publicKey", suite.publicKeyLength, inputs);
53
+ const secretKey = readIndependentByteArrayProperty(value, "secretKey", ML_DSA_SECRET_KEY_LENGTH, [...inputs, publicKey]);
54
+ return { publicKey, secretKey };
55
+ };
56
+ const readSignature = (value, suite, inputs) => {
53
57
  if (!(value instanceof Uint8Array)) {
54
58
  throw new ReallyMeCryptoError("provider-failure");
55
59
  }
56
- ensureBytes(value, suite.signatureLength);
60
+ // Signature bytes are public, but their shape and ownership are provider
61
+ // postconditions. A malformed provider must never be reported as bad caller
62
+ // input, and an aliased result must not be mistaken for provider-owned data.
63
+ ensureIndependentByteArray(value, inputs);
64
+ if (value.length !== suite.signatureLength) {
65
+ throw new ReallyMeCryptoError("provider-failure");
66
+ }
57
67
  return value;
58
68
  };
59
69
  const readVoid = (value) => {
@@ -66,20 +76,44 @@ export const ReallyMeMlDsa = {
66
76
  const suite = mlDsaSuite(algorithm);
67
77
  return readKeyPair(suite.generateKeyPair(), suite);
68
78
  },
79
+ generateKeyPairWithProvider(provider, algorithm) {
80
+ const suite = mlDsaSuiteWithProvider(algorithm, provider);
81
+ return readKeyPair(suite.generateKeyPair(), suite);
82
+ },
69
83
  deriveKeyPair(algorithm, secretKey) {
70
84
  // Import an existing FIPS 204 seed and reconstruct its public key. Do not
71
85
  // feed passwords or other low-entropy material here.
72
86
  const suite = mlDsaSuite(algorithm);
73
87
  ensureBytes(secretKey, ML_DSA_SECRET_KEY_LENGTH);
74
- return readKeyPair(suite.deriveKeyPair(secretKey), suite);
88
+ return readKeyPair(suite.deriveKeyPair(secretKey), suite, [secretKey]);
89
+ },
90
+ deriveKeyPairWithProvider(provider, algorithm, secretKey) {
91
+ const suite = mlDsaSuiteWithProvider(algorithm, provider);
92
+ ensureBytes(secretKey, ML_DSA_SECRET_KEY_LENGTH);
93
+ return readKeyPair(suite.deriveKeyPair(secretKey), suite, [secretKey]);
75
94
  },
76
95
  sign(algorithm, message, secretKey) {
77
96
  const suite = mlDsaSuite(algorithm);
97
+ ensureByteArrayAtMost(message, MAX_CRYPTO_INPUT_LENGTH);
98
+ ensureBytes(secretKey, ML_DSA_SECRET_KEY_LENGTH);
99
+ return readSignature(suite.sign(secretKey, message), suite, [secretKey, message]);
100
+ },
101
+ signWithProvider(provider, algorithm, message, secretKey) {
102
+ const suite = mlDsaSuiteWithProvider(algorithm, provider);
103
+ ensureByteArrayAtMost(message, MAX_CRYPTO_INPUT_LENGTH);
78
104
  ensureBytes(secretKey, ML_DSA_SECRET_KEY_LENGTH);
79
- return readSignature(suite.sign(secretKey, message), suite);
105
+ return readSignature(suite.sign(secretKey, message), suite, [secretKey, message]);
80
106
  },
81
107
  verify(algorithm, signature, message, publicKey) {
82
108
  const suite = mlDsaSuite(algorithm);
109
+ ensureByteArrayAtMost(message, MAX_CRYPTO_INPUT_LENGTH);
110
+ ensureBytes(publicKey, suite.publicKeyLength);
111
+ ensureBytes(signature, suite.signatureLength);
112
+ readVoid(suite.verify(publicKey, message, signature));
113
+ },
114
+ verifyWithProvider(provider, algorithm, signature, message, publicKey) {
115
+ const suite = mlDsaSuiteWithProvider(algorithm, provider);
116
+ ensureByteArrayAtMost(message, MAX_CRYPTO_INPUT_LENGTH);
83
117
  ensureBytes(publicKey, suite.publicKeyLength);
84
118
  ensureBytes(signature, suite.signatureLength);
85
119
  readVoid(suite.verify(publicKey, message, signature));
package/dist/mlKem.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ReallyMeKemAlgorithm } from "./algorithms.js";
2
+ import type { ReallyMeWasmProvider } from "./wasmProvider.js";
2
3
  export declare const ML_KEM_512_PUBLIC_KEY_LENGTH = 800;
3
4
  export declare const ML_KEM_512_CIPHERTEXT_LENGTH = 768;
4
5
  export declare const ML_KEM_768_PUBLIC_KEY_LENGTH = 1184;
@@ -6,7 +7,6 @@ export declare const ML_KEM_768_CIPHERTEXT_LENGTH = 1088;
6
7
  export declare const ML_KEM_1024_PUBLIC_KEY_LENGTH = 1568;
7
8
  export declare const ML_KEM_1024_CIPHERTEXT_LENGTH = 1568;
8
9
  export declare const ML_KEM_SECRET_KEY_LENGTH = 64;
9
- export declare const ML_KEM_ENCAPSULATION_RANDOMNESS_LENGTH = 32;
10
10
  export declare const ML_KEM_SHARED_SECRET_LENGTH = 32;
11
11
  export type ReallyMeMlKemKeyPair = Readonly<{
12
12
  publicKey: Uint8Array;
@@ -18,8 +18,11 @@ export type ReallyMeMlKemEncapsulation = Readonly<{
18
18
  }>;
19
19
  export declare const ReallyMeMlKem: {
20
20
  readonly generateKeyPair: (algorithm: ReallyMeKemAlgorithm) => ReallyMeMlKemKeyPair;
21
+ readonly generateKeyPairWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeKemAlgorithm) => ReallyMeMlKemKeyPair;
21
22
  readonly deriveKeyPair: (algorithm: ReallyMeKemAlgorithm, secretKey: Uint8Array) => ReallyMeMlKemKeyPair;
23
+ readonly deriveKeyPairWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeKemAlgorithm, secretKey: Uint8Array) => ReallyMeMlKemKeyPair;
22
24
  readonly encapsulate: (algorithm: ReallyMeKemAlgorithm, publicKey: Uint8Array) => ReallyMeMlKemEncapsulation;
23
- readonly encapsulateDeterministicallyForTest: (algorithm: ReallyMeKemAlgorithm, publicKey: Uint8Array, randomness: Uint8Array) => ReallyMeMlKemEncapsulation;
25
+ readonly encapsulateWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeKemAlgorithm, publicKey: Uint8Array) => ReallyMeMlKemEncapsulation;
24
26
  readonly decapsulate: (algorithm: ReallyMeKemAlgorithm, ciphertext: Uint8Array, secretKey: Uint8Array) => Uint8Array;
27
+ readonly decapsulateWithProvider: (provider: ReallyMeWasmProvider, algorithm: ReallyMeKemAlgorithm, ciphertext: Uint8Array, secretKey: Uint8Array) => Uint8Array;
25
28
  };