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