use crate::base::gencontext::GenContext;
use crate::base::ser::{EpeeRead, EpeeWrite, Reader, write_vec};
use crate::base::types::{AccountPublicAddr, AddressV, Value256};
use crate::crypto::consts::{C_POINT_G, C_POINT_X, SC_1DIV8};
use crate::crypto::{Point, Scalar, hash_to_scalar};
use crate::error::Result;
use crate::ftp::WalletBlobLayout;
pub const CRYPTO_HDS_OUT_CONCEALING_POINT: &[u8; 32] = b"ZANO_HDS_OUT_CONCEALING_POINT__\x00";
pub const CRYPTO_HDS_OUT_ASSET_BLIND_MASK: &[u8; 32] = b"ZANO_HDS_OUT_ASSET_BLIND_MASK__\x00";
pub const CRYPTO_HDS_OUT_AMOUNT_BLINDING_MASK: &[u8; 32] = b"ZANO_HDS_OUT_AMOUNT_BLIND_MASK_\x00";
pub const CRYPTO_HDS_OUT_AMOUNT_MASK: &[u8; 32] = b"ZANO_HDS_OUT_AMOUNT_MASK_______\x00";
#[derive(Clone, Copy, Debug, Default)]
pub struct TxDestHtlcOut {
pub expiration: u64,
pub htlc_hash: Value256,
}
impl EpeeWrite for TxDestHtlcOut {
fn write_epee(&self, out: &mut Vec<u8>) {
self.expiration.write_epee(out);
self.htlc_hash.write_epee(out);
}
}
impl EpeeRead for TxDestHtlcOut {
fn read_epee(r: &mut Reader<'_>) -> Result<Self> {
Ok(TxDestHtlcOut {
expiration: u64::read_epee(r)?,
htlc_hash: Value256::read_epee(r)?,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct TxDest {
pub amount: u64,
pub addr: Vec<AddressV>,
pub minimum_sigs: u64,
pub amount_to_provide: u64,
pub unlock_time: u64,
pub htlc_options: Option<TxDestHtlcOut>,
pub asset_id: Option<Point>,
pub flags: u64,
pub payment_id: u64,
}
impl EpeeWrite for TxDest {
fn write_epee(&self, out: &mut Vec<u8>) {
self.write_with(WalletBlobLayout::default(), out)
}
}
impl EpeeRead for TxDest {
fn read_epee(r: &mut Reader<'_>) -> Result<Self> {
TxDest::read_with(r, WalletBlobLayout::default())
}
}
impl TxDest {
pub fn write_with(&self, layout: WalletBlobLayout, out: &mut Vec<u8>) {
self.amount.write_epee(out);
match layout {
WalletBlobLayout::Current => write_vec(&self.addr, out),
WalletBlobLayout::Legacy => {
let legacy: Vec<AccountPublicAddr> = self
.addr
.iter()
.map(|a| a.as_account().copied().unwrap_or_default())
.collect();
write_vec(&legacy, out);
}
}
self.minimum_sigs.write_epee(out);
self.amount_to_provide.write_epee(out);
self.unlock_time.write_epee(out);
match &self.htlc_options {
Some(h) => h.write_epee(out),
None => TxDestHtlcOut::default().write_epee(out),
}
match &self.asset_id {
Some(p) => p.write_epee(out),
None => out.extend_from_slice(&[0u8; 32]),
}
self.flags.write_epee(out);
if layout == WalletBlobLayout::Current {
self.payment_id.write_epee(out);
}
}
pub fn read_with(r: &mut Reader<'_>, layout: WalletBlobLayout) -> Result<Self> {
let amount = u64::read_epee(r)?;
let addr = match layout {
WalletBlobLayout::Current => r.read_vec()?,
WalletBlobLayout::Legacy => r
.read_vec::<AccountPublicAddr>()?
.into_iter()
.map(AddressV::Account)
.collect(),
};
let minimum_sigs = u64::read_epee(r)?;
let amount_to_provide = u64::read_epee(r)?;
let unlock_time = u64::read_epee(r)?;
let htlc_options = TxDestHtlcOut::read_epee(r)?;
let asset_id = Point::read_epee(r)?;
let flags = u64::read_epee(r)?;
let payment_id = match layout {
WalletBlobLayout::Current => u64::read_epee(r)?,
WalletBlobLayout::Legacy => 0,
};
Ok(TxDest {
amount,
addr,
minimum_sigs,
amount_to_provide,
unlock_time,
htlc_options: Some(htlc_options),
asset_id: Some(asset_id),
flags,
payment_id,
})
}
pub fn account(&self) -> Result<&AccountPublicAddr> {
match self.addr.as_slice() {
[AddressV::Account(a)] => Ok(a),
[] => Err(crate::err!("destination has no address")),
[AddressV::Gateway(_)] => Err(crate::err!("gateway destinations are not supported")),
_ => Err(crate::err!("multisig destinations are not supported")),
}
}
pub fn stealth_address(&self, scalar: &Scalar) -> Result<Point> {
let p = crate::crypto::point_from_bytes(self.account()?.spend_key.as_bytes())?;
Ok(Point::mul_base(scalar).add(&p))
}
pub fn concealing_point(&self, scalar: &Scalar) -> Result<Point> {
let h = hs_domain(CRYPTO_HDS_OUT_CONCEALING_POINT, scalar);
let v = crate::crypto::point_from_bytes(self.account()?.view_key.as_bytes())?;
Ok(v.mul(&h))
}
pub fn blinded_asset_id(
&self,
scalar: &Scalar,
ogc: &mut GenContext,
i: usize,
) -> Result<Point> {
let asset_blinding_mask = hs_domain(CRYPTO_HDS_OUT_ASSET_BLIND_MASK, scalar);
ogc.asset_id_blinding_masks[i] = asset_blinding_mask.clone();
let q = self
.asset_id
.ok_or_else(|| crate::err!("destination has no asset id"))?;
let s = q.add(&C_POINT_X.mul(&asset_blinding_mask));
ogc.blinded_asset_ids[i] = s;
Ok(s.mul(&SC_1DIV8))
}
pub fn amount_commitment(&self, scalar: &Scalar, ogc: &mut GenContext, i: usize) -> Point {
let amount_blinding_mask = hs_domain(CRYPTO_HDS_OUT_AMOUNT_BLINDING_MASK, scalar);
let amount = crate::crypto::scalar_int(self.amount);
let t = ogc.blinded_asset_ids[i];
let r = t.mul(&amount).add(&C_POINT_G.mul(&amount_blinding_mask));
ogc.amount_commitments[i] = r;
r.mul(&SC_1DIV8)
}
}
pub fn hs_domain(domain: &[u8; 32], scalar: &Scalar) -> Scalar {
let mut buf = Vec::with_capacity(64);
buf.extend_from_slice(domain);
buf.extend_from_slice(&scalar.to_bytes());
hash_to_scalar(&buf)
}