use std::collections::BTreeSet;
use crate::error::Result;
use crate::{
CashNote, DerivationIndex, MainPubkey, MainSecretKey, NanoTokens, SignedSpend, SpendReason,
TransferError, UnsignedTransaction,
};
use serde::{Deserialize, Serialize};
#[derive(custom_debug::Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SignedTransaction {
#[debug(skip)]
pub output_cashnotes: Vec<CashNote>,
#[debug(skip)]
pub change_cashnote: Option<CashNote>,
pub spends: BTreeSet<SignedSpend>,
}
impl SignedTransaction {
pub fn new(
available_cash_notes: Vec<CashNote>,
recipients: Vec<(NanoTokens, MainPubkey, DerivationIndex, bool)>,
change_to: MainPubkey,
input_reason_hash: SpendReason,
main_key: &MainSecretKey,
) -> Result<Self> {
let unsigned_tx = UnsignedTransaction::new(
available_cash_notes,
recipients,
change_to,
input_reason_hash,
)?;
let signed_tx = unsigned_tx.sign(main_key)?;
Ok(signed_tx)
}
pub fn verify(&self) -> Result<()> {
for cn in self.output_cashnotes.iter() {
cn.verify()?;
}
if let Some(ref cn) = self.change_cashnote {
cn.verify()?;
}
for spend in self.spends.iter() {
spend.verify()?;
}
Ok(())
}
pub fn from_hex(hex: &str) -> Result<Self> {
let decoded_hex = hex::decode(hex).map_err(|e| {
TransferError::TransactionSerialization(format!("Hex decode failed: {e}"))
})?;
let s = rmp_serde::from_slice(&decoded_hex).map_err(|e| {
TransferError::TransactionSerialization(format!("Failed to deserialize: {e}"))
})?;
Ok(s)
}
pub fn to_hex(&self) -> Result<String> {
Ok(hex::encode(rmp_serde::to_vec(self).map_err(|e| {
TransferError::TransactionSerialization(format!("Failed to serialize: {e}"))
})?))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unsigned_tx_serialization() -> Result<()> {
let mut rng = rand::thread_rng();
let cnr_sk = MainSecretKey::random();
let cnr_pk = cnr_sk.main_pubkey();
let cnr_di = DerivationIndex::random(&mut rng);
let cnr_upk = cnr_pk.new_unique_pubkey(&cnr_di);
let spend = SignedSpend::random_spend_to(&mut rng, cnr_upk, 100);
let available_cash_notes = vec![CashNote {
parent_spends: BTreeSet::from_iter([spend]),
main_pubkey: cnr_pk,
derivation_index: cnr_di,
}];
let recipients = vec![
(
NanoTokens::from(1),
MainSecretKey::random().main_pubkey(),
DerivationIndex::random(&mut rng),
false,
),
(
NanoTokens::from(1),
MainSecretKey::random().main_pubkey(),
DerivationIndex::random(&mut rng),
false,
),
];
let change_to = MainSecretKey::random().main_pubkey();
let input_reason_hash = Default::default();
let tx = UnsignedTransaction::new(
available_cash_notes,
recipients,
change_to,
input_reason_hash,
)
.expect("UnsignedTransaction creation to succeed");
let signed_tx = tx.sign(&cnr_sk).expect("Sign to succeed");
let hex = signed_tx.to_hex()?;
let signed_tx2 = SignedTransaction::from_hex(&hex)?;
assert_eq!(signed_tx, signed_tx2);
Ok(())
}
#[test]
fn test_unsigned_tx_verify_simple() -> Result<()> {
let mut rng = rand::thread_rng();
let cnr_sk = MainSecretKey::random();
let cnr_pk = cnr_sk.main_pubkey();
let cnr_di = DerivationIndex::random(&mut rng);
let cnr_upk = cnr_pk.new_unique_pubkey(&cnr_di);
let spend = SignedSpend::random_spend_to(&mut rng, cnr_upk, 100);
let available_cash_notes = vec![CashNote {
parent_spends: BTreeSet::from_iter([spend]),
main_pubkey: cnr_pk,
derivation_index: cnr_di,
}];
let recipients = vec![
(
NanoTokens::from(1),
MainSecretKey::random().main_pubkey(),
DerivationIndex::random(&mut rng),
false,
),
(
NanoTokens::from(1),
MainSecretKey::random().main_pubkey(),
DerivationIndex::random(&mut rng),
false,
),
];
let change_to = MainSecretKey::random().main_pubkey();
let input_reason_hash = Default::default();
let tx = UnsignedTransaction::new(
available_cash_notes,
recipients,
change_to,
input_reason_hash,
)
.expect("UnsignedTransaction creation to succeed");
let signed_tx = tx.sign(&cnr_sk).expect("Sign to succeed");
let res = signed_tx.verify();
assert_eq!(res, Ok(()));
Ok(())
}
}