@calimero-network/mero-js 13.3.0 → 13.4.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 type { SseEventData, WsEventData, AppVersionChangedEvent, GroupMembership
16
16
  export { EphemeralClient, jsonCodec } from './ephemeral/index.js';
17
17
  export type { Codec, EphemeralEntry } from './ephemeral/index.js';
18
18
  export * from './cloud/index.js';
19
+ export { signWarrant, intentHash } from './warrant/index.js';
20
+ export type { WarrantInput } from './warrant/index.js';
19
21
  export * from './capabilities.js';
20
22
  export { DEFAULT_LOCAL_NODE_PORTS, localNodeUrl, nodeEndpoint, probeNodeHealth, discoverLocalNodes, } from './nodeDiscovery.js';
21
23
  export type { DiscoverLocalNodesOptions } from './nodeDiscovery.js';
package/dist/index.js CHANGED
@@ -22,6 +22,8 @@ export { SseClient, WsClient } from './events/index.js';
22
22
  export { EphemeralClient, jsonCodec } from './ephemeral/index.js';
23
23
  // Cloud client (enable-HA, disable-HA)
24
24
  export * from './cloud/index.js';
25
+ // Warrant signing — mint the author's consent for a relay to run one intent
26
+ export { signWarrant, intentHash } from './warrant/index.js';
25
27
  // Member capability bitmask constants & helpers
26
28
  export * from './capabilities.js';
27
29
  // Utilities
package/dist/index.mjs CHANGED
@@ -1119,7 +1119,8 @@ var AdminApiClient = class {
1119
1119
  * CAN_AUTHOR_ON_BEHALF to …", or that the nonce is already spent — so a caller
1120
1120
  * can tell which precondition failed rather than only that one did.
1121
1121
  *
1122
- * This SDK does not mint warrants; see {@link PerformIntentRequest.warrant}.
1122
+ * Mint the warrant with {@link signWarrant}; see
1123
+ * {@link PerformIntentRequest.warrant}.
1123
1124
  */
1124
1125
  async performIntent(contextId, request) {
1125
1126
  return unwrap(
@@ -2495,10 +2496,10 @@ function toHex(bytes) {
2495
2496
  for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
2496
2497
  return out;
2497
2498
  }
2498
- function fromHex(hex) {
2499
- const bytes = new Uint8Array(hex.length / 2);
2499
+ function fromHex(hex2) {
2500
+ const bytes = new Uint8Array(hex2.length / 2);
2500
2501
  for (let i = 0; i < bytes.length; i++) {
2501
- bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
2502
+ bytes[i] = parseInt(hex2.slice(i * 2, i * 2 + 2), 16);
2502
2503
  }
2503
2504
  return bytes;
2504
2505
  }
@@ -2603,6 +2604,182 @@ var CloudClient = class {
2603
2604
  }
2604
2605
  };
2605
2606
 
2607
+ // src/warrant/warrant.ts
2608
+ var SIGN_DOMAIN = new TextEncoder().encode("calimero.warrant.v1");
2609
+ var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
2610
+ var PKCS8_ED25519_PREFIX = new Uint8Array([
2611
+ 48,
2612
+ 46,
2613
+ 2,
2614
+ 1,
2615
+ 0,
2616
+ 48,
2617
+ 5,
2618
+ 6,
2619
+ 3,
2620
+ 43,
2621
+ 101,
2622
+ 112,
2623
+ 4,
2624
+ 34,
2625
+ 4,
2626
+ 32
2627
+ ]);
2628
+ var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
2629
+ function hex(bytes) {
2630
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2631
+ }
2632
+ function fromHex2(value, label, expectedBytes) {
2633
+ const clean = value.trim();
2634
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
2635
+ throw new Error(
2636
+ `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2637
+ );
2638
+ }
2639
+ const bytes = new Uint8Array(expectedBytes);
2640
+ for (let i = 0; i < expectedBytes; i += 1) {
2641
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
2642
+ }
2643
+ return bytes;
2644
+ }
2645
+ function fromBase58(value, label) {
2646
+ const clean = value.trim();
2647
+ let n = 0n;
2648
+ for (const char of clean) {
2649
+ const digit = B58.indexOf(char);
2650
+ if (digit < 0) {
2651
+ throw new Error(`${label} is not base58: unexpected character '${char}'`);
2652
+ }
2653
+ n = n * 58n + BigInt(digit);
2654
+ }
2655
+ const out = new Uint8Array(32);
2656
+ for (let i = 31; i >= 0; i -= 1) {
2657
+ out[i] = Number(n & 0xffn);
2658
+ n >>= 8n;
2659
+ }
2660
+ if (n !== 0n) {
2661
+ throw new Error(`${label} decodes to more than 32 bytes`);
2662
+ }
2663
+ if (toBase58(out) !== clean) {
2664
+ throw new Error(
2665
+ `${label} is not a canonical base58 32-byte id. If you have it in hex, note that ${label} is base58 here while accounts are hex`
2666
+ );
2667
+ }
2668
+ return out;
2669
+ }
2670
+ function toBase58(bytes) {
2671
+ let n = 0n;
2672
+ for (const byte of bytes) {
2673
+ n = n << 8n | BigInt(byte);
2674
+ }
2675
+ let out = "";
2676
+ while (n > 0n) {
2677
+ out = B58[Number(n % 58n)] + out;
2678
+ n /= 58n;
2679
+ }
2680
+ let leadingZeros = 0;
2681
+ while (leadingZeros < bytes.length && bytes[leadingZeros] === 0) {
2682
+ leadingZeros += 1;
2683
+ }
2684
+ return "1".repeat(leadingZeros) + out;
2685
+ }
2686
+ function u64le(value) {
2687
+ const out = new Uint8Array(8);
2688
+ new DataView(out.buffer).setBigUint64(0, BigInt(value), true);
2689
+ return out;
2690
+ }
2691
+ function concat(...parts) {
2692
+ const out = new Uint8Array(parts.reduce((total, p) => total + p.length, 0));
2693
+ let offset = 0;
2694
+ for (const part of parts) {
2695
+ out.set(part, offset);
2696
+ offset += part.length;
2697
+ }
2698
+ return out;
2699
+ }
2700
+ async function domainHash(domain, parts) {
2701
+ const chunks = [u64le(domain.length), domain];
2702
+ for (const part of parts) {
2703
+ chunks.push(u64le(part.length), part);
2704
+ }
2705
+ return new Uint8Array(
2706
+ await crypto.subtle.digest("SHA-256", concat(...chunks))
2707
+ );
2708
+ }
2709
+ async function intentHash(method, argsJson) {
2710
+ const args = new TextEncoder().encode(JSON.stringify(argsJson));
2711
+ return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
2712
+ }
2713
+ async function importSigningKey(secretHex) {
2714
+ const seed = fromHex2(secretHex, "deviceSecret", 32);
2715
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2716
+ try {
2717
+ return await crypto.subtle.importKey(
2718
+ "pkcs8",
2719
+ pkcs8,
2720
+ { name: "Ed25519" },
2721
+ false,
2722
+ ["sign"]
2723
+ );
2724
+ } catch (cause) {
2725
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
2726
+ throw new Error(
2727
+ "this runtime has no WebCrypto Ed25519, so a warrant cannot be signed here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it; otherwise mint the warrant with `merod account warrant`" + detail
2728
+ );
2729
+ }
2730
+ }
2731
+ async function signWarrant(input) {
2732
+ const context = fromBase58(input.context, "context");
2733
+ const authorAccount = fromHex2(input.authorAccount, "authorAccount", 32);
2734
+ const executor = fromHex2(input.executor, "executor", 32);
2735
+ const key = await importSigningKey(input.deviceSecret);
2736
+ const publicKey = await derivePublicKey(input.deviceSecret);
2737
+ const commitment = await intentHash(input.method, input.argsJson);
2738
+ const nonce = u64le(input.nonce);
2739
+ const notAfter = u64le(input.notAfter);
2740
+ const preimage = await domainHash(SIGN_DOMAIN, [
2741
+ context,
2742
+ authorAccount,
2743
+ publicKey,
2744
+ executor,
2745
+ commitment,
2746
+ nonce,
2747
+ notAfter
2748
+ ]);
2749
+ const signature = new Uint8Array(
2750
+ await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
2751
+ );
2752
+ return hex(
2753
+ concat(
2754
+ context,
2755
+ authorAccount,
2756
+ publicKey,
2757
+ executor,
2758
+ commitment,
2759
+ nonce,
2760
+ notAfter,
2761
+ signature
2762
+ )
2763
+ );
2764
+ }
2765
+ async function derivePublicKey(secretHex) {
2766
+ const seed = fromHex2(secretHex, "deviceSecret", 32);
2767
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX, seed);
2768
+ const key = await crypto.subtle.importKey(
2769
+ "pkcs8",
2770
+ pkcs8,
2771
+ { name: "Ed25519" },
2772
+ true,
2773
+ ["sign"]
2774
+ );
2775
+ const jwk = await crypto.subtle.exportKey("jwk", key);
2776
+ const raw = jwk.x;
2777
+ return Uint8Array.from(
2778
+ atob(raw.replace(/-/g, "+").replace(/_/g, "/")),
2779
+ (c) => c.charCodeAt(0)
2780
+ );
2781
+ }
2782
+
2606
2783
  // src/capabilities.ts
2607
2784
  var CAPABILITIES = {
2608
2785
  CAN_CREATE_CONTEXT: 1 << 0,
@@ -2715,12 +2892,14 @@ export {
2715
2892
  createUniversalHttpClient,
2716
2893
  discoverLocalNodes,
2717
2894
  hasCap,
2895
+ intentHash,
2718
2896
  jsonCodec,
2719
2897
  localNodeUrl,
2720
2898
  nodeEndpoint,
2721
2899
  parseAuthCallback,
2722
2900
  probeNodeHealth,
2723
2901
  sameAccount,
2902
+ signWarrant,
2724
2903
  toAccountBase58,
2725
2904
  toAccountHex,
2726
2905
  withCap,