@oxy.so/core 7.23.2 → 7.24.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.
@@ -1,6 +1,39 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AccountsApi = exports.AccountInvitationsApi = exports.AccountAgentKeysApi = exports.AccountMembersApi = exports.kindAcceptsAccountCategories = exports.isSelectableAccountCategoryId = exports.isOperatorSwitchTargetKind = exports.isDelegatedActAsEligibleKind = exports.SELECTABLE_ACCOUNT_CATEGORY_IDS = exports.MAX_ACCOUNT_CATEGORIES = exports.ACCOUNT_KINDS = exports.ACCOUNT_CATEGORY_IDS = void 0;
36
+ exports.AccountsApi = exports.AccountInvitationsApi = exports.AccountIdentityKeyApi = exports.AccountAgentKeysApi = exports.AccountMembersApi = exports.kindAcceptsAccountCategories = exports.isSelectableAccountCategoryId = exports.isOperatorSwitchTargetKind = exports.isDelegatedActAsEligibleKind = exports.SELECTABLE_ACCOUNT_CATEGORY_IDS = exports.MAX_ACCOUNT_CATEGORIES = exports.ACCOUNT_KINDS = exports.ACCOUNT_CATEGORY_IDS = void 0;
4
37
  const client_1 = require("@oxy.so/contracts/client");
5
38
  const userIdentity_1 = require("../utils/userIdentity");
6
39
  const identityCacheSweep_1 = require("../utils/identityCacheSweep");
@@ -88,6 +121,73 @@ class AccountAgentKeysApi {
88
121
  }
89
122
  }
90
123
  exports.AccountAgentKeysApi = AccountAgentKeysApi;
124
+ /**
125
+ * `oxy.accounts.identityKey` — an organization's identity key (ADR 0061): its
126
+ * DID `#key-1`, the key Peable derives its FairCoin receive addresses from.
127
+ * Only the organization's owners and admins may read or change it; the key is
128
+ * created and held in Commons and never signs anybody in.
129
+ *
130
+ * `get` answers `publicKey: null` for an organization that has no key yet (its
131
+ * DID lists no `#key-1`): that is when a client shows its owners "Set up the
132
+ * organization's Peable wallet". A 403 (`ORGANIZATION_KEY_FORBIDDEN`) means the
133
+ * signed-in person is not an owner or admin of a claimed, active organization.
134
+ */
135
+ class AccountIdentityKeyApi {
136
+ constructor(ctx) {
137
+ this.ctx = ctx;
138
+ }
139
+ /** The organization's key (`null` without one) and every key it has had, oldest first. */
140
+ async get(accountId) {
141
+ return (0, client_1.parseContract)(client_1.organizationIdentityKeyStateCheck, await this.ctx.request('GET', `/accounts/${enc(accountId)}/identity-key`, undefined, { cache: false }));
142
+ }
143
+ /** A one-use challenge for one change, with the claims every signer signs. */
144
+ async requestChallenge(accountId, request) {
145
+ return (0, client_1.parseContract)(client_1.organizationKeyChallengeCheck, await this.ctx.request('POST', `/accounts/${enc(accountId)}/identity-key/challenge`, (0, client_1.parseContract)(client_1.organizationKeyChallengeRequestCheck, request), { cache: false }));
146
+ }
147
+ /** Apply a change with its signatures (see {@link link} for the whole ceremony). */
148
+ async apply(accountId, request) {
149
+ return (0, client_1.parseContract)(client_1.organizationIdentityKeyStateCheck, await this.ctx.request('POST', `/accounts/${enc(accountId)}/identity-key`, (0, client_1.parseContract)(client_1.organizationKeyApplyRequestCheck, request), { cache: false }));
150
+ }
151
+ /**
152
+ * Commons' whole ceremony. NATIVE-ONLY: the actor is THIS device's personal
153
+ * root (`KeyManager`), which must be the signed-in person's. `key` is the
154
+ * organization key the device generated (`create`, or `replace` with a new
155
+ * one); `previousKey` is the current organization key when the device holds
156
+ * it, so the replacement is recorded as proven by it.
157
+ */
158
+ async link(accountId, input) {
159
+ const { KeyManager, signIdentityProof } = await Promise.resolve().then(() => __importStar(require('../crypto/index')));
160
+ const [rootPrivateKey, rootPublicKey] = await Promise.all([
161
+ KeyManager.getPrivateKey(),
162
+ KeyManager.getPublicKey(),
163
+ ]);
164
+ if (!rootPrivateKey || !rootPublicKey) {
165
+ throw new Error('No identity on this device to sign with');
166
+ }
167
+ const challenge = await this.requestChallenge(accountId, {
168
+ operation: input.operation,
169
+ publicKey: input.key.publicKey,
170
+ });
171
+ const root = rootPublicKey.trim().toLowerCase();
172
+ if (challenge.actorPublicKey !== root) {
173
+ throw new Error("This device's identity is not the signed-in account's");
174
+ }
175
+ const sign = async (signer, publicKey) => (await signIdentityProof({ privateKey: signer.privateKey, publicKey }, (0, client_1.organizationKeyProofClaims)(challenge, publicKey))).signature;
176
+ const previousPublicKey = challenge.previousPublicKey;
177
+ return this.apply(accountId, {
178
+ operation: input.operation,
179
+ publicKey: challenge.publicKey,
180
+ challenge: challenge.challenge,
181
+ expiresAt: challenge.expiresAt,
182
+ actorSignature: await sign({ privateKey: rootPrivateKey, publicKey: root }, root),
183
+ keySignature: await sign(input.key, challenge.publicKey),
184
+ ...(input.previousKey && previousPublicKey
185
+ ? { previousKeySignature: await sign(input.previousKey, previousPublicKey) }
186
+ : {}),
187
+ });
188
+ }
189
+ }
190
+ exports.AccountIdentityKeyApi = AccountIdentityKeyApi;
91
191
  /** Consent-based invitations; never cached or queued as offline mutations. */
92
192
  class AccountInvitationsApi {
93
193
  constructor(ctx) {
@@ -123,6 +223,7 @@ class AccountsApi {
123
223
  this.members = new AccountMembersApi(ctx);
124
224
  this.invitations = new AccountInvitationsApi(ctx);
125
225
  this.agentKeys = new AccountAgentKeysApi(ctx);
226
+ this.identityKey = new AccountIdentityKeyApi(ctx);
126
227
  }
127
228
  /**
128
229
  * List the accounts the caller can access: their own personal (root)
@@ -1,4 +1,4 @@
1
- import { agentKeyListResponseCheck, agentKeyOperationChallengeCheck, agentKeyOperationCheck, agentKeyOperationResultCheck, executeAgentKeyOperationCheck, parseContract, } from '@oxy.so/contracts/client';
1
+ import { agentKeyListResponseCheck, agentKeyOperationChallengeCheck, agentKeyOperationCheck, agentKeyOperationResultCheck, executeAgentKeyOperationCheck, organizationIdentityKeyStateCheck, organizationKeyApplyRequestCheck, organizationKeyChallengeCheck, organizationKeyChallengeRequestCheck, organizationKeyProofClaims, parseContract, } from '@oxy.so/contracts/client';
2
2
  import { normalizeUserIdentity } from '../utils/userIdentity.js';
3
3
  import { evictOxyIdentityCache } from '../utils/identityCacheSweep.js';
4
4
  import { evictOxyAccountForestCache, oxyAccountDetailCacheKey, OXY_ACCOUNT_PER_ACCOUNT_CACHE_PREFIX, } from '../utils/accountCacheSweep.js';
@@ -75,6 +75,72 @@ export class AccountAgentKeysApi {
75
75
  return parseContract(agentKeyOperationResultCheck, response);
76
76
  }
77
77
  }
78
+ /**
79
+ * `oxy.accounts.identityKey` — an organization's identity key (ADR 0061): its
80
+ * DID `#key-1`, the key Peable derives its FairCoin receive addresses from.
81
+ * Only the organization's owners and admins may read or change it; the key is
82
+ * created and held in Commons and never signs anybody in.
83
+ *
84
+ * `get` answers `publicKey: null` for an organization that has no key yet (its
85
+ * DID lists no `#key-1`): that is when a client shows its owners "Set up the
86
+ * organization's Peable wallet". A 403 (`ORGANIZATION_KEY_FORBIDDEN`) means the
87
+ * signed-in person is not an owner or admin of a claimed, active organization.
88
+ */
89
+ export class AccountIdentityKeyApi {
90
+ constructor(ctx) {
91
+ this.ctx = ctx;
92
+ }
93
+ /** The organization's key (`null` without one) and every key it has had, oldest first. */
94
+ async get(accountId) {
95
+ return parseContract(organizationIdentityKeyStateCheck, await this.ctx.request('GET', `/accounts/${enc(accountId)}/identity-key`, undefined, { cache: false }));
96
+ }
97
+ /** A one-use challenge for one change, with the claims every signer signs. */
98
+ async requestChallenge(accountId, request) {
99
+ return parseContract(organizationKeyChallengeCheck, await this.ctx.request('POST', `/accounts/${enc(accountId)}/identity-key/challenge`, parseContract(organizationKeyChallengeRequestCheck, request), { cache: false }));
100
+ }
101
+ /** Apply a change with its signatures (see {@link link} for the whole ceremony). */
102
+ async apply(accountId, request) {
103
+ return parseContract(organizationIdentityKeyStateCheck, await this.ctx.request('POST', `/accounts/${enc(accountId)}/identity-key`, parseContract(organizationKeyApplyRequestCheck, request), { cache: false }));
104
+ }
105
+ /**
106
+ * Commons' whole ceremony. NATIVE-ONLY: the actor is THIS device's personal
107
+ * root (`KeyManager`), which must be the signed-in person's. `key` is the
108
+ * organization key the device generated (`create`, or `replace` with a new
109
+ * one); `previousKey` is the current organization key when the device holds
110
+ * it, so the replacement is recorded as proven by it.
111
+ */
112
+ async link(accountId, input) {
113
+ const { KeyManager, signIdentityProof } = await import('../crypto/index.js');
114
+ const [rootPrivateKey, rootPublicKey] = await Promise.all([
115
+ KeyManager.getPrivateKey(),
116
+ KeyManager.getPublicKey(),
117
+ ]);
118
+ if (!rootPrivateKey || !rootPublicKey) {
119
+ throw new Error('No identity on this device to sign with');
120
+ }
121
+ const challenge = await this.requestChallenge(accountId, {
122
+ operation: input.operation,
123
+ publicKey: input.key.publicKey,
124
+ });
125
+ const root = rootPublicKey.trim().toLowerCase();
126
+ if (challenge.actorPublicKey !== root) {
127
+ throw new Error("This device's identity is not the signed-in account's");
128
+ }
129
+ const sign = async (signer, publicKey) => (await signIdentityProof({ privateKey: signer.privateKey, publicKey }, organizationKeyProofClaims(challenge, publicKey))).signature;
130
+ const previousPublicKey = challenge.previousPublicKey;
131
+ return this.apply(accountId, {
132
+ operation: input.operation,
133
+ publicKey: challenge.publicKey,
134
+ challenge: challenge.challenge,
135
+ expiresAt: challenge.expiresAt,
136
+ actorSignature: await sign({ privateKey: rootPrivateKey, publicKey: root }, root),
137
+ keySignature: await sign(input.key, challenge.publicKey),
138
+ ...(input.previousKey && previousPublicKey
139
+ ? { previousKeySignature: await sign(input.previousKey, previousPublicKey) }
140
+ : {}),
141
+ });
142
+ }
143
+ }
78
144
  /** Consent-based invitations; never cached or queued as offline mutations. */
79
145
  export class AccountInvitationsApi {
80
146
  constructor(ctx) {
@@ -109,6 +175,7 @@ export class AccountsApi {
109
175
  this.members = new AccountMembersApi(ctx);
110
176
  this.invitations = new AccountInvitationsApi(ctx);
111
177
  this.agentKeys = new AccountAgentKeysApi(ctx);
178
+ this.identityKey = new AccountIdentityKeyApi(ctx);
112
179
  }
113
180
  /**
114
181
  * List the accounts the caller can access: their own personal (root)
@@ -18,7 +18,7 @@ import type { AccountAccessGrant, OrganizationType } from '@oxy.so/contracts';
18
18
  * no per-request "acting-as" header. Identity is carried by the session/token,
19
19
  * so a switch propagates through reload and cross-domain exactly like a login.
20
20
  */
21
- import type { AgentKeyOperation, AgentKeyOperationProof } from '@oxy.so/contracts';
21
+ import type { AgentKeyOperation, AgentKeyOperationProof, OrganizationIdentityKeyState, OrganizationKeyApplyRequest, OrganizationKeyChallenge, OrganizationKeyChallengeRequest, OrganizationKeyOperation } from '@oxy.so/contracts';
22
22
  import type { User } from '../models/interfaces';
23
23
  import type { AccountCategoryId, AccountKind, AvatarCharacter, ChildAccountKind } from '@oxy.so/contracts';
24
24
  import type { SessionLoginResponse } from '../models/session';
@@ -384,6 +384,44 @@ export declare class AccountAgentKeysApi {
384
384
  revokedMethodIds: string[];
385
385
  }>;
386
386
  }
387
+ /** A secp256k1 key pair held on this device: lowercase hex, the public key uncompressed. */
388
+ export interface OrganizationKeyPair {
389
+ privateKey: string;
390
+ publicKey: string;
391
+ }
392
+ /**
393
+ * `oxy.accounts.identityKey` — an organization's identity key (ADR 0061): its
394
+ * DID `#key-1`, the key Peable derives its FairCoin receive addresses from.
395
+ * Only the organization's owners and admins may read or change it; the key is
396
+ * created and held in Commons and never signs anybody in.
397
+ *
398
+ * `get` answers `publicKey: null` for an organization that has no key yet (its
399
+ * DID lists no `#key-1`): that is when a client shows its owners "Set up the
400
+ * organization's Peable wallet". A 403 (`ORGANIZATION_KEY_FORBIDDEN`) means the
401
+ * signed-in person is not an owner or admin of a claimed, active organization.
402
+ */
403
+ export declare class AccountIdentityKeyApi {
404
+ private readonly ctx;
405
+ constructor(ctx: OxyContext);
406
+ /** The organization's key (`null` without one) and every key it has had, oldest first. */
407
+ get(accountId: string): Promise<OrganizationIdentityKeyState>;
408
+ /** A one-use challenge for one change, with the claims every signer signs. */
409
+ requestChallenge(accountId: string, request: OrganizationKeyChallengeRequest): Promise<OrganizationKeyChallenge>;
410
+ /** Apply a change with its signatures (see {@link link} for the whole ceremony). */
411
+ apply(accountId: string, request: OrganizationKeyApplyRequest): Promise<OrganizationIdentityKeyState>;
412
+ /**
413
+ * Commons' whole ceremony. NATIVE-ONLY: the actor is THIS device's personal
414
+ * root (`KeyManager`), which must be the signed-in person's. `key` is the
415
+ * organization key the device generated (`create`, or `replace` with a new
416
+ * one); `previousKey` is the current organization key when the device holds
417
+ * it, so the replacement is recorded as proven by it.
418
+ */
419
+ link(accountId: string, input: {
420
+ operation: OrganizationKeyOperation;
421
+ key: OrganizationKeyPair;
422
+ previousKey?: OrganizationKeyPair;
423
+ }): Promise<OrganizationIdentityKeyState>;
424
+ }
387
425
  export interface AccountInvitation {
388
426
  id: string;
389
427
  accountId: string;
@@ -425,6 +463,8 @@ export declare class AccountsApi {
425
463
  readonly members: AccountMembersApi;
426
464
  readonly invitations: AccountInvitationsApi;
427
465
  readonly agentKeys: AccountAgentKeysApi;
466
+ /** An organization's identity key: its DID `#key-1` and Peable wallet key (ADR 0061). */
467
+ readonly identityKey: AccountIdentityKeyApi;
428
468
  constructor(ctx: OxyContext);
429
469
  /**
430
470
  * List the accounts the caller can access: their own personal (root)
@@ -23,8 +23,8 @@ export declare const oxyAppAssertionClaimsSchema: z.ZodObject<{
23
23
  sid: z.ZodString;
24
24
  app: z.ZodString;
25
25
  platform: z.ZodEnum<{
26
- android: "android";
27
26
  ios: "ios";
27
+ android: "android";
28
28
  web: "web";
29
29
  }>;
30
30
  iat: z.ZodNumber;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "7.23.2",
3
+ "version": "7.24.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -181,7 +181,7 @@
181
181
  "dependencies": {
182
182
  "@noble/ciphers": "^2.4.0",
183
183
  "@noble/hashes": "^2.4.0",
184
- "@oxy.so/contracts": "^6.10.0",
184
+ "@oxy.so/contracts": "^6.11.0",
185
185
  "@oxy.so/protocol": "^1.6.1",
186
186
  "@oxy.so/telemetry": "^2.1.0",
187
187
  "@scure/bip39": "^2.4.0",
@@ -18,13 +18,26 @@ import type { AccountAccessGrant, OrganizationType } from '@oxy.so/contracts';
18
18
  * no per-request "acting-as" header. Identity is carried by the session/token,
19
19
  * so a switch propagates through reload and cross-domain exactly like a login.
20
20
  */
21
- import type { AgentKeyOperation, AgentKeyOperationProof } from '@oxy.so/contracts';
21
+ import type {
22
+ AgentKeyOperation,
23
+ AgentKeyOperationProof,
24
+ OrganizationIdentityKeyState,
25
+ OrganizationKeyApplyRequest,
26
+ OrganizationKeyChallenge,
27
+ OrganizationKeyChallengeRequest,
28
+ OrganizationKeyOperation,
29
+ } from '@oxy.so/contracts';
22
30
  import {
23
31
  agentKeyListResponseCheck,
24
32
  agentKeyOperationChallengeCheck,
25
33
  agentKeyOperationCheck,
26
34
  agentKeyOperationResultCheck,
27
35
  executeAgentKeyOperationCheck,
36
+ organizationIdentityKeyStateCheck,
37
+ organizationKeyApplyRequestCheck,
38
+ organizationKeyChallengeCheck,
39
+ organizationKeyChallengeRequestCheck,
40
+ organizationKeyProofClaims,
28
41
  parseContract,
29
42
  } from '@oxy.so/contracts/client';
30
43
  import type { User } from '../models/interfaces';
@@ -457,6 +470,124 @@ export class AccountAgentKeysApi {
457
470
  }
458
471
  }
459
472
 
473
+ /** A secp256k1 key pair held on this device: lowercase hex, the public key uncompressed. */
474
+ export interface OrganizationKeyPair {
475
+ privateKey: string;
476
+ publicKey: string;
477
+ }
478
+
479
+ /**
480
+ * `oxy.accounts.identityKey` — an organization's identity key (ADR 0061): its
481
+ * DID `#key-1`, the key Peable derives its FairCoin receive addresses from.
482
+ * Only the organization's owners and admins may read or change it; the key is
483
+ * created and held in Commons and never signs anybody in.
484
+ *
485
+ * `get` answers `publicKey: null` for an organization that has no key yet (its
486
+ * DID lists no `#key-1`): that is when a client shows its owners "Set up the
487
+ * organization's Peable wallet". A 403 (`ORGANIZATION_KEY_FORBIDDEN`) means the
488
+ * signed-in person is not an owner or admin of a claimed, active organization.
489
+ */
490
+ export class AccountIdentityKeyApi {
491
+ constructor(private readonly ctx: OxyContext) {}
492
+
493
+ /** The organization's key (`null` without one) and every key it has had, oldest first. */
494
+ async get(accountId: string): Promise<OrganizationIdentityKeyState> {
495
+ return parseContract(
496
+ organizationIdentityKeyStateCheck,
497
+ await this.ctx.request<unknown>(
498
+ 'GET',
499
+ `/accounts/${enc(accountId)}/identity-key`,
500
+ undefined,
501
+ { cache: false },
502
+ ),
503
+ );
504
+ }
505
+
506
+ /** A one-use challenge for one change, with the claims every signer signs. */
507
+ async requestChallenge(
508
+ accountId: string,
509
+ request: OrganizationKeyChallengeRequest,
510
+ ): Promise<OrganizationKeyChallenge> {
511
+ return parseContract(
512
+ organizationKeyChallengeCheck,
513
+ await this.ctx.request<unknown>(
514
+ 'POST',
515
+ `/accounts/${enc(accountId)}/identity-key/challenge`,
516
+ parseContract(organizationKeyChallengeRequestCheck, request),
517
+ { cache: false },
518
+ ),
519
+ );
520
+ }
521
+
522
+ /** Apply a change with its signatures (see {@link link} for the whole ceremony). */
523
+ async apply(
524
+ accountId: string,
525
+ request: OrganizationKeyApplyRequest,
526
+ ): Promise<OrganizationIdentityKeyState> {
527
+ return parseContract(
528
+ organizationIdentityKeyStateCheck,
529
+ await this.ctx.request<unknown>(
530
+ 'POST',
531
+ `/accounts/${enc(accountId)}/identity-key`,
532
+ parseContract(organizationKeyApplyRequestCheck, request),
533
+ { cache: false },
534
+ ),
535
+ );
536
+ }
537
+
538
+ /**
539
+ * Commons' whole ceremony. NATIVE-ONLY: the actor is THIS device's personal
540
+ * root (`KeyManager`), which must be the signed-in person's. `key` is the
541
+ * organization key the device generated (`create`, or `replace` with a new
542
+ * one); `previousKey` is the current organization key when the device holds
543
+ * it, so the replacement is recorded as proven by it.
544
+ */
545
+ async link(
546
+ accountId: string,
547
+ input: {
548
+ operation: OrganizationKeyOperation;
549
+ key: OrganizationKeyPair;
550
+ previousKey?: OrganizationKeyPair;
551
+ },
552
+ ): Promise<OrganizationIdentityKeyState> {
553
+ const { KeyManager, signIdentityProof } = await import('../crypto/index');
554
+ const [rootPrivateKey, rootPublicKey] = await Promise.all([
555
+ KeyManager.getPrivateKey(),
556
+ KeyManager.getPublicKey(),
557
+ ]);
558
+ if (!rootPrivateKey || !rootPublicKey) {
559
+ throw new Error('No identity on this device to sign with');
560
+ }
561
+ const challenge = await this.requestChallenge(accountId, {
562
+ operation: input.operation,
563
+ publicKey: input.key.publicKey,
564
+ });
565
+ const root = rootPublicKey.trim().toLowerCase();
566
+ if (challenge.actorPublicKey !== root) {
567
+ throw new Error("This device's identity is not the signed-in account's");
568
+ }
569
+ const sign = async (signer: OrganizationKeyPair, publicKey: string) =>
570
+ (
571
+ await signIdentityProof(
572
+ { privateKey: signer.privateKey, publicKey },
573
+ organizationKeyProofClaims(challenge, publicKey),
574
+ )
575
+ ).signature;
576
+ const previousPublicKey = challenge.previousPublicKey;
577
+ return this.apply(accountId, {
578
+ operation: input.operation,
579
+ publicKey: challenge.publicKey,
580
+ challenge: challenge.challenge,
581
+ expiresAt: challenge.expiresAt,
582
+ actorSignature: await sign({ privateKey: rootPrivateKey, publicKey: root }, root),
583
+ keySignature: await sign(input.key, challenge.publicKey),
584
+ ...(input.previousKey && previousPublicKey
585
+ ? { previousKeySignature: await sign(input.previousKey, previousPublicKey) }
586
+ : {}),
587
+ });
588
+ }
589
+ }
590
+
460
591
  export interface AccountInvitation {
461
592
  id: string;
462
593
  accountId: string;
@@ -540,11 +671,14 @@ export class AccountsApi {
540
671
  readonly members: AccountMembersApi;
541
672
  readonly invitations: AccountInvitationsApi;
542
673
  readonly agentKeys: AccountAgentKeysApi;
674
+ /** An organization's identity key: its DID `#key-1` and Peable wallet key (ADR 0061). */
675
+ readonly identityKey: AccountIdentityKeyApi;
543
676
 
544
677
  constructor(private readonly ctx: OxyContext) {
545
678
  this.members = new AccountMembersApi(ctx);
546
679
  this.invitations = new AccountInvitationsApi(ctx);
547
680
  this.agentKeys = new AccountAgentKeysApi(ctx);
681
+ this.identityKey = new AccountIdentityKeyApi(ctx);
548
682
  }
549
683
 
550
684
  /**