@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.d.ts CHANGED
@@ -16,6 +16,8 @@ export { EphemeralClient, jsonCodec } from './ephemeral/index.js';
16
16
  export type { Codec, EphemeralEntry } from './ephemeral/index.js';
17
17
  export * from './cloud/index.js';
18
18
  export { signWarrant, intentHash } from './warrant/index.js';
19
+ export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, } from './device-cert/index.js';
20
+ export type { DeviceCertInput } from './device-cert/index.js';
19
21
  export type { WarrantInput } from './warrant/index.js';
20
22
  export * from './capabilities.js';
21
23
  export { DEFAULT_LOCAL_NODE_PORTS, localNodeUrl, nodeEndpoint, probeNodeHealth, discoverLocalNodes, } from './nodeDiscovery.js';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ export { EphemeralClient, jsonCodec } from './ephemeral/index.js';
22
22
  export * from './cloud/index.js';
23
23
  // Warrant signing — mint the author's consent for a relay to run one intent
24
24
  export { signWarrant, intentHash } from './warrant/index.js';
25
+ export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, } from './device-cert/index.js';
25
26
  // Member capability bitmask constants & helpers
26
27
  export * from './capabilities.js';
27
28
  // Utilities
package/dist/index.mjs CHANGED
@@ -1158,6 +1158,32 @@ var AdminApiClient = class {
1158
1158
  )
1159
1159
  );
1160
1160
  }
1161
+ /**
1162
+ * Present a join this caller signed to a node the inviter named as an
1163
+ * admitter.
1164
+ *
1165
+ * For a member with no node. `joinNamespace` publishes the membership op from
1166
+ * the node making the call; this hands an already-signed op to somebody else's
1167
+ * node to publish. The distinction matters because a keyholder has nowhere to
1168
+ * publish from.
1169
+ *
1170
+ * The op must be signed by the **device key** in the credential it carries —
1171
+ * every peer checks that when applying a join. So the admitter cannot author
1172
+ * this, only carry it: a hostile one can refuse, and nothing else. It cannot
1173
+ * admit a different account, change the group, or grant a role.
1174
+ *
1175
+ * @param namespaceId the namespace being joined, 64 hex
1176
+ * @param request the invitation and the hex-encoded signed op
1177
+ */
1178
+ async admitJoin(namespaceId, request) {
1179
+ return unwrap(
1180
+ await this.httpClient.post(
1181
+ `/admin-api/namespaces/${namespaceId}/admit`,
1182
+ request,
1183
+ { timeoutMs: 65e3 }
1184
+ )
1185
+ );
1186
+ }
1161
1187
  async joinNamespace(namespaceId, request) {
1162
1188
  const data = unwrap(
1163
1189
  await this.httpClient.post(
@@ -2522,9 +2548,7 @@ var CloudClient = class {
2522
2548
  }
2523
2549
  };
2524
2550
 
2525
- // src/warrant/warrant.ts
2526
- var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v1");
2527
- var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
2551
+ // src/crypto/internal.ts
2528
2552
  var PKCS8_ED25519_PREFIX = new Uint8Array([
2529
2553
  48,
2530
2554
  46,
@@ -2543,6 +2567,11 @@ var PKCS8_ED25519_PREFIX = new Uint8Array([
2543
2567
  4,
2544
2568
  32
2545
2569
  ]);
2570
+ function u32le(value) {
2571
+ const out = new Uint8Array(4);
2572
+ new DataView(out.buffer).setUint32(0, value, true);
2573
+ return out;
2574
+ }
2546
2575
  function hex(bytes) {
2547
2576
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2548
2577
  }
@@ -2582,10 +2611,6 @@ async function domainHash(domain, parts) {
2582
2611
  await crypto.subtle.digest("SHA-256", concat(...chunks))
2583
2612
  );
2584
2613
  }
2585
- async function intentHash(method, argsJson) {
2586
- const args = new TextEncoder().encode(JSON.stringify(argsJson));
2587
- return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
2588
- }
2589
2614
  async function importSigningKey(secretHex) {
2590
2615
  const seed = fromHex(secretHex, "deviceSecret", 32);
2591
2616
  const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
@@ -2604,6 +2629,31 @@ async function importSigningKey(secretHex) {
2604
2629
  );
2605
2630
  }
2606
2631
  }
2632
+ async function derivePublicKey(secretHex) {
2633
+ const seed = fromHex(secretHex, "deviceSecret", 32);
2634
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2635
+ const key = await crypto.subtle.importKey(
2636
+ "pkcs8",
2637
+ pkcs8,
2638
+ { name: "Ed25519" },
2639
+ true,
2640
+ ["sign"]
2641
+ );
2642
+ const jwk = await crypto.subtle.exportKey("jwk", key);
2643
+ const raw = jwk.x;
2644
+ return Uint8Array.from(
2645
+ atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2646
+ (c) => c.charCodeAt(0)
2647
+ );
2648
+ }
2649
+
2650
+ // src/warrant/warrant.ts
2651
+ var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v1");
2652
+ var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
2653
+ async function intentHash(method, argsJson) {
2654
+ const args = new TextEncoder().encode(JSON.stringify(argsJson));
2655
+ return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
2656
+ }
2607
2657
  async function signWarrant(input) {
2608
2658
  const context = fromHex(input.context, "context", 32);
2609
2659
  const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
@@ -2638,21 +2688,72 @@ async function signWarrant(input) {
2638
2688
  )
2639
2689
  );
2640
2690
  }
2641
- async function derivePublicKey(secretHex) {
2642
- const seed = fromHex(secretHex, "deviceSecret", 32);
2643
- const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2644
- const key = await crypto.subtle.importKey(
2645
- "pkcs8",
2646
- pkcs8,
2647
- { name: "Ed25519" },
2648
- true,
2649
- ["sign"]
2691
+
2692
+ // src/device-cert/device-cert.ts
2693
+ var CERT_DOMAIN = new TextEncoder().encode("calimero.device.cert.v1");
2694
+ var ACCOUNT_ID_DOMAIN = new TextEncoder().encode("calimero.account.genesis.v1");
2695
+ var DEVICE_ID_DOMAIN = new TextEncoder().encode("calimero.device.id.v1");
2696
+ var ACCOUNT_GENESIS_VERSION = 1;
2697
+ async function mintDeviceId(account, nonce) {
2698
+ if (nonce.length !== 16) {
2699
+ throw new Error(`nonce must be 16 bytes, got ${nonce.length}`);
2700
+ }
2701
+ const accountBytes = fromHex(account, "account", 32);
2702
+ return hex(await domainHash(DEVICE_ID_DOMAIN, [accountBytes, nonce]));
2703
+ }
2704
+ async function deviceCertPayload(input) {
2705
+ return domainHash(CERT_DOMAIN, [
2706
+ fromHex(input.account, "account", 32),
2707
+ fromHex(input.device, "device", 32),
2708
+ fromHex(input.signPublicKey, "signPublicKey", 32),
2709
+ fromHex(input.kemPublicKey, "kemPublicKey", 32),
2710
+ u32le(input.keyEpoch),
2711
+ u32le(input.deviceEpoch)
2712
+ ]);
2713
+ }
2714
+ async function signDeviceCert(input) {
2715
+ const rootPublicKey = await derivePublicKey(input.rootSecret);
2716
+ const account = hex(
2717
+ await domainHash(ACCOUNT_ID_DOMAIN, [
2718
+ concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey)
2719
+ ])
2650
2720
  );
2651
- const jwk = await crypto.subtle.exportKey("jwk", key);
2652
- const raw = jwk.x;
2653
- return Uint8Array.from(
2654
- atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2655
- (c) => c.charCodeAt(0)
2721
+ const keyEpoch = 0;
2722
+ const payload = await deviceCertPayload({
2723
+ account,
2724
+ device: input.device,
2725
+ signPublicKey: input.signPublicKey,
2726
+ kemPublicKey: input.kemPublicKey,
2727
+ keyEpoch,
2728
+ deviceEpoch: input.deviceEpoch
2729
+ });
2730
+ const key = await importSigningKey(input.rootSecret);
2731
+ const signature = new Uint8Array(
2732
+ await crypto.subtle.sign("Ed25519", key, payload)
2733
+ );
2734
+ const credential = concat(
2735
+ // AccountGenesis
2736
+ new Uint8Array([ACCOUNT_GENESIS_VERSION]),
2737
+ rootPublicKey,
2738
+ // chain: Vec<RootKeyHandoff>, empty
2739
+ u32le(0),
2740
+ // DeviceCert
2741
+ fromHex(account, "account", 32),
2742
+ fromHex(input.device, "device", 32),
2743
+ fromHex(input.signPublicKey, "signPublicKey", 32),
2744
+ fromHex(input.kemPublicKey, "kemPublicKey", 32),
2745
+ u32le(keyEpoch),
2746
+ u32le(input.deviceEpoch),
2747
+ signature
2748
+ );
2749
+ return hex(credential);
2750
+ }
2751
+ async function accountForRoot(rootSecret) {
2752
+ const rootPublicKey = await derivePublicKey(rootSecret);
2753
+ return hex(
2754
+ await domainHash(ACCOUNT_ID_DOMAIN, [
2755
+ concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey)
2756
+ ])
2656
2757
  );
2657
2758
  }
2658
2759
 
@@ -2748,6 +2849,7 @@ export {
2748
2849
  SseClient,
2749
2850
  WebHttpClient,
2750
2851
  WsClient,
2852
+ accountForRoot,
2751
2853
  buildAuthLoginUrl,
2752
2854
  combineSignals,
2753
2855
  compareSemver,
@@ -2766,14 +2868,17 @@ export {
2766
2868
  createRetryableMethod,
2767
2869
  createTimeoutSignal,
2768
2870
  createUniversalHttpClient,
2871
+ deviceCertPayload,
2769
2872
  discoverLocalNodes,
2770
2873
  hasCap,
2771
2874
  intentHash,
2772
2875
  jsonCodec,
2773
2876
  localNodeUrl,
2877
+ mintDeviceId,
2774
2878
  nodeEndpoint,
2775
2879
  parseAuthCallback,
2776
2880
  probeNodeHealth,
2881
+ signDeviceCert,
2777
2882
  signWarrant,
2778
2883
  withCap,
2779
2884
  withRetry,