@calimero-network/mero-js 15.1.0 → 15.3.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
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  MeroJs: () => MeroJs,
33
33
  RpcClient: () => RpcClient,
34
34
  RpcError: () => RpcError,
35
+ SIGNED_NAMESPACE_OP_SCHEMA_VERSION: () => SIGNED_NAMESPACE_OP_SCHEMA_VERSION,
35
36
  SseClient: () => SseClient,
36
37
  WebHttpClient: () => WebHttpClient,
37
38
  WsClient: () => WsClient,
@@ -56,6 +57,7 @@ __export(index_exports, {
56
57
  createUniversalHttpClient: () => createUniversalHttpClient,
57
58
  deviceCertPayload: () => deviceCertPayload,
58
59
  discoverLocalNodes: () => discoverLocalNodes,
60
+ encodeSignedInvitation: () => encodeSignedInvitation,
59
61
  hasCap: () => hasCap,
60
62
  intentHash: () => intentHash,
61
63
  jsonCodec: () => jsonCodec,
@@ -65,6 +67,7 @@ __export(index_exports, {
65
67
  parseAuthCallback: () => parseAuthCallback,
66
68
  probeNodeHealth: () => probeNodeHealth,
67
69
  signDeviceCert: () => signDeviceCert,
70
+ signMemberJoinOp: () => signMemberJoinOp,
68
71
  signWarrant: () => signWarrant,
69
72
  withCap: () => withCap,
70
73
  withRetry: () => withRetry,
@@ -1232,6 +1235,32 @@ var AdminApiClient = class {
1232
1235
  )
1233
1236
  );
1234
1237
  }
1238
+ /**
1239
+ * Present a join this caller signed to a node the inviter named as an
1240
+ * admitter.
1241
+ *
1242
+ * For a member with no node. `joinNamespace` publishes the membership op from
1243
+ * the node making the call; this hands an already-signed op to somebody else's
1244
+ * node to publish. The distinction matters because a keyholder has nowhere to
1245
+ * publish from.
1246
+ *
1247
+ * The op must be signed by the **device key** in the credential it carries —
1248
+ * every peer checks that when applying a join. So the admitter cannot author
1249
+ * this, only carry it: a hostile one can refuse, and nothing else. It cannot
1250
+ * admit a different account, change the group, or grant a role.
1251
+ *
1252
+ * @param namespaceId the namespace being joined, 64 hex
1253
+ * @param request the invitation and the hex-encoded signed op
1254
+ */
1255
+ async admitJoin(namespaceId, request) {
1256
+ return unwrap(
1257
+ await this.httpClient.post(
1258
+ `/admin-api/namespaces/${namespaceId}/admit`,
1259
+ request,
1260
+ { timeoutMs: 65e3 }
1261
+ )
1262
+ );
1263
+ }
1235
1264
  async joinNamespace(namespaceId, request) {
1236
1265
  const data = unwrap(
1237
1266
  await this.httpClient.post(
@@ -2759,6 +2788,20 @@ async function deviceCertPayload(input) {
2759
2788
  u32le(input.deviceEpoch)
2760
2789
  ]);
2761
2790
  }
2791
+ function accountProofBytes(input) {
2792
+ return concat(
2793
+ new Uint8Array([ACCOUNT_GENESIS_VERSION]),
2794
+ input.rootPublicKey,
2795
+ u32le(0),
2796
+ fromHex(input.account, "account", 32),
2797
+ fromHex(input.device, "device", 32),
2798
+ fromHex(input.signPublicKey, "signPublicKey", 32),
2799
+ fromHex(input.kemPublicKey, "kemPublicKey", 32),
2800
+ u32le(input.keyEpoch),
2801
+ u32le(input.deviceEpoch),
2802
+ input.signature
2803
+ );
2804
+ }
2762
2805
  async function signDeviceCert(input) {
2763
2806
  const rootPublicKey = await derivePublicKey(input.rootSecret);
2764
2807
  const account = hex(
@@ -2779,21 +2822,16 @@ async function signDeviceCert(input) {
2779
2822
  const signature = new Uint8Array(
2780
2823
  await crypto.subtle.sign("Ed25519", key, payload)
2781
2824
  );
2782
- const credential = concat(
2783
- // AccountGenesis
2784
- new Uint8Array([ACCOUNT_GENESIS_VERSION]),
2825
+ const credential = accountProofBytes({
2785
2826
  rootPublicKey,
2786
- // chain: Vec<RootKeyHandoff>, empty
2787
- u32le(0),
2788
- // DeviceCert
2789
- fromHex(account, "account", 32),
2790
- fromHex(input.device, "device", 32),
2791
- fromHex(input.signPublicKey, "signPublicKey", 32),
2792
- fromHex(input.kemPublicKey, "kemPublicKey", 32),
2793
- u32le(keyEpoch),
2794
- u32le(input.deviceEpoch),
2827
+ account,
2828
+ device: input.device,
2829
+ signPublicKey: input.signPublicKey,
2830
+ kemPublicKey: input.kemPublicKey,
2831
+ keyEpoch,
2832
+ deviceEpoch: input.deviceEpoch,
2795
2833
  signature
2796
- );
2834
+ });
2797
2835
  return hex(credential);
2798
2836
  }
2799
2837
  async function accountForRoot(rootSecret) {
@@ -2805,6 +2843,107 @@ async function accountForRoot(rootSecret) {
2805
2843
  );
2806
2844
  }
2807
2845
 
2846
+ // src/namespace-op/namespace-op.ts
2847
+ var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 7;
2848
+ var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
2849
+ var NAMESPACE_OP_ROOT = 0;
2850
+ var ROOT_OP = {
2851
+ GroupCreated: 0,
2852
+ GroupReparented: 1,
2853
+ GroupDeleted: 2,
2854
+ AdminChanged: 3,
2855
+ PolicyUpdated: 4,
2856
+ MemberJoined: 5,
2857
+ KeyDelivery: 6,
2858
+ MemberJoinedOpen: 7,
2859
+ MemberJoinedAt: 8,
2860
+ NamespaceCreated: 9,
2861
+ MemberJoinedViaTeeAttestation: 10
2862
+ };
2863
+ function bytes32(value, label) {
2864
+ if (value.length !== 32) {
2865
+ throw new Error(`${label} must be 32 bytes, got ${value.length}`);
2866
+ }
2867
+ return new Uint8Array(value);
2868
+ }
2869
+ function optionalBytes32(value, label) {
2870
+ if (value === null || value === void 0) {
2871
+ return new Uint8Array([0]);
2872
+ }
2873
+ if (value.length !== 32) {
2874
+ throw new Error(`${label} must be 32 bytes, got ${value.length}`);
2875
+ }
2876
+ return concat(new Uint8Array([1]), new Uint8Array(value));
2877
+ }
2878
+ function borshString(value) {
2879
+ const bytes = new TextEncoder().encode(value);
2880
+ return concat(u32le(bytes.length), bytes);
2881
+ }
2882
+ function encodeAdmitterHint(hint) {
2883
+ if ("multiaddr" in hint) {
2884
+ return concat(new Uint8Array([0]), borshString(hint.multiaddr));
2885
+ }
2886
+ return concat(new Uint8Array([1]), borshString(hint.url));
2887
+ }
2888
+ function encodeSignedInvitation(signed) {
2889
+ const body = signed.invitation;
2890
+ const admitters = body.admitters ?? [];
2891
+ const hints = signed.admitter_hints ?? [];
2892
+ return concat(
2893
+ // GroupInvitationFromAdmin.
2894
+ //
2895
+ // `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`
2896
+ // crosses as hex. Same width, different spelling, and reading either the
2897
+ // wrong way yields 32 bytes that encode cleanly and verify as nothing.
2898
+ bytes32(body.inviter_identity, "inviter_identity"),
2899
+ bytes32(body.group_id, "group_id"),
2900
+ u64le(body.expiration_timestamp),
2901
+ bytes32(body.secret_salt, "secret_salt"),
2902
+ new Uint8Array([body.invited_role]),
2903
+ u32le(admitters.length),
2904
+ ...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32)),
2905
+ // ...then the enclosing SignedGroupOpenInvitation, in declaration order.
2906
+ // `inviter_account` sits between the signature and the hints; putting it
2907
+ // anywhere else still encodes 32 bytes and still verifies as nothing.
2908
+ borshString(signed.inviter_signature),
2909
+ signed.inviter_account ? concat(new Uint8Array([1]), fromHex(signed.inviter_account, "inviter_account", 32)) : new Uint8Array([0]),
2910
+ u32le(hints.length),
2911
+ ...hints.map(encodeAdmitterHint),
2912
+ optionalBytes32(signed.application_id, "application_id"),
2913
+ // Borsh order follows core's field order, where this is `bytecode_id`; the
2914
+ // JSON key is `app_key`, a rename covering both directions that core pins in
2915
+ // a test. Encoding by the Rust name would read `undefined` and emit a None
2916
+ // where the inviter signed a Some.
2917
+ optionalBytes32(signed.app_key, "app_key")
2918
+ );
2919
+ }
2920
+ async function signMemberJoinOp(input) {
2921
+ const parents = input.parentOpHashes ?? [];
2922
+ const credential = fromHex(input.credential, "credential", input.credential.length / 2);
2923
+ const rootOp = concat(
2924
+ new Uint8Array([input.at ? ROOT_OP.MemberJoinedAt : ROOT_OP.MemberJoined]),
2925
+ fromHex(input.member, "member", 32),
2926
+ encodeSignedInvitation(input.invitation),
2927
+ credential
2928
+ );
2929
+ const op = concat(new Uint8Array([NAMESPACE_OP_ROOT]), rootOp);
2930
+ const key = await importSigningKey(input.deviceSecret);
2931
+ const signer = await derivePublicKey(input.deviceSecret);
2932
+ const signable = concat(
2933
+ new Uint8Array([SIGNED_NAMESPACE_OP_SCHEMA_VERSION]),
2934
+ fromHex(input.namespaceId, "namespaceId", 32),
2935
+ u32le(parents.length),
2936
+ ...parents.map((p, i) => fromHex(p, `parentOpHashes[${i}]`, 32)),
2937
+ signer,
2938
+ u64le(input.nonce),
2939
+ op
2940
+ );
2941
+ const signature = new Uint8Array(
2942
+ await crypto.subtle.sign("Ed25519", key, concat(NAMESPACE_SIGN_DOMAIN, signable))
2943
+ );
2944
+ return hex(concat(signable, signature));
2945
+ }
2946
+
2808
2947
  // src/capabilities.ts
2809
2948
  var CAPABILITIES = {
2810
2949
  CAN_CREATE_CONTEXT: 1 << 0,