@cardanowall/sdk-ts 0.1.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/README.md +14 -14
- package/dist/client/index.cjs +1621 -1538
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +52 -52
- package/dist/client/index.d.ts +52 -52
- package/dist/client/index.js +1620 -1537
- package/dist/client/index.js.map +1 -1
- package/dist/conformance/cli.cjs +2367 -2106
- package/dist/conformance/cli.cjs.map +1 -1
- package/dist/conformance/cli.js +2367 -2106
- 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.d.cts +1 -1
- package/dist/identity/index.d.ts +1 -1
- package/dist/identity/index.js +219 -104
- package/dist/identity/index.js.map +1 -1
- package/dist/ids/index.cjs.map +1 -1
- package/dist/ids/index.js.map +1 -1
- package/dist/index.cjs +2808 -2530
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2805 -2527
- package/dist/index.js.map +1 -1
- package/dist/merkle/index.cjs +1 -1
- package/dist/merkle/index.cjs.map +1 -1
- package/dist/merkle/index.js +1 -1
- package/dist/merkle/index.js.map +1 -1
- package/dist/{types-BQMtbRCb.d.cts → types-DGsZTMuZ.d.cts} +6 -6
- package/dist/{types-BQMtbRCb.d.ts → types-DGsZTMuZ.d.ts} +6 -6
- package/dist/verifier/index.cjs +2368 -2107
- package/dist/verifier/index.cjs.map +1 -1
- package/dist/verifier/index.d.cts +3 -3
- package/dist/verifier/index.d.ts +3 -3
- package/dist/verifier/index.js +2369 -2108
- package/dist/verifier/index.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DerivedEd25519KeyPair, DerivedX25519KeyPair, DerivedMlKem768X25519KeyPair } from '@cardanowall/crypto-core/seed-derive';
|
|
2
2
|
import { SealedEnvelope, UnwrapResult, RecipientKeyBundle } from '@cardanowall/crypto-core/sealed-poe';
|
|
3
|
-
import { S as Signer } from '../types-
|
|
3
|
+
import { S as Signer } from '../types-DGsZTMuZ.cjs';
|
|
4
4
|
|
|
5
5
|
interface SeedKeys {
|
|
6
6
|
readonly ed25519: DerivedEd25519KeyPair;
|
package/dist/identity/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DerivedEd25519KeyPair, DerivedX25519KeyPair, DerivedMlKem768X25519KeyPair } from '@cardanowall/crypto-core/seed-derive';
|
|
2
2
|
import { SealedEnvelope, UnwrapResult, RecipientKeyBundle } from '@cardanowall/crypto-core/sealed-poe';
|
|
3
|
-
import { S as Signer } from '../types-
|
|
3
|
+
import { S as Signer } from '../types-DGsZTMuZ.js';
|
|
4
4
|
|
|
5
5
|
interface SeedKeys {
|
|
6
6
|
readonly ed25519: DerivedEd25519KeyPair;
|
package/dist/identity/index.js
CHANGED
|
@@ -857,6 +857,15 @@ function hkdfSha2562(opts2) {
|
|
|
857
857
|
}
|
|
858
858
|
var MLKEM768X25519_ENC_LENGTH = 1120;
|
|
859
859
|
var MLKEM768X25519_SEED_LENGTH2 = 32;
|
|
860
|
+
function mlkem768x25519Keygen2(seed) {
|
|
861
|
+
if (seed.length !== MLKEM768X25519_SEED_LENGTH2) {
|
|
862
|
+
throw new Error(
|
|
863
|
+
`mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH2} bytes, got ${seed.length}`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const { secretKey, publicKey } = XWing.keygen(seed);
|
|
867
|
+
return { secretSeed: secretKey, publicKey };
|
|
868
|
+
}
|
|
860
869
|
function mlkem768x25519Decapsulate(opts2) {
|
|
861
870
|
if (opts2.secretSeed.length !== MLKEM768X25519_SEED_LENGTH2) {
|
|
862
871
|
throw new Error(
|
|
@@ -899,14 +908,6 @@ var EciesSealedPoeError = class extends Error {
|
|
|
899
908
|
this.code = code;
|
|
900
909
|
}
|
|
901
910
|
};
|
|
902
|
-
function encodeCanonicalCbor(value) {
|
|
903
|
-
return encode(value, {
|
|
904
|
-
cde: true,
|
|
905
|
-
collapseBigInts: true,
|
|
906
|
-
rejectDuplicateKeys: true,
|
|
907
|
-
sortKeys: sortCoreDeterministic
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
911
|
var CHUNK_MAX_BYTES = 64;
|
|
911
912
|
function chunkKemCt(value) {
|
|
912
913
|
if (value.length === 0) {
|
|
@@ -929,27 +930,114 @@ function joinKemCt(chunks) {
|
|
|
929
930
|
}
|
|
930
931
|
return out;
|
|
931
932
|
}
|
|
932
|
-
function
|
|
933
|
-
let value;
|
|
933
|
+
function canonicalizeSlots(slots, kem) {
|
|
934
934
|
if (kem === "x25519") {
|
|
935
|
-
|
|
936
|
-
} else {
|
|
937
|
-
value = slots.map((s) => ({
|
|
938
|
-
// Canonicalize the chunk boundaries before the MAC commits to them:
|
|
939
|
-
// reassemble the logical ciphertext and re-split into canonical ≤ 64-byte
|
|
940
|
-
// chunks. The on-wire `kem_ct` array is a transport detail (the Cardano
|
|
941
|
-
// ledger's 64-byte metadatum cap), and a hostile or non-canonical chunking
|
|
942
|
-
// ([1, 63, …] instead of [64, …]) reassembles to the SAME bytes — so the
|
|
943
|
-
// MAC must be invariant to it. Committing to the verbatim wire chunks would
|
|
944
|
-
// let an attacker re-chunk an honest envelope and break the slots_mac match
|
|
945
|
-
// for an honest recipient. Honest (already-64B-chunked) records are
|
|
946
|
-
// unchanged; a real byte flip still changes the reassembled bytes and is
|
|
947
|
-
// still rejected.
|
|
948
|
-
kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
|
|
949
|
-
wrap: s.wrap
|
|
950
|
-
}));
|
|
935
|
+
return slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
|
|
951
936
|
}
|
|
952
|
-
return
|
|
937
|
+
return slots.map((s) => ({
|
|
938
|
+
kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
|
|
939
|
+
wrap: s.wrap
|
|
940
|
+
}));
|
|
941
|
+
}
|
|
942
|
+
function encodeCanonicalCbor(value) {
|
|
943
|
+
return encode(value, {
|
|
944
|
+
cde: true,
|
|
945
|
+
collapseBigInts: true,
|
|
946
|
+
rejectDuplicateKeys: true,
|
|
947
|
+
sortKeys: sortCoreDeterministic
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
|
|
951
|
+
"cardano-poe-slots-transcript-v1"
|
|
952
|
+
);
|
|
953
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
|
|
954
|
+
"cardano-poe-payload-v1"
|
|
955
|
+
);
|
|
956
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
|
|
957
|
+
"cardano-poe-payload-passphrase-v1"
|
|
958
|
+
);
|
|
959
|
+
var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
|
|
960
|
+
"cardano-poe-xwing-kek-salt-v1"
|
|
961
|
+
);
|
|
962
|
+
if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
|
|
963
|
+
throw new Error(
|
|
964
|
+
"CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
|
|
968
|
+
throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
|
|
969
|
+
}
|
|
970
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
|
|
971
|
+
throw new Error(
|
|
972
|
+
"CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
|
|
976
|
+
throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
|
|
977
|
+
}
|
|
978
|
+
var MAX_SLOTS = 1024;
|
|
979
|
+
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
|
+
);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
var SealedPayloadTooLargeError = class extends Error {
|
|
990
|
+
constructor(message) {
|
|
991
|
+
super(message);
|
|
992
|
+
this.name = "SealedPayloadTooLargeError";
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
function computeSlotsHash(args) {
|
|
996
|
+
const transcript = {
|
|
997
|
+
scheme: 1,
|
|
998
|
+
path: "slots",
|
|
999
|
+
aead: "xchacha20-poly1305",
|
|
1000
|
+
kem: args.kem,
|
|
1001
|
+
nonce: args.nonce,
|
|
1002
|
+
slots: canonicalizeSlots(args.slots, args.kem)
|
|
1003
|
+
};
|
|
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);
|
|
1009
|
+
}
|
|
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);
|
|
1021
|
+
}
|
|
1022
|
+
function slotsPayloadKey(args) {
|
|
1023
|
+
return hkdfSha2562({
|
|
1024
|
+
ikm: args.cek,
|
|
1025
|
+
salt: args.nonce,
|
|
1026
|
+
info: CARDANO_POE_HKDF_INFO_PAYLOAD,
|
|
1027
|
+
length: 32
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
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);
|
|
953
1041
|
}
|
|
954
1042
|
var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
|
|
955
1043
|
var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
|
|
@@ -995,6 +1083,13 @@ function concat2(a, b) {
|
|
|
995
1083
|
out.set(b, a.length);
|
|
996
1084
|
return out;
|
|
997
1085
|
}
|
|
1086
|
+
function bytesKey(bytes) {
|
|
1087
|
+
let s = "";
|
|
1088
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1089
|
+
s += String.fromCharCode(bytes[i]);
|
|
1090
|
+
}
|
|
1091
|
+
return s;
|
|
1092
|
+
}
|
|
998
1093
|
function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
999
1094
|
if (envelope.scheme !== 1) {
|
|
1000
1095
|
throw new EciesSealedPoeError(
|
|
@@ -1018,6 +1113,12 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1018
1113
|
if (n < 1) {
|
|
1019
1114
|
throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
|
|
1020
1115
|
}
|
|
1116
|
+
if (n > MAX_SLOTS) {
|
|
1117
|
+
throw new EciesSealedPoeError(
|
|
1118
|
+
"ENC_SLOTS_TOO_MANY",
|
|
1119
|
+
`envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1021
1122
|
if (envelope.nonce.length !== NONCE_LENGTH2) {
|
|
1022
1123
|
throw new EciesSealedPoeError(
|
|
1023
1124
|
"NONCE_LENGTH_MISMATCH",
|
|
@@ -1030,6 +1131,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1030
1131
|
`envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
|
|
1031
1132
|
);
|
|
1032
1133
|
}
|
|
1134
|
+
const seenKemMaterial = /* @__PURE__ */ new Set();
|
|
1033
1135
|
if (envelope.kem === "x25519") {
|
|
1034
1136
|
for (let i = 0; i < n; i++) {
|
|
1035
1137
|
const slot = envelope.slots[i];
|
|
@@ -1045,6 +1147,14 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1045
1147
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1046
1148
|
);
|
|
1047
1149
|
}
|
|
1150
|
+
const key = bytesKey(slot.epk);
|
|
1151
|
+
if (seenKemMaterial.has(key)) {
|
|
1152
|
+
throw new EciesSealedPoeError(
|
|
1153
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
1154
|
+
`envelope.slots[${i}].epk duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
seenKemMaterial.add(key);
|
|
1048
1158
|
}
|
|
1049
1159
|
} else {
|
|
1050
1160
|
for (let i = 0; i < n; i++) {
|
|
@@ -1062,8 +1172,24 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1062
1172
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1063
1173
|
);
|
|
1064
1174
|
}
|
|
1175
|
+
const key = bytesKey(enc);
|
|
1176
|
+
if (seenKemMaterial.has(key)) {
|
|
1177
|
+
throw new EciesSealedPoeError(
|
|
1178
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
1179
|
+
`envelope.slots[${i}].kem_ct duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
seenKemMaterial.add(key);
|
|
1065
1183
|
}
|
|
1066
1184
|
}
|
|
1185
|
+
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;
|
|
1187
|
+
if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
|
|
1188
|
+
throw new EciesSealedPoeError(
|
|
1189
|
+
"ENC_ENVELOPE_TOO_LARGE",
|
|
1190
|
+
`decoded envelope size ${decodedEnvelopeBytes} exceeds MAX_DECODED_ENVELOPE_BYTES=${MAX_DECODED_ENVELOPE_BYTES}`
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1067
1193
|
if (multiPrivKeys !== void 0) {
|
|
1068
1194
|
for (let i = 0; i < multiPrivKeys.length; i++) {
|
|
1069
1195
|
if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
|
|
@@ -1082,60 +1208,42 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1082
1208
|
}
|
|
1083
1209
|
}
|
|
1084
1210
|
}
|
|
1211
|
+
var ZERO_IKM_32 = new Uint8Array(32);
|
|
1085
1212
|
function tryX25519Slot(args) {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
const shared = x25519Ecdh({
|
|
1089
|
-
secretKey: args.recipientSecretKey,
|
|
1090
|
-
theirPublicKey: args.slot.epk
|
|
1091
|
-
});
|
|
1092
|
-
const kek = hkdfSha2562({
|
|
1093
|
-
ikm: shared,
|
|
1094
|
-
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
1095
|
-
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
1096
|
-
length: 32
|
|
1097
|
-
});
|
|
1098
|
-
return chacha20Poly1305Decrypt({
|
|
1099
|
-
key: kek,
|
|
1100
|
-
nonce: ZERO_NONCE_122,
|
|
1101
|
-
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
1102
|
-
ciphertext: args.slot.wrap
|
|
1103
|
-
});
|
|
1104
|
-
} catch (e) {
|
|
1105
|
-
if (!(e instanceof AeadVerificationError) && !(e instanceof X25519LowOrderPointError)) {
|
|
1106
|
-
throw e;
|
|
1107
|
-
}
|
|
1108
|
-
return null;
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1213
|
+
const salt = concat2(args.slot.epk, args.pubRLocal);
|
|
1214
|
+
let shared;
|
|
1111
1215
|
try {
|
|
1112
|
-
|
|
1216
|
+
shared = x25519Ecdh({
|
|
1113
1217
|
secretKey: args.recipientSecretKey,
|
|
1114
1218
|
theirPublicKey: args.slot.epk
|
|
1115
1219
|
});
|
|
1116
|
-
hkdfSha2562({
|
|
1117
|
-
ikm: shared,
|
|
1118
|
-
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
1119
|
-
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
1120
|
-
length: 32
|
|
1121
|
-
});
|
|
1122
1220
|
} catch (e) {
|
|
1123
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;
|
|
1224
|
+
}
|
|
1225
|
+
const kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
|
|
1226
|
+
try {
|
|
1227
|
+
return chacha20Poly1305Decrypt({
|
|
1228
|
+
key: kek,
|
|
1229
|
+
nonce: ZERO_NONCE_122,
|
|
1230
|
+
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
1231
|
+
ciphertext: args.slot.wrap
|
|
1232
|
+
});
|
|
1233
|
+
} catch (e) {
|
|
1234
|
+
if (!(e instanceof AeadVerificationError)) throw e;
|
|
1235
|
+
return null;
|
|
1124
1236
|
}
|
|
1125
|
-
return null;
|
|
1126
1237
|
}
|
|
1127
1238
|
function tryMlkem768X25519Slot(args) {
|
|
1128
1239
|
const enc = joinKemCt(args.slot.kem_ct);
|
|
1129
1240
|
const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
|
|
1130
1241
|
const kek = hkdfSha2562({
|
|
1131
1242
|
ikm: ss,
|
|
1132
|
-
salt:
|
|
1243
|
+
salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
|
|
1133
1244
|
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1134
1245
|
length: 32
|
|
1135
1246
|
});
|
|
1136
|
-
if (!args.liveSlot) {
|
|
1137
|
-
return null;
|
|
1138
|
-
}
|
|
1139
1247
|
try {
|
|
1140
1248
|
return chacha20Poly1305Decrypt({
|
|
1141
1249
|
key: kek,
|
|
@@ -1152,51 +1260,43 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
|
|
|
1152
1260
|
const n = envelope.slots.length;
|
|
1153
1261
|
let cek = null;
|
|
1154
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
|
+
}
|
|
1272
|
+
};
|
|
1155
1273
|
if (envelope.kem === "x25519") {
|
|
1156
1274
|
const pubRLocal = x25519PublicKey2({ secretKey: recipientSecretKey });
|
|
1157
1275
|
for (let i = 0; i < n; i++) {
|
|
1158
1276
|
if (slotsAttemptedOut !== void 0) {
|
|
1159
1277
|
slotsAttemptedOut.count = i + 1;
|
|
1160
1278
|
}
|
|
1161
|
-
|
|
1162
|
-
slot: envelope.slots[i],
|
|
1163
|
-
recipientSecretKey,
|
|
1164
|
-
pubRLocal,
|
|
1165
|
-
liveSlot: cek === null
|
|
1166
|
-
});
|
|
1167
|
-
if (cek === null && candidate !== null) {
|
|
1168
|
-
cek = candidate;
|
|
1169
|
-
matchedSlotIdx = i;
|
|
1170
|
-
}
|
|
1279
|
+
recordMatch(tryX25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubRLocal }), i);
|
|
1171
1280
|
if (cek !== null && !constantTimeN) break;
|
|
1172
1281
|
}
|
|
1173
1282
|
} else {
|
|
1283
|
+
const pubR = mlkem768x25519Keygen2(recipientSecretKey).publicKey;
|
|
1174
1284
|
for (let i = 0; i < n; i++) {
|
|
1175
1285
|
if (slotsAttemptedOut !== void 0) {
|
|
1176
1286
|
slotsAttemptedOut.count = i + 1;
|
|
1177
1287
|
}
|
|
1178
|
-
|
|
1179
|
-
slot: envelope.slots[i],
|
|
1180
|
-
recipientSecretKey,
|
|
1181
|
-
liveSlot: cek === null
|
|
1182
|
-
});
|
|
1183
|
-
if (cek === null && candidate !== null) {
|
|
1184
|
-
cek = candidate;
|
|
1185
|
-
matchedSlotIdx = i;
|
|
1186
|
-
}
|
|
1288
|
+
recordMatch(tryMlkem768X25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubR }), i);
|
|
1187
1289
|
if (cek !== null && !constantTimeN) break;
|
|
1188
1290
|
}
|
|
1189
1291
|
}
|
|
1190
|
-
return cek === null ? null : { cek, slotIdx: matchedSlotIdx };
|
|
1292
|
+
return cek === null ? null : { cek, slotIdx: matchedSlotIdx, cekConflict };
|
|
1191
1293
|
}
|
|
1192
|
-
function
|
|
1193
|
-
return
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
envelope.kem
|
|
1199
|
-
);
|
|
1294
|
+
function slotsHashBytes(envelope) {
|
|
1295
|
+
return computeSlotsHash({
|
|
1296
|
+
kem: envelope.kem,
|
|
1297
|
+
nonce: envelope.nonce,
|
|
1298
|
+
slots: envelope.slots
|
|
1299
|
+
});
|
|
1200
1300
|
}
|
|
1201
1301
|
function eciesSealedPoeUnwrap(args) {
|
|
1202
1302
|
const { envelope, ciphertext } = args;
|
|
@@ -1225,34 +1325,38 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1225
1325
|
} else {
|
|
1226
1326
|
assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
|
|
1227
1327
|
}
|
|
1328
|
+
assertCiphertextWithinBound(ciphertext.length);
|
|
1329
|
+
const slotsHash = slotsHashBytes(envelope);
|
|
1228
1330
|
let matchedCek = null;
|
|
1229
1331
|
let anyCandidateRecovered = false;
|
|
1230
1332
|
if (hasSingle) {
|
|
1231
1333
|
const recipientSecretKey = args.recipientSecretKey;
|
|
1232
|
-
const
|
|
1334
|
+
const candidate = tryRecipientUnwrapWithIdx(
|
|
1233
1335
|
envelope,
|
|
1234
1336
|
recipientSecretKey,
|
|
1235
1337
|
constantTimeN,
|
|
1236
1338
|
args._slotsAttemptedOut
|
|
1237
1339
|
);
|
|
1238
|
-
if (
|
|
1340
|
+
if (candidate === null) {
|
|
1239
1341
|
return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
|
|
1240
1342
|
}
|
|
1241
|
-
|
|
1343
|
+
if (candidate.cekConflict) {
|
|
1344
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1345
|
+
}
|
|
1242
1346
|
const hmacKey = hkdfSha2562({
|
|
1243
|
-
ikm: cek,
|
|
1347
|
+
ikm: candidate.cek,
|
|
1244
1348
|
salt: EMPTY_SALT22,
|
|
1245
1349
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1246
1350
|
length: 32
|
|
1247
1351
|
});
|
|
1248
|
-
const slotsMacCalc = hmac(sha256, hmacKey,
|
|
1352
|
+
const slotsMacCalc = hmac(sha256, hmacKey, slotsHash);
|
|
1249
1353
|
if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1250
1354
|
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1251
1355
|
}
|
|
1252
|
-
matchedCek = cek;
|
|
1356
|
+
matchedCek = candidate.cek;
|
|
1253
1357
|
} else {
|
|
1254
|
-
const slotsCbor = slotsMacCborBytes(envelope);
|
|
1255
1358
|
const recipientSecretKeys = multiPrivKeys;
|
|
1359
|
+
let cekConflict = false;
|
|
1256
1360
|
for (let k = 0; k < recipientSecretKeys.length; k++) {
|
|
1257
1361
|
if (args._privsAttemptedOut !== void 0) {
|
|
1258
1362
|
args._privsAttemptedOut.count = k + 1;
|
|
@@ -1260,7 +1364,7 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1260
1364
|
if (args._slotsAttemptedOut !== void 0) {
|
|
1261
1365
|
args._slotsAttemptedOut.count = 0;
|
|
1262
1366
|
}
|
|
1263
|
-
const
|
|
1367
|
+
const candidate = tryRecipientUnwrapWithIdx(
|
|
1264
1368
|
envelope,
|
|
1265
1369
|
recipientSecretKeys[k],
|
|
1266
1370
|
constantTimeN,
|
|
@@ -1269,20 +1373,25 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1269
1373
|
if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
|
|
1270
1374
|
args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
|
|
1271
1375
|
}
|
|
1272
|
-
if (
|
|
1376
|
+
if (candidate === null) continue;
|
|
1377
|
+
if (candidate.cekConflict) cekConflict = true;
|
|
1378
|
+
const cek = candidate.cek;
|
|
1273
1379
|
const hmacKey = hkdfSha2562({
|
|
1274
1380
|
ikm: cek,
|
|
1275
1381
|
salt: EMPTY_SALT22,
|
|
1276
1382
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1277
1383
|
length: 32
|
|
1278
1384
|
});
|
|
1279
|
-
const slotsMacCalc = hmac(sha256, hmacKey,
|
|
1385
|
+
const slotsMacCalc = hmac(sha256, hmacKey, slotsHash);
|
|
1280
1386
|
if (compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1281
1387
|
matchedCek = cek;
|
|
1282
1388
|
break;
|
|
1283
1389
|
}
|
|
1284
1390
|
anyCandidateRecovered = true;
|
|
1285
1391
|
}
|
|
1392
|
+
if (matchedCek !== null && cekConflict) {
|
|
1393
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1394
|
+
}
|
|
1286
1395
|
if (matchedCek === null) {
|
|
1287
1396
|
return {
|
|
1288
1397
|
matched: false,
|
|
@@ -1290,10 +1399,16 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1290
1399
|
};
|
|
1291
1400
|
}
|
|
1292
1401
|
}
|
|
1293
|
-
const
|
|
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
|
+
});
|
|
1294
1409
|
try {
|
|
1295
1410
|
const plaintext = xchacha20Poly1305Decrypt({
|
|
1296
|
-
key:
|
|
1411
|
+
key: payloadKey,
|
|
1297
1412
|
nonce: envelope.nonce,
|
|
1298
1413
|
aad: adContent,
|
|
1299
1414
|
ciphertext
|