use crate::base::gencontext::GenContext;
use crate::base::ser::{EpeeRead, EpeeWrite, Reader, write_vec};
use crate::base::sig::ZcSig;
use crate::base::tx::Transaction;
use crate::base::types::Value256;
use crate::base::variant::Variant;
use crate::crypto::clsag::ClsagGgxInputRef;
use crate::crypto::consts::{C_POINT_X, SC_1DIV8};
use crate::crypto::{Point, Scalar, double_scalar_base_mult, random_scalar, scalar_int};
use crate::error::{Error, Result};
use crate::inputsigner::{ClsagRequest, InputSigner};
use crate::rng::RngCore;
#[derive(Clone, Debug)]
pub struct TxSourceOutputEntry {
pub out_reference: Variant,
pub stealth_address: Point,
pub concealing_point: Point,
pub amount_commitment: Point,
pub blinded_asset_id: Point,
}
impl EpeeWrite for TxSourceOutputEntry {
fn write_epee(&self, out: &mut Vec<u8>) {
self.out_reference.write_epee(out);
self.stealth_address.write_epee(out);
self.concealing_point.write_epee(out);
self.amount_commitment.write_epee(out);
self.blinded_asset_id.write_epee(out);
}
}
impl EpeeRead for TxSourceOutputEntry {
fn read_epee(r: &mut Reader<'_>) -> Result<Self> {
Ok(TxSourceOutputEntry {
out_reference: Variant::read_epee(r)?,
stealth_address: Point::read_epee(r)?,
concealing_point: Point::read_epee(r)?,
amount_commitment: Point::read_epee(r)?,
blinded_asset_id: Point::read_epee(r)?,
})
}
}
#[derive(Clone, Debug)]
pub struct TxSource {
pub outputs: Vec<TxSourceOutputEntry>,
pub real_output: u64,
pub real_out_tx_key: Point,
pub real_out_amount_blinding_mask: Scalar,
pub real_out_asset_id_blinding_mask: Scalar,
pub real_out_in_tx_index: u64,
pub amount: u64,
pub transfer_index: u64,
pub multisig_id: Value256,
pub ms_sigs_count: u64,
pub ms_keys_count: u64,
pub separately_signed_tx_complete: bool,
pub htlc_origin: String,
pub asset_id: Option<Point>,
pub is_zc_input: bool,
pub hi: Option<Scalar>,
}
impl EpeeWrite for TxSource {
fn write_epee(&self, out: &mut Vec<u8>) {
write_vec(&self.outputs, out);
self.real_output.write_epee(out);
self.real_out_tx_key.write_epee(out);
self.real_out_amount_blinding_mask.write_epee(out);
self.real_out_asset_id_blinding_mask.write_epee(out);
self.real_out_in_tx_index.write_epee(out);
self.amount.write_epee(out);
self.transfer_index.write_epee(out);
self.multisig_id.write_epee(out);
self.ms_sigs_count.write_epee(out);
self.ms_keys_count.write_epee(out);
self.separately_signed_tx_complete.write_epee(out);
self.htlc_origin.write_epee(out);
}
}
impl EpeeRead for TxSource {
fn read_epee(r: &mut Reader<'_>) -> Result<Self> {
Ok(TxSource {
outputs: r.read_vec()?,
real_output: u64::read_epee(r)?,
real_out_tx_key: Point::read_epee(r)?,
real_out_amount_blinding_mask: Scalar::read_epee(r)?,
real_out_asset_id_blinding_mask: Scalar::read_epee(r)?,
real_out_in_tx_index: u64::read_epee(r)?,
amount: u64::read_epee(r)?,
transfer_index: u64::read_epee(r)?,
multisig_id: Value256::read_epee(r)?,
ms_sigs_count: u64::read_epee(r)?,
ms_keys_count: u64::read_epee(r)?,
separately_signed_tx_complete: bool::read_epee(r)?,
htlc_origin: String::read_epee(r)?,
asset_id: None,
is_zc_input: false,
hi: None,
})
}
}
impl TxSource {
pub fn is_zc(&self) -> bool {
self.is_zc_input || self.real_out_asset_id_blinding_mask != Scalar::ZERO
}
#[allow(clippy::too_many_arguments)]
pub fn generate_zc_sig(
&self,
rnd: &mut dyn RngCore,
tx: &Transaction,
input_index: usize,
tx_hash_for_sig: &[u8],
ogc: &mut GenContext,
last_output: bool,
signer: &mut dyn InputSigner,
) -> Result<ZcSig> {
let vin = tx.vin[input_index]
.as_txin_zc_input()
.ok_or_else(|| Error::msg("input is not a txin_zc_input"))?;
let asset_id = match self.asset_id {
Some(p) => p,
None => *ogc
.asset_ids
.first()
.ok_or_else(|| Error::msg("no asset id available for input"))?,
};
let source_blinded_asset_id =
asset_id.add(&C_POINT_X.mul(&self.real_out_asset_id_blinding_mask));
ogc.real_zc_ins_asset_ids.push(asset_id);
let pseudo_out_amount_blinding_mask = if last_output {
let mut ao_term = GenContext::scalar_or_zero(&ogc.ao_amount_blinding_mask);
if !ogc.ao_commitment_in_outputs {
ao_term = ao_term.negate();
}
let mut m = GenContext::scalar_or_zero(&ogc.amount_blinding_masks_sum);
if let Some(sum) = &ogc.pseudo_out_amount_blinding_masks_sum {
m = m.sub(sum);
}
m.add(&ao_term)
} else {
let m = random_scalar(rnd);
GenContext::add_scalar(&mut ogc.pseudo_out_amount_blinding_masks_sum, &m);
m
};
let pseudo_out_asset_id_blinding_mask = random_scalar(rnd);
let pseudo_out_blinded_asset_id =
source_blinded_asset_id.add(&C_POINT_X.mul(&pseudo_out_asset_id_blinding_mask));
GenContext::add_scalar(
&mut ogc.real_in_asset_id_blinding_mask_x_amount_sum,
&self
.real_out_asset_id_blinding_mask
.mul(&scalar_int(self.amount)),
);
ogc.pseudo_outs_blinded_asset_ids
.push(pseudo_out_blinded_asset_id);
ogc.pseudo_outs_plus_real_out_blinding_masks
.push(pseudo_out_asset_id_blinding_mask.add(&self.real_out_asset_id_blinding_mask));
let pseudo_out_amount_commitment = double_scalar_base_mult(
&scalar_int(self.amount),
&source_blinded_asset_id,
&pseudo_out_amount_blinding_mask,
);
GenContext::add_point(
&mut ogc.pseudo_out_amount_commitments_sum,
&pseudo_out_amount_commitment,
);
let ring: Vec<ClsagGgxInputRef> = self
.outputs
.iter()
.map(|o| ClsagGgxInputRef {
stealth_address: o.stealth_address,
amount_commitment: o.amount_commitment,
blinded_asset_id: o.blinded_asset_id,
})
.collect();
let secret1_f = self
.real_out_amount_blinding_mask
.sub(&pseudo_out_amount_blinding_mask);
let secret2_t = pseudo_out_asset_id_blinding_mask.negate();
let hi = self
.hi
.as_ref()
.ok_or_else(|| Error::msg("source is missing its per-output scalar"))?;
let ggx = signer.sign_clsag(
rnd,
&ClsagRequest {
hi,
msg: tx_hash_for_sig,
ring: &ring,
key_image: &vin.key_image,
pseudo_out_amount_commitment: &pseudo_out_amount_commitment,
pseudo_out_blinded_asset_id: &pseudo_out_blinded_asset_id,
secret1_f: &secret1_f,
secret2_t: &secret2_t,
secret_index: self.real_output,
},
)?;
Ok(ZcSig {
pseudo_out_amount_commitment: pseudo_out_amount_commitment.mul(&SC_1DIV8),
pseudo_out_blinded_asset_id: pseudo_out_blinded_asset_id.mul(&SC_1DIV8),
ggx,
})
}
}