Save/Restore all accounts

This commit is contained in:
Hanh 2022-02-28 23:35:29 +08:00
parent 67477d1220
commit 766748ccfb
5 changed files with 118 additions and 1 deletions

View File

@ -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

View File

@ -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<String>,
pub z_sk: Option<String>,
pub ivk: String,
pub z_addr: String,
pub t_sk: Option<String>,
pub t_addr: Option<String>,
}
impl DbAdapter {
pub fn new(db_path: &str) -> anyhow::Result<DbAdapter> {
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<String> {
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<String> = r.get(1)?;
let z_sk: Option<String> = r.get(2)?;
let ivk: String = r.get(3)?;
let z_addr: String = r.get(4)?;
let t_sk: Option<String> = r.get(5)?;
let t_addr: Option<String> = r.get(6)?;
Ok(AccountBackup {
name,
seed,
z_sk,
ivk,
z_addr,
t_sk,
t_addr,
})
})?;
let mut accounts: Vec<AccountBackup> = 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::<u8>::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::<u8>::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<AccountBackup> = 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();

View File

@ -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<String> {
let address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &payment_address);
Ok(address)
}
pub fn generate_random_enc_key() -> anyhow::Result<String> {
let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key);
let key = bech32::encode("zwk", key.to_base32(), Variant::Bech32)?;
Ok(key)
}

View File

@ -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;

View File

@ -538,6 +538,14 @@ impl Wallet {
Ok(payment_json)
}
pub fn get_full_backup(&self, key: &str) -> anyhow::Result<String> {
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,