Wallet Security

View as Markdown
AssetStorageEncryption
Share 1 (private key share)expo-secure-store (iOS Keychain — kSecAttrAccessibleWhenUnlockedThisDeviceOnly; Android — Keystore-backed)AES-256-GCM with platform-provided key
Credential cacheApp sandbox file system (FileSystem.documentDirectory + 'creds.enc')AES-256-GCM, KEK derived from device-bound material via KeyStore.deriveKEK
Status List 2021 cacheSame as credential cacheSame
Tokens (access/refresh)expo-secure-storeOS-managed
Encrypted recovery package (rebuilds Share 1 after re-auth + custodian release of Share 3)Optional — exported to user-chosen sink (email, file system)AES-256-GCM, KEK derived from PBKDF2(passphrase)

At-rest invariants:

  • The wallet never stores the unsigned/plain Share 1 outside the secure enclave.
  • A locked device (biometrics not yet authenticated) cannot decrypt any of the wallet stores; the only thing visible at process start is the metadata index (DID list + display labels).
  • The recovery passphrase is never persisted to disk; it is held in volatile memory only during a backup or restore flow, then wiped via runtime.zeroize().
  • All DIDComm signing and decryption happen in a synchronous, non-yielding code path — no await between key import and key zero — to minimise the window of plaintext exposure.

Important: Expo’s SecureStore has a 2 KB per-key value limit on iOS. The wallet stores credential bodies in encrypted files, not in SecureStore, and uses SecureStore only for keys, tokens, and small metadata. Do not bypass this.