Q Safe Wallet
  • Getting Started
    • Overview
    • FAQs
  • Technical Details
    • Account Creation in Wallet
    • Quranium Chain Transaction Signing
    • ML-KEM Encryption for Backups
    • Backup and Recovery
  • All Features
    • Onboarding
    • Activity Section
    • Add Account
    • Multichain
    • Sending Transaction
    • Deposit Funds
    • Swapping
  • MULTICHAIN COMPATIBILITY
    • Quranium
    • Bitcoin & Derivatives
    • EVM
    • Solana
    • Other Chains
  • BEST PRACTICES
    • Secure Your Backup and Mnemonic
    • Validate Transaction Details Before Signing
    • Monitor Account Activity Across Chains
    • Safe Interaction with DApps
Powered by GitBook
On this page
  • 1. Automatic Backups
  • 2. Manual Recovery
  1. Technical Details

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 :

  1. A shared secret is generated using ML-KEM.

  2. The actual data is encrypted using XSalsa20-Poly1305 , a symmetric cipher.

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

  1. User enters their 12/24-word mnemonic during recovery.

  2. The wallet regenerates:

    • SLH-DSA signing keypair (for Quranium Chain).

    • ML-KEM encryption keypair (for decrypting backups).

  3. 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, and nonce.

Decryption Steps:

  1. Use the ML-KEM private key to extract the shared secret from the capsule.

  2. 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));
}
PreviousML-KEM Encryption for BackupsNextOnboarding

Last updated 23 days ago