Skip to main content

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

DataStored at
AES encryption key (random password)User's Google Drive
Encrypted ECDSA keyshareauth-svc backend
Encrypted EdDSA keyshareauth-svc backend
Face biometric templateFaceTec 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.