@calimero-network/mero-js 15.0.0 → 15.2.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
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  SseClient: () => SseClient,
36
36
  WebHttpClient: () => WebHttpClient,
37
37
  WsClient: () => WsClient,
38
+ accountForRoot: () => accountForRoot,
38
39
  buildAuthLoginUrl: () => buildAuthLoginUrl,
39
40
  combineSignals: () => combineSignals,
40
41
  compareSemver: () => compareSemver,
@@ -53,14 +54,17 @@ __export(index_exports, {
53
54
  createRetryableMethod: () => createRetryableMethod,
54
55
  createTimeoutSignal: () => createTimeoutSignal,
55
56
  createUniversalHttpClient: () => createUniversalHttpClient,
57
+ deviceCertPayload: () => deviceCertPayload,
56
58
  discoverLocalNodes: () => discoverLocalNodes,
57
59
  hasCap: () => hasCap,
58
60
  intentHash: () => intentHash,
59
61
  jsonCodec: () => jsonCodec,
60
62
  localNodeUrl: () => localNodeUrl,
63
+ mintDeviceId: () => mintDeviceId,
61
64
  nodeEndpoint: () => nodeEndpoint,
62
65
  parseAuthCallback: () => parseAuthCallback,
63
66
  probeNodeHealth: () => probeNodeHealth,
67
+ signDeviceCert: () => signDeviceCert,
64
68
  signWarrant: () => signWarrant,
65
69
  withCap: () => withCap,
66
70
  withRetry: () => withRetry,
@@ -1228,6 +1232,32 @@ var AdminApiClient = class {
1228
1232
  )
1229
1233
  );
1230
1234
  }
1235
+ /**
1236
+ * Present a join this caller signed to a node the inviter named as an
1237
+ * admitter.
1238
+ *
1239
+ * For a member with no node. `joinNamespace` publishes the membership op from
1240
+ * the node making the call; this hands an already-signed op to somebody else's
1241
+ * node to publish. The distinction matters because a keyholder has nowhere to
1242
+ * publish from.
1243
+ *
1244
+ * The op must be signed by the **device key** in the credential it carries —
1245
+ * every peer checks that when applying a join. So the admitter cannot author
1246
+ * this, only carry it: a hostile one can refuse, and nothing else. It cannot
1247
+ * admit a different account, change the group, or grant a role.
1248
+ *
1249
+ * @param namespaceId the namespace being joined, 64 hex
1250
+ * @param request the invitation and the hex-encoded signed op
1251
+ */
1252
+ async admitJoin(namespaceId, request) {
1253
+ return unwrap(
1254
+ await this.httpClient.post(
1255
+ `/admin-api/namespaces/${namespaceId}/admit`,
1256
+ request,
1257
+ { timeoutMs: 65e3 }
1258
+ )
1259
+ );
1260
+ }
1231
1261
  async joinNamespace(namespaceId, request) {
1232
1262
  const data = unwrap(
1233
1263
  await this.httpClient.post(
@@ -2592,9 +2622,7 @@ var CloudClient = class {
2592
2622
  }
2593
2623
  };
2594
2624
 
2595
- // src/warrant/warrant.ts
2596
- var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v1");
2597
- var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
2625
+ // src/crypto/internal.ts
2598
2626
  var PKCS8_ED25519_PREFIX = new Uint8Array([
2599
2627
  48,
2600
2628
  46,
@@ -2613,6 +2641,11 @@ var PKCS8_ED25519_PREFIX = new Uint8Array([
2613
2641
  4,
2614
2642
  32
2615
2643
  ]);
2644
+ function u32le(value) {
2645
+ const out = new Uint8Array(4);
2646
+ new DataView(out.buffer).setUint32(0, value, true);
2647
+ return out;
2648
+ }
2616
2649
  function hex(bytes) {
2617
2650
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2618
2651
  }
@@ -2652,10 +2685,6 @@ async function domainHash(domain, parts) {
2652
2685
  await crypto.subtle.digest("SHA-256", concat(...chunks))
2653
2686
  );
2654
2687
  }
2655
- async function intentHash(method, argsJson) {
2656
- const args = new TextEncoder().encode(JSON.stringify(argsJson));
2657
- return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
2658
- }
2659
2688
  async function importSigningKey(secretHex) {
2660
2689
  const seed = fromHex(secretHex, "deviceSecret", 32);
2661
2690
  const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
@@ -2674,6 +2703,31 @@ async function importSigningKey(secretHex) {
2674
2703
  );
2675
2704
  }
2676
2705
  }
2706
+ async function derivePublicKey(secretHex) {
2707
+ const seed = fromHex(secretHex, "deviceSecret", 32);
2708
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2709
+ const key = await crypto.subtle.importKey(
2710
+ "pkcs8",
2711
+ pkcs8,
2712
+ { name: "Ed25519" },
2713
+ true,
2714
+ ["sign"]
2715
+ );
2716
+ const jwk = await crypto.subtle.exportKey("jwk", key);
2717
+ const raw = jwk.x;
2718
+ return Uint8Array.from(
2719
+ atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2720
+ (c) => c.charCodeAt(0)
2721
+ );
2722
+ }
2723
+
2724
+ // src/warrant/warrant.ts
2725
+ var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v1");
2726
+ var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
2727
+ async function intentHash(method, argsJson) {
2728
+ const args = new TextEncoder().encode(JSON.stringify(argsJson));
2729
+ return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
2730
+ }
2677
2731
  async function signWarrant(input) {
2678
2732
  const context = fromHex(input.context, "context", 32);
2679
2733
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
@@ -2708,21 +2762,72 @@ async function signWarrant(input) {
2708
2762
  )
2709
2763
  );
2710
2764
  }
2711
- async function derivePublicKey(secretHex) {
2712
- const seed = fromHex(secretHex, "deviceSecret", 32);
2713
- const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2714
- const key = await crypto.subtle.importKey(
2715
- "pkcs8",
2716
- pkcs8,
2717
- { name: "Ed25519" },
2718
- true,
2719
- ["sign"]
2765
+
2766
+ // src/device-cert/device-cert.ts
2767
+ var CERT_DOMAIN = new TextEncoder().encode("calimero.device.cert.v1");
2768
+ var ACCOUNT_ID_DOMAIN = new TextEncoder().encode("calimero.account.genesis.v1");
2769
+ var DEVICE_ID_DOMAIN = new TextEncoder().encode("calimero.device.id.v1");
2770
+ var ACCOUNT_GENESIS_VERSION = 1;
2771
+ async function mintDeviceId(account, nonce) {
2772
+ if (nonce.length !== 16) {
2773
+ throw new Error(`nonce must be 16 bytes, got ${nonce.length}`);
2774
+ }
2775
+ const accountBytes = fromHex(account, "account", 32);
2776
+ return hex(await domainHash(DEVICE_ID_DOMAIN, [accountBytes, nonce]));
2777
+ }
2778
+ async function deviceCertPayload(input) {
2779
+ return domainHash(CERT_DOMAIN, [
2780
+ fromHex(input.account, "account", 32),
2781
+ fromHex(input.device, "device", 32),
2782
+ fromHex(input.signPublicKey, "signPublicKey", 32),
2783
+ fromHex(input.kemPublicKey, "kemPublicKey", 32),
2784
+ u32le(input.keyEpoch),
2785
+ u32le(input.deviceEpoch)
2786
+ ]);
2787
+ }
2788
+ async function signDeviceCert(input) {
2789
+ const rootPublicKey = await derivePublicKey(input.rootSecret);
2790
+ const account = hex(
2791
+ await domainHash(ACCOUNT_ID_DOMAIN, [
2792
+ concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey)
2793
+ ])
2720
2794
  );
2721
- const jwk = await crypto.subtle.exportKey("jwk", key);
2722
- const raw = jwk.x;
2723
- return Uint8Array.from(
2724
- atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2725
- (c) => c.charCodeAt(0)
2795
+ const keyEpoch = 0;
2796
+ const payload = await deviceCertPayload({
2797
+ account,
2798
+ device: input.device,
2799
+ signPublicKey: input.signPublicKey,
2800
+ kemPublicKey: input.kemPublicKey,
2801
+ keyEpoch,
2802
+ deviceEpoch: input.deviceEpoch
2803
+ });
2804
+ const key = await importSigningKey(input.rootSecret);
2805
+ const signature = new Uint8Array(
2806
+ await crypto.subtle.sign("Ed25519", key, payload)
2807
+ );
2808
+ const credential = concat(
2809
+ // AccountGenesis
2810
+ new Uint8Array([ACCOUNT_GENESIS_VERSION]),
2811
+ rootPublicKey,
2812
+ // chain: Vec<RootKeyHandoff>, empty
2813
+ u32le(0),
2814
+ // DeviceCert
2815
+ fromHex(account, "account", 32),
2816
+ fromHex(input.device, "device", 32),
2817
+ fromHex(input.signPublicKey, "signPublicKey", 32),
2818
+ fromHex(input.kemPublicKey, "kemPublicKey", 32),
2819
+ u32le(keyEpoch),
2820
+ u32le(input.deviceEpoch),
2821
+ signature
2822
+ );
2823
+ return hex(credential);
2824
+ }
2825
+ async function accountForRoot(rootSecret) {
2826
+ const rootPublicKey = await derivePublicKey(rootSecret);
2827
+ return hex(
2828
+ await domainHash(ACCOUNT_ID_DOMAIN, [
2829
+ concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey)
2830
+ ])
2726
2831
  );
2727
2832
  }
2728
2833