Unified Y/Zcash
This commit is contained in:
parent
766748ccfb
commit
056ffa454a
|
@ -4,3 +4,4 @@
|
|||
src/generated/*.rs
|
||||
docs/_site/
|
||||
*.db
|
||||
Cargo.lock
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use sync::{scan_all, NETWORK};
|
||||
use sync::scan_all;
|
||||
use tokio::runtime::Runtime;
|
||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
|
||||
fn scan(c: &mut Criterion) {
|
||||
dotenv::dotenv().unwrap();
|
||||
|
@ -10,7 +10,7 @@ fn scan(c: &mut Criterion) {
|
|||
|
||||
let ivk = dotenv::var("IVK").unwrap();
|
||||
let fvk =
|
||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)
|
||||
decode_extended_full_viewing_key(Network::MainNetwork.hrp_sapling_extended_full_viewing_key(), &ivk)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
|
@ -19,7 +19,7 @@ fn scan(c: &mut Criterion) {
|
|||
c.bench_function("scan all", |b| {
|
||||
b.iter(|| {
|
||||
let r = Runtime::new().unwrap();
|
||||
r.block_on(scan_all(fvks.clone().as_slice())).unwrap();
|
||||
r.block_on(scan_all(&Network::MainNetwork, fvks.clone().as_slice())).unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
28
src/chain.rs
28
src/chain.rs
|
@ -2,7 +2,7 @@ use crate::commitment::{CTree, Witness};
|
|||
use crate::db::AccountViewKey;
|
||||
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||
use crate::lw_rpc::*;
|
||||
use crate::{advance_tree, NETWORK};
|
||||
use crate::advance_tree;
|
||||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
use log::info;
|
||||
|
@ -12,7 +12,7 @@ use std::time::Instant;
|
|||
use thiserror::Error;
|
||||
use tonic::transport::{Certificate, Channel, ClientTlsConfig};
|
||||
use tonic::Request;
|
||||
use zcash_primitives::consensus::{BlockHeight, NetworkUpgrade, Parameters};
|
||||
use zcash_primitives::consensus::{BlockHeight, Network, NetworkUpgrade, Parameters};
|
||||
use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness};
|
||||
use zcash_primitives::sapling::note_encryption::try_sapling_compact_note_decryption;
|
||||
use zcash_primitives::sapling::{Node, Note, PaymentAddress};
|
||||
|
@ -128,7 +128,8 @@ pub fn to_output_description(co: &CompactOutput) -> CompactOutputDescription {
|
|||
od
|
||||
}
|
||||
|
||||
fn decrypt_notes<'a>(
|
||||
fn decrypt_notes<'a, N: Parameters>(
|
||||
network: &N,
|
||||
block: &'a CompactBlock,
|
||||
vks: &HashMap<u32, AccountViewKey>,
|
||||
) -> DecryptedBlock<'a> {
|
||||
|
@ -147,7 +148,7 @@ fn decrypt_notes<'a>(
|
|||
for (&account, vk) in vks.iter() {
|
||||
let od = to_output_description(co);
|
||||
if let Some((note, pa)) =
|
||||
try_sapling_compact_note_decryption(&NETWORK, height, &vk.ivk, &od)
|
||||
try_sapling_compact_note_decryption(network, height, &vk.ivk, &od)
|
||||
{
|
||||
notes.push(DecryptedNote {
|
||||
account,
|
||||
|
@ -180,10 +181,10 @@ impl DecryptNode {
|
|||
DecryptNode { vks }
|
||||
}
|
||||
|
||||
pub fn decrypt_blocks<'a>(&self, blocks: &'a [CompactBlock]) -> Vec<DecryptedBlock<'a>> {
|
||||
pub fn decrypt_blocks<'a>(&self, network: &Network, blocks: &'a [CompactBlock]) -> Vec<DecryptedBlock<'a>> {
|
||||
let mut decrypted_blocks: Vec<DecryptedBlock> = blocks
|
||||
.par_iter()
|
||||
.map(|b| decrypt_notes(b, &self.vks))
|
||||
.map(|b| decrypt_notes(network, b, &self.vks))
|
||||
.collect();
|
||||
decrypted_blocks.sort_by(|a, b| a.height.cmp(&b.height));
|
||||
decrypted_blocks
|
||||
|
@ -307,10 +308,10 @@ pub async fn connect_lightwalletd(url: &str) -> anyhow::Result<CompactTxStreamer
|
|||
Ok(client)
|
||||
}
|
||||
|
||||
pub async fn sync(vks: HashMap<u32, AccountViewKey>, ld_url: &str) -> anyhow::Result<()> {
|
||||
pub async fn sync(network: &Network, vks: HashMap<u32, AccountViewKey>, ld_url: &str) -> anyhow::Result<()> {
|
||||
let decrypter = DecryptNode::new(vks);
|
||||
let mut client = connect_lightwalletd(ld_url).await?;
|
||||
let start_height: u32 = crate::NETWORK
|
||||
let start_height: u32 = network
|
||||
.activation_height(NetworkUpgrade::Sapling)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
@ -321,7 +322,7 @@ pub async fn sync(vks: HashMap<u32, AccountViewKey>, ld_url: &str) -> anyhow::Re
|
|||
eprintln!("Download chain: {} ms", start.elapsed().as_millis());
|
||||
|
||||
let start = Instant::now();
|
||||
let blocks = decrypter.decrypt_blocks(&cbs);
|
||||
let blocks = decrypter.decrypt_blocks(network, &cbs);
|
||||
eprintln!("Decrypt Notes: {} ms", start.elapsed().as_millis());
|
||||
|
||||
let start = Instant::now();
|
||||
|
@ -348,12 +349,13 @@ mod tests {
|
|||
use crate::db::AccountViewKey;
|
||||
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||
use crate::LWD_URL;
|
||||
use crate::NETWORK;
|
||||
use dotenv;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||
use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
|
||||
use zcash_primitives::consensus::{Network, NetworkUpgrade, Parameters};
|
||||
|
||||
const NETWORK: &Network = &Network::MainNetwork;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_latest_height() -> anyhow::Result<()> {
|
||||
|
@ -376,7 +378,7 @@ mod tests {
|
|||
fvks.insert(1, AccountViewKey::from_fvk(&fvk));
|
||||
let decrypter = DecryptNode::new(fvks);
|
||||
let mut client = CompactTxStreamerClient::connect(LWD_URL).await?;
|
||||
let start_height: u32 = crate::NETWORK
|
||||
let start_height: u32 = NETWORK
|
||||
.activation_height(NetworkUpgrade::Sapling)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
@ -387,7 +389,7 @@ mod tests {
|
|||
eprintln!("Download chain: {} ms", start.elapsed().as_millis());
|
||||
|
||||
let start = Instant::now();
|
||||
let blocks = decrypter.decrypt_blocks(&cbs);
|
||||
let blocks = decrypter.decrypt_blocks(&Network::MainNetwork, &cbs);
|
||||
eprintln!("Decrypt Notes: {} ms", start.elapsed().as_millis());
|
||||
|
||||
// no need to calculate tree before the first note if we can
|
||||
|
|
|
@ -87,13 +87,14 @@ impl ContactDecoder {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zcash_params::coin::CoinType;
|
||||
use crate::contact::{serialize_contacts, Contact};
|
||||
use crate::db::DEFAULT_DB_PATH;
|
||||
use crate::{DbAdapter, Wallet, LWD_URL};
|
||||
|
||||
#[test]
|
||||
fn test_contacts() {
|
||||
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
|
||||
let contact = Contact {
|
||||
id: 0,
|
||||
name: "hanh".to_string(),
|
||||
|
@ -106,14 +107,15 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_serialize() {
|
||||
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
|
||||
let contacts = db.get_unsaved_contacts().unwrap();
|
||||
let memos = serialize_contacts(&contacts).unwrap();
|
||||
for m in memos.iter() {
|
||||
println!("{:?}", m);
|
||||
}
|
||||
|
||||
let mut wallet = Wallet::new("zec.db", LWD_URL);
|
||||
let mut wallet = Wallet::new(CoinType::Zcash, "zec.db");
|
||||
wallet.set_lwd_url(LWD_URL).unwrap();
|
||||
let tx_id = wallet.save_contacts_tx(&memos, 1, 3).await.unwrap();
|
||||
println!("{}", tx_id);
|
||||
}
|
||||
|
|
132
src/db.rs
132
src/db.rs
|
@ -3,19 +3,17 @@ use crate::contact::Contact;
|
|||
use crate::prices::Quote;
|
||||
use crate::taddr::derive_tkeys;
|
||||
use crate::transaction::TransactionInfo;
|
||||
use crate::{CTree, Witness, NETWORK};
|
||||
use crate::{CTree, Witness};
|
||||
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::consensus::{Network, 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};
|
||||
use zcash_params::coin::{CoinType, get_coin_chain, get_coin_id};
|
||||
|
||||
mod migration;
|
||||
|
||||
|
@ -23,6 +21,7 @@ mod migration;
|
|||
pub const DEFAULT_DB_PATH: &str = "zec.db";
|
||||
|
||||
pub struct DbAdapter {
|
||||
pub coin_type: CoinType,
|
||||
pub connection: Connection,
|
||||
}
|
||||
|
||||
|
@ -63,8 +62,10 @@ impl AccountViewKey {
|
|||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AccountBackup {
|
||||
pub coin: u8,
|
||||
pub name: String,
|
||||
pub seed: Option<String>,
|
||||
pub index: u32,
|
||||
pub z_sk: Option<String>,
|
||||
pub ivk: String,
|
||||
pub z_addr: String,
|
||||
|
@ -73,10 +74,10 @@ pub struct AccountBackup {
|
|||
}
|
||||
|
||||
impl DbAdapter {
|
||||
pub fn new(db_path: &str) -> anyhow::Result<DbAdapter> {
|
||||
pub fn new(coin_type: CoinType, db_path: &str) -> anyhow::Result<DbAdapter> {
|
||||
let connection = Connection::open(db_path)?;
|
||||
connection.execute("PRAGMA synchronous = off", NO_PARAMS)?;
|
||||
Ok(DbAdapter { connection })
|
||||
Ok(DbAdapter { coin_type, connection })
|
||||
}
|
||||
|
||||
pub fn begin_transaction(&mut self) -> anyhow::Result<Transaction> {
|
||||
|
@ -103,6 +104,7 @@ impl DbAdapter {
|
|||
&self,
|
||||
name: &str,
|
||||
seed: Option<&str>,
|
||||
index: u32,
|
||||
sk: Option<&str>,
|
||||
ivk: &str,
|
||||
address: &str,
|
||||
|
@ -114,9 +116,9 @@ impl DbAdapter {
|
|||
return Ok(-1);
|
||||
}
|
||||
self.connection.execute(
|
||||
"INSERT INTO accounts(name, seed, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
"INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
||||
ON CONFLICT DO NOTHING",
|
||||
params![name, seed, sk, ivk, address],
|
||||
params![name, seed, index, sk, ivk, address],
|
||||
)?;
|
||||
let id_tx: i32 = self.connection.query_row(
|
||||
"SELECT id_account FROM accounts WHERE ivk = ?1",
|
||||
|
@ -126,6 +128,16 @@ impl DbAdapter {
|
|||
Ok(id_tx)
|
||||
}
|
||||
|
||||
pub fn next_account_id(&self, seed: &str) -> anyhow::Result<i32> {
|
||||
let index = self
|
||||
.connection
|
||||
.query_row("SELECT MAX(aindex) FROM accounts WHERE seed = ?1", [seed], |row| {
|
||||
let aindex: Option<i32> = row.get(0)?;
|
||||
Ok(aindex.unwrap_or(-1))
|
||||
})? + 1;
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub fn get_fvks(&self) -> anyhow::Result<HashMap<u32, AccountViewKey>> {
|
||||
let mut statement = self
|
||||
.connection
|
||||
|
@ -135,7 +147,7 @@ impl DbAdapter {
|
|||
let ivk: String = row.get(1)?;
|
||||
let sk: Option<String> = row.get(2)?;
|
||||
let fvk = decode_extended_full_viewing_key(
|
||||
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||
self.network().hrp_sapling_extended_full_viewing_key(),
|
||||
&ivk,
|
||||
)
|
||||
.unwrap()
|
||||
|
@ -327,7 +339,7 @@ impl DbAdapter {
|
|||
|
||||
pub fn get_db_height(&self) -> anyhow::Result<u32> {
|
||||
let height: u32 = self.get_last_sync_height()?.unwrap_or_else(|| {
|
||||
crate::NETWORK
|
||||
self.network()
|
||||
.activation_height(NetworkUpgrade::Sapling)
|
||||
.unwrap()
|
||||
.into()
|
||||
|
@ -696,8 +708,8 @@ impl DbAdapter {
|
|||
pub fn create_taddr(&self, account: u32) -> anyhow::Result<()> {
|
||||
let seed = self.get_seed(account)?;
|
||||
if let Some(seed) = seed {
|
||||
let bip44_path = format!("m/44'/{}'/0'/0/0", NETWORK.coin_type());
|
||||
let (sk, address) = derive_tkeys(&seed, &bip44_path)?;
|
||||
let bip44_path = format!("m/44'/{}'/0'/0/0", self.network().coin_type());
|
||||
let (sk, address) = derive_tkeys(self.network(), &seed, &bip44_path)?;
|
||||
self.connection.execute(
|
||||
"INSERT INTO taddrs(account, sk, address) VALUES (?1, ?2, ?3) \
|
||||
ON CONFLICT DO NOTHING",
|
||||
|
@ -829,22 +841,24 @@ impl DbAdapter {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
const NONCE: &'static[u8; 12] = b"unique nonce";
|
||||
|
||||
pub fn get_full_backup(&self, key: &str) -> anyhow::Result<String> {
|
||||
pub fn get_full_backup(&self) -> anyhow::Result<Vec<AccountBackup>> {
|
||||
let coin = get_coin_id(self.coin_type);
|
||||
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")?;
|
||||
"SELECT name, seed, aindex, 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)?;
|
||||
let index: u32 = r.get(2)?;
|
||||
let z_sk: Option<String> = r.get(3)?;
|
||||
let ivk: String = r.get(4)?;
|
||||
let z_addr: String = r.get(5)?;
|
||||
let t_sk: Option<String> = r.get(6)?;
|
||||
let t_addr: Option<String> = r.get(7)?;
|
||||
Ok(AccountBackup {
|
||||
coin,
|
||||
name,
|
||||
seed,
|
||||
index,
|
||||
z_sk,
|
||||
ivk,
|
||||
z_addr,
|
||||
|
@ -856,70 +870,48 @@ impl DbAdapter {
|
|||
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)
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
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)?;
|
||||
pub fn restore_full_backup(&self, accounts: &[AccountBackup]) -> anyhow::Result<()> {
|
||||
let coin = get_coin_id(self.coin_type);
|
||||
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])?;
|
||||
log::info!("{} {} {}", a.name, a.coin, coin);
|
||||
if a.coin == coin {
|
||||
let do_insert = || {
|
||||
self.connection.execute("INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1,?2,?3,?4,?5,?6)",
|
||||
params![a.name, a.seed, a.index, 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>(())
|
||||
};
|
||||
if let Err(e) = do_insert() {
|
||||
log::info!("{:?}", e);
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
};
|
||||
let _ = do_insert();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn network(&self) -> &'static Network {
|
||||
let chain = get_coin_chain(self.coin_type);
|
||||
chain.network()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bech32::{ToBase32, Variant};
|
||||
use zcash_params::coin::CoinType;
|
||||
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();
|
||||
let mut db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
|
||||
db.init_db().unwrap();
|
||||
db.trim_to_height(0).unwrap();
|
||||
|
||||
|
@ -956,7 +948,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_balance() {
|
||||
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
|
||||
let balance = db.get_balance(1).unwrap();
|
||||
println!("{}", balance);
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
|||
id_account INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
seed TEXT,
|
||||
aindex INTEGER NOT NULL,
|
||||
sk TEXT,
|
||||
ivk TEXT NOT NULL UNIQUE,
|
||||
address TEXT NOT NULL)",
|
||||
|
@ -91,6 +92,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
|||
rcm BLOB NOT NULL,
|
||||
nf BLOB NOT NULL UNIQUE,
|
||||
spent INTEGER,
|
||||
excluded BOOL,
|
||||
CONSTRAINT tx_output UNIQUE (tx, output_index))",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
|
@ -119,24 +121,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
|||
address TEXT NOT NULL)",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
}
|
||||
|
||||
if version < 2 {
|
||||
connection.execute("ALTER TABLE received_notes ADD excluded BOOL", NO_PARAMS)?;
|
||||
}
|
||||
|
||||
if version < 3 {
|
||||
connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS contacts (
|
||||
account INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
PRIMARY KEY (account, address))",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
}
|
||||
|
||||
if version < 4 {
|
||||
connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS historical_prices (
|
||||
currency TEXT NOT NULL,
|
||||
|
@ -145,14 +130,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
|||
PRIMARY KEY (currency, timestamp))",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
}
|
||||
|
||||
if version < 5 {
|
||||
connection.execute("DELETE FROM historical_prices", NO_PARAMS)?;
|
||||
}
|
||||
|
||||
if version < 6 {
|
||||
connection.execute("DROP TABLE contacts", NO_PARAMS)?;
|
||||
connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS contacts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
@ -163,19 +141,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
|||
)?;
|
||||
}
|
||||
|
||||
if version < 7 {
|
||||
connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS secret_shares (
|
||||
account INTEGER PRIMARY KEY,
|
||||
idx INTEGER NOT NULL,
|
||||
participants INTEGER NOT NULL,
|
||||
threshold INTEGER NOT NULL,
|
||||
secret TEXT NOT NULL)",
|
||||
NO_PARAMS,
|
||||
)?;
|
||||
}
|
||||
|
||||
update_schema_version(&connection, 7)?;
|
||||
update_schema_version(&connection, 1)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
137
src/key.rs
137
src/key.rs
|
@ -1,79 +1,104 @@
|
|||
use bech32::{ToBase32, Variant};
|
||||
use crate::NETWORK;
|
||||
use bip39::{Language, Mnemonic, Seed};
|
||||
use rand::RngCore;
|
||||
use rand::rngs::OsRng;
|
||||
use zcash_client_backend::address::RecipientAddress;
|
||||
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,
|
||||
};
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey};
|
||||
use zcash_params::coin::{CoinChain, CoinType, get_coin_chain};
|
||||
|
||||
pub fn decode_key(key: &str) -> anyhow::Result<(Option<String>, Option<String>, String, String)> {
|
||||
let res = if let Ok(mnemonic) = Mnemonic::from_phrase(&key, Language::English) {
|
||||
let (sk, ivk, pa) = derive_secret_key(&mnemonic)?;
|
||||
Ok((Some(key.to_string()), Some(sk), ivk, pa))
|
||||
} else if let Ok(Some(sk)) =
|
||||
decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &key)
|
||||
{
|
||||
let (ivk, pa) = derive_viewing_key(&sk)?;
|
||||
Ok((None, Some(key.to_string()), ivk, pa))
|
||||
} else if let Ok(Some(fvk)) =
|
||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &key)
|
||||
{
|
||||
let pa = derive_address(&fvk)?;
|
||||
Ok((None, None, key.to_string(), pa))
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Not a valid key"))
|
||||
};
|
||||
res
|
||||
pub struct KeyHelpers {
|
||||
coin_type: CoinType,
|
||||
}
|
||||
|
||||
pub fn is_valid_key(key: &str) -> i8 {
|
||||
if Mnemonic::from_phrase(&key, Language::English).is_ok() {
|
||||
return 0;
|
||||
impl KeyHelpers {
|
||||
pub fn new(coin_type: CoinType) -> Self {
|
||||
KeyHelpers {
|
||||
coin_type
|
||||
}
|
||||
}
|
||||
if let Ok(Some(_)) =
|
||||
decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &key)
|
||||
{
|
||||
return 1;
|
||||
|
||||
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
|
||||
|
||||
pub fn decode_key(&self, key: &str, index: u32) -> anyhow::Result<(Option<String>, Option<String>, String, String)> {
|
||||
let network = self.chain().network();
|
||||
let res = if let Ok(mnemonic) = Mnemonic::from_phrase(&key, Language::English) {
|
||||
let (sk, ivk, pa) = self.derive_secret_key(&mnemonic, index)?;
|
||||
Ok((Some(key.to_string()), Some(sk), ivk, pa))
|
||||
} else if let Ok(Some(sk)) =
|
||||
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &key)
|
||||
{
|
||||
let (ivk, pa) = self.derive_viewing_key(&sk)?;
|
||||
Ok((None, Some(key.to_string()), ivk, pa))
|
||||
} else if let Ok(Some(fvk)) =
|
||||
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &key)
|
||||
{
|
||||
let pa = self.derive_address(&fvk)?;
|
||||
Ok((None, None, key.to_string(), pa))
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Not a valid key"))
|
||||
};
|
||||
res
|
||||
}
|
||||
if let Ok(Some(_)) =
|
||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &key)
|
||||
{
|
||||
return 2;
|
||||
|
||||
pub fn is_valid_key(&self, key: &str) -> i8 {
|
||||
let network = self.chain().network();
|
||||
if Mnemonic::from_phrase(&key, Language::English).is_ok() {
|
||||
return 0;
|
||||
}
|
||||
if let Ok(Some(_)) =
|
||||
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &key)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if let Ok(Some(_)) =
|
||||
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &key)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
-1
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
pub fn derive_secret_key(mnemonic: &Mnemonic) -> anyhow::Result<(String, String, String)> {
|
||||
let seed = Seed::new(&mnemonic, "");
|
||||
let master = ExtendedSpendingKey::master(seed.as_bytes());
|
||||
let path = [
|
||||
ChildIndex::Hardened(32),
|
||||
ChildIndex::Hardened(NETWORK.coin_type()),
|
||||
ChildIndex::Hardened(0),
|
||||
];
|
||||
let extsk = ExtendedSpendingKey::from_path(&master, &path);
|
||||
let sk = encode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &extsk);
|
||||
pub fn derive_secret_key(&self, mnemonic: &Mnemonic, index: u32) -> anyhow::Result<(String, String, String)> {
|
||||
let network = self.chain().network();
|
||||
let seed = Seed::new(&mnemonic, "");
|
||||
let master = ExtendedSpendingKey::master(seed.as_bytes());
|
||||
let path = [
|
||||
ChildIndex::Hardened(32),
|
||||
ChildIndex::Hardened(network.coin_type()),
|
||||
ChildIndex::Hardened(index),
|
||||
];
|
||||
let extsk = ExtendedSpendingKey::from_path(&master, &path);
|
||||
let sk = encode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk);
|
||||
|
||||
let (fvk, pa) = derive_viewing_key(&extsk)?;
|
||||
Ok((sk, fvk, pa))
|
||||
}
|
||||
let (fvk, pa) = self.derive_viewing_key(&extsk)?;
|
||||
Ok((sk, fvk, pa))
|
||||
}
|
||||
|
||||
pub fn derive_viewing_key(extsk: &ExtendedSpendingKey) -> anyhow::Result<(String, String)> {
|
||||
let fvk = ExtendedFullViewingKey::from(extsk);
|
||||
let pa = derive_address(&fvk)?;
|
||||
let fvk =
|
||||
encode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk);
|
||||
Ok((fvk, pa))
|
||||
}
|
||||
pub fn derive_viewing_key(&self, extsk: &ExtendedSpendingKey) -> anyhow::Result<(String, String)> {
|
||||
let network = self.chain().network();
|
||||
let fvk = ExtendedFullViewingKey::from(extsk);
|
||||
let pa = self.derive_address(&fvk)?;
|
||||
let fvk =
|
||||
encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk);
|
||||
Ok((fvk, pa))
|
||||
}
|
||||
|
||||
pub fn derive_address(fvk: &ExtendedFullViewingKey) -> anyhow::Result<String> {
|
||||
let (_, payment_address) = fvk.default_address().unwrap();
|
||||
let address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &payment_address);
|
||||
Ok(address)
|
||||
pub fn derive_address(&self, fvk: &ExtendedFullViewingKey) -> anyhow::Result<String> {
|
||||
let network = self.chain().network();
|
||||
let (_, payment_address) = fvk.default_address().unwrap();
|
||||
let address = encode_payment_address(network.hrp_sapling_payment_address(), &payment_address);
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
pub fn valid_address(&self, address: &str) -> bool {
|
||||
let recipient = RecipientAddress::decode(self.chain().network(), address);
|
||||
recipient.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_random_enc_key() -> anyhow::Result<String> {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#[path = "generated/cash.z.wallet.sdk.rpc.rs"]
|
||||
pub mod lw_rpc;
|
||||
|
||||
pub use zcash_params::coin::{get_branch, NETWORK, TICKER};
|
||||
pub use zcash_params::coin::{CoinType, get_coin_type, get_branch};
|
||||
|
||||
// Mainnet
|
||||
// pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
|
||||
|
@ -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, generate_random_enc_key};
|
||||
pub use crate::key::{KeyHelpers, generate_random_enc_key};
|
||||
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||
pub use crate::lw_rpc::*;
|
||||
pub use crate::mempool::MemPool;
|
||||
|
@ -58,4 +58,4 @@ pub use crate::pay::{broadcast_tx, Tx, TxIn, TxOut};
|
|||
pub use crate::print::*;
|
||||
pub use crate::scan::{latest_height, scan_all, sync_async};
|
||||
pub use crate::ua::{get_sapling, get_ua};
|
||||
pub use crate::wallet::{RecipientMemo, Wallet, WalletBalance};
|
||||
pub use crate::wallet::{RecipientMemo, Wallet, WalletBalance, encrypt_backup, decrypt_backup};
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use std::time::Instant;
|
||||
use sync::{advance_tree, scan_all, CTree, Witness, NETWORK};
|
||||
use sync::{advance_tree, scan_all, CTree, Witness};
|
||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness};
|
||||
use zcash_primitives::sapling::Node;
|
||||
|
||||
const NETWORK: Network = Network::MainNetwork;
|
||||
|
||||
#[tokio::main]
|
||||
#[allow(dead_code)]
|
||||
async fn main_scan() {
|
||||
|
@ -17,7 +19,7 @@ async fn main_scan() {
|
|||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
scan_all(&vec![fvk]).await.unwrap();
|
||||
scan_all(&NETWORK, &vec![fvk]).await.unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
use clap::{App, Arg};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use sync::{decode_key, Tx, NETWORK};
|
||||
use std::str::FromStr;
|
||||
use sync::{KeyHelpers, Tx};
|
||||
use zcash_client_backend::encoding::decode_extended_spending_key;
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
use zcash_proofs::prover::LocalTxProver;
|
||||
use zcash_params::coin::CoinType;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let key = dotenv::var("KEY").unwrap();
|
||||
let (_seed, sk, _ivk, _address) = decode_key(&key)?;
|
||||
|
||||
let matches = App::new("Multisig CLI")
|
||||
let matches = App::new("Cold wallet Signer CLI")
|
||||
.version("1.0")
|
||||
.arg(
|
||||
Arg::with_name("coin")
|
||||
.short("coin")
|
||||
.long("coin")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("tx_filename")
|
||||
.short("tx")
|
||||
|
@ -27,12 +32,23 @@ async fn main() -> anyhow::Result<()> {
|
|||
)
|
||||
.get_matches();
|
||||
|
||||
let tx_filename = matches.value_of("tx_filename").unwrap();
|
||||
let out_filename = matches.value_of("out_filename").unwrap();
|
||||
let coin = matches.value_of("coin").expect("coin argument missing");
|
||||
let tx_filename = matches.value_of("tx_filename").expect("input filename missing");
|
||||
let out_filename = matches.value_of("out_filename").expect("output filename missing");
|
||||
|
||||
let (coin_type, network) = match coin {
|
||||
"zcash" => (CoinType::Zcash, Network::MainNetwork),
|
||||
"ycash" => (CoinType::Ycash, Network::YCashMainNetwork),
|
||||
_ => panic!("Invalid coin")
|
||||
};
|
||||
let key = dotenv::var("KEY").unwrap();
|
||||
let index = u32::from_str(&dotenv::var("INDEX").unwrap_or_else(|_| "0".to_string())).unwrap();
|
||||
let kh = KeyHelpers::new(coin_type);
|
||||
let (_seed, sk, _ivk, _address) = kh.decode_key(&key, index)?;
|
||||
|
||||
let sk = sk.unwrap();
|
||||
let sk =
|
||||
decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &sk)?.unwrap();
|
||||
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &sk)?.unwrap();
|
||||
|
||||
let mut file = File::open(tx_filename)?;
|
||||
let mut s = String::new();
|
||||
|
|
|
@ -10,11 +10,12 @@ use zcash_client_backend::data_api::wallet::ANCHOR_OFFSET;
|
|||
use zcash_primitives::memo::Memo;
|
||||
use zcash_primitives::merkle_tree::Hashable;
|
||||
use zcash_primitives::sapling::Node;
|
||||
use zcash_params::coin::CoinType;
|
||||
|
||||
const DB_NAME: &str = "zec.db";
|
||||
|
||||
fn init() {
|
||||
let db = DbAdapter::new(DB_NAME).unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, DB_NAME).unwrap();
|
||||
db.init_db().unwrap();
|
||||
}
|
||||
|
||||
|
@ -31,8 +32,9 @@ async fn test() -> anyhow::Result<()> {
|
|||
let progress = |height| {
|
||||
log::info!("Height = {}", height);
|
||||
};
|
||||
let mut wallet = Wallet::new(DB_NAME, LWD_URL);
|
||||
wallet.new_account_with_key("main", &seed).unwrap();
|
||||
let mut wallet = Wallet::new(CoinType::Zcash, DB_NAME);
|
||||
wallet.set_lwd_url(LWD_URL).unwrap();
|
||||
wallet.new_account_with_key("main", &seed, 0).unwrap();
|
||||
// wallet.new_account_with_key("test", &seed2).unwrap();
|
||||
// wallet.new_account_with_key("zecpages", &ivk).unwrap();
|
||||
|
||||
|
@ -94,7 +96,8 @@ async fn test_sync() {
|
|||
log::info!("Height = {}", height);
|
||||
};
|
||||
|
||||
let wallet = Wallet::new(DB_NAME, LWD_URL);
|
||||
let mut wallet = Wallet::new(CoinType::Zcash, DB_NAME);
|
||||
wallet.set_lwd_url(LWD_URL).unwrap();
|
||||
wallet.sync(true, ANCHOR_OFFSET, progress).await.unwrap();
|
||||
}
|
||||
|
||||
|
@ -109,13 +112,13 @@ fn test_make_wallet() {
|
|||
|
||||
#[allow(dead_code)]
|
||||
fn test_rewind() {
|
||||
let mut db = DbAdapter::new(DB_NAME).unwrap();
|
||||
let mut db = DbAdapter::new(CoinType::Zcash, DB_NAME).unwrap();
|
||||
db.trim_to_height(1314000).unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn test_get_balance() {
|
||||
let db = DbAdapter::new(DB_NAME).unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, DB_NAME).unwrap();
|
||||
let balance = db.get_balance(1).unwrap();
|
||||
println!("Balance = {}", (balance as f64) / 100_000_000.0);
|
||||
}
|
||||
|
@ -138,7 +141,7 @@ fn test_invalid_witness() {
|
|||
|
||||
#[allow(dead_code)]
|
||||
fn w() {
|
||||
let db = DbAdapter::new("zec.db").unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, "zec.db").unwrap();
|
||||
// let w_b: Vec<u8> = db.connection.query_row("SELECT witness FROM sapling_witnesses WHERE note = 66 AND height = 1466097", NO_PARAMS, |row| row.get(0)).unwrap();
|
||||
// let w = Witness::read(0, &*w_b).unwrap();
|
||||
// print_witness2(&w);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::chain::to_output_description;
|
||||
use crate::{
|
||||
connect_lightwalletd, get_latest_height, CompactTx, CompactTxStreamerClient, DbAdapter,
|
||||
Exclude, NETWORK,
|
||||
Exclude
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use tonic::transport::Channel;
|
||||
|
@ -10,6 +10,7 @@ use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
|||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||
use zcash_primitives::sapling::note_encryption::try_sapling_compact_note_decryption;
|
||||
use zcash_primitives::sapling::SaplingIvk;
|
||||
use zcash_params::coin::{CoinChain, CoinType, get_coin_chain};
|
||||
|
||||
const DEFAULT_EXCLUDE_LEN: u8 = 1;
|
||||
|
||||
|
@ -20,6 +21,7 @@ struct MemPoolTransacton {
|
|||
}
|
||||
|
||||
pub struct MemPool {
|
||||
coin_type: CoinType,
|
||||
db_path: String,
|
||||
account: u32,
|
||||
ivk: Option<SaplingIvk>,
|
||||
|
@ -31,8 +33,9 @@ pub struct MemPool {
|
|||
}
|
||||
|
||||
impl MemPool {
|
||||
pub fn new(db_path: &str, ld_url: &str) -> MemPool {
|
||||
pub fn new(coin_type: CoinType, db_path: &str) -> MemPool {
|
||||
MemPool {
|
||||
coin_type,
|
||||
db_path: db_path.to_string(),
|
||||
account: 0,
|
||||
ivk: None,
|
||||
|
@ -40,12 +43,12 @@ impl MemPool {
|
|||
transactions: HashMap::new(),
|
||||
nfs: HashMap::new(),
|
||||
balance: 0,
|
||||
ld_url: ld_url.to_string(),
|
||||
ld_url: "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_account(&mut self, account: u32) -> anyhow::Result<()> {
|
||||
let db = DbAdapter::new(&self.db_path)?;
|
||||
let db = DbAdapter::new(self.coin_type, &self.db_path)?;
|
||||
let ivk = db.get_ivk(account)?;
|
||||
self.account = account;
|
||||
self.set_ivk(&ivk);
|
||||
|
@ -55,7 +58,7 @@ impl MemPool {
|
|||
|
||||
fn set_ivk(&mut self, ivk: &str) {
|
||||
let fvk =
|
||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)
|
||||
decode_extended_full_viewing_key(self.chain().network().hrp_sapling_extended_full_viewing_key(), &ivk)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let ivk = fvk.fvk.vk.ivk();
|
||||
|
@ -82,7 +85,7 @@ impl MemPool {
|
|||
}
|
||||
|
||||
pub fn clear(&mut self, height: u32) -> anyhow::Result<()> {
|
||||
let db = DbAdapter::new(&self.db_path)?;
|
||||
let db = DbAdapter::new(self.coin_type, &self.db_path)?;
|
||||
self.height = BlockHeight::from_u32(height);
|
||||
self.nfs = db.get_nullifier_amounts(self.account, true)?;
|
||||
self.transactions.clear();
|
||||
|
@ -141,7 +144,7 @@ impl MemPool {
|
|||
for co in tx.outputs.iter() {
|
||||
let od = to_output_description(co);
|
||||
if let Some((note, _)) =
|
||||
try_sapling_compact_note_decryption(&NETWORK, self.height, ivk, &od)
|
||||
try_sapling_compact_note_decryption(self.chain().network(), self.height, ivk, &od)
|
||||
{
|
||||
balance += note.value as i64; // value is incoming
|
||||
}
|
||||
|
@ -149,6 +152,13 @@ impl MemPool {
|
|||
|
||||
balance
|
||||
}
|
||||
|
||||
pub fn set_lwd_url(&mut self, ld_url: &str) -> anyhow::Result<()> {
|
||||
self.ld_url = ld_url.to_string();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -157,12 +167,14 @@ mod tests {
|
|||
use crate::mempool::MemPool;
|
||||
use crate::{DbAdapter, LWD_URL};
|
||||
use std::time::Duration;
|
||||
use zcash_params::coin::CoinType;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_mempool() {
|
||||
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
|
||||
let ivk = db.get_ivk(1).unwrap();
|
||||
let mut mempool = MemPool::new("zec.db", LWD_URL);
|
||||
let mut mempool = MemPool::new(CoinType::Zcash, "zec.db");
|
||||
mempool.set_lwd_url(LWD_URL).unwrap();
|
||||
mempool.set_ivk(&ivk);
|
||||
loop {
|
||||
mempool.scan().await.unwrap();
|
||||
|
|
32
src/pay.rs
32
src/pay.rs
|
@ -2,7 +2,7 @@ use crate::db::SpendableNote;
|
|||
use crate::wallet::RecipientMemo;
|
||||
use crate::{
|
||||
connect_lightwalletd, get_branch, get_latest_height, hex_to_hash, GetAddressUtxosReply,
|
||||
RawTransaction, NETWORK,
|
||||
RawTransaction
|
||||
};
|
||||
use jubjub::Fr;
|
||||
use rand::prelude::SliceRandom;
|
||||
|
@ -27,9 +27,11 @@ use zcash_primitives::transaction::builder::{Builder, Progress};
|
|||
use zcash_primitives::transaction::components::amount::{DEFAULT_FEE, MAX_MONEY};
|
||||
use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut as ZTxOut};
|
||||
use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
|
||||
use zcash_params::coin::{CoinChain, CoinType, get_coin_chain};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Tx {
|
||||
pub coin_type: CoinType,
|
||||
pub height: u32,
|
||||
pub t_inputs: Vec<TTxIn>,
|
||||
pub inputs: Vec<TxIn>,
|
||||
|
@ -39,8 +41,9 @@ pub struct Tx {
|
|||
}
|
||||
|
||||
impl Tx {
|
||||
pub fn new(height: u32) -> Self {
|
||||
pub fn new(coin_type: CoinType, height: u32) -> Self {
|
||||
Tx {
|
||||
coin_type,
|
||||
height,
|
||||
t_inputs: vec![],
|
||||
inputs: vec![],
|
||||
|
@ -78,12 +81,14 @@ pub struct TxOut {
|
|||
|
||||
pub struct TxBuilder {
|
||||
pub tx: Tx,
|
||||
coin_type: CoinType,
|
||||
}
|
||||
|
||||
impl TxBuilder {
|
||||
pub fn new(height: u32) -> Self {
|
||||
pub fn new(coin_type: CoinType, height: u32) -> Self {
|
||||
TxBuilder {
|
||||
tx: Tx::new(height),
|
||||
coin_type,
|
||||
tx: Tx::new(coin_type, height),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +112,7 @@ impl TxBuilder {
|
|||
let tx_in = TxIn {
|
||||
diversifier: hex::encode(diversifier.0),
|
||||
fvk: encode_extended_full_viewing_key(
|
||||
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||
self.chain().network().hrp_sapling_extended_full_viewing_key(),
|
||||
&fvk,
|
||||
),
|
||||
amount: u64::from(amount),
|
||||
|
@ -151,7 +156,7 @@ impl TxBuilder {
|
|||
ovk: &OutgoingViewingKey,
|
||||
address: &PaymentAddress,
|
||||
) -> anyhow::Result<()> {
|
||||
self.tx.change = encode_payment_address(NETWORK.hrp_sapling_payment_address(), address);
|
||||
self.tx.change = encode_payment_address(self.chain().network().hrp_sapling_payment_address(), address);
|
||||
self.tx.ovk = hex::encode(ovk.0);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -241,7 +246,7 @@ impl TxBuilder {
|
|||
self.set_change(&ovk, &change)?;
|
||||
|
||||
for r in recipients.iter() {
|
||||
let to_addr = RecipientAddress::decode(&NETWORK, &r.address)
|
||||
let to_addr = RecipientAddress::decode(self.chain().network(), &r.address)
|
||||
.ok_or(anyhow::anyhow!("Invalid address"))?;
|
||||
let memo = &r.memo;
|
||||
|
||||
|
@ -274,6 +279,8 @@ impl TxBuilder {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
|
||||
}
|
||||
|
||||
impl Tx {
|
||||
|
@ -287,13 +294,14 @@ impl Tx {
|
|||
prover: &impl TxProver<OsRng>,
|
||||
progress_callback: impl Fn(Progress) + Send + 'static,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let chain = get_coin_chain(self.coin_type);
|
||||
let last_height = BlockHeight::from_u32(self.height as u32);
|
||||
let mut builder = Builder::new(NETWORK, last_height);
|
||||
let mut builder = Builder::new(*chain.network(), last_height);
|
||||
|
||||
let ovk = hex_to_hash(&self.ovk)?;
|
||||
builder.send_change_to(
|
||||
OutgoingViewingKey(ovk),
|
||||
decode_payment_address(NETWORK.hrp_sapling_payment_address(), &self.change)
|
||||
decode_payment_address(chain.network().hrp_sapling_payment_address(), &self.change)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
);
|
||||
|
@ -320,7 +328,7 @@ impl Tx {
|
|||
hex::decode_to_slice(&txin.diversifier, &mut diversifier)?;
|
||||
let diversifier = Diversifier(diversifier);
|
||||
let fvk = decode_extended_full_viewing_key(
|
||||
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||
chain.network().hrp_sapling_extended_full_viewing_key(),
|
||||
&txin.fvk,
|
||||
)?
|
||||
.unwrap();
|
||||
|
@ -339,7 +347,7 @@ impl Tx {
|
|||
}
|
||||
|
||||
for txout in self.outputs.iter() {
|
||||
let recipient = RecipientAddress::decode(&NETWORK, &txout.addr).unwrap();
|
||||
let recipient = RecipientAddress::decode(chain.network(), &txout.addr).unwrap();
|
||||
let amount = Amount::from_u64(txout.amount).unwrap();
|
||||
match recipient {
|
||||
RecipientAddress::Transparent(ta) => {
|
||||
|
@ -367,7 +375,7 @@ impl Tx {
|
|||
progress_callback(progress);
|
||||
}
|
||||
});
|
||||
let consensus_branch_id = get_branch(u32::from(last_height));
|
||||
let consensus_branch_id = get_branch(chain.network(), u32::from(last_height));
|
||||
let (tx, _) = builder.build(consensus_branch_id, prover)?;
|
||||
let mut raw_tx = vec![];
|
||||
tx.write(&mut raw_tx)?;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{DbAdapter, TICKER};
|
||||
use crate::DbAdapter;
|
||||
use chrono::NaiveDateTime;
|
||||
use zcash_params::coin::get_coin_chain;
|
||||
|
||||
const DAY_SEC: i64 = 24 * 3600;
|
||||
|
||||
|
@ -15,6 +16,7 @@ pub async fn fetch_historical_prices(
|
|||
currency: &str,
|
||||
db: &DbAdapter,
|
||||
) -> anyhow::Result<Vec<Quote>> {
|
||||
let chain = get_coin_chain(db.coin_type);
|
||||
let json_error = || anyhow::anyhow!("Invalid JSON");
|
||||
let today = now / DAY_SEC;
|
||||
let from_day = today - days as i64;
|
||||
|
@ -33,7 +35,7 @@ pub async fn fetch_historical_prices(
|
|||
let client = reqwest::Client::new();
|
||||
let url = format!(
|
||||
"https://api.coingecko.com/api/v3/coins/{}/market_chart/range",
|
||||
TICKER
|
||||
chain.ticker()
|
||||
);
|
||||
let params = [
|
||||
("from", from.to_string()),
|
||||
|
@ -69,11 +71,12 @@ mod tests {
|
|||
use crate::prices::fetch_historical_prices;
|
||||
use crate::DbAdapter;
|
||||
use std::time::SystemTime;
|
||||
use zcash_params::coin::CoinType;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_fetch_quotes() {
|
||||
let currency = "EUR";
|
||||
let mut db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||
let mut db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
|
|
22
src/scan.rs
22
src/scan.rs
|
@ -17,11 +17,12 @@ use std::sync::Arc;
|
|||
use std::time::Instant;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::Mutex;
|
||||
use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
|
||||
use zcash_primitives::consensus::{Network, NetworkUpgrade, Parameters};
|
||||
use zcash_primitives::sapling::Node;
|
||||
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||
use zcash_params::coin::{CoinType, get_coin_chain};
|
||||
|
||||
pub async fn scan_all(fvks: &[ExtendedFullViewingKey]) -> anyhow::Result<()> {
|
||||
pub async fn scan_all(network: &Network, fvks: &[ExtendedFullViewingKey]) -> anyhow::Result<()> {
|
||||
let fvks: HashMap<_, _> = fvks
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -31,7 +32,7 @@ pub async fn scan_all(fvks: &[ExtendedFullViewingKey]) -> anyhow::Result<()> {
|
|||
|
||||
let total_start = Instant::now();
|
||||
let mut client = CompactTxStreamerClient::connect(LWD_URL).await?;
|
||||
let start_height: u32 = crate::NETWORK
|
||||
let start_height: u32 = network
|
||||
.activation_height(NetworkUpgrade::Sapling)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
@ -42,7 +43,7 @@ pub async fn scan_all(fvks: &[ExtendedFullViewingKey]) -> anyhow::Result<()> {
|
|||
info!("Download chain: {} ms", start.elapsed().as_millis());
|
||||
|
||||
let start = Instant::now();
|
||||
let blocks = decrypter.decrypt_blocks(&cbs);
|
||||
let blocks = decrypter.decrypt_blocks(network, &cbs);
|
||||
info!("Decrypt Notes: {} ms", start.elapsed().as_millis());
|
||||
|
||||
let witnesses = calculate_tree_state_v2(&cbs, &blocks);
|
||||
|
@ -85,6 +86,7 @@ pub struct TxIdHeight {
|
|||
}
|
||||
|
||||
pub async fn sync_async(
|
||||
coin_type: CoinType,
|
||||
chunk_size: u32,
|
||||
get_tx: bool,
|
||||
db_path: &str,
|
||||
|
@ -94,10 +96,14 @@ pub async fn sync_async(
|
|||
) -> anyhow::Result<()> {
|
||||
let ld_url = ld_url.to_owned();
|
||||
let db_path = db_path.to_string();
|
||||
let network = {
|
||||
let chain = get_coin_chain(coin_type);
|
||||
chain.network().clone()
|
||||
};
|
||||
|
||||
let mut client = connect_lightwalletd(&ld_url).await?;
|
||||
let (start_height, mut prev_hash, vks) = {
|
||||
let db = DbAdapter::new(&db_path)?;
|
||||
let db = DbAdapter::new(coin_type, &db_path)?;
|
||||
let height = db.get_db_height()?;
|
||||
let hash = db.get_db_hash(height)?;
|
||||
let vks = db.get_fvks()?;
|
||||
|
@ -137,7 +143,7 @@ pub async fn sync_async(
|
|||
let proc_callback = progress_callback.clone();
|
||||
|
||||
let processor = tokio::spawn(async move {
|
||||
let mut db = DbAdapter::new(&db_path)?;
|
||||
let mut db = DbAdapter::new(coin_type, &db_path)?;
|
||||
let mut nfs = db.get_nullifiers()?;
|
||||
|
||||
let (mut tree, mut witnesses) = db.get_tree()?;
|
||||
|
@ -156,7 +162,7 @@ pub async fn sync_async(
|
|||
{
|
||||
// db tx scope
|
||||
let db_tx = db.begin_transaction()?;
|
||||
let dec_blocks = decrypter.decrypt_blocks(&blocks.0);
|
||||
let dec_blocks = decrypter.decrypt_blocks(&network, &blocks.0);
|
||||
log::info!("Dec start : {}", dec_blocks[0].height);
|
||||
let start = Instant::now();
|
||||
for b in dec_blocks.iter() {
|
||||
|
@ -303,7 +309,7 @@ pub async fn sync_async(
|
|||
return c;
|
||||
});
|
||||
let ids: Vec<_> = ids.into_iter().map(|e| e.id_tx).collect();
|
||||
retrieve_tx_info(&mut client, &db_path2, &ids).await?;
|
||||
retrieve_tx_info(coin_type, &mut client, &db_path2, &ids).await?;
|
||||
}
|
||||
log::info!("Transaction Details : {}", start.elapsed().as_millis());
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, GetAddressUtxosReply,
|
||||
NETWORK,
|
||||
};
|
||||
use bip39::{Language, Mnemonic, Seed};
|
||||
use ripemd160::{Digest, Ripemd160};
|
||||
|
@ -10,7 +9,7 @@ use tiny_hderive::bip32::ExtendedPrivKey;
|
|||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
use zcash_client_backend::encoding::encode_transparent_address;
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
use zcash_primitives::legacy::TransparentAddress;
|
||||
|
||||
pub async fn get_taddr_balance(
|
||||
|
@ -49,7 +48,7 @@ pub async fn get_utxos(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn derive_tkeys(phrase: &str, path: &str) -> anyhow::Result<(String, String)> {
|
||||
pub fn derive_tkeys(network: &Network, phrase: &str, path: &str) -> anyhow::Result<(String, String)> {
|
||||
let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?;
|
||||
let seed = Seed::new(&mnemonic, "");
|
||||
let secp = Secp256k1::<All>::new();
|
||||
|
@ -60,8 +59,8 @@ pub fn derive_tkeys(phrase: &str, path: &str) -> anyhow::Result<(String, String)
|
|||
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
|
||||
let address = TransparentAddress::PublicKey(pub_key.into());
|
||||
let address = encode_transparent_address(
|
||||
&NETWORK.b58_pubkey_address_prefix(),
|
||||
&NETWORK.b58_script_address_prefix(),
|
||||
&network.b58_pubkey_address_prefix(),
|
||||
&network.b58_script_address_prefix(),
|
||||
&address,
|
||||
);
|
||||
let sk = secret_key.to_string();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::contact::{Contact, ContactDecoder};
|
||||
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter, NETWORK};
|
||||
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter};
|
||||
use futures::StreamExt;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
|
@ -10,13 +10,14 @@ use tonic::Request;
|
|||
use zcash_client_backend::encoding::{
|
||||
decode_extended_full_viewing_key, encode_payment_address, encode_transparent_address,
|
||||
};
|
||||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||
use zcash_primitives::consensus::{BlockHeight, Network, Parameters};
|
||||
use zcash_primitives::memo::Memo;
|
||||
use zcash_primitives::sapling::note_encryption::{
|
||||
try_sapling_note_decryption, try_sapling_output_recovery,
|
||||
};
|
||||
use zcash_primitives::transaction::Transaction;
|
||||
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||
use zcash_params::coin::{CoinType, get_coin_chain};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TransactionInfo {
|
||||
|
@ -39,6 +40,7 @@ pub struct ContactRef {
|
|||
}
|
||||
|
||||
pub async fn decode_transaction(
|
||||
network: &Network,
|
||||
client: &mut CompactTxStreamerClient<Channel>,
|
||||
nfs: &HashMap<(u32, Vec<u8>), u64>,
|
||||
id_tx: u32,
|
||||
|
@ -79,29 +81,29 @@ pub async fn decode_transaction(
|
|||
for output in tx.vout.iter() {
|
||||
if let Some(taddr) = output.script_pubkey.address() {
|
||||
address = encode_transparent_address(
|
||||
&NETWORK.b58_pubkey_address_prefix(),
|
||||
&NETWORK.b58_script_address_prefix(),
|
||||
&network.b58_pubkey_address_prefix(),
|
||||
&network.b58_script_address_prefix(),
|
||||
&taddr,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for output in tx.shielded_outputs.iter() {
|
||||
if let Some((note, pa, memo)) = try_sapling_note_decryption(&NETWORK, height, &ivk, output)
|
||||
if let Some((note, pa, memo)) = try_sapling_note_decryption(network, height, &ivk, output)
|
||||
{
|
||||
amount += note.value as i64; // change or self transfer
|
||||
let _ = contact_decoder.add_memo(&memo); // ignore memo that is not for contacts
|
||||
let memo = Memo::try_from(memo)?;
|
||||
if address.is_empty() {
|
||||
address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
|
||||
address = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
|
||||
}
|
||||
if memo != Memo::Empty {
|
||||
tx_memo = memo;
|
||||
}
|
||||
} else if let Some((_note, pa, memo)) =
|
||||
try_sapling_output_recovery(&NETWORK, height, &ovk, &output)
|
||||
try_sapling_output_recovery(network, height, &ovk, &output)
|
||||
{
|
||||
address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
|
||||
address = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
|
||||
let memo = Memo::try_from(memo)?;
|
||||
if memo != Memo::Empty {
|
||||
tx_memo = memo;
|
||||
|
@ -146,11 +148,16 @@ struct DecodeTxParams<'a> {
|
|||
}
|
||||
|
||||
pub async fn retrieve_tx_info(
|
||||
coin_type: CoinType,
|
||||
client: &mut CompactTxStreamerClient<Channel>,
|
||||
db_path: &str,
|
||||
tx_ids: &[u32],
|
||||
) -> anyhow::Result<()> {
|
||||
let db = DbAdapter::new(db_path)?;
|
||||
let network = {
|
||||
let chain = get_coin_chain(coin_type);
|
||||
chain.network().clone()
|
||||
};
|
||||
let db = DbAdapter::new(coin_type, db_path)?;
|
||||
|
||||
let nfs = db.get_nullifiers_raw()?;
|
||||
let mut nf_map: HashMap<(u32, Vec<u8>), u64> = HashMap::new();
|
||||
|
@ -163,7 +170,7 @@ pub async fn retrieve_tx_info(
|
|||
for (index, &id_tx) in tx_ids.iter().enumerate() {
|
||||
let (account, height, tx_hash, ivk) = db.get_txhash(id_tx)?;
|
||||
let fvk: &ExtendedFullViewingKey = fvk_cache.entry(account).or_insert_with(|| {
|
||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)
|
||||
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &ivk)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
});
|
||||
|
@ -183,6 +190,7 @@ pub async fn retrieve_tx_info(
|
|||
|
||||
let res = tokio_stream::iter(decode_tx_params).for_each_concurrent(None, |mut p| async move {
|
||||
if let Ok(tx_info) = decode_transaction(
|
||||
&network,
|
||||
&mut p.client,
|
||||
p.nf_map,
|
||||
p.id_tx,
|
||||
|
@ -229,10 +237,11 @@ pub async fn retrieve_tx_info(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::transaction::decode_transaction;
|
||||
use crate::{connect_lightwalletd, DbAdapter, LWD_URL, NETWORK};
|
||||
use crate::{connect_lightwalletd, DbAdapter, LWD_URL};
|
||||
use std::collections::HashMap;
|
||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
use zcash_params::coin::CoinType;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_decode_transaction() {
|
||||
|
@ -240,7 +249,7 @@ mod tests {
|
|||
hex::decode("b47da170329dc311b98892eac23e83025f8bb3ce10bb07535698c91fb37e1e54")
|
||||
.unwrap();
|
||||
let mut client = connect_lightwalletd(LWD_URL).await.unwrap();
|
||||
let db = DbAdapter::new("./zec.db").unwrap();
|
||||
let db = DbAdapter::new(CoinType::Zcash, "./zec.db").unwrap();
|
||||
let account = 1;
|
||||
let nfs = db.get_nullifiers_raw().unwrap();
|
||||
let mut nf_map: HashMap<(u32, Vec<u8>), u64> = HashMap::new();
|
||||
|
@ -251,11 +260,11 @@ mod tests {
|
|||
}
|
||||
let fvk = db.get_ivk(account).unwrap();
|
||||
let fvk =
|
||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
|
||||
decode_extended_full_viewing_key(Network::MainNetwork.hrp_sapling_extended_full_viewing_key(), &fvk)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let tx_info =
|
||||
decode_transaction(&mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1)
|
||||
decode_transaction(&Network::MainNetwork, &mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
println!("{:?}", tx_info);
|
||||
|
|
141
src/wallet.rs
141
src/wallet.rs
|
@ -1,5 +1,5 @@
|
|||
use crate::contact::{serialize_contacts, Contact};
|
||||
use crate::key::{decode_key, is_valid_key};
|
||||
use crate::key::KeyHelpers;
|
||||
use crate::pay::Tx;
|
||||
use crate::pay::TxBuilder;
|
||||
use crate::prices::fetch_historical_prices;
|
||||
|
@ -7,7 +7,7 @@ use crate::scan::ProgressCallback;
|
|||
use crate::taddr::{get_taddr_balance, get_utxos};
|
||||
use crate::{
|
||||
broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree,
|
||||
DbAdapter, NETWORK,
|
||||
DbAdapter,
|
||||
};
|
||||
use bip39::{Language, Mnemonic};
|
||||
use lazycell::AtomicLazyCell;
|
||||
|
@ -19,6 +19,10 @@ use serde::Serialize;
|
|||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use anyhow::anyhow;
|
||||
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
|
||||
use chacha20poly1305::aead::{Aead, NewAead};
|
||||
use bech32::FromBase32;
|
||||
use tokio::sync::Mutex;
|
||||
use tonic::Request;
|
||||
use zcash_client_backend::address::RecipientAddress;
|
||||
|
@ -27,18 +31,21 @@ use zcash_client_backend::encoding::{
|
|||
};
|
||||
use zcash_client_backend::zip321::{Payment, TransactionRequest};
|
||||
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
|
||||
use zcash_primitives::consensus::Parameters;
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
use zcash_primitives::memo::Memo;
|
||||
use zcash_primitives::transaction::builder::Progress;
|
||||
use zcash_primitives::transaction::components::Amount;
|
||||
use zcash_proofs::prover::LocalTxProver;
|
||||
use zcash_params::coin::TICKER;
|
||||
use zcash_params::coin::{CoinChain, CoinType, get_coin_chain};
|
||||
use crate::db::AccountBackup;
|
||||
|
||||
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
|
||||
|
||||
pub struct Wallet {
|
||||
coin_type: CoinType,
|
||||
pub db_path: String,
|
||||
db: DbAdapter,
|
||||
key_helpers: KeyHelpers,
|
||||
prover: AtomicLazyCell<LocalTxProver>,
|
||||
pub ld_url: String,
|
||||
}
|
||||
|
@ -92,14 +99,17 @@ impl From<&Recipient> for RecipientMemo {
|
|||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn new(db_path: &str, ld_url: &str) -> Wallet {
|
||||
let db = DbAdapter::new(db_path).unwrap();
|
||||
pub fn new(coin_type: CoinType, db_path: &str) -> Wallet {
|
||||
let db = DbAdapter::new(coin_type, db_path).unwrap();
|
||||
let key_helpers = KeyHelpers::new(coin_type);
|
||||
db.init_db().unwrap();
|
||||
Wallet {
|
||||
coin_type,
|
||||
db_path: db_path.to_string(),
|
||||
db,
|
||||
key_helpers,
|
||||
prover: AtomicLazyCell::new(),
|
||||
ld_url: ld_url.to_string(),
|
||||
ld_url: "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,27 +117,29 @@ impl Wallet {
|
|||
self.db.reset_db()
|
||||
}
|
||||
|
||||
pub fn valid_key(key: &str) -> i8 {
|
||||
is_valid_key(key)
|
||||
pub fn valid_key(&self, key: &str) -> i8 {
|
||||
self.key_helpers.is_valid_key(key)
|
||||
}
|
||||
|
||||
pub fn valid_address(address: &str) -> bool {
|
||||
let recipient = RecipientAddress::decode(&NETWORK, address);
|
||||
recipient.is_some()
|
||||
}
|
||||
|
||||
pub fn new_account(&self, name: &str, data: &str) -> anyhow::Result<i32> {
|
||||
pub fn new_account(&self, name: &str, data: &str, index: u32) -> anyhow::Result<i32> {
|
||||
if data.is_empty() {
|
||||
let mut entropy = [0u8; 32];
|
||||
OsRng.fill_bytes(&mut entropy);
|
||||
let mnemonic = Mnemonic::from_entropy(&entropy, Language::English)?;
|
||||
let seed = mnemonic.phrase();
|
||||
self.new_account_with_key(name, seed)
|
||||
self.new_account_with_key(name, seed, index)
|
||||
} else {
|
||||
self.new_account_with_key(name, data)
|
||||
self.new_account_with_key(name, data, index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_sub_account(&self, id: u32, name: &str) -> anyhow::Result<i32> {
|
||||
let seed = self.db.get_seed(id)?.ok_or_else(|| anyhow!("Account has no seed"))?;
|
||||
let index = self.db.next_account_id(&seed)?;
|
||||
let new_id = self.new_account_with_key(name, &seed, index as u32)?;
|
||||
Ok(new_id)
|
||||
}
|
||||
|
||||
pub fn get_backup(&self, account: u32) -> anyhow::Result<String> {
|
||||
let (seed, sk, ivk) = self.db.get_backup(account)?;
|
||||
if let Some(seed) = seed {
|
||||
|
@ -144,11 +156,11 @@ impl Wallet {
|
|||
Ok(sk)
|
||||
}
|
||||
|
||||
pub fn new_account_with_key(&self, name: &str, key: &str) -> anyhow::Result<i32> {
|
||||
let (seed, sk, ivk, pa) = decode_key(key)?;
|
||||
pub fn new_account_with_key(&self, name: &str, key: &str, index: u32) -> anyhow::Result<i32> {
|
||||
let (seed, sk, ivk, pa) = self.key_helpers.decode_key(key, index)?;
|
||||
let account = self
|
||||
.db
|
||||
.store_account(name, seed.as_deref(), sk.as_deref(), &ivk, &pa)?;
|
||||
.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?;
|
||||
if account > 0 {
|
||||
self.db.create_taddr(account as u32)?;
|
||||
}
|
||||
|
@ -156,6 +168,7 @@ impl Wallet {
|
|||
}
|
||||
|
||||
async fn scan_async(
|
||||
coin_type: CoinType,
|
||||
get_tx: bool,
|
||||
db_path: &str,
|
||||
chunk_size: u32,
|
||||
|
@ -164,6 +177,7 @@ impl Wallet {
|
|||
ld_url: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
crate::scan::sync_async(
|
||||
coin_type,
|
||||
chunk_size,
|
||||
get_tx,
|
||||
db_path,
|
||||
|
@ -182,6 +196,7 @@ impl Wallet {
|
|||
|
||||
// Not a method in order to avoid locking the instance
|
||||
pub async fn sync_ex(
|
||||
coin_type: CoinType,
|
||||
get_tx: bool,
|
||||
anchor_offset: u32,
|
||||
db_path: &str,
|
||||
|
@ -190,6 +205,7 @@ impl Wallet {
|
|||
) -> anyhow::Result<()> {
|
||||
let cb = Arc::new(Mutex::new(progress_callback));
|
||||
Self::scan_async(
|
||||
coin_type,
|
||||
get_tx,
|
||||
db_path,
|
||||
DEFAULT_CHUNK_SIZE,
|
||||
|
@ -198,7 +214,7 @@ impl Wallet {
|
|||
ld_url,
|
||||
)
|
||||
.await?;
|
||||
Self::scan_async(get_tx, db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?;
|
||||
Self::scan_async(coin_type, get_tx, db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -209,6 +225,7 @@ impl Wallet {
|
|||
progress_callback: impl Fn(u32) + Send + 'static,
|
||||
) -> anyhow::Result<()> {
|
||||
Self::sync_ex(
|
||||
self.db.coin_type,
|
||||
get_tx,
|
||||
anchor_offset,
|
||||
&self.db_path,
|
||||
|
@ -249,11 +266,11 @@ impl Wallet {
|
|||
use_transparent: bool,
|
||||
anchor_offset: u32,
|
||||
) -> anyhow::Result<(Tx, Vec<u32>)> {
|
||||
let mut tx_builder = TxBuilder::new(last_height);
|
||||
let mut tx_builder = TxBuilder::new(self.db.coin_type, last_height);
|
||||
|
||||
let fvk = self.db.get_ivk(account)?;
|
||||
let fvk =
|
||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
|
||||
decode_extended_full_viewing_key(self.network().hrp_sapling_extended_full_viewing_key(), &fvk)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let utxos = if use_transparent {
|
||||
|
@ -283,7 +300,7 @@ impl Wallet {
|
|||
.db
|
||||
.get_tsk(account)?
|
||||
.map(|tsk| SecretKey::from_str(&tsk).unwrap());
|
||||
let extsk = decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &zsk)
|
||||
let extsk = decode_extended_spending_key(self.network().hrp_sapling_extended_spending_key(), &zsk)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let prover = self
|
||||
|
@ -374,7 +391,7 @@ impl Wallet {
|
|||
pub fn new_diversified_address(&self, account: u32) -> anyhow::Result<String> {
|
||||
let ivk = self.get_ivk(account)?;
|
||||
let fvk = decode_extended_full_viewing_key(
|
||||
NETWORK.hrp_sapling_extended_full_viewing_key(),
|
||||
self.network().hrp_sapling_extended_full_viewing_key(),
|
||||
&ivk,
|
||||
)?
|
||||
.unwrap();
|
||||
|
@ -384,7 +401,7 @@ impl Wallet {
|
|||
.address(diversifier_index)
|
||||
.map_err(|_| anyhow::anyhow!("Cannot generate new address"))?;
|
||||
self.db.store_diversifier(account, &new_diversifier_index)?;
|
||||
let pa = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
|
||||
let pa = encode_payment_address(self.network().hrp_sapling_payment_address(), &pa);
|
||||
Ok(pa)
|
||||
}
|
||||
|
||||
|
@ -484,8 +501,8 @@ impl Wallet {
|
|||
self.db.truncate_data()
|
||||
}
|
||||
|
||||
pub fn make_payment_uri(address: &str, amount: u64, memo: &str) -> anyhow::Result<String> {
|
||||
let addr = RecipientAddress::decode(&NETWORK, address)
|
||||
pub fn make_payment_uri(&self, address: &str, amount: u64, memo: &str) -> anyhow::Result<String> {
|
||||
let addr = RecipientAddress::decode(self.network(), address)
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid address"))?;
|
||||
let payment = Payment {
|
||||
recipient_address: addr,
|
||||
|
@ -499,18 +516,18 @@ impl Wallet {
|
|||
payments: vec![payment],
|
||||
};
|
||||
let uri = treq
|
||||
.to_uri(&NETWORK)
|
||||
.to_uri(self.network())
|
||||
.ok_or_else(|| anyhow::anyhow!("Cannot build Payment URI"))?;
|
||||
let uri = format!("{}{}", TICKER, &uri[5..]); // hack to replace the URI scheme
|
||||
let uri = format!("{}{}", self.chain().ticker(), &uri[5..]); // hack to replace the URI scheme
|
||||
Ok(uri)
|
||||
}
|
||||
|
||||
pub fn parse_payment_uri(uri: &str) -> anyhow::Result<String> {
|
||||
if uri[..5].ne(TICKER) {
|
||||
pub fn parse_payment_uri(&self, uri: &str) -> anyhow::Result<String> {
|
||||
if uri[..5].ne(self.chain().ticker()) {
|
||||
anyhow::bail!("Invalid Payment URI");
|
||||
}
|
||||
let uri = format!("zcash{}", &uri[5..]); // hack to replace the URI scheme
|
||||
let treq = TransactionRequest::from_uri(&NETWORK, &uri)
|
||||
let treq = TransactionRequest::from_uri(self.network(), &uri)
|
||||
.map_err(|_| anyhow::anyhow!("Invalid Payment URI"))?;
|
||||
if treq.payments.len() != 1 {
|
||||
anyhow::bail!("Invalid Payment URI")
|
||||
|
@ -528,7 +545,7 @@ impl Wallet {
|
|||
None => Ok(String::new()),
|
||||
}?;
|
||||
let payment = MyPayment {
|
||||
address: payment.recipient_address.encode(&NETWORK),
|
||||
address: payment.recipient_address.encode(self.network()),
|
||||
amount: u64::from(payment.amount),
|
||||
memo,
|
||||
};
|
||||
|
@ -538,12 +555,12 @@ impl Wallet {
|
|||
Ok(payment_json)
|
||||
}
|
||||
|
||||
pub fn get_full_backup(&self, key: &str) -> anyhow::Result<String> {
|
||||
self.db.get_full_backup(key)
|
||||
pub fn get_full_backup(&self) -> anyhow::Result<Vec<AccountBackup>> {
|
||||
self.db.get_full_backup()
|
||||
}
|
||||
|
||||
pub fn restore_full_backup(&self, key: &str, backup: &str) -> anyhow::Result<()> {
|
||||
self.db.restore_full_backup(key, backup)
|
||||
pub fn restore_full_backup(&self, accounts: &[AccountBackup]) -> anyhow::Result<()> {
|
||||
self.db.restore_full_backup(accounts)
|
||||
}
|
||||
|
||||
pub fn store_share_secret(&self, account: u32, secret: &str, index: usize, threshold: usize, participants: usize) -> anyhow::Result<()> {
|
||||
|
@ -565,6 +582,40 @@ impl Wallet {
|
|||
let recipient_memos: Vec<_> = recipients.iter().map(|r| RecipientMemo::from(r)).collect();
|
||||
Ok(recipient_memos)
|
||||
}
|
||||
|
||||
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
|
||||
fn network(&self) -> &Network { self.chain().network() }
|
||||
}
|
||||
|
||||
const NONCE: &'static[u8; 12] = b"unique nonce";
|
||||
|
||||
pub fn encrypt_backup(accounts: &[AccountBackup], key: &str) -> anyhow::Result<String> {
|
||||
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(NONCE), &*accounts_bin).map_err(|_e| anyhow::anyhow!("Failed to encrypt backup"))?;
|
||||
let backup = base64::encode(cipher_text);
|
||||
Ok(backup)
|
||||
}
|
||||
|
||||
pub fn decrypt_backup(key: &str, backup: &str) -> anyhow::Result<Vec<AccountBackup>> {
|
||||
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(NONCE), &*backup).map_err(|_e| anyhow::anyhow!("Failed to decrypt backup"))?;
|
||||
|
||||
let accounts: Vec<AccountBackup> = bincode::deserialize(&backup)?;
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -576,10 +627,11 @@ struct MyPayment {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::key::derive_secret_key;
|
||||
use crate::key::KeyHelpers;
|
||||
use crate::wallet::Wallet;
|
||||
use crate::LWD_URL;
|
||||
use bip39::{Language, Mnemonic};
|
||||
use zcash_params::coin::CoinType;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wallet_seed() {
|
||||
|
@ -587,8 +639,9 @@ mod tests {
|
|||
env_logger::init();
|
||||
|
||||
let seed = dotenv::var("SEED").unwrap();
|
||||
let wallet = Wallet::new("zec.db", LWD_URL);
|
||||
wallet.new_account_with_key("test", &seed).unwrap();
|
||||
let mut wallet = Wallet::new(CoinType::Zcash, "zec.db");
|
||||
wallet.set_lwd_url(LWD_URL).unwrap();
|
||||
wallet.new_account_with_key("test", &seed, 0).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -597,8 +650,9 @@ mod tests {
|
|||
env_logger::init();
|
||||
|
||||
let seed = dotenv::var("SEED").unwrap();
|
||||
let kh = KeyHelpers::new(CoinType::Zcash);
|
||||
let (sk, vk, pa) =
|
||||
derive_secret_key(&Mnemonic::from_phrase(&seed, Language::English).unwrap()).unwrap();
|
||||
kh.derive_secret_key(&Mnemonic::from_phrase(&seed, Language::English).unwrap(), 0).unwrap();
|
||||
println!("{} {} {}", sk, vk, pa);
|
||||
// let wallet = Wallet::new("zec.db");
|
||||
//
|
||||
|
@ -608,7 +662,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
pub fn test_diversified_address() {
|
||||
let wallet = Wallet::new("zec.db", LWD_URL);
|
||||
let mut wallet = Wallet::new(CoinType::Zcash, "zec.db");
|
||||
wallet.set_lwd_url(LWD_URL).unwrap();
|
||||
let address = wallet.new_diversified_address(1).unwrap();
|
||||
println!("{}", address);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue