Skip to content

fix: prevent seed replacement when retrieveSeed fails during unlock#12

Open
ben-kaufman wants to merge 1 commit intotetherto:mainfrom
ben-kaufman:fix/prevent-silent-seed-replacement
Open

fix: prevent seed replacement when retrieveSeed fails during unlock#12
ben-kaufman wants to merge 1 commit intotetherto:mainfrom
ben-kaufman:fix/prevent-silent-seed-replacement

Conversation

@ben-kaufman
Copy link

@ben-kaufman ben-kaufman commented Mar 24, 2026

Problem

WDKService.createWallet() is used for both wallet creation and unlock. When retrieveSeed() returns null, it silently falls back to createSeed(), generating a new random seed and overwriting the existing one in Keychain.

During the unlock flow, unlockWallet() in wallet-context.tsx calls WDKService.createWallet() directly. If retrieveSeed() fails (biometric cancelled, Keychain corrupted after iOS passcode change, partial storage loss), the user's wallet is silently replaced. All funds become inaccessible with no error shown.

Why the fallback is dead code during creation

The context-layer createWallet() already calls WDKService.createSeed() or WDKService.importSeedPhrase() before WDKService.createWallet(). By the time the service method runs, the seed is already stored and retrieveSeed() will succeed. The createSeed() fallback never executes in the creation path.

WDKService.createWallet() main role is not to generate seeds, but to initializes the WDK worklet using a previously stored seed. Seed generation is handled upstream by createSeed() or importSeedPhrase(), which the context-layer wrapper always calls before reaching this method.

Why the fallback is destructive during unlock

unlockWallet() calls WDKService.createWallet() with no prior seed creation step. If retrieveSeed() returns null:

  1. createSeed() generates new random entropy
  2. Overwrites WDK_STORAGE_ENTROPY, WDK_STORAGE_SEED, and WDK_STORAGE_SALT in Keychain, there's no "already exists" guard
  3. Worklet starts with the new seed
  4. New addresses derived on all chains
  5. Old wallet with funds is gone, irrecoverably, unless backed up

retrieveSeed() returns null for benign reasons

It checks three Keychain entries (entropy, seed, salt). If any one is missing, for example due to partial Keychain corruption, failed migration, or biometric access timeout, it returns null. None of these warrant replacing the wallet.

Fix

In WDKService.createWallet(), replace the createSeed() fallback with a thrown error:

 let seed = await this.retrieveSeed(params.prf);
 if (!seed) {
-  seed = await this.createSeed(params);
+  throw new Error('Seed retrieval failed. Please restore from recovery phrase.');
 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant