Verifiable Backup
Verifiable Backup allows users to generate a backup of their MPC wallet that can be verified as correct without decryption. This feature enhances security and privacy in backup management.
Since backup is a sensitive operation, we don't expose the backup endpoint directly in the SDK. Instead, we provide an endpoint in the cloud node /v3/ecdsa/backup or /v3/eddsa/backup that can be used to generate the verifiable backup. We provide a method in the client SDK to verify the backup without decryption.
It's the responsibility of the company using the SDK to expose this endpoint to the user in a secure way.
For example:
- When the user requests a backup, the backend can request additional authentication from the user (e.g., 2FA) before generating the backup.
- Once the user is authenticated, the backend can get the verifiable backup from the cloud node and pass it to the user.
- The user can then verify the backup using duoSession.verifyBackup().
The /v3/ecdsa/backup or /v3/eddsa/backup endpoint is not exposed directly in the SDK. Here for the sake of simplicity, we will use the getBackupData method to make the request from the client. Do NOT expose this endpoint to the user directly in a production application.
Step 1 : Create Session
- Create DuoSession if you haven't already.
Step 2 : Perform Verification
-
Get the backup data ready. Here for the sake of simplicity, we will use the
getBackupData(as show in the example below) method to make the request from the client. -
Call duoSession.verifyBackup() with all the required params (as explained below) which returns a
ResultofSuccess(which represents the operation outcome) orFailurewith exception.
Example
- ECDSA
- EdDSA
let rsaPublicKey = """
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAp69leU/BZJUTrrdUht/fb+REtPuNIGCH8lV+98p3oZDFi7u1XLOA
ZDxanD0pqnWrZJ0DNM2g7Ve/5/zIAQAUgIrkhu7DSFQjg5OgQH2TnzDdWLofVILq
UW42YkZ+smXaur8LsvgghSAQQng8fVYfoAZC/QPVHe9PX3BG4rvuaRziXP0DcX0I
NGuzk7r++6FgDwaN8oyy/1CvCUNLfEmXytqBl9xy5ElipZAguRZVybHE2wndCWln
zC3lwmPppr8tiWMYNlJO3VjL3wgchYzoZUXBSUa3ZyocI7jLYp9qNelEWlbukYBY
TrtUcVKIxp3OfyZ0edqcR8xKD1wBQKRzLQIDAQAB
-----END RSA PUBLIC KEY-----
"""
private func performBackupAndVerifyBackup(keyId: String, duoSession: DuoSession) async -> Bool {
// MARK: make backup network request : get the keyshare backup from server.
// Address the key by its keyId (from keygen/import) — no keyshare bytes here.
let backupKeyResponse = try? await getBackupData(
url: "http://\(CLOUD_NODE_URI):\(PORT)/v3/ecdsa/backup",
keyId: keyId,
label: "test-backup"
)
let verifyResult = await duoSession.verifyBackup(
keyId: keyId,
backupData: Data(
base64Encoded: backupKeyResponse!.verifiableBackup
)!,
rsaPublicKey: Data(rsaPublicKey.utf8),
label: "test-backup"
)
// returns false if the backup is not authentic or the operation fails
switch verifyResult {
case .success(let isBackupValid):
// true if the backup is authentic, false otherwise
return isBackupValid
case .failure(let error):
// show error to user or abort process
Swift.print(error)
return false
}
}
private func getBackupData(url: String, keyId: String, label: String)
async throws -> BackupKeyResponse
{
let request = BackupKeyRequest(
keyId: keyId,
rsaPubkeyPem: rsaPublicKey,
label: label
)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let requestData = try encoder.encode(request)
let response = try await sendPostRequest(
url: URL(string: url)!,
body: requestData
)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(BackupKeyResponse.self, from: response)
}
struct BackupKeyRequest: Codable {
let keyId: String
let rsaPubkeyPem: String
let label: String
}
struct BackupKeyResponse: Codable {
let verifiableBackup: String
}
let rsaPublicKey = """
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAp69leU/BZJUTrrdUht/fb+REtPuNIGCH8lV+98p3oZDFi7u1XLOA
ZDxanD0pqnWrZJ0DNM2g7Ve/5/zIAQAUgIrkhu7DSFQjg5OgQH2TnzDdWLofVILq
UW42YkZ+smXaur8LsvgghSAQQng8fVYfoAZC/QPVHe9PX3BG4rvuaRziXP0DcX0I
NGuzk7r++6FgDwaN8oyy/1CvCUNLfEmXytqBl9xy5ElipZAguRZVybHE2wndCWln
zC3lwmPppr8tiWMYNlJO3VjL3wgchYzoZUXBSUa3ZyocI7jLYp9qNelEWlbukYBY
TrtUcVKIxp3OfyZ0edqcR8xKD1wBQKRzLQIDAQAB
-----END RSA PUBLIC KEY-----
"""
private func performBackupAndVerifyBackup(keyId: String, duoSession: DuoSession) async -> Bool {
// MARK: make backup network request : get the keyshare backup from server.
// Address the key by its keyId (from keygen/import) — no keyshare bytes here.
let backupKeyResponse = try? await getBackupData(
url: "http://\(CLOUD_NODE_URI):\(PORT)/v3/eddsa/backup",
keyId: keyId,
label: "test-backup"
)
let verifyResult = await duoSession.verifyBackup(
keyId: keyId,
backupData: Data(
base64Encoded: backupKeyResponse!.verifiableBackup
)!,
rsaPublicKey: Data(rsaPublicKey.utf8),
label: "test-backup"
)
// returns false if the backup is not authentic or the operation fails
switch verifyResult {
case .success(let isBackupValid):
// true if the backup is authentic, false otherwise
return isBackupValid
case .failure(let error):
// show error to user or abort process
Swift.print(error)
return false
}
}
private func getBackupData(url: String, keyId: String, label: String)
async throws -> BackupKeyResponse
{
let request = BackupKeyRequest(
keyId: keyId,
rsaPubkeyPem: rsaPublicKey,
label: label
)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let requestData = try encoder.encode(request)
let response = try await sendPostRequest(
url: URL(string: url)!,
body: requestData
)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(BackupKeyResponse.self, from: response)
}
struct BackupKeyRequest: Codable {
let keyId: String
let rsaPubkeyPem: String
let label: String
}
struct BackupKeyResponse: Codable {
let verifiableBackup: String
}
rsaPublicKey: The RSA public key in PEM format.label: The label used as associated data while performing RSA encryption of the server's keyshare. The label is required while decrypting/verifying the backup.- Request and Response format is documented in the /v3/ecdsa/backup or /v3/eddsa/backup endpoint.
keyId: Addresses the client's keyshare in your storage client (fromkeygen/import).verifiableBackup: The verifiable encrypted backup of the server's keyshare.