@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
package/dist/cjs/api/identity.js
CHANGED
|
@@ -59,6 +59,7 @@ const client_1 = require("@oxy.so/contracts/client");
|
|
|
59
59
|
const client_2 = require("@oxy.so/contracts/client");
|
|
60
60
|
const platform_1 = require("../utils/platform");
|
|
61
61
|
const logger_1 = require("../logger");
|
|
62
|
+
const errorUtils_1 = require("../utils/errorUtils");
|
|
62
63
|
/** Short-TTL read cache for public identity reads. */
|
|
63
64
|
const SHORT_TTL = 60 * 1000;
|
|
64
65
|
/**
|
|
@@ -354,10 +355,31 @@ class IdentityApi {
|
|
|
354
355
|
}
|
|
355
356
|
}
|
|
356
357
|
}
|
|
358
|
+
// 8. The rotation deleted the encrypted backup (it was sealed under the
|
|
359
|
+
// replaced phrase). Seal a new one under the new phrase, carrying the
|
|
360
|
+
// retired ring — now with the key just replaced — so the coins under it
|
|
361
|
+
// survive a lost phone as long as the NEW phrase does (R4-4). Always,
|
|
362
|
+
// not only when one existed: after a rotation it is the only copy of the
|
|
363
|
+
// replaced key off this phone. Best-effort: the rotation already succeeded.
|
|
364
|
+
let backupRefreshed;
|
|
365
|
+
if (!(0, platform_1.isWeb)() && !localPersistFailed) {
|
|
366
|
+
try {
|
|
367
|
+
await this.backup.create(pending.phrase);
|
|
368
|
+
backupRefreshed = true;
|
|
369
|
+
}
|
|
370
|
+
catch (backupError) {
|
|
371
|
+
backupRefreshed = false;
|
|
372
|
+
logger_1.logger.warn('Key rotated, but the encrypted backup could not be re-created under the new phrase.', { component: 'oxy.identity', method: 'rotateKey' }, backupError);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
357
375
|
this.invalidate(userId);
|
|
358
|
-
return
|
|
359
|
-
|
|
360
|
-
|
|
376
|
+
return {
|
|
377
|
+
newPublicKey,
|
|
378
|
+
newPhrase: pending.phrase,
|
|
379
|
+
words: pending.words,
|
|
380
|
+
...(localPersistFailed ? { localPersistFailed: true } : {}),
|
|
381
|
+
...(backupRefreshed === undefined ? {} : { backupRefreshed }),
|
|
382
|
+
};
|
|
361
383
|
}
|
|
362
384
|
/**
|
|
363
385
|
* The signed-in user's signed, open-format data-export bundle
|
|
@@ -549,6 +571,10 @@ const BACKUP_ALGORITHM = 'xchacha20poly1305';
|
|
|
549
571
|
* to recognise WHICH identity a backup belongs to, but only a prefix.
|
|
550
572
|
*/
|
|
551
573
|
const PUBLIC_KEY_HINT_LENGTH = 16;
|
|
574
|
+
/** Whether a request error is the restore lookup's constant-shape 404. */
|
|
575
|
+
function isNotFound(error) {
|
|
576
|
+
return (0, errorUtils_1.extractErrorStatus)(error) === 404;
|
|
577
|
+
}
|
|
552
578
|
/** Encode bytes as lowercase hex (cross-platform, no Buffer dependency). */
|
|
553
579
|
function toHex(bytes) {
|
|
554
580
|
let out = '';
|
|
@@ -608,7 +634,13 @@ class IdentityBackupApi {
|
|
|
608
634
|
const publicKey = KeyManager.derivePublicKey(privateKey);
|
|
609
635
|
const createdAt = new Date().toISOString();
|
|
610
636
|
const publicKeyHint = publicKey.slice(0, PUBLIC_KEY_HINT_LENGTH);
|
|
611
|
-
const
|
|
637
|
+
const retiredKeys = await this.retiredKeysToCarry(publicKey);
|
|
638
|
+
const payload = {
|
|
639
|
+
privateKey,
|
|
640
|
+
publicKey,
|
|
641
|
+
createdAt,
|
|
642
|
+
...(retiredKeys.length > 0 ? { retiredKeys } : {}),
|
|
643
|
+
};
|
|
612
644
|
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
613
645
|
const aad = buildBackupAad(BACKUP_ENVELOPE_VERSION, publicKeyHint);
|
|
614
646
|
const { nonce, ciphertext } = encryptAead(backupKey, plaintext, aad);
|
|
@@ -626,6 +658,107 @@ class IdentityBackupApi {
|
|
|
626
658
|
cache: false,
|
|
627
659
|
});
|
|
628
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* The retired ring this device holds for the person, limited to keys Oxy's
|
|
663
|
+
* key history names as THIS account's retired keys (the ring's iOS group is
|
|
664
|
+
* writable by the wallet app too, and a previous person's ring can outlive a
|
|
665
|
+
* "Start over"). Empty where no ring is held (web, any app but Commons).
|
|
666
|
+
* Throws when the history cannot be read while a ring exists, so a backup
|
|
667
|
+
* never silently drops keys it should carry.
|
|
668
|
+
*/
|
|
669
|
+
async retiredKeysToCarry(currentPublicKey) {
|
|
670
|
+
if ((0, platform_1.isWeb)())
|
|
671
|
+
return [];
|
|
672
|
+
const { exportRetiredIdentityKeys } = await Promise.resolve().then(() => __importStar(require('../crypto/index')));
|
|
673
|
+
let held;
|
|
674
|
+
try {
|
|
675
|
+
held = await exportRetiredIdentityKeys();
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return []; // Not Commons: this app holds no ring to carry.
|
|
679
|
+
}
|
|
680
|
+
if (held.length === 0)
|
|
681
|
+
return [];
|
|
682
|
+
const history = await this.ctx.request('GET', '/identity/key-history', undefined, {
|
|
683
|
+
cache: false,
|
|
684
|
+
});
|
|
685
|
+
const parsed = (0, client_1.checkContract)(client_2.identityKeyHistoryCheck, history);
|
|
686
|
+
if (!parsed)
|
|
687
|
+
throw new Error('identity/key-history returned an unexpected response shape');
|
|
688
|
+
const retired = new Set(parsed.retired.map((entry) => entry.publicKey.toLowerCase()));
|
|
689
|
+
const current = currentPublicKey.toLowerCase();
|
|
690
|
+
return held.filter((key) => key.publicKey.toLowerCase() !== current && retired.has(key.publicKey.toLowerCase()));
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Fetch and open the backup the phrase locates, checking it is the phrase's
|
|
694
|
+
* own identity. With `missingIsNull`, `null` when there is none (404);
|
|
695
|
+
* otherwise the request's own error propagates.
|
|
696
|
+
*/
|
|
697
|
+
async open(phrase, missingIsNull = false) {
|
|
698
|
+
const { KeyManager, RecoveryPhraseService, decryptAead } = await Promise.resolve().then(() => __importStar(require('../crypto/index')));
|
|
699
|
+
const { backupKey, lookupId } = await RecoveryPhraseService.deriveBackupMaterial(phrase);
|
|
700
|
+
let envelope;
|
|
701
|
+
try {
|
|
702
|
+
envelope = await this.ctx.request('GET', `/identity/backup/${encodeURIComponent(lookupId)}`, undefined, { cache: false });
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
if (missingIsNull && isNotFound(error))
|
|
706
|
+
return null;
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
if (envelope.algorithm !== BACKUP_ALGORITHM) {
|
|
710
|
+
throw new Error(`Unsupported backup algorithm: ${envelope.algorithm}`);
|
|
711
|
+
}
|
|
712
|
+
const aad = buildBackupAad(envelope.version, envelope.publicKeyHint);
|
|
713
|
+
const plaintext = decryptAead(backupKey, fromHex(envelope.nonce), fromHex(envelope.ciphertext), aad);
|
|
714
|
+
const payload = JSON.parse(new TextDecoder().decode(plaintext));
|
|
715
|
+
if (!payload.privateKey || !payload.publicKey) {
|
|
716
|
+
throw new Error('Backup payload is missing key material');
|
|
717
|
+
}
|
|
718
|
+
const phrasePk = (await RecoveryPhraseService.derivePublicKeyFromPhrase(phrase)).toLowerCase();
|
|
719
|
+
const payloadPk = payload.publicKey.toLowerCase();
|
|
720
|
+
const privatePk = KeyManager.derivePublicKey(payload.privateKey).toLowerCase();
|
|
721
|
+
if (phrasePk !== payloadPk || privatePk !== payloadPk) {
|
|
722
|
+
throw new Error('Backup payload does not match the recovery phrase');
|
|
723
|
+
}
|
|
724
|
+
return payload;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Keep every retired key a backup carries in this device's ring. Each pair
|
|
728
|
+
* must be healthy and must not be the current key; the envelope itself is
|
|
729
|
+
* authenticated by the phrase. Commons only (the ring refuses elsewhere).
|
|
730
|
+
* Resolves how many keys are now held; a key that fails is skipped.
|
|
731
|
+
*/
|
|
732
|
+
async keepCarriedRetiredKeys(payload) {
|
|
733
|
+
const { KeyManager, retireIdentityKey } = await Promise.resolve().then(() => __importStar(require('../crypto/index')));
|
|
734
|
+
let kept = 0;
|
|
735
|
+
for (const key of Array.isArray(payload.retiredKeys) ? payload.retiredKeys : []) {
|
|
736
|
+
try {
|
|
737
|
+
if (typeof key?.privateKey !== 'string' || typeof key.publicKey !== 'string')
|
|
738
|
+
continue;
|
|
739
|
+
if (key.publicKey.toLowerCase() === payload.publicKey.toLowerCase())
|
|
740
|
+
continue;
|
|
741
|
+
if (KeyManager.derivePublicKey(key.privateKey).toLowerCase() !== key.publicKey.toLowerCase()) {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
await retireIdentityKey({ privateKey: key.privateKey, publicKey: key.publicKey });
|
|
745
|
+
kept += 1;
|
|
746
|
+
}
|
|
747
|
+
catch (error) {
|
|
748
|
+
logger_1.logger.warn('Could not keep a retired key from the encrypted backup', { component: 'oxy.identity', method: 'backup.restoreRetiredKeys' }, error);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return kept;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Restore ONLY the retired keys the phrase's backup carries into this
|
|
755
|
+
* device's ring (Commons, after a phrase import or a silent device-backup
|
|
756
|
+
* restore). Resolves how many were kept; 0 when there is no backup.
|
|
757
|
+
*/
|
|
758
|
+
async restoreRetiredKeys(phrase) {
|
|
759
|
+
const payload = await this.open(phrase, true);
|
|
760
|
+
return payload ? this.keepCarriedRetiredKeys(payload) : 0;
|
|
761
|
+
}
|
|
629
762
|
/**
|
|
630
763
|
* Whether the signed-in user has a stored backup, plus its non-sensitive hint
|
|
631
764
|
* and timestamp. Returns no ciphertext and no locator.
|
|
@@ -656,25 +789,15 @@ class IdentityBackupApi {
|
|
|
656
789
|
* fails authentication (tamper), or an existing identity blocks the import.
|
|
657
790
|
*/
|
|
658
791
|
async restore(phrase, options) {
|
|
659
|
-
const { KeyManager
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (!payload.privateKey || !payload.publicKey) {
|
|
669
|
-
throw new Error('Backup payload is missing key material');
|
|
670
|
-
}
|
|
671
|
-
const phrasePk = (await RecoveryPhraseService.derivePublicKeyFromPhrase(phrase)).toLowerCase();
|
|
672
|
-
const payloadPk = payload.publicKey.toLowerCase();
|
|
673
|
-
const privatePk = KeyManager.derivePublicKey(payload.privateKey).toLowerCase();
|
|
674
|
-
if (phrasePk !== payloadPk || privatePk !== payloadPk) {
|
|
675
|
-
throw new Error('Backup payload does not match the recovery phrase');
|
|
676
|
-
}
|
|
677
|
-
return KeyManager.importKeyPair(payload.privateKey, { overwrite: options?.overwrite === true });
|
|
792
|
+
const { KeyManager } = await Promise.resolve().then(() => __importStar(require('../crypto/index')));
|
|
793
|
+
const payload = await this.open(phrase);
|
|
794
|
+
if (!payload)
|
|
795
|
+
throw new Error('No encrypted backup was found for this recovery phrase.');
|
|
796
|
+
const publicKey = await KeyManager.importKeyPair(payload.privateKey, {
|
|
797
|
+
overwrite: options?.overwrite === true,
|
|
798
|
+
});
|
|
799
|
+
await this.keepCarriedRetiredKeys(payload);
|
|
800
|
+
return publicKey;
|
|
678
801
|
}
|
|
679
802
|
}
|
|
680
803
|
exports.IdentityBackupApi = IdentityBackupApi;
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
31
|
exports.IDENTITY_MARKER_STORAGE_KEY = void 0;
|
|
32
32
|
exports.readIdentityMarker = readIdentityMarker;
|
|
33
|
+
exports.readIdentityMarkerStrict = readIdentityMarkerStrict;
|
|
33
34
|
exports.writeIdentityMarker = writeIdentityMarker;
|
|
34
35
|
exports.updateIdentityMarker = updateIdentityMarker;
|
|
35
36
|
exports.clearIdentityMarker = clearIdentityMarker;
|
|
@@ -152,6 +153,20 @@ async function readIdentityMarker() {
|
|
|
152
153
|
return null;
|
|
153
154
|
}
|
|
154
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Read the marker, THROWING when the storage cannot answer (instead of the
|
|
158
|
+
* fail-open `null` of {@link readIdentityMarker}). For a caller that must not
|
|
159
|
+
* mistake "unreadable" for "never written": the iOS keychain migration, which
|
|
160
|
+
* adopts a pre-split secret only when this marker vouches for it, and records
|
|
161
|
+
* once that there is nothing to adopt.
|
|
162
|
+
*/
|
|
163
|
+
async function readIdentityMarkerStrict() {
|
|
164
|
+
const storage = await getStorage();
|
|
165
|
+
if (!storage) {
|
|
166
|
+
throw new Error('The identity marker storage is unavailable.');
|
|
167
|
+
}
|
|
168
|
+
return deserialize(await storage.getItem(exports.IDENTITY_MARKER_STORAGE_KEY));
|
|
169
|
+
}
|
|
155
170
|
/**
|
|
156
171
|
* Write (create/replace) the marker. Returns `true` when it durably landed,
|
|
157
172
|
* `false` when storage was unavailable or the write threw. Callers treat a
|
package/dist/cjs/crypto/index.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* common chunk, which is the cost the lazy loading exists to avoid.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.
|
|
16
|
-
exports.verifySignature = exports.signMessage = exports.loadCommonsIdentityBridge = exports.canonicalize = exports.createContentHasher = exports.solveRegistrationPow = exports.deriveIdentityLinkCode = exports.signIdentityProof = exports.digestIdentityPayload = exports.authChallengeDigest = exports.signSocialReceiveDigest = void 0;
|
|
15
|
+
exports.AEAD_NONCE_LENGTH = exports.AEAD_KEY_LENGTH = exports.decryptAead = exports.encryptAead = exports.hkdfSha256 = exports.BACKUP_KDF_ENCRYPTION_INFO = exports.RecoveryPhraseService = exports.SignatureService = exports.exportRetiredIdentityKeys = exports.retiredKeyFromPrivateKey = exports.retiredKeyFromPhrase = exports.syncRetiredKeys = exports.forgetRetiredIdentityKeys = exports.forgetRetiredIdentityKey = exports.listRetiredIdentityKeys = exports.retireIdentityKey = exports.getPreSplitExposure = exports.ensureKeychainGroupsMigrated = exports.groupedKeychainStore = exports.deleteThroughPlan = exports.readThroughPlan = exports.ownKeychainItemPlan = exports.keychainWriteOption = exports.keychainDeletePlan = exports.keychainReadPlan = exports.keychainAccessGroupValue = exports.resolveKeychainAccess = exports.DEV_KEYCHAIN_GROUP_SUFFIX = exports.mayWriteKeychainItem = exports.mayHoldKeychainItem = exports.keychainGroupFor = exports.KeychainAccessUnavailableError = exports.KEYCHAIN_ACCESS_GROUPS = exports.isHealthyOrganizationKeyPair = exports.isOrganizationAccountId = exports.syncOrganizationKeys = exports.deleteOrganizationKey = exports.listOrganizationKeys = exports.getOrganizationRecoveryPhrase = exports.getOrganizationPrivateKey = exports.getOrganizationPublicKey = exports.storeOrganizationKey = exports.organizationKeyFromPhrase = exports.generateOrganizationKey = exports.updateIdentityMarker = exports.readIdentityMarker = exports.IdentityUnavailableError = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = void 0;
|
|
16
|
+
exports.verifySignature = exports.signMessage = exports.loadCommonsIdentityBridge = exports.canonicalize = exports.createContentHasher = exports.solveRegistrationPow = exports.deriveIdentityLinkCode = exports.signIdentityProof = exports.digestIdentityPayload = exports.authChallengeDigest = exports.signSocialReceiveDigest = exports.deriveScopedSeedFromKey = exports.MAX_SOCIAL_RECEIVE_INDEX = exports.SOCIAL_RECEIVE_CHAIN_CODE_KEY = exports.SCOPED_SEED_KDF_SALT = exports.deriveSharedSecret = void 0;
|
|
17
17
|
// MUST stay the first import: the shim has to exist before any module below
|
|
18
18
|
// evaluates or draws randomness (see `./polyfill`).
|
|
19
19
|
require("./polyfill");
|
|
@@ -44,6 +44,8 @@ Object.defineProperty(exports, "KEYCHAIN_ACCESS_GROUPS", { enumerable: true, get
|
|
|
44
44
|
Object.defineProperty(exports, "KeychainAccessUnavailableError", { enumerable: true, get: function () { return keychainAccessGroups_1.KeychainAccessUnavailableError; } });
|
|
45
45
|
Object.defineProperty(exports, "keychainGroupFor", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainGroupFor; } });
|
|
46
46
|
Object.defineProperty(exports, "mayHoldKeychainItem", { enumerable: true, get: function () { return keychainAccessGroups_1.mayHoldKeychainItem; } });
|
|
47
|
+
Object.defineProperty(exports, "mayWriteKeychainItem", { enumerable: true, get: function () { return keychainAccessGroups_1.mayWriteKeychainItem; } });
|
|
48
|
+
Object.defineProperty(exports, "DEV_KEYCHAIN_GROUP_SUFFIX", { enumerable: true, get: function () { return keychainAccessGroups_1.DEV_KEYCHAIN_GROUP_SUFFIX; } });
|
|
47
49
|
Object.defineProperty(exports, "resolveKeychainAccess", { enumerable: true, get: function () { return keychainAccessGroups_1.resolveKeychainAccess; } });
|
|
48
50
|
Object.defineProperty(exports, "keychainAccessGroupValue", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainAccessGroupValue; } });
|
|
49
51
|
Object.defineProperty(exports, "keychainReadPlan", { enumerable: true, get: function () { return keychainAccessGroups_1.keychainReadPlan; } });
|
|
@@ -55,6 +57,7 @@ Object.defineProperty(exports, "deleteThroughPlan", { enumerable: true, get: fun
|
|
|
55
57
|
Object.defineProperty(exports, "groupedKeychainStore", { enumerable: true, get: function () { return keychainAccessGroups_1.groupedKeychainStore; } });
|
|
56
58
|
var keychainGroupMigration_1 = require("./keychainGroupMigration");
|
|
57
59
|
Object.defineProperty(exports, "ensureKeychainGroupsMigrated", { enumerable: true, get: function () { return keychainGroupMigration_1.ensureKeychainGroupsMigrated; } });
|
|
60
|
+
Object.defineProperty(exports, "getPreSplitExposure", { enumerable: true, get: function () { return keychainGroupMigration_1.getPreSplitExposure; } });
|
|
58
61
|
// Retired identity keys: kept for spending after a rotation or replacement
|
|
59
62
|
// (docs/adr/0061-organization-identity-key.md D7 as amended 2026-11-06)
|
|
60
63
|
var retiredKeys_1 = require("./retiredKeys");
|
|
@@ -64,6 +67,8 @@ Object.defineProperty(exports, "forgetRetiredIdentityKey", { enumerable: true, g
|
|
|
64
67
|
Object.defineProperty(exports, "forgetRetiredIdentityKeys", { enumerable: true, get: function () { return retiredKeys_1.forgetRetiredIdentityKeys; } });
|
|
65
68
|
Object.defineProperty(exports, "syncRetiredKeys", { enumerable: true, get: function () { return retiredKeys_1.syncRetiredKeys; } });
|
|
66
69
|
Object.defineProperty(exports, "retiredKeyFromPhrase", { enumerable: true, get: function () { return retiredKeys_1.retiredKeyFromPhrase; } });
|
|
70
|
+
Object.defineProperty(exports, "retiredKeyFromPrivateKey", { enumerable: true, get: function () { return retiredKeys_1.retiredKeyFromPrivateKey; } });
|
|
71
|
+
Object.defineProperty(exports, "exportRetiredIdentityKeys", { enumerable: true, get: function () { return retiredKeys_1.exportRetiredIdentityKeys; } });
|
|
67
72
|
var signatureService_1 = require("./signatureService");
|
|
68
73
|
Object.defineProperty(exports, "SignatureService", { enumerable: true, get: function () { return signatureService_1.SignatureService; } });
|
|
69
74
|
var recoveryPhrase_1 = require("./recoveryPhrase");
|
|
@@ -211,9 +211,7 @@ async function loadRawSecureStore() {
|
|
|
211
211
|
async function keychainItemStore(item) {
|
|
212
212
|
const raw = await loadRawSecureStore();
|
|
213
213
|
const access = await (0, keychainAccessGroups_1.resolveKeychainAccess)();
|
|
214
|
-
|
|
215
|
-
await (0, keychainGroupMigration_1.ensureKeychainGroupsMigrated)();
|
|
216
|
-
}
|
|
214
|
+
await (0, keychainGroupMigration_1.prepareKeychainAccess)(access);
|
|
217
215
|
return (0, keychainAccessGroups_1.groupedKeychainStore)(raw, access, item);
|
|
218
216
|
}
|
|
219
217
|
/** The store for the identity slots (primary, backup, legacy, phrase): Commons only on iOS. */
|
|
@@ -2089,13 +2087,12 @@ class KeyManager {
|
|
|
2089
2087
|
if (force) {
|
|
2090
2088
|
await KeyManager._bestEffortDeleteBackupsAllGenerations(store);
|
|
2091
2089
|
await KeyManager._clearSharedSlot(store);
|
|
2092
|
-
// The
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
}
|
|
2090
|
+
// The retired ring is NOT cleared (R4-4): it is money under the person's
|
|
2091
|
+
// previous keys, and "Start over" or a forced delete must not cost it.
|
|
2092
|
+
// The same person restoring their identity finds it again; a different
|
|
2093
|
+
// identity on this phone cannot use it (each entry is checked against
|
|
2094
|
+
// that account's key history before use). Only deleting the Oxy ACCOUNT
|
|
2095
|
+
// clears it, explicitly (`forgetRetiredIdentityKeys`, Commons' flow).
|
|
2099
2096
|
}
|
|
2100
2097
|
// The device backup goes on EVERY delete, forced or not: it is restored
|
|
2101
2098
|
// silently, so leaving it would bring a deleted identity back on the next
|