Backup & Recovery
MPC keyshares only exist on the device. If the app is uninstalled or the device is lost, the wallet is gone — unless it was backed up. The backup system uses a combination of FaceTec biometrics, AES encryption, Google Drive, and auth-svc to make recovery possible on a new device.
Where each piece is stored
| Data | Stored at |
|---|---|
| AES encryption key (random password) | User's Google Drive |
| Encrypted ECDSA keyshare | auth-svc backend |
| Encrypted EdDSA keyshare | auth-svc backend |
| Face biometric template | FaceTec servers |
Backup
How it works
Code walkthrough
The backup logic lives in api/mpc-keys/useBackupWallet.ts:
// 1. Request Google Drive access token
const accessToken = await requestGoogleDriveAccessToken();
const gdrive = new GoogleDrive(accessToken);
// 2. Load keyshares from secure storage
const ecdsaShare = await storageProvider.getKeyshare(ecdsa.mpcShareId);
const eddsaShare = await storageProvider.getKeyshare(eddsa.mpcShareId);
// 3. Generate a random password and AES-encrypt both keyshares
const randomPassword = await encryptionService.generateRandomPassword();
const ecdsaShareEncrypted = await encryptionService.encryptData(formatBackup(ecdsaShare), randomPassword);
const eddsaShareEncrypted = await encryptionService.encryptData(formatBackup(eddsaShare), randomPassword);
// 4. Save the password to Google Drive and verify the round-trip
await gdrive.createBackup(gdrive.getFileName(userId), randomPassword);
const backupPassword = await gdrive.retrieveBackup(gdrive.getFileName(userId));
// throws if verification fails
// 5. Upload encrypted keyshares to auth-svc
await APIClient.saveBackup({
keyshares: [
{ key_id: ecdsa.mpcShareId, encrypted_keyshare: ecdsaShareEncrypted },
{ key_id: eddsa.mpcShareId, encrypted_keyshare: eddsaShareEncrypted },
],
});
The FaceTec Enroll3D step runs on the /backup/facelock screen before this flow — it registers the user's face template on FaceTec servers so it can be matched during recovery.
Recovery
Recovery runs on a new device (or after reinstalling the app) and requires the same Google account used during backup.
How it works
Code walkthrough
The recovery logic lives in api/mpc-keys/useRecoverWallet.ts:
// encryptionKey comes from Google Drive, face_session_token from FaceTec Match3D
const { encryptionKey, ...rest } = variables;
// 1. Fetch encrypted keyshares from auth-svc (face token authorizes the request)
const recoveryResponse = await APIClient.recoverKeyshares(rest);
const { keyshares } = recoveryResponse;
// 2. Decrypt both keyshares with the key from Google Drive
const mpcKey1 = await encryptionService.decryptData(keyshares[0].encrypted_keyshare, encryptionKey);
const mpcKey2 = await encryptionService.decryptData(keyshares[1].encrypted_keyshare, encryptionKey);
// 3. Restore keyshares to secure storage
const restoredKey1 = restoreBackup(mpcKey1);
const restoredKey2 = restoreBackup(mpcKey2);
await storageProvider.setKeyshare(key1.keyIdHex, restoredKey1.value);
await storageProvider.setKeyshare(key2.keyIdHex, restoredKey2.value);
// 4. Update wallet store with recovered share IDs
setEcdsaMpcShare(key1);
setEddsaMpcShare(key2);
finishSetup();
Recovery requires the same Google account that was used when the backup was created. The encryption key file in Google Drive is tied to the user's account — signing in with a different account will not find it.