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 MPC keysharesauth-svc backend
Face biometric templateFaceTec servers

Backup

How it works

Code walkthrough

The backup logic lives in queries/api/mpc-keys/useBackupWallet.ts:

// 1. Request Google Drive access token
const accessToken = await requestGoogleDriveAccessToken();
const gdrive = new GoogleDrive(accessToken);

// 2. Generate a random password and AES-encrypt every enabled keyshare
const randomPassword = await encryptionService.generateRandomPassword();
const backups = await Promise.all(
supportedProtocols.map(async (protocol) => {
const keyId = wallet[protocol].mpcShareId;
const share = await storageProvider.getKeyshare(keyId);
const shareData = formatBackup({ value: share, keyType: protocol });
const encrypted = await encryptionService.encryptData(shareData, randomPassword);
return { keyId, encrypted };
}),
);

// 3. 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

// 4. Upload encrypted keyshares to auth-svc
await APIClient.saveBackup({
keyshares: backups.map(({ keyId, encrypted }) => ({ key_id: keyId, encrypted_keyshare: encrypted })),
});

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 queries/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 and restore each keyshare with the key from Google Drive
const restored = await Promise.all(
keyshares.map(async (k) => {
const decrypted = await encryptionService.decryptData(k.encrypted_keyshare, encryptionKey);
const data = restoreBackup(decrypted);
const [type, key] = await createKeyshareObject(data);
await storageProvider.setKeyshare(key.keyIdHex, data.value);
return { type, key };
}),
);

// 3. Update wallet store with recovered share IDs
for (const protocol of supportedProtocols) {
const entry = restored.find((r) => r.type === protocol);
switch (protocol) {
case Algorithms.ecdsa:
setEcdsaMpcShare(entry.key);
break;
case Algorithms.eddsa:
setEddsaMpcShare(entry.key);
break;
case Algorithms.mldsa:
setMldsaMpcShare(entry.key);
break;
}
}
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.