@cardanowall/crypto-core 0.2.0 → 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.
package/dist/index.cjs CHANGED
@@ -797,27 +797,141 @@ function joinKemCt(chunks) {
797
797
  }
798
798
  return out;
799
799
  }
800
- function slotsToMacCbor(slots, kem) {
801
- let value;
800
+ function canonicalizeSlots(slots, kem) {
802
801
  if (kem === "x25519") {
803
- value = slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
804
- } else {
805
- value = slots.map((s) => ({
806
- // Canonicalize the chunk boundaries before the MAC commits to them:
807
- // reassemble the logical ciphertext and re-split into canonical ≤ 64-byte
808
- // chunks. The on-wire `kem_ct` array is a transport detail (the Cardano
809
- // ledger's 64-byte metadatum cap), and a hostile or non-canonical chunking
810
- // ([1, 63, …] instead of [64, …]) reassembles to the SAME bytes — so the
811
- // MAC must be invariant to it. Committing to the verbatim wire chunks would
812
- // let an attacker re-chunk an honest envelope and break the slots_mac match
813
- // for an honest recipient. Honest (already-64B-chunked) records are
814
- // unchanged; a real byte flip still changes the reassembled bytes and is
815
- // still rejected.
816
- kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
817
- wrap: s.wrap
818
- }));
819
- }
820
- return encodeCanonicalCbor(value);
802
+ return slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
803
+ }
804
+ return slots.map((s) => ({
805
+ kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
806
+ wrap: s.wrap
807
+ }));
808
+ }
809
+ var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
810
+ "cardano-poe-slots-transcript-v1"
811
+ );
812
+ var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
813
+ "cardano-poe-payload-v1"
814
+ );
815
+ var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
816
+ "cardano-poe-payload-passphrase-v1"
817
+ );
818
+ var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
819
+ "cardano-poe-xwing-kek-salt-v1"
820
+ );
821
+ if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
822
+ throw new Error(
823
+ "CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
824
+ );
825
+ }
826
+ if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
827
+ throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
828
+ }
829
+ if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
830
+ throw new Error(
831
+ "CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
832
+ );
833
+ }
834
+ if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
835
+ throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
836
+ }
837
+ var CARDANO_POE_PW_NORM_PROFILE = "cardano-poe-pw-norm-v1";
838
+ var MAX_SLOTS = 1024;
839
+ var MAX_DECODED_ENVELOPE_BYTES = 65536;
840
+ var MAX_SEALED_PLAINTEXT = 274877906880;
841
+ var MAX_SEALED_CIPHERTEXT = MAX_SEALED_PLAINTEXT + 16;
842
+ function assertPlaintextWithinBound(plaintextLength) {
843
+ if (plaintextLength >= MAX_SEALED_PLAINTEXT) {
844
+ throw new SealedPayloadTooLargeError(
845
+ `plaintext length ${plaintextLength} is at or above the maximum sealed payload size ${MAX_SEALED_PLAINTEXT}`
846
+ );
847
+ }
848
+ }
849
+ function assertCiphertextWithinBound(ciphertextLength) {
850
+ if (ciphertextLength >= MAX_SEALED_CIPHERTEXT) {
851
+ throw new SealedPayloadTooLargeError(
852
+ `ciphertext length ${ciphertextLength} is at or above the maximum sealed ciphertext size ${MAX_SEALED_CIPHERTEXT}`
853
+ );
854
+ }
855
+ }
856
+ var SealedPayloadTooLargeError = class extends Error {
857
+ constructor(message) {
858
+ super(message);
859
+ this.name = "SealedPayloadTooLargeError";
860
+ }
861
+ };
862
+ function computeSlotsHash(args) {
863
+ const transcript = {
864
+ scheme: 1,
865
+ path: "slots",
866
+ aead: "xchacha20-poly1305",
867
+ kem: args.kem,
868
+ nonce: args.nonce,
869
+ slots: canonicalizeSlots(args.slots, args.kem)
870
+ };
871
+ const encoded = encodeCanonicalCbor(transcript);
872
+ const message = new Uint8Array(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length + encoded.length);
873
+ message.set(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, 0);
874
+ message.set(encoded, CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length);
875
+ return sha2_js.sha256(message);
876
+ }
877
+ function adContentSlots(args) {
878
+ const ad = {
879
+ scheme: 1,
880
+ path: "slots",
881
+ aead: "xchacha20-poly1305",
882
+ kem: args.kem,
883
+ nonce: args.nonce,
884
+ slots_hash: args.slotsHash,
885
+ slots_mac: args.slotsMac
886
+ };
887
+ return encodeCanonicalCbor(ad);
888
+ }
889
+ function adContentPassphrase(args) {
890
+ const ad = {
891
+ scheme: 1,
892
+ path: "passphrase",
893
+ aead: "xchacha20-poly1305",
894
+ nonce: args.nonce,
895
+ passphrase: {
896
+ alg: args.passphrase.alg,
897
+ salt: args.passphrase.salt,
898
+ params: {
899
+ m: args.passphrase.params.m,
900
+ t: args.passphrase.params.t,
901
+ p: args.passphrase.params.p
902
+ },
903
+ normalization: CARDANO_POE_PW_NORM_PROFILE
904
+ }
905
+ };
906
+ return encodeCanonicalCbor(ad);
907
+ }
908
+ function slotsPayloadKey(args) {
909
+ return hkdfSha256({
910
+ ikm: args.cek,
911
+ salt: args.nonce,
912
+ info: CARDANO_POE_HKDF_INFO_PAYLOAD,
913
+ length: 32
914
+ });
915
+ }
916
+ function passphrasePayloadKey(args) {
917
+ return hkdfSha256({
918
+ ikm: args.cek,
919
+ salt: args.nonce,
920
+ info: CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE,
921
+ length: 32
922
+ });
923
+ }
924
+ function xwingKekSalt(args) {
925
+ const message = new Uint8Array(
926
+ CARDANO_POE_XWING_KEK_SALT_PREFIX.length + args.kemCt.length + args.pubR.length
927
+ );
928
+ let offset = 0;
929
+ message.set(CARDANO_POE_XWING_KEK_SALT_PREFIX, offset);
930
+ offset += CARDANO_POE_XWING_KEK_SALT_PREFIX.length;
931
+ message.set(args.kemCt, offset);
932
+ offset += args.kemCt.length;
933
+ message.set(args.pubR, offset);
934
+ return sha2_js.sha256(message);
821
935
  }
822
936
 
823
937
  // src/sealed-poe/wrap.ts
@@ -911,7 +1025,7 @@ function wrapSlotMlkem768X25519(args) {
911
1025
  }
912
1026
  const kek = hkdfSha256({
913
1027
  ikm: ss,
914
- salt: EMPTY_SALT2,
1028
+ salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
915
1029
  info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
916
1030
  length: 32
917
1031
  });
@@ -930,6 +1044,7 @@ function eciesSealedPoeWrap(args) {
930
1044
  const { plaintext, recipientPublicKeys } = args;
931
1045
  const kem = args.kem ?? "x25519";
932
1046
  const n = recipientPublicKeys.length;
1047
+ assertPlaintextWithinBound(plaintext.length);
933
1048
  if (n < 1) {
934
1049
  throw new EciesSealedPoeError(
935
1050
  "ENC_SLOTS_EMPTY",
@@ -999,6 +1114,7 @@ function eciesSealedPoeWrap(args) {
999
1114
  );
1000
1115
  }
1001
1116
  let envelope;
1117
+ let slotsHash;
1002
1118
  if (kem === "x25519") {
1003
1119
  const slots = [];
1004
1120
  for (let i = 0; i < n; i++) {
@@ -1014,14 +1130,14 @@ function eciesSealedPoeWrap(args) {
1014
1130
  if (args.skipShuffle !== true) {
1015
1131
  csprngShuffle(slots);
1016
1132
  }
1017
- const slotsMac = computeSlotsMac(cek, slots, "x25519");
1133
+ slotsHash = computeSlotsHash({ kem: "x25519", nonce, slots });
1018
1134
  envelope = {
1019
1135
  scheme: 1,
1020
1136
  aead: "xchacha20-poly1305",
1021
1137
  kem: "x25519",
1022
1138
  nonce,
1023
1139
  slots,
1024
- slots_mac: slotsMac
1140
+ slots_mac: computeSlotsMac(cek, slotsHash)
1025
1141
  };
1026
1142
  } else {
1027
1143
  const slots = [];
@@ -1037,34 +1153,39 @@ function eciesSealedPoeWrap(args) {
1037
1153
  if (args.skipShuffle !== true) {
1038
1154
  csprngShuffle(slots);
1039
1155
  }
1040
- const slotsMac = computeSlotsMac(cek, slots, "mlkem768x25519");
1156
+ slotsHash = computeSlotsHash({ kem: "mlkem768x25519", nonce, slots });
1041
1157
  envelope = {
1042
1158
  scheme: 1,
1043
1159
  aead: "xchacha20-poly1305",
1044
1160
  kem: "mlkem768x25519",
1045
1161
  nonce,
1046
1162
  slots,
1047
- slots_mac: slotsMac
1163
+ slots_mac: computeSlotsMac(cek, slotsHash)
1048
1164
  };
1049
1165
  }
1050
- const adContent = concat(nonce, envelope.slots_mac);
1166
+ const payloadKey = slotsPayloadKey({ cek, nonce });
1167
+ const adContent = adContentSlots({
1168
+ kem: envelope.kem,
1169
+ nonce,
1170
+ slotsHash,
1171
+ slotsMac: envelope.slots_mac
1172
+ });
1051
1173
  const ciphertext = xchacha20Poly1305Encrypt({
1052
- key: cek,
1174
+ key: payloadKey,
1053
1175
  nonce,
1054
1176
  aad: adContent,
1055
1177
  plaintext
1056
1178
  });
1057
1179
  return { envelope, ciphertext };
1058
1180
  }
1059
- function computeSlotsMac(cek, slots, kem) {
1181
+ function computeSlotsMac(cek, slotsHash) {
1060
1182
  const hmacKey = hkdfSha256({
1061
1183
  ikm: cek,
1062
1184
  salt: EMPTY_SALT2,
1063
1185
  info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1064
1186
  length: 32
1065
1187
  });
1066
- const slotsCbor = slotsToMacCbor(slots, kem);
1067
- const slotsMac = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
1188
+ const slotsMac = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
1068
1189
  if (slotsMac.length !== SLOTS_MAC_LENGTH) {
1069
1190
  throw new Error(`internal: slots_mac.length=${slotsMac.length}, expected ${SLOTS_MAC_LENGTH}`);
1070
1191
  }
@@ -1086,6 +1207,13 @@ function concat2(a, b) {
1086
1207
  out.set(b, a.length);
1087
1208
  return out;
1088
1209
  }
1210
+ function bytesKey(bytes) {
1211
+ let s = "";
1212
+ for (let i = 0; i < bytes.length; i++) {
1213
+ s += String.fromCharCode(bytes[i]);
1214
+ }
1215
+ return s;
1216
+ }
1089
1217
  function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1090
1218
  if (envelope.scheme !== 1) {
1091
1219
  throw new EciesSealedPoeError(
@@ -1109,6 +1237,12 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1109
1237
  if (n < 1) {
1110
1238
  throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
1111
1239
  }
1240
+ if (n > MAX_SLOTS) {
1241
+ throw new EciesSealedPoeError(
1242
+ "ENC_SLOTS_TOO_MANY",
1243
+ `envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
1244
+ );
1245
+ }
1112
1246
  if (envelope.nonce.length !== NONCE_LENGTH2) {
1113
1247
  throw new EciesSealedPoeError(
1114
1248
  "NONCE_LENGTH_MISMATCH",
@@ -1121,6 +1255,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1121
1255
  `envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
1122
1256
  );
1123
1257
  }
1258
+ const seenKemMaterial = /* @__PURE__ */ new Set();
1124
1259
  if (envelope.kem === "x25519") {
1125
1260
  for (let i = 0; i < n; i++) {
1126
1261
  const slot = envelope.slots[i];
@@ -1136,6 +1271,14 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1136
1271
  `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
1137
1272
  );
1138
1273
  }
1274
+ const key = bytesKey(slot.epk);
1275
+ if (seenKemMaterial.has(key)) {
1276
+ throw new EciesSealedPoeError(
1277
+ "ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
1278
+ `envelope.slots[${i}].epk duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
1279
+ );
1280
+ }
1281
+ seenKemMaterial.add(key);
1139
1282
  }
1140
1283
  } else {
1141
1284
  for (let i = 0; i < n; i++) {
@@ -1153,8 +1296,24 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1153
1296
  `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
1154
1297
  );
1155
1298
  }
1299
+ const key = bytesKey(enc);
1300
+ if (seenKemMaterial.has(key)) {
1301
+ throw new EciesSealedPoeError(
1302
+ "ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
1303
+ `envelope.slots[${i}].kem_ct duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
1304
+ );
1305
+ }
1306
+ seenKemMaterial.add(key);
1156
1307
  }
1157
1308
  }
1309
+ const perSlotBytes = envelope.kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH2 + WRAP_LENGTH2 : MLKEM768X25519_ENC_LENGTH + WRAP_LENGTH2;
1310
+ const decodedEnvelopeBytes = NONCE_LENGTH2 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
1311
+ if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
1312
+ throw new EciesSealedPoeError(
1313
+ "ENC_ENVELOPE_TOO_LARGE",
1314
+ `decoded envelope size ${decodedEnvelopeBytes} exceeds MAX_DECODED_ENVELOPE_BYTES=${MAX_DECODED_ENVELOPE_BYTES}`
1315
+ );
1316
+ }
1158
1317
  if (multiPrivKeys !== void 0) {
1159
1318
  for (let i = 0; i < multiPrivKeys.length; i++) {
1160
1319
  if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
@@ -1173,60 +1332,42 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1173
1332
  }
1174
1333
  }
1175
1334
  }
1335
+ var ZERO_IKM_32 = new Uint8Array(32);
1176
1336
  function tryX25519Slot(args) {
1177
- if (args.liveSlot) {
1178
- try {
1179
- const shared = x25519Ecdh({
1180
- secretKey: args.recipientSecretKey,
1181
- theirPublicKey: args.slot.epk
1182
- });
1183
- const kek = hkdfSha256({
1184
- ikm: shared,
1185
- salt: concat2(args.slot.epk, args.pubRLocal),
1186
- info: CARDANO_POE_HKDF_INFO_KEK,
1187
- length: 32
1188
- });
1189
- return chacha20Poly1305Decrypt({
1190
- key: kek,
1191
- nonce: ZERO_NONCE_122,
1192
- aad: CARDANO_POE_HKDF_INFO_KEK,
1193
- ciphertext: args.slot.wrap
1194
- });
1195
- } catch (e) {
1196
- if (!(e instanceof AeadVerificationError) && !(e instanceof X25519LowOrderPointError)) {
1197
- throw e;
1198
- }
1199
- return null;
1200
- }
1201
- }
1337
+ const salt = concat2(args.slot.epk, args.pubRLocal);
1338
+ let shared;
1202
1339
  try {
1203
- const shared = x25519Ecdh({
1340
+ shared = x25519Ecdh({
1204
1341
  secretKey: args.recipientSecretKey,
1205
1342
  theirPublicKey: args.slot.epk
1206
1343
  });
1207
- hkdfSha256({
1208
- ikm: shared,
1209
- salt: concat2(args.slot.epk, args.pubRLocal),
1210
- info: CARDANO_POE_HKDF_INFO_KEK,
1211
- length: 32
1212
- });
1213
1344
  } catch (e) {
1214
1345
  if (!(e instanceof X25519LowOrderPointError)) throw e;
1346
+ hkdfSha256({ ikm: ZERO_IKM_32, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1347
+ return null;
1348
+ }
1349
+ const kek = hkdfSha256({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1350
+ try {
1351
+ return chacha20Poly1305Decrypt({
1352
+ key: kek,
1353
+ nonce: ZERO_NONCE_122,
1354
+ aad: CARDANO_POE_HKDF_INFO_KEK,
1355
+ ciphertext: args.slot.wrap
1356
+ });
1357
+ } catch (e) {
1358
+ if (!(e instanceof AeadVerificationError)) throw e;
1359
+ return null;
1215
1360
  }
1216
- return null;
1217
1361
  }
1218
1362
  function tryMlkem768X25519Slot(args) {
1219
1363
  const enc = joinKemCt(args.slot.kem_ct);
1220
1364
  const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
1221
1365
  const kek = hkdfSha256({
1222
1366
  ikm: ss,
1223
- salt: EMPTY_SALT3,
1367
+ salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
1224
1368
  info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
1225
1369
  length: 32
1226
1370
  });
1227
- if (!args.liveSlot) {
1228
- return null;
1229
- }
1230
1371
  try {
1231
1372
  return chacha20Poly1305Decrypt({
1232
1373
  key: kek,
@@ -1243,51 +1384,43 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
1243
1384
  const n = envelope.slots.length;
1244
1385
  let cek = null;
1245
1386
  let matchedSlotIdx = -1;
1387
+ let cekConflict = false;
1388
+ const recordMatch = (candidate, i) => {
1389
+ if (candidate === null) return;
1390
+ if (cek === null) {
1391
+ cek = candidate;
1392
+ matchedSlotIdx = i;
1393
+ } else if (!compareCt(candidate, cek)) {
1394
+ cekConflict = true;
1395
+ }
1396
+ };
1246
1397
  if (envelope.kem === "x25519") {
1247
1398
  const pubRLocal = x25519PublicKey({ secretKey: recipientSecretKey });
1248
1399
  for (let i = 0; i < n; i++) {
1249
1400
  if (slotsAttemptedOut !== void 0) {
1250
1401
  slotsAttemptedOut.count = i + 1;
1251
1402
  }
1252
- const candidate = tryX25519Slot({
1253
- slot: envelope.slots[i],
1254
- recipientSecretKey,
1255
- pubRLocal,
1256
- liveSlot: cek === null
1257
- });
1258
- if (cek === null && candidate !== null) {
1259
- cek = candidate;
1260
- matchedSlotIdx = i;
1261
- }
1403
+ recordMatch(tryX25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubRLocal }), i);
1262
1404
  if (cek !== null && !constantTimeN) break;
1263
1405
  }
1264
1406
  } else {
1407
+ const pubR = mlkem768x25519Keygen(recipientSecretKey).publicKey;
1265
1408
  for (let i = 0; i < n; i++) {
1266
1409
  if (slotsAttemptedOut !== void 0) {
1267
1410
  slotsAttemptedOut.count = i + 1;
1268
1411
  }
1269
- const candidate = tryMlkem768X25519Slot({
1270
- slot: envelope.slots[i],
1271
- recipientSecretKey,
1272
- liveSlot: cek === null
1273
- });
1274
- if (cek === null && candidate !== null) {
1275
- cek = candidate;
1276
- matchedSlotIdx = i;
1277
- }
1412
+ recordMatch(tryMlkem768X25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubR }), i);
1278
1413
  if (cek !== null && !constantTimeN) break;
1279
1414
  }
1280
1415
  }
1281
- return cek === null ? null : { cek, slotIdx: matchedSlotIdx };
1282
- }
1283
- function tryRecipientUnwrap(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
1284
- return tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut)?.cek ?? null;
1416
+ return cek === null ? null : { cek, slotIdx: matchedSlotIdx, cekConflict };
1285
1417
  }
1286
- function slotsMacCborBytes(envelope) {
1287
- return slotsToMacCbor(
1288
- envelope.slots,
1289
- envelope.kem
1290
- );
1418
+ function slotsHashBytes(envelope) {
1419
+ return computeSlotsHash({
1420
+ kem: envelope.kem,
1421
+ nonce: envelope.nonce,
1422
+ slots: envelope.slots
1423
+ });
1291
1424
  }
1292
1425
  function eciesSealedPoeUnwrap(args) {
1293
1426
  const { envelope, ciphertext } = args;
@@ -1316,34 +1449,38 @@ function eciesSealedPoeUnwrap(args) {
1316
1449
  } else {
1317
1450
  assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
1318
1451
  }
1452
+ assertCiphertextWithinBound(ciphertext.length);
1453
+ const slotsHash = slotsHashBytes(envelope);
1319
1454
  let matchedCek = null;
1320
1455
  let anyCandidateRecovered = false;
1321
1456
  if (hasSingle) {
1322
1457
  const recipientSecretKey = args.recipientSecretKey;
1323
- const cek = tryRecipientUnwrap(
1458
+ const candidate = tryRecipientUnwrapWithIdx(
1324
1459
  envelope,
1325
1460
  recipientSecretKey,
1326
1461
  constantTimeN,
1327
1462
  args._slotsAttemptedOut
1328
1463
  );
1329
- if (cek === null) {
1464
+ if (candidate === null) {
1330
1465
  return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
1331
1466
  }
1332
- const slotsCbor = slotsMacCborBytes(envelope);
1467
+ if (candidate.cekConflict) {
1468
+ return { matched: false, reason: "TAMPERED_HEADER" };
1469
+ }
1333
1470
  const hmacKey = hkdfSha256({
1334
- ikm: cek,
1471
+ ikm: candidate.cek,
1335
1472
  salt: EMPTY_SALT3,
1336
1473
  info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1337
1474
  length: 32
1338
1475
  });
1339
- const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
1476
+ const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
1340
1477
  if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
1341
1478
  return { matched: false, reason: "TAMPERED_HEADER" };
1342
1479
  }
1343
- matchedCek = cek;
1480
+ matchedCek = candidate.cek;
1344
1481
  } else {
1345
- const slotsCbor = slotsMacCborBytes(envelope);
1346
1482
  const recipientSecretKeys = multiPrivKeys;
1483
+ let cekConflict = false;
1347
1484
  for (let k = 0; k < recipientSecretKeys.length; k++) {
1348
1485
  if (args._privsAttemptedOut !== void 0) {
1349
1486
  args._privsAttemptedOut.count = k + 1;
@@ -1351,7 +1488,7 @@ function eciesSealedPoeUnwrap(args) {
1351
1488
  if (args._slotsAttemptedOut !== void 0) {
1352
1489
  args._slotsAttemptedOut.count = 0;
1353
1490
  }
1354
- const cek = tryRecipientUnwrap(
1491
+ const candidate = tryRecipientUnwrapWithIdx(
1355
1492
  envelope,
1356
1493
  recipientSecretKeys[k],
1357
1494
  constantTimeN,
@@ -1360,20 +1497,25 @@ function eciesSealedPoeUnwrap(args) {
1360
1497
  if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
1361
1498
  args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
1362
1499
  }
1363
- if (cek === null) continue;
1500
+ if (candidate === null) continue;
1501
+ if (candidate.cekConflict) cekConflict = true;
1502
+ const cek = candidate.cek;
1364
1503
  const hmacKey = hkdfSha256({
1365
1504
  ikm: cek,
1366
1505
  salt: EMPTY_SALT3,
1367
1506
  info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1368
1507
  length: 32
1369
1508
  });
1370
- const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
1509
+ const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
1371
1510
  if (compareCt(slotsMacCalc, envelope.slots_mac)) {
1372
1511
  matchedCek = cek;
1373
1512
  break;
1374
1513
  }
1375
1514
  anyCandidateRecovered = true;
1376
1515
  }
1516
+ if (matchedCek !== null && cekConflict) {
1517
+ return { matched: false, reason: "TAMPERED_HEADER" };
1518
+ }
1377
1519
  if (matchedCek === null) {
1378
1520
  return {
1379
1521
  matched: false,
@@ -1381,10 +1523,16 @@ function eciesSealedPoeUnwrap(args) {
1381
1523
  };
1382
1524
  }
1383
1525
  }
1384
- const adContent = concat2(envelope.nonce, envelope.slots_mac);
1526
+ const payloadKey = slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce });
1527
+ const adContent = adContentSlots({
1528
+ kem: envelope.kem,
1529
+ nonce: envelope.nonce,
1530
+ slotsHash,
1531
+ slotsMac: envelope.slots_mac
1532
+ });
1385
1533
  try {
1386
1534
  const plaintext = xchacha20Poly1305Decrypt({
1387
- key: matchedCek,
1535
+ key: payloadKey,
1388
1536
  nonce: envelope.nonce,
1389
1537
  aad: adContent,
1390
1538
  ciphertext
@@ -1410,7 +1558,7 @@ function eciesSealedPoeTrialDecrypt(args) {
1410
1558
  );
1411
1559
  }
1412
1560
  assertEnvelopeStructure(envelope, recipientSecretKeys, void 0);
1413
- const slotsCbor = slotsMacCborBytes(envelope);
1561
+ const slotsHash = slotsHashBytes(envelope);
1414
1562
  let anyCandidateRecovered = false;
1415
1563
  for (let k = 0; k < recipientSecretKeys.length; k++) {
1416
1564
  if (args._privsAttemptedOut !== void 0) {
@@ -1429,13 +1577,17 @@ function eciesSealedPoeTrialDecrypt(args) {
1429
1577
  args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
1430
1578
  }
1431
1579
  if (candidate === null) continue;
1580
+ if (candidate.cekConflict) {
1581
+ anyCandidateRecovered = true;
1582
+ continue;
1583
+ }
1432
1584
  const hmacKey = hkdfSha256({
1433
1585
  ikm: candidate.cek,
1434
1586
  salt: EMPTY_SALT3,
1435
1587
  info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1436
1588
  length: 32
1437
1589
  });
1438
- const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
1590
+ const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
1439
1591
  if (compareCt(slotsMacCalc, envelope.slots_mac)) {
1440
1592
  return { kind: "match", slotIdx: candidate.slotIdx, cek: candidate.cek };
1441
1593
  }
@@ -1779,9 +1931,14 @@ function parseAgeRecipient(recipient) {
1779
1931
  exports.AeadVerificationError = AeadVerificationError;
1780
1932
  exports.CARDANO_POE_HKDF_INFO_KEK = CARDANO_POE_HKDF_INFO_KEK;
1781
1933
  exports.CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519;
1934
+ exports.CARDANO_POE_HKDF_INFO_PAYLOAD = CARDANO_POE_HKDF_INFO_PAYLOAD;
1935
+ exports.CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE;
1782
1936
  exports.CARDANO_POE_HKDF_INFO_SLOTS_MAC = CARDANO_POE_HKDF_INFO_SLOTS_MAC;
1937
+ exports.CARDANO_POE_PW_NORM_PROFILE = CARDANO_POE_PW_NORM_PROFILE;
1783
1938
  exports.CARDANO_POE_SIG_DOMAIN_PREFIX = CARDANO_POE_SIG_DOMAIN_PREFIX;
1784
1939
  exports.CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES = CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES;
1940
+ exports.CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX;
1941
+ exports.CARDANO_POE_XWING_KEK_SALT_PREFIX = CARDANO_POE_XWING_KEK_SALT_PREFIX;
1785
1942
  exports.CanonicalCborError = CanonicalCborError;
1786
1943
  exports.CoseSign1BuildError = CoseSign1BuildError;
1787
1944
  exports.CoseVerifyError = CoseVerifyError;
@@ -1790,6 +1947,10 @@ exports.INFO_ED25519 = INFO_ED25519;
1790
1947
  exports.INFO_MLKEM768X25519 = INFO_MLKEM768X25519;
1791
1948
  exports.INFO_X25519 = INFO_X25519;
1792
1949
  exports.LEAVES_LIST_FORMAT_V1 = LEAVES_LIST_FORMAT_V1;
1950
+ exports.MAX_DECODED_ENVELOPE_BYTES = MAX_DECODED_ENVELOPE_BYTES;
1951
+ exports.MAX_SEALED_CIPHERTEXT = MAX_SEALED_CIPHERTEXT;
1952
+ exports.MAX_SEALED_PLAINTEXT = MAX_SEALED_PLAINTEXT;
1953
+ exports.MAX_SLOTS = MAX_SLOTS;
1793
1954
  exports.MERKLE_ALG_ID = MERKLE_ALG_ID;
1794
1955
  exports.MLKEM768X25519_ENC_LENGTH = MLKEM768X25519_ENC_LENGTH;
1795
1956
  exports.MLKEM768X25519_ESEED_LENGTH = MLKEM768X25519_ESEED_LENGTH;
@@ -1797,19 +1958,26 @@ exports.MLKEM768X25519_PUBLIC_KEY_LENGTH = MLKEM768X25519_PUBLIC_KEY_LENGTH;
1797
1958
  exports.MLKEM768X25519_SEED_LENGTH = MLKEM768X25519_SEED_LENGTH;
1798
1959
  exports.MLKEM768X25519_SHARED_SECRET_LENGTH = MLKEM768X25519_SHARED_SECRET_LENGTH;
1799
1960
  exports.MerkleLeavesListError = MerkleLeavesListError;
1961
+ exports.SealedPayloadTooLargeError = SealedPayloadTooLargeError;
1800
1962
  exports.SeedDeriveError = SeedDeriveError;
1801
1963
  exports.X25519LowOrderPointError = X25519LowOrderPointError;
1964
+ exports.adContentPassphrase = adContentPassphrase;
1965
+ exports.adContentSlots = adContentSlots;
1802
1966
  exports.argon2idV13 = argon2idV13;
1967
+ exports.assertCiphertextWithinBound = assertCiphertextWithinBound;
1968
+ exports.assertPlaintextWithinBound = assertPlaintextWithinBound;
1803
1969
  exports.bech32DecodeNoLimit = bech32DecodeNoLimit;
1804
1970
  exports.bech32EncodeNoLimit = bech32EncodeNoLimit;
1805
1971
  exports.blake2b224 = blake2b224;
1806
1972
  exports.blake2b256 = blake2b256;
1807
1973
  exports.buildLabel309SigStructure = buildLabel309SigStructure;
1808
1974
  exports.buildSigStructure = buildSigStructure;
1975
+ exports.canonicalizeSlots = canonicalizeSlots;
1809
1976
  exports.chacha20Poly1305Decrypt = chacha20Poly1305Decrypt;
1810
1977
  exports.chacha20Poly1305Encrypt = chacha20Poly1305Encrypt;
1811
1978
  exports.chunkKemCt = chunkKemCt;
1812
1979
  exports.compareCt = compareCt;
1980
+ exports.computeSlotsHash = computeSlotsHash;
1813
1981
  exports.coseSign1Label309Build = coseSign1Label309Build;
1814
1982
  exports.coseSign1Label309Verify = coseSign1Label309Verify;
1815
1983
  exports.decodeCanonicalCbor = decodeCanonicalCbor;
@@ -1841,10 +2009,11 @@ exports.mlkem768x25519Encapsulate = mlkem768x25519Encapsulate;
1841
2009
  exports.mlkem768x25519Keygen = mlkem768x25519Keygen;
1842
2010
  exports.parseAgeRecipient = parseAgeRecipient;
1843
2011
  exports.parseCoseKeyEd25519 = parseCoseKeyEd25519;
2012
+ exports.passphrasePayloadKey = passphrasePayloadKey;
1844
2013
  exports.sealedEnvelopeFromParsed = sealedEnvelopeFromParsed;
1845
2014
  exports.sha256 = sha256;
1846
2015
  exports.signEd25519 = signEd25519;
1847
- exports.slotsToMacCbor = slotsToMacCbor;
2016
+ exports.slotsPayloadKey = slotsPayloadKey;
1848
2017
  exports.uniformIndexBelow = uniformIndexBelow;
1849
2018
  exports.verifyEd25519 = verifyEd25519;
1850
2019
  exports.x25519Ecdh = x25519Ecdh;
@@ -1852,5 +2021,6 @@ exports.x25519Keygen = x25519Keygen;
1852
2021
  exports.x25519PublicKey = x25519PublicKey;
1853
2022
  exports.xchacha20Poly1305Decrypt = xchacha20Poly1305Decrypt;
1854
2023
  exports.xchacha20Poly1305Encrypt = xchacha20Poly1305Encrypt;
2024
+ exports.xwingKekSalt = xwingKekSalt;
1855
2025
  //# sourceMappingURL=index.cjs.map
1856
2026
  //# sourceMappingURL=index.cjs.map