use crate::{CashNote, Ciphertext, DerivationIndex, MainPubkey, MainSecretKey, SpendAddress};
use rayon::iter::ParallelIterator;
use rayon::prelude::IntoParallelRefIterator;
use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use crate::error::{Error, Result};
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub enum Transfer {
Encrypted(Vec<Ciphertext>),
NetworkRoyalties(Vec<CashNoteRedemption>),
}
impl std::fmt::Debug for Transfer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NetworkRoyalties(cn_redemptions) => {
write!(f, "Transfer::NetworkRoyalties: {cn_redemptions:?}")
}
Self::Encrypted(transfers) => {
let hashed: Vec<_> = transfers
.iter()
.map(|transfer| {
let mut hasher = DefaultHasher::new();
transfer.hash(&mut hasher);
hasher.finish()
})
.collect();
write!(f, "Transfer::Encrypted: {hashed:?}")
}
}
}
}
impl Transfer {
pub fn transfer_from_cash_note(cash_note: &CashNote) -> Result<Self> {
let recipient = cash_note.main_pubkey;
let u = CashNoteRedemption::from_cash_note(cash_note)?;
let t = Self::create(vec![u], recipient)
.map_err(|_| Error::CashNoteRedemptionEncryptionFailed)?;
Ok(t)
}
pub(crate) fn royalties_transfer_from_cash_note(cash_note: &CashNote) -> Result<Self> {
let cnr = CashNoteRedemption::from_cash_note(cash_note)?;
Ok(Self::NetworkRoyalties(vec![cnr]))
}
pub fn create(
cashnote_redemptions: Vec<CashNoteRedemption>,
recipient: MainPubkey,
) -> Result<Self> {
let encrypted_cashnote_redemptions = cashnote_redemptions
.into_iter()
.map(|cashnote_redemption| cashnote_redemption.encrypt(recipient))
.collect::<Result<Vec<Ciphertext>>>()?;
Ok(Self::Encrypted(encrypted_cashnote_redemptions))
}
pub fn cashnote_redemptions(&self, sk: &MainSecretKey) -> Result<Vec<CashNoteRedemption>> {
match self {
Self::Encrypted(cyphers) => {
let cashnote_redemptions: Result<Vec<_>> = cyphers
.par_iter() .map(|cypher| CashNoteRedemption::decrypt(cypher, sk)) .collect(); let cashnote_redemptions = cashnote_redemptions?; Ok(cashnote_redemptions)
}
Self::NetworkRoyalties(cnr) => Ok(cnr.clone()),
}
}
pub fn from_hex(hex: &str) -> Result<Self> {
let mut bytes = hex::decode(hex).map_err(|_| Error::TransferDeserializationFailed)?;
bytes.reverse();
let transfer: Self =
rmp_serde::from_slice(&bytes).map_err(|_| Error::TransferDeserializationFailed)?;
Ok(transfer)
}
pub fn to_hex(&self) -> Result<String> {
let mut serialized =
rmp_serde::to_vec(&self).map_err(|_| Error::TransferSerializationFailed)?;
serialized.reverse();
Ok(hex::encode(serialized))
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Hash)]
pub struct CashNoteRedemption {
pub derivation_index: DerivationIndex,
pub parent_spend: SpendAddress,
}
impl CashNoteRedemption {
pub fn new(derivation_index: DerivationIndex, parent_spend: SpendAddress) -> Self {
Self {
derivation_index,
parent_spend,
}
}
pub fn from_cash_note(cash_note: &CashNote) -> Result<Self> {
let derivation_index = cash_note.derivation_index();
let parent_spend = match cash_note.signed_spends.iter().next() {
Some(s) => SpendAddress::from_unique_pubkey(s.unique_pubkey()),
None => {
return Err(Error::CashNoteHasNoParentSpends);
}
};
Ok(Self::new(derivation_index, parent_spend))
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
rmp_serde::to_vec(self).map_err(|_| Error::CashNoteRedemptionSerialisationFailed)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
rmp_serde::from_slice(bytes).map_err(|_| Error::CashNoteRedemptionSerialisationFailed)
}
pub fn encrypt(&self, pk: MainPubkey) -> Result<Ciphertext> {
let bytes = self.to_bytes()?;
Ok(pk.0.encrypt(bytes))
}
pub fn decrypt(cypher: &Ciphertext, sk: &MainSecretKey) -> Result<Self> {
let bytes = sk
.secret_key()
.decrypt(cypher)
.ok_or(Error::CashNoteRedemptionDecryptionFailed)?;
Self::from_bytes(&bytes)
}
}
#[cfg(test)]
mod tests {
use xor_name::XorName;
use super::*;
#[test]
fn test_cashnote_redemption_conversions() {
let rng = &mut bls::rand::thread_rng();
let cashnote_redemption = CashNoteRedemption::new(
DerivationIndex([42; 32]),
SpendAddress::new(XorName::random(rng)),
);
let sk = MainSecretKey::random();
let pk = sk.main_pubkey();
let bytes = cashnote_redemption.to_bytes().unwrap();
let cipher = cashnote_redemption.encrypt(pk).unwrap();
let cashnote_redemption2 = CashNoteRedemption::from_bytes(&bytes).unwrap();
let cashnote_redemption3 = CashNoteRedemption::decrypt(&cipher, &sk).unwrap();
assert_eq!(cashnote_redemption, cashnote_redemption2);
assert_eq!(cashnote_redemption, cashnote_redemption3);
}
#[test]
fn test_cashnote_redemption_transfer() {
let rng = &mut bls::rand::thread_rng();
let cashnote_redemption = CashNoteRedemption::new(
DerivationIndex([42; 32]),
SpendAddress::new(XorName::random(rng)),
);
let sk = MainSecretKey::random();
let pk = sk.main_pubkey();
let payment = Transfer::create(vec![cashnote_redemption.clone()], pk).unwrap();
let cashnote_redemptions = payment.cashnote_redemptions(&sk).unwrap();
assert_eq!(cashnote_redemptions, vec![cashnote_redemption]);
}
}