v0.0.12: Encrypted backup/restore + history module

Protocol:
- history.rs: derive_history_key (HKDF from seed, info="warzone-history")
- encrypt_history / decrypt_history (ChaCha20-Poly1305, WZH1 magic)
- 2 new tests (roundtrip + wrong seed), total 19/19

CLI:
- `warzone backup [output.wzb]` — exports all sessions + pre-keys
  as encrypted blob (only your seed can decrypt)
- `warzone restore <input.wzb>` — imports backup, merges (no overwrite)
- Backup format: WZH1 magic + nonce + encrypted JSON

Storage:
- export_all() — dumps sessions + pre-keys as base64 JSON
- import_all() — merges backup data (skip existing entries)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-27 12:59:54 +04:00
parent fff443bb6d
commit 653c6c050b
5 changed files with 160 additions and 1 deletions

View File

@@ -55,6 +55,17 @@ enum Commands {
#[arg(short, long, default_value = "http://localhost:7700")]
server: String,
},
/// Export encrypted backup of local data (sessions, history)
Backup {
/// Output file path
#[arg(default_value = "warzone-backup.wzb")]
output: String,
},
/// Restore from encrypted backup
Restore {
/// Backup file path
input: String,
},
}
#[tokio::main]
@@ -102,6 +113,25 @@ async fn main() -> anyhow::Result<()> {
let db = storage::LocalDb::open()?;
tui::run_tui(our_fp, peer, server, identity, poll_seed, db).await?;
}
Commands::Backup { output } => {
let data_dir = keystore::data_dir();
// Collect all sled data as JSON
let db = storage::LocalDb::open()?;
let backup_data = db.export_all()?;
let json = serde_json::to_vec(&backup_data)?;
let encrypted = warzone_protocol::history::encrypt_history(&seed.0, &json);
std::fs::write(&output, &encrypted)?;
println!("Backup saved to {} ({} bytes encrypted)", output, encrypted.len());
}
Commands::Restore { input } => {
let encrypted = std::fs::read(&input)?;
let json = warzone_protocol::history::decrypt_history(&seed.0, &encrypted)
.map_err(|_| anyhow::anyhow!("Decryption failed — wrong seed?"))?;
let backup_data: serde_json::Value = serde_json::from_slice(&json)?;
let db = storage::LocalDb::open()?;
let count = db.import_all(&backup_data)?;
println!("Restored {} entries from {}", count, input);
}
}
Ok(())