Multisig stub

This commit is contained in:
Hanh 2021-11-12 09:39:50 +08:00
parent 2470a4e6af
commit ccb25c61f9
22 changed files with 825 additions and 797 deletions

View File

@ -13,11 +13,11 @@ harness = false
[[bin]]
name = "warp-cli"
path = "src/main/warp_cli.rs"
[[bin]]
name = "cli"
path = "src/main/cli.rs"
#
#[[bin]]
#name = "cli"
#path = "src/main/cli.rs"
#
[[bin]]
name = "sign"
path = "src/main/sign.rs"
@ -54,29 +54,37 @@ ripemd160 = "0.9.1"
sha2 = "0.9.5"
lazy_static = "1.4.0"
rustyline = "8.2.0"
clap = "3.0.0-beta.2"
clap = "2.3.3"
chrono = "0.4.19"
lazycell = "1.3.0"
reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-features = false }
bech32 = "0.8.1"
rand_chacha = "0.3.1"
blake2b_simd = "0.5.11"
[dependencies.zcash_multisig]
git = "https://github.com/hhanh00/zcash-multisig.git"
rev = "ce5baab5a25021950f51a9d53dc6c39ea9fe2997"
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
[dependencies.zcash_client_backend]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "6baf1ba63261dda09e5b1d4b476977842a1f61c8"
rev = "1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
[dependencies.zcash_primitives]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "6baf1ba63261dda09e5b1d4b476977842a1f61c8"
rev = "1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
features = [ "transparent-inputs" ]
[dependencies.zcash_proofs]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "6baf1ba63261dda09e5b1d4b476977842a1f61c8"
rev = "1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
[dependencies.zcash_params]
git = "https://github.com/hhanh00/zcash-params.git"
branch = "main"
rev = "f54214d0a6752188efd1404e39b10be58a27ea0f"
[dependencies.zcash_address]
git = "https://github.com/hhanh00/librustzcash.git"

View File

@ -2,7 +2,10 @@ 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,11 +1,11 @@
use crate::commitment::{CTree, Witness};
use crate::hash::{pedersen_hash, pedersen_hash_inner};
use ff::PrimeField;
use group::Curve;
use jubjub::{AffinePoint, ExtendedPoint};
use rayon::prelude::IntoParallelIterator;
use rayon::prelude::*;
use zcash_primitives::sapling::Node;
use jubjub::{ExtendedPoint, AffinePoint};
use group::Curve;
use ff::PrimeField;
#[inline(always)]
fn batch_node_combine1(depth: usize, left: &Node, right: &Node) -> ExtendedPoint {
@ -171,33 +171,35 @@ fn combine_level(commitments: &mut [Node], offset: Option<Node>, n: usize, depth
assert_eq!(n % 2, 0);
let nn = n / 2;
let next_level: Vec<_> =
if nn > 100 {
let hash_extended: Vec<_> = (0..nn)
.into_par_iter()
.map(|i| {
batch_node_combine1(
depth,
CTreeBuilder::get(commitments, 2 * i, &offset),
CTreeBuilder::get(commitments, 2 * i + 1, &offset),
)
})
.collect();
let mut hash_affine: Vec<AffinePoint> = vec![AffinePoint::identity(); nn];
ExtendedPoint::batch_normalize(&hash_extended, &mut hash_affine);
hash_affine.iter().map(|p| Node::new(p.get_u().to_repr())).collect()
} else {
(0..nn)
.into_par_iter()
.map(|i| {
node_combine(
depth,
CTreeBuilder::get(commitments, 2 * i, &offset),
CTreeBuilder::get(commitments, 2 * i + 1, &offset),
)
})
.collect()
};
let next_level: Vec<_> = if nn > 100 {
let hash_extended: Vec<_> = (0..nn)
.into_par_iter()
.map(|i| {
batch_node_combine1(
depth,
CTreeBuilder::get(commitments, 2 * i, &offset),
CTreeBuilder::get(commitments, 2 * i + 1, &offset),
)
})
.collect();
let mut hash_affine: Vec<AffinePoint> = vec![AffinePoint::identity(); nn];
ExtendedPoint::batch_normalize(&hash_extended, &mut hash_affine);
hash_affine
.iter()
.map(|p| Node::new(p.get_u().to_repr()))
.collect()
} else {
(0..nn)
.into_par_iter()
.map(|i| {
node_combine(
depth,
CTreeBuilder::get(commitments, 2 * i, &offset),
CTreeBuilder::get(commitments, 2 * i + 1, &offset),
)
})
.collect()
};
commitments[0..nn].copy_from_slice(&next_level);
nn

View File

@ -204,27 +204,6 @@ async fn get_tree_state(client: &mut CompactTxStreamerClient<Channel>, height: u
rep.tree
}
pub async fn send_transaction(
client: &mut CompactTxStreamerClient<Channel>,
raw_tx: &[u8],
height: u32,
) -> anyhow::Result<String> {
let raw_tx = RawTransaction {
data: raw_tx.to_vec(),
height: height as u64,
};
let rep = client
.send_transaction(Request::new(raw_tx))
.await?
.into_inner();
let code = rep.error_code;
if code == 0 {
Ok(rep.error_message)
} else {
Err(anyhow::anyhow!(rep.error_message))
}
}
/* Using the IncrementalWitness */
#[allow(dead_code)]
fn calculate_tree_state_v1(

View File

@ -1,7 +0,0 @@
use zcash_primitives::consensus::{BlockHeight, BranchId, Network};
pub const NETWORK: Network = Network::TestNetwork;
pub const TICKER: &str = "zcash";
pub fn get_branch(height: u32) -> BranchId {
BranchId::for_height(&NETWORK, BlockHeight::from_u32(height))
}

View File

@ -1,7 +0,0 @@
use zcash_primitives::consensus::{BranchId, Network};
pub const NETWORK: Network = Network::YCashMainNetwork;
pub const TICKER: &str = "ycash";
pub fn get_branch(_height: u32) -> BranchId {
BranchId::Ycash
}

View File

@ -1,7 +0,0 @@
use zcash_primitives::consensus::{BlockHeight, BranchId, Network};
pub const NETWORK: Network = Network::MainNetwork;
pub const TICKER: &str = "zcash";
pub fn get_branch(height: u32) -> BranchId {
BranchId::for_height(&NETWORK, BlockHeight::from_u32(height))
}

View File

@ -1,7 +0,0 @@
use zcash_primitives::consensus::{BlockHeight, BranchId, Network};
pub const NETWORK: Network = Network::TestNetwork;
pub const TICKER: &str = "zcash";
pub fn get_branch(height: u32) -> BranchId {
BranchId::for_height(&NETWORK, BlockHeight::from_u32(height))
}

View File

@ -1,7 +1,7 @@
use prost::bytes::{BufMut, Buf};
use prost::bytes::{Buf, BufMut};
use serde::{Deserialize, Serialize};
use zcash_primitives::memo::{Memo, MemoBytes};
use std::convert::TryFrom;
use zcash_primitives::memo::{Memo, MemoBytes};
const CONTACT_COOKIE: u32 = 0x434E5440;
@ -15,17 +15,20 @@ pub struct Contact {
pub fn serialize_contacts(contacts: &[Contact]) -> anyhow::Result<Vec<Memo>> {
let cs_bin = bincode::serialize(&contacts)?;
let chunks = cs_bin.chunks(500);
let memos: Vec<_> = chunks.enumerate().map(|(i, c)| {
let n = i as u8;
let mut bytes = [0u8; 511];
let mut bb: Vec<u8> = vec![];
bb.put_u32(CONTACT_COOKIE);
bb.put_u8(n);
bb.put_u16(c.len() as u16);
bb.put_slice(c);
bytes[0..bb.len()].copy_from_slice(&bb);
Memo::Arbitrary(Box::new(bytes))
}).collect();
let memos: Vec<_> = chunks
.enumerate()
.map(|(i, c)| {
let n = i as u8;
let mut bytes = [0u8; 511];
let mut bb: Vec<u8> = vec![];
bb.put_u32(CONTACT_COOKIE);
bb.put_u8(n);
bb.put_u16(c.len() as u16);
bb.put_slice(c);
bytes[0..bb.len()].copy_from_slice(&bb);
Memo::Arbitrary(Box::new(bytes))
})
.collect();
Ok(memos)
}
@ -41,7 +44,7 @@ impl ContactDecoder {
chunks.resize(n, vec![]);
ContactDecoder {
has_contacts: false,
chunks
chunks,
}
}
@ -58,7 +61,7 @@ impl ContactDecoder {
pub fn finalize(&self) -> anyhow::Result<Vec<Contact>> {
if !self.has_contacts {
return Ok(Vec::new())
return Ok(Vec::new());
}
let data: Vec<_> = self.chunks.iter().cloned().flatten().collect();
let contacts = bincode::deserialize::<Vec<Contact>>(&data)?;
@ -84,9 +87,9 @@ impl ContactDecoder {
#[cfg(test)]
mod tests {
use crate::{DbAdapter, LWD_URL, Wallet};
use crate::contact::{serialize_contacts, Contact};
use crate::db::DEFAULT_DB_PATH;
use crate::{DbAdapter, Wallet, LWD_URL};
#[test]
fn test_contacts() {
@ -94,7 +97,9 @@ mod tests {
let contact = Contact {
id: 0,
name: "hanh".to_string(),
address: "zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35".to_string(),
address:
"zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35"
.to_string(),
};
db.store_contact(&contact, true).unwrap();
}

121
src/db.rs
View File

@ -1,17 +1,17 @@
use crate::chain::{Nf, NfRef};
use crate::contact::Contact;
use crate::prices::Quote;
use crate::taddr::derive_tkeys;
use crate::transaction::TransactionInfo;
use crate::{CTree, Witness, NETWORK};
use chrono::NaiveDateTime;
use rusqlite::{params, Connection, OptionalExtension, NO_PARAMS, Transaction};
use rusqlite::{params, Connection, OptionalExtension, Transaction, NO_PARAMS};
use std::collections::HashMap;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk};
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
use crate::contact::Contact;
mod migration;
@ -33,6 +33,7 @@ pub struct ReceivedNote {
pub spent: Option<u32>,
}
#[derive(Clone)]
pub struct SpendableNote {
pub id: u32,
pub note: Note,
@ -86,10 +87,11 @@ impl DbAdapter {
ivk: &str,
address: &str,
) -> anyhow::Result<i32> {
let mut statement = self.connection.prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?;
if statement.exists(
params![ivk])? {
return Ok(-1)
let mut statement = self
.connection
.prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?;
if statement.exists(params![ivk])? {
return Ok(-1);
}
self.connection.execute(
"INSERT INTO accounts(name, seed, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5)
@ -197,7 +199,7 @@ impl DbAdapter {
height: u32,
timestamp: u32,
tx_index: u32,
db_tx: &Transaction
db_tx: &Transaction,
) -> anyhow::Result<u32> {
log::debug!("+transaction");
db_tx.execute(
@ -219,7 +221,7 @@ impl DbAdapter {
note: &ReceivedNote,
id_tx: u32,
position: usize,
db_tx: &Transaction
db_tx: &Transaction,
) -> anyhow::Result<u32> {
log::debug!("+received_note {}", id_tx);
db_tx.execute("INSERT INTO received_notes(account, tx, height, position, output_index, diversifier, value, rcm, nf, spent)
@ -475,7 +477,9 @@ impl DbAdapter {
log::debug!("+purge_old_witnesses");
let min_height: Option<u32> = self.connection.query_row(
"SELECT MAX(height) FROM sapling_witnesses WHERE height <= ?1",
params![height], |row| row.get(0))?;
params![height],
|row| row.get(0),
)?;
// Leave at least one sapling witness
if let Some(min_height) = min_height {
@ -498,8 +502,7 @@ impl DbAdapter {
VALUES (?1, ?2, ?3)",
params![&contact.name, &contact.address, dirty],
)?;
}
else {
} else {
self.connection.execute(
"INSERT INTO contacts(id, name, address, dirty)
VALUES (?1, ?2, ?3, ?4) ON CONFLICT (id) DO UPDATE SET
@ -511,16 +514,14 @@ impl DbAdapter {
}
pub fn get_unsaved_contacts(&self) -> anyhow::Result<Vec<Contact>> {
let mut statement = self.connection.prepare("SELECT id, name, address FROM contacts WHERE dirty = TRUE")?;
let mut statement = self
.connection
.prepare("SELECT id, name, address FROM contacts WHERE dirty = TRUE")?;
let rows = statement.query_map(NO_PARAMS, |row| {
let id: u32 = row.get(0)?;
let name: String = row.get(1)?;
let address: String = row.get(2)?;
let contact = Contact {
id,
name,
address,
};
let contact = Contact { id, name, address };
Ok(contact)
})?;
let mut contacts: Vec<Contact> = vec![];
@ -653,15 +654,18 @@ impl DbAdapter {
Ok(address)
}
pub fn get_tsk(&self, account: u32) -> anyhow::Result<String> {
let sk = self.connection.query_row(
"SELECT sk FROM taddrs WHERE account = ?1",
params![account],
|row| {
let address: String = row.get(0)?;
Ok(address)
},
)?;
pub fn get_tsk(&self, account: u32) -> anyhow::Result<Option<String>> {
let sk = self
.connection
.query_row(
"SELECT sk FROM taddrs WHERE account = ?1",
params![account],
|row| {
let address: String = row.get(0)?;
Ok(address)
},
)
.optional()?;
Ok(sk)
}
@ -728,21 +732,68 @@ impl DbAdapter {
Ok(quote)
}
pub fn store_share_secret(
&self,
account: u32,
secret: &str,
index: usize,
threshold: usize,
participants: usize,
) -> anyhow::Result<()> {
self.connection.execute(
"INSERT INTO secret_shares(account, secret, idx, threshold, participants) VALUES (?1, ?2, ?3, ?4, ?5) \
ON CONFLICT (account) DO UPDATE SET secret = excluded.secret, threshold = excluded.threshold, participants = excluded.participants",
params![account, &secret, index as u32, threshold as u32, participants as u32],
)?;
Ok(())
}
pub fn get_share_secret(&self, account: u32) -> anyhow::Result<String> {
let secret = self
.connection
.query_row(
"SELECT secret FROM secret_shares WHERE account = ?1",
params![account],
|row| {
let secret: String = row.get(0)?;
Ok(secret)
},
)
.optional()?;
Ok(secret.unwrap_or("".to_string()))
}
pub fn truncate_data(&self) -> anyhow::Result<()> {
self.connection.execute("DELETE FROM blocks", NO_PARAMS)?;
self.connection.execute("DELETE FROM contacts", NO_PARAMS)?;
self.connection.execute("DELETE FROM diversifiers", NO_PARAMS)?;
self.connection.execute("DELETE FROM historical_prices", NO_PARAMS)?;
self.connection.execute("DELETE FROM received_notes", NO_PARAMS)?;
self.connection.execute("DELETE FROM sapling_witnesses", NO_PARAMS)?;
self.connection.execute("DELETE FROM transactions", NO_PARAMS)?;
self.connection
.execute("DELETE FROM diversifiers", NO_PARAMS)?;
self.connection
.execute("DELETE FROM historical_prices", NO_PARAMS)?;
self.connection
.execute("DELETE FROM received_notes", NO_PARAMS)?;
self.connection
.execute("DELETE FROM sapling_witnesses", NO_PARAMS)?;
self.connection
.execute("DELETE FROM transactions", NO_PARAMS)?;
Ok(())
}
pub fn delete_account(&self, account: u32) -> anyhow::Result<()> {
self.connection.execute("DELETE FROM received_notes WHERE account = ?1", params![account])?;
self.connection.execute("DELETE FROM accounts WHERE id_account = ?1", params![account])?;
self.connection.execute("DELETE FROM taddrs WHERE account = ?1", params![account])?;
self.connection.execute(
"DELETE FROM received_notes WHERE account = ?1",
params![account],
)?;
self.connection.execute(
"DELETE FROM accounts WHERE id_account = ?1",
params![account],
)?;
self.connection
.execute("DELETE FROM taddrs WHERE account = ?1", params![account])?;
self.connection.execute(
"DELETE FROM secret_shares WHERE account = ?1",
params![account],
)?;
Ok(())
}
}
@ -774,7 +825,7 @@ mod tests {
},
id_tx,
5,
&db_tx
&db_tx,
)
.unwrap();
let witness = Witness {

View File

@ -136,17 +136,11 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
}
if version < 5 {
connection.execute(
"DELETE FROM historical_prices",
NO_PARAMS,
)?;
connection.execute("DELETE FROM historical_prices", NO_PARAMS)?;
}
if version < 6 {
connection.execute(
"DROP TABLE contacts",
NO_PARAMS,
)?;
connection.execute("DROP TABLE contacts", NO_PARAMS)?;
connection.execute(
"CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY,
@ -157,7 +151,19 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
)?;
}
update_schema_version(&connection, 6)?;
if version < 7 {
connection.execute(
"CREATE TABLE IF NOT EXISTS secret_shares (
account INTEGER PRIMARY KEY,
idx INTEGER NOT NULL,
participants INTEGER NOT NULL,
threshold INTEGER NOT NULL,
secret TEXT NOT NULL)",
NO_PARAMS,
)?;
}
update_schema_version(&connection, 7)?;
Ok(())
}

View File

@ -1,6 +1,6 @@
use ff::PrimeField;
use group::{Curve, GroupEncoding};
use jubjub::{Fr, SubgroupPoint, ExtendedPoint, ExtendedNielsPoint};
use jubjub::{ExtendedNielsPoint, ExtendedPoint, Fr, SubgroupPoint};
use lazy_static::lazy_static;
use std::io::Read;
use std::ops::AddAssign;
@ -20,7 +20,8 @@ fn read_generators_bin() -> Vec<ExtendedNielsPoint> {
for _k in 0..256 {
let mut bb = [0u8; 32];
generators_bin.read(&mut bb).unwrap();
let p = ExtendedPoint::from(SubgroupPoint::from_bytes_unchecked(&bb).unwrap()).to_niels();
let p = ExtendedPoint::from(SubgroupPoint::from_bytes_unchecked(&bb).unwrap())
.to_niels();
gens.push(p);
}
}
@ -51,10 +52,7 @@ type Hash = [u8; 32];
pub fn pedersen_hash(depth: u8, left: &Hash, right: &Hash) -> Hash {
let p = pedersen_hash_inner(depth, left, right);
let h = jubjub::ExtendedPoint::from(p)
.to_affine()
.get_u()
.to_repr();
let h = jubjub::ExtendedPoint::from(p).to_affine().get_u().to_repr();
h
}
@ -123,7 +121,11 @@ pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoin
result
}
fn generator_multiplication(acc: &Fr, gens: &[ExtendedNielsPoint], i_generator: u32) -> ExtendedPoint {
fn generator_multiplication(
acc: &Fr,
gens: &[ExtendedNielsPoint],
i_generator: u32,
) -> ExtendedPoint {
let acc = acc.to_repr();
let mut tmp = jubjub::ExtendedPoint::identity();

View File

@ -27,21 +27,21 @@ pub fn decode_key(key: &str) -> anyhow::Result<(Option<String>, Option<String>,
res
}
pub fn is_valid_key(key: &str) -> bool {
pub fn is_valid_key(key: &str) -> i8 {
if Mnemonic::from_phrase(&key, Language::English).is_ok() {
return true;
return 0;
}
if let Ok(Some(_)) =
decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &key)
{
return true;
return 1;
}
if let Ok(Some(_)) =
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &key)
{
return true;
return 2;
}
false
-1
}
pub fn derive_secret_key(mnemonic: &Mnemonic) -> anyhow::Result<(String, String, String)> {

View File

@ -1,14 +1,15 @@
// #![allow(dead_code)]
// #![allow(unused_imports)]
#[path = "generated/cash.z.wallet.sdk.rpc.rs"]
pub mod lw_rpc;
mod coin;
pub use coin::{get_branch, NETWORK, TICKER};
pub use zcash_params::coin::{get_branch, NETWORK, TICKER};
// Mainnet
// 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";
@ -16,15 +17,15 @@ pub use coin::{get_branch, NETWORK, TICKER};
// pub const LWD_URL: &str = "http://127.0.0.1:9067";
// YCash
pub const LWD_URL: &str = "https://lite.ycash.xyz:9067";
// pub const LWD_URL: &str = "https://lite.ycash.xyz:9067";
mod builder;
mod chain;
mod commitment;
mod contact;
mod db;
mod hash;
mod key;
mod ua;
mod mempool;
mod pay;
mod prices;
@ -32,9 +33,15 @@ mod print;
mod scan;
mod taddr;
mod transaction;
mod contact;
mod ua;
mod wallet;
pub fn hex_to_hash(hex: &str) -> anyhow::Result<[u8; 32]> {
let mut hash = [0u8; 32];
hex::decode_to_slice(hex, &mut hash)?;
Ok(hash)
}
pub use crate::builder::advance_tree;
pub use crate::chain::{
calculate_tree_state_v2, connect_lightwalletd, download_chain, get_latest_height, sync,
@ -47,8 +54,8 @@ pub use crate::key::{decode_key, is_valid_key};
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
pub use crate::lw_rpc::*;
pub use crate::mempool::MemPool;
pub use crate::pay::{broadcast_tx, sign_offline_tx, Tx};
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::wallet::{Wallet, WalletBalance};
pub use crate::ua::{get_sapling, get_ua};
pub use crate::wallet::{RecipientMemo, Wallet, WalletBalance};

View File

@ -1,33 +1,50 @@
use clap::Clap;
use clap::{App, Arg};
use std::fs::File;
use std::io::{Read, Write};
use sync::{decode_key, sign_offline_tx, Tx, NETWORK};
use sync::{decode_key, Tx, NETWORK};
use zcash_client_backend::encoding::decode_extended_spending_key;
use zcash_primitives::consensus::Parameters;
use zcash_proofs::prover::LocalTxProver;
#[derive(Clap, Debug)]
struct SignArgs {
tx_filename: String,
out_filename: String,
}
fn main() -> anyhow::Result<()> {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let key = dotenv::var("KEY").unwrap();
let (_seed, sk, _ivk, _address) = decode_key(&key)?;
let opts: SignArgs = SignArgs::parse();
let matches = App::new("Multisig CLI")
.version("1.0")
.arg(
Arg::with_name("tx_filename")
.short("tx")
.long("tx")
.takes_value(true),
)
.arg(
Arg::with_name("out_filename")
.short("o")
.long("out")
.takes_value(true),
)
.get_matches();
let tx_filename = matches.value_of("tx_filename").unwrap();
let out_filename = matches.value_of("out_filename").unwrap();
let sk = sk.unwrap();
let sk =
decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &sk)?.unwrap();
let file_name = opts.tx_filename;
let mut file = File::open(file_name)?;
let mut file = File::open(tx_filename)?;
let mut s = String::new();
file.read_to_string(&mut s).unwrap();
let tx: Tx = serde_json::from_str(&s)?;
let raw_tx = sign_offline_tx(&tx, &sk)?;
let prover = LocalTxProver::with_default_location()
.ok_or_else(|| anyhow::anyhow!("Cannot create prover. Missing zcash-params?"))?;
let raw_tx = tx.sign(None, &sk, &prover, |p| {
println!("Progress {}", p.cur());
})?;
let mut out_file = File::create(opts.out_filename)?;
let mut out_file = File::create(out_filename)?;
writeln!(out_file, "{}", hex::encode(&raw_tx))?;
Ok(())
}

View File

@ -2,7 +2,9 @@ use bip39::{Language, Mnemonic};
use rand::rngs::OsRng;
use rand::{thread_rng, RngCore};
use rusqlite::NO_PARAMS;
use sync::{pedersen_hash, print_witness2, ChainError, DbAdapter, Wallet, Witness, LWD_URL};
use sync::{
pedersen_hash, print_witness2, ChainError, DbAdapter, RecipientMemo, Wallet, Witness, LWD_URL,
};
use zcash_client_backend::data_api::wallet::ANCHOR_OFFSET;
use zcash_primitives::merkle_tree::Hashable;
use zcash_primitives::sapling::Node;
@ -20,14 +22,14 @@ async fn test() -> anyhow::Result<()> {
dotenv::dotenv().unwrap();
env_logger::init();
let seed = dotenv::var("SEED").unwrap();
let seed2 = dotenv::var("SEED2").unwrap();
let ivk = dotenv::var("IVK").unwrap();
let seed = dotenv::var("SEED3").unwrap();
// let seed2 = dotenv::var("SEED2").unwrap();
// let ivk = dotenv::var("IVK").unwrap();
let address = dotenv::var("ADDRESS").unwrap();
let progress = |height| {
log::info!("Height = {}", height);
};
let wallet = Wallet::new(DB_NAME, LWD_URL);
let mut wallet = Wallet::new(DB_NAME, LWD_URL);
wallet.new_account_with_key("main", &seed).unwrap();
// wallet.new_account_with_key("test", &seed2).unwrap();
// wallet.new_account_with_key("zecpages", &ivk).unwrap();
@ -56,11 +58,24 @@ async fn test() -> anyhow::Result<()> {
// .unwrap();
// println!("TXID = {}", tx_id);
let tx = wallet
.prepare_payment(1, &address, 50000, "test memo", 0, 2)
.await
.unwrap();
println!("TX = {}", tx);
// let last_height = wallet.get_latest_height().await.unwrap();
// let tx = wallet
// .build_sign_send_multi_payment(
// 1,
// last_height,
// &[RecipientMemo {
// address,
// amount: 1000,
// memo: Default::default(),
// max_amount_per_note: 0,
// }],
// false,
// ANCHOR_OFFSET,
// |_| {},
// )
// .await
// .unwrap();
// println!("TX = {}", tx);
Ok(())
}

View File

@ -1,96 +1,103 @@
use crate::db::SpendableNote;
use crate::wallet::RecipientMemo;
use crate::{connect_lightwalletd, get_latest_height, RawTransaction, NETWORK};
use crate::{
connect_lightwalletd, get_branch, get_latest_height, hex_to_hash, GetAddressUtxosReply,
RawTransaction, NETWORK,
};
use jubjub::Fr;
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
use secp256k1::SecretKey;
use serde::{Deserialize, Serialize};
use std::sync::mpsc;
use tonic::Request;
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, encode_extended_full_viewing_key,
decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key,
encode_payment_address,
};
use zcash_primitives::consensus::{BlockHeight, BranchId, Network, Parameters};
use zcash_primitives::memo::{MemoBytes, Memo};
use zcash_primitives::consensus::{BlockHeight, Parameters};
use zcash_primitives::legacy::Script;
use zcash_primitives::memo::{Memo, MemoBytes};
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::sapling::keys::OutgoingViewingKey;
use zcash_primitives::sapling::{Diversifier, Node, Rseed, PaymentAddress};
use zcash_primitives::transaction::builder::Builder;
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
use zcash_primitives::transaction::components::Amount;
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_proofs::prover::LocalTxProver;
#[derive(Serialize, Deserialize, Debug)]
pub struct Tx {
height: u32,
inputs: Vec<TxIn>,
outputs: Vec<TxOut>,
pub height: u32,
pub t_inputs: Vec<TTxIn>,
pub inputs: Vec<TxIn>,
pub outputs: Vec<TxOut>,
pub change: String,
pub ovk: String,
}
impl Tx {
pub fn new(height: u32) -> Self {
Tx {
height,
t_inputs: vec![],
inputs: vec![],
outputs: vec![],
change: "".to_string(),
ovk: "".to_string(),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TxIn {
diversifier: String,
fvk: String,
amount: u64,
rseed: String,
witness: String,
pub diversifier: String,
pub fvk: String,
pub amount: u64,
pub rseed: String,
pub witness: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct TTxIn {
pub op: String,
pub n: u32,
pub amount: u64,
pub script: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct TxOut {
addr: String,
amount: u64,
ovk: String,
memo: String,
pub addr: String,
pub amount: u64,
pub ovk: String,
pub memo: String,
}
pub trait TxBuilder {
fn add_input(
&mut self,
skey: Option<ExtendedSpendingKey>,
diversifier: &Diversifier,
fvk: &ExtendedFullViewingKey,
amount: Amount,
rseed: &[u8],
witness: &[u8],
) -> anyhow::Result<()>;
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()>;
fn add_z_output(
&mut self,
address: &str,
ovk: &OutgoingViewingKey,
amount: Amount,
memo: &Memo,
) -> anyhow::Result<()>;
fn set_change(&mut self, ovk: &OutgoingViewingKey, address: &PaymentAddress) -> anyhow::Result<()>;
}
pub struct ColdTxBuilder {
pub struct TxBuilder {
pub tx: Tx,
}
impl ColdTxBuilder {
impl TxBuilder {
pub fn new(height: u32) -> Self {
ColdTxBuilder {
TxBuilder {
tx: Tx::new(height),
}
}
}
impl TxBuilder for ColdTxBuilder {
fn add_input(
fn add_t_input(&mut self, op: OutPoint, amount: u64, script: &[u8]) {
self.tx.t_inputs.push(TTxIn {
op: hex::encode(op.hash()),
n: op.n(),
amount,
script: hex::encode(script),
});
}
fn add_z_input(
&mut self,
_skey: Option<ExtendedSpendingKey>,
diversifier: &Diversifier,
fvk: &ExtendedFullViewingKey,
amount: Amount,
@ -139,182 +146,232 @@ impl TxBuilder for ColdTxBuilder {
Ok(())
}
fn set_change(&mut self, _ovk: &OutgoingViewingKey, _address: &PaymentAddress) -> anyhow::Result<()> { Ok(()) }
}
impl TxBuilder for Builder<'_, Network, OsRng> {
fn add_input(
fn set_change(
&mut self,
skey: Option<ExtendedSpendingKey>,
diversifier: &Diversifier,
fvk: &ExtendedFullViewingKey,
amount: Amount,
rseed: &[u8],
witness: &[u8],
) -> anyhow::Result<()> {
let pa = fvk.fvk.vk.to_payment_address(diversifier.clone()).unwrap();
let mut rseed_bytes = [0u8; 32];
rseed_bytes.copy_from_slice(rseed);
let fr = Fr::from_bytes(&rseed_bytes).unwrap();
let note = pa
.create_note(u64::from(amount), Rseed::BeforeZip212(fr))
.unwrap();
let witness = IncrementalWitness::<Node>::read(&*witness).unwrap();
let merkle_path = witness.path().unwrap();
self.add_sapling_spend(skey.unwrap(), diversifier.clone(), note, merkle_path)?;
Ok(())
}
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()> {
let to_addr = RecipientAddress::decode(&NETWORK, address)
.ok_or(anyhow::anyhow!("Not a valid address"))?;
if let RecipientAddress::Transparent(t_address) = to_addr {
self.add_transparent_output(&t_address, amount)?;
}
Ok(())
}
fn add_z_output(
&mut self,
address: &str,
ovk: &OutgoingViewingKey,
amount: Amount,
memo: &Memo,
address: &PaymentAddress,
) -> anyhow::Result<()> {
let to_addr = RecipientAddress::decode(&NETWORK, address)
.ok_or(anyhow::anyhow!("Not a valid address"))?;
if let RecipientAddress::Shielded(pa) = to_addr {
let memo_bytes = MemoBytes::from(memo);
self.add_sapling_output(Some(ovk.clone()), pa.clone(), amount, Some(memo_bytes))?;
}
self.tx.change = encode_payment_address(NETWORK.hrp_sapling_payment_address(), address);
self.tx.ovk = hex::encode(ovk.0);
Ok(())
}
fn set_change(&mut self,
ovk: &OutgoingViewingKey,
address: &PaymentAddress) -> anyhow::Result<()> {
self.send_change_to(ovk.clone(), address.clone());
/// Add inputs to the transaction
///
/// Select utxos and shielded notes and add them to
/// the transaction
///
/// Returns an array of received note ids
pub fn select_inputs(
&mut self,
fvk: &ExtendedFullViewingKey,
notes: &[SpendableNote],
utxos: &[GetAddressUtxosReply],
target_amount: u64,
) -> anyhow::Result<Vec<u32>> {
let mut selected_notes: Vec<u32> = vec![];
let target_amount = Amount::from_u64(target_amount).unwrap();
let mut t_amount = Amount::zero();
// If we use the transparent address, we use all the utxos
if !utxos.is_empty() {
for utxo in utxos.iter() {
let mut tx_hash = [0u8; 32];
tx_hash.copy_from_slice(&utxo.txid);
let op = OutPoint::new(tx_hash, utxo.index as u32);
self.add_t_input(op, utxo.value_zat as u64, &utxo.script);
t_amount += Amount::from_i64(utxo.value_zat).unwrap();
}
}
let target_amount_with_fee = target_amount + DEFAULT_FEE;
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;
// Pick spendable notes until we exceed the target_amount_with_fee or we ran out of notes
let mut notes = notes.to_vec();
notes.shuffle(&mut OsRng);
for n in notes.iter() {
if amount.is_positive() {
let a = amount.min(
Amount::from_u64(n.note.value)
.map_err(|_| anyhow::anyhow!("Invalid amount"))?,
);
amount -= a;
let mut witness_bytes: Vec<u8> = vec![];
n.witness.write(&mut witness_bytes)?;
if let Rseed::BeforeZip212(rseed) = n.note.rseed {
// rseed are stored as pre-zip212
self.add_z_input(
&n.diversifier,
fvk,
Amount::from_u64(n.note.value).unwrap(),
&rseed.to_bytes(),
&witness_bytes,
)?;
selected_notes.push(n.id);
}
}
}
if amount.is_positive() {
log::info!("Not enough balance");
anyhow::bail!(
"Not enough balance, need {} zats, missing {} zats",
u64::from(target_amount_with_fee),
u64::from(amount)
);
}
}
Ok(selected_notes)
}
/// Add outputs
///
/// Expand the recipients if their amount exceeds the max amount per note
/// Set the change
pub fn select_outputs(
&mut self,
fvk: &ExtendedFullViewingKey,
recipients: &[RecipientMemo],
) -> anyhow::Result<()> {
let ovk = &fvk.fvk.ovk;
let (_, change) = fvk.default_address().unwrap();
self.set_change(&ovk, &change)?;
for r in recipients.iter() {
let to_addr = RecipientAddress::decode(&NETWORK, &r.address)
.ok_or(anyhow::anyhow!("Invalid address"))?;
let memo = &r.memo;
let amount = Amount::from_u64(r.amount).unwrap();
let max_amount_per_note = r.max_amount_per_note;
let max_amount_per_note = if max_amount_per_note != 0 {
Amount::from_u64(max_amount_per_note).unwrap()
} else {
Amount::from_i64(MAX_MONEY).unwrap()
};
let mut remaining_amount = amount;
while remaining_amount.is_positive() {
let note_amount = remaining_amount.min(max_amount_per_note);
remaining_amount -= note_amount;
match &to_addr {
RecipientAddress::Shielded(_pa) => {
log::info!("Sapling output: {}", r.amount);
self.add_z_output(&r.address, ovk, note_amount, &memo)
}
RecipientAddress::Transparent(_address) => {
self.add_t_output(&r.address, note_amount)
}
}?;
}
}
Ok(())
}
}
pub fn prepare_tx<B: TxBuilder>(
builder: &mut B,
skey: Option<ExtendedSpendingKey>,
notes: &[SpendableNote],
target_amount: Option<Amount>,
fvk: &ExtendedFullViewingKey,
recipients: &[RecipientMemo],
) -> anyhow::Result<Vec<u32>> {
let mut selected_notes: Vec<u32> = vec![];
if let Some(a) = target_amount {
let mut amount = a + DEFAULT_FEE;
let target_amount_with_fee = amount;
for n in notes.iter() {
if amount.is_positive() {
let a = amount.min(
Amount::from_u64(n.note.value).map_err(|_| anyhow::anyhow!("Invalid amount"))?,
);
amount -= a;
let mut witness_bytes: Vec<u8> = vec![];
n.witness.write(&mut witness_bytes)?;
if let Rseed::BeforeZip212(rseed) = n.note.rseed {
// rseed are stored as pre-zip212
builder.add_input(
skey.clone(),
&n.diversifier,
fvk,
Amount::from_u64(n.note.value).unwrap(),
&rseed.to_bytes(),
&witness_bytes,
)?;
selected_notes.push(n.id);
impl Tx {
/// Sign the transaction with the transparent and shielded secret keys
///
/// Returns the raw transaction bytes
pub fn sign(
&self,
tsk: Option<SecretKey>,
zsk: &ExtendedSpendingKey,
prover: &impl TxProver<OsRng>,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<Vec<u8>> {
let last_height = BlockHeight::from_u32(self.height as u32);
let mut builder = Builder::new(NETWORK, last_height);
let ovk = hex_to_hash(&self.ovk)?;
builder.send_change_to(
OutgoingViewingKey(ovk),
decode_payment_address(NETWORK.hrp_sapling_payment_address(), &self.change)
.unwrap()
.unwrap(),
);
if let Some(tsk) = tsk {
for txin in self.t_inputs.iter() {
let mut txid = [0u8; 32];
hex::decode_to_slice(&txin.op, &mut txid)?;
builder.add_transparent_input(
tsk,
OutPoint::new(txid, txin.n),
ZTxOut {
value: Amount::from_u64(txin.amount).unwrap(),
script_pubkey: Script(hex::decode(&txin.script).unwrap()),
},
)?;
}
} else if !self.t_inputs.is_empty() {
anyhow::bail!("Missing secret key of transparent account");
}
for txin in self.inputs.iter() {
let mut diversifier = [0u8; 11];
hex::decode_to_slice(&txin.diversifier, &mut diversifier)?;
let diversifier = Diversifier(diversifier);
let fvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&txin.fvk,
)?
.unwrap();
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
let mut rseed_bytes = [0u8; 32];
hex::decode_to_slice(&txin.rseed, &mut rseed_bytes)?;
let rseed = Fr::from_bytes(&rseed_bytes).unwrap();
let note = pa
.create_note(txin.amount, Rseed::BeforeZip212(rseed))
.unwrap();
let w = hex::decode(&txin.witness)?;
let witness = IncrementalWitness::<Node>::read(&*w)?;
let merkle_path = witness.path().unwrap();
builder.add_sapling_spend(zsk.clone(), diversifier, note, merkle_path)?;
}
for txout in self.outputs.iter() {
let recipient = RecipientAddress::decode(&NETWORK, &txout.addr).unwrap();
let amount = Amount::from_u64(txout.amount).unwrap();
match recipient {
RecipientAddress::Transparent(ta) => {
builder.add_transparent_output(&ta, amount)?;
}
RecipientAddress::Shielded(pa) => {
let mut ovk = [0u8; 32];
hex::decode_to_slice(&txout.ovk, &mut ovk)?;
let ovk = OutgoingViewingKey(ovk);
let mut memo = vec![0; 512];
let m = hex::decode(&txout.memo)?;
memo[..m.len()].copy_from_slice(&m);
let memo = MemoBytes::from_bytes(&memo)?;
builder.add_sapling_output(Some(ovk), pa, amount, Some(memo))?;
}
}
}
if amount.is_positive() {
log::info!("Not enough balance");
anyhow::bail!(
"Not enough balance, need {} zats, missing {} zats",
u64::from(target_amount_with_fee),
u64::from(amount)
);
}
}
log::info!("Preparing tx");
let ovk = &fvk.fvk.ovk;
let (_, change) = fvk.default_address().unwrap();
builder.set_change(&ovk, &change)?;
let (progress_tx, progress_rx) = mpsc::channel::<Progress>();
for r in recipients.iter() {
let to_addr = RecipientAddress::decode(&NETWORK, &r.address)
.ok_or(anyhow::anyhow!("Invalid address"))?;
let amount = Amount::from_u64(r.amount).unwrap();
match &to_addr {
RecipientAddress::Shielded(_pa) => {
log::info!("Sapling output: {}", r.amount);
builder.add_z_output(&r.address, ovk, amount, &r.memo)
builder.with_progress_notifier(progress_tx);
tokio::spawn(async move {
while let Ok(progress) = progress_rx.recv() {
log::info!("Progress: {}", progress.cur());
progress_callback(progress);
}
RecipientAddress::Transparent(_address) => builder.add_t_output(&r.address, amount),
}?;
});
let consensus_branch_id = get_branch(u32::from(last_height));
let (tx, _) = builder.build(consensus_branch_id, prover)?;
let mut raw_tx = vec![];
tx.write(&mut raw_tx)?;
Ok(raw_tx)
}
Ok(selected_notes)
}
pub fn sign_offline_tx(tx: &Tx, sk: &ExtendedSpendingKey) -> anyhow::Result<Vec<u8>> {
let last_height = BlockHeight::from_u32(tx.height as u32);
let mut builder = Builder::new(NETWORK, last_height);
for txin in tx.inputs.iter() {
let mut diversifier = [0u8; 11];
hex::decode_to_slice(&txin.diversifier, &mut diversifier)?;
let diversifier = Diversifier(diversifier);
let fvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&txin.fvk,
)?
.unwrap();
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
let mut rseed_bytes = [0u8; 32];
hex::decode_to_slice(&txin.rseed, &mut rseed_bytes)?;
let rseed = Fr::from_bytes(&rseed_bytes).unwrap();
let note = pa
.create_note(txin.amount, Rseed::BeforeZip212(rseed))
.unwrap();
let w = hex::decode(&txin.witness)?;
let witness = IncrementalWitness::<Node>::read(&*w)?;
let merkle_path = witness.path().unwrap();
builder.add_sapling_spend(sk.clone(), diversifier, note, merkle_path)?;
}
for txout in tx.outputs.iter() {
let recipient = RecipientAddress::decode(&NETWORK, &txout.addr).unwrap();
let amount = Amount::from_u64(txout.amount).unwrap();
match recipient {
RecipientAddress::Transparent(ta) => {
builder.add_transparent_output(&ta, amount)?;
}
RecipientAddress::Shielded(pa) => {
let mut ovk = [0u8; 32];
hex::decode_to_slice(&txout.ovk, &mut ovk)?;
let ovk = OutgoingViewingKey(ovk);
let mut memo = vec![0; 512];
let m = hex::decode(&txout.memo)?;
memo[..m.len()].copy_from_slice(&m);
let memo = MemoBytes::from_bytes(&memo)?;
builder.add_sapling_output(Some(ovk), pa, amount, Some(memo))?;
}
}
}
let prover = LocalTxProver::with_default_location().unwrap();
let consensus_branch_id = BranchId::for_height(&NETWORK, last_height);
let (tx, _) = builder.build(consensus_branch_id, &prover)?;
let mut raw_tx = vec![];
tx.write(&mut raw_tx)?;
Ok(raw_tx)
}
pub async fn broadcast_tx(tx: &[u8], ld_url: &str) -> anyhow::Result<String> {
@ -328,5 +385,10 @@ pub async fn broadcast_tx(tx: &[u8], ld_url: &str) -> anyhow::Result<String> {
.send_transaction(Request::new(raw_tx))
.await?
.into_inner();
Ok(rep.error_message)
let code = rep.error_code;
if code == 0 {
Ok(rep.error_message)
} else {
Err(anyhow::anyhow!(rep.error_message))
}
}

View File

@ -9,6 +9,7 @@ use crate::{
};
use ff::PrimeField;
use log::{debug, info};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::ops::Range;
use std::panic;
@ -19,7 +20,6 @@ use tokio::sync::Mutex;
use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
use zcash_primitives::sapling::Node;
use zcash_primitives::zip32::ExtendedFullViewingKey;
use std::cmp::Ordering;
pub async fn scan_all(fvks: &[ExtendedFullViewingKey]) -> anyhow::Result<()> {
let fvks: HashMap<_, _> = fvks
@ -153,7 +153,8 @@ pub async fn sync_async(
let mut new_ids_tx: HashMap<u32, TxIdHeight> = HashMap::new();
let mut witnesses: Vec<Witness> = vec![];
{ // db tx scope
{
// db tx scope
let db_tx = db.begin_transaction()?;
let dec_blocks = decrypter.decrypt_blocks(&blocks.0);
log::info!("Dec start : {}", dec_blocks[0].height);
@ -187,11 +188,14 @@ pub async fn sync_async(
n.tx_index as u32,
&db_tx,
)?;
new_ids_tx.insert(id_tx, TxIdHeight {
new_ids_tx.insert(
id_tx,
height: n.height,
index: n.tx_index as u32,
});
TxIdHeight {
id_tx,
height: n.height,
index: n.tx_index as u32,
},
);
let id_note = DbAdapter::store_received_note(
&ReceivedNote {
account: n.account,
@ -205,7 +209,7 @@ pub async fn sync_async(
},
id_tx,
n.position_in_block,
&db_tx
&db_tx,
)?;
DbAdapter::add_value(id_tx, note.value as i64, &db_tx)?;
nfs.insert(
@ -227,7 +231,8 @@ pub async fn sync_async(
nf.copy_from_slice(&cs.nf);
let nf = Nf(nf);
if my_nfs.contains(&nf) {
let (account, note_value) = DbAdapter::get_received_note_value(&nf, &db_tx)?;
let (account, note_value) =
DbAdapter::get_received_note_value(&nf, &db_tx)?;
let txid = &*tx.hash;
let id_tx = DbAdapter::store_transaction(
txid,
@ -235,13 +240,16 @@ pub async fn sync_async(
b.height,
b.compact_block.time,
tx_index as u32,
&db_tx
&db_tx,
)?;
new_ids_tx.insert(id_tx, TxIdHeight {
new_ids_tx.insert(
id_tx,
height: b.height,
index: tx_index as u32,
});
TxIdHeight {
id_tx,
height: b.height,
index: tx_index as u32,
},
);
DbAdapter::add_value(id_tx, -(note_value as i64), &db_tx)?;
}
}
@ -286,7 +294,7 @@ pub async fn sync_async(
let start = Instant::now();
if get_tx && !new_ids_tx.is_empty() {
let mut ids: Vec<_> = new_ids_tx.into_iter().map(|(_,v)| v).collect();
let mut ids: Vec<_> = new_ids_tx.into_iter().map(|(_, v)| v).collect();
ids.sort_by(|a, b| {
let c = a.height.cmp(&b.height);
if c == Ordering::Equal {
@ -295,8 +303,7 @@ pub async fn sync_async(
return c;
});
let ids: Vec<_> = ids.into_iter().map(|e| e.id_tx).collect();
retrieve_tx_info(&mut client, &db_path2, &ids)
.await?;
retrieve_tx_info(&mut client, &db_path2, &ids).await?;
}
log::info!("Transaction Details : {}", start.elapsed().as_millis());

View File

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

View File

@ -1,3 +1,4 @@
use crate::contact::{Contact, ContactDecoder};
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter, NETWORK};
use futures::StreamExt;
use std::collections::HashMap;
@ -16,7 +17,6 @@ use zcash_primitives::sapling::note_encryption::{
};
use zcash_primitives::transaction::Transaction;
use zcash_primitives::zip32::ExtendedFullViewingKey;
use crate::contact::{ContactDecoder, Contact};
#[derive(Debug)]
pub struct TransactionInfo {
@ -78,7 +78,11 @@ pub async fn decode_transaction(
for output in tx.vout.iter() {
if let Some(taddr) = output.script_pubkey.address() {
address = encode_transparent_address(&NETWORK.b58_pubkey_address_prefix(), &NETWORK.b58_script_address_prefix(), &taddr);
address = encode_transparent_address(
&NETWORK.b58_pubkey_address_prefix(),
&NETWORK.b58_script_address_prefix(),
&taddr,
);
}
}
@ -107,13 +111,12 @@ pub async fn decode_transaction(
let fee = u64::from(tx.value_balance);
let memo =
match tx_memo {
Memo::Empty => "".to_string(),
Memo::Text(text) => text.to_string(),
Memo::Future(_) => "Unrecognized".to_string(),
Memo::Arbitrary(_) => "Unrecognized".to_string(),
};
let memo = match tx_memo {
Memo::Empty => "".to_string(),
Memo::Text(text) => text.to_string(),
Memo::Future(_) => "Unrecognized".to_string(),
Memo::Arbitrary(_) => "Unrecognized".to_string(),
};
let contacts = contact_decoder.finalize()?;
let tx_info = TransactionInfo {
height: u32::from(height),

View File

@ -1,6 +1,6 @@
use zcash_address::unified::{Receiver, Address};
use zcash_address::{FromAddress, Network, UnsupportedAddress, ZcashAddress, ToAddress};
use std::convert::TryFrom;
use zcash_address::unified::{Address, Receiver};
use zcash_address::{FromAddress, Network, ToAddress, UnsupportedAddress, ZcashAddress};
#[derive(Debug, Clone)]
pub struct MyReceiver {
@ -10,15 +10,21 @@ pub struct MyReceiver {
impl FromAddress for MyReceiver {
fn from_sapling(net: Network, data: [u8; 43]) -> Result<Self, UnsupportedAddress> {
Ok(MyReceiver { net: net, receiver: Receiver::Sapling(data) })
Ok(MyReceiver {
net: net,
receiver: Receiver::Sapling(data),
})
}
fn from_unified(net: Network, data: Address) -> Result<Self, UnsupportedAddress> {
for r in data.receivers().iter() {
match r {
Receiver::Sapling(data) => {
return Ok(MyReceiver { net: net, receiver: Receiver::Sapling(data.clone()) });
},
return Ok(MyReceiver {
net: net,
receiver: Receiver::Sapling(data.clone()),
});
}
_ => (),
}
}
@ -26,16 +32,20 @@ impl FromAddress for MyReceiver {
}
fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Result<Self, UnsupportedAddress> {
Ok(MyReceiver { net: net, receiver: Receiver::P2pkh(data) })
Ok(MyReceiver {
net: net,
receiver: Receiver::P2pkh(data),
})
}
}
pub fn get_ua(sapling_addr: &str, transparent_addr: &str) -> anyhow::Result<ZcashAddress> {
let sapling_addr = ZcashAddress::try_from_encoded(sapling_addr)?;
let transparent_addr = ZcashAddress::try_from_encoded(transparent_addr)?;
let receivers: Vec<_> = vec![sapling_addr, transparent_addr].iter().map(|r| {
r.clone().convert::<MyReceiver>().unwrap()
}).collect();
let receivers: Vec<_> = vec![sapling_addr, transparent_addr]
.iter()
.map(|r| r.clone().convert::<MyReceiver>().unwrap())
.collect();
let net = receivers.first().unwrap().net.clone();
let receivers: Vec<_> = receivers.iter().map(|r| r.receiver.clone()).collect();
let ua: Address = Address::try_from(receivers)?;
@ -54,11 +64,14 @@ pub fn get_sapling(ua_addr: &str) -> anyhow::Result<ZcashAddress> {
#[cfg(test)]
mod tests {
use crate::ua::{get_ua, get_sapling};
use crate::ua::{get_sapling, get_ua};
#[test]
fn test_ua() -> anyhow::Result<()> {
let ua = get_ua("zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35", "t1UWSSWaojmV5dgDhrSfZC6MAfCwVQ9LLoo")?;
let ua = get_ua(
"zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35",
"t1UWSSWaojmV5dgDhrSfZC6MAfCwVQ9LLoo",
)?;
let ua_str = ua.to_string();
println!("{}", ua);
let za = get_sapling(&ua_str)?;

View File

@ -1,41 +1,39 @@
use crate::chain::send_transaction;
use crate::db::SpendableNote;
use crate::contact::{serialize_contacts, Contact};
use crate::key::{decode_key, is_valid_key};
use crate::pay::prepare_tx;
use crate::pay::{ColdTxBuilder, Tx};
use crate::pay::Tx;
use crate::pay::TxBuilder;
use crate::prices::fetch_historical_prices;
use crate::scan::ProgressCallback;
use crate::taddr::{get_taddr_balance, shield_taddr, add_utxos};
use crate::taddr::{get_taddr_balance, get_utxos};
use crate::{
connect_lightwalletd, get_branch, get_latest_height, BlockId, CTree, DbAdapter, NETWORK,
broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree,
DbAdapter, NETWORK,
};
use bip39::{Language, Mnemonic};
use rand::prelude::SliceRandom;
use lazycell::AtomicLazyCell;
use rand::rngs::OsRng;
use rand::RngCore;
use secp256k1::SecretKey;
use serde::Deserialize;
use std::sync::{mpsc, Arc};
use serde::Serialize;
use std::convert::TryFrom;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::Mutex;
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_params::{OUTPUT_PARAMS, SPEND_PARAMS};
use zcash_primitives::consensus::{BlockHeight, Parameters};
use zcash_primitives::transaction::builder::{Builder, Progress};
use zcash_primitives::transaction::components::amount::{MAX_MONEY, DEFAULT_FEE};
use zcash_primitives::transaction::components::Amount;
use zcash_primitives::zip32::ExtendedFullViewingKey;
use zcash_proofs::prover::LocalTxProver;
use zcash_primitives::memo::Memo;
use std::str::FromStr;
use crate::contact::{Contact, serialize_contacts};
use lazycell::AtomicLazyCell;
use zcash_client_backend::zip321::{Payment, TransactionRequest};
use crate::coin::TICKER;
use std::convert::TryFrom;
use serde::Serialize;
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
use zcash_primitives::consensus::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_multisig::{SecretShare, split_account};
use zcash_params::coin::TICKER;
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
@ -63,17 +61,24 @@ impl Default for WalletBalance {
}
}
// to_address: &str,
// amount: u64,
// memo: &str,
// max_amount_per_note: u64,
#[derive(Deserialize)]
pub struct Recipient {
pub address: String,
pub amount: u64,
pub memo: String,
pub max_amount_per_note: u64,
}
pub struct RecipientMemo {
pub address: String,
pub amount: u64,
pub memo: Memo,
pub max_amount_per_note: u64,
}
impl From<&Recipient> for RecipientMemo {
@ -82,6 +87,7 @@ impl From<&Recipient> for RecipientMemo {
address: r.address.clone(),
amount: r.amount,
memo: Memo::from_str(&r.memo).unwrap(),
max_amount_per_note: r.max_amount_per_note,
}
}
}
@ -98,7 +104,7 @@ impl Wallet {
}
}
pub fn valid_key(key: &str) -> bool {
pub fn valid_key(key: &str) -> i8 {
is_valid_key(key)
}
@ -227,162 +233,128 @@ impl Wallet {
self.db.trim_to_height(height)
}
pub async fn send_multi_payment(
&mut self,
account: u32,
recipients_json: &str,
anchor_offset: u32,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<String> {
let recipients: Vec<Recipient> = serde_json::from_str(recipients_json)?;
let recipients: Vec<_> = recipients.iter().map(|r| RecipientMemo::from(r)).collect();
self._send_payment(account, &recipients, anchor_offset, false, progress_callback)
.await
}
pub async fn prepare_payment(
async fn prepare_multi_payment(
&self,
account: u32,
to_address: &str,
amount: u64,
memo: &str,
max_amount_per_note: u64,
anchor_offset: u32,
) -> anyhow::Result<String> {
let last_height = self.get_latest_height().await?;
let recipients = Self::_build_recipients(to_address, amount, max_amount_per_note, memo)?;
let tx = self._prepare_payment(account, amount, last_height, &recipients, anchor_offset)?;
let tx_str = serde_json::to_string(&tx)?;
Ok(tx_str)
}
fn _prepare_payment(
&self,
account: u32,
amount: u64,
last_height: u32,
recipients: &[RecipientMemo],
anchor_offset: u32,
) -> anyhow::Result<Tx> {
let amount = Amount::from_u64(amount).unwrap();
let ivk = self.db.get_ivk(account)?;
let extfvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&ivk,
)?
.unwrap();
let notes = self.get_spendable_notes(account, &extfvk, last_height, anchor_offset)?;
let mut builder = ColdTxBuilder::new(last_height);
prepare_tx(&mut builder, None, &notes, Some(amount), &extfvk, recipients)?;
Ok(builder.tx)
}
pub async fn send_payment(
&mut self,
account: u32,
to_address: &str,
amount: u64,
memo: &str,
max_amount_per_note: u64,
anchor_offset: u32,
use_transparent: bool,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<String> {
let recipients = Self::_build_recipients(to_address, amount, max_amount_per_note, memo)?;
self._send_payment(account, &recipients, anchor_offset, use_transparent, progress_callback)
.await
}
async fn _send_payment(
&mut self,
account: u32,
recipients: &[RecipientMemo],
anchor_offset: u32,
use_transparent: bool,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<String> {
let mut client = connect_lightwalletd(&self.ld_url).await?;
let mut t_amount = Amount::zero();
if use_transparent {
let t_address = self.db.get_taddr(account)?;
if let Some(t_address) = t_address {
t_amount = Amount::from_u64(get_taddr_balance(&mut client, &t_address).await?).unwrap();
}
}
) -> anyhow::Result<(Tx, Vec<u32>)> {
let mut tx_builder = TxBuilder::new(last_height);
let secret_key = self.db.get_sk(account)?;
let _target_amount = Amount::from_u64(recipients.iter().map(|r| r.amount).sum()).unwrap();
let target_amount =
if _target_amount + DEFAULT_FEE > t_amount {
Some(_target_amount - t_amount)
}
else {
None
}; // DEFAULT_FEE is not part of target_amount
let skey =
decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &secret_key)?
let fvk = self.db.get_ivk(account)?;
let fvk =
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
.unwrap()
.unwrap();
let extfvk = ExtendedFullViewingKey::from(&skey);
let last_height = self.get_latest_height().await?;
let notes = self.get_spendable_notes(account, &extfvk, last_height, anchor_offset)?;
log::info!("Spendable notes = {}", notes.len());
let utxos = if use_transparent {
let mut client = connect_lightwalletd(&self.ld_url).await?;
get_utxos(&mut client, &self.db, account).await?
} else {
vec![]
};
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
log::info!("Preparing tx");
let selected_notes = prepare_tx(
&mut builder,
Some(skey.clone()),
&notes,
target_amount,
&extfvk,
recipients,
)?;
let (progress_tx, progress_rx) = mpsc::channel::<Progress>();
builder.with_progress_notifier(progress_tx);
tokio::spawn(async move {
while let Ok(progress) = progress_rx.recv() {
log::info!("Progress: {}", progress.cur());
progress_callback(progress);
}
});
if use_transparent {
let t_address = self.db.get_taddr(account)?.unwrap();
add_utxos(&mut builder,
&mut client,
&self.db,
account,
&t_address).await?;
}
let target_amount: u64 = recipients.iter().map(|r| r.amount).sum();
let anchor_height = last_height.saturating_sub(anchor_offset);
let spendable_notes = self.db.get_spendable_notes(account, anchor_height, &fvk)?;
let note_ids = tx_builder.select_inputs(&fvk, &spendable_notes, &utxos, target_amount)?;
tx_builder.select_outputs(&fvk, recipients)?;
Ok((tx_builder.tx, note_ids))
}
fn sign(
&mut self,
tx: &Tx,
account: u32,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<Vec<u8>> {
self._ensure_prover()?;
let prover = self.prover.borrow().unwrap();
let consensus_branch_id = get_branch(last_height);
let (tx, _) = builder.build(consensus_branch_id, prover)?;
log::info!("Tx built");
let mut raw_tx: Vec<u8> = vec![];
tx.write(&mut raw_tx)?;
let mut client = connect_lightwalletd(&self.ld_url).await?;
let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?;
log::info!("Tx ID = {}", tx_id);
let zsk = self.db.get_sk(account)?;
let tsk = self
.db
.get_tsk(account)?
.map(|tsk| SecretKey::from_str(&tsk).unwrap());
let extsk = decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &zsk)
.unwrap()
.unwrap();
let prover = self
.prover
.borrow()
.ok_or_else(|| anyhow::anyhow!("Prover not initialized"))?;
let raw_tx = tx.sign(tsk, &extsk, prover, progress_callback)?;
Ok(raw_tx)
}
fn mark_spend(&mut self, selected_notes: &[u32]) -> anyhow::Result<()> {
let db_tx = self.db.begin_transaction()?;
for id_note in selected_notes.iter() {
DbAdapter::mark_spent(*id_note, 0, &db_tx)?;
}
db_tx.commit()?;
Ok(())
}
/// Build a multi payment for offline signing
pub async fn build_only_multi_payment(
&mut self,
account: u32,
last_height: u32,
recipients: &[RecipientMemo],
use_transparent: bool,
anchor_offset: u32,
) -> anyhow::Result<String> {
let (tx, _) = self
.prepare_multi_payment(
account,
last_height,
recipients,
use_transparent,
anchor_offset,
)
.await?;
let tx_str = serde_json::to_string(&tx)?;
Ok(tx_str)
}
/// Build, sign and broadcast a multi payment
pub async fn build_sign_send_multi_payment(
&mut self,
account: u32,
last_height: u32,
recipients: &[RecipientMemo],
use_transparent: bool,
anchor_offset: u32,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<String> {
let (tx, note_ids) = self
.prepare_multi_payment(
account,
last_height,
recipients,
use_transparent,
anchor_offset,
)
.await?;
let raw_tx = self.sign(&tx, account, progress_callback)?;
let tx_id = broadcast_tx(&raw_tx, &self.ld_url).await?;
self.mark_spend(&note_ids)?;
Ok(tx_id)
}
pub async fn shield_taddr(&mut self, account: u32, last_height: u32) -> anyhow::Result<String> {
let tx_id = self
.build_sign_send_multi_payment(account, last_height, &[], true, 0, |_| {})
.await?;
Ok(tx_id)
}
fn _ensure_prover(&mut self) -> anyhow::Result<()> {
if !self.prover.filled() {
let prover = LocalTxProver::from_bytes(SPEND_PARAMS, OUTPUT_PARAMS);
self.prover.fill(prover).map_err(|_| anyhow::anyhow!("dup prover"))?;
self.prover
.fill(prover)
.map_err(|_| anyhow::anyhow!("dup prover"))?;
}
Ok(())
}
@ -418,13 +390,13 @@ impl Wallet {
Ok(balance)
}
pub async fn shield_taddr(&mut self, account: u32) -> anyhow::Result<String> {
self._ensure_prover()?;
let prover = self.prover.borrow().unwrap();
shield_taddr(&self.db, account, prover, &self.ld_url).await
}
pub fn store_contact(&self, id: u32, name: &str, address: &str, dirty: bool) -> anyhow::Result<()> {
pub fn store_contact(
&self,
id: u32,
name: &str,
address: &str,
dirty: bool,
) -> anyhow::Result<()> {
let contact = Contact {
id,
name: name.to_string(),
@ -434,43 +406,49 @@ impl Wallet {
Ok(())
}
pub async fn commit_unsaved_contacts(&mut self, account: u32, anchor_offset: u32) -> anyhow::Result<String> {
pub async fn commit_unsaved_contacts(
&mut self,
account: u32,
anchor_offset: u32,
) -> anyhow::Result<String> {
let contacts = self.db.get_unsaved_contacts()?;
let memos = serialize_contacts(&contacts)?;
let tx_id = self.save_contacts_tx(&memos, account, anchor_offset).await.unwrap();
let tx_id = self
.save_contacts_tx(&memos, account, anchor_offset)
.await
.unwrap();
Ok(tx_id)
}
pub async fn save_contacts_tx(&mut self, memos: &[Memo], account: u32, anchor_offset: u32) -> anyhow::Result<String> {
pub async fn save_contacts_tx(
&mut self,
memos: &[Memo],
account: u32,
anchor_offset: u32,
) -> anyhow::Result<String> {
let mut client = connect_lightwalletd(&self.ld_url).await?;
let last_height = get_latest_height(&mut client).await?;
let secret_key = self.db.get_sk(account)?;
let address = self.db.get_address(account)?;
let skey = decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &secret_key)?.unwrap();
let extfvk = ExtendedFullViewingKey::from(&skey);
let notes = self.get_spendable_notes(account, &extfvk, last_height, anchor_offset)?;
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
let recipients: Vec<_> = memos.iter().map(|m| {
RecipientMemo {
let recipients: Vec<_> = memos
.iter()
.map(|m| RecipientMemo {
address: address.clone(),
amount: 0,
memo: m.clone(),
}
}).collect();
prepare_tx(&mut builder, Some(skey), &notes, Some(Amount::zero()), &extfvk, &recipients)?;
max_amount_per_note: 0,
})
.collect();
self._ensure_prover()?;
let prover = self.prover.borrow().unwrap();
let consensus_branch_id = get_branch(last_height);
let (tx, _) = builder.build(consensus_branch_id, prover)?;
let mut raw_tx: Vec<u8> = vec![];
tx.write(&mut raw_tx)?;
let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?;
log::info!("Tx ID = {}", tx_id);
let tx_id = self
.build_sign_send_multi_payment(
account,
last_height,
&recipients,
false,
anchor_offset,
|_| {},
)
.await?;
Ok(tx_id)
}
@ -479,61 +457,13 @@ impl Wallet {
Ok(())
}
pub fn get_spendable_notes(
&self,
account: u32,
extfvk: &ExtendedFullViewingKey,
last_height: u32,
anchor_offset: u32,
) -> anyhow::Result<Vec<SpendableNote>> {
let anchor_height = self.db
.get_last_sync_height()?
.ok_or_else(|| anyhow::anyhow!("No spendable notes"))?;
let anchor_height = anchor_height.min(last_height - anchor_offset);
log::info!("Anchor = {}", anchor_height);
let mut notes = self.db
.get_spendable_notes(account, anchor_height, extfvk)?;
notes.shuffle(&mut OsRng);
log::info!("Spendable notes = {}", notes.len());
Ok(notes)
}
fn _build_recipients(
to_address: &str,
amount: u64,
max_amount_per_note: u64,
memo: &str,
) -> anyhow::Result<Vec<RecipientMemo>> {
let mut recipients: Vec<RecipientMemo> = vec![];
let target_amount = Amount::from_u64(amount).unwrap();
let max_amount_per_note = if max_amount_per_note != 0 {
Amount::from_u64(max_amount_per_note).unwrap()
} else {
Amount::from_i64(MAX_MONEY).unwrap()
};
let mut remaining_amount = target_amount;
while remaining_amount.is_positive() {
let note_amount = remaining_amount.min(max_amount_per_note);
let recipient = RecipientMemo {
address: to_address.to_string(),
amount: u64::from(note_amount),
memo: Memo::from_str(memo)?,
};
recipients.push(recipient);
remaining_amount -= note_amount;
}
Ok(recipients)
}
pub async fn sync_historical_prices(
&mut self,
now: i64,
days: u32,
currency: &str,
) -> anyhow::Result<u32> {
let quotes = fetch_historical_prices(now, days, currency, &self.db)
.await?;
let quotes = fetch_historical_prices(now, days, currency, &self.db).await?;
self.db.store_historical_prices(&quotes, currency)?;
Ok(quotes.len() as u32)
}
@ -548,19 +478,22 @@ impl Wallet {
}
pub fn make_payment_uri(address: &str, amount: u64, memo: &str) -> anyhow::Result<String> {
let addr = RecipientAddress::decode(&NETWORK, address).ok_or_else(|| anyhow::anyhow!("Invalid address"))?;
let addr = RecipientAddress::decode(&NETWORK, address)
.ok_or_else(|| anyhow::anyhow!("Invalid address"))?;
let payment = Payment {
recipient_address: addr,
amount: Amount::from_u64(amount).map_err(|_| anyhow::anyhow!("Invalid amount"))?,
memo: Some(Memo::from_str(memo)?.into()),
label: None,
message: None,
other_params: vec![]
other_params: vec![],
};
let treq = TransactionRequest {
payments: vec![payment],
};
let uri = treq.to_uri(&NETWORK).ok_or_else(|| anyhow::anyhow!("Cannot build Payment URI"))?;
let uri = treq
.to_uri(&NETWORK)
.ok_or_else(|| anyhow::anyhow!("Cannot build Payment URI"))?;
let uri = format!("{}{}", TICKER, &uri[5..]); // hack to replace the URI scheme
Ok(uri)
}
@ -570,8 +503,11 @@ impl Wallet {
anyhow::bail!("Invalid Payment URI");
}
let uri = format!("zcash{}", &uri[5..]); // hack to replace the URI scheme
let treq = TransactionRequest::from_uri(&NETWORK, &uri).map_err(|_| anyhow::anyhow!("Invalid Payment URI"))?;
if treq.payments.len() != 1 { anyhow::bail!("Invalid Payment URI") }
let treq = TransactionRequest::from_uri(&NETWORK, &uri)
.map_err(|_| anyhow::anyhow!("Invalid Payment URI"))?;
if treq.payments.len() != 1 {
anyhow::bail!("Invalid Payment URI")
}
let payment = &treq.payments[0];
let memo = match payment.memo {
Some(ref memo) => {
@ -582,7 +518,7 @@ impl Wallet {
_ => Err(anyhow::anyhow!("Invalid Memo")),
}
}
None => Ok(String::new())
None => Ok(String::new()),
}?;
let payment = MyPayment {
address: payment.recipient_address.encode(&NETWORK),
@ -594,6 +530,40 @@ impl Wallet {
Ok(payment_json)
}
pub fn store_share_secret(&self, account: u32, secret: &str) -> anyhow::Result<()> {
let share = SecretShare::decode(secret)?;
self.db.store_share_secret(
account,
secret,
share.index,
share.threshold,
share.participants,
)
}
pub fn get_share_secret(&self, account: u32) -> anyhow::Result<String> {
self.db.get_share_secret(account)
}
pub fn split_account(&self, t: usize, n: usize, account: u32) -> anyhow::Result<String> {
let sk = self.db.get_sk(account)?;
let esk = decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &sk)
.unwrap()
.unwrap();
let secret_key = esk.expsk.ask;
let nsk = esk.expsk.nsk;
let shares = split_account(t, n, secret_key, nsk)?;
let shares: Vec<_> = shares.iter().map(|s| s.encode().unwrap()).collect();
let res = shares.join("|");
Ok(res)
}
pub fn parse_recipients(recipients: &str) -> anyhow::Result<Vec<RecipientMemo>> {
let recipients: Vec<Recipient> = serde_json::from_str(recipients)?;
let recipient_memos: Vec<_> = recipients.iter().map(|r| RecipientMemo::from(r)).collect();
Ok(recipient_memos)
}
}
#[derive(Serialize)]