@calimero-network/mero-js 23.4.3 → 23.5.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.mjs CHANGED
@@ -3471,6 +3471,54 @@ async function resolveSigner(secret, signer, label2) {
3471
3471
  throw new Error(`${label2} or signer is required`);
3472
3472
  }
3473
3473
 
3474
+ // src/cloud/account-ownership.ts
3475
+ var DOMAIN = new TextEncoder().encode("calimero.mdma.account-ownership-claim.v1\0");
3476
+ var DEFAULT_TTL_MS = 6e4;
3477
+ var MAX_TTL_MS = 5 * 6e4;
3478
+ var ACCOUNT_OWNERSHIP_AUDIENCE = "mdma:enable-ha-namespace";
3479
+ var ACCOUNT_HA_AUDIENCE = "mdma:enable-ha-namespace-as-account";
3480
+ function base64(bytes) {
3481
+ let binary = "";
3482
+ for (const b of bytes) binary += String.fromCharCode(b);
3483
+ return btoa(binary);
3484
+ }
3485
+ var hex32 = (value, what) => {
3486
+ if (!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${what} must be 64 hex characters`);
3487
+ return value.toLowerCase();
3488
+ };
3489
+ async function signClaim(input, audience, subject) {
3490
+ const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
3491
+ if (!(ttl > 0 && ttl <= MAX_TTL_MS)) throw new Error(`ttlMs must be in (0, ${MAX_TTL_MS}]`);
3492
+ const groupId = hex32(input.namespaceId, "namespaceId");
3493
+ const accountId = hex32(input.accountId, "accountId");
3494
+ const salt = hex32(input.salt, "salt");
3495
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
3496
+ const issued = (input.now ?? Date.now)();
3497
+ const nonce2 = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");
3498
+ const payload = new TextEncoder().encode(
3499
+ JSON.stringify({
3500
+ v: 1,
3501
+ audience,
3502
+ group_id: groupId,
3503
+ account_id: accountId,
3504
+ salt,
3505
+ ...subject === void 0 ? {} : { subject },
3506
+ nonce: nonce2,
3507
+ issued_at_ms: issued,
3508
+ expires_at_ms: issued + ttl
3509
+ })
3510
+ );
3511
+ const signature = await signer.sign(concat(DOMAIN, payload));
3512
+ return { kind: "account", credential: input.credential, signed_payload: base64(payload), signature: base64(signature) };
3513
+ }
3514
+ async function signAccountOwnershipClaim(input) {
3515
+ if (!input.subject) throw new Error("subject must be the cloud login email");
3516
+ return signClaim(input, ACCOUNT_OWNERSHIP_AUDIENCE, input.subject);
3517
+ }
3518
+ async function signAccountHaClaim(input) {
3519
+ return signClaim(input, ACCOUNT_HA_AUDIENCE, void 0);
3520
+ }
3521
+
3474
3522
  // src/device-cert/device-cert.ts
3475
3523
  var CERT_DOMAIN = new TextEncoder().encode("calimero.device.cert.v1");
3476
3524
  var ACCOUNT_ID_DOMAIN = new TextEncoder().encode("calimero.account.genesis.v1");
@@ -5762,7 +5810,7 @@ async function signAccountLink(input) {
5762
5810
  const signature = await signer.sign(
5763
5811
  concat(ACCOUNT_LINK_DOMAIN, new TextEncoder().encode(input.nonce))
5764
5812
  );
5765
- return base64(signature);
5813
+ return base642(signature);
5766
5814
  }
5767
5815
  async function signAccountLogin(input) {
5768
5816
  if (!input.nonce) throw new Error("nonce is required to sign an account login");
@@ -5770,7 +5818,7 @@ async function signAccountLogin(input) {
5770
5818
  const signature = await signer.sign(
5771
5819
  concat(ACCOUNT_LOGIN_DOMAIN, new TextEncoder().encode(input.nonce))
5772
5820
  );
5773
- return base64(signature);
5821
+ return base642(signature);
5774
5822
  }
5775
5823
  async function withPhrase(secret, phrase) {
5776
5824
  return { ...await accountRootFromSecret(secret), phrase };
@@ -5823,7 +5871,7 @@ function bitsOf(bytes) {
5823
5871
  for (const byte of bytes) bits += byte.toString(2).padStart(8, "0");
5824
5872
  return bits;
5825
5873
  }
5826
- function base64(bytes) {
5874
+ function base642(bytes) {
5827
5875
  let binary = "";
5828
5876
  for (const byte of bytes) binary += String.fromCharCode(byte);
5829
5877
  return btoa(binary);
@@ -5843,7 +5891,7 @@ async function signRoutingChallenge(nonce2, deviceSecret) {
5843
5891
  const signature = new Uint8Array(
5844
5892
  await crypto.subtle.sign({ name: "Ed25519" }, key, message)
5845
5893
  );
5846
- return base642(signature);
5894
+ return base643(signature);
5847
5895
  }
5848
5896
  async function routingProofHeaders(challenge, credential) {
5849
5897
  return {
@@ -5855,7 +5903,7 @@ async function routingProofHeaders(challenge, credential) {
5855
5903
  )
5856
5904
  };
5857
5905
  }
5858
- function base642(bytes) {
5906
+ function base643(bytes) {
5859
5907
  let binary = "";
5860
5908
  for (const byte of bytes) binary += String.fromCharCode(byte);
5861
5909
  return btoa(binary);
@@ -5872,6 +5920,39 @@ function teeRoleOf(row) {
5872
5920
  function pickExecutingRelay(relays) {
5873
5921
  return relays.find((r) => r.canExecute && r.relayUrl !== null && r.executorAccount !== null) ?? null;
5874
5922
  }
5923
+ var AccountHaRefusedError = class extends HTTPError {
5924
+ constructor(code, status, statusText, url, headers, bodyText) {
5925
+ super(status, statusText, url, headers, bodyText);
5926
+ this.code = code;
5927
+ this.name = "AccountHaRefusedError";
5928
+ }
5929
+ };
5930
+ var AccountNotLinkedError = class extends AccountHaRefusedError {
5931
+ constructor() {
5932
+ super(...arguments);
5933
+ this.name = "AccountNotLinkedError";
5934
+ }
5935
+ };
5936
+ AccountNotLinkedError.CODE = "account_not_linked";
5937
+ var AccountLinkedToSeveralUsersError = class extends AccountHaRefusedError {
5938
+ constructor() {
5939
+ super(...arguments);
5940
+ this.name = "AccountLinkedToSeveralUsersError";
5941
+ }
5942
+ };
5943
+ AccountLinkedToSeveralUsersError.CODE = "account_linked_to_several_users";
5944
+ function conflictCode(bodyText) {
5945
+ if (!bodyText) return void 0;
5946
+ try {
5947
+ const body = JSON.parse(bodyText);
5948
+ if (typeof body?.error === "string") return body.error;
5949
+ if (typeof body?.detail === "string") return body.detail;
5950
+ const nested = body?.detail?.error;
5951
+ if (typeof nested === "string") return nested;
5952
+ } catch {
5953
+ }
5954
+ return void 0;
5955
+ }
5875
5956
  var REFRESH_HEADER = "x-mdma-session-refresh";
5876
5957
  var REFRESH_EXPIRES_HEADER = "x-mdma-session-expires";
5877
5958
  var CloudClient = class {
@@ -6332,6 +6413,52 @@ var CloudClient = class {
6332
6413
  { body: { groups: [], ownership_proof: ownershipProof } }
6333
6414
  );
6334
6415
  }
6416
+ /**
6417
+ * Enable HA for a namespace this account founded, with **no cloud session**.
6418
+ *
6419
+ * The anonymous sibling of {@link enableFoundedNamespaceHa}: it signs the
6420
+ * founder's claim itself ({@link signAccountHaClaim} — no `subject`, its own
6421
+ * audience) and posts it to
6422
+ * `/api/cloud/accounts/{accountId}/namespaces/{namespaceId}/enable-ha`. The
6423
+ * cloud bills the user the account is linked to through the wallet, so the
6424
+ * account must be linked to exactly one:
6425
+ *
6426
+ * - not linked at all → {@link AccountNotLinkedError};
6427
+ * - linked to several → {@link AccountLinkedToSeveralUsersError}.
6428
+ *
6429
+ * A claim that does not verify is a `403` `HTTPError`; quota and the rest
6430
+ * answer as the session route does. Returns the cloud's response body, the
6431
+ * same shape the session route's enable-ha returns.
6432
+ */
6433
+ async enableHaAsAccount(options) {
6434
+ const proof = await signAccountHaClaim({
6435
+ namespaceId: options.namespaceId,
6436
+ accountId: options.accountId,
6437
+ salt: options.salt,
6438
+ credential: options.credential,
6439
+ deviceSecret: options.deviceSecret,
6440
+ signer: options.signer,
6441
+ ttlMs: options.ttlMs
6442
+ });
6443
+ const account = options.accountId.toLowerCase();
6444
+ const namespace = options.namespaceId.toLowerCase();
6445
+ try {
6446
+ return await this.request(
6447
+ "POST",
6448
+ `/api/cloud/accounts/${encodeURIComponent(account)}/namespaces/${encodeURIComponent(namespace)}/enable-ha`,
6449
+ { body: { ownership_proof: proof }, anonymous: true }
6450
+ );
6451
+ } catch (err) {
6452
+ if (err instanceof HTTPError && err.status === 409) {
6453
+ const code = conflictCode(err.bodyText);
6454
+ const Typed = code === AccountNotLinkedError.CODE ? AccountNotLinkedError : code === AccountLinkedToSeveralUsersError.CODE ? AccountLinkedToSeveralUsersError : void 0;
6455
+ if (Typed && code) {
6456
+ throw new Typed(code, err.status, err.statusText, err.url, err.headers, err.bodyText);
6457
+ }
6458
+ }
6459
+ throw err;
6460
+ }
6461
+ }
6335
6462
  /**
6336
6463
  * Stop hosting a claimed namespace.
6337
6464
  *
@@ -9840,43 +9967,6 @@ function assertSafeReturnTo(returnTo) {
9840
9967
  );
9841
9968
  }
9842
9969
 
9843
- // src/cloud/account-ownership.ts
9844
- var DOMAIN = new TextEncoder().encode("calimero.mdma.account-ownership-claim.v1\0");
9845
- var DEFAULT_TTL_MS = 6e4;
9846
- var MAX_TTL_MS = 5 * 6e4;
9847
- function base643(bytes) {
9848
- let binary = "";
9849
- for (const b of bytes) binary += String.fromCharCode(b);
9850
- return btoa(binary);
9851
- }
9852
- var hex32 = (value, what) => {
9853
- if (!/^[0-9a-fA-F]{64}$/.test(value)) throw new Error(`${what} must be 64 hex characters`);
9854
- return value.toLowerCase();
9855
- };
9856
- async function signAccountOwnershipClaim(input) {
9857
- const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
9858
- if (!(ttl > 0 && ttl <= MAX_TTL_MS)) throw new Error(`ttlMs must be in (0, ${MAX_TTL_MS}]`);
9859
- if (!input.subject) throw new Error("subject must be the cloud login email");
9860
- const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
9861
- const issued = (input.now ?? Date.now)();
9862
- const nonce2 = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");
9863
- const payload = new TextEncoder().encode(
9864
- JSON.stringify({
9865
- v: 1,
9866
- audience: "mdma:enable-ha-namespace",
9867
- group_id: hex32(input.namespaceId, "namespaceId"),
9868
- account_id: hex32(input.accountId, "accountId"),
9869
- salt: hex32(input.salt, "salt"),
9870
- subject: input.subject,
9871
- nonce: nonce2,
9872
- issued_at_ms: issued,
9873
- expires_at_ms: issued + ttl
9874
- })
9875
- );
9876
- const signature = await signer.sign(concat(DOMAIN, payload));
9877
- return { kind: "account", credential: input.credential, signed_payload: base643(payload), signature: base643(signature) };
9878
- }
9879
-
9880
9970
  // src/sealed/verify.ts
9881
9971
  var MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
9882
9972
  var REGISTERS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
@@ -11074,6 +11164,11 @@ async function attestRelayNodeKey(opts) {
11074
11164
  return { nodeKey: hex(nodeKey), mock };
11075
11165
  }
11076
11166
  export {
11167
+ ACCOUNT_HA_AUDIENCE,
11168
+ ACCOUNT_OWNERSHIP_AUDIENCE,
11169
+ AccountHaRefusedError,
11170
+ AccountLinkedToSeveralUsersError,
11171
+ AccountNotLinkedError,
11077
11172
  AdminApiClient,
11078
11173
  AuthApiClient,
11079
11174
  AuthRevokedError,
@@ -11230,6 +11325,7 @@ export {
11230
11325
  rootGuardedOpBytes,
11231
11326
  routingProofHeaders,
11232
11327
  shouldRetain,
11328
+ signAccountHaClaim,
11233
11329
  signAccountLink,
11234
11330
  signAccountLogin,
11235
11331
  signAccountOwnershipClaim,