@cardanowall/sdk-ts 0.3.0 → 0.5.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 +1146 -365
  2. package/dist/client/index.cjs.map +1 -1
  3. package/dist/client/index.d.cts +48 -7
  4. package/dist/client/index.d.ts +48 -7
  5. package/dist/client/index.js +1144 -367
  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 +5480 -2520
  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 +5460 -2516
  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-DNu_IrWZ.d.cts} +236 -7
  40. package/dist/{types-DGsZTMuZ.d.ts → types-DNu_IrWZ.d.ts} +236 -7
  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
@@ -11,10 +11,11 @@ import { anumber, abytes, randomBytes as randomBytes$1, u32, swap32IfBE } from '
11
11
  import { FFTCore, reverseBits } from '@noble/curves/abstract/fft.js';
12
12
  import * as ed from '@noble/ed25519';
13
13
  import '@noble/ciphers/utils.js';
14
+ import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
14
15
  import { hmac } from '@noble/hashes/hmac.js';
15
- import { xchacha20poly1305, chacha20poly1305 } from '@noble/ciphers/chacha.js';
16
16
  import { encode } from 'cbor2';
17
17
  import { sortCoreDeterministic } from 'cbor2/sorts';
18
+ import 'hash-wasm';
18
19
 
19
20
  // ../crypto-core/dist/seed-derive.js
20
21
  var abytesDoc = abytes;
@@ -845,16 +846,6 @@ function chacha20Poly1305Decrypt(opts2) {
845
846
  throw new AeadVerificationError("chacha20-poly1305 decrypt failed", { cause });
846
847
  }
847
848
  }
848
- function xchacha20Poly1305Decrypt(opts2) {
849
- try {
850
- return xchacha20poly1305(opts2.key, opts2.nonce, opts2.aad).decrypt(opts2.ciphertext);
851
- } catch (cause) {
852
- throw new AeadVerificationError("xchacha20-poly1305 decrypt failed", { cause });
853
- }
854
- }
855
- function hkdfSha2562(opts2) {
856
- return hkdf(sha256, opts2.ikm, opts2.salt, opts2.info, opts2.length);
857
- }
858
849
  var MLKEM768X25519_ENC_LENGTH = 1120;
859
850
  var MLKEM768X25519_SEED_LENGTH2 = 32;
860
851
  function mlkem768x25519Keygen2(seed) {
@@ -900,6 +891,9 @@ function x25519Ecdh(opts2) {
900
891
  throw e;
901
892
  }
902
893
  }
894
+ function hkdfSha2562(opts2) {
895
+ return hkdf(sha256, opts2.ikm, opts2.salt, opts2.info, opts2.length);
896
+ }
903
897
  var EciesSealedPoeError = class extends Error {
904
898
  code;
905
899
  constructor(code, message, options) {
@@ -908,36 +902,138 @@ var EciesSealedPoeError = class extends Error {
908
902
  this.code = code;
909
903
  }
910
904
  };
911
- var CHUNK_MAX_BYTES = 64;
912
- function chunkKemCt(value) {
913
- if (value.length === 0) {
914
- throw new Error("chunkKemCt: refusing to chunk an empty byte string");
905
+ var CHUNK_SIZE = 65536;
906
+ var TAG_SIZE = 16;
907
+ var NONCE_LENGTH = 12;
908
+ var COUNTER_LENGTH = 11;
909
+ var SEALED_CHUNK_SIZE = CHUNK_SIZE + TAG_SIZE;
910
+ var PAYLOAD_KEY_LENGTH = 32;
911
+ var EMPTY_AAD = new Uint8Array(0);
912
+ var StreamTamperedError = class extends Error {
913
+ code = "TAMPERED_CIPHERTEXT";
914
+ constructor(message, options) {
915
+ super(message, options);
916
+ this.name = "StreamTamperedError";
917
+ }
918
+ };
919
+ var ChunkNonce = class {
920
+ nonce = new Uint8Array(NONCE_LENGTH);
921
+ finished = false;
922
+ next(final) {
923
+ if (this.finished) {
924
+ throw new Error("STREAM: no chunks may follow the final chunk");
925
+ }
926
+ if (final) {
927
+ this.finished = true;
928
+ this.nonce[COUNTER_LENGTH] = 1;
929
+ }
930
+ const out = this.nonce.slice();
931
+ this.increment();
932
+ return out;
915
933
  }
916
- const chunks = [];
917
- for (let i = 0; i < value.length; i += CHUNK_MAX_BYTES) {
918
- chunks.push(value.subarray(i, Math.min(i + CHUNK_MAX_BYTES, value.length)));
934
+ get done() {
935
+ return this.finished;
919
936
  }
920
- return chunks;
921
- }
922
- function joinKemCt(chunks) {
923
- let total = 0;
924
- for (const c of chunks) total += c.length;
925
- const out = new Uint8Array(total);
926
- let offset = 0;
927
- for (const c of chunks) {
928
- out.set(c, offset);
929
- offset += c.length;
937
+ increment() {
938
+ for (let i = COUNTER_LENGTH - 1; i >= 0; i--) {
939
+ const v = this.nonce[i] + 1 & 255;
940
+ this.nonce[i] = v;
941
+ if (v !== 0) return;
942
+ }
943
+ throw new Error("STREAM: chunk counter overflow");
944
+ }
945
+ };
946
+ function assertPayloadKey(payloadKey) {
947
+ if (payloadKey.length !== PAYLOAD_KEY_LENGTH) {
948
+ throw new Error(
949
+ `STREAM: payloadKey MUST be exactly ${PAYLOAD_KEY_LENGTH} bytes, got ${payloadKey.length}`
950
+ );
930
951
  }
931
- return out;
932
952
  }
933
- function canonicalizeSlots(slots, kem) {
934
- if (kem === "x25519") {
935
- return slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
953
+ var StreamOpener = class {
954
+ payloadKey;
955
+ nonce = new ChunkNonce();
956
+ chunkIndex = 0;
957
+ constructor(payloadKey) {
958
+ assertPayloadKey(payloadKey);
959
+ this.payloadKey = payloadKey;
936
960
  }
937
- return slots.map((s) => ({
938
- kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
939
- wrap: s.wrap
940
- }));
961
+ openChunk(sealedChunk, final) {
962
+ if (sealedChunk.length < TAG_SIZE) {
963
+ throw new StreamTamperedError(
964
+ `STREAM: sealed chunk shorter than the ${TAG_SIZE}-byte tag floor`
965
+ );
966
+ }
967
+ if (!final && sealedChunk.length !== SEALED_CHUNK_SIZE) {
968
+ throw new StreamTamperedError(
969
+ `STREAM: non-final sealed chunk MUST be exactly ${SEALED_CHUNK_SIZE} bytes, got ${sealedChunk.length}`
970
+ );
971
+ }
972
+ if (final && sealedChunk.length > SEALED_CHUNK_SIZE) {
973
+ throw new StreamTamperedError(
974
+ `STREAM: final sealed chunk MUST be at most ${SEALED_CHUNK_SIZE} bytes, got ${sealedChunk.length}`
975
+ );
976
+ }
977
+ if (final && sealedChunk.length === TAG_SIZE && this.chunkIndex > 0) {
978
+ throw new StreamTamperedError("STREAM: zero-length final chunk on a non-empty stream");
979
+ }
980
+ let plaintext;
981
+ try {
982
+ plaintext = chacha20Poly1305Decrypt({
983
+ key: this.payloadKey,
984
+ nonce: this.nonce.next(final),
985
+ aad: EMPTY_AAD,
986
+ ciphertext: sealedChunk
987
+ });
988
+ } catch (e) {
989
+ if (!(e instanceof AeadVerificationError)) throw e;
990
+ throw new StreamTamperedError(`STREAM: chunk ${this.chunkIndex} tag verification failed`, {
991
+ cause: e
992
+ });
993
+ }
994
+ this.chunkIndex += 1;
995
+ return plaintext;
996
+ }
997
+ };
998
+ function streamOpen(args) {
999
+ const { ciphertext } = args;
1000
+ const total = ciphertext.length;
1001
+ if (total < TAG_SIZE) {
1002
+ throw new StreamTamperedError(
1003
+ `STREAM: ciphertext shorter than the ${TAG_SIZE}-byte single-tag floor`
1004
+ );
1005
+ }
1006
+ const rem = total % SEALED_CHUNK_SIZE;
1007
+ let nonFinalCount;
1008
+ let finalSealedLength;
1009
+ if (rem === 0) {
1010
+ nonFinalCount = total / SEALED_CHUNK_SIZE - 1;
1011
+ finalSealedLength = SEALED_CHUNK_SIZE;
1012
+ } else if (rem >= TAG_SIZE) {
1013
+ nonFinalCount = (total - rem) / SEALED_CHUNK_SIZE;
1014
+ finalSealedLength = rem;
1015
+ } else {
1016
+ throw new StreamTamperedError("STREAM: trailing bytes cannot form a well-formed final chunk");
1017
+ }
1018
+ if (nonFinalCount > 0 && finalSealedLength === TAG_SIZE) {
1019
+ throw new StreamTamperedError("STREAM: zero-length final chunk on a non-empty stream");
1020
+ }
1021
+ const opener = new StreamOpener(args.payloadKey);
1022
+ const out = new Uint8Array(nonFinalCount * CHUNK_SIZE + finalSealedLength - TAG_SIZE);
1023
+ let readOffset = 0;
1024
+ let writeOffset = 0;
1025
+ for (let i = 0; i < nonFinalCount; i++) {
1026
+ const plaintext = opener.openChunk(
1027
+ ciphertext.subarray(readOffset, readOffset + SEALED_CHUNK_SIZE),
1028
+ false
1029
+ );
1030
+ out.set(plaintext, writeOffset);
1031
+ readOffset += SEALED_CHUNK_SIZE;
1032
+ writeOffset += CHUNK_SIZE;
1033
+ }
1034
+ const finalPlaintext = opener.openChunk(ciphertext.subarray(readOffset), true);
1035
+ out.set(finalPlaintext, writeOffset);
1036
+ return out;
941
1037
  }
942
1038
  function encodeCanonicalCbor(value) {
943
1039
  return encode(value, {
@@ -947,23 +1043,54 @@ function encodeCanonicalCbor(value) {
947
1043
  sortKeys: sortCoreDeterministic
948
1044
  });
949
1045
  }
1046
+ var CARDANO_POE_ITEM_HASHES_PREFIX = new TextEncoder().encode(
1047
+ "cardano-poe-item-hashes-v1"
1048
+ );
950
1049
  var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
951
1050
  "cardano-poe-slots-transcript-v1"
952
1051
  );
1052
+ var CARDANO_POE_PASSPHRASE_TRANSCRIPT_PREFIX = new TextEncoder().encode(
1053
+ "cardano-poe-passphrase-transcript-v1"
1054
+ );
1055
+ var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
1056
+ "cardano-poe-slots-mac-v1"
1057
+ );
1058
+ var CARDANO_POE_HKDF_INFO_PASSPHRASE_MAC = new TextEncoder().encode(
1059
+ "cardano-poe-passphrase-mac-v1"
1060
+ );
953
1061
  var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
954
1062
  "cardano-poe-payload-v1"
955
1063
  );
956
1064
  var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
957
1065
  "cardano-poe-payload-passphrase-v1"
958
1066
  );
1067
+ var CARDANO_POE_X25519_KEK_SALT_PREFIX = new TextEncoder().encode(
1068
+ "cardano-poe-x25519-kek-salt-v1"
1069
+ );
959
1070
  var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
960
1071
  "cardano-poe-xwing-kek-salt-v1"
961
1072
  );
1073
+ if (CARDANO_POE_ITEM_HASHES_PREFIX.length !== 26) {
1074
+ throw new Error("CARDANO_POE_ITEM_HASHES_PREFIX byte-length invariant violated (expected 26)");
1075
+ }
962
1076
  if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
963
1077
  throw new Error(
964
1078
  "CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
965
1079
  );
966
1080
  }
1081
+ if (CARDANO_POE_PASSPHRASE_TRANSCRIPT_PREFIX.length !== 36) {
1082
+ throw new Error(
1083
+ "CARDANO_POE_PASSPHRASE_TRANSCRIPT_PREFIX byte-length invariant violated (expected 36)"
1084
+ );
1085
+ }
1086
+ if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
1087
+ throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
1088
+ }
1089
+ if (CARDANO_POE_HKDF_INFO_PASSPHRASE_MAC.length !== 29) {
1090
+ throw new Error(
1091
+ "CARDANO_POE_HKDF_INFO_PASSPHRASE_MAC byte-length invariant violated (expected 29)"
1092
+ );
1093
+ }
967
1094
  if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
968
1095
  throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
969
1096
  }
@@ -972,52 +1099,62 @@ if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
972
1099
  "CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
973
1100
  );
974
1101
  }
1102
+ if (CARDANO_POE_X25519_KEK_SALT_PREFIX.length !== 30) {
1103
+ throw new Error(
1104
+ "CARDANO_POE_X25519_KEK_SALT_PREFIX byte-length invariant violated (expected 30)"
1105
+ );
1106
+ }
975
1107
  if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
976
1108
  throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
977
1109
  }
978
1110
  var MAX_SLOTS = 1024;
979
1111
  var MAX_DECODED_ENVELOPE_BYTES = 65536;
980
- var MAX_SEALED_PLAINTEXT = 274877906880;
981
- var MAX_SEALED_CIPHERTEXT = MAX_SEALED_PLAINTEXT + 16;
982
- function assertCiphertextWithinBound(ciphertextLength) {
983
- if (ciphertextLength >= MAX_SEALED_CIPHERTEXT) {
984
- throw new SealedPayloadTooLargeError(
985
- `ciphertext length ${ciphertextLength} is at or above the maximum sealed ciphertext size ${MAX_SEALED_CIPHERTEXT}`
986
- );
1112
+ var EMPTY_SALT2 = new Uint8Array(0);
1113
+ function labelledSha256(prefix, ...parts) {
1114
+ let total = prefix.length;
1115
+ for (const p of parts) total += p.length;
1116
+ const message = new Uint8Array(total);
1117
+ message.set(prefix, 0);
1118
+ let offset = prefix.length;
1119
+ for (const p of parts) {
1120
+ message.set(p, offset);
1121
+ offset += p.length;
987
1122
  }
1123
+ return sha256(message);
988
1124
  }
989
- var SealedPayloadTooLargeError = class extends Error {
990
- constructor(message) {
991
- super(message);
992
- this.name = "SealedPayloadTooLargeError";
1125
+ function itemHashesHash(hashes3) {
1126
+ if (Object.keys(hashes3).length === 0) {
1127
+ throw new EciesSealedPoeError(
1128
+ "ENC_REQUIRES_CONTENT_HASH",
1129
+ "hashes MUST carry at least one content-hash entry"
1130
+ );
993
1131
  }
994
- };
1132
+ return labelledSha256(CARDANO_POE_ITEM_HASHES_PREFIX, encodeCanonicalCbor(hashes3));
1133
+ }
995
1134
  function computeSlotsHash(args) {
1135
+ const slots = args.kem === "x25519" ? args.slots.map((s) => ({ epk: s.epk, wrap: s.wrap })) : args.slots.map((s) => ({
1136
+ kem_ct: s.kem_ct,
1137
+ wrap: s.wrap
1138
+ }));
996
1139
  const transcript = {
997
1140
  scheme: 1,
998
1141
  path: "slots",
999
- aead: "xchacha20-poly1305",
1142
+ aead: args.aead,
1000
1143
  kem: args.kem,
1001
1144
  nonce: args.nonce,
1002
- slots: canonicalizeSlots(args.slots, args.kem)
1145
+ slots,
1146
+ hashes_hash: args.hashesHash
1003
1147
  };
1004
- const encoded = encodeCanonicalCbor(transcript);
1005
- const message = new Uint8Array(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length + encoded.length);
1006
- message.set(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, 0);
1007
- message.set(encoded, CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length);
1008
- return sha256(message);
1148
+ return labelledSha256(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, encodeCanonicalCbor(transcript));
1009
1149
  }
1010
- function adContentSlots(args) {
1011
- const ad = {
1012
- scheme: 1,
1013
- path: "slots",
1014
- aead: "xchacha20-poly1305",
1015
- kem: args.kem,
1016
- nonce: args.nonce,
1017
- slots_hash: args.slotsHash,
1018
- slots_mac: args.slotsMac
1019
- };
1020
- return encodeCanonicalCbor(ad);
1150
+ function computeSlotsMac(args) {
1151
+ const macKey = hkdfSha2562({
1152
+ ikm: args.cek,
1153
+ salt: EMPTY_SALT2,
1154
+ info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1155
+ length: 32
1156
+ });
1157
+ return hmac(sha256, macKey, args.slotsHash);
1021
1158
  }
1022
1159
  function slotsPayloadKey(args) {
1023
1160
  return hkdfSha2562({
@@ -1027,25 +1164,17 @@ function slotsPayloadKey(args) {
1027
1164
  length: 32
1028
1165
  });
1029
1166
  }
1167
+ function x25519KekSalt(args) {
1168
+ return labelledSha256(CARDANO_POE_X25519_KEK_SALT_PREFIX, args.nonce, args.epk, args.pubR);
1169
+ }
1030
1170
  function xwingKekSalt(args) {
1031
- const message = new Uint8Array(
1032
- CARDANO_POE_XWING_KEK_SALT_PREFIX.length + args.kemCt.length + args.pubR.length
1033
- );
1034
- let offset = 0;
1035
- message.set(CARDANO_POE_XWING_KEK_SALT_PREFIX, offset);
1036
- offset += CARDANO_POE_XWING_KEK_SALT_PREFIX.length;
1037
- message.set(args.kemCt, offset);
1038
- offset += args.kemCt.length;
1039
- message.set(args.pubR, offset);
1040
- return sha256(message);
1171
+ return labelledSha256(CARDANO_POE_XWING_KEK_SALT_PREFIX, args.nonce, args.kemCt, args.pubR);
1041
1172
  }
1173
+ var SEALED_POE_AEAD = "chacha20-poly1305-stream64k";
1042
1174
  var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
1043
1175
  var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
1044
1176
  "cardano-poe-kek-mlkem768x25519-v1"
1045
1177
  );
1046
- var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
1047
- "cardano-poe-slots-mac-v1"
1048
- );
1049
1178
  var ZERO_NONCE_12 = new Uint8Array(12);
1050
1179
  if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
1051
1180
  throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
@@ -1055,9 +1184,6 @@ if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
1055
1184
  "CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
1056
1185
  );
1057
1186
  }
1058
- if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
1059
- throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
1060
- }
1061
1187
  if (ZERO_NONCE_12.length !== 12) {
1062
1188
  throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
1063
1189
  }
@@ -1067,22 +1193,56 @@ function compareCt(a, b) {
1067
1193
  for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
1068
1194
  return diff === 0;
1069
1195
  }
1196
+ var CEK_LENGTH2 = 32;
1197
+ function newSlotAcceptanceState() {
1198
+ return {
1199
+ foundBit: 0,
1200
+ conflictBit: 0,
1201
+ selectedCek: new Uint8Array(CEK_LENGTH2),
1202
+ selectedSlotIdx: -1
1203
+ };
1204
+ }
1205
+ function foldSlotAcceptance(state, ok, candidateCek, slotIdx) {
1206
+ if (candidateCek.length !== CEK_LENGTH2) {
1207
+ throw new Error(
1208
+ `candidate CEK MUST be exactly ${CEK_LENGTH2} bytes, got ${candidateCek.length}`
1209
+ );
1210
+ }
1211
+ const okBit = ok & 1;
1212
+ const firstBit = okBit & (state.foundBit ^ 1);
1213
+ const firstByteMask = -firstBit & 255;
1214
+ const firstWordMask = -firstBit | 0;
1215
+ let diff = 0;
1216
+ for (let i = 0; i < CEK_LENGTH2; i++) {
1217
+ diff |= candidateCek[i] ^ state.selectedCek[i];
1218
+ }
1219
+ const neqBit = (diff | -diff) >>> 31 & 1;
1220
+ state.conflictBit |= okBit & state.foundBit & neqBit;
1221
+ for (let i = 0; i < CEK_LENGTH2; i++) {
1222
+ state.selectedCek[i] = candidateCek[i] & firstByteMask | state.selectedCek[i] & ~firstByteMask & 255;
1223
+ }
1224
+ state.selectedSlotIdx = slotIdx & firstWordMask | state.selectedSlotIdx & ~firstWordMask;
1225
+ state.foundBit |= okBit;
1226
+ }
1227
+ function finishSlotAcceptance(state) {
1228
+ const found = state.foundBit === 1;
1229
+ return {
1230
+ found,
1231
+ cekConflict: state.conflictBit === 1,
1232
+ selectedCek: found ? state.selectedCek : null,
1233
+ selectedSlotIdx: state.selectedSlotIdx
1234
+ };
1235
+ }
1070
1236
  function selectBundleSecrets(envelope, bundle) {
1071
1237
  return envelope.kem === "x25519" ? bundle.x25519PrivateKeys : bundle.mlkem768x25519SecretSeeds;
1072
1238
  }
1073
1239
  var ZERO_NONCE_122 = new Uint8Array(12);
1074
- var EMPTY_SALT22 = new Uint8Array(0);
1240
+ var CEK_LENGTH3 = 32;
1075
1241
  var X25519_SECRET_KEY_LENGTH2 = 32;
1076
1242
  var X25519_PUBLIC_KEY_LENGTH2 = 32;
1077
- var NONCE_LENGTH2 = 24;
1243
+ var NONCE_LENGTH3 = 24;
1078
1244
  var WRAP_LENGTH2 = 48;
1079
1245
  var SLOTS_MAC_LENGTH2 = 32;
1080
- function concat2(a, b) {
1081
- const out = new Uint8Array(a.length + b.length);
1082
- out.set(a, 0);
1083
- out.set(b, a.length);
1084
- return out;
1085
- }
1086
1246
  function bytesKey(bytes) {
1087
1247
  let s = "";
1088
1248
  for (let i = 0; i < bytes.length; i++) {
@@ -1093,14 +1253,14 @@ function bytesKey(bytes) {
1093
1253
  function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1094
1254
  if (envelope.scheme !== 1) {
1095
1255
  throw new EciesSealedPoeError(
1096
- "UNSUPPORTED_ENC_VERSION",
1256
+ "UNSUPPORTED_ENVELOPE_SCHEME",
1097
1257
  `envelope.scheme=${String(envelope.scheme)} unsupported (expected 1)`
1098
1258
  );
1099
1259
  }
1100
- if (envelope.aead !== "xchacha20-poly1305") {
1260
+ if (envelope.aead !== SEALED_POE_AEAD) {
1101
1261
  throw new EciesSealedPoeError(
1102
1262
  "UNSUPPORTED_AEAD_ALG",
1103
- `envelope.aead=${String(envelope.aead)} unsupported (expected 'xchacha20-poly1305')`
1263
+ `envelope.aead=${String(envelope.aead)} unsupported (expected '${SEALED_POE_AEAD}')`
1104
1264
  );
1105
1265
  }
1106
1266
  if (envelope.kem !== "x25519" && envelope.kem !== "mlkem768x25519") {
@@ -1119,10 +1279,10 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1119
1279
  `envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
1120
1280
  );
1121
1281
  }
1122
- if (envelope.nonce.length !== NONCE_LENGTH2) {
1282
+ if (envelope.nonce.length !== NONCE_LENGTH3) {
1123
1283
  throw new EciesSealedPoeError(
1124
1284
  "NONCE_LENGTH_MISMATCH",
1125
- `envelope.nonce MUST be exactly ${NONCE_LENGTH2} bytes, got ${envelope.nonce.length}`
1285
+ `envelope.nonce MUST be exactly ${NONCE_LENGTH3} bytes, got ${envelope.nonce.length}`
1126
1286
  );
1127
1287
  }
1128
1288
  if (envelope.slots_mac.length !== SLOTS_MAC_LENGTH2) {
@@ -1159,11 +1319,10 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1159
1319
  } else {
1160
1320
  for (let i = 0; i < n; i++) {
1161
1321
  const slot = envelope.slots[i];
1162
- const enc = joinKemCt(slot.kem_ct);
1163
- if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
1322
+ if (slot.kem_ct.length !== MLKEM768X25519_ENC_LENGTH) {
1164
1323
  throw new EciesSealedPoeError(
1165
1324
  "KEM_CT_LENGTH_MISMATCH",
1166
- `envelope.slots[${i}].kem_ct MUST reassemble to exactly ${MLKEM768X25519_ENC_LENGTH} bytes, got ${enc.length}`
1325
+ `envelope.slots[${i}].kem_ct MUST be exactly ${MLKEM768X25519_ENC_LENGTH} bytes, got ${slot.kem_ct.length}`
1167
1326
  );
1168
1327
  }
1169
1328
  if (slot.wrap.length !== WRAP_LENGTH2) {
@@ -1172,7 +1331,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1172
1331
  `envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
1173
1332
  );
1174
1333
  }
1175
- const key = bytesKey(enc);
1334
+ const key = bytesKey(slot.kem_ct);
1176
1335
  if (seenKemMaterial.has(key)) {
1177
1336
  throw new EciesSealedPoeError(
1178
1337
  "ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
@@ -1183,7 +1342,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1183
1342
  }
1184
1343
  }
1185
1344
  const perSlotBytes = envelope.kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH2 + WRAP_LENGTH2 : MLKEM768X25519_ENC_LENGTH + WRAP_LENGTH2;
1186
- const decodedEnvelopeBytes = NONCE_LENGTH2 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
1345
+ const decodedEnvelopeBytes = NONCE_LENGTH3 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
1187
1346
  if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
1188
1347
  throw new EciesSealedPoeError(
1189
1348
  "ENC_ENVELOPE_TOO_LARGE",
@@ -1209,66 +1368,63 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
1209
1368
  }
1210
1369
  }
1211
1370
  var ZERO_IKM_32 = new Uint8Array(32);
1212
- function tryX25519Slot(args) {
1213
- const salt = concat2(args.slot.epk, args.pubRLocal);
1214
- let shared;
1371
+ var DUMMY_CEK_32 = new Uint8Array(32);
1372
+ function wrapOpenOrDummy(kek, aad, wrap) {
1215
1373
  try {
1216
- shared = x25519Ecdh({
1217
- secretKey: args.recipientSecretKey,
1218
- theirPublicKey: args.slot.epk
1374
+ const plaintext = chacha20Poly1305Decrypt({
1375
+ key: kek,
1376
+ nonce: ZERO_NONCE_122,
1377
+ aad,
1378
+ ciphertext: wrap
1219
1379
  });
1380
+ if (plaintext.length === CEK_LENGTH3) {
1381
+ return { ok: 1, candidate: plaintext };
1382
+ }
1383
+ return { ok: 0, candidate: DUMMY_CEK_32 };
1220
1384
  } catch (e) {
1221
- if (!(e instanceof X25519LowOrderPointError)) throw e;
1222
- hkdfSha2562({ ikm: ZERO_IKM_32, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1223
- return null;
1385
+ if (!(e instanceof AeadVerificationError)) throw e;
1386
+ return { ok: 0, candidate: DUMMY_CEK_32 };
1224
1387
  }
1225
- const kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1388
+ }
1389
+ function tryX25519Slot(args) {
1390
+ const salt = x25519KekSalt({ nonce: args.nonce, epk: args.slot.epk, pubR: args.pubRLocal });
1391
+ let kemOk = 1;
1392
+ let kek;
1226
1393
  try {
1227
- return chacha20Poly1305Decrypt({
1228
- key: kek,
1229
- nonce: ZERO_NONCE_122,
1230
- aad: CARDANO_POE_HKDF_INFO_KEK,
1231
- ciphertext: args.slot.wrap
1394
+ const shared = x25519Ecdh({
1395
+ secretKey: args.recipientSecretKey,
1396
+ theirPublicKey: args.slot.epk
1232
1397
  });
1398
+ kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1233
1399
  } catch (e) {
1234
- if (!(e instanceof AeadVerificationError)) throw e;
1235
- return null;
1400
+ if (!(e instanceof X25519LowOrderPointError)) throw e;
1401
+ kemOk = 0;
1402
+ kek = hkdfSha2562({ ikm: ZERO_IKM_32, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
1236
1403
  }
1404
+ const opened = wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK, args.slot.wrap);
1405
+ return { ok: kemOk & opened.ok, candidate: opened.candidate };
1237
1406
  }
1238
1407
  function tryMlkem768X25519Slot(args) {
1239
- const enc = joinKemCt(args.slot.kem_ct);
1240
- const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
1408
+ const ss = mlkem768x25519Decapsulate({
1409
+ secretSeed: args.recipientSecretKey,
1410
+ enc: args.slot.kem_ct
1411
+ });
1241
1412
  const kek = hkdfSha2562({
1242
1413
  ikm: ss,
1243
- salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
1414
+ salt: xwingKekSalt({ nonce: args.nonce, kemCt: args.slot.kem_ct, pubR: args.pubR }),
1244
1415
  info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
1245
1416
  length: 32
1246
1417
  });
1247
- try {
1248
- return chacha20Poly1305Decrypt({
1249
- key: kek,
1250
- nonce: ZERO_NONCE_122,
1251
- aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
1252
- ciphertext: args.slot.wrap
1253
- });
1254
- } catch (e) {
1255
- if (!(e instanceof AeadVerificationError)) throw e;
1256
- return null;
1257
- }
1418
+ return wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519, args.slot.wrap);
1258
1419
  }
1259
- function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
1420
+ function runPrivPass(envelope, recipientSecretKey, slotsHash, slotsAttemptedOut) {
1260
1421
  const n = envelope.slots.length;
1261
- let cek = null;
1262
- let matchedSlotIdx = -1;
1263
- let cekConflict = false;
1264
- const recordMatch = (candidate, i) => {
1265
- if (candidate === null) return;
1266
- if (cek === null) {
1267
- cek = candidate;
1268
- matchedSlotIdx = i;
1269
- } else if (!compareCt(candidate, cek)) {
1270
- cekConflict = true;
1271
- }
1422
+ const state = newSlotAcceptanceState();
1423
+ let anyOpenedBit = 0;
1424
+ const acceptSlot = (slot, i) => {
1425
+ anyOpenedBit |= slot.ok;
1426
+ const macOk = Number(compareCt(computeSlotsMac({ cek: slot.candidate, slotsHash }), envelope.slots_mac)) & 1;
1427
+ foldSlotAcceptance(state, slot.ok & macOk, slot.candidate, i);
1272
1428
  };
1273
1429
  if (envelope.kem === "x25519") {
1274
1430
  const pubRLocal = x25519PublicKey2({ secretKey: recipientSecretKey });
@@ -1276,8 +1432,15 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
1276
1432
  if (slotsAttemptedOut !== void 0) {
1277
1433
  slotsAttemptedOut.count = i + 1;
1278
1434
  }
1279
- recordMatch(tryX25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubRLocal }), i);
1280
- if (cek !== null && !constantTimeN) break;
1435
+ acceptSlot(
1436
+ tryX25519Slot({
1437
+ slot: envelope.slots[i],
1438
+ nonce: envelope.nonce,
1439
+ recipientSecretKey,
1440
+ pubRLocal
1441
+ }),
1442
+ i
1443
+ );
1281
1444
  }
1282
1445
  } else {
1283
1446
  const pubR = mlkem768x25519Keygen2(recipientSecretKey).publicKey;
@@ -1285,22 +1448,37 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
1285
1448
  if (slotsAttemptedOut !== void 0) {
1286
1449
  slotsAttemptedOut.count = i + 1;
1287
1450
  }
1288
- recordMatch(tryMlkem768X25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubR }), i);
1289
- if (cek !== null && !constantTimeN) break;
1451
+ acceptSlot(
1452
+ tryMlkem768X25519Slot({
1453
+ slot: envelope.slots[i],
1454
+ nonce: envelope.nonce,
1455
+ recipientSecretKey,
1456
+ pubR
1457
+ }),
1458
+ i
1459
+ );
1290
1460
  }
1291
1461
  }
1292
- return cek === null ? null : { cek, slotIdx: matchedSlotIdx, cekConflict };
1462
+ const outcome = finishSlotAcceptance(state);
1463
+ return {
1464
+ found: outcome.found,
1465
+ selectedCek: outcome.selectedCek,
1466
+ selectedSlotIdx: outcome.selectedSlotIdx,
1467
+ cekConflict: outcome.cekConflict,
1468
+ anyOpened: anyOpenedBit === 1
1469
+ };
1293
1470
  }
1294
- function slotsHashBytes(envelope) {
1471
+ function slotsHashBytes(envelope, hashes3) {
1295
1472
  return computeSlotsHash({
1473
+ aead: envelope.aead,
1296
1474
  kem: envelope.kem,
1297
1475
  nonce: envelope.nonce,
1298
- slots: envelope.slots
1476
+ slots: envelope.slots,
1477
+ hashesHash: itemHashesHash(hashes3)
1299
1478
  });
1300
1479
  }
1301
1480
  function eciesSealedPoeUnwrap(args) {
1302
1481
  const { envelope, ciphertext } = args;
1303
- const constantTimeN = args.constantTimeN ?? true;
1304
1482
  const hasSingle = "recipientSecretKey" in args;
1305
1483
  const hasBundle = "recipientKeyBundle" in args;
1306
1484
  const multiPrivKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : "recipientSecretKeys" in args ? args.recipientSecretKeys : void 0;
@@ -1325,100 +1503,47 @@ function eciesSealedPoeUnwrap(args) {
1325
1503
  } else {
1326
1504
  assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
1327
1505
  }
1328
- assertCiphertextWithinBound(ciphertext.length);
1329
- const slotsHash = slotsHashBytes(envelope);
1506
+ const slotsHash = slotsHashBytes(envelope, args.hashes);
1330
1507
  let matchedCek = null;
1331
- let anyCandidateRecovered = false;
1332
- if (hasSingle) {
1333
- const recipientSecretKey = args.recipientSecretKey;
1334
- const candidate = tryRecipientUnwrapWithIdx(
1335
- envelope,
1336
- recipientSecretKey,
1337
- constantTimeN,
1338
- args._slotsAttemptedOut
1339
- );
1340
- if (candidate === null) {
1341
- return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
1508
+ let anyOpenedAcrossPrivs = false;
1509
+ const privKeys = hasMulti ? multiPrivKeys : [args.recipientSecretKey];
1510
+ for (let k = 0; k < privKeys.length; k++) {
1511
+ if (args._privsAttemptedOut !== void 0) {
1512
+ args._privsAttemptedOut.count = k + 1;
1342
1513
  }
1343
- if (candidate.cekConflict) {
1344
- return { matched: false, reason: "TAMPERED_HEADER" };
1514
+ if (args._slotsAttemptedOut !== void 0) {
1515
+ args._slotsAttemptedOut.count = 0;
1345
1516
  }
1346
- const hmacKey = hkdfSha2562({
1347
- ikm: candidate.cek,
1348
- salt: EMPTY_SALT22,
1349
- info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1350
- length: 32
1351
- });
1352
- const slotsMacCalc = hmac(sha256, hmacKey, slotsHash);
1353
- if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
1354
- return { matched: false, reason: "TAMPERED_HEADER" };
1355
- }
1356
- matchedCek = candidate.cek;
1357
- } else {
1358
- const recipientSecretKeys = multiPrivKeys;
1359
- let cekConflict = false;
1360
- for (let k = 0; k < recipientSecretKeys.length; k++) {
1361
- if (args._privsAttemptedOut !== void 0) {
1362
- args._privsAttemptedOut.count = k + 1;
1363
- }
1364
- if (args._slotsAttemptedOut !== void 0) {
1365
- args._slotsAttemptedOut.count = 0;
1366
- }
1367
- const candidate = tryRecipientUnwrapWithIdx(
1368
- envelope,
1369
- recipientSecretKeys[k],
1370
- constantTimeN,
1371
- args._slotsAttemptedOut
1372
- );
1373
- if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
1374
- args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
1375
- }
1376
- if (candidate === null) continue;
1377
- if (candidate.cekConflict) cekConflict = true;
1378
- const cek = candidate.cek;
1379
- const hmacKey = hkdfSha2562({
1380
- ikm: cek,
1381
- salt: EMPTY_SALT22,
1382
- info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
1383
- length: 32
1384
- });
1385
- const slotsMacCalc = hmac(sha256, hmacKey, slotsHash);
1386
- if (compareCt(slotsMacCalc, envelope.slots_mac)) {
1387
- matchedCek = cek;
1388
- break;
1389
- }
1390
- anyCandidateRecovered = true;
1517
+ const pass = runPrivPass(envelope, privKeys[k], slotsHash, args._slotsAttemptedOut);
1518
+ if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
1519
+ args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
1391
1520
  }
1392
- if (matchedCek !== null && cekConflict) {
1521
+ anyOpenedAcrossPrivs = anyOpenedAcrossPrivs || pass.anyOpened;
1522
+ if (!pass.found) continue;
1523
+ if (pass.cekConflict) {
1393
1524
  return { matched: false, reason: "TAMPERED_HEADER" };
1394
1525
  }
1395
- if (matchedCek === null) {
1396
- return {
1397
- matched: false,
1398
- reason: anyCandidateRecovered ? "TAMPERED_HEADER" : "WRONG_RECIPIENT_KEY"
1399
- };
1400
- }
1526
+ matchedCek = pass.selectedCek;
1527
+ break;
1528
+ }
1529
+ if (matchedCek === null) {
1530
+ return {
1531
+ matched: false,
1532
+ reason: anyOpenedAcrossPrivs ? "TAMPERED_HEADER" : "WRONG_RECIPIENT_KEY"
1533
+ };
1401
1534
  }
1402
- const payloadKey = slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce });
1403
- const adContent = adContentSlots({
1404
- kem: envelope.kem,
1405
- nonce: envelope.nonce,
1406
- slotsHash,
1407
- slotsMac: envelope.slots_mac
1408
- });
1409
1535
  try {
1410
- const plaintext = xchacha20Poly1305Decrypt({
1411
- key: payloadKey,
1412
- nonce: envelope.nonce,
1413
- aad: adContent,
1536
+ const plaintext = streamOpen({
1537
+ payloadKey: slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce }),
1414
1538
  ciphertext
1415
1539
  });
1416
1540
  return { matched: true, plaintext };
1417
1541
  } catch (e) {
1418
- if (!(e instanceof AeadVerificationError)) throw e;
1542
+ if (!(e instanceof StreamTamperedError)) throw e;
1419
1543
  return { matched: false, reason: "TAMPERED_CIPHERTEXT" };
1420
1544
  }
1421
1545
  }
1546
+ new TextEncoder();
1422
1547
  ed.hashes.sha512 = sha512;
1423
1548
  ed.Point.CURVE().n;
1424
1549
  function signEd25519(opts2) {
@@ -1460,6 +1585,7 @@ function decryptSealedFromSeed(args) {
1460
1585
  return eciesSealedPoeUnwrap({
1461
1586
  envelope: args.envelope,
1462
1587
  ciphertext: args.ciphertext,
1588
+ hashes: args.hashes,
1463
1589
  recipientKeyBundle: recipientKeyBundleFromSeed(args.seed)
1464
1590
  });
1465
1591
  }