@calimero-network/mero-js 18.2.0 → 19.0.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/README.md +47 -0
- package/dist/admin-api/admin-client.d.ts +22 -1
- package/dist/admin-api/admin-client.js +27 -1
- package/dist/admin-api/admin-types.d.ts +34 -0
- package/dist/capabilities.d.ts +14 -2
- package/dist/capabilities.js +15 -3
- package/dist/cloud/cloud-client.d.ts +264 -0
- package/dist/cloud/cloud-client.js +319 -1
- package/dist/cloud/connect.d.ts +94 -0
- package/dist/cloud/connect.js +150 -0
- package/dist/cloud/index.d.ts +3 -1
- package/dist/cloud/index.js +1 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +664 -30
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.mjs +664 -30
- package/dist/index.mjs.map +3 -3
- package/dist/mero-js.d.ts +28 -0
- package/dist/mero-js.js +17 -0
- package/dist/relay/index.d.ts +4 -0
- package/dist/relay/index.js +2 -0
- package/dist/relay/nonce-source.d.ts +60 -0
- package/dist/relay/nonce-source.js +95 -0
- package/dist/relay/relay-client.d.ts +141 -0
- package/dist/relay/relay-client.js +157 -0
- package/dist/warrant/warrant.d.ts +89 -10
- package/dist/warrant/warrant.js +109 -12
- package/package.json +2 -1
package/dist/warrant/warrant.js
CHANGED
|
@@ -7,11 +7,17 @@
|
|
|
7
7
|
* so "a member with no node" in practice meant "a member with a node it is not
|
|
8
8
|
* allowed to use for anything else".
|
|
9
9
|
*
|
|
10
|
-
* **No dependencies, and that is not incidental.** The
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* **No dependencies, and that is not incidental.** The primitives are all built
|
|
11
|
+
* in: `crypto.subtle.digest('SHA-256')` for the hash, `crypto.subtle` Ed25519
|
|
12
|
+
* for the signature, and `DataView` for the integers.
|
|
13
|
+
*
|
|
14
|
+
* Warrant v2 (core#3933) gave up the fixed-width encoding that used to make this
|
|
15
|
+
* pure concatenation: `method` travels in the clear as a string and each cited
|
|
16
|
+
* head list is a vector, so three borsh `u32` counts now sit inside the bytes.
|
|
17
|
+
* Two widths to keep straight, and they are not the same: the counts **inside
|
|
18
|
+
* the encoding** are `u32`, while the counts the **signing preimage** hashes are
|
|
19
|
+
* `u64`. Same numbers, different serialization — reusing one for the other
|
|
20
|
+
* produces a warrant that is well-formed and verifies nowhere.
|
|
15
21
|
*
|
|
16
22
|
* **The byte contract is pinned in core**, at
|
|
17
23
|
* `crates/account/src/tests/warrant_wire_fixture.rs`. The domain strings below
|
|
@@ -21,9 +27,16 @@
|
|
|
21
27
|
* this producing well-formed warrants whose signatures verify nowhere, surfacing
|
|
22
28
|
* at a relay as a 403: an authorization refusal, nowhere near the cause.
|
|
23
29
|
*/
|
|
24
|
-
import { concat, derivePublicKey, domainHash, fromHex, hex, importSigningKey, u64le, } from '../crypto/internal.js';
|
|
25
|
-
const SIGN_DOMAIN = new TextEncoder().encode('calimero.warrant.
|
|
30
|
+
import { concat, derivePublicKey, domainHash, fromHex, hex, importSigningKey, u32le, u64le, } from '../crypto/internal.js';
|
|
31
|
+
const SIGN_DOMAIN = new TextEncoder().encode('calimero.warrant.v2');
|
|
26
32
|
const INTENT_DOMAIN = new TextEncoder().encode('calimero.warrant.intent.v1');
|
|
33
|
+
/**
|
|
34
|
+
* core's `MAX_WARRANT_CITED_HEADS`. A node refuses more than this before doing
|
|
35
|
+
* any Ed25519 work, so minting past it produces a warrant nothing will spend.
|
|
36
|
+
*/
|
|
37
|
+
const MAX_CITED_HEADS = 64;
|
|
38
|
+
/** 32 zero bytes — the `appVersion` default. See {@link WarrantInput.appVersion}. */
|
|
39
|
+
const UNPINNED_APP = '00'.repeat(32);
|
|
27
40
|
/**
|
|
28
41
|
* The commitment a warrant carries in place of the intent itself.
|
|
29
42
|
*
|
|
@@ -37,29 +50,113 @@ export async function intentHash(method, argsJson) {
|
|
|
37
50
|
/**
|
|
38
51
|
* Sign a warrant and return it hex-encoded, ready for `performIntent`.
|
|
39
52
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* was signed.
|
|
53
|
+
* Returns the encoding rather than an object because the signature covers
|
|
54
|
+
* exactly these bytes: a caller that rebuilt the fields from JSON would have a
|
|
55
|
+
* second spelling able to disagree with what was signed.
|
|
44
56
|
*/
|
|
45
57
|
export async function signWarrant(input) {
|
|
46
58
|
const context = fromHex(input.context, 'context', 32);
|
|
47
59
|
const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);
|
|
48
60
|
const executor = fromHex(input.executor, 'executor', 32);
|
|
61
|
+
const appVersion = fromHex(input.appVersion ?? UNPINNED_APP, 'appVersion', 32);
|
|
62
|
+
const accountHeads = citedHeads(input.accountHeads, 'accountHeads');
|
|
63
|
+
const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');
|
|
49
64
|
const key = await importSigningKey(input.deviceSecret);
|
|
50
65
|
const publicKey = await derivePublicKey(input.deviceSecret);
|
|
66
|
+
const method = new TextEncoder().encode(input.method);
|
|
51
67
|
const commitment = await intentHash(input.method, input.argsJson);
|
|
52
68
|
const nonce = u64le(input.nonce);
|
|
53
69
|
const notAfter = u64le(input.notAfter);
|
|
70
|
+
// Each head list is preceded by its own count. `domainHash` length-prefixes
|
|
71
|
+
// every part it is given, so the heads are individually unambiguous — but the
|
|
72
|
+
// two lists are adjacent, so without the counts `accountHeads = [a, b],
|
|
73
|
+
// governanceFloor = []` hashes identically to `[a], [b]`, and a relay could
|
|
74
|
+
// relabel which plane a head was cited from. u64 HERE; u32 in the encoding.
|
|
54
75
|
const preimage = await domainHash(SIGN_DOMAIN, [
|
|
55
76
|
context,
|
|
56
77
|
authorAccount,
|
|
57
78
|
publicKey,
|
|
58
79
|
executor,
|
|
80
|
+
appVersion,
|
|
81
|
+
method,
|
|
59
82
|
commitment,
|
|
83
|
+
u64le(accountHeads.length),
|
|
84
|
+
...accountHeads,
|
|
85
|
+
u64le(governanceFloor.length),
|
|
86
|
+
...governanceFloor,
|
|
60
87
|
nonce,
|
|
61
88
|
notAfter,
|
|
62
89
|
]);
|
|
63
90
|
const signature = new Uint8Array(await crypto.subtle.sign({ name: 'Ed25519' }, key, preimage));
|
|
64
|
-
|
|
91
|
+
// Borsh: a `String` is a u32 length then its UTF-8 bytes; a `Vec<[u8; 32]>` is
|
|
92
|
+
// a u32 count then the elements. u32 HERE; u64 in the preimage above.
|
|
93
|
+
return hex(concat(context, authorAccount, publicKey, executor, appVersion, u32le(method.length), method, commitment, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, nonce, notAfter, signature));
|
|
94
|
+
}
|
|
95
|
+
/** Decode a cited-head list, refusing one longer than a node will accept. */
|
|
96
|
+
function citedHeads(heads, label) {
|
|
97
|
+
const list = heads ?? [];
|
|
98
|
+
if (list.length > MAX_CITED_HEADS) {
|
|
99
|
+
throw new Error(`${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`);
|
|
100
|
+
}
|
|
101
|
+
return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Decode a hex warrant back into its fields.
|
|
105
|
+
*
|
|
106
|
+
* Exists because v2's offsets are no longer constants: `method` is a string and
|
|
107
|
+
* the head lists are vectors, so everything after `appVersion` moves with the
|
|
108
|
+
* method's length. Anything reading a warrant by fixed offset — a test, a client
|
|
109
|
+
* inspecting one it was handed — silently reads the wrong field instead of
|
|
110
|
+
* failing, which is how the three call sites that did so passed while comparing
|
|
111
|
+
* the method's length prefix to a nonce.
|
|
112
|
+
*
|
|
113
|
+
* One copy of the layout, beside the encoder that produces it, so the two cannot
|
|
114
|
+
* drift. Internal: not exported from the package root, because a client that
|
|
115
|
+
* needs a warrant's fields has the input it signed.
|
|
116
|
+
*/
|
|
117
|
+
export function parseWarrant(warrant) {
|
|
118
|
+
const at = (start, bytes) => warrant.slice(start * 2, (start + bytes) * 2);
|
|
119
|
+
// A borsh u32 is little-endian, so the hex pairs read back to front.
|
|
120
|
+
const u32At = (start) => Number.parseInt((at(start, 4).match(/../g) ?? []).reverse().join(''), 16);
|
|
121
|
+
let o = 0;
|
|
122
|
+
const take = (bytes) => {
|
|
123
|
+
const field = at(o, bytes);
|
|
124
|
+
o += bytes;
|
|
125
|
+
return field;
|
|
126
|
+
};
|
|
127
|
+
const context = take(32);
|
|
128
|
+
const authorAccount = take(32);
|
|
129
|
+
const deviceKey = take(32);
|
|
130
|
+
const executor = take(32);
|
|
131
|
+
const appVersion = take(32);
|
|
132
|
+
const methodLen = u32At(o);
|
|
133
|
+
o += 4;
|
|
134
|
+
const method = take(methodLen);
|
|
135
|
+
const intentHashField = take(32);
|
|
136
|
+
const accountCount = u32At(o);
|
|
137
|
+
o += 4;
|
|
138
|
+
const accountHeads = Array.from({ length: accountCount }, () => take(32));
|
|
139
|
+
const governanceCount = u32At(o);
|
|
140
|
+
o += 4;
|
|
141
|
+
const governanceFloor = Array.from({ length: governanceCount }, () => take(32));
|
|
142
|
+
const nonce = take(8);
|
|
143
|
+
const notAfter = take(8);
|
|
144
|
+
const signature = take(64);
|
|
145
|
+
if (o * 2 !== warrant.length) {
|
|
146
|
+
throw new Error(`warrant is ${warrant.length / 2} bytes but its fields account for ${o}`);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
context,
|
|
150
|
+
authorAccount,
|
|
151
|
+
deviceKey,
|
|
152
|
+
executor,
|
|
153
|
+
appVersion,
|
|
154
|
+
method,
|
|
155
|
+
intentHash: intentHashField,
|
|
156
|
+
accountHeads,
|
|
157
|
+
governanceFloor,
|
|
158
|
+
nonce,
|
|
159
|
+
notAfter,
|
|
160
|
+
signature,
|
|
161
|
+
};
|
|
65
162
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calimero-network/mero-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.0",
|
|
4
4
|
"description": "Pure JavaScript SDK for Calimero",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"typecheck": "tsc --noEmit",
|
|
51
51
|
"typecheck:contract": "tsc --noEmit -p tsconfig.contract.json",
|
|
52
52
|
"typecheck:consumer": "tsc --noEmit -p tsconfig.consumer.json",
|
|
53
|
+
"typecheck:e2e": "tsc --noEmit -p tsconfig.e2e.json",
|
|
53
54
|
"test:contract": "vitest run --config vitest.config.ts src/__contract__",
|
|
54
55
|
"tsc": "tsc -b",
|
|
55
56
|
"clean": "pnpm rimraf dist",
|