t-scan
This commit is contained in:
parent
00a1c54046
commit
6c5e8a6da7
13
Cargo.toml
13
Cargo.toml
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
5
build.rs
5
build.rs
|
@ -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();
|
||||
|
|
75
src/chain.rs
75
src/chain.rs
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
96
src/db.rs
96
src/db.rs
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
40
src/key.rs
40
src/key.rs
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(())
|
||||
}
|
21
src/lib.rs
21
src/lib.rs
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
use sync::sweep_ledger;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
sweep_ledger().await.unwrap();
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
25
src/pay.rs
25
src/pay.rs
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
47
src/taddr.rs
47
src/taddr.rs
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
176
src/wallet.rs
176
src/wallet.rs
|
@ -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");
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue