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, Hash)]
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, Hash)]
79pub struct TransactionOutput {
80 pub amount: u64,
81 pub receiver: Public,
82}
83
84#[derive(Encode, Decode, Debug, Clone, Serialize, Deserialize, PartialEq, Hash)]
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 let scale = 1.0 - margin; let scale_int = (scale * 1_000_000.0) as u64;
137 target *= BigUint::from(scale_int);
138 target /= BigUint::from(1_000_000u64);
139 }
140 }
141 let mut rng = rand::rng();
142 loop {
143 self.nonce = rng.random();
144 let hashing_buf = self.get_tx_hashing_buf()?;
145 if BigUint::from_bytes_be(&*Hash::new(&hashing_buf)) <= target {
146 self.transaction_id = Some(Hash::new(&hashing_buf));
147 return Ok(());
148 }
149 }
150 }
151
152 pub fn get_input_signing_buf(&self) -> Result<Vec<u8>, EncodeError> {
154 let mut signature_less_transaction: Transaction = self.clone();
155
156 signature_less_transaction.transaction_id = None;
157 signature_less_transaction.nonce = 0;
158
159 for input in &mut signature_less_transaction.inputs {
160 input.signature = None; }
162
163 Ok(bincode::encode_to_vec(
164 signature_less_transaction,
165 bincode::config::standard(),
166 )?)
167 }
168
169 pub fn get_tx_hashing_buf(&self) -> Result<Vec<u8>, EncodeError> {
171 let mut signature_less_transaction = self.clone();
172
173 signature_less_transaction.transaction_id = None;
174
175 Ok(bincode::encode_to_vec(
176 signature_less_transaction,
177 bincode::config::standard(),
178 )?)
179 }
180
181 pub fn check_completeness(&self) -> Result<(), TransactionError> {
182 self.transaction_id.ok_or(TransactionError::MissingId)?;
183 for input in &self.inputs {
184 input.signature.ok_or(TransactionError::MissingSignatures)?;
185 }
186
187 Ok(())
188 }
189
190 pub fn address_count(&self) -> usize {
191 self.inputs.len() + self.outputs.len()
192 }
193
194 pub fn contains_address(&self, address: Public) -> bool {
196 for input in &self.inputs {
197 if input.output_owner == address {
198 return true;
199 }
200 }
201 for output in &self.outputs {
202 if output.receiver == address {
203 return true;
204 }
205 }
206 false
207 }
208}