@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.js
CHANGED
|
@@ -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,18 +846,17 @@ 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;
|
|
851
|
+
function mlkem768x25519Keygen2(seed) {
|
|
852
|
+
if (seed.length !== MLKEM768X25519_SEED_LENGTH2) {
|
|
853
|
+
throw new Error(
|
|
854
|
+
`mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH2} bytes, got ${seed.length}`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
const { secretKey, publicKey } = XWing.keygen(seed);
|
|
858
|
+
return { secretSeed: secretKey, publicKey };
|
|
859
|
+
}
|
|
860
860
|
function mlkem768x25519Decapsulate(opts2) {
|
|
861
861
|
if (opts2.secretSeed.length !== MLKEM768X25519_SEED_LENGTH2) {
|
|
862
862
|
throw new Error(
|
|
@@ -891,6 +891,9 @@ function x25519Ecdh(opts2) {
|
|
|
891
891
|
throw e;
|
|
892
892
|
}
|
|
893
893
|
}
|
|
894
|
+
function hkdfSha2562(opts2) {
|
|
895
|
+
return hkdf(sha256, opts2.ikm, opts2.salt, opts2.info, opts2.length);
|
|
896
|
+
}
|
|
894
897
|
var EciesSealedPoeError = class extends Error {
|
|
895
898
|
code;
|
|
896
899
|
constructor(code, message, options) {
|
|
@@ -899,6 +902,139 @@ var EciesSealedPoeError = class extends Error {
|
|
|
899
902
|
this.code = code;
|
|
900
903
|
}
|
|
901
904
|
};
|
|
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;
|
|
933
|
+
}
|
|
934
|
+
get done() {
|
|
935
|
+
return this.finished;
|
|
936
|
+
}
|
|
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
|
+
);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
var StreamOpener = class {
|
|
954
|
+
payloadKey;
|
|
955
|
+
nonce = new ChunkNonce();
|
|
956
|
+
chunkIndex = 0;
|
|
957
|
+
constructor(payloadKey) {
|
|
958
|
+
assertPayloadKey(payloadKey);
|
|
959
|
+
this.payloadKey = payloadKey;
|
|
960
|
+
}
|
|
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;
|
|
1037
|
+
}
|
|
902
1038
|
function encodeCanonicalCbor(value) {
|
|
903
1039
|
return encode(value, {
|
|
904
1040
|
cde: true,
|
|
@@ -907,57 +1043,138 @@ function encodeCanonicalCbor(value) {
|
|
|
907
1043
|
sortKeys: sortCoreDeterministic
|
|
908
1044
|
});
|
|
909
1045
|
}
|
|
910
|
-
var
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1046
|
+
var CARDANO_POE_ITEM_HASHES_PREFIX = new TextEncoder().encode(
|
|
1047
|
+
"cardano-poe-item-hashes-v1"
|
|
1048
|
+
);
|
|
1049
|
+
var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
|
|
1050
|
+
"cardano-poe-slots-transcript-v1"
|
|
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
|
+
);
|
|
1061
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
|
|
1062
|
+
"cardano-poe-payload-v1"
|
|
1063
|
+
);
|
|
1064
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
|
|
1065
|
+
"cardano-poe-payload-passphrase-v1"
|
|
1066
|
+
);
|
|
1067
|
+
var CARDANO_POE_X25519_KEK_SALT_PREFIX = new TextEncoder().encode(
|
|
1068
|
+
"cardano-poe-x25519-kek-salt-v1"
|
|
1069
|
+
);
|
|
1070
|
+
var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
|
|
1071
|
+
"cardano-poe-xwing-kek-salt-v1"
|
|
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
|
+
}
|
|
1076
|
+
if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
|
|
1077
|
+
throw new Error(
|
|
1078
|
+
"CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
|
|
1079
|
+
);
|
|
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
|
+
}
|
|
1094
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
|
|
1095
|
+
throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
|
|
1096
|
+
}
|
|
1097
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
|
|
1098
|
+
throw new Error(
|
|
1099
|
+
"CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
|
|
1100
|
+
);
|
|
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
|
+
}
|
|
1107
|
+
if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
|
|
1108
|
+
throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
|
|
1109
|
+
}
|
|
1110
|
+
var MAX_SLOTS = 1024;
|
|
1111
|
+
var MAX_DECODED_ENVELOPE_BYTES = 65536;
|
|
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;
|
|
929
1122
|
}
|
|
930
|
-
return
|
|
1123
|
+
return sha256(message);
|
|
931
1124
|
}
|
|
932
|
-
function
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
}));
|
|
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
|
+
);
|
|
951
1131
|
}
|
|
952
|
-
return encodeCanonicalCbor(
|
|
1132
|
+
return labelledSha256(CARDANO_POE_ITEM_HASHES_PREFIX, encodeCanonicalCbor(hashes3));
|
|
1133
|
+
}
|
|
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
|
+
}));
|
|
1139
|
+
const transcript = {
|
|
1140
|
+
scheme: 1,
|
|
1141
|
+
path: "slots",
|
|
1142
|
+
aead: args.aead,
|
|
1143
|
+
kem: args.kem,
|
|
1144
|
+
nonce: args.nonce,
|
|
1145
|
+
slots,
|
|
1146
|
+
hashes_hash: args.hashesHash
|
|
1147
|
+
};
|
|
1148
|
+
return labelledSha256(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, encodeCanonicalCbor(transcript));
|
|
953
1149
|
}
|
|
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);
|
|
1158
|
+
}
|
|
1159
|
+
function slotsPayloadKey(args) {
|
|
1160
|
+
return hkdfSha2562({
|
|
1161
|
+
ikm: args.cek,
|
|
1162
|
+
salt: args.nonce,
|
|
1163
|
+
info: CARDANO_POE_HKDF_INFO_PAYLOAD,
|
|
1164
|
+
length: 32
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
function x25519KekSalt(args) {
|
|
1168
|
+
return labelledSha256(CARDANO_POE_X25519_KEK_SALT_PREFIX, args.nonce, args.epk, args.pubR);
|
|
1169
|
+
}
|
|
1170
|
+
function xwingKekSalt(args) {
|
|
1171
|
+
return labelledSha256(CARDANO_POE_XWING_KEK_SALT_PREFIX, args.nonce, args.kemCt, args.pubR);
|
|
1172
|
+
}
|
|
1173
|
+
var SEALED_POE_AEAD = "chacha20-poly1305-stream64k";
|
|
954
1174
|
var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
|
|
955
1175
|
var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
|
|
956
1176
|
"cardano-poe-kek-mlkem768x25519-v1"
|
|
957
1177
|
);
|
|
958
|
-
var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
|
|
959
|
-
"cardano-poe-slots-mac-v1"
|
|
960
|
-
);
|
|
961
1178
|
var ZERO_NONCE_12 = new Uint8Array(12);
|
|
962
1179
|
if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
|
|
963
1180
|
throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
|
|
@@ -967,9 +1184,6 @@ if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
|
|
|
967
1184
|
"CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
|
|
968
1185
|
);
|
|
969
1186
|
}
|
|
970
|
-
if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
|
|
971
|
-
throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
|
|
972
|
-
}
|
|
973
1187
|
if (ZERO_NONCE_12.length !== 12) {
|
|
974
1188
|
throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
|
|
975
1189
|
}
|
|
@@ -979,33 +1193,74 @@ function compareCt(a, b) {
|
|
|
979
1193
|
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
980
1194
|
return diff === 0;
|
|
981
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
|
+
}
|
|
982
1236
|
function selectBundleSecrets(envelope, bundle) {
|
|
983
1237
|
return envelope.kem === "x25519" ? bundle.x25519PrivateKeys : bundle.mlkem768x25519SecretSeeds;
|
|
984
1238
|
}
|
|
985
1239
|
var ZERO_NONCE_122 = new Uint8Array(12);
|
|
986
|
-
var
|
|
1240
|
+
var CEK_LENGTH3 = 32;
|
|
987
1241
|
var X25519_SECRET_KEY_LENGTH2 = 32;
|
|
988
1242
|
var X25519_PUBLIC_KEY_LENGTH2 = 32;
|
|
989
|
-
var
|
|
1243
|
+
var NONCE_LENGTH3 = 24;
|
|
990
1244
|
var WRAP_LENGTH2 = 48;
|
|
991
1245
|
var SLOTS_MAC_LENGTH2 = 32;
|
|
992
|
-
function
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1246
|
+
function bytesKey(bytes) {
|
|
1247
|
+
let s = "";
|
|
1248
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1249
|
+
s += String.fromCharCode(bytes[i]);
|
|
1250
|
+
}
|
|
1251
|
+
return s;
|
|
997
1252
|
}
|
|
998
1253
|
function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
999
1254
|
if (envelope.scheme !== 1) {
|
|
1000
1255
|
throw new EciesSealedPoeError(
|
|
1001
|
-
"
|
|
1256
|
+
"UNSUPPORTED_ENVELOPE_SCHEME",
|
|
1002
1257
|
`envelope.scheme=${String(envelope.scheme)} unsupported (expected 1)`
|
|
1003
1258
|
);
|
|
1004
1259
|
}
|
|
1005
|
-
if (envelope.aead !==
|
|
1260
|
+
if (envelope.aead !== SEALED_POE_AEAD) {
|
|
1006
1261
|
throw new EciesSealedPoeError(
|
|
1007
1262
|
"UNSUPPORTED_AEAD_ALG",
|
|
1008
|
-
`envelope.aead=${String(envelope.aead)} unsupported (expected '
|
|
1263
|
+
`envelope.aead=${String(envelope.aead)} unsupported (expected '${SEALED_POE_AEAD}')`
|
|
1009
1264
|
);
|
|
1010
1265
|
}
|
|
1011
1266
|
if (envelope.kem !== "x25519" && envelope.kem !== "mlkem768x25519") {
|
|
@@ -1018,10 +1273,16 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1018
1273
|
if (n < 1) {
|
|
1019
1274
|
throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
|
|
1020
1275
|
}
|
|
1021
|
-
if (
|
|
1276
|
+
if (n > MAX_SLOTS) {
|
|
1277
|
+
throw new EciesSealedPoeError(
|
|
1278
|
+
"ENC_SLOTS_TOO_MANY",
|
|
1279
|
+
`envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
if (envelope.nonce.length !== NONCE_LENGTH3) {
|
|
1022
1283
|
throw new EciesSealedPoeError(
|
|
1023
1284
|
"NONCE_LENGTH_MISMATCH",
|
|
1024
|
-
`envelope.nonce MUST be exactly ${
|
|
1285
|
+
`envelope.nonce MUST be exactly ${NONCE_LENGTH3} bytes, got ${envelope.nonce.length}`
|
|
1025
1286
|
);
|
|
1026
1287
|
}
|
|
1027
1288
|
if (envelope.slots_mac.length !== SLOTS_MAC_LENGTH2) {
|
|
@@ -1030,6 +1291,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1030
1291
|
`envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
|
|
1031
1292
|
);
|
|
1032
1293
|
}
|
|
1294
|
+
const seenKemMaterial = /* @__PURE__ */ new Set();
|
|
1033
1295
|
if (envelope.kem === "x25519") {
|
|
1034
1296
|
for (let i = 0; i < n; i++) {
|
|
1035
1297
|
const slot = envelope.slots[i];
|
|
@@ -1045,15 +1307,22 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1045
1307
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1046
1308
|
);
|
|
1047
1309
|
}
|
|
1310
|
+
const key = bytesKey(slot.epk);
|
|
1311
|
+
if (seenKemMaterial.has(key)) {
|
|
1312
|
+
throw new EciesSealedPoeError(
|
|
1313
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
1314
|
+
`envelope.slots[${i}].epk duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
seenKemMaterial.add(key);
|
|
1048
1318
|
}
|
|
1049
1319
|
} else {
|
|
1050
1320
|
for (let i = 0; i < n; i++) {
|
|
1051
1321
|
const slot = envelope.slots[i];
|
|
1052
|
-
|
|
1053
|
-
if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
|
|
1322
|
+
if (slot.kem_ct.length !== MLKEM768X25519_ENC_LENGTH) {
|
|
1054
1323
|
throw new EciesSealedPoeError(
|
|
1055
1324
|
"KEM_CT_LENGTH_MISMATCH",
|
|
1056
|
-
`envelope.slots[${i}].kem_ct MUST
|
|
1325
|
+
`envelope.slots[${i}].kem_ct MUST be exactly ${MLKEM768X25519_ENC_LENGTH} bytes, got ${slot.kem_ct.length}`
|
|
1057
1326
|
);
|
|
1058
1327
|
}
|
|
1059
1328
|
if (slot.wrap.length !== WRAP_LENGTH2) {
|
|
@@ -1062,8 +1331,24 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1062
1331
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1063
1332
|
);
|
|
1064
1333
|
}
|
|
1334
|
+
const key = bytesKey(slot.kem_ct);
|
|
1335
|
+
if (seenKemMaterial.has(key)) {
|
|
1336
|
+
throw new EciesSealedPoeError(
|
|
1337
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
1338
|
+
`envelope.slots[${i}].kem_ct duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
seenKemMaterial.add(key);
|
|
1065
1342
|
}
|
|
1066
1343
|
}
|
|
1344
|
+
const perSlotBytes = envelope.kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH2 + WRAP_LENGTH2 : MLKEM768X25519_ENC_LENGTH + WRAP_LENGTH2;
|
|
1345
|
+
const decodedEnvelopeBytes = NONCE_LENGTH3 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
|
|
1346
|
+
if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
|
|
1347
|
+
throw new EciesSealedPoeError(
|
|
1348
|
+
"ENC_ENVELOPE_TOO_LARGE",
|
|
1349
|
+
`decoded envelope size ${decodedEnvelopeBytes} exceeds MAX_DECODED_ENVELOPE_BYTES=${MAX_DECODED_ENVELOPE_BYTES}`
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1067
1352
|
if (multiPrivKeys !== void 0) {
|
|
1068
1353
|
for (let i = 0; i < multiPrivKeys.length; i++) {
|
|
1069
1354
|
if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
|
|
@@ -1082,125 +1367,118 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
1082
1367
|
}
|
|
1083
1368
|
}
|
|
1084
1369
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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;
|
|
1370
|
+
var ZERO_IKM_32 = new Uint8Array(32);
|
|
1371
|
+
var DUMMY_CEK_32 = new Uint8Array(32);
|
|
1372
|
+
function wrapOpenOrDummy(kek, aad, wrap) {
|
|
1373
|
+
try {
|
|
1374
|
+
const plaintext = chacha20Poly1305Decrypt({
|
|
1375
|
+
key: kek,
|
|
1376
|
+
nonce: ZERO_NONCE_122,
|
|
1377
|
+
aad,
|
|
1378
|
+
ciphertext: wrap
|
|
1379
|
+
});
|
|
1380
|
+
if (plaintext.length === CEK_LENGTH3) {
|
|
1381
|
+
return { ok: 1, candidate: plaintext };
|
|
1109
1382
|
}
|
|
1383
|
+
return { ok: 0, candidate: DUMMY_CEK_32 };
|
|
1384
|
+
} catch (e) {
|
|
1385
|
+
if (!(e instanceof AeadVerificationError)) throw e;
|
|
1386
|
+
return { ok: 0, candidate: DUMMY_CEK_32 };
|
|
1110
1387
|
}
|
|
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;
|
|
1111
1393
|
try {
|
|
1112
1394
|
const shared = x25519Ecdh({
|
|
1113
1395
|
secretKey: args.recipientSecretKey,
|
|
1114
1396
|
theirPublicKey: args.slot.epk
|
|
1115
1397
|
});
|
|
1116
|
-
hkdfSha2562({
|
|
1117
|
-
ikm: shared,
|
|
1118
|
-
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
1119
|
-
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
1120
|
-
length: 32
|
|
1121
|
-
});
|
|
1398
|
+
kek = hkdfSha2562({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
|
|
1122
1399
|
} catch (e) {
|
|
1123
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 });
|
|
1124
1403
|
}
|
|
1125
|
-
|
|
1404
|
+
const opened = wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK, args.slot.wrap);
|
|
1405
|
+
return { ok: kemOk & opened.ok, candidate: opened.candidate };
|
|
1126
1406
|
}
|
|
1127
1407
|
function tryMlkem768X25519Slot(args) {
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1408
|
+
const ss = mlkem768x25519Decapsulate({
|
|
1409
|
+
secretSeed: args.recipientSecretKey,
|
|
1410
|
+
enc: args.slot.kem_ct
|
|
1411
|
+
});
|
|
1130
1412
|
const kek = hkdfSha2562({
|
|
1131
1413
|
ikm: ss,
|
|
1132
|
-
salt:
|
|
1414
|
+
salt: xwingKekSalt({ nonce: args.nonce, kemCt: args.slot.kem_ct, pubR: args.pubR }),
|
|
1133
1415
|
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1134
1416
|
length: 32
|
|
1135
1417
|
});
|
|
1136
|
-
|
|
1137
|
-
return null;
|
|
1138
|
-
}
|
|
1139
|
-
try {
|
|
1140
|
-
return chacha20Poly1305Decrypt({
|
|
1141
|
-
key: kek,
|
|
1142
|
-
nonce: ZERO_NONCE_122,
|
|
1143
|
-
aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1144
|
-
ciphertext: args.slot.wrap
|
|
1145
|
-
});
|
|
1146
|
-
} catch (e) {
|
|
1147
|
-
if (!(e instanceof AeadVerificationError)) throw e;
|
|
1148
|
-
return null;
|
|
1149
|
-
}
|
|
1418
|
+
return wrapOpenOrDummy(kek, CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519, args.slot.wrap);
|
|
1150
1419
|
}
|
|
1151
|
-
function
|
|
1420
|
+
function runPrivPass(envelope, recipientSecretKey, slotsHash, slotsAttemptedOut) {
|
|
1152
1421
|
const n = envelope.slots.length;
|
|
1153
|
-
|
|
1154
|
-
let
|
|
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);
|
|
1428
|
+
};
|
|
1155
1429
|
if (envelope.kem === "x25519") {
|
|
1156
1430
|
const pubRLocal = x25519PublicKey2({ secretKey: recipientSecretKey });
|
|
1157
1431
|
for (let i = 0; i < n; i++) {
|
|
1158
1432
|
if (slotsAttemptedOut !== void 0) {
|
|
1159
1433
|
slotsAttemptedOut.count = i + 1;
|
|
1160
1434
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
}
|
|
1171
|
-
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
|
+
);
|
|
1172
1444
|
}
|
|
1173
1445
|
} else {
|
|
1446
|
+
const pubR = mlkem768x25519Keygen2(recipientSecretKey).publicKey;
|
|
1174
1447
|
for (let i = 0; i < n; i++) {
|
|
1175
1448
|
if (slotsAttemptedOut !== void 0) {
|
|
1176
1449
|
slotsAttemptedOut.count = i + 1;
|
|
1177
1450
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
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
|
+
);
|
|
1188
1460
|
}
|
|
1189
1461
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
+
};
|
|
1194
1470
|
}
|
|
1195
|
-
function
|
|
1196
|
-
return
|
|
1197
|
-
envelope.
|
|
1198
|
-
envelope.kem
|
|
1199
|
-
|
|
1471
|
+
function slotsHashBytes(envelope, hashes3) {
|
|
1472
|
+
return computeSlotsHash({
|
|
1473
|
+
aead: envelope.aead,
|
|
1474
|
+
kem: envelope.kem,
|
|
1475
|
+
nonce: envelope.nonce,
|
|
1476
|
+
slots: envelope.slots,
|
|
1477
|
+
hashesHash: itemHashesHash(hashes3)
|
|
1478
|
+
});
|
|
1200
1479
|
}
|
|
1201
1480
|
function eciesSealedPoeUnwrap(args) {
|
|
1202
1481
|
const { envelope, ciphertext } = args;
|
|
1203
|
-
const constantTimeN = args.constantTimeN ?? true;
|
|
1204
1482
|
const hasSingle = "recipientSecretKey" in args;
|
|
1205
1483
|
const hasBundle = "recipientKeyBundle" in args;
|
|
1206
1484
|
const multiPrivKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : "recipientSecretKeys" in args ? args.recipientSecretKeys : void 0;
|
|
@@ -1225,85 +1503,47 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
1225
1503
|
} else {
|
|
1226
1504
|
assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
|
|
1227
1505
|
}
|
|
1506
|
+
const slotsHash = slotsHashBytes(envelope, args.hashes);
|
|
1228
1507
|
let matchedCek = null;
|
|
1229
|
-
let
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
recipientSecretKey,
|
|
1235
|
-
constantTimeN,
|
|
1236
|
-
args._slotsAttemptedOut
|
|
1237
|
-
);
|
|
1238
|
-
if (cek === null) {
|
|
1239
|
-
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;
|
|
1240
1513
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
ikm: cek,
|
|
1244
|
-
salt: EMPTY_SALT22,
|
|
1245
|
-
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1246
|
-
length: 32
|
|
1247
|
-
});
|
|
1248
|
-
const slotsMacCalc = hmac(sha256, hmacKey, slotsCbor);
|
|
1249
|
-
if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1250
|
-
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1514
|
+
if (args._slotsAttemptedOut !== void 0) {
|
|
1515
|
+
args._slotsAttemptedOut.count = 0;
|
|
1251
1516
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
const recipientSecretKeys = multiPrivKeys;
|
|
1256
|
-
for (let k = 0; k < recipientSecretKeys.length; k++) {
|
|
1257
|
-
if (args._privsAttemptedOut !== void 0) {
|
|
1258
|
-
args._privsAttemptedOut.count = k + 1;
|
|
1259
|
-
}
|
|
1260
|
-
if (args._slotsAttemptedOut !== void 0) {
|
|
1261
|
-
args._slotsAttemptedOut.count = 0;
|
|
1262
|
-
}
|
|
1263
|
-
const cek = tryRecipientUnwrap(
|
|
1264
|
-
envelope,
|
|
1265
|
-
recipientSecretKeys[k],
|
|
1266
|
-
constantTimeN,
|
|
1267
|
-
args._slotsAttemptedOut
|
|
1268
|
-
);
|
|
1269
|
-
if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
|
|
1270
|
-
args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
|
|
1271
|
-
}
|
|
1272
|
-
if (cek === null) continue;
|
|
1273
|
-
const hmacKey = hkdfSha2562({
|
|
1274
|
-
ikm: cek,
|
|
1275
|
-
salt: EMPTY_SALT22,
|
|
1276
|
-
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1277
|
-
length: 32
|
|
1278
|
-
});
|
|
1279
|
-
const slotsMacCalc = hmac(sha256, hmacKey, slotsCbor);
|
|
1280
|
-
if (compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1281
|
-
matchedCek = cek;
|
|
1282
|
-
break;
|
|
1283
|
-
}
|
|
1284
|
-
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);
|
|
1285
1520
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
};
|
|
1521
|
+
anyOpenedAcrossPrivs = anyOpenedAcrossPrivs || pass.anyOpened;
|
|
1522
|
+
if (!pass.found) continue;
|
|
1523
|
+
if (pass.cekConflict) {
|
|
1524
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1291
1525
|
}
|
|
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
|
+
};
|
|
1292
1534
|
}
|
|
1293
|
-
const adContent = concat2(envelope.nonce, envelope.slots_mac);
|
|
1294
1535
|
try {
|
|
1295
|
-
const plaintext =
|
|
1296
|
-
|
|
1297
|
-
nonce: envelope.nonce,
|
|
1298
|
-
aad: adContent,
|
|
1536
|
+
const plaintext = streamOpen({
|
|
1537
|
+
payloadKey: slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce }),
|
|
1299
1538
|
ciphertext
|
|
1300
1539
|
});
|
|
1301
1540
|
return { matched: true, plaintext };
|
|
1302
1541
|
} catch (e) {
|
|
1303
|
-
if (!(e instanceof
|
|
1542
|
+
if (!(e instanceof StreamTamperedError)) throw e;
|
|
1304
1543
|
return { matched: false, reason: "TAMPERED_CIPHERTEXT" };
|
|
1305
1544
|
}
|
|
1306
1545
|
}
|
|
1546
|
+
new TextEncoder();
|
|
1307
1547
|
ed.hashes.sha512 = sha512;
|
|
1308
1548
|
ed.Point.CURVE().n;
|
|
1309
1549
|
function signEd25519(opts2) {
|
|
@@ -1345,6 +1585,7 @@ function decryptSealedFromSeed(args) {
|
|
|
1345
1585
|
return eciesSealedPoeUnwrap({
|
|
1346
1586
|
envelope: args.envelope,
|
|
1347
1587
|
ciphertext: args.ciphertext,
|
|
1588
|
+
hashes: args.hashes,
|
|
1348
1589
|
recipientKeyBundle: recipientKeyBundleFromSeed(args.seed)
|
|
1349
1590
|
});
|
|
1350
1591
|
}
|