@cardanowall/crypto-core 0.0.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/LICENSE +202 -0
- package/README.md +192 -0
- package/dist/aead.cjs +44 -0
- package/dist/aead.cjs.map +1 -0
- package/dist/aead.d.cts +38 -0
- package/dist/aead.d.ts +38 -0
- package/dist/aead.js +38 -0
- package/dist/aead.js.map +1 -0
- package/dist/cbor.cjs +69 -0
- package/dist/cbor.cjs.map +1 -0
- package/dist/cbor.d.cts +17 -0
- package/dist/cbor.d.ts +17 -0
- package/dist/cbor.js +64 -0
- package/dist/cbor.js.map +1 -0
- package/dist/cose.cjs +430 -0
- package/dist/cose.cjs.map +1 -0
- package/dist/cose.d.cts +72 -0
- package/dist/cose.d.ts +72 -0
- package/dist/cose.js +398 -0
- package/dist/cose.js.map +1 -0
- package/dist/hash.cjs +165 -0
- package/dist/hash.cjs.map +1 -0
- package/dist/hash.d.cts +30 -0
- package/dist/hash.d.ts +30 -0
- package/dist/hash.js +155 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.cjs +1856 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1759 -0
- package/dist/index.js.map +1 -0
- package/dist/kdf.cjs +26 -0
- package/dist/kdf.cjs.map +1 -0
- package/dist/kdf.d.cts +25 -0
- package/dist/kdf.d.ts +25 -0
- package/dist/kdf.js +23 -0
- package/dist/kdf.js.map +1 -0
- package/dist/kem.cjs +86 -0
- package/dist/kem.cjs.map +1 -0
- package/dist/kem.d.cts +47 -0
- package/dist/kem.d.ts +47 -0
- package/dist/kem.js +73 -0
- package/dist/kem.js.map +1 -0
- package/dist/merkle.cjs +284 -0
- package/dist/merkle.cjs.map +1 -0
- package/dist/merkle.d.cts +24 -0
- package/dist/merkle.d.ts +24 -0
- package/dist/merkle.js +279 -0
- package/dist/merkle.js.map +1 -0
- package/dist/recipient.cjs +141 -0
- package/dist/recipient.cjs.map +1 -0
- package/dist/recipient.d.cts +16 -0
- package/dist/recipient.d.ts +16 -0
- package/dist/recipient.js +135 -0
- package/dist/recipient.js.map +1 -0
- package/dist/sealed-poe.cjs +851 -0
- package/dist/sealed-poe.cjs.map +1 -0
- package/dist/sealed-poe.d.cts +134 -0
- package/dist/sealed-poe.d.ts +134 -0
- package/dist/sealed-poe.js +838 -0
- package/dist/sealed-poe.js.map +1 -0
- package/dist/seed-derive.cjs +129 -0
- package/dist/seed-derive.cjs.map +1 -0
- package/dist/seed-derive.d.cts +28 -0
- package/dist/seed-derive.d.ts +28 -0
- package/dist/seed-derive.js +101 -0
- package/dist/seed-derive.js.map +1 -0
- package/dist/sig.cjs +77 -0
- package/dist/sig.cjs.map +1 -0
- package/dist/sig.d.cts +17 -0
- package/dist/sig.d.ts +17 -0
- package/dist/sig.js +53 -0
- package/dist/sig.js.map +1 -0
- package/dist/util.cjs +36 -0
- package/dist/util.cjs.map +1 -0
- package/dist/util.d.cts +5 -0
- package/dist/util.d.ts +5 -0
- package/dist/util.js +33 -0
- package/dist/util.js.map +1 -0
- package/package.json +122 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1856 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var sha2_js = require('@noble/hashes/sha2.js');
|
|
4
|
+
var blake2_js = require('@noble/hashes/blake2.js');
|
|
5
|
+
var hashWasm = require('hash-wasm');
|
|
6
|
+
var hkdf_js = require('@noble/hashes/hkdf.js');
|
|
7
|
+
var ed = require('@noble/ed25519');
|
|
8
|
+
var ed25519_js = require('@noble/curves/ed25519.js');
|
|
9
|
+
var hybrid_js = require('@noble/post-quantum/hybrid.js');
|
|
10
|
+
var chacha_js = require('@noble/ciphers/chacha.js');
|
|
11
|
+
var cbor2 = require('cbor2');
|
|
12
|
+
var sorts = require('cbor2/sorts');
|
|
13
|
+
var utils_js = require('@noble/ciphers/utils.js');
|
|
14
|
+
var hmac_js = require('@noble/hashes/hmac.js');
|
|
15
|
+
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var ed__namespace = /*#__PURE__*/_interopNamespace(ed);
|
|
35
|
+
|
|
36
|
+
// src/hash/sha-256.ts
|
|
37
|
+
function sha256(input) {
|
|
38
|
+
return sha2_js.sha256(input);
|
|
39
|
+
}
|
|
40
|
+
function blake2b256(input) {
|
|
41
|
+
return blake2_js.blake2b(input, { dkLen: 32 });
|
|
42
|
+
}
|
|
43
|
+
function blake2b224(input) {
|
|
44
|
+
return blake2_js.blake2b(input, { dkLen: 28 });
|
|
45
|
+
}
|
|
46
|
+
function dualHash(input) {
|
|
47
|
+
return {
|
|
48
|
+
sha256: sha256(input),
|
|
49
|
+
blake2b256: blake2b256(input)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function dualHashStream(source) {
|
|
53
|
+
const [sha, blake] = await Promise.all([hashWasm.createSHA256(), hashWasm.createBLAKE2b(256)]);
|
|
54
|
+
sha.init();
|
|
55
|
+
blake.init();
|
|
56
|
+
for await (const chunk of source) {
|
|
57
|
+
sha.update(chunk);
|
|
58
|
+
blake.update(chunk);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
sha256: sha.digest("binary"),
|
|
62
|
+
blake2b256: blake.digest("binary")
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/util/compare-ct.ts
|
|
67
|
+
function compareCt(a, b) {
|
|
68
|
+
if (a.length !== b.length) return false;
|
|
69
|
+
let diff = 0;
|
|
70
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
71
|
+
return diff === 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/hash/merkle-sha2-256.ts
|
|
75
|
+
var MERKLE_ALG_ID = "rfc9162-sha256";
|
|
76
|
+
var LEAF_PREFIX = 0;
|
|
77
|
+
var NODE_PREFIX = 1;
|
|
78
|
+
var DIGEST_LENGTH = 32;
|
|
79
|
+
function validateLeaves(leaves, fnName) {
|
|
80
|
+
if (leaves.length === 0) {
|
|
81
|
+
throw new Error(`${fnName}: empty leaf list (n == 0 is forbidden by RFC 9162 \xA72.1.1)`);
|
|
82
|
+
}
|
|
83
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
84
|
+
const leaf = leaves[i];
|
|
85
|
+
if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`${fnName}: leaf[${i}] must be a Uint8Array(${DIGEST_LENGTH}); got length ${leaf instanceof Uint8Array ? leaf.length : "non-Uint8Array"}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function merkleSha2256Root(leaves) {
|
|
93
|
+
validateLeaves(leaves, "merkleSha2256Root");
|
|
94
|
+
return mthRecursive(leaves, 0, leaves.length);
|
|
95
|
+
}
|
|
96
|
+
function merkleSha2256InclusionProof(leaves, index) {
|
|
97
|
+
validateLeaves(leaves, "merkleSha2256InclusionProof");
|
|
98
|
+
if (!Number.isInteger(index) || index < 0 || index >= leaves.length) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`merkleSha2256InclusionProof: index ${index} out of range [0, ${leaves.length})`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return auditPath(leaves, index, 0, leaves.length);
|
|
104
|
+
}
|
|
105
|
+
function merkleSha2256VerifyInclusion(leaf, index, treeSize, proof, root) {
|
|
106
|
+
if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) return false;
|
|
107
|
+
if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH) return false;
|
|
108
|
+
if (!Number.isInteger(index) || !Number.isInteger(treeSize) || treeSize < 1 || index < 0 || index >= treeSize) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
for (let i = 0; i < proof.length; i++) {
|
|
112
|
+
const sibling = proof[i];
|
|
113
|
+
if (!(sibling instanceof Uint8Array) || sibling.length !== DIGEST_LENGTH) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (treeSize === 1) {
|
|
118
|
+
if (proof.length !== 0 || index !== 0) return false;
|
|
119
|
+
return compareCt(hashLeaf(leaf), root);
|
|
120
|
+
}
|
|
121
|
+
let h = hashLeaf(leaf);
|
|
122
|
+
let sn = index;
|
|
123
|
+
let fn = treeSize - 1;
|
|
124
|
+
for (let i = 0; i < proof.length; i++) {
|
|
125
|
+
if (fn === 0) return false;
|
|
126
|
+
const sibling = proof[i];
|
|
127
|
+
if ((sn & 1) === 1 || sn === fn) {
|
|
128
|
+
h = hashNode(sibling, h);
|
|
129
|
+
while ((sn & 1) === 0 && sn !== 0) {
|
|
130
|
+
sn >>>= 1;
|
|
131
|
+
fn >>>= 1;
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
h = hashNode(h, sibling);
|
|
135
|
+
}
|
|
136
|
+
sn >>>= 1;
|
|
137
|
+
fn >>>= 1;
|
|
138
|
+
}
|
|
139
|
+
if (fn !== 0) return false;
|
|
140
|
+
return compareCt(h, root);
|
|
141
|
+
}
|
|
142
|
+
function largestPow2Lt(n) {
|
|
143
|
+
let k = 1;
|
|
144
|
+
while (k * 2 < n) k *= 2;
|
|
145
|
+
return k;
|
|
146
|
+
}
|
|
147
|
+
function hashLeaf(d) {
|
|
148
|
+
const buf = new Uint8Array(1 + d.length);
|
|
149
|
+
buf[0] = LEAF_PREFIX;
|
|
150
|
+
buf.set(d, 1);
|
|
151
|
+
return sha2_js.sha256(buf);
|
|
152
|
+
}
|
|
153
|
+
function hashNode(left, right) {
|
|
154
|
+
const buf = new Uint8Array(1 + left.length + right.length);
|
|
155
|
+
buf[0] = NODE_PREFIX;
|
|
156
|
+
buf.set(left, 1);
|
|
157
|
+
buf.set(right, 1 + left.length);
|
|
158
|
+
return sha2_js.sha256(buf);
|
|
159
|
+
}
|
|
160
|
+
function mthRecursive(leaves, start, end) {
|
|
161
|
+
const n = end - start;
|
|
162
|
+
if (n === 1) {
|
|
163
|
+
return hashLeaf(leaves[start]);
|
|
164
|
+
}
|
|
165
|
+
const k = largestPow2Lt(n);
|
|
166
|
+
const left = mthRecursive(leaves, start, start + k);
|
|
167
|
+
const right = mthRecursive(leaves, start + k, end);
|
|
168
|
+
return hashNode(left, right);
|
|
169
|
+
}
|
|
170
|
+
function auditPath(leaves, i, start, end) {
|
|
171
|
+
const n = end - start;
|
|
172
|
+
if (n === 1) return [];
|
|
173
|
+
const k = largestPow2Lt(n);
|
|
174
|
+
if (i < k) {
|
|
175
|
+
const subPath2 = auditPath(leaves, i, start, start + k);
|
|
176
|
+
subPath2.push(mthRecursive(leaves, start + k, end));
|
|
177
|
+
return subPath2;
|
|
178
|
+
}
|
|
179
|
+
const subPath = auditPath(leaves, i - k, start + k, end);
|
|
180
|
+
subPath.push(mthRecursive(leaves, start, start + k));
|
|
181
|
+
return subPath;
|
|
182
|
+
}
|
|
183
|
+
function hkdfSha256(opts) {
|
|
184
|
+
return hkdf_js.hkdf(sha2_js.sha256, opts.ikm, opts.salt, opts.info, opts.length);
|
|
185
|
+
}
|
|
186
|
+
async function argon2idV13(opts) {
|
|
187
|
+
return await hashWasm.argon2id({
|
|
188
|
+
password: opts.password,
|
|
189
|
+
salt: opts.salt,
|
|
190
|
+
parallelism: opts.parallelism,
|
|
191
|
+
iterations: opts.iterations,
|
|
192
|
+
memorySize: opts.memSizeKB,
|
|
193
|
+
hashLength: opts.outBytes,
|
|
194
|
+
outputType: "binary"
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
ed__namespace.hashes.sha512 = sha2_js.sha512;
|
|
198
|
+
var L = ed__namespace.Point.CURVE().n;
|
|
199
|
+
function signEd25519(opts) {
|
|
200
|
+
return ed__namespace.sign(opts.message, opts.seed);
|
|
201
|
+
}
|
|
202
|
+
function leBytesToBigInt(bytes) {
|
|
203
|
+
let value = 0n;
|
|
204
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
205
|
+
value = value << 8n | BigInt(bytes[i]);
|
|
206
|
+
}
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
function verifyEd25519(opts) {
|
|
210
|
+
const { signature, message, publicKey } = opts;
|
|
211
|
+
if (signature.length !== 64 || publicKey.length !== 32) return false;
|
|
212
|
+
const S = leBytesToBigInt(signature.subarray(32, 64));
|
|
213
|
+
if (S >= L) return false;
|
|
214
|
+
let A;
|
|
215
|
+
let R;
|
|
216
|
+
try {
|
|
217
|
+
A = ed__namespace.Point.fromBytes(publicKey);
|
|
218
|
+
R = ed__namespace.Point.fromBytes(signature.subarray(0, 32));
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (A.isSmallOrder() || R.isSmallOrder()) return false;
|
|
223
|
+
const k = leBytesToBigInt(ed__namespace.hash(concatBytes(signature.subarray(0, 32), publicKey, message))) % L;
|
|
224
|
+
const sB = S === 0n ? ed__namespace.Point.ZERO : ed__namespace.Point.BASE.multiplyUnsafe(S);
|
|
225
|
+
const kA = k === 0n ? ed__namespace.Point.ZERO : A.multiplyUnsafe(k);
|
|
226
|
+
return sB.subtract(kA).subtract(R).is0();
|
|
227
|
+
}
|
|
228
|
+
function concatBytes(...parts) {
|
|
229
|
+
let total = 0;
|
|
230
|
+
for (const p of parts) total += p.length;
|
|
231
|
+
const out = new Uint8Array(total);
|
|
232
|
+
let offset = 0;
|
|
233
|
+
for (const p of parts) {
|
|
234
|
+
out.set(p, offset);
|
|
235
|
+
offset += p.length;
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
function getPublicKeyEd25519(opts) {
|
|
240
|
+
return ed__namespace.getPublicKey(opts.seed);
|
|
241
|
+
}
|
|
242
|
+
var X25519LowOrderPointError = class extends Error {
|
|
243
|
+
code = "X25519_LOW_ORDER_POINT";
|
|
244
|
+
constructor(options) {
|
|
245
|
+
super("x25519 ECDH rejected: peer public key is a small-order point", options);
|
|
246
|
+
this.name = "X25519LowOrderPointError";
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var NOBLE_LOW_ORDER_MESSAGE = "invalid private or public key received";
|
|
250
|
+
function x25519Keygen() {
|
|
251
|
+
return ed25519_js.x25519.keygen();
|
|
252
|
+
}
|
|
253
|
+
function x25519PublicKey(opts) {
|
|
254
|
+
return ed25519_js.x25519.getPublicKey(opts.secretKey);
|
|
255
|
+
}
|
|
256
|
+
function x25519Ecdh(opts) {
|
|
257
|
+
try {
|
|
258
|
+
return ed25519_js.x25519.getSharedSecret(opts.secretKey, opts.theirPublicKey);
|
|
259
|
+
} catch (e) {
|
|
260
|
+
if (e instanceof Error && e.message === NOBLE_LOW_ORDER_MESSAGE) {
|
|
261
|
+
throw new X25519LowOrderPointError({ cause: e });
|
|
262
|
+
}
|
|
263
|
+
throw e;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
var MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216;
|
|
267
|
+
var MLKEM768X25519_ENC_LENGTH = 1120;
|
|
268
|
+
var MLKEM768X25519_SHARED_SECRET_LENGTH = 32;
|
|
269
|
+
var MLKEM768X25519_SEED_LENGTH = 32;
|
|
270
|
+
var MLKEM768X25519_ESEED_LENGTH = 64;
|
|
271
|
+
function mlkem768x25519Keygen(seed) {
|
|
272
|
+
if (seed.length !== MLKEM768X25519_SEED_LENGTH) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`mlkem768x25519 seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${seed.length}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
const { secretKey, publicKey } = hybrid_js.XWing.keygen(seed);
|
|
278
|
+
return { secretSeed: secretKey, publicKey };
|
|
279
|
+
}
|
|
280
|
+
function mlkem768x25519Encapsulate(opts) {
|
|
281
|
+
if (opts.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`mlkem768x25519 public key must be ${MLKEM768X25519_PUBLIC_KEY_LENGTH} bytes, got ${opts.publicKey.length}`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (opts.eseed !== void 0 && opts.eseed.length !== MLKEM768X25519_ESEED_LENGTH) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`mlkem768x25519 eseed must be ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${opts.eseed.length}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const { cipherText, sharedSecret } = hybrid_js.XWing.encapsulate(opts.publicKey, opts.eseed);
|
|
292
|
+
return { enc: cipherText, ss: sharedSecret };
|
|
293
|
+
}
|
|
294
|
+
function mlkem768x25519Decapsulate(opts) {
|
|
295
|
+
if (opts.secretSeed.length !== MLKEM768X25519_SEED_LENGTH) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
`mlkem768x25519 secret seed must be ${MLKEM768X25519_SEED_LENGTH} bytes, got ${opts.secretSeed.length}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (opts.enc.length !== MLKEM768X25519_ENC_LENGTH) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`mlkem768x25519 enc must be ${MLKEM768X25519_ENC_LENGTH} bytes, got ${opts.enc.length}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return hybrid_js.XWing.decapsulate(opts.enc, opts.secretSeed);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/aead/errors.ts
|
|
309
|
+
var AeadVerificationError = class extends Error {
|
|
310
|
+
code = "aead_verification_failed";
|
|
311
|
+
constructor(message, options) {
|
|
312
|
+
super(message, options);
|
|
313
|
+
this.name = "AeadVerificationError";
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// src/aead/chacha20-poly1305.ts
|
|
318
|
+
function chacha20Poly1305Encrypt(opts) {
|
|
319
|
+
return chacha_js.chacha20poly1305(opts.key, opts.nonce, opts.aad).encrypt(opts.plaintext);
|
|
320
|
+
}
|
|
321
|
+
function chacha20Poly1305Decrypt(opts) {
|
|
322
|
+
try {
|
|
323
|
+
return chacha_js.chacha20poly1305(opts.key, opts.nonce, opts.aad).decrypt(opts.ciphertext);
|
|
324
|
+
} catch (cause) {
|
|
325
|
+
throw new AeadVerificationError("chacha20-poly1305 decrypt failed", { cause });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function xchacha20Poly1305Encrypt(opts) {
|
|
329
|
+
return chacha_js.xchacha20poly1305(opts.key, opts.nonce, opts.aad).encrypt(opts.plaintext);
|
|
330
|
+
}
|
|
331
|
+
function xchacha20Poly1305Decrypt(opts) {
|
|
332
|
+
try {
|
|
333
|
+
return chacha_js.xchacha20poly1305(opts.key, opts.nonce, opts.aad).decrypt(opts.ciphertext);
|
|
334
|
+
} catch (cause) {
|
|
335
|
+
throw new AeadVerificationError("xchacha20-poly1305 decrypt failed", { cause });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/util/hex.ts
|
|
340
|
+
function hexToBytes(hex) {
|
|
341
|
+
if ((hex.length & 1) !== 0) {
|
|
342
|
+
throw new Error(`hexToBytes: input length ${hex.length} is not even`);
|
|
343
|
+
}
|
|
344
|
+
const out = new Uint8Array(hex.length >>> 1);
|
|
345
|
+
for (let i = 0; i < out.length; i++) {
|
|
346
|
+
const hi = charToNibble(hex.charCodeAt(i * 2));
|
|
347
|
+
const lo = charToNibble(hex.charCodeAt(i * 2 + 1));
|
|
348
|
+
if (hi < 0 || lo < 0) {
|
|
349
|
+
throw new Error(`hexToBytes: non-hex character at offset ${i * 2}`);
|
|
350
|
+
}
|
|
351
|
+
out[i] = hi << 4 | lo;
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
function charToNibble(code) {
|
|
356
|
+
if (code >= 48 && code <= 57) return code - 48;
|
|
357
|
+
if (code >= 97 && code <= 102) return code - 87;
|
|
358
|
+
return -1;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/cbor/errors.ts
|
|
362
|
+
var CanonicalCborError = class extends Error {
|
|
363
|
+
code;
|
|
364
|
+
constructor(code, message, options) {
|
|
365
|
+
super(message, options);
|
|
366
|
+
this.name = "CanonicalCborError";
|
|
367
|
+
this.code = code;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/cbor/canonical.ts
|
|
372
|
+
function encodeCanonicalCbor(value) {
|
|
373
|
+
return cbor2.encode(value, {
|
|
374
|
+
cde: true,
|
|
375
|
+
collapseBigInts: true,
|
|
376
|
+
rejectDuplicateKeys: true,
|
|
377
|
+
sortKeys: sorts.sortCoreDeterministic
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function decodeCanonicalCbor(bytes) {
|
|
381
|
+
try {
|
|
382
|
+
return cbor2.decode(bytes, {
|
|
383
|
+
...cbor2.cdeDecodeOptions,
|
|
384
|
+
rejectStreaming: true,
|
|
385
|
+
rejectDuplicateKeys: true,
|
|
386
|
+
// A CIP-309 record carries integers, byte/text strings, arrays, maps and
|
|
387
|
+
// `null` — and nothing else. Without these rejections the major-type-7
|
|
388
|
+
// surface leaks into the decoder: a float16/32/64 that happens to hold an
|
|
389
|
+
// integral value (e.g. 1.0) silently decodes to the integer 1 and passes
|
|
390
|
+
// a `z.literal(1)` / Number.isInteger schema check, so two byte strings
|
|
391
|
+
// that are NOT byte-identical canonicalise to the same record. That
|
|
392
|
+
// breaks the cross-implementation parity invariant (the Python twin
|
|
393
|
+
// already rejects non-integer `v` / `enc.scheme` outright). Reject the
|
|
394
|
+
// whole non-record surface — floats, negative zero, undefined, and
|
|
395
|
+
// non-{true,false,null} simple values — so any such input surfaces as
|
|
396
|
+
// MALFORMED_CBOR via mapDecodeError rather than decoding to a look-alike.
|
|
397
|
+
rejectFloats: true,
|
|
398
|
+
rejectNegativeZero: true,
|
|
399
|
+
rejectUndefined: true,
|
|
400
|
+
rejectSimple: true
|
|
401
|
+
});
|
|
402
|
+
} catch (cause) {
|
|
403
|
+
throw mapDecodeError(cause);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function mapDecodeError(cause) {
|
|
407
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
408
|
+
const lower = message.toLowerCase();
|
|
409
|
+
const isIndefinite = lower.includes("streaming") || lower.includes("indefinite");
|
|
410
|
+
const detail = isIndefinite ? `indefinite-length items are not permitted in canonical CBOR: ${message}` : message;
|
|
411
|
+
return new CanonicalCborError("MALFORMED_CBOR", `cbor decode failed: ${detail}`, { cause });
|
|
412
|
+
}
|
|
413
|
+
function decodeCbor(bytes) {
|
|
414
|
+
return cbor2.decode(bytes);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/cose/errors.ts
|
|
418
|
+
var CoseVerifyError = class extends Error {
|
|
419
|
+
code;
|
|
420
|
+
constructor(code, message, options) {
|
|
421
|
+
super(message, options);
|
|
422
|
+
this.name = "CoseVerifyError";
|
|
423
|
+
this.code = code;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// src/cose/sign1.ts
|
|
428
|
+
var CARDANO_POE_SIG_DOMAIN_PREFIX = "cardano-poe-record-sig-v1";
|
|
429
|
+
var CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES = new TextEncoder().encode(
|
|
430
|
+
CARDANO_POE_SIG_DOMAIN_PREFIX
|
|
431
|
+
);
|
|
432
|
+
if (CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length !== 25) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
`cardano-poe-record-sig-v1 prefix must encode to exactly 25 UTF-8 bytes, got ${CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length}`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
var EMPTY_BYTES = new Uint8Array(0);
|
|
438
|
+
function buildSigStructure(args) {
|
|
439
|
+
return encodeCanonicalCbor([
|
|
440
|
+
args.context,
|
|
441
|
+
args.bodyProtectedBytes,
|
|
442
|
+
args.externalAad,
|
|
443
|
+
args.payload
|
|
444
|
+
]);
|
|
445
|
+
}
|
|
446
|
+
function buildCip309SigStructure(args) {
|
|
447
|
+
const toSign = new Uint8Array(
|
|
448
|
+
CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length + args.recordBodyCbor.length
|
|
449
|
+
);
|
|
450
|
+
toSign.set(CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES, 0);
|
|
451
|
+
toSign.set(args.recordBodyCbor, CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length);
|
|
452
|
+
return buildSigStructure({
|
|
453
|
+
context: "Signature1",
|
|
454
|
+
bodyProtectedBytes: args.bodyProtectedBytes,
|
|
455
|
+
externalAad: EMPTY_BYTES,
|
|
456
|
+
payload: toSign
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function encodeCoseSign1(args) {
|
|
460
|
+
const protectedBytes = args.protectedHeader.size === 0 ? EMPTY_BYTES : encodeCanonicalCbor(args.protectedHeader);
|
|
461
|
+
return encodeCanonicalCbor([
|
|
462
|
+
protectedBytes,
|
|
463
|
+
args.unprotectedHeader,
|
|
464
|
+
args.payload,
|
|
465
|
+
args.signature
|
|
466
|
+
]);
|
|
467
|
+
}
|
|
468
|
+
function asCoseHeader(value) {
|
|
469
|
+
if (value instanceof Map) return value;
|
|
470
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
471
|
+
return new Map(Object.entries(value));
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
function decodeCoseSign1(bytes) {
|
|
476
|
+
let arr;
|
|
477
|
+
try {
|
|
478
|
+
arr = decodeCanonicalCbor(bytes);
|
|
479
|
+
} catch (cause) {
|
|
480
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "cose decode failed", { cause });
|
|
481
|
+
}
|
|
482
|
+
if (!Array.isArray(arr) || arr.length !== 4) {
|
|
483
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "expected 4-element array");
|
|
484
|
+
}
|
|
485
|
+
const [protectedBytesRaw, unprotectedRaw, payloadRaw, signatureRaw] = arr;
|
|
486
|
+
if (!(protectedBytesRaw instanceof Uint8Array)) {
|
|
487
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "protected_bytes must be bytes");
|
|
488
|
+
}
|
|
489
|
+
const unprotectedHeader = asCoseHeader(unprotectedRaw);
|
|
490
|
+
if (unprotectedHeader === null) {
|
|
491
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "unprotected header must be map");
|
|
492
|
+
}
|
|
493
|
+
if (payloadRaw !== null && !(payloadRaw instanceof Uint8Array)) {
|
|
494
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "payload must be bytes or null");
|
|
495
|
+
}
|
|
496
|
+
if (!(signatureRaw instanceof Uint8Array) || signatureRaw.length !== 64) {
|
|
497
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "signature must be 64 bytes");
|
|
498
|
+
}
|
|
499
|
+
let protectedHeader;
|
|
500
|
+
if (protectedBytesRaw.length === 0) {
|
|
501
|
+
protectedHeader = /* @__PURE__ */ new Map();
|
|
502
|
+
} else {
|
|
503
|
+
let decodedProtected;
|
|
504
|
+
try {
|
|
505
|
+
decodedProtected = decodeCanonicalCbor(protectedBytesRaw);
|
|
506
|
+
} catch (cause) {
|
|
507
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "protected header decode failed", { cause });
|
|
508
|
+
}
|
|
509
|
+
const ph = asCoseHeader(decodedProtected);
|
|
510
|
+
if (ph === null) {
|
|
511
|
+
throw new CoseVerifyError("MALFORMED_SIG_COSE", "protected header must decode to map");
|
|
512
|
+
}
|
|
513
|
+
if (ph.size === 0) {
|
|
514
|
+
throw new CoseVerifyError(
|
|
515
|
+
"MALFORMED_SIG_COSE",
|
|
516
|
+
"empty protected header must encode as 0x40 (zero-length bstr), not as an empty map"
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
protectedHeader = ph;
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
protectedHeader,
|
|
523
|
+
protectedBytes: protectedBytesRaw,
|
|
524
|
+
unprotectedHeader,
|
|
525
|
+
payload: payloadRaw,
|
|
526
|
+
signature: signatureRaw
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
var CoseSign1BuildError = class extends Error {
|
|
530
|
+
code;
|
|
531
|
+
constructor(code, message) {
|
|
532
|
+
super(message);
|
|
533
|
+
this.name = "CoseSign1BuildError";
|
|
534
|
+
this.code = code;
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
function coseSign1Cip309Build(args) {
|
|
538
|
+
if (args.signerSecretKey === void 0 && args.signer === void 0) {
|
|
539
|
+
throw new CoseSign1BuildError(
|
|
540
|
+
"SIGNER_NOT_PROVIDED",
|
|
541
|
+
"coseSign1Cip309Build requires either signerSecretKey or signer"
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
if (args.signerSecretKey !== void 0 && args.signer !== void 0) {
|
|
545
|
+
throw new CoseSign1BuildError(
|
|
546
|
+
"SIGNER_AND_SEED_BOTH_PROVIDED",
|
|
547
|
+
"coseSign1Cip309Build accepts signerSecretKey XOR signer (not both)"
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const protectedBytes = args.protectedHeader.size === 0 ? EMPTY_BYTES : encodeCanonicalCbor(args.protectedHeader);
|
|
551
|
+
const sigStructureBytes = buildCip309SigStructure({
|
|
552
|
+
bodyProtectedBytes: protectedBytes,
|
|
553
|
+
recordBodyCbor: args.recordBodyCbor
|
|
554
|
+
});
|
|
555
|
+
let signature;
|
|
556
|
+
if (args.signer !== void 0) {
|
|
557
|
+
signature = args.signer(sigStructureBytes);
|
|
558
|
+
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
559
|
+
throw new CoseSign1BuildError(
|
|
560
|
+
"SIGNER_NOT_PROVIDED",
|
|
561
|
+
`injected signer must return a 64-byte Uint8Array; got ${signature instanceof Uint8Array ? `${signature.length}-byte Uint8Array` : typeof signature}`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
signature = signEd25519({ seed: args.signerSecretKey, message: sigStructureBytes });
|
|
566
|
+
}
|
|
567
|
+
return encodeCoseSign1({
|
|
568
|
+
protectedHeader: args.protectedHeader,
|
|
569
|
+
unprotectedHeader: args.unprotectedHeader,
|
|
570
|
+
payload: null,
|
|
571
|
+
signature
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
function coseSign1Cip309Verify(args) {
|
|
575
|
+
let decoded;
|
|
576
|
+
try {
|
|
577
|
+
decoded = decodeCoseSign1(args.message);
|
|
578
|
+
} catch (e) {
|
|
579
|
+
if (e instanceof CoseVerifyError) {
|
|
580
|
+
return { ok: false, error: { code: e.code, message: "errors.cose.malformed" } };
|
|
581
|
+
}
|
|
582
|
+
if (e instanceof CanonicalCborError) {
|
|
583
|
+
return {
|
|
584
|
+
ok: false,
|
|
585
|
+
error: { code: "MALFORMED_SIG_COSE", message: "errors.cose.malformed_cbor" }
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
throw e;
|
|
589
|
+
}
|
|
590
|
+
if (decoded.payload !== null) {
|
|
591
|
+
return {
|
|
592
|
+
ok: false,
|
|
593
|
+
error: {
|
|
594
|
+
code: "MALFORMED_SIG_COSE_SIGN1",
|
|
595
|
+
message: "errors.cose.attached_payload_forbidden"
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
const alg = decoded.protectedHeader.get(1);
|
|
600
|
+
if (typeof alg !== "number" || alg !== -8) {
|
|
601
|
+
return {
|
|
602
|
+
ok: false,
|
|
603
|
+
error: { code: "UNSUPPORTED_SIG_ALG", message: "errors.cose.unsupported_alg" }
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const kidRaw = decoded.protectedHeader.get(4);
|
|
607
|
+
let signerKey;
|
|
608
|
+
if (kidRaw instanceof Uint8Array && kidRaw.length === 32) {
|
|
609
|
+
signerKey = kidRaw;
|
|
610
|
+
} else if (args.expectedSignerKey instanceof Uint8Array && args.expectedSignerKey.length === 32) {
|
|
611
|
+
signerKey = args.expectedSignerKey;
|
|
612
|
+
}
|
|
613
|
+
if (signerKey === void 0) {
|
|
614
|
+
return {
|
|
615
|
+
ok: false,
|
|
616
|
+
error: { code: "KID_UNRESOLVED", message: "errors.cose.kid_unresolved" }
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (kidRaw instanceof Uint8Array && kidRaw.length === 32 && args.expectedSignerKey instanceof Uint8Array && args.expectedSignerKey.length === 32 && !compareCt(kidRaw, args.expectedSignerKey)) {
|
|
620
|
+
return {
|
|
621
|
+
ok: false,
|
|
622
|
+
error: { code: "KID_UNRESOLVED", message: "errors.cose.kid_mismatch" }
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
const hashedFlag = decoded.unprotectedHeader.get("hashed");
|
|
626
|
+
let sigStructureBytes;
|
|
627
|
+
if (hashedFlag === true) {
|
|
628
|
+
const toSign = new Uint8Array(
|
|
629
|
+
CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length + args.detachedRecordBodyCbor.length
|
|
630
|
+
);
|
|
631
|
+
toSign.set(CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES, 0);
|
|
632
|
+
toSign.set(args.detachedRecordBodyCbor, CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length);
|
|
633
|
+
const hashedPayload = blake2b224(toSign);
|
|
634
|
+
sigStructureBytes = buildSigStructure({
|
|
635
|
+
context: "Signature1",
|
|
636
|
+
bodyProtectedBytes: decoded.protectedBytes,
|
|
637
|
+
externalAad: EMPTY_BYTES,
|
|
638
|
+
payload: hashedPayload
|
|
639
|
+
});
|
|
640
|
+
} else {
|
|
641
|
+
sigStructureBytes = buildCip309SigStructure({
|
|
642
|
+
bodyProtectedBytes: decoded.protectedBytes,
|
|
643
|
+
recordBodyCbor: args.detachedRecordBodyCbor
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const valid = verifyEd25519({
|
|
647
|
+
publicKey: signerKey,
|
|
648
|
+
message: sigStructureBytes,
|
|
649
|
+
signature: decoded.signature
|
|
650
|
+
});
|
|
651
|
+
if (!valid) {
|
|
652
|
+
return {
|
|
653
|
+
ok: false,
|
|
654
|
+
error: { code: "SIGNATURE_INVALID", message: "errors.cose.signature_invalid" }
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
return { ok: true, signerKey, alg };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// src/cose/cose-key.ts
|
|
661
|
+
var COSE_KEY_LABEL_KTY = 1;
|
|
662
|
+
var COSE_KEY_LABEL_ALG = 3;
|
|
663
|
+
var COSE_KEY_LABEL_CRV = -1;
|
|
664
|
+
var COSE_KEY_LABEL_X = -2;
|
|
665
|
+
var KTY_OKP = 1;
|
|
666
|
+
var ALG_EDDSA = -8;
|
|
667
|
+
var CRV_ED25519 = 6;
|
|
668
|
+
var ED25519_PUBLIC_KEY_LENGTH = 32;
|
|
669
|
+
function asMap(value) {
|
|
670
|
+
if (value instanceof Map) return value;
|
|
671
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
672
|
+
return new Map(Object.entries(value));
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
function parseCoseKeyEd25519(blob) {
|
|
677
|
+
let decoded;
|
|
678
|
+
try {
|
|
679
|
+
decoded = decodeCanonicalCbor(blob);
|
|
680
|
+
} catch {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
const map = asMap(decoded);
|
|
684
|
+
if (map === null) return null;
|
|
685
|
+
const kty = map.get(COSE_KEY_LABEL_KTY);
|
|
686
|
+
if (typeof kty !== "number" || kty !== KTY_OKP) return null;
|
|
687
|
+
const crv = map.get(COSE_KEY_LABEL_CRV);
|
|
688
|
+
if (typeof crv !== "number" || crv !== CRV_ED25519) return null;
|
|
689
|
+
if (map.has(COSE_KEY_LABEL_ALG)) {
|
|
690
|
+
const alg = map.get(COSE_KEY_LABEL_ALG);
|
|
691
|
+
if (typeof alg !== "number" || alg !== ALG_EDDSA) return null;
|
|
692
|
+
}
|
|
693
|
+
const x = map.get(COSE_KEY_LABEL_X);
|
|
694
|
+
if (!(x instanceof Uint8Array) || x.length !== ED25519_PUBLIC_KEY_LENGTH) return null;
|
|
695
|
+
return x;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/seed-derive/errors.ts
|
|
699
|
+
var SeedDeriveError = class extends Error {
|
|
700
|
+
code;
|
|
701
|
+
constructor(code, message, options) {
|
|
702
|
+
super(message, options);
|
|
703
|
+
this.name = "SeedDeriveError";
|
|
704
|
+
this.code = code;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/seed-derive/derive.ts
|
|
709
|
+
var INFO_ED25519 = new TextEncoder().encode("cardano-poe-ed25519-v1");
|
|
710
|
+
var INFO_X25519 = new TextEncoder().encode("cardano-poe-x25519-v1");
|
|
711
|
+
var INFO_MLKEM768X25519 = new TextEncoder().encode(
|
|
712
|
+
"cardano-poe-mlkem768x25519-v1"
|
|
713
|
+
);
|
|
714
|
+
if (INFO_ED25519.length !== 22) {
|
|
715
|
+
throw new Error("INFO_ED25519 byte-length invariant violated (expected 22)");
|
|
716
|
+
}
|
|
717
|
+
if (INFO_X25519.length !== 21) {
|
|
718
|
+
throw new Error("INFO_X25519 byte-length invariant violated (expected 21)");
|
|
719
|
+
}
|
|
720
|
+
if (INFO_MLKEM768X25519.length !== 29) {
|
|
721
|
+
throw new Error("INFO_MLKEM768X25519 byte-length invariant violated (expected 29)");
|
|
722
|
+
}
|
|
723
|
+
var EMPTY_SALT = new Uint8Array(0);
|
|
724
|
+
var SEED_LENGTH = 32;
|
|
725
|
+
var DERIVED_LENGTH = 32;
|
|
726
|
+
function assertSeedLength(seed) {
|
|
727
|
+
if (seed.length !== SEED_LENGTH) {
|
|
728
|
+
throw new SeedDeriveError(
|
|
729
|
+
"INVALID_SEED_LENGTH",
|
|
730
|
+
`seed must be exactly 32 bytes, got ${seed.length}`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function deriveEd25519KeypairFromSeed(seed) {
|
|
735
|
+
assertSeedLength(seed);
|
|
736
|
+
const secretKey = hkdfSha256({
|
|
737
|
+
ikm: seed,
|
|
738
|
+
salt: EMPTY_SALT,
|
|
739
|
+
info: INFO_ED25519,
|
|
740
|
+
length: DERIVED_LENGTH
|
|
741
|
+
});
|
|
742
|
+
const publicKey = getPublicKeyEd25519({ seed: secretKey });
|
|
743
|
+
return { secretKey, publicKey };
|
|
744
|
+
}
|
|
745
|
+
function deriveX25519KeypairFromSeed(seed) {
|
|
746
|
+
assertSeedLength(seed);
|
|
747
|
+
const secretKey = hkdfSha256({
|
|
748
|
+
ikm: seed,
|
|
749
|
+
salt: EMPTY_SALT,
|
|
750
|
+
info: INFO_X25519,
|
|
751
|
+
length: DERIVED_LENGTH
|
|
752
|
+
});
|
|
753
|
+
const publicKey = x25519PublicKey({ secretKey });
|
|
754
|
+
return { secretKey, publicKey };
|
|
755
|
+
}
|
|
756
|
+
function deriveMlKem768X25519KeypairFromSeed(seed) {
|
|
757
|
+
assertSeedLength(seed);
|
|
758
|
+
const xwingSeed = hkdfSha256({
|
|
759
|
+
ikm: seed,
|
|
760
|
+
salt: EMPTY_SALT,
|
|
761
|
+
info: INFO_MLKEM768X25519,
|
|
762
|
+
length: DERIVED_LENGTH
|
|
763
|
+
});
|
|
764
|
+
return mlkem768x25519Keygen(xwingSeed);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// src/sealed-poe/errors.ts
|
|
768
|
+
var EciesSealedPoeError = class extends Error {
|
|
769
|
+
code;
|
|
770
|
+
constructor(code, message, options) {
|
|
771
|
+
super(message, options);
|
|
772
|
+
this.name = "EciesSealedPoeError";
|
|
773
|
+
this.code = code;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
// src/sealed-poe/slots-codec.ts
|
|
778
|
+
var CHUNK_MAX_BYTES = 64;
|
|
779
|
+
function chunkKemCt(value) {
|
|
780
|
+
if (value.length === 0) {
|
|
781
|
+
throw new Error("chunkKemCt: refusing to chunk an empty byte string");
|
|
782
|
+
}
|
|
783
|
+
const chunks = [];
|
|
784
|
+
for (let i = 0; i < value.length; i += CHUNK_MAX_BYTES) {
|
|
785
|
+
chunks.push(value.subarray(i, Math.min(i + CHUNK_MAX_BYTES, value.length)));
|
|
786
|
+
}
|
|
787
|
+
return chunks;
|
|
788
|
+
}
|
|
789
|
+
function joinKemCt(chunks) {
|
|
790
|
+
let total = 0;
|
|
791
|
+
for (const c of chunks) total += c.length;
|
|
792
|
+
const out = new Uint8Array(total);
|
|
793
|
+
let offset = 0;
|
|
794
|
+
for (const c of chunks) {
|
|
795
|
+
out.set(c, offset);
|
|
796
|
+
offset += c.length;
|
|
797
|
+
}
|
|
798
|
+
return out;
|
|
799
|
+
}
|
|
800
|
+
function slotsToMacCbor(slots, kem) {
|
|
801
|
+
let value;
|
|
802
|
+
if (kem === "x25519") {
|
|
803
|
+
value = slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
|
|
804
|
+
} else {
|
|
805
|
+
value = slots.map((s) => ({
|
|
806
|
+
// Canonicalize the chunk boundaries before the MAC commits to them:
|
|
807
|
+
// reassemble the logical ciphertext and re-split into canonical ≤ 64-byte
|
|
808
|
+
// chunks. The on-wire `kem_ct` array is a transport detail (the Cardano
|
|
809
|
+
// ledger's 64-byte metadatum cap), and a hostile or non-canonical chunking
|
|
810
|
+
// ([1, 63, …] instead of [64, …]) reassembles to the SAME bytes — so the
|
|
811
|
+
// MAC must be invariant to it. Committing to the verbatim wire chunks would
|
|
812
|
+
// let an attacker re-chunk an honest envelope and break the slots_mac match
|
|
813
|
+
// for an honest recipient. Honest (already-64B-chunked) records are
|
|
814
|
+
// unchanged; a real byte flip still changes the reassembled bytes and is
|
|
815
|
+
// still rejected.
|
|
816
|
+
kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
|
|
817
|
+
wrap: s.wrap
|
|
818
|
+
}));
|
|
819
|
+
}
|
|
820
|
+
return encodeCanonicalCbor(value);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/sealed-poe/wrap.ts
|
|
824
|
+
var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
|
|
825
|
+
var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
|
|
826
|
+
"cardano-poe-kek-mlkem768x25519-v1"
|
|
827
|
+
);
|
|
828
|
+
var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
|
|
829
|
+
"cardano-poe-slots-mac-v1"
|
|
830
|
+
);
|
|
831
|
+
var ZERO_NONCE_12 = new Uint8Array(12);
|
|
832
|
+
var EMPTY_SALT2 = new Uint8Array(0);
|
|
833
|
+
var X25519_PUBLIC_KEY_LENGTH = 32;
|
|
834
|
+
var X25519_SECRET_KEY_LENGTH = 32;
|
|
835
|
+
var CEK_LENGTH = 32;
|
|
836
|
+
var NONCE_LENGTH = 24;
|
|
837
|
+
var WRAP_LENGTH = 48;
|
|
838
|
+
var SLOTS_MAC_LENGTH = 32;
|
|
839
|
+
if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
|
|
840
|
+
throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
|
|
841
|
+
}
|
|
842
|
+
if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
|
|
843
|
+
throw new Error(
|
|
844
|
+
"CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
|
|
848
|
+
throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
|
|
849
|
+
}
|
|
850
|
+
if (ZERO_NONCE_12.length !== 12) {
|
|
851
|
+
throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
|
|
852
|
+
}
|
|
853
|
+
function concat(a, b) {
|
|
854
|
+
const out = new Uint8Array(a.length + b.length);
|
|
855
|
+
out.set(a, 0);
|
|
856
|
+
out.set(b, a.length);
|
|
857
|
+
return out;
|
|
858
|
+
}
|
|
859
|
+
function uniformIndexBelow(m) {
|
|
860
|
+
const limit = 4294967296 - 4294967296 % m;
|
|
861
|
+
const buf = new Uint32Array(1);
|
|
862
|
+
let x;
|
|
863
|
+
do {
|
|
864
|
+
crypto.getRandomValues(buf);
|
|
865
|
+
x = buf[0];
|
|
866
|
+
} while (x >= limit);
|
|
867
|
+
return x % m;
|
|
868
|
+
}
|
|
869
|
+
function csprngShuffle(arr) {
|
|
870
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
871
|
+
const j = uniformIndexBelow(i + 1);
|
|
872
|
+
const tmp = arr[i];
|
|
873
|
+
arr[i] = arr[j];
|
|
874
|
+
arr[j] = tmp;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function wrapSlotX25519(args) {
|
|
878
|
+
const privEph = args.privEph ?? utils_js.randomBytes(X25519_SECRET_KEY_LENGTH);
|
|
879
|
+
if (privEph.length !== X25519_SECRET_KEY_LENGTH) {
|
|
880
|
+
throw new EciesSealedPoeError(
|
|
881
|
+
"INVALID_EPHEMERAL_SECRET_LENGTH",
|
|
882
|
+
`ephemeralSecrets[${args.slotIdx}] MUST be exactly ${X25519_SECRET_KEY_LENGTH} bytes, got ${privEph.length}`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
const epk = x25519PublicKey({ secretKey: privEph });
|
|
886
|
+
const shared = x25519Ecdh({ secretKey: privEph, theirPublicKey: args.pubR });
|
|
887
|
+
const kek = hkdfSha256({
|
|
888
|
+
ikm: shared,
|
|
889
|
+
salt: concat(epk, args.pubR),
|
|
890
|
+
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
891
|
+
length: 32
|
|
892
|
+
});
|
|
893
|
+
const wrap = chacha20Poly1305Encrypt({
|
|
894
|
+
key: kek,
|
|
895
|
+
nonce: ZERO_NONCE_12,
|
|
896
|
+
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
897
|
+
plaintext: args.cek
|
|
898
|
+
});
|
|
899
|
+
if (wrap.length !== WRAP_LENGTH) {
|
|
900
|
+
throw new Error(`internal: wrap.length=${wrap.length}, expected ${WRAP_LENGTH}`);
|
|
901
|
+
}
|
|
902
|
+
return { epk, wrap };
|
|
903
|
+
}
|
|
904
|
+
function wrapSlotMlkem768X25519(args) {
|
|
905
|
+
const { enc, ss } = mlkem768x25519Encapsulate({
|
|
906
|
+
publicKey: args.pubR,
|
|
907
|
+
...args.eseed !== void 0 ? { eseed: args.eseed } : {}
|
|
908
|
+
});
|
|
909
|
+
if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
|
|
910
|
+
throw new Error(`internal: enc.length=${enc.length}, expected ${MLKEM768X25519_ENC_LENGTH}`);
|
|
911
|
+
}
|
|
912
|
+
const kek = hkdfSha256({
|
|
913
|
+
ikm: ss,
|
|
914
|
+
salt: EMPTY_SALT2,
|
|
915
|
+
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
916
|
+
length: 32
|
|
917
|
+
});
|
|
918
|
+
const wrap = chacha20Poly1305Encrypt({
|
|
919
|
+
key: kek,
|
|
920
|
+
nonce: ZERO_NONCE_12,
|
|
921
|
+
aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
922
|
+
plaintext: args.cek
|
|
923
|
+
});
|
|
924
|
+
if (wrap.length !== WRAP_LENGTH) {
|
|
925
|
+
throw new Error(`internal: wrap.length=${wrap.length}, expected ${WRAP_LENGTH}`);
|
|
926
|
+
}
|
|
927
|
+
return { kem_ct: chunkKemCt(enc), wrap };
|
|
928
|
+
}
|
|
929
|
+
function eciesSealedPoeWrap(args) {
|
|
930
|
+
const { plaintext, recipientPublicKeys } = args;
|
|
931
|
+
const kem = args.kem ?? "x25519";
|
|
932
|
+
const n = recipientPublicKeys.length;
|
|
933
|
+
if (n < 1) {
|
|
934
|
+
throw new EciesSealedPoeError(
|
|
935
|
+
"ENC_SLOTS_EMPTY",
|
|
936
|
+
`recipientPublicKeys.length=${n} must be >= 1`
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
const expectedPubLen = kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH : MLKEM768X25519_PUBLIC_KEY_LENGTH;
|
|
940
|
+
for (let i = 0; i < n; i++) {
|
|
941
|
+
const pub = recipientPublicKeys[i];
|
|
942
|
+
if (pub === void 0 || pub.length !== expectedPubLen) {
|
|
943
|
+
throw new EciesSealedPoeError(
|
|
944
|
+
"KEM_EPK_LENGTH_MISMATCH",
|
|
945
|
+
`recipientPublicKeys[${i}] MUST be exactly ${expectedPubLen} bytes for kem='${kem}'`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (kem === "x25519") {
|
|
950
|
+
if (args.eseeds !== void 0) {
|
|
951
|
+
throw new EciesSealedPoeError(
|
|
952
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
953
|
+
"eseeds is an X-Wing (mlkem768x25519) override and MUST NOT be supplied for kem='x25519'"
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
if (args.ephemeralSecrets !== void 0 && args.ephemeralSecrets.length !== n) {
|
|
957
|
+
throw new EciesSealedPoeError(
|
|
958
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
959
|
+
`ephemeralSecrets.length=${args.ephemeralSecrets.length} must match recipientPublicKeys.length=${n}`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
} else {
|
|
963
|
+
if (args.ephemeralSecrets !== void 0) {
|
|
964
|
+
throw new EciesSealedPoeError(
|
|
965
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
966
|
+
"ephemeralSecrets is an X25519 override and MUST NOT be supplied for kem='mlkem768x25519'"
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
if (args.eseeds !== void 0) {
|
|
970
|
+
if (args.eseeds.length !== n) {
|
|
971
|
+
throw new EciesSealedPoeError(
|
|
972
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
973
|
+
`eseeds.length=${args.eseeds.length} must match recipientPublicKeys.length=${n}`
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
for (let i = 0; i < n; i++) {
|
|
977
|
+
const eseed = args.eseeds[i];
|
|
978
|
+
if (eseed.length !== MLKEM768X25519_ESEED_LENGTH) {
|
|
979
|
+
throw new EciesSealedPoeError(
|
|
980
|
+
"INVALID_EPHEMERAL_SECRET_LENGTH",
|
|
981
|
+
`eseeds[${i}] MUST be exactly ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${eseed.length}`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const cek = args.cek ?? utils_js.randomBytes(CEK_LENGTH);
|
|
988
|
+
const nonce = args.nonce ?? utils_js.randomBytes(NONCE_LENGTH);
|
|
989
|
+
if (cek.length !== CEK_LENGTH) {
|
|
990
|
+
throw new EciesSealedPoeError(
|
|
991
|
+
"INVALID_CEK_LENGTH",
|
|
992
|
+
`cek MUST be exactly ${CEK_LENGTH} bytes, got ${cek.length}`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
if (nonce.length !== NONCE_LENGTH) {
|
|
996
|
+
throw new EciesSealedPoeError(
|
|
997
|
+
"NONCE_LENGTH_MISMATCH",
|
|
998
|
+
`nonce MUST be exactly ${NONCE_LENGTH} bytes, got ${nonce.length}`
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
let envelope;
|
|
1002
|
+
if (kem === "x25519") {
|
|
1003
|
+
const slots = [];
|
|
1004
|
+
for (let i = 0; i < n; i++) {
|
|
1005
|
+
slots.push(
|
|
1006
|
+
wrapSlotX25519({
|
|
1007
|
+
pubR: recipientPublicKeys[i],
|
|
1008
|
+
privEph: args.ephemeralSecrets ? args.ephemeralSecrets[i] : void 0,
|
|
1009
|
+
cek,
|
|
1010
|
+
slotIdx: i
|
|
1011
|
+
})
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
if (args.skipShuffle !== true) {
|
|
1015
|
+
csprngShuffle(slots);
|
|
1016
|
+
}
|
|
1017
|
+
const slotsMac = computeSlotsMac(cek, slots, "x25519");
|
|
1018
|
+
envelope = {
|
|
1019
|
+
scheme: 1,
|
|
1020
|
+
aead: "xchacha20-poly1305",
|
|
1021
|
+
kem: "x25519",
|
|
1022
|
+
nonce,
|
|
1023
|
+
slots,
|
|
1024
|
+
slots_mac: slotsMac
|
|
1025
|
+
};
|
|
1026
|
+
} else {
|
|
1027
|
+
const slots = [];
|
|
1028
|
+
for (let i = 0; i < n; i++) {
|
|
1029
|
+
slots.push(
|
|
1030
|
+
wrapSlotMlkem768X25519({
|
|
1031
|
+
pubR: recipientPublicKeys[i],
|
|
1032
|
+
eseed: args.eseeds ? args.eseeds[i] : void 0,
|
|
1033
|
+
cek
|
|
1034
|
+
})
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
if (args.skipShuffle !== true) {
|
|
1038
|
+
csprngShuffle(slots);
|
|
1039
|
+
}
|
|
1040
|
+
const slotsMac = computeSlotsMac(cek, slots, "mlkem768x25519");
|
|
1041
|
+
envelope = {
|
|
1042
|
+
scheme: 1,
|
|
1043
|
+
aead: "xchacha20-poly1305",
|
|
1044
|
+
kem: "mlkem768x25519",
|
|
1045
|
+
nonce,
|
|
1046
|
+
slots,
|
|
1047
|
+
slots_mac: slotsMac
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
const adContent = concat(nonce, envelope.slots_mac);
|
|
1051
|
+
const ciphertext = xchacha20Poly1305Encrypt({
|
|
1052
|
+
key: cek,
|
|
1053
|
+
nonce,
|
|
1054
|
+
aad: adContent,
|
|
1055
|
+
plaintext
|
|
1056
|
+
});
|
|
1057
|
+
return { envelope, ciphertext };
|
|
1058
|
+
}
|
|
1059
|
+
function computeSlotsMac(cek, slots, kem) {
|
|
1060
|
+
const hmacKey = hkdfSha256({
|
|
1061
|
+
ikm: cek,
|
|
1062
|
+
salt: EMPTY_SALT2,
|
|
1063
|
+
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1064
|
+
length: 32
|
|
1065
|
+
});
|
|
1066
|
+
const slotsCbor = slotsToMacCbor(slots, kem);
|
|
1067
|
+
const slotsMac = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
|
|
1068
|
+
if (slotsMac.length !== SLOTS_MAC_LENGTH) {
|
|
1069
|
+
throw new Error(`internal: slots_mac.length=${slotsMac.length}, expected ${SLOTS_MAC_LENGTH}`);
|
|
1070
|
+
}
|
|
1071
|
+
return slotsMac;
|
|
1072
|
+
}
|
|
1073
|
+
function selectBundleSecrets(envelope, bundle) {
|
|
1074
|
+
return envelope.kem === "x25519" ? bundle.x25519PrivateKeys : bundle.mlkem768x25519SecretSeeds;
|
|
1075
|
+
}
|
|
1076
|
+
var ZERO_NONCE_122 = new Uint8Array(12);
|
|
1077
|
+
var EMPTY_SALT3 = new Uint8Array(0);
|
|
1078
|
+
var X25519_SECRET_KEY_LENGTH2 = 32;
|
|
1079
|
+
var X25519_PUBLIC_KEY_LENGTH2 = 32;
|
|
1080
|
+
var NONCE_LENGTH2 = 24;
|
|
1081
|
+
var WRAP_LENGTH2 = 48;
|
|
1082
|
+
var SLOTS_MAC_LENGTH2 = 32;
|
|
1083
|
+
function concat2(a, b) {
|
|
1084
|
+
const out = new Uint8Array(a.length + b.length);
|
|
1085
|
+
out.set(a, 0);
|
|
1086
|
+
out.set(b, a.length);
|
|
1087
|
+
return out;
|
|
1088
|
+
}
|
|
1089
|
+
function assertEnvelopeStructure(envelope, multiPrivKeys, singlePrivKey) {
|
|
1090
|
+
if (envelope.scheme !== 1) {
|
|
1091
|
+
throw new EciesSealedPoeError(
|
|
1092
|
+
"UNSUPPORTED_ENC_VERSION",
|
|
1093
|
+
`envelope.scheme=${String(envelope.scheme)} unsupported (expected 1)`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
if (envelope.aead !== "xchacha20-poly1305") {
|
|
1097
|
+
throw new EciesSealedPoeError(
|
|
1098
|
+
"UNSUPPORTED_AEAD_ALG",
|
|
1099
|
+
`envelope.aead=${String(envelope.aead)} unsupported (expected 'xchacha20-poly1305')`
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
if (envelope.kem !== "x25519" && envelope.kem !== "mlkem768x25519") {
|
|
1103
|
+
throw new EciesSealedPoeError(
|
|
1104
|
+
"UNSUPPORTED_KEM_ALG",
|
|
1105
|
+
`envelope.kem=${String(envelope.kem)} unsupported (expected 'x25519' or 'mlkem768x25519')`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
const n = envelope.slots.length;
|
|
1109
|
+
if (n < 1) {
|
|
1110
|
+
throw new EciesSealedPoeError("ENC_SLOTS_EMPTY", `envelope.slots.length=${n} must be >= 1`);
|
|
1111
|
+
}
|
|
1112
|
+
if (envelope.nonce.length !== NONCE_LENGTH2) {
|
|
1113
|
+
throw new EciesSealedPoeError(
|
|
1114
|
+
"NONCE_LENGTH_MISMATCH",
|
|
1115
|
+
`envelope.nonce MUST be exactly ${NONCE_LENGTH2} bytes, got ${envelope.nonce.length}`
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
if (envelope.slots_mac.length !== SLOTS_MAC_LENGTH2) {
|
|
1119
|
+
throw new EciesSealedPoeError(
|
|
1120
|
+
"ENC_SLOTS_MAC_INVALID_LENGTH",
|
|
1121
|
+
`envelope.slots_mac MUST be exactly ${SLOTS_MAC_LENGTH2} bytes, got ${envelope.slots_mac.length}`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
if (envelope.kem === "x25519") {
|
|
1125
|
+
for (let i = 0; i < n; i++) {
|
|
1126
|
+
const slot = envelope.slots[i];
|
|
1127
|
+
if (slot.epk.length !== X25519_PUBLIC_KEY_LENGTH2) {
|
|
1128
|
+
throw new EciesSealedPoeError(
|
|
1129
|
+
"KEM_EPK_LENGTH_MISMATCH",
|
|
1130
|
+
`envelope.slots[${i}].epk MUST be exactly ${X25519_PUBLIC_KEY_LENGTH2} bytes, got ${slot.epk.length}`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
if (slot.wrap.length !== WRAP_LENGTH2) {
|
|
1134
|
+
throw new EciesSealedPoeError(
|
|
1135
|
+
"WRAP_LENGTH_MISMATCH",
|
|
1136
|
+
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
} else {
|
|
1141
|
+
for (let i = 0; i < n; i++) {
|
|
1142
|
+
const slot = envelope.slots[i];
|
|
1143
|
+
const enc = joinKemCt(slot.kem_ct);
|
|
1144
|
+
if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
|
|
1145
|
+
throw new EciesSealedPoeError(
|
|
1146
|
+
"KEM_CT_LENGTH_MISMATCH",
|
|
1147
|
+
`envelope.slots[${i}].kem_ct MUST reassemble to exactly ${MLKEM768X25519_ENC_LENGTH} bytes, got ${enc.length}`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
if (slot.wrap.length !== WRAP_LENGTH2) {
|
|
1151
|
+
throw new EciesSealedPoeError(
|
|
1152
|
+
"WRAP_LENGTH_MISMATCH",
|
|
1153
|
+
`envelope.slots[${i}].wrap MUST be exactly ${WRAP_LENGTH2} bytes, got ${slot.wrap.length}`
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
if (multiPrivKeys !== void 0) {
|
|
1159
|
+
for (let i = 0; i < multiPrivKeys.length; i++) {
|
|
1160
|
+
if (multiPrivKeys[i].length !== X25519_SECRET_KEY_LENGTH2) {
|
|
1161
|
+
throw new EciesSealedPoeError(
|
|
1162
|
+
"INVALID_RECIPIENT_KEY",
|
|
1163
|
+
`recipientSecretKeys[${i}] MUST be exactly ${X25519_SECRET_KEY_LENGTH2} bytes, got ${multiPrivKeys[i].length}`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
} else if (singlePrivKey !== void 0) {
|
|
1168
|
+
if (singlePrivKey.length !== X25519_SECRET_KEY_LENGTH2) {
|
|
1169
|
+
throw new EciesSealedPoeError(
|
|
1170
|
+
"INVALID_RECIPIENT_KEY",
|
|
1171
|
+
`recipientSecretKey MUST be exactly ${X25519_SECRET_KEY_LENGTH2} bytes, got ${singlePrivKey.length}`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function tryX25519Slot(args) {
|
|
1177
|
+
if (args.liveSlot) {
|
|
1178
|
+
try {
|
|
1179
|
+
const shared = x25519Ecdh({
|
|
1180
|
+
secretKey: args.recipientSecretKey,
|
|
1181
|
+
theirPublicKey: args.slot.epk
|
|
1182
|
+
});
|
|
1183
|
+
const kek = hkdfSha256({
|
|
1184
|
+
ikm: shared,
|
|
1185
|
+
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
1186
|
+
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
1187
|
+
length: 32
|
|
1188
|
+
});
|
|
1189
|
+
return chacha20Poly1305Decrypt({
|
|
1190
|
+
key: kek,
|
|
1191
|
+
nonce: ZERO_NONCE_122,
|
|
1192
|
+
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
1193
|
+
ciphertext: args.slot.wrap
|
|
1194
|
+
});
|
|
1195
|
+
} catch (e) {
|
|
1196
|
+
if (!(e instanceof AeadVerificationError) && !(e instanceof X25519LowOrderPointError)) {
|
|
1197
|
+
throw e;
|
|
1198
|
+
}
|
|
1199
|
+
return null;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
try {
|
|
1203
|
+
const shared = x25519Ecdh({
|
|
1204
|
+
secretKey: args.recipientSecretKey,
|
|
1205
|
+
theirPublicKey: args.slot.epk
|
|
1206
|
+
});
|
|
1207
|
+
hkdfSha256({
|
|
1208
|
+
ikm: shared,
|
|
1209
|
+
salt: concat2(args.slot.epk, args.pubRLocal),
|
|
1210
|
+
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
1211
|
+
length: 32
|
|
1212
|
+
});
|
|
1213
|
+
} catch (e) {
|
|
1214
|
+
if (!(e instanceof X25519LowOrderPointError)) throw e;
|
|
1215
|
+
}
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
function tryMlkem768X25519Slot(args) {
|
|
1219
|
+
const enc = joinKemCt(args.slot.kem_ct);
|
|
1220
|
+
const ss = mlkem768x25519Decapsulate({ secretSeed: args.recipientSecretKey, enc });
|
|
1221
|
+
const kek = hkdfSha256({
|
|
1222
|
+
ikm: ss,
|
|
1223
|
+
salt: EMPTY_SALT3,
|
|
1224
|
+
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1225
|
+
length: 32
|
|
1226
|
+
});
|
|
1227
|
+
if (!args.liveSlot) {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
try {
|
|
1231
|
+
return chacha20Poly1305Decrypt({
|
|
1232
|
+
key: kek,
|
|
1233
|
+
nonce: ZERO_NONCE_122,
|
|
1234
|
+
aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1235
|
+
ciphertext: args.slot.wrap
|
|
1236
|
+
});
|
|
1237
|
+
} catch (e) {
|
|
1238
|
+
if (!(e instanceof AeadVerificationError)) throw e;
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
function tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
|
|
1243
|
+
const n = envelope.slots.length;
|
|
1244
|
+
let cek = null;
|
|
1245
|
+
let matchedSlotIdx = -1;
|
|
1246
|
+
if (envelope.kem === "x25519") {
|
|
1247
|
+
const pubRLocal = x25519PublicKey({ secretKey: recipientSecretKey });
|
|
1248
|
+
for (let i = 0; i < n; i++) {
|
|
1249
|
+
if (slotsAttemptedOut !== void 0) {
|
|
1250
|
+
slotsAttemptedOut.count = i + 1;
|
|
1251
|
+
}
|
|
1252
|
+
const candidate = tryX25519Slot({
|
|
1253
|
+
slot: envelope.slots[i],
|
|
1254
|
+
recipientSecretKey,
|
|
1255
|
+
pubRLocal,
|
|
1256
|
+
liveSlot: cek === null
|
|
1257
|
+
});
|
|
1258
|
+
if (cek === null && candidate !== null) {
|
|
1259
|
+
cek = candidate;
|
|
1260
|
+
matchedSlotIdx = i;
|
|
1261
|
+
}
|
|
1262
|
+
if (cek !== null && !constantTimeN) break;
|
|
1263
|
+
}
|
|
1264
|
+
} else {
|
|
1265
|
+
for (let i = 0; i < n; i++) {
|
|
1266
|
+
if (slotsAttemptedOut !== void 0) {
|
|
1267
|
+
slotsAttemptedOut.count = i + 1;
|
|
1268
|
+
}
|
|
1269
|
+
const candidate = tryMlkem768X25519Slot({
|
|
1270
|
+
slot: envelope.slots[i],
|
|
1271
|
+
recipientSecretKey,
|
|
1272
|
+
liveSlot: cek === null
|
|
1273
|
+
});
|
|
1274
|
+
if (cek === null && candidate !== null) {
|
|
1275
|
+
cek = candidate;
|
|
1276
|
+
matchedSlotIdx = i;
|
|
1277
|
+
}
|
|
1278
|
+
if (cek !== null && !constantTimeN) break;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return cek === null ? null : { cek, slotIdx: matchedSlotIdx };
|
|
1282
|
+
}
|
|
1283
|
+
function tryRecipientUnwrap(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut) {
|
|
1284
|
+
return tryRecipientUnwrapWithIdx(envelope, recipientSecretKey, constantTimeN, slotsAttemptedOut)?.cek ?? null;
|
|
1285
|
+
}
|
|
1286
|
+
function slotsMacCborBytes(envelope) {
|
|
1287
|
+
return slotsToMacCbor(
|
|
1288
|
+
envelope.slots,
|
|
1289
|
+
envelope.kem
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
function eciesSealedPoeUnwrap(args) {
|
|
1293
|
+
const { envelope, ciphertext } = args;
|
|
1294
|
+
const constantTimeN = args.constantTimeN ?? true;
|
|
1295
|
+
const hasSingle = "recipientSecretKey" in args;
|
|
1296
|
+
const hasBundle = "recipientKeyBundle" in args;
|
|
1297
|
+
const multiPrivKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : "recipientSecretKeys" in args ? args.recipientSecretKeys : void 0;
|
|
1298
|
+
const hasMulti = multiPrivKeys !== void 0;
|
|
1299
|
+
if (hasSingle === hasMulti) {
|
|
1300
|
+
throw new EciesSealedPoeError(
|
|
1301
|
+
"INVALID_RECIPIENT_KEY",
|
|
1302
|
+
"exactly one of recipientSecretKey / recipientSecretKeys / recipientKeyBundle MUST be supplied"
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
if (hasMulti && multiPrivKeys.length === 0) {
|
|
1306
|
+
if (hasBundle) {
|
|
1307
|
+
return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
|
|
1308
|
+
}
|
|
1309
|
+
throw new EciesSealedPoeError(
|
|
1310
|
+
"INVALID_RECIPIENT_KEY",
|
|
1311
|
+
"recipientSecretKeys MUST be a non-empty array, got length=0"
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
if (hasMulti) {
|
|
1315
|
+
assertEnvelopeStructure(envelope, multiPrivKeys, void 0);
|
|
1316
|
+
} else {
|
|
1317
|
+
assertEnvelopeStructure(envelope, void 0, args.recipientSecretKey);
|
|
1318
|
+
}
|
|
1319
|
+
let matchedCek = null;
|
|
1320
|
+
let anyCandidateRecovered = false;
|
|
1321
|
+
if (hasSingle) {
|
|
1322
|
+
const recipientSecretKey = args.recipientSecretKey;
|
|
1323
|
+
const cek = tryRecipientUnwrap(
|
|
1324
|
+
envelope,
|
|
1325
|
+
recipientSecretKey,
|
|
1326
|
+
constantTimeN,
|
|
1327
|
+
args._slotsAttemptedOut
|
|
1328
|
+
);
|
|
1329
|
+
if (cek === null) {
|
|
1330
|
+
return { matched: false, reason: "WRONG_RECIPIENT_KEY" };
|
|
1331
|
+
}
|
|
1332
|
+
const slotsCbor = slotsMacCborBytes(envelope);
|
|
1333
|
+
const hmacKey = hkdfSha256({
|
|
1334
|
+
ikm: cek,
|
|
1335
|
+
salt: EMPTY_SALT3,
|
|
1336
|
+
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1337
|
+
length: 32
|
|
1338
|
+
});
|
|
1339
|
+
const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
|
|
1340
|
+
if (!compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1341
|
+
return { matched: false, reason: "TAMPERED_HEADER" };
|
|
1342
|
+
}
|
|
1343
|
+
matchedCek = cek;
|
|
1344
|
+
} else {
|
|
1345
|
+
const slotsCbor = slotsMacCborBytes(envelope);
|
|
1346
|
+
const recipientSecretKeys = multiPrivKeys;
|
|
1347
|
+
for (let k = 0; k < recipientSecretKeys.length; k++) {
|
|
1348
|
+
if (args._privsAttemptedOut !== void 0) {
|
|
1349
|
+
args._privsAttemptedOut.count = k + 1;
|
|
1350
|
+
}
|
|
1351
|
+
if (args._slotsAttemptedOut !== void 0) {
|
|
1352
|
+
args._slotsAttemptedOut.count = 0;
|
|
1353
|
+
}
|
|
1354
|
+
const cek = tryRecipientUnwrap(
|
|
1355
|
+
envelope,
|
|
1356
|
+
recipientSecretKeys[k],
|
|
1357
|
+
constantTimeN,
|
|
1358
|
+
args._slotsAttemptedOut
|
|
1359
|
+
);
|
|
1360
|
+
if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
|
|
1361
|
+
args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
|
|
1362
|
+
}
|
|
1363
|
+
if (cek === null) continue;
|
|
1364
|
+
const hmacKey = hkdfSha256({
|
|
1365
|
+
ikm: cek,
|
|
1366
|
+
salt: EMPTY_SALT3,
|
|
1367
|
+
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1368
|
+
length: 32
|
|
1369
|
+
});
|
|
1370
|
+
const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
|
|
1371
|
+
if (compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1372
|
+
matchedCek = cek;
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
anyCandidateRecovered = true;
|
|
1376
|
+
}
|
|
1377
|
+
if (matchedCek === null) {
|
|
1378
|
+
return {
|
|
1379
|
+
matched: false,
|
|
1380
|
+
reason: anyCandidateRecovered ? "TAMPERED_HEADER" : "WRONG_RECIPIENT_KEY"
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
const adContent = concat2(envelope.nonce, envelope.slots_mac);
|
|
1385
|
+
try {
|
|
1386
|
+
const plaintext = xchacha20Poly1305Decrypt({
|
|
1387
|
+
key: matchedCek,
|
|
1388
|
+
nonce: envelope.nonce,
|
|
1389
|
+
aad: adContent,
|
|
1390
|
+
ciphertext
|
|
1391
|
+
});
|
|
1392
|
+
return { matched: true, plaintext };
|
|
1393
|
+
} catch (e) {
|
|
1394
|
+
if (!(e instanceof AeadVerificationError)) throw e;
|
|
1395
|
+
return { matched: false, reason: "TAMPERED_CIPHERTEXT" };
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
function eciesSealedPoeTrialDecrypt(args) {
|
|
1399
|
+
const { envelope } = args;
|
|
1400
|
+
const constantTimeN = args.constantTimeN ?? true;
|
|
1401
|
+
const hasBundle = "recipientKeyBundle" in args;
|
|
1402
|
+
const recipientSecretKeys = hasBundle ? selectBundleSecrets(envelope, args.recipientKeyBundle) : args.recipientSecretKeys;
|
|
1403
|
+
if (recipientSecretKeys.length === 0) {
|
|
1404
|
+
if (hasBundle) {
|
|
1405
|
+
return { kind: "no_aead_pass" };
|
|
1406
|
+
}
|
|
1407
|
+
throw new EciesSealedPoeError(
|
|
1408
|
+
"INVALID_RECIPIENT_KEY",
|
|
1409
|
+
"recipientSecretKeys MUST be a non-empty array, got length=0"
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
assertEnvelopeStructure(envelope, recipientSecretKeys, void 0);
|
|
1413
|
+
const slotsCbor = slotsMacCborBytes(envelope);
|
|
1414
|
+
let anyCandidateRecovered = false;
|
|
1415
|
+
for (let k = 0; k < recipientSecretKeys.length; k++) {
|
|
1416
|
+
if (args._privsAttemptedOut !== void 0) {
|
|
1417
|
+
args._privsAttemptedOut.count = k + 1;
|
|
1418
|
+
}
|
|
1419
|
+
if (args._slotsAttemptedOut !== void 0) {
|
|
1420
|
+
args._slotsAttemptedOut.count = 0;
|
|
1421
|
+
}
|
|
1422
|
+
const candidate = tryRecipientUnwrapWithIdx(
|
|
1423
|
+
envelope,
|
|
1424
|
+
recipientSecretKeys[k],
|
|
1425
|
+
constantTimeN,
|
|
1426
|
+
args._slotsAttemptedOut
|
|
1427
|
+
);
|
|
1428
|
+
if (args._slotsAttemptedOut?.perPrivCounts !== void 0) {
|
|
1429
|
+
args._slotsAttemptedOut.perPrivCounts.push(args._slotsAttemptedOut.count);
|
|
1430
|
+
}
|
|
1431
|
+
if (candidate === null) continue;
|
|
1432
|
+
const hmacKey = hkdfSha256({
|
|
1433
|
+
ikm: candidate.cek,
|
|
1434
|
+
salt: EMPTY_SALT3,
|
|
1435
|
+
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1436
|
+
length: 32
|
|
1437
|
+
});
|
|
1438
|
+
const slotsMacCalc = hmac_js.hmac(sha2_js.sha256, hmacKey, slotsCbor);
|
|
1439
|
+
if (compareCt(slotsMacCalc, envelope.slots_mac)) {
|
|
1440
|
+
return { kind: "match", slotIdx: candidate.slotIdx, cek: candidate.cek };
|
|
1441
|
+
}
|
|
1442
|
+
anyCandidateRecovered = true;
|
|
1443
|
+
}
|
|
1444
|
+
return anyCandidateRecovered ? { kind: "aead_pass_no_mac_match" } : { kind: "no_aead_pass" };
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// src/sealed-poe/envelope-from-parsed.ts
|
|
1448
|
+
function sealedEnvelopeFromParsed(enc) {
|
|
1449
|
+
if (enc.scheme !== 1 || enc.aead !== "xchacha20-poly1305") return null;
|
|
1450
|
+
if (enc.nonce === void 0 || enc.slots_mac === void 0) return null;
|
|
1451
|
+
const slots = enc.slots;
|
|
1452
|
+
if (slots === void 0 || slots.length < 1) return null;
|
|
1453
|
+
if (enc.kem === "x25519") {
|
|
1454
|
+
const x25519Slots = [];
|
|
1455
|
+
for (const s of slots) {
|
|
1456
|
+
if (s.epk === void 0 || s.wrap === void 0) return null;
|
|
1457
|
+
x25519Slots.push({ epk: s.epk, wrap: s.wrap });
|
|
1458
|
+
}
|
|
1459
|
+
return {
|
|
1460
|
+
scheme: 1,
|
|
1461
|
+
aead: "xchacha20-poly1305",
|
|
1462
|
+
kem: "x25519",
|
|
1463
|
+
nonce: enc.nonce,
|
|
1464
|
+
slots: x25519Slots,
|
|
1465
|
+
slots_mac: enc.slots_mac
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
if (enc.kem === "mlkem768x25519") {
|
|
1469
|
+
const hybridSlots = [];
|
|
1470
|
+
for (const s of slots) {
|
|
1471
|
+
if (s.kem_ct === void 0 || s.wrap === void 0) return null;
|
|
1472
|
+
hybridSlots.push({ kem_ct: s.kem_ct, wrap: s.wrap });
|
|
1473
|
+
}
|
|
1474
|
+
return {
|
|
1475
|
+
scheme: 1,
|
|
1476
|
+
aead: "xchacha20-poly1305",
|
|
1477
|
+
kem: "mlkem768x25519",
|
|
1478
|
+
nonce: enc.nonce,
|
|
1479
|
+
slots: hybridSlots,
|
|
1480
|
+
slots_mac: enc.slots_mac
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// src/merkle/leaves-list.ts
|
|
1487
|
+
var LEAVES_LIST_FORMAT_V1 = "cardano-poe-merkle-leaves-v1";
|
|
1488
|
+
var TREE_ALG_RFC9162 = "rfc9162-sha256";
|
|
1489
|
+
var DIGEST_LENGTH2 = 32;
|
|
1490
|
+
var REGISTERED_FORMATS = /* @__PURE__ */ new Set([LEAVES_LIST_FORMAT_V1]);
|
|
1491
|
+
var MerkleLeavesListError = class extends Error {
|
|
1492
|
+
code;
|
|
1493
|
+
constructor(code, message) {
|
|
1494
|
+
super(message ? `${code}: ${message}` : code);
|
|
1495
|
+
this.code = code;
|
|
1496
|
+
this.name = "MerkleLeavesListError";
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
function encodeLeavesList(args) {
|
|
1500
|
+
if (!(args.root instanceof Uint8Array) || args.root.length !== DIGEST_LENGTH2) {
|
|
1501
|
+
throw new MerkleLeavesListError(
|
|
1502
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1503
|
+
`root must be a Uint8Array(${DIGEST_LENGTH2})`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
if (args.leaves.length < 1) {
|
|
1507
|
+
throw new MerkleLeavesListError(
|
|
1508
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1509
|
+
"leaves array must be non-empty"
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const leavesCopy = [];
|
|
1513
|
+
for (let i = 0; i < args.leaves.length; i++) {
|
|
1514
|
+
const leaf = args.leaves[i];
|
|
1515
|
+
if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH2) {
|
|
1516
|
+
throw new MerkleLeavesListError(
|
|
1517
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1518
|
+
`leaves[${i}] must be a Uint8Array(${DIGEST_LENGTH2})`
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
leavesCopy.push(leaf);
|
|
1522
|
+
}
|
|
1523
|
+
if (args.leafAlg !== void 0 && typeof args.leafAlg !== "string") {
|
|
1524
|
+
throw new MerkleLeavesListError(
|
|
1525
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1526
|
+
"leaf_alg must be a string when present"
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
const map = {
|
|
1530
|
+
format: LEAVES_LIST_FORMAT_V1,
|
|
1531
|
+
tree_alg: TREE_ALG_RFC9162,
|
|
1532
|
+
root: args.root,
|
|
1533
|
+
leaves: leavesCopy,
|
|
1534
|
+
leaf_count: leavesCopy.length
|
|
1535
|
+
};
|
|
1536
|
+
if (args.leafAlg !== void 0) {
|
|
1537
|
+
map["leaf_alg"] = args.leafAlg;
|
|
1538
|
+
}
|
|
1539
|
+
return encodeCanonicalCbor(map);
|
|
1540
|
+
}
|
|
1541
|
+
function decodeLeavesList(bytes) {
|
|
1542
|
+
const decoded = decodeCanonicalCbor(bytes);
|
|
1543
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
|
|
1544
|
+
throw new MerkleLeavesListError(
|
|
1545
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1546
|
+
"leaves-list MUST be a CBOR map"
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
const m = decoded;
|
|
1550
|
+
const format = m["format"];
|
|
1551
|
+
if (typeof format !== "string") {
|
|
1552
|
+
throw new MerkleLeavesListError(
|
|
1553
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1554
|
+
"format must be a text string"
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
if (!REGISTERED_FORMATS.has(format)) {
|
|
1558
|
+
throw new MerkleLeavesListError(
|
|
1559
|
+
"SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED",
|
|
1560
|
+
`format '${format}' is not in the registered set`
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
const treeAlg = m["tree_alg"];
|
|
1564
|
+
if (treeAlg !== TREE_ALG_RFC9162) {
|
|
1565
|
+
throw new MerkleLeavesListError(
|
|
1566
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1567
|
+
`tree_alg '${String(treeAlg)}' is not '${TREE_ALG_RFC9162}'`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
const root = m["root"];
|
|
1571
|
+
if (!(root instanceof Uint8Array) || root.length !== DIGEST_LENGTH2) {
|
|
1572
|
+
throw new MerkleLeavesListError(
|
|
1573
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1574
|
+
`root must be a ${DIGEST_LENGTH2}-byte byte string`
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
const leavesRaw = m["leaves"];
|
|
1578
|
+
if (!Array.isArray(leavesRaw) || leavesRaw.length < 1) {
|
|
1579
|
+
throw new MerkleLeavesListError(
|
|
1580
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1581
|
+
"leaves must be a non-empty array"
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
const leaves = [];
|
|
1585
|
+
for (let i = 0; i < leavesRaw.length; i++) {
|
|
1586
|
+
const leaf = leavesRaw[i];
|
|
1587
|
+
if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH2) {
|
|
1588
|
+
throw new MerkleLeavesListError(
|
|
1589
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1590
|
+
`leaves[${i}] must be a ${DIGEST_LENGTH2}-byte byte string`
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
leaves.push(leaf);
|
|
1594
|
+
}
|
|
1595
|
+
const leafCountRaw = m["leaf_count"];
|
|
1596
|
+
let leafCount;
|
|
1597
|
+
if (typeof leafCountRaw === "number" && Number.isInteger(leafCountRaw) && leafCountRaw >= 0) {
|
|
1598
|
+
leafCount = leafCountRaw;
|
|
1599
|
+
} else if (typeof leafCountRaw === "bigint" && leafCountRaw >= 0n) {
|
|
1600
|
+
if (leafCountRaw > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
1601
|
+
throw new MerkleLeavesListError(
|
|
1602
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1603
|
+
"leaf_count exceeds Number.MAX_SAFE_INTEGER"
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
leafCount = Number(leafCountRaw);
|
|
1607
|
+
} else {
|
|
1608
|
+
throw new MerkleLeavesListError(
|
|
1609
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1610
|
+
"leaf_count must be a non-negative CBOR uint"
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
if (leaves.length !== leafCount) {
|
|
1614
|
+
throw new MerkleLeavesListError(
|
|
1615
|
+
"SCHEMA_MERKLE_LEAF_COUNT_MISMATCH",
|
|
1616
|
+
`leaves.length (${leaves.length}) != leaf_count (${leafCount})`
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
let leafAlg;
|
|
1620
|
+
if (m["leaf_alg"] !== void 0) {
|
|
1621
|
+
if (typeof m["leaf_alg"] !== "string") {
|
|
1622
|
+
throw new MerkleLeavesListError(
|
|
1623
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
1624
|
+
"leaf_alg must be a text string when present"
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
leafAlg = m["leaf_alg"];
|
|
1628
|
+
}
|
|
1629
|
+
const recomputed = merkleSha2256Root(leaves);
|
|
1630
|
+
if (!compareCt(recomputed, root)) {
|
|
1631
|
+
throw new MerkleLeavesListError(
|
|
1632
|
+
"MERKLE_ROOT_MISMATCH",
|
|
1633
|
+
"leaves recompute does not match declared root"
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
const out = {
|
|
1637
|
+
format: LEAVES_LIST_FORMAT_V1,
|
|
1638
|
+
treeAlg: TREE_ALG_RFC9162,
|
|
1639
|
+
root,
|
|
1640
|
+
leaves,
|
|
1641
|
+
leafCount,
|
|
1642
|
+
...leafAlg !== void 0 ? { leafAlg } : {}
|
|
1643
|
+
};
|
|
1644
|
+
return out;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/recipient/bech32.ts
|
|
1648
|
+
var BECH32_ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
1649
|
+
var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
1650
|
+
var ENCODING_CONST = 1;
|
|
1651
|
+
function polymodStep(pre) {
|
|
1652
|
+
const b = pre >> 25;
|
|
1653
|
+
let chk = (pre & 33554431) << 5;
|
|
1654
|
+
for (let i = 0; i < POLYMOD_GENERATORS.length; i++) {
|
|
1655
|
+
if ((b >> i & 1) === 1) chk ^= POLYMOD_GENERATORS[i];
|
|
1656
|
+
}
|
|
1657
|
+
return chk;
|
|
1658
|
+
}
|
|
1659
|
+
function bytesToWords(bytes) {
|
|
1660
|
+
const words = [];
|
|
1661
|
+
let carry = 0;
|
|
1662
|
+
let pos = 0;
|
|
1663
|
+
const mask = (1 << 5) - 1;
|
|
1664
|
+
for (const n of bytes) {
|
|
1665
|
+
carry = carry << 8 | n;
|
|
1666
|
+
pos += 8;
|
|
1667
|
+
for (; pos >= 5; pos -= 5) words.push(carry >> pos - 5 & mask);
|
|
1668
|
+
carry &= (1 << pos) - 1;
|
|
1669
|
+
}
|
|
1670
|
+
if (pos > 0) words.push(carry << 5 - pos & mask);
|
|
1671
|
+
return words;
|
|
1672
|
+
}
|
|
1673
|
+
function checksum(prefix, words) {
|
|
1674
|
+
let chk = 1;
|
|
1675
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
1676
|
+
const c = prefix.charCodeAt(i);
|
|
1677
|
+
if (c < 33 || c > 126) throw new Error(`bech32: invalid prefix (${prefix})`);
|
|
1678
|
+
chk = polymodStep(chk) ^ c >> 5;
|
|
1679
|
+
}
|
|
1680
|
+
chk = polymodStep(chk);
|
|
1681
|
+
for (let i = 0; i < prefix.length; i++) chk = polymodStep(chk) ^ prefix.charCodeAt(i) & 31;
|
|
1682
|
+
for (const v of words) chk = polymodStep(chk) ^ v;
|
|
1683
|
+
for (let i = 0; i < 6; i++) chk = polymodStep(chk);
|
|
1684
|
+
chk ^= ENCODING_CONST;
|
|
1685
|
+
let out = "";
|
|
1686
|
+
for (let i = 0; i < 6; i++) out += BECH32_ALPHABET[chk >> 5 * (5 - i) & 31];
|
|
1687
|
+
return out;
|
|
1688
|
+
}
|
|
1689
|
+
function bech32EncodeNoLimit(prefix, bytes) {
|
|
1690
|
+
if (prefix.length === 0) throw new Error("bech32: empty prefix");
|
|
1691
|
+
const words = bytesToWords(bytes);
|
|
1692
|
+
let payload = "";
|
|
1693
|
+
for (const w of words) payload += BECH32_ALPHABET[w];
|
|
1694
|
+
const lowered = prefix.toLowerCase();
|
|
1695
|
+
return `${lowered}1${payload}${checksum(lowered, words)}`;
|
|
1696
|
+
}
|
|
1697
|
+
function checksumValid(prefix, words) {
|
|
1698
|
+
let chk = 1;
|
|
1699
|
+
for (let i = 0; i < prefix.length; i++) chk = polymodStep(chk) ^ prefix.charCodeAt(i) >> 5;
|
|
1700
|
+
chk = polymodStep(chk);
|
|
1701
|
+
for (let i = 0; i < prefix.length; i++) chk = polymodStep(chk) ^ prefix.charCodeAt(i) & 31;
|
|
1702
|
+
for (const v of words) chk = polymodStep(chk) ^ v;
|
|
1703
|
+
return chk === ENCODING_CONST;
|
|
1704
|
+
}
|
|
1705
|
+
function wordsToBytes(words) {
|
|
1706
|
+
const out = [];
|
|
1707
|
+
let carry = 0;
|
|
1708
|
+
let pos = 0;
|
|
1709
|
+
for (const w of words) {
|
|
1710
|
+
carry = carry << 5 | w;
|
|
1711
|
+
pos += 5;
|
|
1712
|
+
for (; pos >= 8; pos -= 8) out.push(carry >> pos - 8 & 255);
|
|
1713
|
+
carry &= (1 << pos) - 1;
|
|
1714
|
+
}
|
|
1715
|
+
if (pos >= 5 || carry !== 0) throw new Error("bech32: non-canonical padding");
|
|
1716
|
+
return Uint8Array.from(out);
|
|
1717
|
+
}
|
|
1718
|
+
function bech32DecodeNoLimit(input) {
|
|
1719
|
+
if (input.length === 0) throw new Error("bech32: empty string");
|
|
1720
|
+
const hasLower = input !== input.toUpperCase();
|
|
1721
|
+
const hasUpper = input !== input.toLowerCase();
|
|
1722
|
+
if (hasLower && hasUpper) throw new Error("bech32: mixed-case string");
|
|
1723
|
+
const s = input.toLowerCase();
|
|
1724
|
+
const sep = s.lastIndexOf("1");
|
|
1725
|
+
if (sep < 1) throw new Error("bech32: missing human-readable prefix");
|
|
1726
|
+
if (s.length - sep - 1 < 6) throw new Error("bech32: data too short for checksum");
|
|
1727
|
+
const hrp = s.slice(0, sep);
|
|
1728
|
+
for (let i = 0; i < hrp.length; i++) {
|
|
1729
|
+
const c = hrp.charCodeAt(i);
|
|
1730
|
+
if (c < 33 || c > 126) throw new Error("bech32: invalid prefix character");
|
|
1731
|
+
}
|
|
1732
|
+
const words = [];
|
|
1733
|
+
for (let i = sep + 1; i < s.length; i++) {
|
|
1734
|
+
const v = BECH32_ALPHABET.indexOf(s[i]);
|
|
1735
|
+
if (v === -1) throw new Error("bech32: invalid data character");
|
|
1736
|
+
words.push(v);
|
|
1737
|
+
}
|
|
1738
|
+
if (!checksumValid(hrp, words)) throw new Error("bech32: bad checksum");
|
|
1739
|
+
return { hrp, bytes: wordsToBytes(words.slice(0, words.length - 6)) };
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/recipient/age-recipient.ts
|
|
1743
|
+
var X25519_HRP = "age";
|
|
1744
|
+
var XWING_HRP = "age1pqc";
|
|
1745
|
+
var X25519_PUBLIC_KEY_BYTES = 32;
|
|
1746
|
+
var XWING_PUBLIC_KEY_BYTES = 1216;
|
|
1747
|
+
function encodeAgeX25519Recipient(publicKey) {
|
|
1748
|
+
if (publicKey.length !== X25519_PUBLIC_KEY_BYTES) {
|
|
1749
|
+
throw new Error("encodeAgeX25519Recipient: publicKey must be exactly 32 bytes");
|
|
1750
|
+
}
|
|
1751
|
+
return bech32EncodeNoLimit(X25519_HRP, publicKey);
|
|
1752
|
+
}
|
|
1753
|
+
function encodeAgeXWingRecipient(publicKey) {
|
|
1754
|
+
if (publicKey.length !== XWING_PUBLIC_KEY_BYTES) {
|
|
1755
|
+
throw new Error("encodeAgeXWingRecipient: publicKey must be exactly 1216 bytes");
|
|
1756
|
+
}
|
|
1757
|
+
return bech32EncodeNoLimit(XWING_HRP, publicKey);
|
|
1758
|
+
}
|
|
1759
|
+
function parseAgeRecipient(recipient) {
|
|
1760
|
+
if (typeof recipient !== "string") {
|
|
1761
|
+
throw new Error("parseAgeRecipient: recipient must be a string");
|
|
1762
|
+
}
|
|
1763
|
+
const { hrp, bytes } = bech32DecodeNoLimit(recipient.trim());
|
|
1764
|
+
if (hrp === X25519_HRP) {
|
|
1765
|
+
if (bytes.length !== X25519_PUBLIC_KEY_BYTES) {
|
|
1766
|
+
throw new Error("parseAgeRecipient: age recipient must carry a 32-byte X25519 key");
|
|
1767
|
+
}
|
|
1768
|
+
return { kem: "x25519", publicKey: bytes };
|
|
1769
|
+
}
|
|
1770
|
+
if (hrp === XWING_HRP) {
|
|
1771
|
+
if (bytes.length !== XWING_PUBLIC_KEY_BYTES) {
|
|
1772
|
+
throw new Error("parseAgeRecipient: age1pqc recipient must carry a 1216-byte X-Wing key");
|
|
1773
|
+
}
|
|
1774
|
+
return { kem: "mlkem768x25519", publicKey: bytes };
|
|
1775
|
+
}
|
|
1776
|
+
throw new Error(`parseAgeRecipient: unrecognized recipient prefix "${hrp}"`);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
exports.AeadVerificationError = AeadVerificationError;
|
|
1780
|
+
exports.CARDANO_POE_HKDF_INFO_KEK = CARDANO_POE_HKDF_INFO_KEK;
|
|
1781
|
+
exports.CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519;
|
|
1782
|
+
exports.CARDANO_POE_HKDF_INFO_SLOTS_MAC = CARDANO_POE_HKDF_INFO_SLOTS_MAC;
|
|
1783
|
+
exports.CARDANO_POE_SIG_DOMAIN_PREFIX = CARDANO_POE_SIG_DOMAIN_PREFIX;
|
|
1784
|
+
exports.CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES = CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES;
|
|
1785
|
+
exports.CanonicalCborError = CanonicalCborError;
|
|
1786
|
+
exports.CoseSign1BuildError = CoseSign1BuildError;
|
|
1787
|
+
exports.CoseVerifyError = CoseVerifyError;
|
|
1788
|
+
exports.EciesSealedPoeError = EciesSealedPoeError;
|
|
1789
|
+
exports.INFO_ED25519 = INFO_ED25519;
|
|
1790
|
+
exports.INFO_MLKEM768X25519 = INFO_MLKEM768X25519;
|
|
1791
|
+
exports.INFO_X25519 = INFO_X25519;
|
|
1792
|
+
exports.LEAVES_LIST_FORMAT_V1 = LEAVES_LIST_FORMAT_V1;
|
|
1793
|
+
exports.MERKLE_ALG_ID = MERKLE_ALG_ID;
|
|
1794
|
+
exports.MLKEM768X25519_ENC_LENGTH = MLKEM768X25519_ENC_LENGTH;
|
|
1795
|
+
exports.MLKEM768X25519_ESEED_LENGTH = MLKEM768X25519_ESEED_LENGTH;
|
|
1796
|
+
exports.MLKEM768X25519_PUBLIC_KEY_LENGTH = MLKEM768X25519_PUBLIC_KEY_LENGTH;
|
|
1797
|
+
exports.MLKEM768X25519_SEED_LENGTH = MLKEM768X25519_SEED_LENGTH;
|
|
1798
|
+
exports.MLKEM768X25519_SHARED_SECRET_LENGTH = MLKEM768X25519_SHARED_SECRET_LENGTH;
|
|
1799
|
+
exports.MerkleLeavesListError = MerkleLeavesListError;
|
|
1800
|
+
exports.SeedDeriveError = SeedDeriveError;
|
|
1801
|
+
exports.X25519LowOrderPointError = X25519LowOrderPointError;
|
|
1802
|
+
exports.argon2idV13 = argon2idV13;
|
|
1803
|
+
exports.bech32DecodeNoLimit = bech32DecodeNoLimit;
|
|
1804
|
+
exports.bech32EncodeNoLimit = bech32EncodeNoLimit;
|
|
1805
|
+
exports.blake2b224 = blake2b224;
|
|
1806
|
+
exports.blake2b256 = blake2b256;
|
|
1807
|
+
exports.buildCip309SigStructure = buildCip309SigStructure;
|
|
1808
|
+
exports.buildSigStructure = buildSigStructure;
|
|
1809
|
+
exports.chacha20Poly1305Decrypt = chacha20Poly1305Decrypt;
|
|
1810
|
+
exports.chacha20Poly1305Encrypt = chacha20Poly1305Encrypt;
|
|
1811
|
+
exports.chunkKemCt = chunkKemCt;
|
|
1812
|
+
exports.compareCt = compareCt;
|
|
1813
|
+
exports.coseSign1Cip309Build = coseSign1Cip309Build;
|
|
1814
|
+
exports.coseSign1Cip309Verify = coseSign1Cip309Verify;
|
|
1815
|
+
exports.decodeCanonicalCbor = decodeCanonicalCbor;
|
|
1816
|
+
exports.decodeCbor = decodeCbor;
|
|
1817
|
+
exports.decodeCoseSign1 = decodeCoseSign1;
|
|
1818
|
+
exports.decodeLeavesList = decodeLeavesList;
|
|
1819
|
+
exports.deriveEd25519KeypairFromSeed = deriveEd25519KeypairFromSeed;
|
|
1820
|
+
exports.deriveMlKem768X25519KeypairFromSeed = deriveMlKem768X25519KeypairFromSeed;
|
|
1821
|
+
exports.deriveX25519KeypairFromSeed = deriveX25519KeypairFromSeed;
|
|
1822
|
+
exports.dualHash = dualHash;
|
|
1823
|
+
exports.dualHashStream = dualHashStream;
|
|
1824
|
+
exports.eciesSealedPoeTrialDecrypt = eciesSealedPoeTrialDecrypt;
|
|
1825
|
+
exports.eciesSealedPoeUnwrap = eciesSealedPoeUnwrap;
|
|
1826
|
+
exports.eciesSealedPoeWrap = eciesSealedPoeWrap;
|
|
1827
|
+
exports.encodeAgeX25519Recipient = encodeAgeX25519Recipient;
|
|
1828
|
+
exports.encodeAgeXWingRecipient = encodeAgeXWingRecipient;
|
|
1829
|
+
exports.encodeCanonicalCbor = encodeCanonicalCbor;
|
|
1830
|
+
exports.encodeCoseSign1 = encodeCoseSign1;
|
|
1831
|
+
exports.encodeLeavesList = encodeLeavesList;
|
|
1832
|
+
exports.getPublicKeyEd25519 = getPublicKeyEd25519;
|
|
1833
|
+
exports.hexToBytes = hexToBytes;
|
|
1834
|
+
exports.hkdfSha256 = hkdfSha256;
|
|
1835
|
+
exports.joinKemCt = joinKemCt;
|
|
1836
|
+
exports.merkleSha2256InclusionProof = merkleSha2256InclusionProof;
|
|
1837
|
+
exports.merkleSha2256Root = merkleSha2256Root;
|
|
1838
|
+
exports.merkleSha2256VerifyInclusion = merkleSha2256VerifyInclusion;
|
|
1839
|
+
exports.mlkem768x25519Decapsulate = mlkem768x25519Decapsulate;
|
|
1840
|
+
exports.mlkem768x25519Encapsulate = mlkem768x25519Encapsulate;
|
|
1841
|
+
exports.mlkem768x25519Keygen = mlkem768x25519Keygen;
|
|
1842
|
+
exports.parseAgeRecipient = parseAgeRecipient;
|
|
1843
|
+
exports.parseCoseKeyEd25519 = parseCoseKeyEd25519;
|
|
1844
|
+
exports.sealedEnvelopeFromParsed = sealedEnvelopeFromParsed;
|
|
1845
|
+
exports.sha256 = sha256;
|
|
1846
|
+
exports.signEd25519 = signEd25519;
|
|
1847
|
+
exports.slotsToMacCbor = slotsToMacCbor;
|
|
1848
|
+
exports.uniformIndexBelow = uniformIndexBelow;
|
|
1849
|
+
exports.verifyEd25519 = verifyEd25519;
|
|
1850
|
+
exports.x25519Ecdh = x25519Ecdh;
|
|
1851
|
+
exports.x25519Keygen = x25519Keygen;
|
|
1852
|
+
exports.x25519PublicKey = x25519PublicKey;
|
|
1853
|
+
exports.xchacha20Poly1305Decrypt = xchacha20Poly1305Decrypt;
|
|
1854
|
+
exports.xchacha20Poly1305Encrypt = xchacha20Poly1305Encrypt;
|
|
1855
|
+
//# sourceMappingURL=index.cjs.map
|
|
1856
|
+
//# sourceMappingURL=index.cjs.map
|