@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.mjs CHANGED
@@ -823,6 +823,14 @@ var AdminApiClient = class {
823
823
  async healthCheck() {
824
824
  return unwrap(await this.httpClient.get("/admin-api/health"));
825
825
  }
826
+ /**
827
+ * Whether the node has finished starting and its store answers. Public, like
828
+ * {@link healthCheck}; unlike it, a node that is up but not ready answers
829
+ * `503`, which this surfaces as an `HTTPError` whose body names the stage.
830
+ */
831
+ async readinessCheck() {
832
+ return unwrap(await this.httpClient.get("/admin-api/ready"));
833
+ }
826
834
  async isAuthed() {
827
835
  return this.httpClient.get("/admin-api/is-authed");
828
836
  }
@@ -1217,6 +1225,23 @@ var AdminApiClient = class {
1217
1225
  )
1218
1226
  );
1219
1227
  }
1228
+ /**
1229
+ * Read a context as the session's ACCOUNT, without minting a warrant.
1230
+ *
1231
+ * Needs an account-authenticated session — one from {@link login}, not the
1232
+ * admin password login — and refuses otherwise with a `401`. The account must
1233
+ * be a member of the group owning the context (`403` if not), and the method
1234
+ * must be declared read-only in the app's ABI (`409` if not; a write needs
1235
+ * {@link performIntent}).
1236
+ */
1237
+ async queryContext(contextId, request) {
1238
+ return unwrap(
1239
+ await this.httpClient.post(
1240
+ `/admin-api/contexts/${contextId}/query`,
1241
+ request
1242
+ )
1243
+ );
1244
+ }
1220
1245
  /**
1221
1246
  * Ask what this node can do for a member in `contextId`, before they sign
1222
1247
  * anything.
@@ -1539,6 +1564,32 @@ var AdminApiClient = class {
1539
1564
  }
1540
1565
  return response;
1541
1566
  }
1567
+ /**
1568
+ * Member accounts in the group and the devices bound to each. An admin node
1569
+ * sees every account; a plain member only its own. `403` if this node is not a
1570
+ * member of the group.
1571
+ */
1572
+ async listMemberDevices(groupId, options = {}) {
1573
+ const params = new URLSearchParams();
1574
+ if (options.offset !== void 0) params.set("offset", String(options.offset));
1575
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
1576
+ const query = params.toString();
1577
+ const path = `/admin-api/groups/${groupId}/member-devices${query ? `?${query}` : ""}`;
1578
+ return this.httpClient.get(path);
1579
+ }
1580
+ /**
1581
+ * Seal a small payload to a member account's root key. This node must be a
1582
+ * member of the group, and `account` must be an account the group knows, so
1583
+ * the route reaches no further than the caller already does.
1584
+ */
1585
+ async sealToAccount(groupId, account, request) {
1586
+ return unwrap(
1587
+ await this.httpClient.post(
1588
+ `/admin-api/groups/${groupId}/accounts/${account}/seal`,
1589
+ request
1590
+ )
1591
+ );
1592
+ }
1542
1593
  async listGroupContexts(groupId) {
1543
1594
  return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}/contexts`));
1544
1595
  }
@@ -1797,6 +1848,10 @@ var AdminApiClient = class {
1797
1848
  async teeAttest(request) {
1798
1849
  return unwrap(await this.httpClient.post("/admin-api/tee/attest", request));
1799
1850
  }
1851
+ /**
1852
+ * @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in
1853
+ * core#3262), so this fails. Verify quotes with `createQuoteVerifier`.
1854
+ */
1800
1855
  async teeVerifyQuote(request) {
1801
1856
  return unwrap(
1802
1857
  await this.httpClient.post("/admin-api/tee/verify-quote", request)
@@ -6595,9 +6650,16 @@ async function initiate(remoteStatic, prologue, fixedEphemeral) {
6595
6650
  }
6596
6651
  async function x25519KeyPair(secret) {
6597
6652
  if (!secret) {
6598
- const pair = await crypto.subtle.generateKey({ name: "X25519" }, true, [
6599
- "deriveBits"
6600
- ]);
6653
+ let pair;
6654
+ try {
6655
+ pair = await crypto.subtle.generateKey({ name: "X25519" }, true, [
6656
+ "deriveBits"
6657
+ ]);
6658
+ } catch (error) {
6659
+ throw new Error(
6660
+ `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.`
6661
+ );
6662
+ }
6601
6663
  const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
6602
6664
  return { privateKey: pair.privateKey, publicKey };
6603
6665
  }
@@ -6690,7 +6752,8 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
6690
6752
  const attested = await admin.teeAttest({
6691
6753
  nonce: nonce2,
6692
6754
  applicationId: options.applicationId,
6693
- bindTransportKey: true
6755
+ bindTransportKey: true,
6756
+ ...verify.includeCollateral ? { includeCollateral: true } : {}
6694
6757
  });
6695
6758
  if (!attested.transportPublicKey) {
6696
6759
  throw new Error("The node did not report a transport key; it predates sealed transport");
@@ -6698,7 +6761,13 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
6698
6761
  const transportKey = fromHex(attested.transportPublicKey, "transportPublicKey", 32);
6699
6762
  const inner = options.applicationHash ? fromHex(options.applicationHash, "applicationHash", 32) : new Uint8Array(32);
6700
6763
  const reportDataSuffix = hex(await transportKeyBinding(inner, transportKey));
6701
- if (!await verify({ quoteB64: attested.quoteB64, nonce: nonce2, reportDataSuffix })) {
6764
+ const verified = await verify({
6765
+ quoteB64: attested.quoteB64,
6766
+ nonce: nonce2,
6767
+ reportDataSuffix,
6768
+ collateral: attested.collateral
6769
+ });
6770
+ if (!verified) {
6702
6771
  throw new Error("The attestation did not verify, so its transport key is not trusted");
6703
6772
  }
6704
6773
  return transportKey;
@@ -6960,6 +7029,83 @@ async function envelopeRefusal(response) {
6960
7029
  return new SealedTransportError(response.status, code, message, retryAfterMs);
6961
7030
  }
6962
7031
 
7032
+ // src/sealed/verify.ts
7033
+ var MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
7034
+ function createQuoteVerifier(options) {
7035
+ const allowedMrtd = measurements(options.allowedMrtd, "allowedMrtd");
7036
+ if (allowedMrtd.length === 0) {
7037
+ throw new Error("allowedMrtd is empty, so no quote would ever be accepted");
7038
+ }
7039
+ const allowedRtmr = [
7040
+ measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
7041
+ measurements(options.allowedRtmr1 ?? [], "allowedRtmr1"),
7042
+ measurements(options.allowedRtmr2 ?? [], "allowedRtmr2"),
7043
+ measurements(options.allowedRtmr3 ?? [], "allowedRtmr3")
7044
+ ];
7045
+ const allowedStatuses = options.allowedTcbStatuses ?? ["UpToDate"];
7046
+ const now = options.now ?? Date.now;
7047
+ const verifier = async ({
7048
+ quoteB64,
7049
+ nonce: nonce2,
7050
+ reportDataSuffix,
7051
+ collateral
7052
+ }) => {
7053
+ const quote = fromBase64(quoteB64);
7054
+ const against = collateral ?? await options.fetchCollateral?.(quote);
7055
+ if (!against) {
7056
+ throw new Error(
7057
+ "The node sent no collateral for its quote, and no fetchCollateral was given to get it"
7058
+ );
7059
+ }
7060
+ let verified;
7061
+ try {
7062
+ verified = options.dcapVerify(quote, against, Math.floor(now() / 1e3));
7063
+ } catch (error) {
7064
+ throw new Error(`The quote did not verify: ${error?.message ?? String(error)}`);
7065
+ }
7066
+ if (verified.status === "Revoked" || !allowedStatuses.includes(verified.status)) {
7067
+ throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);
7068
+ }
7069
+ const td = tdReport(verified.report);
7070
+ if (!allowedMrtd.includes(hex(td.mrTd))) {
7071
+ throw new Error(`MRTD ${hex(td.mrTd)} is not an image this verifier trusts`);
7072
+ }
7073
+ const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];
7074
+ registers.forEach((register, index) => {
7075
+ const allowed = allowedRtmr[index];
7076
+ if (allowed.length > 0 && !allowed.includes(hex(register))) {
7077
+ throw new Error(`RTMR${index} ${hex(register)} is not one this verifier trusts`);
7078
+ }
7079
+ });
7080
+ if (hex(td.reportData) !== `${nonce2}${reportDataSuffix}`.toLowerCase()) {
7081
+ throw new Error("The quote does not commit to this nonce and binding");
7082
+ }
7083
+ return true;
7084
+ };
7085
+ return Object.assign(verifier, { includeCollateral: true });
7086
+ }
7087
+ function measurements(values, label) {
7088
+ return values.map((value) => {
7089
+ const normalized = value.trim().toLowerCase();
7090
+ if (!MEASUREMENT_HEX.test(normalized)) {
7091
+ throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7092
+ }
7093
+ return normalized;
7094
+ });
7095
+ }
7096
+ function tdReport(report) {
7097
+ const data = report.data;
7098
+ if (report.type === "td10" && data) return data;
7099
+ if (report.type === "td15" && data?.base) return data.base;
7100
+ throw new Error(`The quote is a ${report.type} quote, not a TDX one`);
7101
+ }
7102
+ function fromBase64(value) {
7103
+ const binary = atob(value);
7104
+ const bytes = new Uint8Array(binary.length);
7105
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
7106
+ return bytes;
7107
+ }
7108
+
6963
7109
  // src/namespace-op/namespace-op.ts
6964
7110
  var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 8;
6965
7111
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
@@ -7335,6 +7481,7 @@ export {
7335
7481
  createNodeAuthApiClient,
7336
7482
  createNodeHttpClient,
7337
7483
  createProofSigner,
7484
+ createQuoteVerifier,
7338
7485
  createRetryableMethod,
7339
7486
  createSealedFetch,
7340
7487
  createTimeoutSignal,