use super::{
DerivationIndex, DerivedSecretKey, Hash, MainPubkey, MainSecretKey, NanoTokens, SignedSpend,
UniquePubkey,
};
use crate::{Result, TransferError};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::fmt::Debug;
use tiny_keccak::{Hasher, Sha3};
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub struct CashNote {
pub parent_spends: BTreeSet<SignedSpend>,
pub main_pubkey: MainPubkey,
pub derivation_index: DerivationIndex,
}
impl Debug for CashNote {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CashNote")
.field("unique_pubkey", &self.unique_pubkey())
.field("main_pubkey", &self.main_pubkey)
.field("derivation_index", &self.derivation_index)
.field("parent_spends", &self.parent_spends)
.finish()
}
}
impl CashNote {
pub fn unique_pubkey(&self) -> UniquePubkey {
self.main_pubkey()
.new_unique_pubkey(&self.derivation_index())
}
pub fn main_pubkey(&self) -> &MainPubkey {
&self.main_pubkey
}
pub fn derived_key(&self, main_key: &MainSecretKey) -> Result<DerivedSecretKey> {
if &main_key.main_pubkey() != self.main_pubkey() {
return Err(TransferError::MainSecretKeyDoesNotMatchMainPubkey);
}
Ok(main_key.derive_key(&self.derivation_index()))
}
pub fn derived_pubkey(&self, main_pubkey: &MainPubkey) -> Result<UniquePubkey> {
if main_pubkey != self.main_pubkey() {
return Err(TransferError::MainPubkeyMismatch);
}
Ok(main_pubkey.new_unique_pubkey(&self.derivation_index()))
}
pub fn derivation_index(&self) -> DerivationIndex {
self.derivation_index
}
pub fn value(&self) -> NanoTokens {
let mut total_amount: u64 = 0;
for p in self.parent_spends.iter() {
let amount = p
.spend
.get_output_amount(&self.unique_pubkey())
.unwrap_or(NanoTokens::zero());
total_amount += amount.as_nano();
}
NanoTokens::from(total_amount)
}
pub fn hash(&self) -> Hash {
let mut sha3 = Sha3::v256();
sha3.update(&self.main_pubkey.to_bytes());
sha3.update(&self.derivation_index.0);
for sp in self.parent_spends.iter() {
sha3.update(&sp.to_bytes());
}
let mut hash = [0u8; 32];
sha3.finalize(&mut hash);
Hash::from(hash)
}
pub fn verify(&self) -> Result<(), TransferError> {
if self.parent_spends.is_empty() {
return Err(TransferError::CashNoteMissingAncestors);
}
let unique_pubkey = self.unique_pubkey();
if !self
.parent_spends
.iter()
.all(|p| p.spend.get_output_amount(&unique_pubkey).is_some())
{
return Err(TransferError::InvalidParentSpend(format!(
"Parent spends refered in CashNote: {unique_pubkey:?} do not refer to its pubkey as an output"
)));
}
Ok(())
}
pub fn from_hex(hex: &str) -> Result<Self, TransferError> {
let mut bytes =
hex::decode(hex).map_err(|e| TransferError::HexDeserializationFailed(e.to_string()))?;
bytes.reverse();
let cashnote: CashNote = rmp_serde::from_slice(&bytes)
.map_err(|e| TransferError::HexDeserializationFailed(e.to_string()))?;
Ok(cashnote)
}
pub fn to_hex(&self) -> Result<String, TransferError> {
let mut serialized = rmp_serde::to_vec(&self)
.map_err(|e| TransferError::HexSerializationFailed(e.to_string()))?;
serialized.reverse();
Ok(hex::encode(serialized))
}
}