@cardanowall/crypto-core 0.0.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 (81) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +192 -0
  3. package/dist/aead.cjs +44 -0
  4. package/dist/aead.cjs.map +1 -0
  5. package/dist/aead.d.cts +38 -0
  6. package/dist/aead.d.ts +38 -0
  7. package/dist/aead.js +38 -0
  8. package/dist/aead.js.map +1 -0
  9. package/dist/cbor.cjs +69 -0
  10. package/dist/cbor.cjs.map +1 -0
  11. package/dist/cbor.d.cts +17 -0
  12. package/dist/cbor.d.ts +17 -0
  13. package/dist/cbor.js +64 -0
  14. package/dist/cbor.js.map +1 -0
  15. package/dist/cose.cjs +430 -0
  16. package/dist/cose.cjs.map +1 -0
  17. package/dist/cose.d.cts +72 -0
  18. package/dist/cose.d.ts +72 -0
  19. package/dist/cose.js +398 -0
  20. package/dist/cose.js.map +1 -0
  21. package/dist/hash.cjs +165 -0
  22. package/dist/hash.cjs.map +1 -0
  23. package/dist/hash.d.cts +30 -0
  24. package/dist/hash.d.ts +30 -0
  25. package/dist/hash.js +155 -0
  26. package/dist/hash.js.map +1 -0
  27. package/dist/index.cjs +1856 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +12 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.js +1759 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/kdf.cjs +26 -0
  34. package/dist/kdf.cjs.map +1 -0
  35. package/dist/kdf.d.cts +25 -0
  36. package/dist/kdf.d.ts +25 -0
  37. package/dist/kdf.js +23 -0
  38. package/dist/kdf.js.map +1 -0
  39. package/dist/kem.cjs +86 -0
  40. package/dist/kem.cjs.map +1 -0
  41. package/dist/kem.d.cts +47 -0
  42. package/dist/kem.d.ts +47 -0
  43. package/dist/kem.js +73 -0
  44. package/dist/kem.js.map +1 -0
  45. package/dist/merkle.cjs +284 -0
  46. package/dist/merkle.cjs.map +1 -0
  47. package/dist/merkle.d.cts +24 -0
  48. package/dist/merkle.d.ts +24 -0
  49. package/dist/merkle.js +279 -0
  50. package/dist/merkle.js.map +1 -0
  51. package/dist/recipient.cjs +141 -0
  52. package/dist/recipient.cjs.map +1 -0
  53. package/dist/recipient.d.cts +16 -0
  54. package/dist/recipient.d.ts +16 -0
  55. package/dist/recipient.js +135 -0
  56. package/dist/recipient.js.map +1 -0
  57. package/dist/sealed-poe.cjs +851 -0
  58. package/dist/sealed-poe.cjs.map +1 -0
  59. package/dist/sealed-poe.d.cts +134 -0
  60. package/dist/sealed-poe.d.ts +134 -0
  61. package/dist/sealed-poe.js +838 -0
  62. package/dist/sealed-poe.js.map +1 -0
  63. package/dist/seed-derive.cjs +129 -0
  64. package/dist/seed-derive.cjs.map +1 -0
  65. package/dist/seed-derive.d.cts +28 -0
  66. package/dist/seed-derive.d.ts +28 -0
  67. package/dist/seed-derive.js +101 -0
  68. package/dist/seed-derive.js.map +1 -0
  69. package/dist/sig.cjs +77 -0
  70. package/dist/sig.cjs.map +1 -0
  71. package/dist/sig.d.cts +17 -0
  72. package/dist/sig.d.ts +17 -0
  73. package/dist/sig.js +53 -0
  74. package/dist/sig.js.map +1 -0
  75. package/dist/util.cjs +36 -0
  76. package/dist/util.cjs.map +1 -0
  77. package/dist/util.d.cts +5 -0
  78. package/dist/util.d.ts +5 -0
  79. package/dist/util.js +33 -0
  80. package/dist/util.js.map +1 -0
  81. package/package.json +122 -0
@@ -0,0 +1,851 @@
1
+ 'use strict';
2
+
3
+ var utils_js = require('@noble/ciphers/utils.js');
4
+ var hmac_js = require('@noble/hashes/hmac.js');
5
+ var sha2_js = require('@noble/hashes/sha2.js');
6
+ var chacha_js = require('@noble/ciphers/chacha.js');
7
+ var hkdf_js = require('@noble/hashes/hkdf.js');
8
+ var hybrid_js = require('@noble/post-quantum/hybrid.js');
9
+ var ed25519_js = require('@noble/curves/ed25519.js');
10
+ var cbor2 = require('cbor2');
11
+ var sorts = require('cbor2/sorts');
12
+
13
+ // src/sealed-poe/wrap.ts
14
+
15
+ // src/aead/errors.ts
16
+ var AeadVerificationError = class extends Error {
17
+ code = "aead_verification_failed";
18
+ constructor(message, options) {
19
+ super(message, options);
20
+ this.name = "AeadVerificationError";
21
+ }
22
+ };
23
+
24
+ // src/aead/chacha20-poly1305.ts
25
+ function chacha20Poly1305Encrypt(opts) {
26
+ return chacha_js.chacha20poly1305(opts.key, opts.nonce, opts.aad).encrypt(opts.plaintext);
27
+ }
28
+ function chacha20Poly1305Decrypt(opts) {
29
+ try {
30
+ return chacha_js.chacha20poly1305(opts.key, opts.nonce, opts.aad).decrypt(opts.ciphertext);
31
+ } catch (cause) {
32
+ throw new AeadVerificationError("chacha20-poly1305 decrypt failed", { cause });
33
+ }
34
+ }
35
+ function xchacha20Poly1305Encrypt(opts) {
36
+ return chacha_js.xchacha20poly1305(opts.key, opts.nonce, opts.aad).encrypt(opts.plaintext);
37
+ }
38
+ function xchacha20Poly1305Decrypt(opts) {
39
+ try {
40
+ return chacha_js.xchacha20poly1305(opts.key, opts.nonce, opts.aad).decrypt(opts.ciphertext);
41
+ } catch (cause) {
42
+ throw new AeadVerificationError("xchacha20-poly1305 decrypt failed", { cause });
43
+ }
44
+ }
45
+ function hkdfSha256(opts) {
46
+ return hkdf_js.hkdf(sha2_js.sha256, opts.ikm, opts.salt, opts.info, opts.length);
47
+ }
48
+ var MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216;
49
+ var MLKEM768X25519_ENC_LENGTH = 1120;
50
+ var MLKEM768X25519_SEED_LENGTH = 32;
51
+ var MLKEM768X25519_ESEED_LENGTH = 64;
52
+ function mlkem768x25519Encapsulate(opts) {
53
+ if (opts.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {
54
+ throw new Error(
55
+ `mlkem768x25519 public key must be ${MLKEM768X25519_PUBLIC_KEY_LENGTH} bytes, got ${opts.publicKey.length}`
56
+ );
57
+ }
58
+ if (opts.eseed !== void 0 && opts.eseed.length !== MLKEM768X25519_ESEED_LENGTH) {
59
+ throw new Error(
60
+ `mlkem768x25519 eseed must be ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${opts.eseed.length}`
61
+ );
62
+ }
63
+ const { cipherText, sharedSecret } = hybrid_js.XWing.encapsulate(opts.publicKey, opts.eseed);
64
+ return { enc: cipherText, ss: sharedSecret };
65
+ }
66
+ function mlkem768x25519Decapsulate(opts) {
67
+ if (opts.secretSeed.length !== MLKEM768X25519_SEED_LENGTH) {
68
+ throw new Error(
69
+ `mlkem768x25519 secret seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${opts.secretSeed.length}`
70
+ );
71
+ }
72
+ if (opts.enc.length !== MLKEM768X25519_ENC_LENGTH) {
73
+ throw new Error(
74
+ `mlkem768x25519 enc must be ${MLKEM768X25519_ENC_LENGTH} bytes, got ${opts.enc.length}`
75
+ );
76
+ }
77
+ return hybrid_js.XWing.decapsulate(opts.enc, opts.secretSeed);
78
+ }
79
+ var X25519LowOrderPointError = class extends Error {
80
+ code = "X25519_LOW_ORDER_POINT";
81
+ constructor(options) {
82
+ super("x25519 ECDH rejected: peer public key is a small-order point", options);
83
+ this.name = "X25519LowOrderPointError";
84
+ }
85
+ };
86
+ var NOBLE_LOW_ORDER_MESSAGE = "invalid private or public key received";
87
+ function x25519PublicKey(opts) {
88
+ return ed25519_js.x25519.getPublicKey(opts.secretKey);
89
+ }
90
+ function x25519Ecdh(opts) {
91
+ try {
92
+ return ed25519_js.x25519.getSharedSecret(opts.secretKey, opts.theirPublicKey);
93
+ } catch (e) {
94
+ if (e instanceof Error && e.message === NOBLE_LOW_ORDER_MESSAGE) {
95
+ throw new X25519LowOrderPointError({ cause: e });
96
+ }
97
+ throw e;
98
+ }
99
+ }
100
+
101
+ // src/sealed-poe/errors.ts
102
+ var EciesSealedPoeError = class extends Error {
103
+ code;
104
+ constructor(code, message, options) {
105
+ super(message, options);
106
+ this.name = "EciesSealedPoeError";
107
+ this.code = code;
108
+ }
109
+ };
110
+ function encodeCanonicalCbor(value) {
111
+ return cbor2.encode(value, {
112
+ cde: true,
113
+ collapseBigInts: true,
114
+ rejectDuplicateKeys: true,
115
+ sortKeys: sorts.sortCoreDeterministic
116
+ });
117
+ }
118
+
119
+ // src/sealed-poe/slots-codec.ts
120
+ var CHUNK_MAX_BYTES = 64;
121
+ function chunkKemCt(value) {
122
+ if (value.length === 0) {
123
+ throw new Error("chunkKemCt: refusing to chunk an empty byte string");
124
+ }
125
+ const chunks = [];
126
+ for (let i = 0; i < value.length; i += CHUNK_MAX_BYTES) {
127
+ chunks.push(value.subarray(i, Math.min(i + CHUNK_MAX_BYTES, value.length)));
128
+ }
129
+ return chunks;
130
+ }
131
+ function joinKemCt(chunks) {
132
+ let total = 0;
133
+ for (const c of chunks) total += c.length;
134
+ const out = new Uint8Array(total);
135
+ let offset = 0;
136
+ for (const c of chunks) {
137
+ out.set(c, offset);
138
+ offset += c.length;
139
+ }
140
+ return out;
141
+ }
142
+ function slotsToMacCbor(slots, kem) {
143
+ let value;
144
+ if (kem === "x25519") {
145
+ value = slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
146
+ } else {
147
+ value = slots.map((s) => ({
148
+ // Canonicalize the chunk boundaries before the MAC commits to them:
149
+ // reassemble the logical ciphertext and re-split into canonical ≤ 64-byte
150
+ // chunks. The on-wire `kem_ct` array is a transport detail (the Cardano
151
+ // ledger's 64-byte metadatum cap), and a hostile or non-canonical chunking
152
+ // ([1, 63, …] instead of [64, …]) reassembles to the SAME bytes — so the
153
+ // MAC must be invariant to it. Committing to the verbatim wire chunks would
154
+ // let an attacker re-chunk an honest envelope and break the slots_mac match
155
+ // for an honest recipient. Honest (already-64B-chunked) records are
156
+ // unchanged; a real byte flip still changes the reassembled bytes and is
157
+ // still rejected.
158
+ kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
159
+ wrap: s.wrap
160
+ }));
161
+ }
162
+ return encodeCanonicalCbor(value);
163
+ }
164
+
165
+ // src/sealed-poe/wrap.ts
166
+ var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
167
+ var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
168
+ "cardano-poe-kek-mlkem768x25519-v1"
169
+ );
170
+ var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
171
+ "cardano-poe-slots-mac-v1"
172
+ );
173
+ var ZERO_NONCE_12 = new Uint8Array(12);
174
+ var EMPTY_SALT = new Uint8Array(0);
175
+ var X25519_PUBLIC_KEY_LENGTH = 32;
176
+ var X25519_SECRET_KEY_LENGTH = 32;
177
+ var CEK_LENGTH = 32;
178
+ var NONCE_LENGTH = 24;
179
+ var WRAP_LENGTH = 48;
180
+ var SLOTS_MAC_LENGTH = 32;
181
+ if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
182
+ throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
183
+ }
184
+ if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
185
+ throw new Error(
186
+ "CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
187
+ );
188
+ }
189
+ if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
190
+ throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
191
+ }
192
+ if (ZERO_NONCE_12.length !== 12) {
193
+ throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
194
+ }
195
+ function concat(a, b) {
196
+ const out = new Uint8Array(a.length + b.length);
197
+ out.set(a, 0);
198
+ out.set(b, a.length);
199
+ return out;
200
+ }
201
+ function uniformIndexBelow(m) {
202
+ const limit = 4294967296 - 4294967296 % m;
203
+ const buf = new Uint32Array(1);
204
+ let x;
205
+ do {
206
+ crypto.getRandomValues(buf);
207
+ x = buf[0];
208
+ } while (x >= limit);
209
+ return x % m;
210
+ }
211
+ function csprngShuffle(arr) {
212
+ for (let i = arr.length - 1; i > 0; i--) {
213
+ const j = uniformIndexBelow(i + 1);
214
+ const tmp = arr[i];
215
+ arr[i] = arr[j];
216
+ arr[j] = tmp;
217
+ }
218
+ }
219
+ function wrapSlotX25519(args) {
220
+ const privEph = args.privEph ?? utils_js.randomBytes(X25519_SECRET_KEY_LENGTH);
221
+ if (privEph.length !== X25519_SECRET_KEY_LENGTH) {
222
+ throw new EciesSealedPoeError(
223
+ "INVALID_EPHEMERAL_SECRET_LENGTH",
224
+ `ephemeralSecrets[${args.slotIdx}] MUST be exactly ${X25519_SECRET_KEY_LENGTH} bytes, got ${privEph.length}`
225
+ );
226
+ }
227
+ const epk = x25519PublicKey({ secretKey: privEph });
228
+ const shared = x25519Ecdh({ secretKey: privEph, theirPublicKey: args.pubR });
229
+ const kek = hkdfSha256({
230
+ ikm: shared,
231
+ salt: concat(epk, args.pubR),
232
+ info: CARDANO_POE_HKDF_INFO_KEK,
233
+ length: 32
234
+ });
235
+ const wrap = chacha20Poly1305Encrypt({
236
+ key: kek,
237
+ nonce: ZERO_NONCE_12,
238
+ aad: CARDANO_POE_HKDF_INFO_KEK,
239
+ plaintext: args.cek
240
+ });
241
+ if (wrap.length !== WRAP_LENGTH) {
242
+ throw new Error(`internal: wrap.length=${wrap.length}, expected ${WRAP_LENGTH}`);
243
+ }
244
+ return { epk, wrap };
245
+ }
246
+ function wrapSlotMlkem768X25519(args) {
247
+ const { enc, ss } = mlkem768x25519Encapsulate({
248
+ publicKey: args.pubR,
249
+ ...args.eseed !== void 0 ? { eseed: args.eseed } : {}
250
+ });
251
+ if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
252
+ throw new Error(`internal: enc.length=${enc.length}, expected ${MLKEM768X25519_ENC_LENGTH}`);
253
+ }
254
+ const kek = hkdfSha256({
255
+ ikm: ss,
256
+ salt: EMPTY_SALT,
257
+ info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
258
+ length: 32
259
+ });
260
+ const wrap = chacha20Poly1305Encrypt({
261
+ key: kek,
262
+ nonce: ZERO_NONCE_12,
263
+ aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
264
+ plaintext: args.cek
265
+ });
266
+ if (wrap.length !== WRAP_LENGTH) {
267
+ throw new Error(`internal: wrap.length=${wrap.length}, expected ${WRAP_LENGTH}`);
268
+ }
269
+ return { kem_ct: chunkKemCt(enc), wrap };
270
+ }
271
+ function eciesSealedPoeWrap(args) {
272
+ const { plaintext, recipientPublicKeys } = args;
273
+ const kem = args.kem ?? "x25519";
274
+ const n = recipientPublicKeys.length;
275
+ if (n < 1) {
276
+ throw new EciesSealedPoeError(
277
+ "ENC_SLOTS_EMPTY",
278
+ `recipientPublicKeys.length=${n} must be >= 1`
279
+ );
280
+ }
281
+ const expectedPubLen = kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH : MLKEM768X25519_PUBLIC_KEY_LENGTH;
282
+ for (let i = 0; i < n; i++) {
283
+ const pub = recipientPublicKeys[i];
284
+ if (pub === void 0 || pub.length !== expectedPubLen) {
285
+ throw new EciesSealedPoeError(
286
+ "KEM_EPK_LENGTH_MISMATCH",
287
+ `recipientPublicKeys[${i}] MUST be exactly ${expectedPubLen} bytes for kem='${kem}'`
288
+ );
289
+ }
290
+ }
291
+ if (kem === "x25519") {
292
+ if (args.eseeds !== void 0) {
293
+ throw new EciesSealedPoeError(
294
+ "EPHEMERAL_SECRETS_COUNT_MISMATCH",
295
+ "eseeds is an X-Wing (mlkem768x25519) override and MUST NOT be supplied for kem='x25519'"
296
+ );
297
+ }
298
+ if (args.ephemeralSecrets !== void 0 && args.ephemeralSecrets.length !== n) {
299
+ throw new EciesSealedPoeError(
300
+ "EPHEMERAL_SECRETS_COUNT_MISMATCH",
301
+ `ephemeralSecrets.length=${args.ephemeralSecrets.length} must match recipientPublicKeys.length=${n}`
302
+ );
303
+ }
304
+ } else {
305
+ if (args.ephemeralSecrets !== void 0) {
306
+ throw new EciesSealedPoeError(
307
+ "EPHEMERAL_SECRETS_COUNT_MISMATCH",
308
+ "ephemeralSecrets is an X25519 override and MUST NOT be supplied for kem='mlkem768x25519'"
309
+ );
310
+ }
311
+ if (args.eseeds !== void 0) {
312
+ if (args.eseeds.length !== n) {
313
+ throw new EciesSealedPoeError(
314
+ "EPHEMERAL_SECRETS_COUNT_MISMATCH",
315
+ `eseeds.length=${args.eseeds.length} must match recipientPublicKeys.length=${n}`
316
+ );
317
+ }
318
+ for (let i = 0; i < n; i++) {
319
+ const eseed = args.eseeds[i];
320
+ if (eseed.length !== MLKEM768X25519_ESEED_LENGTH) {
321
+ throw new EciesSealedPoeError(
322
+ "INVALID_EPHEMERAL_SECRET_LENGTH",
323
+ `eseeds[${i}] MUST be exactly ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${eseed.length}`
324
+ );
325
+ }
326
+ }
327
+ }
328
+ }
329
+ const cek = args.cek ?? utils_js.randomBytes(CEK_LENGTH);
330
+ const nonce = args.nonce ?? utils_js.randomBytes(NONCE_LENGTH);
331
+ if (cek.length !== CEK_LENGTH) {
332
+ throw new EciesSealedPoeError(
333
+ "INVALID_CEK_LENGTH",
334
+ `cek MUST be exactly ${CEK_LENGTH} bytes, got ${cek.length}`
335
+ );
336
+ }
337
+ if (nonce.length !== NONCE_LENGTH) {
338
+ throw new EciesSealedPoeError(
339
+ "NONCE_LENGTH_MISMATCH",
340
+ `nonce MUST be exactly ${NONCE_LENGTH} bytes, got ${nonce.length}`
341
+ );
342
+ }
343
+ let envelope;
344
+ if (kem === "x25519") {
345
+ const slots = [];
346
+ for (let i = 0; i < n; i++) {
347
+ slots.push(
348
+ wrapSlotX25519({
349
+ pubR: recipientPublicKeys[i],
350
+ privEph: args.ephemeralSecrets ? args.ephemeralSecrets[i] : void 0,
351
+ cek,
352
+ slotIdx: i
353
+ })
354
+ );
355
+ }
356
+ if (args.skipShuffle !== true) {
357
+ csprngShuffle(slots);
358
+ }
359
+ const slotsMac = computeSlotsMac(cek, slots, "x25519");
360
+ envelope = {
361
+ scheme: 1,
362
+ aead: "xchacha20-poly1305",
363
+ kem: "x25519",
364
+ nonce,
365
+ slots,
366
+ slots_mac: slotsMac
367
+ };
368
+ } else {
369
+ const slots = [];
370
+ for (let i = 0; i < n; i++) {
371
+ slots.push(
372
+ wrapSlotMlkem768X25519({
373
+ pubR: recipientPublicKeys[i],
374
+ eseed: args.eseeds ? args.eseeds[i] : void 0,
375
+ cek
376
+ })
377
+ );
378
+ }
379
+ if (args.skipShuffle !== true) {
380
+ csprngShuffle(slots);
381
+ }
382
+ const slotsMac = computeSlotsMac(cek, slots, "mlkem768x25519");
383
+ envelope = {
384
+ scheme: 1,
385
+ aead: "xchacha20-poly1305",
386
+ kem: "mlkem768x25519",
387
+ nonce,
388
+ slots,
389
+ slots_mac: slotsMac
390
+ };
391
+ }
392
+ const adContent = concat(nonce, envelope.slots_mac);
393
+ const ciphertext = xchacha20Poly1305Encrypt({
394
+ key: cek,
395
+ nonce,
396
+ aad: adContent,
397
+ plaintext
398
+ });
399
+ return { envelope, ciphertext };
400
+ }
401
+ function computeSlotsMac(cek, slots, kem) {
402
+ const hmacKey = hkdfSha256({
403
+ ikm: cek,
404
+ salt: EMPTY_SALT,
405
+ info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
406
+ length: 32
407
+ });
408
+ const slotsCbor = slotsToMacCbor(slots, kem);
409
+ const slotsMac = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
410
+ if (slotsMac.length !== SLOTS_MAC_LENGTH) {
411
+ throw new Error(`internal: slots_mac.length=${slotsMac.length}, expected ${SLOTS_MAC_LENGTH}`);
412
+ }
413
+ return slotsMac;
414
+ }
415
+
416
+ // src/util/compare-ct.ts
417
+ function compareCt(a, b) {
418
+ if (a.length !== b.length) return false;
419
+ let diff = 0;
420
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
421
+ return diff === 0;
422
+ }
423
+
424
+ // src/sealed-poe/unwrap.ts
425
+ function selectBundleSecrets(envelope, bundle) {
426
+ return envelope.kem === "x25519" ? bundle.x25519PrivateKeys : bundle.mlkem768x25519SecretSeeds;
427
+ }
428
+ var ZERO_NONCE_122 = new Uint8Array(12);
429
+ var EMPTY_SALT2 = new Uint8Array(0);
430
+ var X25519_SECRET_KEY_LENGTH2 = 32;
431
+ var X25519_PUBLIC_KEY_LENGTH2 = 32;
432
+ var NONCE_LENGTH2 = 24;
433
+ var WRAP_LENGTH2 = 48;
434
+ var SLOTS_MAC_LENGTH2 = 32;
435
+ function concat2(a, b) {
436
+ const out = new Uint8Array(a.length + b.length);
437
+ out.set(a, 0);
438
+ out.set(b, a.length);
439
+ return out;
440
+ }
441
+ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
442
+ if (envelope.scheme !== 1) {
443
+ throw new EciesSealedPoeError(
444
+ "UNSUPPORTED_ENC_VERSION",
445
+ `envelope.scheme=${String(envelope.scheme)} unsupported (expected 1)`
446
+ );
447
+ }
448
+ if (envelope.aead !== "xchacha20-poly1305") {
449
+ throw new EciesSealedPoeError(
450
+ "UNSUPPORTED_AEAD_ALG",
451
+ `envelope.aead=${String(envelope.aead)} unsupported (expected 'xchacha20-poly1305')`
452
+ );
453
+ }
454
+ if (envelope.kem !== "x25519" && envelope.kem !== "mlkem768x25519") {
455
+ throw new EciesSealedPoeError(
456
+ "UNSUPPORTED_KEM_ALG",
457
+ `envelope.kem=${String(envelope.kem)} unsupported (expected 'x25519' or 'mlkem768x25519')`
458
+ );
459
+ }
460
+ const n = envelope.slots.length;
461
+ if (n < 1) {
462
+ throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
463
+ }
464
+ if (envelope.nonce.length !== NONCE_LENGTH2) {
465
+ throw new EciesSealedPoeError(
466
+ "NONCE_LENGTH_MISMATCH",
467
+ `envelope.nonce MUST be exactly ${NONCE_LENGTH2} bytes, got ${envelope.nonce.length}`
468
+ );
469
+ }
470
+ if (envelope.slots_mac.length !== SLOTS_MAC_LENGTH2) {
471
+ throw new EciesSealedPoeError(
472
+ "ENC_SLOTS_MAC_INVALID_LENGTH",
473
+ `envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
474
+ );
475
+ }
476
+ if (envelope.kem === "x25519") {
477
+ for (let i = 0; i < n; i++) {
478
+ const slot = envelope.slots[i];
479
+ if (slot.epk.length !== X25519_PUBLIC_KEY_LENGTH2) {
480
+ throw new EciesSealedPoeError(
481
+ "KEM_EPK_LENGTH_MISMATCH",
482
+ `envelope.slots[${i}].epk MUST be exactly ${X25519_PUBLIC_KEY_LENGTH2} bytes, got ${slot.epk.length}`
483
+ );
484
+ }
485
+ if (slot.wrap.length !== WRAP_LENGTH2) {
486
+ throw new EciesSealedPoeError(
487
+ "WRAP_LENGTH_MISMATCH",
488
+ `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
489
+ );
490
+ }
491
+ }
492
+ } else {
493
+ for (let i = 0; i < n; i++) {
494
+ const slot = envelope.slots[i];
495
+ const enc = joinKemCt(slot.kem_ct);
496
+ if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
497
+ throw new EciesSealedPoeError(
498
+ "KEM_CT_LENGTH_MISMATCH",
499
+ `envelope.slots[${i}].kem_ct MUST reassemble to exactly ${MLKEM768X25519_ENC_LENGTH} bytes, got ${enc.length}`
500
+ );
501
+ }
502
+ if (slot.wrap.length !== WRAP_LENGTH2) {
503
+ throw new EciesSealedPoeError(
504
+ "WRAP_LENGTH_MISMATCH",
505
+ `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
506
+ );
507
+ }
508
+ }
509
+ }
510
+ if (multiPrivKeys !== void 0) {
511
+ for (let i = 0; i < multiPrivKeys.length; i++) {
512
+ if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
513
+ throw new EciesSealedPoeError(
514
+ "INVALID_RECIPIENT_KEY",
515
+ `recipientSecretKeys[${i}] MUST be exactly ${X25519_SECRET_KEY_LENGTH2} bytes, got ${multiPrivKeys[i].length}`
516
+ );
517
+ }
518
+ }
519
+ } else if (singlePrivKey !== void 0) {
520
+ if (singlePrivKey.length !== X25519_SECRET_KEY_LENGTH2) {
521
+ throw new EciesSealedPoeError(
522
+ "INVALID_RECIPIENT_KEY",
523
+ `recipientSecretKey MUST be exactly ${X25519_SECRET_KEY_LENGTH2} bytes, got ${singlePrivKey.length}`
524
+ );
525
+ }
526
+ }
527
+ }
528
+ function tryX25519Slot(args) {
529
+ if (args.liveSlot) {
530
+ try {
531
+ const shared = x25519Ecdh({
532
+ secretKey: args.recipientSecretKey,
533
+ theirPublicKey: args.slot.epk
534
+ });
535
+ const kek = hkdfSha256({
536
+ ikm: shared,
537
+ salt: concat2(args.slot.epk, args.pubRLocal),
538
+ info: CARDANO_POE_HKDF_INFO_KEK,
539
+ length: 32
540
+ });
541
+ return chacha20Poly1305Decrypt({
542
+ key: kek,
543
+ nonce: ZERO_NONCE_122,
544
+ aad: CARDANO_POE_HKDF_INFO_KEK,
545
+ ciphertext: args.slot.wrap
546
+ });
547
+ } catch (e) {
548
+ if (!(e instanceof AeadVerificationError) && !(e instanceof X25519LowOrderPointError)) {
549
+ throw e;
550
+ }
551
+ return null;
552
+ }
553
+ }
554
+ try {
555
+ const shared = x25519Ecdh({
556
+ secretKey: args.recipientSecretKey,
557
+ theirPublicKey: args.slot.epk
558
+ });
559
+ hkdfSha256({
560
+ ikm: shared,
561
+ salt: concat2(args.slot.epk, args.pubRLocal),
562
+ info: CARDANO_POE_HKDF_INFO_KEK,
563
+ length: 32
564
+ });
565
+ } catch (e) {
566
+ if (!(e instanceof X25519LowOrderPointError)) throw e;
567
+ }
568
+ return null;
569
+ }
570
+ function tryMlkem768X25519Slot(args) {
571
+ const enc = joinKemCt(args.slot.kem_ct);
572
+ const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
573
+ const kek = hkdfSha256({
574
+ ikm: ss,
575
+ salt: EMPTY_SALT2,
576
+ info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
577
+ length: 32
578
+ });
579
+ if (!args.liveSlot) {
580
+ return null;
581
+ }
582
+ try {
583
+ return chacha20Poly1305Decrypt({
584
+ key: kek,
585
+ nonce: ZERO_NONCE_122,
586
+ aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
587
+ ciphertext: args.slot.wrap
588
+ });
589
+ } catch (e) {
590
+ if (!(e instanceof AeadVerificationError)) throw e;
591
+ return null;
592
+ }
593
+ }
594
+ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
595
+ const n = envelope.slots.length;
596
+ let cek = null;
597
+ let matchedSlotIdx = -1;
598
+ if (envelope.kem === "x25519") {
599
+ const pubRLocal = x25519PublicKey({ secretKey: recipientSecretKey });
600
+ for (let i = 0; i < n; i++) {
601
+ if (slotsAttemptedOut !== void 0) {
602
+ slotsAttemptedOut.count = i + 1;
603
+ }
604
+ const candidate = tryX25519Slot({
605
+ slot: envelope.slots[i],
606
+ recipientSecretKey,
607
+ pubRLocal,
608
+ liveSlot: cek === null
609
+ });
610
+ if (cek === null && candidate !== null) {
611
+ cek = candidate;
612
+ matchedSlotIdx = i;
613
+ }
614
+ if (cek !== null && !constantTimeN) break;
615
+ }
616
+ } else {
617
+ for (let i = 0; i < n; i++) {
618
+ if (slotsAttemptedOut !== void 0) {
619
+ slotsAttemptedOut.count = i + 1;
620
+ }
621
+ const candidate = tryMlkem768X25519Slot({
622
+ slot: envelope.slots[i],
623
+ recipientSecretKey,
624
+ liveSlot: cek === null
625
+ });
626
+ if (cek === null && candidate !== null) {
627
+ cek = candidate;
628
+ matchedSlotIdx = i;
629
+ }
630
+ if (cek !== null && !constantTimeN) break;
631
+ }
632
+ }
633
+ return cek === null ? null : { cek, slotIdx: matchedSlotIdx };
634
+ }
635
+ function tryRecipientUnwrap(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
636
+ return tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut)?.cek ?? null;
637
+ }
638
+ function slotsMacCborBytes(envelope) {
639
+ return slotsToMacCbor(
640
+ envelope.slots,
641
+ envelope.kem
642
+ );
643
+ }
644
+ function eciesSealedPoeUnwrap(args) {
645
+ const { envelope, ciphertext } = args;
646
+ const constantTimeN = args.constantTimeN ?? true;
647
+ const hasSingle = "recipientSecretKey" in args;
648
+ const hasBundle = "recipientKeyBundle" in args;
649
+ const multiPrivKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : "recipientSecretKeys" in args ? args.recipientSecretKeys : void 0;
650
+ const hasMulti = multiPrivKeys !== void 0;
651
+ if (hasSingle === hasMulti) {
652
+ throw new EciesSealedPoeError(
653
+ "INVALID_RECIPIENT_KEY",
654
+ "exactly one of recipientSecretKey / recipientSecretKeys / recipientKeyBundle MUST be supplied"
655
+ );
656
+ }
657
+ if (hasMulti && multiPrivKeys.length === 0) {
658
+ if (hasBundle) {
659
+ return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
660
+ }
661
+ throw new EciesSealedPoeError(
662
+ "INVALID_RECIPIENT_KEY",
663
+ "recipientSecretKeys MUST be a non-empty array, got length=0"
664
+ );
665
+ }
666
+ if (hasMulti) {
667
+ assertEnvelopeStructure(envelope, multiPrivKeys, void 0);
668
+ } else {
669
+ assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
670
+ }
671
+ let matchedCek = null;
672
+ let anyCandidateRecovered = false;
673
+ if (hasSingle) {
674
+ const recipientSecretKey = args.recipientSecretKey;
675
+ const cek = tryRecipientUnwrap(
676
+ envelope,
677
+ recipientSecretKey,
678
+ constantTimeN,
679
+ args._slotsAttemptedOut
680
+ );
681
+ if (cek === null) {
682
+ return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
683
+ }
684
+ const slotsCbor = slotsMacCborBytes(envelope);
685
+ const hmacKey = hkdfSha256({
686
+ ikm: cek,
687
+ salt: EMPTY_SALT2,
688
+ info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
689
+ length: 32
690
+ });
691
+ const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
692
+ if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
693
+ return { matched: false, reason: "TAMPERED_HEADER" };
694
+ }
695
+ matchedCek = cek;
696
+ } else {
697
+ const slotsCbor = slotsMacCborBytes(envelope);
698
+ const recipientSecretKeys = multiPrivKeys;
699
+ for (let k = 0; k < recipientSecretKeys.length; k++) {
700
+ if (args._privsAttemptedOut !== void 0) {
701
+ args._privsAttemptedOut.count = k + 1;
702
+ }
703
+ if (args._slotsAttemptedOut !== void 0) {
704
+ args._slotsAttemptedOut.count = 0;
705
+ }
706
+ const cek = tryRecipientUnwrap(
707
+ envelope,
708
+ recipientSecretKeys[k],
709
+ constantTimeN,
710
+ args._slotsAttemptedOut
711
+ );
712
+ if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
713
+ args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
714
+ }
715
+ if (cek === null) continue;
716
+ const hmacKey = hkdfSha256({
717
+ ikm: cek,
718
+ salt: EMPTY_SALT2,
719
+ info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
720
+ length: 32
721
+ });
722
+ const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
723
+ if (compareCt(slotsMacCalc, envelope.slots_mac)) {
724
+ matchedCek = cek;
725
+ break;
726
+ }
727
+ anyCandidateRecovered = true;
728
+ }
729
+ if (matchedCek === null) {
730
+ return {
731
+ matched: false,
732
+ reason: anyCandidateRecovered ? "TAMPERED_HEADER" : "WRONG_RECIPIENT_KEY"
733
+ };
734
+ }
735
+ }
736
+ const adContent = concat2(envelope.nonce, envelope.slots_mac);
737
+ try {
738
+ const plaintext = xchacha20Poly1305Decrypt({
739
+ key: matchedCek,
740
+ nonce: envelope.nonce,
741
+ aad: adContent,
742
+ ciphertext
743
+ });
744
+ return { matched: true, plaintext };
745
+ } catch (e) {
746
+ if (!(e instanceof AeadVerificationError)) throw e;
747
+ return { matched: false, reason: "TAMPERED_CIPHERTEXT" };
748
+ }
749
+ }
750
+ function eciesSealedPoeTrialDecrypt(args) {
751
+ const { envelope } = args;
752
+ const constantTimeN = args.constantTimeN ?? true;
753
+ const hasBundle = "recipientKeyBundle" in args;
754
+ const recipientSecretKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : args.recipientSecretKeys;
755
+ if (recipientSecretKeys.length === 0) {
756
+ if (hasBundle) {
757
+ return { kind: "no_aead_pass" };
758
+ }
759
+ throw new EciesSealedPoeError(
760
+ "INVALID_RECIPIENT_KEY",
761
+ "recipientSecretKeys MUST be a non-empty array, got length=0"
762
+ );
763
+ }
764
+ assertEnvelopeStructure(envelope, recipientSecretKeys, void 0);
765
+ const slotsCbor = slotsMacCborBytes(envelope);
766
+ let anyCandidateRecovered = false;
767
+ for (let k = 0; k < recipientSecretKeys.length; k++) {
768
+ if (args._privsAttemptedOut !== void 0) {
769
+ args._privsAttemptedOut.count = k + 1;
770
+ }
771
+ if (args._slotsAttemptedOut !== void 0) {
772
+ args._slotsAttemptedOut.count = 0;
773
+ }
774
+ const candidate = tryRecipientUnwrapWithIdx(
775
+ envelope,
776
+ recipientSecretKeys[k],
777
+ constantTimeN,
778
+ args._slotsAttemptedOut
779
+ );
780
+ if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
781
+ args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
782
+ }
783
+ if (candidate === null) continue;
784
+ const hmacKey = hkdfSha256({
785
+ ikm: candidate.cek,
786
+ salt: EMPTY_SALT2,
787
+ info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
788
+ length: 32
789
+ });
790
+ const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
791
+ if (compareCt(slotsMacCalc, envelope.slots_mac)) {
792
+ return { kind: "match", slotIdx: candidate.slotIdx, cek: candidate.cek };
793
+ }
794
+ anyCandidateRecovered = true;
795
+ }
796
+ return anyCandidateRecovered ? { kind: "aead_pass_no_mac_match" } : { kind: "no_aead_pass" };
797
+ }
798
+
799
+ // src/sealed-poe/envelope-from-parsed.ts
800
+ function sealedEnvelopeFromParsed(enc) {
801
+ if (enc.scheme !== 1 || enc.aead !== "xchacha20-poly1305") return null;
802
+ if (enc.nonce === void 0 || enc.slots_mac === void 0) return null;
803
+ const slots = enc.slots;
804
+ if (slots === void 0 || slots.length < 1) return null;
805
+ if (enc.kem === "x25519") {
806
+ const x25519Slots = [];
807
+ for (const s of slots) {
808
+ if (s.epk === void 0 || s.wrap === void 0) return null;
809
+ x25519Slots.push({ epk: s.epk, wrap: s.wrap });
810
+ }
811
+ return {
812
+ scheme: 1,
813
+ aead: "xchacha20-poly1305",
814
+ kem: "x25519",
815
+ nonce: enc.nonce,
816
+ slots: x25519Slots,
817
+ slots_mac: enc.slots_mac
818
+ };
819
+ }
820
+ if (enc.kem === "mlkem768x25519") {
821
+ const hybridSlots = [];
822
+ for (const s of slots) {
823
+ if (s.kem_ct === void 0 || s.wrap === void 0) return null;
824
+ hybridSlots.push({ kem_ct: s.kem_ct, wrap: s.wrap });
825
+ }
826
+ return {
827
+ scheme: 1,
828
+ aead: "xchacha20-poly1305",
829
+ kem: "mlkem768x25519",
830
+ nonce: enc.nonce,
831
+ slots: hybridSlots,
832
+ slots_mac: enc.slots_mac
833
+ };
834
+ }
835
+ return null;
836
+ }
837
+
838
+ exports.CARDANO_POE_HKDF_INFO_KEK = CARDANO_POE_HKDF_INFO_KEK;
839
+ exports.CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519;
840
+ exports.CARDANO_POE_HKDF_INFO_SLOTS_MAC = CARDANO_POE_HKDF_INFO_SLOTS_MAC;
841
+ exports.EciesSealedPoeError = EciesSealedPoeError;
842
+ exports.chunkKemCt = chunkKemCt;
843
+ exports.eciesSealedPoeTrialDecrypt = eciesSealedPoeTrialDecrypt;
844
+ exports.eciesSealedPoeUnwrap = eciesSealedPoeUnwrap;
845
+ exports.eciesSealedPoeWrap = eciesSealedPoeWrap;
846
+ exports.joinKemCt = joinKemCt;
847
+ exports.sealedEnvelopeFromParsed = sealedEnvelopeFromParsed;
848
+ exports.slotsToMacCbor = slotsToMacCbor;
849
+ exports.uniformIndexBelow = uniformIndexBelow;
850
+ //# sourceMappingURL=sealed-poe.cjs.map
851
+ //# sourceMappingURL=sealed-poe.cjs.map