@cardanowall/crypto-core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canonical-DHeJLYDR.d.cts +7 -0
- package/dist/canonical-DHeJLYDR.d.ts +7 -0
- package/dist/cbor.d.cts +2 -6
- package/dist/cbor.d.ts +2 -6
- package/dist/index.cjs +280 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +262 -110
- package/dist/index.js.map +1 -1
- package/dist/kem.cjs.map +1 -1
- package/dist/kem.js.map +1 -1
- package/dist/sealed-poe.cjs +299 -118
- package/dist/sealed-poe.cjs.map +1 -1
- package/dist/sealed-poe.d.cts +55 -3
- package/dist/sealed-poe.d.ts +55 -3
- package/dist/sealed-poe.js +281 -118
- package/dist/sealed-poe.js.map +1 -1
- package/dist/seed-derive.cjs.map +1 -1
- package/dist/seed-derive.js.map +1 -1
- package/package.json +1 -1
package/dist/sealed-poe.js
CHANGED
|
@@ -47,6 +47,15 @@ var MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216;
|
|
|
47
47
|
var MLKEM768X25519_ENC_LENGTH = 1120;
|
|
48
48
|
var MLKEM768X25519_SEED_LENGTH = 32;
|
|
49
49
|
var MLKEM768X25519_ESEED_LENGTH = 64;
|
|
50
|
+
function mlkem768x25519Keygen(seed) {
|
|
51
|
+
if (seed.length !== MLKEM768X25519_SEED_LENGTH) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${seed.length}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const { secretKey, publicKey } = XWing.keygen(seed);
|
|
57
|
+
return { secretSeed: secretKey, publicKey };
|
|
58
|
+
}
|
|
50
59
|
function mlkem768x25519Encapsulate(opts) {
|
|
51
60
|
if (opts.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {
|
|
52
61
|
throw new Error(
|
|
@@ -105,14 +114,6 @@ var EciesSealedPoeError = class extends Error {
|
|
|
105
114
|
this.code = code;
|
|
106
115
|
}
|
|
107
116
|
};
|
|
108
|
-
function encodeCanonicalCbor(value) {
|
|
109
|
-
return encode(value, {
|
|
110
|
-
cde: true,
|
|
111
|
-
collapseBigInts: true,
|
|
112
|
-
rejectDuplicateKeys: true,
|
|
113
|
-
sortKeys: sortCoreDeterministic
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
117
|
|
|
117
118
|
// src/sealed-poe/slots-codec.ts
|
|
118
119
|
var CHUNK_MAX_BYTES = 64;
|
|
@@ -137,27 +138,151 @@ function joinKemCt(chunks) {
|
|
|
137
138
|
}
|
|
138
139
|
return out;
|
|
139
140
|
}
|
|
140
|
-
function
|
|
141
|
-
let value;
|
|
141
|
+
function canonicalizeSlots(slots, kem) {
|
|
142
142
|
if (kem === "x25519") {
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
143
|
+
return slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
|
|
144
|
+
}
|
|
145
|
+
return slots.map((s) => ({
|
|
146
|
+
kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
|
|
147
|
+
wrap: s.wrap
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
function encodeCanonicalCbor(value) {
|
|
151
|
+
return encode(value, {
|
|
152
|
+
cde: true,
|
|
153
|
+
collapseBigInts: true,
|
|
154
|
+
rejectDuplicateKeys: true,
|
|
155
|
+
sortKeys: sortCoreDeterministic
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/sealed-poe/transcript.ts
|
|
160
|
+
var CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX = new TextEncoder().encode(
|
|
161
|
+
"cardano-poe-slots-transcript-v1"
|
|
162
|
+
);
|
|
163
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD = new TextEncoder().encode(
|
|
164
|
+
"cardano-poe-payload-v1"
|
|
165
|
+
);
|
|
166
|
+
var CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE = new TextEncoder().encode(
|
|
167
|
+
"cardano-poe-payload-passphrase-v1"
|
|
168
|
+
);
|
|
169
|
+
var CARDANO_POE_XWING_KEK_SALT_PREFIX = new TextEncoder().encode(
|
|
170
|
+
"cardano-poe-xwing-kek-salt-v1"
|
|
171
|
+
);
|
|
172
|
+
if (CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length !== 31) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX byte-length invariant violated (expected 31)"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD.length !== 22) {
|
|
178
|
+
throw new Error("CARDANO_POE_HKDF_INFO_PAYLOAD byte-length invariant violated (expected 22)");
|
|
179
|
+
}
|
|
180
|
+
if (CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE.length !== 33) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
"CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE byte-length invariant violated (expected 33)"
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (CARDANO_POE_XWING_KEK_SALT_PREFIX.length !== 29) {
|
|
186
|
+
throw new Error("CARDANO_POE_XWING_KEK_SALT_PREFIX byte-length invariant violated (expected 29)");
|
|
187
|
+
}
|
|
188
|
+
var CARDANO_POE_PW_NORM_PROFILE = "cardano-poe-pw-norm-v1";
|
|
189
|
+
var MAX_SLOTS = 1024;
|
|
190
|
+
var MAX_DECODED_ENVELOPE_BYTES = 65536;
|
|
191
|
+
var MAX_SEALED_PLAINTEXT = 274877906880;
|
|
192
|
+
var MAX_SEALED_CIPHERTEXT = MAX_SEALED_PLAINTEXT + 16;
|
|
193
|
+
function assertPlaintextWithinBound(plaintextLength) {
|
|
194
|
+
if (plaintextLength >= MAX_SEALED_PLAINTEXT) {
|
|
195
|
+
throw new SealedPayloadTooLargeError(
|
|
196
|
+
`plaintext length ${plaintextLength} is at or above the maximum sealed payload size ${MAX_SEALED_PLAINTEXT}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function assertCiphertextWithinBound(ciphertextLength) {
|
|
201
|
+
if (ciphertextLength >= MAX_SEALED_CIPHERTEXT) {
|
|
202
|
+
throw new SealedPayloadTooLargeError(
|
|
203
|
+
`ciphertext length ${ciphertextLength} is at or above the maximum sealed ciphertext size ${MAX_SEALED_CIPHERTEXT}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
var SealedPayloadTooLargeError = class extends Error {
|
|
208
|
+
constructor(message) {
|
|
209
|
+
super(message);
|
|
210
|
+
this.name = "SealedPayloadTooLargeError";
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
function computeSlotsHash(args) {
|
|
214
|
+
const transcript = {
|
|
215
|
+
scheme: 1,
|
|
216
|
+
path: "slots",
|
|
217
|
+
aead: "xchacha20-poly1305",
|
|
218
|
+
kem: args.kem,
|
|
219
|
+
nonce: args.nonce,
|
|
220
|
+
slots: canonicalizeSlots(args.slots, args.kem)
|
|
221
|
+
};
|
|
222
|
+
const encoded = encodeCanonicalCbor(transcript);
|
|
223
|
+
const message = new Uint8Array(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length + encoded.length);
|
|
224
|
+
message.set(CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, 0);
|
|
225
|
+
message.set(encoded, CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX.length);
|
|
226
|
+
return sha256(message);
|
|
227
|
+
}
|
|
228
|
+
function adContentSlots(args) {
|
|
229
|
+
const ad = {
|
|
230
|
+
scheme: 1,
|
|
231
|
+
path: "slots",
|
|
232
|
+
aead: "xchacha20-poly1305",
|
|
233
|
+
kem: args.kem,
|
|
234
|
+
nonce: args.nonce,
|
|
235
|
+
slots_hash: args.slotsHash,
|
|
236
|
+
slots_mac: args.slotsMac
|
|
237
|
+
};
|
|
238
|
+
return encodeCanonicalCbor(ad);
|
|
239
|
+
}
|
|
240
|
+
function adContentPassphrase(args) {
|
|
241
|
+
const ad = {
|
|
242
|
+
scheme: 1,
|
|
243
|
+
path: "passphrase",
|
|
244
|
+
aead: "xchacha20-poly1305",
|
|
245
|
+
nonce: args.nonce,
|
|
246
|
+
passphrase: {
|
|
247
|
+
alg: args.passphrase.alg,
|
|
248
|
+
salt: args.passphrase.salt,
|
|
249
|
+
params: {
|
|
250
|
+
m: args.passphrase.params.m,
|
|
251
|
+
t: args.passphrase.params.t,
|
|
252
|
+
p: args.passphrase.params.p
|
|
253
|
+
},
|
|
254
|
+
normalization: CARDANO_POE_PW_NORM_PROFILE
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
return encodeCanonicalCbor(ad);
|
|
258
|
+
}
|
|
259
|
+
function slotsPayloadKey(args) {
|
|
260
|
+
return hkdfSha256({
|
|
261
|
+
ikm: args.cek,
|
|
262
|
+
salt: args.nonce,
|
|
263
|
+
info: CARDANO_POE_HKDF_INFO_PAYLOAD,
|
|
264
|
+
length: 32
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function passphrasePayloadKey(args) {
|
|
268
|
+
return hkdfSha256({
|
|
269
|
+
ikm: args.cek,
|
|
270
|
+
salt: args.nonce,
|
|
271
|
+
info: CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE,
|
|
272
|
+
length: 32
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function xwingKekSalt(args) {
|
|
276
|
+
const message = new Uint8Array(
|
|
277
|
+
CARDANO_POE_XWING_KEK_SALT_PREFIX.length + args.kemCt.length + args.pubR.length
|
|
278
|
+
);
|
|
279
|
+
let offset = 0;
|
|
280
|
+
message.set(CARDANO_POE_XWING_KEK_SALT_PREFIX, offset);
|
|
281
|
+
offset += CARDANO_POE_XWING_KEK_SALT_PREFIX.length;
|
|
282
|
+
message.set(args.kemCt, offset);
|
|
283
|
+
offset += args.kemCt.length;
|
|
284
|
+
message.set(args.pubR, offset);
|
|
285
|
+
return sha256(message);
|
|
161
286
|
}
|
|
162
287
|
|
|
163
288
|
// src/sealed-poe/wrap.ts
|
|
@@ -251,7 +376,7 @@ function wrapSlotMlkem768X25519(args) {
|
|
|
251
376
|
}
|
|
252
377
|
const kek = hkdfSha256({
|
|
253
378
|
ikm: ss,
|
|
254
|
-
salt:
|
|
379
|
+
salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
|
|
255
380
|
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
256
381
|
length: 32
|
|
257
382
|
});
|
|
@@ -270,6 +395,7 @@ function eciesSealedPoeWrap(args) {
|
|
|
270
395
|
const { plaintext, recipientPublicKeys } = args;
|
|
271
396
|
const kem = args.kem ?? "x25519";
|
|
272
397
|
const n = recipientPublicKeys.length;
|
|
398
|
+
assertPlaintextWithinBound(plaintext.length);
|
|
273
399
|
if (n < 1) {
|
|
274
400
|
throw new EciesSealedPoeError(
|
|
275
401
|
"ENC_SLOTS_EMPTY",
|
|
@@ -339,6 +465,7 @@ function eciesSealedPoeWrap(args) {
|
|
|
339
465
|
);
|
|
340
466
|
}
|
|
341
467
|
let envelope;
|
|
468
|
+
let slotsHash;
|
|
342
469
|
if (kem === "x25519") {
|
|
343
470
|
const slots = [];
|
|
344
471
|
for (let i = 0; i < n; i++) {
|
|
@@ -354,14 +481,14 @@ function eciesSealedPoeWrap(args) {
|
|
|
354
481
|
if (args.skipShuffle !== true) {
|
|
355
482
|
csprngShuffle(slots);
|
|
356
483
|
}
|
|
357
|
-
|
|
484
|
+
slotsHash = computeSlotsHash({ kem: "x25519", nonce, slots });
|
|
358
485
|
envelope = {
|
|
359
486
|
scheme: 1,
|
|
360
487
|
aead: "xchacha20-poly1305",
|
|
361
488
|
kem: "x25519",
|
|
362
489
|
nonce,
|
|
363
490
|
slots,
|
|
364
|
-
slots_mac:
|
|
491
|
+
slots_mac: computeSlotsMac(cek, slotsHash)
|
|
365
492
|
};
|
|
366
493
|
} else {
|
|
367
494
|
const slots = [];
|
|
@@ -377,34 +504,39 @@ function eciesSealedPoeWrap(args) {
|
|
|
377
504
|
if (args.skipShuffle !== true) {
|
|
378
505
|
csprngShuffle(slots);
|
|
379
506
|
}
|
|
380
|
-
|
|
507
|
+
slotsHash = computeSlotsHash({ kem: "mlkem768x25519", nonce, slots });
|
|
381
508
|
envelope = {
|
|
382
509
|
scheme: 1,
|
|
383
510
|
aead: "xchacha20-poly1305",
|
|
384
511
|
kem: "mlkem768x25519",
|
|
385
512
|
nonce,
|
|
386
513
|
slots,
|
|
387
|
-
slots_mac:
|
|
514
|
+
slots_mac: computeSlotsMac(cek, slotsHash)
|
|
388
515
|
};
|
|
389
516
|
}
|
|
390
|
-
const
|
|
517
|
+
const payloadKey = slotsPayloadKey({ cek, nonce });
|
|
518
|
+
const adContent = adContentSlots({
|
|
519
|
+
kem: envelope.kem,
|
|
520
|
+
nonce,
|
|
521
|
+
slotsHash,
|
|
522
|
+
slotsMac: envelope.slots_mac
|
|
523
|
+
});
|
|
391
524
|
const ciphertext = xchacha20Poly1305Encrypt({
|
|
392
|
-
key:
|
|
525
|
+
key: payloadKey,
|
|
393
526
|
nonce,
|
|
394
527
|
aad: adContent,
|
|
395
528
|
plaintext
|
|
396
529
|
});
|
|
397
530
|
return { envelope, ciphertext };
|
|
398
531
|
}
|
|
399
|
-
function computeSlotsMac(cek,
|
|
532
|
+
function computeSlotsMac(cek, slotsHash) {
|
|
400
533
|
const hmacKey = hkdfSha256({
|
|
401
534
|
ikm: cek,
|
|
402
535
|
salt: EMPTY_SALT,
|
|
403
536
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
404
537
|
length: 32
|
|
405
538
|
});
|
|
406
|
-
const
|
|
407
|
-
const slotsMac = hmac(sha256, hmacKey, slotsCbor);
|
|
539
|
+
const slotsMac = hmac(sha256, hmacKey, slotsHash);
|
|
408
540
|
if (slotsMac.length !== SLOTS_MAC_LENGTH) {
|
|
409
541
|
throw new Error(`internal: slots_mac.length=${slotsMac.length}, expected ${SLOTS_MAC_LENGTH}`);
|
|
410
542
|
}
|
|
@@ -436,6 +568,13 @@ function concat2(a, b) {
|
|
|
436
568
|
out.set(b, a.length);
|
|
437
569
|
return out;
|
|
438
570
|
}
|
|
571
|
+
function bytesKey(bytes) {
|
|
572
|
+
let s = "";
|
|
573
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
574
|
+
s += String.fromCharCode(bytes[i]);
|
|
575
|
+
}
|
|
576
|
+
return s;
|
|
577
|
+
}
|
|
439
578
|
function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
440
579
|
if (envelope.scheme !== 1) {
|
|
441
580
|
throw new EciesSealedPoeError(
|
|
@@ -459,6 +598,12 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
459
598
|
if (n < 1) {
|
|
460
599
|
throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
|
|
461
600
|
}
|
|
601
|
+
if (n > MAX_SLOTS) {
|
|
602
|
+
throw new EciesSealedPoeError(
|
|
603
|
+
"ENC_SLOTS_TOO_MANY",
|
|
604
|
+
`envelope.slots.length=${n} exceeds MAX_SLOTS=${MAX_SLOTS}`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
462
607
|
if (envelope.nonce.length !== NONCE_LENGTH2) {
|
|
463
608
|
throw new EciesSealedPoeError(
|
|
464
609
|
"NONCE_LENGTH_MISMATCH",
|
|
@@ -471,6 +616,7 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
471
616
|
`envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
|
|
472
617
|
);
|
|
473
618
|
}
|
|
619
|
+
const seenKemMaterial = /* @__PURE__ */ new Set();
|
|
474
620
|
if (envelope.kem === "x25519") {
|
|
475
621
|
for (let i = 0; i < n; i++) {
|
|
476
622
|
const slot = envelope.slots[i];
|
|
@@ -486,6 +632,14 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
486
632
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
487
633
|
);
|
|
488
634
|
}
|
|
635
|
+
const key = bytesKey(slot.epk);
|
|
636
|
+
if (seenKemMaterial.has(key)) {
|
|
637
|
+
throw new EciesSealedPoeError(
|
|
638
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
639
|
+
`envelope.slots[${i}].epk duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
seenKemMaterial.add(key);
|
|
489
643
|
}
|
|
490
644
|
} else {
|
|
491
645
|
for (let i = 0; i < n; i++) {
|
|
@@ -503,8 +657,24 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
503
657
|
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
504
658
|
);
|
|
505
659
|
}
|
|
660
|
+
const key = bytesKey(enc);
|
|
661
|
+
if (seenKemMaterial.has(key)) {
|
|
662
|
+
throw new EciesSealedPoeError(
|
|
663
|
+
"ENC_SLOTS_DUPLICATE_KEM_MATERIAL",
|
|
664
|
+
`envelope.slots[${i}].kem_ct duplicates an earlier slot \u2014 per-slot KEK uniqueness is violated`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
seenKemMaterial.add(key);
|
|
506
668
|
}
|
|
507
669
|
}
|
|
670
|
+
const perSlotBytes = envelope.kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH2 + WRAP_LENGTH2 : MLKEM768X25519_ENC_LENGTH + WRAP_LENGTH2;
|
|
671
|
+
const decodedEnvelopeBytes = NONCE_LENGTH2 + SLOTS_MAC_LENGTH2 + n * perSlotBytes;
|
|
672
|
+
if (decodedEnvelopeBytes > MAX_DECODED_ENVELOPE_BYTES) {
|
|
673
|
+
throw new EciesSealedPoeError(
|
|
674
|
+
"ENC_ENVELOPE_TOO_LARGE",
|
|
675
|
+
`decoded envelope size ${decodedEnvelopeBytes} exceeds MAX_DECODED_ENVELOPE_BYTES=${MAX_DECODED_ENVELOPE_BYTES}`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
508
678
|
if (multiPrivKeys !== void 0) {
|
|
509
679
|
for (let i = 0; i < multiPrivKeys.length; i++) {
|
|
510
680
|
if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
|
|
@@ -523,60 +693,42 @@ function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
|
523
693
|
}
|
|
524
694
|
}
|
|
525
695
|
}
|
|
696
|
+
var ZERO_IKM_32 = new Uint8Array(32);
|
|
526
697
|
function tryX25519Slot(args) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const shared = x25519Ecdh({
|
|
530
|
-
secretKey: args.recipientSecretKey,
|
|
531
|
-
theirPublicKey: args.slot.epk
|
|
532
|
-
});
|
|
533
|
-
const kek = hkdfSha256({
|
|
534
|
-
ikm: shared,
|
|
535
|
-
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
536
|
-
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
537
|
-
length: 32
|
|
538
|
-
});
|
|
539
|
-
return chacha20Poly1305Decrypt({
|
|
540
|
-
key: kek,
|
|
541
|
-
nonce: ZERO_NONCE_122,
|
|
542
|
-
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
543
|
-
ciphertext: args.slot.wrap
|
|
544
|
-
});
|
|
545
|
-
} catch (e) {
|
|
546
|
-
if (!(e instanceof AeadVerificationError) && !(e instanceof X25519LowOrderPointError)) {
|
|
547
|
-
throw e;
|
|
548
|
-
}
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
698
|
+
const salt = concat2(args.slot.epk, args.pubRLocal);
|
|
699
|
+
let shared;
|
|
552
700
|
try {
|
|
553
|
-
|
|
701
|
+
shared = x25519Ecdh({
|
|
554
702
|
secretKey: args.recipientSecretKey,
|
|
555
703
|
theirPublicKey: args.slot.epk
|
|
556
704
|
});
|
|
557
|
-
hkdfSha256({
|
|
558
|
-
ikm: shared,
|
|
559
|
-
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
560
|
-
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
561
|
-
length: 32
|
|
562
|
-
});
|
|
563
705
|
} catch (e) {
|
|
564
706
|
if (!(e instanceof X25519LowOrderPointError)) throw e;
|
|
707
|
+
hkdfSha256({ ikm: ZERO_IKM_32, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
const kek = hkdfSha256({ ikm: shared, salt, info: CARDANO_POE_HKDF_INFO_KEK, length: 32 });
|
|
711
|
+
try {
|
|
712
|
+
return chacha20Poly1305Decrypt({
|
|
713
|
+
key: kek,
|
|
714
|
+
nonce: ZERO_NONCE_122,
|
|
715
|
+
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
716
|
+
ciphertext: args.slot.wrap
|
|
717
|
+
});
|
|
718
|
+
} catch (e) {
|
|
719
|
+
if (!(e instanceof AeadVerificationError)) throw e;
|
|
720
|
+
return null;
|
|
565
721
|
}
|
|
566
|
-
return null;
|
|
567
722
|
}
|
|
568
723
|
function tryMlkem768X25519Slot(args) {
|
|
569
724
|
const enc = joinKemCt(args.slot.kem_ct);
|
|
570
725
|
const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
|
|
571
726
|
const kek = hkdfSha256({
|
|
572
727
|
ikm: ss,
|
|
573
|
-
salt:
|
|
728
|
+
salt: xwingKekSalt({ kemCt: enc, pubR: args.pubR }),
|
|
574
729
|
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
575
730
|
length: 32
|
|
576
731
|
});
|
|
577
|
-
if (!args.liveSlot) {
|
|
578
|
-
return null;
|
|
579
|
-
}
|
|
580
732
|
try {
|
|
581
733
|
return chacha20Poly1305Decrypt({
|
|
582
734
|
key: kek,
|
|
@@ -593,51 +745,43 @@ function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN,
|
|
|
593
745
|
const n = envelope.slots.length;
|
|
594
746
|
let cek = null;
|
|
595
747
|
let matchedSlotIdx = -1;
|
|
748
|
+
let cekConflict = false;
|
|
749
|
+
const recordMatch = (candidate, i) => {
|
|
750
|
+
if (candidate === null) return;
|
|
751
|
+
if (cek === null) {
|
|
752
|
+
cek = candidate;
|
|
753
|
+
matchedSlotIdx = i;
|
|
754
|
+
} else if (!compareCt(candidate, cek)) {
|
|
755
|
+
cekConflict = true;
|
|
756
|
+
}
|
|
757
|
+
};
|
|
596
758
|
if (envelope.kem === "x25519") {
|
|
597
759
|
const pubRLocal = x25519PublicKey({ secretKey: recipientSecretKey });
|
|
598
760
|
for (let i = 0; i < n; i++) {
|
|
599
761
|
if (slotsAttemptedOut !== void 0) {
|
|
600
762
|
slotsAttemptedOut.count = i + 1;
|
|
601
763
|
}
|
|
602
|
-
|
|
603
|
-
slot: envelope.slots[i],
|
|
604
|
-
recipientSecretKey,
|
|
605
|
-
pubRLocal,
|
|
606
|
-
liveSlot: cek === null
|
|
607
|
-
});
|
|
608
|
-
if (cek === null && candidate !== null) {
|
|
609
|
-
cek = candidate;
|
|
610
|
-
matchedSlotIdx = i;
|
|
611
|
-
}
|
|
764
|
+
recordMatch(tryX25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubRLocal }), i);
|
|
612
765
|
if (cek !== null && !constantTimeN) break;
|
|
613
766
|
}
|
|
614
767
|
} else {
|
|
768
|
+
const pubR = mlkem768x25519Keygen(recipientSecretKey).publicKey;
|
|
615
769
|
for (let i = 0; i < n; i++) {
|
|
616
770
|
if (slotsAttemptedOut !== void 0) {
|
|
617
771
|
slotsAttemptedOut.count = i + 1;
|
|
618
772
|
}
|
|
619
|
-
|
|
620
|
-
slot: envelope.slots[i],
|
|
621
|
-
recipientSecretKey,
|
|
622
|
-
liveSlot: cek === null
|
|
623
|
-
});
|
|
624
|
-
if (cek === null && candidate !== null) {
|
|
625
|
-
cek = candidate;
|
|
626
|
-
matchedSlotIdx = i;
|
|
627
|
-
}
|
|
773
|
+
recordMatch(tryMlkem768X25519Slot({ slot: envelope.slots[i], recipientSecretKey, pubR }), i);
|
|
628
774
|
if (cek !== null && !constantTimeN) break;
|
|
629
775
|
}
|
|
630
776
|
}
|
|
631
|
-
return cek === null ? null : { cek, slotIdx: matchedSlotIdx };
|
|
777
|
+
return cek === null ? null : { cek, slotIdx: matchedSlotIdx, cekConflict };
|
|
632
778
|
}
|
|
633
|
-
function
|
|
634
|
-
return
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
envelope.kem
|
|
640
|
-
);
|
|
779
|
+
function slotsHashBytes(envelope) {
|
|
780
|
+
return computeSlotsHash({
|
|
781
|
+
kem: envelope.kem,
|
|
782
|
+
nonce: envelope.nonce,
|
|
783
|
+
slots: envelope.slots
|
|
784
|
+
});
|
|
641
785
|
}
|
|
642
786
|
function eciesSealedPoeUnwrap(args) {
|
|
643
787
|
const { envelope, ciphertext } = args;
|
|
@@ -666,34 +810,38 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
666
810
|
} else {
|
|
667
811
|
assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
|
|
668
812
|
}
|
|
813
|
+
assertCiphertextWithinBound(ciphertext.length);
|
|
814
|
+
const slotsHash = slotsHashBytes(envelope);
|
|
669
815
|
let matchedCek = null;
|
|
670
816
|
let anyCandidateRecovered = false;
|
|
671
817
|
if (hasSingle) {
|
|
672
818
|
const recipientSecretKey = args.recipientSecretKey;
|
|
673
|
-
const
|
|
819
|
+
const candidate = tryRecipientUnwrapWithIdx(
|
|
674
820
|
envelope,
|
|
675
821
|
recipientSecretKey,
|
|
676
822
|
constantTimeN,
|
|
677
823
|
args._slotsAttemptedOut
|
|
678
824
|
);
|
|
679
|
-
if (
|
|
825
|
+
if (candidate === null) {
|
|
680
826
|
return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
|
|
681
827
|
}
|
|
682
|
-
|
|
828
|
+
if (candidate.cekConflict) {
|
|
829
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
830
|
+
}
|
|
683
831
|
const hmacKey = hkdfSha256({
|
|
684
|
-
ikm: cek,
|
|
832
|
+
ikm: candidate.cek,
|
|
685
833
|
salt: EMPTY_SALT2,
|
|
686
834
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
687
835
|
length: 32
|
|
688
836
|
});
|
|
689
|
-
const slotsMacCalc = hmac(sha256, hmacKey,
|
|
837
|
+
const slotsMacCalc = hmac(sha256, hmacKey, slotsHash);
|
|
690
838
|
if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
691
839
|
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
692
840
|
}
|
|
693
|
-
matchedCek = cek;
|
|
841
|
+
matchedCek = candidate.cek;
|
|
694
842
|
} else {
|
|
695
|
-
const slotsCbor = slotsMacCborBytes(envelope);
|
|
696
843
|
const recipientSecretKeys = multiPrivKeys;
|
|
844
|
+
let cekConflict = false;
|
|
697
845
|
for (let k = 0; k < recipientSecretKeys.length; k++) {
|
|
698
846
|
if (args._privsAttemptedOut !== void 0) {
|
|
699
847
|
args._privsAttemptedOut.count = k + 1;
|
|
@@ -701,7 +849,7 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
701
849
|
if (args._slotsAttemptedOut !== void 0) {
|
|
702
850
|
args._slotsAttemptedOut.count = 0;
|
|
703
851
|
}
|
|
704
|
-
const
|
|
852
|
+
const candidate = tryRecipientUnwrapWithIdx(
|
|
705
853
|
envelope,
|
|
706
854
|
recipientSecretKeys[k],
|
|
707
855
|
constantTimeN,
|
|
@@ -710,20 +858,25 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
710
858
|
if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
|
|
711
859
|
args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
|
|
712
860
|
}
|
|
713
|
-
if (
|
|
861
|
+
if (candidate === null) continue;
|
|
862
|
+
if (candidate.cekConflict) cekConflict = true;
|
|
863
|
+
const cek = candidate.cek;
|
|
714
864
|
const hmacKey = hkdfSha256({
|
|
715
865
|
ikm: cek,
|
|
716
866
|
salt: EMPTY_SALT2,
|
|
717
867
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
718
868
|
length: 32
|
|
719
869
|
});
|
|
720
|
-
const slotsMacCalc = hmac(sha256, hmacKey,
|
|
870
|
+
const slotsMacCalc = hmac(sha256, hmacKey, slotsHash);
|
|
721
871
|
if (compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
722
872
|
matchedCek = cek;
|
|
723
873
|
break;
|
|
724
874
|
}
|
|
725
875
|
anyCandidateRecovered = true;
|
|
726
876
|
}
|
|
877
|
+
if (matchedCek !== null && cekConflict) {
|
|
878
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
879
|
+
}
|
|
727
880
|
if (matchedCek === null) {
|
|
728
881
|
return {
|
|
729
882
|
matched: false,
|
|
@@ -731,10 +884,16 @@ function eciesSealedPoeUnwrap(args) {
|
|
|
731
884
|
};
|
|
732
885
|
}
|
|
733
886
|
}
|
|
734
|
-
const
|
|
887
|
+
const payloadKey = slotsPayloadKey({ cek: matchedCek, nonce: envelope.nonce });
|
|
888
|
+
const adContent = adContentSlots({
|
|
889
|
+
kem: envelope.kem,
|
|
890
|
+
nonce: envelope.nonce,
|
|
891
|
+
slotsHash,
|
|
892
|
+
slotsMac: envelope.slots_mac
|
|
893
|
+
});
|
|
735
894
|
try {
|
|
736
895
|
const plaintext = xchacha20Poly1305Decrypt({
|
|
737
|
-
key:
|
|
896
|
+
key: payloadKey,
|
|
738
897
|
nonce: envelope.nonce,
|
|
739
898
|
aad: adContent,
|
|
740
899
|
ciphertext
|
|
@@ -760,7 +919,7 @@ function eciesSealedPoeTrialDecrypt(args) {
|
|
|
760
919
|
);
|
|
761
920
|
}
|
|
762
921
|
assertEnvelopeStructure(envelope, recipientSecretKeys, void 0);
|
|
763
|
-
const
|
|
922
|
+
const slotsHash = slotsHashBytes(envelope);
|
|
764
923
|
let anyCandidateRecovered = false;
|
|
765
924
|
for (let k = 0; k < recipientSecretKeys.length; k++) {
|
|
766
925
|
if (args._privsAttemptedOut !== void 0) {
|
|
@@ -779,13 +938,17 @@ function eciesSealedPoeTrialDecrypt(args) {
|
|
|
779
938
|
args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
|
|
780
939
|
}
|
|
781
940
|
if (candidate === null) continue;
|
|
941
|
+
if (candidate.cekConflict) {
|
|
942
|
+
anyCandidateRecovered = true;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
782
945
|
const hmacKey = hkdfSha256({
|
|
783
946
|
ikm: candidate.cek,
|
|
784
947
|
salt: EMPTY_SALT2,
|
|
785
948
|
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
786
949
|
length: 32
|
|
787
950
|
});
|
|
788
|
-
const slotsMacCalc = hmac(sha256, hmacKey,
|
|
951
|
+
const slotsMacCalc = hmac(sha256, hmacKey, slotsHash);
|
|
789
952
|
if (compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
790
953
|
return { kind: "match", slotIdx: candidate.slotIdx, cek: candidate.cek };
|
|
791
954
|
}
|
|
@@ -833,6 +996,6 @@ function sealedEnvelopeFromParsed(enc) {
|
|
|
833
996
|
return null;
|
|
834
997
|
}
|
|
835
998
|
|
|
836
|
-
export { CARDANO_POE_HKDF_INFO_KEK, CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519, CARDANO_POE_HKDF_INFO_SLOTS_MAC, EciesSealedPoeError, chunkKemCt, eciesSealedPoeTrialDecrypt, eciesSealedPoeUnwrap, eciesSealedPoeWrap, joinKemCt, sealedEnvelopeFromParsed,
|
|
999
|
+
export { CARDANO_POE_HKDF_INFO_KEK, CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519, CARDANO_POE_HKDF_INFO_PAYLOAD, CARDANO_POE_HKDF_INFO_PAYLOAD_PASSPHRASE, CARDANO_POE_HKDF_INFO_SLOTS_MAC, CARDANO_POE_PW_NORM_PROFILE, CARDANO_POE_SLOTS_TRANSCRIPT_PREFIX, CARDANO_POE_XWING_KEK_SALT_PREFIX, EciesSealedPoeError, MAX_DECODED_ENVELOPE_BYTES, MAX_SEALED_CIPHERTEXT, MAX_SEALED_PLAINTEXT, MAX_SLOTS, SealedPayloadTooLargeError, adContentPassphrase, adContentSlots, assertCiphertextWithinBound, assertPlaintextWithinBound, canonicalizeSlots, chunkKemCt, computeSlotsHash, eciesSealedPoeTrialDecrypt, eciesSealedPoeUnwrap, eciesSealedPoeWrap, joinKemCt, passphrasePayloadKey, sealedEnvelopeFromParsed, slotsPayloadKey, uniformIndexBelow, xwingKekSalt };
|
|
837
1000
|
//# sourceMappingURL=sealed-poe.js.map
|
|
838
1001
|
//# sourceMappingURL=sealed-poe.js.map
|