@cardanowall/sdk-ts 0.3.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 +1140 -363
  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 +1138 -365
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/conformance/cli.cjs +4400 -2121
  8. package/dist/conformance/cli.cjs.map +1 -1
  9. package/dist/conformance/cli.js +4401 -2122
  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 +356 -230
  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 +356 -230
  28. package/dist/identity/index.js.map +1 -1
  29. package/dist/index.cjs +5474 -2518
  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 +5454 -2514
  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 +4419 -2147
  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 +4407 -2143
  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,16 +868,6 @@ 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;
882
873
  function mlkem768x25519Keygen2(seed) {
@@ -922,6 +913,9 @@ function x25519Ecdh(opts2) {
922
913
  throw e;
923
914
  }
924
915
  }
916
+ function hkdfSha2562(opts2) {
917
+ return hkdf_js.hkdf(sha2_js.sha256, opts2.ikm, opts2.salt, opts2.info, opts2.length);
918
+ }
925
919
  var EciesSealedPoeError = class extends Error {
926
920
  code;
927
921
  constructor(code, message, options) {
@@ -930,36 +924,138 @@ var EciesSealedPoeError = class extends Error {
930
924
  this.code = code;
931
925
  }
932
926
  };
933
- var CHUNK_MAX_BYTES = 64;
934
- function chunkKemCt(value) {
935
- if (value.length === 0) {
936
- throw new Error("chunkKemCt: refusing to chunk an empty byte string");
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;
937
955
  }
938
- const chunks = [];
939
- for (let i = 0; i < value.length; i += CHUNK_MAX_BYTES) {
940
- chunks.push(value.subarray(i, Math.min(i + CHUNK_MAX_BYTES, value.length)));
956
+ get done() {
957
+ return this.finished;
941
958
  }
942
- return chunks;
943
- }
944
- function joinKemCt(chunks) {
945
- let total = 0;
946
- for (const c of chunks) total += c.length;
947
- const out = new Uint8Array(total);
948
- let offset = 0;
949
- for (const c of chunks) {
950
- out.set(c, offset);
951
- offset += c.length;
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
+ );
952
973
  }
953
- return out;
954
974
  }
955
- function canonicalizeSlots(slots, kem) {
956
- if (kem === "x25519") {
957
- return slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
975
+ var StreamOpener = class {
976
+ payloadKey;
977
+ nonce = new ChunkNonce();
978
+ chunkIndex = 0;
979
+ constructor(payloadKey) {
980
+ assertPayloadKey(payloadKey);
981
+ this.payloadKey = payloadKey;
958
982
  }
959
- return slots.map((s) => ({
960
- kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
961
- wrap: s.wrap
962
- }));
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;
963
1059
  }
964
1060
  function encodeCanonicalCbor(value) {
965
1061
  return cbor2.encode(value, {
@@ -969,23 +1065,54 @@ function encodeCanonicalCbor(value) {
969
1065
  sortKeys: sorts.sortCoreDeterministic
970
1066
  });
971
1067
  }
1068
+ var CARDANO_POE_ITEM_HASHES_PREFIX = new TextEncoder().encode(
1069
+ "cardano-poe-item-hashes-v1"
1070
+ );
972
1071
  var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
973
1072
  "cardano-poe-slots-transcript-v1"
974
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
+ );
975
1083
  var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
976
1084
  "cardano-poe-payload-v1"
977
1085
  );
978
1086
  var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
979
1087
  "cardano-poe-payload-passphrase-v1"
980
1088
  );
1089
+ var CARDANO_POE_X25519_KEK_SALT_PREFIX = new TextEncoder().encode(
1090
+ "cardano-poe-x25519-kek-salt-v1"
1091
+ );
981
1092
  var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
982
1093
  "cardano-poe-xwing-kek-salt-v1"
983
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
+ }
984
1098
  if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
985
1099
  throw new Error(
986
1100
  "CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
987
1101
  );
988
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
+ }
989
1116
  if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
990
1117
  throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
991
1118
  }
@@ -994,52 +1121,62 @@ if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
994
1121
  "CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
995
1122
  );
996
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
+ }
997
1129
  if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
998
1130
  throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
999
1131
  }
1000
1132
  var MAX_SLOTS = 1024;
1001
1133
  var MAX_DECODED_ENVELOPE_BYTES = 65536;
1002
- var MAX_SEALED_PLAINTEXT = 274877906880;
1003
- var MAX_SEALED_CIPHERTEXT = MAX_SEALED_PLAINTEXT + 16;
1004
- function assertCiphertextWithinBound(ciphertextLength) {
1005
- if (ciphertextLength >= MAX_SEALED_CIPHERTEXT) {
1006
- throw new SealedPayloadTooLargeError(
1007
- `ciphertext length ${ciphertextLength} is at or above the maximum sealed ciphertext size ${MAX_SEALED_CIPHERTEXT}`
1008
- );
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;
1009
1144
  }
1145
+ return sha2_js.sha256(message);
1010
1146
  }
1011
- var SealedPayloadTooLargeError = class extends Error {
1012
- constructor(message) {
1013
- super(message);
1014
- this.name = "SealedPayloadTooLargeError";
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
+ );
1015
1153
  }
1016
- };
1154
+ return labelledSha256(CARDANO_POE_ITEM_HASHES_PREFIX, encodeCanonicalCbor(hashes3));
1155
+ }
1017
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
+ }));
1018
1161
  const transcript = {
1019
1162
  scheme: 1,
1020
1163
  path: "slots",
1021
- aead: "xchacha20-poly1305",
1164
+ aead: args.aead,
1022
1165
  kem: args.kem,
1023
1166
  nonce: args.nonce,
1024
- slots: canonicalizeSlots(args.slots, args.kem)
1167
+ slots,
1168
+ hashes_hash: args.hashesHash
1025
1169
  };
1026
- const encoded = encodeCanonicalCbor(transcript);
1027
- const message = new Uint8Array(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length + encoded.length);
1028
- message.set(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, 0);
1029
- message.set(encoded, CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length);
1030
- return sha2_js.sha256(message);
1170
+ return labelledSha256(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, encodeCanonicalCbor(transcript));
1031
1171
  }
1032
- function adContentSlots(args) {
1033
- const ad = {
1034
- scheme: 1,
1035
- path: "slots",
1036
- aead: "xchacha20-poly1305",
1037
- kem: args.kem,
1038
- nonce: args.nonce,
1039
- slots_hash: args.slotsHash,
1040
- slots_mac: args.slotsMac
1041
- };
1042
- return encodeCanonicalCbor(ad);
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);
1043
1180
  }
1044
1181
  function slotsPayloadKey(args) {
1045
1182
  return hkdfSha2562({
@@ -1049,25 +1186,17 @@ function slotsPayloadKey(args) {
1049
1186
  length: 32
1050
1187
  });
1051
1188
  }
1189
+ function x25519KekSalt(args) {
1190
+ return labelledSha256(CARDANO_POE_X25519_KEK_SALT_PREFIX, args.nonce, args.epk, args.pubR);
1191
+ }
1052
1192
  function xwingKekSalt(args) {
1053
- const message = new Uint8Array(
1054
- CARDANO_POE_XWING_KEK_SALT_PREFIX.length + args.kemCt.length + args.pubR.length
1055
- );
1056
- let offset = 0;
1057
- message.set(CARDANO_POE_XWING_KEK_SALT_PREFIX, offset);
1058
- offset += CARDANO_POE_XWING_KEK_SALT_PREFIX.length;
1059
- message.set(args.kemCt, offset);
1060
- offset += args.kemCt.length;
1061
- message.set(args.pubR, offset);
1062
- return sha2_js.sha256(message);
1193
+ return labelledSha256(CARDANO_POE_XWING_KEK_SALT_PREFIX, args.nonce, args.kemCt, args.pubR);
1063
1194
  }
1195
+ var SEALED_POE_AEAD = "chacha20-poly1305-stream64k";
1064
1196
  var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
1065
1197
  var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
1066
1198
  "cardano-poe-kek-mlkem768x25519-v1"
1067
1199
  );
1068
- var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
1069
- "cardano-poe-slots-mac-v1"
1070
- );
1071
1200
  var ZERO_NONCE_12 = new Uint8Array(12);
1072
1201
  if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
1073
1202
  throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
@@ -1077,9 +1206,6 @@ if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
1077
1206
  "CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
1078
1207
  );
1079
1208
  }
1080
- if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
1081
- throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
1082
- }
1083
1209
  if (ZERO_NONCE_12.length !== 12) {
1084
1210
  throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
1085
1211
  }
@@ -1089,22 +1215,56 @@ function compareCt(a, b) {
1089
1215
  for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
1090
1216
  return diff === 0;
1091
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
+ }
1092
1258
  function selectBundleSecrets(envelope, bundle) {
1093
1259
  return envelope.kem === "x25519" ? bundle.x25519PrivateKeys : bundle.mlkem768x25519SecretSeeds;
1094
1260
  }
1095
1261
  var ZERO_NONCE_122 = new Uint8Array(12);
1096
- var EMPTY_SALT22 = new Uint8Array(0);
1262
+ var CEK_LENGTH3 = 32;
1097
1263
  var X25519_SECRET_KEY_LENGTH2 = 32;
1098
1264
  var X25519_PUBLIC_KEY_LENGTH2 = 32;
1099
- var NONCE_LENGTH2 = 24;
1265
+ var NONCE_LENGTH3 = 24;
1100
1266
  var WRAP_LENGTH2 = 48;
1101
1267
  var SLOTS_MAC_LENGTH2 = 32;
1102
- function concat2(a, b) {
1103
- const out = new Uint8Array(a.length + b.length);
1104
- out.set(a, 0);
1105
- out.set(b, a.length);
1106
- return out;
1107
- }
1108
1268
  function bytesKey(bytes) {
1109
1269
  let s = "";
1110
1270
  for (let i = 0; i < bytes.length; i++) {
@@ -1115,14 +1275,14 @@ function bytesKey(bytes) {
1115
1275
  function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1116
1276
  if (envelope.scheme !== 1) {
1117
1277
  throw new EciesSealedPoeError(
1118
- "UNSUPPORTED_ENC_VERSION",
1278
+ "UNSUPPORTED_ENVELOPE_SCHEME",
1119
1279
  `envelope.scheme=${String(envelope.scheme)} unsupported (expected 1)`
1120
1280
  );
1121
1281
  }
1122
- if (envelope.aead !== "xchacha20-poly1305") {
1282
+ if (envelope.aead !== SEALED_POE_AEAD) {
1123
1283
  throw new EciesSealedPoeError(
1124
1284
  "UNSUPPORTED_AEAD_ALG",
1125
- `envelope.aead=${String(envelope.aead)} unsupported (expected 'xchacha20-poly1305')`
1285
+ `envelope.aead=${String(envelope.aead)} unsupported (expected '${SEALED_POE_AEAD}')`
1126
1286
  );
1127
1287
  }
1128
1288
  if (envelope.kem !== "x25519" && envelope.kem !== "mlkem768x25519") {
@@ -1141,10 +1301,10 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1141
1301
  `envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
1142
1302
  );
1143
1303
  }
1144
- if (envelope.nonce.length !== NONCE_LENGTH2) {
1304
+ if (envelope.nonce.length !== NONCE_LENGTH3) {
1145
1305
  throw new EciesSealedPoeError(
1146
1306
  "NONCE_LENGTH_MISMATCH",
1147
- `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}`
1148
1308
  );
1149
1309
  }
1150
1310
  if (envelope.slots_mac.length !== SLOTS_MAC_LENGTH2) {
@@ -1181,11 +1341,10 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1181
1341
  } else {
1182
1342
  for (let i = 0; i < n; i++) {
1183
1343
  const slot = envelope.slots[i];
1184
- const enc = joinKemCt(slot.kem_ct);
1185
- if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
1344
+ if (slot.kem_ct.length !== MLKEM768X25519_ENC_LENGTH) {
1186
1345
  throw new EciesSealedPoeError(
1187
1346
  "KEM_CT_LENGTH_MISMATCH",
1188
- `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}`
1189
1348
  );
1190
1349
  }
1191
1350
  if (slot.wrap.length !== WRAP_LENGTH2) {
@@ -1194,7 +1353,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1194
1353
  `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
1195
1354
  );
1196
1355
  }
1197
- const key = bytesKey(enc);
1356
+ const key = bytesKey(slot.kem_ct);
1198
1357
  if (seenKemMaterial.has(key)) {
1199
1358
  throw new EciesSealedPoeError(
1200
1359
  "ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
@@ -1205,7 +1364,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1205
1364
  }
1206
1365
  }
1207
1366
  const perSlotBytes = envelope.kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH2 + WRAP_LENGTH2 : MLKEM768X25519_ENC_LENGTH + WRAP_LENGTH2;
1208
- const decodedEnvelopeBytes = NONCE_LENGTH2 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
1367
+ const decodedEnvelopeBytes = NONCE_LENGTH3 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
1209
1368
  if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
1210
1369
  throw new EciesSealedPoeError(
1211
1370
  "ENC_ENVELOPE_TOO_LARGE",
@@ -1231,66 +1390,63 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1231
1390
  }
1232
1391
  }
1233
1392
  var ZERO_IKM_32 = new Uint8Array(32);
1234
- function tryX25519Slot(args) {
1235
- const salt = concat2(args.slot.epk, args.pubRLocal);
1236
- let shared;
1393
+ var DUMMY_CEK_32 = new Uint8Array(32);
1394
+ function wrapOpenOrDummy(kek, aad, wrap) {
1237
1395
  try {
1238
- shared = x25519Ecdh({
1239
- secretKey: args.recipientSecretKey,
1240
- theirPublicKey: args.slot.epk
1396
+ const plaintext = chacha20Poly1305Decrypt({
1397
+ key: kek,
1398
+ nonce: ZERO_NONCE_122,
1399
+ aad,
1400
+ ciphertext: wrap
1241
1401
  });
1402
+ if (plaintext.length === CEK_LENGTH3) {
1403
+ return { ok: 1, candidate: plaintext };
1404
+ }
1405
+ return { ok: 0, candidate: DUMMY_CEK_32 };
1242
1406
  } catch (e) {
1243
- if (!(e instanceof X25519LowOrderPointError)) throw e;
1244
- hkdfSha2562({ ikm: ZERO_IKM_32, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1245
- return null;
1407
+ if (!(e instanceof AeadVerificationError)) throw e;
1408
+ return { ok: 0, candidate: DUMMY_CEK_32 };
1246
1409
  }
1247
- const kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
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;
1248
1415
  try {
1249
- return chacha20Poly1305Decrypt({
1250
- key: kek,
1251
- nonce: ZERO_NONCE_122,
1252
- aad: CARDANO_POE_HKDF_INFO_KEK,
1253
- ciphertext: args.slot.wrap
1416
+ const shared = x25519Ecdh({
1417
+ secretKey: args.recipientSecretKey,
1418
+ theirPublicKey: args.slot.epk
1254
1419
  });
1420
+ kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1255
1421
  } catch (e) {
1256
- if (!(e instanceof AeadVerificationError)) throw e;
1257
- return null;
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 });
1258
1425
  }
1426
+ const opened = wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK, args.slot.wrap);
1427
+ return { ok: kemOk & opened.ok, candidate: opened.candidate };
1259
1428
  }
1260
1429
  function tryMlkem768X25519Slot(args) {
1261
- const enc = joinKemCt(args.slot.kem_ct);
1262
- const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
1430
+ const ss = mlkem768x25519Decapsulate({
1431
+ secretSeed: args.recipientSecretKey,
1432
+ enc: args.slot.kem_ct
1433
+ });
1263
1434
  const kek = hkdfSha2562({
1264
1435
  ikm: ss,
1265
- salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
1436
+ salt: xwingKekSalt({ nonce: args.nonce, kemCt: args.slot.kem_ct, pubR: args.pubR }),
1266
1437
  info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
1267
1438
  length: 32
1268
1439
  });
1269
- try {
1270
- return chacha20Poly1305Decrypt({
1271
- key: kek,
1272
- nonce: ZERO_NONCE_122,
1273
- aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
1274
- ciphertext: args.slot.wrap
1275
- });
1276
- } catch (e) {
1277
- if (!(e instanceof AeadVerificationError)) throw e;
1278
- return null;
1279
- }
1440
+ return wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519, args.slot.wrap);
1280
1441
  }
1281
- function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
1442
+ function runPrivPass(envelope, recipientSecretKey, slotsHash, slotsAttemptedOut) {
1282
1443
  const n = envelope.slots.length;
1283
- let cek = null;
1284
- let matchedSlotIdx = -1;
1285
- let cekConflict = false;
1286
- const recordMatch = (candidate, i) => {
1287
- if (candidate === null) return;
1288
- if (cek === null) {
1289
- cek = candidate;
1290
- matchedSlotIdx = i;
1291
- } else if (!compareCt(candidate, cek)) {
1292
- cekConflict = true;
1293
- }
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);
1294
1450
  };
1295
1451
  if (envelope.kem === "x25519") {
1296
1452
  const pubRLocal = x25519PublicKey2({ secretKey: recipientSecretKey });
@@ -1298,8 +1454,15 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
1298
1454
  if (slotsAttemptedOut !== void 0) {
1299
1455
  slotsAttemptedOut.count = i + 1;
1300
1456
  }
1301
- recordMatch(tryX25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubRLocal }), i);
1302
- 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
+ );
1303
1466
  }
1304
1467
  } else {
1305
1468
  const pubR = mlkem768x25519Keygen2(recipientSecretKey).publicKey;
@@ -1307,22 +1470,37 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
1307
1470
  if (slotsAttemptedOut !== void 0) {
1308
1471
  slotsAttemptedOut.count = i + 1;
1309
1472
  }
1310
- recordMatch(tryMlkem768X25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubR }), i);
1311
- 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
+ );
1312
1482
  }
1313
1483
  }
1314
- return cek === null ? null : { cek, slotIdx: matchedSlotIdx, cekConflict };
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
+ };
1315
1492
  }
1316
- function slotsHashBytes(envelope) {
1493
+ function slotsHashBytes(envelope, hashes3) {
1317
1494
  return computeSlotsHash({
1495
+ aead: envelope.aead,
1318
1496
  kem: envelope.kem,
1319
1497
  nonce: envelope.nonce,
1320
- slots: envelope.slots
1498
+ slots: envelope.slots,
1499
+ hashesHash: itemHashesHash(hashes3)
1321
1500
  });
1322
1501
  }
1323
1502
  function eciesSealedPoeUnwrap(args) {
1324
1503
  const { envelope, ciphertext } = args;
1325
- const constantTimeN = args.constantTimeN ?? true;
1326
1504
  const hasSingle = "recipientSecretKey" in args;
1327
1505
  const hasBundle = "recipientKeyBundle" in args;
1328
1506
  const multiPrivKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : "recipientSecretKeys" in args ? args.recipientSecretKeys : void 0;
@@ -1347,100 +1525,47 @@ function eciesSealedPoeUnwrap(args) {
1347
1525
  } else {
1348
1526
  assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
1349
1527
  }
1350
- assertCiphertextWithinBound(ciphertext.length);
1351
- const slotsHash = slotsHashBytes(envelope);
1528
+ const slotsHash = slotsHashBytes(envelope, args.hashes);
1352
1529
  let matchedCek = null;
1353
- let anyCandidateRecovered = false;
1354
- if (hasSingle) {
1355
- const recipientSecretKey = args.recipientSecretKey;
1356
- const candidate = tryRecipientUnwrapWithIdx(
1357
- envelope,
1358
- recipientSecretKey,
1359
- constantTimeN,
1360
- args._slotsAttemptedOut
1361
- );
1362
- if (candidate === null) {
1363
- return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
1364
- }
1365
- if (candidate.cekConflict) {
1366
- return { matched: false, reason: "TAMPERED_HEADER" };
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;
1367
1535
  }
1368
- const hmacKey = hkdfSha2562({
1369
- ikm: candidate.cek,
1370
- salt: EMPTY_SALT22,
1371
- info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1372
- length: 32
1373
- });
1374
- const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
1375
- if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
1376
- return { matched: false, reason: "TAMPERED_HEADER" };
1536
+ if (args._slotsAttemptedOut !== void 0) {
1537
+ args._slotsAttemptedOut.count = 0;
1377
1538
  }
1378
- matchedCek = candidate.cek;
1379
- } else {
1380
- const recipientSecretKeys = multiPrivKeys;
1381
- let cekConflict = false;
1382
- for (let k = 0; k < recipientSecretKeys.length; k++) {
1383
- if (args._privsAttemptedOut !== void 0) {
1384
- args._privsAttemptedOut.count = k + 1;
1385
- }
1386
- if (args._slotsAttemptedOut !== void 0) {
1387
- args._slotsAttemptedOut.count = 0;
1388
- }
1389
- const candidate = tryRecipientUnwrapWithIdx(
1390
- envelope,
1391
- recipientSecretKeys[k],
1392
- constantTimeN,
1393
- args._slotsAttemptedOut
1394
- );
1395
- if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
1396
- args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
1397
- }
1398
- if (candidate === null) continue;
1399
- if (candidate.cekConflict) cekConflict = true;
1400
- const cek = candidate.cek;
1401
- const hmacKey = hkdfSha2562({
1402
- ikm: cek,
1403
- salt: EMPTY_SALT22,
1404
- info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1405
- length: 32
1406
- });
1407
- const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
1408
- if (compareCt(slotsMacCalc, envelope.slots_mac)) {
1409
- matchedCek = cek;
1410
- break;
1411
- }
1412
- 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);
1413
1542
  }
1414
- if (matchedCek !== null && cekConflict) {
1543
+ anyOpenedAcrossPrivs = anyOpenedAcrossPrivs || pass.anyOpened;
1544
+ if (!pass.found) continue;
1545
+ if (pass.cekConflict) {
1415
1546
  return { matched: false, reason: "TAMPERED_HEADER" };
1416
1547
  }
1417
- if (matchedCek === null) {
1418
- return {
1419
- matched: false,
1420
- reason: anyCandidateRecovered ? "TAMPERED_HEADER" : "WRONG_RECIPIENT_KEY"
1421
- };
1422
- }
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
+ };
1423
1556
  }
1424
- const payloadKey = slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce });
1425
- const adContent = adContentSlots({
1426
- kem: envelope.kem,
1427
- nonce: envelope.nonce,
1428
- slotsHash,
1429
- slotsMac: envelope.slots_mac
1430
- });
1431
1557
  try {
1432
- const plaintext = xchacha20Poly1305Decrypt({
1433
- key: payloadKey,
1434
- nonce: envelope.nonce,
1435
- aad: adContent,
1558
+ const plaintext = streamOpen({
1559
+ payloadKey: slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce }),
1436
1560
  ciphertext
1437
1561
  });
1438
1562
  return { matched: true, plaintext };
1439
1563
  } catch (e) {
1440
- if (!(e instanceof AeadVerificationError)) throw e;
1564
+ if (!(e instanceof StreamTamperedError)) throw e;
1441
1565
  return { matched: false, reason: "TAMPERED_CIPHERTEXT" };
1442
1566
  }
1443
1567
  }
1568
+ new TextEncoder();
1444
1569
  ed__namespace.hashes.sha512 = sha2_js.sha512;
1445
1570
  ed__namespace.Point.CURVE().n;
1446
1571
  function signEd25519(opts2) {
@@ -1482,6 +1607,7 @@ function decryptSealedFromSeed(args) {
1482
1607
  return eciesSealedPoeUnwrap({
1483
1608
  envelope: args.envelope,
1484
1609
  ciphertext: args.ciphertext,
1610
+ hashes: args.hashes,
1485
1611
  recipientKeyBundle: recipientKeyBundleFromSeed(args.seed)
1486
1612
  });
1487
1613
  }