@calimero-network/mero-js 22.6.0 → 22.7.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/capabilities.d.ts +42 -0
- package/dist/capabilities.js +64 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +260 -98
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +260 -98
- package/dist/index.mjs.map +3 -3
- package/dist/relay/index.d.ts +1 -1
- package/dist/relay/relay-client.d.ts +89 -1
- package/dist/relay/relay-client.js +103 -3
- package/dist/warrant/governance-op.d.ts +47 -1
- package/dist/warrant/governance-op.js +62 -2
- package/dist/warrant/index.d.ts +2 -2
- package/dist/warrant/index.js +1 -1
- package/package.json +1 -1
package/dist/relay/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { RelayClient, IntentRefusedError } from './relay-client.js';
|
|
2
|
-
export type { RelayClientConfig, RelayDescription, IntentResult, CreationDescription, CreateContextInput, CreatedContext, GovernanceDescription, GovernInput, GovernResult, } from './relay-client.js';
|
|
2
|
+
export type { RelayClientConfig, RelayDescription, IntentResult, CreationDescription, CreateContextInput, CreatedContext, GovernanceDescription, GovernInput, GovernResult, FoundNamespaceInput, FoundedNamespace, } from './relay-client.js';
|
|
3
3
|
export { createMemoryNonceSource, createLocalStorageNonceSource } from './nonce-source.js';
|
|
4
4
|
export type { NonceSource } from './nonce-source.js';
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* monotonic per-device sequence, and one minted against the wrong executor is
|
|
28
28
|
* unspendable: the number is gone and the write never happened.
|
|
29
29
|
*/
|
|
30
|
-
import type
|
|
30
|
+
import { type GovernanceOp } from '../warrant/governance-op.js';
|
|
31
31
|
import { type Signer } from '../signer/signer.js';
|
|
32
32
|
import type { NonceSource } from './nonce-source.js';
|
|
33
33
|
/** Where and as whom to write, plus the key that consents. */
|
|
@@ -169,6 +169,61 @@ export interface GovernResult {
|
|
|
169
169
|
/** The group the op acted on, hex: the new subgroup, for a creation. */
|
|
170
170
|
groupId: string;
|
|
171
171
|
}
|
|
172
|
+
/** What {@link RelayClient.foundNamespace} founds with. */
|
|
173
|
+
export interface FoundNamespaceInput {
|
|
174
|
+
/**
|
|
175
|
+
* The salt the namespace id is derived from, hex (32 bytes). Random when
|
|
176
|
+
* absent, which is what almost every caller wants: one account founds many
|
|
177
|
+
* namespaces by varying it.
|
|
178
|
+
*/
|
|
179
|
+
salt?: string;
|
|
180
|
+
/**
|
|
181
|
+
* The relay's account, hex: the warrant's `executor`. The namespace does not
|
|
182
|
+
* exist yet, so there is nothing to ask `describeGovernance` about. Falls back
|
|
183
|
+
* to the client's configured or already-learned `executorAccount`.
|
|
184
|
+
*/
|
|
185
|
+
executorAccount?: string;
|
|
186
|
+
/**
|
|
187
|
+
* The namespace root's default capability mask (`MemberCapabilities` bits),
|
|
188
|
+
* set right after founding. A namespace is founded with core's minimal
|
|
189
|
+
* default (`CAN_JOIN_OPEN_SUBGROUPS` only), so an app that wants members to
|
|
190
|
+
* create contexts or invite, for example, names its own mask here.
|
|
191
|
+
*
|
|
192
|
+
* Set through the relay with a delegated `DefaultCapabilitiesSet`, applied as
|
|
193
|
+
* the founder, who is the namespace's admin. It cannot include
|
|
194
|
+
* `CAN_AUTHOR_ON_BEHALF` (bit 9): which relays may write for members is never
|
|
195
|
+
* decided through a relay, and such a mask is refused before anything is
|
|
196
|
+
* signed.
|
|
197
|
+
*/
|
|
198
|
+
defaultCapabilities?: number;
|
|
199
|
+
}
|
|
200
|
+
/** A namespace founded through a relay. */
|
|
201
|
+
export interface FoundedNamespace {
|
|
202
|
+
/** The new namespace's id, hex: `foundedNamespaceId(author, salt)`. */
|
|
203
|
+
namespaceId: string;
|
|
204
|
+
/**
|
|
205
|
+
* The salt it was derived with, hex. Keep it if anyone will need to be shown
|
|
206
|
+
* who founded the namespace: `(founder, salt)` reproduces the id.
|
|
207
|
+
*/
|
|
208
|
+
salt: string;
|
|
209
|
+
/**
|
|
210
|
+
* Whether the relay admitted itself as the namespace's first TEE, which also
|
|
211
|
+
* sets its default relay-mode admission policy. `false` from a relay that is
|
|
212
|
+
* not a TEE, or whose attestation failed (see `teeError`). The namespace is
|
|
213
|
+
* founded either way.
|
|
214
|
+
*/
|
|
215
|
+
teeEnabled: boolean;
|
|
216
|
+
/** Why `teeEnabled` is `false`, when the relay tried and failed. */
|
|
217
|
+
teeError?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Present only when `defaultCapabilities` was asked for: whether the mask was
|
|
220
|
+
* set. `false` leaves the namespace founded with core's default; retry with
|
|
221
|
+
* `govern({ groupId: namespaceId, op: defaultCapabilitiesSetOp(mask) })`.
|
|
222
|
+
*/
|
|
223
|
+
defaultCapabilitiesSet?: boolean;
|
|
224
|
+
/** Why `defaultCapabilitiesSet` is `false`. */
|
|
225
|
+
defaultCapabilitiesError?: string;
|
|
226
|
+
}
|
|
172
227
|
/** Where an accepted intent landed. */
|
|
173
228
|
export interface IntentResult<T = unknown> {
|
|
174
229
|
/** The context's scope root after the run — did this change anything? */
|
|
@@ -284,6 +339,39 @@ export declare class RelayClient {
|
|
|
284
339
|
* other warrant.
|
|
285
340
|
*/
|
|
286
341
|
govern(input: GovernInput): Promise<GovernResult>;
|
|
342
|
+
/**
|
|
343
|
+
* Found a namespace through the relay, with the author as its founder, owner
|
|
344
|
+
* and admin. For an account with no node, this is how it gets a namespace at
|
|
345
|
+
* all.
|
|
346
|
+
*
|
|
347
|
+
* The author signs the exact genesis (`NamespaceCreatedV2`, carrying the
|
|
348
|
+
* author's own credential and the salt) under a root-plane governance
|
|
349
|
+
* warrant scoped to the new namespace's id, which is derived from the
|
|
350
|
+
* author's account and the salt, so the relay can neither choose another id
|
|
351
|
+
* nor found it for anyone else. The relay takes an identity in the new
|
|
352
|
+
* namespace, mints its key, publishes the genesis, and is seated as the
|
|
353
|
+
* founding relay (a `Member` holding `CAN_AUTHOR_ON_BEHALF`), so later
|
|
354
|
+
* `govern`, `createContext` and `execute` calls in the namespace work
|
|
355
|
+
* through it straight away. A TEE relay then admits itself as the
|
|
356
|
+
* namespace's first TEE; `teeEnabled` reports whether it did.
|
|
357
|
+
*
|
|
358
|
+
* The executor cannot be learned from the relay first: the namespace does not
|
|
359
|
+
* exist, so `describeGovernance` has nothing to answer about. It comes from
|
|
360
|
+
* `input.executorAccount`, the configured one, or one an earlier call learned
|
|
361
|
+
* (`describe`, `createContext`, `govern`), and without any of them this
|
|
362
|
+
* throws before taking a nonce.
|
|
363
|
+
* The nonce is spent in the new namespace's ledger.
|
|
364
|
+
*
|
|
365
|
+
* With `defaultCapabilities`, the founder then sets the namespace's default
|
|
366
|
+
* capability mask through the same relay (a second warrant, a second nonce)
|
|
367
|
+
* and `defaultCapabilitiesSet` reports whether it landed. A failure there
|
|
368
|
+
* does not throw, since the namespace is already founded.
|
|
369
|
+
*
|
|
370
|
+
* Refusals (`400`/`403`) come back as {@link IntentRefusedError}; a salt
|
|
371
|
+
* that names an existing namespace is a `409`, an `HTTPError`.
|
|
372
|
+
*/
|
|
373
|
+
foundNamespace(input?: FoundNamespaceInput): Promise<FoundedNamespace>;
|
|
374
|
+
private postGovernance;
|
|
287
375
|
/**
|
|
288
376
|
* The executor a warrant must name: the one the relay answered with, which a
|
|
289
377
|
* configured account must agree with. Remembered for later calls.
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
import { signWarrant } from '../warrant/warrant.js';
|
|
31
31
|
import { signCreationWarrant } from '../warrant/creation-warrant.js';
|
|
32
32
|
import { signGovernanceWarrant } from '../warrant/governance-warrant.js';
|
|
33
|
+
import { defaultCapabilitiesSetOp, foundedNamespaceId, namespaceCreatedOp, } from '../warrant/governance-op.js';
|
|
33
34
|
import { HTTPError } from '../http-client/web-client.js';
|
|
34
|
-
import { hex } from '../crypto/internal.js';
|
|
35
|
+
import { fromHex, hex } from '../crypto/internal.js';
|
|
35
36
|
import { resolveSigner } from '../signer/signer.js';
|
|
36
37
|
/** How long a freshly minted warrant stays presentable, in seconds. */
|
|
37
38
|
const DEFAULT_TTL_SECONDS = 300;
|
|
@@ -237,8 +238,107 @@ export class RelayClient {
|
|
|
237
238
|
notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),
|
|
238
239
|
signer,
|
|
239
240
|
});
|
|
240
|
-
const
|
|
241
|
-
return { groupId:
|
|
241
|
+
const data = await this.postGovernance(input.groupId, input.op, warrant);
|
|
242
|
+
return { groupId: data.groupId };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Found a namespace through the relay, with the author as its founder, owner
|
|
246
|
+
* and admin. For an account with no node, this is how it gets a namespace at
|
|
247
|
+
* all.
|
|
248
|
+
*
|
|
249
|
+
* The author signs the exact genesis (`NamespaceCreatedV2`, carrying the
|
|
250
|
+
* author's own credential and the salt) under a root-plane governance
|
|
251
|
+
* warrant scoped to the new namespace's id, which is derived from the
|
|
252
|
+
* author's account and the salt, so the relay can neither choose another id
|
|
253
|
+
* nor found it for anyone else. The relay takes an identity in the new
|
|
254
|
+
* namespace, mints its key, publishes the genesis, and is seated as the
|
|
255
|
+
* founding relay (a `Member` holding `CAN_AUTHOR_ON_BEHALF`), so later
|
|
256
|
+
* `govern`, `createContext` and `execute` calls in the namespace work
|
|
257
|
+
* through it straight away. A TEE relay then admits itself as the
|
|
258
|
+
* namespace's first TEE; `teeEnabled` reports whether it did.
|
|
259
|
+
*
|
|
260
|
+
* The executor cannot be learned from the relay first: the namespace does not
|
|
261
|
+
* exist, so `describeGovernance` has nothing to answer about. It comes from
|
|
262
|
+
* `input.executorAccount`, the configured one, or one an earlier call learned
|
|
263
|
+
* (`describe`, `createContext`, `govern`), and without any of them this
|
|
264
|
+
* throws before taking a nonce.
|
|
265
|
+
* The nonce is spent in the new namespace's ledger.
|
|
266
|
+
*
|
|
267
|
+
* With `defaultCapabilities`, the founder then sets the namespace's default
|
|
268
|
+
* capability mask through the same relay (a second warrant, a second nonce)
|
|
269
|
+
* and `defaultCapabilitiesSet` reports whether it landed. A failure there
|
|
270
|
+
* does not throw, since the namespace is already founded.
|
|
271
|
+
*
|
|
272
|
+
* Refusals (`400`/`403`) come back as {@link IntentRefusedError}; a salt
|
|
273
|
+
* that names an existing namespace is a `409`, an `HTTPError`.
|
|
274
|
+
*/
|
|
275
|
+
async foundNamespace(input = {}) {
|
|
276
|
+
const executor = input.executorAccount
|
|
277
|
+
? this.checkedExecutor(input.executorAccount)
|
|
278
|
+
: this.executorAccount;
|
|
279
|
+
if (!executor) {
|
|
280
|
+
throw new Error("foundNamespace needs the relay's account: pass executorAccount or configure it " +
|
|
281
|
+
'(checked before signing, no nonce was spent)');
|
|
282
|
+
}
|
|
283
|
+
const salt = input.salt
|
|
284
|
+
? hex(fromHex(input.salt, 'salt', 32))
|
|
285
|
+
: hex(crypto.getRandomValues(new Uint8Array(32)));
|
|
286
|
+
const namespaceId = await foundedNamespaceId(this.config.authorAccount, salt);
|
|
287
|
+
const op = namespaceCreatedOp({
|
|
288
|
+
founder: this.config.authorAccount,
|
|
289
|
+
credential: this.config.authorProof,
|
|
290
|
+
salt,
|
|
291
|
+
});
|
|
292
|
+
// Encoded (and so validated) before anything is signed: a mask the relay
|
|
293
|
+
// would refuse must not cost the founding a nonce.
|
|
294
|
+
const capabilitiesOp = input.defaultCapabilities === undefined
|
|
295
|
+
? undefined
|
|
296
|
+
: defaultCapabilitiesSetOp(input.defaultCapabilities);
|
|
297
|
+
// Resolved before the nonce, so a misconfigured key burns none.
|
|
298
|
+
const signer = await this.authorSigner();
|
|
299
|
+
const nonce = await this.config.nonces.next();
|
|
300
|
+
const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
301
|
+
const warrant = await signGovernanceWarrant({
|
|
302
|
+
scope: namespaceId,
|
|
303
|
+
op,
|
|
304
|
+
authorAccount: this.config.authorAccount,
|
|
305
|
+
executor,
|
|
306
|
+
nonce,
|
|
307
|
+
notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),
|
|
308
|
+
signer,
|
|
309
|
+
});
|
|
310
|
+
const data = await this.postGovernance(namespaceId, op, warrant);
|
|
311
|
+
const founded = {
|
|
312
|
+
namespaceId: data.groupId,
|
|
313
|
+
salt,
|
|
314
|
+
teeEnabled: data.teeEnabled ?? false,
|
|
315
|
+
...(data.teeError ? { teeError: data.teeError } : {}),
|
|
316
|
+
};
|
|
317
|
+
if (!capabilitiesOp)
|
|
318
|
+
return founded;
|
|
319
|
+
// A second op, under its own warrant, on the namespace root: the founder is
|
|
320
|
+
// its admin and the relay was seated to act for members, so it goes through
|
|
321
|
+
// the ordinary `govern` path. The namespace exists whatever happens here, so
|
|
322
|
+
// a failure is reported rather than thrown.
|
|
323
|
+
try {
|
|
324
|
+
await this.govern({ groupId: founded.namespaceId, op: capabilitiesOp });
|
|
325
|
+
return { ...founded, defaultCapabilitiesSet: true };
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
return {
|
|
329
|
+
...founded,
|
|
330
|
+
defaultCapabilitiesSet: false,
|
|
331
|
+
defaultCapabilitiesError: err instanceof Error ? err.message : String(err),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async postGovernance(groupId, op, warrant) {
|
|
336
|
+
const body = await this.json('POST', `/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`, {
|
|
337
|
+
warrant,
|
|
338
|
+
authorProof: this.config.authorProof,
|
|
339
|
+
op: hex(op.bytes),
|
|
340
|
+
});
|
|
341
|
+
return body.data;
|
|
242
342
|
}
|
|
243
343
|
/**
|
|
244
344
|
* The executor a warrant must name: the one the relay answered with, which a
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Sources in core, for the next person to check a discriminant:
|
|
17
17
|
* `crates/governance-types/src/lib.rs` (`GroupOp`, `RootOp`),
|
|
18
|
-
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
18
|
+
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
19
|
+
* `crates/account/src/namespace_id.rs` (`founded_namespace_id`), and the vectors in
|
|
19
20
|
* `crates/governance-types/src/tests.rs` (`delegated_governance_op_vectors_are_stable`).
|
|
20
21
|
*/
|
|
21
22
|
/**
|
|
@@ -51,6 +52,15 @@ export declare function memberRemovedOp(member: string): GovernanceOp;
|
|
|
51
52
|
export declare function memberLeftOp(member: string): GovernanceOp;
|
|
52
53
|
/** Set `member`'s role (an account, hex). */
|
|
53
54
|
export declare function memberRoleSetOp(member: string, memberRole: GovernanceMemberRole): GovernanceOp;
|
|
55
|
+
/**
|
|
56
|
+
* Set the group's default capability mask (`MemberCapabilities` bits, a `u32`),
|
|
57
|
+
* the capabilities every member holds without a per-member grant. Needs admin.
|
|
58
|
+
*
|
|
59
|
+
* The relay refuses any change to `CAN_AUTHOR_ON_BEHALF` (bit 9) through this
|
|
60
|
+
* path, and no namespace starts with it in its default mask, so a mask that
|
|
61
|
+
* sets it is refused here rather than burning a nonce on a certain 403.
|
|
62
|
+
*/
|
|
63
|
+
export declare function defaultCapabilitiesSetOp(capabilities: number): GovernanceOp;
|
|
54
64
|
/** What a subgroup is created with. */
|
|
55
65
|
export interface GroupCreatedInput {
|
|
56
66
|
/** The new subgroup's id, hex (32 bytes). Choose it fresh; a taken id is refused. */
|
|
@@ -79,3 +89,39 @@ export declare function groupReparentedOp(childGroupId: string, newParentId: str
|
|
|
79
89
|
* empty, for the relay to enumerate.
|
|
80
90
|
*/
|
|
81
91
|
export declare function groupDeletedOp(rootGroupId: string): GovernanceOp;
|
|
92
|
+
/**
|
|
93
|
+
* The id of the namespace `founder` founds with `salt`: core's
|
|
94
|
+
* `founded_namespace_id`, `domain_hash("calimero.namespace.id.v1", [founder, salt])`.
|
|
95
|
+
*
|
|
96
|
+
* Anyone holding the pair can recompute it, and only the founder's account
|
|
97
|
+
* hashes to it, which is why a relay founding a namespace for a member cannot
|
|
98
|
+
* choose the id: the member computes it and signs it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function foundedNamespaceId(founder: string, salt: string): Promise<string>;
|
|
101
|
+
/** What a namespace is founded with. */
|
|
102
|
+
export interface NamespaceCreatedInput {
|
|
103
|
+
/** The founding account, hex: the **author's**. It becomes founder, owner and admin. */
|
|
104
|
+
founder: string;
|
|
105
|
+
/**
|
|
106
|
+
* The founder's `AccountProof<DeviceCert>`, hex: the credential the author
|
|
107
|
+
* already presents as `authorProof`. Its account must be `founder` and its
|
|
108
|
+
* device key the one that signs the warrant; the genesis apply checks both
|
|
109
|
+
* and binds the founder's device from it.
|
|
110
|
+
*/
|
|
111
|
+
credential: string;
|
|
112
|
+
/**
|
|
113
|
+
* The salt the namespace id is derived with, hex (32 bytes). Choose it at
|
|
114
|
+
* random; {@link foundedNamespaceId} gives the id it yields.
|
|
115
|
+
*/
|
|
116
|
+
salt: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Found a namespace: core's `RootOp::NamespaceCreatedV2` genesis, a root op
|
|
120
|
+
* published on the new namespace's own log.
|
|
121
|
+
*
|
|
122
|
+
* Its delegable form is the op itself, nothing cleared, so the author signs the
|
|
123
|
+
* exact genesis. Borsh is `[9] || founder || credential || salt`: the credential
|
|
124
|
+
* is a nested struct, not a length-prefixed byte string, so it is written
|
|
125
|
+
* inline. The warrant's scope must be {@link foundedNamespaceId}`(founder, salt)`.
|
|
126
|
+
*/
|
|
127
|
+
export declare function namespaceCreatedOp(input: NamespaceCreatedInput): GovernanceOp;
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Sources in core, for the next person to check a discriminant:
|
|
17
17
|
* `crates/governance-types/src/lib.rs` (`GroupOp`, `RootOp`),
|
|
18
|
-
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
18
|
+
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
19
|
+
* `crates/account/src/namespace_id.rs` (`founded_namespace_id`), and the vectors in
|
|
19
20
|
* `crates/governance-types/src/tests.rs` (`delegated_governance_op_vectors_are_stable`).
|
|
20
21
|
*/
|
|
21
|
-
import {
|
|
22
|
+
import { CAPABILITIES, hasCap } from '../capabilities.js';
|
|
23
|
+
import { concat, domainHash, fromHex, fromHexUnsized, hex, u32le } from '../crypto/internal.js';
|
|
22
24
|
/**
|
|
23
25
|
* `GroupMemberRole` discriminants. The two TEE roles (3 and 4) are minted only
|
|
24
26
|
* by attestation admission and never by a member's op, so they are not offered.
|
|
@@ -34,13 +36,17 @@ const GROUP_OP = {
|
|
|
34
36
|
MemberRemoved: 2,
|
|
35
37
|
MemberLeft: 3,
|
|
36
38
|
MemberRoleSet: 4,
|
|
39
|
+
DefaultCapabilitiesSet: 6,
|
|
37
40
|
};
|
|
38
41
|
/** `RootOp` discriminants, by position in core's enum. */
|
|
39
42
|
const ROOT_OP = {
|
|
40
43
|
GroupCreated: 0,
|
|
41
44
|
GroupReparented: 1,
|
|
42
45
|
GroupDeleted: 2,
|
|
46
|
+
NamespaceCreatedV2: 9,
|
|
43
47
|
};
|
|
48
|
+
/** core's `NAMESPACE_ID_DOMAIN`, which a founded namespace id is hashed under. */
|
|
49
|
+
const NAMESPACE_ID_DOMAIN = new TextEncoder().encode('calimero.namespace.id.v1');
|
|
44
50
|
/** A post-state hash left for the relay to compute: 32 zero bytes. */
|
|
45
51
|
const CLEARED_HASH = new Uint8Array(32);
|
|
46
52
|
/** An empty borsh `Vec`: a zero `u32` count. */
|
|
@@ -94,6 +100,27 @@ export function memberRoleSetOp(member, memberRole) {
|
|
|
94
100
|
bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, 'member', 32), role(memberRole)),
|
|
95
101
|
};
|
|
96
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Set the group's default capability mask (`MemberCapabilities` bits, a `u32`),
|
|
105
|
+
* the capabilities every member holds without a per-member grant. Needs admin.
|
|
106
|
+
*
|
|
107
|
+
* The relay refuses any change to `CAN_AUTHOR_ON_BEHALF` (bit 9) through this
|
|
108
|
+
* path, and no namespace starts with it in its default mask, so a mask that
|
|
109
|
+
* sets it is refused here rather than burning a nonce on a certain 403.
|
|
110
|
+
*/
|
|
111
|
+
export function defaultCapabilitiesSetOp(capabilities) {
|
|
112
|
+
if (!Number.isInteger(capabilities) || capabilities < 0 || capabilities > 4294967295) {
|
|
113
|
+
throw new Error(`capabilities must be a u32 bit mask, got ${String(capabilities)}`);
|
|
114
|
+
}
|
|
115
|
+
// Never changed through a relay.
|
|
116
|
+
if (hasCap(capabilities, CAPABILITIES.CAN_AUTHOR_ON_BEHALF)) {
|
|
117
|
+
throw new Error('capabilities may not include CAN_AUTHOR_ON_BEHALF (512): a relay never carries a change to it');
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
kind: 'group',
|
|
121
|
+
bytes: concat(tag(GROUP_OP.DefaultCapabilitiesSet), u32le(capabilities)),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
97
124
|
/**
|
|
98
125
|
* Create a subgroup under `parentId`, a root op published on the namespace.
|
|
99
126
|
*
|
|
@@ -123,3 +150,36 @@ export function groupDeletedOp(rootGroupId) {
|
|
|
123
150
|
bytes: concat(tag(ROOT_OP.GroupDeleted), fromHex(rootGroupId, 'rootGroupId', 32), EMPTY_VEC, EMPTY_VEC),
|
|
124
151
|
};
|
|
125
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* The id of the namespace `founder` founds with `salt`: core's
|
|
155
|
+
* `founded_namespace_id`, `domain_hash("calimero.namespace.id.v1", [founder, salt])`.
|
|
156
|
+
*
|
|
157
|
+
* Anyone holding the pair can recompute it, and only the founder's account
|
|
158
|
+
* hashes to it, which is why a relay founding a namespace for a member cannot
|
|
159
|
+
* choose the id: the member computes it and signs it.
|
|
160
|
+
*/
|
|
161
|
+
export async function foundedNamespaceId(founder, salt) {
|
|
162
|
+
return hex(await domainHash(NAMESPACE_ID_DOMAIN, [
|
|
163
|
+
fromHex(founder, 'founder', 32),
|
|
164
|
+
fromHex(salt, 'salt', 32),
|
|
165
|
+
]));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Found a namespace: core's `RootOp::NamespaceCreatedV2` genesis, a root op
|
|
169
|
+
* published on the new namespace's own log.
|
|
170
|
+
*
|
|
171
|
+
* Its delegable form is the op itself, nothing cleared, so the author signs the
|
|
172
|
+
* exact genesis. Borsh is `[9] || founder || credential || salt`: the credential
|
|
173
|
+
* is a nested struct, not a length-prefixed byte string, so it is written
|
|
174
|
+
* inline. The warrant's scope must be {@link foundedNamespaceId}`(founder, salt)`.
|
|
175
|
+
*/
|
|
176
|
+
export function namespaceCreatedOp(input) {
|
|
177
|
+
const credential = fromHexUnsized(input.credential, 'credential');
|
|
178
|
+
if (credential.length === 0) {
|
|
179
|
+
throw new Error('credential must be the founder\'s AccountProof<DeviceCert>, got nothing');
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
kind: 'root',
|
|
183
|
+
bytes: concat(tag(ROOT_OP.NamespaceCreatedV2), fromHex(input.founder, 'founder', 32), credential, fromHex(input.salt, 'salt', 32)),
|
|
184
|
+
};
|
|
185
|
+
}
|
package/dist/warrant/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { signCreationWarrant, parseCreationWarrant, creationInitHash, } from './
|
|
|
4
4
|
export type { CreationWarrantInput, CreationWarrantFields, SignedCreationWarrant, } from './creation-warrant.js';
|
|
5
5
|
export { signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, } from './governance-warrant.js';
|
|
6
6
|
export type { GovernanceWarrantInput, GovernanceWarrantFields, } from './governance-warrant.js';
|
|
7
|
-
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, } from './governance-op.js';
|
|
8
|
-
export type { GovernanceOp, GovernanceOpKind, GovernanceMemberRole, GroupCreatedInput, } from './governance-op.js';
|
|
7
|
+
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from './governance-op.js';
|
|
8
|
+
export type { GovernanceOp, GovernanceOpKind, GovernanceMemberRole, GroupCreatedInput, NamespaceCreatedInput, } from './governance-op.js';
|
package/dist/warrant/index.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
export { signWarrant, intentHash } from './warrant.js';
|
|
3
3
|
export { signCreationWarrant, parseCreationWarrant, creationInitHash, } from './creation-warrant.js';
|
|
4
4
|
export { signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, } from './governance-warrant.js';
|
|
5
|
-
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, } from './governance-op.js';
|
|
5
|
+
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from './governance-op.js';
|