@cardanowall/sdk-ts 0.2.0 → 0.4.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 (49) hide show
  1. package/dist/client/index.cjs +2566 -1706
  2. package/dist/client/index.cjs.map +1 -1
  3. package/dist/client/index.d.cts +42 -5
  4. package/dist/client/index.d.ts +42 -5
  5. package/dist/client/index.js +2564 -1708
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/conformance/cli.cjs +5978 -3438
  8. package/dist/conformance/cli.cjs.map +1 -1
  9. package/dist/conformance/cli.js +5978 -3438
  10. package/dist/conformance/cli.js.map +1 -1
  11. package/dist/fetch/index.cjs +33 -14
  12. package/dist/fetch/index.cjs.map +1 -1
  13. package/dist/fetch/index.d.cts +2 -2
  14. package/dist/fetch/index.d.ts +2 -2
  15. package/dist/fetch/index.js +32 -15
  16. package/dist/fetch/index.js.map +1 -1
  17. package/dist/{fetch-outbound-BT5-NiYN.d.cts → fetch-outbound-dOK3ZxYa.d.cts} +7 -3
  18. package/dist/{fetch-outbound-BT5-NiYN.d.ts → fetch-outbound-dOK3ZxYa.d.ts} +7 -3
  19. package/dist/hash/index.cjs +1 -1
  20. package/dist/hash/index.cjs.map +1 -1
  21. package/dist/hash/index.js +1 -1
  22. package/dist/hash/index.js.map +1 -1
  23. package/dist/identity/index.cjs +460 -219
  24. package/dist/identity/index.cjs.map +1 -1
  25. package/dist/identity/index.d.cts +3 -2
  26. package/dist/identity/index.d.ts +3 -2
  27. package/dist/identity/index.js +460 -219
  28. package/dist/identity/index.js.map +1 -1
  29. package/dist/index.cjs +6912 -3678
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +7 -7
  32. package/dist/index.d.ts +7 -7
  33. package/dist/index.js +6890 -3672
  34. package/dist/index.js.map +1 -1
  35. package/dist/merkle/index.cjs +1 -1
  36. package/dist/merkle/index.js +1 -1
  37. package/dist/types-Cexm4VH9.d.cts +119 -0
  38. package/dist/types-CgoBub9J.d.ts +119 -0
  39. package/dist/{types-DGsZTMuZ.d.cts → types-Dp4wUSFI.d.cts} +220 -1
  40. package/dist/{types-DGsZTMuZ.d.ts → types-Dp4wUSFI.d.ts} +220 -1
  41. package/dist/verifier/index.cjs +5738 -3205
  42. package/dist/verifier/index.cjs.map +1 -1
  43. package/dist/verifier/index.d.cts +159 -111
  44. package/dist/verifier/index.d.ts +159 -111
  45. package/dist/verifier/index.js +5726 -3201
  46. package/dist/verifier/index.js.map +1 -1
  47. package/package.json +3 -3
  48. package/dist/types-B8Q3gW54.d.ts +0 -123
  49. package/dist/types-CLXdbjqr.d.cts +0 -123
@@ -13,10 +13,11 @@ var utils_js = require('@noble/hashes/utils.js');
13
13
  var fft_js = require('@noble/curves/abstract/fft.js');
14
14
  var ed = require('@noble/ed25519');
15
15
  require('@noble/ciphers/utils.js');
16
- var hmac_js = require('@noble/hashes/hmac.js');
17
16
  var chacha_js = require('@noble/ciphers/chacha.js');
17
+ var hmac_js = require('@noble/hashes/hmac.js');
18
18
  var cbor2 = require('cbor2');
19
19
  var sorts = require('cbor2/sorts');
20
+ require('hash-wasm');
20
21
 
21
22
  function _interopNamespace(e) {
22
23
  if (e && e.__esModule) return e;
@@ -867,18 +868,17 @@ function chacha20Poly1305Decrypt(opts2) {
867
868
  throw new AeadVerificationError("chacha20-poly1305 decrypt failed", { cause });
868
869
  }
869
870
  }
870
- function xchacha20Poly1305Decrypt(opts2) {
871
- try {
872
- return chacha_js.xchacha20poly1305(opts2.key, opts2.nonce, opts2.aad).decrypt(opts2.ciphertext);
873
- } catch (cause) {
874
- throw new AeadVerificationError("xchacha20-poly1305 decrypt failed", { cause });
875
- }
876
- }
877
- function hkdfSha2562(opts2) {
878
- return hkdf_js.hkdf(sha2_js.sha256, opts2.ikm, opts2.salt, opts2.info, opts2.length);
879
- }
880
871
  var MLKEM768X25519_ENC_LENGTH = 1120;
881
872
  var MLKEM768X25519_SEED_LENGTH2 = 32;
873
+ function mlkem768x25519Keygen2(seed) {
874
+ if (seed.length !== MLKEM768X25519_SEED_LENGTH2) {
875
+ throw new Error(
876
+ `mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH2} bytes, got ${seed.length}`
877
+ );
878
+ }
879
+ const { secretKey, publicKey } = XWing.keygen(seed);
880
+ return { secretSeed: secretKey, publicKey };
881
+ }
882
882
  function mlkem768x25519Decapsulate(opts2) {
883
883
  if (opts2.secretSeed.length !== MLKEM768X25519_SEED_LENGTH2) {
884
884
  throw new Error(
@@ -913,6 +913,9 @@ function x25519Ecdh(opts2) {
913
913
  throw e;
914
914
  }
915
915
  }
916
+ function hkdfSha2562(opts2) {
917
+ return hkdf_js.hkdf(sha2_js.sha256, opts2.ikm, opts2.salt, opts2.info, opts2.length);
918
+ }
916
919
  var EciesSealedPoeError = class extends Error {
917
920
  code;
918
921
  constructor(code, message, options) {
@@ -921,6 +924,139 @@ var EciesSealedPoeError = class extends Error {
921
924
  this.code = code;
922
925
  }
923
926
  };
927
+ var CHUNK_SIZE = 65536;
928
+ var TAG_SIZE = 16;
929
+ var NONCE_LENGTH = 12;
930
+ var COUNTER_LENGTH = 11;
931
+ var SEALED_CHUNK_SIZE = CHUNK_SIZE + TAG_SIZE;
932
+ var PAYLOAD_KEY_LENGTH = 32;
933
+ var EMPTY_AAD = new Uint8Array(0);
934
+ var StreamTamperedError = class extends Error {
935
+ code = "TAMPERED_CIPHERTEXT";
936
+ constructor(message, options) {
937
+ super(message, options);
938
+ this.name = "StreamTamperedError";
939
+ }
940
+ };
941
+ var ChunkNonce = class {
942
+ nonce = new Uint8Array(NONCE_LENGTH);
943
+ finished = false;
944
+ next(final) {
945
+ if (this.finished) {
946
+ throw new Error("STREAM: no chunks may follow the final chunk");
947
+ }
948
+ if (final) {
949
+ this.finished = true;
950
+ this.nonce[COUNTER_LENGTH] = 1;
951
+ }
952
+ const out = this.nonce.slice();
953
+ this.increment();
954
+ return out;
955
+ }
956
+ get done() {
957
+ return this.finished;
958
+ }
959
+ increment() {
960
+ for (let i = COUNTER_LENGTH - 1; i >= 0; i--) {
961
+ const v = this.nonce[i] + 1 & 255;
962
+ this.nonce[i] = v;
963
+ if (v !== 0) return;
964
+ }
965
+ throw new Error("STREAM: chunk counter overflow");
966
+ }
967
+ };
968
+ function assertPayloadKey(payloadKey) {
969
+ if (payloadKey.length !== PAYLOAD_KEY_LENGTH) {
970
+ throw new Error(
971
+ `STREAM: payloadKey MUST be exactly ${PAYLOAD_KEY_LENGTH} bytes, got ${payloadKey.length}`
972
+ );
973
+ }
974
+ }
975
+ var StreamOpener = class {
976
+ payloadKey;
977
+ nonce = new ChunkNonce();
978
+ chunkIndex = 0;
979
+ constructor(payloadKey) {
980
+ assertPayloadKey(payloadKey);
981
+ this.payloadKey = payloadKey;
982
+ }
983
+ openChunk(sealedChunk, final) {
984
+ if (sealedChunk.length < TAG_SIZE) {
985
+ throw new StreamTamperedError(
986
+ `STREAM: sealed chunk shorter than the ${TAG_SIZE}-byte tag floor`
987
+ );
988
+ }
989
+ if (!final && sealedChunk.length !== SEALED_CHUNK_SIZE) {
990
+ throw new StreamTamperedError(
991
+ `STREAM: non-final sealed chunk MUST be exactly ${SEALED_CHUNK_SIZE} bytes, got ${sealedChunk.length}`
992
+ );
993
+ }
994
+ if (final && sealedChunk.length > SEALED_CHUNK_SIZE) {
995
+ throw new StreamTamperedError(
996
+ `STREAM: final sealed chunk MUST be at most ${SEALED_CHUNK_SIZE} bytes, got ${sealedChunk.length}`
997
+ );
998
+ }
999
+ if (final && sealedChunk.length === TAG_SIZE && this.chunkIndex > 0) {
1000
+ throw new StreamTamperedError("STREAM: zero-length final chunk on a non-empty stream");
1001
+ }
1002
+ let plaintext;
1003
+ try {
1004
+ plaintext = chacha20Poly1305Decrypt({
1005
+ key: this.payloadKey,
1006
+ nonce: this.nonce.next(final),
1007
+ aad: EMPTY_AAD,
1008
+ ciphertext: sealedChunk
1009
+ });
1010
+ } catch (e) {
1011
+ if (!(e instanceof AeadVerificationError)) throw e;
1012
+ throw new StreamTamperedError(`STREAM: chunk ${this.chunkIndex} tag verification failed`, {
1013
+ cause: e
1014
+ });
1015
+ }
1016
+ this.chunkIndex += 1;
1017
+ return plaintext;
1018
+ }
1019
+ };
1020
+ function streamOpen(args) {
1021
+ const { ciphertext } = args;
1022
+ const total = ciphertext.length;
1023
+ if (total < TAG_SIZE) {
1024
+ throw new StreamTamperedError(
1025
+ `STREAM: ciphertext shorter than the ${TAG_SIZE}-byte single-tag floor`
1026
+ );
1027
+ }
1028
+ const rem = total % SEALED_CHUNK_SIZE;
1029
+ let nonFinalCount;
1030
+ let finalSealedLength;
1031
+ if (rem === 0) {
1032
+ nonFinalCount = total / SEALED_CHUNK_SIZE - 1;
1033
+ finalSealedLength = SEALED_CHUNK_SIZE;
1034
+ } else if (rem >= TAG_SIZE) {
1035
+ nonFinalCount = (total - rem) / SEALED_CHUNK_SIZE;
1036
+ finalSealedLength = rem;
1037
+ } else {
1038
+ throw new StreamTamperedError("STREAM: trailing bytes cannot form a well-formed final chunk");
1039
+ }
1040
+ if (nonFinalCount > 0 && finalSealedLength === TAG_SIZE) {
1041
+ throw new StreamTamperedError("STREAM: zero-length final chunk on a non-empty stream");
1042
+ }
1043
+ const opener = new StreamOpener(args.payloadKey);
1044
+ const out = new Uint8Array(nonFinalCount * CHUNK_SIZE + finalSealedLength - TAG_SIZE);
1045
+ let readOffset = 0;
1046
+ let writeOffset = 0;
1047
+ for (let i = 0; i < nonFinalCount; i++) {
1048
+ const plaintext = opener.openChunk(
1049
+ ciphertext.subarray(readOffset, readOffset + SEALED_CHUNK_SIZE),
1050
+ false
1051
+ );
1052
+ out.set(plaintext, writeOffset);
1053
+ readOffset += SEALED_CHUNK_SIZE;
1054
+ writeOffset += CHUNK_SIZE;
1055
+ }
1056
+ const finalPlaintext = opener.openChunk(ciphertext.subarray(readOffset), true);
1057
+ out.set(finalPlaintext, writeOffset);
1058
+ return out;
1059
+ }
924
1060
  function encodeCanonicalCbor(value) {
925
1061
  return cbor2.encode(value, {
926
1062
  cde: true,
@@ -929,57 +1065,138 @@ function encodeCanonicalCbor(value) {
929
1065
  sortKeys: sorts.sortCoreDeterministic
930
1066
  });
931
1067
  }
932
- var CHUNK_MAX_BYTES = 64;
933
- function chunkKemCt(value) {
934
- if (value.length === 0) {
935
- throw new Error("chunkKemCt: refusing to chunk an empty byte string");
936
- }
937
- const chunks = [];
938
- for (let i = 0; i < value.length; i += CHUNK_MAX_BYTES) {
939
- chunks.push(value.subarray(i, Math.min(i + CHUNK_MAX_BYTES, value.length)));
940
- }
941
- return chunks;
942
- }
943
- function joinKemCt(chunks) {
944
- let total = 0;
945
- for (const c of chunks) total += c.length;
946
- const out = new Uint8Array(total);
947
- let offset = 0;
948
- for (const c of chunks) {
949
- out.set(c, offset);
950
- offset += c.length;
1068
+ var CARDANO_POE_ITEM_HASHES_PREFIX = new TextEncoder().encode(
1069
+ "cardano-poe-item-hashes-v1"
1070
+ );
1071
+ var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
1072
+ "cardano-poe-slots-transcript-v1"
1073
+ );
1074
+ var CARDANO_POE_PASSPHRASE_TRANSCRIPT_PREFIX = new TextEncoder().encode(
1075
+ "cardano-poe-passphrase-transcript-v1"
1076
+ );
1077
+ var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
1078
+ "cardano-poe-slots-mac-v1"
1079
+ );
1080
+ var CARDANO_POE_HKDF_INFO_PASSPHRASE_MAC = new TextEncoder().encode(
1081
+ "cardano-poe-passphrase-mac-v1"
1082
+ );
1083
+ var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
1084
+ "cardano-poe-payload-v1"
1085
+ );
1086
+ var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
1087
+ "cardano-poe-payload-passphrase-v1"
1088
+ );
1089
+ var CARDANO_POE_X25519_KEK_SALT_PREFIX = new TextEncoder().encode(
1090
+ "cardano-poe-x25519-kek-salt-v1"
1091
+ );
1092
+ var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
1093
+ "cardano-poe-xwing-kek-salt-v1"
1094
+ );
1095
+ if (CARDANO_POE_ITEM_HASHES_PREFIX.length !== 26) {
1096
+ throw new Error("CARDANO_POE_ITEM_HASHES_PREFIX byte-length invariant violated (expected 26)");
1097
+ }
1098
+ if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
1099
+ throw new Error(
1100
+ "CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
1101
+ );
1102
+ }
1103
+ if (CARDANO_POE_PASSPHRASE_TRANSCRIPT_PREFIX.length !== 36) {
1104
+ throw new Error(
1105
+ "CARDANO_POE_PASSPHRASE_TRANSCRIPT_PREFIX byte-length invariant violated (expected 36)"
1106
+ );
1107
+ }
1108
+ if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
1109
+ throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
1110
+ }
1111
+ if (CARDANO_POE_HKDF_INFO_PASSPHRASE_MAC.length !== 29) {
1112
+ throw new Error(
1113
+ "CARDANO_POE_HKDF_INFO_PASSPHRASE_MAC byte-length invariant violated (expected 29)"
1114
+ );
1115
+ }
1116
+ if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
1117
+ throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
1118
+ }
1119
+ if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
1120
+ throw new Error(
1121
+ "CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
1122
+ );
1123
+ }
1124
+ if (CARDANO_POE_X25519_KEK_SALT_PREFIX.length !== 30) {
1125
+ throw new Error(
1126
+ "CARDANO_POE_X25519_KEK_SALT_PREFIX byte-length invariant violated (expected 30)"
1127
+ );
1128
+ }
1129
+ if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
1130
+ throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
1131
+ }
1132
+ var MAX_SLOTS = 1024;
1133
+ var MAX_DECODED_ENVELOPE_BYTES = 65536;
1134
+ var EMPTY_SALT2 = new Uint8Array(0);
1135
+ function labelledSha256(prefix, ...parts) {
1136
+ let total = prefix.length;
1137
+ for (const p of parts) total += p.length;
1138
+ const message = new Uint8Array(total);
1139
+ message.set(prefix, 0);
1140
+ let offset = prefix.length;
1141
+ for (const p of parts) {
1142
+ message.set(p, offset);
1143
+ offset += p.length;
951
1144
  }
952
- return out;
1145
+ return sha2_js.sha256(message);
953
1146
  }
954
- function slotsToMacCbor(slots, kem) {
955
- let value;
956
- if (kem === "x25519") {
957
- value = slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
958
- } else {
959
- value = slots.map((s) => ({
960
- // Canonicalize the chunk boundaries before the MAC commits to them:
961
- // reassemble the logical ciphertext and re-split into canonical ≤ 64-byte
962
- // chunks. The on-wire `kem_ct` array is a transport detail (the Cardano
963
- // ledger's 64-byte metadatum cap), and a hostile or non-canonical chunking
964
- // ([1, 63, …] instead of [64, …]) reassembles to the SAME bytes — so the
965
- // MAC must be invariant to it. Committing to the verbatim wire chunks would
966
- // let an attacker re-chunk an honest envelope and break the slots_mac match
967
- // for an honest recipient. Honest (already-64B-chunked) records are
968
- // unchanged; a real byte flip still changes the reassembled bytes and is
969
- // still rejected.
970
- kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
971
- wrap: s.wrap
972
- }));
1147
+ function itemHashesHash(hashes3) {
1148
+ if (Object.keys(hashes3).length === 0) {
1149
+ throw new EciesSealedPoeError(
1150
+ "ENC_REQUIRES_CONTENT_HASH",
1151
+ "hashes MUST carry at least one content-hash entry"
1152
+ );
973
1153
  }
974
- return encodeCanonicalCbor(value);
1154
+ return labelledSha256(CARDANO_POE_ITEM_HASHES_PREFIX, encodeCanonicalCbor(hashes3));
1155
+ }
1156
+ function computeSlotsHash(args) {
1157
+ const slots = args.kem === "x25519" ? args.slots.map((s) => ({ epk: s.epk, wrap: s.wrap })) : args.slots.map((s) => ({
1158
+ kem_ct: s.kem_ct,
1159
+ wrap: s.wrap
1160
+ }));
1161
+ const transcript = {
1162
+ scheme: 1,
1163
+ path: "slots",
1164
+ aead: args.aead,
1165
+ kem: args.kem,
1166
+ nonce: args.nonce,
1167
+ slots,
1168
+ hashes_hash: args.hashesHash
1169
+ };
1170
+ return labelledSha256(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, encodeCanonicalCbor(transcript));
1171
+ }
1172
+ function computeSlotsMac(args) {
1173
+ const macKey = hkdfSha2562({
1174
+ ikm: args.cek,
1175
+ salt: EMPTY_SALT2,
1176
+ info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1177
+ length: 32
1178
+ });
1179
+ return hmac_js.hmac(sha2_js.sha256, macKey, args.slotsHash);
1180
+ }
1181
+ function slotsPayloadKey(args) {
1182
+ return hkdfSha2562({
1183
+ ikm: args.cek,
1184
+ salt: args.nonce,
1185
+ info: CARDANO_POE_HKDF_INFO_PAYLOAD,
1186
+ length: 32
1187
+ });
975
1188
  }
1189
+ function x25519KekSalt(args) {
1190
+ return labelledSha256(CARDANO_POE_X25519_KEK_SALT_PREFIX, args.nonce, args.epk, args.pubR);
1191
+ }
1192
+ function xwingKekSalt(args) {
1193
+ return labelledSha256(CARDANO_POE_XWING_KEK_SALT_PREFIX, args.nonce, args.kemCt, args.pubR);
1194
+ }
1195
+ var SEALED_POE_AEAD = "chacha20-poly1305-stream64k";
976
1196
  var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
977
1197
  var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
978
1198
  "cardano-poe-kek-mlkem768x25519-v1"
979
1199
  );
980
- var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
981
- "cardano-poe-slots-mac-v1"
982
- );
983
1200
  var ZERO_NONCE_12 = new Uint8Array(12);
984
1201
  if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
985
1202
  throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
@@ -989,9 +1206,6 @@ if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
989
1206
  "CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
990
1207
  );
991
1208
  }
992
- if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
993
- throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
994
- }
995
1209
  if (ZERO_NONCE_12.length !== 12) {
996
1210
  throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
997
1211
  }
@@ -1001,33 +1215,74 @@ function compareCt(a, b) {
1001
1215
  for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
1002
1216
  return diff === 0;
1003
1217
  }
1218
+ var CEK_LENGTH2 = 32;
1219
+ function newSlotAcceptanceState() {
1220
+ return {
1221
+ foundBit: 0,
1222
+ conflictBit: 0,
1223
+ selectedCek: new Uint8Array(CEK_LENGTH2),
1224
+ selectedSlotIdx: -1
1225
+ };
1226
+ }
1227
+ function foldSlotAcceptance(state, ok, candidateCek, slotIdx) {
1228
+ if (candidateCek.length !== CEK_LENGTH2) {
1229
+ throw new Error(
1230
+ `candidate CEK MUST be exactly ${CEK_LENGTH2} bytes, got ${candidateCek.length}`
1231
+ );
1232
+ }
1233
+ const okBit = ok & 1;
1234
+ const firstBit = okBit & (state.foundBit ^ 1);
1235
+ const firstByteMask = -firstBit & 255;
1236
+ const firstWordMask = -firstBit | 0;
1237
+ let diff = 0;
1238
+ for (let i = 0; i < CEK_LENGTH2; i++) {
1239
+ diff |= candidateCek[i] ^ state.selectedCek[i];
1240
+ }
1241
+ const neqBit = (diff | -diff) >>> 31 & 1;
1242
+ state.conflictBit |= okBit & state.foundBit & neqBit;
1243
+ for (let i = 0; i < CEK_LENGTH2; i++) {
1244
+ state.selectedCek[i] = candidateCek[i] & firstByteMask | state.selectedCek[i] & ~firstByteMask & 255;
1245
+ }
1246
+ state.selectedSlotIdx = slotIdx & firstWordMask | state.selectedSlotIdx & ~firstWordMask;
1247
+ state.foundBit |= okBit;
1248
+ }
1249
+ function finishSlotAcceptance(state) {
1250
+ const found = state.foundBit === 1;
1251
+ return {
1252
+ found,
1253
+ cekConflict: state.conflictBit === 1,
1254
+ selectedCek: found ? state.selectedCek : null,
1255
+ selectedSlotIdx: state.selectedSlotIdx
1256
+ };
1257
+ }
1004
1258
  function selectBundleSecrets(envelope, bundle) {
1005
1259
  return envelope.kem === "x25519" ? bundle.x25519PrivateKeys : bundle.mlkem768x25519SecretSeeds;
1006
1260
  }
1007
1261
  var ZERO_NONCE_122 = new Uint8Array(12);
1008
- var EMPTY_SALT22 = new Uint8Array(0);
1262
+ var CEK_LENGTH3 = 32;
1009
1263
  var X25519_SECRET_KEY_LENGTH2 = 32;
1010
1264
  var X25519_PUBLIC_KEY_LENGTH2 = 32;
1011
- var NONCE_LENGTH2 = 24;
1265
+ var NONCE_LENGTH3 = 24;
1012
1266
  var WRAP_LENGTH2 = 48;
1013
1267
  var SLOTS_MAC_LENGTH2 = 32;
1014
- function concat2(a, b) {
1015
- const out = new Uint8Array(a.length + b.length);
1016
- out.set(a, 0);
1017
- out.set(b, a.length);
1018
- return out;
1268
+ function bytesKey(bytes) {
1269
+ let s = "";
1270
+ for (let i = 0; i < bytes.length; i++) {
1271
+ s += String.fromCharCode(bytes[i]);
1272
+ }
1273
+ return s;
1019
1274
  }
1020
1275
  function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1021
1276
  if (envelope.scheme !== 1) {
1022
1277
  throw new EciesSealedPoeError(
1023
- "UNSUPPORTED_ENC_VERSION",
1278
+ "UNSUPPORTED_ENVELOPE_SCHEME",
1024
1279
  `envelope.scheme=${String(envelope.scheme)} unsupported (expected 1)`
1025
1280
  );
1026
1281
  }
1027
- if (envelope.aead !== "xchacha20-poly1305") {
1282
+ if (envelope.aead !== SEALED_POE_AEAD) {
1028
1283
  throw new EciesSealedPoeError(
1029
1284
  "UNSUPPORTED_AEAD_ALG",
1030
- `envelope.aead=${String(envelope.aead)} unsupported (expected 'xchacha20-poly1305')`
1285
+ `envelope.aead=${String(envelope.aead)} unsupported (expected '${SEALED_POE_AEAD}')`
1031
1286
  );
1032
1287
  }
1033
1288
  if (envelope.kem !== "x25519" && envelope.kem !== "mlkem768x25519") {
@@ -1040,10 +1295,16 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1040
1295
  if (n < 1) {
1041
1296
  throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
1042
1297
  }
1043
- if (envelope.nonce.length !== NONCE_LENGTH2) {
1298
+ if (n > MAX_SLOTS) {
1299
+ throw new EciesSealedPoeError(
1300
+ "ENC_SLOTS_TOO_MANY",
1301
+ `envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
1302
+ );
1303
+ }
1304
+ if (envelope.nonce.length !== NONCE_LENGTH3) {
1044
1305
  throw new EciesSealedPoeError(
1045
1306
  "NONCE_LENGTH_MISMATCH",
1046
- `envelope.nonce MUST be exactly ${NONCE_LENGTH2} bytes, got ${envelope.nonce.length}`
1307
+ `envelope.nonce MUST be exactly ${NONCE_LENGTH3} bytes, got ${envelope.nonce.length}`
1047
1308
  );
1048
1309
  }
1049
1310
  if (envelope.slots_mac.length !== SLOTS_MAC_LENGTH2) {
@@ -1052,6 +1313,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1052
1313
  `envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
1053
1314
  );
1054
1315
  }
1316
+ const seenKemMaterial = /* @__PURE__ */ new Set();
1055
1317
  if (envelope.kem === "x25519") {
1056
1318
  for (let i = 0; i < n; i++) {
1057
1319
  const slot = envelope.slots[i];
@@ -1067,15 +1329,22 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1067
1329
  `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
1068
1330
  );
1069
1331
  }
1332
+ const key = bytesKey(slot.epk);
1333
+ if (seenKemMaterial.has(key)) {
1334
+ throw new EciesSealedPoeError(
1335
+ "ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
1336
+ `envelope.slots[${i}].epk duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
1337
+ );
1338
+ }
1339
+ seenKemMaterial.add(key);
1070
1340
  }
1071
1341
  } else {
1072
1342
  for (let i = 0; i < n; i++) {
1073
1343
  const slot = envelope.slots[i];
1074
- const enc = joinKemCt(slot.kem_ct);
1075
- if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
1344
+ if (slot.kem_ct.length !== MLKEM768X25519_ENC_LENGTH) {
1076
1345
  throw new EciesSealedPoeError(
1077
1346
  "KEM_CT_LENGTH_MISMATCH",
1078
- `envelope.slots[${i}].kem_ct MUST reassemble to exactly ${MLKEM768X25519_ENC_LENGTH} bytes, got ${enc.length}`
1347
+ `envelope.slots[${i}].kem_ct MUST be exactly ${MLKEM768X25519_ENC_LENGTH} bytes, got ${slot.kem_ct.length}`
1079
1348
  );
1080
1349
  }
1081
1350
  if (slot.wrap.length !== WRAP_LENGTH2) {
@@ -1084,8 +1353,24 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1084
1353
  `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
1085
1354
  );
1086
1355
  }
1356
+ const key = bytesKey(slot.kem_ct);
1357
+ if (seenKemMaterial.has(key)) {
1358
+ throw new EciesSealedPoeError(
1359
+ "ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
1360
+ `envelope.slots[${i}].kem_ct duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
1361
+ );
1362
+ }
1363
+ seenKemMaterial.add(key);
1087
1364
  }
1088
1365
  }
1366
+ const perSlotBytes = envelope.kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH2 + WRAP_LENGTH2 : MLKEM768X25519_ENC_LENGTH + WRAP_LENGTH2;
1367
+ const decodedEnvelopeBytes = NONCE_LENGTH3 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
1368
+ if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
1369
+ throw new EciesSealedPoeError(
1370
+ "ENC_ENVELOPE_TOO_LARGE",
1371
+ `decoded envelope size ${decodedEnvelopeBytes} exceeds MAX_DECODED_ENVELOPE_BYTES=${MAX_DECODED_ENVELOPE_BYTES}`
1372
+ );
1373
+ }
1089
1374
  if (multiPrivKeys !== void 0) {
1090
1375
  for (let i = 0; i < multiPrivKeys.length; i++) {
1091
1376
  if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
@@ -1104,125 +1389,118 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1104
1389
  }
1105
1390
  }
1106
1391
  }
1107
- function tryX25519Slot(args) {
1108
- if (args.liveSlot) {
1109
- try {
1110
- const shared = x25519Ecdh({
1111
- secretKey: args.recipientSecretKey,
1112
- theirPublicKey: args.slot.epk
1113
- });
1114
- const kek = hkdfSha2562({
1115
- ikm: shared,
1116
- salt: concat2(args.slot.epk, args.pubRLocal),
1117
- info: CARDANO_POE_HKDF_INFO_KEK,
1118
- length: 32
1119
- });
1120
- return chacha20Poly1305Decrypt({
1121
- key: kek,
1122
- nonce: ZERO_NONCE_122,
1123
- aad: CARDANO_POE_HKDF_INFO_KEK,
1124
- ciphertext: args.slot.wrap
1125
- });
1126
- } catch (e) {
1127
- if (!(e instanceof AeadVerificationError) && !(e instanceof X25519LowOrderPointError)) {
1128
- throw e;
1129
- }
1130
- return null;
1392
+ var ZERO_IKM_32 = new Uint8Array(32);
1393
+ var DUMMY_CEK_32 = new Uint8Array(32);
1394
+ function wrapOpenOrDummy(kek, aad, wrap) {
1395
+ try {
1396
+ const plaintext = chacha20Poly1305Decrypt({
1397
+ key: kek,
1398
+ nonce: ZERO_NONCE_122,
1399
+ aad,
1400
+ ciphertext: wrap
1401
+ });
1402
+ if (plaintext.length === CEK_LENGTH3) {
1403
+ return { ok: 1, candidate: plaintext };
1131
1404
  }
1405
+ return { ok: 0, candidate: DUMMY_CEK_32 };
1406
+ } catch (e) {
1407
+ if (!(e instanceof AeadVerificationError)) throw e;
1408
+ return { ok: 0, candidate: DUMMY_CEK_32 };
1132
1409
  }
1410
+ }
1411
+ function tryX25519Slot(args) {
1412
+ const salt = x25519KekSalt({ nonce: args.nonce, epk: args.slot.epk, pubR: args.pubRLocal });
1413
+ let kemOk = 1;
1414
+ let kek;
1133
1415
  try {
1134
1416
  const shared = x25519Ecdh({
1135
1417
  secretKey: args.recipientSecretKey,
1136
1418
  theirPublicKey: args.slot.epk
1137
1419
  });
1138
- hkdfSha2562({
1139
- ikm: shared,
1140
- salt: concat2(args.slot.epk, args.pubRLocal),
1141
- info: CARDANO_POE_HKDF_INFO_KEK,
1142
- length: 32
1143
- });
1420
+ kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1144
1421
  } catch (e) {
1145
1422
  if (!(e instanceof X25519LowOrderPointError)) throw e;
1423
+ kemOk = 0;
1424
+ kek = hkdfSha2562({ ikm: ZERO_IKM_32, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1146
1425
  }
1147
- return null;
1426
+ const opened = wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK, args.slot.wrap);
1427
+ return { ok: kemOk & opened.ok, candidate: opened.candidate };
1148
1428
  }
1149
1429
  function tryMlkem768X25519Slot(args) {
1150
- const enc = joinKemCt(args.slot.kem_ct);
1151
- const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
1430
+ const ss = mlkem768x25519Decapsulate({
1431
+ secretSeed: args.recipientSecretKey,
1432
+ enc: args.slot.kem_ct
1433
+ });
1152
1434
  const kek = hkdfSha2562({
1153
1435
  ikm: ss,
1154
- salt: EMPTY_SALT22,
1436
+ salt: xwingKekSalt({ nonce: args.nonce, kemCt: args.slot.kem_ct, pubR: args.pubR }),
1155
1437
  info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
1156
1438
  length: 32
1157
1439
  });
1158
- if (!args.liveSlot) {
1159
- return null;
1160
- }
1161
- try {
1162
- return chacha20Poly1305Decrypt({
1163
- key: kek,
1164
- nonce: ZERO_NONCE_122,
1165
- aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
1166
- ciphertext: args.slot.wrap
1167
- });
1168
- } catch (e) {
1169
- if (!(e instanceof AeadVerificationError)) throw e;
1170
- return null;
1171
- }
1440
+ return wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519, args.slot.wrap);
1172
1441
  }
1173
- function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
1442
+ function runPrivPass(envelope, recipientSecretKey, slotsHash, slotsAttemptedOut) {
1174
1443
  const n = envelope.slots.length;
1175
- let cek = null;
1176
- let matchedSlotIdx = -1;
1444
+ const state = newSlotAcceptanceState();
1445
+ let anyOpenedBit = 0;
1446
+ const acceptSlot = (slot, i) => {
1447
+ anyOpenedBit |= slot.ok;
1448
+ const macOk = Number(compareCt(computeSlotsMac({ cek: slot.candidate, slotsHash }), envelope.slots_mac)) & 1;
1449
+ foldSlotAcceptance(state, slot.ok & macOk, slot.candidate, i);
1450
+ };
1177
1451
  if (envelope.kem === "x25519") {
1178
1452
  const pubRLocal = x25519PublicKey2({ secretKey: recipientSecretKey });
1179
1453
  for (let i = 0; i < n; i++) {
1180
1454
  if (slotsAttemptedOut !== void 0) {
1181
1455
  slotsAttemptedOut.count = i + 1;
1182
1456
  }
1183
- const candidate = tryX25519Slot({
1184
- slot: envelope.slots[i],
1185
- recipientSecretKey,
1186
- pubRLocal,
1187
- liveSlot: cek === null
1188
- });
1189
- if (cek === null && candidate !== null) {
1190
- cek = candidate;
1191
- matchedSlotIdx = i;
1192
- }
1193
- if (cek !== null && !constantTimeN) break;
1457
+ acceptSlot(
1458
+ tryX25519Slot({
1459
+ slot: envelope.slots[i],
1460
+ nonce: envelope.nonce,
1461
+ recipientSecretKey,
1462
+ pubRLocal
1463
+ }),
1464
+ i
1465
+ );
1194
1466
  }
1195
1467
  } else {
1468
+ const pubR = mlkem768x25519Keygen2(recipientSecretKey).publicKey;
1196
1469
  for (let i = 0; i < n; i++) {
1197
1470
  if (slotsAttemptedOut !== void 0) {
1198
1471
  slotsAttemptedOut.count = i + 1;
1199
1472
  }
1200
- const candidate = tryMlkem768X25519Slot({
1201
- slot: envelope.slots[i],
1202
- recipientSecretKey,
1203
- liveSlot: cek === null
1204
- });
1205
- if (cek === null && candidate !== null) {
1206
- cek = candidate;
1207
- matchedSlotIdx = i;
1208
- }
1209
- if (cek !== null && !constantTimeN) break;
1473
+ acceptSlot(
1474
+ tryMlkem768X25519Slot({
1475
+ slot: envelope.slots[i],
1476
+ nonce: envelope.nonce,
1477
+ recipientSecretKey,
1478
+ pubR
1479
+ }),
1480
+ i
1481
+ );
1210
1482
  }
1211
1483
  }
1212
- return cek === null ? null : { cek, slotIdx: matchedSlotIdx };
1213
- }
1214
- function tryRecipientUnwrap(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
1215
- return tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut)?.cek ?? null;
1484
+ const outcome = finishSlotAcceptance(state);
1485
+ return {
1486
+ found: outcome.found,
1487
+ selectedCek: outcome.selectedCek,
1488
+ selectedSlotIdx: outcome.selectedSlotIdx,
1489
+ cekConflict: outcome.cekConflict,
1490
+ anyOpened: anyOpenedBit === 1
1491
+ };
1216
1492
  }
1217
- function slotsMacCborBytes(envelope) {
1218
- return slotsToMacCbor(
1219
- envelope.slots,
1220
- envelope.kem
1221
- );
1493
+ function slotsHashBytes(envelope, hashes3) {
1494
+ return computeSlotsHash({
1495
+ aead: envelope.aead,
1496
+ kem: envelope.kem,
1497
+ nonce: envelope.nonce,
1498
+ slots: envelope.slots,
1499
+ hashesHash: itemHashesHash(hashes3)
1500
+ });
1222
1501
  }
1223
1502
  function eciesSealedPoeUnwrap(args) {
1224
1503
  const { envelope, ciphertext } = args;
1225
- const constantTimeN = args.constantTimeN ?? true;
1226
1504
  const hasSingle = "recipientSecretKey" in args;
1227
1505
  const hasBundle = "recipientKeyBundle" in args;
1228
1506
  const multiPrivKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : "recipientSecretKeys" in args ? args.recipientSecretKeys : void 0;
@@ -1247,85 +1525,47 @@ function eciesSealedPoeUnwrap(args) {
1247
1525
  } else {
1248
1526
  assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
1249
1527
  }
1528
+ const slotsHash = slotsHashBytes(envelope, args.hashes);
1250
1529
  let matchedCek = null;
1251
- let anyCandidateRecovered = false;
1252
- if (hasSingle) {
1253
- const recipientSecretKey = args.recipientSecretKey;
1254
- const cek = tryRecipientUnwrap(
1255
- envelope,
1256
- recipientSecretKey,
1257
- constantTimeN,
1258
- args._slotsAttemptedOut
1259
- );
1260
- if (cek === null) {
1261
- return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
1530
+ let anyOpenedAcrossPrivs = false;
1531
+ const privKeys = hasMulti ? multiPrivKeys : [args.recipientSecretKey];
1532
+ for (let k = 0; k < privKeys.length; k++) {
1533
+ if (args._privsAttemptedOut !== void 0) {
1534
+ args._privsAttemptedOut.count = k + 1;
1262
1535
  }
1263
- const slotsCbor = slotsMacCborBytes(envelope);
1264
- const hmacKey = hkdfSha2562({
1265
- ikm: cek,
1266
- salt: EMPTY_SALT22,
1267
- info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1268
- length: 32
1269
- });
1270
- const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
1271
- if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
1272
- return { matched: false, reason: "TAMPERED_HEADER" };
1536
+ if (args._slotsAttemptedOut !== void 0) {
1537
+ args._slotsAttemptedOut.count = 0;
1273
1538
  }
1274
- matchedCek = cek;
1275
- } else {
1276
- const slotsCbor = slotsMacCborBytes(envelope);
1277
- const recipientSecretKeys = multiPrivKeys;
1278
- for (let k = 0; k < recipientSecretKeys.length; k++) {
1279
- if (args._privsAttemptedOut !== void 0) {
1280
- args._privsAttemptedOut.count = k + 1;
1281
- }
1282
- if (args._slotsAttemptedOut !== void 0) {
1283
- args._slotsAttemptedOut.count = 0;
1284
- }
1285
- const cek = tryRecipientUnwrap(
1286
- envelope,
1287
- recipientSecretKeys[k],
1288
- constantTimeN,
1289
- args._slotsAttemptedOut
1290
- );
1291
- if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
1292
- args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
1293
- }
1294
- if (cek === null) continue;
1295
- const hmacKey = hkdfSha2562({
1296
- ikm: cek,
1297
- salt: EMPTY_SALT22,
1298
- info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1299
- length: 32
1300
- });
1301
- const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
1302
- if (compareCt(slotsMacCalc, envelope.slots_mac)) {
1303
- matchedCek = cek;
1304
- break;
1305
- }
1306
- anyCandidateRecovered = true;
1539
+ const pass = runPrivPass(envelope, privKeys[k], slotsHash, args._slotsAttemptedOut);
1540
+ if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
1541
+ args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
1307
1542
  }
1308
- if (matchedCek === null) {
1309
- return {
1310
- matched: false,
1311
- reason: anyCandidateRecovered ? "TAMPERED_HEADER" : "WRONG_RECIPIENT_KEY"
1312
- };
1543
+ anyOpenedAcrossPrivs = anyOpenedAcrossPrivs || pass.anyOpened;
1544
+ if (!pass.found) continue;
1545
+ if (pass.cekConflict) {
1546
+ return { matched: false, reason: "TAMPERED_HEADER" };
1313
1547
  }
1548
+ matchedCek = pass.selectedCek;
1549
+ break;
1550
+ }
1551
+ if (matchedCek === null) {
1552
+ return {
1553
+ matched: false,
1554
+ reason: anyOpenedAcrossPrivs ? "TAMPERED_HEADER" : "WRONG_RECIPIENT_KEY"
1555
+ };
1314
1556
  }
1315
- const adContent = concat2(envelope.nonce, envelope.slots_mac);
1316
1557
  try {
1317
- const plaintext = xchacha20Poly1305Decrypt({
1318
- key: matchedCek,
1319
- nonce: envelope.nonce,
1320
- aad: adContent,
1558
+ const plaintext = streamOpen({
1559
+ payloadKey: slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce }),
1321
1560
  ciphertext
1322
1561
  });
1323
1562
  return { matched: true, plaintext };
1324
1563
  } catch (e) {
1325
- if (!(e instanceof AeadVerificationError)) throw e;
1564
+ if (!(e instanceof StreamTamperedError)) throw e;
1326
1565
  return { matched: false, reason: "TAMPERED_CIPHERTEXT" };
1327
1566
  }
1328
1567
  }
1568
+ new TextEncoder();
1329
1569
  ed__namespace.hashes.sha512 = sha2_js.sha512;
1330
1570
  ed__namespace.Point.CURVE().n;
1331
1571
  function signEd25519(opts2) {
@@ -1367,6 +1607,7 @@ function decryptSealedFromSeed(args) {
1367
1607
  return eciesSealedPoeUnwrap({
1368
1608
  envelope: args.envelope,
1369
1609
  ciphertext: args.ciphertext,
1610
+ hashes: args.hashes,
1370
1611
  recipientKeyBundle: recipientKeyBundleFromSeed(args.seed)
1371
1612
  });
1372
1613
  }