@calimero-network/mero-js 22.8.0 → 23.0.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
@@ -21,16 +21,22 @@ export * from "./cloud/index.js";
21
21
  export * from "./sealed/index.js";
22
22
  export * from "./relay/index.js";
23
23
  export * from "./request-proof/index.js";
24
- export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from "./warrant/index.js";
24
+ export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, createdSubgroupId, subgroupCreation, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from "./warrant/index.js";
25
25
  export { signerFromSecret, signerFromCryptoKey } from "./signer/index.js";
26
26
  export type { Signer } from "./signer/index.js";
27
27
  export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, createAccountRootSigner, accountRootSignerFromPhrase, signAccountLink, signAccountLogin, resolveRoot, resolveRootPair, } from "./account/index.js";
28
28
  export type { AccountRoot, RecoverableAccountRoot, AccountRootSigner, NewAccountRootSigner, AccountLinkInput, AccountLoginInput, RootSource, ResolvedRoot, } from "./account/index.js";
29
29
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, accountForRootPublicKey, parseDeviceCredential, verifyDeviceCredential, } from "./device-cert/index.js";
30
30
  export type { DeviceCertInput, DeviceCredential } from "./device-cert/index.js";
31
+ export { signDeviceScope, deviceScopePayload } from "./device-cert/index.js";
32
+ export type { DeviceScopeInput } from "./device-cert/index.js";
33
+ export { signGroupInvitation, encodeGroupInvitation, groupInvitationHash, defaultAdmitters, MAX_INVITATION_VALIDITY_SECS, INVITED_ROLE, } from "./invitation/index.js";
34
+ export type { GroupInvitationInput } from "./invitation/index.js";
31
35
  export { signMemberJoinOp, encodeSignedInvitation, SIGNED_NAMESPACE_OP_SCHEMA_VERSION, } from "./namespace-op/index.js";
32
36
  export type { SignMemberJoinInput } from "./namespace-op/index.js";
33
- export type { WarrantInput, CreationWarrantInput, CreationWarrantFields, SignedCreationWarrant, GovernanceWarrantInput, GovernanceWarrantFields, GovernanceOp, GovernanceOpKind, GovernanceMemberRole, GroupCreatedInput, NamespaceCreatedInput, } from "./warrant/index.js";
37
+ export { OWNER_OP_KIND, transferOwnershipOp, groupDeleteOp, adminChangedOp, teeAuthoringPolicyOp, teeAdmissionPolicyOp, teeReleaseAdmissionPolicyOp, ownerOpDigest, ownerOpSigningPayload, signOwnerOpProof, rootGuardedOpBytes, } from "./owner-op/index.js";
38
+ export type { OwnerOp, OwnerOpKind, OwnerOpPlane, OwnerOpProofInput, TeeAdmissionPolicyOpInput, TeeReleaseAdmissionPolicyOpInput, } from "./owner-op/index.js";
39
+ export type { WarrantInput, CreationWarrantInput, CreationWarrantFields, SignedCreationWarrant, GovernanceWarrantInput, GovernanceWarrantFields, GovernanceOp, GovernanceOpKind, GovernanceMemberRole, GroupCreatedInput, SubgroupCreation, NamespaceCreatedInput, } from "./warrant/index.js";
34
40
  export { requestBodyHash, signRequest, callerProof, createProofSigner, } from "./request/index.js";
35
41
  export type { CallerProofInput, ProofRequest, ProofSignerOptions, } from "./request/index.js";
36
42
  export type { RequestSigInput } from "./request/index.js";
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ export * from "./relay/index.js";
34
34
  // Request proofs — a caller's identity on the request itself, no session needed
35
35
  export * from "./request-proof/index.js";
36
36
  // Warrant signing — mint the author's consent for a relay to run one intent
37
- export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from "./warrant/index.js";
37
+ export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, createdSubgroupId, subgroupCreation, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from "./warrant/index.js";
38
38
  // Who signs — a key's capability, separated from its material, so a
39
39
  // non-extractable key can be used wherever a hex secret could.
40
40
  export { signerFromSecret, signerFromCryptoKey } from "./signer/index.js";
@@ -46,7 +46,13 @@ export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, crea
46
46
  // name it in a signature only by re-declaring it.
47
47
  resolveRoot, resolveRootPair, } from "./account/index.js";
48
48
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, accountForRootPublicKey, parseDeviceCredential, verifyDeviceCredential, } from "./device-cert/index.js";
49
+ // A device's scope — signed by the root beside its certificate, and what a relay
50
+ // needs with it to bind the device of an account that has no node.
51
+ export { signDeviceScope, deviceScopePayload } from "./device-cert/index.js";
52
+ // Invitations minted off-node, signed by a bound device key.
53
+ export { signGroupInvitation, encodeGroupInvitation, groupInvitationHash, defaultAdmitters, MAX_INVITATION_VALIDITY_SECS, INVITED_ROLE, } from "./invitation/index.js";
49
54
  export { signMemberJoinOp, encodeSignedInvitation, SIGNED_NAMESPACE_OP_SCHEMA_VERSION, } from "./namespace-op/index.js";
55
+ export { OWNER_OP_KIND, transferOwnershipOp, groupDeleteOp, adminChangedOp, teeAuthoringPolicyOp, teeAdmissionPolicyOp, teeReleaseAdmissionPolicyOp, ownerOpDigest, ownerOpSigningPayload, signOwnerOpProof, rootGuardedOpBytes, } from "./owner-op/index.js";
50
56
  // Login-statement signing — the device's half of a password-free session
51
57
  export { requestBodyHash, signRequest, callerProof, createProofSigner, } from "./request/index.js";
52
58
  export { signLoginStatement } from "./login/index.js";
package/dist/index.mjs CHANGED
@@ -1810,6 +1810,29 @@ var AdminApiClient = class {
1810
1810
  )
1811
1811
  );
1812
1812
  }
1813
+ /**
1814
+ * Carry the device link of an account **this node does not hold**, so the
1815
+ * namespace binds that device's key to the account.
1816
+ *
1817
+ * The relay half of a nodeless account minting invitations. An account added
1818
+ * to a group by account holds its grants but has no device bound, so peers
1819
+ * refuse whatever its device signs as "bound to no account". The credential
1820
+ * and scope are root-signed and self-certifying; this node adds its own
1821
+ * member endorsement and publishes the `AccountDeviceLinked`. After that the
1822
+ * device signs invitations itself — see `signGroupInvitation`.
1823
+ *
1824
+ * Refusals: `400` when a proof does not verify or the scope does not reach
1825
+ * this namespace (re-sign it), `403` when the device was revoked here or the
1826
+ * account is a member of nothing in the namespace (add it first).
1827
+ */
1828
+ async linkAccountDevice(namespaceId, request) {
1829
+ return unwrap(
1830
+ await this.httpClient.post(
1831
+ `/admin-api/namespaces/${namespaceId}/account/link-device`,
1832
+ request
1833
+ )
1834
+ );
1835
+ }
1813
1836
  // ---- Group Management ----
1814
1837
  async getGroupInfo(groupId) {
1815
1838
  return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}`));
@@ -2144,9 +2167,45 @@ var AdminApiClient = class {
2144
2167
  * and admitted TEEs stay members. The node applies the same op as
2145
2168
  * `setTeeAuthoringPolicy(groupId, { allowedMrtd: [] })`. `groupId` must be a
2146
2169
  * namespace root.
2170
+ *
2171
+ * `rootProof` is the signing admin's own root proof for that empty policy
2172
+ * (`teeAuthoringPolicyOp([])`); omit it on a node that holds that root.
2173
+ */
2174
+ async disableTeeAuthoringPolicy(groupId, options = {}) {
2175
+ const path = `/admin-api/groups/${groupId}/settings/tee-authoring-policy`;
2176
+ if (options.rootProof === void 0) {
2177
+ await this.httpClient.delete(path);
2178
+ return;
2179
+ }
2180
+ await this.httpClient.request(path, {
2181
+ method: "DELETE",
2182
+ body: JSON.stringify({ rootProof: options.rootProof }),
2183
+ headers: { "Content-Type": "application/json" }
2184
+ });
2185
+ }
2186
+ // ---- Root-guarded owner ops (core schema 18) ----
2187
+ /**
2188
+ * Hand the group to `newOwner`, who must already be one of its admins.
2189
+ * Owner-only, and it needs the owner account's root proof; see
2190
+ * {@link RootGuardedOpRequest}.
2191
+ */
2192
+ async transferOwnership(groupId, request) {
2193
+ await this.httpClient.post(`/admin-api/groups/${groupId}/transfer-ownership`, request);
2194
+ }
2195
+ /**
2196
+ * Repoint the namespace's admin pin at `newAdmin`, a member of its root.
2197
+ * Owner-only, with the owner account's root proof; see {@link RootGuardedOpRequest}.
2198
+ */
2199
+ async changeNamespaceAdmin(namespaceId, request) {
2200
+ await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/admin`, request);
2201
+ }
2202
+ /**
2203
+ * Delete a group through the owner-only path: it must hold no contexts, and
2204
+ * the op needs the owner account's root proof. {@link deleteGroup} is the
2205
+ * admin-level cascading delete.
2147
2206
  */
2148
- async disableTeeAuthoringPolicy(groupId) {
2149
- await this.httpClient.delete(`/admin-api/groups/${groupId}/settings/tee-authoring-policy`);
2207
+ async ownerDeleteGroup(groupId, request = {}) {
2208
+ await this.httpClient.post(`/admin-api/groups/${groupId}/owner-delete`, request);
2150
2209
  }
2151
2210
  // ---- Group / member / context metadata ----
2152
2211
  async setGroupMetadata(groupId, request) {
@@ -3495,6 +3554,54 @@ async function accountForRootPublicKey(rootPublicKey) {
3495
3554
  );
3496
3555
  }
3497
3556
 
3557
+ // src/device-cert/device-scope.ts
3558
+ var SCOPE_DOMAIN = new TextEncoder().encode("calimero.device.scope.v1");
3559
+ async function deviceScopePayload(input) {
3560
+ return domainHash(SCOPE_DOMAIN, [
3561
+ fromHex(input.account, "account", 32),
3562
+ fromHex(input.device, "device", 32),
3563
+ u32le(input.scopeEpoch),
3564
+ u32le(input.keyEpoch),
3565
+ ...input.applications.map((a, i) => fromHex(a, `applications[${i}]`, 32))
3566
+ ]);
3567
+ }
3568
+ async function signDeviceScope(input) {
3569
+ const signer = await resolveSigner(input.rootSecret, input.signer, "rootSecret");
3570
+ const rootPublicKey = fromHex(signer.publicKey, "signer.publicKey", 32);
3571
+ const account = await accountForRootPublicKey(hex(rootPublicKey));
3572
+ const applications = input.applications ?? [];
3573
+ const scopeEpoch = input.scopeEpoch ?? 0;
3574
+ if (!Number.isInteger(scopeEpoch) || scopeEpoch < 0 || scopeEpoch > 4294967295) {
3575
+ throw new Error(`scopeEpoch must be a u32, got ${scopeEpoch}`);
3576
+ }
3577
+ const keyEpoch = 0;
3578
+ const payload = await deviceScopePayload({
3579
+ account,
3580
+ device: input.device,
3581
+ applications,
3582
+ scopeEpoch,
3583
+ keyEpoch
3584
+ });
3585
+ const signature = await signer.sign(payload);
3586
+ if (signature.length !== 64) {
3587
+ throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
3588
+ }
3589
+ return hex(
3590
+ concat(
3591
+ new Uint8Array([ACCOUNT_GENESIS_VERSION]),
3592
+ rootPublicKey,
3593
+ u32le(0),
3594
+ fromHex(account, "account", 32),
3595
+ fromHex(input.device, "device", 32),
3596
+ u32le(applications.length),
3597
+ ...applications.map((a, i) => fromHex(a, `applications[${i}]`, 32)),
3598
+ u32le(scopeEpoch),
3599
+ u32le(keyEpoch),
3600
+ signature
3601
+ )
3602
+ );
3603
+ }
3604
+
3498
3605
  // src/account/wordlist.ts
3499
3606
  var ENGLISH_WORDLIST = [
3500
3607
  "abandon",
@@ -7092,9 +7199,9 @@ function parseCreationWarrant(warrant) {
7092
7199
  const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
7093
7200
  const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
7094
7201
  const option = (what) => {
7095
- const tag2 = take(1, what)[0];
7096
- if (tag2 === 0) return null;
7097
- if (tag2 !== 1) throw new Error(`${what} has option tag ${tag2}, expected 0 or 1`);
7202
+ const tag3 = take(1, what)[0];
7203
+ if (tag3 === 0) return null;
7204
+ if (tag3 !== 1) throw new Error(`${what} has option tag ${tag3}, expected 0 or 1`);
7098
7205
  const len = u32(what);
7099
7206
  if (len > MAX_LABEL_BYTES) {
7100
7207
  throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
@@ -7338,6 +7445,17 @@ function memberRoleSetOp(member, memberRole) {
7338
7445
  bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, "member", 32), role(memberRole))
7339
7446
  };
7340
7447
  }
7448
+ var SUBGROUP_ID_DOMAIN = new TextEncoder().encode("calimero.subgroup.id.v1");
7449
+ async function createdSubgroupId(admin, parentId, restricted, salt) {
7450
+ return hex(
7451
+ await domainHash(SUBGROUP_ID_DOMAIN, [
7452
+ fromHex(admin, "admin", 32),
7453
+ fromHex(parentId, "parentId", 32),
7454
+ new Uint8Array([restricted ? 1 : 0]),
7455
+ fromHex(salt, "salt", 32)
7456
+ ])
7457
+ );
7458
+ }
7341
7459
  function defaultCapabilitiesSetOp(capabilities) {
7342
7460
  if (!Number.isInteger(capabilities) || capabilities < 0 || capabilities > 4294967295) {
7343
7461
  throw new Error(`capabilities must be a u32 bit mask, got ${String(capabilities)}`);
@@ -7360,10 +7478,16 @@ function groupCreatedOp(input) {
7360
7478
  fromHex(input.groupId, "groupId", 32),
7361
7479
  fromHex(input.parentId, "parentId", 32),
7362
7480
  tag(input.restricted ? 1 : 0),
7363
- fromHex(input.admin, "admin", 32)
7481
+ fromHex(input.admin, "admin", 32),
7482
+ fromHex(input.salt, "salt", 32)
7364
7483
  )
7365
7484
  };
7366
7485
  }
7486
+ async function subgroupCreation(input) {
7487
+ const salt = input.salt ?? hex(crypto.getRandomValues(new Uint8Array(32)));
7488
+ const groupId = await createdSubgroupId(input.admin, input.parentId, input.restricted, salt);
7489
+ return { groupId, salt, op: groupCreatedOp({ ...input, groupId, salt }) };
7490
+ }
7367
7491
  function groupReparentedOp(childGroupId, newParentId) {
7368
7492
  return {
7369
7493
  kind: "root",
@@ -7921,12 +8045,12 @@ function audienceSigningBytes(audience) {
7921
8045
  );
7922
8046
  }
7923
8047
  function audienceWire(audience) {
7924
- const tag2 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
8048
+ const tag3 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
7925
8049
  if (audience.kind === "cli") {
7926
- return tag2;
8050
+ return tag3;
7927
8051
  }
7928
8052
  const body = audienceBody(audience);
7929
- return concat(tag2, u32le(body.length), body);
8053
+ return concat(tag3, u32le(body.length), body);
7930
8054
  }
7931
8055
  async function signLoginStatement(input) {
7932
8056
  const node = fromHex(input.node, "node", 32);
@@ -9611,8 +9735,8 @@ var SIGNATURE_HASHES = {
9611
9735
  };
9612
9736
  function read(bytes, offset, limit) {
9613
9737
  if (offset + 2 > limit) throw new Error("it is truncated");
9614
- const tag2 = bytes[offset];
9615
- if ((tag2 & 31) === 31) throw new Error("it uses a tag this parser does not read");
9738
+ const tag3 = bytes[offset];
9739
+ if ((tag3 & 31) === 31) throw new Error("it uses a tag this parser does not read");
9616
9740
  let length = bytes[offset + 1];
9617
9741
  let start = offset + 2;
9618
9742
  if (length & 128) {
@@ -9624,7 +9748,7 @@ function read(bytes, offset, limit) {
9624
9748
  }
9625
9749
  const end = start + length;
9626
9750
  if (end > limit) throw new Error("it is truncated");
9627
- return { tag: tag2, bytes, offset, start, end };
9751
+ return { tag: tag3, bytes, offset, start, end };
9628
9752
  }
9629
9753
  function parseDer(bytes) {
9630
9754
  const element = read(bytes, 0, bytes.length);
@@ -9640,8 +9764,8 @@ function children(element) {
9640
9764
  }
9641
9765
  return out;
9642
9766
  }
9643
- function expectTag(element, tag2, what) {
9644
- if (!element || element.tag !== tag2) throw new Error(`${what} is not where it should be`);
9767
+ function expectTag(element, tag3, what) {
9768
+ if (!element || element.tag !== tag3) throw new Error(`${what} is not where it should be`);
9645
9769
  return element;
9646
9770
  }
9647
9771
  function contents(element) {
@@ -10103,8 +10227,103 @@ function splitVersion2(version) {
10103
10227
  return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
10104
10228
  }
10105
10229
 
10230
+ // src/invitation/invitation.ts
10231
+ var MAX_INVITATION_VALIDITY_SECS = 24 * 60 * 60;
10232
+ var INVITED_ROLE = { Admin: 0, Member: 1, ReadOnly: 2 };
10233
+ function encodeGroupInvitation(body) {
10234
+ const admitters = body.admitters ?? [];
10235
+ return concat(
10236
+ // `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`
10237
+ // crosses as hex. Same width, different spelling.
10238
+ bytes32(body.inviter_identity, "inviter_identity"),
10239
+ bytes32(body.group_id, "group_id"),
10240
+ u64le(body.expiration_timestamp),
10241
+ bytes32(body.secret_salt, "secret_salt"),
10242
+ new Uint8Array([body.invited_role]),
10243
+ u32le(admitters.length),
10244
+ ...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32))
10245
+ );
10246
+ }
10247
+ async function groupInvitationHash(body) {
10248
+ return new Uint8Array(
10249
+ await crypto.subtle.digest("SHA-256", encodeGroupInvitation(body))
10250
+ );
10251
+ }
10252
+ function defaultAdmitters(members) {
10253
+ const admins = new Set(
10254
+ members.filter((m) => m.role === "Admin").map((m) => m.identity.toLowerCase())
10255
+ );
10256
+ return [...admins].sort();
10257
+ }
10258
+ async function signGroupInvitation(input) {
10259
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
10260
+ const inviterKey = fromHex(signer.publicKey, "signer.publicKey", 32);
10261
+ const groupId = fromHex(input.groupId, "groupId", 32);
10262
+ fromHex(input.inviterAccount, "inviterAccount", 32);
10263
+ let admitters = [...input.admitters ?? []].map((a) => a.trim().toLowerCase());
10264
+ if (admitters.length === 0) {
10265
+ if (!input.members) {
10266
+ throw new Error(
10267
+ "name the admitters, or pass the group members to default them to its admins: an invitation with no admitters is claimable by broadcast"
10268
+ );
10269
+ }
10270
+ admitters = defaultAdmitters(input.members);
10271
+ if (admitters.length === 0) {
10272
+ throw new Error(
10273
+ "the member list names no admin, so there is nobody to default the admitters to"
10274
+ );
10275
+ }
10276
+ }
10277
+ admitters.forEach((a, i) => fromHex(a, `admitters[${i}]`, 32));
10278
+ const invitedRole = input.invitedRole ?? INVITED_ROLE.Member;
10279
+ if (![0, 1, 2].includes(invitedRole)) {
10280
+ throw new Error(`invitedRole must be 0 (Admin), 1 (Member) or 2 (ReadOnly), got ${invitedRole}`);
10281
+ }
10282
+ const now = input.now ?? Math.floor(Date.now() / 1e3);
10283
+ const validFor = Math.min(
10284
+ input.validForSecs ?? MAX_INVITATION_VALIDITY_SECS,
10285
+ MAX_INVITATION_VALIDITY_SECS
10286
+ );
10287
+ if (!Number.isInteger(validFor) || validFor <= 0) {
10288
+ throw new Error(`validForSecs must be a positive whole number, got ${input.validForSecs}`);
10289
+ }
10290
+ const nonce2 = input.nonce ?? crypto.getRandomValues(new Uint8Array(32));
10291
+ if (nonce2.length !== 32) {
10292
+ throw new Error(`nonce must be 32 bytes, got ${nonce2.length}`);
10293
+ }
10294
+ const invitation = {
10295
+ inviter_identity: Array.from(inviterKey),
10296
+ group_id: Array.from(groupId),
10297
+ expiration_timestamp: now + validFor,
10298
+ secret_salt: Array.from(nonce2),
10299
+ invited_role: invitedRole,
10300
+ admitters
10301
+ };
10302
+ const signature = await signer.sign(await groupInvitationHash(invitation));
10303
+ if (signature.length !== 64) {
10304
+ throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
10305
+ }
10306
+ const signed = {
10307
+ invitation,
10308
+ inviter_signature: hex(signature),
10309
+ inviter_account: input.inviterAccount.trim().toLowerCase()
10310
+ };
10311
+ if (input.admitterAddrs?.length) signed.admitter_addrs = [...input.admitterAddrs];
10312
+ if (input.applicationId) {
10313
+ signed.application_id = Array.from(fromHex(input.applicationId, "applicationId", 32));
10314
+ }
10315
+ if (input.appKey) signed.app_key = Array.from(fromHex(input.appKey, "appKey", 32));
10316
+ return signed;
10317
+ }
10318
+ function bytes32(value, label2) {
10319
+ if (value.length !== 32) {
10320
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
10321
+ }
10322
+ return new Uint8Array(value);
10323
+ }
10324
+
10106
10325
  // src/namespace-op/namespace-op.ts
10107
- var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 16;
10326
+ var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 20;
10108
10327
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
10109
10328
  var NAMESPACE_OP_ROOT = 0;
10110
10329
  var ROOT_OP2 = {
@@ -10120,12 +10339,6 @@ var ROOT_OP2 = {
10120
10339
  NamespaceCreatedV2: 9,
10121
10340
  MemberJoinedViaTeeAttestation: 10
10122
10341
  };
10123
- function bytes32(value, label2) {
10124
- if (value.length !== 32) {
10125
- throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
10126
- }
10127
- return new Uint8Array(value);
10128
- }
10129
10342
  function optionalBytes32(value, label2) {
10130
10343
  if (value === null || value === void 0) {
10131
10344
  return new Uint8Array([0]);
@@ -10141,21 +10354,11 @@ function borshString2(value) {
10141
10354
  }
10142
10355
  function encodeSignedInvitation(signed) {
10143
10356
  const body = signed.invitation;
10144
- const admitters = body.admitters ?? [];
10145
10357
  const admitterAddrs = signed.admitter_addrs ?? [];
10146
10358
  return concat(
10147
- // GroupInvitationFromAdmin.
10148
- //
10149
- // `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`
10150
- // crosses as hex. Same width, different spelling, and reading either the
10151
- // wrong way yields 32 bytes that encode cleanly and verify as nothing.
10152
- bytes32(body.inviter_identity, "inviter_identity"),
10153
- bytes32(body.group_id, "group_id"),
10154
- u64le(body.expiration_timestamp),
10155
- bytes32(body.secret_salt, "secret_salt"),
10156
- new Uint8Array([body.invited_role]),
10157
- u32le(admitters.length),
10158
- ...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32)),
10359
+ // GroupInvitationFromAdmin, from the one encoder the SDK's own invitation
10360
+ // signer uses: the bytes signed and the bytes carried must not drift.
10361
+ encodeGroupInvitation(body),
10159
10362
  // ...then the enclosing SignedGroupOpenInvitation, in declaration order.
10160
10363
  // `inviter_account` sits between the signature and the addresses; putting it
10161
10364
  // anywhere else still encodes 32 bytes and still verifies as nothing.
@@ -10210,6 +10413,183 @@ async function signMemberJoinOp(input) {
10210
10413
  return hex(concat(signable, signature, unendorsed));
10211
10414
  }
10212
10415
 
10416
+ // src/owner-op/owner-op.ts
10417
+ var OWNER_OP_SIGN_DOMAIN = new TextEncoder().encode("calimero.account.owner-op.v1");
10418
+ var OWNER_OP_BODY_DOMAIN = new TextEncoder().encode("calimero.account.owner-op.body.v1");
10419
+ var ACCOUNT_ID_DOMAIN2 = new TextEncoder().encode("calimero.account.genesis.v1");
10420
+ var ACCOUNT_GENESIS_VERSION2 = 2;
10421
+ var OWNER_OP_KIND = {
10422
+ transferOwnership: 0,
10423
+ adminChanged: 1,
10424
+ groupDelete: 2,
10425
+ teeAdmissionPolicy: 3,
10426
+ teeAuthoringPolicy: 4,
10427
+ teeReleaseAdmissionPolicy: 5
10428
+ };
10429
+ var GROUP_OP2 = {
10430
+ GroupDelete: 14,
10431
+ TransferOwnership: 21,
10432
+ TeeAuthoringPolicySet: 33,
10433
+ TeeAdmissionPolicySetV2: 37,
10434
+ TeeReleaseAdmissionPolicySetV2: 38,
10435
+ RootGuarded: 42
10436
+ };
10437
+ var ROOT_OP3 = {
10438
+ AdminChanged: 3,
10439
+ RootGuarded: 12
10440
+ };
10441
+ var TEE_MODE = { replica: 0, relay: 1 };
10442
+ var te = new TextEncoder();
10443
+ function tag2(byte) {
10444
+ return new Uint8Array([byte]);
10445
+ }
10446
+ function borshString3(value) {
10447
+ const bytes = te.encode(value);
10448
+ return concat(u32le(bytes.length), bytes);
10449
+ }
10450
+ function borshStrings(values) {
10451
+ return concat(u32le(values.length), ...values.map(borshString3));
10452
+ }
10453
+ function borshBool(value) {
10454
+ return tag2(value ? 1 : 0);
10455
+ }
10456
+ function teeMode(mode) {
10457
+ const byte = TEE_MODE[mode ?? "replica"];
10458
+ if (byte === void 0) {
10459
+ throw new Error(`mode must be replica or relay, got ${String(mode)}`);
10460
+ }
10461
+ return tag2(byte);
10462
+ }
10463
+ function transferOwnershipOp(newOwner) {
10464
+ return {
10465
+ kind: "transferOwnership",
10466
+ plane: "group",
10467
+ bytes: concat(tag2(GROUP_OP2.TransferOwnership), fromHex(newOwner, "newOwner", 32))
10468
+ };
10469
+ }
10470
+ function groupDeleteOp() {
10471
+ return { kind: "groupDelete", plane: "group", bytes: tag2(GROUP_OP2.GroupDelete) };
10472
+ }
10473
+ function adminChangedOp(newAdmin) {
10474
+ return {
10475
+ kind: "adminChanged",
10476
+ plane: "root",
10477
+ bytes: concat(tag2(ROOT_OP3.AdminChanged), fromHex(newAdmin, "newAdmin", 32))
10478
+ };
10479
+ }
10480
+ function teeAuthoringPolicyOp(allowedMrtd) {
10481
+ return {
10482
+ kind: "teeAuthoringPolicy",
10483
+ plane: "group",
10484
+ bytes: concat(tag2(GROUP_OP2.TeeAuthoringPolicySet), borshStrings(allowedMrtd))
10485
+ };
10486
+ }
10487
+ function teeAdmissionPolicyOp(input) {
10488
+ return {
10489
+ kind: "teeAdmissionPolicy",
10490
+ plane: "group",
10491
+ bytes: concat(
10492
+ tag2(GROUP_OP2.TeeAdmissionPolicySetV2),
10493
+ borshStrings(input.allowedMrtd),
10494
+ borshStrings(input.allowedRtmr0),
10495
+ borshStrings(input.allowedRtmr1),
10496
+ borshStrings(input.allowedRtmr2),
10497
+ borshStrings(input.allowedRtmr3),
10498
+ borshStrings(input.allowedTcbStatuses),
10499
+ borshBool(input.acceptMock),
10500
+ teeMode(input.mode)
10501
+ )
10502
+ };
10503
+ }
10504
+ function teeReleaseAdmissionPolicyOp(input) {
10505
+ const minRelease = input.minReleaseVersion === void 0 ? tag2(0) : concat(tag2(1), borshString3(input.minReleaseVersion));
10506
+ return {
10507
+ kind: "teeReleaseAdmissionPolicy",
10508
+ plane: "group",
10509
+ bytes: concat(
10510
+ tag2(GROUP_OP2.TeeReleaseAdmissionPolicySetV2),
10511
+ borshStrings(input.allowedProfiles),
10512
+ minRelease,
10513
+ borshStrings(input.allowedTcbStatuses),
10514
+ borshBool(input.acceptMock),
10515
+ teeMode(input.mode)
10516
+ )
10517
+ };
10518
+ }
10519
+ async function ownerOpDigest(opBytes) {
10520
+ return domainHash(OWNER_OP_BODY_DOMAIN, [opBytes]);
10521
+ }
10522
+ async function ownerOpSigningPayload(input) {
10523
+ return domainHash(OWNER_OP_SIGN_DOMAIN, [
10524
+ fromHex(input.account, "account", 32),
10525
+ fromHex(input.namespaceId, "namespaceId", 32),
10526
+ fromHex(input.groupId, "groupId", 32),
10527
+ tag2(kindByte2(input.kind)),
10528
+ input.opDigest,
10529
+ u64le(input.counter),
10530
+ u32le(input.keyEpoch)
10531
+ ]);
10532
+ }
10533
+ function kindByte2(kind) {
10534
+ const byte = OWNER_OP_KIND[kind];
10535
+ if (byte === void 0) {
10536
+ throw new Error(`unknown owner-op kind ${String(kind)}`);
10537
+ }
10538
+ return byte;
10539
+ }
10540
+ var HANDOFF_BYTES = 32 + 4 + 32 + 64;
10541
+ async function signOwnerOpProof(input) {
10542
+ const rootPublicKey = await derivePublicKey(input.rootSecret);
10543
+ const genesis = concat(tag2(ACCOUNT_GENESIS_VERSION2), rootPublicKey);
10544
+ const account = hex(await domainHash(ACCOUNT_ID_DOMAIN2, [genesis]));
10545
+ const keyEpoch = input.keyEpoch ?? 0;
10546
+ const chain = (input.chain ?? []).map((handoff, index) => {
10547
+ const bytes = fromHexUnsized(handoff, `chain[${index}]`);
10548
+ if (bytes.length !== HANDOFF_BYTES) {
10549
+ throw new Error(
10550
+ `chain[${index}] must be a ${HANDOFF_BYTES}-byte RootKeyHandoff, got ${bytes.length}`
10551
+ );
10552
+ }
10553
+ return bytes;
10554
+ });
10555
+ if (keyEpoch > chain.length) {
10556
+ throw new Error(
10557
+ `keyEpoch ${keyEpoch} needs ${keyEpoch} handoffs in chain, got ${chain.length}`
10558
+ );
10559
+ }
10560
+ const opDigest = await ownerOpDigest(input.op.bytes);
10561
+ const payload = await ownerOpSigningPayload({
10562
+ account,
10563
+ namespaceId: input.namespaceId,
10564
+ groupId: input.groupId,
10565
+ kind: input.op.kind,
10566
+ opDigest,
10567
+ counter: input.counter,
10568
+ keyEpoch
10569
+ });
10570
+ const key = await importSigningKey(input.rootSecret);
10571
+ const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", key, payload));
10572
+ return hex(
10573
+ concat(
10574
+ genesis,
10575
+ u32le(chain.length),
10576
+ ...chain,
10577
+ fromHex(account, "account", 32),
10578
+ fromHex(input.namespaceId, "namespaceId", 32),
10579
+ fromHex(input.groupId, "groupId", 32),
10580
+ tag2(kindByte2(input.op.kind)),
10581
+ opDigest,
10582
+ u64le(input.counter),
10583
+ u32le(keyEpoch),
10584
+ signature
10585
+ )
10586
+ );
10587
+ }
10588
+ function rootGuardedOpBytes(op, rootProof) {
10589
+ const wrapper = op.plane === "group" ? GROUP_OP2.RootGuarded : ROOT_OP3.RootGuarded;
10590
+ return concat(tag2(wrapper), op.bytes, fromHexUnsized(rootProof, "rootProof"));
10591
+ }
10592
+
10213
10593
  // src/nodeDiscovery.ts
10214
10594
  var DEFAULT_LOCAL_NODE_PORTS = [2428, 2429, 2528, 2529];
10215
10595
  var LOCAL_HOST = "localhost";
@@ -10370,12 +10750,15 @@ export {
10370
10750
  EphemeralClient,
10371
10751
  HANDSHAKE_PATH,
10372
10752
  HTTPError,
10753
+ INVITED_ROLE,
10373
10754
  IntentRefusedError,
10374
10755
  LocalStorageTokenStore,
10756
+ MAX_INVITATION_VALIDITY_SECS,
10375
10757
  MemoryTokenStore,
10376
10758
  MeroClient,
10377
10759
  MeroJs,
10378
10760
  NODE_RELEASE_SIGNER,
10761
+ OWNER_OP_KIND,
10379
10762
  REQUEST_PROOF_HEADER,
10380
10763
  RelayClient,
10381
10764
  RelayObserver,
@@ -10397,6 +10780,7 @@ export {
10397
10780
  accountRootFromPhrase,
10398
10781
  accountRootFromSecret,
10399
10782
  accountRootSignerFromPhrase,
10783
+ adminChangedOp,
10400
10784
  admitterAddrsFromNetworkStatus,
10401
10785
  attestKeyBinding,
10402
10786
  attestRelayNodeKey,
@@ -10444,12 +10828,16 @@ export {
10444
10828
  createSignedReleaseVerifier,
10445
10829
  createTimeoutSignal,
10446
10830
  createUniversalHttpClient,
10831
+ createdSubgroupId,
10447
10832
  creationInitHash,
10833
+ defaultAdmitters,
10448
10834
  defaultAudience,
10449
10835
  defaultCapabilitiesSetOp,
10450
10836
  deviceCertPayload,
10451
10837
  deviceEnrolmentUrl,
10838
+ deviceScopePayload,
10452
10839
  discoverLocalNodes,
10840
+ encodeGroupInvitation,
10453
10841
  encodeSignedInvitation,
10454
10842
  fetchAttestedTransportKey,
10455
10843
  fetchNodeRelease,
@@ -10459,7 +10847,9 @@ export {
10459
10847
  generateSessionKey,
10460
10848
  governanceOpHash,
10461
10849
  groupCreatedOp,
10850
+ groupDeleteOp,
10462
10851
  groupDeletedOp,
10852
+ groupInvitationHash,
10463
10853
  groupReparentedOp,
10464
10854
  hasCap,
10465
10855
  hexEncodeUtf8,
@@ -10476,6 +10866,8 @@ export {
10476
10866
  namespaceCreatedOp,
10477
10867
  nodeEndpoint,
10478
10868
  nodeReleaseVersionFromImage,
10869
+ ownerOpDigest,
10870
+ ownerOpSigningPayload,
10479
10871
  parseAuthCallback,
10480
10872
  parseCreationWarrant,
10481
10873
  parseDeviceCredential,
@@ -10489,15 +10881,19 @@ export {
10489
10881
  requestBodyHash,
10490
10882
  resolveRoot,
10491
10883
  resolveRootPair,
10884
+ rootGuardedOpBytes,
10492
10885
  routingProofHeaders,
10493
10886
  shouldRetain,
10494
10887
  signAccountLink,
10495
10888
  signAccountLogin,
10496
10889
  signCreationWarrant,
10497
10890
  signDeviceCert,
10891
+ signDeviceScope,
10498
10892
  signGovernanceWarrant,
10893
+ signGroupInvitation,
10499
10894
  signLoginStatement,
10500
10895
  signMemberJoinOp,
10896
+ signOwnerOpProof,
10501
10897
  signRequest,
10502
10898
  signRequestProof,
10503
10899
  signRoutingChallenge,
@@ -10505,6 +10901,11 @@ export {
10505
10901
  signedPath,
10506
10902
  signerFromCryptoKey,
10507
10903
  signerFromSecret,
10904
+ subgroupCreation,
10905
+ teeAdmissionPolicyOp,
10906
+ teeAuthoringPolicyOp,
10907
+ teeReleaseAdmissionPolicyOp,
10908
+ transferOwnershipOp,
10508
10909
  transportKeyBinding,
10509
10910
  trustSignedRelease,
10510
10911
  trustedMeasurementsFromReleases,