use super::{
DerivationIndex, DerivedSecretKey, Hash, MainPubkey, MainSecretKey, NanoTokens, SignedSpend,
Transaction, UniquePubkey,
};
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use tiny_keccak::{Hasher, Sha3};
#[derive(custom_debug::Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub struct CashNote {
pub id: UniquePubkey,
#[debug(skip)]
pub src_tx: Transaction,
pub signed_spends: BTreeSet<SignedSpend>,
pub main_pubkey: MainPubkey,
pub derivation_index: DerivationIndex,
}
impl CashNote {
pub fn unique_pubkey(&self) -> UniquePubkey {
self.id
}
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(Error::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(Error::MainPubkeyMismatch);
}
Ok(main_pubkey.new_unique_pubkey(&self.derivation_index()))
}
pub fn derivation_index(&self) -> DerivationIndex {
self.derivation_index
}
pub fn reason(&self) -> Hash {
self.signed_spends
.iter()
.next()
.map(|c| c.reason())
.unwrap_or_default()
}
pub fn value(&self) -> Result<NanoTokens> {
Ok(self
.src_tx
.outputs
.iter()
.find(|o| &self.unique_pubkey() == o.unique_pubkey())
.ok_or(Error::OutputNotFound)?
.amount)
}
pub fn hash(&self) -> Hash {
let mut sha3 = Sha3::v256();
sha3.update(self.src_tx.hash().as_ref());
sha3.update(&self.main_pubkey.to_bytes());
sha3.update(&self.derivation_index.0);
for sp in self.signed_spends.iter() {
sha3.update(&sp.to_bytes());
}
sha3.update(self.reason().as_ref());
let mut hash = [0u8; 32];
sha3.finalize(&mut hash);
Hash::from(hash)
}
pub fn verify(&self, main_key: &MainSecretKey) -> Result<(), Error> {
self.src_tx
.verify_against_inputs_spent(&self.signed_spends)?;
let unique_pubkey = self.derived_key(main_key)?.unique_pubkey();
if !self
.src_tx
.outputs
.iter()
.any(|o| unique_pubkey.eq(o.unique_pubkey()))
{
return Err(Error::CashNoteCiphersNotPresentInTransactionOutput);
}
let reason = self.reason();
let reasons_are_equal = |s: &SignedSpend| reason == s.reason();
if !self.signed_spends.iter().all(reasons_are_equal) {
return Err(Error::SignedSpendReasonMismatch(unique_pubkey));
}
Ok(())
}
pub fn from_hex(hex: &str) -> Result<Self, Error> {
let mut bytes =
hex::decode(hex).map_err(|e| Error::HexDeserializationFailed(e.to_string()))?;
bytes.reverse();
let cashnote: CashNote = rmp_serde::from_slice(&bytes)
.map_err(|e| Error::HexDeserializationFailed(e.to_string()))?;
Ok(cashnote)
}
pub fn to_hex(&self) -> Result<String, Error> {
let mut serialized =
rmp_serde::to_vec(&self).map_err(|e| Error::HexSerializationFailed(e.to_string()))?;
serialized.reverse();
Ok(hex::encode(serialized))
}
}