snap_coin/core/
transaction.rs1use bincode::{Decode, Encode, error::EncodeError};
2use num_bigint::BigUint;
3use rand::Rng;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::crypto::{
8 Hash, Signature,
9 keys::{Private, Public},
10};
11
12pub type TransactionId = Hash;
14
15pub const MAX_TRANSACTION_IO: usize = 150;
16
17#[derive(Error, Debug)]
18pub enum TransactionError {
19 #[error("{0}")]
20 EncodeError(#[from] EncodeError),
21
22 #[error("Transaction missing ID")]
23 MissingId,
24
25 #[error("Transaction missing signature/s")]
26 MissingSignatures,
27
28 #[error("Transaction hash is invalid: {0}")]
29 InvalidHash(String),
30
31 #[error("Transaction hash does not meet required difficulty: {0}")]
32 InsufficientDifficulty(String),
33
34 #[error("Transaction has no inputs")]
35 NoInputs,
36
37 #[error("Transaction input not found in UTXOs: {0}")]
38 InputNotFound(String),
39
40 #[error("Transaction input index invalid for transaction {tx_id}: {input_tx_id}")]
41 InvalidInputIndex { tx_id: String, input_tx_id: String },
42
43 #[error("Referenced transaction input is already spent")]
44 SpentInputIndex,
45
46 #[error("Transaction input signature is invalid for transaction {0}")]
47 InvalidSignature(String),
48
49 #[error("Transaction input output owner is invalid for transaction {0}")]
50 IncorrectOutputOwner(String),
51
52 #[error("Double spending detected in the same transaction {0}")]
53 DoubleSpend(String),
54
55 #[error("Transaction output amount cannot be zero for transaction {0}")]
56 ZeroOutput(String),
57
58 #[error("Transaction inputs and outputs don't sum up to same amount for transaction {0}")]
59 SumMismatch(String),
60
61 #[error("Transaction has too many inputs or outputs")]
62 TooMuchIO,
63
64 #[error("{0}")]
65 Other(String),
66}
67
68#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
70pub struct TransactionInput {
71 pub transaction_id: TransactionId,
72 pub output_index: usize,
73 pub signature: Option<Signature>,
74 pub output_owner: Public,
75}
76
77#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
79pub struct TransactionOutput {
80 pub amount: u64,
81 pub receiver: Public,
82}
83
84#[derive(Encode, Decode, Debug, Clone, Serialize, Deserialize)]
89pub struct Transaction {
90 pub inputs: Vec<TransactionInput>,
91 pub outputs: Vec<TransactionOutput>,
92 pub transaction_id: Option<TransactionId>,
93 pub nonce: u64,
94 pub timestamp: u64,
95}
96
97impl Transaction {
98 pub fn new_transaction_now(
101 inputs: Vec<TransactionInput>,
102 outputs: Vec<TransactionOutput>,
103 signing_keys: &mut Vec<Private>,
104 ) -> Result<Self, EncodeError> {
105 let mut transaction = Self {
106 inputs,
107 outputs,
108 transaction_id: None,
109 nonce: 0,
110 timestamp: chrono::Utc::now().timestamp() as u64,
111 };
112 let signing_buf = transaction.get_input_signing_buf()?;
113
114 for (input, key) in transaction.inputs.iter_mut().zip(signing_keys.iter_mut()) {
115 input.signature = Some(Signature::new_signature(key, &signing_buf));
116 }
117
118 Ok(transaction)
119 }
120
121 pub fn compute_pow(
127 &mut self,
128 live_tx_difficulty: &[u8; 32],
129 difficulty_margin: Option<f64>,
130 ) -> Result<(), EncodeError> {
131 let mut target = BigUint::from_bytes_be(live_tx_difficulty);
132
133 if let Some(margin) = difficulty_margin {
134 if margin > 0.0 {
135 target /= BigUint::from((margin * 1000.0) as u64);
137 }
138 }
139 let mut rng = rand::rng();
140 loop {
141 self.nonce = rng.random();
142 let hashing_buf = self.get_tx_hashing_buf()?;
143 if BigUint::from_bytes_be(&*Hash::new(&hashing_buf)) <= target {
144 self.transaction_id = Some(Hash::new(&hashing_buf));
145 return Ok(());
146 }
147 }
148 }
149
150 pub fn get_input_signing_buf(&self) -> Result<Vec<u8>, EncodeError> {
152 let mut signature_less_transaction: Transaction = self.clone();
153
154 signature_less_transaction.transaction_id = None;
155 signature_less_transaction.nonce = 0;
156
157 for input in &mut signature_less_transaction.inputs {
158 input.signature = None; }
160
161 Ok(bincode::encode_to_vec(
162 signature_less_transaction,
163 bincode::config::standard(),
164 )?)
165 }
166
167 pub fn get_tx_hashing_buf(&self) -> Result<Vec<u8>, EncodeError> {
169 let mut signature_less_transaction = self.clone();
170
171 signature_less_transaction.transaction_id = None;
172
173 Ok(bincode::encode_to_vec(
174 signature_less_transaction,
175 bincode::config::standard(),
176 )?)
177 }
178
179 pub fn check_completeness(&self) -> Result<(), TransactionError> {
180 self.transaction_id.ok_or(TransactionError::MissingId)?;
181 for input in &self.inputs {
182 input.signature.ok_or(TransactionError::MissingSignatures)?;
183 }
184
185 Ok(())
186 }
187
188 pub fn address_count(&self) -> usize {
189 self.inputs.len() + self.outputs.len()
190 }
191
192 pub fn contains_address(&self, address: Public) -> bool {
194 for input in &self.inputs {
195 if input.output_owner == address {
196 return true;
197 }
198 }
199 for output in &self.outputs {
200 if output.receiver == address {
201 return true;
202 }
203 }
204 false
205 }
206}