@calimero-network/mero-js 19.15.0 → 19.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -24,7 +24,8 @@ export type { DeviceCertInput } from "./device-cert/index.js";
24
24
  export { signMemberJoinOp, encodeSignedInvitation, SIGNED_NAMESPACE_OP_SCHEMA_VERSION, } from "./namespace-op/index.js";
25
25
  export type { SignMemberJoinInput } from "./namespace-op/index.js";
26
26
  export type { WarrantInput } from "./warrant/index.js";
27
- export { requestBodyHash, signRequest } from "./request/index.js";
27
+ export { requestBodyHash, signRequest, callerProof, createProofSigner, } from "./request/index.js";
28
+ export type { CallerProofInput, ProofRequest, ProofSignerOptions, } from "./request/index.js";
28
29
  export type { RequestSigInput } from "./request/index.js";
29
30
  export { signLoginStatement } from "./login/index.js";
30
31
  export type { Audience, LoginStatementInput } from "./login/index.js";
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, sign
29
29
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, } from "./device-cert/index.js";
30
30
  export { signMemberJoinOp, encodeSignedInvitation, SIGNED_NAMESPACE_OP_SCHEMA_VERSION, } from "./namespace-op/index.js";
31
31
  // Login-statement signing — the device's half of a password-free session
32
- export { requestBodyHash, signRequest } from "./request/index.js";
32
+ export { requestBodyHash, signRequest, callerProof, createProofSigner, } from "./request/index.js";
33
33
  export { signLoginStatement } from "./login/index.js";
34
34
  export { login, generateSessionKey } from "./login/index.js";
35
35
  // Member capability bitmask constants & helpers
package/dist/index.mjs CHANGED
@@ -85,6 +85,14 @@ function headersToRecord(headers) {
85
85
  });
86
86
  return record;
87
87
  }
88
+ function pathForProof(url) {
89
+ try {
90
+ return new URL(url).pathname;
91
+ } catch {
92
+ const query = url.indexOf("?");
93
+ return query === -1 ? url : url.slice(0, query);
94
+ }
95
+ }
88
96
  var WebHttpClient = class {
89
97
  constructor(transport) {
90
98
  this.transport = transport;
@@ -211,6 +219,16 @@ var WebHttpClient = class {
211
219
  if (init?.keepalive !== void 0) {
212
220
  requestInit.keepalive = init.keepalive;
213
221
  }
222
+ if (this.transport.getProof) {
223
+ const proof = await this.transport.getProof({
224
+ method: requestInit.method ?? "GET",
225
+ path: pathForProof(url),
226
+ body: requestInit.body
227
+ });
228
+ if (proof) {
229
+ headersObj["X-Calimero-Proof"] = proof;
230
+ }
231
+ }
214
232
  try {
215
233
  const response = await this.transport.fetch(url, requestInit);
216
234
  if (!response.ok) {
@@ -407,6 +425,7 @@ function createBrowserHttpClient(options) {
407
425
  fetch: (url, init) => globalThis.fetch(url, init),
408
426
  baseUrl: options.baseUrl,
409
427
  getAuthToken: options.getAuthToken,
428
+ getProof: options.getProof,
410
429
  onTokenRefresh: options.onTokenRefresh,
411
430
  refreshToken: options.refreshToken,
412
431
  onAuthRevoked: options.onAuthRevoked,
@@ -433,6 +452,7 @@ function createNodeHttpClient(options) {
433
452
  fetch: isDefaultFetch ? (url, init) => globalThis.fetch(url, init) : (url, init) => fetchImpl(url, init),
434
453
  baseUrl: options.baseUrl,
435
454
  getAuthToken: options.getAuthToken,
455
+ getProof: options.getProof,
436
456
  onTokenRefresh: options.onTokenRefresh,
437
457
  refreshToken: options.refreshToken,
438
458
  onAuthRevoked: options.onAuthRevoked,
@@ -1893,6 +1913,196 @@ function buildAuthLoginUrl(nodeUrl, opts) {
1893
1913
  return `${base}/auth/login?${params.toString()}`;
1894
1914
  }
1895
1915
 
1916
+ // src/crypto/internal.ts
1917
+ var PKCS8_ED25519_PREFIX = new Uint8Array([
1918
+ 48,
1919
+ 46,
1920
+ 2,
1921
+ 1,
1922
+ 0,
1923
+ 48,
1924
+ 5,
1925
+ 6,
1926
+ 3,
1927
+ 43,
1928
+ 101,
1929
+ 112,
1930
+ 4,
1931
+ 34,
1932
+ 4,
1933
+ 32
1934
+ ]);
1935
+ function u32le(value) {
1936
+ const out = new Uint8Array(4);
1937
+ new DataView(out.buffer).setUint32(0, value, true);
1938
+ return out;
1939
+ }
1940
+ function hex(bytes) {
1941
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
1942
+ }
1943
+ function fromHex(value, label, expectedBytes) {
1944
+ const clean = value.trim();
1945
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
1946
+ throw new Error(
1947
+ `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
1948
+ );
1949
+ }
1950
+ const bytes = new Uint8Array(expectedBytes);
1951
+ for (let i = 0; i < expectedBytes; i += 1) {
1952
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
1953
+ }
1954
+ return bytes;
1955
+ }
1956
+ function fromHexUnsized(value, label) {
1957
+ const clean = value.trim();
1958
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
1959
+ throw new Error(`${label} must be an even number of hex characters`);
1960
+ }
1961
+ const bytes = new Uint8Array(clean.length / 2);
1962
+ for (let i = 0; i < bytes.length; i += 1) {
1963
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
1964
+ }
1965
+ return bytes;
1966
+ }
1967
+ function u64le(value) {
1968
+ const out = new Uint8Array(8);
1969
+ new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
1970
+ return out;
1971
+ }
1972
+ function concat(...parts) {
1973
+ const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
1974
+ let offset = 0;
1975
+ for (const part of parts) {
1976
+ out.set(part, offset);
1977
+ offset += part.length;
1978
+ }
1979
+ return out;
1980
+ }
1981
+ async function domainHash(domain, parts) {
1982
+ const chunks = [u64le(domain.length), domain];
1983
+ for (const part of parts) {
1984
+ chunks.push(u64le(part.length), part);
1985
+ }
1986
+ return new Uint8Array(
1987
+ await crypto.subtle.digest("SHA-256", concat(...chunks))
1988
+ );
1989
+ }
1990
+ async function importSigningKey(secretHex) {
1991
+ const seed = fromHex(secretHex, "deviceSecret", 32);
1992
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
1993
+ try {
1994
+ return await crypto.subtle.importKey(
1995
+ "pkcs8",
1996
+ pkcs8,
1997
+ { name: "Ed25519" },
1998
+ false,
1999
+ ["sign"]
2000
+ );
2001
+ } catch (cause) {
2002
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
2003
+ throw new Error(
2004
+ "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
2005
+ );
2006
+ }
2007
+ }
2008
+ async function derivePublicKey(secretHex) {
2009
+ const seed = fromHex(secretHex, "deviceSecret", 32);
2010
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2011
+ const key = await crypto.subtle.importKey(
2012
+ "pkcs8",
2013
+ pkcs8,
2014
+ { name: "Ed25519" },
2015
+ true,
2016
+ ["sign"]
2017
+ );
2018
+ const jwk = await crypto.subtle.exportKey("jwk", key);
2019
+ const raw = jwk.x;
2020
+ return Uint8Array.from(
2021
+ atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2022
+ (c) => c.charCodeAt(0)
2023
+ );
2024
+ }
2025
+
2026
+ // src/request/request-sig.ts
2027
+ var SIGN_DOMAIN = new TextEncoder().encode("calimero.auth.request.v1");
2028
+ var BODY_DOMAIN = new TextEncoder().encode("calimero.auth.request.body.v1");
2029
+ function borshString(value) {
2030
+ const bytes = new TextEncoder().encode(value);
2031
+ return concat(u32le(bytes.length), bytes);
2032
+ }
2033
+ async function requestBodyHash(body = new Uint8Array(0)) {
2034
+ const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
2035
+ return domainHash(BODY_DOMAIN, [bytes]);
2036
+ }
2037
+ async function signRequest(input) {
2038
+ const key = await importSigningKey(input.signerSecret);
2039
+ const bodyHash = await requestBodyHash(input.body);
2040
+ const issuedAt = u64le(input.issuedAt);
2041
+ const expiresAt = u64le(input.expiresAt);
2042
+ const encoder = new TextEncoder();
2043
+ const preimage = await domainHash(SIGN_DOMAIN, [
2044
+ encoder.encode(input.method),
2045
+ encoder.encode(input.path),
2046
+ bodyHash,
2047
+ issuedAt,
2048
+ expiresAt
2049
+ ]);
2050
+ const signature = new Uint8Array(
2051
+ await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
2052
+ );
2053
+ return hex(
2054
+ concat(
2055
+ borshString(input.method),
2056
+ borshString(input.path),
2057
+ bodyHash,
2058
+ issuedAt,
2059
+ expiresAt,
2060
+ signature
2061
+ )
2062
+ );
2063
+ }
2064
+
2065
+ // src/request/caller-proof.ts
2066
+ function callerProof(input) {
2067
+ const credential = fromHexUnsized(input.credential, "credential");
2068
+ const request = fromHexUnsized(input.request, "request signature");
2069
+ const session = input.session === void 0 ? new Uint8Array([0]) : concat(new Uint8Array([1]), fromHexUnsized(input.session, "session statement"));
2070
+ return hex(concat(credential, session, request));
2071
+ }
2072
+
2073
+ // src/request/proof-signer.ts
2074
+ function bodyBytes(body) {
2075
+ if (body === void 0 || body === null) return new Uint8Array(0);
2076
+ if (typeof body === "string") return body;
2077
+ if (body instanceof Uint8Array) return body;
2078
+ if (body instanceof ArrayBuffer) return new Uint8Array(body);
2079
+ if (ArrayBuffer.isView(body)) {
2080
+ return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
2081
+ }
2082
+ throw new Error(
2083
+ `a request-carried proof cannot commit to a ${body?.constructor?.name ?? typeof body} body: pass a string or bytes, or use a session for this call`
2084
+ );
2085
+ }
2086
+ function createProofSigner(options) {
2087
+ const ttl = options.ttlSeconds ?? 120;
2088
+ const clock = options.now ?? (() => Math.floor(Date.now() / 1e3));
2089
+ return async (request) => {
2090
+ const issuedAt = clock();
2091
+ return callerProof({
2092
+ credential: options.credential,
2093
+ session: options.session,
2094
+ request: await signRequest({
2095
+ method: request.method,
2096
+ path: request.path,
2097
+ body: bodyBytes(request.body),
2098
+ issuedAt,
2099
+ expiresAt: issuedAt + ttl,
2100
+ signerSecret: options.signerSecret
2101
+ })
2102
+ });
2103
+ };
2104
+ }
2105
+
1896
2106
  // src/rpc/index.ts
1897
2107
  var RpcError = class extends Error {
1898
2108
  constructor(code, message, data, type) {
@@ -2532,105 +2742,6 @@ var EphemeralClient = class {
2532
2742
  }
2533
2743
  };
2534
2744
 
2535
- // src/crypto/internal.ts
2536
- var PKCS8_ED25519_PREFIX = new Uint8Array([
2537
- 48,
2538
- 46,
2539
- 2,
2540
- 1,
2541
- 0,
2542
- 48,
2543
- 5,
2544
- 6,
2545
- 3,
2546
- 43,
2547
- 101,
2548
- 112,
2549
- 4,
2550
- 34,
2551
- 4,
2552
- 32
2553
- ]);
2554
- function u32le(value) {
2555
- const out = new Uint8Array(4);
2556
- new DataView(out.buffer).setUint32(0, value, true);
2557
- return out;
2558
- }
2559
- function hex(bytes) {
2560
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2561
- }
2562
- function fromHex(value, label, expectedBytes) {
2563
- const clean = value.trim();
2564
- if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
2565
- throw new Error(
2566
- `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2567
- );
2568
- }
2569
- const bytes = new Uint8Array(expectedBytes);
2570
- for (let i = 0; i < expectedBytes; i += 1) {
2571
- bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
2572
- }
2573
- return bytes;
2574
- }
2575
- function u64le(value) {
2576
- const out = new Uint8Array(8);
2577
- new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
2578
- return out;
2579
- }
2580
- function concat(...parts) {
2581
- const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
2582
- let offset = 0;
2583
- for (const part of parts) {
2584
- out.set(part, offset);
2585
- offset += part.length;
2586
- }
2587
- return out;
2588
- }
2589
- async function domainHash(domain, parts) {
2590
- const chunks = [u64le(domain.length), domain];
2591
- for (const part of parts) {
2592
- chunks.push(u64le(part.length), part);
2593
- }
2594
- return new Uint8Array(
2595
- await crypto.subtle.digest("SHA-256", concat(...chunks))
2596
- );
2597
- }
2598
- async function importSigningKey(secretHex) {
2599
- const seed = fromHex(secretHex, "deviceSecret", 32);
2600
- const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2601
- try {
2602
- return await crypto.subtle.importKey(
2603
- "pkcs8",
2604
- pkcs8,
2605
- { name: "Ed25519" },
2606
- false,
2607
- ["sign"]
2608
- );
2609
- } catch (cause) {
2610
- const detail = cause instanceof Error ? `: ${cause.message}` : "";
2611
- throw new Error(
2612
- "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
2613
- );
2614
- }
2615
- }
2616
- async function derivePublicKey(secretHex) {
2617
- const seed = fromHex(secretHex, "deviceSecret", 32);
2618
- const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2619
- const key = await crypto.subtle.importKey(
2620
- "pkcs8",
2621
- pkcs8,
2622
- { name: "Ed25519" },
2623
- true,
2624
- ["sign"]
2625
- );
2626
- const jwk = await crypto.subtle.exportKey("jwk", key);
2627
- const raw = jwk.x;
2628
- return Uint8Array.from(
2629
- atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2630
- (c) => c.charCodeAt(0)
2631
- );
2632
- }
2633
-
2634
2745
  // src/device-cert/device-cert.ts
2635
2746
  var CERT_DOMAIN = new TextEncoder().encode("calimero.device.cert.v1");
2636
2747
  var ACCOUNT_ID_DOMAIN = new TextEncoder().encode("calimero.account.genesis.v1");
@@ -5628,6 +5739,10 @@ var CloudClient = class {
5628
5739
  };
5629
5740
 
5630
5741
  // src/mero-js.ts
5742
+ function resolveProofSigner(config) {
5743
+ if (!config) return void 0;
5744
+ return typeof config === "function" ? config : createProofSigner(config);
5745
+ }
5631
5746
  var REFRESH_LOCK_NAME = "mero-js:token-refresh";
5632
5747
  function getLockManager() {
5633
5748
  const nav = globalThis.navigator;
@@ -5666,8 +5781,10 @@ var MeroJs = class {
5666
5781
  this.tokenData = this.tokenStore.getTokens();
5667
5782
  }
5668
5783
  const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
5784
+ const getProof = resolveProofSigner(this.config.requestProof);
5669
5785
  this.httpClient = createBrowserHttpClient({
5670
5786
  baseUrl: this.config.baseUrl,
5787
+ getProof,
5671
5788
  getAuthToken: async () => {
5672
5789
  const token = await this.getValidToken();
5673
5790
  return token?.access_token || "";
@@ -6021,7 +6138,7 @@ var LocalStorageTokenStore = class {
6021
6138
  };
6022
6139
 
6023
6140
  // src/warrant/warrant.ts
6024
- var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v2");
6141
+ var SIGN_DOMAIN2 = new TextEncoder().encode("calimero.warrant.v2");
6025
6142
  var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
6026
6143
  var MAX_CITED_HEADS = 64;
6027
6144
  var UNPINNED_APP = "00".repeat(32);
@@ -6046,7 +6163,7 @@ async function signWarrant(input) {
6046
6163
  const commitment = await intentHash(input.method, input.argsJson);
6047
6164
  const nonce = u64le(input.nonce);
6048
6165
  const notAfter = u64le(input.notAfter);
6049
- const preimage = await domainHash(SIGN_DOMAIN, [
6166
+ const preimage = await domainHash(SIGN_DOMAIN2, [
6050
6167
  context,
6051
6168
  authorAccount,
6052
6169
  publicKey,
@@ -6365,7 +6482,7 @@ function optionalBytes32(value, label) {
6365
6482
  }
6366
6483
  return concat(new Uint8Array([1]), new Uint8Array(value));
6367
6484
  }
6368
- function borshString(value) {
6485
+ function borshString2(value) {
6369
6486
  const bytes = new TextEncoder().encode(value);
6370
6487
  return concat(u32le(bytes.length), bytes);
6371
6488
  }
@@ -6389,7 +6506,7 @@ function encodeSignedInvitation(signed) {
6389
6506
  // ...then the enclosing SignedGroupOpenInvitation, in declaration order.
6390
6507
  // `inviter_account` sits between the signature and the addresses; putting it
6391
6508
  // anywhere else still encodes 32 bytes and still verifies as nothing.
6392
- borshString(signed.inviter_signature),
6509
+ borshString2(signed.inviter_signature),
6393
6510
  signed.inviter_account ? concat(new Uint8Array([1]), fromHex(signed.inviter_account, "inviter_account", 32)) : new Uint8Array([0]),
6394
6511
  // A `Vec<String>`, so each entry is a bare borsh string. This carried an
6395
6512
  // enum until core dropped the URL variant, and an enum writes a
@@ -6397,7 +6514,7 @@ function encodeSignedInvitation(signed) {
6397
6514
  // every following byte and fail as `invalid invitation signature`, pointing
6398
6515
  // nowhere near this line.
6399
6516
  u32le(admitterAddrs.length),
6400
- ...admitterAddrs.map(borshString),
6517
+ ...admitterAddrs.map(borshString2),
6401
6518
  optionalBytes32(signed.application_id, "application_id"),
6402
6519
  // Borsh order follows core's field order, where this is `bytecode_id`; the
6403
6520
  // JSON key is `app_key`, a rename covering both directions that core pins in
@@ -6442,45 +6559,6 @@ async function signMemberJoinOp(input) {
6442
6559
  return hex(concat(signable, signature, unendorsed));
6443
6560
  }
6444
6561
 
6445
- // src/request/request-sig.ts
6446
- var SIGN_DOMAIN2 = new TextEncoder().encode("calimero.auth.request.v1");
6447
- var BODY_DOMAIN = new TextEncoder().encode("calimero.auth.request.body.v1");
6448
- function borshString2(value) {
6449
- const bytes = new TextEncoder().encode(value);
6450
- return concat(u32le(bytes.length), bytes);
6451
- }
6452
- async function requestBodyHash(body = new Uint8Array(0)) {
6453
- const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
6454
- return domainHash(BODY_DOMAIN, [bytes]);
6455
- }
6456
- async function signRequest(input) {
6457
- const key = await importSigningKey(input.signerSecret);
6458
- const bodyHash = await requestBodyHash(input.body);
6459
- const issuedAt = u64le(input.issuedAt);
6460
- const expiresAt = u64le(input.expiresAt);
6461
- const encoder = new TextEncoder();
6462
- const preimage = await domainHash(SIGN_DOMAIN2, [
6463
- encoder.encode(input.method),
6464
- encoder.encode(input.path),
6465
- bodyHash,
6466
- issuedAt,
6467
- expiresAt
6468
- ]);
6469
- const signature = new Uint8Array(
6470
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6471
- );
6472
- return hex(
6473
- concat(
6474
- borshString2(input.method),
6475
- borshString2(input.path),
6476
- bodyHash,
6477
- issuedAt,
6478
- expiresAt,
6479
- signature
6480
- )
6481
- );
6482
- }
6483
-
6484
6562
  // src/login/login.ts
6485
6563
  var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.auth.login.v1");
6486
6564
  var AUDIENCE_TAG = {
@@ -6722,6 +6800,7 @@ export {
6722
6800
  accountRootFromPhrase,
6723
6801
  accountRootFromSecret,
6724
6802
  buildAuthLoginUrl,
6803
+ callerProof,
6725
6804
  combineSignals,
6726
6805
  compareSemver,
6727
6806
  connectCloud,
@@ -6739,6 +6818,7 @@ export {
6739
6818
  createNodeAdminApiClient,
6740
6819
  createNodeAuthApiClient,
6741
6820
  createNodeHttpClient,
6821
+ createProofSigner,
6742
6822
  createRetryableMethod,
6743
6823
  createTimeoutSignal,
6744
6824
  createUniversalHttpClient,