@cardanowall/sdk-ts 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 +207 -0
- package/dist/client/index.cjs +2695 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +397 -0
- package/dist/client/index.d.ts +397 -0
- package/dist/client/index.js +2641 -0
- package/dist/client/index.js.map +1 -0
- package/dist/conformance/cli.cjs +4901 -0
- package/dist/conformance/cli.cjs.map +1 -0
- package/dist/conformance/cli.d.cts +18 -0
- package/dist/conformance/cli.d.ts +18 -0
- package/dist/conformance/cli.js +4878 -0
- package/dist/conformance/cli.js.map +1 -0
- package/dist/fetch/index.cjs +335 -0
- package/dist/fetch/index.cjs.map +1 -0
- package/dist/fetch/index.d.cts +13 -0
- package/dist/fetch/index.d.ts +13 -0
- package/dist/fetch/index.js +323 -0
- package/dist/fetch/index.js.map +1 -0
- package/dist/fetch-outbound-BT5-NiYN.d.cts +76 -0
- package/dist/fetch-outbound-BT5-NiYN.d.ts +76 -0
- package/dist/hash/index.cjs +25 -0
- package/dist/hash/index.cjs.map +1 -0
- package/dist/hash/index.d.cts +1 -0
- package/dist/hash/index.d.ts +1 -0
- package/dist/hash/index.js +21 -0
- package/dist/hash/index.js.map +1 -0
- package/dist/identity/index.cjs +1388 -0
- package/dist/identity/index.cjs.map +1 -0
- package/dist/identity/index.d.cts +27 -0
- package/dist/identity/index.d.ts +27 -0
- package/dist/identity/index.js +1362 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/ids/index.cjs +146 -0
- package/dist/ids/index.cjs.map +1 -0
- package/dist/ids/index.d.cts +55 -0
- package/dist/ids/index.d.ts +55 -0
- package/dist/ids/index.js +135 -0
- package/dist/ids/index.js.map +1 -0
- package/dist/index-BhnlWJAY.d.cts +10 -0
- package/dist/index-BhnlWJAY.d.ts +10 -0
- package/dist/index-Cg1QqVmA.d.cts +19 -0
- package/dist/index-Cg1QqVmA.d.ts +19 -0
- package/dist/index.cjs +7127 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +7004 -0
- package/dist/index.js.map +1 -0
- package/dist/merkle/index.cjs +396 -0
- package/dist/merkle/index.cjs.map +1 -0
- package/dist/merkle/index.d.cts +2 -0
- package/dist/merkle/index.d.ts +2 -0
- package/dist/merkle/index.js +387 -0
- package/dist/merkle/index.js.map +1 -0
- package/dist/types-B8Q3gW54.d.ts +123 -0
- package/dist/types-BQMtbRCb.d.cts +321 -0
- package/dist/types-BQMtbRCb.d.ts +321 -0
- package/dist/types-CLXdbjqr.d.cts +123 -0
- package/dist/verifier/index.cjs +4901 -0
- package/dist/verifier/index.cjs.map +1 -0
- package/dist/verifier/index.d.cts +176 -0
- package/dist/verifier/index.d.ts +176 -0
- package/dist/verifier/index.js +4848 -0
- package/dist/verifier/index.js.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1,2641 @@
|
|
|
1
|
+
import { encode } from 'cbor2';
|
|
2
|
+
import { sortCoreDeterministic } from 'cbor2/sorts';
|
|
3
|
+
import { blake2b } from '@noble/hashes/blake2.js';
|
|
4
|
+
import * as ed from '@noble/ed25519';
|
|
5
|
+
import { sha512, sha256 as sha256$1 } from '@noble/hashes/sha2.js';
|
|
6
|
+
import 'hash-wasm';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { randomBytes as randomBytes$1 } from '@noble/ciphers/utils.js';
|
|
9
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
|
10
|
+
import { xchacha20poly1305, chacha20poly1305 } from '@noble/ciphers/chacha.js';
|
|
11
|
+
import { hkdf } from '@noble/hashes/hkdf.js';
|
|
12
|
+
import '@noble/curves/abstract/edwards.js';
|
|
13
|
+
import '@noble/curves/abstract/montgomery.js';
|
|
14
|
+
import '@noble/curves/abstract/weierstrass.js';
|
|
15
|
+
import { x25519 } from '@noble/curves/ed25519.js';
|
|
16
|
+
import '@noble/curves/nist.js';
|
|
17
|
+
import { concatBytes, asciiToBytes, bytesToNumberLE, bytesToNumberBE } from '@noble/curves/utils.js';
|
|
18
|
+
import { sha3_256, shake256, sha3_512, shake128 } from '@noble/hashes/sha3.js';
|
|
19
|
+
import { anumber, abytes, randomBytes as randomBytes$2, u32, swap32IfBE } from '@noble/hashes/utils.js';
|
|
20
|
+
import { FFTCore, reverseBits } from '@noble/curves/abstract/fft.js';
|
|
21
|
+
|
|
22
|
+
// ../crypto-core/dist/cbor.js
|
|
23
|
+
function encodeCanonicalCbor(value) {
|
|
24
|
+
return encode(value, {
|
|
25
|
+
cde: true,
|
|
26
|
+
collapseBigInts: true,
|
|
27
|
+
rejectDuplicateKeys: true,
|
|
28
|
+
sortKeys: sortCoreDeterministic
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function encodeCanonicalCbor2(value) {
|
|
32
|
+
return encode(value, {
|
|
33
|
+
cde: true,
|
|
34
|
+
collapseBigInts: true,
|
|
35
|
+
rejectDuplicateKeys: true,
|
|
36
|
+
sortKeys: sortCoreDeterministic
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
ed.hashes.sha512 = sha512;
|
|
40
|
+
ed.Point.CURVE().n;
|
|
41
|
+
var CARDANO_POE_SIG_DOMAIN_PREFIX = "cardano-poe-record-sig-v1";
|
|
42
|
+
var CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES = new TextEncoder().encode(
|
|
43
|
+
CARDANO_POE_SIG_DOMAIN_PREFIX
|
|
44
|
+
);
|
|
45
|
+
if (CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length !== 25) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`cardano-poe-record-sig-v1 prefix must encode to exactly 25 UTF-8 bytes, got ${CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
var EMPTY_BYTES = new Uint8Array(0);
|
|
51
|
+
function buildSigStructure(args) {
|
|
52
|
+
return encodeCanonicalCbor2([
|
|
53
|
+
args.context,
|
|
54
|
+
args.bodyProtectedBytes,
|
|
55
|
+
args.externalAad,
|
|
56
|
+
args.payload
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
function buildCip309SigStructure(args) {
|
|
60
|
+
const toSign = new Uint8Array(
|
|
61
|
+
CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length + args.recordBodyCbor.length
|
|
62
|
+
);
|
|
63
|
+
toSign.set(CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES, 0);
|
|
64
|
+
toSign.set(args.recordBodyCbor, CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length);
|
|
65
|
+
return buildSigStructure({
|
|
66
|
+
context: "Signature1",
|
|
67
|
+
bodyProtectedBytes: args.bodyProtectedBytes,
|
|
68
|
+
externalAad: EMPTY_BYTES,
|
|
69
|
+
payload: toSign
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function encodeCoseSign1(args) {
|
|
73
|
+
const protectedBytes = args.protectedHeader.size === 0 ? EMPTY_BYTES : encodeCanonicalCbor2(args.protectedHeader);
|
|
74
|
+
return encodeCanonicalCbor2([
|
|
75
|
+
protectedBytes,
|
|
76
|
+
args.unprotectedHeader,
|
|
77
|
+
args.payload,
|
|
78
|
+
args.signature
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
function sha256(input) {
|
|
82
|
+
return sha256$1(input);
|
|
83
|
+
}
|
|
84
|
+
function blake2b256(input) {
|
|
85
|
+
return blake2b(input, { dkLen: 32 });
|
|
86
|
+
}
|
|
87
|
+
function blake2b224(input) {
|
|
88
|
+
return blake2b(input, { dkLen: 28 });
|
|
89
|
+
}
|
|
90
|
+
var MERKLE_ALG_ID = "rfc9162-sha256";
|
|
91
|
+
var LEAF_PREFIX = 0;
|
|
92
|
+
var NODE_PREFIX = 1;
|
|
93
|
+
var DIGEST_LENGTH = 32;
|
|
94
|
+
function validateLeaves(leaves, fnName) {
|
|
95
|
+
if (leaves.length === 0) {
|
|
96
|
+
throw new Error(`${fnName}: empty leaf list (n == 0 is forbidden by RFC 9162 \xA72.1.1)`);
|
|
97
|
+
}
|
|
98
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
99
|
+
const leaf = leaves[i];
|
|
100
|
+
if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`${fnName}: leaf[${i}] must be a Uint8Array(${DIGEST_LENGTH}); got length ${leaf instanceof Uint8Array ? leaf.length : "non-Uint8Array"}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function merkleSha2256Root(leaves) {
|
|
108
|
+
validateLeaves(leaves, "merkleSha2256Root");
|
|
109
|
+
return mthRecursive(leaves, 0, leaves.length);
|
|
110
|
+
}
|
|
111
|
+
function largestPow2Lt(n) {
|
|
112
|
+
let k = 1;
|
|
113
|
+
while (k * 2 < n) k *= 2;
|
|
114
|
+
return k;
|
|
115
|
+
}
|
|
116
|
+
function hashLeaf(d) {
|
|
117
|
+
const buf = new Uint8Array(1 + d.length);
|
|
118
|
+
buf[0] = LEAF_PREFIX;
|
|
119
|
+
buf.set(d, 1);
|
|
120
|
+
return sha256$1(buf);
|
|
121
|
+
}
|
|
122
|
+
function hashNode(left, right) {
|
|
123
|
+
const buf = new Uint8Array(1 + left.length + right.length);
|
|
124
|
+
buf[0] = NODE_PREFIX;
|
|
125
|
+
buf.set(left, 1);
|
|
126
|
+
buf.set(right, 1 + left.length);
|
|
127
|
+
return sha256$1(buf);
|
|
128
|
+
}
|
|
129
|
+
function mthRecursive(leaves, start, end) {
|
|
130
|
+
const n = end - start;
|
|
131
|
+
if (n === 1) {
|
|
132
|
+
return hashLeaf(leaves[start]);
|
|
133
|
+
}
|
|
134
|
+
const k = largestPow2Lt(n);
|
|
135
|
+
const left = mthRecursive(leaves, start, start + k);
|
|
136
|
+
const right = mthRecursive(leaves, start + k, end);
|
|
137
|
+
return hashNode(left, right);
|
|
138
|
+
}
|
|
139
|
+
var ChunkedBytesArraySchema = z.array(
|
|
140
|
+
z.instanceof(Uint8Array).refine((b) => b.length >= 1 && b.length <= 64, {
|
|
141
|
+
params: { code: "CHUNK_TOO_LARGE" }
|
|
142
|
+
})
|
|
143
|
+
).min(1);
|
|
144
|
+
var UTF8_ENCODER = new TextEncoder();
|
|
145
|
+
var UriChunkArraySchema = z.array(
|
|
146
|
+
z.string().refine(
|
|
147
|
+
(s) => {
|
|
148
|
+
const n = UTF8_ENCODER.encode(s).length;
|
|
149
|
+
return n >= 1 && n <= 64;
|
|
150
|
+
},
|
|
151
|
+
{ params: { code: "CHUNK_TOO_LARGE" } }
|
|
152
|
+
)
|
|
153
|
+
).min(1);
|
|
154
|
+
var HashDigestSchema = z.instanceof(Uint8Array);
|
|
155
|
+
var HashesMapSchema = z.record(z.string(), HashDigestSchema);
|
|
156
|
+
var MerkleCommitSchema = z.object({
|
|
157
|
+
alg: z.string(),
|
|
158
|
+
root: z.instanceof(Uint8Array),
|
|
159
|
+
leaf_count: z.number().int().min(1),
|
|
160
|
+
uris: z.array(UriChunkArraySchema).min(1).optional()
|
|
161
|
+
}).strict();
|
|
162
|
+
var SlotSchema = z.object({
|
|
163
|
+
epk: z.instanceof(Uint8Array).optional(),
|
|
164
|
+
kem_ct: ChunkedBytesArraySchema.optional(),
|
|
165
|
+
wrap: z.instanceof(Uint8Array).optional()
|
|
166
|
+
});
|
|
167
|
+
z.object({
|
|
168
|
+
m: z.number().int(),
|
|
169
|
+
t: z.number().int(),
|
|
170
|
+
p: z.number().int()
|
|
171
|
+
}).strict();
|
|
172
|
+
var PassphraseBlockSchema = z.object({
|
|
173
|
+
alg: z.string(),
|
|
174
|
+
salt: z.instanceof(Uint8Array).superRefine((bytes, ctx) => {
|
|
175
|
+
if (bytes.length < 16) {
|
|
176
|
+
ctx.addIssue({
|
|
177
|
+
code: "custom",
|
|
178
|
+
path: [],
|
|
179
|
+
message: `passphrase.salt length ${bytes.length} < 16`,
|
|
180
|
+
params: { code: "ENC_PASSPHRASE_SALT_TOO_SHORT" }
|
|
181
|
+
});
|
|
182
|
+
} else if (bytes.length > 64) {
|
|
183
|
+
ctx.addIssue({
|
|
184
|
+
code: "custom",
|
|
185
|
+
path: [],
|
|
186
|
+
message: `passphrase.salt length ${bytes.length} > 64`,
|
|
187
|
+
params: { code: "ENC_PASSPHRASE_SALT_TOO_LONG" }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}),
|
|
191
|
+
params: z.record(z.string(), z.unknown())
|
|
192
|
+
}).strict();
|
|
193
|
+
z.object({
|
|
194
|
+
scheme: z.unknown(),
|
|
195
|
+
aead: z.string(),
|
|
196
|
+
kem: z.string().optional(),
|
|
197
|
+
nonce: z.instanceof(Uint8Array),
|
|
198
|
+
slots: z.array(SlotSchema).optional(),
|
|
199
|
+
slots_mac: z.instanceof(Uint8Array).refine((b) => b.length === 32, {
|
|
200
|
+
params: { code: "ENC_SLOTS_MAC_INVALID_LENGTH" }
|
|
201
|
+
}).optional(),
|
|
202
|
+
passphrase: PassphraseBlockSchema.optional()
|
|
203
|
+
}).strict();
|
|
204
|
+
var ItemEntrySchema = z.object({
|
|
205
|
+
hashes: HashesMapSchema,
|
|
206
|
+
uris: z.array(UriChunkArraySchema).min(1).optional(),
|
|
207
|
+
// Captured as `unknown` so the validator can run the
|
|
208
|
+
// `ENC_REQUIRES_CONTENT_HASH` pre-check ahead of any inner-shape errors
|
|
209
|
+
// and surface the most informative code first.
|
|
210
|
+
enc: z.unknown().optional()
|
|
211
|
+
}).strict();
|
|
212
|
+
var SigEntrySchema = z.object({
|
|
213
|
+
cose_key: ChunkedBytesArraySchema.optional(),
|
|
214
|
+
cose_sign1: ChunkedBytesArraySchema
|
|
215
|
+
}).strict();
|
|
216
|
+
var SupersedesSchema = z.instanceof(Uint8Array).refine((b) => b.length === 32, {
|
|
217
|
+
params: { code: "SUPERSEDES_TX_INVALID_LENGTH" }
|
|
218
|
+
});
|
|
219
|
+
var VersionLiteralSchema = z.literal(1);
|
|
220
|
+
z.looseObject({
|
|
221
|
+
v: VersionLiteralSchema,
|
|
222
|
+
items: z.array(ItemEntrySchema).optional(),
|
|
223
|
+
merkle: z.array(MerkleCommitSchema).optional(),
|
|
224
|
+
supersedes: SupersedesSchema.optional(),
|
|
225
|
+
sigs: z.array(SigEntrySchema).optional(),
|
|
226
|
+
crit: z.array(z.string()).optional()
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ../poe-standard/src/encoder.ts
|
|
230
|
+
function encodePoeRecord(record) {
|
|
231
|
+
return encodeCanonicalCbor(recordToCbor(record));
|
|
232
|
+
}
|
|
233
|
+
function encodeRecordBodyForSigning(record) {
|
|
234
|
+
const body = recordToCborInternal(
|
|
235
|
+
record,
|
|
236
|
+
/* includeSigs */
|
|
237
|
+
false
|
|
238
|
+
);
|
|
239
|
+
return encodeCanonicalCbor(body);
|
|
240
|
+
}
|
|
241
|
+
function recordToCbor(record) {
|
|
242
|
+
return recordToCborInternal(
|
|
243
|
+
record,
|
|
244
|
+
/* includeSigs */
|
|
245
|
+
true
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
function recordToCborInternal(record, includeSigs) {
|
|
249
|
+
const out = { v: record.v };
|
|
250
|
+
if (record.items !== void 0) out["items"] = record.items.map(itemToCbor);
|
|
251
|
+
if (record.merkle !== void 0) out["merkle"] = record.merkle.map(merkleToCbor);
|
|
252
|
+
if (record.supersedes !== void 0) out["supersedes"] = record.supersedes;
|
|
253
|
+
if (includeSigs && record.sigs !== void 0) out["sigs"] = record.sigs.map(sigEntryToCbor);
|
|
254
|
+
if (record.crit !== void 0) out["crit"] = record.crit.slice();
|
|
255
|
+
for (const [k, v] of Object.entries(record)) {
|
|
256
|
+
if (k === "v" || k === "items" || k === "merkle" || k === "supersedes" || k === "sigs" || k === "crit") {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
out[k] = v;
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
function itemToCbor(item) {
|
|
264
|
+
const out = { hashes: hashesToCbor(item.hashes) };
|
|
265
|
+
if (item.uris !== void 0) {
|
|
266
|
+
out["uris"] = item.uris.map((chunks) => chunks.slice());
|
|
267
|
+
}
|
|
268
|
+
if (item.enc !== void 0) {
|
|
269
|
+
out["enc"] = envelopeToCbor(item.enc);
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
function hashesToCbor(hashes2) {
|
|
274
|
+
const out = {};
|
|
275
|
+
for (const [alg, digest] of Object.entries(hashes2)) {
|
|
276
|
+
out[alg] = digest;
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
function merkleToCbor(commit) {
|
|
281
|
+
const out = {
|
|
282
|
+
alg: commit.alg,
|
|
283
|
+
root: commit.root,
|
|
284
|
+
leaf_count: commit.leaf_count
|
|
285
|
+
};
|
|
286
|
+
if (commit.uris !== void 0) {
|
|
287
|
+
out["uris"] = commit.uris.map((chunks) => chunks.slice());
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
function envelopeToCbor(enc) {
|
|
292
|
+
const out = {
|
|
293
|
+
scheme: enc.scheme,
|
|
294
|
+
aead: enc.aead,
|
|
295
|
+
nonce: enc.nonce
|
|
296
|
+
};
|
|
297
|
+
if (enc.kem !== void 0) out["kem"] = enc.kem;
|
|
298
|
+
if (enc.slots !== void 0) out["slots"] = enc.slots.map(slotToCbor);
|
|
299
|
+
if (enc.slots_mac !== void 0) out["slots_mac"] = enc.slots_mac;
|
|
300
|
+
if (enc.passphrase !== void 0) out["passphrase"] = passphraseToCbor(enc.passphrase);
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
function slotToCbor(slot) {
|
|
304
|
+
if (slot.kem_ct !== void 0) {
|
|
305
|
+
return { kem_ct: slot.kem_ct.map((c) => c), wrap: slot.wrap };
|
|
306
|
+
}
|
|
307
|
+
return { epk: slot.epk, wrap: slot.wrap };
|
|
308
|
+
}
|
|
309
|
+
function passphraseToCbor(pp) {
|
|
310
|
+
return {
|
|
311
|
+
alg: pp.alg,
|
|
312
|
+
salt: pp.salt,
|
|
313
|
+
params: pp.params
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function sigEntryToCbor(entry) {
|
|
317
|
+
const out = { cose_sign1: entry.cose_sign1.map((b) => b) };
|
|
318
|
+
if (entry.cose_key !== void 0) {
|
|
319
|
+
out["cose_key"] = entry.cose_key.map((b) => b);
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ../poe-standard/src/chunked.ts
|
|
325
|
+
var CHUNK_MAX_BYTES = 64;
|
|
326
|
+
var UTF8_ENCODER2 = new TextEncoder();
|
|
327
|
+
function chunkBytes(value) {
|
|
328
|
+
if (value.length === 0) return [new Uint8Array(0)];
|
|
329
|
+
const chunks = [];
|
|
330
|
+
for (let i = 0; i < value.length; i += CHUNK_MAX_BYTES) {
|
|
331
|
+
chunks.push(value.subarray(i, Math.min(i + CHUNK_MAX_BYTES, value.length)));
|
|
332
|
+
}
|
|
333
|
+
return chunks;
|
|
334
|
+
}
|
|
335
|
+
function chunkUri(uri) {
|
|
336
|
+
const bytes = UTF8_ENCODER2.encode(uri);
|
|
337
|
+
if (bytes.length === 0) return [""];
|
|
338
|
+
if (bytes.length <= CHUNK_MAX_BYTES) return [uri];
|
|
339
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
340
|
+
const chunks = [];
|
|
341
|
+
let cursor = 0;
|
|
342
|
+
while (cursor < bytes.length) {
|
|
343
|
+
let end = Math.min(cursor + CHUNK_MAX_BYTES, bytes.length);
|
|
344
|
+
while (end < bytes.length && (bytes[end] & 192) === 128) end--;
|
|
345
|
+
chunks.push(decoder.decode(bytes.subarray(cursor, end)));
|
|
346
|
+
cursor = end;
|
|
347
|
+
}
|
|
348
|
+
return chunks;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/client/off-host-sign.ts
|
|
352
|
+
var EMPTY_BYTES2 = new Uint8Array(0);
|
|
353
|
+
var ED25519_PUBLIC_KEY_LENGTH = 32;
|
|
354
|
+
var ED25519_SIGNATURE_LENGTH = 64;
|
|
355
|
+
var OffHostSignError = class extends Error {
|
|
356
|
+
code;
|
|
357
|
+
constructor(code, message) {
|
|
358
|
+
super(message);
|
|
359
|
+
this.name = "OffHostSignError";
|
|
360
|
+
this.code = code;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
function buildToSign(record) {
|
|
364
|
+
const body = encodeRecordBodyForSigning(record);
|
|
365
|
+
const out = new Uint8Array(CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length + body.length);
|
|
366
|
+
out.set(CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES, 0);
|
|
367
|
+
out.set(body, CARDANO_POE_SIG_DOMAIN_PREFIX_BYTES.length);
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
function encodePath1ProtectedHeader(signerPubkey) {
|
|
371
|
+
const protectedHeader = /* @__PURE__ */ new Map([
|
|
372
|
+
[1, -8],
|
|
373
|
+
[4, signerPubkey]
|
|
374
|
+
]);
|
|
375
|
+
const protectedHeaderBytes = encodeCanonicalCbor(protectedHeader);
|
|
376
|
+
return { protectedHeader, protectedHeaderBytes };
|
|
377
|
+
}
|
|
378
|
+
function prepareSigStructure(args) {
|
|
379
|
+
if (args.signerPubkey.length !== ED25519_PUBLIC_KEY_LENGTH) {
|
|
380
|
+
throw new OffHostSignError(
|
|
381
|
+
"INVALID_PUBKEY_LENGTH",
|
|
382
|
+
`signerPubkey must be 32 bytes (Ed25519 raw public key), got ${args.signerPubkey.length}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const { protectedHeaderBytes } = encodePath1ProtectedHeader(args.signerPubkey);
|
|
386
|
+
const recordBodyCbor = encodeRecordBodyForSigning(args.record);
|
|
387
|
+
const sigStructureBytes = buildCip309SigStructure({
|
|
388
|
+
bodyProtectedBytes: protectedHeaderBytes,
|
|
389
|
+
recordBodyCbor
|
|
390
|
+
});
|
|
391
|
+
return { sigStructureBytes, protectedHeaderBytes };
|
|
392
|
+
}
|
|
393
|
+
function assembleCoseSign1(args) {
|
|
394
|
+
if (args.signerPubkey.length !== ED25519_PUBLIC_KEY_LENGTH) {
|
|
395
|
+
throw new OffHostSignError(
|
|
396
|
+
"INVALID_PUBKEY_LENGTH",
|
|
397
|
+
`signerPubkey must be 32 bytes (Ed25519 raw public key), got ${args.signerPubkey.length}`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (args.signature.length !== ED25519_SIGNATURE_LENGTH) {
|
|
401
|
+
throw new OffHostSignError(
|
|
402
|
+
"INVALID_SIGNATURE_LENGTH",
|
|
403
|
+
`signature must be 64 bytes (Ed25519 raw signature), got ${args.signature.length}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
const { protectedHeader } = encodePath1ProtectedHeader(args.signerPubkey);
|
|
407
|
+
const coseSign1Bytes = encodeCoseSign1({
|
|
408
|
+
protectedHeader,
|
|
409
|
+
unprotectedHeader: /* @__PURE__ */ new Map(),
|
|
410
|
+
payload: null,
|
|
411
|
+
signature: args.signature
|
|
412
|
+
});
|
|
413
|
+
const chunks = chunkBytes(coseSign1Bytes);
|
|
414
|
+
const sigEntry = { cose_sign1: chunks };
|
|
415
|
+
return { coseSign1Bytes, sigEntry };
|
|
416
|
+
}
|
|
417
|
+
function prepareSigStructureHashed(args) {
|
|
418
|
+
if (args.signerPubkey.length !== ED25519_PUBLIC_KEY_LENGTH) {
|
|
419
|
+
throw new OffHostSignError(
|
|
420
|
+
"INVALID_PUBKEY_LENGTH",
|
|
421
|
+
`signerPubkey must be 32 bytes (Ed25519 raw public key), got ${args.signerPubkey.length}`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
const { protectedHeaderBytes } = encodePath1ProtectedHeader(args.signerPubkey);
|
|
425
|
+
const toSign = buildToSign(args.record);
|
|
426
|
+
const toSignHashBytes = blake2b224(toSign);
|
|
427
|
+
const sigStructureBytes = buildSigStructure({
|
|
428
|
+
context: "Signature1",
|
|
429
|
+
bodyProtectedBytes: protectedHeaderBytes,
|
|
430
|
+
externalAad: EMPTY_BYTES2,
|
|
431
|
+
payload: toSignHashBytes
|
|
432
|
+
});
|
|
433
|
+
return { sigStructureBytes, protectedHeaderBytes, toSignHashBytes };
|
|
434
|
+
}
|
|
435
|
+
function assembleCoseSign1Hashed(args) {
|
|
436
|
+
if (args.signerPubkey.length !== ED25519_PUBLIC_KEY_LENGTH) {
|
|
437
|
+
throw new OffHostSignError(
|
|
438
|
+
"INVALID_PUBKEY_LENGTH",
|
|
439
|
+
`signerPubkey must be 32 bytes (Ed25519 raw public key), got ${args.signerPubkey.length}`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
if (args.signature.length !== ED25519_SIGNATURE_LENGTH) {
|
|
443
|
+
throw new OffHostSignError(
|
|
444
|
+
"INVALID_SIGNATURE_LENGTH",
|
|
445
|
+
`signature must be 64 bytes (Ed25519 raw signature), got ${args.signature.length}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
const { protectedHeader } = encodePath1ProtectedHeader(args.signerPubkey);
|
|
449
|
+
const unprotectedHeader = /* @__PURE__ */ new Map([["hashed", true]]);
|
|
450
|
+
const coseSign1Bytes = encodeCoseSign1({
|
|
451
|
+
protectedHeader,
|
|
452
|
+
unprotectedHeader,
|
|
453
|
+
payload: null,
|
|
454
|
+
signature: args.signature
|
|
455
|
+
});
|
|
456
|
+
const chunks = chunkBytes(coseSign1Bytes);
|
|
457
|
+
const sigEntry = { cose_sign1: chunks };
|
|
458
|
+
return { coseSign1Bytes, sigEntry };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/client/http-error.ts
|
|
462
|
+
var CANONICAL_PROBLEM_KEYS = /* @__PURE__ */ new Set([
|
|
463
|
+
"type",
|
|
464
|
+
"title",
|
|
465
|
+
"status",
|
|
466
|
+
"detail",
|
|
467
|
+
"code",
|
|
468
|
+
"trace_id",
|
|
469
|
+
"errors",
|
|
470
|
+
"instance"
|
|
471
|
+
]);
|
|
472
|
+
function extractProblemExtensions(problem) {
|
|
473
|
+
const out = {};
|
|
474
|
+
for (const [key, value] of Object.entries(problem)) {
|
|
475
|
+
if (!CANONICAL_PROBLEM_KEYS.has(key)) {
|
|
476
|
+
out[key] = value;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
var Cip309HttpError = class extends Error {
|
|
482
|
+
problem;
|
|
483
|
+
code;
|
|
484
|
+
httpStatus;
|
|
485
|
+
title;
|
|
486
|
+
detail;
|
|
487
|
+
type;
|
|
488
|
+
traceId;
|
|
489
|
+
instance;
|
|
490
|
+
errors;
|
|
491
|
+
extensions;
|
|
492
|
+
requestId;
|
|
493
|
+
retryAfterSeconds;
|
|
494
|
+
constructor(init) {
|
|
495
|
+
super(init.problem.detail || `${init.problem.title} (HTTP ${init.problem.status})`);
|
|
496
|
+
this.name = "Cip309HttpError";
|
|
497
|
+
this.problem = init.problem;
|
|
498
|
+
this.code = init.problem.code;
|
|
499
|
+
this.httpStatus = init.problem.status;
|
|
500
|
+
this.title = init.problem.title;
|
|
501
|
+
this.detail = init.problem.detail;
|
|
502
|
+
this.type = init.problem.type;
|
|
503
|
+
this.traceId = init.problem.trace_id;
|
|
504
|
+
this.instance = init.problem.instance;
|
|
505
|
+
this.errors = init.problem.errors;
|
|
506
|
+
this.extensions = init.extensions ?? extractProblemExtensions(init.problem);
|
|
507
|
+
this.requestId = init.requestId ?? init.problem.trace_id;
|
|
508
|
+
this.retryAfterSeconds = init.retryAfterSeconds;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// src/client/batch-empty-error.ts
|
|
513
|
+
var BatchEmptyError = class extends Cip309HttpError {
|
|
514
|
+
constructor(init) {
|
|
515
|
+
super(init);
|
|
516
|
+
this.name = "BatchEmptyError";
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// src/client/batch-too-large-error.ts
|
|
521
|
+
function readInt(value) {
|
|
522
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
523
|
+
}
|
|
524
|
+
var BatchTooLargeError = class extends Cip309HttpError {
|
|
525
|
+
max;
|
|
526
|
+
got;
|
|
527
|
+
constructor(init) {
|
|
528
|
+
super(init);
|
|
529
|
+
this.name = "BatchTooLargeError";
|
|
530
|
+
this.max = readInt(this.extensions["max"]);
|
|
531
|
+
this.got = readInt(this.extensions["got"]);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// src/client/forbidden-error.ts
|
|
536
|
+
var ForbiddenError = class extends Cip309HttpError {
|
|
537
|
+
constructor(init) {
|
|
538
|
+
super(init);
|
|
539
|
+
this.name = "ForbiddenError";
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/client/idempotency-conflict-error.ts
|
|
544
|
+
var IdempotencyConflictError = class extends Cip309HttpError {
|
|
545
|
+
constructor(init) {
|
|
546
|
+
super(init);
|
|
547
|
+
this.name = "IdempotencyConflictError";
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// src/client/insufficient-funds-error.ts
|
|
552
|
+
function readBigIntString(value) {
|
|
553
|
+
if (typeof value !== "string") return void 0;
|
|
554
|
+
if (!/^-?[0-9]+$/.test(value)) return void 0;
|
|
555
|
+
try {
|
|
556
|
+
return BigInt(value);
|
|
557
|
+
} catch {
|
|
558
|
+
return void 0;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function readString(value) {
|
|
562
|
+
return typeof value === "string" ? value : void 0;
|
|
563
|
+
}
|
|
564
|
+
var InsufficientFundsError = class extends Cip309HttpError {
|
|
565
|
+
balanceUsdMicros;
|
|
566
|
+
requiredUsdMicros;
|
|
567
|
+
topUpUrl;
|
|
568
|
+
constructor(init) {
|
|
569
|
+
super(init);
|
|
570
|
+
this.name = "InsufficientFundsError";
|
|
571
|
+
this.balanceUsdMicros = readBigIntString(this.extensions["balance_usd_micros"]);
|
|
572
|
+
this.requiredUsdMicros = readBigIntString(this.extensions["required_usd_micros"]);
|
|
573
|
+
this.topUpUrl = readString(this.extensions["top_up_url"]);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/client/insufficient-scope-error.ts
|
|
578
|
+
function readScopeArray(value) {
|
|
579
|
+
if (!Array.isArray(value)) return [];
|
|
580
|
+
return value.filter((entry) => typeof entry === "string");
|
|
581
|
+
}
|
|
582
|
+
var InsufficientScopeError = class extends Cip309HttpError {
|
|
583
|
+
requiredScopes;
|
|
584
|
+
grantedScopes;
|
|
585
|
+
constructor(init) {
|
|
586
|
+
super(init);
|
|
587
|
+
this.name = "InsufficientScopeError";
|
|
588
|
+
this.requiredScopes = readScopeArray(this.extensions["required"]);
|
|
589
|
+
this.grantedScopes = readScopeArray(this.extensions["granted"]);
|
|
590
|
+
}
|
|
591
|
+
/** Convenience for the single-scope case; first entry of `requiredScopes`. */
|
|
592
|
+
get requiredScope() {
|
|
593
|
+
return this.requiredScopes[0];
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// src/client/internal-server-error.ts
|
|
598
|
+
var InternalServerError = class extends Cip309HttpError {
|
|
599
|
+
constructor(init) {
|
|
600
|
+
super(init);
|
|
601
|
+
this.name = "InternalServerError";
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// src/client/invalid-body-error.ts
|
|
606
|
+
var InvalidBodyError = class extends Cip309HttpError {
|
|
607
|
+
constructor(init) {
|
|
608
|
+
super(init);
|
|
609
|
+
this.name = "InvalidBodyError";
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// src/client/malformed-cbor-error.ts
|
|
614
|
+
var MalformedCborError = class extends Cip309HttpError {
|
|
615
|
+
constructor(init) {
|
|
616
|
+
super(init);
|
|
617
|
+
this.name = "MalformedCborError";
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// src/client/not-found-error.ts
|
|
622
|
+
var NotFoundError = class extends Cip309HttpError {
|
|
623
|
+
constructor(init) {
|
|
624
|
+
super(init);
|
|
625
|
+
this.name = "NotFoundError";
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/client/quote-already-consumed-error.ts
|
|
630
|
+
function readString2(value) {
|
|
631
|
+
return typeof value === "string" ? value : void 0;
|
|
632
|
+
}
|
|
633
|
+
var QuoteAlreadyConsumedError = class extends Cip309HttpError {
|
|
634
|
+
quoteId;
|
|
635
|
+
constructor(init) {
|
|
636
|
+
super(init);
|
|
637
|
+
this.name = "QuoteAlreadyConsumedError";
|
|
638
|
+
this.quoteId = readString2(this.extensions["quote_id"]);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/client/quote-expired-error.ts
|
|
643
|
+
function readString3(value) {
|
|
644
|
+
return typeof value === "string" ? value : void 0;
|
|
645
|
+
}
|
|
646
|
+
var QuoteExpiredError = class extends Cip309HttpError {
|
|
647
|
+
quoteId;
|
|
648
|
+
constructor(init) {
|
|
649
|
+
super(init);
|
|
650
|
+
this.name = "QuoteExpiredError";
|
|
651
|
+
this.quoteId = readString3(this.extensions["quote_id"]);
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// src/client/quote-not-found-error.ts
|
|
656
|
+
function readString4(value) {
|
|
657
|
+
return typeof value === "string" ? value : void 0;
|
|
658
|
+
}
|
|
659
|
+
var QuoteNotFoundError = class extends Cip309HttpError {
|
|
660
|
+
quoteId;
|
|
661
|
+
constructor(init) {
|
|
662
|
+
super(init);
|
|
663
|
+
this.name = "QuoteNotFoundError";
|
|
664
|
+
this.quoteId = readString4(this.extensions["quote_id"]);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// src/client/rate-limited-error.ts
|
|
669
|
+
var RateLimitedError = class extends Cip309HttpError {
|
|
670
|
+
constructor(init) {
|
|
671
|
+
super(init);
|
|
672
|
+
this.name = "RateLimitedError";
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// src/client/record-not-found-error.ts
|
|
677
|
+
var RecordNotFoundError = class extends Cip309HttpError {
|
|
678
|
+
constructor(init) {
|
|
679
|
+
super(init);
|
|
680
|
+
this.name = "RecordNotFoundError";
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// src/client/service-unavailable-error.ts
|
|
685
|
+
var ServiceUnavailableError = class extends Cip309HttpError {
|
|
686
|
+
constructor(init) {
|
|
687
|
+
super(init);
|
|
688
|
+
this.name = "ServiceUnavailableError";
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
// src/client/unauthorized-error.ts
|
|
693
|
+
var UnauthorizedError = class extends Cip309HttpError {
|
|
694
|
+
constructor(init) {
|
|
695
|
+
super(init);
|
|
696
|
+
this.name = "UnauthorizedError";
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// src/client/validation-failed-error.ts
|
|
701
|
+
var ValidationFailedError = class extends Cip309HttpError {
|
|
702
|
+
constructor(init) {
|
|
703
|
+
super(init);
|
|
704
|
+
this.name = "ValidationFailedError";
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/client/parse-http-error.ts
|
|
709
|
+
function asString(value) {
|
|
710
|
+
return typeof value === "string" ? value : void 0;
|
|
711
|
+
}
|
|
712
|
+
function asNumber(value) {
|
|
713
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
714
|
+
}
|
|
715
|
+
function asProblemErrorEntries(value) {
|
|
716
|
+
if (!Array.isArray(value)) return void 0;
|
|
717
|
+
const out = [];
|
|
718
|
+
for (const entry of value) {
|
|
719
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
720
|
+
const e = entry;
|
|
721
|
+
out.push({
|
|
722
|
+
field: typeof e["field"] === "string" ? e["field"] : "",
|
|
723
|
+
code: typeof e["code"] === "string" ? e["code"] : "",
|
|
724
|
+
detail: typeof e["detail"] === "string" ? e["detail"] : ""
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
function synthesiseProblem(httpStatus, requestId) {
|
|
730
|
+
const code = `http-${httpStatus}`;
|
|
731
|
+
return {
|
|
732
|
+
type: `about:blank`,
|
|
733
|
+
title: `HTTP ${httpStatus}`,
|
|
734
|
+
status: httpStatus,
|
|
735
|
+
detail: `Server returned HTTP ${httpStatus} without a problem+json body.`,
|
|
736
|
+
code,
|
|
737
|
+
trace_id: requestId ?? ""
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function toProblemDetails(httpStatus, body, requestId) {
|
|
741
|
+
if (body === null || typeof body !== "object") {
|
|
742
|
+
return synthesiseProblem(httpStatus, requestId);
|
|
743
|
+
}
|
|
744
|
+
const b = body;
|
|
745
|
+
const code = asString(b["code"]);
|
|
746
|
+
const status = asNumber(b["status"]) ?? httpStatus;
|
|
747
|
+
const title = asString(b["title"]);
|
|
748
|
+
if (code === void 0 && title === void 0) {
|
|
749
|
+
return synthesiseProblem(httpStatus, requestId);
|
|
750
|
+
}
|
|
751
|
+
const errors = asProblemErrorEntries(b["errors"]);
|
|
752
|
+
const base = {
|
|
753
|
+
...b,
|
|
754
|
+
// RFC 7807 §4.2: `about:blank` is the default when no type URI is supplied.
|
|
755
|
+
// The client is gateway-agnostic, so it must not invent a vendor-specific
|
|
756
|
+
// problem-type namespace; the machine-readable discriminator is `code`.
|
|
757
|
+
type: asString(b["type"]) ?? "about:blank",
|
|
758
|
+
title: title ?? `HTTP ${status}`,
|
|
759
|
+
status,
|
|
760
|
+
detail: asString(b["detail"]) ?? "",
|
|
761
|
+
code: code ?? `http-${status}`,
|
|
762
|
+
trace_id: asString(b["trace_id"]) ?? requestId ?? ""
|
|
763
|
+
};
|
|
764
|
+
if (errors !== void 0) base["errors"] = errors;
|
|
765
|
+
return base;
|
|
766
|
+
}
|
|
767
|
+
function parseHttpError(args) {
|
|
768
|
+
const problem = toProblemDetails(args.httpStatus, args.body, args.requestId);
|
|
769
|
+
const extensions = extractProblemExtensions(problem);
|
|
770
|
+
const init = {
|
|
771
|
+
problem,
|
|
772
|
+
extensions,
|
|
773
|
+
requestId: args.requestId,
|
|
774
|
+
retryAfterSeconds: args.retryAfterSeconds
|
|
775
|
+
};
|
|
776
|
+
switch (problem.code) {
|
|
777
|
+
case "unauthorized":
|
|
778
|
+
return new UnauthorizedError(init);
|
|
779
|
+
case "forbidden":
|
|
780
|
+
case "csrf-invalid":
|
|
781
|
+
return new ForbiddenError(init);
|
|
782
|
+
case "insufficient-scope":
|
|
783
|
+
return new InsufficientScopeError(init);
|
|
784
|
+
case "insufficient-funds":
|
|
785
|
+
return new InsufficientFundsError(init);
|
|
786
|
+
case "quote-expired":
|
|
787
|
+
return new QuoteExpiredError(init);
|
|
788
|
+
case "quote-not-found":
|
|
789
|
+
return new QuoteNotFoundError(init);
|
|
790
|
+
case "quote-already-consumed":
|
|
791
|
+
return new QuoteAlreadyConsumedError(init);
|
|
792
|
+
case "not-found":
|
|
793
|
+
return new NotFoundError(init);
|
|
794
|
+
case "record-not-found":
|
|
795
|
+
return new RecordNotFoundError(init);
|
|
796
|
+
case "idempotency-key-conflict":
|
|
797
|
+
return new IdempotencyConflictError(init);
|
|
798
|
+
case "rate-limited":
|
|
799
|
+
return new RateLimitedError(init);
|
|
800
|
+
case "validation-failed":
|
|
801
|
+
return new ValidationFailedError(init);
|
|
802
|
+
case "invalid-body":
|
|
803
|
+
return new InvalidBodyError(init);
|
|
804
|
+
case "malformed-cbor":
|
|
805
|
+
return new MalformedCborError(init);
|
|
806
|
+
case "batch-too-large":
|
|
807
|
+
return new BatchTooLargeError(init);
|
|
808
|
+
case "batch-empty":
|
|
809
|
+
return new BatchEmptyError(init);
|
|
810
|
+
case "internal-error":
|
|
811
|
+
return new InternalServerError(init);
|
|
812
|
+
// A gateway that prices on a live FX oracle may surface a transient
|
|
813
|
+
// `fx-stale` pricing outage; to a vendor-neutral client that is just a
|
|
814
|
+
// temporary inability to serve, i.e. a service-unavailable condition.
|
|
815
|
+
case "service-unavailable":
|
|
816
|
+
case "fx-stale":
|
|
817
|
+
return new ServiceUnavailableError(init);
|
|
818
|
+
default:
|
|
819
|
+
return new Cip309HttpError(init);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/client/http-helpers.ts
|
|
824
|
+
async function readJson(response) {
|
|
825
|
+
const text = await response.text();
|
|
826
|
+
if (text.length === 0) return null;
|
|
827
|
+
try {
|
|
828
|
+
return JSON.parse(text);
|
|
829
|
+
} catch {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function parseRetryAfter(header) {
|
|
834
|
+
if (header === null) return void 0;
|
|
835
|
+
const parsed = Number(header);
|
|
836
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
837
|
+
}
|
|
838
|
+
async function throwIfNotOk(response) {
|
|
839
|
+
if (response.ok) return;
|
|
840
|
+
const body = await readJson(response);
|
|
841
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
842
|
+
const retryAfterSeconds = parseRetryAfter(response.headers.get("retry-after"));
|
|
843
|
+
throw parseHttpError({ httpStatus: response.status, body, requestId, retryAfterSeconds });
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/client/account.ts
|
|
847
|
+
function buildHeaders(apiKey) {
|
|
848
|
+
const headers = new Headers({
|
|
849
|
+
"content-type": "application/json",
|
|
850
|
+
accept: "application/json"
|
|
851
|
+
});
|
|
852
|
+
if (apiKey !== void 0) headers.set("authorization", `Bearer ${apiKey}`);
|
|
853
|
+
return headers;
|
|
854
|
+
}
|
|
855
|
+
var AccountNamespace = class {
|
|
856
|
+
config;
|
|
857
|
+
constructor(config) {
|
|
858
|
+
this.config = config;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Fetch the caller's current prepaid USD balance.
|
|
862
|
+
*
|
|
863
|
+
* Returns `{ balanceUsdMicros }`, the gateway's `balance_usd_micros` field
|
|
864
|
+
* (USD micro-cents as a decimal string). The string is preserved verbatim —
|
|
865
|
+
* never parsed into a number — so no precision is lost. An account with no
|
|
866
|
+
* ledger activity yet reads `"0"`.
|
|
867
|
+
*
|
|
868
|
+
* Requires authentication: 401 (UnauthorizedError) when anonymous, 403
|
|
869
|
+
* (InsufficientScopeError) when the Bearer key lacks the `account:read`
|
|
870
|
+
* scope.
|
|
871
|
+
*/
|
|
872
|
+
async balance() {
|
|
873
|
+
const response = await this.config.fetch(`${this.config.baseUrl}/api/v1/account/balance`, {
|
|
874
|
+
method: "GET",
|
|
875
|
+
headers: buildHeaders(this.config.apiKey)
|
|
876
|
+
});
|
|
877
|
+
await throwIfNotOk(response);
|
|
878
|
+
const body = await readJson(response);
|
|
879
|
+
return { balanceUsdMicros: body.balance_usd_micros };
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/client/invalid-client-config-error.ts
|
|
884
|
+
var InvalidClientConfigError = class extends Error {
|
|
885
|
+
code = "INVALID_CLIENT_CONFIG";
|
|
886
|
+
constructor(message) {
|
|
887
|
+
super(message);
|
|
888
|
+
this.name = "InvalidClientConfigError";
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
// src/hex.ts
|
|
893
|
+
function bytesToHex(bytes) {
|
|
894
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
895
|
+
}
|
|
896
|
+
function encodeCanonicalCbor3(value) {
|
|
897
|
+
return encode(value, {
|
|
898
|
+
cde: true,
|
|
899
|
+
collapseBigInts: true,
|
|
900
|
+
rejectDuplicateKeys: true,
|
|
901
|
+
sortKeys: sortCoreDeterministic
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
var LEAVES_LIST_FORMAT_V1 = "cardano-poe-merkle-leaves-v1";
|
|
905
|
+
var TREE_ALG_RFC9162 = "rfc9162-sha256";
|
|
906
|
+
var DIGEST_LENGTH2 = 32;
|
|
907
|
+
var MerkleLeavesListError = class extends Error {
|
|
908
|
+
code;
|
|
909
|
+
constructor(code, message) {
|
|
910
|
+
super(message ? `${code}: ${message}` : code);
|
|
911
|
+
this.code = code;
|
|
912
|
+
this.name = "MerkleLeavesListError";
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
function encodeLeavesList(args) {
|
|
916
|
+
if (!(args.root instanceof Uint8Array) || args.root.length !== DIGEST_LENGTH2) {
|
|
917
|
+
throw new MerkleLeavesListError(
|
|
918
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
919
|
+
`root must be a Uint8Array(${DIGEST_LENGTH2})`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
if (args.leaves.length < 1) {
|
|
923
|
+
throw new MerkleLeavesListError(
|
|
924
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
925
|
+
"leaves array must be non-empty"
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
const leavesCopy = [];
|
|
929
|
+
for (let i = 0; i < args.leaves.length; i++) {
|
|
930
|
+
const leaf = args.leaves[i];
|
|
931
|
+
if (!(leaf instanceof Uint8Array) || leaf.length !== DIGEST_LENGTH2) {
|
|
932
|
+
throw new MerkleLeavesListError(
|
|
933
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
934
|
+
`leaves[${i}] must be a Uint8Array(${DIGEST_LENGTH2})`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
leavesCopy.push(leaf);
|
|
938
|
+
}
|
|
939
|
+
if (args.leafAlg !== void 0 && typeof args.leafAlg !== "string") {
|
|
940
|
+
throw new MerkleLeavesListError(
|
|
941
|
+
"SCHEMA_MERKLE_LEAVES_MALFORMED",
|
|
942
|
+
"leaf_alg must be a string when present"
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
const map = {
|
|
946
|
+
format: LEAVES_LIST_FORMAT_V1,
|
|
947
|
+
tree_alg: TREE_ALG_RFC9162,
|
|
948
|
+
root: args.root,
|
|
949
|
+
leaves: leavesCopy,
|
|
950
|
+
leaf_count: leavesCopy.length
|
|
951
|
+
};
|
|
952
|
+
if (args.leafAlg !== void 0) {
|
|
953
|
+
map["leaf_alg"] = args.leafAlg;
|
|
954
|
+
}
|
|
955
|
+
return encodeCanonicalCbor3(map);
|
|
956
|
+
}
|
|
957
|
+
var abytesDoc = abytes;
|
|
958
|
+
var randomBytes = randomBytes$2;
|
|
959
|
+
function equalBytes(a, b) {
|
|
960
|
+
if (a.length !== b.length)
|
|
961
|
+
return false;
|
|
962
|
+
let diff = 0;
|
|
963
|
+
for (let i = 0; i < a.length; i++)
|
|
964
|
+
diff |= a[i] ^ b[i];
|
|
965
|
+
return diff === 0;
|
|
966
|
+
}
|
|
967
|
+
function copyBytes(bytes) {
|
|
968
|
+
return Uint8Array.from(abytes(bytes));
|
|
969
|
+
}
|
|
970
|
+
function splitCoder(label, ...lengths) {
|
|
971
|
+
const getLength = (c) => typeof c === "number" ? c : c.bytesLen;
|
|
972
|
+
const bytesLen = lengths.reduce((sum, a) => sum + getLength(a), 0);
|
|
973
|
+
return {
|
|
974
|
+
bytesLen,
|
|
975
|
+
encode: (bufs) => {
|
|
976
|
+
const res = new Uint8Array(bytesLen);
|
|
977
|
+
for (let i = 0, pos = 0; i < lengths.length; i++) {
|
|
978
|
+
const c = lengths[i];
|
|
979
|
+
const l = getLength(c);
|
|
980
|
+
const b = typeof c === "number" ? bufs[i] : c.encode(bufs[i]);
|
|
981
|
+
abytes(b, l, label);
|
|
982
|
+
res.set(b, pos);
|
|
983
|
+
if (typeof c !== "number")
|
|
984
|
+
b.fill(0);
|
|
985
|
+
pos += l;
|
|
986
|
+
}
|
|
987
|
+
return res;
|
|
988
|
+
},
|
|
989
|
+
decode: (buf) => {
|
|
990
|
+
abytes(buf, bytesLen, label);
|
|
991
|
+
const res = [];
|
|
992
|
+
for (const c of lengths) {
|
|
993
|
+
const l = getLength(c);
|
|
994
|
+
const b = buf.subarray(0, l);
|
|
995
|
+
res.push(typeof c === "number" ? b : c.decode(b));
|
|
996
|
+
buf = buf.subarray(l);
|
|
997
|
+
}
|
|
998
|
+
return res;
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
function vecCoder(c, vecLen) {
|
|
1003
|
+
const coder = c;
|
|
1004
|
+
const bytesLen = vecLen * coder.bytesLen;
|
|
1005
|
+
return {
|
|
1006
|
+
bytesLen,
|
|
1007
|
+
encode: (u) => {
|
|
1008
|
+
if (u.length !== vecLen)
|
|
1009
|
+
throw new RangeError(`vecCoder.encode: wrong length=${u.length}. Expected: ${vecLen}`);
|
|
1010
|
+
const res = new Uint8Array(bytesLen);
|
|
1011
|
+
for (let i = 0, pos = 0; i < u.length; i++) {
|
|
1012
|
+
const b = coder.encode(u[i]);
|
|
1013
|
+
res.set(b, pos);
|
|
1014
|
+
b.fill(0);
|
|
1015
|
+
pos += b.length;
|
|
1016
|
+
}
|
|
1017
|
+
return res;
|
|
1018
|
+
},
|
|
1019
|
+
decode: (a) => {
|
|
1020
|
+
abytes(a, bytesLen);
|
|
1021
|
+
const r = [];
|
|
1022
|
+
for (let i = 0; i < a.length; i += coder.bytesLen)
|
|
1023
|
+
r.push(coder.decode(a.subarray(i, i + coder.bytesLen)));
|
|
1024
|
+
return r;
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
function cleanBytes(...list) {
|
|
1029
|
+
for (const t of list) {
|
|
1030
|
+
if (Array.isArray(t))
|
|
1031
|
+
for (const b of t)
|
|
1032
|
+
b.fill(0);
|
|
1033
|
+
else
|
|
1034
|
+
t.fill(0);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function getMask(bits) {
|
|
1038
|
+
if (!Number.isSafeInteger(bits) || bits < 0 || bits > 32)
|
|
1039
|
+
throw new RangeError(`expected bits in [0..32], got ${bits}`);
|
|
1040
|
+
return bits === 32 ? 4294967295 : ~(-1 << bits) >>> 0;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// ../../node_modules/.pnpm/@noble+post-quantum@0.6.1/node_modules/@noble/post-quantum/_crystals.js
|
|
1044
|
+
var genCrystals = (opts2) => {
|
|
1045
|
+
const { newPoly, N: N2, Q: Q2, F: F2, ROOT_OF_UNITY: ROOT_OF_UNITY2, brvBits} = opts2;
|
|
1046
|
+
const mod = (a, modulo = Q2) => {
|
|
1047
|
+
const result = a % modulo | 0;
|
|
1048
|
+
return (result >= 0 ? result | 0 : modulo + result | 0) | 0;
|
|
1049
|
+
};
|
|
1050
|
+
const smod = (a, modulo = Q2) => {
|
|
1051
|
+
const r = mod(a, modulo) | 0;
|
|
1052
|
+
return (r > modulo >> 1 ? r - modulo | 0 : r) | 0;
|
|
1053
|
+
};
|
|
1054
|
+
function getZettas() {
|
|
1055
|
+
const out = newPoly(N2);
|
|
1056
|
+
for (let i = 0; i < N2; i++) {
|
|
1057
|
+
const b = reverseBits(i, brvBits);
|
|
1058
|
+
const p = BigInt(ROOT_OF_UNITY2) ** BigInt(b) % BigInt(Q2);
|
|
1059
|
+
out[i] = Number(p) | 0;
|
|
1060
|
+
}
|
|
1061
|
+
return out;
|
|
1062
|
+
}
|
|
1063
|
+
const nttZetas = getZettas();
|
|
1064
|
+
const field = {
|
|
1065
|
+
add: (a, b) => mod((a | 0) + (b | 0)) | 0,
|
|
1066
|
+
sub: (a, b) => mod((a | 0) - (b | 0)) | 0,
|
|
1067
|
+
mul: (a, b) => mod((a | 0) * (b | 0)) | 0,
|
|
1068
|
+
inv: (_a) => {
|
|
1069
|
+
throw new Error("not implemented");
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
const nttOpts = {
|
|
1073
|
+
N: N2,
|
|
1074
|
+
roots: nttZetas,
|
|
1075
|
+
invertButterflies: true,
|
|
1076
|
+
skipStages: 1 ,
|
|
1077
|
+
brp: false
|
|
1078
|
+
};
|
|
1079
|
+
const dif = FFTCore(field, { dit: false, ...nttOpts });
|
|
1080
|
+
const dit = FFTCore(field, { dit: true, ...nttOpts });
|
|
1081
|
+
const NTT = {
|
|
1082
|
+
encode: (r) => {
|
|
1083
|
+
return dif(r);
|
|
1084
|
+
},
|
|
1085
|
+
decode: (r) => {
|
|
1086
|
+
dit(r);
|
|
1087
|
+
for (let i = 0; i < r.length; i++)
|
|
1088
|
+
r[i] = mod(F2 * r[i]);
|
|
1089
|
+
return r;
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
const bitsCoder = (d, c) => {
|
|
1093
|
+
const mask = getMask(d);
|
|
1094
|
+
const bytesLen = d * (N2 / 8);
|
|
1095
|
+
return {
|
|
1096
|
+
bytesLen,
|
|
1097
|
+
encode: (poly_) => {
|
|
1098
|
+
const poly = poly_;
|
|
1099
|
+
const r = new Uint8Array(bytesLen);
|
|
1100
|
+
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < poly.length; i++) {
|
|
1101
|
+
buf |= (c.encode(poly[i]) & mask) << bufLen;
|
|
1102
|
+
bufLen += d;
|
|
1103
|
+
for (; bufLen >= 8; bufLen -= 8, buf >>= 8)
|
|
1104
|
+
r[pos++] = buf & getMask(bufLen);
|
|
1105
|
+
}
|
|
1106
|
+
return r;
|
|
1107
|
+
},
|
|
1108
|
+
decode: (bytes) => {
|
|
1109
|
+
const r = newPoly(N2);
|
|
1110
|
+
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) {
|
|
1111
|
+
buf |= bytes[i] << bufLen;
|
|
1112
|
+
bufLen += 8;
|
|
1113
|
+
for (; bufLen >= d; bufLen -= d, buf >>= d)
|
|
1114
|
+
r[pos++] = c.decode(buf & mask);
|
|
1115
|
+
}
|
|
1116
|
+
return r;
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
};
|
|
1120
|
+
return {
|
|
1121
|
+
mod,
|
|
1122
|
+
smod,
|
|
1123
|
+
nttZetas,
|
|
1124
|
+
NTT: {
|
|
1125
|
+
encode: (r) => NTT.encode(r),
|
|
1126
|
+
decode: (r) => NTT.decode(r)
|
|
1127
|
+
},
|
|
1128
|
+
bitsCoder
|
|
1129
|
+
};
|
|
1130
|
+
};
|
|
1131
|
+
var createXofShake = (shake) => (seed, blockLen) => {
|
|
1132
|
+
if (!blockLen)
|
|
1133
|
+
blockLen = shake.blockLen;
|
|
1134
|
+
const _seed = new Uint8Array(seed.length + 2);
|
|
1135
|
+
_seed.set(seed);
|
|
1136
|
+
const seedLen = seed.length;
|
|
1137
|
+
const buf = new Uint8Array(blockLen);
|
|
1138
|
+
let h = shake.create({});
|
|
1139
|
+
let calls = 0;
|
|
1140
|
+
let xofs = 0;
|
|
1141
|
+
return {
|
|
1142
|
+
stats: () => ({ calls, xofs }),
|
|
1143
|
+
get: (x, y) => {
|
|
1144
|
+
_seed[seedLen + 0] = x;
|
|
1145
|
+
_seed[seedLen + 1] = y;
|
|
1146
|
+
h.destroy();
|
|
1147
|
+
h = shake.create({}).update(_seed);
|
|
1148
|
+
calls++;
|
|
1149
|
+
return () => {
|
|
1150
|
+
xofs++;
|
|
1151
|
+
return h.xofInto(buf);
|
|
1152
|
+
};
|
|
1153
|
+
},
|
|
1154
|
+
clean: () => {
|
|
1155
|
+
h.destroy();
|
|
1156
|
+
cleanBytes(buf, _seed);
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
};
|
|
1160
|
+
var XOF128 = /* @__PURE__ */ createXofShake(shake128);
|
|
1161
|
+
|
|
1162
|
+
// ../../node_modules/.pnpm/@noble+post-quantum@0.6.1/node_modules/@noble/post-quantum/ml-kem.js
|
|
1163
|
+
var N = 256;
|
|
1164
|
+
var Q = 3329;
|
|
1165
|
+
var F = 3303;
|
|
1166
|
+
var ROOT_OF_UNITY = 17;
|
|
1167
|
+
var crystals = /* @__PURE__ */ genCrystals({
|
|
1168
|
+
N,
|
|
1169
|
+
Q,
|
|
1170
|
+
F,
|
|
1171
|
+
ROOT_OF_UNITY,
|
|
1172
|
+
newPoly: (n) => new Uint16Array(n),
|
|
1173
|
+
brvBits: 7});
|
|
1174
|
+
var PARAMS = /* @__PURE__ */ (() => Object.freeze({
|
|
1175
|
+
512: Object.freeze({ N, Q, K: 2, ETA1: 3, ETA2: 2, du: 10, dv: 4, RBGstrength: 128 }),
|
|
1176
|
+
768: Object.freeze({ N, Q, K: 3, ETA1: 2, ETA2: 2, du: 10, dv: 4, RBGstrength: 192 }),
|
|
1177
|
+
1024: Object.freeze({ N, Q, K: 4, ETA1: 2, ETA2: 2, du: 11, dv: 5, RBGstrength: 256 })
|
|
1178
|
+
}))();
|
|
1179
|
+
var compress = (d) => {
|
|
1180
|
+
if (d >= 12)
|
|
1181
|
+
return { encode: (i) => i, decode: (i) => i >= Q ? i - Q : i };
|
|
1182
|
+
const a = 2 ** (d - 1);
|
|
1183
|
+
return {
|
|
1184
|
+
// This only matches standalone Compress_d after bitsCoder masks the result into Z_(2^d).
|
|
1185
|
+
encode: (i) => ((i << d) + Q / 2) / Q,
|
|
1186
|
+
// const decompress = (i: number) => round((Q / 2 ** d) * i);
|
|
1187
|
+
decode: (i) => i * Q + a >>> d
|
|
1188
|
+
};
|
|
1189
|
+
};
|
|
1190
|
+
var byteCoder = (d) => crystals.bitsCoder(d, { encode: (i) => i, decode: (i) => i >= Q ? i - Q : i } );
|
|
1191
|
+
var polyCoder = (d) => d === 12 ? byteCoder(12) : crystals.bitsCoder(d, compress(d));
|
|
1192
|
+
function polyAdd(a_, b_) {
|
|
1193
|
+
const a = a_;
|
|
1194
|
+
const b = b_;
|
|
1195
|
+
for (let i = 0; i < N; i++)
|
|
1196
|
+
a[i] = crystals.mod(a[i] + b[i]);
|
|
1197
|
+
}
|
|
1198
|
+
function polySub(a_, b_) {
|
|
1199
|
+
const a = a_;
|
|
1200
|
+
const b = b_;
|
|
1201
|
+
for (let i = 0; i < N; i++)
|
|
1202
|
+
a[i] = crystals.mod(a[i] - b[i]);
|
|
1203
|
+
}
|
|
1204
|
+
function BaseCaseMultiply(a0, a1, b0, b1, zeta) {
|
|
1205
|
+
const c0 = crystals.mod(a1 * b1 * zeta + a0 * b0);
|
|
1206
|
+
const c1 = crystals.mod(a0 * b1 + a1 * b0);
|
|
1207
|
+
return { c0, c1 };
|
|
1208
|
+
}
|
|
1209
|
+
function MultiplyNTTs(f_, g_) {
|
|
1210
|
+
const f = f_;
|
|
1211
|
+
const g = g_;
|
|
1212
|
+
for (let i = 0; i < N / 2; i++) {
|
|
1213
|
+
let z3 = crystals.nttZetas[64 + (i >> 1)];
|
|
1214
|
+
if (i & 1)
|
|
1215
|
+
z3 = -z3;
|
|
1216
|
+
const { c0, c1 } = BaseCaseMultiply(f[2 * i + 0], f[2 * i + 1], g[2 * i + 0], g[2 * i + 1], z3);
|
|
1217
|
+
f[2 * i + 0] = c0;
|
|
1218
|
+
f[2 * i + 1] = c1;
|
|
1219
|
+
}
|
|
1220
|
+
return f;
|
|
1221
|
+
}
|
|
1222
|
+
function SampleNTT(xof_) {
|
|
1223
|
+
const xof = xof_;
|
|
1224
|
+
const r = new Uint16Array(N);
|
|
1225
|
+
for (let j = 0; j < N; ) {
|
|
1226
|
+
const b = xof();
|
|
1227
|
+
if (b.length % 3)
|
|
1228
|
+
throw new Error("SampleNTT: unaligned block");
|
|
1229
|
+
for (let i = 0; j < N && i + 3 <= b.length; i += 3) {
|
|
1230
|
+
const d1 = (b[i + 0] >> 0 | b[i + 1] << 8) & 4095;
|
|
1231
|
+
const d2 = (b[i + 1] >> 4 | b[i + 2] << 4) & 4095;
|
|
1232
|
+
if (d1 < Q)
|
|
1233
|
+
r[j++] = d1;
|
|
1234
|
+
if (j < N && d2 < Q)
|
|
1235
|
+
r[j++] = d2;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return r;
|
|
1239
|
+
}
|
|
1240
|
+
var sampleCBDBytes = (buf, eta) => {
|
|
1241
|
+
const r = new Uint16Array(N);
|
|
1242
|
+
const b32 = u32(buf);
|
|
1243
|
+
swap32IfBE(b32);
|
|
1244
|
+
let len = 0;
|
|
1245
|
+
for (let i = 0, p = 0, bb = 0, t0 = 0; i < b32.length; i++) {
|
|
1246
|
+
let b = b32[i];
|
|
1247
|
+
for (let j = 0; j < 32; j++) {
|
|
1248
|
+
bb += b & 1;
|
|
1249
|
+
b >>= 1;
|
|
1250
|
+
len += 1;
|
|
1251
|
+
if (len === eta) {
|
|
1252
|
+
t0 = bb;
|
|
1253
|
+
bb = 0;
|
|
1254
|
+
} else if (len === 2 * eta) {
|
|
1255
|
+
r[p++] = crystals.mod(t0 - bb);
|
|
1256
|
+
bb = 0;
|
|
1257
|
+
len = 0;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
swap32IfBE(b32);
|
|
1262
|
+
if (len)
|
|
1263
|
+
throw new Error(`sampleCBD: leftover bits: ${len}`);
|
|
1264
|
+
return r;
|
|
1265
|
+
};
|
|
1266
|
+
function sampleCBD(PRF_, seed, nonce, eta) {
|
|
1267
|
+
const PRF = PRF_;
|
|
1268
|
+
return sampleCBDBytes(PRF(eta * N / 4, seed, nonce), eta);
|
|
1269
|
+
}
|
|
1270
|
+
var genKPKE = (opts_) => {
|
|
1271
|
+
const opts2 = opts_;
|
|
1272
|
+
const { K, PRF, XOF, HASH512, ETA1, ETA2, du, dv } = opts2;
|
|
1273
|
+
const poly1 = polyCoder(1);
|
|
1274
|
+
const polyV = polyCoder(dv);
|
|
1275
|
+
const polyU = polyCoder(du);
|
|
1276
|
+
const publicCoder = splitCoder("publicKey", vecCoder(polyCoder(12), K), 32);
|
|
1277
|
+
const secretCoder = vecCoder(polyCoder(12), K);
|
|
1278
|
+
const cipherCoder = splitCoder("ciphertext", vecCoder(polyU, K), polyV);
|
|
1279
|
+
const seedCoder = splitCoder("seed", 32, 32);
|
|
1280
|
+
return {
|
|
1281
|
+
secretCoder,
|
|
1282
|
+
lengths: {
|
|
1283
|
+
secretKey: secretCoder.bytesLen,
|
|
1284
|
+
publicKey: publicCoder.bytesLen,
|
|
1285
|
+
cipherText: cipherCoder.bytesLen
|
|
1286
|
+
},
|
|
1287
|
+
keygen: (seed) => {
|
|
1288
|
+
abytesDoc(seed, 32, "seed");
|
|
1289
|
+
const seedDst = new Uint8Array(33);
|
|
1290
|
+
seedDst.set(seed);
|
|
1291
|
+
seedDst[32] = K;
|
|
1292
|
+
const seedHash = HASH512(seedDst);
|
|
1293
|
+
const [rho, sigma] = seedCoder.decode(seedHash);
|
|
1294
|
+
const sHat = [];
|
|
1295
|
+
const tHat = [];
|
|
1296
|
+
for (let i = 0; i < K; i++)
|
|
1297
|
+
sHat.push(crystals.NTT.encode(sampleCBD(PRF, sigma, i, ETA1)));
|
|
1298
|
+
const x = XOF(rho);
|
|
1299
|
+
for (let i = 0; i < K; i++) {
|
|
1300
|
+
const e = crystals.NTT.encode(sampleCBD(PRF, sigma, K + i, ETA1));
|
|
1301
|
+
for (let j = 0; j < K; j++) {
|
|
1302
|
+
const aji = SampleNTT(x.get(j, i));
|
|
1303
|
+
polyAdd(e, MultiplyNTTs(aji, sHat[j]));
|
|
1304
|
+
}
|
|
1305
|
+
tHat.push(e);
|
|
1306
|
+
}
|
|
1307
|
+
x.clean();
|
|
1308
|
+
const res = {
|
|
1309
|
+
publicKey: publicCoder.encode([tHat, rho]),
|
|
1310
|
+
secretKey: secretCoder.encode(sHat)
|
|
1311
|
+
};
|
|
1312
|
+
cleanBytes(rho, sigma, sHat, tHat, seedDst, seedHash);
|
|
1313
|
+
return res;
|
|
1314
|
+
},
|
|
1315
|
+
encrypt: (publicKey, msg, seed) => {
|
|
1316
|
+
const [tHat, rho] = publicCoder.decode(publicKey);
|
|
1317
|
+
const rHat = [];
|
|
1318
|
+
for (let i = 0; i < K; i++)
|
|
1319
|
+
rHat.push(crystals.NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
|
|
1320
|
+
const x = XOF(rho);
|
|
1321
|
+
const tmp2 = new Uint16Array(N);
|
|
1322
|
+
const u = [];
|
|
1323
|
+
for (let i = 0; i < K; i++) {
|
|
1324
|
+
const e1 = sampleCBD(PRF, seed, K + i, ETA2);
|
|
1325
|
+
const tmp = new Uint16Array(N);
|
|
1326
|
+
for (let j = 0; j < K; j++) {
|
|
1327
|
+
const aij = SampleNTT(x.get(i, j));
|
|
1328
|
+
polyAdd(tmp, MultiplyNTTs(aij, rHat[j]));
|
|
1329
|
+
}
|
|
1330
|
+
polyAdd(e1, crystals.NTT.decode(tmp));
|
|
1331
|
+
u.push(e1);
|
|
1332
|
+
polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i]));
|
|
1333
|
+
cleanBytes(tmp);
|
|
1334
|
+
}
|
|
1335
|
+
x.clean();
|
|
1336
|
+
const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
|
|
1337
|
+
polyAdd(e2, crystals.NTT.decode(tmp2));
|
|
1338
|
+
const v = poly1.decode(msg);
|
|
1339
|
+
polyAdd(v, e2);
|
|
1340
|
+
cleanBytes(tHat, rHat, tmp2, e2);
|
|
1341
|
+
return cipherCoder.encode([u, v]);
|
|
1342
|
+
},
|
|
1343
|
+
decrypt: (cipherText, privateKey) => {
|
|
1344
|
+
const [u, v] = cipherCoder.decode(cipherText);
|
|
1345
|
+
const sk = secretCoder.decode(privateKey);
|
|
1346
|
+
const tmp = new Uint16Array(N);
|
|
1347
|
+
for (let i = 0; i < K; i++)
|
|
1348
|
+
polyAdd(tmp, MultiplyNTTs(sk[i], crystals.NTT.encode(u[i])));
|
|
1349
|
+
polySub(v, crystals.NTT.decode(tmp));
|
|
1350
|
+
cleanBytes(tmp, sk, u);
|
|
1351
|
+
return poly1.encode(v);
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
};
|
|
1355
|
+
function createKyber(opts2) {
|
|
1356
|
+
const rawOpts = opts2;
|
|
1357
|
+
const KPKE = genKPKE(rawOpts);
|
|
1358
|
+
const { HASH256, HASH512, KDF } = rawOpts;
|
|
1359
|
+
const { secretCoder: KPKESecretCoder, lengths } = KPKE;
|
|
1360
|
+
const secretCoder = splitCoder("secretKey", lengths.secretKey, lengths.publicKey, 32, 32);
|
|
1361
|
+
const msgLen = 32;
|
|
1362
|
+
const seedLen = 64;
|
|
1363
|
+
const kemLengths = Object.freeze({
|
|
1364
|
+
...lengths,
|
|
1365
|
+
seed: 64,
|
|
1366
|
+
msg: msgLen,
|
|
1367
|
+
msgRand: msgLen,
|
|
1368
|
+
secretKey: secretCoder.bytesLen
|
|
1369
|
+
});
|
|
1370
|
+
return Object.freeze({
|
|
1371
|
+
info: Object.freeze({ type: "ml-kem" }),
|
|
1372
|
+
lengths: kemLengths,
|
|
1373
|
+
keygen: (seed = randomBytes(seedLen)) => {
|
|
1374
|
+
abytesDoc(seed, seedLen, "seed");
|
|
1375
|
+
const { publicKey, secretKey: sk } = KPKE.keygen(seed.subarray(0, 32));
|
|
1376
|
+
const publicKeyHash = HASH256(publicKey);
|
|
1377
|
+
const secretKey = secretCoder.encode([sk, publicKey, publicKeyHash, seed.subarray(32)]);
|
|
1378
|
+
cleanBytes(sk, publicKeyHash);
|
|
1379
|
+
return {
|
|
1380
|
+
publicKey,
|
|
1381
|
+
secretKey
|
|
1382
|
+
};
|
|
1383
|
+
},
|
|
1384
|
+
getPublicKey: (secretKey) => {
|
|
1385
|
+
const [_sk, publicKey, _publicKeyHash, _z] = secretCoder.decode(secretKey);
|
|
1386
|
+
return Uint8Array.from(publicKey);
|
|
1387
|
+
},
|
|
1388
|
+
encapsulate: (publicKey, msg = randomBytes(msgLen)) => {
|
|
1389
|
+
abytesDoc(publicKey, lengths.publicKey, "publicKey");
|
|
1390
|
+
abytesDoc(msg, msgLen, "message");
|
|
1391
|
+
const eke = publicKey.subarray(0, 384 * opts2.K);
|
|
1392
|
+
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke)));
|
|
1393
|
+
if (!equalBytes(ek, eke)) {
|
|
1394
|
+
cleanBytes(ek);
|
|
1395
|
+
throw new Error("ML-KEM.encapsulate: wrong publicKey modulus");
|
|
1396
|
+
}
|
|
1397
|
+
cleanBytes(ek);
|
|
1398
|
+
const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest();
|
|
1399
|
+
const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
|
1400
|
+
cleanBytes(kr.subarray(32));
|
|
1401
|
+
return {
|
|
1402
|
+
cipherText,
|
|
1403
|
+
sharedSecret: kr.subarray(0, 32)
|
|
1404
|
+
};
|
|
1405
|
+
},
|
|
1406
|
+
decapsulate: (cipherText, secretKey) => {
|
|
1407
|
+
abytesDoc(secretKey, secretCoder.bytesLen, "secretKey");
|
|
1408
|
+
abytesDoc(cipherText, lengths.cipherText, "cipherText");
|
|
1409
|
+
const k768 = secretCoder.bytesLen - 96;
|
|
1410
|
+
const start = k768 + 32;
|
|
1411
|
+
const test = HASH256(secretKey.subarray(k768 / 2, start));
|
|
1412
|
+
if (!equalBytes(test, secretKey.subarray(start, start + 32)))
|
|
1413
|
+
throw new Error("invalid secretKey: hash check failed");
|
|
1414
|
+
const [sk, publicKey, publicKeyHash, z3] = secretCoder.decode(secretKey);
|
|
1415
|
+
const msg = KPKE.decrypt(cipherText, sk);
|
|
1416
|
+
const kr = HASH512.create().update(msg).update(publicKeyHash).digest();
|
|
1417
|
+
const Khat = kr.subarray(0, 32);
|
|
1418
|
+
const cipherText2 = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
|
1419
|
+
const isValid = equalBytes(cipherText, cipherText2);
|
|
1420
|
+
const Kbar = KDF.create({ dkLen: 32 }).update(z3).update(cipherText).digest();
|
|
1421
|
+
cleanBytes(msg, cipherText2, !isValid ? Khat : Kbar);
|
|
1422
|
+
return isValid ? Khat : Kbar;
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
function shakePRF(dkLen, key, nonce) {
|
|
1427
|
+
return shake256.create({ dkLen }).update(key).update(new Uint8Array([nonce])).digest();
|
|
1428
|
+
}
|
|
1429
|
+
var opts = /* @__PURE__ */ (() => ({
|
|
1430
|
+
HASH256: sha3_256,
|
|
1431
|
+
HASH512: sha3_512,
|
|
1432
|
+
KDF: shake256,
|
|
1433
|
+
XOF: XOF128,
|
|
1434
|
+
PRF: shakePRF
|
|
1435
|
+
}))();
|
|
1436
|
+
var mk = (params) => createKyber({
|
|
1437
|
+
...opts,
|
|
1438
|
+
...params
|
|
1439
|
+
});
|
|
1440
|
+
var ml_kem768 = /* @__PURE__ */ (() => mk(PARAMS[768]))();
|
|
1441
|
+
|
|
1442
|
+
// ../../node_modules/.pnpm/@noble+post-quantum@0.6.1/node_modules/@noble/post-quantum/hybrid.js
|
|
1443
|
+
function ecKeygen(curve, allowZeroKey = false) {
|
|
1444
|
+
const lengths = curve.lengths;
|
|
1445
|
+
let keygen = curve.keygen;
|
|
1446
|
+
if (allowZeroKey) {
|
|
1447
|
+
if (!("getSharedSecret" in curve && "sign" in curve && "verify" in curve))
|
|
1448
|
+
throw new Error("allowZeroKey requires a Weierstrass curve");
|
|
1449
|
+
const wCurve = curve;
|
|
1450
|
+
const Fn = wCurve.Point.Fn;
|
|
1451
|
+
keygen = (seed = randomBytes(lengths.seed)) => {
|
|
1452
|
+
abytes(seed, lengths.seed, "seed");
|
|
1453
|
+
const seedScalar = Fn.isLE ? bytesToNumberLE(seed) : bytesToNumberBE(seed);
|
|
1454
|
+
const secretKey = Fn.toBytes(Fn.create(seedScalar));
|
|
1455
|
+
return {
|
|
1456
|
+
secretKey,
|
|
1457
|
+
publicKey: curve.getPublicKey(secretKey)
|
|
1458
|
+
};
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
return {
|
|
1462
|
+
lengths: { secretKey: lengths.secretKey, publicKey: lengths.publicKey, seed: lengths.seed },
|
|
1463
|
+
keygen: (seed) => keygen(seed),
|
|
1464
|
+
getPublicKey: (secretKey) => curve.getPublicKey(secretKey)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
function ecdhKem(curve, allowZeroKey = false) {
|
|
1468
|
+
const kg = ecKeygen(curve, allowZeroKey);
|
|
1469
|
+
if (!curve.getSharedSecret)
|
|
1470
|
+
throw new Error("wrong curve");
|
|
1471
|
+
return {
|
|
1472
|
+
lengths: { ...kg.lengths, msg: kg.lengths.seed, cipherText: kg.lengths.publicKey },
|
|
1473
|
+
keygen: kg.keygen,
|
|
1474
|
+
getPublicKey: kg.getPublicKey,
|
|
1475
|
+
encapsulate(publicKey, rand = randomBytes(curve.lengths.seed)) {
|
|
1476
|
+
const seed = copyBytes(rand);
|
|
1477
|
+
let ek = void 0;
|
|
1478
|
+
try {
|
|
1479
|
+
ek = this.keygen(seed).secretKey;
|
|
1480
|
+
const sharedSecret = this.decapsulate(publicKey, ek);
|
|
1481
|
+
const cipherText = curve.getPublicKey(ek);
|
|
1482
|
+
return { sharedSecret, cipherText };
|
|
1483
|
+
} finally {
|
|
1484
|
+
cleanBytes(seed);
|
|
1485
|
+
if (ek)
|
|
1486
|
+
cleanBytes(ek);
|
|
1487
|
+
}
|
|
1488
|
+
},
|
|
1489
|
+
decapsulate(cipherText, secretKey) {
|
|
1490
|
+
const res = curve.getSharedSecret(secretKey, cipherText);
|
|
1491
|
+
return curve.lengths.publicKeyHasPrefix ? res.subarray(1) : res;
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
function splitLengths(lst, name) {
|
|
1496
|
+
return splitCoder(name, ...lst.map((i) => {
|
|
1497
|
+
if (typeof i.lengths[name] !== "number")
|
|
1498
|
+
throw new Error("wrong length: " + name);
|
|
1499
|
+
return i.lengths[name];
|
|
1500
|
+
}));
|
|
1501
|
+
}
|
|
1502
|
+
function expandSeedXof(xof) {
|
|
1503
|
+
return ((seed, seedLen) => xof(seed, { dkLen: seedLen }));
|
|
1504
|
+
}
|
|
1505
|
+
function combineKeys(realSeedLen, expandSeed_, ...ck_) {
|
|
1506
|
+
const expandSeed = expandSeed_;
|
|
1507
|
+
const ck = ck_;
|
|
1508
|
+
const seedCoder = splitLengths(ck, "seed");
|
|
1509
|
+
const pkCoder = splitLengths(ck, "publicKey");
|
|
1510
|
+
anumber(realSeedLen);
|
|
1511
|
+
function expandDecapsulationKey(seed) {
|
|
1512
|
+
abytes(seed, realSeedLen);
|
|
1513
|
+
const expandedRaw = expandSeed(seed, seedCoder.bytesLen);
|
|
1514
|
+
const expandedSeed = expandedRaw.buffer === seed.buffer ? copyBytes(expandedRaw) : expandedRaw;
|
|
1515
|
+
const expanded = [];
|
|
1516
|
+
const keySecret = [];
|
|
1517
|
+
const secretKey = [];
|
|
1518
|
+
const publicKey = [];
|
|
1519
|
+
let ok = false;
|
|
1520
|
+
try {
|
|
1521
|
+
for (const part of seedCoder.decode(expandedSeed))
|
|
1522
|
+
expanded.push(copyBytes(part));
|
|
1523
|
+
for (let i = 0; i < ck.length; i++) {
|
|
1524
|
+
const keys = ck[i].keygen(expanded[i]);
|
|
1525
|
+
keySecret.push(keys.secretKey);
|
|
1526
|
+
secretKey.push(copyBytes(keys.secretKey));
|
|
1527
|
+
publicKey.push(keys.publicKey);
|
|
1528
|
+
}
|
|
1529
|
+
ok = true;
|
|
1530
|
+
return { secretKey, publicKey };
|
|
1531
|
+
} finally {
|
|
1532
|
+
cleanBytes(expandedSeed, expanded, keySecret);
|
|
1533
|
+
if (!ok)
|
|
1534
|
+
cleanBytes(secretKey);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return {
|
|
1538
|
+
info: { lengths: { seed: realSeedLen, publicKey: pkCoder.bytesLen, secretKey: realSeedLen } },
|
|
1539
|
+
getPublicKey(secretKey) {
|
|
1540
|
+
return this.keygen(secretKey).publicKey;
|
|
1541
|
+
},
|
|
1542
|
+
keygen(seed = randomBytes(realSeedLen)) {
|
|
1543
|
+
const { publicKey: pk, secretKey } = expandDecapsulationKey(seed);
|
|
1544
|
+
try {
|
|
1545
|
+
const publicKey = pkCoder.encode(pk);
|
|
1546
|
+
return { secretKey: seed, publicKey };
|
|
1547
|
+
} finally {
|
|
1548
|
+
cleanBytes(pk);
|
|
1549
|
+
cleanBytes(secretKey);
|
|
1550
|
+
}
|
|
1551
|
+
},
|
|
1552
|
+
expandDecapsulationKey,
|
|
1553
|
+
realSeedLen
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
function combineKEMS(realSeedLen, realMsgLen, expandSeed, combiner, ...kems) {
|
|
1557
|
+
const rawCombiner = combiner;
|
|
1558
|
+
const rawKems = kems;
|
|
1559
|
+
const keys = combineKeys(realSeedLen, expandSeed, ...rawKems);
|
|
1560
|
+
const ctCoder = splitLengths(rawKems, "cipherText");
|
|
1561
|
+
const pkCoder = splitLengths(rawKems, "publicKey");
|
|
1562
|
+
const msgCoder = splitLengths(rawKems, "msg");
|
|
1563
|
+
anumber(realMsgLen);
|
|
1564
|
+
const lengths = Object.freeze({
|
|
1565
|
+
...keys.info.lengths,
|
|
1566
|
+
msg: realMsgLen,
|
|
1567
|
+
msgRand: msgCoder.bytesLen,
|
|
1568
|
+
cipherText: ctCoder.bytesLen
|
|
1569
|
+
});
|
|
1570
|
+
return Object.freeze({
|
|
1571
|
+
lengths,
|
|
1572
|
+
getPublicKey: keys.getPublicKey,
|
|
1573
|
+
keygen: keys.keygen,
|
|
1574
|
+
encapsulate(pk, randomness = randomBytes(msgCoder.bytesLen)) {
|
|
1575
|
+
const pks = pkCoder.decode(pk);
|
|
1576
|
+
const rand = msgCoder.decode(randomness);
|
|
1577
|
+
const sharedSecret = [];
|
|
1578
|
+
const cipherText = [];
|
|
1579
|
+
try {
|
|
1580
|
+
for (let i = 0; i < rawKems.length; i++) {
|
|
1581
|
+
const enc = rawKems[i].encapsulate(pks[i], rand[i]);
|
|
1582
|
+
sharedSecret.push(enc.sharedSecret);
|
|
1583
|
+
cipherText.push(enc.cipherText);
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
// Detach the combiner result before cleanup: a caller-provided combiner may alias one of
|
|
1587
|
+
// the child sharedSecret buffers, and those child buffers are zeroized immediately below.
|
|
1588
|
+
sharedSecret: copyBytes(rawCombiner(pks, cipherText, sharedSecret)),
|
|
1589
|
+
cipherText: ctCoder.encode(cipherText)
|
|
1590
|
+
};
|
|
1591
|
+
} finally {
|
|
1592
|
+
cleanBytes(sharedSecret, cipherText);
|
|
1593
|
+
}
|
|
1594
|
+
},
|
|
1595
|
+
decapsulate(ct, seed) {
|
|
1596
|
+
const cts = ctCoder.decode(ct);
|
|
1597
|
+
const { publicKey, secretKey } = keys.expandDecapsulationKey(seed);
|
|
1598
|
+
const sharedSecret = rawKems.map((i, j) => i.decapsulate(cts[j], secretKey[j]));
|
|
1599
|
+
try {
|
|
1600
|
+
return copyBytes(rawCombiner(publicKey, cts, sharedSecret));
|
|
1601
|
+
} finally {
|
|
1602
|
+
cleanBytes(secretKey, sharedSecret);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
var x25519kem = /* @__PURE__ */ ecdhKem(x25519);
|
|
1608
|
+
var ml_kem768_x25519 = /* @__PURE__ */ (() => combineKEMS(
|
|
1609
|
+
32,
|
|
1610
|
+
32,
|
|
1611
|
+
expandSeedXof(shake256),
|
|
1612
|
+
// Awesome label, so much escaping hell in a single line.
|
|
1613
|
+
(pk, ct, ss) => sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes("\\.//^\\"))),
|
|
1614
|
+
ml_kem768,
|
|
1615
|
+
x25519kem
|
|
1616
|
+
))();
|
|
1617
|
+
var XWing = /* @__PURE__ */ (() => ml_kem768_x25519)();
|
|
1618
|
+
function chacha20Poly1305Encrypt(opts2) {
|
|
1619
|
+
return chacha20poly1305(opts2.key, opts2.nonce, opts2.aad).encrypt(opts2.plaintext);
|
|
1620
|
+
}
|
|
1621
|
+
function xchacha20Poly1305Encrypt(opts2) {
|
|
1622
|
+
return xchacha20poly1305(opts2.key, opts2.nonce, opts2.aad).encrypt(opts2.plaintext);
|
|
1623
|
+
}
|
|
1624
|
+
function hkdfSha256(opts2) {
|
|
1625
|
+
return hkdf(sha256$1, opts2.ikm, opts2.salt, opts2.info, opts2.length);
|
|
1626
|
+
}
|
|
1627
|
+
var MLKEM768X25519_PUBLIC_KEY_LENGTH = 1216;
|
|
1628
|
+
var MLKEM768X25519_ENC_LENGTH = 1120;
|
|
1629
|
+
var MLKEM768X25519_ESEED_LENGTH = 64;
|
|
1630
|
+
function mlkem768x25519Encapsulate(opts2) {
|
|
1631
|
+
if (opts2.publicKey.length !== MLKEM768X25519_PUBLIC_KEY_LENGTH) {
|
|
1632
|
+
throw new Error(
|
|
1633
|
+
`mlkem768x25519 public key must be ${MLKEM768X25519_PUBLIC_KEY_LENGTH} bytes, got ${opts2.publicKey.length}`
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
if (opts2.eseed !== void 0 && opts2.eseed.length !== MLKEM768X25519_ESEED_LENGTH) {
|
|
1637
|
+
throw new Error(
|
|
1638
|
+
`mlkem768x25519 eseed must be ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${opts2.eseed.length}`
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
const { cipherText, sharedSecret } = XWing.encapsulate(opts2.publicKey, opts2.eseed);
|
|
1642
|
+
return { enc: cipherText, ss: sharedSecret };
|
|
1643
|
+
}
|
|
1644
|
+
var X25519LowOrderPointError = class extends Error {
|
|
1645
|
+
code = "X25519_LOW_ORDER_POINT";
|
|
1646
|
+
constructor(options) {
|
|
1647
|
+
super("x25519 ECDH rejected: peer public key is a small-order point", options);
|
|
1648
|
+
this.name = "X25519LowOrderPointError";
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
var NOBLE_LOW_ORDER_MESSAGE = "invalid private or public key received";
|
|
1652
|
+
function x25519PublicKey(opts2) {
|
|
1653
|
+
return x25519.getPublicKey(opts2.secretKey);
|
|
1654
|
+
}
|
|
1655
|
+
function x25519Ecdh(opts2) {
|
|
1656
|
+
try {
|
|
1657
|
+
return x25519.getSharedSecret(opts2.secretKey, opts2.theirPublicKey);
|
|
1658
|
+
} catch (e) {
|
|
1659
|
+
if (e instanceof Error && e.message === NOBLE_LOW_ORDER_MESSAGE) {
|
|
1660
|
+
throw new X25519LowOrderPointError({ cause: e });
|
|
1661
|
+
}
|
|
1662
|
+
throw e;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
var EciesSealedPoeError = class extends Error {
|
|
1666
|
+
code;
|
|
1667
|
+
constructor(code, message, options) {
|
|
1668
|
+
super(message, options);
|
|
1669
|
+
this.name = "EciesSealedPoeError";
|
|
1670
|
+
this.code = code;
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
function encodeCanonicalCbor4(value) {
|
|
1674
|
+
return encode(value, {
|
|
1675
|
+
cde: true,
|
|
1676
|
+
collapseBigInts: true,
|
|
1677
|
+
rejectDuplicateKeys: true,
|
|
1678
|
+
sortKeys: sortCoreDeterministic
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
var CHUNK_MAX_BYTES2 = 64;
|
|
1682
|
+
function chunkKemCt(value) {
|
|
1683
|
+
if (value.length === 0) {
|
|
1684
|
+
throw new Error("chunkKemCt: refusing to chunk an empty byte string");
|
|
1685
|
+
}
|
|
1686
|
+
const chunks = [];
|
|
1687
|
+
for (let i = 0; i < value.length; i += CHUNK_MAX_BYTES2) {
|
|
1688
|
+
chunks.push(value.subarray(i, Math.min(i + CHUNK_MAX_BYTES2, value.length)));
|
|
1689
|
+
}
|
|
1690
|
+
return chunks;
|
|
1691
|
+
}
|
|
1692
|
+
function joinKemCt(chunks) {
|
|
1693
|
+
let total = 0;
|
|
1694
|
+
for (const c of chunks) total += c.length;
|
|
1695
|
+
const out = new Uint8Array(total);
|
|
1696
|
+
let offset = 0;
|
|
1697
|
+
for (const c of chunks) {
|
|
1698
|
+
out.set(c, offset);
|
|
1699
|
+
offset += c.length;
|
|
1700
|
+
}
|
|
1701
|
+
return out;
|
|
1702
|
+
}
|
|
1703
|
+
function slotsToMacCbor(slots, kem) {
|
|
1704
|
+
let value;
|
|
1705
|
+
if (kem === "x25519") {
|
|
1706
|
+
value = slots.map((s) => ({ epk: s.epk, wrap: s.wrap }));
|
|
1707
|
+
} else {
|
|
1708
|
+
value = slots.map((s) => ({
|
|
1709
|
+
// Canonicalize the chunk boundaries before the MAC commits to them:
|
|
1710
|
+
// reassemble the logical ciphertext and re-split into canonical ≤ 64-byte
|
|
1711
|
+
// chunks. The on-wire `kem_ct` array is a transport detail (the Cardano
|
|
1712
|
+
// ledger's 64-byte metadatum cap), and a hostile or non-canonical chunking
|
|
1713
|
+
// ([1, 63, …] instead of [64, …]) reassembles to the SAME bytes — so the
|
|
1714
|
+
// MAC must be invariant to it. Committing to the verbatim wire chunks would
|
|
1715
|
+
// let an attacker re-chunk an honest envelope and break the slots_mac match
|
|
1716
|
+
// for an honest recipient. Honest (already-64B-chunked) records are
|
|
1717
|
+
// unchanged; a real byte flip still changes the reassembled bytes and is
|
|
1718
|
+
// still rejected.
|
|
1719
|
+
kem_ct: chunkKemCt(joinKemCt(s.kem_ct)),
|
|
1720
|
+
wrap: s.wrap
|
|
1721
|
+
}));
|
|
1722
|
+
}
|
|
1723
|
+
return encodeCanonicalCbor4(value);
|
|
1724
|
+
}
|
|
1725
|
+
var CARDANO_POE_HKDF_INFO_KEK = new TextEncoder().encode("cardano-poe-kek-v1");
|
|
1726
|
+
var CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 = new TextEncoder().encode(
|
|
1727
|
+
"cardano-poe-kek-mlkem768x25519-v1"
|
|
1728
|
+
);
|
|
1729
|
+
var CARDANO_POE_HKDF_INFO_SLOTS_MAC = new TextEncoder().encode(
|
|
1730
|
+
"cardano-poe-slots-mac-v1"
|
|
1731
|
+
);
|
|
1732
|
+
var ZERO_NONCE_12 = new Uint8Array(12);
|
|
1733
|
+
var EMPTY_SALT = new Uint8Array(0);
|
|
1734
|
+
var X25519_PUBLIC_KEY_LENGTH = 32;
|
|
1735
|
+
var X25519_SECRET_KEY_LENGTH = 32;
|
|
1736
|
+
var CEK_LENGTH = 32;
|
|
1737
|
+
var NONCE_LENGTH = 24;
|
|
1738
|
+
var WRAP_LENGTH = 48;
|
|
1739
|
+
var SLOTS_MAC_LENGTH = 32;
|
|
1740
|
+
if (CARDANO_POE_HKDF_INFO_KEK.length !== 18) {
|
|
1741
|
+
throw new Error("CARDANO_POE_HKDF_INFO_KEK byte-length invariant violated (expected 18)");
|
|
1742
|
+
}
|
|
1743
|
+
if (CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519.length !== 33) {
|
|
1744
|
+
throw new Error(
|
|
1745
|
+
"CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519 byte-length invariant violated (expected 33)"
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
if (CARDANO_POE_HKDF_INFO_SLOTS_MAC.length !== 24) {
|
|
1749
|
+
throw new Error("CARDANO_POE_HKDF_INFO_SLOTS_MAC byte-length invariant violated (expected 24)");
|
|
1750
|
+
}
|
|
1751
|
+
if (ZERO_NONCE_12.length !== 12) {
|
|
1752
|
+
throw new Error("ZERO_NONCE_12 byte-length invariant violated (expected 12)");
|
|
1753
|
+
}
|
|
1754
|
+
function concat(a, b) {
|
|
1755
|
+
const out = new Uint8Array(a.length + b.length);
|
|
1756
|
+
out.set(a, 0);
|
|
1757
|
+
out.set(b, a.length);
|
|
1758
|
+
return out;
|
|
1759
|
+
}
|
|
1760
|
+
function uniformIndexBelow(m) {
|
|
1761
|
+
const limit = 4294967296 - 4294967296 % m;
|
|
1762
|
+
const buf = new Uint32Array(1);
|
|
1763
|
+
let x;
|
|
1764
|
+
do {
|
|
1765
|
+
crypto.getRandomValues(buf);
|
|
1766
|
+
x = buf[0];
|
|
1767
|
+
} while (x >= limit);
|
|
1768
|
+
return x % m;
|
|
1769
|
+
}
|
|
1770
|
+
function csprngShuffle(arr) {
|
|
1771
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
1772
|
+
const j = uniformIndexBelow(i + 1);
|
|
1773
|
+
const tmp = arr[i];
|
|
1774
|
+
arr[i] = arr[j];
|
|
1775
|
+
arr[j] = tmp;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function wrapSlotX25519(args) {
|
|
1779
|
+
const privEph = args.privEph ?? randomBytes$1(X25519_SECRET_KEY_LENGTH);
|
|
1780
|
+
if (privEph.length !== X25519_SECRET_KEY_LENGTH) {
|
|
1781
|
+
throw new EciesSealedPoeError(
|
|
1782
|
+
"INVALID_EPHEMERAL_SECRET_LENGTH",
|
|
1783
|
+
`ephemeralSecrets[${args.slotIdx}] MUST be exactly ${X25519_SECRET_KEY_LENGTH} bytes, got ${privEph.length}`
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
const epk = x25519PublicKey({ secretKey: privEph });
|
|
1787
|
+
const shared = x25519Ecdh({ secretKey: privEph, theirPublicKey: args.pubR });
|
|
1788
|
+
const kek = hkdfSha256({
|
|
1789
|
+
ikm: shared,
|
|
1790
|
+
salt: concat(epk, args.pubR),
|
|
1791
|
+
info: CARDANO_POE_HKDF_INFO_KEK,
|
|
1792
|
+
length: 32
|
|
1793
|
+
});
|
|
1794
|
+
const wrap = chacha20Poly1305Encrypt({
|
|
1795
|
+
key: kek,
|
|
1796
|
+
nonce: ZERO_NONCE_12,
|
|
1797
|
+
aad: CARDANO_POE_HKDF_INFO_KEK,
|
|
1798
|
+
plaintext: args.cek
|
|
1799
|
+
});
|
|
1800
|
+
if (wrap.length !== WRAP_LENGTH) {
|
|
1801
|
+
throw new Error(`internal: wrap.length=${wrap.length}, expected ${WRAP_LENGTH}`);
|
|
1802
|
+
}
|
|
1803
|
+
return { epk, wrap };
|
|
1804
|
+
}
|
|
1805
|
+
function wrapSlotMlkem768X25519(args) {
|
|
1806
|
+
const { enc, ss } = mlkem768x25519Encapsulate({
|
|
1807
|
+
publicKey: args.pubR,
|
|
1808
|
+
...args.eseed !== void 0 ? { eseed: args.eseed } : {}
|
|
1809
|
+
});
|
|
1810
|
+
if (enc.length !== MLKEM768X25519_ENC_LENGTH) {
|
|
1811
|
+
throw new Error(`internal: enc.length=${enc.length}, expected ${MLKEM768X25519_ENC_LENGTH}`);
|
|
1812
|
+
}
|
|
1813
|
+
const kek = hkdfSha256({
|
|
1814
|
+
ikm: ss,
|
|
1815
|
+
salt: EMPTY_SALT,
|
|
1816
|
+
info: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1817
|
+
length: 32
|
|
1818
|
+
});
|
|
1819
|
+
const wrap = chacha20Poly1305Encrypt({
|
|
1820
|
+
key: kek,
|
|
1821
|
+
nonce: ZERO_NONCE_12,
|
|
1822
|
+
aad: CARDANO_POE_HKDF_INFO_KEK_MLKEM768X25519,
|
|
1823
|
+
plaintext: args.cek
|
|
1824
|
+
});
|
|
1825
|
+
if (wrap.length !== WRAP_LENGTH) {
|
|
1826
|
+
throw new Error(`internal: wrap.length=${wrap.length}, expected ${WRAP_LENGTH}`);
|
|
1827
|
+
}
|
|
1828
|
+
return { kem_ct: chunkKemCt(enc), wrap };
|
|
1829
|
+
}
|
|
1830
|
+
function eciesSealedPoeWrap(args) {
|
|
1831
|
+
const { plaintext, recipientPublicKeys } = args;
|
|
1832
|
+
const kem = args.kem ?? "x25519";
|
|
1833
|
+
const n = recipientPublicKeys.length;
|
|
1834
|
+
if (n < 1) {
|
|
1835
|
+
throw new EciesSealedPoeError(
|
|
1836
|
+
"ENC_SLOTS_EMPTY",
|
|
1837
|
+
`recipientPublicKeys.length=${n} must be >= 1`
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
const expectedPubLen = kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH : MLKEM768X25519_PUBLIC_KEY_LENGTH;
|
|
1841
|
+
for (let i = 0; i < n; i++) {
|
|
1842
|
+
const pub = recipientPublicKeys[i];
|
|
1843
|
+
if (pub === void 0 || pub.length !== expectedPubLen) {
|
|
1844
|
+
throw new EciesSealedPoeError(
|
|
1845
|
+
"KEM_EPK_LENGTH_MISMATCH",
|
|
1846
|
+
`recipientPublicKeys[${i}] MUST be exactly ${expectedPubLen} bytes for kem='${kem}'`
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
if (kem === "x25519") {
|
|
1851
|
+
if (args.eseeds !== void 0) {
|
|
1852
|
+
throw new EciesSealedPoeError(
|
|
1853
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
1854
|
+
"eseeds is an X-Wing (mlkem768x25519) override and MUST NOT be supplied for kem='x25519'"
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
if (args.ephemeralSecrets !== void 0 && args.ephemeralSecrets.length !== n) {
|
|
1858
|
+
throw new EciesSealedPoeError(
|
|
1859
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
1860
|
+
`ephemeralSecrets.length=${args.ephemeralSecrets.length} must match recipientPublicKeys.length=${n}`
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
} else {
|
|
1864
|
+
if (args.ephemeralSecrets !== void 0) {
|
|
1865
|
+
throw new EciesSealedPoeError(
|
|
1866
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
1867
|
+
"ephemeralSecrets is an X25519 override and MUST NOT be supplied for kem='mlkem768x25519'"
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
if (args.eseeds !== void 0) {
|
|
1871
|
+
if (args.eseeds.length !== n) {
|
|
1872
|
+
throw new EciesSealedPoeError(
|
|
1873
|
+
"EPHEMERAL_SECRETS_COUNT_MISMATCH",
|
|
1874
|
+
`eseeds.length=${args.eseeds.length} must match recipientPublicKeys.length=${n}`
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
for (let i = 0; i < n; i++) {
|
|
1878
|
+
const eseed = args.eseeds[i];
|
|
1879
|
+
if (eseed.length !== MLKEM768X25519_ESEED_LENGTH) {
|
|
1880
|
+
throw new EciesSealedPoeError(
|
|
1881
|
+
"INVALID_EPHEMERAL_SECRET_LENGTH",
|
|
1882
|
+
`eseeds[${i}] MUST be exactly ${MLKEM768X25519_ESEED_LENGTH} bytes, got ${eseed.length}`
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
const cek = args.cek ?? randomBytes$1(CEK_LENGTH);
|
|
1889
|
+
const nonce = args.nonce ?? randomBytes$1(NONCE_LENGTH);
|
|
1890
|
+
if (cek.length !== CEK_LENGTH) {
|
|
1891
|
+
throw new EciesSealedPoeError(
|
|
1892
|
+
"INVALID_CEK_LENGTH",
|
|
1893
|
+
`cek MUST be exactly ${CEK_LENGTH} bytes, got ${cek.length}`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
if (nonce.length !== NONCE_LENGTH) {
|
|
1897
|
+
throw new EciesSealedPoeError(
|
|
1898
|
+
"NONCE_LENGTH_MISMATCH",
|
|
1899
|
+
`nonce MUST be exactly ${NONCE_LENGTH} bytes, got ${nonce.length}`
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
let envelope;
|
|
1903
|
+
if (kem === "x25519") {
|
|
1904
|
+
const slots = [];
|
|
1905
|
+
for (let i = 0; i < n; i++) {
|
|
1906
|
+
slots.push(
|
|
1907
|
+
wrapSlotX25519({
|
|
1908
|
+
pubR: recipientPublicKeys[i],
|
|
1909
|
+
privEph: args.ephemeralSecrets ? args.ephemeralSecrets[i] : void 0,
|
|
1910
|
+
cek,
|
|
1911
|
+
slotIdx: i
|
|
1912
|
+
})
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
if (args.skipShuffle !== true) {
|
|
1916
|
+
csprngShuffle(slots);
|
|
1917
|
+
}
|
|
1918
|
+
const slotsMac = computeSlotsMac(cek, slots, "x25519");
|
|
1919
|
+
envelope = {
|
|
1920
|
+
scheme: 1,
|
|
1921
|
+
aead: "xchacha20-poly1305",
|
|
1922
|
+
kem: "x25519",
|
|
1923
|
+
nonce,
|
|
1924
|
+
slots,
|
|
1925
|
+
slots_mac: slotsMac
|
|
1926
|
+
};
|
|
1927
|
+
} else {
|
|
1928
|
+
const slots = [];
|
|
1929
|
+
for (let i = 0; i < n; i++) {
|
|
1930
|
+
slots.push(
|
|
1931
|
+
wrapSlotMlkem768X25519({
|
|
1932
|
+
pubR: recipientPublicKeys[i],
|
|
1933
|
+
eseed: args.eseeds ? args.eseeds[i] : void 0,
|
|
1934
|
+
cek
|
|
1935
|
+
})
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
if (args.skipShuffle !== true) {
|
|
1939
|
+
csprngShuffle(slots);
|
|
1940
|
+
}
|
|
1941
|
+
const slotsMac = computeSlotsMac(cek, slots, "mlkem768x25519");
|
|
1942
|
+
envelope = {
|
|
1943
|
+
scheme: 1,
|
|
1944
|
+
aead: "xchacha20-poly1305",
|
|
1945
|
+
kem: "mlkem768x25519",
|
|
1946
|
+
nonce,
|
|
1947
|
+
slots,
|
|
1948
|
+
slots_mac: slotsMac
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
const adContent = concat(nonce, envelope.slots_mac);
|
|
1952
|
+
const ciphertext = xchacha20Poly1305Encrypt({
|
|
1953
|
+
key: cek,
|
|
1954
|
+
nonce,
|
|
1955
|
+
aad: adContent,
|
|
1956
|
+
plaintext
|
|
1957
|
+
});
|
|
1958
|
+
return { envelope, ciphertext };
|
|
1959
|
+
}
|
|
1960
|
+
function computeSlotsMac(cek, slots, kem) {
|
|
1961
|
+
const hmacKey = hkdfSha256({
|
|
1962
|
+
ikm: cek,
|
|
1963
|
+
salt: EMPTY_SALT,
|
|
1964
|
+
info: CARDANO_POE_HKDF_INFO_SLOTS_MAC,
|
|
1965
|
+
length: 32
|
|
1966
|
+
});
|
|
1967
|
+
const slotsCbor = slotsToMacCbor(slots, kem);
|
|
1968
|
+
const slotsMac = hmac(sha256$1, hmacKey, slotsCbor);
|
|
1969
|
+
if (slotsMac.length !== SLOTS_MAC_LENGTH) {
|
|
1970
|
+
throw new Error(`internal: slots_mac.length=${slotsMac.length}, expected ${SLOTS_MAC_LENGTH}`);
|
|
1971
|
+
}
|
|
1972
|
+
return slotsMac;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// src/client/partial-upload-error.ts
|
|
1976
|
+
var PartialUploadError = class extends Error {
|
|
1977
|
+
response;
|
|
1978
|
+
failed;
|
|
1979
|
+
constructor(response) {
|
|
1980
|
+
const failed = response.uploads.filter((u) => u.ok === false);
|
|
1981
|
+
super(
|
|
1982
|
+
`${failed.length} of ${response.uploads.length} upload(s) failed: ${failed.map((f) => `[${f.idx}] ${f.error.code} \u2014 ${f.error.detail}`).join("; ")}`
|
|
1983
|
+
);
|
|
1984
|
+
this.name = "PartialUploadError";
|
|
1985
|
+
this.response = response;
|
|
1986
|
+
this.failed = failed;
|
|
1987
|
+
}
|
|
1988
|
+
/** Convenience: the `idx` of every failed entry, in input order. */
|
|
1989
|
+
get failedIndices() {
|
|
1990
|
+
return this.failed.map((f) => f.idx);
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
|
|
1994
|
+
// src/client/publish.ts
|
|
1995
|
+
var ED25519_PUBLIC_KEY_LENGTH2 = 32;
|
|
1996
|
+
var ED25519_SIGNATURE_LENGTH2 = 64;
|
|
1997
|
+
var X25519_PUBLIC_KEY_LENGTH2 = 32;
|
|
1998
|
+
var MLKEM768X25519_PUBLIC_KEY_LENGTH2 = 1216;
|
|
1999
|
+
var LEAF_DIGEST_LENGTH = 32;
|
|
2000
|
+
var STORAGE_TARGET_ARWEAVE = "arweave";
|
|
2001
|
+
var PublishError = class extends Error {
|
|
2002
|
+
code;
|
|
2003
|
+
constructor(code, message) {
|
|
2004
|
+
super(message);
|
|
2005
|
+
this.name = "PublishError";
|
|
2006
|
+
this.code = code;
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
function bytesToHex2(bytes) {
|
|
2010
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2011
|
+
}
|
|
2012
|
+
function hexToBytes(hex) {
|
|
2013
|
+
if (hex.length % 2 !== 0) {
|
|
2014
|
+
throw new PublishError("INVALID_LEAVES", `hex string has odd length: ${hex.length}`);
|
|
2015
|
+
}
|
|
2016
|
+
const out = new Uint8Array(hex.length / 2);
|
|
2017
|
+
for (let i = 0; i < out.length; i++) {
|
|
2018
|
+
const byte = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
2019
|
+
if (Number.isNaN(byte)) {
|
|
2020
|
+
throw new PublishError("INVALID_LEAVES", `invalid hex byte at offset ${i * 2}`);
|
|
2021
|
+
}
|
|
2022
|
+
out[i] = byte;
|
|
2023
|
+
}
|
|
2024
|
+
return out;
|
|
2025
|
+
}
|
|
2026
|
+
function toBytes(content) {
|
|
2027
|
+
if (typeof content === "string") return new TextEncoder().encode(content);
|
|
2028
|
+
return content;
|
|
2029
|
+
}
|
|
2030
|
+
function cloneToOwnedBuffer(src) {
|
|
2031
|
+
const out = new Uint8Array(new ArrayBuffer(src.length));
|
|
2032
|
+
out.set(src);
|
|
2033
|
+
return out;
|
|
2034
|
+
}
|
|
2035
|
+
function hashContent(bytes, alg) {
|
|
2036
|
+
if (alg === "sha2-256") return cloneToOwnedBuffer(sha256(bytes));
|
|
2037
|
+
if (alg === "blake2b-256") return cloneToOwnedBuffer(blake2b256(bytes));
|
|
2038
|
+
throw new PublishError(
|
|
2039
|
+
"UNSUPPORTED_HASH_ALG",
|
|
2040
|
+
`hashAlg must be 'sha2-256' or 'blake2b-256', got '${alg}'`
|
|
2041
|
+
);
|
|
2042
|
+
}
|
|
2043
|
+
function assertSigner(signer) {
|
|
2044
|
+
if (!(signer.signerPubkey instanceof Uint8Array) || signer.signerPubkey.length !== ED25519_PUBLIC_KEY_LENGTH2) {
|
|
2045
|
+
throw new PublishError(
|
|
2046
|
+
"INVALID_SIGNER_PUBKEY",
|
|
2047
|
+
`signer.signerPubkey must be a Uint8Array(${ED25519_PUBLIC_KEY_LENGTH2})`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
if (typeof signer.sign !== "function") {
|
|
2051
|
+
throw new PublishError("INVALID_SIGNER_PUBKEY", "signer.sign must be a function");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
function buildJsonHeaders(apiKey, idempotencyKey) {
|
|
2055
|
+
const headers = new Headers({ "content-type": "application/json", accept: "application/json" });
|
|
2056
|
+
if (apiKey !== void 0) headers.set("authorization", `Bearer ${apiKey}`);
|
|
2057
|
+
if (idempotencyKey !== void 0) headers.set("idempotency-key", idempotencyKey);
|
|
2058
|
+
return headers;
|
|
2059
|
+
}
|
|
2060
|
+
function buildMultipartHeaders(apiKey, idempotencyKey) {
|
|
2061
|
+
const headers = new Headers({ accept: "application/json" });
|
|
2062
|
+
if (apiKey !== void 0) headers.set("authorization", `Bearer ${apiKey}`);
|
|
2063
|
+
if (idempotencyKey !== void 0) headers.set("idempotency-key", idempotencyKey);
|
|
2064
|
+
return headers;
|
|
2065
|
+
}
|
|
2066
|
+
async function readJson2(response) {
|
|
2067
|
+
const text = await response.text();
|
|
2068
|
+
if (text.length === 0) return null;
|
|
2069
|
+
try {
|
|
2070
|
+
return JSON.parse(text);
|
|
2071
|
+
} catch {
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function parseRetryAfter2(header) {
|
|
2076
|
+
if (header === null) return void 0;
|
|
2077
|
+
const parsed = Number(header);
|
|
2078
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2079
|
+
}
|
|
2080
|
+
async function throwIfNotOk2(response) {
|
|
2081
|
+
if (response.ok) return;
|
|
2082
|
+
const body = await readJson2(response);
|
|
2083
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
2084
|
+
const retryAfterSeconds = parseRetryAfter2(response.headers.get("retry-after"));
|
|
2085
|
+
throw parseHttpError({ httpStatus: response.status, body, requestId, retryAfterSeconds });
|
|
2086
|
+
}
|
|
2087
|
+
async function signAndEncodeRecord(record, signer) {
|
|
2088
|
+
const { sigStructureBytes } = prepareSigStructure({
|
|
2089
|
+
record,
|
|
2090
|
+
signerPubkey: signer.signerPubkey
|
|
2091
|
+
});
|
|
2092
|
+
const signature = await signer.sign(sigStructureBytes);
|
|
2093
|
+
if (!(signature instanceof Uint8Array) || signature.length !== ED25519_SIGNATURE_LENGTH2) {
|
|
2094
|
+
throw new PublishError(
|
|
2095
|
+
"INVALID_SIGNER_SIGNATURE",
|
|
2096
|
+
`signer.sign() must return a Uint8Array(${ED25519_SIGNATURE_LENGTH2}); got length ${signature instanceof Uint8Array ? signature.length : "non-Uint8Array"}`
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
const { sigEntry } = assembleCoseSign1({
|
|
2100
|
+
signerPubkey: signer.signerPubkey,
|
|
2101
|
+
signature
|
|
2102
|
+
});
|
|
2103
|
+
const signed = { ...record, sigs: [sigEntry] };
|
|
2104
|
+
return encodePoeRecord(signed);
|
|
2105
|
+
}
|
|
2106
|
+
async function encodeRecord(record, signer) {
|
|
2107
|
+
if (signer === void 0) return encodePoeRecord(record);
|
|
2108
|
+
return signAndEncodeRecord(record, signer);
|
|
2109
|
+
}
|
|
2110
|
+
async function postPublish(config, recordBytesHex, quoteId, idempotencyKey) {
|
|
2111
|
+
const body = { record: recordBytesHex, quote_id: quoteId };
|
|
2112
|
+
const response = await config.fetch(`${config.baseUrl}/api/v1/poe/publish`, {
|
|
2113
|
+
method: "POST",
|
|
2114
|
+
headers: buildJsonHeaders(config.apiKey, idempotencyKey),
|
|
2115
|
+
body: JSON.stringify(body)
|
|
2116
|
+
});
|
|
2117
|
+
await throwIfNotOk2(response);
|
|
2118
|
+
const parsed = await readJson2(response);
|
|
2119
|
+
return { ...parsed, dedup_hit: response.status === 200 };
|
|
2120
|
+
}
|
|
2121
|
+
async function postUploads(config, blobs, idempotencyKey) {
|
|
2122
|
+
const form = new FormData();
|
|
2123
|
+
form.append("target", STORAGE_TARGET_ARWEAVE);
|
|
2124
|
+
for (let idx = 0; idx < blobs.length; idx++) {
|
|
2125
|
+
const bytes = blobs[idx];
|
|
2126
|
+
form.append(
|
|
2127
|
+
`file_${idx}`,
|
|
2128
|
+
new Blob([bytes], { type: "application/octet-stream" }),
|
|
2129
|
+
`file_${idx}.bin`
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
const response = await config.fetch(`${config.baseUrl}/api/v1/poe/uploads`, {
|
|
2133
|
+
method: "POST",
|
|
2134
|
+
headers: buildMultipartHeaders(config.apiKey, idempotencyKey),
|
|
2135
|
+
body: form
|
|
2136
|
+
});
|
|
2137
|
+
await throwIfNotOk2(response);
|
|
2138
|
+
const result = await readJson2(response);
|
|
2139
|
+
const anyFailed = result.uploads.some((u) => u.ok === false);
|
|
2140
|
+
if (anyFailed) {
|
|
2141
|
+
throw new PartialUploadError(result);
|
|
2142
|
+
}
|
|
2143
|
+
return result;
|
|
2144
|
+
}
|
|
2145
|
+
async function publishContent(config, input) {
|
|
2146
|
+
if (input.signer !== void 0) assertSigner(input.signer);
|
|
2147
|
+
const hashAlg = input.hashAlg ?? "sha2-256";
|
|
2148
|
+
const contentBytes = toBytes(input.content);
|
|
2149
|
+
const digest = hashContent(contentBytes, hashAlg);
|
|
2150
|
+
const record = {
|
|
2151
|
+
v: 1,
|
|
2152
|
+
items: [{ hashes: { [hashAlg]: digest } }]
|
|
2153
|
+
};
|
|
2154
|
+
const recordBytes = await encodeRecord(record, input.signer);
|
|
2155
|
+
return postPublish(config, bytesToHex2(recordBytes), input.quoteId, input.idempotencyKey);
|
|
2156
|
+
}
|
|
2157
|
+
var DIGEST_BYTE_LENGTH = {
|
|
2158
|
+
"sha2-256": 32,
|
|
2159
|
+
"blake2b-256": 32
|
|
2160
|
+
};
|
|
2161
|
+
async function publishPrehashed(config, input) {
|
|
2162
|
+
if (input.signer !== void 0) assertSigner(input.signer);
|
|
2163
|
+
const entries = Object.entries(input.hashes);
|
|
2164
|
+
const present = entries.filter(([, hex]) => typeof hex === "string" && hex.length > 0);
|
|
2165
|
+
if (present.length === 0) {
|
|
2166
|
+
throw new PublishError(
|
|
2167
|
+
"INVALID_DIGEST",
|
|
2168
|
+
"publishPrehashed requires at least one digest in `hashes`"
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
const decoded = {};
|
|
2172
|
+
for (const [alg, hex] of present) {
|
|
2173
|
+
if (!(alg in DIGEST_BYTE_LENGTH)) {
|
|
2174
|
+
throw new PublishError(
|
|
2175
|
+
"UNSUPPORTED_HASH_ALG",
|
|
2176
|
+
`unsupported hash algorithm '${alg}' (expected 'sha2-256' or 'blake2b-256')`
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
const bytes = hexToBytes(hex);
|
|
2180
|
+
const expected = DIGEST_BYTE_LENGTH[alg];
|
|
2181
|
+
if (bytes.length !== expected) {
|
|
2182
|
+
throw new PublishError(
|
|
2183
|
+
"INVALID_DIGEST",
|
|
2184
|
+
`hashes[${alg}] must be a ${expected}-byte digest (got ${bytes.length} bytes)`
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
decoded[alg] = cloneToOwnedBuffer(bytes);
|
|
2188
|
+
}
|
|
2189
|
+
const record = {
|
|
2190
|
+
v: 1,
|
|
2191
|
+
items: [{ hashes: decoded }]
|
|
2192
|
+
};
|
|
2193
|
+
const recordBytes = await encodeRecord(record, input.signer);
|
|
2194
|
+
return postPublish(config, bytesToHex2(recordBytes), input.quoteId, input.idempotencyKey);
|
|
2195
|
+
}
|
|
2196
|
+
async function publishSealed(config, input) {
|
|
2197
|
+
if (input.signer !== void 0) assertSigner(input.signer);
|
|
2198
|
+
if (input.recipients.length < 1) {
|
|
2199
|
+
throw new PublishError(
|
|
2200
|
+
"INVALID_RECIPIENT",
|
|
2201
|
+
"publishSealed requires at least one recipient public key"
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
const kem = input.kem ?? "mlkem768x25519";
|
|
2205
|
+
const expectedRecipientLength = kem === "x25519" ? X25519_PUBLIC_KEY_LENGTH2 : MLKEM768X25519_PUBLIC_KEY_LENGTH2;
|
|
2206
|
+
for (let i = 0; i < input.recipients.length; i++) {
|
|
2207
|
+
const pub = input.recipients[i];
|
|
2208
|
+
if (!(pub instanceof Uint8Array) || pub.length !== expectedRecipientLength) {
|
|
2209
|
+
throw new PublishError(
|
|
2210
|
+
"INVALID_RECIPIENT",
|
|
2211
|
+
`recipients[${i}] must be a ${expectedRecipientLength}-byte public key for kem='${kem}'`
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
const hashAlg = input.hashAlg ?? "sha2-256";
|
|
2216
|
+
const plaintext = toBytes(input.content);
|
|
2217
|
+
const plaintextDigest = hashContent(plaintext, hashAlg);
|
|
2218
|
+
const sealed = eciesSealedPoeWrap({
|
|
2219
|
+
plaintext,
|
|
2220
|
+
recipientPublicKeys: input.recipients.map((r) => r),
|
|
2221
|
+
kem
|
|
2222
|
+
});
|
|
2223
|
+
const uploadsResp = await postUploads(config, [sealed.ciphertext], input.idempotencyKey);
|
|
2224
|
+
const uri = uploadsResp.uploads[0].uri;
|
|
2225
|
+
const env = sealed.envelope;
|
|
2226
|
+
const slots = env.kem === "mlkem768x25519" ? env.slots.map((s) => ({
|
|
2227
|
+
kem_ct: s.kem_ct.map((c) => cloneToOwnedBuffer(c)),
|
|
2228
|
+
wrap: cloneToOwnedBuffer(s.wrap)
|
|
2229
|
+
})) : env.slots.map((s) => ({
|
|
2230
|
+
epk: cloneToOwnedBuffer(s.epk),
|
|
2231
|
+
wrap: cloneToOwnedBuffer(s.wrap)
|
|
2232
|
+
}));
|
|
2233
|
+
const envelope = {
|
|
2234
|
+
scheme: 1,
|
|
2235
|
+
aead: env.aead,
|
|
2236
|
+
kem: env.kem,
|
|
2237
|
+
nonce: cloneToOwnedBuffer(env.nonce),
|
|
2238
|
+
slots,
|
|
2239
|
+
slots_mac: cloneToOwnedBuffer(env.slots_mac)
|
|
2240
|
+
};
|
|
2241
|
+
const record = {
|
|
2242
|
+
v: 1,
|
|
2243
|
+
items: [
|
|
2244
|
+
{
|
|
2245
|
+
hashes: { [hashAlg]: plaintextDigest },
|
|
2246
|
+
uris: [chunkUri(uri)],
|
|
2247
|
+
enc: envelope
|
|
2248
|
+
}
|
|
2249
|
+
]
|
|
2250
|
+
};
|
|
2251
|
+
const recordBytes = await encodeRecord(record, input.signer);
|
|
2252
|
+
return postPublish(config, bytesToHex2(recordBytes), input.quoteId, input.idempotencyKey);
|
|
2253
|
+
}
|
|
2254
|
+
async function publishMerkle(config, input) {
|
|
2255
|
+
if (input.signer !== void 0) assertSigner(input.signer);
|
|
2256
|
+
if (input.hashAlg !== void 0 && input.hashAlg !== "sha2-256") {
|
|
2257
|
+
throw new PublishError(
|
|
2258
|
+
"UNSUPPORTED_HASH_ALG",
|
|
2259
|
+
`publishMerkle only supports 'sha2-256' leaves; got '${input.hashAlg}'`
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
if (input.leaves.length < 1) {
|
|
2263
|
+
throw new PublishError("INVALID_LEAVES", "publishMerkle requires at least one leaf hash");
|
|
2264
|
+
}
|
|
2265
|
+
const leaves = input.leaves.map((leaf, idx) => {
|
|
2266
|
+
const bytes = typeof leaf === "string" ? hexToBytes(leaf) : leaf;
|
|
2267
|
+
if (!(bytes instanceof Uint8Array) || bytes.length !== LEAF_DIGEST_LENGTH) {
|
|
2268
|
+
throw new PublishError(
|
|
2269
|
+
"INVALID_LEAVES",
|
|
2270
|
+
`leaves[${idx}] must be a ${LEAF_DIGEST_LENGTH}-byte sha2-256 digest`
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
return bytes;
|
|
2274
|
+
});
|
|
2275
|
+
const root = cloneToOwnedBuffer(merkleSha2256Root(leaves));
|
|
2276
|
+
const leavesListCbor = encodeLeavesList({ leaves, root });
|
|
2277
|
+
const uploadsResp = await postUploads(config, [leavesListCbor], input.idempotencyKey);
|
|
2278
|
+
const uri = uploadsResp.uploads[0].uri;
|
|
2279
|
+
const merkleEntry = {
|
|
2280
|
+
alg: MERKLE_ALG_ID,
|
|
2281
|
+
root,
|
|
2282
|
+
leaf_count: leaves.length,
|
|
2283
|
+
uris: [chunkUri(uri)]
|
|
2284
|
+
};
|
|
2285
|
+
const record = { v: 1, merkle: [merkleEntry] };
|
|
2286
|
+
const recordBytes = await encodeRecord(record, input.signer);
|
|
2287
|
+
const published = await postPublish(
|
|
2288
|
+
config,
|
|
2289
|
+
bytesToHex2(recordBytes),
|
|
2290
|
+
input.quoteId,
|
|
2291
|
+
input.idempotencyKey
|
|
2292
|
+
);
|
|
2293
|
+
return {
|
|
2294
|
+
id: published.id,
|
|
2295
|
+
tx_hash: published.tx_hash,
|
|
2296
|
+
status: published.status,
|
|
2297
|
+
root: bytesToHex2(root),
|
|
2298
|
+
leaf_count: leaves.length,
|
|
2299
|
+
ar_uri: uri,
|
|
2300
|
+
balance_after_usd_micros: published.balance_after_usd_micros
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// src/client/poe.ts
|
|
2305
|
+
function buildJsonHeaders2(args) {
|
|
2306
|
+
const headers = new Headers({ "content-type": "application/json", accept: "application/json" });
|
|
2307
|
+
if (args.apiKey !== void 0) headers.set("authorization", `Bearer ${args.apiKey}`);
|
|
2308
|
+
if (args.idempotencyKey !== void 0) headers.set("idempotency-key", args.idempotencyKey);
|
|
2309
|
+
return headers;
|
|
2310
|
+
}
|
|
2311
|
+
function buildMultipartHeaders2(args) {
|
|
2312
|
+
const headers = new Headers({ accept: "application/json" });
|
|
2313
|
+
if (args.apiKey !== void 0) headers.set("authorization", `Bearer ${args.apiKey}`);
|
|
2314
|
+
if (args.idempotencyKey !== void 0) headers.set("idempotency-key", args.idempotencyKey);
|
|
2315
|
+
return headers;
|
|
2316
|
+
}
|
|
2317
|
+
function toHex(record) {
|
|
2318
|
+
return typeof record === "string" ? record : bytesToHex(record);
|
|
2319
|
+
}
|
|
2320
|
+
var PoeNamespace = class {
|
|
2321
|
+
config;
|
|
2322
|
+
constructor(config) {
|
|
2323
|
+
this.config = config;
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Request an opaque price lock for an upcoming /publish call. The gateway
|
|
2327
|
+
* prices the described publish from the supplied byte counts, records the
|
|
2328
|
+
* lock, and returns a sealed price token: `quote_id`, the total `amount` in
|
|
2329
|
+
* `currency`, and an `expires_at`. The gateway's pricing internals are
|
|
2330
|
+
* deliberately NOT part of the response.
|
|
2331
|
+
*
|
|
2332
|
+
* `amount` is a decimal string; promote it to `BigInt` (or a decimal type)
|
|
2333
|
+
* at the application boundary if you need exact arithmetic.
|
|
2334
|
+
*
|
|
2335
|
+
* Pass the returned `quote_id` to `publish()` (or one of the high-level
|
|
2336
|
+
* `publishContent` / `publishSealed` / `publishMerkle` helpers).
|
|
2337
|
+
*/
|
|
2338
|
+
async quote(input) {
|
|
2339
|
+
const body = {
|
|
2340
|
+
record_bytes: input.recordBytes,
|
|
2341
|
+
recipient_count: input.recipientCount,
|
|
2342
|
+
file_bytes_total: input.fileBytesTotal
|
|
2343
|
+
};
|
|
2344
|
+
const response = await this.config.fetch(`${this.config.baseUrl}/api/v1/poe/quote`, {
|
|
2345
|
+
method: "POST",
|
|
2346
|
+
headers: buildJsonHeaders2({ apiKey: this.config.apiKey }),
|
|
2347
|
+
body: JSON.stringify(body)
|
|
2348
|
+
});
|
|
2349
|
+
await throwIfNotOk(response);
|
|
2350
|
+
return await readJson(response);
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Upload 1..32 binary files to a storage backend. Returns one entry per file
|
|
2354
|
+
* — successful entries carry the `ar://` URI + content hash, failed entries
|
|
2355
|
+
* carry an error code / detail so the caller can retry just the failed
|
|
2356
|
+
* indices.
|
|
2357
|
+
*
|
|
2358
|
+
* Billing: free. The storage cost is part of the publish quote (POST
|
|
2359
|
+
* /api/v1/poe/quote → POST /api/v1/poe/publish) and is debited once at
|
|
2360
|
+
* publish time against the locked price snapshot.
|
|
2361
|
+
*
|
|
2362
|
+
* On HTTP-level failure (auth, rate limit, malformed request) this throws
|
|
2363
|
+
* a typed `Cip309HttpError` subclass. Per-file failures inside a 200
|
|
2364
|
+
* response are NOT thrown by `uploads()` itself — the response body is
|
|
2365
|
+
* returned verbatim so the caller can decide how to react. The
|
|
2366
|
+
* higher-level helpers (`publishSealed`, `publishMerkle`) treat any failed
|
|
2367
|
+
* file as a `PartialUploadError`.
|
|
2368
|
+
*/
|
|
2369
|
+
async uploads(input) {
|
|
2370
|
+
const form = new FormData();
|
|
2371
|
+
form.append("target", input.target);
|
|
2372
|
+
for (let idx = 0; idx < input.data.length; idx++) {
|
|
2373
|
+
const bytes = input.data[idx];
|
|
2374
|
+
form.append(
|
|
2375
|
+
`file_${idx}`,
|
|
2376
|
+
new Blob([bytes], { type: "application/octet-stream" }),
|
|
2377
|
+
`file_${idx}.bin`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
const headers = buildMultipartHeaders2({
|
|
2381
|
+
apiKey: this.config.apiKey,
|
|
2382
|
+
idempotencyKey: input.idempotencyKey
|
|
2383
|
+
});
|
|
2384
|
+
const response = await this.config.fetch(`${this.config.baseUrl}/api/v1/poe/uploads`, {
|
|
2385
|
+
method: "POST",
|
|
2386
|
+
headers,
|
|
2387
|
+
body: form
|
|
2388
|
+
});
|
|
2389
|
+
await throwIfNotOk(response);
|
|
2390
|
+
return await readJson(response);
|
|
2391
|
+
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Submit a single finalised canonical-CBOR record to Cardano. Caller is
|
|
2394
|
+
* responsible for constructing the record bytes (use `publishContent` /
|
|
2395
|
+
* `publishSealed` / `publishMerkle` for the assisted flows) and for
|
|
2396
|
+
* acquiring a `quote_id` via `quote()` first.
|
|
2397
|
+
*
|
|
2398
|
+
* Returns 202 (`dedup_hit: false`) on freshly enqueued records, or 200
|
|
2399
|
+
* (`dedup_hit: true`) when the same record bytes were previously submitted
|
|
2400
|
+
* by this account. Dedup hits debit nothing.
|
|
2401
|
+
*/
|
|
2402
|
+
async publish(input) {
|
|
2403
|
+
const body = {
|
|
2404
|
+
record: toHex(input.record),
|
|
2405
|
+
quote_id: input.quoteId
|
|
2406
|
+
};
|
|
2407
|
+
if (input.signatures !== void 0) body.signatures = input.signatures;
|
|
2408
|
+
const response = await this.config.fetch(`${this.config.baseUrl}/api/v1/poe/publish`, {
|
|
2409
|
+
method: "POST",
|
|
2410
|
+
headers: buildJsonHeaders2({
|
|
2411
|
+
apiKey: this.config.apiKey,
|
|
2412
|
+
idempotencyKey: input.idempotencyKey
|
|
2413
|
+
}),
|
|
2414
|
+
body: JSON.stringify(body)
|
|
2415
|
+
});
|
|
2416
|
+
await throwIfNotOk(response);
|
|
2417
|
+
const parsed = await readJson(response);
|
|
2418
|
+
return { ...parsed, dedup_hit: response.status === 200 };
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Submit 1..50 finalised records as independent Cardano transactions.
|
|
2422
|
+
* Each entry carries its own `quote_id` — request quotes ahead of time
|
|
2423
|
+
* with one `quote()` call per record. Returns 200 with `results[]` —
|
|
2424
|
+
* successful entries land alongside failed ones; per-record errors do NOT
|
|
2425
|
+
* roll back the batch.
|
|
2426
|
+
*/
|
|
2427
|
+
async publishBatch(input) {
|
|
2428
|
+
const body = {
|
|
2429
|
+
records: input.records.map((r) => ({
|
|
2430
|
+
record: toHex(r.record),
|
|
2431
|
+
quote_id: r.quoteId,
|
|
2432
|
+
...r.signatures !== void 0 ? { signatures: r.signatures } : {}
|
|
2433
|
+
}))
|
|
2434
|
+
};
|
|
2435
|
+
const response = await this.config.fetch(`${this.config.baseUrl}/api/v1/poe/publish-batch`, {
|
|
2436
|
+
method: "POST",
|
|
2437
|
+
headers: buildJsonHeaders2({
|
|
2438
|
+
apiKey: this.config.apiKey,
|
|
2439
|
+
idempotencyKey: input.idempotencyKey
|
|
2440
|
+
}),
|
|
2441
|
+
body: JSON.stringify(body)
|
|
2442
|
+
});
|
|
2443
|
+
await throwIfNotOk(response);
|
|
2444
|
+
return await readJson(response);
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* High-level hash-only publish: hash the supplied content, build a
|
|
2448
|
+
* single-item CIP-309 record, optionally sign it with the caller-supplied
|
|
2449
|
+
* signer, and submit. No Arweave, no storage round-trip — anchors the
|
|
2450
|
+
* digest only.
|
|
2451
|
+
*/
|
|
2452
|
+
async publishContent(input) {
|
|
2453
|
+
return publishContent(this.config, input);
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Hash-already-computed publish: caller already holds the digest(s) — e.g.
|
|
2457
|
+
* the CLI `--hash <hex>` mode, an air-gapped offline hashing flow, or any
|
|
2458
|
+
* pipeline that proxies digests from another tool. No client-side hashing.
|
|
2459
|
+
*/
|
|
2460
|
+
async publishPrehashed(input) {
|
|
2461
|
+
return publishPrehashed(this.config, input);
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Sealed-PoE: encrypt the supplied content to the recipient X25519 public
|
|
2465
|
+
* keys (age-style sealed envelope), upload the ciphertext to Arweave via
|
|
2466
|
+
* /uploads, build a CIP-309 record with the resulting `ar://` URI, sign
|
|
2467
|
+
* it (optional), and submit via /publish.
|
|
2468
|
+
*
|
|
2469
|
+
* The sender SHOULD include their own X25519 public key in `recipients`
|
|
2470
|
+
* to retain decrypt access — the SDK does NOT inject the sender silently.
|
|
2471
|
+
*/
|
|
2472
|
+
async publishSealed(input) {
|
|
2473
|
+
return publishSealed(this.config, input);
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Merkle batch publish: compute the RFC 9162 §2.1.1 root over N
|
|
2477
|
+
* caller-supplied 32-byte leaf hashes, upload the canonical leaves-list
|
|
2478
|
+
* CBOR to Arweave via /uploads, bind the root + leaf_count into
|
|
2479
|
+
* `merkle[0]` of an on-chain record, optionally sign, and submit.
|
|
2480
|
+
*
|
|
2481
|
+
* Returns the on-chain id + tx hash + root + leaf count + the canonical
|
|
2482
|
+
* `ar://<tx>` URI of the leaves-list. Anyone with that URI can later
|
|
2483
|
+
* fetch the leaves-list, recompute the root, and prove inclusion of any
|
|
2484
|
+
* leaf via `merkleSha2256VerifyInclusion`.
|
|
2485
|
+
*/
|
|
2486
|
+
async publishMerkle(input) {
|
|
2487
|
+
return publishMerkle(this.config, input);
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
|
|
2491
|
+
// src/client/records.ts
|
|
2492
|
+
function buildHeaders2(apiKey) {
|
|
2493
|
+
const headers = new Headers({
|
|
2494
|
+
"content-type": "application/json",
|
|
2495
|
+
accept: "application/json"
|
|
2496
|
+
});
|
|
2497
|
+
if (apiKey !== void 0) headers.set("authorization", `Bearer ${apiKey}`);
|
|
2498
|
+
return headers;
|
|
2499
|
+
}
|
|
2500
|
+
function deriveTipBlockHeight(records) {
|
|
2501
|
+
let tip = null;
|
|
2502
|
+
for (const r of records) {
|
|
2503
|
+
if (r.block_height === null) continue;
|
|
2504
|
+
const candidate = r.block_height + r.num_confirmations - 1;
|
|
2505
|
+
tip = tip === null ? candidate : Math.max(tip, candidate);
|
|
2506
|
+
}
|
|
2507
|
+
return tip;
|
|
2508
|
+
}
|
|
2509
|
+
var RecordsNamespace = class {
|
|
2510
|
+
config;
|
|
2511
|
+
constructor(config) {
|
|
2512
|
+
this.config = config;
|
|
2513
|
+
}
|
|
2514
|
+
/**
|
|
2515
|
+
* List records as a paginated `RecordsListResponse` whose `data[]` entries
|
|
2516
|
+
* are the same `RecordResource` projection `get()` returns.
|
|
2517
|
+
*
|
|
2518
|
+
* Pass `{ sealed: true }` to restrict the page to sealed records addressed
|
|
2519
|
+
* to the authenticated caller (the gateway resolves the recipient from the
|
|
2520
|
+
* bearer identity); omit it to list every record the caller may read. Page
|
|
2521
|
+
* with `{ cursor: previous.next_cursor }` until `has_more` is false.
|
|
2522
|
+
*/
|
|
2523
|
+
async list(input) {
|
|
2524
|
+
const params = new URLSearchParams();
|
|
2525
|
+
if (input?.sealed === true) params.set("sealed", "true");
|
|
2526
|
+
if (input?.limit !== void 0) params.set("limit", String(input.limit));
|
|
2527
|
+
if (input?.cursor !== void 0 && input.cursor !== null) {
|
|
2528
|
+
params.set("cursor", input.cursor);
|
|
2529
|
+
}
|
|
2530
|
+
const query = params.toString();
|
|
2531
|
+
const url = `${this.config.baseUrl}/api/v1/records${query === "" ? "" : `?${query}`}`;
|
|
2532
|
+
const response = await this.config.fetch(url, {
|
|
2533
|
+
method: "GET",
|
|
2534
|
+
headers: buildHeaders2(this.config.apiKey)
|
|
2535
|
+
});
|
|
2536
|
+
await throwIfNotOk(response);
|
|
2537
|
+
const page = await readJson(response);
|
|
2538
|
+
if (page.tip_block_height === void 0 || page.tip_block_height === null) {
|
|
2539
|
+
return { ...page, tip_block_height: deriveTipBlockHeight(page.data) };
|
|
2540
|
+
}
|
|
2541
|
+
return page;
|
|
2542
|
+
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Fetch a record by Cardano transaction hash. Returns the JSON
|
|
2545
|
+
* `RecordResource` projection — same shape every `records.list` page entry
|
|
2546
|
+
* carries inside `data[]`.
|
|
2547
|
+
*
|
|
2548
|
+
* 404 (RecordNotFoundError) on tx_hashes the indexer has not seen, OR on
|
|
2549
|
+
* un-anchored rows when the caller is not their owner (oracle-safe
|
|
2550
|
+
* indistinguishable response per the route's privacy invariant).
|
|
2551
|
+
*/
|
|
2552
|
+
async get(txHash) {
|
|
2553
|
+
const response = await this.config.fetch(
|
|
2554
|
+
`${this.config.baseUrl}/api/v1/records/${encodeURIComponent(txHash)}`,
|
|
2555
|
+
{
|
|
2556
|
+
method: "GET",
|
|
2557
|
+
headers: buildHeaders2(this.config.apiKey)
|
|
2558
|
+
}
|
|
2559
|
+
);
|
|
2560
|
+
await throwIfNotOk(response);
|
|
2561
|
+
return await readJson(response);
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Run the canonical CIP-309 verifier against the record at `txHash`.
|
|
2565
|
+
* Returns the same `VerifyReport` shape the standalone verifier emits —
|
|
2566
|
+
* `VerifyReport` IS the wire body of this endpoint, with no transformer in
|
|
2567
|
+
* between.
|
|
2568
|
+
*
|
|
2569
|
+
* Auth required (Bearer with `poe:read` scope, or NextAuth session
|
|
2570
|
+
* cookie). Optional `verify_uris` toggles URI hash-equivalence checks;
|
|
2571
|
+
* `decryption[]` drives trial-decrypt of sealed envelopes per item.
|
|
2572
|
+
*/
|
|
2573
|
+
async verify(txHash, input) {
|
|
2574
|
+
const response = await this.config.fetch(
|
|
2575
|
+
`${this.config.baseUrl}/api/v1/records/${encodeURIComponent(txHash)}/verify`,
|
|
2576
|
+
{
|
|
2577
|
+
method: "POST",
|
|
2578
|
+
headers: buildHeaders2(this.config.apiKey),
|
|
2579
|
+
body: JSON.stringify(input ?? {})
|
|
2580
|
+
}
|
|
2581
|
+
);
|
|
2582
|
+
await throwIfNotOk(response);
|
|
2583
|
+
return await readJson(response);
|
|
2584
|
+
}
|
|
2585
|
+
};
|
|
2586
|
+
|
|
2587
|
+
// src/client/cip309-client.ts
|
|
2588
|
+
function resolveFetch(provided) {
|
|
2589
|
+
if (provided !== void 0) return provided;
|
|
2590
|
+
if (typeof globalThis.fetch === "function") {
|
|
2591
|
+
return globalThis.fetch.bind(globalThis);
|
|
2592
|
+
}
|
|
2593
|
+
throw new Error(
|
|
2594
|
+
"Cip309Client: no fetch implementation available. Pass `fetch` in the config or run on a platform with globalThis.fetch."
|
|
2595
|
+
);
|
|
2596
|
+
}
|
|
2597
|
+
function resolveBaseUrl(config) {
|
|
2598
|
+
const baseUrl = config.baseUrl?.trim();
|
|
2599
|
+
if (baseUrl === void 0 || baseUrl === "") {
|
|
2600
|
+
throw new InvalidClientConfigError(
|
|
2601
|
+
"Cip309Client: baseUrl is required. Pass the base URL of the CIP-309 gateway you are targeting (e.g. https://gateway.example.com)."
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
return baseUrl.replace(/\/$/, "");
|
|
2605
|
+
}
|
|
2606
|
+
var Cip309Client = class {
|
|
2607
|
+
poe;
|
|
2608
|
+
records;
|
|
2609
|
+
account;
|
|
2610
|
+
/**
|
|
2611
|
+
* Construct a client against a CIP-309 gateway.
|
|
2612
|
+
*
|
|
2613
|
+
* `config.baseUrl` is required — there is no default deployment. The
|
|
2614
|
+
* `config.apiKey`, when supplied, is an opaque bearer token sent verbatim as
|
|
2615
|
+
* `Authorization: Bearer <apiKey>`; omit it for anonymous read-only access.
|
|
2616
|
+
*
|
|
2617
|
+
* PoE submissions debit the gateway's own balance model. Acquire a price lock
|
|
2618
|
+
* via `client.poe.quote(...)` first; the resulting `quote_id` is consumed by
|
|
2619
|
+
* the publish call.
|
|
2620
|
+
*/
|
|
2621
|
+
constructor(config) {
|
|
2622
|
+
const fetchImpl = resolveFetch(config.fetch);
|
|
2623
|
+
const baseUrl = resolveBaseUrl(config);
|
|
2624
|
+
const resolved = { apiKey: config.apiKey, baseUrl, fetch: fetchImpl };
|
|
2625
|
+
this.poe = new PoeNamespace(resolved);
|
|
2626
|
+
this.records = new RecordsNamespace(resolved);
|
|
2627
|
+
this.account = new AccountNamespace(resolved);
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
/*! Bundled license information:
|
|
2631
|
+
|
|
2632
|
+
@noble/post-quantum/utils.js:
|
|
2633
|
+
@noble/post-quantum/_crystals.js:
|
|
2634
|
+
@noble/post-quantum/ml-kem.js:
|
|
2635
|
+
@noble/post-quantum/hybrid.js:
|
|
2636
|
+
(*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) *)
|
|
2637
|
+
*/
|
|
2638
|
+
|
|
2639
|
+
export { AccountNamespace, BatchEmptyError, BatchTooLargeError, Cip309Client, Cip309HttpError, ForbiddenError, IdempotencyConflictError, InsufficientFundsError, InsufficientScopeError, InternalServerError, InvalidBodyError, InvalidClientConfigError, MalformedCborError, NotFoundError, OffHostSignError, PartialUploadError, PoeNamespace, PublishError, QuoteAlreadyConsumedError, QuoteExpiredError, QuoteNotFoundError, RateLimitedError, RecordNotFoundError, RecordsNamespace, ServiceUnavailableError, UnauthorizedError, ValidationFailedError, assembleCoseSign1, assembleCoseSign1Hashed, buildToSign, parseHttpError, prepareSigStructure, prepareSigStructureHashed };
|
|
2640
|
+
//# sourceMappingURL=index.js.map
|
|
2641
|
+
//# sourceMappingURL=index.js.map
|