Skip to main content

bsv_rs/script/
transaction.rs

1//! Transaction interface traits for Script module integration.
2//!
3//! This module defines traits that the future Transaction module will implement
4//! to integrate with the Script module for spend validation.
5//!
6//! The traits provide a clean abstraction layer that allows the Script module
7//! to work with transaction data without depending on specific Transaction
8//! implementations.
9//!
10//! # Example Implementation
11//!
12//! ```rust,ignore
13//! use bsv_rs::script::transaction::{TransactionInputContext, TransactionOutputContext};
14//!
15//! impl TransactionInputContext for TransactionInput {
16//!     fn source_txid(&self) -> &[u8; 32] { &self.prev_txid }
17//!     fn source_output_index(&self) -> u32 { self.prev_output_index }
18//!     fn sequence(&self) -> u32 { self.sequence }
19//!     fn source_satoshis(&self) -> Option<u64> { self.source_satoshis }
20//!     fn source_locking_script(&self) -> Option<&LockingScript> { self.source_script.as_ref() }
21//! }
22//! ```
23
24use super::{LockingScript, UnlockingScript};
25
26// ============================================================================
27// Transaction Input Context
28// ============================================================================
29
30/// Context from a transaction input needed for script validation.
31///
32/// This trait abstracts the transaction input data needed by the Script module
33/// to validate spends. The Transaction module will implement this trait.
34pub trait TransactionInputContext {
35    /// The transaction ID of the UTXO being spent (32 bytes, internal byte order).
36    fn source_txid(&self) -> &[u8; 32];
37
38    /// The index of the output being spent in the source transaction.
39    fn source_output_index(&self) -> u32;
40
41    /// The sequence number of this input.
42    fn sequence(&self) -> u32;
43
44    /// The satoshi value of the UTXO being spent.
45    ///
46    /// Returns `None` if the source transaction data is not available.
47    /// This is required for sighash computation (BIP-143).
48    fn source_satoshis(&self) -> Option<u64>;
49
50    /// The locking script of the UTXO being spent.
51    ///
52    /// Returns `None` if the source transaction data is not available.
53    fn source_locking_script(&self) -> Option<&LockingScript>;
54
55    /// The unlocking script for this input.
56    fn unlocking_script(&self) -> &UnlockingScript;
57}
58
59// ============================================================================
60// Transaction Output Context
61// ============================================================================
62
63/// Context from a transaction output.
64///
65/// This trait abstracts transaction output data. The Transaction module
66/// will implement this trait.
67pub trait TransactionOutputContext {
68    /// The satoshi value of this output.
69    fn satoshis(&self) -> u64;
70
71    /// The locking script of this output.
72    fn locking_script(&self) -> &LockingScript;
73}
74
75// ============================================================================
76// Transaction Context
77// ============================================================================
78
79/// Full transaction context for script validation.
80///
81/// This trait provides all the data needed by the Spend validator to
82/// validate a transaction input. The Transaction module will implement
83/// this trait.
84pub trait TransactionContext {
85    /// The input type implementing TransactionInputContext.
86    type Input: TransactionInputContext;
87
88    /// The output type implementing TransactionOutputContext.
89    type Output: TransactionOutputContext;
90
91    /// Transaction version.
92    fn version(&self) -> i32;
93
94    /// All inputs in this transaction.
95    fn inputs(&self) -> &[Self::Input];
96
97    /// All outputs in this transaction.
98    fn outputs(&self) -> &[Self::Output];
99
100    /// The lock time.
101    fn lock_time(&self) -> u32;
102
103    /// Get a specific input by index.
104    fn input(&self, index: usize) -> Option<&Self::Input> {
105        self.inputs().get(index)
106    }
107
108    /// Get a specific output by index.
109    fn output(&self, index: usize) -> Option<&Self::Output> {
110        self.outputs().get(index)
111    }
112
113    /// Number of inputs.
114    fn input_count(&self) -> usize {
115        self.inputs().len()
116    }
117
118    /// Number of outputs.
119    fn output_count(&self) -> usize {
120        self.outputs().len()
121    }
122}
123
124// ============================================================================
125// Spend Validation Extension
126// ============================================================================
127
128/// Extension trait for validating all inputs of a transaction.
129///
130/// This trait provides convenient methods for validating transaction spends
131/// when the transaction implements `TransactionContext`.
132pub trait SpendValidation: TransactionContext {
133    /// Validate a specific input by index.
134    ///
135    /// Returns `Ok(true)` if the input is valid, `Ok(false)` if invalid,
136    /// or `Err` with details if validation fails with an error.
137    fn validate_input(
138        &self,
139        index: usize,
140    ) -> Result<bool, Box<crate::script::ScriptEvaluationError>>;
141
142    /// Validate all inputs in the transaction.
143    ///
144    /// Returns `Ok(())` if all inputs are valid, or the first error encountered.
145    fn validate_all_inputs(&self) -> Result<(), Box<crate::script::ScriptEvaluationError>> {
146        for i in 0..self.input_count() {
147            match self.validate_input(i) {
148                Ok(true) => continue,
149                Ok(false) => {
150                    return Err(Box::new(crate::script::ScriptEvaluationError {
151                        message: format!("Input {} validation returned false", i),
152                        source_txid: String::new(),
153                        source_output_index: 0,
154                        context: crate::script::ExecutionContext::UnlockingScript,
155                        program_counter: 0,
156                        stack: vec![],
157                        alt_stack: vec![],
158                        if_stack: vec![],
159                        stack_mem: 0,
160                        alt_stack_mem: 0,
161                    }));
162                }
163                Err(e) => return Err(e),
164            }
165        }
166        Ok(())
167    }
168}
169
170// ============================================================================
171// UTXO Provider
172// ============================================================================
173
174/// Trait for looking up UTXOs for spend validation.
175///
176/// Transaction validation requires knowing the locking scripts and values
177/// of the UTXOs being spent. This trait abstracts UTXO lookup.
178pub trait UtxoProvider {
179    /// Look up a UTXO by transaction ID and output index.
180    ///
181    /// Returns the satoshi value and locking script if found.
182    fn get_utxo(&self, txid: &[u8; 32], output_index: u32) -> Option<(u64, LockingScript)>;
183}
184
185// ============================================================================
186// Simple Implementations for Testing
187// ============================================================================
188
189/// A simple UTXO for testing purposes.
190#[derive(Debug, Clone)]
191pub struct SimpleUtxo {
192    /// Satoshi value.
193    pub satoshis: u64,
194    /// Locking script.
195    pub locking_script: LockingScript,
196}
197
198impl TransactionOutputContext for SimpleUtxo {
199    fn satoshis(&self) -> u64 {
200        self.satoshis
201    }
202
203    fn locking_script(&self) -> &LockingScript {
204        &self.locking_script
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn test_simple_utxo() {
214        let utxo = SimpleUtxo {
215            satoshis: 100_000,
216            locking_script: LockingScript::from_asm("OP_DUP OP_HASH160").unwrap(),
217        };
218
219        assert_eq!(utxo.satoshis(), 100_000);
220        assert!(utxo.locking_script().to_asm().contains("OP_DUP"));
221    }
222}