use crate::base::ser::{EpeeRead, EpeeWrite, MAX_VEC_LEN, Reader, write_vec};
use crate::base::types::{AccountPublicAddr, Value256};
use crate::base::variant::Variant;
use crate::base::varint::append_varint;
use crate::crypto::{Point, chacha8, chacha8_generate_key};
use crate::error::{Error, Result};
use crate::txdest::TxDest;
use crate::txsource::TxSource;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum WalletBlobLayout {
#[default]
Current,
Legacy,
}
#[derive(Clone, Debug)]
pub struct FinalizeTxParam {
pub unlock_time: u64,
pub extra: Vec<Variant>,
pub attachments: Vec<Variant>,
pub crypt_address: AccountPublicAddr,
pub tx_outs_attr: u8,
pub shuffle: bool,
pub flags: u8,
pub multisig_id: Value256,
pub sources: Vec<TxSource>,
pub selected_transfers: Vec<u64>,
pub prepared_destinations: Vec<TxDest>,
pub expiration_time: u64,
pub spend_pub_key: Point,
pub tx_version: u64,
pub tx_hardfork_id: u64,
pub mode_separate_fee: u64,
pub layout: WalletBlobLayout,
}
impl EpeeWrite for FinalizeTxParam {
fn write_epee(&self, out: &mut Vec<u8>) {
self.write_with(self.layout, out)
}
}
impl EpeeRead for FinalizeTxParam {
fn read_epee(r: &mut Reader<'_>) -> Result<Self> {
FinalizeTxParam::read_with(r, WalletBlobLayout::default())
}
}
impl FinalizeTxParam {
pub fn write_with(&self, layout: WalletBlobLayout, out: &mut Vec<u8>) {
self.unlock_time.write_epee(out);
write_vec(&self.extra, out);
write_vec(&self.attachments, out);
self.crypt_address.write_epee(out);
out.push(self.tx_outs_attr);
self.shuffle.write_epee(out);
out.push(self.flags);
self.multisig_id.write_epee(out);
append_varint(out, self.sources.len() as u64);
for src in &self.sources {
src.write_with(layout, out);
}
append_varint(out, self.selected_transfers.len() as u64);
for t in &self.selected_transfers {
append_varint(out, *t);
}
append_varint(out, self.prepared_destinations.len() as u64);
for dst in &self.prepared_destinations {
dst.write_with(layout, out);
}
self.expiration_time.write_epee(out);
self.spend_pub_key.write_epee(out);
self.tx_version.write_epee(out);
self.tx_hardfork_id.write_epee(out);
self.mode_separate_fee.write_epee(out);
}
pub fn read_with(r: &mut Reader<'_>, layout: WalletBlobLayout) -> Result<Self> {
let unlock_time = u64::read_epee(r)?;
let extra = r.read_vec()?;
let attachments = r.read_vec()?;
let crypt_address = AccountPublicAddr::read_epee(r)?;
let tx_outs_attr = r.read_byte()?;
let shuffle = bool::read_epee(r)?;
let flags = r.read_byte()?;
let multisig_id = Value256::read_epee(r)?;
let sources = read_vec_with(r, |r| TxSource::read_with(r, layout))?;
let n = r.read_varint()?;
if n > MAX_VEC_LEN {
return Err(crate::err!("selected_transfers too large: {n}"));
}
let mut selected_transfers = Vec::with_capacity(n as usize);
for _ in 0..n {
selected_transfers.push(r.read_varint()?);
}
Ok(FinalizeTxParam {
unlock_time,
extra,
attachments,
crypt_address,
tx_outs_attr,
shuffle,
flags,
multisig_id,
sources,
selected_transfers,
prepared_destinations: read_vec_with(r, |r| TxDest::read_with(r, layout))?,
expiration_time: u64::read_epee(r)?,
spend_pub_key: Point::read_epee(r)?,
tx_version: u64::read_epee(r)?,
tx_hardfork_id: u64::read_epee(r)?,
mode_separate_fee: u64::read_epee(r)?,
layout,
})
}
pub fn parse(buf: &[u8], view_secret_key: &[u8]) -> Result<FinalizeTxParam> {
let code = chacha8_generate_key(view_secret_key)?;
let plain = chacha8(&code, &[0u8; 8], buf)?;
try_both_layouts(&plain, FinalizeTxParam::read_with)
}
}
pub(crate) fn read_vec_with<T>(
r: &mut Reader<'_>,
mut read: impl FnMut(&mut Reader<'_>) -> Result<T>,
) -> Result<Vec<T>> {
let n = r.read_varint()?;
if n > MAX_VEC_LEN {
return Err(crate::err!("vector too large: {n}"));
}
let mut v = Vec::with_capacity(n as usize);
for _ in 0..n {
v.push(read(r)?);
}
Ok(v)
}
pub(crate) fn try_both_layouts<T>(
plain: &[u8],
read: impl Fn(&mut Reader<'_>, WalletBlobLayout) -> Result<T>,
) -> Result<T> {
let mut first_err = None;
for layout in [WalletBlobLayout::Current, WalletBlobLayout::Legacy] {
let mut r = Reader::new(plain);
match read(&mut r, layout) {
Ok(v) if r.is_empty() => return Ok(v),
Ok(_) => {
first_err.get_or_insert_with(|| Error::msg("trailing data"));
}
Err(e) => {
first_err.get_or_insert(e);
}
}
}
Err(first_err.unwrap_or_else(|| Error::msg("cannot parse wallet blob")))
}