@calimero-network/mero-js 24.1.3 → 24.3.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.cjs CHANGED
@@ -1766,16 +1766,18 @@ var AdminApiClient = class {
1766
1766
  * Ask what this node can do for a member in `contextId`, before they sign
1767
1767
  * anything.
1768
1768
  *
1769
- * The two facts {@link performIntent} needs and a client cannot derive: whose
1770
- * account goes in the warrant's `executor`, and whether this node holds
1771
- * `CAN_AUTHOR_ON_BEHALF` on the owning group. `canAuthorOnBehalf: false` comes
1772
- * back as an answer rather than an error — it is the default state of every
1773
- * context — so a caller can say "an admin of `groupId` must grant it to
1774
- * `executorAccount`" instead of presenting a warrant that will be refused
1775
- * after the author has spent a nonce on it.
1776
- *
1777
- * `404` means the context belongs to no group, or this node holds no account
1778
- * yet; either way it can be named as no warrant's executor.
1769
+ * The facts {@link performIntent} needs and a client cannot derive: whose
1770
+ * account and key go in the warrant's `executor` and `executor_key`, which
1771
+ * release it pins, and whether this node holds `CAN_AUTHOR_ON_BEHALF` on the
1772
+ * owning group.
1773
+ * `canAuthorOnBehalf: false` comes back as an answer rather than an error, as
1774
+ * it is the default state of every context, so a caller can say "an admin of
1775
+ * `groupId` must grant it to `executorAccount`" instead of presenting a
1776
+ * warrant that will be refused after the author has spent a nonce on it.
1777
+ *
1778
+ * `404` means the context belongs to no group, this node holds no account or
1779
+ * identity in it yet, or its group names no release yet; no warrant can be
1780
+ * spent here until that changes.
1779
1781
  *
1780
1782
  * For a caller with no credential on the node at all — a browser tab holding
1781
1783
  * one signing key — use `RelayClient`, which reads this same route on a node
@@ -7472,7 +7474,7 @@ function splitVersion2(version) {
7472
7474
  var SIGN_DOMAIN2 = new TextEncoder().encode("calimero.warrant.v2");
7473
7475
  var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
7474
7476
  var MAX_CITED_HEADS = 64;
7475
- var UNPINNED_APP = "00".repeat(32);
7477
+ var MAX_RELEASE_VERSION_LEN = 256;
7476
7478
  async function intentHash(method, argsJson) {
7477
7479
  const args = new TextEncoder().encode(JSON.stringify(argsJson));
7478
7480
  return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
@@ -7481,11 +7483,9 @@ async function signWarrant(input) {
7481
7483
  const context = fromHex(input.context, "context", 32);
7482
7484
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
7483
7485
  const executor = fromHex(input.executor, "executor", 32);
7484
- const appVersion = fromHex(
7485
- input.appVersion ?? UNPINNED_APP,
7486
- "appVersion",
7487
- 32
7488
- );
7486
+ const executorKey = fromHex(input.executorKey, "executorKey", 32);
7487
+ const releaseBytecodeId = fromHex(input.releaseBytecodeId, "releaseBytecodeId", 32);
7488
+ const releaseVersion2 = releaseVersionBytes(input.releaseVersion ?? "");
7489
7489
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
7490
7490
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
7491
7491
  const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
@@ -7499,7 +7499,9 @@ async function signWarrant(input) {
7499
7499
  authorAccount,
7500
7500
  publicKey,
7501
7501
  executor,
7502
- appVersion,
7502
+ executorKey,
7503
+ releaseBytecodeId,
7504
+ releaseVersion2,
7503
7505
  method,
7504
7506
  commitment,
7505
7507
  u64le(accountHeads.length),
@@ -7516,7 +7518,10 @@ async function signWarrant(input) {
7516
7518
  authorAccount,
7517
7519
  publicKey,
7518
7520
  executor,
7519
- appVersion,
7521
+ executorKey,
7522
+ releaseBytecodeId,
7523
+ u32le(releaseVersion2.length),
7524
+ releaseVersion2,
7520
7525
  u32le(method.length),
7521
7526
  method,
7522
7527
  commitment,
@@ -7537,6 +7542,18 @@ function checkedSignature(signature) {
7537
7542
  }
7538
7543
  return signature;
7539
7544
  }
7545
+ function releaseVersionBytes(version) {
7546
+ const bytes = new TextEncoder().encode(version);
7547
+ checkReleaseVersionLength(bytes.length);
7548
+ return bytes;
7549
+ }
7550
+ function checkReleaseVersionLength(len) {
7551
+ if (len > MAX_RELEASE_VERSION_LEN) {
7552
+ throw new Error(
7553
+ `releaseVersion is ${len} bytes, over the ${MAX_RELEASE_VERSION_LEN} a node accepts`
7554
+ );
7555
+ }
7556
+ }
7540
7557
  function citedHeads(heads, label2) {
7541
7558
  const list = heads ?? [];
7542
7559
  if (list.length > MAX_CITED_HEADS) {
@@ -7562,6 +7579,7 @@ async function creationWarrantPreimage(p) {
7562
7579
  p.authorAccount,
7563
7580
  p.deviceKey,
7564
7581
  p.executor,
7582
+ p.executorKey,
7565
7583
  p.applicationId,
7566
7584
  new Uint8Array([p.serviceName === null ? 0 : 1]),
7567
7585
  p.serviceName ?? new Uint8Array(0),
@@ -7581,6 +7599,7 @@ async function signCreationWarrant(input) {
7581
7599
  const seed = input.seed === void 0 ? crypto.getRandomValues(new Uint8Array(32)) : fromHex(input.seed, "seed", 32);
7582
7600
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
7583
7601
  const executor = fromHex(input.executor, "executor", 32);
7602
+ const executorKey = fromHex(input.executorKey, "executorKey", 32);
7584
7603
  const applicationId = fromHex(input.applicationId, "applicationId", 32);
7585
7604
  const serviceName = label(input.serviceName, "serviceName");
7586
7605
  const name = label(input.name, "name");
@@ -7595,6 +7614,7 @@ async function signCreationWarrant(input) {
7595
7614
  authorAccount,
7596
7615
  deviceKey,
7597
7616
  executor,
7617
+ executorKey,
7598
7618
  applicationId,
7599
7619
  serviceName,
7600
7620
  name,
@@ -7612,6 +7632,7 @@ async function signCreationWarrant(input) {
7612
7632
  authorAccount,
7613
7633
  deviceKey,
7614
7634
  executor,
7635
+ executorKey,
7615
7636
  applicationId,
7616
7637
  borshOption(serviceName),
7617
7638
  borshOption(name),
@@ -7671,6 +7692,7 @@ function parseCreationWarrant(warrant) {
7671
7692
  authorAccount: hex(take(32, "authorAccount")),
7672
7693
  deviceKey: hex(take(32, "deviceKey")),
7673
7694
  executor: hex(take(32, "executor")),
7695
+ executorKey: hex(take(32, "executorKey")),
7674
7696
  applicationId: hex(take(32, "applicationId")),
7675
7697
  serviceName: option("serviceName"),
7676
7698
  name: option("name"),
@@ -7717,6 +7739,7 @@ async function governanceWarrantPreimage(p) {
7717
7739
  p.authorAccount,
7718
7740
  p.deviceKey,
7719
7741
  p.executor,
7742
+ p.executorKey,
7720
7743
  p.opHash,
7721
7744
  u64le(p.accountHeads.length),
7722
7745
  ...p.accountHeads,
@@ -7730,6 +7753,7 @@ async function signGovernanceWarrant(input) {
7730
7753
  const scope = fromHex(input.scope, "scope", 32);
7731
7754
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
7732
7755
  const executor = fromHex(input.executor, "executor", 32);
7756
+ const executorKey = fromHex(input.executorKey, "executorKey", 32);
7733
7757
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
7734
7758
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
7735
7759
  const kind = kindByte(input.op.kind);
@@ -7742,6 +7766,7 @@ async function signGovernanceWarrant(input) {
7742
7766
  authorAccount,
7743
7767
  deviceKey,
7744
7768
  executor,
7769
+ executorKey,
7745
7770
  opHash,
7746
7771
  accountHeads,
7747
7772
  governanceFloor,
@@ -7756,6 +7781,7 @@ async function signGovernanceWarrant(input) {
7756
7781
  authorAccount,
7757
7782
  deviceKey,
7758
7783
  executor,
7784
+ executorKey,
7759
7785
  opHash,
7760
7786
  u32le(accountHeads.length),
7761
7787
  ...accountHeads,
@@ -7807,6 +7833,7 @@ function parseGovernanceWarrant(warrant) {
7807
7833
  authorAccount: hex(take(32, "authorAccount")),
7808
7834
  deviceKey: hex(take(32, "deviceKey")),
7809
7835
  executor: hex(take(32, "executor")),
7836
+ executorKey: hex(take(32, "executorKey")),
7810
7837
  opHash: hex(take(32, "opHash")),
7811
7838
  accountHeads: heads("accountHeads"),
7812
7839
  governanceFloor: heads("governanceFloor"),
@@ -8111,7 +8138,6 @@ var RelayClient = class {
8111
8138
  constructor(config) {
8112
8139
  this.config = config;
8113
8140
  this.baseUrl = config.relayUrl.replace(/\/+$/, "");
8114
- this.executorAccount = config.executorAccount;
8115
8141
  const injected = config.fetch;
8116
8142
  this.fetchImpl = injected ? (input, init) => injected(input, init) : (input, init) => globalThis.fetch(input, init);
8117
8143
  this.timeoutMs = config.timeoutMs ?? 1e4;
@@ -8161,8 +8187,10 @@ var RelayClient = class {
8161
8187
  * as diagnostics after a failure.
8162
8188
  */
8163
8189
  async describe(contextId) {
8164
- const body = await this.json("GET", `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`);
8165
- this.executorAccount = body.data.executorAccount;
8190
+ const body = await this.json(
8191
+ "GET",
8192
+ `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`
8193
+ );
8166
8194
  return body.data;
8167
8195
  }
8168
8196
  /**
@@ -8173,14 +8201,23 @@ var RelayClient = class {
8173
8201
  * apply. Nothing accumulates and nothing is reusable.
8174
8202
  */
8175
8203
  async execute(contextId, method, argsJson = {}) {
8176
- const executor = this.executorAccount ?? (await this.describe(contextId)).executorAccount;
8204
+ const described = await this.describe(contextId);
8205
+ const executor = this.checkedExecutor(described);
8206
+ const releaseBytecodeId = hex(
8207
+ fromHex(described.releaseBytecodeId ?? "", "the relay's releaseBytecodeId", 32)
8208
+ );
8209
+ const releaseVersion2 = described.releaseVersion ?? "";
8210
+ releaseVersionBytes(releaseVersion2);
8177
8211
  const signer = await this.authorSigner();
8178
8212
  const nonce2 = await this.config.nonces.next();
8179
8213
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
8180
8214
  const warrant = await signWarrant({
8181
8215
  context: contextId,
8182
8216
  authorAccount: this.config.authorAccount,
8183
- executor,
8217
+ executor: executor.executorAccount,
8218
+ executorKey: executor.executorKey,
8219
+ releaseBytecodeId,
8220
+ releaseVersion: releaseVersion2,
8184
8221
  method,
8185
8222
  argsJson,
8186
8223
  nonce: nonce2,
@@ -8260,7 +8297,7 @@ var RelayClient = class {
8260
8297
  403
8261
8298
  );
8262
8299
  }
8263
- const executor = this.checkedExecutor(described.executorAccount);
8300
+ const executor = this.checkedExecutor(described);
8264
8301
  const initArgs = input.initArgs ?? {};
8265
8302
  const signer = await this.authorSigner();
8266
8303
  const nonce2 = await this.config.nonces.next();
@@ -8269,7 +8306,8 @@ var RelayClient = class {
8269
8306
  group: input.groupId,
8270
8307
  seed: input.seed,
8271
8308
  authorAccount: this.config.authorAccount,
8272
- executor,
8309
+ executor: executor.executorAccount,
8310
+ executorKey: executor.executorKey,
8273
8311
  applicationId: input.applicationId,
8274
8312
  serviceName: input.serviceName,
8275
8313
  name: input.name,
@@ -8291,7 +8329,7 @@ var RelayClient = class {
8291
8329
  }
8292
8330
  /**
8293
8331
  * Ask the relay whether it may publish governance ops for members of
8294
- * `groupId`, and which account a warrant must name.
8332
+ * `groupId`, and which account and key a warrant must name.
8295
8333
  */
8296
8334
  async describeGovernance(groupId) {
8297
8335
  const body = await this.json(
@@ -8325,7 +8363,7 @@ var RelayClient = class {
8325
8363
  403
8326
8364
  );
8327
8365
  }
8328
- const executor = this.checkedExecutor(described.executorAccount);
8366
+ const executor = this.checkedExecutor(described);
8329
8367
  const signer = await this.authorSigner();
8330
8368
  const nonce2 = await this.config.nonces.next();
8331
8369
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
@@ -8333,7 +8371,8 @@ var RelayClient = class {
8333
8371
  scope: input.groupId,
8334
8372
  op: input.op,
8335
8373
  authorAccount: this.config.authorAccount,
8336
- executor,
8374
+ executor: executor.executorAccount,
8375
+ executorKey: executor.executorKey,
8337
8376
  nonce: nonce2,
8338
8377
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
8339
8378
  signer
@@ -8359,9 +8398,7 @@ var RelayClient = class {
8359
8398
  *
8360
8399
  * The executor cannot be learned from the relay first: the namespace does not
8361
8400
  * exist, so `describeGovernance` has nothing to answer about. It comes from
8362
- * `input.executorAccount`, the configured one, or one an earlier call learned
8363
- * (`describe`, `createContext`, `govern`), and without any of them this
8364
- * throws before taking a nonce.
8401
+ * `input.executor` only, and without it this throws before taking a nonce.
8365
8402
  * The nonce is spent in the new namespace's ledger.
8366
8403
  *
8367
8404
  * With `defaultCapabilities`, the founder then sets the namespace's default
@@ -8372,13 +8409,13 @@ var RelayClient = class {
8372
8409
  * Refusals (`400`/`403`) come back as {@link IntentRefusedError}; a salt
8373
8410
  * that names an existing namespace is a `409`, an `HTTPError`.
8374
8411
  */
8375
- async foundNamespace(input = {}) {
8376
- const executor = input.executorAccount ? this.checkedExecutor(input.executorAccount) : this.executorAccount;
8377
- if (!executor) {
8412
+ async foundNamespace(input) {
8413
+ if (!input?.executor) {
8378
8414
  throw new Error(
8379
- "foundNamespace needs the relay's account: pass executorAccount or configure it (checked before signing, no nonce was spent)"
8415
+ "foundNamespace needs the relay's account and signing key: pass executor (checked before signing, no nonce was spent)"
8380
8416
  );
8381
8417
  }
8418
+ const executor = this.checkedExecutor(input.executor);
8382
8419
  const salt = input.salt ? hex(fromHex(input.salt, "salt", 32)) : hex(crypto.getRandomValues(new Uint8Array(32)));
8383
8420
  const namespaceId = await foundedNamespaceId(this.config.authorAccount, salt);
8384
8421
  const op = namespaceCreatedOp({
@@ -8395,7 +8432,8 @@ var RelayClient = class {
8395
8432
  scope: namespaceId,
8396
8433
  op,
8397
8434
  authorAccount: this.config.authorAccount,
8398
- executor,
8435
+ executor: executor.executorAccount,
8436
+ executorKey: executor.executorKey,
8399
8437
  nonce: nonce2,
8400
8438
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
8401
8439
  signer
@@ -8436,18 +8474,21 @@ var RelayClient = class {
8436
8474
  return body.data;
8437
8475
  }
8438
8476
  /**
8439
- * The executor a warrant must name: the one the relay answered with, which a
8440
- * configured account must agree with. Remembered for later calls.
8477
+ * The relay's answer, checked against a configured account.
8478
+ * Decoded here so a relay naming no key is refused before a nonce is taken.
8441
8479
  */
8442
8480
  checkedExecutor(answered) {
8481
+ const executor = {
8482
+ executorAccount: hex(fromHex(answered.executorAccount ?? "", "the relay's executorAccount", 32)),
8483
+ executorKey: hex(fromHex(answered.executorKey ?? "", "the relay's executorKey", 32))
8484
+ };
8443
8485
  const configured = this.config.executorAccount;
8444
- if (configured && configured.toLowerCase() !== answered.toLowerCase()) {
8486
+ if (configured && configured.toLowerCase() !== executor.executorAccount) {
8445
8487
  throw new Error(
8446
- `configured executorAccount ${configured} is not the relay's account ${answered}; a warrant naming it would be unspendable`
8488
+ `configured executorAccount ${configured} is not the relay's account ${executor.executorAccount}; a warrant naming it would be unspendable`
8447
8489
  );
8448
8490
  }
8449
- this.executorAccount = answered;
8450
- return answered;
8491
+ return executor;
8451
8492
  }
8452
8493
  /**
8453
8494
  * One request, waiting out a rate limit rather than failing the caller.
@@ -10789,19 +10830,10 @@ async function foundDelegatedNamespace(s, req = {}, deps = {}) {
10789
10830
  const relays = knownRelays(s).map((u) => u.replace(/\/+$/, ""));
10790
10831
  const relay = relays[0];
10791
10832
  if (!relay) throw new Error("no relay is known for this account, so there is nowhere to found a namespace");
10792
- const known = Object.entries(readRelayMap(s.account).namespaces).find(
10793
- ([, url]) => url.replace(/\/+$/, "") === relay
10794
- )?.[0];
10795
- const named = s.executorAccount && s.relayUrl && s.relayUrl.replace(/\/+$/, "") === relay ? s.executorAccount : null;
10796
- if (!named && !known) {
10797
- throw new Error(
10798
- "the executor account of this relay is not known: join a namespace on it first, or connect with the relay's executor account (the cloud shows it beside the relay)"
10799
- );
10800
- }
10801
- const executorAccount = named ?? (await client(s, relay, governanceNonce(relay, known), deps.fetch).describeGovernance(known)).executorAccount;
10833
+ const executor = await foundingExecutor(s, relay, deps.fetch);
10802
10834
  let next = BigInt(Date.now());
10803
10835
  const founded = await client(s, relay, { next: async () => next++ }, deps.fetch).foundNamespace({
10804
- executorAccount,
10836
+ executor,
10805
10837
  ...req.defaultCapabilities !== void 0 ? { defaultCapabilities: req.defaultCapabilities } : {},
10806
10838
  ...req.application ? { application: req.application } : {}
10807
10839
  });
@@ -10821,6 +10853,33 @@ async function foundDelegatedNamespace(s, req = {}, deps = {}) {
10821
10853
  };
10822
10854
  return { namespaceId: founded.namespaceId, teeEnabled: founded.teeEnabled, ...ha };
10823
10855
  }
10856
+ async function foundingExecutor(s, relay, fetch2) {
10857
+ const named = s.executorAccount && s.relayUrl?.replace(/\/+$/, "") === relay ? s.executorAccount : null;
10858
+ if (named) return { executorAccount: named, executorKey: await attestedNodeKey(relay) };
10859
+ const known = Object.entries(readRelayMap(s.account).namespaces).find(
10860
+ ([, url]) => url.replace(/\/+$/, "") === relay
10861
+ )?.[0];
10862
+ if (!known) {
10863
+ throw new Error(
10864
+ "the executor account of this relay is not known: join a namespace on it first, or connect with the relay's executor account (the cloud shows it beside the relay)"
10865
+ );
10866
+ }
10867
+ const executorKey = await attestedNodeKey(relay);
10868
+ const described = await client(s, relay, governanceNonce(relay, known), fetch2).describeGovernance(known);
10869
+ if (described.executorKey?.toLowerCase() !== executorKey) {
10870
+ throw new Error(
10871
+ `the relay's discovery names signing key ${described.executorKey}, not the relay's node key ${executorKey}: refusing to found through it`
10872
+ );
10873
+ }
10874
+ return { executorAccount: described.executorAccount, executorKey };
10875
+ }
10876
+ async function attestedNodeKey(relay) {
10877
+ const key = await attemptRelayNodeKey(relay);
10878
+ if (key.kind === "learned") return key.nodeKey;
10879
+ throw new Error(
10880
+ key.kind === "unavailable" ? "the relay's signing key could not be learned: its attestation did not answer, try again" : "the relay's attestation was refused, so founding through it is not possible (only a key from its operator, pinned with pinRelayNodeKey, can stand in)"
10881
+ );
10882
+ }
10824
10883
  var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so invitees can find this namespace";
10825
10884
  var HA_REFUSAL_MESSAGES = {
10826
10885
  account_not_linked: HA_ACCOUNT_NOT_LINKED_MESSAGE,