@calimero-network/mero-js 22.4.0 → 22.5.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.
@@ -17,11 +17,15 @@ import type { SignedGroupOpenInvitation } from '../admin-api/admin-types.js';
17
17
  /**
18
18
  * Core's `SIGNED_NAMESPACE_OP_SCHEMA_VERSION`. A node refuses any other value.
19
19
  *
20
- * 12: core gained the `RelayTee` role and the TEE admission policy's
21
- * replica/relay mode. No layout this module signs changed; the version moves
22
- * with core's so a v11 node and a v12 node never share a namespace.
20
+ * 15: core went to 13 (TEE rows locked to the TEE roles, a TEE's relay role
21
+ * read at its namespace root row, inherited read-only roles read-only in Open
22
+ * subgroups), then 14 (`GroupOp::ContextRegisteredOnBehalf`) and 15
23
+ * (`GroupOp::OnBehalf` / `RootOp::OnBehalf`) for delegated context creation and
24
+ * governance through a relay. None of these changes a layout this module
25
+ * signs; the version moves with core's so nodes on different versions never
26
+ * share a namespace.
23
27
  */
24
- export declare const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 12;
28
+ export declare const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 15;
25
29
  /**
26
30
  * Borsh-encode a `SignedGroupOpenInvitation`.
27
31
  *
@@ -2,11 +2,15 @@ import { concat, derivePublicKey, fromHex, hex, importSigningKey, u32le, u64le,
2
2
  /**
3
3
  * Core's `SIGNED_NAMESPACE_OP_SCHEMA_VERSION`. A node refuses any other value.
4
4
  *
5
- * 12: core gained the `RelayTee` role and the TEE admission policy's
6
- * replica/relay mode. No layout this module signs changed; the version moves
7
- * with core's so a v11 node and a v12 node never share a namespace.
5
+ * 15: core went to 13 (TEE rows locked to the TEE roles, a TEE's relay role
6
+ * read at its namespace root row, inherited read-only roles read-only in Open
7
+ * subgroups), then 14 (`GroupOp::ContextRegisteredOnBehalf`) and 15
8
+ * (`GroupOp::OnBehalf` / `RootOp::OnBehalf`) for delegated context creation and
9
+ * governance through a relay. None of these changes a layout this module
10
+ * signs; the version moves with core's so nodes on different versions never
11
+ * share a namespace.
8
12
  */
9
- export const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 12;
13
+ export const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 15;
10
14
  /** Domain prefixed to the signable bytes; core's `NAMESPACE_GOVERNANCE_SIGN_DOMAIN`. */
11
15
  const NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode('calimero.namespace.v1');
12
16
  /** `NamespaceOp::Root` is the first variant. */
@@ -1,4 +1,4 @@
1
1
  export { RelayClient, IntentRefusedError } from './relay-client.js';
2
- export type { RelayClientConfig, RelayDescription, IntentResult, CreationDescription, CreateContextInput, CreatedContext, } from './relay-client.js';
2
+ export type { RelayClientConfig, RelayDescription, IntentResult, CreationDescription, CreateContextInput, CreatedContext, GovernanceDescription, GovernInput, GovernResult, } from './relay-client.js';
3
3
  export { createMemoryNonceSource, createLocalStorageNonceSource } from './nonce-source.js';
4
4
  export type { NonceSource } from './nonce-source.js';
@@ -27,6 +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 { GovernanceOp } from '../warrant/governance-op.js';
30
31
  import type { NonceSource } from './nonce-source.js';
31
32
  /** Where and as whom to write, plus the key that consents. */
32
33
  export interface RelayClientConfig {
@@ -129,6 +130,34 @@ export interface CreatedContext {
129
130
  /** The relay's identity in the new context, hex. */
130
131
  memberPublicKey: string;
131
132
  }
133
+ /** What a relay says about publishing governance ops for members of one group. */
134
+ export interface GovernanceDescription {
135
+ /** The account a governance warrant must name as `executor`, hex. */
136
+ executorAccount: string;
137
+ /** The group asked about, hex. */
138
+ groupId: string;
139
+ /**
140
+ * Whether the relay may act for members here (a `RelayTee` role or
141
+ * `CAN_AUTHOR_ON_BEHALF`). It needs no right to the op itself: that is
142
+ * checked on the author, as the op is applied as the author.
143
+ */
144
+ canActOnBehalf: boolean;
145
+ }
146
+ /** What {@link RelayClient.govern} publishes. */
147
+ export interface GovernInput {
148
+ /**
149
+ * Where the op is published, hex: the group itself for a group op, the
150
+ * **namespace** for a root op (creating, moving or deleting a subgroup).
151
+ */
152
+ groupId: string;
153
+ /** The op, from one of the encoders (`memberAddedOp`, `groupCreatedOp`, ...). */
154
+ op: GovernanceOp;
155
+ }
156
+ /** Where a delegated governance op landed. */
157
+ export interface GovernResult {
158
+ /** The group the op acted on, hex: the new subgroup, for a creation. */
159
+ groupId: string;
160
+ }
132
161
  /** Where an accepted intent landed. */
133
162
  export interface IntentResult<T = unknown> {
134
163
  /** The context's scope root after the run — did this change anything? */
@@ -216,5 +245,32 @@ export declare class RelayClient {
216
245
  * `403 sealed_route_unguarded`.
217
246
  */
218
247
  createContext(input: CreateContextInput): Promise<CreatedContext>;
248
+ /**
249
+ * Ask the relay whether it may publish governance ops for members of
250
+ * `groupId`, and which account a warrant must name.
251
+ */
252
+ describeGovernance(groupId: string): Promise<GovernanceDescription>;
253
+ /**
254
+ * Publish one governance op through the relay, as the author.
255
+ *
256
+ * The warrant commits to the op's exact bytes and is scoped to `groupId`, so
257
+ * the relay can neither change what the op does nor spend it elsewhere. Every
258
+ * peer applies the op as the author, so the author's own rights decide
259
+ * (`MANAGE_MEMBERS` to add a member, `CAN_CREATE_SUBGROUP` to create a
260
+ * subgroup, and so on); a lack of them comes back as an
261
+ * {@link IntentRefusedError} from the relay.
262
+ *
263
+ * Always asks {@link describeGovernance} first and refuses *before* taking a
264
+ * nonce when the relay has no standing, for the reason `createContext` does.
265
+ * The nonce comes from the configured {@link NonceSource}; it is spent in a
266
+ * per-group ledger, and a monotonic source is correct for it as for every
267
+ * other warrant.
268
+ */
269
+ govern(input: GovernInput): Promise<GovernResult>;
270
+ /**
271
+ * The executor a warrant must name: the one the relay answered with, which a
272
+ * configured account must agree with. Remembered for later calls.
273
+ */
274
+ private checkedExecutor;
219
275
  private json;
220
276
  }
@@ -29,7 +29,9 @@
29
29
  */
30
30
  import { signWarrant } from '../warrant/warrant.js';
31
31
  import { signCreationWarrant } from '../warrant/creation-warrant.js';
32
+ import { signGovernanceWarrant } from '../warrant/governance-warrant.js';
32
33
  import { HTTPError } from '../http-client/web-client.js';
34
+ import { hex } from '../crypto/internal.js';
33
35
  /** How long a freshly minted warrant stays presentable, in seconds. */
34
36
  const DEFAULT_TTL_SECONDS = 300;
35
37
  /**
@@ -153,13 +155,7 @@ export class RelayClient {
153
155
  `${described.groupId}; it needs CAN_CREATE_CONTEXT or admin ` +
154
156
  '(checked before signing — no nonce was spent)', false, 403);
155
157
  }
156
- const configured = this.config.executorAccount;
157
- if (configured && configured.toLowerCase() !== described.executorAccount.toLowerCase()) {
158
- throw new Error(`configured executorAccount ${configured} is not the relay's account ` +
159
- `${described.executorAccount}; a warrant naming it would be unspendable`);
160
- }
161
- const executor = described.executorAccount;
162
- this.executorAccount = executor;
158
+ const executor = this.checkedExecutor(described.executorAccount);
163
159
  const initArgs = input.initArgs ?? {};
164
160
  const nonce = await this.config.nonces.next();
165
161
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
@@ -183,6 +179,65 @@ export class RelayClient {
183
179
  memberPublicKey: body.data.memberPublicKey,
184
180
  };
185
181
  }
182
+ /**
183
+ * Ask the relay whether it may publish governance ops for members of
184
+ * `groupId`, and which account a warrant must name.
185
+ */
186
+ async describeGovernance(groupId) {
187
+ const body = await this.json('GET', `/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`);
188
+ return body.data;
189
+ }
190
+ /**
191
+ * Publish one governance op through the relay, as the author.
192
+ *
193
+ * The warrant commits to the op's exact bytes and is scoped to `groupId`, so
194
+ * the relay can neither change what the op does nor spend it elsewhere. Every
195
+ * peer applies the op as the author, so the author's own rights decide
196
+ * (`MANAGE_MEMBERS` to add a member, `CAN_CREATE_SUBGROUP` to create a
197
+ * subgroup, and so on); a lack of them comes back as an
198
+ * {@link IntentRefusedError} from the relay.
199
+ *
200
+ * Always asks {@link describeGovernance} first and refuses *before* taking a
201
+ * nonce when the relay has no standing, for the reason `createContext` does.
202
+ * The nonce comes from the configured {@link NonceSource}; it is spent in a
203
+ * per-group ledger, and a monotonic source is correct for it as for every
204
+ * other warrant.
205
+ */
206
+ async govern(input) {
207
+ const described = await this.describeGovernance(input.groupId);
208
+ if (!described.canActOnBehalf) {
209
+ throw new IntentRefusedError(`the relay (${described.executorAccount}) has no standing to act for members of group ` +
210
+ `${described.groupId}; an admin must admit it as a relay or grant it CAN_AUTHOR_ON_BEHALF ` +
211
+ '(checked before signing, no nonce was spent)', false, 403);
212
+ }
213
+ const executor = this.checkedExecutor(described.executorAccount);
214
+ const nonce = await this.config.nonces.next();
215
+ const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
216
+ const warrant = await signGovernanceWarrant({
217
+ scope: input.groupId,
218
+ op: input.op,
219
+ authorAccount: this.config.authorAccount,
220
+ executor,
221
+ nonce,
222
+ notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),
223
+ deviceSecret: this.config.deviceSecret,
224
+ });
225
+ const body = await this.json('POST', `/admin-api/groups/${encodeURIComponent(input.groupId)}/governance-intents`, { warrant, authorProof: this.config.authorProof, op: hex(input.op.bytes) });
226
+ return { groupId: body.data.groupId };
227
+ }
228
+ /**
229
+ * The executor a warrant must name: the one the relay answered with, which a
230
+ * configured account must agree with. Remembered for later calls.
231
+ */
232
+ checkedExecutor(answered) {
233
+ const configured = this.config.executorAccount;
234
+ if (configured && configured.toLowerCase() !== answered.toLowerCase()) {
235
+ throw new Error(`configured executorAccount ${configured} is not the relay's account ` +
236
+ `${answered}; a warrant naming it would be unspendable`);
237
+ }
238
+ this.executorAccount = answered;
239
+ return answered;
240
+ }
186
241
  async json(method, path, body) {
187
242
  const headers = { Accept: 'application/json' };
188
243
  if (body !== undefined)
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Encode the governance ops a member may have a relay publish for them.
3
+ *
4
+ * A governance warrant commits to an op's **borsh bytes**, not to a description
5
+ * of it, so the client has to produce exactly the bytes the node decodes. These
6
+ * encoders cover the delegable ops (core's `GroupOp::delegable_form` and
7
+ * `RootOp::delegable_form`) that a keyholder app is likely to need. Every
8
+ * discriminant is a variant's position in core's enum, which is how borsh
9
+ * encodes a Rust enum; they are pinned against core's own vectors in the test.
10
+ *
11
+ * **Delegable form.** A removal, a leave and a cascade delete carry fields only
12
+ * the publisher can compute (post-state hashes, the enumerated subtree). The
13
+ * member signs them cleared, and these encoders only ever produce the cleared
14
+ * form, which is the one a relay accepts. The relay fills them in afterwards.
15
+ *
16
+ * Sources in core, for the next person to check a discriminant:
17
+ * `crates/governance-types/src/lib.rs` (`GroupOp`, `RootOp`),
18
+ * `crates/primitives/src/context.rs` (`GroupMemberRole`), and the vectors in
19
+ * `crates/governance-types/src/tests.rs` (`delegated_governance_op_vectors_are_stable`).
20
+ */
21
+ /**
22
+ * Which plane an op is published on: `group` is a `GroupOp` on the group's own
23
+ * log, `root` a `RootOp` on the namespace log. The kind is signed over, so
24
+ * consent to one can never be presented as the other.
25
+ */
26
+ export type GovernanceOpKind = 'group' | 'root';
27
+ /** An encoded governance op and the plane it belongs to. */
28
+ export interface GovernanceOp {
29
+ kind: GovernanceOpKind;
30
+ /** The op's borsh bytes in delegable form: what the warrant commits to. */
31
+ bytes: Uint8Array;
32
+ }
33
+ /** A group member's role. Borsh discriminants follow core's `GroupMemberRole`. */
34
+ export type GovernanceMemberRole = 'Admin' | 'Member' | 'ReadOnly';
35
+ /**
36
+ * Add `member` (an account, hex) to the group with `role`.
37
+ *
38
+ * Applied as the author: adding a `Member` needs `MANAGE_MEMBERS` or admin,
39
+ * adding an `Admin` needs admin.
40
+ */
41
+ export declare function memberAddedOp(member: string, memberRole: GovernanceMemberRole): GovernanceOp;
42
+ /**
43
+ * Remove `member` (an account, hex) from the group, in delegable form: both
44
+ * post-state claims cleared, for the relay to compute from its own view.
45
+ */
46
+ export declare function memberRemovedOp(member: string): GovernanceOp;
47
+ /**
48
+ * The author leaves the group, in delegable form. The node applies it only when
49
+ * `member` is the author's own account.
50
+ */
51
+ export declare function memberLeftOp(member: string): GovernanceOp;
52
+ /** Set `member`'s role (an account, hex). */
53
+ export declare function memberRoleSetOp(member: string, memberRole: GovernanceMemberRole): GovernanceOp;
54
+ /** What a subgroup is created with. */
55
+ export interface GroupCreatedInput {
56
+ /** The new subgroup's id, hex (32 bytes). Choose it fresh; a taken id is refused. */
57
+ groupId: string;
58
+ /** The group it is nested under, hex: the namespace root or an existing subgroup. */
59
+ parentId: string;
60
+ /** `true` for a Restricted subgroup (the usual default), `false` for born-Open. */
61
+ restricted: boolean;
62
+ /**
63
+ * The subgroup's founding admin, hex: the **author's** account. The node
64
+ * refuses any other, since the op is applied as the author.
65
+ */
66
+ admin: string;
67
+ }
68
+ /**
69
+ * Create a subgroup under `parentId`, a root op published on the namespace.
70
+ *
71
+ * Needs `CAN_CREATE_SUBGROUP` or admin on the parent; the author, not the
72
+ * relay, becomes the new subgroup's admin.
73
+ */
74
+ export declare function groupCreatedOp(input: GroupCreatedInput): GovernanceOp;
75
+ /** Move `childGroupId` under `newParentId`, a root op. */
76
+ export declare function groupReparentedOp(childGroupId: string, newParentId: string): GovernanceOp;
77
+ /**
78
+ * Delete `rootGroupId` and its subtree, in delegable form: both cascade lists
79
+ * empty, for the relay to enumerate.
80
+ */
81
+ export declare function groupDeletedOp(rootGroupId: string): GovernanceOp;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Encode the governance ops a member may have a relay publish for them.
3
+ *
4
+ * A governance warrant commits to an op's **borsh bytes**, not to a description
5
+ * of it, so the client has to produce exactly the bytes the node decodes. These
6
+ * encoders cover the delegable ops (core's `GroupOp::delegable_form` and
7
+ * `RootOp::delegable_form`) that a keyholder app is likely to need. Every
8
+ * discriminant is a variant's position in core's enum, which is how borsh
9
+ * encodes a Rust enum; they are pinned against core's own vectors in the test.
10
+ *
11
+ * **Delegable form.** A removal, a leave and a cascade delete carry fields only
12
+ * the publisher can compute (post-state hashes, the enumerated subtree). The
13
+ * member signs them cleared, and these encoders only ever produce the cleared
14
+ * form, which is the one a relay accepts. The relay fills them in afterwards.
15
+ *
16
+ * Sources in core, for the next person to check a discriminant:
17
+ * `crates/governance-types/src/lib.rs` (`GroupOp`, `RootOp`),
18
+ * `crates/primitives/src/context.rs` (`GroupMemberRole`), and the vectors in
19
+ * `crates/governance-types/src/tests.rs` (`delegated_governance_op_vectors_are_stable`).
20
+ */
21
+ import { concat, fromHex, u32le } from '../crypto/internal.js';
22
+ /**
23
+ * `GroupMemberRole` discriminants. The two TEE roles (3 and 4) are minted only
24
+ * by attestation admission and never by a member's op, so they are not offered.
25
+ */
26
+ const ROLE = {
27
+ Admin: 0,
28
+ Member: 1,
29
+ ReadOnly: 2,
30
+ };
31
+ /** `GroupOp` discriminants, by position in core's enum. */
32
+ const GROUP_OP = {
33
+ MemberAdded: 1,
34
+ MemberRemoved: 2,
35
+ MemberLeft: 3,
36
+ MemberRoleSet: 4,
37
+ };
38
+ /** `RootOp` discriminants, by position in core's enum. */
39
+ const ROOT_OP = {
40
+ GroupCreated: 0,
41
+ GroupReparented: 1,
42
+ GroupDeleted: 2,
43
+ };
44
+ /** A post-state hash left for the relay to compute: 32 zero bytes. */
45
+ const CLEARED_HASH = new Uint8Array(32);
46
+ /** An empty borsh `Vec`: a zero `u32` count. */
47
+ const EMPTY_VEC = u32le(0);
48
+ function role(value) {
49
+ const byte = ROLE[value];
50
+ if (byte === undefined) {
51
+ throw new Error(`role must be Admin, Member or ReadOnly, got ${String(value)}`);
52
+ }
53
+ return new Uint8Array([byte]);
54
+ }
55
+ function tag(byte) {
56
+ return new Uint8Array([byte]);
57
+ }
58
+ /**
59
+ * Add `member` (an account, hex) to the group with `role`.
60
+ *
61
+ * Applied as the author: adding a `Member` needs `MANAGE_MEMBERS` or admin,
62
+ * adding an `Admin` needs admin.
63
+ */
64
+ export function memberAddedOp(member, memberRole) {
65
+ return {
66
+ kind: 'group',
67
+ bytes: concat(tag(GROUP_OP.MemberAdded), fromHex(member, 'member', 32), role(memberRole)),
68
+ };
69
+ }
70
+ /**
71
+ * Remove `member` (an account, hex) from the group, in delegable form: both
72
+ * post-state claims cleared, for the relay to compute from its own view.
73
+ */
74
+ export function memberRemovedOp(member) {
75
+ return {
76
+ kind: 'group',
77
+ bytes: concat(tag(GROUP_OP.MemberRemoved), fromHex(member, 'member', 32), CLEARED_HASH, EMPTY_VEC),
78
+ };
79
+ }
80
+ /**
81
+ * The author leaves the group, in delegable form. The node applies it only when
82
+ * `member` is the author's own account.
83
+ */
84
+ export function memberLeftOp(member) {
85
+ return {
86
+ kind: 'group',
87
+ bytes: concat(tag(GROUP_OP.MemberLeft), fromHex(member, 'member', 32), CLEARED_HASH, EMPTY_VEC),
88
+ };
89
+ }
90
+ /** Set `member`'s role (an account, hex). */
91
+ export function memberRoleSetOp(member, memberRole) {
92
+ return {
93
+ kind: 'group',
94
+ bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, 'member', 32), role(memberRole)),
95
+ };
96
+ }
97
+ /**
98
+ * Create a subgroup under `parentId`, a root op published on the namespace.
99
+ *
100
+ * Needs `CAN_CREATE_SUBGROUP` or admin on the parent; the author, not the
101
+ * relay, becomes the new subgroup's admin.
102
+ */
103
+ export function groupCreatedOp(input) {
104
+ return {
105
+ kind: 'root',
106
+ bytes: concat(tag(ROOT_OP.GroupCreated), fromHex(input.groupId, 'groupId', 32), fromHex(input.parentId, 'parentId', 32), tag(input.restricted ? 1 : 0), fromHex(input.admin, 'admin', 32)),
107
+ };
108
+ }
109
+ /** Move `childGroupId` under `newParentId`, a root op. */
110
+ export function groupReparentedOp(childGroupId, newParentId) {
111
+ return {
112
+ kind: 'root',
113
+ bytes: concat(tag(ROOT_OP.GroupReparented), fromHex(childGroupId, 'childGroupId', 32), fromHex(newParentId, 'newParentId', 32)),
114
+ };
115
+ }
116
+ /**
117
+ * Delete `rootGroupId` and its subtree, in delegable form: both cascade lists
118
+ * empty, for the relay to enumerate.
119
+ */
120
+ export function groupDeletedOp(rootGroupId) {
121
+ return {
122
+ kind: 'root',
123
+ bytes: concat(tag(ROOT_OP.GroupDeleted), fromHex(rootGroupId, 'rootGroupId', 32), EMPTY_VEC, EMPTY_VEC),
124
+ };
125
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Mint a governance warrant: the author's consent for one relay to publish one
3
+ * governance op in the author's name, once.
4
+ *
5
+ * The intent warrant lets an account with no node write to a context and the
6
+ * creation warrant lets it create one. This covers the governance around them:
7
+ * adding the other person to a DM, creating a channel's subgroup, changing a
8
+ * role. The relay wraps the op in `OnBehalf` and publishes it; every peer applies
9
+ * the inner op **as the author**, so the author's own authority decides
10
+ * (`MANAGE_MEMBERS` to add a member, `CAN_CREATE_SUBGROUP` to create a subgroup,
11
+ * and so on) and the relay needs only standing to act for members.
12
+ *
13
+ * **It commits to the op's bytes.** The warrant carries
14
+ * `H(kind || op bytes)` over the op's delegable form (see `governance-op.ts`), so
15
+ * what the op does, to whom and where is signed byte for byte and the relay
16
+ * chooses nothing about it. `kind` is inside the commitment, so a group op can
17
+ * never be presented as a root op whose bytes happen to coincide.
18
+ *
19
+ * **`scope` is where the op is published**: the group itself for a group op,
20
+ * the namespace for a root op.
21
+ *
22
+ * Same primitives and the same two-widths trap as `warrant.ts`: counts inside
23
+ * the encoding are borsh `u32`, counts in the signing preimage are `u64`.
24
+ *
25
+ * **The byte contract is pinned in core**, at
26
+ * `crates/account/src/tests/governance_wire_fixture.rs`, and this module's test
27
+ * asserts the same vectors. A drift would surface at a relay as a 400.
28
+ */
29
+ import type { GovernanceOp, GovernanceOpKind } from './governance-op.js';
30
+ /** What a governance warrant authorises. */
31
+ export interface GovernanceWarrantInput {
32
+ /**
33
+ * Where the op is published, hex (32 bytes): the group for a group op, the
34
+ * namespace for a root op. The relay refuses a warrant presented anywhere else.
35
+ */
36
+ scope: string;
37
+ /** The op, from one of the encoders in `governance-op.ts`. Its kind is signed over. */
38
+ op: GovernanceOp;
39
+ /** The author's account, hex: whose authority the op is applied under. */
40
+ authorAccount: string;
41
+ /** The relay authorised to publish it, hex. An account, not a key. */
42
+ executor: string;
43
+ /**
44
+ * Monotonic per author **device**, spent in a per-group ledger. The same
45
+ * nonce source `RelayClient` uses for every other warrant is correct.
46
+ */
47
+ nonce: number | bigint;
48
+ /** Unix seconds after which the relay must refuse it. */
49
+ notAfter: number | bigint;
50
+ /** Account-log heads the author saw, each hex (32 bytes). At most 64. */
51
+ accountHeads?: string[];
52
+ /** Governance heads the author's view descended from, each hex (32 bytes). At most 64. */
53
+ governanceFloor?: string[];
54
+ /** The author device's ed25519 signing secret, hex (32 bytes). Never sent. */
55
+ deviceSecret: string;
56
+ }
57
+ /** A governance warrant's fields, decoded from the wire encoding. */
58
+ export interface GovernanceWarrantFields {
59
+ scope: string;
60
+ kind: GovernanceOpKind;
61
+ authorAccount: string;
62
+ /** The author device key `signGovernanceWarrant` derived from the secret. */
63
+ deviceKey: string;
64
+ executor: string;
65
+ opHash: string;
66
+ accountHeads: string[];
67
+ governanceFloor: string[];
68
+ nonce: bigint;
69
+ notAfter: bigint;
70
+ signature: string;
71
+ }
72
+ /**
73
+ * The commitment a governance warrant carries in place of the op:
74
+ * `H(kind || op bytes)` under its own domain.
75
+ */
76
+ export declare function governanceOpHash(op: GovernanceOp): Promise<Uint8Array>;
77
+ /** The already-decoded fields the signature covers. Internal. */
78
+ export interface GovernancePreimageParts {
79
+ scope: Uint8Array;
80
+ kind: GovernanceOpKind;
81
+ authorAccount: Uint8Array;
82
+ deviceKey: Uint8Array;
83
+ executor: Uint8Array;
84
+ opHash: Uint8Array;
85
+ accountHeads: Uint8Array[];
86
+ governanceFloor: Uint8Array[];
87
+ nonce: number | bigint;
88
+ notAfter: number | bigint;
89
+ }
90
+ /**
91
+ * The bytes the device key signs. Exported for the conformance test only, not
92
+ * from the package root.
93
+ */
94
+ export declare function governanceWarrantPreimage(p: GovernancePreimageParts): Promise<Uint8Array>;
95
+ /**
96
+ * Sign a governance warrant and return it hex-encoded (borsh
97
+ * `GovernanceWarrant`), ready for the relay alongside the op's own bytes.
98
+ */
99
+ export declare function signGovernanceWarrant(input: GovernanceWarrantInput): Promise<string>;
100
+ /**
101
+ * Decode a hex governance warrant back into its fields.
102
+ *
103
+ * Refuses truncated or trailing bytes and an unknown kind. Does not verify the
104
+ * signature.
105
+ */
106
+ export declare function parseGovernanceWarrant(warrant: string): GovernanceWarrantFields;