use super::types::*; use crate::coinconfig::get_prover; use crate::orchard::{get_proving_key, OrchardHasher, ORCHARD_ROOTS}; use crate::sapling::{SaplingHasher, SAPLING_ROOTS}; use crate::sync::tree::TreeCheckpoint; use crate::sync::Witness; use crate::{AccountData, CoinConfig}; use anyhow::anyhow; use jubjub::Fr; use orchard::builder::Builder as OrchardBuilder; use orchard::bundle::Flags; use orchard::keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey}; use orchard::note::Nullifier; use orchard::value::NoteValue; use orchard::{Address, Anchor, Bundle}; use rand::{CryptoRng, RngCore}; use ripemd::{Digest, Ripemd160}; use secp256k1::{All, PublicKey, Secp256k1, SecretKey}; use sha2::Sha256; use std::str::FromStr; use zcash_client_backend::encoding::decode_extended_spending_key; use zcash_primitives::consensus::{BlockHeight, BranchId, Network, Parameters}; use zcash_primitives::legacy::TransparentAddress; use zcash_primitives::merkle_tree::IncrementalWitness; 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::{signature_hash, SignableInput}; use zcash_primitives::transaction::txid::TxIdDigester; use zcash_primitives::transaction::{Transaction, TransactionData, TxVersion}; use zcash_primitives::zip32::ExtendedSpendingKey; pub struct SecretKeys { pub transparent: Option, pub sapling: Option, pub orchard: Option, } pub struct TxBuilderContext { pub height: u32, pub sapling_anchor: [u8; 32], pub orchard_anchor: Option<[u8; 32]>, } impl TxBuilderContext { pub fn from_height(coin: u8, height: u32) -> anyhow::Result { let c = CoinConfig::get(coin); let db = c.db.as_ref().unwrap(); let db = db.lock().unwrap(); let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "sapling")?; let hasher = SaplingHasher {}; let sapling_anchor = tree.root(32, &SAPLING_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, orchard_anchor, }; Ok(context) } } pub fn build_tx( network: &Network, skeys: &SecretKeys, plan: &TransactionPlan, mut rng: impl RngCore + CryptoRng + Clone, ) -> anyhow::Result> { let secp = Secp256k1::::new(); let transparent_address = skeys.transparent.map(|tkey| { let pub_key = PublicKey::from_secret_key(&secp, &tkey); let pub_key = pub_key.serialize(); let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key)); TransparentAddress::PublicKey(pub_key.into()) }); let sapling_fvk = skeys .sapling .as_ref() .map(|sk| sk.to_extended_full_viewing_key()); let sapling_ovk = sapling_fvk.as_ref().map(|efvk| efvk.fvk.ovk.clone()); let okeys = skeys.orchard.map(|sk| { let orchard_fvk = FullViewingKey::from(&sk); let orchard_ovk = orchard_fvk.clone().to_ovk(Scope::External); (orchard_fvk, orchard_ovk) }); let (orchard_fvk, orchard_ovk) = match okeys { Some((a, b)) => (Some(a), Some(b)), _ => (None, None), }; let mut has_orchard = false; let mut builder = Builder::new_with_rng(*network, BlockHeight::from_u32(plan.anchor_height), &mut rng); let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&plan.orchard_anchor) .unwrap() .into(); let mut orchard_builder = OrchardBuilder::new(Flags::from_parts(true, true), anchor); for spend in plan.spends.iter() { match &spend.source { Source::Transparent { txid, index } => { let utxo = OutPoint::new(*txid, *index); let coin = TxOut { value: Amount::from_u64(spend.amount).unwrap(), script_pubkey: transparent_address .ok_or(anyhow!("No transparent key")) .map(|ta| ta.script())?, }; builder .add_transparent_input(skeys.transparent.unwrap(), utxo, coin) .map_err(|e| anyhow!(e.to_string()))?; } Source::Sapling { diversifier, rseed, witness, .. } => { let diversifier = Diversifier(*diversifier); let sapling_address = sapling_fvk .as_ref() .ok_or(anyhow!("No sapling key"))? .fvk .vk .to_payment_address(diversifier) .unwrap(); let rseed = Rseed::BeforeZip212(Fr::from_bytes(rseed).unwrap()); let note = sapling_address.create_note(spend.amount, rseed); let witness = IncrementalWitness::::read(witness.as_slice())?; let merkle_path = witness.path().unwrap(); builder .add_sapling_spend( skeys.sapling.clone().ok_or(anyhow!("No Sapling Key"))?, diversifier, note, merkle_path, ) .map_err(|e| anyhow!(e.to_string()))?; } Source::Orchard { id_note, diversifier, rho, rseed, witness, } => { has_orchard = true; let diversifier = orchard::keys::Diversifier::from_bytes(*diversifier); let sender_address = orchard_fvk .as_ref() .ok_or(anyhow!("No Orchard key")) .map(|fvk| fvk.address(diversifier, Scope::External))?; let value = NoteValue::from_raw(spend.amount); let rho = Nullifier::from_bytes(&rho).unwrap(); let rseed = orchard::note::RandomSeed::from_bytes(*rseed, &rho).unwrap(); let note = orchard::Note::from_parts(sender_address, value, rho, rseed).unwrap(); let witness = Witness::from_bytes(*id_note, &witness)?; let auth_path: Vec<_> = witness .auth_path(32, &ORCHARD_ROOTS, &OrchardHasher::new()) .iter() .map(|n| orchard::tree::MerkleHashOrchard::from_bytes(n).unwrap()) .collect(); let merkle_path = orchard::tree::MerklePath::from_parts( witness.position as u32, auth_path.try_into().unwrap(), ); orchard_builder .add_spend(orchard_fvk.clone().unwrap(), note, merkle_path) .map_err(|e| anyhow!(e.to_string()))?; } } } for output in plan.outputs.iter() { let value = Amount::from_u64(output.amount).unwrap(); match &output.destination { Destination::Transparent(_addr) => { let transparent_address = output.destination.transparent(); builder .add_transparent_output(&transparent_address, value) .map_err(|e| anyhow!(e.to_string()))?; } Destination::Sapling(addr) => { let sapling_address = PaymentAddress::from_bytes(addr).unwrap(); builder .add_sapling_output(sapling_ovk, sapling_address, value, output.memo.clone()) .map_err(|e| anyhow!(e.to_string()))?; } Destination::Orchard(addr) => { has_orchard = true; let orchard_address = Address::from_raw_address_bytes(addr).unwrap(); orchard_builder .add_recipient( orchard_ovk.clone(), orchard_address, NoteValue::from_raw(output.amount), Some(*output.memo.as_array()), ) .map_err(|_| anyhow!("Orchard::add_recipient"))?; } } } let transparent_bundle = builder.transparent_builder.build(); let mut ctx = get_prover().new_sapling_proving_context(); let sapling_bundle = builder .sapling_builder .build( get_prover(), &mut ctx, &mut rng, BlockHeight::from_u32(plan.anchor_height), None, ) .unwrap(); let mut orchard_bundle: Option> = None; if has_orchard { orchard_bundle = Some(orchard_builder.build(&mut rng).unwrap()); } let consensus_branch_id = BranchId::for_height(network, BlockHeight::from_u32(plan.anchor_height)); let version = TxVersion::suggested_for_branch(consensus_branch_id); let unauthed_tx: TransactionData = TransactionData::from_parts( version, consensus_branch_id, 0, BlockHeight::from_u32(plan.expiry_height), transparent_bundle, None, sapling_bundle, orchard_bundle, ); let txid_parts = unauthed_tx.digest(TxIdDigester); 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() .map(|tb| tb.clone().apply_signatures(&unauthed_tx, &txid_parts)); let sapling_bundle = unauthed_tx.sapling_bundle().map(|sb| { sb.clone() .apply_signatures(get_prover(), &mut ctx, &mut rng, &sig_hash) .unwrap() .0 }); let mut orchard_signing_keys = vec![]; if let Some(sk) = skeys.orchard { orchard_signing_keys.push(SpendAuthorizingKey::from(&sk)); } let orchard_bundle = unauthed_tx.orchard_bundle().map(|ob| { let proven = ob .clone() .create_proof(get_proving_key(), &mut rng) .unwrap(); proven .apply_signatures(&mut rng, sig_hash, &orchard_signing_keys) .unwrap() }); let tx_data: TransactionData = TransactionData::from_parts( version, consensus_branch_id, 0, BlockHeight::from_u32(plan.expiry_height), transparent_bundle, None, sapling_bundle, orchard_bundle, ); let tx = Transaction::from_data(tx_data).unwrap(); let mut tx_bytes = vec![]; tx.write(&mut tx_bytes).unwrap(); Ok(tx_bytes) } pub fn get_secret_keys(coin: u8, account: u32) -> anyhow::Result { let c = CoinConfig::get(coin); let db = c.db()?; let transparent_sk = db .get_tsk(account)? .map(|tsk| SecretKey::from_str(&tsk).unwrap()); let AccountData { sk, .. } = db.get_account_info(account)?; let sapling_sk = sk.ok_or(anyhow!("No secret key"))?; let sapling_sk = decode_extended_spending_key( c.chain.network().hrp_sapling_extended_spending_key(), &sapling_sk, ) .unwrap(); let orchard_sk = db .get_orchard(account)? .and_then(|ob| ob.sk.map(|sk| SpendingKey::from_bytes(sk).unwrap())); let sk = SecretKeys { transparent: transparent_sk, sapling: Some(sapling_sk), orchard: orchard_sk, }; Ok(sk) }