2022-10-19 23:24:36 -07:00
|
|
|
//! Account related API
|
|
|
|
|
2022-06-08 05:48:16 -07:00
|
|
|
// Account creation
|
|
|
|
|
2022-06-10 02:16:00 -07:00
|
|
|
use crate::coinconfig::CoinConfig;
|
2022-09-04 04:19:49 -07:00
|
|
|
use crate::db::AccountData;
|
2022-06-08 05:48:16 -07:00
|
|
|
use crate::key2::decode_key;
|
2022-07-23 06:25:08 -07:00
|
|
|
use crate::taddr::{derive_taddr, derive_tkeys};
|
2022-08-26 21:43:36 -07:00
|
|
|
use crate::transaction::retrieve_tx_info;
|
2022-11-06 04:50:51 -08:00
|
|
|
use crate::unified::UnifiedAddressType;
|
|
|
|
use crate::zip32::derive_zip32;
|
2022-10-19 23:24:36 -07:00
|
|
|
use crate::{connect_lightwalletd, AccountInfo, KeyPack};
|
2022-06-08 05:48:16 -07:00
|
|
|
use anyhow::anyhow;
|
|
|
|
use bip39::{Language, Mnemonic};
|
|
|
|
use rand::rngs::OsRng;
|
|
|
|
use rand::RngCore;
|
2022-08-26 05:54:52 -07:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::BufReader;
|
2022-06-08 05:48:16 -07:00
|
|
|
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, encode_payment_address};
|
|
|
|
use zcash_primitives::consensus::Parameters;
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Create a new account
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `name`: account name
|
|
|
|
/// * `key`: `Some(key)` where key is either a passphrase,
|
|
|
|
/// a secret key or a viewing key for an existing account,
|
|
|
|
/// or `None` for a new randomly generated account
|
|
|
|
/// * `index`: `Some(x)` for account at index `x` or
|
|
|
|
/// `None` for main account (same as x = 0)
|
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
/// `account id`
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn new_account(
|
|
|
|
coin: u8,
|
|
|
|
name: &str,
|
|
|
|
key: Option<String>,
|
|
|
|
index: Option<u32>,
|
|
|
|
) -> anyhow::Result<u32> {
|
|
|
|
let key = match key {
|
|
|
|
Some(key) => key,
|
|
|
|
None => {
|
|
|
|
let mut entropy = [0u8; 32];
|
|
|
|
OsRng.fill_bytes(&mut entropy);
|
|
|
|
let mnemonic = Mnemonic::from_entropy(&entropy, Language::English)?;
|
|
|
|
mnemonic.phrase().to_string()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let id_account = new_account_with_key(coin, name, &key, index.unwrap_or(0))?;
|
|
|
|
Ok(id_account)
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Create one or many sub accounts of the current account
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
/// ```rust
|
|
|
|
/// crate::api::account::new_sub_account("test", None, 5)
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
/// * `name`: name of the sub accounts. Every sub account will have the same name
|
|
|
|
/// * `index`: Starting index. If `None`, use the index following the highest used index
|
|
|
|
/// * `count`: Number of subaccounts to create
|
2022-07-14 18:12:25 -07:00
|
|
|
pub fn new_sub_account(name: &str, index: Option<u32>, count: u32) -> anyhow::Result<()> {
|
2022-06-08 05:48:16 -07:00
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let db = c.db()?;
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { seed, .. } = db.get_account_info(c.id_account)?;
|
2022-06-08 05:48:16 -07:00
|
|
|
let seed = seed.ok_or_else(|| anyhow!("Account has no seed"))?;
|
|
|
|
let index = index.unwrap_or_else(|| db.next_account_id(&seed).unwrap());
|
|
|
|
drop(db);
|
2022-07-14 18:12:25 -07:00
|
|
|
for i in 0..count {
|
|
|
|
new_account_with_key(c.coin, name, &seed, index + i)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
2022-06-08 05:48:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn new_account_with_key(coin: u8, name: &str, key: &str, index: u32) -> anyhow::Result<u32> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let (seed, sk, ivk, pa) = decode_key(coin, key, index)?;
|
|
|
|
let db = c.db()?;
|
2022-11-15 02:21:47 -08:00
|
|
|
let account = db.get_account_id(&ivk)?;
|
|
|
|
let account = match account {
|
|
|
|
Some(account) => account,
|
|
|
|
None => {
|
|
|
|
let account =
|
|
|
|
db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?;
|
|
|
|
if c.chain.has_transparent() {
|
|
|
|
db.create_taddr(account)?;
|
|
|
|
}
|
|
|
|
if c.chain.has_unified() {
|
|
|
|
db.create_orchard(account)?;
|
|
|
|
}
|
|
|
|
db.store_ua_settings(account, false, true, c.chain.has_unified())?;
|
|
|
|
account
|
2022-10-28 23:40:05 -07:00
|
|
|
}
|
2022-11-15 02:21:47 -08:00
|
|
|
};
|
2022-06-08 05:48:16 -07:00
|
|
|
Ok(account)
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Update the transparent secret key for the given account from a derivation path
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `id_account`: account id as returned from [new_account]
|
|
|
|
/// * `path`: derivation path
|
|
|
|
///
|
|
|
|
/// Account must have a seed phrase
|
2022-07-16 20:23:56 -07:00
|
|
|
pub fn import_transparent_key(coin: u8, id_account: u32, path: &str) -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let db = c.db()?;
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { seed, .. } = db.get_account_info(c.id_account)?;
|
2022-07-16 20:23:56 -07:00
|
|
|
let seed = seed.ok_or_else(|| anyhow!("Account has no seed"))?;
|
|
|
|
let (sk, addr) = derive_tkeys(c.chain.network(), &seed, path)?;
|
|
|
|
db.store_transparent_key(id_account, &sk, &addr)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Update the transparent secret key for the given account
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `id_account`: account id as returned from [new_account]
|
|
|
|
/// * `sk`: secret key
|
2022-07-23 06:25:08 -07:00
|
|
|
pub fn import_transparent_secret_key(coin: u8, id_account: u32, sk: &str) -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let db = c.db()?;
|
|
|
|
let (sk, addr) = derive_taddr(c.chain.network(), sk)?;
|
|
|
|
db.store_transparent_key(id_account, &sk, &addr)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Generate a new diversified address
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn new_diversified_address() -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let db = c.db()?;
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { fvk, .. } = db.get_account_info(c.id_account)?;
|
2022-06-08 05:48:16 -07:00
|
|
|
let fvk = decode_extended_full_viewing_key(
|
|
|
|
c.chain.network().hrp_sapling_extended_full_viewing_key(),
|
2022-09-04 04:19:49 -07:00
|
|
|
&fvk,
|
2022-11-06 04:50:51 -08:00
|
|
|
)
|
|
|
|
.map_err(|_| anyhow!("Bech32 Decode Error"))?;
|
2022-06-08 05:48:16 -07:00
|
|
|
let mut diversifier_index = db.get_diversifier(c.id_account)?;
|
|
|
|
diversifier_index.increment().unwrap();
|
|
|
|
let (new_diversifier_index, pa) = fvk
|
|
|
|
.find_address(diversifier_index)
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Cannot generate new address"))?;
|
|
|
|
db.store_diversifier(c.id_account, &new_diversifier_index)?;
|
|
|
|
let pa = encode_payment_address(c.chain.network().hrp_sapling_payment_address(), &pa);
|
|
|
|
Ok(pa)
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Retrieve the transparent balance for the current account from the LWD server
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn get_taddr_balance_default() -> anyhow::Result<u64> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
get_taddr_balance(c.coin, c.id_account).await
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Retrieve the transparent balance from the LWD server
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `id_account`: account id as returned from [new_account]
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn get_taddr_balance(coin: u8, id_account: u32) -> anyhow::Result<u64> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let mut client = c.connect_lwd().await?;
|
|
|
|
let address = c.db()?.get_taddr(id_account)?;
|
|
|
|
let balance = match address {
|
|
|
|
None => 0u64,
|
|
|
|
Some(address) => crate::taddr::get_taddr_balance(&mut client, &address).await?,
|
|
|
|
};
|
|
|
|
Ok(balance)
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Look for accounts that have some transparent balance. Stop when the gap limit
|
|
|
|
/// is exceeded and no balance was found
|
|
|
|
/// # Arguments
|
|
|
|
/// * `gap_limit`: number of accounts with 0 balance before the scan stops
|
2022-06-08 05:48:16 -07:00
|
|
|
pub async fn scan_transparent_accounts(gap_limit: usize) -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let mut client = c.connect_lwd().await?;
|
|
|
|
crate::taddr::scan_transparent_accounts(c.chain.network(), &mut client, gap_limit).await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Get the backup string. It is either the passphrase, the secret key or the viewing key
|
|
|
|
/// depending on how the account was created
|
|
|
|
/// # Arguments
|
|
|
|
/// * `id_account`: account id as returned from [new_account]
|
|
|
|
///
|
|
|
|
/// Use the current active coin
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn get_backup(account: u32) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get_active();
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { seed, sk, fvk, .. } = c.db()?.get_account_info(account)?;
|
2022-06-08 05:48:16 -07:00
|
|
|
if let Some(seed) = seed {
|
|
|
|
return Ok(seed);
|
|
|
|
}
|
|
|
|
if let Some(sk) = sk {
|
|
|
|
return Ok(sk);
|
|
|
|
}
|
2022-09-04 04:19:49 -07:00
|
|
|
Ok(fvk)
|
2022-06-08 05:48:16 -07:00
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Get the secret key. Returns empty string if the account has no secret key
|
|
|
|
/// # Arguments
|
|
|
|
/// * `id_account`: account id as returned from [new_account]
|
|
|
|
///
|
|
|
|
/// Use the current active coin
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn get_sk(account: u32) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get_active();
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { sk, .. } = c.db()?.get_account_info(account)?;
|
|
|
|
Ok(sk.unwrap_or(String::new()))
|
2022-06-08 05:48:16 -07:00
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Reset the database
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn reset_db(coin: u8) -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let db = c.db()?;
|
|
|
|
db.reset_db()
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Truncate all non account data for the current active coin
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn truncate_data() -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let db = c.db()?;
|
|
|
|
db.truncate_data()
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Truncate all synchronization data for the current active coin
|
2022-09-02 01:44:31 -07:00
|
|
|
pub fn truncate_sync_data() -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let db = c.db()?;
|
|
|
|
db.truncate_sync_data()
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Delete an account
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `id_account`: account id as returned from [new_account]
|
2022-06-08 05:48:16 -07:00
|
|
|
pub fn delete_account(coin: u8, account: u32) -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let db = c.db()?;
|
|
|
|
db.delete_account(account)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-07-17 19:55:57 -07:00
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Import a ZWL data file
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `name`: prefix for the imported accounts
|
|
|
|
/// * `data`: data file
|
2022-07-17 19:55:57 -07:00
|
|
|
pub fn import_from_zwl(coin: u8, name: &str, data: &str) -> anyhow::Result<()> {
|
|
|
|
let c = CoinConfig::get(coin);
|
2022-10-19 23:24:36 -07:00
|
|
|
let sks = crate::misc::read_zwl(data)?;
|
2022-07-17 19:55:57 -07:00
|
|
|
let db = c.db()?;
|
|
|
|
for (i, key) in sks.iter().enumerate() {
|
|
|
|
let name = format!("{}-{}", name, i + 1);
|
|
|
|
let (seed, sk, ivk, pa) = decode_key(coin, key, 0)?;
|
|
|
|
db.store_account(&name, seed.as_deref(), 0, sk.as_deref(), &ivk, &pa)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-07-26 19:11:36 -07:00
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Derive keys using Zip-32
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `id_account`: account id as returned from [new_account]. Must have a passphrase
|
|
|
|
/// * `account`: derived account index
|
|
|
|
/// * `external`: external/internal
|
|
|
|
/// * `address`: address index
|
2022-07-26 19:11:36 -07:00
|
|
|
pub fn derive_keys(
|
|
|
|
coin: u8,
|
|
|
|
id_account: u32,
|
|
|
|
account: u32,
|
|
|
|
external: u32,
|
|
|
|
address: Option<u32>,
|
|
|
|
) -> anyhow::Result<KeyPack> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let db = c.db()?;
|
2022-09-04 04:19:49 -07:00
|
|
|
let AccountData { seed, .. } = db.get_account_info(id_account)?;
|
2022-07-26 19:11:36 -07:00
|
|
|
let seed = seed.unwrap();
|
|
|
|
derive_zip32(c.chain.network(), &seed, account, external, address)
|
|
|
|
}
|
2022-08-26 05:54:52 -07:00
|
|
|
|
2022-10-19 23:24:36 -07:00
|
|
|
/// Import synchronization data obtained from external source
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `file`: file that contains the synchronization data
|
2022-08-26 21:43:36 -07:00
|
|
|
pub async fn import_sync_data(coin: u8, file: &str) -> anyhow::Result<()> {
|
2022-08-26 05:54:52 -07:00
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let mut db = c.db()?;
|
|
|
|
let file = File::open(file)?;
|
|
|
|
let file = BufReader::new(file);
|
|
|
|
let account_info: AccountInfo = serde_json::from_reader(file)?;
|
2022-11-05 18:49:17 -07:00
|
|
|
db.import_from_syncdata(&account_info)?;
|
2022-08-26 05:54:52 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
2022-10-30 03:03:38 -07:00
|
|
|
|
|
|
|
/// Get the Unified address
|
|
|
|
/// # Arguments
|
|
|
|
/// * `coin`: 0 for zcash, 1 for ycash
|
|
|
|
/// * `id_account`: account id as returned from [new_account]
|
2022-11-03 22:06:11 -07:00
|
|
|
/// * t, s, o: include transparent, sapling, orchard receivers?
|
2022-10-30 03:03:38 -07:00
|
|
|
///
|
|
|
|
/// The address depends on the UA settings and may include transparent, sapling & orchard receivers
|
2022-11-06 04:50:51 -08:00
|
|
|
pub fn get_unified_address(
|
|
|
|
coin: u8,
|
|
|
|
id_account: u32,
|
|
|
|
t: bool,
|
|
|
|
s: bool,
|
|
|
|
o: bool,
|
|
|
|
) -> anyhow::Result<String> {
|
2022-10-30 03:03:38 -07:00
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let db = c.db()?;
|
2022-11-03 22:06:11 -07:00
|
|
|
let tpe = UnifiedAddressType {
|
|
|
|
transparent: t,
|
|
|
|
sapling: s,
|
2022-11-06 04:50:51 -08:00
|
|
|
orchard: o,
|
2022-11-03 22:06:11 -07:00
|
|
|
};
|
2022-11-12 17:39:12 -08:00
|
|
|
let address = crate::get_unified_address(c.chain.network(), &db, id_account, Some(tpe))?; // use ua settings
|
|
|
|
Ok(address)
|
|
|
|
}
|
|
|
|
|
2022-11-15 02:21:47 -08:00
|
|
|
fn get_sapling_address(coin: u8, id_account: u32) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let db = c.db()?;
|
|
|
|
let AccountData { address, .. } = db.get_account_info(id_account)?;
|
|
|
|
Ok(address)
|
|
|
|
}
|
|
|
|
|
2022-11-12 17:39:12 -08:00
|
|
|
pub fn get_address(coin: u8, id_account: u32, address_type: u8) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get(coin);
|
2022-11-15 02:21:47 -08:00
|
|
|
let address = if c.chain.has_unified() {
|
|
|
|
let t = address_type & 1 != 0;
|
|
|
|
let s = address_type & 2 != 0;
|
|
|
|
let o = address_type & 4 != 0;
|
2022-11-12 17:39:12 -08:00
|
|
|
|
2022-11-15 02:21:47 -08:00
|
|
|
get_unified_address(coin, id_account, t, s, o)?
|
|
|
|
} else {
|
|
|
|
get_sapling_address(coin, id_account)?
|
|
|
|
};
|
2022-10-30 03:03:38 -07:00
|
|
|
Ok(address)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Decode a unified address into its receivers
|
|
|
|
///
|
|
|
|
/// For testing only. The format of the returned value is subject to change
|
|
|
|
pub fn decode_unified_address(coin: u8, address: &str) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get(coin);
|
|
|
|
let res = crate::decode_unified_address(c.chain.network(), address)?;
|
|
|
|
Ok(res.to_string())
|
|
|
|
}
|