1use alloc::vec::Vec;
4
5use hashes::{sha256, sha256d};
6use primitives::{Transaction, TxOut};
7
8use crate::{TidecoinValidationError, WitnessProgram};
9
10#[derive(Debug, Clone, Copy)]
12pub struct TransactionContext<'tx> {
13 tx: &'tx Transaction,
14}
15
16impl<'tx> TransactionContext<'tx> {
17 pub fn new(tx: &'tx Transaction) -> Self {
19 Self { tx }
20 }
21
22 pub fn tx(&self) -> &'tx Transaction {
24 self.tx
25 }
26
27 pub fn ensure_input_index(&self, input_index: usize) -> Result<(), TidecoinValidationError> {
29 if input_index >= self.tx.inputs.len() {
30 Err(TidecoinValidationError::InvalidInputIndex {
31 index: input_index,
32 inputs: self.tx.inputs.len(),
33 })
34 } else {
35 Ok(())
36 }
37 }
38
39 pub fn build_precomputed(
41 &self,
42 spent_outputs: Option<&SpentOutputs>,
43 force: bool,
44 ) -> PrecomputedTransactionData {
45 PrecomputedTransactionData::new(self.tx, spent_outputs, force)
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct SpentOutputs {
52 txouts: Vec<TxOut>,
53}
54
55impl SpentOutputs {
56 pub fn from_txouts(txouts: Vec<TxOut>) -> Self {
58 Self { txouts }
59 }
60
61 pub fn txouts(&self) -> &[TxOut] {
63 &self.txouts
64 }
65}
66
67#[derive(Debug, Clone, Default)]
69pub struct PrecomputedTransactionData {
70 pub prevouts_hash32: Option<sha256::Hash>,
72 pub sequences_hash32: Option<sha256::Hash>,
74 pub outputs_hash32: Option<sha256::Hash>,
76 pub spent_amounts_hash32: Option<sha256::Hash>,
78 pub spent_scripts_hash32: Option<sha256::Hash>,
80 pub hash_prevouts: Option<sha256d::Hash>,
82 pub hash_sequence: Option<sha256d::Hash>,
84 pub hash_outputs: Option<sha256d::Hash>,
86 pub witness_v0_ready: bool,
88 pub witness_v1_ready: bool,
90}
91
92impl PrecomputedTransactionData {
93 pub fn new(tx: &Transaction, spent_outputs: Option<&SpentOutputs>, force: bool) -> Self {
95 let mut data = Self::default();
96
97 let (mut uses_witness_v0, mut uses_witness_v1) = (force, force);
98
99 for (idx, input) in tx.inputs.iter().enumerate() {
100 if input.witness.is_empty() {
101 continue;
102 }
103
104 if let Some(spent) = spent_outputs {
105 if WitnessProgram::is_v1_512(spent.txouts[idx].script_pubkey.as_bytes()) {
106 uses_witness_v1 = true;
107 } else {
108 uses_witness_v0 = true;
109 }
110 } else {
111 uses_witness_v0 = true;
112 }
113
114 if uses_witness_v0 && uses_witness_v1 {
115 break;
116 }
117 }
118
119 if uses_witness_v0 || uses_witness_v1 {
120 let prevouts = hash_prevouts_single(tx);
121 let sequences = hash_sequences_single(tx);
122 let outputs = hash_outputs_single(tx);
123 data.prevouts_hash32 = Some(prevouts);
124 data.sequences_hash32 = Some(sequences);
125 data.outputs_hash32 = Some(outputs);
126 }
127
128 if uses_witness_v0 {
129 data.hash_prevouts = Some(hash_prevouts_double(tx));
130 data.hash_sequence = Some(hash_sequences_double(tx));
131 data.hash_outputs = Some(hash_outputs_double(tx));
132 data.witness_v0_ready = true;
133 }
134
135 if uses_witness_v1 {
136 if let Some(spent) = spent_outputs {
137 data.spent_amounts_hash32 = Some(hash_spent_amounts_single(spent));
138 data.spent_scripts_hash32 = Some(hash_spent_scripts_single(spent));
139 data.witness_v1_ready = true;
140 }
141 }
142
143 data
144 }
145}
146
147fn hash_prevouts_single(tx: &Transaction) -> sha256::Hash {
148 hash_serialized(tx.inputs.iter().map(|input| &input.previous_output))
149}
150
151fn hash_sequences_single(tx: &Transaction) -> sha256::Hash {
152 hash_serialized(tx.inputs.iter().map(|input| &input.sequence))
153}
154
155fn hash_outputs_single(tx: &Transaction) -> sha256::Hash {
156 hash_serialized(tx.outputs.iter())
157}
158
159fn hash_prevouts_double(tx: &Transaction) -> sha256d::Hash {
160 hash_serialized_double(tx.inputs.iter().map(|input| &input.previous_output))
161}
162
163fn hash_sequences_double(tx: &Transaction) -> sha256d::Hash {
164 hash_serialized_double(tx.inputs.iter().map(|input| &input.sequence))
165}
166
167fn hash_outputs_double(tx: &Transaction) -> sha256d::Hash {
168 hash_serialized_double(tx.outputs.iter())
169}
170
171fn hash_spent_amounts_single(spent: &SpentOutputs) -> sha256::Hash {
172 let mut engine = sha256::Hash::engine();
173 for txout in spent.txouts() {
174 io::encode_to_writer(&txout.amount, &mut engine)
175 .expect("hash engine writes are infallible");
176 }
177 sha256::Hash::from_engine(engine)
178}
179
180fn hash_spent_scripts_single(spent: &SpentOutputs) -> sha256::Hash {
181 hash_serialized(spent.txouts().iter().map(|txout| txout.script_pubkey.as_script()))
182}
183
184fn hash_serialized<'a, I, T>(items: I) -> sha256::Hash
185where
186 I: IntoIterator<Item = &'a T>,
187 T: encoding::Encodable + 'a + ?Sized,
188{
189 let mut engine = sha256::Hash::engine();
190 for item in items {
191 io::encode_to_writer(item, &mut engine).expect("hash engine writes are infallible");
192 }
193 sha256::Hash::from_engine(engine)
194}
195
196fn hash_serialized_double<'a, I, T>(items: I) -> sha256d::Hash
197where
198 I: IntoIterator<Item = &'a T>,
199 T: encoding::Encodable + 'a + ?Sized,
200{
201 let mut engine = sha256d::Hash::engine();
202 for item in items {
203 io::encode_to_writer(item, &mut engine).expect("hash engine writes are infallible");
204 }
205 sha256d::Hash::from_engine(engine)
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use alloc::vec;
212 use encoding::encode_to_vec;
213 use primitives::{
214 absolute::LockTime, transaction::Version, Amount, OutPoint, ScriptPubKeyBuf, Sequence,
215 TxIn, Txid, Witness,
216 };
217
218 #[test]
219 fn borrows_transaction_and_detects_hashes() {
220 let tx = Transaction {
221 version: Version::TWO,
222 lock_time: LockTime::ZERO,
223 inputs: vec![TxIn {
224 previous_output: OutPoint { txid: Txid::from_byte_array([1u8; 32]), vout: 0 },
225 script_sig: primitives::ScriptSigBuf::new(),
226 sequence: Sequence::MAX,
227 witness: Witness::new(),
228 }],
229 outputs: vec![TxOut {
230 amount: Amount::from_sat(42).expect("valid amount"),
231 script_pubkey: ScriptPubKeyBuf::new(),
232 }],
233 };
234
235 let _encoded = encode_to_vec(&tx);
236 let ctx = TransactionContext::new(&tx);
237
238 assert_eq!(ctx.tx().compute_txid(), tx.compute_txid());
239
240 let pre = ctx.build_precomputed(None, true);
241 assert!(pre.hash_prevouts.is_some());
242 assert!(pre.hash_sequence.is_some());
243 assert!(pre.hash_outputs.is_some());
244 }
245
246 #[test]
247 fn spent_outputs_from_txouts() {
248 let spent = SpentOutputs::from_txouts(vec![TxOut {
249 amount: Amount::from_sat(10).expect("valid amount"),
250 script_pubkey: ScriptPubKeyBuf::from_bytes([0x51u8; 34].to_vec()),
251 }]);
252 assert_eq!(spent.txouts().len(), 1);
253 assert_eq!(spent.txouts()[0].amount.to_sat(), 10);
254 }
255
256 #[test]
257 fn precomputed_marks_witness_v1_ready_when_prevouts_known() {
258 let witness_v1_script =
259 ScriptPubKeyBuf::from_bytes([vec![0x51, 64], vec![0u8; 64]].concat());
260
261 let tx = Transaction {
262 version: Version::TWO,
263 lock_time: LockTime::ZERO,
264 inputs: vec![TxIn {
265 previous_output: OutPoint { txid: Txid::from_byte_array([2u8; 32]), vout: 0 },
266 script_sig: primitives::ScriptSigBuf::new(),
267 sequence: Sequence::MAX,
268 witness: Witness::from(vec![vec![0x01]]),
269 }],
270 outputs: vec![TxOut {
271 amount: Amount::from_sat(100).expect("valid amount"),
272 script_pubkey: ScriptPubKeyBuf::new(),
273 }],
274 };
275
276 let spent = SpentOutputs::from_txouts(vec![TxOut {
277 amount: Amount::from_sat(50).expect("valid amount"),
278 script_pubkey: witness_v1_script,
279 }]);
280
281 let ctx = TransactionContext::new(&tx);
282 let pre = ctx.build_precomputed(Some(&spent), false);
283 assert!(pre.witness_v1_ready);
284 assert!(pre.spent_amounts_hash32.is_some());
285 assert!(pre.spent_scripts_hash32.is_some());
286 assert!(!pre.witness_v0_ready);
287 }
288}