@calimero-network/mero-js 22.3.0 → 22.4.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
@@ -79,6 +79,7 @@ __export(index_exports, {
79
79
  createSignedReleaseVerifier: () => createSignedReleaseVerifier,
80
80
  createTimeoutSignal: () => createTimeoutSignal,
81
81
  createUniversalHttpClient: () => createUniversalHttpClient,
82
+ creationInitHash: () => creationInitHash,
82
83
  deviceCertPayload: () => deviceCertPayload,
83
84
  discoverLocalNodes: () => discoverLocalNodes,
84
85
  encodeSignedInvitation: () => encodeSignedInvitation,
@@ -98,6 +99,7 @@ __export(index_exports, {
98
99
  nodeEndpoint: () => nodeEndpoint,
99
100
  nodeReleaseVersionFromImage: () => nodeReleaseVersionFromImage,
100
101
  parseAuthCallback: () => parseAuthCallback,
102
+ parseCreationWarrant: () => parseCreationWarrant,
101
103
  probeNodeHealth: () => probeNodeHealth,
102
104
  redeemInvitation: () => redeemInvitation,
103
105
  requestBodyHash: () => requestBodyHash,
@@ -105,6 +107,7 @@ __export(index_exports, {
105
107
  shouldRetain: () => shouldRetain,
106
108
  signAccountLink: () => signAccountLink,
107
109
  signAccountLogin: () => signAccountLogin,
110
+ signCreationWarrant: () => signCreationWarrant,
108
111
  signDeviceCert: () => signDeviceCert,
109
112
  signLoginStatement: () => signLoginStatement,
110
113
  signMemberJoinOp: () => signMemberJoinOp,
@@ -2306,11 +2309,11 @@ function u32le(value) {
2306
2309
  function hex(bytes) {
2307
2310
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2308
2311
  }
2309
- function fromHex(value, label, expectedBytes) {
2312
+ function fromHex(value, label2, expectedBytes) {
2310
2313
  const clean = value.trim();
2311
2314
  if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
2312
2315
  throw new Error(
2313
- `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2316
+ `${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2314
2317
  );
2315
2318
  }
2316
2319
  const bytes = new Uint8Array(expectedBytes);
@@ -2319,10 +2322,10 @@ function fromHex(value, label, expectedBytes) {
2319
2322
  }
2320
2323
  return bytes;
2321
2324
  }
2322
- function fromHexUnsized(value, label) {
2325
+ function fromHexUnsized(value, label2) {
2323
2326
  const clean = value.trim();
2324
2327
  if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
2325
- throw new Error(`${label} must be an even number of hex characters`);
2328
+ throw new Error(`${label2} must be an even number of hex characters`);
2326
2329
  }
2327
2330
  const bytes = new Uint8Array(clean.length / 2);
2328
2331
  for (let i = 0; i < bytes.length; i += 1) {
@@ -6636,14 +6639,172 @@ async function signWarrant(input) {
6636
6639
  )
6637
6640
  );
6638
6641
  }
6639
- function citedHeads(heads, label) {
6642
+ function citedHeads(heads, label2) {
6640
6643
  const list = heads ?? [];
6641
6644
  if (list.length > MAX_CITED_HEADS) {
6642
6645
  throw new Error(
6643
- `${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
6646
+ `${label2} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
6644
6647
  );
6645
6648
  }
6646
- return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));
6649
+ return list.map((head, i) => fromHex(head, `${label2}[${i}]`, 32));
6650
+ }
6651
+
6652
+ // src/warrant/creation-warrant.ts
6653
+ var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.context-creation-warrant.v1");
6654
+ var INIT_DOMAIN = new TextEncoder().encode("calimero.context-creation.init.v1");
6655
+ var MAX_LABEL_BYTES = 256;
6656
+ async function creationInitHash(initArgs) {
6657
+ const bytes = new TextEncoder().encode(JSON.stringify(initArgs));
6658
+ return domainHash(INIT_DOMAIN, [bytes]);
6659
+ }
6660
+ async function creationWarrantPreimage(p) {
6661
+ return domainHash(SIGN_DOMAIN3, [
6662
+ p.group,
6663
+ p.seed,
6664
+ p.authorAccount,
6665
+ p.deviceKey,
6666
+ p.executor,
6667
+ p.applicationId,
6668
+ new Uint8Array([p.serviceName === null ? 0 : 1]),
6669
+ p.serviceName ?? new Uint8Array(0),
6670
+ new Uint8Array([p.name === null ? 0 : 1]),
6671
+ p.name ?? new Uint8Array(0),
6672
+ p.initHash,
6673
+ u64le(p.accountHeads.length),
6674
+ ...p.accountHeads,
6675
+ u64le(p.governanceFloor.length),
6676
+ ...p.governanceFloor,
6677
+ u64le(p.nonce),
6678
+ u64le(p.notAfter)
6679
+ ]);
6680
+ }
6681
+ async function signCreationWarrant(input) {
6682
+ const group = fromHex(input.group, "group", 32);
6683
+ const seed = input.seed === void 0 ? crypto.getRandomValues(new Uint8Array(32)) : fromHex(input.seed, "seed", 32);
6684
+ const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
6685
+ const executor = fromHex(input.executor, "executor", 32);
6686
+ const applicationId = fromHex(input.applicationId, "applicationId", 32);
6687
+ const serviceName = label(input.serviceName, "serviceName");
6688
+ const name = label(input.name, "name");
6689
+ const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6690
+ const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6691
+ const key = await importSigningKey(input.deviceSecret);
6692
+ const deviceKey = await derivePublicKey(input.deviceSecret);
6693
+ const initHash = await creationInitHash(input.initArgs);
6694
+ const preimage = await creationWarrantPreimage({
6695
+ group,
6696
+ seed,
6697
+ authorAccount,
6698
+ deviceKey,
6699
+ executor,
6700
+ applicationId,
6701
+ serviceName,
6702
+ name,
6703
+ initHash,
6704
+ accountHeads,
6705
+ governanceFloor,
6706
+ nonce: input.nonce,
6707
+ notAfter: input.notAfter
6708
+ });
6709
+ const signature = new Uint8Array(
6710
+ await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6711
+ );
6712
+ const warrant = hex(
6713
+ concat(
6714
+ group,
6715
+ seed,
6716
+ authorAccount,
6717
+ deviceKey,
6718
+ executor,
6719
+ applicationId,
6720
+ borshOption(serviceName),
6721
+ borshOption(name),
6722
+ initHash,
6723
+ u32le(accountHeads.length),
6724
+ ...accountHeads,
6725
+ u32le(governanceFloor.length),
6726
+ ...governanceFloor,
6727
+ u64le(input.nonce),
6728
+ u64le(input.notAfter),
6729
+ signature
6730
+ )
6731
+ );
6732
+ return { warrant, seed: hex(seed) };
6733
+ }
6734
+ function parseCreationWarrant(warrant) {
6735
+ const clean = warrant.trim();
6736
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
6737
+ throw new Error("creation warrant must be an even number of hex characters");
6738
+ }
6739
+ const bytes = new Uint8Array(clean.length / 2);
6740
+ for (let i = 0; i < bytes.length; i += 1) {
6741
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
6742
+ }
6743
+ const view = new DataView(bytes.buffer);
6744
+ let o = 0;
6745
+ const take = (n, what) => {
6746
+ if (o + n > bytes.length) {
6747
+ throw new Error(`creation warrant ends inside ${what} (${bytes.length} bytes)`);
6748
+ }
6749
+ const out = bytes.subarray(o, o + n);
6750
+ o += n;
6751
+ return out;
6752
+ };
6753
+ const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
6754
+ const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
6755
+ const option = (what) => {
6756
+ const tag = take(1, what)[0];
6757
+ if (tag === 0) return null;
6758
+ if (tag !== 1) throw new Error(`${what} has option tag ${tag}, expected 0 or 1`);
6759
+ const len = u32(what);
6760
+ if (len > MAX_LABEL_BYTES) {
6761
+ throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
6762
+ }
6763
+ return new TextDecoder("utf-8", { fatal: true }).decode(take(len, what));
6764
+ };
6765
+ const heads = (what) => {
6766
+ const count = u32(what);
6767
+ if (count > 64) {
6768
+ throw new Error(`${what} cites ${count} heads, over the 64 a node accepts`);
6769
+ }
6770
+ return Array.from({ length: count }, () => hex(take(32, what)));
6771
+ };
6772
+ const fields = {
6773
+ group: hex(take(32, "group")),
6774
+ seed: hex(take(32, "seed")),
6775
+ authorAccount: hex(take(32, "authorAccount")),
6776
+ deviceKey: hex(take(32, "deviceKey")),
6777
+ executor: hex(take(32, "executor")),
6778
+ applicationId: hex(take(32, "applicationId")),
6779
+ serviceName: option("serviceName"),
6780
+ name: option("name"),
6781
+ initHash: hex(take(32, "initHash")),
6782
+ accountHeads: heads("accountHeads"),
6783
+ governanceFloor: heads("governanceFloor"),
6784
+ nonce: u64("nonce"),
6785
+ notAfter: u64("notAfter"),
6786
+ signature: hex(take(64, "signature"))
6787
+ };
6788
+ if (o !== bytes.length) {
6789
+ throw new Error(
6790
+ `creation warrant is ${bytes.length} bytes but its fields account for ${o}`
6791
+ );
6792
+ }
6793
+ return fields;
6794
+ }
6795
+ function label(value, what) {
6796
+ if (value === void 0) return null;
6797
+ const bytes = new TextEncoder().encode(value);
6798
+ if (bytes.length > MAX_LABEL_BYTES) {
6799
+ throw new Error(
6800
+ `${what} is ${bytes.length} UTF-8 bytes, over the ${MAX_LABEL_BYTES} a node accepts`
6801
+ );
6802
+ }
6803
+ return bytes;
6804
+ }
6805
+ function borshOption(bytes) {
6806
+ if (bytes === null) return new Uint8Array([0]);
6807
+ return concat(new Uint8Array([1]), u32le(bytes.length), bytes);
6647
6808
  }
6648
6809
 
6649
6810
  // src/relay/relay-client.ts
@@ -6706,6 +6867,94 @@ var RelayClient = class {
6706
6867
  );
6707
6868
  return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };
6708
6869
  }
6870
+ /**
6871
+ * Ask the relay what it can do about creating contexts in `groupId`.
6872
+ *
6873
+ * Pass `author` (an account, hex) to also learn whether that account may
6874
+ * create here — `authorMayCreate` is present only then.
6875
+ */
6876
+ async describeCreation(groupId, opts = {}) {
6877
+ const query = opts.author ? `?author=${encodeURIComponent(opts.author)}` : "";
6878
+ const body = await this.json(
6879
+ "GET",
6880
+ `/admin-api/groups/${encodeURIComponent(groupId)}/context-intents${query}`
6881
+ );
6882
+ return body.data;
6883
+ }
6884
+ /**
6885
+ * Create a context in a group through the relay, as the author.
6886
+ *
6887
+ * Always asks {@link describeCreation} first — with the author, so both
6888
+ * standing checks happen before anything is signed. A relay without standing
6889
+ * in the group, or an author without `CAN_CREATE_CONTEXT`, is refused here as
6890
+ * an {@link IntentRefusedError} *before* a nonce is taken: the nonce source
6891
+ * is monotonic, and a warrant nobody will spend burns one for nothing.
6892
+ *
6893
+ * The creation warrant's nonce is spent in the new context's per-device
6894
+ * ledger — the same one later `execute` calls in that context draw from — so
6895
+ * it comes from the configured {@link NonceSource} like any other warrant.
6896
+ *
6897
+ * Sealing: nothing here is plaintext-specific. The request goes through the
6898
+ * injected `fetch`, so a `createAttestedSealedFetch` seals it exactly as it
6899
+ * seals `execute`. Whether the relay *accepts* it sealed is the node's call:
6900
+ * a proxy-auth relay only admits sealed requests for routes `merod` serves
6901
+ * without a credential, so `/admin-api/groups/:id/context-intents` must be on
6902
+ * that list on the core side or the request comes back
6903
+ * `403 sealed_route_unguarded`.
6904
+ */
6905
+ async createContext(input) {
6906
+ const described = await this.describeCreation(input.groupId, {
6907
+ author: this.config.authorAccount
6908
+ });
6909
+ if (!described.canCreateOnBehalf) {
6910
+ throw new IntentRefusedError(
6911
+ `the relay (${described.executorAccount}) has no standing to act for members of group ${described.groupId}; an admin must admit it as a relay or grant it CAN_AUTHOR_ON_BEHALF (checked before signing \u2014 no nonce was spent)`,
6912
+ false,
6913
+ 403
6914
+ );
6915
+ }
6916
+ if (described.authorMayCreate === false) {
6917
+ throw new IntentRefusedError(
6918
+ `the author (${this.config.authorAccount}) may not create contexts in group ${described.groupId}; it needs CAN_CREATE_CONTEXT or admin (checked before signing \u2014 no nonce was spent)`,
6919
+ false,
6920
+ 403
6921
+ );
6922
+ }
6923
+ const configured = this.config.executorAccount;
6924
+ if (configured && configured.toLowerCase() !== described.executorAccount.toLowerCase()) {
6925
+ throw new Error(
6926
+ `configured executorAccount ${configured} is not the relay's account ${described.executorAccount}; a warrant naming it would be unspendable`
6927
+ );
6928
+ }
6929
+ const executor = described.executorAccount;
6930
+ this.executorAccount = executor;
6931
+ const initArgs = input.initArgs ?? {};
6932
+ const nonce2 = await this.config.nonces.next();
6933
+ const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
6934
+ const { warrant } = await signCreationWarrant({
6935
+ group: input.groupId,
6936
+ seed: input.seed,
6937
+ authorAccount: this.config.authorAccount,
6938
+ executor,
6939
+ applicationId: input.applicationId,
6940
+ serviceName: input.serviceName,
6941
+ name: input.name,
6942
+ initArgs,
6943
+ nonce: nonce2,
6944
+ notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
6945
+ deviceSecret: this.config.deviceSecret
6946
+ });
6947
+ const body = await this.json(
6948
+ "POST",
6949
+ `/admin-api/groups/${encodeURIComponent(input.groupId)}/context-intents`,
6950
+ { warrant, authorProof: this.config.authorProof, initArgs }
6951
+ );
6952
+ return {
6953
+ contextId: body.data.contextId,
6954
+ groupId: body.data.groupId,
6955
+ memberPublicKey: body.data.memberPublicKey
6956
+ };
6957
+ }
6709
6958
  async json(method, path, body) {
6710
6959
  const headers = { Accept: "application/json" };
6711
6960
  if (body !== void 0) headers["Content-Type"] = "application/json";
@@ -7405,8 +7654,8 @@ var REGISTERS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
7405
7654
  function createQuoteVerifier(options) {
7406
7655
  const allowedMrtd = measurements(options.allowedMrtd ?? [], "allowedMrtd");
7407
7656
  const allowedImages = (options.allowedMeasurements ?? []).map((image, index) => {
7408
- const label = `allowedMeasurements[${index}]`;
7409
- return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${label}.${register}`)[0]).join();
7657
+ const label2 = `allowedMeasurements[${index}]`;
7658
+ return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${label2}.${register}`)[0]).join();
7410
7659
  });
7411
7660
  const allowedRtmr = [
7412
7661
  measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
@@ -7475,11 +7724,11 @@ function createQuoteVerifier(options) {
7475
7724
  };
7476
7725
  return Object.assign(verifier, { includeCollateral: true });
7477
7726
  }
7478
- function measurements(values, label) {
7727
+ function measurements(values, label2) {
7479
7728
  return values.map((value) => {
7480
7729
  const normalized = value.trim().toLowerCase();
7481
7730
  if (!MEASUREMENT_HEX.test(normalized)) {
7482
- throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7731
+ throw new Error(`${label2} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7483
7732
  }
7484
7733
  return normalized;
7485
7734
  });
@@ -8101,18 +8350,18 @@ var ROOT_OP = {
8101
8350
  NamespaceCreatedV2: 9,
8102
8351
  MemberJoinedViaTeeAttestation: 10
8103
8352
  };
8104
- function bytes32(value, label) {
8353
+ function bytes32(value, label2) {
8105
8354
  if (value.length !== 32) {
8106
- throw new Error(`${label} must be 32 bytes, got ${value.length}`);
8355
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
8107
8356
  }
8108
8357
  return new Uint8Array(value);
8109
8358
  }
8110
- function optionalBytes32(value, label) {
8359
+ function optionalBytes32(value, label2) {
8111
8360
  if (value === null || value === void 0) {
8112
8361
  return new Uint8Array([0]);
8113
8362
  }
8114
8363
  if (value.length !== 32) {
8115
- throw new Error(`${label} must be 32 bytes, got ${value.length}`);
8364
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
8116
8365
  }
8117
8366
  return concat(new Uint8Array([1]), new Uint8Array(value));
8118
8367
  }
@@ -8194,7 +8443,7 @@ async function signMemberJoinOp(input) {
8194
8443
  }
8195
8444
 
8196
8445
  // src/login/login.ts
8197
- var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.auth.login.v1");
8446
+ var SIGN_DOMAIN4 = new TextEncoder().encode("calimero.auth.login.v1");
8198
8447
  var AUDIENCE_TAG = {
8199
8448
  webOrigin: 0,
8200
8449
  codeSigningId: 1,
@@ -8233,7 +8482,7 @@ async function signLoginStatement(input) {
8233
8482
  const deviceKey = await derivePublicKey(input.deviceSecret);
8234
8483
  const issuedAt = u64le(input.issuedAt);
8235
8484
  const expiresAt = u64le(input.expiresAt);
8236
- const preimage = await domainHash(SIGN_DOMAIN3, [
8485
+ const preimage = await domainHash(SIGN_DOMAIN4, [
8237
8486
  node,
8238
8487
  audienceSigningBytes(input.audience),
8239
8488
  challenge,