@oxy.so/core 7.30.0 → 7.32.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/cjs/api/identity.js +146 -23
- package/dist/cjs/crypto/identityMarker.js +15 -0
- package/dist/cjs/crypto/index.js +7 -2
- package/dist/cjs/crypto/keyManager.js +7 -10
- package/dist/cjs/crypto/keychainGroupMigration.js +327 -160
- package/dist/cjs/crypto/organizationKeys.js +33 -5
- package/dist/cjs/crypto/retiredKeys.js +80 -18
- package/dist/cjs/session/authStateStore.js +13 -0
- package/dist/cjs/session/index.js +5 -2
- package/dist/cjs/utils/keychainAccessGroups.js +208 -51
- package/dist/esm/api/identity.js +146 -23
- package/dist/esm/crypto/identityMarker.js +14 -0
- package/dist/esm/crypto/index.js +3 -3
- package/dist/esm/crypto/keyManager.js +9 -12
- package/dist/esm/crypto/keychainGroupMigration.js +327 -162
- package/dist/esm/crypto/organizationKeys.js +34 -6
- package/dist/esm/crypto/retiredKeys.js +76 -17
- package/dist/esm/session/authStateStore.js +12 -0
- package/dist/esm/session/index.js +1 -1
- package/dist/esm/utils/keychainAccessGroups.js +205 -51
- package/dist/types/api/identity.d.ts +36 -0
- package/dist/types/crypto/identityMarker.d.ts +8 -0
- package/dist/types/crypto/index.d.ts +4 -4
- package/dist/types/crypto/keychainGroupMigration.d.ts +28 -6
- package/dist/types/crypto/organizationKeys.d.ts +4 -2
- package/dist/types/crypto/retiredKeys.d.ts +30 -4
- package/dist/types/session/authStateStore.d.ts +9 -0
- package/dist/types/session/index.d.ts +2 -2
- package/dist/types/utils/keychainAccessGroups.d.ts +108 -26
- package/package.json +2 -2
- package/src/api/identity.ts +185 -40
- package/src/crypto/identityMarker.ts +15 -0
- package/src/crypto/index.ts +10 -1
- package/src/crypto/keyManager.ts +9 -15
- package/src/crypto/keychainGroupMigration.ts +396 -186
- package/src/crypto/organizationKeys.ts +38 -6
- package/src/crypto/retiredKeys.ts +87 -17
- package/src/session/authStateStore.ts +14 -0
- package/src/session/index.ts +8 -1
- package/src/utils/keychainAccessGroups.ts +276 -52
|
@@ -67,6 +67,14 @@ export interface RotateKeyResult {
|
|
|
67
67
|
* should surface a recovery prompt. Omitted on full success.
|
|
68
68
|
*/
|
|
69
69
|
localPersistFailed?: true;
|
|
70
|
+
/**
|
|
71
|
+
* Whether the encrypted identity backup was re-created under the NEW phrase,
|
|
72
|
+
* carrying the retired ring with the key just replaced (R4-4). The server
|
|
73
|
+
* deletes the old backup in the rotation's transaction; `false` (native, the
|
|
74
|
+
* upload failed) means the caller should offer "Create backup" again, or the
|
|
75
|
+
* replaced key lives only on this phone. Absent on web.
|
|
76
|
+
*/
|
|
77
|
+
backupRefreshed?: boolean;
|
|
70
78
|
}
|
|
71
79
|
/**
|
|
72
80
|
* Derive a user's Oxy DID from their stable account id.
|
|
@@ -268,6 +276,34 @@ export declare class IdentityBackupApi {
|
|
|
268
276
|
* from SecureStore.
|
|
269
277
|
*/
|
|
270
278
|
create(phrase: string): Promise<BackupStatusResponse>;
|
|
279
|
+
/**
|
|
280
|
+
* The retired ring this device holds for the person, limited to keys Oxy's
|
|
281
|
+
* key history names as THIS account's retired keys (the ring's iOS group is
|
|
282
|
+
* writable by the wallet app too, and a previous person's ring can outlive a
|
|
283
|
+
* "Start over"). Empty where no ring is held (web, any app but Commons).
|
|
284
|
+
* Throws when the history cannot be read while a ring exists, so a backup
|
|
285
|
+
* never silently drops keys it should carry.
|
|
286
|
+
*/
|
|
287
|
+
private retiredKeysToCarry;
|
|
288
|
+
/**
|
|
289
|
+
* Fetch and open the backup the phrase locates, checking it is the phrase's
|
|
290
|
+
* own identity. With `missingIsNull`, `null` when there is none (404);
|
|
291
|
+
* otherwise the request's own error propagates.
|
|
292
|
+
*/
|
|
293
|
+
private open;
|
|
294
|
+
/**
|
|
295
|
+
* Keep every retired key a backup carries in this device's ring. Each pair
|
|
296
|
+
* must be healthy and must not be the current key; the envelope itself is
|
|
297
|
+
* authenticated by the phrase. Commons only (the ring refuses elsewhere).
|
|
298
|
+
* Resolves how many keys are now held; a key that fails is skipped.
|
|
299
|
+
*/
|
|
300
|
+
private keepCarriedRetiredKeys;
|
|
301
|
+
/**
|
|
302
|
+
* Restore ONLY the retired keys the phrase's backup carries into this
|
|
303
|
+
* device's ring (Commons, after a phrase import or a silent device-backup
|
|
304
|
+
* restore). Resolves how many were kept; 0 when there is no backup.
|
|
305
|
+
*/
|
|
306
|
+
restoreRetiredKeys(phrase: string): Promise<number>;
|
|
271
307
|
/**
|
|
272
308
|
* Whether the signed-in user has a stored backup, plus its non-sensitive hint
|
|
273
309
|
* and timestamp. Returns no ciphertext and no locator.
|
|
@@ -66,6 +66,14 @@ export interface WriteIdentityMarkerInput {
|
|
|
66
66
|
* rest.
|
|
67
67
|
*/
|
|
68
68
|
export declare function readIdentityMarker(): Promise<IdentityMarker | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Read the marker, THROWING when the storage cannot answer (instead of the
|
|
71
|
+
* fail-open `null` of {@link readIdentityMarker}). For a caller that must not
|
|
72
|
+
* mistake "unreadable" for "never written": the iOS keychain migration, which
|
|
73
|
+
* adopts a pre-split secret only when this marker vouches for it, and records
|
|
74
|
+
* once that there is nothing to adopt.
|
|
75
|
+
*/
|
|
76
|
+
export declare function readIdentityMarkerStrict(): Promise<IdentityMarker | null>;
|
|
69
77
|
/**
|
|
70
78
|
* Write (create/replace) the marker. Returns `true` when it durably landed,
|
|
71
79
|
* `false` when storage was unavailable or the write threw. Callers treat a
|
|
@@ -19,11 +19,11 @@ export type { IdentityDeviceBackupStore } from './deviceBackup';
|
|
|
19
19
|
export type { IdentitySignerStore } from './identitySigner';
|
|
20
20
|
export { generateOrganizationKey, organizationKeyFromPhrase, storeOrganizationKey, getOrganizationPublicKey, getOrganizationPrivateKey, getOrganizationRecoveryPhrase, listOrganizationKeys, deleteOrganizationKey, syncOrganizationKeys, isOrganizationAccountId, isHealthyOrganizationKeyPair, } from './organizationKeys';
|
|
21
21
|
export type { OrganizationKeyPair, GeneratedOrganizationKey, StoredOrganizationKey, } from './organizationKeys';
|
|
22
|
-
export { KEYCHAIN_ACCESS_GROUPS, KeychainAccessUnavailableError, keychainGroupFor, mayHoldKeychainItem, resolveKeychainAccess, keychainAccessGroupValue, keychainReadPlan, keychainDeletePlan, keychainWriteOption, ownKeychainItemPlan, readThroughPlan, deleteThroughPlan, groupedKeychainStore, } from '../utils/keychainAccessGroups';
|
|
23
|
-
export type { KeychainAccess, KeychainAccessGroupName, KeychainGroupOption, KeychainItemClass, KeychainRole, KeychainStoreLike, } from '../utils/keychainAccessGroups';
|
|
24
|
-
export { ensureKeychainGroupsMigrated } from './keychainGroupMigration';
|
|
22
|
+
export { KEYCHAIN_ACCESS_GROUPS, KeychainAccessUnavailableError, keychainGroupFor, mayHoldKeychainItem, mayWriteKeychainItem, DEV_KEYCHAIN_GROUP_SUFFIX, resolveKeychainAccess, keychainAccessGroupValue, keychainReadPlan, keychainDeletePlan, keychainWriteOption, ownKeychainItemPlan, readThroughPlan, deleteThroughPlan, groupedKeychainStore, } from '../utils/keychainAccessGroups';
|
|
23
|
+
export type { KeychainAccess, KeychainAccessGroupName, KeychainGroup, KeychainGroupOption, KeychainItemClass, KeychainRole, KeychainStoreLike, OwnKeychainItemNames, } from '../utils/keychainAccessGroups';
|
|
24
|
+
export { ensureKeychainGroupsMigrated, getPreSplitExposure, } from './keychainGroupMigration';
|
|
25
25
|
export type { KeychainGroupMigrationReport, KeychainItemMigrationOutcome, } from './keychainGroupMigration';
|
|
26
|
-
export { retireIdentityKey, listRetiredIdentityKeys, forgetRetiredIdentityKey, forgetRetiredIdentityKeys, syncRetiredKeys, retiredKeyFromPhrase, } from './retiredKeys';
|
|
26
|
+
export { retireIdentityKey, listRetiredIdentityKeys, forgetRetiredIdentityKey, forgetRetiredIdentityKeys, syncRetiredKeys, retiredKeyFromPhrase, retiredKeyFromPrivateKey, exportRetiredIdentityKeys, } from './retiredKeys';
|
|
27
27
|
export type { RetiredIdentityKey, RetiredKeyPair, RetiredKeySelector } from './retiredKeys';
|
|
28
28
|
export { SignatureService } from './signatureService';
|
|
29
29
|
export type { SignedMessage, AuthChallenge } from './signatureService';
|
|
@@ -1,20 +1,42 @@
|
|
|
1
|
+
import { type KeychainAccess } from '../utils/keychainAccessGroups';
|
|
1
2
|
/** What happened to one item (or pair) in a run. */
|
|
2
|
-
export type KeychainItemMigrationOutcome = 'absent' | 'moved' | '
|
|
3
|
+
export type KeychainItemMigrationOutcome = 'absent' | 'moved' | 'retired' | 'discarded' | 'failed' | 'deferred';
|
|
3
4
|
export interface KeychainGroupMigrationReport {
|
|
4
5
|
/**
|
|
5
6
|
* - `not-applicable`: not Commons on a split-aware iOS build; nothing to move.
|
|
6
|
-
* - `complete`:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - `deferred`: the keychain could not be read (locked);
|
|
10
|
-
* storage access.
|
|
7
|
+
* - `complete`: the completion record exists; the shared group is never
|
|
8
|
+
* read again for a secret.
|
|
9
|
+
* - `incomplete`: a copy or delete failed; retried next launch.
|
|
10
|
+
* - `deferred`: the keychain or the marker could not be read (locked);
|
|
11
|
+
* retried on the next storage access.
|
|
11
12
|
*/
|
|
12
13
|
state: 'not-applicable' | 'complete' | 'incomplete' | 'deferred';
|
|
13
14
|
/** Outcome per item label (`primary`, `phrase`, `organization-1`, …). */
|
|
14
15
|
items: Record<string, KeychainItemMigrationOutcome>;
|
|
16
|
+
/** Whether this device's marker vouched for the shared copies (absent before a run). */
|
|
17
|
+
proven?: boolean;
|
|
15
18
|
}
|
|
16
19
|
/** Run the migration once. Exported for tests; production code awaits the gate. */
|
|
17
20
|
export declare function runKeychainGroupMigration(): Promise<KeychainGroupMigrationReport>;
|
|
21
|
+
/**
|
|
22
|
+
* What every keychain-backed identity access awaits first, by role: Commons
|
|
23
|
+
* runs (or has run) the migration; the Peable wallet checks once whether
|
|
24
|
+
* Commons' completion record is in the wallet group, and from then on never
|
|
25
|
+
* falls back to the shared group for a secret. Never throws.
|
|
26
|
+
*/
|
|
27
|
+
export declare function prepareKeychainAccess(access: KeychainAccess): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Commons: whether this device's identity keys sat in the shared keychain
|
|
30
|
+
* group before the split, where every Oxy iOS app could read them. `publicKey`
|
|
31
|
+
* is the personal key that was exposed (recommend a rotation while it is still
|
|
32
|
+
* the current key), `organizations` the organization accounts whose key was.
|
|
33
|
+
* `null` when nothing was exposed, the migration has not finished, or this is
|
|
34
|
+
* not Commons on iOS.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getPreSplitExposure(): Promise<{
|
|
37
|
+
publicKey: string | null;
|
|
38
|
+
organizations: string[];
|
|
39
|
+
} | null>;
|
|
18
40
|
/**
|
|
19
41
|
* The gate every Commons identity-storage access awaits. One run per process
|
|
20
42
|
* (`complete`, `incomplete` and `not-applicable` are kept; `incomplete` is
|
|
@@ -60,8 +60,10 @@ export declare function organizationKeyFromPhrase(phrase: string): Promise<Organ
|
|
|
60
60
|
* it; on iOS only Commons holds the groups it writes (any other app throws).
|
|
61
61
|
*
|
|
62
62
|
* Refuses a pair whose public key does not derive from its private key, and a
|
|
63
|
-
* phrase that does not derive the pair.
|
|
64
|
-
* the organization
|
|
63
|
+
* phrase that does not derive the pair. A DIFFERENT key this device already
|
|
64
|
+
* held for the organization is first kept in the organization's retired ring
|
|
65
|
+
* (ADR 0061 D7 as amended; R4-12): coins under it still need it. When that
|
|
66
|
+
* cannot be confirmed nothing is overwritten and this throws.
|
|
65
67
|
*/
|
|
66
68
|
export declare function storeOrganizationKey(accountId: string, key: StoredOrganizationKey): Promise<void>;
|
|
67
69
|
/**
|
|
@@ -70,6 +70,12 @@ export declare function retiredKeyScope(selector?: RetiredKeySelector): string |
|
|
|
70
70
|
* caller about to replace the key can stop before anything is lost.
|
|
71
71
|
*/
|
|
72
72
|
export declare function retireIdentityKey(key: RetiredKeyPair, selector?: RetiredKeySelector): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* {@link retireIdentityKey} from inside the iOS access-group migration, which
|
|
75
|
+
* keeps a replaced pre-split copy before deleting it from the shared group and
|
|
76
|
+
* cannot await itself. @internal
|
|
77
|
+
*/
|
|
78
|
+
export declare function retireIdentityKeyDuringMigration(key: RetiredKeyPair, selector?: RetiredKeySelector): Promise<void>;
|
|
73
79
|
/**
|
|
74
80
|
* The retired keys of `selector`'s account this app can reach, oldest first.
|
|
75
81
|
* iOS and Commons: the ring's index. Android elsewhere: Commons' identity host
|
|
@@ -93,10 +99,12 @@ export declare function getRetiredPrivateKey(publicKey: string, selector?: Retir
|
|
|
93
99
|
*/
|
|
94
100
|
export declare function forgetRetiredIdentityKey(publicKey: string, selector?: RetiredKeySelector): Promise<void>;
|
|
95
101
|
/**
|
|
96
|
-
* Forget every retired key of one ring (the person's, or an organization's)
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
102
|
+
* Forget every retired key of one ring (the person's, or an organization's).
|
|
103
|
+
* Commons only, and only when the person deletes their Oxy ACCOUNT (Commons'
|
|
104
|
+
* delete-account flow), which tells them the coins go with it. Deleting the
|
|
105
|
+
* identity from this device — "Start over", a forced delete — never calls it:
|
|
106
|
+
* the ring is money, and the same person restoring their identity later must
|
|
107
|
+
* find it (R4-4). Resolves how many it removed.
|
|
100
108
|
*/
|
|
101
109
|
export declare function forgetRetiredIdentityKeys(selector?: RetiredKeySelector): Promise<number>;
|
|
102
110
|
/**
|
|
@@ -106,6 +114,24 @@ export declare function forgetRetiredIdentityKeys(selector?: RetiredKeySelector)
|
|
|
106
114
|
* Resolves how many it mirrored. Elsewhere 0.
|
|
107
115
|
*/
|
|
108
116
|
export declare function syncRetiredKeys(): Promise<number>;
|
|
117
|
+
/**
|
|
118
|
+
* Every retired key of `selector`'s ring WITH its private half, for the
|
|
119
|
+
* encrypted identity backup (`oxy.identity.backup.create`), which carries them
|
|
120
|
+
* under the CURRENT phrase so a lost phone costs no more than the current
|
|
121
|
+
* phrase does (R4-4). Commons only; an entry whose private half is missing or
|
|
122
|
+
* does not derive its public key is left out. Never log or transmit the result
|
|
123
|
+
* except inside that encrypted envelope.
|
|
124
|
+
*/
|
|
125
|
+
export declare function exportRetiredIdentityKeys(selector?: RetiredKeySelector): Promise<Array<RetiredKeyPair & {
|
|
126
|
+
retiredAt: number;
|
|
127
|
+
}>>;
|
|
128
|
+
/**
|
|
129
|
+
* The pair a previous key's raw PRIVATE KEY gives (64 hex, an optional `0x`),
|
|
130
|
+
* for a person whose old identity was imported as a private key and so has no
|
|
131
|
+
* phrase to re-enter. Stores nothing: the caller checks the public key against
|
|
132
|
+
* the account's key history and only then calls {@link retireIdentityKey}.
|
|
133
|
+
*/
|
|
134
|
+
export declare function retiredKeyFromPrivateKey(privateKeyHex: string): RetiredKeyPair;
|
|
109
135
|
/**
|
|
110
136
|
* The pair a previous recovery phrase derives (`seed[0:32]`, the personal-root
|
|
111
137
|
* and organization-key derivation; 12–24 words). Stores nothing: the caller
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* ESM-safe (no `require()`).
|
|
18
18
|
*/
|
|
19
|
+
import type { OwnKeychainItemNames } from '../utils/keychainAccessGroups';
|
|
19
20
|
/**
|
|
20
21
|
* The persisted session credential set for a single origin.
|
|
21
22
|
*
|
|
@@ -124,6 +125,14 @@ export interface NativeKeyValueStorage {
|
|
|
124
125
|
*/
|
|
125
126
|
export declare const AUTH_STATE_STORAGE_KEY = "oxy.auth.v1";
|
|
126
127
|
export declare const AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY = "oxy.auth.automatic-identity-signin-suppressed.v1";
|
|
128
|
+
/**
|
|
129
|
+
* What `ownKeychainItemPlan` (ADR 0062 D6) must know about a key a native
|
|
130
|
+
* session store keeps — this module's three and the identity pin: every Oxy app
|
|
131
|
+
* writes it under the same name, so on iOS a copy in the shared group may be
|
|
132
|
+
* another app's session and is never read as this app's. The sign-out marker
|
|
133
|
+
* can only withhold, so a shared copy of it still counts.
|
|
134
|
+
*/
|
|
135
|
+
export declare function nativeSessionItemNames(key: string): OwnKeychainItemNames;
|
|
127
136
|
/**
|
|
128
137
|
* Versioned BEST-EFFORT warm-token storage key. Holds the short-lived
|
|
129
138
|
* `{ accessToken, expiresAt }` pair only. Its write is genuinely non-fatal — a
|
|
@@ -25,12 +25,12 @@ export { resolveAccountDelegationAccess } from './accountDelegationAccess';
|
|
|
25
25
|
export type { AccountDelegationAccess, AccountDelegationNode, } from './accountDelegationAccess';
|
|
26
26
|
export { AccountDialogController, createAccountDialogController, } from './accountDialogController';
|
|
27
27
|
export type { AccountDialogControllerOptions, AccountDialogSnapshot, AccountDialogView, CommonsAvailability, ContextChoiceOutcome, SignInFailureReason, SignInFlowPhase, SignInFlowState, SignInProgress, } from './accountDialogController';
|
|
28
|
-
export { createWebAuthStateStore, createNativeAuthStateStore, createMemoryAuthStateStore, AUTH_STATE_STORAGE_KEY, } from './authStateStore';
|
|
28
|
+
export { createWebAuthStateStore, createNativeAuthStateStore, createMemoryAuthStateStore, nativeSessionItemNames, AUTH_STATE_STORAGE_KEY, AUTH_STATE_TOKEN_STORAGE_KEY, AUTOMATIC_IDENTITY_SIGNIN_SUPPRESSED_KEY, } from './authStateStore';
|
|
29
29
|
export type { PersistedAuthState, AuthStateStore, AuthStateWriteGuard, NativeKeyValueStorage, } from './authStateStore';
|
|
30
30
|
export { createSharedMirroringAuthStateStore, decideSharedDeviceJoin, decideSharedDevicePublish, normalizeSharedDeviceSessionRead, publishProvenDeviceCredential, readLocalDeviceCredential, } from './sharedDeviceCredential';
|
|
31
31
|
export type { SharedDeviceCredential, SharedDeviceCredentialRead, SharedDeviceCredentialStore, SharedDeviceJoinDecision, SharedDeviceJoinSkipReason, SharedDevicePublishDecision, SharedDevicePublishOutcome, SharedDevicePublishSkipReason, } from './sharedDeviceCredential';
|
|
32
32
|
export { KeychainAccessUnavailableError, keychainAccessGroupValue, ownKeychainItemPlan, readThroughPlan, deleteThroughPlan, resolveKeychainAccess, } from '../utils/keychainAccessGroups';
|
|
33
|
-
export type { KeychainAccess, KeychainGroupOption } from '../utils/keychainAccessGroups';
|
|
33
|
+
export type { KeychainAccess, KeychainGroupOption, OwnKeychainItemNames, } from '../utils/keychainAccessGroups';
|
|
34
34
|
export { createWebIdentityPinStore, createNativeIdentityPinStore, createMemoryIdentityPinStore, identityPinMatches, IDENTITY_PIN_STORAGE_KEY, } from './identityPin';
|
|
35
35
|
export type { IdentityPin, IdentityPinStore } from './identityPin';
|
|
36
36
|
export { resolveIdentityPin, establishIdentitySession, } from './identitySession';
|
|
@@ -4,14 +4,31 @@ export declare const KEYCHAIN_ACCESS_GROUPS: {
|
|
|
4
4
|
readonly wallet: "so.oxy.wallet";
|
|
5
5
|
readonly shared: "group.so.oxy.shared";
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* The suffix a DEVELOPMENT build's restricted groups carry (`so.oxy.commons.dev`,
|
|
9
|
+
* `to.peable.app.dev`: `so.oxy.identity.dev`, `so.oxy.wallet.dev`). A dev build
|
|
10
|
+
* sits beside the production app on the same phone, is signed by the same team
|
|
11
|
+
* and runs JavaScript from a dev server, so it never shares a restricted group
|
|
12
|
+
* with production (`@oxy.so/app-preset` enforces the entitlements). The shared
|
|
13
|
+
* group has no variant: it holds nothing secret.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEV_KEYCHAIN_GROUP_SUFFIX: ".dev";
|
|
7
16
|
export type KeychainAccessGroupName = keyof typeof KEYCHAIN_ACCESS_GROUPS;
|
|
17
|
+
/**
|
|
18
|
+
* Every group an item class can be assigned: the three Oxy groups, or `app`,
|
|
19
|
+
* this app's own group, whose name is the app's and so is not a constant.
|
|
20
|
+
*/
|
|
21
|
+
export type KeychainGroup = KeychainAccessGroupName | 'app';
|
|
8
22
|
/** What a keychain item IS, which decides its group. See the table above. */
|
|
9
|
-
export type KeychainItemClass = 'identity-secret' | 'spending-key' | 'shared';
|
|
23
|
+
export type KeychainItemClass = 'identity-secret' | 'spending-key' | 'shared' | 'app';
|
|
10
24
|
/** Which holder this binary is, from its default access group. */
|
|
11
25
|
export type KeychainRole = 'holder' | 'wallet' | 'member';
|
|
12
26
|
/**
|
|
13
27
|
* - `grouped`: iOS with the probe answered. `prefix` ends with its dot
|
|
14
|
-
* (`ABCDE12345.`); `defaultGroup` is the full default group string
|
|
28
|
+
* (`ABCDE12345.`); `defaultGroup` is the full default group string;
|
|
29
|
+
* `appGroup`, when the native module proved it, this app's own group
|
|
30
|
+
* (`ABCDE12345.so.oxy.mention`). Absent on a native build that predates that
|
|
31
|
+
* probe: see {@link ownKeychainItemPlan} for what such a build does.
|
|
15
32
|
* - `ungrouped`: Android, web, or an iOS native build without the probe.
|
|
16
33
|
*/
|
|
17
34
|
export type KeychainAccess = {
|
|
@@ -19,6 +36,9 @@ export type KeychainAccess = {
|
|
|
19
36
|
prefix: string;
|
|
20
37
|
defaultGroup: string;
|
|
21
38
|
role: KeychainRole;
|
|
39
|
+
appGroup?: string;
|
|
40
|
+
/** {@link DEV_KEYCHAIN_GROUP_SUFFIX} for a dev build; absent for production. */
|
|
41
|
+
variant?: typeof DEV_KEYCHAIN_GROUP_SUFFIX;
|
|
22
42
|
} | {
|
|
23
43
|
mode: 'ungrouped';
|
|
24
44
|
};
|
|
@@ -36,31 +56,55 @@ export declare class KeychainAccessUnavailableError extends Error {
|
|
|
36
56
|
* The single place each item class is assigned a group. A new kind of item
|
|
37
57
|
* (F1's retained keys, for one) picks its class and reuses everything below.
|
|
38
58
|
*/
|
|
39
|
-
export declare function keychainGroupFor(item: KeychainItemClass): KeychainAccessGroupName;
|
|
59
|
+
export declare function keychainGroupFor(item: Exclude<KeychainItemClass, 'app'>): KeychainAccessGroupName;
|
|
60
|
+
export declare function keychainGroupFor(item: KeychainItemClass): KeychainGroup;
|
|
61
|
+
/**
|
|
62
|
+
* Whether `role` may WRITE `item`. Only Commons writes a secret: the Peable
|
|
63
|
+
* wallet reads, derives and signs with spending keys (and the retired ring)
|
|
64
|
+
* but never adds one — a key the wallet could add is one the wallet could
|
|
65
|
+
* choose. (The keychain itself lets every app in the wallet group write it;
|
|
66
|
+
* this is the SDK's half, and readers check what they find, ADR 0062.)
|
|
67
|
+
*/
|
|
68
|
+
export declare function mayWriteKeychainItem(role: KeychainRole, item: KeychainItemClass): boolean;
|
|
40
69
|
/** Whether `role` holds the group `item` lives in. */
|
|
41
70
|
export declare function mayHoldKeychainItem(role: KeychainRole, item: KeychainItemClass): boolean;
|
|
42
|
-
/**
|
|
43
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Parse the default access group the probe reported, and the app's own group
|
|
73
|
+
* when the second probe proved it. Exported for tests.
|
|
74
|
+
*/
|
|
75
|
+
export declare function keychainAccessFromDefaultGroup(defaultGroup: string, appGroup?: string): KeychainAccess;
|
|
44
76
|
/**
|
|
45
77
|
* This binary's keychain access, memoized once known. THROWS
|
|
46
78
|
* {@link KeychainAccessUnavailableError} while the probe cannot answer, which
|
|
47
79
|
* every caller already treats as "storage unavailable, retry".
|
|
48
80
|
*/
|
|
49
81
|
export declare function resolveKeychainAccess(): Promise<KeychainAccess>;
|
|
50
|
-
/** Forget the memoized access. Tests only. @internal */
|
|
82
|
+
/** Forget the memoized access and the fallback state. Tests only. @internal */
|
|
51
83
|
export declare function resetKeychainAccessForTests(): void;
|
|
52
|
-
/** The full `kSecAttrAccessGroup` value of `group`, or `undefined` when ungrouped. */
|
|
53
|
-
export declare function keychainAccessGroupValue(access: KeychainAccess, group: KeychainAccessGroupName): string | undefined;
|
|
54
84
|
/**
|
|
55
|
-
* The
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
|
|
85
|
+
* The full `kSecAttrAccessGroup` value of `group`, or `undefined` for "no
|
|
86
|
+
* group" (the app's default): always when ungrouped, and for `app` on a build
|
|
87
|
+
* that cannot place items there (see {@link ownKeychainItemPlan}).
|
|
88
|
+
*/
|
|
89
|
+
export declare function keychainAccessGroupValue(access: KeychainAccess, group: KeychainGroup): string | undefined;
|
|
90
|
+
/** The migration's switch for Commons' fallback. @internal */
|
|
91
|
+
export declare function setHolderSharedSecretFallback(open: boolean): void;
|
|
92
|
+
/** The wallet saw Commons' completion record: no shared fallback again in this process. @internal */
|
|
93
|
+
export declare function closeWalletSharedSecretFallback(): void;
|
|
94
|
+
/**
|
|
95
|
+
* The option bags a READ of `item` tries, in order: its own group, then — for
|
|
96
|
+
* an identity secret or a spending key, and only while
|
|
97
|
+
* {@link sharedFallbackOpen} says so — the shared group, where those lived
|
|
98
|
+
* before the split. Empty when this app may not hold the item: the caller
|
|
99
|
+
* answers `null` without touching the keychain. Evaluated per read: the
|
|
100
|
+
* fallback closes during a process's life. An app's own items (`app`) take
|
|
101
|
+
* {@link ownKeychainItemPlan}.
|
|
59
102
|
*/
|
|
60
103
|
export declare function keychainReadPlan(access: KeychainAccess, item: KeychainItemClass): KeychainGroupOption[];
|
|
61
104
|
/**
|
|
62
|
-
* Every place a DELETE of `item` must reach:
|
|
63
|
-
*
|
|
105
|
+
* Every place a DELETE of `item` must reach: its own group AND the shared
|
|
106
|
+
* group, whatever the read fallback says, so a delete never leaves a copy
|
|
107
|
+
* behind where every Oxy app can read it.
|
|
64
108
|
*/
|
|
65
109
|
export declare function keychainDeletePlan(access: KeychainAccess, item: KeychainItemClass): KeychainGroupOption[];
|
|
66
110
|
/**
|
|
@@ -70,17 +114,51 @@ export declare function keychainDeletePlan(access: KeychainAccess, item: Keychai
|
|
|
70
114
|
*/
|
|
71
115
|
export declare function keychainWriteOption(access: KeychainAccess, item: KeychainItemClass): KeychainGroupOption;
|
|
72
116
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
117
|
+
* Who else writes an own item's NAME, which decides whose an earlier copy of
|
|
118
|
+
* it can be.
|
|
119
|
+
*
|
|
120
|
+
* - `this-app`: no other app uses the name (the Peable wallet's own items). A
|
|
121
|
+
* copy anywhere this app can read is its own.
|
|
122
|
+
* - `every-oxy-app`: every Oxy app writes the same name (the SDK's session
|
|
123
|
+
* store: `oxy.auth.v1`, the warm token, the identity pin). Before the
|
|
124
|
+
* app group, every iOS app but Commons and the Peable wallet wrote it to the
|
|
125
|
+
* ONE copy in the shared group, so a copy there may be another app's
|
|
126
|
+
* session, or Commons' vault session. Only this app's own locations are
|
|
127
|
+
* read, written or deleted; the shared copy is left to the apps still
|
|
128
|
+
* running a build that uses it.
|
|
129
|
+
* - `every-oxy-app-withholding`: as `every-oxy-app`, for a value whose only
|
|
130
|
+
* effect is to WITHHOLD (the explicit sign-out marker). A shared copy still
|
|
131
|
+
* counts — honouring another app's marker can keep this app from signing in
|
|
132
|
+
* automatically, never sign it in — and its delete reaches that copy, as a
|
|
133
|
+
* release always did before.
|
|
134
|
+
*/
|
|
135
|
+
export type OwnKeychainItemNames = 'this-app' | 'every-oxy-app' | 'every-oxy-app-withholding';
|
|
136
|
+
/**
|
|
137
|
+
* The plan for an app's OWN items (class `app`, ADR 0062 D6): written to the
|
|
138
|
+
* app's own group (`<prefix><bundle id>`), which no other app can read, and
|
|
139
|
+
* read from it FIRST. Then:
|
|
140
|
+
*
|
|
141
|
+
* - from where this app kept them before: Commons' and the Peable wallet's
|
|
142
|
+
* default group (D3), which no other app writes its own items to;
|
|
143
|
+
* - for `this-app` names, from every group (`{}`), where they were before the
|
|
144
|
+
* split; deletes reach every group;
|
|
145
|
+
* - for `every-oxy-app` names, nowhere else (see {@link OwnKeychainItemNames}),
|
|
146
|
+
* and deletes reach exactly the read locations, earlier ones first, so a
|
|
147
|
+
* failed delete never leaves an older copy as the only one;
|
|
148
|
+
* - for `every-oxy-app-withholding` names, the shared group last on read and
|
|
149
|
+
* first on delete.
|
|
150
|
+
*
|
|
151
|
+
* Nothing is copied or deleted to migrate: the next write lands in the app
|
|
152
|
+
* group and the read order makes it win, so a crash or a failure at any step
|
|
153
|
+
* leaves the value the app had.
|
|
80
154
|
*
|
|
81
|
-
*
|
|
155
|
+
* A native build whose module predates the app-group probe (`appGroup`
|
|
156
|
+
* absent) keeps its items where that build put them: Commons and the wallet in
|
|
157
|
+
* their default group, every other app the calls it always made (the one
|
|
158
|
+
* shared item, until that app's next native build). Android and web: no
|
|
159
|
+
* options.
|
|
82
160
|
*/
|
|
83
|
-
export declare function ownKeychainItemPlan(access: KeychainAccess): {
|
|
161
|
+
export declare function ownKeychainItemPlan(access: KeychainAccess, names?: OwnKeychainItemNames): {
|
|
84
162
|
read: KeychainGroupOption[];
|
|
85
163
|
write: KeychainGroupOption;
|
|
86
164
|
delete: KeychainGroupOption[];
|
|
@@ -97,8 +175,12 @@ export declare function readThroughPlan(store: KeychainStoreLike, key: string, o
|
|
|
97
175
|
export declare function deleteThroughPlan(store: KeychainStoreLike, key: string, options: object, plan: readonly KeychainGroupOption[]): Promise<void>;
|
|
98
176
|
/**
|
|
99
177
|
* `store` with every call routed to `item`'s groups for this app: reads walk
|
|
100
|
-
* the read plan, writes carry the write option (throwing
|
|
101
|
-
* not
|
|
102
|
-
*
|
|
178
|
+
* the read plan (evaluated per read), writes carry the write option (throwing
|
|
179
|
+
* for a role that may not write the item), deletes reach every location. A
|
|
180
|
+
* secret WRITE also deletes the item's copy in the shared group (R4-6): a value
|
|
181
|
+
* Commons writes replaces whatever any app could read there, and a copy that
|
|
182
|
+
* cannot be removed fails the write instead of staying readable. The constants
|
|
183
|
+
* of the wrapped store are kept, so `store.WHEN_UNLOCKED_THIS_DEVICE_ONLY` still
|
|
184
|
+
* reads.
|
|
103
185
|
*/
|
|
104
186
|
export declare function groupedKeychainStore<S extends KeychainStoreLike>(store: S, access: KeychainAccess, item: KeychainItemClass): S;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.32.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",
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
"@noble/ciphers": "^2.4.0",
|
|
183
183
|
"@noble/hashes": "^2.4.0",
|
|
184
184
|
"@oxy.so/contracts": "^6.12.0",
|
|
185
|
-
"@oxy.so/protocol": "^1.
|
|
185
|
+
"@oxy.so/protocol": "^1.10.0",
|
|
186
186
|
"@oxy.so/telemetry": "^2.1.0",
|
|
187
187
|
"@scure/bip39": "^2.4.0",
|
|
188
188
|
"buffer": "^6.0.3",
|