From 766748ccfb9c69f8bccaeb0b7c4b02b5a9777e71 Mon Sep 17 00:00:00 2001 From: Hanh Date: Mon, 28 Feb 2022 23:35:29 +0800 Subject: [PATCH] Save/Restore all accounts --- Cargo.toml | 2 ++ src/db.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/key.rs | 10 ++++++ src/lib.rs | 2 +- src/wallet.rs | 8 +++++ 5 files changed, 118 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 49e777e..120cbe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,8 @@ reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-featu bech32 = "0.8.1" rand_chacha = "0.3.1" blake2b_simd = "0.5.11" +chacha20poly1305 = "0.9.0" +base64 = "^0.13" # librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd diff --git a/src/db.rs b/src/db.rs index ca36c0a..e11f7f5 100644 --- a/src/db.rs +++ b/src/db.rs @@ -7,11 +7,15 @@ use crate::{CTree, Witness, NETWORK}; use chrono::NaiveDateTime; use rusqlite::{params, Connection, OptionalExtension, Transaction, NO_PARAMS}; use std::collections::HashMap; +use bech32::FromBase32; use zcash_client_backend::encoding::decode_extended_full_viewing_key; use zcash_primitives::consensus::{NetworkUpgrade, Parameters}; use zcash_primitives::merkle_tree::IncrementalWitness; use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk}; use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey}; +use serde::{Serialize, Deserialize}; +use chacha20poly1305::{Key, ChaCha20Poly1305, Nonce}; +use chacha20poly1305::aead::{Aead, NewAead}; mod migration; @@ -57,6 +61,17 @@ impl AccountViewKey { } } +#[derive(Serialize, Deserialize)] +pub struct AccountBackup { + pub name: String, + pub seed: Option, + pub z_sk: Option, + pub ivk: String, + pub z_addr: String, + pub t_sk: Option, + pub t_addr: Option, +} + impl DbAdapter { pub fn new(db_path: &str) -> anyhow::Result { let connection = Connection::open(db_path)?; @@ -813,13 +828,95 @@ impl DbAdapter { )?; Ok(()) } + + const NONCE: &'static[u8; 12] = b"unique nonce"; + + pub fn get_full_backup(&self, key: &str) -> anyhow::Result { + let mut statement = self.connection.prepare( + "SELECT name, seed, a.sk AS z_sk, ivk, a.address AS z_addr, t.sk as t_sk, t.address AS t_addr FROM accounts a LEFT JOIN taddrs t ON a.id_account = t.account")?; + let rows = statement.query_map(NO_PARAMS, |r| { + let name: String = r.get(0)?; + let seed: Option = r.get(1)?; + let z_sk: Option = r.get(2)?; + let ivk: String = r.get(3)?; + let z_addr: String = r.get(4)?; + let t_sk: Option = r.get(5)?; + let t_addr: Option = r.get(6)?; + Ok(AccountBackup { + name, + seed, + z_sk, + ivk, + z_addr, + t_sk, + t_addr, + }) + })?; + let mut accounts: Vec = vec![]; + for r in rows { + accounts.push(r?); + } + let accounts_bin = bincode::serialize(&accounts)?; + + let (hrp, key, _) = bech32::decode(key)?; + if hrp != "zwk" { anyhow::bail!("Invalid backup key") } + let key = Vec::::from_base32(&key)?; + let key = Key::from_slice(&key); + + let cipher = ChaCha20Poly1305::new(key); + // nonce is constant because we always use a different key! + let cipher_text = cipher.encrypt(Nonce::from_slice(Self::NONCE), &*accounts_bin).map_err(|_e| anyhow::anyhow!("Failed to encrypt backup"))?; + let backup = base64::encode(cipher_text); + Ok(backup) + } + + pub fn restore_full_backup(&self, key: &str, backup: &str) -> anyhow::Result<()> { + let (hrp, key, _) = bech32::decode(key)?; + if hrp != "zwk" { anyhow::bail!("Not a valid decryption key"); } + let key = Vec::::from_base32(&key)?; + let key = Key::from_slice(&key); + + let cipher = ChaCha20Poly1305::new(key); + let backup = base64::decode(backup)?; + let backup = cipher.decrypt(Nonce::from_slice(Self::NONCE), &*backup).map_err(|_e| anyhow::anyhow!("Failed to decrypt backup"))?; + + let accounts: Vec = bincode::deserialize(&backup)?; + for a in accounts { + log::info!("{}", a.name); + let do_insert = || { + self.connection.execute("INSERT INTO accounts(name, seed, sk, ivk, address) VALUES (?1,?2,?3,?4,?5)", + params![a.name, a.seed, a.z_sk, a.ivk, a.z_addr])?; + let id_account = self.connection.last_insert_rowid() as u32; + if let Some(t_addr) = a.t_addr { + self.connection.execute("INSERT INTO taddrs(account, sk, address) VALUES (?1,?2,?3)", + params![id_account, a.t_sk, t_addr])?; + } + Ok::<_, anyhow::Error>(()) + }; + let _ = do_insert(); + } + + Ok(()) + } } #[cfg(test)] mod tests { + use bech32::{ToBase32, Variant}; use crate::db::{DbAdapter, ReceivedNote, DEFAULT_DB_PATH}; use crate::{CTree, Witness}; + #[test] + fn test_db_backup() { + let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap(); + let k = [0u8; 32]; + let k = bech32::encode("zwk", k.to_base32(), Variant::Bech32).unwrap(); + let b = db.get_full_backup(&k).unwrap(); + println!("{} {}", k, b); + + db.restore_full_backup(&k, &b).unwrap(); + } + #[test] fn test_db() { let mut db = DbAdapter::new(DEFAULT_DB_PATH).unwrap(); diff --git a/src/key.rs b/src/key.rs index aaff0da..97f6018 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,5 +1,8 @@ +use bech32::{ToBase32, Variant}; use crate::NETWORK; use bip39::{Language, Mnemonic, Seed}; +use rand::RngCore; +use rand::rngs::OsRng; use zcash_client_backend::encoding::{ decode_extended_full_viewing_key, decode_extended_spending_key, encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address, @@ -72,3 +75,10 @@ pub fn derive_address(fvk: &ExtendedFullViewingKey) -> anyhow::Result { let address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &payment_address); Ok(address) } + +pub fn generate_random_enc_key() -> anyhow::Result { + let mut key = [0u8; 32]; + OsRng.fill_bytes(&mut key); + let key = bech32::encode("zwk", key.to_base32(), Variant::Bech32)?; + Ok(key) +} diff --git a/src/lib.rs b/src/lib.rs index b089d28..9cfc359 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ pub use crate::chain::{ pub use crate::commitment::{CTree, Witness}; pub use crate::db::DbAdapter; pub use crate::hash::pedersen_hash; -pub use crate::key::{decode_key, is_valid_key}; +pub use crate::key::{decode_key, is_valid_key, generate_random_enc_key}; pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient; pub use crate::lw_rpc::*; pub use crate::mempool::MemPool; diff --git a/src/wallet.rs b/src/wallet.rs index 5254d7e..a32e6fd 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -538,6 +538,14 @@ impl Wallet { Ok(payment_json) } + pub fn get_full_backup(&self, key: &str) -> anyhow::Result { + self.db.get_full_backup(key) + } + + pub fn restore_full_backup(&self, key: &str, backup: &str) -> anyhow::Result<()> { + self.db.restore_full_backup(key, backup) + } + pub fn store_share_secret(&self, account: u32, secret: &str, index: usize, threshold: usize, participants: usize) -> anyhow::Result<()> { self.db.store_share_secret( account,