@calimero-network/mero-js 19.24.1 → 19.26.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
@@ -67,6 +67,7 @@ __export(index_exports, {
67
67
  createNodeAuthApiClient: () => createNodeAuthApiClient,
68
68
  createNodeHttpClient: () => createNodeHttpClient,
69
69
  createProofSigner: () => createProofSigner,
70
+ createQuoteVerifier: () => createQuoteVerifier,
70
71
  createRetryableMethod: () => createRetryableMethod,
71
72
  createSealedFetch: () => createSealedFetch,
72
73
  createTimeoutSignal: () => createTimeoutSignal,
@@ -929,6 +930,14 @@ var AdminApiClient = class {
929
930
  async healthCheck() {
930
931
  return unwrap(await this.httpClient.get("/admin-api/health"));
931
932
  }
933
+ /**
934
+ * Whether the node has finished starting and its store answers. Public, like
935
+ * {@link healthCheck}; unlike it, a node that is up but not ready answers
936
+ * `503`, which this surfaces as an `HTTPError` whose body names the stage.
937
+ */
938
+ async readinessCheck() {
939
+ return unwrap(await this.httpClient.get("/admin-api/ready"));
940
+ }
932
941
  async isAuthed() {
933
942
  return this.httpClient.get("/admin-api/is-authed");
934
943
  }
@@ -1323,6 +1332,23 @@ var AdminApiClient = class {
1323
1332
  )
1324
1333
  );
1325
1334
  }
1335
+ /**
1336
+ * Read a context as the session's ACCOUNT, without minting a warrant.
1337
+ *
1338
+ * Needs an account-authenticated session — one from {@link login}, not the
1339
+ * admin password login — and refuses otherwise with a `401`. The account must
1340
+ * be a member of the group owning the context (`403` if not), and the method
1341
+ * must be declared read-only in the app's ABI (`409` if not; a write needs
1342
+ * {@link performIntent}).
1343
+ */
1344
+ async queryContext(contextId, request) {
1345
+ return unwrap(
1346
+ await this.httpClient.post(
1347
+ `/admin-api/contexts/${contextId}/query`,
1348
+ request
1349
+ )
1350
+ );
1351
+ }
1326
1352
  /**
1327
1353
  * Ask what this node can do for a member in `contextId`, before they sign
1328
1354
  * anything.
@@ -1645,6 +1671,32 @@ var AdminApiClient = class {
1645
1671
  }
1646
1672
  return response;
1647
1673
  }
1674
+ /**
1675
+ * Member accounts in the group and the devices bound to each. An admin node
1676
+ * sees every account; a plain member only its own. `403` if this node is not a
1677
+ * member of the group.
1678
+ */
1679
+ async listMemberDevices(groupId, options = {}) {
1680
+ const params = new URLSearchParams();
1681
+ if (options.offset !== void 0) params.set("offset", String(options.offset));
1682
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
1683
+ const query = params.toString();
1684
+ const path = `/admin-api/groups/${groupId}/member-devices${query ? `?${query}` : ""}`;
1685
+ return this.httpClient.get(path);
1686
+ }
1687
+ /**
1688
+ * Seal a small payload to a member account's root key. This node must be a
1689
+ * member of the group, and `account` must be an account the group knows, so
1690
+ * the route reaches no further than the caller already does.
1691
+ */
1692
+ async sealToAccount(groupId, account, request) {
1693
+ return unwrap(
1694
+ await this.httpClient.post(
1695
+ `/admin-api/groups/${groupId}/accounts/${account}/seal`,
1696
+ request
1697
+ )
1698
+ );
1699
+ }
1648
1700
  async listGroupContexts(groupId) {
1649
1701
  return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}/contexts`));
1650
1702
  }
@@ -1903,6 +1955,10 @@ var AdminApiClient = class {
1903
1955
  async teeAttest(request) {
1904
1956
  return unwrap(await this.httpClient.post("/admin-api/tee/attest", request));
1905
1957
  }
1958
+ /**
1959
+ * @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in
1960
+ * core#3262), so this fails. Verify quotes with `createQuoteVerifier`.
1961
+ */
1906
1962
  async teeVerifyQuote(request) {
1907
1963
  return unwrap(
1908
1964
  await this.httpClient.post("/admin-api/tee/verify-quote", request)
@@ -6701,9 +6757,16 @@ async function initiate(remoteStatic, prologue, fixedEphemeral) {
6701
6757
  }
6702
6758
  async function x25519KeyPair(secret) {
6703
6759
  if (!secret) {
6704
- const pair = await crypto.subtle.generateKey({ name: "X25519" }, true, [
6705
- "deriveBits"
6706
- ]);
6760
+ let pair;
6761
+ try {
6762
+ pair = await crypto.subtle.generateKey({ name: "X25519" }, true, [
6763
+ "deriveBits"
6764
+ ]);
6765
+ } catch (error) {
6766
+ throw new Error(
6767
+ `Sealed transport needs WebCrypto with X25519, which this runtime lacks (${error?.name ?? "no crypto.subtle"}). Current browsers and Node have it; in React Native, install react-native-quick-crypto as the global crypto.`
6768
+ );
6769
+ }
6707
6770
  const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
6708
6771
  return { privateKey: pair.privateKey, publicKey };
6709
6772
  }
@@ -6796,7 +6859,8 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
6796
6859
  const attested = await admin.teeAttest({
6797
6860
  nonce: nonce2,
6798
6861
  applicationId: options.applicationId,
6799
- bindTransportKey: true
6862
+ bindTransportKey: true,
6863
+ ...verify.includeCollateral ? { includeCollateral: true } : {}
6800
6864
  });
6801
6865
  if (!attested.transportPublicKey) {
6802
6866
  throw new Error("The node did not report a transport key; it predates sealed transport");
@@ -6804,7 +6868,13 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
6804
6868
  const transportKey = fromHex(attested.transportPublicKey, "transportPublicKey", 32);
6805
6869
  const inner = options.applicationHash ? fromHex(options.applicationHash, "applicationHash", 32) : new Uint8Array(32);
6806
6870
  const reportDataSuffix = hex(await transportKeyBinding(inner, transportKey));
6807
- if (!await verify({ quoteB64: attested.quoteB64, nonce: nonce2, reportDataSuffix })) {
6871
+ const verified = await verify({
6872
+ quoteB64: attested.quoteB64,
6873
+ nonce: nonce2,
6874
+ reportDataSuffix,
6875
+ collateral: attested.collateral
6876
+ });
6877
+ if (!verified) {
6808
6878
  throw new Error("The attestation did not verify, so its transport key is not trusted");
6809
6879
  }
6810
6880
  return transportKey;
@@ -7066,6 +7136,83 @@ async function envelopeRefusal(response) {
7066
7136
  return new SealedTransportError(response.status, code, message, retryAfterMs);
7067
7137
  }
7068
7138
 
7139
+ // src/sealed/verify.ts
7140
+ var MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
7141
+ function createQuoteVerifier(options) {
7142
+ const allowedMrtd = measurements(options.allowedMrtd, "allowedMrtd");
7143
+ if (allowedMrtd.length === 0) {
7144
+ throw new Error("allowedMrtd is empty, so no quote would ever be accepted");
7145
+ }
7146
+ const allowedRtmr = [
7147
+ measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
7148
+ measurements(options.allowedRtmr1 ?? [], "allowedRtmr1"),
7149
+ measurements(options.allowedRtmr2 ?? [], "allowedRtmr2"),
7150
+ measurements(options.allowedRtmr3 ?? [], "allowedRtmr3")
7151
+ ];
7152
+ const allowedStatuses = options.allowedTcbStatuses ?? ["UpToDate"];
7153
+ const now = options.now ?? Date.now;
7154
+ const verifier = async ({
7155
+ quoteB64,
7156
+ nonce: nonce2,
7157
+ reportDataSuffix,
7158
+ collateral
7159
+ }) => {
7160
+ const quote = fromBase64(quoteB64);
7161
+ const against = collateral ?? await options.fetchCollateral?.(quote);
7162
+ if (!against) {
7163
+ throw new Error(
7164
+ "The node sent no collateral for its quote, and no fetchCollateral was given to get it"
7165
+ );
7166
+ }
7167
+ let verified;
7168
+ try {
7169
+ verified = options.dcapVerify(quote, against, Math.floor(now() / 1e3));
7170
+ } catch (error) {
7171
+ throw new Error(`The quote did not verify: ${error?.message ?? String(error)}`);
7172
+ }
7173
+ if (verified.status === "Revoked" || !allowedStatuses.includes(verified.status)) {
7174
+ throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);
7175
+ }
7176
+ const td = tdReport(verified.report);
7177
+ if (!allowedMrtd.includes(hex(td.mrTd))) {
7178
+ throw new Error(`MRTD ${hex(td.mrTd)} is not an image this verifier trusts`);
7179
+ }
7180
+ const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];
7181
+ registers.forEach((register, index) => {
7182
+ const allowed = allowedRtmr[index];
7183
+ if (allowed.length > 0 && !allowed.includes(hex(register))) {
7184
+ throw new Error(`RTMR${index} ${hex(register)} is not one this verifier trusts`);
7185
+ }
7186
+ });
7187
+ if (hex(td.reportData) !== `${nonce2}${reportDataSuffix}`.toLowerCase()) {
7188
+ throw new Error("The quote does not commit to this nonce and binding");
7189
+ }
7190
+ return true;
7191
+ };
7192
+ return Object.assign(verifier, { includeCollateral: true });
7193
+ }
7194
+ function measurements(values, label) {
7195
+ return values.map((value) => {
7196
+ const normalized = value.trim().toLowerCase();
7197
+ if (!MEASUREMENT_HEX.test(normalized)) {
7198
+ throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7199
+ }
7200
+ return normalized;
7201
+ });
7202
+ }
7203
+ function tdReport(report) {
7204
+ const data = report.data;
7205
+ if (report.type === "td10" && data) return data;
7206
+ if (report.type === "td15" && data?.base) return data.base;
7207
+ throw new Error(`The quote is a ${report.type} quote, not a TDX one`);
7208
+ }
7209
+ function fromBase64(value) {
7210
+ const binary = atob(value);
7211
+ const bytes = new Uint8Array(binary.length);
7212
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
7213
+ return bytes;
7214
+ }
7215
+
7069
7216
  // src/namespace-op/namespace-op.ts
7070
7217
  var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 8;
7071
7218
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");