This commit is contained in:
Hanh 2022-06-08 00:58:24 +08:00
parent 00a1c54046
commit 6c5e8a6da7
22 changed files with 765 additions and 311 deletions

View File

@ -14,14 +14,10 @@ harness = false
name = "warp-cli"
path = "src/main/warp_cli.rs"
[[bin]]
name = "ledger"
path = "src/main/ledger.rs"
#
#[[bin]]
#name = "cli"
#path = "src/main/cli.rs"
#
#name = "ledger"
#path = "src/main/ledger.rs"
[[bin]]
name = "sign"
path = "src/main/sign.rs"
@ -72,9 +68,10 @@ base64 = "^0.13"
ledger-apdu = { version = "0.9.0", optional = true }
hmac = { version = "0.12.1", optional = true }
ed25519-bip32 = { version = "0.4.1", optional = true }
ledger-transport-hid = { version = "0.9", optional = true }
[features]
ledger = ["ledger-apdu", "hmac", "ed25519-bip32"]
ledger = ["ledger-apdu", "hmac", "ed25519-bip32", "ledger-transport-hid"]
ledger_sapling = ["ledger"]
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd

View File

@ -9,17 +9,20 @@ fn scan(c: &mut Criterion) {
env_logger::init();
let ivk = dotenv::var("IVK").unwrap();
let fvk =
decode_extended_full_viewing_key(Network::MainNetwork.hrp_sapling_extended_full_viewing_key(), &ivk)
.unwrap()
.unwrap();
let fvk = decode_extended_full_viewing_key(
Network::MainNetwork.hrp_sapling_extended_full_viewing_key(),
&ivk,
)
.unwrap()
.unwrap();
let fvks = &vec![fvk];
c.bench_function("scan all", |b| {
b.iter(|| {
let r = Runtime::new().unwrap();
r.block_on(scan_all(&Network::MainNetwork, fvks.clone().as_slice())).unwrap();
r.block_on(scan_all(&Network::MainNetwork, fvks.clone().as_slice()))
.unwrap();
});
});
}

View File

@ -2,10 +2,7 @@ fn main() {
tonic_build::configure()
.out_dir("src/generated")
.compile(
&[
"proto/service.proto",
"proto/compact_formats.proto",
],
&["proto/service.proto", "proto/compact_formats.proto"],
&["proto"],
)
.unwrap();

View File

@ -1,8 +1,8 @@
use crate::advance_tree;
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;
use ff::PrimeField;
use log::info;
use rayon::prelude::*;
@ -13,13 +13,13 @@ use thiserror::Error;
use tonic::transport::{Certificate, Channel, ClientTlsConfig};
use tonic::Request;
use zcash_note_encryption::batch::try_compact_note_decryption;
use zcash_note_encryption::{Domain, EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE};
use zcash_primitives::consensus::{BlockHeight, Network, NetworkUpgrade, Parameters};
use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness};
use zcash_primitives::sapling::note_encryption::SaplingDomain;
use zcash_primitives::sapling::{Node, Note, PaymentAddress};
use zcash_primitives::transaction::components::sapling::CompactOutputDescription;
use zcash_primitives::zip32::ExtendedFullViewingKey;
use zcash_note_encryption::{COMPACT_NOTE_SIZE, Domain, EphemeralKeyBytes, ShieldedOutput};
const MAX_CHUNK: u32 = 50000;
@ -32,33 +32,51 @@ pub async fn get_latest_height(
Ok(block_id.height as u32)
}
pub async fn get_activation_date(network: &Network, client: &mut CompactTxStreamerClient<Channel>) -> anyhow::Result<u32> {
pub async fn get_activation_date(
network: &Network,
client: &mut CompactTxStreamerClient<Channel>,
) -> anyhow::Result<u32> {
let height = network.activation_height(NetworkUpgrade::Sapling).unwrap();
let time = get_block_date(client, u32::from(height)).await?;
Ok(time)
}
pub async fn get_block_date(client: &mut CompactTxStreamerClient<Channel>, height: u32) -> anyhow::Result<u32> {
let block = client.get_block(Request::new(BlockId { height: height as u64, hash: vec![] })).await?.into_inner();
pub async fn get_block_date(
client: &mut CompactTxStreamerClient<Channel>,
height: u32,
) -> anyhow::Result<u32> {
let block = client
.get_block(Request::new(BlockId {
height: height as u64,
hash: vec![],
}))
.await?
.into_inner();
Ok(block.time)
}
pub async fn get_block_by_time(network: &Network, client: &mut CompactTxStreamerClient<Channel>, time: u32) -> anyhow::Result<u32> {
pub async fn get_block_by_time(
network: &Network,
client: &mut CompactTxStreamerClient<Channel>,
time: u32,
) -> anyhow::Result<u32> {
let mut start = u32::from(network.activation_height(NetworkUpgrade::Sapling).unwrap());
let mut end = get_latest_height(client).await?;
if time <= get_block_date(client, start).await? { return Ok(0); }
if time >= get_block_date(client, end).await? { return Ok(end); }
if time <= get_block_date(client, start).await? {
return Ok(0);
}
if time >= get_block_date(client, end).await? {
return Ok(end);
}
let mut block_mid;
while end - start >= 1000 {
block_mid = (start + end) / 2;
let mid = get_block_date(client, block_mid).await?;
if time < mid {
end = block_mid - 1;
}
else if time > mid {
} else if time > mid {
start = block_mid + 1;
}
else {
} else {
return Ok(block_mid);
}
}
@ -177,8 +195,14 @@ struct AccountOutput<'a, N: Parameters> {
_phantom: PhantomData<N>,
}
impl <'a, N: Parameters> AccountOutput<'a, N> {
fn new(tx_index: usize, output_index: usize, block_output_index: usize, vtx: &'a CompactTx, co: &CompactOutput) -> Self {
impl<'a, N: Parameters> AccountOutput<'a, N> {
fn new(
tx_index: usize,
output_index: usize,
block_output_index: usize,
vtx: &'a CompactTx,
co: &CompactOutput,
) -> Self {
let mut epk_bytes = [0u8; 32];
epk_bytes.copy_from_slice(&co.epk);
let epk = EphemeralKeyBytes::from(epk_bytes);
@ -201,7 +225,9 @@ impl <'a, N: Parameters> AccountOutput<'a, N> {
}
}
impl <'a, N: Parameters> ShieldedOutput<SaplingDomain<N>, COMPACT_NOTE_SIZE> for AccountOutput<'a, N> {
impl<'a, N: Parameters> ShieldedOutput<SaplingDomain<N>, COMPACT_NOTE_SIZE>
for AccountOutput<'a, N>
{
fn ephemeral_key(&self) -> EphemeralKeyBytes {
self.epk.clone()
}
@ -235,7 +261,8 @@ fn decrypt_notes<'a, N: Parameters>(
for (output_index, co) in vtx.outputs.iter().enumerate() {
let domain = SaplingDomain::<N>::for_height(network.clone(), height);
let output = AccountOutput::<N>::new(tx_index, output_index, count_outputs as usize, vtx, co);
let output =
AccountOutput::<N>::new(tx_index, output_index, count_outputs as usize, vtx, co);
outputs.push((domain, output));
// let od = to_output_description(co);
@ -264,8 +291,8 @@ fn decrypt_notes<'a, N: Parameters>(
}
let start = Instant::now();
let notes_decrypted = try_compact_note_decryption::<SaplingDomain<N>, AccountOutput<N>>
(&vvks, &outputs);
let notes_decrypted =
try_compact_note_decryption::<SaplingDomain<N>, AccountOutput<N>>(&vvks, &outputs);
let elapsed = start.elapsed().as_millis() as usize;
for (pos, opt_note) in notes_decrypted.iter().enumerate() {
@ -302,7 +329,11 @@ impl DecryptNode {
DecryptNode { vks }
}
pub fn decrypt_blocks<'a>(&self, network: &Network, blocks: &'a [CompactBlock]) -> Vec<DecryptedBlock<'a>> {
pub fn decrypt_blocks<'a>(
&self,
network: &Network,
blocks: &'a [CompactBlock],
) -> Vec<DecryptedBlock<'a>> {
let vks: Vec<_> = self.vks.iter().collect();
let mut decrypted_blocks: Vec<DecryptedBlock> = blocks
.par_iter()
@ -430,7 +461,11 @@ pub async fn connect_lightwalletd(url: &str) -> anyhow::Result<CompactTxStreamer
Ok(client)
}
pub async fn sync(network: &Network, 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 = network

View File

@ -1,9 +1,9 @@
use crate::chain::DecryptedNote;
use byteorder::WriteBytesExt;
use std::io::{Read, Write};
use zcash_encoding::{Optional, Vector};
use zcash_primitives::merkle_tree::{CommitmentTree, Hashable};
use zcash_primitives::sapling::Node;
use zcash_encoding::{Optional, Vector};
/*
Same behavior and structure as CommitmentTree<Node> from librustzcash

View File

@ -87,10 +87,10 @@ 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};
use zcash_params::coin::CoinType;
#[test]
fn test_contacts() {

View File

@ -1,18 +1,18 @@
use crate::chain::{Nf, NfRef};
use crate::contact::Contact;
use crate::prices::Quote;
use crate::taddr::derive_tkeys;
use crate::taddr::{derive_tkeys, TBalance};
use crate::transaction::TransactionInfo;
use crate::{CTree, Witness};
use rusqlite::{params, Connection, OptionalExtension, Transaction};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_params::coin::{get_coin_chain, get_coin_id, CoinType};
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 zcash_params::coin::{CoinType, get_coin_chain, get_coin_id};
mod migration;
@ -75,11 +75,12 @@ pub struct AccountBackup {
impl DbAdapter {
pub fn new(coin_type: CoinType, db_path: &str) -> anyhow::Result<DbAdapter> {
let connection = Connection::open(db_path)?;
connection.query_row("PRAGMA journal_mode = WAL", [], |_| {
Ok(())
})?;
connection.query_row("PRAGMA journal_mode = WAL", [], |_| Ok(()))?;
connection.execute("PRAGMA synchronous = NORMAL", [])?;
Ok(DbAdapter { coin_type, connection })
Ok(DbAdapter {
coin_type,
connection,
})
}
pub fn begin_transaction(&mut self) -> anyhow::Result<Transaction> {
@ -131,12 +132,14 @@ impl DbAdapter {
}
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 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;
},
)? + 1;
Ok(index)
}
@ -191,10 +194,7 @@ impl DbAdapter {
"DELETE FROM transactions WHERE height >= ?1",
params![height],
)?;
tx.execute(
"DELETE FROM messages WHERE height >= ?1",
params![height],
)?;
tx.execute("DELETE FROM messages WHERE height >= ?1", params![height])?;
tx.commit()?;
Ok(())
@ -338,9 +338,7 @@ impl DbAdapter {
pub fn get_last_sync_height(&self) -> anyhow::Result<Option<u32>> {
let height: Option<u32> =
self.connection
.query_row("SELECT MAX(height) FROM blocks", [], |row| {
row.get(0)
})?;
.query_row("SELECT MAX(height) FROM blocks", [], |row| row.get(0))?;
Ok(height)
}
@ -791,18 +789,14 @@ impl DbAdapter {
pub fn truncate_data(&self) -> anyhow::Result<()> {
self.connection.execute("DELETE FROM blocks", [])?;
self.connection.execute("DELETE FROM contacts", [])?;
self.connection
.execute("DELETE FROM diversifiers", [])?;
self.connection.execute("DELETE FROM diversifiers", [])?;
self.connection
.execute("DELETE FROM historical_prices", [])?;
self.connection
.execute("DELETE FROM received_notes", [])?;
self.connection.execute("DELETE FROM received_notes", [])?;
self.connection
.execute("DELETE FROM sapling_witnesses", [])?;
self.connection
.execute("DELETE FROM transactions", [])?;
self.connection
.execute("DELETE FROM messages", [])?;
self.connection.execute("DELETE FROM transactions", [])?;
self.connection.execute("DELETE FROM messages", [])?;
Ok(())
}
@ -835,7 +829,10 @@ impl DbAdapter {
}
pub fn get_full_backup(&self) -> anyhow::Result<Vec<AccountBackup>> {
let _ = self.connection.execute("ALTER TABLE accounts ADD COLUMN aindex INT NOT NULL DEFAULT 0", []); // ignore error
let _ = self.connection.execute(
"ALTER TABLE accounts ADD COLUMN aindex INT NOT NULL DEFAULT 0",
[],
); // ignore error
let mut statement = self.connection.prepare(
"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")?;
@ -878,8 +875,10 @@ impl DbAdapter {
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])?;
self.connection.execute(
"INSERT INTO taddrs(account, sk, address) VALUES (?1,?2,?3)",
params![id_account, a.t_sk, t_addr],
)?;
}
Ok::<_, anyhow::Error>(())
};
@ -899,14 +898,36 @@ impl DbAdapter {
}
pub fn mark_message_read(&self, message_id: u32, read: bool) -> anyhow::Result<()> {
self.connection.execute("UPDATE messages SET read = ?1 WHERE id = ?2",
params![read, message_id])?;
self.connection.execute(
"UPDATE messages SET read = ?1 WHERE id = ?2",
params![read, message_id],
)?;
Ok(())
}
pub fn mark_all_messages_read(&self, account: u32, read: bool) -> anyhow::Result<()> {
self.connection.execute("UPDATE messages SET read = ?1 WHERE account = ?2",
params![read, account])?;
self.connection.execute(
"UPDATE messages SET read = ?1 WHERE account = ?2",
params![read, account],
)?;
Ok(())
}
pub fn store_t_scan(&self, addresses: &[TBalance]) -> anyhow::Result<()> {
self.connection.execute(
"CREATE TABLE IF NOT EXISTS taddr_scan(\
id INTEGER NOT NULL PRIMARY KEY,
address TEXT NOT NULL,
value INTEGER NOT NULL,
aindex INTEGER NOT NULL)",
[],
)?;
for addr in addresses.iter() {
self.connection.execute(
"INSERT INTO taddr_scan(address, value, aindex) VALUES (?1, ?2, ?3)",
params![addr.address, addr.balance, addr.index],
)?;
}
Ok(())
}
@ -917,8 +938,11 @@ impl DbAdapter {
}
fn get_coin_id_by_address(address: &str) -> u8 {
if address.starts_with("ys") { 1 }
else { 0 }
if address.starts_with("ys") {
1
} else {
0
}
}
pub struct ZMessage {
@ -938,9 +962,9 @@ impl ZMessage {
#[cfg(test)]
mod tests {
use zcash_params::coin::CoinType;
use crate::db::{DbAdapter, ReceivedNote, DEFAULT_DB_PATH};
use crate::{CTree, Witness};
use zcash_params::coin::CoinType;
#[test]
fn test_db() {

View File

@ -143,15 +143,13 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
if version < 2 {
connection.execute(
"CREATE INDEX i_received_notes ON received_notes(account)", [])?;
connection.execute(
"CREATE INDEX i_account ON accounts(address)", [])?;
connection.execute(
"CREATE INDEX i_contact ON contacts(address)", [])?;
connection.execute(
"CREATE INDEX i_transaction ON transactions(account)", [])?;
connection.execute(
"CREATE INDEX i_witness ON sapling_witnesses(height)", [])?;
"CREATE INDEX i_received_notes ON received_notes(account)",
[],
)?;
connection.execute("CREATE INDEX i_account ON accounts(address)", [])?;
connection.execute("CREATE INDEX i_contact ON contacts(address)", [])?;
connection.execute("CREATE INDEX i_transaction ON transactions(account)", [])?;
connection.execute("CREATE INDEX i_witness ON sapling_witnesses(height)", [])?;
}
if version < 3 {

View File

@ -1,15 +1,15 @@
use bech32::{ToBase32, Variant};
use bip39::{Language, Mnemonic, Seed};
use rand::RngCore;
use rand::rngs::OsRng;
use rand::RngCore;
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_params::coin::{get_coin_chain, CoinChain, CoinType};
use zcash_primitives::consensus::Parameters;
use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey};
use zcash_params::coin::{CoinChain, CoinType, get_coin_chain};
pub struct KeyHelpers {
coin_type: CoinType,
@ -17,25 +17,29 @@ pub struct KeyHelpers {
impl KeyHelpers {
pub fn new(coin_type: CoinType) -> Self {
KeyHelpers {
coin_type
}
KeyHelpers { coin_type }
}
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
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)> {
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)
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)
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))
@ -51,19 +55,23 @@ impl KeyHelpers {
return 0;
}
if let Ok(Some(_)) =
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &key)
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)
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &key)
{
return 2;
}
-1
}
pub fn derive_secret_key(&self, mnemonic: &Mnemonic, index: u32) -> anyhow::Result<(String, String, String)> {
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());
@ -79,7 +87,10 @@ impl KeyHelpers {
Ok((sk, fvk, pa))
}
pub fn derive_viewing_key(&self, extsk: &ExtendedSpendingKey) -> anyhow::Result<(String, String)> {
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)?;
@ -91,7 +102,8 @@ impl KeyHelpers {
pub fn derive_address(&self, fvk: &ExtendedFullViewingKey) -> anyhow::Result<String> {
let network = self.chain().network();
let (_, payment_address) = fvk.default_address();
let address = encode_payment_address(network.hrp_sapling_payment_address(), &payment_address);
let address =
encode_payment_address(network.hrp_sapling_payment_address(), &payment_address);
Ok(address)
}

View File

@ -1,4 +1,20 @@
use serde::{Deserialize, Serialize};
#[cfg(feature = "ledger_sapling")]
pub mod sapling;
mod transparent;
#[derive(Serialize, Deserialize)]
#[allow(non_snake_case)]
struct APDURequest {
apduHex: String,
}
#[derive(Serialize, Deserialize)]
struct APDUReply {
data: String,
error: Option<String>,
}
pub use transparent::sweep_ledger;

View File

@ -1,56 +1,51 @@
#![allow(unused_imports)]
use crate::ledger::{APDUReply, APDURequest};
use crate::{Tx, TxIn, TxOut};
use anyhow::anyhow;
use bip39::{Mnemonic, Language, Seed};
use byteorder::{ByteOrder, LE, LittleEndian, WriteBytesExt};
use ed25519_bip32::{DerivationScheme, XPrv};
use sha2::{Sha256, Sha512};
use hmac::{Hmac, Mac};
use hmac::digest::{crypto_common, FixedOutput, MacMarker, Update};
use bip39::{Language, Mnemonic, Seed};
use blake2b_simd::Params;
use jubjub::{ExtendedPoint, Fr, SubgroupPoint};
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt, LE};
use ed25519_bip32::{DerivationScheme, XPrv};
use group::GroupEncoding;
use hmac::digest::{crypto_common, FixedOutput, MacMarker, Update};
use hmac::{Hmac, Mac};
use jubjub::{ExtendedPoint, Fr, SubgroupPoint};
use ledger_apdu::{APDUAnswer, APDUCommand};
use rand::rngs::OsRng;
use zcash_primitives::zip32::{ExtendedSpendingKey, ChildIndex, ExtendedFullViewingKey, ChainCode, DiversifierKey};
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_payment_address, encode_extended_spending_key, encode_payment_address};
use serde::{Deserialize, Serialize};
use sha2::{Sha256, Sha512};
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, decode_payment_address, encode_extended_spending_key,
encode_payment_address,
};
use zcash_primitives::consensus::Network::MainNetwork;
use zcash_primitives::consensus::{BlockHeight, Network, Parameters};
use zcash_primitives::constants::{PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR};
use zcash_primitives::keys::OutgoingViewingKey;
use zcash_primitives::sapling::keys::{ExpandedSpendingKey, FullViewingKey};
use zcash_primitives::sapling::{Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, ViewingKey};
use serde::{Serialize, Deserialize};
use serde::__private::de::Content::ByteBuf;
use zcash_primitives::memo::Memo;
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::sapling::keys::{ExpandedSpendingKey, FullViewingKey};
use zcash_primitives::sapling::note_encryption::sapling_note_encryption;
use zcash_primitives::sapling::prover::TxProver;
use zcash_primitives::sapling::redjubjub::{PublicKey, Signature};
use zcash_primitives::sapling::{
Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed, ViewingKey,
};
use zcash_primitives::transaction::builder::Builder;
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
use zcash_primitives::transaction::components::OutputDescription;
use zcash_primitives::transaction::components::sapling::GrothProofBytes;
use zcash_primitives::transaction::components::OutputDescription;
use zcash_primitives::zip32::{
ChainCode, ChildIndex, DiversifierKey, ExtendedFullViewingKey, ExtendedSpendingKey,
};
use zcash_proofs::sapling::SaplingProvingContext;
use crate::{Tx, TxIn, TxOut};
const HARDENED: u32 = 0x8000_0000;
const NETWORK: &Network = &MainNetwork;
const EXPIRY: u32 = 50;
const LEDGER_IP: &str = "192.168.0.101";
#[derive(Serialize, Deserialize)]
#[allow(non_snake_case)]
struct APDURequest {
apduHex: String,
}
#[derive(Serialize, Deserialize)]
struct APDUReply {
data: String,
error: Option<String>,
}
// fn get_ivk(app: &LedgerApp) -> anyhow::Result<String> {
// let command = ApduCommand {
// cla: 0x85,
@ -184,7 +179,12 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
let mut change = 0;
for sin in tx.inputs.iter() {
buffer.write_u32::<LE>(HARDENED)?;
let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &sin.fvk).unwrap().unwrap();
let fvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&sin.fvk,
)
.unwrap()
.unwrap();
let (_, pa) = fvk.default_address();
let address = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
assert_eq!(pa.to_bytes().len(), 43);
@ -196,7 +196,9 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
// assert_eq!(buffer.len(), 4+55*s_in_count);
for sout in tx.outputs.iter() {
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &sout.addr).unwrap().unwrap();
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &sout.addr)
.unwrap()
.unwrap();
assert_eq!(pa.to_bytes().len(), 43);
buffer.extend_from_slice(&pa.to_bytes());
buffer.write_u64::<LE>(sout.amount)?;
@ -219,7 +221,9 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
tx.outputs.push(output_change);
s_out_count += 1;
let pa_change = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &tx.change).unwrap().unwrap();
let pa_change = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &tx.change)
.unwrap()
.unwrap();
buffer.extend_from_slice(&pa_change.to_bytes());
buffer.write_u64::<LE>(change as u64)?;
buffer.push(0xF6); // no memo
@ -245,7 +249,7 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
p2: 0,
data: c.to_vec(),
};
let rep = send_request(&command).await;
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
}
@ -261,7 +265,7 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
p2: 0,
data: vec![],
};
let rep = send_request(&command).await;
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
let ak = &rep.apdu_data()[0..32];
let nsk = &rep.apdu_data()[32..64];
@ -297,13 +301,14 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
p2: 0,
data: vec![],
};
let rep = send_request(&command).await;
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
let rcv = &rep.apdu_data()[0..32];
let rseed = &rep.apdu_data()[32..64];
let rcv = Fr::from_bytes(&slice_to_hash(&rcv)).unwrap();
let rseed = slice_to_hash(&rseed);
let output_description = get_output_description(tx, i, change as u64, rcv, rseed, &mut context, prover);
let output_description =
get_output_description(tx, i, change as u64, rcv, rseed, &mut context, prover);
buffer.extend_from_slice(&output_description.cv.to_bytes());
buffer.extend_from_slice(&output_description.cmu.to_bytes());
buffer.extend_from_slice(&output_description.ephemeral_key.0);
@ -313,14 +318,20 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
output_descriptions.push(output_description);
}
let hash_data = get_hash_data(tx.height, u64::from(DEFAULT_FEE),
&spend_datas, &output_descriptions);
let hash_data = get_hash_data(
tx.height,
u64::from(DEFAULT_FEE),
&spend_datas,
&output_descriptions,
);
buffer.extend_from_slice(&hash_data);
let sig_hash: [u8; 32] = slice_to_hash(Params::new()
.hash_length(32)
.personal(&hex::decode("5a6361736853696748617368a675ffe9")?) // consensus branch id = canopy
.hash(&hash_data)
.as_bytes());
let sig_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(&hex::decode("5a6361736853696748617368a675ffe9")?) // consensus branch id = canopy
.hash(&hash_data)
.as_bytes(),
);
let mut tx_hash = [0u8; 32];
@ -340,7 +351,7 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
p2: 0,
data: c.to_vec(),
};
let rep = send_request(&command).await;
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
if p1 == 2 {
tx_hash.copy_from_slice(&rep.apdu_data()[0..32]);
@ -356,7 +367,7 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
p2: 0,
data: vec![],
};
let rep = send_request(&command).await;
let rep = send_http_request(&command).await;
log::debug!("{}", rep.retcode());
let signature = &rep.apdu_data()[0..64];
signatures.push(rep.apdu_data()[0..64].to_vec())
@ -365,21 +376,42 @@ pub async fn build_tx_ledger(tx: &mut Tx, prover: &impl TxProver) -> anyhow::Res
log::debug!("tx hash: {}", hex::encode(tx_hash));
log::debug!("sig hash: {}", hex::encode(sig_hash));
let binding_signature = prover.binding_sig(&mut context, DEFAULT_FEE, &sig_hash).map_err(|_| anyhow!("Cannot create binding signature"))?;
let binding_signature = prover
.binding_sig(&mut context, DEFAULT_FEE, &sig_hash)
.map_err(|_| anyhow!("Cannot create binding signature"))?;
let mut sig_buffer: Vec<u8> = vec![];
binding_signature.write(&mut sig_buffer).unwrap();
log::debug!("binding_signature: {}", hex::encode(&sig_buffer));
let tx = get_tx_data(tx.height, u64::from(DEFAULT_FEE), &spend_datas, &output_descriptions,
&signatures, &binding_signature);
let tx = get_tx_data(
tx.height,
u64::from(DEFAULT_FEE),
&spend_datas,
&output_descriptions,
&signatures,
&binding_signature,
);
Ok(tx)
}
fn get_spend_proof<T: TxProver>(tx: &Tx, i: usize, ak: SubgroupPoint, nsk: Fr, ar: Fr, rcv: Fr, context: &mut T::SaplingProvingContext, prover: &T) -> SpendData
{
fn get_spend_proof<T: TxProver>(
tx: &Tx,
i: usize,
ak: SubgroupPoint,
nsk: Fr,
ar: Fr,
rcv: Fr,
context: &mut T::SaplingProvingContext,
prover: &T,
) -> SpendData {
let txin = &tx.inputs[i];
let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &txin.fvk).unwrap().unwrap();
let fvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&txin.fvk,
)
.unwrap()
.unwrap();
let mut diversifier = [0u8; 11];
hex::decode_to_slice(&txin.diversifier, &mut diversifier).unwrap();
let diversifier = Diversifier(diversifier);
@ -397,16 +429,23 @@ fn get_spend_proof<T: TxProver>(tx: &Tx, i: usize, ak: SubgroupPoint, nsk: Fr, a
let cmu = Node::new(note.cmu().into());
let anchor = merkle_path.root(cmu).into();
let pgk = ProofGenerationKey {
ak,
nsk
};
let pgk = ProofGenerationKey { ak, nsk };
let value = txin.amount;
let vk = pgk.to_viewing_key();
let (spend_proof, cv, rk) = prover.spend_proof_with_rcv(context, rcv,
pgk, diversifier, Rseed::BeforeZip212(rseed), ar, value, anchor, merkle_path
).unwrap();
let (spend_proof, cv, rk) = prover
.spend_proof_with_rcv(
context,
rcv,
pgk,
diversifier,
Rseed::BeforeZip212(rseed),
ar,
value,
anchor,
merkle_path,
)
.unwrap();
let spend_data = SpendData {
position,
cv,
@ -418,8 +457,15 @@ fn get_spend_proof<T: TxProver>(tx: &Tx, i: usize, ak: SubgroupPoint, nsk: Fr, a
spend_data
}
fn get_output_description<T: TxProver>(tx: &Tx, i: usize, change_amount: u64, rcv: Fr, rseed: [u8; 32], context: &mut T::SaplingProvingContext, prover: &T)
-> OutputDescription<GrothProofBytes> {
fn get_output_description<T: TxProver>(
tx: &Tx,
i: usize,
change_amount: u64,
rcv: Fr,
rseed: [u8; 32],
context: &mut T::SaplingProvingContext,
prover: &T,
) -> OutputDescription<GrothProofBytes> {
let txout = if i == tx.outputs.len() {
TxOut {
addr: tx.change.clone(),
@ -427,13 +473,15 @@ fn get_output_description<T: TxProver>(tx: &Tx, i: usize, change_amount: u64, rc
ovk: tx.ovk.clone(),
memo: "".to_string(),
}
} else { tx.outputs[i].clone() };
} else {
tx.outputs[i].clone()
};
let ovk = OutgoingViewingKey(string_to_hash(&tx.ovk));
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &txout.addr).unwrap().unwrap();
let rseed = Rseed::AfterZip212(rseed);
let note = pa
.create_note(txout.amount, rseed)
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &txout.addr)
.unwrap()
.unwrap();
let rseed = Rseed::AfterZip212(rseed);
let note = pa.create_note(txout.amount, rseed).unwrap();
let encryptor = sapling_note_encryption::<_, zcash_primitives::consensus::MainNetwork>(
Some(ovk),
@ -446,7 +494,14 @@ fn get_output_description<T: TxProver>(tx: &Tx, i: usize, change_amount: u64, rc
let cmu = note.cmu();
let epk = *encryptor.epk();
let (zkproof, cv) = prover.output_proof_with_rcv(context, rcv, *encryptor.esk(), pa.clone(), note.rcm(), txout.amount);
let (zkproof, cv) = prover.output_proof_with_rcv(
context,
rcv,
*encryptor.esk(),
pa.clone(),
note.rcm(),
txout.amount,
);
let enc_ciphertext = encryptor.encrypt_note_plaintext();
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, &mut OsRng);
@ -456,7 +511,7 @@ fn get_output_description<T: TxProver>(tx: &Tx, i: usize, change_amount: u64, rc
ephemeral_key: epk.to_bytes().into(),
enc_ciphertext,
out_ciphertext,
zkproof
zkproof,
}
}
@ -470,34 +525,49 @@ fn slice_to_hash(s: &[u8]) -> [u8; 32] {
b
}
async fn send_request(command: &APDUCommand<Vec<u8>>) -> APDUAnswer<Vec<u8>> {
async fn send_http_request(command: &APDUCommand<Vec<u8>>) -> APDUAnswer<Vec<u8>> {
let port = 9000;
let apdu_hex = hex::encode(command.serialize());
let client = reqwest::Client::new();
let rep = client.post(format!("http://{}:{}", LEDGER_IP, port)).json(&APDURequest {
apduHex: apdu_hex,
}).header("Content-Type", "application/json").send().await.unwrap();
let rep = client
.post(format!("http://{}:{}", LEDGER_IP, port))
.json(&APDURequest { apduHex: apdu_hex })
.header("Content-Type", "application/json")
.send()
.await
.unwrap();
let rep: APDUReply = rep.json().await.unwrap();
let answer = APDUAnswer::from_answer(hex::decode(rep.data).unwrap());
answer.unwrap()
}
fn get_hash_data(expiry_height: u32, sapling_value_balance: u64, spend_datas: &[SpendData], output_descriptions: &[OutputDescription<GrothProofBytes>]) -> Vec<u8> {
let prevout_hash: [u8; 32] = slice_to_hash(Params::new()
.hash_length(32)
.personal(b"ZcashPrevoutHash")
.hash(&[])
.as_bytes());
let out_hash: [u8; 32] = slice_to_hash(Params::new()
.hash_length(32)
.personal(b"ZcashOutputsHash")
.hash(&[])
.as_bytes());
let sequence_hash: [u8; 32] = slice_to_hash(Params::new()
.hash_length(32)
.personal(b"ZcashSequencHash")
.hash(&[])
.as_bytes());
fn get_hash_data(
expiry_height: u32,
sapling_value_balance: u64,
spend_datas: &[SpendData],
output_descriptions: &[OutputDescription<GrothProofBytes>],
) -> Vec<u8> {
let prevout_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashPrevoutHash")
.hash(&[])
.as_bytes(),
);
let out_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashOutputsHash")
.hash(&[])
.as_bytes(),
);
let sequence_hash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashSequencHash")
.hash(&[])
.as_bytes(),
);
let mut data: Vec<u8> = vec![];
for sp in spend_datas.iter() {
@ -507,10 +577,13 @@ fn get_hash_data(expiry_height: u32, sapling_value_balance: u64, spend_datas: &[
data.extend_from_slice(&sp.rk.to_bytes());
data.extend_from_slice(&sp.zkproof);
}
let shieldedspendhash: [u8; 32] = slice_to_hash(Params::new()
.hash_length(32)
.personal(b"ZcashSSpendsHash")
.hash(&data).as_bytes());
let shieldedspendhash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashSSpendsHash")
.hash(&data)
.as_bytes(),
);
let mut data: Vec<u8> = vec![];
for output_description in output_descriptions.iter() {
@ -521,10 +594,13 @@ fn get_hash_data(expiry_height: u32, sapling_value_balance: u64, spend_datas: &[
data.extend_from_slice(&output_description.out_ciphertext);
data.extend_from_slice(&output_description.zkproof);
}
let shieldedoutputhash: [u8; 32] = slice_to_hash(Params::new()
.hash_length(32)
.personal(b"ZcashSOutputHash")
.hash(&data).as_bytes());
let shieldedoutputhash: [u8; 32] = slice_to_hash(
Params::new()
.hash_length(32)
.personal(b"ZcashSOutputHash")
.hash(&data)
.as_bytes(),
);
let mut tx_hash_data: Vec<u8> = vec![];
@ -537,7 +613,9 @@ fn get_hash_data(expiry_height: u32, sapling_value_balance: u64, spend_datas: &[
tx_hash_data.extend_from_slice(&shieldedspendhash);
tx_hash_data.extend_from_slice(&shieldedoutputhash);
tx_hash_data.write_u32::<LE>(0).unwrap();
tx_hash_data.write_u32::<LE>(expiry_height + EXPIRY).unwrap();
tx_hash_data
.write_u32::<LE>(expiry_height + EXPIRY)
.unwrap();
tx_hash_data.write_u64::<LE>(sapling_value_balance).unwrap();
tx_hash_data.write_u32::<LE>(1).unwrap();
@ -546,8 +624,14 @@ fn get_hash_data(expiry_height: u32, sapling_value_balance: u64, spend_datas: &[
tx_hash_data
}
fn get_tx_data(expiry_height: u32, sapling_value_balance: u64, spend_datas: &[SpendData], output_descriptions: &[OutputDescription<GrothProofBytes>],
signatures: &[Vec<u8>], binding_signature: &Signature) -> Vec<u8> {
fn get_tx_data(
expiry_height: u32,
sapling_value_balance: u64,
spend_datas: &[SpendData],
output_descriptions: &[OutputDescription<GrothProofBytes>],
signatures: &[Vec<u8>],
binding_signature: &Signature,
) -> Vec<u8> {
let mut tx_data: Vec<u8> = vec![];
tx_data.write_u32::<LE>(0x80000004).unwrap();
tx_data.write_u32::<LE>(0x892F2085).unwrap();
@ -586,17 +670,17 @@ fn get_tx_data(expiry_height: u32, sapling_value_balance: u64, spend_datas: &[Sp
#[cfg(test)]
mod tests {
use std::fs::File;
use std::io::Read;
use crate::ledger::{build_tx_ledger, send_request, slice_to_hash};
use crate::Tx;
use blake2b_simd::Params;
use group::GroupEncoding;
use jubjub::{Fr, ExtendedPoint, SubgroupPoint};
use jubjub::{ExtendedPoint, Fr, SubgroupPoint};
use ledger_apdu::*;
use std::fs::File;
use std::io::Read;
use zcash_primitives::constants::SPENDING_KEY_GENERATOR;
use zcash_primitives::sapling::redjubjub::{PublicKey, Signature};
use zcash_proofs::prover::LocalTxProver;
use crate::ledger::{build_tx_ledger, send_request, slice_to_hash};
use crate::Tx;
#[tokio::test]
async fn get_version() {
@ -625,7 +709,10 @@ mod tests {
let answer = send_request(&command).await;
let address = String::from_utf8(answer.apdu_data()[43..].to_ascii_lowercase()).unwrap();
println!("{}", address);
assert_eq!(address, "zs1m8d7506t4rpcgaag392xae698gx8j5at63qpg54ssprg6eqej0grmkfu76tq6p495z3w6s8qlll");
assert_eq!(
address,
"zs1m8d7506t4rpcgaag392xae698gx8j5at63qpg54ssprg6eqej0grmkfu76tq6p495z3w6s8qlll"
);
}
#[tokio::test]

View File

@ -0,0 +1,132 @@
use crate::ledger::{APDUReply, APDURequest};
use crate::taddr::{get_taddr_balance, get_utxos};
use crate::{connect_lightwalletd, GetAddressUtxosArg, LWD_URL};
use anyhow::Result;
use byteorder::{BigEndian as BE, LittleEndian as LE, ReadBytesExt, WriteBytesExt};
use ledger_apdu::{APDUAnswer, APDUCommand};
use ledger_transport_hid::hidapi::HidApi;
use ledger_transport_hid::TransportNativeHID;
use ripemd::Digest;
use ripemd::Ripemd160;
use secp256k1::PublicKey;
use sha2::Sha256;
use std::io::{Read, Write};
use std::str::from_utf8;
use tonic::Request;
use zcash_client_backend::encoding::encode_transparent_address;
use zcash_primitives::consensus::Network::MainNetwork;
use zcash_primitives::consensus::Parameters;
use zcash_primitives::legacy::TransparentAddress;
const HARDENED: u32 = 0x80000000;
pub async fn sweep_ledger() -> Result<()> {
let api = HidApi::new()?;
let device = TransportNativeHID::list_ledgers(&api).next().unwrap();
let transport = TransportNativeHID::open_device(&api, &device).unwrap();
let mut data = vec![];
data.write_u8(5)?;
data.write_u32::<BE>(44 | HARDENED)?;
data.write_u32::<BE>(MainNetwork.coin_type() | HARDENED)?;
data.write_u32::<BE>(HARDENED)?;
data.write_u32::<BE>(0x0)?;
data.write_u32::<BE>(0x0)?;
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x40,
p1: 0,
p2: 0,
data: data.as_slice(),
})
.unwrap();
println!("{}", res.retcode());
let mut data = res.apdu_data();
println!("{}", hex::encode(&data));
let len = data.read_u8()?;
let mut pk = vec![0u8; len as usize];
data.read_exact(&mut pk).unwrap();
println!("{}", hex::encode(&pk));
let pub_key = PublicKey::from_slice(&pk).unwrap();
let pub_key = pub_key.serialize();
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
let pub_key_hash: [u8; 20] = pub_key.into();
let address = TransparentAddress::PublicKey(pub_key_hash.clone());
let address = encode_transparent_address(
&MainNetwork.b58_pubkey_address_prefix(),
&MainNetwork.b58_script_address_prefix(),
&address,
);
println!("{}", address);
let mut client = connect_lightwalletd(LWD_URL).await?;
let balance = get_taddr_balance(&mut client, &address).await.unwrap();
println!("{}", balance);
let req = GetAddressUtxosArg {
addresses: vec![address.to_string()],
start_height: 0,
max_entries: 0,
};
let utxo_rep = client
.get_address_utxos(Request::new(req))
.await?
.into_inner();
let mut first = true;
for utxo_reply in utxo_rep.address_utxos.iter() {
let mut data = vec![];
data.write_u8(0)?;
data.write_all(&utxo_reply.txid)?;
data.write_u32::<LE>(utxo_reply.index as u32)?;
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x40,
p1: if first { 0 } else { 0x80 },
p2: 0,
data: data.as_slice(),
})
.unwrap();
first = false;
}
let data = [0u8];
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x4A,
p1: 0xFF,
p2: 0,
data: data.as_slice(),
})
.unwrap();
println!("{}", res.retcode());
let mut data = vec![];
data.write_u8(1)?;
data.write_u64::<LE>(balance)?;
data.write_u8(25)?;
data.write_u8(0x76)?;
data.write_u8(0xa9)?;
data.write_u8(0x14)?;
data.write_all(&pub_key_hash)?;
data.write_u8(0x88)?;
data.write_u8(0xac)?;
let res = transport
.exchange(&APDUCommand {
cla: 0xE0,
ins: 0x4A,
p1: 0x80,
p2: 0,
data: data.as_slice(),
})
.unwrap();
println!("{}", res.retcode());
Ok(())
}

View File

@ -3,13 +3,13 @@
#[path = "generated/cash.z.wallet.sdk.rpc.rs"]
pub mod lw_rpc;
pub use zcash_params::coin::{CoinType, get_coin_type, get_branch};
pub use zcash_params::coin::{get_branch, get_coin_type, CoinType};
// Mainnet
// pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
// pub const LWD_URL: &str = "https://lwdv3.zecwallet.co";
// pub const LWD_URL: &str = "http://lwd.hanh.me:9067";
pub const LWD_URL: &str = "http://127.0.0.1:9067";
// pub const LWD_URL: &str = "http://127.0.0.1:9067";
// Testnet
// pub const LWD_URL: &str = "https://testnet.lightwalletd.com:9067";
@ -40,8 +40,14 @@ mod wallet;
mod ledger;
#[cfg(not(feature = "ledger"))]
#[allow(dead_code)]
mod ledger {
pub async fn build_tx_ledger(_tx: &mut super::pay::Tx, _prover: &impl zcash_primitives::sapling::prover::TxProver) -> anyhow::Result<Vec<u8>> { unreachable!() }
pub async fn build_tx_ledger(
_tx: &mut super::pay::Tx,
_prover: &impl zcash_primitives::sapling::prover::TxProver,
) -> anyhow::Result<Vec<u8>> {
unreachable!()
}
}
pub fn hex_to_hash(hex: &str) -> anyhow::Result<[u8; 32]> {
@ -58,7 +64,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::{KeyHelpers, generate_random_enc_key};
pub use crate::key::{generate_random_enc_key, KeyHelpers};
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
pub use crate::lw_rpc::*;
pub use crate::mempool::MemPool;
@ -66,7 +72,10 @@ 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, encrypt_backup, decrypt_backup};
pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance};
#[cfg(feature = "ledger_sapling")]
pub use crate::ledger::sapling::build_tx_ledger;
#[cfg(feature = "ledger")]
pub use crate::ledger::sweep_ledger;

View File

@ -0,0 +1,6 @@
use sync::sweep_ledger;
#[tokio::main]
async fn main() {
sweep_ledger().await.unwrap();
}

View File

@ -1,23 +1,18 @@
use clap::{Command, Arg};
use clap::{Arg, Command};
use std::fs::File;
use std::io::{Read, Write};
use std::str::FromStr;
use sync::{KeyHelpers, Tx};
use zcash_client_backend::encoding::decode_extended_spending_key;
use zcash_params::coin::CoinType;
use zcash_primitives::consensus::{Network, Parameters};
use zcash_proofs::prover::LocalTxProver;
use zcash_params::coin::CoinType;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let matches = Command::new("Cold wallet Signer CLI")
.version("1.0")
.arg(
Arg::new("coin")
.short('c')
.long("coin")
.takes_value(true),
)
.arg(Arg::new("coin").short('c').long("coin").takes_value(true))
.arg(
Arg::new("tx_filename")
.short('t')
@ -33,13 +28,17 @@ async fn main() -> anyhow::Result<()> {
.get_matches();
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 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")
_ => panic!("Invalid coin"),
};
let key = dotenv::var("KEY").unwrap();
let index = u32::from_str(&dotenv::var("INDEX").unwrap_or_else(|_| "0".to_string())).unwrap();

View File

@ -1,12 +1,10 @@
use bip39::{Language, Mnemonic};
use rand::rngs::OsRng;
use rand::{thread_rng, RngCore};
use sync::{
pedersen_hash, print_witness2, ChainError, DbAdapter, Wallet, Witness, LWD_URL,
};
use sync::{pedersen_hash, print_witness2, ChainError, DbAdapter, Wallet, Witness, LWD_URL};
use zcash_params::coin::CoinType;
use zcash_primitives::merkle_tree::Hashable;
use zcash_primitives::sapling::Node;
use zcash_params::coin::CoinType;
const DB_NAME: &str = "zec.db";

View File

@ -1,16 +1,15 @@
use crate::chain::to_output_description;
use crate::{
connect_lightwalletd, get_latest_height, CompactTx, CompactTxStreamerClient, DbAdapter,
Exclude
connect_lightwalletd, get_latest_height, CompactTx, CompactTxStreamerClient, DbAdapter, Exclude,
};
use std::collections::HashMap;
use tonic::transport::Channel;
use tonic::Request;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
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;
@ -57,10 +56,14 @@ impl MemPool {
}
fn set_ivk(&mut self, ivk: &str) {
let fvk =
decode_extended_full_viewing_key(self.chain().network().hrp_sapling_extended_full_viewing_key(), &ivk)
.unwrap()
.unwrap();
let fvk = decode_extended_full_viewing_key(
self.chain()
.network()
.hrp_sapling_extended_full_viewing_key(),
&ivk,
)
.unwrap()
.unwrap();
let ivk = fvk.fvk.vk.ivk();
self.ivk = Some(ivk);
}
@ -158,7 +161,9 @@ impl MemPool {
Ok(())
}
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
fn chain(&self) -> &dyn CoinChain {
get_coin_chain(self.coin_type)
}
}
#[cfg(test)]

View File

@ -1,34 +1,33 @@
use crate::db::SpendableNote;
use crate::wallet::RecipientMemo;
use crate::{
connect_lightwalletd, get_latest_height, hex_to_hash, GetAddressUtxosReply,
RawTransaction
connect_lightwalletd, get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction,
};
use anyhow::anyhow;
use jubjub::Fr;
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
use secp256k1::SecretKey;
use serde::{Deserialize, Serialize};
use std::sync::mpsc;
use anyhow::anyhow;
use tonic::Request;
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key,
encode_payment_address,
};
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
use zcash_primitives::consensus::{BlockHeight, Parameters};
use zcash_primitives::keys::OutgoingViewingKey;
use zcash_primitives::legacy::Script;
use zcash_primitives::memo::{Memo, MemoBytes};
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::keys::OutgoingViewingKey;
use zcash_primitives::sapling::prover::TxProver;
use zcash_primitives::sapling::{Diversifier, Node, PaymentAddress, Rseed};
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 {
@ -113,7 +112,9 @@ impl TxBuilder {
let tx_in = TxIn {
diversifier: hex::encode(diversifier.0),
fvk: encode_extended_full_viewing_key(
self.chain().network().hrp_sapling_extended_full_viewing_key(),
self.chain()
.network()
.hrp_sapling_extended_full_viewing_key(),
&fvk,
),
amount: u64::from(amount),
@ -157,7 +158,10 @@ impl TxBuilder {
ovk: &OutgoingViewingKey,
address: &PaymentAddress,
) -> anyhow::Result<()> {
self.tx.change = encode_payment_address(self.chain().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(())
}
@ -188,7 +192,8 @@ impl TxBuilder {
t_amount += Amount::from_i64(utxo.value_zat).unwrap();
}
}
let target_amount_with_fee = (target_amount + DEFAULT_FEE).ok_or(anyhow!("Invalid amount"))?;
let target_amount_with_fee =
(target_amount + DEFAULT_FEE).ok_or(anyhow!("Invalid amount"))?;
if target_amount_with_fee > t_amount {
// We need to use some shielded notes because the transparent balance is not enough
let mut amount = (target_amount_with_fee - t_amount).unwrap();
@ -281,7 +286,9 @@ impl TxBuilder {
Ok(())
}
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
fn chain(&self) -> &dyn CoinChain {
get_coin_chain(self.coin_type)
}
}
impl Tx {

View File

@ -17,10 +17,10 @@ use std::sync::Arc;
use std::time::Instant;
use tokio::sync::mpsc;
use tokio::sync::Mutex;
use zcash_params::coin::{get_coin_chain, CoinType};
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(network: &Network, fvks: &[ExtendedFullViewingKey]) -> anyhow::Result<()> {
let fvks: HashMap<_, _> = fvks
@ -311,7 +311,9 @@ pub async fn sync_async(
return c;
});
let ids: Vec<_> = ids.into_iter().map(|e| e.id_tx).collect();
retrieve_tx_info(coin_type, &mut client, &db_path2, &ids).await.unwrap();
retrieve_tx_info(coin_type, &mut client, &db_path2, &ids)
.await
.unwrap();
}
log::info!("Transaction Details : {}", start.elapsed().as_millis());

View File

@ -2,7 +2,7 @@ use crate::{
AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, GetAddressUtxosReply,
};
use bip39::{Language, Mnemonic, Seed};
use ripemd::{Ripemd160, Digest};
use ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use sha2::Sha256;
use tiny_hderive::bip32::ExtendedPrivKey;
@ -48,7 +48,44 @@ pub async fn get_utxos(
}
}
pub fn derive_tkeys(network: &Network, phrase: &str, path: &str) -> anyhow::Result<(String, String)> {
pub async fn scan_transparent_accounts(
network: &Network,
client: &mut CompactTxStreamerClient<Channel>,
db: &DbAdapter,
account: u32,
gap_limit: usize,
) -> anyhow::Result<()> {
let mut addresses = vec![];
let (seed, mut index) = db.get_seed(account)?;
if let Some(seed) = seed {
let mut gap = 0;
while gap < gap_limit {
let bip44_path = format!("m/44'/{}'/0'/0/{}", network.coin_type(), index);
log::info!("{} {}", index, bip44_path);
let (_, address) = derive_tkeys(network, &seed, &bip44_path)?;
let balance = get_taddr_balance(client, &address).await?;
if balance > 0 {
addresses.push(TBalance {
index,
address,
balance,
});
gap = 0;
} else {
gap += 1;
}
index += 1;
}
}
db.store_t_scan(&addresses)?;
Ok(())
}
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();
@ -66,3 +103,9 @@ pub fn derive_tkeys(network: &Network, phrase: &str, path: &str) -> anyhow::Resu
let sk = secret_key.display_secret().to_string();
Ok((sk, address))
}
pub struct TBalance {
pub index: u32,
pub address: String,
pub balance: u64,
}

View File

@ -1,16 +1,18 @@
use crate::contact::{Contact, ContactDecoder};
use crate::wallet::decode_memo;
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter};
use anyhow::anyhow;
use futures::StreamExt;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::mpsc;
use std::sync::mpsc::SyncSender;
use anyhow::anyhow;
use tonic::transport::Channel;
use tonic::Request;
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, encode_payment_address, encode_transparent_address,
};
use zcash_params::coin::{get_branch, get_coin_chain, CoinType};
use zcash_primitives::consensus::{BlockHeight, Network, Parameters};
use zcash_primitives::memo::Memo;
use zcash_primitives::sapling::note_encryption::{
@ -18,8 +20,6 @@ use zcash_primitives::sapling::note_encryption::{
};
use zcash_primitives::transaction::Transaction;
use zcash_primitives::zip32::ExtendedFullViewingKey;
use zcash_params::coin::{CoinType, get_coin_chain, get_branch};
use crate::wallet::decode_memo;
#[derive(Debug)]
pub struct TransactionInfo {
@ -101,8 +101,7 @@ pub async fn decode_transaction(
}
for output in sapling_bundle.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)?;
@ -242,7 +241,12 @@ pub async fn retrieve_tx_info(
});
}
db.store_tx_metadata(tx_info.id_tx, &tx_info)?;
let z_msg = decode_memo(&tx_info.memo, &tx_info.address, tx_info.timestamp, tx_info.height);
let z_msg = decode_memo(
&tx_info.memo,
&tx_info.address,
tx_info.timestamp,
tx_info.height,
);
if !z_msg.is_empty() {
db.store_message(tx_info.account, &z_msg)?;
}
@ -268,8 +272,8 @@ mod tests {
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::{Network, Parameters};
use zcash_params::coin::CoinType;
use zcash_primitives::consensus::{Network, Parameters};
#[tokio::test]
async fn test_decode_transaction() {
@ -287,14 +291,26 @@ mod tests {
}
}
let fvk = db.get_ivk(account).unwrap();
let fvk =
decode_extended_full_viewing_key(Network::MainNetwork.hrp_sapling_extended_full_viewing_key(), &fvk)
.unwrap()
.unwrap();
let tx_info =
decode_transaction(&Network::MainNetwork, &mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1000, 1)
.await
.unwrap();
let fvk = decode_extended_full_viewing_key(
Network::MainNetwork.hrp_sapling_extended_full_viewing_key(),
&fvk,
)
.unwrap()
.unwrap();
let tx_info = decode_transaction(
&Network::MainNetwork,
&mut client,
&nf_map,
1,
account,
&fvk,
&tx_hash,
1313212,
1000,
1,
)
.await
.unwrap();
println!("{:?}", tx_info);
}
}

View File

@ -1,12 +1,21 @@
use crate::chain::{get_activation_date, get_block_by_time};
use crate::contact::{serialize_contacts, Contact};
use crate::db::{AccountBackup, ZMessage};
use crate::key::KeyHelpers;
use crate::pay::Tx;
use crate::pay::TxBuilder;
use crate::prices::fetch_historical_prices;
use crate::scan::ProgressCallback;
use crate::taddr::{get_taddr_balance, get_utxos};
use crate::{broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree, DbAdapter, CompactTxStreamerClient};
use crate::taddr::{get_taddr_balance, get_utxos, scan_transparent_accounts};
use crate::{
broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree, CompactTxStreamerClient,
DbAdapter,
};
use anyhow::anyhow;
use bech32::FromBase32;
use bip39::{Language, Mnemonic};
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use lazycell::AtomicLazyCell;
use rand::rngs::OsRng;
use rand::RngCore;
@ -14,30 +23,23 @@ use secp256k1::SecretKey;
use serde::Deserialize;
use serde::Serialize;
use std::convert::TryFrom;
use std::fs::File;
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 tonic::transport::Channel;
use tonic::Request;
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, decode_extended_spending_key, encode_payment_address,
};
use zcash_client_backend::zip321::{Payment, TransactionRequest};
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
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::{CoinChain, CoinType, get_coin_chain};
use crate::chain::{get_activation_date, get_block_by_time};
use crate::db::{AccountBackup, ZMessage};
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
@ -93,8 +95,7 @@ impl RecipientMemo {
fn from_recipient(from: &str, r: &Recipient) -> Self {
let memo = if !r.reply_to && r.subject.is_empty() {
r.memo.clone()
}
else {
} else {
encode_memo(from, r.reply_to, &r.subject, &r.memo)
};
RecipientMemo {
@ -116,15 +117,18 @@ pub fn decode_memo(memo: &str, recipient: &str, timestamp: u32, height: u32) ->
let memo_lines: Vec<_> = memo.splitn(4, '\n').collect();
let msg = if memo_lines[0] == "\u{1F6E1}MSG" {
ZMessage {
sender: if memo_lines[1].is_empty() { None } else { Some(memo_lines[1].to_string()) },
sender: if memo_lines[1].is_empty() {
None
} else {
Some(memo_lines[1].to_string())
},
recipient: recipient.to_string(),
subject: memo_lines[2].to_string(),
body: memo_lines[3].to_string(),
timestamp,
height,
}
}
else {
} else {
ZMessage {
sender: None,
recipient: recipient.to_string(),
@ -152,7 +156,7 @@ impl Wallet {
}
}
pub fn reset_db(&self) -> anyhow::Result<()> {
pub fn reset_db(&self) -> anyhow::Result<()> {
self.db.reset_db()
}
@ -180,6 +184,13 @@ impl Wallet {
Ok(new_id)
}
pub fn new_sub_account_index(&self, id: u32, name: &str, index: u32) -> anyhow::Result<i32> {
let (seed, _) = self.db.get_seed(id)?;
let seed = seed.ok_or_else(|| anyhow!("Account has no seed"))?;
let new_id = self.new_account_with_key(name, &seed, index)?;
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 {
@ -198,9 +209,9 @@ impl Wallet {
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(), index, sk.as_deref(), &ivk, &pa)?;
let account =
self.db
.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?;
if account > 0 {
self.db.create_taddr(account as u32)?;
}
@ -254,7 +265,16 @@ impl Wallet {
ld_url,
)
.await?;
Self::scan_async(coin_type, 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(())
}
@ -282,7 +302,11 @@ impl Wallet {
Ok(())
}
async fn store_tree_state(&self, client: &mut CompactTxStreamerClient<Channel>, height: u32) -> anyhow::Result<()> {
async fn store_tree_state(
&self,
client: &mut CompactTxStreamerClient<Channel>,
height: u32,
) -> anyhow::Result<()> {
let block_id = BlockId {
height: height as u64,
hash: vec![],
@ -316,10 +340,12 @@ impl Wallet {
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(self.network().hrp_sapling_extended_full_viewing_key(), &fvk)
.unwrap()
.unwrap();
let fvk = decode_extended_full_viewing_key(
self.network().hrp_sapling_extended_full_viewing_key(),
&fvk,
)
.unwrap()
.unwrap();
let utxos = if use_transparent {
let mut client = connect_lightwalletd(&self.ld_url).await?;
get_utxos(&mut client, &self.db, account).await?
@ -347,9 +373,10 @@ impl Wallet {
.db
.get_tsk(account)?
.map(|tsk| SecretKey::from_str(&tsk).unwrap());
let extsk = decode_extended_spending_key(self.network().hrp_sapling_extended_spending_key(), &zsk)
.unwrap()
.unwrap();
let extsk =
decode_extended_spending_key(self.network().hrp_sapling_extended_spending_key(), &zsk)
.unwrap()
.unwrap();
let prover = self
.prover
.borrow()
@ -569,7 +596,12 @@ impl Wallet {
self.db.truncate_data()
}
pub fn make_payment_uri(&self, address: &str, amount: u64, memo: &str) -> anyhow::Result<String> {
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 {
@ -631,37 +663,51 @@ impl Wallet {
self.db.restore_full_backup(accounts)
}
pub fn store_share_secret(&self, account: u32, secret: &str, index: usize, threshold: usize, participants: usize) -> anyhow::Result<()> {
self.db.store_share_secret(
account,
secret,
index,
threshold,
participants,
)
pub fn store_share_secret(
&self,
account: u32,
secret: &str,
index: usize,
threshold: usize,
participants: usize,
) -> anyhow::Result<()> {
self.db
.store_share_secret(account, secret, index, threshold, participants)
}
pub fn get_share_secret(&self, account: u32) -> anyhow::Result<String> {
self.db.get_share_secret(account)
}
pub fn parse_recipients(&self, account: u32, recipients: &str) -> anyhow::Result<Vec<RecipientMemo>> {
pub fn parse_recipients(
&self,
account: u32,
recipients: &str,
) -> anyhow::Result<Vec<RecipientMemo>> {
let address = self.db.get_address(account)?;
let recipients: Vec<Recipient> = serde_json::from_str(recipients)?;
let recipient_memos: Vec<_> = recipients.iter().map(|r| RecipientMemo::from_recipient(&address, r)).collect();
let recipient_memos: Vec<_> = recipients
.iter()
.map(|r| RecipientMemo::from_recipient(&address, r))
.collect();
Ok(recipient_memos)
}
#[cfg(feature = "ledger_sapling")]
pub async fn ledger_sign(&mut self, tx_filename: &str) -> anyhow::Result<String> {
self._ensure_prover()?;
let file = File::open(tx_filename)?;
let file = std::file::File::open(tx_filename)?;
let mut tx: Tx = serde_json::from_reader(&file)?;
let raw_tx = crate::build_tx_ledger(&mut tx, self.prover.borrow().unwrap()).await?;
let tx_id = broadcast_tx(&raw_tx, &self.ld_url).await?;
Ok(tx_id)
}
#[cfg(not(feature = "ledger_sapling"))]
pub async fn ledger_sign(&mut self, _tx_filename: &str) -> anyhow::Result<String> {
unimplemented!()
}
pub async fn get_activation_date(&self) -> anyhow::Result<u32> {
let mut client = connect_lightwalletd(&self.ld_url).await?;
let date_time = get_activation_date(self.network(), &mut client).await?;
@ -674,26 +720,44 @@ impl Wallet {
Ok(date_time)
}
fn chain(&self) -> &dyn CoinChain { get_coin_chain(self.coin_type) }
fn network(&self) -> &Network { self.chain().network() }
pub async fn scan_transparent_accounts(
&self,
account: u32,
gap_limit: usize,
) -> anyhow::Result<()> {
let mut client = connect_lightwalletd(&self.ld_url).await?;
scan_transparent_accounts(self.network(), &mut client, &self.db, account, gap_limit)
.await?;
Ok(())
}
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";
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 backup = if !key.is_empty() {
let (hrp, key, _) = bech32::decode(key)?;
if hrp != "zwk" { anyhow::bail!("Invalid backup 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 cipher_text = cipher
.encrypt(Nonce::from_slice(NONCE), &*accounts_bin)
.map_err(|_e| anyhow::anyhow!("Failed to encrypt backup"))?;
base64::encode(cipher_text)
}
else {
} else {
base64::encode(accounts_bin)
};
Ok(backup)
@ -702,15 +766,18 @@ pub fn encrypt_backup(accounts: &[AccountBackup], key: &str) -> anyhow::Result<S
pub fn decrypt_backup(key: &str, backup: &str) -> anyhow::Result<Vec<AccountBackup>> {
let backup = if !key.is_empty() {
let (hrp, key, _) = bech32::decode(key)?;
if hrp != "zwk" { anyhow::bail!("Not a valid decryption 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)?;
cipher.decrypt(Nonce::from_slice(NONCE), &*backup).map_err(|_e| anyhow::anyhow!("Failed to decrypt backup"))?
}
else {
cipher
.decrypt(Nonce::from_slice(NONCE), &*backup)
.map_err(|_e| anyhow::anyhow!("Failed to decrypt backup"))?
} else {
base64::decode(backup)?
};
@ -751,8 +818,9 @@ mod tests {
let seed = dotenv::var("SEED").unwrap();
let kh = KeyHelpers::new(CoinType::Zcash);
let (sk, vk, pa) =
kh.derive_secret_key(&Mnemonic::from_phrase(&seed, Language::English).unwrap(), 0).unwrap();
let (sk, vk, pa) = kh
.derive_secret_key(&Mnemonic::from_phrase(&seed, Language::English).unwrap(), 0)
.unwrap();
println!("{} {} {}", sk, vk, pa);
// let wallet = Wallet::new("zec.db");
//