@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.cjs CHANGED
@@ -30,12 +30,15 @@ __export(index_exports, {
30
30
  EphemeralClient: () => EphemeralClient,
31
31
  HANDSHAKE_PATH: () => HANDSHAKE_PATH,
32
32
  HTTPError: () => HTTPError,
33
+ INVITED_ROLE: () => INVITED_ROLE,
33
34
  IntentRefusedError: () => IntentRefusedError,
34
35
  LocalStorageTokenStore: () => LocalStorageTokenStore,
36
+ MAX_INVITATION_VALIDITY_SECS: () => MAX_INVITATION_VALIDITY_SECS,
35
37
  MemoryTokenStore: () => MemoryTokenStore,
36
38
  MeroClient: () => MeroClient,
37
39
  MeroJs: () => MeroJs,
38
40
  NODE_RELEASE_SIGNER: () => NODE_RELEASE_SIGNER,
41
+ OWNER_OP_KIND: () => OWNER_OP_KIND,
39
42
  REQUEST_PROOF_HEADER: () => REQUEST_PROOF_HEADER,
40
43
  RelayClient: () => RelayClient,
41
44
  RelayObserver: () => RelayObserver,
@@ -57,6 +60,7 @@ __export(index_exports, {
57
60
  accountRootFromPhrase: () => accountRootFromPhrase,
58
61
  accountRootFromSecret: () => accountRootFromSecret,
59
62
  accountRootSignerFromPhrase: () => accountRootSignerFromPhrase,
63
+ adminChangedOp: () => adminChangedOp,
60
64
  admitterAddrsFromNetworkStatus: () => admitterAddrsFromNetworkStatus,
61
65
  attestKeyBinding: () => attestKeyBinding,
62
66
  attestRelayNodeKey: () => attestRelayNodeKey,
@@ -104,12 +108,16 @@ __export(index_exports, {
104
108
  createSignedReleaseVerifier: () => createSignedReleaseVerifier,
105
109
  createTimeoutSignal: () => createTimeoutSignal,
106
110
  createUniversalHttpClient: () => createUniversalHttpClient,
111
+ createdSubgroupId: () => createdSubgroupId,
107
112
  creationInitHash: () => creationInitHash,
113
+ defaultAdmitters: () => defaultAdmitters,
108
114
  defaultAudience: () => defaultAudience,
109
115
  defaultCapabilitiesSetOp: () => defaultCapabilitiesSetOp,
110
116
  deviceCertPayload: () => deviceCertPayload,
111
117
  deviceEnrolmentUrl: () => deviceEnrolmentUrl,
118
+ deviceScopePayload: () => deviceScopePayload,
112
119
  discoverLocalNodes: () => discoverLocalNodes,
120
+ encodeGroupInvitation: () => encodeGroupInvitation,
113
121
  encodeSignedInvitation: () => encodeSignedInvitation,
114
122
  fetchAttestedTransportKey: () => fetchAttestedTransportKey,
115
123
  fetchNodeRelease: () => fetchNodeRelease,
@@ -119,7 +127,9 @@ __export(index_exports, {
119
127
  generateSessionKey: () => generateSessionKey,
120
128
  governanceOpHash: () => governanceOpHash,
121
129
  groupCreatedOp: () => groupCreatedOp,
130
+ groupDeleteOp: () => groupDeleteOp,
122
131
  groupDeletedOp: () => groupDeletedOp,
132
+ groupInvitationHash: () => groupInvitationHash,
123
133
  groupReparentedOp: () => groupReparentedOp,
124
134
  hasCap: () => hasCap,
125
135
  hexEncodeUtf8: () => hexEncodeUtf8,
@@ -136,6 +146,8 @@ __export(index_exports, {
136
146
  namespaceCreatedOp: () => namespaceCreatedOp,
137
147
  nodeEndpoint: () => nodeEndpoint,
138
148
  nodeReleaseVersionFromImage: () => nodeReleaseVersionFromImage,
149
+ ownerOpDigest: () => ownerOpDigest,
150
+ ownerOpSigningPayload: () => ownerOpSigningPayload,
139
151
  parseAuthCallback: () => parseAuthCallback,
140
152
  parseCreationWarrant: () => parseCreationWarrant,
141
153
  parseDeviceCredential: () => parseDeviceCredential,
@@ -149,15 +161,19 @@ __export(index_exports, {
149
161
  requestBodyHash: () => requestBodyHash,
150
162
  resolveRoot: () => resolveRoot,
151
163
  resolveRootPair: () => resolveRootPair,
164
+ rootGuardedOpBytes: () => rootGuardedOpBytes,
152
165
  routingProofHeaders: () => routingProofHeaders,
153
166
  shouldRetain: () => shouldRetain,
154
167
  signAccountLink: () => signAccountLink,
155
168
  signAccountLogin: () => signAccountLogin,
156
169
  signCreationWarrant: () => signCreationWarrant,
157
170
  signDeviceCert: () => signDeviceCert,
171
+ signDeviceScope: () => signDeviceScope,
158
172
  signGovernanceWarrant: () => signGovernanceWarrant,
173
+ signGroupInvitation: () => signGroupInvitation,
159
174
  signLoginStatement: () => signLoginStatement,
160
175
  signMemberJoinOp: () => signMemberJoinOp,
176
+ signOwnerOpProof: () => signOwnerOpProof,
161
177
  signRequest: () => signRequest,
162
178
  signRequestProof: () => signRequestProof,
163
179
  signRoutingChallenge: () => signRoutingChallenge,
@@ -165,6 +181,11 @@ __export(index_exports, {
165
181
  signedPath: () => signedPath,
166
182
  signerFromCryptoKey: () => signerFromCryptoKey,
167
183
  signerFromSecret: () => signerFromSecret,
184
+ subgroupCreation: () => subgroupCreation,
185
+ teeAdmissionPolicyOp: () => teeAdmissionPolicyOp,
186
+ teeAuthoringPolicyOp: () => teeAuthoringPolicyOp,
187
+ teeReleaseAdmissionPolicyOp: () => teeReleaseAdmissionPolicyOp,
188
+ transferOwnershipOp: () => transferOwnershipOp,
168
189
  transportKeyBinding: () => transportKeyBinding,
169
190
  trustSignedRelease: () => trustSignedRelease,
170
191
  trustedMeasurementsFromReleases: () => trustedMeasurementsFromReleases,
@@ -1988,6 +2009,29 @@ var AdminApiClient = class {
1988
2009
  )
1989
2010
  );
1990
2011
  }
2012
+ /**
2013
+ * Carry the device link of an account **this node does not hold**, so the
2014
+ * namespace binds that device's key to the account.
2015
+ *
2016
+ * The relay half of a nodeless account minting invitations. An account added
2017
+ * to a group by account holds its grants but has no device bound, so peers
2018
+ * refuse whatever its device signs as "bound to no account". The credential
2019
+ * and scope are root-signed and self-certifying; this node adds its own
2020
+ * member endorsement and publishes the `AccountDeviceLinked`. After that the
2021
+ * device signs invitations itself — see `signGroupInvitation`.
2022
+ *
2023
+ * Refusals: `400` when a proof does not verify or the scope does not reach
2024
+ * this namespace (re-sign it), `403` when the device was revoked here or the
2025
+ * account is a member of nothing in the namespace (add it first).
2026
+ */
2027
+ async linkAccountDevice(namespaceId, request) {
2028
+ return unwrap(
2029
+ await this.httpClient.post(
2030
+ `/admin-api/namespaces/${namespaceId}/account/link-device`,
2031
+ request
2032
+ )
2033
+ );
2034
+ }
1991
2035
  // ---- Group Management ----
1992
2036
  async getGroupInfo(groupId) {
1993
2037
  return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}`));
@@ -2322,9 +2366,45 @@ var AdminApiClient = class {
2322
2366
  * and admitted TEEs stay members. The node applies the same op as
2323
2367
  * `setTeeAuthoringPolicy(groupId, { allowedMrtd: [] })`. `groupId` must be a
2324
2368
  * namespace root.
2369
+ *
2370
+ * `rootProof` is the signing admin's own root proof for that empty policy
2371
+ * (`teeAuthoringPolicyOp([])`); omit it on a node that holds that root.
2372
+ */
2373
+ async disableTeeAuthoringPolicy(groupId, options = {}) {
2374
+ const path = `/admin-api/groups/${groupId}/settings/tee-authoring-policy`;
2375
+ if (options.rootProof === void 0) {
2376
+ await this.httpClient.delete(path);
2377
+ return;
2378
+ }
2379
+ await this.httpClient.request(path, {
2380
+ method: "DELETE",
2381
+ body: JSON.stringify({ rootProof: options.rootProof }),
2382
+ headers: { "Content-Type": "application/json" }
2383
+ });
2384
+ }
2385
+ // ---- Root-guarded owner ops (core schema 18) ----
2386
+ /**
2387
+ * Hand the group to `newOwner`, who must already be one of its admins.
2388
+ * Owner-only, and it needs the owner account's root proof; see
2389
+ * {@link RootGuardedOpRequest}.
2390
+ */
2391
+ async transferOwnership(groupId, request) {
2392
+ await this.httpClient.post(`/admin-api/groups/${groupId}/transfer-ownership`, request);
2393
+ }
2394
+ /**
2395
+ * Repoint the namespace's admin pin at `newAdmin`, a member of its root.
2396
+ * Owner-only, with the owner account's root proof; see {@link RootGuardedOpRequest}.
2397
+ */
2398
+ async changeNamespaceAdmin(namespaceId, request) {
2399
+ await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/admin`, request);
2400
+ }
2401
+ /**
2402
+ * Delete a group through the owner-only path: it must hold no contexts, and
2403
+ * the op needs the owner account's root proof. {@link deleteGroup} is the
2404
+ * admin-level cascading delete.
2325
2405
  */
2326
- async disableTeeAuthoringPolicy(groupId) {
2327
- await this.httpClient.delete(`/admin-api/groups/${groupId}/settings/tee-authoring-policy`);
2406
+ async ownerDeleteGroup(groupId, request = {}) {
2407
+ await this.httpClient.post(`/admin-api/groups/${groupId}/owner-delete`, request);
2328
2408
  }
2329
2409
  // ---- Group / member / context metadata ----
2330
2410
  async setGroupMetadata(groupId, request) {
@@ -3673,6 +3753,54 @@ async function accountForRootPublicKey(rootPublicKey) {
3673
3753
  );
3674
3754
  }
3675
3755
 
3756
+ // src/device-cert/device-scope.ts
3757
+ var SCOPE_DOMAIN = new TextEncoder().encode("calimero.device.scope.v1");
3758
+ async function deviceScopePayload(input) {
3759
+ return domainHash(SCOPE_DOMAIN, [
3760
+ fromHex(input.account, "account", 32),
3761
+ fromHex(input.device, "device", 32),
3762
+ u32le(input.scopeEpoch),
3763
+ u32le(input.keyEpoch),
3764
+ ...input.applications.map((a, i) => fromHex(a, `applications[${i}]`, 32))
3765
+ ]);
3766
+ }
3767
+ async function signDeviceScope(input) {
3768
+ const signer = await resolveSigner(input.rootSecret, input.signer, "rootSecret");
3769
+ const rootPublicKey = fromHex(signer.publicKey, "signer.publicKey", 32);
3770
+ const account = await accountForRootPublicKey(hex(rootPublicKey));
3771
+ const applications = input.applications ?? [];
3772
+ const scopeEpoch = input.scopeEpoch ?? 0;
3773
+ if (!Number.isInteger(scopeEpoch) || scopeEpoch < 0 || scopeEpoch > 4294967295) {
3774
+ throw new Error(`scopeEpoch must be a u32, got ${scopeEpoch}`);
3775
+ }
3776
+ const keyEpoch = 0;
3777
+ const payload = await deviceScopePayload({
3778
+ account,
3779
+ device: input.device,
3780
+ applications,
3781
+ scopeEpoch,
3782
+ keyEpoch
3783
+ });
3784
+ const signature = await signer.sign(payload);
3785
+ if (signature.length !== 64) {
3786
+ throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
3787
+ }
3788
+ return hex(
3789
+ concat(
3790
+ new Uint8Array([ACCOUNT_GENESIS_VERSION]),
3791
+ rootPublicKey,
3792
+ u32le(0),
3793
+ fromHex(account, "account", 32),
3794
+ fromHex(input.device, "device", 32),
3795
+ u32le(applications.length),
3796
+ ...applications.map((a, i) => fromHex(a, `applications[${i}]`, 32)),
3797
+ u32le(scopeEpoch),
3798
+ u32le(keyEpoch),
3799
+ signature
3800
+ )
3801
+ );
3802
+ }
3803
+
3676
3804
  // src/account/wordlist.ts
3677
3805
  var ENGLISH_WORDLIST = [
3678
3806
  "abandon",
@@ -7270,9 +7398,9 @@ function parseCreationWarrant(warrant) {
7270
7398
  const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
7271
7399
  const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
7272
7400
  const option = (what) => {
7273
- const tag2 = take(1, what)[0];
7274
- if (tag2 === 0) return null;
7275
- if (tag2 !== 1) throw new Error(`${what} has option tag ${tag2}, expected 0 or 1`);
7401
+ const tag3 = take(1, what)[0];
7402
+ if (tag3 === 0) return null;
7403
+ if (tag3 !== 1) throw new Error(`${what} has option tag ${tag3}, expected 0 or 1`);
7276
7404
  const len = u32(what);
7277
7405
  if (len > MAX_LABEL_BYTES) {
7278
7406
  throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
@@ -7516,6 +7644,17 @@ function memberRoleSetOp(member, memberRole) {
7516
7644
  bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, "member", 32), role(memberRole))
7517
7645
  };
7518
7646
  }
7647
+ var SUBGROUP_ID_DOMAIN = new TextEncoder().encode("calimero.subgroup.id.v1");
7648
+ async function createdSubgroupId(admin, parentId, restricted, salt) {
7649
+ return hex(
7650
+ await domainHash(SUBGROUP_ID_DOMAIN, [
7651
+ fromHex(admin, "admin", 32),
7652
+ fromHex(parentId, "parentId", 32),
7653
+ new Uint8Array([restricted ? 1 : 0]),
7654
+ fromHex(salt, "salt", 32)
7655
+ ])
7656
+ );
7657
+ }
7519
7658
  function defaultCapabilitiesSetOp(capabilities) {
7520
7659
  if (!Number.isInteger(capabilities) || capabilities < 0 || capabilities > 4294967295) {
7521
7660
  throw new Error(`capabilities must be a u32 bit mask, got ${String(capabilities)}`);
@@ -7538,10 +7677,16 @@ function groupCreatedOp(input) {
7538
7677
  fromHex(input.groupId, "groupId", 32),
7539
7678
  fromHex(input.parentId, "parentId", 32),
7540
7679
  tag(input.restricted ? 1 : 0),
7541
- fromHex(input.admin, "admin", 32)
7680
+ fromHex(input.admin, "admin", 32),
7681
+ fromHex(input.salt, "salt", 32)
7542
7682
  )
7543
7683
  };
7544
7684
  }
7685
+ async function subgroupCreation(input) {
7686
+ const salt = input.salt ?? hex(crypto.getRandomValues(new Uint8Array(32)));
7687
+ const groupId = await createdSubgroupId(input.admin, input.parentId, input.restricted, salt);
7688
+ return { groupId, salt, op: groupCreatedOp({ ...input, groupId, salt }) };
7689
+ }
7545
7690
  function groupReparentedOp(childGroupId, newParentId) {
7546
7691
  return {
7547
7692
  kind: "root",
@@ -8099,12 +8244,12 @@ function audienceSigningBytes(audience) {
8099
8244
  );
8100
8245
  }
8101
8246
  function audienceWire(audience) {
8102
- const tag2 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
8247
+ const tag3 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
8103
8248
  if (audience.kind === "cli") {
8104
- return tag2;
8249
+ return tag3;
8105
8250
  }
8106
8251
  const body = audienceBody(audience);
8107
- return concat(tag2, u32le(body.length), body);
8252
+ return concat(tag3, u32le(body.length), body);
8108
8253
  }
8109
8254
  async function signLoginStatement(input) {
8110
8255
  const node = fromHex(input.node, "node", 32);
@@ -9789,8 +9934,8 @@ var SIGNATURE_HASHES = {
9789
9934
  };
9790
9935
  function read(bytes, offset, limit) {
9791
9936
  if (offset + 2 > limit) throw new Error("it is truncated");
9792
- const tag2 = bytes[offset];
9793
- if ((tag2 & 31) === 31) throw new Error("it uses a tag this parser does not read");
9937
+ const tag3 = bytes[offset];
9938
+ if ((tag3 & 31) === 31) throw new Error("it uses a tag this parser does not read");
9794
9939
  let length = bytes[offset + 1];
9795
9940
  let start = offset + 2;
9796
9941
  if (length & 128) {
@@ -9802,7 +9947,7 @@ function read(bytes, offset, limit) {
9802
9947
  }
9803
9948
  const end = start + length;
9804
9949
  if (end > limit) throw new Error("it is truncated");
9805
- return { tag: tag2, bytes, offset, start, end };
9950
+ return { tag: tag3, bytes, offset, start, end };
9806
9951
  }
9807
9952
  function parseDer(bytes) {
9808
9953
  const element = read(bytes, 0, bytes.length);
@@ -9818,8 +9963,8 @@ function children(element) {
9818
9963
  }
9819
9964
  return out;
9820
9965
  }
9821
- function expectTag(element, tag2, what) {
9822
- if (!element || element.tag !== tag2) throw new Error(`${what} is not where it should be`);
9966
+ function expectTag(element, tag3, what) {
9967
+ if (!element || element.tag !== tag3) throw new Error(`${what} is not where it should be`);
9823
9968
  return element;
9824
9969
  }
9825
9970
  function contents(element) {
@@ -10281,8 +10426,103 @@ function splitVersion2(version) {
10281
10426
  return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
10282
10427
  }
10283
10428
 
10429
+ // src/invitation/invitation.ts
10430
+ var MAX_INVITATION_VALIDITY_SECS = 24 * 60 * 60;
10431
+ var INVITED_ROLE = { Admin: 0, Member: 1, ReadOnly: 2 };
10432
+ function encodeGroupInvitation(body) {
10433
+ const admitters = body.admitters ?? [];
10434
+ return concat(
10435
+ // `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`
10436
+ // crosses as hex. Same width, different spelling.
10437
+ bytes32(body.inviter_identity, "inviter_identity"),
10438
+ bytes32(body.group_id, "group_id"),
10439
+ u64le(body.expiration_timestamp),
10440
+ bytes32(body.secret_salt, "secret_salt"),
10441
+ new Uint8Array([body.invited_role]),
10442
+ u32le(admitters.length),
10443
+ ...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32))
10444
+ );
10445
+ }
10446
+ async function groupInvitationHash(body) {
10447
+ return new Uint8Array(
10448
+ await crypto.subtle.digest("SHA-256", encodeGroupInvitation(body))
10449
+ );
10450
+ }
10451
+ function defaultAdmitters(members) {
10452
+ const admins = new Set(
10453
+ members.filter((m) => m.role === "Admin").map((m) => m.identity.toLowerCase())
10454
+ );
10455
+ return [...admins].sort();
10456
+ }
10457
+ async function signGroupInvitation(input) {
10458
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
10459
+ const inviterKey = fromHex(signer.publicKey, "signer.publicKey", 32);
10460
+ const groupId = fromHex(input.groupId, "groupId", 32);
10461
+ fromHex(input.inviterAccount, "inviterAccount", 32);
10462
+ let admitters = [...input.admitters ?? []].map((a) => a.trim().toLowerCase());
10463
+ if (admitters.length === 0) {
10464
+ if (!input.members) {
10465
+ throw new Error(
10466
+ "name the admitters, or pass the group members to default them to its admins: an invitation with no admitters is claimable by broadcast"
10467
+ );
10468
+ }
10469
+ admitters = defaultAdmitters(input.members);
10470
+ if (admitters.length === 0) {
10471
+ throw new Error(
10472
+ "the member list names no admin, so there is nobody to default the admitters to"
10473
+ );
10474
+ }
10475
+ }
10476
+ admitters.forEach((a, i) => fromHex(a, `admitters[${i}]`, 32));
10477
+ const invitedRole = input.invitedRole ?? INVITED_ROLE.Member;
10478
+ if (![0, 1, 2].includes(invitedRole)) {
10479
+ throw new Error(`invitedRole must be 0 (Admin), 1 (Member) or 2 (ReadOnly), got ${invitedRole}`);
10480
+ }
10481
+ const now = input.now ?? Math.floor(Date.now() / 1e3);
10482
+ const validFor = Math.min(
10483
+ input.validForSecs ?? MAX_INVITATION_VALIDITY_SECS,
10484
+ MAX_INVITATION_VALIDITY_SECS
10485
+ );
10486
+ if (!Number.isInteger(validFor) || validFor <= 0) {
10487
+ throw new Error(`validForSecs must be a positive whole number, got ${input.validForSecs}`);
10488
+ }
10489
+ const nonce2 = input.nonce ?? crypto.getRandomValues(new Uint8Array(32));
10490
+ if (nonce2.length !== 32) {
10491
+ throw new Error(`nonce must be 32 bytes, got ${nonce2.length}`);
10492
+ }
10493
+ const invitation = {
10494
+ inviter_identity: Array.from(inviterKey),
10495
+ group_id: Array.from(groupId),
10496
+ expiration_timestamp: now + validFor,
10497
+ secret_salt: Array.from(nonce2),
10498
+ invited_role: invitedRole,
10499
+ admitters
10500
+ };
10501
+ const signature = await signer.sign(await groupInvitationHash(invitation));
10502
+ if (signature.length !== 64) {
10503
+ throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
10504
+ }
10505
+ const signed = {
10506
+ invitation,
10507
+ inviter_signature: hex(signature),
10508
+ inviter_account: input.inviterAccount.trim().toLowerCase()
10509
+ };
10510
+ if (input.admitterAddrs?.length) signed.admitter_addrs = [...input.admitterAddrs];
10511
+ if (input.applicationId) {
10512
+ signed.application_id = Array.from(fromHex(input.applicationId, "applicationId", 32));
10513
+ }
10514
+ if (input.appKey) signed.app_key = Array.from(fromHex(input.appKey, "appKey", 32));
10515
+ return signed;
10516
+ }
10517
+ function bytes32(value, label2) {
10518
+ if (value.length !== 32) {
10519
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
10520
+ }
10521
+ return new Uint8Array(value);
10522
+ }
10523
+
10284
10524
  // src/namespace-op/namespace-op.ts
10285
- var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 16;
10525
+ var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 20;
10286
10526
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
10287
10527
  var NAMESPACE_OP_ROOT = 0;
10288
10528
  var ROOT_OP2 = {
@@ -10298,12 +10538,6 @@ var ROOT_OP2 = {
10298
10538
  NamespaceCreatedV2: 9,
10299
10539
  MemberJoinedViaTeeAttestation: 10
10300
10540
  };
10301
- function bytes32(value, label2) {
10302
- if (value.length !== 32) {
10303
- throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
10304
- }
10305
- return new Uint8Array(value);
10306
- }
10307
10541
  function optionalBytes32(value, label2) {
10308
10542
  if (value === null || value === void 0) {
10309
10543
  return new Uint8Array([0]);
@@ -10319,21 +10553,11 @@ function borshString2(value) {
10319
10553
  }
10320
10554
  function encodeSignedInvitation(signed) {
10321
10555
  const body = signed.invitation;
10322
- const admitters = body.admitters ?? [];
10323
10556
  const admitterAddrs = signed.admitter_addrs ?? [];
10324
10557
  return concat(
10325
- // GroupInvitationFromAdmin.
10326
- //
10327
- // `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`
10328
- // crosses as hex. Same width, different spelling, and reading either the
10329
- // wrong way yields 32 bytes that encode cleanly and verify as nothing.
10330
- bytes32(body.inviter_identity, "inviter_identity"),
10331
- bytes32(body.group_id, "group_id"),
10332
- u64le(body.expiration_timestamp),
10333
- bytes32(body.secret_salt, "secret_salt"),
10334
- new Uint8Array([body.invited_role]),
10335
- u32le(admitters.length),
10336
- ...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32)),
10558
+ // GroupInvitationFromAdmin, from the one encoder the SDK's own invitation
10559
+ // signer uses: the bytes signed and the bytes carried must not drift.
10560
+ encodeGroupInvitation(body),
10337
10561
  // ...then the enclosing SignedGroupOpenInvitation, in declaration order.
10338
10562
  // `inviter_account` sits between the signature and the addresses; putting it
10339
10563
  // anywhere else still encodes 32 bytes and still verifies as nothing.
@@ -10388,6 +10612,183 @@ async function signMemberJoinOp(input) {
10388
10612
  return hex(concat(signable, signature, unendorsed));
10389
10613
  }
10390
10614
 
10615
+ // src/owner-op/owner-op.ts
10616
+ var OWNER_OP_SIGN_DOMAIN = new TextEncoder().encode("calimero.account.owner-op.v1");
10617
+ var OWNER_OP_BODY_DOMAIN = new TextEncoder().encode("calimero.account.owner-op.body.v1");
10618
+ var ACCOUNT_ID_DOMAIN2 = new TextEncoder().encode("calimero.account.genesis.v1");
10619
+ var ACCOUNT_GENESIS_VERSION2 = 2;
10620
+ var OWNER_OP_KIND = {
10621
+ transferOwnership: 0,
10622
+ adminChanged: 1,
10623
+ groupDelete: 2,
10624
+ teeAdmissionPolicy: 3,
10625
+ teeAuthoringPolicy: 4,
10626
+ teeReleaseAdmissionPolicy: 5
10627
+ };
10628
+ var GROUP_OP2 = {
10629
+ GroupDelete: 14,
10630
+ TransferOwnership: 21,
10631
+ TeeAuthoringPolicySet: 33,
10632
+ TeeAdmissionPolicySetV2: 37,
10633
+ TeeReleaseAdmissionPolicySetV2: 38,
10634
+ RootGuarded: 42
10635
+ };
10636
+ var ROOT_OP3 = {
10637
+ AdminChanged: 3,
10638
+ RootGuarded: 12
10639
+ };
10640
+ var TEE_MODE = { replica: 0, relay: 1 };
10641
+ var te = new TextEncoder();
10642
+ function tag2(byte) {
10643
+ return new Uint8Array([byte]);
10644
+ }
10645
+ function borshString3(value) {
10646
+ const bytes = te.encode(value);
10647
+ return concat(u32le(bytes.length), bytes);
10648
+ }
10649
+ function borshStrings(values) {
10650
+ return concat(u32le(values.length), ...values.map(borshString3));
10651
+ }
10652
+ function borshBool(value) {
10653
+ return tag2(value ? 1 : 0);
10654
+ }
10655
+ function teeMode(mode) {
10656
+ const byte = TEE_MODE[mode ?? "replica"];
10657
+ if (byte === void 0) {
10658
+ throw new Error(`mode must be replica or relay, got ${String(mode)}`);
10659
+ }
10660
+ return tag2(byte);
10661
+ }
10662
+ function transferOwnershipOp(newOwner) {
10663
+ return {
10664
+ kind: "transferOwnership",
10665
+ plane: "group",
10666
+ bytes: concat(tag2(GROUP_OP2.TransferOwnership), fromHex(newOwner, "newOwner", 32))
10667
+ };
10668
+ }
10669
+ function groupDeleteOp() {
10670
+ return { kind: "groupDelete", plane: "group", bytes: tag2(GROUP_OP2.GroupDelete) };
10671
+ }
10672
+ function adminChangedOp(newAdmin) {
10673
+ return {
10674
+ kind: "adminChanged",
10675
+ plane: "root",
10676
+ bytes: concat(tag2(ROOT_OP3.AdminChanged), fromHex(newAdmin, "newAdmin", 32))
10677
+ };
10678
+ }
10679
+ function teeAuthoringPolicyOp(allowedMrtd) {
10680
+ return {
10681
+ kind: "teeAuthoringPolicy",
10682
+ plane: "group",
10683
+ bytes: concat(tag2(GROUP_OP2.TeeAuthoringPolicySet), borshStrings(allowedMrtd))
10684
+ };
10685
+ }
10686
+ function teeAdmissionPolicyOp(input) {
10687
+ return {
10688
+ kind: "teeAdmissionPolicy",
10689
+ plane: "group",
10690
+ bytes: concat(
10691
+ tag2(GROUP_OP2.TeeAdmissionPolicySetV2),
10692
+ borshStrings(input.allowedMrtd),
10693
+ borshStrings(input.allowedRtmr0),
10694
+ borshStrings(input.allowedRtmr1),
10695
+ borshStrings(input.allowedRtmr2),
10696
+ borshStrings(input.allowedRtmr3),
10697
+ borshStrings(input.allowedTcbStatuses),
10698
+ borshBool(input.acceptMock),
10699
+ teeMode(input.mode)
10700
+ )
10701
+ };
10702
+ }
10703
+ function teeReleaseAdmissionPolicyOp(input) {
10704
+ const minRelease = input.minReleaseVersion === void 0 ? tag2(0) : concat(tag2(1), borshString3(input.minReleaseVersion));
10705
+ return {
10706
+ kind: "teeReleaseAdmissionPolicy",
10707
+ plane: "group",
10708
+ bytes: concat(
10709
+ tag2(GROUP_OP2.TeeReleaseAdmissionPolicySetV2),
10710
+ borshStrings(input.allowedProfiles),
10711
+ minRelease,
10712
+ borshStrings(input.allowedTcbStatuses),
10713
+ borshBool(input.acceptMock),
10714
+ teeMode(input.mode)
10715
+ )
10716
+ };
10717
+ }
10718
+ async function ownerOpDigest(opBytes) {
10719
+ return domainHash(OWNER_OP_BODY_DOMAIN, [opBytes]);
10720
+ }
10721
+ async function ownerOpSigningPayload(input) {
10722
+ return domainHash(OWNER_OP_SIGN_DOMAIN, [
10723
+ fromHex(input.account, "account", 32),
10724
+ fromHex(input.namespaceId, "namespaceId", 32),
10725
+ fromHex(input.groupId, "groupId", 32),
10726
+ tag2(kindByte2(input.kind)),
10727
+ input.opDigest,
10728
+ u64le(input.counter),
10729
+ u32le(input.keyEpoch)
10730
+ ]);
10731
+ }
10732
+ function kindByte2(kind) {
10733
+ const byte = OWNER_OP_KIND[kind];
10734
+ if (byte === void 0) {
10735
+ throw new Error(`unknown owner-op kind ${String(kind)}`);
10736
+ }
10737
+ return byte;
10738
+ }
10739
+ var HANDOFF_BYTES = 32 + 4 + 32 + 64;
10740
+ async function signOwnerOpProof(input) {
10741
+ const rootPublicKey = await derivePublicKey(input.rootSecret);
10742
+ const genesis = concat(tag2(ACCOUNT_GENESIS_VERSION2), rootPublicKey);
10743
+ const account = hex(await domainHash(ACCOUNT_ID_DOMAIN2, [genesis]));
10744
+ const keyEpoch = input.keyEpoch ?? 0;
10745
+ const chain = (input.chain ?? []).map((handoff, index) => {
10746
+ const bytes = fromHexUnsized(handoff, `chain[${index}]`);
10747
+ if (bytes.length !== HANDOFF_BYTES) {
10748
+ throw new Error(
10749
+ `chain[${index}] must be a ${HANDOFF_BYTES}-byte RootKeyHandoff, got ${bytes.length}`
10750
+ );
10751
+ }
10752
+ return bytes;
10753
+ });
10754
+ if (keyEpoch > chain.length) {
10755
+ throw new Error(
10756
+ `keyEpoch ${keyEpoch} needs ${keyEpoch} handoffs in chain, got ${chain.length}`
10757
+ );
10758
+ }
10759
+ const opDigest = await ownerOpDigest(input.op.bytes);
10760
+ const payload = await ownerOpSigningPayload({
10761
+ account,
10762
+ namespaceId: input.namespaceId,
10763
+ groupId: input.groupId,
10764
+ kind: input.op.kind,
10765
+ opDigest,
10766
+ counter: input.counter,
10767
+ keyEpoch
10768
+ });
10769
+ const key = await importSigningKey(input.rootSecret);
10770
+ const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", key, payload));
10771
+ return hex(
10772
+ concat(
10773
+ genesis,
10774
+ u32le(chain.length),
10775
+ ...chain,
10776
+ fromHex(account, "account", 32),
10777
+ fromHex(input.namespaceId, "namespaceId", 32),
10778
+ fromHex(input.groupId, "groupId", 32),
10779
+ tag2(kindByte2(input.op.kind)),
10780
+ opDigest,
10781
+ u64le(input.counter),
10782
+ u32le(keyEpoch),
10783
+ signature
10784
+ )
10785
+ );
10786
+ }
10787
+ function rootGuardedOpBytes(op, rootProof) {
10788
+ const wrapper = op.plane === "group" ? GROUP_OP2.RootGuarded : ROOT_OP3.RootGuarded;
10789
+ return concat(tag2(wrapper), op.bytes, fromHexUnsized(rootProof, "rootProof"));
10790
+ }
10791
+
10391
10792
  // src/nodeDiscovery.ts
10392
10793
  var DEFAULT_LOCAL_NODE_PORTS = [2428, 2429, 2528, 2529];
10393
10794
  var LOCAL_HOST = "localhost";