Verifiable Backup
Verifiable Backup lets users generate an encrypted backup of their MPC wallet that can be verified as correct without decryption. This means you can confirm a backup is valid before storing it, without ever exposing the private key.
Backup is not exposed directly in the client SDK. Instead, you make a POST request to the cloud node's backup endpoint to generate an encrypted backup of the server's share, then use the SDK's verify method to confirm the backup is correct.
It's your responsibility to expose this endpoint securely. For example:
- When a user requests a backup, your backend authenticates them (2FA, etc.).
- Your backend calls the cloud node's backup endpoint and returns the encrypted backup to the client.
- The client verifies the backup using the SDK's verify method.
Never expose the backup endpoint directly to users.
- ECDSA
- EdDSA
import { type EcdsaSession, ecdsaVerifyBackup } from '@silencelaboratories/silent-shard-sdk/ecdsa';
import { Platform } from 'react-native';
const CLOUD_NODE_URI = Platform.select({ android: '10.0.2.2', ios: 'localhost' }) + ':8080';
export const keyBackup = async (
session: EcdsaSession,
// RSA public key in PEM format
rsaPublicKeyPEM: string = '<RSA_PUBLIC_KEY_PEM>'
) => {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
const keyshare = await session.keygen();
// Backup
const label = 'example-backup';
const backupRes = await fetch(`http://${CLOUD_NODE_URI}/v3/ecdsa/backup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
key_id: keyshare.keyIdHex, // Use Hex version of keyId for server request
rsa_pubkey_pem: rsaPublicKeyPEM,
label,
}),
});
const backupJson = await backupRes.json();
const backupConfig = {
keyshare: keyshare,
rsa_public_pem: rsaPublicKeyPEM,
backup: backupJson.verifiable_backup,
label,
};
const isVerified = await ecdsaVerifyBackup(backupConfig);
console.log('Is backup verified:', isVerified);
};
import { type EddsaSession, eddsaVerifyBackup } from '@silencelaboratories/silent-shard-sdk/eddsa';
import { Platform } from 'react-native';
const CLOUD_NODE_URI = Platform.select({ android: '10.0.2.2', ios: 'localhost' }) + ':8080';
export const keyBackup = async (
session: EddsaSession,
// RSA public key in PEM format
rsaPublicKeyPEM: string = '<RSA_PUBLIC_KEY_PEM>'
) => {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
const keyshare = await session.keygen();
// Backup
const label = 'example-backup';
const backupRes = await fetch(`http://${CLOUD_NODE_URI}/v3/eddsa/backup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
key_id: keyshare.keyIdHex, // Use Hex version of keyId for server request
rsa_pubkey_pem: rsaPublicKeyPEM,
label,
}),
});
const backupJson = await backupRes.json();
const backupConfig = {
keyshare: keyshare,
rsa_public_pem: rsaPublicKeyPEM,
backup: backupJson.verifiable_backup,
label,
};
const isVerified = await eddsaVerifyBackup(backupConfig);
console.log('Is backup verified:', isVerified);
};
Please refer to the Session creation guide to learn how to create a session.
Step 1: Fetch the server's encrypted backup. Make a POST request to the backup endpoint (/v3/ecdsa/backup or /v3/eddsa/backup) with:
key_id: the keyshare's key ID (hex string, from the Keyshare'skeyIdHex)rsa_pubkey_pem: an RSA public key in PEM formatlabel: a label used as associated data for the RSA encryption
Step 2: Verify the backup. Call ecdsaVerifyBackup(), which takes a EcdsaVerifyBackupConfig with:
keyshare: your client share (a Keyshare)rsaPublicKeyPEM: the RSA public key in PEM formatbackup: the encrypted backup from the server responselabel: the same label used when requesting the backup
Returns a boolean indicating whether the backup is valid.
- ECDSA
- EdDSA
- Taproot
import 'dart:convert';
import 'dart:io';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
import 'package:http/http.dart' as http;
const port = 8080;
final nodeUri = Platform.isAndroid ? '10.0.2.2:$port' : '0.0.0.0:$port';
Future<void> keyBackup(
sdk.EcdsaSession session,
// RSA public key in PEM format
String rsaPublicKeyPEM,
) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.DklsKeyshare keyshare = await session.keygen();
// Backup
const label = 'example-backup';
final backupUrl = 'http://$nodeUri/v3/ecdsa/backup';
final backupRes = await http.post(Uri.parse(backupUrl),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
"key_id": keyshare.keyIdHex,
"rsa_pubkey_pem": rsaPublicKeyPEM,
"label": label,
}));
final backupResponse = BackupResponse.fromJson(jsonDecode(backupRes.body));
final isVerified = sdk.ecdsaDuoVerifyBackup(
keyshare: keyshare,
backup: backupResponse.verfiableBackup,
label: label,
rsaPublicKeyPem: rsaPublicKeyPEM,
);
print('Is backup verified: $isVerified');
}
class BackupResponse {
final String? keyId;
final String? algo;
final String verfiableBackup;
BackupResponse.fromJson(Map<String, dynamic> json)
: keyId = json['key_id'],
algo = json['algo'],
verfiableBackup = json['verifiable_backup'];
}
import 'dart:convert';
import 'dart:io';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
import 'package:http/http.dart' as http;
const port = 8080;
final nodeUri = Platform.isAndroid ? '10.0.2.2:$port' : '0.0.0.0:$port';
Future<void> keyBackup(
sdk.EddsaSession session,
// RSA public key in PEM format
String rsaPublicKeyPEM,
) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.SchnorrKeyshare keyshare = await session.keygen();
// Backup
const label = 'example-backup';
final backupUrl = 'http://$nodeUri/v3/eddsa/backup';
final backupRes = await http.post(Uri.parse(backupUrl),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
"key_id": keyshare.keyIdHex,
"rsa_pubkey_pem": rsaPublicKeyPEM,
"label": label,
}));
final backupResponse = BackupResponse.fromJson(jsonDecode(backupRes.body));
final isVerified = await sdk.schnorrDuoVerifyBackup(
keyshare: keyshare,
backup: backupResponse.verfiableBackup,
rsaPublicKeyPem: rsaPublicKeyPEM,
label: label,
);
print('Is backup verified: $isVerified');
}
class BackupResponse {
final String? keyId;
final String? algo;
final String verfiableBackup;
BackupResponse.fromJson(Map<String, dynamic> json)
: keyId = json['key_id'],
algo = json['algo'],
verfiableBackup = json['verifiable_backup'];
}
import 'dart:convert';
import 'dart:io';
import 'package:silent_shard_sdk/silent_shard_sdk.dart' as sdk;
import 'package:http/http.dart' as http;
const port = 8080;
final nodeUri = Platform.isAndroid ? '10.0.2.2:$port' : '0.0.0.0:$port';
Future<void> keyBackup(
sdk.TaprootSession session,
// RSA public key in PEM format
String rsaPublicKeyPEM,
) async {
// Creating a new keyshare for demo purpose. In real application, you can use an existing keyshare.
final sdk.TaprootKeyshare keyshare = await session.keygen();
const label = 'example-backup';
final backupUrl = 'http://$nodeUri/v3/taproot/backup';
final backupRes = await http.post(Uri.parse(backupUrl),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({
'key_id': keyshare.keyIdHex,
'rsa_pubkey_pem': rsaPublicKeyPEM,
'label': label,
}));
final body = jsonDecode(backupRes.body) as Map<String, dynamic>;
final verifiableBackup = body['verifiable_backup'] as String;
final isVerified = await sdk.taprootDuoVerifyBackup(
keyshare: keyshare,
backup: verifiableBackup,
rsaPublicKeyPem: rsaPublicKeyPEM,
label: label,
);
print('Is backup verified: $isVerified');
}
Please refer to the Session creation guide to learn how to create a session.
Step 1: Fetch the server's encrypted backup. Make a POST request to the backup endpoint (/v3/backup) with:
key_id: the keyshare's key ID (base64 string)rsa_pubkey_pem: an RSA public key in PEM formatlabel: a label used as associated data for the RSA encryption
Step 2: Verify the backup. Call ecdsaVerifyBackup with:
keyshare: your client share (a DklsKeyshare)rsaPublicKeyPEM: the RSA public key in PEM formatbackup: the encrypted backup from the server responselabel: the same label used when requesting the backup
Returns a boolean indicating whether the backup is valid.
- ECDSA
- EdDSA
object Constants {
val 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-----
""".trimIndent()
}
suspend fun backupAndVerify(keyId: String, duoSession: DuoSession): Boolean {
return withContext(Dispatchers.IO) {
//Get BackupData from another party
val backupKeyResponse = getBackupData(
url = buildString {
append(websocketConfig.postRequestUrl)
append("/v3")
append("/ecdsa/backup")
}, keyId = keyId, label = "test-backup"
)
//Verify if BackupData is valid
duoSession.verifyBackup(
keyId = keyId,
backupData = backupKeyResponse.verifiableBackup.decodeBase64Bytes(),
rsaPublicKey = rsaPublicKey.encodeToByteArray(),
label = "test-backup"
).getOrThrow()
}
}
private fun getBackupData(url: String, keyId: String, label: String) = BackupKeyRequest(
keyId = keyId, rsaPublicKey = Constants.rsaPublicKey, label = label
).let {
json.encodeToString(it)
}.let { request: String ->
sendPostRequest(
url, null, request
).decodeToString().let { response: String ->
json.decodeFromString<BackupKeyResponse>(response)
}
}
data class BackupKeyRequest(
@SerialName("key_id") val keyId: String,
@SerialName("rsa_pubkey_pem") val rsaPublicKey: String,
@SerialName("label") val label: String,
)
data class BackupKeyResponse(
@SerialName("verifiable_backup") val verifiableBackup: String,
)
object Constants {
val 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-----
""".trimIndent()
}
suspend fun backupAndVerify(keyId: String, duoSession: DuoSession): Boolean {
return withContext(Dispatchers.IO) {
//Get BackupData from another party
val backupKeyResponse = getBackupData(
url = buildString {
append(websocketConfig.postRequestUrl)
append("/v3")
append("/eddsa/backup")
}, keyId = keyId, label = "test-backup"
)
//Verify if BackupData is valid
duoSession.verifyBackup(
keyId = keyId,
backupData = backupKeyResponse.verifiableBackup.decodeBase64Bytes(),
rsaPublicKey = rsaPublicKey.encodeToByteArray(),
label = "test-backup"
).getOrThrow()
}
}
private fun getBackupData(url: String, keyId: String, label: String) = BackupKeyRequest(
keyId = keyId, rsaPublicKey = Constants.rsaPublicKey, label = label
).let {
json.encodeToString(it)
}.let { request: String ->
sendPostRequest(
url, null, request
).decodeToString().let { response: String ->
json.decodeFromString<BackupKeyResponse>(response)
}
}
data class BackupKeyRequest(
@SerialName("key_id") val keyId: String,
@SerialName("rsa_pubkey_pem") val rsaPublicKey: String,
@SerialName("label") val label: String,
)
data class BackupKeyResponse(
@SerialName("verifiable_backup") val verifiableBackup: String,
)
Please refer to the Session creation guide to learn how to create a session.
Step 1: Fetch the server's encrypted backup. Make a POST request to the backup endpoint (/v3/ecdsa/backup or /v3/eddsa/backup) with the key ID and RSA public key.
Step 2: Verify the backup. Call duoSession.verifyBackup() with:
keyshare: your client share (ByteArray)rsaPublicKey: the RSA public key in PEM formatbackup: the encrypted backup from the server responselabel: the label used as associated data for RSA encryption
The result is a Result type: Success if the backup is valid, or Failure with an Exception.
- 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
}
Please refer to the Session creation guide to learn how to create a session.
Step 1: Fetch the server's encrypted backup. Make a POST request to the backup endpoint (/v3/ecdsa/backup or /v3/eddsa/backup) with the key ID and RSA public key.
Step 2: Verify the backup. Call duoSession.verifyBackup() with:
keyshare: your client share (Data)rsaPublicKey: the RSA public key in PEM formatbackup: the encrypted backup from the server responselabel: the label used as associated data for RSA encryption
The result is a Result type: Success if the backup is valid, or Failure with an error.