@calimero-network/mero-js 13.2.5 → 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/admin-api/admin-client.d.ts +28 -1
- package/dist/admin-api/admin-client.js +29 -0
- package/dist/admin-api/admin-types.d.ts +55 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +215 -3
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +215 -3
- package/dist/index.mjs.map +4 -4
- package/dist/warrant/index.d.ts +2 -0
- package/dist/warrant/index.js +2 -0
- package/dist/warrant/warrant.d.ts +77 -0
- package/dist/warrant/warrant.js +196 -0
- package/package.json +1 -1
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
|
@@ -1096,6 +1096,40 @@ var AdminApiClient = class {
|
|
|
1096
1096
|
const node = await this.getNodeIdentity();
|
|
1097
1097
|
return { namespaceId, publicKey: node.publicKey, account: node.accountId };
|
|
1098
1098
|
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Ask this node to run one method on a member's behalf, under a warrant that
|
|
1101
|
+
* member signed.
|
|
1102
|
+
*
|
|
1103
|
+
* The node executes and signs the envelope with its own key; the change is
|
|
1104
|
+
* attributed to the **author**. Both halves travel together, so every peer
|
|
1105
|
+
* re-checks that the member consented instead of trusting the relay.
|
|
1106
|
+
*
|
|
1107
|
+
* Three things have to be true first, and none is implied by the others:
|
|
1108
|
+
*
|
|
1109
|
+
* - the author's **account** is a member of the group owning the context —
|
|
1110
|
+
* their device joins nothing and is in no group's binding rows, which is
|
|
1111
|
+
* exactly the case a device certificate covers;
|
|
1112
|
+
* - this node holds `CAN_AUTHOR_ON_BEHALF` on that group, which is implied by
|
|
1113
|
+
* neither membership nor admin;
|
|
1114
|
+
* - the warrant has not been spent. It is single-use: presenting a valid one
|
|
1115
|
+
* twice is refused, because the signature stays valid forever and replay is
|
|
1116
|
+
* not forgery.
|
|
1117
|
+
*
|
|
1118
|
+
* A refusal comes back as a `403` carrying the reason — "an admin must grant
|
|
1119
|
+
* CAN_AUTHOR_ON_BEHALF to …", or that the nonce is already spent — so a caller
|
|
1120
|
+
* can tell which precondition failed rather than only that one did.
|
|
1121
|
+
*
|
|
1122
|
+
* Mint the warrant with {@link signWarrant}; see
|
|
1123
|
+
* {@link PerformIntentRequest.warrant}.
|
|
1124
|
+
*/
|
|
1125
|
+
async performIntent(contextId, request) {
|
|
1126
|
+
return unwrap(
|
|
1127
|
+
await this.httpClient.post(
|
|
1128
|
+
`/admin-api/contexts/${contextId}/intents`,
|
|
1129
|
+
request
|
|
1130
|
+
)
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1099
1133
|
async listNamespacesForApplication(applicationId) {
|
|
1100
1134
|
return unwrap(await this.httpClient.get(`/admin-api/namespaces/for-application/${applicationId}`));
|
|
1101
1135
|
}
|
|
@@ -2462,10 +2496,10 @@ function toHex(bytes) {
|
|
|
2462
2496
|
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
2463
2497
|
return out;
|
|
2464
2498
|
}
|
|
2465
|
-
function fromHex(
|
|
2466
|
-
const bytes = new Uint8Array(
|
|
2499
|
+
function fromHex(hex2) {
|
|
2500
|
+
const bytes = new Uint8Array(hex2.length / 2);
|
|
2467
2501
|
for (let i = 0; i < bytes.length; i++) {
|
|
2468
|
-
bytes[i] = parseInt(
|
|
2502
|
+
bytes[i] = parseInt(hex2.slice(i * 2, i * 2 + 2), 16);
|
|
2469
2503
|
}
|
|
2470
2504
|
return bytes;
|
|
2471
2505
|
}
|
|
@@ -2570,6 +2604,182 @@ var CloudClient = class {
|
|
|
2570
2604
|
}
|
|
2571
2605
|
};
|
|
2572
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
|
+
|
|
2573
2783
|
// src/capabilities.ts
|
|
2574
2784
|
var CAPABILITIES = {
|
|
2575
2785
|
CAN_CREATE_CONTEXT: 1 << 0,
|
|
@@ -2682,12 +2892,14 @@ export {
|
|
|
2682
2892
|
createUniversalHttpClient,
|
|
2683
2893
|
discoverLocalNodes,
|
|
2684
2894
|
hasCap,
|
|
2895
|
+
intentHash,
|
|
2685
2896
|
jsonCodec,
|
|
2686
2897
|
localNodeUrl,
|
|
2687
2898
|
nodeEndpoint,
|
|
2688
2899
|
parseAuthCallback,
|
|
2689
2900
|
probeNodeHealth,
|
|
2690
2901
|
sameAccount,
|
|
2902
|
+
signWarrant,
|
|
2691
2903
|
toAccountBase58,
|
|
2692
2904
|
toAccountHex,
|
|
2693
2905
|
withCap,
|