zcash-sync/src/api/account.rs

433 lines
14 KiB
Rust
Raw Normal View History

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-12-30 12:34:26 -08:00
use crate::db::data_generated::fb::{
2023-03-13 00:49:15 -07:00
BackupT, KeyPackT, AddressBalanceVecT, AddressBalanceT,
2022-12-30 12:34:26 -08:00
};
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-11-17 01:13:51 -08:00
use crate::orchard::OrchardKeyBytes;
2022-07-23 06:25:08 -07:00
use crate::taddr::{derive_taddr, derive_tkeys};
2022-11-06 04:50:51 -08:00
use crate::unified::UnifiedAddressType;
use crate::zip32::derive_zip32;
2022-06-08 05:48:16 -07:00
use anyhow::anyhow;
use bip39::{Language, Mnemonic};
2022-11-17 01:13:51 -08:00
use orchard::keys::{FullViewingKey, Scope};
2022-06-08 05:48:16 -07:00
use rand::rngs::OsRng;
use rand::RngCore;
2022-11-17 01:13:51 -08:00
use zcash_address::unified::{Address as UA, Receiver};
use zcash_address::{ToAddress, ZcashAddress};
2022-06-08 05:48:16 -07:00
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, encode_payment_address};
2022-11-17 01:13:51 -08:00
use zcash_client_backend::keys::UnifiedFullViewingKey;
2022-06-08 05:48:16 -07:00
use zcash_primitives::consensus::Parameters;
2023-02-25 15:53:50 -08:00
use zcash_primitives::zip32::DiversifierIndex;
2022-06-08 05:48:16 -07:00
2023-03-07 19:46:54 -08:00
pub fn check_account(coin: u8, account: u32) -> bool {
let c = CoinConfig::get(coin);
let db = c.db().unwrap();
db.get_account_info(account).is_ok()
}
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);
2022-11-17 01:13:51 -08:00
let (seed, sk, ivk, pa, ofvk) = decode_key(coin, key, index)?;
2022-06-08 05:48:16 -07:00
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() {
2022-11-17 01:13:51 -08:00
match ofvk {
Some(fvk) => {
db.store_orchard_fvk(account, &fvk.to_bytes())?;
}
None => {
db.create_orchard(account)?;
}
}
2022-11-15 02:21:47 -08:00
}
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-11-17 01:13:51 -08:00
pub fn convert_to_watchonly(coin: u8, id_account: u32) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
db.convert_to_watchonly(id_account)?;
Ok(())
}
2023-03-01 05:09:45 -08:00
pub fn get_backup_package(coin: u8, id_account: u32) -> anyhow::Result<BackupT> {
2022-11-17 01:13:51 -08:00
let c = CoinConfig::get(coin);
let network = c.chain.network();
let db = c.db()?;
let AccountData {
name,
seed,
sk,
fvk,
aindex,
..
} = db.get_account_info(id_account)?;
let orchard_keys = db.get_orchard(id_account)?;
2023-02-28 00:55:46 -08:00
let uvk = orchard_keys.map(|OrchardKeyBytes { fvk: ofvk, .. }| {
// orchard sk is not serializable and must derived from seed
let sapling_efvk =
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk)
.unwrap();
let sapling_dfvk = sapling_efvk.to_diversifiable_full_viewing_key();
2023-03-10 22:43:10 -08:00
let orchard_fvk = FullViewingKey::from_bytes(&ofvk);
2023-02-28 00:55:46 -08:00
let ufvk = UnifiedFullViewingKey::new(Some(sapling_dfvk), orchard_fvk).unwrap();
2023-03-01 05:09:45 -08:00
ufvk.encode(network)
2023-02-28 00:55:46 -08:00
});
2023-03-01 05:09:45 -08:00
let backup = BackupT {
name: Some(name),
seed,
index: aindex,
sk,
fvk: Some(fvk),
uvk,
};
Ok(backup)
2022-11-17 01:13:51 -08:00
}
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()?;
2023-02-21 00:36:29 -08:00
let (_, addr) = derive_taddr(c.chain.network(), sk)?;
2022-07-23 06:25:08 -07:00
db.store_transparent_key(id_account, &sk, &addr)?;
Ok(())
}
2022-10-19 23:24:36 -07:00
/// Generate a new diversified address
2023-02-25 15:53:50 -08:00
pub fn get_diversified_address(ua_type: u8, time: u32) -> anyhow::Result<String> {
2022-12-20 11:55:12 -08:00
let ua_type = ua_type & 6; // don't include transparent component
if ua_type == 0 {
anyhow::bail!("Must include a shielded receiver");
}
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 { 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"))?;
2023-02-25 15:53:50 -08:00
let mut di = [0u8; 11];
di[4..8].copy_from_slice(&time.to_le_bytes());
let diversifier_index = DiversifierIndex(di);
let (_, pa) = fvk
2022-06-08 05:48:16 -07:00
.find_address(diversifier_index)
.ok_or_else(|| anyhow::anyhow!("Cannot generate new address"))?;
2022-11-17 01:13:51 -08:00
let orchard_keys = db.get_orchard(c.id_account)?;
2022-12-21 14:54:25 -08:00
if ua_type == 2 || orchard_keys.is_none() {
// sapling only
return Ok(encode_payment_address(
c.chain.network().hrp_sapling_payment_address(),
&pa,
));
2022-12-20 11:55:12 -08:00
}
let orchard_keys = orchard_keys.unwrap();
let mut receivers = vec![];
if ua_type & 2 != 0 {
receivers.push(Receiver::Sapling(pa.to_bytes()));
}
if ua_type & 4 != 0 {
let orchard_fvk = FullViewingKey::from_bytes(&orchard_keys.fvk).unwrap();
let index = diversifier_index.0; // any sapling index is fine for orchard
let orchard_address = orchard_fvk.address_at(index, Scope::External);
receivers.push(Receiver::Orchard(orchard_address.to_raw_address_bytes()));
}
let unified_address = UA(receivers);
let address = ZcashAddress::from_unified(
c.chain.network().address_network().unwrap(),
unified_address,
);
let address = address.encode();
2022-11-17 01:13:51 -08:00
Ok(address)
2022-06-08 05:48:16 -07:00
}
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
2023-03-13 00:49:15 -07:00
pub async fn scan_transparent_accounts(coin: u8, account: u32, gap_limit: usize) -> anyhow::Result<AddressBalanceVecT> {
let c = CoinConfig::get(coin);
let db = c.db()?;
let account_data = db.get_account_info(account)?;
let AccountData {
seed, aindex, ..
} = account_data;
let mut addresses = vec![];
if let Some(seed) = seed {
let mut client = c.connect_lwd().await?;
addresses.extend(crate::taddr::scan_transparent_accounts(c.chain.network(), &mut client, &seed, aindex, gap_limit).await?);
2022-12-30 07:29:59 -08:00
}
2023-03-13 00:49:15 -07:00
let addresses: Vec<_> = addresses.iter().map(|a| AddressBalanceT { index: a.index,
address: Some(a.address.clone()), balance: a.balance }).collect();
let addresses = AddressBalanceVecT {
values: Some(addresses)
};
Ok(addresses)
2022-06-08 05:48:16 -07:00
}
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);
2022-11-17 01:13:51 -08:00
let (seed, sk, ivk, pa, _ufvk) = decode_key(coin, key, 0)?;
2022-07-17 19:55:57 -07:00
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>,
2023-03-10 22:43:10 -08:00
) -> anyhow::Result<KeyPackT> {
2022-07-26 19:11:36 -07:00
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)?;
let seed = seed.ok_or_else(|| anyhow!("Account has no seed"))?;
2022-07-26 19:11:36 -07:00
derive_zip32(c.chain.network(), &seed, account, external, address)
}
2022-08-26 05:54:52 -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?
///
/// The address depends on the UA settings and may include transparent, sapling & orchard receivers
2022-12-21 14:54:25 -08:00
pub fn get_unified_address(coin: u8, id_account: u32, address_type: u8) -> anyhow::Result<String> {
let c = CoinConfig::get(coin);
let db = c.db()?;
2022-11-03 22:06:11 -07:00
let tpe = UnifiedAddressType {
2022-11-29 07:24:01 -08:00
transparent: address_type & 1 != 0,
sapling: address_type & 2 != 0,
orchard: address_type & 4 != 0,
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() {
2022-11-29 07:24:01 -08:00
get_unified_address(coin, id_account, address_type)?
2022-11-15 02:21:47 -08:00
} else {
get_sapling_address(coin, id_account)?
};
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())
}