Multisig stub
This commit is contained in:
parent
2470a4e6af
commit
ccb25c61f9
28
Cargo.toml
28
Cargo.toml
|
@ -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"
|
||||
|
|
5
build.rs
5
build.rs
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
21
src/chain.rs
21
src/chain.rs
|
@ -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(
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
121
src/db.rs
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
16
src/hash.rs
16
src/hash.rs
|
@ -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();
|
||||
|
|
10
src/key.rs
10
src/key.rs
|
@ -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)> {
|
||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -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};
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
480
src/pay.rs
480
src/pay.rs
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
39
src/scan.rs
39
src/scan.rs
|
@ -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());
|
||||
|
||||
|
|
134
src/taddr.rs
134
src/taddr.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
35
src/ua.rs
35
src/ua.rs
|
@ -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)?;
|
||||
|
|
456
src/wallet.rs
456
src/wallet.rs
|
@ -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, ¬es, 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()),
|
||||
¬es,
|
||||
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(¬e_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), ¬es, 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("es, 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)]
|
||||
|
|
Loading…
Reference in New Issue