use super::{Hash, NanoTokens, Transaction, UniquePubkey};
use crate::{DerivationIndex, Result, Signature, SpendAddress, TransferError};
use custom_debug::Debug;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
#[derive(Debug, Clone, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SignedSpend {
pub spend: Spend,
#[debug(skip)]
pub derived_key_sig: Signature,
}
impl SignedSpend {
pub fn unique_pubkey(&self) -> &UniquePubkey {
&self.spend.unique_pubkey
}
pub fn address(&self) -> SpendAddress {
SpendAddress::from_unique_pubkey(&self.spend.unique_pubkey)
}
pub fn spent_tx_hash(&self) -> Hash {
self.spend.spent_tx.hash()
}
pub fn spent_tx(&self) -> Transaction {
self.spend.spent_tx.clone()
}
pub fn parent_tx_hash(&self) -> Hash {
self.spend.parent_tx.hash()
}
pub fn token(&self) -> &NanoTokens {
&self.spend.token
}
pub fn reason(&self) -> Hash {
self.spend.reason
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Default::default();
bytes.extend(self.spend.to_bytes());
bytes.extend(self.derived_key_sig.to_bytes());
bytes
}
pub fn verify(&self, spent_tx_hash: Hash) -> Result<()> {
if spent_tx_hash != self.spent_tx_hash() {
return Err(TransferError::TransactionHashMismatch(
spent_tx_hash,
self.spent_tx_hash(),
));
}
let parent_tx = &self.spend.parent_tx;
let unique_key = self.unique_pubkey();
if !parent_tx
.outputs
.iter()
.any(|o| o.unique_pubkey() == unique_key)
{
return Err(TransferError::InvalidParentTx(format!(
"spend {unique_key} is not an output of the its parent tx: {parent_tx:?}"
)));
}
let spent_tx = &self.spend.spent_tx;
if !spent_tx
.inputs
.iter()
.any(|i| i.unique_pubkey() == unique_key)
{
return Err(TransferError::InvalidSpentTx(format!(
"spend {unique_key} is not an input of the its spent tx: {spent_tx:?}"
)));
}
let claimed_value = self.spend.token;
let creation_value = self
.spend
.parent_tx
.outputs
.iter()
.find(|o| o.unique_pubkey == self.spend.unique_pubkey)
.map(|o| o.amount)
.unwrap_or(NanoTokens::zero());
let spent_value = self
.spend
.spent_tx
.inputs
.iter()
.find(|i| i.unique_pubkey == self.spend.unique_pubkey)
.map(|i| i.amount)
.unwrap_or(NanoTokens::zero());
if claimed_value != creation_value || creation_value != spent_value {
return Err(TransferError::InvalidSpendValue(*self.unique_pubkey()));
}
if self
.spend
.unique_pubkey
.verify(&self.derived_key_sig, self.spend.to_bytes())
{
Ok(())
} else {
Err(TransferError::InvalidSpendSignature(*self.unique_pubkey()))
}
}
pub fn verify_parent_spends<'a, T>(&self, parent_spends: T) -> Result<()>
where
T: IntoIterator<Item = &'a SignedSpend> + Clone,
{
let unique_key = self.unique_pubkey();
trace!("Verifying parent_spends for {unique_key}");
let tx_our_cash_note_was_created_in = self.parent_tx_hash();
for p in parent_spends.clone().into_iter() {
let tx_parent_was_spent_in = p.spent_tx_hash();
if tx_our_cash_note_was_created_in != tx_parent_was_spent_in {
return Err(TransferError::InvalidParentSpend(format!(
"Parent spend was spent in another transaction. Expected: {tx_our_cash_note_was_created_in:?} Got: {tx_parent_was_spent_in:?}"
)));
}
}
if let Err(e) = self
.spend
.parent_tx
.verify_against_inputs_spent(parent_spends)
{
return Err(TransferError::InvalidParentSpend(format!(
"Parent Tx verification failed: {e:?}"
)));
}
trace!("Validated parent_spends for {unique_key}");
Ok(())
}
}
impl PartialEq for SignedSpend {
fn eq(&self, other: &Self) -> bool {
self.spend == other.spend && self.derived_key_sig == other.derived_key_sig
}
}
impl Eq for SignedSpend {}
impl std::hash::Hash for SignedSpend {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let bytes = self.to_bytes();
bytes.hash(state);
}
}
#[derive(custom_debug::Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Spend {
pub unique_pubkey: UniquePubkey,
#[debug(skip)]
pub spent_tx: Transaction,
#[debug(skip)]
pub reason: Hash,
#[debug(skip)]
pub token: NanoTokens,
#[debug(skip)]
pub parent_tx: Transaction,
#[debug(skip)]
pub network_royalties: Vec<DerivationIndex>,
}
impl Spend {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Default::default();
bytes.extend(self.unique_pubkey.to_bytes());
bytes.extend(self.spent_tx.hash().as_ref());
bytes.extend(self.reason.as_ref());
bytes.extend(self.token.to_bytes());
bytes.extend(self.parent_tx.hash().as_ref());
bytes
}
pub fn hash(&self) -> Hash {
Hash::hash(&self.to_bytes())
}
}
impl PartialOrd for Spend {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Spend {
fn cmp(&self, other: &Self) -> Ordering {
self.unique_pubkey.cmp(&other.unique_pubkey)
}
}