@oxy.so/core 7.31.0 → 7.33.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.
Files changed (33) hide show
  1. package/dist/cjs/api/identity.js +155 -24
  2. package/dist/cjs/crypto/identityMarker.js +15 -0
  3. package/dist/cjs/crypto/index.js +11 -2
  4. package/dist/cjs/crypto/keyManager.js +111 -16
  5. package/dist/cjs/crypto/keychainGroupMigration.js +327 -160
  6. package/dist/cjs/crypto/organizationKeys.js +33 -5
  7. package/dist/cjs/crypto/retiredKeys.js +315 -64
  8. package/dist/cjs/utils/keychainAccessGroups.js +111 -25
  9. package/dist/esm/api/identity.js +155 -24
  10. package/dist/esm/crypto/identityMarker.js +14 -0
  11. package/dist/esm/crypto/index.js +3 -3
  12. package/dist/esm/crypto/keyManager.js +113 -18
  13. package/dist/esm/crypto/keychainGroupMigration.js +327 -162
  14. package/dist/esm/crypto/organizationKeys.js +34 -6
  15. package/dist/esm/crypto/retiredKeys.js +306 -63
  16. package/dist/esm/utils/keychainAccessGroups.js +107 -24
  17. package/dist/types/api/identity.d.ts +36 -0
  18. package/dist/types/crypto/identityMarker.d.ts +8 -0
  19. package/dist/types/crypto/index.d.ts +3 -3
  20. package/dist/types/crypto/keyManager.d.ts +45 -1
  21. package/dist/types/crypto/keychainGroupMigration.d.ts +28 -6
  22. package/dist/types/crypto/organizationKeys.d.ts +4 -2
  23. package/dist/types/crypto/retiredKeys.d.ts +108 -17
  24. package/dist/types/utils/keychainAccessGroups.d.ts +41 -11
  25. package/package.json +1 -1
  26. package/src/api/identity.ts +198 -41
  27. package/src/crypto/identityMarker.ts +15 -0
  28. package/src/crypto/index.ts +12 -1
  29. package/src/crypto/keyManager.ts +127 -26
  30. package/src/crypto/keychainGroupMigration.ts +390 -185
  31. package/src/crypto/organizationKeys.ts +38 -6
  32. package/src/crypto/retiredKeys.ts +369 -65
  33. package/src/utils/keychainAccessGroups.ts +121 -24
@@ -47,7 +47,8 @@ import {
47
47
  } from '../utils/keychainAccessGroups';
48
48
  import { isAndroid, isIOS, isWeb } from '../utils/platform';
49
49
  import { registeredIdentitySignerStore } from './identitySigner';
50
- import { ensureKeychainGroupsMigrated } from './keychainGroupMigration';
50
+ import { prepareKeychainAccess } from './keychainGroupMigration';
51
+ import { retireIdentityKey } from './retiredKeys';
51
52
  import {
52
53
  ORGANIZATION_ANDROID_KEYCHAIN_SERVICE as ANDROID_KEYCHAIN_SERVICE,
53
54
  ORGANIZATION_INDEX_KEYCHAIN_SERVICE as INDEX_KEYCHAIN_SERVICE,
@@ -158,9 +159,7 @@ export async function organizationKeyFromPhrase(phrase: string): Promise<Organiz
158
159
  async function secureStore(item: KeychainItemClass): Promise<ExpoSecureStoreLike> {
159
160
  const raw = await loadSecureStore();
160
161
  const access = await resolveKeychainAccess();
161
- if (access.mode === 'grouped' && access.role === 'holder') {
162
- await ensureKeychainGroupsMigrated();
163
- }
162
+ await prepareKeychainAccess(access);
164
163
  return groupedKeychainStore(raw, access, item);
165
164
  }
166
165
 
@@ -252,8 +251,10 @@ async function readLocalPair(accountId: string): Promise<OrganizationKeyPair | n
252
251
  * it; on iOS only Commons holds the groups it writes (any other app throws).
253
252
  *
254
253
  * Refuses a pair whose public key does not derive from its private key, and a
255
- * phrase that does not derive the pair. Overwrites whatever this device held for
256
- * the organization (a replaced key is the caller's decision, ADR 0061 D7).
254
+ * phrase that does not derive the pair. A DIFFERENT key this device already
255
+ * held for the organization is first kept in the organization's retired ring
256
+ * (ADR 0061 D7 as amended; R4-12): coins under it still need it. When that
257
+ * cannot be confirmed nothing is overwritten and this throws.
257
258
  */
258
259
  export async function storeOrganizationKey(
259
260
  accountId: string,
@@ -280,6 +281,19 @@ export async function storeOrganizationKey(
280
281
  );
281
282
  }
282
283
  const store = await pairStore();
284
+ const previous = await heldPrivateKey(store, accountId);
285
+ if (previous) {
286
+ const previousPublic = deriveSecp256k1PublicKey(previous);
287
+ if (previousPublic !== publicKey) {
288
+ try {
289
+ await retireIdentityKey({ privateKey: previous, publicKey: previousPublic }, { accountId });
290
+ } catch (error) {
291
+ throw new Error(
292
+ `organization key: the key this device held could not be kept as a previous key, so it was not replaced (${error instanceof Error ? error.message : 'unknown'})`,
293
+ );
294
+ }
295
+ }
296
+ }
283
297
  await store.setItemAsync(privateKeyName(accountId), privateKey, pairOptions(store, true));
284
298
  await store.setItemAsync(publicKeyName(accountId), publicKey, pairOptions(store, false));
285
299
  if (
@@ -362,6 +376,24 @@ export async function listOrganizationKeys(): Promise<string[]> {
362
376
  return readIndex(await indexStore());
363
377
  }
364
378
 
379
+ /**
380
+ * The private key this device holds for `accountId`, whatever its public half
381
+ * says (an unhealthy pair is still a key coins may sit under): the slot, then
382
+ * on Android the signer store's copy. `null` when there is none.
383
+ */
384
+ async function heldPrivateKey(
385
+ store: ExpoSecureStoreLike,
386
+ accountId: string,
387
+ ): Promise<string | null> {
388
+ const local = await store.getItemAsync(privateKeyName(accountId), pairOptions(store, false));
389
+ if (local && isValidSecp256k1PrivateKey(local)) return normalizeSecp256k1PrivateKey(local);
390
+ const signer = isAndroid() ? registeredIdentitySignerStore() : null;
391
+ const mirrored = (await signer?.readOrganization?.(accountId)) ?? null;
392
+ return mirrored && isValidSecp256k1PrivateKey(mirrored.privateKey)
393
+ ? normalizeSecp256k1PrivateKey(mirrored.privateKey)
394
+ : null;
395
+ }
396
+
365
397
  /** Forget an organization key on this device: its pair, its phrase, its signer copy and its index entry. */
366
398
  export async function deleteOrganizationKey(accountId: string): Promise<void> {
367
399
  assertAccountId(accountId);