@calimero-network/mero-js 25.2.0 → 25.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -31,6 +31,244 @@ function createTimeoutSignal(timeoutMs) {
31
31
  return controller.signal;
32
32
  }
33
33
 
34
+ // src/crypto/internal.ts
35
+ var PKCS8_ED25519_PREFIX = new Uint8Array([
36
+ 48,
37
+ 46,
38
+ 2,
39
+ 1,
40
+ 0,
41
+ 48,
42
+ 5,
43
+ 6,
44
+ 3,
45
+ 43,
46
+ 101,
47
+ 112,
48
+ 4,
49
+ 34,
50
+ 4,
51
+ 32
52
+ ]);
53
+ function u32le(value) {
54
+ const out = new Uint8Array(4);
55
+ new DataView(out.buffer).setUint32(0, value, true);
56
+ return out;
57
+ }
58
+ function hex(bytes) {
59
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
60
+ }
61
+ function fromHex(value, label2, expectedBytes) {
62
+ const clean = value.trim();
63
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
64
+ throw new Error(
65
+ `${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
66
+ );
67
+ }
68
+ const bytes = new Uint8Array(expectedBytes);
69
+ for (let i = 0; i < expectedBytes; i += 1) {
70
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
71
+ }
72
+ return bytes;
73
+ }
74
+ function fromHexUnsized(value, label2) {
75
+ const clean = value.trim();
76
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
77
+ throw new Error(`${label2} must be an even number of hex characters`);
78
+ }
79
+ const bytes = new Uint8Array(clean.length / 2);
80
+ for (let i = 0; i < bytes.length; i += 1) {
81
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
82
+ }
83
+ return bytes;
84
+ }
85
+ function u64le(value) {
86
+ const out = new Uint8Array(8);
87
+ new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
88
+ return out;
89
+ }
90
+ function concat(...parts) {
91
+ const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
92
+ let offset = 0;
93
+ for (const part of parts) {
94
+ out.set(part, offset);
95
+ offset += part.length;
96
+ }
97
+ return out;
98
+ }
99
+ async function domainHash(domain, parts) {
100
+ const chunks = [u64le(domain.length), domain];
101
+ for (const part of parts) {
102
+ chunks.push(u64le(part.length), part);
103
+ }
104
+ return new Uint8Array(
105
+ await crypto.subtle.digest("SHA-256", concat(...chunks))
106
+ );
107
+ }
108
+ async function importSigningKey(secretHex) {
109
+ const seed = fromHex(secretHex, "deviceSecret", 32);
110
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
111
+ try {
112
+ return await crypto.subtle.importKey(
113
+ "pkcs8",
114
+ pkcs8,
115
+ { name: "Ed25519" },
116
+ false,
117
+ ["sign"]
118
+ );
119
+ } catch (cause) {
120
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
121
+ throw new Error(
122
+ "this runtime has no WebCrypto Ed25519, so a warrant cannot be signed here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; otherwise mint the warrant with `merod account warrant`" + detail
123
+ );
124
+ }
125
+ }
126
+ async function verifySignature(publicKey, signature, payload) {
127
+ const key = await crypto.subtle.importKey(
128
+ "raw",
129
+ fromHex(publicKey, "publicKey", 32),
130
+ { name: "Ed25519" },
131
+ false,
132
+ ["verify"]
133
+ );
134
+ return crypto.subtle.verify({ name: "Ed25519" }, key, signature, payload);
135
+ }
136
+ async function derivePublicKey(secretHex) {
137
+ const seed = fromHex(secretHex, "deviceSecret", 32);
138
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
139
+ const key = await crypto.subtle.importKey(
140
+ "pkcs8",
141
+ pkcs8,
142
+ { name: "Ed25519" },
143
+ true,
144
+ ["sign"]
145
+ );
146
+ const jwk = await crypto.subtle.exportKey("jwk", key);
147
+ const raw = jwk.x;
148
+ return Uint8Array.from(
149
+ atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
150
+ (c) => c.charCodeAt(0)
151
+ );
152
+ }
153
+
154
+ // src/request/request-sig.ts
155
+ var SIGN_DOMAIN = new TextEncoder().encode("calimero.auth.request.v2");
156
+ var BODY_DOMAIN = new TextEncoder().encode("calimero.auth.request.body.v1");
157
+ function borshString(value) {
158
+ const bytes = new TextEncoder().encode(value);
159
+ return concat(u32le(bytes.length), bytes);
160
+ }
161
+ async function requestBodyHash(body = new Uint8Array(0)) {
162
+ const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
163
+ return domainHash(BODY_DOMAIN, [bytes]);
164
+ }
165
+ async function signRequest(input) {
166
+ const key = await importSigningKey(input.signerSecret);
167
+ const bodyHash = await requestBodyHash(input.body);
168
+ const issuedAt = u64le(input.issuedAt);
169
+ const expiresAt = u64le(input.expiresAt);
170
+ const encoder = new TextEncoder();
171
+ const preimage = await domainHash(SIGN_DOMAIN, [
172
+ encoder.encode(input.method),
173
+ encoder.encode(input.path),
174
+ bodyHash,
175
+ issuedAt,
176
+ expiresAt
177
+ ]);
178
+ const signature = new Uint8Array(
179
+ await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
180
+ );
181
+ return hex(
182
+ concat(
183
+ borshString(input.method),
184
+ borshString(input.path),
185
+ bodyHash,
186
+ issuedAt,
187
+ expiresAt,
188
+ signature
189
+ )
190
+ );
191
+ }
192
+
193
+ // src/request-proof/request-proof.ts
194
+ var utf8 = (text) => new TextEncoder().encode(text);
195
+ var REQUEST_PROOF_HEADER = "X-Calimero-Proof";
196
+ var DEFAULT_VALID_FOR = 300;
197
+ async function signRequestProof(input) {
198
+ const { method, path, credential } = input;
199
+ if (!method) throw new Error("signRequestProof needs the method the request will use");
200
+ if (!path.startsWith("/")) {
201
+ throw new Error(`signRequestProof needs a path, got ${JSON.stringify(path)}`);
202
+ }
203
+ const bodyHash = await requestBodyHash(input.body);
204
+ const issuedAt = Math.floor(input.now ?? Date.now() / 1e3);
205
+ const expiresAt = issuedAt + (input.validForSeconds ?? DEFAULT_VALID_FOR);
206
+ const payload = await domainHash(SIGN_DOMAIN, [
207
+ utf8(method),
208
+ utf8(path),
209
+ bodyHash,
210
+ u64le(issuedAt),
211
+ u64le(expiresAt)
212
+ ]);
213
+ const signature = await signPayload(payload, input);
214
+ if (signature.length !== 64) {
215
+ throw new Error(`the signer returned ${signature.length} bytes, not a 64-byte signature`);
216
+ }
217
+ const requestSig = concat(
218
+ u32le(utf8(method).length),
219
+ utf8(method),
220
+ u32le(utf8(path).length),
221
+ utf8(path),
222
+ bodyHash,
223
+ u64le(issuedAt),
224
+ u64le(expiresAt),
225
+ signature
226
+ );
227
+ const accountProof = hexToBytes(credential, "credential");
228
+ return hex(concat(accountProof, new Uint8Array([0]), requestSig));
229
+ }
230
+ async function signPayload(payload, input) {
231
+ if (input.signer) return input.signer.sign(payload);
232
+ if (!input.deviceSecret) {
233
+ throw new Error("signRequestProof needs either `deviceSecret` or `signer`");
234
+ }
235
+ const key = await importSigningKey(input.deviceSecret);
236
+ return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, key, payload));
237
+ }
238
+ function hexToBytes(value, label2) {
239
+ const clean = value.trim();
240
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
241
+ throw new Error(`${label2} is not an even-length hex string`);
242
+ }
243
+ return fromHex(clean, label2, clean.length / 2);
244
+ }
245
+
246
+ // src/request-proof/authorizer.ts
247
+ function createProofAuthorizer(credential) {
248
+ if (!credential.credential) {
249
+ throw new Error("a proof authorizer needs the `credential` from signDeviceCert");
250
+ }
251
+ if (!credential.deviceSecret && !credential.signer) {
252
+ throw new Error("a proof authorizer needs either `deviceSecret` or `signer`");
253
+ }
254
+ return async ({ method, path, body }) => {
255
+ const proof = await signRequestProof({
256
+ method,
257
+ path,
258
+ body,
259
+ credential: credential.credential,
260
+ deviceSecret: credential.deviceSecret,
261
+ signer: credential.signer,
262
+ validForSeconds: credential.validForSeconds
263
+ });
264
+ return { [REQUEST_PROOF_HEADER]: proof };
265
+ };
266
+ }
267
+ function signedPath(url2) {
268
+ const parsed = new URL(url2, globalThis.location?.href ?? "http://localhost");
269
+ return parsed.pathname + parsed.search;
270
+ }
271
+
34
272
  // src/http-client/web-client.ts
35
273
  var TERMINAL_AUTH_ERRORS = /* @__PURE__ */ new Set(["token_reuse", "token_revoked"]);
36
274
  function extractErrorMessage(bodyText) {
@@ -86,13 +324,31 @@ function headersToRecord(headers) {
86
324
  });
87
325
  return record;
88
326
  }
89
- function pathForProof(url2) {
327
+ function withoutBareQuery(url2) {
328
+ let parsed;
90
329
  try {
91
- return new URL(url2).pathname;
330
+ parsed = new URL(url2, globalThis.location?.href);
92
331
  } catch {
93
- const query = url2.indexOf("?");
94
- return query === -1 ? url2 : url2.slice(0, query);
332
+ return url2;
95
333
  }
334
+ if (parsed.search === "") parsed.search = "";
335
+ return parsed.href;
336
+ }
337
+ function carriesProof(headers) {
338
+ return Object.keys(headers).some(
339
+ (name) => name.toLowerCase() === REQUEST_PROOF_HEADER.toLowerCase()
340
+ );
341
+ }
342
+ function refuseProofRedirect(response, url2) {
343
+ const redirected = response.type === "opaqueredirect" || response.status >= 300 && response.status < 400;
344
+ if (!redirected) return;
345
+ throw new HTTPError(
346
+ 0,
347
+ "Redirect Not Followed",
348
+ url2,
349
+ response.headers,
350
+ `the node answered with a redirect; a request carrying ${REQUEST_PROOF_HEADER} is not followed to another location`
351
+ );
96
352
  }
97
353
  var WebHttpClient = class {
98
354
  constructor(transport2) {
@@ -157,7 +413,7 @@ var WebHttpClient = class {
157
413
  }
158
414
  async makeRequest(path, init, retryCount = 0, requestStartTime) {
159
415
  const MAX_RETRY_ATTEMPTS = 1;
160
- const url2 = this.buildUrl(path);
416
+ const url2 = withoutBareQuery(this.buildUrl(path));
161
417
  const startTime = requestStartTime ?? Date.now();
162
418
  const signal = this.createAbortSignal(init);
163
419
  const headers = await this.buildHeaders(init?.headers);
@@ -182,7 +438,7 @@ var WebHttpClient = class {
182
438
  headersObj,
183
439
  await this.transport.authorizeRequest({
184
440
  method,
185
- path: new URL(url2).pathname,
441
+ path: signedPath(url2),
186
442
  // Narrowed by the guard above; `BodyInit` is wider than what can be
187
443
  // signed.
188
444
  body
@@ -242,15 +498,20 @@ var WebHttpClient = class {
242
498
  if (this.transport.getProof) {
243
499
  const proof = await this.transport.getProof({
244
500
  method: requestInit.method ?? "GET",
245
- path: pathForProof(url2),
501
+ path: signedPath(url2),
246
502
  body: requestInit.body
247
503
  });
248
504
  if (proof) {
249
505
  headersObj["X-Calimero-Proof"] = proof;
250
506
  }
251
507
  }
508
+ const proofAttached = carriesProof(headersObj);
509
+ if (proofAttached && requestInit.redirect !== "error") {
510
+ requestInit.redirect = "manual";
511
+ }
252
512
  try {
253
513
  const response = await this.transport.fetch(url2, requestInit);
514
+ if (proofAttached) refuseProofRedirect(response, url2);
254
515
  if (!response.ok) {
255
516
  const bodyText = await this.getBodyText(response);
256
517
  const httpError = new HTTPError(
@@ -2269,6 +2530,10 @@ var AdminApiClient = class {
2269
2530
  async getMigrationStatus(namespaceId) {
2270
2531
  return this.httpClient.get(`/admin-api/groups/${pathSegment(namespaceId)}/migration-status`);
2271
2532
  }
2533
+ /** Revoked devices and withdrawn slots of a namespace; 403 unless this node is a bound member. */
2534
+ async listNamespaceRevokedDevices(namespaceId) {
2535
+ return this.httpClient.get(`/admin-api/namespaces/${pathSegment(namespaceId)}/revoked-devices`);
2536
+ }
2272
2537
  /** Per-group cascade-migration snapshots for a namespace. */
2273
2538
  async getCascadeStatus(namespaceId) {
2274
2539
  return unwrap(
@@ -2460,165 +2725,6 @@ function buildAuthLoginUrl(nodeUrl, opts) {
2460
2725
  return `${base}/auth/login?${params.toString()}`;
2461
2726
  }
2462
2727
 
2463
- // src/crypto/internal.ts
2464
- var PKCS8_ED25519_PREFIX = new Uint8Array([
2465
- 48,
2466
- 46,
2467
- 2,
2468
- 1,
2469
- 0,
2470
- 48,
2471
- 5,
2472
- 6,
2473
- 3,
2474
- 43,
2475
- 101,
2476
- 112,
2477
- 4,
2478
- 34,
2479
- 4,
2480
- 32
2481
- ]);
2482
- function u32le(value) {
2483
- const out = new Uint8Array(4);
2484
- new DataView(out.buffer).setUint32(0, value, true);
2485
- return out;
2486
- }
2487
- function hex(bytes) {
2488
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2489
- }
2490
- function fromHex(value, label2, expectedBytes) {
2491
- const clean = value.trim();
2492
- if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
2493
- throw new Error(
2494
- `${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2495
- );
2496
- }
2497
- const bytes = new Uint8Array(expectedBytes);
2498
- for (let i = 0; i < expectedBytes; i += 1) {
2499
- bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
2500
- }
2501
- return bytes;
2502
- }
2503
- function fromHexUnsized(value, label2) {
2504
- const clean = value.trim();
2505
- if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
2506
- throw new Error(`${label2} must be an even number of hex characters`);
2507
- }
2508
- const bytes = new Uint8Array(clean.length / 2);
2509
- for (let i = 0; i < bytes.length; i += 1) {
2510
- bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
2511
- }
2512
- return bytes;
2513
- }
2514
- function u64le(value) {
2515
- const out = new Uint8Array(8);
2516
- new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
2517
- return out;
2518
- }
2519
- function concat(...parts) {
2520
- const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
2521
- let offset = 0;
2522
- for (const part of parts) {
2523
- out.set(part, offset);
2524
- offset += part.length;
2525
- }
2526
- return out;
2527
- }
2528
- async function domainHash(domain, parts) {
2529
- const chunks = [u64le(domain.length), domain];
2530
- for (const part of parts) {
2531
- chunks.push(u64le(part.length), part);
2532
- }
2533
- return new Uint8Array(
2534
- await crypto.subtle.digest("SHA-256", concat(...chunks))
2535
- );
2536
- }
2537
- async function importSigningKey(secretHex) {
2538
- const seed = fromHex(secretHex, "deviceSecret", 32);
2539
- const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2540
- try {
2541
- return await crypto.subtle.importKey(
2542
- "pkcs8",
2543
- pkcs8,
2544
- { name: "Ed25519" },
2545
- false,
2546
- ["sign"]
2547
- );
2548
- } catch (cause) {
2549
- const detail = cause instanceof Error ? `: ${cause.message}` : "";
2550
- throw new Error(
2551
- "this runtime has no WebCrypto Ed25519, so a warrant cannot be signed here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; otherwise mint the warrant with `merod account warrant`" + detail
2552
- );
2553
- }
2554
- }
2555
- async function verifySignature(publicKey, signature, payload) {
2556
- const key = await crypto.subtle.importKey(
2557
- "raw",
2558
- fromHex(publicKey, "publicKey", 32),
2559
- { name: "Ed25519" },
2560
- false,
2561
- ["verify"]
2562
- );
2563
- return crypto.subtle.verify({ name: "Ed25519" }, key, signature, payload);
2564
- }
2565
- async function derivePublicKey(secretHex) {
2566
- const seed = fromHex(secretHex, "deviceSecret", 32);
2567
- const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2568
- const key = await crypto.subtle.importKey(
2569
- "pkcs8",
2570
- pkcs8,
2571
- { name: "Ed25519" },
2572
- true,
2573
- ["sign"]
2574
- );
2575
- const jwk = await crypto.subtle.exportKey("jwk", key);
2576
- const raw = jwk.x;
2577
- return Uint8Array.from(
2578
- atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2579
- (c) => c.charCodeAt(0)
2580
- );
2581
- }
2582
-
2583
- // src/request/request-sig.ts
2584
- var SIGN_DOMAIN = new TextEncoder().encode("calimero.auth.request.v1");
2585
- var BODY_DOMAIN = new TextEncoder().encode("calimero.auth.request.body.v1");
2586
- function borshString(value) {
2587
- const bytes = new TextEncoder().encode(value);
2588
- return concat(u32le(bytes.length), bytes);
2589
- }
2590
- async function requestBodyHash(body = new Uint8Array(0)) {
2591
- const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
2592
- return domainHash(BODY_DOMAIN, [bytes]);
2593
- }
2594
- async function signRequest(input) {
2595
- const key = await importSigningKey(input.signerSecret);
2596
- const bodyHash = await requestBodyHash(input.body);
2597
- const issuedAt = u64le(input.issuedAt);
2598
- const expiresAt = u64le(input.expiresAt);
2599
- const encoder = new TextEncoder();
2600
- const preimage = await domainHash(SIGN_DOMAIN, [
2601
- encoder.encode(input.method),
2602
- encoder.encode(input.path),
2603
- bodyHash,
2604
- issuedAt,
2605
- expiresAt
2606
- ]);
2607
- const signature = new Uint8Array(
2608
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
2609
- );
2610
- return hex(
2611
- concat(
2612
- borshString(input.method),
2613
- borshString(input.path),
2614
- bodyHash,
2615
- issuedAt,
2616
- expiresAt,
2617
- signature
2618
- )
2619
- );
2620
- }
2621
-
2622
2728
  // src/request/caller-proof.ts
2623
2729
  function callerProof(input) {
2624
2730
  const credential = fromHexUnsized(input.credential, "credential");
@@ -2847,13 +2953,17 @@ var SseClient = class {
2847
2953
  this.abortController = new AbortController();
2848
2954
  try {
2849
2955
  const url2 = `${this.baseUrl}/sse`;
2956
+ const headers = {
2957
+ ...await this.authorize({ method: "GET", path: signedPath(url2) }),
2958
+ "Accept": "text/event-stream"
2959
+ };
2960
+ const proofAttached = carriesProof(headers);
2850
2961
  const response = await this.fetchImpl(url2, {
2851
- headers: {
2852
- ...await this.authorize({ method: "GET", path: new URL(url2).pathname }),
2853
- "Accept": "text/event-stream"
2854
- },
2855
- signal: this.abortController.signal
2962
+ headers,
2963
+ signal: this.abortController.signal,
2964
+ ...proofAttached && { redirect: "manual" }
2856
2965
  });
2966
+ if (proofAttached) refuseProofRedirect(response, url2);
2857
2967
  if (!response.ok) {
2858
2968
  const terminal = isTerminalAuthFailure(response);
2859
2969
  const bodyText = await response.text().catch(() => void 0);
@@ -3051,15 +3161,19 @@ var SseClient = class {
3051
3161
  if (ids.groupIds && ids.groupIds.length > 0) params.groupIds = ids.groupIds;
3052
3162
  const url2 = `${this.baseUrl}/sse/subscription`;
3053
3163
  const body = JSON.stringify({ id: this.sessionId, method, params });
3164
+ const headers = {
3165
+ ...await this.authorize({ method: "POST", path: signedPath(url2), body }),
3166
+ "Content-Type": "application/json"
3167
+ };
3168
+ const proofAttached = carriesProof(headers);
3054
3169
  const response = await this.fetchImpl(url2, {
3055
3170
  method: "POST",
3056
- headers: {
3057
- ...await this.authorize({ method: "POST", path: new URL(url2).pathname, body }),
3058
- "Content-Type": "application/json"
3059
- },
3171
+ headers,
3060
3172
  body,
3061
- signal: timeout.signal
3173
+ signal: timeout.signal,
3174
+ ...proofAttached && { redirect: "manual" }
3062
3175
  });
3176
+ if (proofAttached) refuseProofRedirect(response, url2);
3063
3177
  if (!response.ok) {
3064
3178
  const terminal = isTerminalAuthFailure(response);
3065
3179
  const bodyText = await response.text().catch(() => void 0);
@@ -6179,7 +6293,7 @@ var MAX_HANDSHAKE_REPLY = 1024;
6179
6293
  var RENEW_BEFORE_MS = 6e4;
6180
6294
  var TRANSPORT_BINDING_DOMAIN = "calimero.tee-attest.transport-key.v1";
6181
6295
  var NULL_BODY_STATUSES = [101, 204, 205, 304];
6182
- var utf8 = new TextEncoder();
6296
+ var utf82 = new TextEncoder();
6183
6297
  var StaleTransportKeyError = class extends Error {
6184
6298
  constructor() {
6185
6299
  super("The node no longer holds the attested transport key; attest again for the current one");
@@ -6198,7 +6312,7 @@ var SealedTransportError = class extends Error {
6198
6312
  async function transportKeyBinding(inner, transportKey) {
6199
6313
  const digest = await crypto.subtle.digest(
6200
6314
  "SHA-256",
6201
- concat(utf8.encode(TRANSPORT_BINDING_DOMAIN), inner, transportKey)
6315
+ concat(utf82.encode(TRANSPORT_BINDING_DOMAIN), inner, transportKey)
6202
6316
  );
6203
6317
  return new Uint8Array(digest);
6204
6318
  }
@@ -6402,7 +6516,7 @@ function nonce(requestId, index) {
6402
6516
  return bytes;
6403
6517
  }
6404
6518
  function joinInner(head, body) {
6405
- const encoded = utf8.encode(JSON.stringify(head));
6519
+ const encoded = utf82.encode(JSON.stringify(head));
6406
6520
  const len = new Uint8Array(4);
6407
6521
  new DataView(len.buffer).setUint32(0, encoded.length, false);
6408
6522
  return concat(len, encoded, body);
@@ -6892,7 +7006,7 @@ var FULCIO_ISSUER_V1 = "1.3.6.1.4.1.57264.1.1";
6892
7006
  var SAN_URI = 134;
6893
7007
  var UTF8_STRING = 12;
6894
7008
  var SEQUENCE2 = 48;
6895
- var utf82 = new TextEncoder();
7009
+ var utf83 = new TextEncoder();
6896
7010
  var fromUtf8 = (bytes) => new TextDecoder().decode(bytes);
6897
7011
  async function verifyCosignBundle(body, bundle, signer, signature) {
6898
7012
  const { base64Signature, cert, rekorBundle } = cosignBundle(bundle);
@@ -6905,7 +7019,7 @@ async function verifyCosignBundle(body, bundle, signer, signature) {
6905
7019
  const leaf = parseCertificate(leafDer);
6906
7020
  const log = REKOR_LOGS.find((candidate) => candidate.logId === payload.logID.toLowerCase());
6907
7021
  if (!log) throw new Error(`The bundle names Rekor log ${payload.logID}, which the Sigstore trust root does not know`);
6908
- const canonical = utf82.encode(
7022
+ const canonical = utf83.encode(
6909
7023
  `{"body":${JSON.stringify(payload.body)},"integratedTime":${payload.integratedTime},"logID":${JSON.stringify(payload.logID)},"logIndex":${payload.logIndex}}`
6910
7024
  );
6911
7025
  const setVerifies = await verifyEcdsa(
@@ -8643,92 +8757,6 @@ var RelayObserver = class {
8643
8757
  }
8644
8758
  };
8645
8759
 
8646
- // src/request-proof/request-proof.ts
8647
- var utf83 = (text) => new TextEncoder().encode(text);
8648
- var REQUEST_SIGN_DOMAIN = utf83("calimero.auth.request.v1");
8649
- var REQUEST_BODY_DOMAIN = utf83("calimero.auth.request.body.v1");
8650
- var REQUEST_PROOF_HEADER = "X-Calimero-Proof";
8651
- var DEFAULT_VALID_FOR = 300;
8652
- async function signRequestProof(input) {
8653
- const { method, path, credential } = input;
8654
- if (!method) throw new Error("signRequestProof needs the method the request will use");
8655
- if (!path.startsWith("/")) {
8656
- throw new Error(`signRequestProof needs a path, got ${JSON.stringify(path)}`);
8657
- }
8658
- if (path.includes("?")) {
8659
- throw new Error(
8660
- `the path must carry no query string, got ${JSON.stringify(path)} \u2014 a query is excluded from the signature on purpose, because a proxy may rewrite one. Anything that must be bound belongs in the body.`
8661
- );
8662
- }
8663
- const bodyBytes2 = input.body === void 0 ? new Uint8Array() : typeof input.body === "string" ? utf83(input.body) : input.body;
8664
- const bodyHash = await domainHash(REQUEST_BODY_DOMAIN, [bodyBytes2]);
8665
- const issuedAt = Math.floor(input.now ?? Date.now() / 1e3);
8666
- const expiresAt = issuedAt + (input.validForSeconds ?? DEFAULT_VALID_FOR);
8667
- const payload = await domainHash(REQUEST_SIGN_DOMAIN, [
8668
- utf83(method),
8669
- utf83(path),
8670
- bodyHash,
8671
- u64le(issuedAt),
8672
- u64le(expiresAt)
8673
- ]);
8674
- const signature = await signPayload(payload, input);
8675
- if (signature.length !== 64) {
8676
- throw new Error(`the signer returned ${signature.length} bytes, not a 64-byte signature`);
8677
- }
8678
- const requestSig = concat(
8679
- u32le(utf83(method).length),
8680
- utf83(method),
8681
- u32le(utf83(path).length),
8682
- utf83(path),
8683
- bodyHash,
8684
- u64le(issuedAt),
8685
- u64le(expiresAt),
8686
- signature
8687
- );
8688
- const accountProof = hexToBytes(credential, "credential");
8689
- return hex(concat(accountProof, new Uint8Array([0]), requestSig));
8690
- }
8691
- async function signPayload(payload, input) {
8692
- if (input.signer) return input.signer.sign(payload);
8693
- if (!input.deviceSecret) {
8694
- throw new Error("signRequestProof needs either `deviceSecret` or `signer`");
8695
- }
8696
- const key = await importSigningKey(input.deviceSecret);
8697
- return new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, key, payload));
8698
- }
8699
- function hexToBytes(value, label2) {
8700
- const clean = value.trim();
8701
- if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
8702
- throw new Error(`${label2} is not an even-length hex string`);
8703
- }
8704
- return fromHex(clean, label2, clean.length / 2);
8705
- }
8706
-
8707
- // src/request-proof/authorizer.ts
8708
- function createProofAuthorizer(credential) {
8709
- if (!credential.credential) {
8710
- throw new Error("a proof authorizer needs the `credential` from signDeviceCert");
8711
- }
8712
- if (!credential.deviceSecret && !credential.signer) {
8713
- throw new Error("a proof authorizer needs either `deviceSecret` or `signer`");
8714
- }
8715
- return async ({ method, path, body }) => {
8716
- const proof = await signRequestProof({
8717
- method,
8718
- path,
8719
- body,
8720
- credential: credential.credential,
8721
- deviceSecret: credential.deviceSecret,
8722
- signer: credential.signer,
8723
- validForSeconds: credential.validForSeconds
8724
- });
8725
- return { [REQUEST_PROOF_HEADER]: proof };
8726
- };
8727
- }
8728
- function signedPath(url2) {
8729
- return new URL(url2).pathname;
8730
- }
8731
-
8732
8760
  // src/presence/statement.ts
8733
8761
  var PRESENCE_DOMAIN = new TextEncoder().encode("calimero/presence/1");
8734
8762
  async function stateHash(state) {