@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.d.ts CHANGED
@@ -18,14 +18,14 @@ export type { Codec, EphemeralEntry } from "./ephemeral/index.js";
18
18
  export * from "./cloud/index.js";
19
19
  export * from "./sealed/index.js";
20
20
  export * from "./relay/index.js";
21
- export { signWarrant, intentHash } from "./warrant/index.js";
21
+ export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, } from "./warrant/index.js";
22
22
  export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, signAccountLink, signAccountLogin, } from "./account/index.js";
23
23
  export type { AccountRoot, RecoverableAccountRoot, AccountLinkInput, AccountLoginInput, } from "./account/index.js";
24
24
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, } from "./device-cert/index.js";
25
25
  export type { DeviceCertInput } from "./device-cert/index.js";
26
26
  export { signMemberJoinOp, encodeSignedInvitation, SIGNED_NAMESPACE_OP_SCHEMA_VERSION, } from "./namespace-op/index.js";
27
27
  export type { SignMemberJoinInput } from "./namespace-op/index.js";
28
- export type { WarrantInput } from "./warrant/index.js";
28
+ export type { WarrantInput, CreationWarrantInput, CreationWarrantFields, SignedCreationWarrant, } from "./warrant/index.js";
29
29
  export { requestBodyHash, signRequest, callerProof, createProofSigner, } from "./request/index.js";
30
30
  export type { CallerProofInput, ProofRequest, ProofSignerOptions, } from "./request/index.js";
31
31
  export type { RequestSigInput } from "./request/index.js";
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ export * from "./sealed/index.js";
27
27
  // Relay client — write through delegated execution, holding only a signing key
28
28
  export * from "./relay/index.js";
29
29
  // Warrant signing — mint the author's consent for a relay to run one intent
30
- export { signWarrant, intentHash } from "./warrant/index.js";
30
+ export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, } from "./warrant/index.js";
31
31
  // Account roots — mint one with a recovery phrase, or prove you hold one
32
32
  export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, signAccountLink, signAccountLogin, } from "./account/index.js";
33
33
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, } from "./device-cert/index.js";
package/dist/index.mjs CHANGED
@@ -2183,11 +2183,11 @@ function u32le(value) {
2183
2183
  function hex(bytes) {
2184
2184
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2185
2185
  }
2186
- function fromHex(value, label, expectedBytes) {
2186
+ function fromHex(value, label2, expectedBytes) {
2187
2187
  const clean = value.trim();
2188
2188
  if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
2189
2189
  throw new Error(
2190
- `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2190
+ `${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2191
2191
  );
2192
2192
  }
2193
2193
  const bytes = new Uint8Array(expectedBytes);
@@ -2196,10 +2196,10 @@ function fromHex(value, label, expectedBytes) {
2196
2196
  }
2197
2197
  return bytes;
2198
2198
  }
2199
- function fromHexUnsized(value, label) {
2199
+ function fromHexUnsized(value, label2) {
2200
2200
  const clean = value.trim();
2201
2201
  if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
2202
- throw new Error(`${label} must be an even number of hex characters`);
2202
+ throw new Error(`${label2} must be an even number of hex characters`);
2203
2203
  }
2204
2204
  const bytes = new Uint8Array(clean.length / 2);
2205
2205
  for (let i = 0; i < bytes.length; i += 1) {
@@ -6513,14 +6513,172 @@ async function signWarrant(input) {
6513
6513
  )
6514
6514
  );
6515
6515
  }
6516
- function citedHeads(heads, label) {
6516
+ function citedHeads(heads, label2) {
6517
6517
  const list = heads ?? [];
6518
6518
  if (list.length > MAX_CITED_HEADS) {
6519
6519
  throw new Error(
6520
- `${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
6520
+ `${label2} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
6521
6521
  );
6522
6522
  }
6523
- return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));
6523
+ return list.map((head, i) => fromHex(head, `${label2}[${i}]`, 32));
6524
+ }
6525
+
6526
+ // src/warrant/creation-warrant.ts
6527
+ var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.context-creation-warrant.v1");
6528
+ var INIT_DOMAIN = new TextEncoder().encode("calimero.context-creation.init.v1");
6529
+ var MAX_LABEL_BYTES = 256;
6530
+ async function creationInitHash(initArgs) {
6531
+ const bytes = new TextEncoder().encode(JSON.stringify(initArgs));
6532
+ return domainHash(INIT_DOMAIN, [bytes]);
6533
+ }
6534
+ async function creationWarrantPreimage(p) {
6535
+ return domainHash(SIGN_DOMAIN3, [
6536
+ p.group,
6537
+ p.seed,
6538
+ p.authorAccount,
6539
+ p.deviceKey,
6540
+ p.executor,
6541
+ p.applicationId,
6542
+ new Uint8Array([p.serviceName === null ? 0 : 1]),
6543
+ p.serviceName ?? new Uint8Array(0),
6544
+ new Uint8Array([p.name === null ? 0 : 1]),
6545
+ p.name ?? new Uint8Array(0),
6546
+ p.initHash,
6547
+ u64le(p.accountHeads.length),
6548
+ ...p.accountHeads,
6549
+ u64le(p.governanceFloor.length),
6550
+ ...p.governanceFloor,
6551
+ u64le(p.nonce),
6552
+ u64le(p.notAfter)
6553
+ ]);
6554
+ }
6555
+ async function signCreationWarrant(input) {
6556
+ const group = fromHex(input.group, "group", 32);
6557
+ const seed = input.seed === void 0 ? crypto.getRandomValues(new Uint8Array(32)) : fromHex(input.seed, "seed", 32);
6558
+ const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
6559
+ const executor = fromHex(input.executor, "executor", 32);
6560
+ const applicationId = fromHex(input.applicationId, "applicationId", 32);
6561
+ const serviceName = label(input.serviceName, "serviceName");
6562
+ const name = label(input.name, "name");
6563
+ const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6564
+ const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6565
+ const key = await importSigningKey(input.deviceSecret);
6566
+ const deviceKey = await derivePublicKey(input.deviceSecret);
6567
+ const initHash = await creationInitHash(input.initArgs);
6568
+ const preimage = await creationWarrantPreimage({
6569
+ group,
6570
+ seed,
6571
+ authorAccount,
6572
+ deviceKey,
6573
+ executor,
6574
+ applicationId,
6575
+ serviceName,
6576
+ name,
6577
+ initHash,
6578
+ accountHeads,
6579
+ governanceFloor,
6580
+ nonce: input.nonce,
6581
+ notAfter: input.notAfter
6582
+ });
6583
+ const signature = new Uint8Array(
6584
+ await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6585
+ );
6586
+ const warrant = hex(
6587
+ concat(
6588
+ group,
6589
+ seed,
6590
+ authorAccount,
6591
+ deviceKey,
6592
+ executor,
6593
+ applicationId,
6594
+ borshOption(serviceName),
6595
+ borshOption(name),
6596
+ initHash,
6597
+ u32le(accountHeads.length),
6598
+ ...accountHeads,
6599
+ u32le(governanceFloor.length),
6600
+ ...governanceFloor,
6601
+ u64le(input.nonce),
6602
+ u64le(input.notAfter),
6603
+ signature
6604
+ )
6605
+ );
6606
+ return { warrant, seed: hex(seed) };
6607
+ }
6608
+ function parseCreationWarrant(warrant) {
6609
+ const clean = warrant.trim();
6610
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
6611
+ throw new Error("creation warrant must be an even number of hex characters");
6612
+ }
6613
+ const bytes = new Uint8Array(clean.length / 2);
6614
+ for (let i = 0; i < bytes.length; i += 1) {
6615
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
6616
+ }
6617
+ const view = new DataView(bytes.buffer);
6618
+ let o = 0;
6619
+ const take = (n, what) => {
6620
+ if (o + n > bytes.length) {
6621
+ throw new Error(`creation warrant ends inside ${what} (${bytes.length} bytes)`);
6622
+ }
6623
+ const out = bytes.subarray(o, o + n);
6624
+ o += n;
6625
+ return out;
6626
+ };
6627
+ const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
6628
+ const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
6629
+ const option = (what) => {
6630
+ const tag = take(1, what)[0];
6631
+ if (tag === 0) return null;
6632
+ if (tag !== 1) throw new Error(`${what} has option tag ${tag}, expected 0 or 1`);
6633
+ const len = u32(what);
6634
+ if (len > MAX_LABEL_BYTES) {
6635
+ throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
6636
+ }
6637
+ return new TextDecoder("utf-8", { fatal: true }).decode(take(len, what));
6638
+ };
6639
+ const heads = (what) => {
6640
+ const count = u32(what);
6641
+ if (count > 64) {
6642
+ throw new Error(`${what} cites ${count} heads, over the 64 a node accepts`);
6643
+ }
6644
+ return Array.from({ length: count }, () => hex(take(32, what)));
6645
+ };
6646
+ const fields = {
6647
+ group: hex(take(32, "group")),
6648
+ seed: hex(take(32, "seed")),
6649
+ authorAccount: hex(take(32, "authorAccount")),
6650
+ deviceKey: hex(take(32, "deviceKey")),
6651
+ executor: hex(take(32, "executor")),
6652
+ applicationId: hex(take(32, "applicationId")),
6653
+ serviceName: option("serviceName"),
6654
+ name: option("name"),
6655
+ initHash: hex(take(32, "initHash")),
6656
+ accountHeads: heads("accountHeads"),
6657
+ governanceFloor: heads("governanceFloor"),
6658
+ nonce: u64("nonce"),
6659
+ notAfter: u64("notAfter"),
6660
+ signature: hex(take(64, "signature"))
6661
+ };
6662
+ if (o !== bytes.length) {
6663
+ throw new Error(
6664
+ `creation warrant is ${bytes.length} bytes but its fields account for ${o}`
6665
+ );
6666
+ }
6667
+ return fields;
6668
+ }
6669
+ function label(value, what) {
6670
+ if (value === void 0) return null;
6671
+ const bytes = new TextEncoder().encode(value);
6672
+ if (bytes.length > MAX_LABEL_BYTES) {
6673
+ throw new Error(
6674
+ `${what} is ${bytes.length} UTF-8 bytes, over the ${MAX_LABEL_BYTES} a node accepts`
6675
+ );
6676
+ }
6677
+ return bytes;
6678
+ }
6679
+ function borshOption(bytes) {
6680
+ if (bytes === null) return new Uint8Array([0]);
6681
+ return concat(new Uint8Array([1]), u32le(bytes.length), bytes);
6524
6682
  }
6525
6683
 
6526
6684
  // src/relay/relay-client.ts
@@ -6583,6 +6741,94 @@ var RelayClient = class {
6583
6741
  );
6584
6742
  return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };
6585
6743
  }
6744
+ /**
6745
+ * Ask the relay what it can do about creating contexts in `groupId`.
6746
+ *
6747
+ * Pass `author` (an account, hex) to also learn whether that account may
6748
+ * create here — `authorMayCreate` is present only then.
6749
+ */
6750
+ async describeCreation(groupId, opts = {}) {
6751
+ const query = opts.author ? `?author=${encodeURIComponent(opts.author)}` : "";
6752
+ const body = await this.json(
6753
+ "GET",
6754
+ `/admin-api/groups/${encodeURIComponent(groupId)}/context-intents${query}`
6755
+ );
6756
+ return body.data;
6757
+ }
6758
+ /**
6759
+ * Create a context in a group through the relay, as the author.
6760
+ *
6761
+ * Always asks {@link describeCreation} first — with the author, so both
6762
+ * standing checks happen before anything is signed. A relay without standing
6763
+ * in the group, or an author without `CAN_CREATE_CONTEXT`, is refused here as
6764
+ * an {@link IntentRefusedError} *before* a nonce is taken: the nonce source
6765
+ * is monotonic, and a warrant nobody will spend burns one for nothing.
6766
+ *
6767
+ * The creation warrant's nonce is spent in the new context's per-device
6768
+ * ledger — the same one later `execute` calls in that context draw from — so
6769
+ * it comes from the configured {@link NonceSource} like any other warrant.
6770
+ *
6771
+ * Sealing: nothing here is plaintext-specific. The request goes through the
6772
+ * injected `fetch`, so a `createAttestedSealedFetch` seals it exactly as it
6773
+ * seals `execute`. Whether the relay *accepts* it sealed is the node's call:
6774
+ * a proxy-auth relay only admits sealed requests for routes `merod` serves
6775
+ * without a credential, so `/admin-api/groups/:id/context-intents` must be on
6776
+ * that list on the core side or the request comes back
6777
+ * `403 sealed_route_unguarded`.
6778
+ */
6779
+ async createContext(input) {
6780
+ const described = await this.describeCreation(input.groupId, {
6781
+ author: this.config.authorAccount
6782
+ });
6783
+ if (!described.canCreateOnBehalf) {
6784
+ throw new IntentRefusedError(
6785
+ `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)`,
6786
+ false,
6787
+ 403
6788
+ );
6789
+ }
6790
+ if (described.authorMayCreate === false) {
6791
+ throw new IntentRefusedError(
6792
+ `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)`,
6793
+ false,
6794
+ 403
6795
+ );
6796
+ }
6797
+ const configured = this.config.executorAccount;
6798
+ if (configured && configured.toLowerCase() !== described.executorAccount.toLowerCase()) {
6799
+ throw new Error(
6800
+ `configured executorAccount ${configured} is not the relay's account ${described.executorAccount}; a warrant naming it would be unspendable`
6801
+ );
6802
+ }
6803
+ const executor = described.executorAccount;
6804
+ this.executorAccount = executor;
6805
+ const initArgs = input.initArgs ?? {};
6806
+ const nonce2 = await this.config.nonces.next();
6807
+ const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
6808
+ const { warrant } = await signCreationWarrant({
6809
+ group: input.groupId,
6810
+ seed: input.seed,
6811
+ authorAccount: this.config.authorAccount,
6812
+ executor,
6813
+ applicationId: input.applicationId,
6814
+ serviceName: input.serviceName,
6815
+ name: input.name,
6816
+ initArgs,
6817
+ nonce: nonce2,
6818
+ notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
6819
+ deviceSecret: this.config.deviceSecret
6820
+ });
6821
+ const body = await this.json(
6822
+ "POST",
6823
+ `/admin-api/groups/${encodeURIComponent(input.groupId)}/context-intents`,
6824
+ { warrant, authorProof: this.config.authorProof, initArgs }
6825
+ );
6826
+ return {
6827
+ contextId: body.data.contextId,
6828
+ groupId: body.data.groupId,
6829
+ memberPublicKey: body.data.memberPublicKey
6830
+ };
6831
+ }
6586
6832
  async json(method, path, body) {
6587
6833
  const headers = { Accept: "application/json" };
6588
6834
  if (body !== void 0) headers["Content-Type"] = "application/json";
@@ -7282,8 +7528,8 @@ var REGISTERS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
7282
7528
  function createQuoteVerifier(options) {
7283
7529
  const allowedMrtd = measurements(options.allowedMrtd ?? [], "allowedMrtd");
7284
7530
  const allowedImages = (options.allowedMeasurements ?? []).map((image, index) => {
7285
- const label = `allowedMeasurements[${index}]`;
7286
- return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${label}.${register}`)[0]).join();
7531
+ const label2 = `allowedMeasurements[${index}]`;
7532
+ return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${label2}.${register}`)[0]).join();
7287
7533
  });
7288
7534
  const allowedRtmr = [
7289
7535
  measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
@@ -7352,11 +7598,11 @@ function createQuoteVerifier(options) {
7352
7598
  };
7353
7599
  return Object.assign(verifier, { includeCollateral: true });
7354
7600
  }
7355
- function measurements(values, label) {
7601
+ function measurements(values, label2) {
7356
7602
  return values.map((value) => {
7357
7603
  const normalized = value.trim().toLowerCase();
7358
7604
  if (!MEASUREMENT_HEX.test(normalized)) {
7359
- throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7605
+ throw new Error(`${label2} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7360
7606
  }
7361
7607
  return normalized;
7362
7608
  });
@@ -7978,18 +8224,18 @@ var ROOT_OP = {
7978
8224
  NamespaceCreatedV2: 9,
7979
8225
  MemberJoinedViaTeeAttestation: 10
7980
8226
  };
7981
- function bytes32(value, label) {
8227
+ function bytes32(value, label2) {
7982
8228
  if (value.length !== 32) {
7983
- throw new Error(`${label} must be 32 bytes, got ${value.length}`);
8229
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
7984
8230
  }
7985
8231
  return new Uint8Array(value);
7986
8232
  }
7987
- function optionalBytes32(value, label) {
8233
+ function optionalBytes32(value, label2) {
7988
8234
  if (value === null || value === void 0) {
7989
8235
  return new Uint8Array([0]);
7990
8236
  }
7991
8237
  if (value.length !== 32) {
7992
- throw new Error(`${label} must be 32 bytes, got ${value.length}`);
8238
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
7993
8239
  }
7994
8240
  return concat(new Uint8Array([1]), new Uint8Array(value));
7995
8241
  }
@@ -8071,7 +8317,7 @@ async function signMemberJoinOp(input) {
8071
8317
  }
8072
8318
 
8073
8319
  // src/login/login.ts
8074
- var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.auth.login.v1");
8320
+ var SIGN_DOMAIN4 = new TextEncoder().encode("calimero.auth.login.v1");
8075
8321
  var AUDIENCE_TAG = {
8076
8322
  webOrigin: 0,
8077
8323
  codeSigningId: 1,
@@ -8110,7 +8356,7 @@ async function signLoginStatement(input) {
8110
8356
  const deviceKey = await derivePublicKey(input.deviceSecret);
8111
8357
  const issuedAt = u64le(input.issuedAt);
8112
8358
  const expiresAt = u64le(input.expiresAt);
8113
- const preimage = await domainHash(SIGN_DOMAIN3, [
8359
+ const preimage = await domainHash(SIGN_DOMAIN4, [
8114
8360
  node,
8115
8361
  audienceSigningBytes(input.audience),
8116
8362
  challenge,
@@ -8345,6 +8591,7 @@ export {
8345
8591
  createSignedReleaseVerifier,
8346
8592
  createTimeoutSignal,
8347
8593
  createUniversalHttpClient,
8594
+ creationInitHash,
8348
8595
  deviceCertPayload,
8349
8596
  discoverLocalNodes,
8350
8597
  encodeSignedInvitation,
@@ -8364,6 +8611,7 @@ export {
8364
8611
  nodeEndpoint,
8365
8612
  nodeReleaseVersionFromImage,
8366
8613
  parseAuthCallback,
8614
+ parseCreationWarrant,
8367
8615
  probeNodeHealth,
8368
8616
  redeemInvitation,
8369
8617
  requestBodyHash,
@@ -8371,6 +8619,7 @@ export {
8371
8619
  shouldRetain,
8372
8620
  signAccountLink,
8373
8621
  signAccountLogin,
8622
+ signCreationWarrant,
8374
8623
  signDeviceCert,
8375
8624
  signLoginStatement,
8376
8625
  signMemberJoinOp,