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