1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
// Copyright 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.
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::{Result, TransferError};
/// Transfer sent to a recipient
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub enum Transfer {
/// List of encrypted CashNoteRedemptions from which a recipient can verify and get money
/// Only the recipient can decrypt these CashNoteRedemptions
Encrypted(Vec<Ciphertext>),
/// The network requires a payment as network royalties for storage which nodes can validate
/// and verify, these CashNoteRedemptions need to be sent to storage nodes as payment proof as well.
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) => {
// Iterate over the transfers and log the hash of each encrypted transfer
let hashed: Vec<_> = transfers
.iter()
.map(|transfer| {
// Calculate the hash of the transfer
let mut hasher = DefaultHasher::new();
transfer.hash(&mut hasher);
hasher.finish()
})
.collect();
// Write the encrypted transfers to the formatter
write!(f, "Transfer::Encrypted: {hashed:?}")
}
}
}
}
impl Transfer {
/// This function is used to create a Transfer from a CashNote, can be done offline, and sent to the recipient.
/// Creates a Transfer from the given cash_note
/// This Transfer can be sent safely to the recipients as all data in it is encrypted
/// The recipients can then decrypt the data and use it to verify and reconstruct the CashNote
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(|_| TransferError::CashNoteRedemptionEncryptionFailed)?;
Ok(t)
}
/// This function is used to create a Network Royalties Transfer from a CashNote
/// can be done offline, and sent to the recipient.
/// Note that this type of transfer is not encrypted
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]))
}
/// Create a new transfer
/// cashnote_redemptions: List of CashNoteRedemptions to be used for payment
/// recipient: main Public key (donation key) of the recipient,
/// not to be confused with the derived keys
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))
}
/// Get the CashNoteRedemptions from the Payment
/// This is used by the recipient of a payment to decrypt the cashnote_redemptions in a payment
pub fn cashnote_redemptions(&self, sk: &MainSecretKey) -> Result<Vec<CashNoteRedemption>> {
match self {
Self::Encrypted(cyphers) => {
let cashnote_redemptions: Result<Vec<_>> = cyphers
.par_iter() // Use Rayon's par_iter for parallel processing
.map(|cypher| CashNoteRedemption::decrypt(cypher, sk)) // Decrypt each CashNoteRedemption
.collect(); // Collect results into a vector
let cashnote_redemptions = cashnote_redemptions?; // Propagate error if any
Ok(cashnote_redemptions)
}
Self::NetworkRoyalties(cnr) => Ok(cnr.clone()),
}
}
/// Deserializes a `Transfer` represented as a hex string to a `Transfer`.
pub fn from_hex(hex: &str) -> Result<Self> {
let mut bytes =
hex::decode(hex).map_err(|_| TransferError::TransferDeserializationFailed)?;
bytes.reverse();
let transfer: Self = rmp_serde::from_slice(&bytes)
.map_err(|_| TransferError::TransferDeserializationFailed)?;
Ok(transfer)
}
/// Serialize this `Transfer` instance to a readable hex string that a human can copy paste
pub fn to_hex(&self) -> Result<String> {
let mut serialized =
rmp_serde::to_vec(&self).map_err(|_| TransferError::TransferSerializationFailed)?;
serialized.reverse();
Ok(hex::encode(serialized))
}
}
/// Unspent Transaction (Tx) Output
/// Information can be used by the Tx recipient of this output
/// to check that they received money and to spend it
///
/// This struct contains sensitive information that should be kept secret
/// so it should be encrypted to the recipient's public key (public address)
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Hash)]
pub struct CashNoteRedemption {
/// derivation index of the CashNoteRedemption
/// with this derivation index the owner can derive
/// the secret key from their main key needed to spend this CashNoteRedemption
pub derivation_index: DerivationIndex,
/// spentbook entry of one of one of the inputs (parent spends)
/// using data found at this address the owner can check that the output is valid money
pub parent_spend: SpendAddress,
/// For what purpose this cash_note was created
pub purpose: String,
}
impl CashNoteRedemption {
/// Create a new CashNoteRedemption
pub fn new(
derivation_index: DerivationIndex,
parent_spend: SpendAddress,
purpose: String,
) -> Self {
Self {
derivation_index,
parent_spend,
purpose,
}
}
pub fn from_cash_note(cash_note: &CashNote) -> Result<Self> {
let derivation_index = cash_note.derivation_index();
let parent_spend = match cash_note.parent_spends.iter().next() {
Some(s) => SpendAddress::from_unique_pubkey(s.unique_pubkey()),
None => {
return Err(TransferError::CashNoteHasNoParentSpends);
}
};
Ok(Self::new(
derivation_index,
parent_spend,
cash_note.purpose.clone(),
))
}
/// Serialize the CashNoteRedemption to bytes
pub fn to_bytes(&self) -> Result<Vec<u8>> {
rmp_serde::to_vec(self).map_err(|_| TransferError::CashNoteRedemptionSerialisationFailed)
}
/// Deserialize the CashNoteRedemption from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
rmp_serde::from_slice(bytes)
.map_err(|_| TransferError::CashNoteRedemptionSerialisationFailed)
}
/// Encrypt the CashNoteRedemption to a public key
pub fn encrypt(&self, pk: MainPubkey) -> Result<Ciphertext> {
let bytes = self.to_bytes()?;
Ok(pk.0.encrypt(bytes))
}
/// Decrypt the CashNoteRedemption with a secret key
pub fn decrypt(cypher: &Ciphertext, sk: &MainSecretKey) -> Result<Self> {
let bytes = sk
.secret_key()
.decrypt(cypher)
.ok_or(TransferError::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)),
Default::default(),
);
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)),
Default::default(),
);
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]);
}
}