use crate::{CashNote, Ciphertext, DerivationIndex, MainPubkey, MainSecretKey, SpendAddress};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::error::{Error, Result};
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, custom_debug::Debug)]
pub struct Transfer {
encrypted_cashnote_redemptions: Vec<Ciphertext>,
}
impl Transfer {
pub fn transfers_from_cash_notes(cash_notes: Vec<CashNote>) -> Result<Vec<Transfer>> {
let mut cashnote_redemptions_map: BTreeMap<MainPubkey, Vec<CashNoteRedemption>> =
BTreeMap::new();
for cash_note in cash_notes {
let recipient = cash_note.main_pubkey;
let derivation_index = cash_note.derivation_index();
let parent_spend_addr = match cash_note.signed_spends.iter().next() {
Some(s) => SpendAddress::from_unique_pubkey(s.unique_pubkey()),
None => {
warn!(
"Skipping CashNote {cash_note:?} while creating Transfer as it has no parent spends."
);
continue;
}
};
info!("Creating Transfer for CashNote {cash_note:?} with recipient {recipient:?} of value {:?}", cash_note.value()?);
let u = CashNoteRedemption::new(derivation_index, parent_spend_addr);
cashnote_redemptions_map
.entry(recipient)
.or_default()
.push(u);
}
let mut transfers = Vec::new();
for (recipient, cashnote_redemptions) in cashnote_redemptions_map {
let t = Transfer::create(cashnote_redemptions, recipient)
.map_err(|_| Error::CashNoteRedemptionEncryptionFailed)?;
transfers.push(t)
}
Ok(transfers)
}
pub fn transfers_from_cash_note(cash_note: CashNote) -> Result<Transfer> {
let recipient = cash_note.main_pubkey;
let derivation_index = cash_note.derivation_index();
let parent_spend_addr = match cash_note.signed_spends.iter().next() {
Some(s) => SpendAddress::from_unique_pubkey(s.unique_pubkey()),
None => {
return Err(Error::CashNoteHasNoParentSpends);
}
};
let u = CashNoteRedemption::new(derivation_index, parent_spend_addr);
let t = Transfer::create(vec![u], recipient)
.map_err(|_| Error::CashNoteRedemptionEncryptionFailed)?;
Ok(t)
}
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(Transfer {
encrypted_cashnote_redemptions,
})
}
pub fn cashnote_redemptions(&self, sk: &MainSecretKey) -> Result<Vec<CashNoteRedemption>> {
let mut cashnote_redemptions = Vec::new();
for cypher in &self.encrypted_cashnote_redemptions {
let cashnote_redemption = CashNoteRedemption::decrypt(cypher, sk)?;
cashnote_redemptions.push(cashnote_redemption);
}
Ok(cashnote_redemptions)
}
pub fn from_hex(hex: &str) -> Result<Self> {
let mut bytes = hex::decode(hex).map_err(|_| Error::TransferDeserializationFailed)?;
bytes.reverse();
let transfer: Transfer =
bincode::deserialize(&bytes).map_err(|_| Error::TransferDeserializationFailed)?;
Ok(transfer)
}
pub fn to_hex(&self) -> Result<String> {
let mut serialized =
bincode::serialize(&self).map_err(|_| Error::TransferSerializationFailed)?;
serialized.reverse();
Ok(hex::encode(serialized))
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, custom_debug::Debug)]
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 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([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([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]);
}
}