@cardanowall/sdk-ts 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.cjs +1608 -1525
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.js +1608 -1525
- package/dist/client/index.js.map +1 -1
- package/dist/conformance/cli.cjs +2334 -2073
- package/dist/conformance/cli.cjs.map +1 -1
- package/dist/conformance/cli.js +2334 -2073
- package/dist/conformance/cli.js.map +1 -1
- package/dist/identity/index.cjs +219 -104
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.js +219 -104
- package/dist/identity/index.js.map +1 -1
- package/dist/index.cjs +2762 -2484
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2762 -2484
- package/dist/index.js.map +1 -1
- package/dist/verifier/index.cjs +2335 -2074
- package/dist/verifier/index.cjs.map +1 -1
- package/dist/verifier/index.js +2336 -2075
- package/dist/verifier/index.js.map +1 -1
- package/package.json +3 -3
package/dist/identity/index.cjs
CHANGED
|
@@ -879,6 +879,15 @@ function hkdfSha2562(opts2) {
|
|
|
879
879
|
}
|
|
880
880
|
var MLKEM768X25519_ENC_LENGTH = 1120;
|
|
881
881
|
var MLKEM768X25519_SEED_LENGTH2 = 32;
|
|
882
|
+
function mlkem768x25519Keygen2(seed) {
|
|
883
|
+
if (seed.length !== MLKEM768X25519_SEED_LENGTH2) {
|
|
884
|
+
throw new Error(
|
|
885
|
+
`mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH2} bytes, got ${seed.length}`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
const { secretKey, publicKey } = XWing.keygen(seed);
|
|
889
|
+
return { secretSeed: secretKey, publicKey };
|
|
890
|
+
}
|
|
882
891
|
function mlkem768x25519Decapsulate(opts2) {
|
|
883
892
|
if (opts2.secretSeed.length !== MLKEM768X25519_SEED_LENGTH2) {
|
|
884
893
|
throw new Error(
|
|
@@ -921,14 +930,6 @@ var EciesSealedPoeError = class extends Error {
|
|
|
921
930
|
this.code = code;
|
|
922
931
|
}
|
|
923
932
|
};
|
|
924
|
-
function encodeCanonicalCbor(value) {
|
|
925
|
-
return cbor2.encode(value, {
|
|
926
|
-
cde: true,
|
|
927
|
-
collapseBigInts: true,
|
|
928
|
-
rejectDuplicateKeys: true,
|
|
929
|
-
sortKeys: sorts.sortCoreDeterministic
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
933
|
var CHUNK_MAX_BYTES = 64;
|
|
933
934
|
function chunkKemCt(value) {
|
|
934
935
|
if (value.length === 0) {
|
|
@@ -951,27 +952,114 @@ function joinKemCt(chunks) {
|
|
|
951
952
|
}
|
|
952
953
|
return out;
|
|
953
954
|
}
|
|
954
|
-
function
|
|
955
|
-
let value;
|
|
955
|
+
function canonicalizeSlots(slots, kem) {
|
|
956
956
|
if (kem === "x25519") {
|
|
957
|
-
|
|
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
|
-
}));
|
|
957
|
+
return slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
|
|
973
958
|
}
|
|
974
|
-
return
|
|
959
|
+
return slots.map((s) => ({
|
|
960
|
+
kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
|
|
961
|
+
wrap: s.wrap
|
|
962
|
+
}));
|
|
963
|
+
}
|
|
964
|
+
function encodeCanonicalCbor(value) {
|
|
965
|
+
return cbor2.encode(value, {
|
|
966
|
+
cde: true,
|
|
967
|
+
collapseBigInts: true,
|
|
968
|
+
rejectDuplicateKeys: true,
|
|
969
|
+
sortKeys: sorts.sortCoreDeterministic
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
|
|
973
|
+
"cardano-poe-slots-transcript-v1"
|
|
974
|
+
);
|
|
975
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
|
|
976
|
+
"cardano-poe-payload-v1"
|
|
977
|
+
);
|
|
978
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
|
|
979
|
+
"cardano-poe-payload-passphrase-v1"
|
|
980
|
+
);
|
|
981
|
+
var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
|
|
982
|
+
"cardano-poe-xwing-kek-salt-v1"
|
|
983
|
+
);
|
|
984
|
+
if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
|
|
985
|
+
throw new Error(
|
|
986
|
+
"CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
|
|
990
|
+
throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
|
|
991
|
+
}
|
|
992
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
|
|
993
|
+
throw new Error(
|
|
994
|
+
"CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
|
|
998
|
+
throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
|
|
999
|
+
}
|
|
1000
|
+
var MAX_SLOTS = 1024;
|
|
1001
|
+
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
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
var SealedPayloadTooLargeError = class extends Error {
|
|
1012
|
+
constructor(message) {
|
|
1013
|
+
super(message);
|
|
1014
|
+
this.name = "SealedPayloadTooLargeError";
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
function computeSlotsHash(args) {
|
|
1018
|
+
const transcript = {
|
|
1019
|
+
scheme: 1,
|
|
1020
|
+
path: "slots",
|
|
1021
|
+
aead: "xchacha20-poly1305",
|
|
1022
|
+
kem: args.kem,
|
|
1023
|
+
nonce: args.nonce,
|
|
1024
|
+
slots: canonicalizeSlots(args.slots, args.kem)
|
|
1025
|
+
};
|
|
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);
|
|
1031
|
+
}
|
|
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);
|
|
1043
|
+
}
|
|
1044
|
+
function slotsPayloadKey(args) {
|
|
1045
|
+
return hkdfSha2562({
|
|
1046
|
+
ikm: args.cek,
|
|
1047
|
+
salt: args.nonce,
|
|
1048
|
+
info: CARDANO_POE_HKDF_INFO_PAYLOAD,
|
|
1049
|
+
length: 32
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
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);
|
|
975
1063
|
}
|
|
976
1064
|
var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
|
|
977
1065
|
var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
|
|
@@ -1017,6 +1105,13 @@ function concat2(a, b) {
|
|
|
1017
1105
|
out.set(b, a.length);
|
|
1018
1106
|
return out;
|
|
1019
1107
|
}
|
|
1108
|
+
function bytesKey(bytes) {
|
|
1109
|
+
let s = "";
|
|
1110
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1111
|
+
s += String.fromCharCode(bytes[i]);
|
|
1112
|
+
}
|
|
1113
|
+
return s;
|
|
1114
|
+
}
|
|
1020
1115
|
function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
1021
1116
|
if (envelope.scheme !== 1) {
|
|
1022
1117
|
throw new EciesSealedPoeError(
|
|
@@ -1040,6 +1135,12 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1040
1135
|
if (n < 1) {
|
|
1041
1136
|
throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
|
|
1042
1137
|
}
|
|
1138
|
+
if (n > MAX_SLOTS) {
|
|
1139
|
+
throw new EciesSealedPoeError(
|
|
1140
|
+
"ENC_SLOTS_TOO_MANY",
|
|
1141
|
+
`envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1043
1144
|
if (envelope.nonce.length !== NONCE_LENGTH2) {
|
|
1044
1145
|
throw new EciesSealedPoeError(
|
|
1045
1146
|
"NONCE_LENGTH_MISMATCH",
|
|
@@ -1052,6 +1153,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1052
1153
|
`envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
|
|
1053
1154
|
);
|
|
1054
1155
|
}
|
|
1156
|
+
const seenKemMaterial = /* @__PURE__ */ new Set();
|
|
1055
1157
|
if (envelope.kem === "x25519") {
|
|
1056
1158
|
for (let i = 0; i < n; i++) {
|
|
1057
1159
|
const slot = envelope.slots[i];
|
|
@@ -1067,6 +1169,14 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1067
1169
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1068
1170
|
);
|
|
1069
1171
|
}
|
|
1172
|
+
const key = bytesKey(slot.epk);
|
|
1173
|
+
if (seenKemMaterial.has(key)) {
|
|
1174
|
+
throw new EciesSealedPoeError(
|
|
1175
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
1176
|
+
`envelope.slots[${i}].epk duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
seenKemMaterial.add(key);
|
|
1070
1180
|
}
|
|
1071
1181
|
} else {
|
|
1072
1182
|
for (let i = 0; i < n; i++) {
|
|
@@ -1084,8 +1194,24 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1084
1194
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1085
1195
|
);
|
|
1086
1196
|
}
|
|
1197
|
+
const key = bytesKey(enc);
|
|
1198
|
+
if (seenKemMaterial.has(key)) {
|
|
1199
|
+
throw new EciesSealedPoeError(
|
|
1200
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
1201
|
+
`envelope.slots[${i}].kem_ct duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
seenKemMaterial.add(key);
|
|
1087
1205
|
}
|
|
1088
1206
|
}
|
|
1207
|
+
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;
|
|
1209
|
+
if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
|
|
1210
|
+
throw new EciesSealedPoeError(
|
|
1211
|
+
"ENC_ENVELOPE_TOO_LARGE",
|
|
1212
|
+
`decoded envelope size ${decodedEnvelopeBytes} exceeds MAX_DECODED_ENVELOPE_BYTES=${MAX_DECODED_ENVELOPE_BYTES}`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1089
1215
|
if (multiPrivKeys !== void 0) {
|
|
1090
1216
|
for (let i = 0; i < multiPrivKeys.length; i++) {
|
|
1091
1217
|
if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
|
|
@@ -1104,60 +1230,42 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1104
1230
|
}
|
|
1105
1231
|
}
|
|
1106
1232
|
}
|
|
1233
|
+
var ZERO_IKM_32 = new Uint8Array(32);
|
|
1107
1234
|
function tryX25519Slot(args) {
|
|
1108
|
-
|
|
1109
|
-
|
|
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;
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1235
|
+
const salt = concat2(args.slot.epk, args.pubRLocal);
|
|
1236
|
+
let shared;
|
|
1133
1237
|
try {
|
|
1134
|
-
|
|
1238
|
+
shared = x25519Ecdh({
|
|
1135
1239
|
secretKey: args.recipientSecretKey,
|
|
1136
1240
|
theirPublicKey: args.slot.epk
|
|
1137
1241
|
});
|
|
1138
|
-
hkdfSha2562({
|
|
1139
|
-
ikm: shared,
|
|
1140
|
-
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
1141
|
-
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
1142
|
-
length: 32
|
|
1143
|
-
});
|
|
1144
1242
|
} catch (e) {
|
|
1145
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;
|
|
1246
|
+
}
|
|
1247
|
+
const kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
|
|
1248
|
+
try {
|
|
1249
|
+
return chacha20Poly1305Decrypt({
|
|
1250
|
+
key: kek,
|
|
1251
|
+
nonce: ZERO_NONCE_122,
|
|
1252
|
+
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
1253
|
+
ciphertext: args.slot.wrap
|
|
1254
|
+
});
|
|
1255
|
+
} catch (e) {
|
|
1256
|
+
if (!(e instanceof AeadVerificationError)) throw e;
|
|
1257
|
+
return null;
|
|
1146
1258
|
}
|
|
1147
|
-
return null;
|
|
1148
1259
|
}
|
|
1149
1260
|
function tryMlkem768X25519Slot(args) {
|
|
1150
1261
|
const enc = joinKemCt(args.slot.kem_ct);
|
|
1151
1262
|
const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
|
|
1152
1263
|
const kek = hkdfSha2562({
|
|
1153
1264
|
ikm: ss,
|
|
1154
|
-
salt:
|
|
1265
|
+
salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
|
|
1155
1266
|
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1156
1267
|
length: 32
|
|
1157
1268
|
});
|
|
1158
|
-
if (!args.liveSlot) {
|
|
1159
|
-
return null;
|
|
1160
|
-
}
|
|
1161
1269
|
try {
|
|
1162
1270
|
return chacha20Poly1305Decrypt({
|
|
1163
1271
|
key: kek,
|
|
@@ -1174,51 +1282,43 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
|
|
|
1174
1282
|
const n = envelope.slots.length;
|
|
1175
1283
|
let cek = null;
|
|
1176
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
|
+
}
|
|
1294
|
+
};
|
|
1177
1295
|
if (envelope.kem === "x25519") {
|
|
1178
1296
|
const pubRLocal = x25519PublicKey2({ secretKey: recipientSecretKey });
|
|
1179
1297
|
for (let i = 0; i < n; i++) {
|
|
1180
1298
|
if (slotsAttemptedOut !== void 0) {
|
|
1181
1299
|
slotsAttemptedOut.count = i + 1;
|
|
1182
1300
|
}
|
|
1183
|
-
|
|
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
|
-
}
|
|
1301
|
+
recordMatch(tryX25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubRLocal }), i);
|
|
1193
1302
|
if (cek !== null && !constantTimeN) break;
|
|
1194
1303
|
}
|
|
1195
1304
|
} else {
|
|
1305
|
+
const pubR = mlkem768x25519Keygen2(recipientSecretKey).publicKey;
|
|
1196
1306
|
for (let i = 0; i < n; i++) {
|
|
1197
1307
|
if (slotsAttemptedOut !== void 0) {
|
|
1198
1308
|
slotsAttemptedOut.count = i + 1;
|
|
1199
1309
|
}
|
|
1200
|
-
|
|
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
|
-
}
|
|
1310
|
+
recordMatch(tryMlkem768X25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubR }), i);
|
|
1209
1311
|
if (cek !== null && !constantTimeN) break;
|
|
1210
1312
|
}
|
|
1211
1313
|
}
|
|
1212
|
-
return cek === null ? null : { cek, slotIdx: matchedSlotIdx };
|
|
1314
|
+
return cek === null ? null : { cek, slotIdx: matchedSlotIdx, cekConflict };
|
|
1213
1315
|
}
|
|
1214
|
-
function
|
|
1215
|
-
return
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
envelope.kem
|
|
1221
|
-
);
|
|
1316
|
+
function slotsHashBytes(envelope) {
|
|
1317
|
+
return computeSlotsHash({
|
|
1318
|
+
kem: envelope.kem,
|
|
1319
|
+
nonce: envelope.nonce,
|
|
1320
|
+
slots: envelope.slots
|
|
1321
|
+
});
|
|
1222
1322
|
}
|
|
1223
1323
|
function eciesSealedPoeUnwrap(args) {
|
|
1224
1324
|
const { envelope, ciphertext } = args;
|
|
@@ -1247,34 +1347,38 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1247
1347
|
} else {
|
|
1248
1348
|
assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
|
|
1249
1349
|
}
|
|
1350
|
+
assertCiphertextWithinBound(ciphertext.length);
|
|
1351
|
+
const slotsHash = slotsHashBytes(envelope);
|
|
1250
1352
|
let matchedCek = null;
|
|
1251
1353
|
let anyCandidateRecovered = false;
|
|
1252
1354
|
if (hasSingle) {
|
|
1253
1355
|
const recipientSecretKey = args.recipientSecretKey;
|
|
1254
|
-
const
|
|
1356
|
+
const candidate = tryRecipientUnwrapWithIdx(
|
|
1255
1357
|
envelope,
|
|
1256
1358
|
recipientSecretKey,
|
|
1257
1359
|
constantTimeN,
|
|
1258
1360
|
args._slotsAttemptedOut
|
|
1259
1361
|
);
|
|
1260
|
-
if (
|
|
1362
|
+
if (candidate === null) {
|
|
1261
1363
|
return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
|
|
1262
1364
|
}
|
|
1263
|
-
|
|
1365
|
+
if (candidate.cekConflict) {
|
|
1366
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1367
|
+
}
|
|
1264
1368
|
const hmacKey = hkdfSha2562({
|
|
1265
|
-
ikm: cek,
|
|
1369
|
+
ikm: candidate.cek,
|
|
1266
1370
|
salt: EMPTY_SALT22,
|
|
1267
1371
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1268
1372
|
length: 32
|
|
1269
1373
|
});
|
|
1270
|
-
const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey,
|
|
1374
|
+
const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
|
|
1271
1375
|
if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1272
1376
|
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1273
1377
|
}
|
|
1274
|
-
matchedCek = cek;
|
|
1378
|
+
matchedCek = candidate.cek;
|
|
1275
1379
|
} else {
|
|
1276
|
-
const slotsCbor = slotsMacCborBytes(envelope);
|
|
1277
1380
|
const recipientSecretKeys = multiPrivKeys;
|
|
1381
|
+
let cekConflict = false;
|
|
1278
1382
|
for (let k = 0; k < recipientSecretKeys.length; k++) {
|
|
1279
1383
|
if (args._privsAttemptedOut !== void 0) {
|
|
1280
1384
|
args._privsAttemptedOut.count = k + 1;
|
|
@@ -1282,7 +1386,7 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1282
1386
|
if (args._slotsAttemptedOut !== void 0) {
|
|
1283
1387
|
args._slotsAttemptedOut.count = 0;
|
|
1284
1388
|
}
|
|
1285
|
-
const
|
|
1389
|
+
const candidate = tryRecipientUnwrapWithIdx(
|
|
1286
1390
|
envelope,
|
|
1287
1391
|
recipientSecretKeys[k],
|
|
1288
1392
|
constantTimeN,
|
|
@@ -1291,20 +1395,25 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1291
1395
|
if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
|
|
1292
1396
|
args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
|
|
1293
1397
|
}
|
|
1294
|
-
if (
|
|
1398
|
+
if (candidate === null) continue;
|
|
1399
|
+
if (candidate.cekConflict) cekConflict = true;
|
|
1400
|
+
const cek = candidate.cek;
|
|
1295
1401
|
const hmacKey = hkdfSha2562({
|
|
1296
1402
|
ikm: cek,
|
|
1297
1403
|
salt: EMPTY_SALT22,
|
|
1298
1404
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1299
1405
|
length: 32
|
|
1300
1406
|
});
|
|
1301
|
-
const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey,
|
|
1407
|
+
const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsHash);
|
|
1302
1408
|
if (compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1303
1409
|
matchedCek = cek;
|
|
1304
1410
|
break;
|
|
1305
1411
|
}
|
|
1306
1412
|
anyCandidateRecovered = true;
|
|
1307
1413
|
}
|
|
1414
|
+
if (matchedCek !== null && cekConflict) {
|
|
1415
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1416
|
+
}
|
|
1308
1417
|
if (matchedCek === null) {
|
|
1309
1418
|
return {
|
|
1310
1419
|
matched: false,
|
|
@@ -1312,10 +1421,16 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1312
1421
|
};
|
|
1313
1422
|
}
|
|
1314
1423
|
}
|
|
1315
|
-
const
|
|
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
|
+
});
|
|
1316
1431
|
try {
|
|
1317
1432
|
const plaintext = xchacha20Poly1305Decrypt({
|
|
1318
|
-
key:
|
|
1433
|
+
key: payloadKey,
|
|
1319
1434
|
nonce: envelope.nonce,
|
|
1320
1435
|
aad: adContent,
|
|
1321
1436
|
ciphertext
|