use super::spend_reason::SpendReason;
use super::{Hash, NanoTokens, UniquePubkey};
use crate::{
DerivationIndex, DerivedSecretKey, Result, Signature, SpendAddress, TransferError,
NETWORK_ROYALTIES_PK,
};
use custom_debug::Debug;
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
collections::{BTreeMap, BTreeSet},
};
#[derive(Debug, Clone, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SignedSpend {
pub spend: Spend,
#[debug(skip)]
pub derived_key_sig: Signature,
}
impl SignedSpend {
pub fn sign(spend: Spend, sk: &DerivedSecretKey) -> Self {
let derived_key_sig = sk.sign(&spend.to_bytes_for_signing());
Self {
spend,
derived_key_sig,
}
}
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 amount(&self) -> NanoTokens {
self.spend.amount()
}
pub fn reason(&self) -> &SpendReason {
&self.spend.reason
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Default::default();
bytes.extend(self.spend.to_bytes_for_signing());
bytes.extend(self.derived_key_sig.to_bytes());
bytes
}
pub fn verify(&self) -> Result<()> {
if self
.spend
.unique_pubkey
.verify(&self.derived_key_sig, self.spend.to_bytes_for_signing())
{
Ok(())
} else {
Err(TransferError::InvalidSpendSignature(*self.unique_pubkey()))
}
}
pub fn verify_parent_spends(&self, parent_spends: &BTreeSet<SignedSpend>) -> Result<()> {
let unique_key = self.unique_pubkey();
trace!("Verifying parent_spends for {self:?}");
let mut parents_by_key = BTreeMap::new();
for s in parent_spends {
parents_by_key
.entry(s.unique_pubkey())
.or_insert_with(Vec::new)
.push(s);
}
let mut total_inputs: u64 = 0;
for (_, spends) in parents_by_key {
if spends.len() > 1 {
error!("While verifying parents of {unique_key}, found a double spend parent: {spends:?}");
return Err(TransferError::DoubleSpentParent);
}
if let Some(parent) = spends.first() {
match parent.spend.get_output_amount(unique_key) {
Some(amount) => {
total_inputs += amount.as_nano();
}
None => {
return Err(TransferError::InvalidParentSpend(format!(
"Parent spend {:?} doesn't contain self spend {unique_key:?} as one of its output",
parent.unique_pubkey()
)));
}
}
}
}
let total_outputs = self.amount().as_nano();
if total_outputs != total_inputs {
return Err(TransferError::InvalidParentSpend(format!(
"Parents total input value {total_inputs:?} doesn't match Spend's value {total_outputs:?}"
)));
}
trace!("Validated parent_spends for {unique_key}");
Ok(())
}
#[cfg(test)]
pub(crate) fn random_spend_to(
rng: &mut rand::prelude::ThreadRng,
output: UniquePubkey,
value: u64,
) -> Self {
use crate::MainSecretKey;
let sk = MainSecretKey::random();
let index = DerivationIndex::random(rng);
let derived_sk = sk.derive_key(&index);
let unique_pubkey = derived_sk.unique_pubkey();
let reason = SpendReason::default();
let ancestor = MainSecretKey::random()
.derive_key(&DerivationIndex::random(rng))
.unique_pubkey();
let spend = Spend {
unique_pubkey,
reason,
ancestors: BTreeSet::from_iter(vec![ancestor]),
descendants: BTreeMap::from_iter(vec![(output, (NanoTokens::from(value)))]),
royalties: vec![],
};
let derived_key_sig = derived_sk.sign(&spend.to_bytes_for_signing());
Self {
spend,
derived_key_sig,
}
}
}
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(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Spend {
pub unique_pubkey: UniquePubkey,
pub reason: SpendReason,
pub ancestors: BTreeSet<UniquePubkey>,
pub descendants: BTreeMap<UniquePubkey, NanoTokens>,
pub royalties: Vec<DerivationIndex>,
}
impl core::fmt::Debug for Spend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Spend({:?}({:?}))", self.unique_pubkey, self.hash())
}
}
impl Spend {
pub fn to_bytes_for_signing(&self) -> Vec<u8> {
let mut bytes: Vec<u8> = Default::default();
bytes.extend(self.unique_pubkey.to_bytes());
bytes.extend(self.reason.hash().as_ref());
bytes.extend("ancestors".as_bytes());
for ancestor in self.ancestors.iter() {
bytes.extend(&ancestor.to_bytes());
}
bytes.extend("descendants".as_bytes());
for (descendant, amount) in self.descendants.iter() {
bytes.extend(&descendant.to_bytes());
bytes.extend(amount.to_bytes());
}
bytes.extend("royalties".as_bytes());
for royalty in self.royalties.iter() {
bytes.extend(royalty.as_bytes());
}
bytes
}
pub fn hash(&self) -> Hash {
Hash::hash(&self.to_bytes_for_signing())
}
pub fn amount(&self) -> NanoTokens {
let amount: u64 = self
.descendants
.values()
.map(|amount| amount.as_nano())
.sum();
NanoTokens::from(amount)
}
pub fn network_royalties(&self) -> BTreeSet<(UniquePubkey, NanoTokens, DerivationIndex)> {
let roy_pks: BTreeMap<UniquePubkey, DerivationIndex> = self
.royalties
.iter()
.map(|di| (NETWORK_ROYALTIES_PK.new_unique_pubkey(di), *di))
.collect();
self.descendants
.iter()
.filter_map(|(pk, amount)| roy_pks.get(pk).map(|di| (*pk, *amount, *di)))
.collect()
}
pub fn get_output_amount(&self, target: &UniquePubkey) -> Option<NanoTokens> {
self.descendants.get(target).copied()
}
}
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)
}
}