@calimero-network/mero-js 24.1.2 → 24.2.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.
@@ -59,8 +59,14 @@ import { type Signer } from '../signer/signer.js';
59
59
  * writer-set step (calimero-network/core#4263), and a capability revoke voids
60
60
  * the member's concurrent ops that needed a removed bit
61
61
  * (calimero-network/core#4453). No layout this module signs changes.
62
+ *
63
+ * 23: a TEE admission op carries the quote it was admitted on and a TEE
64
+ * authority evidence op the credential it was made for
65
+ * (calimero-network/core#4265). Neither is an op this module signs, so only
66
+ * the version byte moves. Core signs and checks at 23 from 0.11.0-rc.81, the
67
+ * first release with both 22 and 23.
62
68
  */
63
- export declare const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 22;
69
+ export declare const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 23;
64
70
  /**
65
71
  * Borsh-encode a `SignedGroupOpenInvitation`.
66
72
  *
@@ -45,8 +45,14 @@ import { resolveSigner } from '../signer/signer.js';
45
45
  * writer-set step (calimero-network/core#4263), and a capability revoke voids
46
46
  * the member's concurrent ops that needed a removed bit
47
47
  * (calimero-network/core#4453). No layout this module signs changes.
48
+ *
49
+ * 23: a TEE admission op carries the quote it was admitted on and a TEE
50
+ * authority evidence op the credential it was made for
51
+ * (calimero-network/core#4265). Neither is an op this module signs, so only
52
+ * the version byte moves. Core signs and checks at 23 from 0.11.0-rc.81, the
53
+ * first release with both 22 and 23.
48
54
  */
49
- export const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 22;
55
+ export const SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 23;
50
56
  /** Domain prefixed to the signable bytes; core's `NAMESPACE_GOVERNANCE_SIGN_DOMAIN`. */
51
57
  const NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode('calimero.namespace.v1');
52
58
  /** `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, GovernanceDescription, GovernInput, GovernResult, FoundNamespaceInput, FoundedNamespace, } from './relay-client.js';
2
+ export type { RelayClientConfig, RelayExecutor, RelayDescription, IntentResult, CreationDescription, CreateContextInput, CreatedContext, GovernanceDescription, GovernInput, GovernResult, FoundNamespaceInput, FoundedNamespace, } from './relay-client.js';
3
3
  export { createMemoryNonceSource, createLocalStorageNonceSource, createRecoveringNonceSource, authorNonceLookup, WarrantNonceExhaustedError, } from './nonce-source.js';
4
4
  export type { NonceSource, WarrantNonceLookup, RecoveringNonceSourceOptions, } from './nonce-source.js';
@@ -18,10 +18,10 @@
18
18
  * # The three preconditions, and why they are checked in this order
19
19
  *
20
20
  * `describe()` answers the first two in one call, before anything is signed:
21
- * the relay's executor account (which the warrant must name) and whether it
22
- * holds `CAN_AUTHOR_ON_BEHALF` on the owning group. The third — that the
23
- * author's account is a member — only the group knows, and it surfaces as a
24
- * refusal.
21
+ * the relay's executor account and signing key (which the warrant must name)
22
+ * and whether it holds `CAN_AUTHOR_ON_BEHALF` on the owning group. The third,
23
+ * that the author's account is a member, only the group knows, and it surfaces
24
+ * as a refusal.
25
25
  *
26
26
  * Checking before signing is not politeness. A warrant consumes a nonce from a
27
27
  * monotonic per-device sequence, and one minted against the wrong executor is
@@ -42,9 +42,8 @@ export interface RelayClientConfig {
42
42
  /**
43
43
  * The account the relay writes as, hex — a warrant's `executor`.
44
44
  *
45
- * An account rather than a key, so one of the relay's processes rotating its
46
- * signing key does not void warrants already issued to it. Left unset, the
47
- * first `execute` learns it from {@link RelayClient.describe}.
45
+ * Only checked against what the relay answers: a warrant also names the
46
+ * relay's signing key, which only discovery reports, so the first call asks.
48
47
  */
49
48
  executorAccount?: string;
50
49
  /** The author's account, hex — whose consent the warrant carries. */
@@ -101,19 +100,22 @@ export interface RelayClientConfig {
101
100
  */
102
101
  rateLimitRetries?: number;
103
102
  }
104
- /** What a relay says about its ability to run intents in one context. */
105
- export interface RelayDescription {
103
+ /** The relay a warrant names: its account, and the one key of it that may spend the warrant. */
104
+ export interface RelayExecutor {
106
105
  /** The account a warrant must name as `executor`, hex. */
107
106
  executorAccount: string;
107
+ /** The signing key a warrant must name as `executor_key`, hex. */
108
+ executorKey: string;
109
+ }
110
+ /** What a relay says about its ability to run intents in one context. */
111
+ export interface RelayDescription extends RelayExecutor {
108
112
  /** Whether the relay holds `CAN_AUTHOR_ON_BEHALF` on the owning group. */
109
113
  canAuthorOnBehalf: boolean;
110
114
  /** The group whose admin grants that capability, hex. */
111
115
  groupId: string;
112
116
  }
113
117
  /** What a relay says about its ability to create contexts in one group. */
114
- export interface CreationDescription {
115
- /** The account a creation warrant must name as `executor`, hex. */
116
- executorAccount: string;
118
+ export interface CreationDescription extends RelayExecutor {
117
119
  /** The group, hex. */
118
120
  groupId: string;
119
121
  /**
@@ -156,9 +158,7 @@ export interface CreatedContext {
156
158
  memberPublicKey: string;
157
159
  }
158
160
  /** What a relay says about publishing governance ops for members of one group. */
159
- export interface GovernanceDescription {
160
- /** The account a governance warrant must name as `executor`, hex. */
161
- executorAccount: string;
161
+ export interface GovernanceDescription extends RelayExecutor {
162
162
  /** The group asked about, hex. */
163
163
  groupId: string;
164
164
  /**
@@ -192,11 +192,10 @@ export interface FoundNamespaceInput {
192
192
  */
193
193
  salt?: string;
194
194
  /**
195
- * The relay's account, hex: the warrant's `executor`. The namespace does not
196
- * exist yet, so there is nothing to ask `describeGovernance` about. Falls back
197
- * to the client's configured or already-learned `executorAccount`.
195
+ * The relay's account and node key (pinned, or from `resolveRelayNodeKey`). Never
196
+ * a discovery answer: that key is unauthenticated, and no namespace exists yet.
198
197
  */
199
- executorAccount?: string;
198
+ executor: RelayExecutor;
200
199
  /**
201
200
  * The namespace root's default capability mask (`MemberCapabilities` bits),
202
201
  * set right after founding. A namespace is founded with core's minimal
@@ -292,7 +291,7 @@ export declare class IntentRefusedError extends Error {
292
291
  }
293
292
  export declare class RelayClient {
294
293
  private readonly baseUrl;
295
- private executorAccount;
294
+ private executor;
296
295
  private readonly config;
297
296
  private readonly fetchImpl;
298
297
  private readonly timeoutMs;
@@ -389,7 +388,7 @@ export declare class RelayClient {
389
388
  createContext(input: CreateContextInput): Promise<CreatedContext>;
390
389
  /**
391
390
  * Ask the relay whether it may publish governance ops for members of
392
- * `groupId`, and which account a warrant must name.
391
+ * `groupId`, and which account and key a warrant must name.
393
392
  */
394
393
  describeGovernance(groupId: string): Promise<GovernanceDescription>;
395
394
  /**
@@ -427,9 +426,7 @@ export declare class RelayClient {
427
426
  *
428
427
  * The executor cannot be learned from the relay first: the namespace does not
429
428
  * exist, so `describeGovernance` has nothing to answer about. It comes from
430
- * `input.executorAccount`, the configured one, or one an earlier call learned
431
- * (`describe`, `createContext`, `govern`), and without any of them this
432
- * throws before taking a nonce.
429
+ * `input.executor` only, and without it this throws before taking a nonce.
433
430
  * The nonce is spent in the new namespace's ledger.
434
431
  *
435
432
  * With `defaultCapabilities`, the founder then sets the namespace's default
@@ -440,11 +437,11 @@ export declare class RelayClient {
440
437
  * Refusals (`400`/`403`) come back as {@link IntentRefusedError}; a salt
441
438
  * that names an existing namespace is a `409`, an `HTTPError`.
442
439
  */
443
- foundNamespace(input?: FoundNamespaceInput): Promise<FoundedNamespace>;
440
+ foundNamespace(input: FoundNamespaceInput): Promise<FoundedNamespace>;
444
441
  private postGovernance;
445
442
  /**
446
- * The executor a warrant must name: the one the relay answered with, which a
447
- * configured account must agree with. Remembered for later calls.
443
+ * The relay's answer, checked against a configured account and remembered.
444
+ * Decoded here so a relay naming no key is refused before a nonce is taken.
448
445
  */
449
446
  private checkedExecutor;
450
447
  /**
@@ -18,10 +18,10 @@
18
18
  * # The three preconditions, and why they are checked in this order
19
19
  *
20
20
  * `describe()` answers the first two in one call, before anything is signed:
21
- * the relay's executor account (which the warrant must name) and whether it
22
- * holds `CAN_AUTHOR_ON_BEHALF` on the owning group. The third — that the
23
- * author's account is a member — only the group knows, and it surfaces as a
24
- * refusal.
21
+ * the relay's executor account and signing key (which the warrant must name)
22
+ * and whether it holds `CAN_AUTHOR_ON_BEHALF` on the owning group. The third,
23
+ * that the author's account is a member, only the group knows, and it surfaces
24
+ * as a refusal.
25
25
  *
26
26
  * Checking before signing is not politeness. A warrant consumes a nonce from a
27
27
  * monotonic per-device sequence, and one minted against the wrong executor is
@@ -85,7 +85,6 @@ export class RelayClient {
85
85
  constructor(config) {
86
86
  this.config = config;
87
87
  this.baseUrl = config.relayUrl.replace(/\/+$/, '');
88
- this.executorAccount = config.executorAccount;
89
88
  const injected = config.fetch;
90
89
  this.fetchImpl = injected
91
90
  ? (input, init) => injected(input, init)
@@ -139,8 +138,8 @@ export class RelayClient {
139
138
  async describe(contextId) {
140
139
  const body = await this.json('GET', `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`);
141
140
  // Learned, not merely returned: a client that called `describe` should not
142
- // then have to pass the account back in to `execute`.
143
- this.executorAccount = body.data.executorAccount;
141
+ // then have to pass the executor back in to `execute`.
142
+ this.checkedExecutor(body.data);
144
143
  return body.data;
145
144
  }
146
145
  /**
@@ -151,7 +150,7 @@ export class RelayClient {
151
150
  * apply. Nothing accumulates and nothing is reusable.
152
151
  */
153
152
  async execute(contextId, method, argsJson = {}) {
154
- const executor = this.executorAccount ?? (await this.describe(contextId)).executorAccount;
153
+ const executor = this.executor ?? this.checkedExecutor(await this.describe(contextId));
155
154
  // Resolved before the nonce, so a misconfigured key burns none.
156
155
  const signer = await this.authorSigner();
157
156
  const nonce = await this.config.nonces.next();
@@ -159,14 +158,20 @@ export class RelayClient {
159
158
  const warrant = await signWarrant({
160
159
  context: contextId,
161
160
  authorAccount: this.config.authorAccount,
162
- executor,
161
+ executor: executor.executorAccount,
162
+ executorKey: executor.executorKey,
163
163
  method,
164
164
  argsJson,
165
165
  nonce,
166
166
  notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),
167
167
  signer,
168
168
  });
169
- const body = await this.json('POST', `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`, { method, argsJson, warrant, authorProof: this.config.authorProof });
169
+ const body = await this.json('POST', `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`, { method, argsJson, warrant, authorProof: this.config.authorProof }).catch((err) => {
170
+ // A relay that re-keyed refuses every warrant naming its old key, so ask again next time.
171
+ if (err instanceof IntentRefusedError)
172
+ this.executor = undefined;
173
+ throw err;
174
+ });
170
175
  return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };
171
176
  }
172
177
  /**
@@ -224,7 +229,7 @@ export class RelayClient {
224
229
  `${described.groupId}; it needs CAN_CREATE_CONTEXT or admin ` +
225
230
  '(checked before signing — no nonce was spent)', false, 403);
226
231
  }
227
- const executor = this.checkedExecutor(described.executorAccount);
232
+ const executor = this.checkedExecutor(described);
228
233
  const initArgs = input.initArgs ?? {};
229
234
  // Resolved before the nonce, so a misconfigured key burns none.
230
235
  const signer = await this.authorSigner();
@@ -234,7 +239,8 @@ export class RelayClient {
234
239
  group: input.groupId,
235
240
  seed: input.seed,
236
241
  authorAccount: this.config.authorAccount,
237
- executor,
242
+ executor: executor.executorAccount,
243
+ executorKey: executor.executorKey,
238
244
  applicationId: input.applicationId,
239
245
  serviceName: input.serviceName,
240
246
  name: input.name,
@@ -252,7 +258,7 @@ export class RelayClient {
252
258
  }
253
259
  /**
254
260
  * Ask the relay whether it may publish governance ops for members of
255
- * `groupId`, and which account a warrant must name.
261
+ * `groupId`, and which account and key a warrant must name.
256
262
  */
257
263
  async describeGovernance(groupId) {
258
264
  const body = await this.json('GET', `/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`);
@@ -281,7 +287,7 @@ export class RelayClient {
281
287
  `${described.groupId}; an admin must admit it as a relay or grant it CAN_AUTHOR_ON_BEHALF ` +
282
288
  '(checked before signing, no nonce was spent)', false, 403);
283
289
  }
284
- const executor = this.checkedExecutor(described.executorAccount);
290
+ const executor = this.checkedExecutor(described);
285
291
  // Resolved before the nonce, so a misconfigured key burns none.
286
292
  const signer = await this.authorSigner();
287
293
  const nonce = await this.config.nonces.next();
@@ -290,7 +296,8 @@ export class RelayClient {
290
296
  scope: input.groupId,
291
297
  op: input.op,
292
298
  authorAccount: this.config.authorAccount,
293
- executor,
299
+ executor: executor.executorAccount,
300
+ executorKey: executor.executorKey,
294
301
  nonce,
295
302
  notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),
296
303
  signer,
@@ -316,9 +323,7 @@ export class RelayClient {
316
323
  *
317
324
  * The executor cannot be learned from the relay first: the namespace does not
318
325
  * exist, so `describeGovernance` has nothing to answer about. It comes from
319
- * `input.executorAccount`, the configured one, or one an earlier call learned
320
- * (`describe`, `createContext`, `govern`), and without any of them this
321
- * throws before taking a nonce.
326
+ * `input.executor` only, and without it this throws before taking a nonce.
322
327
  * The nonce is spent in the new namespace's ledger.
323
328
  *
324
329
  * With `defaultCapabilities`, the founder then sets the namespace's default
@@ -329,14 +334,12 @@ export class RelayClient {
329
334
  * Refusals (`400`/`403`) come back as {@link IntentRefusedError}; a salt
330
335
  * that names an existing namespace is a `409`, an `HTTPError`.
331
336
  */
332
- async foundNamespace(input = {}) {
333
- const executor = input.executorAccount
334
- ? this.checkedExecutor(input.executorAccount)
335
- : this.executorAccount;
336
- if (!executor) {
337
- throw new Error("foundNamespace needs the relay's account: pass executorAccount or configure it " +
337
+ async foundNamespace(input) {
338
+ if (!input?.executor) {
339
+ throw new Error("foundNamespace needs the relay's account and signing key: pass executor " +
338
340
  '(checked before signing, no nonce was spent)');
339
341
  }
342
+ const executor = this.checkedExecutor(input.executor);
340
343
  const salt = input.salt
341
344
  ? hex(fromHex(input.salt, 'salt', 32))
342
345
  : hex(crypto.getRandomValues(new Uint8Array(32)));
@@ -360,7 +363,8 @@ export class RelayClient {
360
363
  scope: namespaceId,
361
364
  op,
362
365
  authorAccount: this.config.authorAccount,
363
- executor,
366
+ executor: executor.executorAccount,
367
+ executorKey: executor.executorKey,
364
368
  nonce,
365
369
  notAfter: BigInt(Math.floor(Date.now() / 1000)) + BigInt(ttl),
366
370
  signer,
@@ -408,17 +412,21 @@ export class RelayClient {
408
412
  return body.data;
409
413
  }
410
414
  /**
411
- * The executor a warrant must name: the one the relay answered with, which a
412
- * configured account must agree with. Remembered for later calls.
415
+ * The relay's answer, checked against a configured account and remembered.
416
+ * Decoded here so a relay naming no key is refused before a nonce is taken.
413
417
  */
414
418
  checkedExecutor(answered) {
419
+ const executor = {
420
+ executorAccount: hex(fromHex(answered.executorAccount ?? '', "the relay's executorAccount", 32)),
421
+ executorKey: hex(fromHex(answered.executorKey ?? '', "the relay's executorKey", 32)),
422
+ };
415
423
  const configured = this.config.executorAccount;
416
- if (configured && configured.toLowerCase() !== answered.toLowerCase()) {
424
+ if (configured && configured.toLowerCase() !== executor.executorAccount) {
417
425
  throw new Error(`configured executorAccount ${configured} is not the relay's account ` +
418
- `${answered}; a warrant naming it would be unspendable`);
426
+ `${executor.executorAccount}; a warrant naming it would be unspendable`);
419
427
  }
420
- this.executorAccount = answered;
421
- return answered;
428
+ this.executor = executor;
429
+ return executor;
422
430
  }
423
431
  /**
424
432
  * One request, waiting out a rate limit rather than failing the caller.
@@ -36,8 +36,10 @@ export interface CreationWarrantInput {
36
36
  seed?: string;
37
37
  /** The author's account, hex — whose consent this is, and who runs `init`. */
38
38
  authorAccount: string;
39
- /** The relay authorised to act, hex. An account, not a key. */
39
+ /** The relay's account, hex: the subject its authorship grant is held by. */
40
40
  executor: string;
41
+ /** The one signing key of `executor` that may carry it out, hex: discovery's `executorKey`. */
42
+ executorKey: string;
41
43
  /** The application the context is created with, hex (32 bytes). */
42
44
  applicationId: string;
43
45
  /**
@@ -103,6 +105,7 @@ export interface CreationWarrantFields {
103
105
  /** The author device key: the signer's public key. */
104
106
  deviceKey: string;
105
107
  executor: string;
108
+ executorKey: string;
106
109
  applicationId: string;
107
110
  /** `null` when absent — distinct from `""`. */
108
111
  serviceName: string | null;
@@ -129,6 +132,7 @@ export interface CreationPreimageParts {
129
132
  authorAccount: Uint8Array;
130
133
  deviceKey: Uint8Array;
131
134
  executor: Uint8Array;
135
+ executorKey: Uint8Array;
132
136
  applicationId: Uint8Array;
133
137
  serviceName: Uint8Array | null;
134
138
  name: Uint8Array | null;
@@ -51,6 +51,7 @@ export async function creationWarrantPreimage(p) {
51
51
  p.authorAccount,
52
52
  p.deviceKey,
53
53
  p.executor,
54
+ p.executorKey,
54
55
  p.applicationId,
55
56
  new Uint8Array([p.serviceName === null ? 0 : 1]),
56
57
  p.serviceName ?? new Uint8Array(0),
@@ -78,6 +79,7 @@ export async function signCreationWarrant(input) {
78
79
  : fromHex(input.seed, 'seed', 32);
79
80
  const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);
80
81
  const executor = fromHex(input.executor, 'executor', 32);
82
+ const executorKey = fromHex(input.executorKey, 'executorKey', 32);
81
83
  const applicationId = fromHex(input.applicationId, 'applicationId', 32);
82
84
  const serviceName = label(input.serviceName, 'serviceName');
83
85
  const name = label(input.name, 'name');
@@ -92,6 +94,7 @@ export async function signCreationWarrant(input) {
92
94
  authorAccount,
93
95
  deviceKey,
94
96
  executor,
97
+ executorKey,
95
98
  applicationId,
96
99
  serviceName,
97
100
  name,
@@ -102,7 +105,7 @@ export async function signCreationWarrant(input) {
102
105
  notAfter: input.notAfter,
103
106
  });
104
107
  const signature = checkedSignature(await signer.sign(preimage));
105
- const warrant = hex(concat(group, seed, authorAccount, deviceKey, executor, applicationId, borshOption(serviceName), borshOption(name), initHash, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, u64le(input.nonce), u64le(input.notAfter), signature));
108
+ const warrant = hex(concat(group, seed, authorAccount, deviceKey, executor, executorKey, applicationId, borshOption(serviceName), borshOption(name), initHash, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, u64le(input.nonce), u64le(input.notAfter), signature));
106
109
  return { warrant, seed: hex(seed) };
107
110
  }
108
111
  /**
@@ -157,6 +160,7 @@ export function parseCreationWarrant(warrant) {
157
160
  authorAccount: hex(take(32, 'authorAccount')),
158
161
  deviceKey: hex(take(32, 'deviceKey')),
159
162
  executor: hex(take(32, 'executor')),
163
+ executorKey: hex(take(32, 'executorKey')),
160
164
  applicationId: hex(take(32, 'applicationId')),
161
165
  serviceName: option('serviceName'),
162
166
  name: option('name'),
@@ -39,8 +39,10 @@ export interface GovernanceWarrantInput {
39
39
  op: GovernanceOp;
40
40
  /** The author's account, hex: whose authority the op is applied under. */
41
41
  authorAccount: string;
42
- /** The relay authorised to publish it, hex. An account, not a key. */
42
+ /** The relay's account, hex: the subject its authorship grant is held by. */
43
43
  executor: string;
44
+ /** The one signing key of `executor` that may publish it, hex: discovery's `executorKey`. */
45
+ executorKey: string;
44
46
  /**
45
47
  * Monotonic per author **device**, spent in a per-group ledger. The same
46
48
  * nonce source `RelayClient` uses for every other warrant is correct.
@@ -76,6 +78,7 @@ export interface GovernanceWarrantFields {
76
78
  /** The author device key: the signer's public key. */
77
79
  deviceKey: string;
78
80
  executor: string;
81
+ executorKey: string;
79
82
  opHash: string;
80
83
  accountHeads: string[];
81
84
  governanceFloor: string[];
@@ -95,6 +98,7 @@ export interface GovernancePreimageParts {
95
98
  authorAccount: Uint8Array;
96
99
  deviceKey: Uint8Array;
97
100
  executor: Uint8Array;
101
+ executorKey: Uint8Array;
98
102
  opHash: Uint8Array;
99
103
  accountHeads: Uint8Array[];
100
104
  governanceFloor: Uint8Array[];
@@ -53,6 +53,7 @@ export async function governanceWarrantPreimage(p) {
53
53
  p.authorAccount,
54
54
  p.deviceKey,
55
55
  p.executor,
56
+ p.executorKey,
56
57
  p.opHash,
57
58
  u64le(p.accountHeads.length),
58
59
  ...p.accountHeads,
@@ -70,6 +71,7 @@ export async function signGovernanceWarrant(input) {
70
71
  const scope = fromHex(input.scope, 'scope', 32);
71
72
  const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);
72
73
  const executor = fromHex(input.executor, 'executor', 32);
74
+ const executorKey = fromHex(input.executorKey, 'executorKey', 32);
73
75
  const accountHeads = citedHeads(input.accountHeads, 'accountHeads');
74
76
  const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');
75
77
  const kind = kindByte(input.op.kind);
@@ -82,6 +84,7 @@ export async function signGovernanceWarrant(input) {
82
84
  authorAccount,
83
85
  deviceKey,
84
86
  executor,
87
+ executorKey,
85
88
  opHash,
86
89
  accountHeads,
87
90
  governanceFloor,
@@ -89,7 +92,7 @@ export async function signGovernanceWarrant(input) {
89
92
  notAfter: input.notAfter,
90
93
  });
91
94
  const signature = checkedSignature(await signer.sign(preimage));
92
- return hex(concat(scope, new Uint8Array([kind]), authorAccount, deviceKey, executor, opHash, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, u64le(input.nonce), u64le(input.notAfter), signature));
95
+ return hex(concat(scope, new Uint8Array([kind]), authorAccount, deviceKey, executor, executorKey, opHash, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, u64le(input.nonce), u64le(input.notAfter), signature));
93
96
  }
94
97
  /**
95
98
  * Decode a hex governance warrant back into its fields.
@@ -139,6 +142,7 @@ export function parseGovernanceWarrant(warrant) {
139
142
  authorAccount: hex(take(32, 'authorAccount')),
140
143
  deviceKey: hex(take(32, 'deviceKey')),
141
144
  executor: hex(take(32, 'executor')),
145
+ executorKey: hex(take(32, 'executorKey')),
142
146
  opHash: hex(take(32, 'opHash')),
143
147
  accountHeads: heads('accountHeads'),
144
148
  governanceFloor: heads('governanceFloor'),
@@ -42,8 +42,13 @@ export interface WarrantInput {
42
42
  context: string;
43
43
  /** The author's account, hex — whose consent this is. */
44
44
  authorAccount: string;
45
- /** The relay authorised to act, hex. An account, not a key. */
45
+ /** The relay's account, hex: the subject its authorship grant is held by. */
46
46
  executor: string;
47
+ /**
48
+ * The one signing key of `executor` that may spend this warrant, hex: the
49
+ * relay's `executorKey` from discovery. Any other key of the account is refused.
50
+ */
51
+ executorKey: string;
47
52
  /**
48
53
  * The application build this warrant is signed against, hex (32 bytes).
49
54
  *
@@ -152,6 +157,7 @@ export interface WarrantFields {
152
157
  /** The author device key `signWarrant` derived from the secret. */
153
158
  deviceKey: string;
154
159
  executor: string;
160
+ executorKey: string;
155
161
  appVersion: string;
156
162
  method: string;
157
163
  intentHash: string;
@@ -59,6 +59,7 @@ export async function signWarrant(input) {
59
59
  const context = fromHex(input.context, 'context', 32);
60
60
  const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);
61
61
  const executor = fromHex(input.executor, 'executor', 32);
62
+ const executorKey = fromHex(input.executorKey, 'executorKey', 32);
62
63
  const appVersion = fromHex(input.appVersion ?? UNPINNED_APP, 'appVersion', 32);
63
64
  const accountHeads = citedHeads(input.accountHeads, 'accountHeads');
64
65
  const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');
@@ -78,6 +79,7 @@ export async function signWarrant(input) {
78
79
  authorAccount,
79
80
  publicKey,
80
81
  executor,
82
+ executorKey,
81
83
  appVersion,
82
84
  method,
83
85
  commitment,
@@ -91,7 +93,7 @@ export async function signWarrant(input) {
91
93
  const signature = checkedSignature(await signer.sign(preimage));
92
94
  // Borsh: a `String` is a u32 length then its UTF-8 bytes; a `Vec<[u8; 32]>` is
93
95
  // a u32 count then the elements. u32 HERE; u64 in the preimage above.
94
- return hex(concat(context, authorAccount, publicKey, executor, appVersion, u32le(method.length), method, commitment, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, nonce, notAfter, signature));
96
+ return hex(concat(context, authorAccount, publicKey, executor, executorKey, appVersion, u32le(method.length), method, commitment, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, nonce, notAfter, signature));
95
97
  }
96
98
  /**
97
99
  * Decode a cited-head list, refusing one longer than a node will accept.
@@ -146,6 +148,7 @@ export function parseWarrant(warrant) {
146
148
  const authorAccount = take(32);
147
149
  const deviceKey = take(32);
148
150
  const executor = take(32);
151
+ const executorKey = take(32);
149
152
  const appVersion = take(32);
150
153
  const methodLen = u32At(o);
151
154
  o += 4;
@@ -168,6 +171,7 @@ export function parseWarrant(warrant) {
168
171
  authorAccount,
169
172
  deviceKey,
170
173
  executor,
174
+ executorKey,
171
175
  appVersion,
172
176
  method,
173
177
  intentHash: intentHashField,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calimero-network/mero-js",
3
- "version": "24.1.2",
3
+ "version": "24.2.0",
4
4
  "description": "Pure JavaScript SDK for Calimero",
5
5
  "type": "module",
6
6
  "exports": {