zcash-sync/src/pay.rs

448 lines
15 KiB
Rust
Raw Permalink Normal View History

2021-08-05 06:38:48 -07:00
use crate::db::SpendableNote;
2022-06-08 05:48:16 -07:00
// use crate::wallet::RecipientMemo;
2022-11-12 17:39:12 -08:00
use crate::api::recipient::RecipientMemo;
2022-11-06 04:50:51 -08:00
use crate::chain::get_latest_height;
2022-06-08 05:48:16 -07:00
use crate::coinconfig::CoinConfig;
2023-03-10 22:43:10 -08:00
use crate::{GetAddressUtxosReply, RawTransaction};
2022-06-07 09:58:24 -07:00
use anyhow::anyhow;
2021-08-16 06:07:04 -07:00
use jubjub::Fr;
2021-11-11 17:39:50 -08:00
use rand::prelude::SliceRandom;
2021-08-16 06:07:04 -07:00
use rand::rngs::OsRng;
2021-11-11 17:39:50 -08:00
use secp256k1::SecretKey;
2021-08-16 06:07:04 -07:00
use serde::{Deserialize, Serialize};
2021-11-11 17:39:50 -08:00
use std::sync::mpsc;
2021-08-16 06:07:04 -07:00
use tonic::Request;
use zcash_client_backend::address::RecipientAddress;
2022-11-06 04:50:51 -08:00
use zcash_client_backend::encoding::{
2023-03-10 22:43:10 -08:00
decode_extended_full_viewing_key, encode_extended_full_viewing_key, encode_payment_address,
2022-11-06 04:50:51 -08:00
};
2022-06-07 09:58:24 -07:00
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
2021-11-11 17:39:50 -08:00
use zcash_primitives::consensus::{BlockHeight, Parameters};
2022-06-07 09:58:24 -07:00
use zcash_primitives::keys::OutgoingViewingKey;
2021-11-11 17:39:50 -08:00
use zcash_primitives::legacy::Script;
use zcash_primitives::memo::{Memo, MemoBytes};
2021-08-16 06:07:04 -07:00
use zcash_primitives::merkle_tree::IncrementalWitness;
2021-11-11 17:39:50 -08:00
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};
2023-03-10 22:43:10 -08:00
use zcash_primitives::transaction::fees::fixed::FeeRule;
2021-08-16 06:07:04 -07:00
use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
2021-08-05 06:38:48 -07:00
#[derive(Serialize, Deserialize, Debug)]
pub struct Tx {
2022-03-07 06:47:06 -08:00
pub coin_type: CoinType,
2021-11-11 17:39:50 -08:00
pub height: u32,
pub t_inputs: Vec<TTxIn>,
pub inputs: Vec<TxIn>,
pub outputs: Vec<TxOut>,
pub change: String,
pub ovk: String,
2021-08-05 06:38:48 -07:00
}
impl Tx {
2022-03-07 06:47:06 -08:00
pub fn new(coin_type: CoinType, height: u32) -> Self {
2021-08-05 06:38:48 -07:00
Tx {
2022-03-07 06:47:06 -08:00
coin_type,
2021-08-05 06:38:48 -07:00
height,
2021-11-11 17:39:50 -08:00
t_inputs: vec![],
2021-08-05 06:38:48 -07:00
inputs: vec![],
outputs: vec![],
2021-11-11 17:39:50 -08:00
change: "".to_string(),
ovk: "".to_string(),
2021-08-05 06:38:48 -07:00
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TxIn {
2021-11-11 17:39:50 -08:00
pub diversifier: String,
pub fvk: String,
pub amount: u64,
pub rseed: String,
pub witness: String,
2021-08-05 06:38:48 -07:00
}
#[derive(Clone, Serialize, Deserialize, Debug)]
2021-11-11 17:39:50 -08:00
pub struct TTxIn {
pub op: String,
pub n: u32,
pub amount: u64,
pub script: String,
2021-08-05 06:38:48 -07:00
}
2021-11-11 17:39:50 -08:00
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct TxOut {
pub addr: String,
pub amount: u64,
pub ovk: String,
pub memo: String,
2021-08-05 06:38:48 -07:00
}
2022-06-21 17:18:47 -07:00
#[derive(Serialize, Debug)]
pub struct TxSummary {
pub recipients: Vec<RecipientSummary>,
}
#[derive(Serialize, Debug)]
pub struct RecipientSummary {
pub address: String,
pub amount: u64,
}
2022-11-17 01:13:51 -08:00
#[allow(dead_code)]
2021-11-11 17:39:50 -08:00
pub struct TxBuilder {
2021-08-05 06:38:48 -07:00
pub tx: Tx,
2022-03-07 06:47:06 -08:00
coin_type: CoinType,
2021-08-05 06:38:48 -07:00
}
2022-11-17 01:13:51 -08:00
#[allow(dead_code)]
2021-11-11 17:39:50 -08:00
impl TxBuilder {
2022-03-07 06:47:06 -08:00
pub fn new(coin_type: CoinType, height: u32) -> Self {
2021-11-11 17:39:50 -08:00
TxBuilder {
2022-03-07 06:47:06 -08:00
coin_type,
tx: Tx::new(coin_type, height),
2021-08-05 06:38:48 -07:00
}
}
2021-11-11 17:39:50 -08:00
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(
2021-08-16 06:07:04 -07:00
&mut self,
diversifier: &Diversifier,
fvk: &ExtendedFullViewingKey,
amount: Amount,
rseed: &[u8],
witness: &[u8],
) -> anyhow::Result<()> {
2021-08-05 06:38:48 -07:00
let tx_in = TxIn {
diversifier: hex::encode(diversifier.0),
2021-08-16 06:07:04 -07:00
fvk: encode_extended_full_viewing_key(
2022-06-07 09:58:24 -07:00
self.chain()
.network()
.hrp_sapling_extended_full_viewing_key(),
2022-06-08 05:48:16 -07:00
fvk,
2021-08-16 06:07:04 -07:00
),
2021-08-05 06:38:48 -07:00
amount: u64::from(amount),
rseed: hex::encode(rseed),
witness: hex::encode(witness),
};
self.tx.inputs.push(tx_in);
Ok(())
}
fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()> {
let tx_out = TxOut {
addr: address.to_string(),
amount: u64::from(amount),
ovk: String::new(),
memo: String::new(),
};
self.tx.outputs.push(tx_out);
Ok(())
}
2021-08-16 06:07:04 -07:00
fn add_z_output(
&mut self,
address: &str,
ovk: &OutgoingViewingKey,
amount: Amount,
2021-09-08 07:10:22 -07:00
memo: &Memo,
2021-08-16 06:07:04 -07:00
) -> anyhow::Result<()> {
2021-08-05 06:38:48 -07:00
let tx_out = TxOut {
addr: address.to_string(),
amount: u64::from(amount),
ovk: hex::encode(ovk.0),
2021-09-08 07:10:22 -07:00
memo: hex::encode(MemoBytes::from(memo).as_slice()),
2021-08-05 06:38:48 -07:00
};
self.tx.outputs.push(tx_out);
Ok(())
}
2021-10-11 02:13:36 -07:00
2021-11-11 17:39:50 -08:00
fn set_change(
2021-08-16 06:07:04 -07:00
&mut self,
2021-11-11 17:39:50 -08:00
ovk: &OutgoingViewingKey,
address: &PaymentAddress,
2021-08-16 06:07:04 -07:00
) -> anyhow::Result<()> {
2022-06-07 09:58:24 -07:00
self.tx.change = encode_payment_address(
self.chain().network().hrp_sapling_payment_address(),
address,
);
2021-11-11 17:39:50 -08:00
self.tx.ovk = hex::encode(ovk.0);
2021-08-05 06:38:48 -07:00
Ok(())
}
2021-11-11 17:39:50 -08:00
/// 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();
}
2021-08-05 06:38:48 -07:00
}
2022-06-07 09:58:24 -07:00
let target_amount_with_fee =
(target_amount + DEFAULT_FEE).ok_or(anyhow!("Invalid amount"))?;
2021-11-11 17:39:50 -08:00
if target_amount_with_fee > t_amount {
// We need to use some shielded notes because the transparent balance is not enough
2022-03-14 19:40:08 -07:00
let mut amount = (target_amount_with_fee - t_amount).unwrap();
2021-11-11 17:39:50 -08:00
// 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(
2023-03-10 22:43:10 -08:00
Amount::from_u64(n.note.value().inner())
2021-11-11 17:39:50 -08:00
.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,
2023-03-10 22:43:10 -08:00
Amount::from_u64(n.note.value().inner()).unwrap(),
2021-11-11 17:39:50 -08:00
&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)
2021-08-05 06:38:48 -07:00
}
2021-11-11 17:39:50 -08:00
/// Add outputs
///
/// Expand the recipients if their amount exceeds the max amount per note
/// Set the change
pub fn select_outputs(
2021-08-16 06:07:04 -07:00
&mut self,
2021-11-11 17:39:50 -08:00
fvk: &ExtendedFullViewingKey,
recipients: &[RecipientMemo],
2021-08-16 06:07:04 -07:00
) -> anyhow::Result<()> {
2021-11-11 17:39:50 -08:00
let ovk = &fvk.fvk.ovk;
2022-03-14 19:40:08 -07:00
let (_, change) = fvk.default_address();
2022-06-08 05:48:16 -07:00
self.set_change(ovk, &change)?;
2021-11-11 17:39:50 -08:00
for r in recipients.iter() {
2022-03-07 06:47:06 -08:00
let to_addr = RecipientAddress::decode(self.chain().network(), &r.address)
2021-11-11 17:39:50 -08:00
.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 is_first = true; // make at least an output note
2021-11-11 17:39:50 -08:00
let mut remaining_amount = amount;
while remaining_amount.is_positive() || is_first {
is_first = false;
2021-11-11 17:39:50 -08:00
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);
2022-06-08 05:48:16 -07:00
self.add_z_output(&r.address, ovk, note_amount, memo)
2021-11-11 17:39:50 -08:00
}
RecipientAddress::Transparent(_address) => {
self.add_t_output(&r.address, note_amount)
}
RecipientAddress::Unified(_ua) => {
todo!() // TODO
}
2021-11-11 17:39:50 -08:00
}?;
}
2021-08-05 06:38:48 -07:00
}
2021-10-11 02:13:36 -07:00
Ok(())
}
2022-03-07 06:47:06 -08:00
2022-06-07 09:58:24 -07:00
fn chain(&self) -> &dyn CoinChain {
get_coin_chain(self.coin_type)
}
2021-08-05 06:38:48 -07:00
}
2021-11-11 17:39:50 -08:00
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,
2022-03-14 19:40:08 -07:00
prover: &impl TxProver,
2021-11-11 17:39:50 -08:00
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<Vec<u8>> {
2022-03-07 06:47:06 -08:00
let chain = get_coin_chain(self.coin_type);
2021-11-11 17:39:50 -08:00
let last_height = BlockHeight::from_u32(self.height as u32);
2022-03-07 06:47:06 -08:00
let mut builder = Builder::new(*chain.network(), last_height);
2023-03-10 22:43:10 -08:00
let efvk = zsk.to_extended_full_viewing_key();
2021-08-05 06:38:48 -07:00
2021-11-11 17:39:50 -08:00
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)?;
2023-03-10 22:43:10 -08:00
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()),
},
)
.map_err(|e| anyhow!(e.to_string()))?;
2021-08-05 06:38:48 -07:00
}
2021-11-11 17:39:50 -08:00
} else if !self.t_inputs.is_empty() {
anyhow::bail!("Missing secret key of transparent account");
}
2021-08-05 06:38:48 -07:00
2021-11-11 17:39:50 -08:00
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(
2022-03-07 06:47:06 -08:00
chain.network().hrp_sapling_extended_full_viewing_key(),
2021-11-11 17:39:50 -08:00
&txin.fvk,
2022-11-06 04:50:51 -08:00
)
.map_err(|_| anyhow!("Bech32 Decode Error"))?;
2022-06-20 02:05:11 -07:00
if fvk != efvk {
anyhow::bail!("Incorrect account - Secret key mismatch")
}
2021-11-11 17:39:50 -08:00
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();
2023-03-10 22:43:10 -08:00
let note = pa.create_note(txin.amount, Rseed::BeforeZip212(rseed));
2021-11-11 17:39:50 -08:00
let w = hex::decode(&txin.witness)?;
let witness = IncrementalWitness::<Node>::read(&*w)?;
let merkle_path = witness.path().unwrap();
2021-08-05 06:38:48 -07:00
2023-03-10 22:43:10 -08:00
builder
.add_sapling_spend(zsk.clone(), diversifier, note, merkle_path)
.map_err(|e| anyhow!(e.to_string()))?;
2021-11-11 17:39:50 -08:00
}
for txout in self.outputs.iter() {
2022-03-07 06:47:06 -08:00
let recipient = RecipientAddress::decode(chain.network(), &txout.addr).unwrap();
2021-11-11 17:39:50 -08:00
let amount = Amount::from_u64(txout.amount).unwrap();
match recipient {
RecipientAddress::Transparent(ta) => {
2023-03-10 22:43:10 -08:00
builder
.add_transparent_output(&ta, amount)
.map_err(|e| anyhow!(e.to_string()))?;
2021-11-11 17:39:50 -08:00
}
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)?;
2023-03-10 22:43:10 -08:00
builder
.add_sapling_output(Some(ovk), pa, amount, memo)
.map_err(|e| anyhow!(e.to_string()))?;
2021-11-11 17:39:50 -08:00
}
RecipientAddress::Unified(_ua) => {
todo!() // TODO
}
2021-08-05 06:38:48 -07:00
}
}
2021-11-11 17:39:50 -08:00
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);
}
});
2023-03-10 22:43:10 -08:00
let (tx, _) = builder.build(prover, &FeeRule::standard())?;
2021-11-11 17:39:50 -08:00
let mut raw_tx = vec![];
tx.write(&mut raw_tx)?;
2021-08-05 06:38:48 -07:00
2021-11-11 17:39:50 -08:00
Ok(raw_tx)
}
2021-08-05 06:38:48 -07:00
}
2022-10-19 23:24:36 -07:00
/// Broadcast a raw signed transaction to the network
2022-06-08 05:48:16 -07:00
pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
2021-08-05 06:38:48 -07:00
let latest_height = get_latest_height(&mut client).await?;
2021-08-16 06:07:04 -07:00
let raw_tx = RawTransaction {
2021-08-05 06:38:48 -07:00
data: tx.to_vec(),
height: latest_height as u64,
};
2023-04-14 06:00:42 -07:00
2023-04-18 16:55:10 -07:00
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))
}
2021-08-05 06:38:48 -07:00
}
2022-06-21 17:18:47 -07:00
pub fn get_tx_summary(tx: &Tx) -> anyhow::Result<TxSummary> {
let mut recipients = vec![];
for tx_out in tx.outputs.iter() {
recipients.push(RecipientSummary {
address: tx_out.addr.clone(),
amount: tx_out.amount,
});
}
Ok(TxSummary { recipients })
}