feat(vault): vault storage (user-isolated, multi-account)

This commit is contained in:
Ada
2026-02-01 11:57:16 -08:00
parent 8e6c621f7b
commit 50e78c84c9
3 changed files with 40 additions and 19 deletions

View File

@@ -25,7 +25,7 @@ import { VaultAsset, VaultAssetType, Heir } from '../types';
import BiometricModal from '../components/common/BiometricModal';
import { useAuth } from '../context/AuthContext';
import { useVaultAssets } from '../hooks/useVaultAssets';
import { VAULT_STORAGE_KEYS } from '../config';
import { getVaultStorageKeys } from '../config';
import { mnemonicToEntropy, splitSecret, serializeShare } from '../utils/sss';
// Asset type configuration with nautical theme
@@ -183,15 +183,16 @@ export default function VaultScreen() {
const [progressIndex, setProgressIndex] = useState(0);
const [progressAnim] = useState(new Animated.Value(0));
const { user, token } = useAuth();
const vaultKeys = React.useMemo(() => getVaultStorageKeys(user?.id ?? null), [user?.id]);
const [isCapturing, setIsCapturing] = useState(false);
const [treasureContent, setTreasureContent] = useState('');
const mnemonicRef = useRef<View>(null);
const progressTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
// Detect S0 (TEE/SE): if present, later open shows biometric only; if not, mnemonic flow
// Detect S0 (TEE/SE) for current user: if present, later open shows biometric only; if not, mnemonic flow
useEffect(() => {
let cancelled = false;
AsyncStorage.getItem(VAULT_STORAGE_KEYS.SHARE_DEVICE)
AsyncStorage.getItem(vaultKeys.SHARE_DEVICE)
.then((v) => {
if (!cancelled) setHasS0(!!v);
})
@@ -199,7 +200,7 @@ export default function VaultScreen() {
if (!cancelled) setHasS0(false);
});
return () => { cancelled = true; };
}, []);
}, [vaultKeys.SHARE_DEVICE]);
// Only when S0 exists and vault not unlocked: show biometric after short delay.
// When hasS0 is false or null, never show biometric — go straight to mnemonic flow.
@@ -379,9 +380,9 @@ export default function VaultScreen() {
const entropy = mnemonicToEntropy(mnemonicWords, wordList);
const shares = splitSecret(entropy);
const s0 = shares[0]; // device share (S0)
// S0 is stored in AsyncStorage under SHARE_DEVICE — app-level storage, not hardware TEE/SE
await AsyncStorage.setItem(VAULT_STORAGE_KEYS.SHARE_DEVICE, serializeShare(s0));
await AsyncStorage.setItem(VAULT_STORAGE_KEYS.INITIALIZED, '1');
// S0 is stored in AsyncStorage under user-scoped key — app-level storage, not hardware TEE/SE
await AsyncStorage.setItem(vaultKeys.SHARE_DEVICE, serializeShare(s0));
await AsyncStorage.setItem(vaultKeys.INITIALIZED, '1');
setHasS0(true);
setShowMnemonic(false);
setShowBiometric(true);