Save/Restore all accounts
This commit is contained in:
parent
67477d1220
commit
766748ccfb
|
@ -62,6 +62,8 @@ reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-featu
|
||||||
bech32 = "0.8.1"
|
bech32 = "0.8.1"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
blake2b_simd = "0.5.11"
|
blake2b_simd = "0.5.11"
|
||||||
|
chacha20poly1305 = "0.9.0"
|
||||||
|
base64 = "^0.13"
|
||||||
|
|
||||||
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
|
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
|
||||||
|
|
||||||
|
|
97
src/db.rs
97
src/db.rs
|
@ -7,11 +7,15 @@ use crate::{CTree, Witness, NETWORK};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use rusqlite::{params, Connection, OptionalExtension, Transaction, NO_PARAMS};
|
use rusqlite::{params, Connection, OptionalExtension, Transaction, NO_PARAMS};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use bech32::FromBase32;
|
||||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||||
use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
|
use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
|
||||||
use zcash_primitives::merkle_tree::IncrementalWitness;
|
use zcash_primitives::merkle_tree::IncrementalWitness;
|
||||||
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk};
|
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk};
|
||||||
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
|
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use chacha20poly1305::{Key, ChaCha20Poly1305, Nonce};
|
||||||
|
use chacha20poly1305::aead::{Aead, NewAead};
|
||||||
|
|
||||||
mod migration;
|
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 {
|
impl DbAdapter {
|
||||||
pub fn new(db_path: &str) -> anyhow::Result<DbAdapter> {
|
pub fn new(db_path: &str) -> anyhow::Result<DbAdapter> {
|
||||||
let connection = Connection::open(db_path)?;
|
let connection = Connection::open(db_path)?;
|
||||||
|
@ -813,13 +828,95 @@ impl DbAdapter {
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use bech32::{ToBase32, Variant};
|
||||||
use crate::db::{DbAdapter, ReceivedNote, DEFAULT_DB_PATH};
|
use crate::db::{DbAdapter, ReceivedNote, DEFAULT_DB_PATH};
|
||||||
use crate::{CTree, Witness};
|
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]
|
#[test]
|
||||||
fn test_db() {
|
fn test_db() {
|
||||||
let mut db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
let mut db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||||
|
|
10
src/key.rs
10
src/key.rs
|
@ -1,5 +1,8 @@
|
||||||
|
use bech32::{ToBase32, Variant};
|
||||||
use crate::NETWORK;
|
use crate::NETWORK;
|
||||||
use bip39::{Language, Mnemonic, Seed};
|
use bip39::{Language, Mnemonic, Seed};
|
||||||
|
use rand::RngCore;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
use zcash_client_backend::encoding::{
|
use zcash_client_backend::encoding::{
|
||||||
decode_extended_full_viewing_key, decode_extended_spending_key,
|
decode_extended_full_viewing_key, decode_extended_spending_key,
|
||||||
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
|
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);
|
let address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &payment_address);
|
||||||
Ok(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)
|
||||||
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub use crate::chain::{
|
||||||
pub use crate::commitment::{CTree, Witness};
|
pub use crate::commitment::{CTree, Witness};
|
||||||
pub use crate::db::DbAdapter;
|
pub use crate::db::DbAdapter;
|
||||||
pub use crate::hash::pedersen_hash;
|
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::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||||
pub use crate::lw_rpc::*;
|
pub use crate::lw_rpc::*;
|
||||||
pub use crate::mempool::MemPool;
|
pub use crate::mempool::MemPool;
|
||||||
|
|
|
@ -538,6 +538,14 @@ impl Wallet {
|
||||||
Ok(payment_json)
|
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<()> {
|
pub fn store_share_secret(&self, account: u32, secret: &str, index: usize, threshold: usize, participants: usize) -> anyhow::Result<()> {
|
||||||
self.db.store_share_secret(
|
self.db.store_share_secret(
|
||||||
account,
|
account,
|
||||||
|
|
Loading…
Reference in New Issue