Backup and Recovery
1. Automatic Backups
Backups are created automatically when accounts or settings are modified. These backups are encrypted locally using ML-KEM before being uploaded to a remote server.
Why ML-KEM?
NIST-standardized lattice-based algorithm .
Resistant to attacks from both classical and quantum computers.
Used to securely encrypt sensitive data like account records and recovery information.
Encryption Flow: ML-KEM + XSalsa20-Poly1305
The wallet uses a hybrid encryption model :
A shared secret is generated using ML-KEM.
The actual data is encrypted using XSalsa20-Poly1305 , a symmetric cipher.
The shared secret is then encapsulated using the recipient's ML-KEM public key .
{
"capsule": "0x...", // ML-KEM ciphertext (encapsulated shared secret)
"symmetricCiphertext": "0x...", // XSalsa20-Poly1305 encrypted payload
"nonce": "0x..." // Random nonce used for symmetric encryption
}
async encryptBackup(backupBuffer: Buffer, encryptionPublicKey: string): Promise<string> {
const recipientPubKeyBuffer = hexToBuffer(encryptionPublicKey);
// Step 1: Generate shared secret using ML-KEM encapsulation
const { cipherText: capsule, sharedSecret } = ml_kem768.encapsulate(recipientPubKeyBuffer);
// Step 2: Generate random nonce for symmetric encryption
const nonce = crypto.randomBytes(24); // 24-byte nonce for XSalsa20
// Step 3: Encrypt payload using XSalsa20-Poly1305
const symmetricCiphertext = secretbox(backupBuffer, nonce, sharedSecret);
// Step 4: Return structured JSON blob
return JSON.stringify({
capsule: bufferToHex(capsule),
symmetricCiphertext: bufferToHex(symmetricCiphertext),
nonce: bufferToHex(nonce),
});
}
Uploading the Backup
After encryption, the backup is signed using SLH-DSA to ensure authenticity before upload.
const signatureHex = await kr.sign(messageBuffer, {
basePath: QL1evmNetworks.ql1evm.basePath,
signerType: SignerType.slh_dsaevm,
pathIndex: 0,
walletType: WalletType.mnemonic,
});
The backup and signature are sent via an authenticated API call:
await fetch(`${BACKUP_URL}backups/${account.publicKey}/users/${state.userId}`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
signature: signatureHex,
payload: bufferToHex(Buffer.from(encryptedPayload)),
}),
});
2. Manual Recovery
There are two main ways to restore a wallet:
Option A: Recovery via Mnemonic Phrase (12/24 Words)
Process:
User enters their 12/24-word mnemonic during recovery.
The wallet regenerates:
SLH-DSA signing keypair (for Quranium Chain).
ML-KEM encryption keypair (for decrypting backups).
All accounts are re-created deterministically based on derivation paths.
const entropy = Buffer.from(mnemonicToEntropy(mnemonic), "hex");
const seed96 = shake256.create({ dkLen: 96 }).update(entropy).digest();
const keys = slh.slh_dsa_shake_256f.keygen(seed96);
Option B: Restore from Cloud Backup
Use this option if you want to restore specific encrypted backups (e.g., saved settings or additional accounts).
Requirements:
ML-KEM Private Key : Either regenerated from the mnemonic or exported earlier.
Encrypted Backup File : Must contain the
capsule
,symmetricCiphertext
, andnonce
.
Decryption Steps:
Use the ML-KEM private key to extract the shared secret from the capsule.
Decrypt the symmetric ciphertext using the shared secret and nonce.
async decryptBackup(encryptedMessageStr: string, encryptionKeypair: KeyPair): Promise<string> {
const encryptedData = JSON.parse(encryptedMessageStr);
const capsule = hexToBuffer(encryptedData.capsule);
const symmetricCiphertext = hexToBuffer(encryptedData.symmetricCiphertext);
const nonce = hexToBuffer(encryptedData.nonce);
// Step 1: Decapsulate shared secret using ML-KEM
const sharedSecret = ml_kem768.decapsulate(capsule, hexToBuffer(encryptionKeypair.privateKey));
// Step 2: Decrypt symmetric data
const decrypted = secretbox.open(symmetricCiphertext, nonce, sharedSecret);
return bufferToHex(Buffer.from(decrypted));
}
Last updated