diff --git a/src/api/dart_ffi.rs b/src/api/dart_ffi.rs index 2d1b95e..3cd0bfc 100644 --- a/src/api/dart_ffi.rs +++ b/src/api/dart_ffi.rs @@ -479,13 +479,13 @@ pub unsafe extern "C" fn transaction_report(coin: u8, plan: *mut c_char) -> CRes pub async unsafe extern "C" fn sign( coin: u8, account: u32, - tx: *mut c_char, + tx_plan: *mut c_char, _port: i64, ) -> CResult<*mut c_char> { - from_c_str!(tx); + from_c_str!(tx_plan); let res = async { - let tx: TransactionPlan = serde_json::from_str(&tx)?; - let raw_tx = crate::api::payment_v2::sign_plan(coin, account, &tx)?; + let tx_plan: TransactionPlan = serde_json::from_str(&tx_plan)?; + let raw_tx = crate::api::payment_v2::sign_plan(coin, account, &tx_plan)?; let tx_str = base64::encode(&raw_tx); Ok::<_, anyhow::Error>(tx_str) }; diff --git a/src/api/payment_v2.rs b/src/api/payment_v2.rs index c805f1b..7a67b37 100644 --- a/src/api/payment_v2.rs +++ b/src/api/payment_v2.rs @@ -27,6 +27,7 @@ pub async fn build_tx_plan( confirmations: u32, ) -> note_selection::Result { let c = CoinConfig::get(coin); + let network = c.chain.network(); let (fvk, checkpoint_height) = { let db = c.db()?; let AccountData { fvk, .. } = db.get_account_info(account)?; @@ -48,7 +49,7 @@ pub async fn build_tx_plan( while amount > 0 { let a = min(amount, max_amount_per_note); let memo_bytes: MemoBytes = r.memo.clone().into(); - let order = Order::new(id_order, &r.address, a, memo_bytes); + let order = Order::new(network, id_order, &r.address, a, memo_bytes); orders.push(order); amount -= a; id_order += 1; @@ -58,6 +59,7 @@ pub async fn build_tx_plan( let config = TransactionBuilderConfig::new(&change_address); let tx_plan = crate::note_selection::build_tx_plan::( + network, &fvk, checkpoint_height, &context.orchard_anchor, diff --git a/src/note_selection.rs b/src/note_selection.rs index cb64ce6..09b7aae 100644 --- a/src/note_selection.rs +++ b/src/note_selection.rs @@ -11,6 +11,7 @@ pub use utxo::fetch_utxos; use crate::api::recipient::Recipient; use thiserror::Error; +use zcash_primitives::consensus::Network; use ua::decode; use zcash_primitives::memo::Memo; @@ -37,12 +38,12 @@ mod utxo; pub const MAX_ATTEMPTS: usize = 10; #[allow(dead_code)] -pub fn recipients_to_orders(recipients: &[Recipient]) -> Result> { +pub fn recipients_to_orders(network: &Network, recipients: &[Recipient]) -> Result> { let orders: Result> = recipients .iter() .enumerate() .map(|(i, r)| { - let destinations = decode(&r.address)?; + let destinations = decode(network, &r.address)?; Ok::<_, TransactionBuilderError>(Order { id: i as u32, destinations, diff --git a/src/note_selection/builder.rs b/src/note_selection/builder.rs index 29d5d14..4298f2b 100644 --- a/src/note_selection/builder.rs +++ b/src/note_selection/builder.rs @@ -26,8 +26,7 @@ use zcash_primitives::sapling::prover::TxProver; use zcash_primitives::sapling::{Diversifier, Node, PaymentAddress, Rseed}; use zcash_primitives::transaction::builder::Builder; use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut}; -use zcash_primitives::transaction::sighash::SignableInput; -use zcash_primitives::transaction::sighash_v5::v5_signature_hash; +use zcash_primitives::transaction::sighash::{SignableInput, signature_hash}; use zcash_primitives::transaction::txid::TxIdDigester; use zcash_primitives::transaction::{Transaction, TransactionData, TxVersion}; use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; @@ -41,7 +40,7 @@ pub struct SecretKeys { pub struct TxBuilderContext { pub height: u32, pub sapling_anchor: [u8; 32], - pub orchard_anchor: [u8; 32], + pub orchard_anchor: Option<[u8; 32]>, } impl TxBuilderContext { @@ -52,9 +51,13 @@ impl TxBuilderContext { let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "sapling")?; let hasher = SaplingHasher {}; let sapling_anchor = tree.root(32, &SAPLING_ROOTS, &hasher); - let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "orchard")?; - let hasher = OrchardHasher::new(); - let orchard_anchor = tree.root(32, &ORCHARD_ROOTS, &hasher); + + let orchard_anchor = if c.chain.has_unified() { + let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "orchard")?; + let hasher = OrchardHasher::new(); + Some(tree.root(32, &ORCHARD_ROOTS, &hasher)) + } + else { None }; let context = TxBuilderContext { height, sapling_anchor, @@ -208,10 +211,13 @@ pub fn build_tx( orchard_bundle = Some(orchard_builder.build(rng.clone()).unwrap()); } + let consensus_branch_id = BranchId::for_height(network, BlockHeight::from_u32(plan.height)); + let version = TxVersion::suggested_for_branch(consensus_branch_id); + let unauthed_tx: TransactionData = TransactionData::from_parts( - TxVersion::Zip225, - BranchId::Nu5, + version, + consensus_branch_id, 0, BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT), transparent_bundle, @@ -221,8 +227,8 @@ pub fn build_tx( ); let txid_parts = unauthed_tx.digest(TxIdDigester); - let sig_hash = v5_signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts); - let sig_hash: [u8; 32] = sig_hash.as_bytes().try_into().unwrap(); + let sig_hash = signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts); + let sig_hash: [u8; 32] = sig_hash.as_ref().clone(); let transparent_bundle = unauthed_tx .transparent_bundle() @@ -252,8 +258,8 @@ pub fn build_tx( let tx_data: TransactionData = TransactionData::from_parts( - TxVersion::Zip225, - BranchId::Nu5, + version, + consensus_branch_id, 0, BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT), transparent_bundle, diff --git a/src/note_selection/optimize.rs b/src/note_selection/optimize.rs index 1bc445e..56dcaac 100644 --- a/src/note_selection/optimize.rs +++ b/src/note_selection/optimize.rs @@ -1,3 +1,4 @@ +use zcash_primitives::consensus::Network; use super::{types::*, Result}; use crate::note_selection::fee::FeeCalculator; use crate::note_selection::ua::decode; @@ -287,15 +288,17 @@ pub fn outputs_for_change( } pub fn build_tx_plan( + network: &Network, fvk: &str, height: u32, - orchard_anchor: &Hash, + orchard_anchor: &Option, utxos: &[UTXO], orders: &[Order], config: &TransactionBuilderConfig, ) -> Result { let mut fee = 0; + println!("build_tx_plan"); for _ in 0..MAX_ATTEMPTS { let balances = sum_utxos(utxos)?; let (groups, amounts) = group_orders(&orders, fee)?; @@ -311,7 +314,7 @@ pub fn build_tx_plan( let mut fills = fill(&orders, &groups, &amounts, &allocation)?; let (notes, change) = select_inputs(&utxos, &allocation)?; - let change_destinations = decode(&config.change_address)?; + let change_destinations = decode(network, &config.change_address)?; let change_outputs = outputs_for_change(&change_destinations, &change)?; fills.extend(change_outputs); @@ -320,7 +323,7 @@ pub fn build_tx_plan( let tx_plan = TransactionPlan { fvk: fvk.to_string(), height, - orchard_anchor: orchard_anchor.clone(), + orchard_anchor: orchard_anchor.unwrap_or(Hash::default()), spends: notes, outputs: fills, net_chg, diff --git a/src/note_selection/tests.rs b/src/note_selection/tests.rs index 8738cf4..79fac54 100644 --- a/src/note_selection/tests.rs +++ b/src/note_selection/tests.rs @@ -3,12 +3,12 @@ use super::types::*; use super::TransactionBuilderError::NotEnoughFunds; use crate::note_selection::build_tx_plan; use crate::note_selection::fee::{FeeCalculator, FeeZIP327}; -use crate::note_selection::optimize::{outputs_for_change, select_inputs}; -use crate::note_selection::ua::decode; +use crate::note_selection::optimize::select_inputs; use crate::Hash; use assert_matches::assert_matches; use serde::Serialize; use serde_json::Value; +use zcash_primitives::consensus::Network; use zcash_primitives::memo::MemoBytes; macro_rules! utxo { @@ -689,14 +689,6 @@ fn test_select_utxo() { const CHANGE_ADDRESS: &str = "u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qqmktzwktw772nlle6skkkxwmtzxaan3slntqev03g70tzpky3c58hfgvfjkcky255cwqgfuzdjcktfl7pjalt5sl33se75pmga09etn9dplr98eq2g8cgmvgvx6jx2a2xhy39x96c6rumvlyt35whml87r064qdzw30e"; -#[test] -fn test_change_fills() { - let _ = env_logger::try_init(); - let destinations = decode(CHANGE_ADDRESS).unwrap(); - let outputs = outputs_for_change(&destinations, &&PoolAllocation([0, 10000, 10000])).unwrap(); - log::info!("{:?}", outputs); -} - #[test] fn test_fees() { let _ = env_logger::try_init(); @@ -733,6 +725,7 @@ fn test_tx_plan() { tso!(7, 70), ]; let tx_plan = build_tx_plan::( + &Network::MainNetwork, "", 0, &Hash::default(), diff --git a/src/note_selection/types.rs b/src/note_selection/types.rs index b332c4f..b81eca4 100644 --- a/src/note_selection/types.rs +++ b/src/note_selection/types.rs @@ -200,8 +200,8 @@ impl Destination { } impl Order { - pub fn new(id: u32, address: &str, amount: u64, memo: MemoBytes) -> Self { - let destinations = decode(address).unwrap(); + pub fn new(network: &Network, id: u32, address: &str, amount: u64, memo: MemoBytes) -> Self { + let destinations = decode(network, address).unwrap(); Order { id, destinations, diff --git a/src/note_selection/ua.rs b/src/note_selection/ua.rs index fd96304..8636fd1 100644 --- a/src/note_selection/ua.rs +++ b/src/note_selection/ua.rs @@ -1,41 +1,53 @@ use super::types::*; use zcash_address::unified::{Container, Receiver}; use zcash_address::{AddressKind, ZcashAddress}; +use zcash_client_backend::encoding::{decode_payment_address, decode_transparent_address}; +use zcash_primitives::consensus::{Network, Parameters}; +use zcash_primitives::legacy::TransparentAddress; -pub fn decode(address: &str) -> anyhow::Result<[Option; 3]> { +pub fn decode(network: &Network, address: &str) -> anyhow::Result<[Option; 3]> { let mut destinations: [Option; 3] = [None; 3]; - let address = ZcashAddress::try_from_encoded(address)?; - match address.kind { - AddressKind::Sprout(_) => {} - AddressKind::Sapling(data) => { - let destination = Destination::Sapling(data); - destinations[Pool::Sapling as usize] = Some(destination); - } - AddressKind::Unified(unified_address) => { - for address in unified_address.items() { - match address { - Receiver::Orchard(data) => { - let destination = Destination::Orchard(data); - destinations[Pool::Orchard as usize] = Some(destination); + if let Ok(data) = decode_payment_address(network.hrp_sapling_payment_address(), address) { + let destination = Destination::Sapling(data.to_bytes()); + destinations[Pool::Sapling as usize] = Some(destination); + } + else if let Ok(Some(TransparentAddress::PublicKey(data))) = decode_transparent_address(&network.b58_pubkey_address_prefix(), &network.b58_script_address_prefix(), address) { + let destination = Destination::Transparent(data); + destinations[Pool::Transparent as usize] = Some(destination); + } + else if let Ok(address) = ZcashAddress::try_from_encoded(address) { // ZcashAddress only supports Zcash + match address.kind { + AddressKind::Sprout(_) => {} + AddressKind::Sapling(data) => { + let destination = Destination::Sapling(data); + destinations[Pool::Sapling as usize] = Some(destination); + } + AddressKind::Unified(unified_address) => { + for address in unified_address.items() { + match address { + Receiver::Orchard(data) => { + let destination = Destination::Orchard(data); + destinations[Pool::Orchard as usize] = Some(destination); + } + Receiver::Sapling(data) => { + let destination = Destination::Sapling(data); + destinations[Pool::Sapling as usize] = Some(destination); + } + Receiver::P2pkh(data) => { + let destination = Destination::Transparent(data); + destinations[Pool::Transparent as usize] = Some(destination); + } + Receiver::P2sh(_) => {} + Receiver::Unknown { .. } => {} } - Receiver::Sapling(data) => { - let destination = Destination::Sapling(data); - destinations[Pool::Sapling as usize] = Some(destination); - } - Receiver::P2pkh(data) => { - let destination = Destination::Transparent(data); - destinations[Pool::Transparent as usize] = Some(destination); - } - Receiver::P2sh(_) => {} - Receiver::Unknown { .. } => {} } } + AddressKind::P2pkh(data) => { + let destination = Destination::Transparent(data); + destinations[Pool::Transparent as usize] = Some(destination); + } + AddressKind::P2sh(_) => {} } - AddressKind::P2pkh(data) => { - let destination = Destination::Transparent(data); - destinations[Pool::Transparent as usize] = Some(destination); - } - AddressKind::P2sh(_) => {} } Ok(destinations) diff --git a/src/prices.rs b/src/prices.rs index 3b3b3f4..e390a0a 100644 --- a/src/prices.rs +++ b/src/prices.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use crate::DbAdapter; use chrono::NaiveDateTime; use zcash_params::coin::get_coin_chain; @@ -52,7 +53,10 @@ pub async fn fetch_historical_prices( let ts = p[0].as_i64().ok_or_else(json_error)? / 1000; let price = p[1].as_f64().ok_or_else(json_error)?; // rounded to daily - let date = NaiveDateTime::from_timestamp(ts, 0).date().and_hms(0, 0, 0); + let date = NaiveDateTime::from_timestamp_opt(ts, 0) + .ok_or(anyhow!("Invalid Date"))? + .date().and_hms_opt(0, 0, 0) + .ok_or(anyhow!("Invalid Date"))?; let timestamp = date.timestamp(); if timestamp != prev_timestamp { let quote = Quote { timestamp, price };