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/backup-key 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/backup-key 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
method to make the request from the client. -
Call
duoSession.verifyBackup()
with all the required params (as explained below) which returnsResult
ofSuccess
(which represents the operation outcome) orFailure
with 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(keyshare: Data, duoSession: DuoSession) async -> Bool
{
// MARK: get key-id for keyshare to backup
let keyshareKeyIdResult = await SilentShardDuo.ECDSA.getKeyshareKeyIdAsUrlSafeBase64(keyshare)
let keyshareKeyId : String
switch keyshareKeyIdResult {
case .success(let keyshareKeyIdSuccess):
do {
// do something with key-id string : here use it for perform /v3/backup-key network request
keyshareKeyId = keyshareKeyIdSuccess
}
case .failure(let error):
do {
// show error to user or abort process
Swift.print(error)
return false
}
}
// MARK: make backup network request : get the keyshare backup from server
let backupKeyResponse = try? await getBackupData(
url: "http://\(CLOUD_NODE_URI):\(PORT)/v3/backup-key",
keyId: keyshareKeyId,
label: "test-backup"
)
let verifyResult = await duoSession.verifyBackup(
keyshare: keyshare,
backupData: Data(
base64Encoded: backupKeyResponse!.verifiableBackup)!,
rsaPublicKey: Data(rsaPublicKey.utf8),
label: "test-backup"
)
// returns nil if operation fails
switch verifyResult {
case .success :
do {
// success
return true
}
case .failure(let error):
do {
// 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(keyshare: Data, duoSession: DuoSession) async -> Bool
{
// MARK: get key-id for keyshare to backup
let keyshareKeyIdResult = await SilentShardDuo.EdDSA.getKeyshareKeyIdAsUrlSafeBase64(keyshare)
let keyshareKeyId : String
switch keyshareKeyIdResult {
case .success(let keyshareKeyIdSuccess):
do {
// do something with key-id string : here use it for perform /v3/backup-key network request
keyshareKeyId = keyshareKeyIdSuccess
}
case .failure(let error):
do {
// show error to user or abort process
Swift.print(error)
return false
}
}
// MARK: make backup network request : get the keyshare backup from server
let backupKeyResponse = try? await getBackupData(
url: "http://\(CLOUD_NODE_URI):\(PORT)/v3/backup-key",
keyId: keyshareKeyId,
label: "test-backup"
)
let verifyResult = await duoSession.verifyBackup(
keyshare: keyshare,
backupData: Data(
base64Encoded: backupKeyResponse!.verifiableBackup)!,
rsaPublicKey: Data(rsaPublicKey.utf8),
label: "test-backup"
)
// returns nil if operation fails
switch verifyResult {
case .success :
do {
// success
return true
}
case .failure(let error):
do {
// 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 verifiable-backup endpoint.
keyshare
: The client's share of the MPC wallet.verifiableBackup
: The verifiable encrypted backup of the server's keyshare.