This commit is contained in:
Hanh 2021-07-09 21:33:05 +08:00
parent 7d40ad28c6
commit cb7a2e04b9
9 changed files with 257 additions and 32 deletions

View File

@ -38,6 +38,10 @@ bls12_381 = "^0.4.0"
ff = "^0.9"
group = "^0.9"
byteorder = "^1.4"
secp256k1 = "0.20.2"
tiny-hderive = "0.3.0"
ripemd160 = "0.9.1"
sha2 = "0.9.5"
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
@ -46,6 +50,7 @@ path = "/home/hanh/projects/librustzcash/zcash_client_backend"
[dependencies.zcash_primitives]
path = "/home/hanh/projects/librustzcash/zcash_primitives"
features = [ "transparent-inputs" ]
[dependencies.zcash_proofs]
path = "/home/hanh/projects/librustzcash/zcash_proofs"

View File

@ -1,7 +1,7 @@
use crate::commitment::{CTree, Witness};
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
use crate::lw_rpc::*;
use crate::{advance_tree, NETWORK, LWD_URL};
use crate::{advance_tree, NETWORK};
use ff::PrimeField;
use group::GroupEncoding;
use log::info;
@ -316,9 +316,9 @@ pub fn calculate_tree_state_v2(cbs: &[CompactBlock], blocks: &[DecryptedBlock])
new_witnesses
}
pub async fn connect_lightwalletd() -> anyhow::Result<CompactTxStreamerClient<Channel>> {
let mut channel = tonic::transport::Channel::from_shared(LWD_URL)?;
if LWD_URL.starts_with("https") {
pub async fn connect_lightwalletd(url: &str) -> anyhow::Result<CompactTxStreamerClient<Channel>> {
let mut channel = tonic::transport::Channel::from_shared(url.to_owned())?;
if url.starts_with("https") {
let pem = include_bytes!("ca.pem");
let ca = Certificate::from_pem(pem);
let tls = ClientTlsConfig::new().ca_certificate(ca);
@ -328,7 +328,7 @@ pub async fn connect_lightwalletd() -> anyhow::Result<CompactTxStreamerClient<Ch
Ok(client)
}
pub async fn sync(fvks: &HashMap<u32, String>) -> anyhow::Result<()> {
pub async fn sync(fvks: &HashMap<u32, String>, ld_url: &str) -> anyhow::Result<()> {
let fvks: HashMap<_, _> = fvks.iter().map(|(&account, fvk)| {
let fvk =
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
@ -337,7 +337,7 @@ pub async fn sync(fvks: &HashMap<u32, String>) -> anyhow::Result<()> {
(account, fvk)
}).collect();
let decrypter = DecryptNode::new(fvks);
let mut client = connect_lightwalletd().await?;
let mut client = connect_lightwalletd(ld_url).await?;
let start_height: u32 = crate::NETWORK
.activation_height(NetworkUpgrade::Sapling)
.unwrap()

View File

@ -6,6 +6,7 @@ use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed};
use zcash_primitives::zip32::{ExtendedFullViewingKey, DiversifierIndex};
use crate::taddr::{derive_tkeys, BIP44_PATH};
#[allow(dead_code)]
pub const DEFAULT_DB_PATH: &str = "zec.db";
@ -105,6 +106,14 @@ impl DbAdapter {
NO_PARAMS,
)?;
self.connection.execute(
"CREATE TABLE IF NOT EXISTS taddrs (
account INTEGER PRIMARY KEY NOT NULL,
sk TEXT NOT NULL,
address TEXT NOT NULL)",
NO_PARAMS,
)?;
Ok(())
}
@ -447,6 +456,20 @@ impl DbAdapter {
Ok((seed, sk, ivk))
}
pub fn get_seed(&self, account: u32) -> anyhow::Result<Option<String>> {
log::info!("+get_seed");
let seed = self.connection.query_row(
"SELECT seed FROM accounts WHERE id_account = ?1",
params![account],
|row| {
let sk: String = row.get(0)?;
Ok(sk)
},
).optional()?;
log::info!("-get_seed");
Ok(seed)
}
pub fn get_sk(&self, account: u32) -> anyhow::Result<String> {
log::info!("+get_sk");
let sk = self.connection.query_row(
@ -475,6 +498,20 @@ impl DbAdapter {
Ok(ivk)
}
pub fn get_address(&self, account: u32) -> anyhow::Result<String> {
log::debug!("+get_address");
let address = self.connection.query_row(
"SELECT address FROM accounts WHERE id_account = ?1",
params![account],
|row| {
let address: String = row.get(0)?;
Ok(address)
},
)?;
log::debug!("-get_address");
Ok(address)
}
pub fn get_diversifier(&self, account: u32) -> anyhow::Result<DiversifierIndex> {
let diversifier_index = self.connection.query_row(
"SELECT diversifier_index FROM diversifiers WHERE account = ?1",
@ -484,7 +521,7 @@ impl DbAdapter {
let mut div = [0u8; 11];
div.copy_from_slice(&d);
Ok(div)
}
},
).optional()?.unwrap_or_else(|| [0u8; 11]);
Ok(DiversifierIndex(diversifier_index))
}
@ -497,6 +534,38 @@ impl DbAdapter {
params![account, diversifier_bytes])?;
Ok(())
}
pub fn get_taddr(&self, account: u32) -> anyhow::Result<Option<String>> {
let address = self.connection.query_row(
"SELECT address FROM taddrs WHERE account = ?1",
params![account],
|row| {
let address: String = row.get(0)?;
Ok(address)
}).optional()?;
Ok(address)
}
pub fn get_tsk(&self, account: u32) -> anyhow::Result<String> {
let sk = self.connection.query_row(
"SELECT sk FROM taddrs WHERE account = ?1",
params![account],
|row| {
let address: String = row.get(0)?;
Ok(address)
})?;
Ok(sk)
}
pub fn create_taddr(&self, account: u32) -> anyhow::Result<()> {
let seed = self.get_seed(account)?;
if let Some(seed) = seed {
let (sk, address) = derive_tkeys(&seed, BIP44_PATH)?;
self.connection.execute("INSERT INTO taddrs(account, sk, address) VALUES (?1, ?2, ?3) \
ON CONFLICT DO NOTHING", params![account, &sk, &address])?;
}
Ok(())
}
}
#[cfg(test)]

View File

@ -22,6 +22,7 @@ mod key;
mod mempool;
mod print;
mod scan;
mod taddr;
mod wallet;
pub use crate::builder::advance_tree;

View File

@ -1,7 +1,7 @@
use bip39::{Language, Mnemonic};
use rand::rngs::OsRng;
use rand::RngCore;
use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2};
use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2, LWD_URL};
use rusqlite::NO_PARAMS;
const DB_NAME: &str = "zec.db";
@ -22,7 +22,7 @@ async fn test() -> anyhow::Result<()> {
let progress = |height| {
log::info!("Height = {}", height);
};
let wallet = Wallet::new(DB_NAME);
let wallet = Wallet::new(DB_NAME, LWD_URL);
wallet.new_account_with_key("test", &seed).unwrap();
let res = wallet.sync(progress).await;
if let Err(err) = res {
@ -34,7 +34,7 @@ async fn test() -> anyhow::Result<()> {
}
}
let tx_id = wallet
.send_payment(1, &address, 50000, u64::max_value(), move |progress| { println!("{}", progress.cur()); })
.send_payment(1, &address, 50000, u64::max_value(), 2, move |progress| { println!("{}", progress.cur()); })
.await
.unwrap();
println!("TXID = {}", tx_id);

View File

@ -27,10 +27,11 @@ pub struct MemPool {
transactions: HashMap<Vec<u8>, MemPoolTransacton>,
nfs: HashMap<Vec<u8>, u64>,
balance: i64,
ld_url: String,
}
impl MemPool {
pub fn new(db_path: &str) -> MemPool {
pub fn new(db_path: &str, ld_url: &str) -> MemPool {
MemPool {
db_path: db_path.to_string(),
account: 0,
@ -39,6 +40,7 @@ impl MemPool {
transactions: HashMap::new(),
nfs: HashMap::new(),
balance: 0,
ld_url: ld_url.to_string(),
}
}
@ -63,7 +65,7 @@ impl MemPool {
pub async fn scan(&mut self) -> anyhow::Result<i64> {
if self.ivk.is_some() {
let ivk = self.ivk.as_ref().unwrap().clone();
let mut client = connect_lightwalletd().await?;
let mut client = connect_lightwalletd(&self.ld_url).await?;
let height = BlockHeight::from(get_latest_height(&mut client).await?);
if self.height != height {
// New blocks invalidate the mempool
@ -154,14 +156,14 @@ impl MemPool {
mod tests {
use crate::db::DEFAULT_DB_PATH;
use crate::mempool::MemPool;
use crate::DbAdapter;
use crate::{DbAdapter, LWD_URL};
use std::time::Duration;
#[tokio::test]
async fn test_mempool() {
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
let ivk = db.get_ivk(1).unwrap();
let mut mempool = MemPool::new("zec.db");
let mut mempool = MemPool::new("zec.db", LWD_URL);
mempool.set_ivk(&ivk);
loop {
mempool.scan().await.unwrap();

View File

@ -75,10 +75,12 @@ pub async fn sync_async(
db_path: &str,
target_height_offset: u32,
progress_callback: ProgressCallback,
ld_url: &str
) -> anyhow::Result<()> {
let ld_url = ld_url.to_owned();
let db_path = db_path.to_string();
let mut client = connect_lightwalletd().await?;
let mut client = connect_lightwalletd(&ld_url).await?;
let (start_height, mut prev_hash, fvks) = {
let db = DbAdapter::new(&db_path)?;
let height = db.get_db_height()?;
@ -102,7 +104,7 @@ pub async fn sync_async(
let (processor_tx, mut processor_rx) = mpsc::channel::<Blocks>(1);
let downloader = tokio::spawn(async move {
let mut client = connect_lightwalletd().await?;
let mut client = connect_lightwalletd(&ld_url).await?;
while let Some(range) = download_rx.recv().await {
log::info!("+ {:?}", range);
let blocks = download_chain(&mut client, range.start, range.end, prev_hash).await?;
@ -302,8 +304,8 @@ pub async fn sync_async(
Ok(())
}
pub async fn latest_height() -> anyhow::Result<u32> {
let mut client = connect_lightwalletd().await?;
pub async fn latest_height(ld_url: &str) -> anyhow::Result<u32> {
let mut client = connect_lightwalletd(ld_url).await?;
let height = get_latest_height(&mut client).await?;
Ok(height)
}

120
src/taddr.rs Normal file
View File

@ -0,0 +1,120 @@
use zcash_primitives::transaction::builder::Builder;
use crate::{CompactTxStreamerClient, AddressList, DbAdapter, NETWORK, connect_lightwalletd, get_latest_height, GetAddressUtxosArg};
use tonic::transport::Channel;
use tonic::Request;
use zcash_primitives::consensus::{BlockHeight, Parameters, BranchId};
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut};
use std::str::FromStr;
use anyhow::Context;
use zcash_primitives::legacy::{Script, TransparentAddress};
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_payment_address, encode_transparent_address};
use crate::chain::send_transaction;
use zcash_proofs::prover::LocalTxProver;
use ripemd160::{Ripemd160, Digest};
use sha2::Sha256;
use secp256k1::{SecretKey, PublicKey, Secp256k1, All};
use tiny_hderive::bip32::ExtendedPrivKey;
use bip39::{Mnemonic, Language, Seed};
pub const BIP44_PATH: &str = "m/44'/133'/0'/0/0";
pub async fn get_taddr_balance(client: &mut CompactTxStreamerClient<Channel>, address: &str) -> anyhow::Result<u64> {
let req = AddressList {
addresses: vec![address.to_string()],
};
let rep = client.get_taddress_balance(Request::new(req)).await?.into_inner();
Ok(rep.value_zat as u64)
}
pub async fn shield_taddr(db: &DbAdapter, account: u32, prover: &LocalTxProver, ld_url: &str) -> anyhow::Result<String> {
let mut client = connect_lightwalletd(ld_url).await?;
let last_height = get_latest_height(&mut client).await?;
let ivk = db.get_ivk(account)?;
let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)?.unwrap();
let z_address = db.get_address(account)?;
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &z_address)?.unwrap();
let t_address = db.get_taddr(account)?;
if t_address.is_none() { anyhow::bail!("No transparent address"); }
let t_address = t_address.unwrap();
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
let amount = Amount::from_u64(get_taddr_balance(&mut client, &t_address).await?).unwrap();
if amount <= DEFAULT_FEE { anyhow::bail!("Not enough balance"); }
let amount = amount - DEFAULT_FEE;
let sk = db.get_tsk(account)?;
let seckey =
secp256k1::SecretKey::from_str(&sk).context("Cannot parse secret key")?;
let req = GetAddressUtxosArg {
addresses: vec![t_address.to_string()],
start_height: 0,
max_entries: 0,
};
let utxo_rep = client.get_address_utxos(Request::new(req)).await?.into_inner();
for utxo in utxo_rep.address_utxos.iter() {
let mut tx_hash = [0u8; 32];
tx_hash.copy_from_slice(&utxo.txid);
let op = OutPoint::new(tx_hash, utxo.index as u32);
let script = Script(utxo.script.clone());
let txout = TxOut {
value: Amount::from_i64(utxo.value_zat).unwrap(),
script_pubkey: script,
};
builder.add_transparent_input(seckey, op, txout)?;
}
let ovk = fvk.fvk.ovk;
builder.add_sapling_output(Some(ovk), pa, amount, None)?;
let consensus_branch_id =
BranchId::for_height(&NETWORK, BlockHeight::from_u32(last_height));
let (tx, _) = builder.build(consensus_branch_id, prover)?;
let mut raw_tx: Vec<u8> = vec![];
tx.write(&mut raw_tx)?;
let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?;
log::info!("Tx ID = {}", tx_id);
Ok(tx_id)
}
pub fn derive_tkeys(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();
let ext = ExtendedPrivKey::derive(&seed.as_bytes(), path).unwrap();
let secret_key = SecretKey::from_slice(&ext.secret()).unwrap();
let pub_key = PublicKey::from_secret_key(&secp, &secret_key);
let pub_key = pub_key.serialize();
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(), &address);
let sk = secret_key.to_string();
Ok((sk, address))
}
#[cfg(test)]
mod tests {
use crate::{DbAdapter, LWD_URL};
use crate::db::DEFAULT_DB_PATH;
use crate::taddr::{shield_taddr, derive_tkeys};
use zcash_proofs::prover::LocalTxProver;
#[tokio::test]
async fn test_shield_addr() {
let prover = LocalTxProver::with_default_location().unwrap();
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
let txid = shield_taddr(&db, 1, &prover, LWD_URL).await.unwrap();
println!("{}", txid);
}
#[test]
fn test_derive() {
let seed = dotenv::var("SEED").unwrap();
for i in 0..10 {
let (_sk, addr) = derive_tkeys(&seed, &format!("m/44'/133'/0'/0/{}", i)).unwrap();
println!("{}", addr);
}
}
}

View File

@ -11,7 +11,6 @@ use std::sync::{mpsc, Arc};
use tokio::sync::Mutex;
use tonic::Request;
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::data_api::wallet::ANCHOR_OFFSET;
use zcash_client_backend::encoding::{decode_extended_spending_key, decode_extended_full_viewing_key, encode_payment_address};
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
use zcash_primitives::consensus::{BlockHeight, BranchId, Parameters};
@ -20,6 +19,7 @@ use zcash_primitives::transaction::components::amount::{DEFAULT_FEE, MAX_MONEY};
use zcash_primitives::transaction::components::Amount;
use zcash_primitives::zip32::ExtendedFullViewingKey;
use zcash_proofs::prover::LocalTxProver;
use crate::taddr::{get_taddr_balance, shield_taddr};
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
@ -27,6 +27,7 @@ pub struct Wallet {
pub db_path: String,
db: DbAdapter,
prover: LocalTxProver,
pub ld_url: String,
}
#[repr(C)]
@ -47,7 +48,7 @@ impl Default for WalletBalance {
}
impl Wallet {
pub fn new(db_path: &str) -> Wallet {
pub fn new(db_path: &str, ld_url: &str) -> Wallet {
let prover = LocalTxProver::from_bytes(SPEND_PARAMS, OUTPUT_PARAMS);
let db = DbAdapter::new(db_path).unwrap();
db.init_db().unwrap();
@ -55,6 +56,7 @@ impl Wallet {
db_path: db_path.to_string(),
db,
prover,
ld_url: ld_url.to_string(),
}
}
@ -95,6 +97,7 @@ impl Wallet {
let account = self
.db
.store_account(name, seed.as_deref(), sk.as_deref(), &ivk, &pa)?;
self.db.create_taddr(account)?;
Ok(account)
}
@ -103,12 +106,13 @@ impl Wallet {
chunk_size: u32,
target_height_offset: u32,
progress_callback: ProgressCallback,
ld_url: &str
) -> anyhow::Result<()> {
crate::scan::sync_async(chunk_size, db_path, target_height_offset, progress_callback).await
crate::scan::sync_async(chunk_size, db_path, target_height_offset, progress_callback, ld_url).await
}
pub async fn get_latest_height() -> anyhow::Result<u32> {
let mut client = connect_lightwalletd().await?;
pub async fn get_latest_height(&self) -> anyhow::Result<u32> {
let mut client = connect_lightwalletd(&self.ld_url).await?;
let last_height = get_latest_height(&mut client).await?;
Ok(last_height)
}
@ -117,10 +121,11 @@ impl Wallet {
pub async fn sync_ex(
db_path: &str,
progress_callback: impl Fn(u32) + Send + 'static,
ld_url: &str
) -> anyhow::Result<()> {
let cb = Arc::new(Mutex::new(progress_callback));
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 10, cb.clone()).await?;
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone()).await?;
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 10, cb.clone(), ld_url).await?;
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?;
Ok(())
}
@ -128,11 +133,11 @@ impl Wallet {
&self,
progress_callback: impl Fn(u32) + Send + 'static,
) -> anyhow::Result<()> {
Self::sync_ex(&self.db_path, progress_callback).await
Self::sync_ex(&self.db_path, progress_callback, &self.ld_url).await
}
pub async fn skip_to_last_height(&self) -> anyhow::Result<()> {
let mut client = connect_lightwalletd().await?;
let mut client = connect_lightwalletd(&self.ld_url).await?;
let last_height = get_latest_height(&mut client).await?;
let block_id = BlockId {
height: last_height as u64,
@ -160,6 +165,7 @@ impl Wallet {
to_address: &str,
amount: u64,
max_amount_per_note: u64,
anchor_offset: u32,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<String> {
let secret_key = self.db.get_sk(account)?;
@ -172,13 +178,13 @@ impl Wallet {
let extfvk = ExtendedFullViewingKey::from(&skey);
let (_, change_address) = extfvk.default_address().unwrap();
let ovk = extfvk.fvk.ovk;
let last_height = Self::get_latest_height().await?;
let last_height = self.get_latest_height().await?;
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
let anchor_height = self
.db
.get_last_sync_height()?
.ok_or_else(|| anyhow::anyhow!("No spendable notes"))?;
let anchor_height = anchor_height.min(last_height - ANCHOR_OFFSET);
let anchor_height = anchor_height.min(last_height - anchor_offset);
log::info!("Anchor = {}", anchor_height);
let mut notes = self
.db
@ -252,7 +258,7 @@ impl Wallet {
let mut raw_tx: Vec<u8> = vec![];
tx.write(&mut raw_tx)?;
let mut client = connect_lightwalletd().await?;
let mut client = connect_lightwalletd(&self.ld_url).await?;
let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?;
log::info!("Tx ID = {}", tx_id);
@ -276,6 +282,25 @@ impl Wallet {
let pa = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
Ok(pa)
}
pub async fn get_taddr_balance(&self, account: u32) -> anyhow::Result<u64> {
let mut client = connect_lightwalletd(&self.ld_url).await?;
let address = self.db.get_taddr(account)?;
let balance = match address {
None => 0u64,
Some(address) => get_taddr_balance(&mut client, &address).await?,
};
Ok(balance)
}
pub async fn shield_taddr(&self, account: u32) -> anyhow::Result<String> {
shield_taddr(&self.db, account, &self.prover, &self.ld_url).await
}
pub fn set_lwd_url(&mut self, ld_url: &str) -> anyhow::Result<()> {
self.ld_url = ld_url.to_string();
Ok(())
}
}
#[cfg(test)]
@ -283,6 +308,7 @@ mod tests {
use crate::key::derive_secret_key;
use crate::wallet::Wallet;
use bip39::{Language, Mnemonic};
use crate::LWD_URL;
#[tokio::test]
async fn test_wallet_seed() {
@ -290,7 +316,7 @@ mod tests {
env_logger::init();
let seed = dotenv::var("SEED").unwrap();
let wallet = Wallet::new("zec.db");
let wallet = Wallet::new("zec.db", LWD_URL);
wallet.new_account_with_key("test", &seed).unwrap();
}
@ -311,7 +337,7 @@ mod tests {
#[test]
pub fn test_diversified_address() {
let wallet = Wallet::new("zec.db");
let wallet = Wallet::new("zec.db", LWD_URL);
let address = wallet.new_diversified_address(1).unwrap();
println!("{}", address);
}