bsv_wasm/transaction/
txin.rs

1use crate::BSVErrors;
2use crate::VarInt;
3use crate::VarIntReader;
4use crate::VarIntWriter;
5use std::io::Cursor;
6use std::io::Read;
7use std::io::Write;
8
9use crate::{
10    utils::{from_reverse_hex, to_reverse_hex},
11    Script,
12};
13use serde::*;
14#[cfg(target_arch = "wasm32")]
15use wasm_bindgen::{prelude::*, throw_str, JsValue};
16
17use byteorder::*;
18
19#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen)]
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct TxIn {
22    #[serde(serialize_with = "to_reverse_hex", deserialize_with = "from_reverse_hex")]
23    pub(crate) prev_tx_id: Vec<u8>,
24    pub(crate) vout: u32,
25    pub(crate) script_sig: Script,
26    pub(crate) sequence: u32,
27
28    /**
29     * Part of the extended transaction serialisation format.
30     * TODO: Rename this to locking script (it is the representation of this TxIn's past life as a UTXO)
31     */
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub(crate) unlocking_script: Option<Script>,
34    /**
35     * Part of the extended transaction serialisation format.
36     */
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub(crate) satoshis: Option<u64>,
39}
40
41impl TxIn {
42    pub(crate) fn get_finalised_script_impl(&self) -> Result<Script, BSVErrors> {
43        match self.unlocking_script.as_ref() {
44            // If there is a specified unlocking script, prepend it to the locking script
45            Some(unlock_script) => {
46                let mut script_sig_bytes = self.script_sig.to_bytes();
47                let unlock_script_bytes = unlock_script.to_bytes();
48
49                script_sig_bytes.extend_from_slice(&unlock_script_bytes);
50                Script::from_bytes_impl(&script_sig_bytes)
51            }
52            None => Ok(self.script_sig.clone()),
53        }
54    }
55
56    pub(crate) fn from_hex_impl(hex_str: &str) -> Result<TxIn, BSVErrors> {
57        let txin_bytes = hex::decode(hex_str)?;
58
59        let mut cursor = Cursor::new(txin_bytes);
60
61        TxIn::read_in(&mut cursor)
62    }
63
64    pub(crate) fn is_coinbase_outpoint_impl(prev_tx_id: &Vec<u8>, vout: &u32) -> bool {
65        prev_tx_id == &vec![0u8; 32] && vout == &0xFFFFFFFF
66    }
67
68    pub(crate) fn is_coinbase_impl(&self) -> bool {
69        TxIn::is_coinbase_outpoint_impl(&self.prev_tx_id, &self.vout)
70    }
71
72    pub(crate) fn read_in(cursor: &mut Cursor<Vec<u8>>) -> Result<TxIn, BSVErrors> {
73        // PrevTxId - 32 bytes
74        let mut prev_tx_id = vec![0; 32];
75        if let Err(e) = cursor.read(&mut prev_tx_id) {
76            return Err(BSVErrors::DeserialiseTxIn("prev_tx_id".to_string(), e));
77        }
78        // Error in the original bitcoin client means that all txids in TxIns are reversed
79        prev_tx_id.reverse();
80
81        // VOut - 4 bytes
82        let vout = match cursor.read_u32::<LittleEndian>() {
83            Ok(v) => v,
84            Err(e) => return Err(BSVErrors::DeserialiseTxIn("vout".to_string(), e)),
85        };
86
87        // Script Sig Size - VarInt
88        let script_sig_size = match cursor.read_varint() {
89            Ok(v) => v,
90            Err(e) => return Err(BSVErrors::DeserialiseTxIn("script_sig_size".to_string(), e)),
91        };
92
93        // Script Sig
94        let mut script_sig = vec![0; script_sig_size as usize];
95        if let Err(e) = cursor.read(&mut script_sig) {
96            return Err(BSVErrors::DeserialiseTxIn("script_sig".to_string(), e));
97        }
98
99        // Sequence - 4 bytes
100        let sequence = match cursor.read_u32::<LittleEndian>() {
101            Ok(v) => v,
102            Err(e) => return Err(BSVErrors::DeserialiseTxIn("sequence".to_string(), e)),
103        };
104
105        Ok(TxIn {
106            script_sig: match TxIn::is_coinbase_outpoint_impl(&prev_tx_id, &vout) {
107                true => Script::from_coinbase_bytes_impl(&script_sig)?,
108                false => Script::from_bytes_impl(&script_sig)?,
109            },
110            prev_tx_id,
111            vout,
112            sequence,
113            satoshis: None,
114            unlocking_script: None,
115        })
116    }
117
118    pub(crate) fn to_bytes_impl(&self) -> Result<Vec<u8>, BSVErrors> {
119        let mut buffer = vec![];
120
121        // Bitcoin TX Hex serialises txids in reverse.
122        let mut prev_tx_id = self.prev_tx_id.clone();
123        prev_tx_id.reverse();
124        // Write Prev TxID first
125        if let Err(e) = buffer.write(&prev_tx_id) {
126            return Err(BSVErrors::SerialiseTxIn("prev_tx_id".to_string(), e));
127        }
128
129        // Vout
130        if let Err(e) = buffer.write_u32::<LittleEndian>(self.vout) {
131            return Err(BSVErrors::SerialiseTxIn("vout".to_string(), e));
132        }
133
134        let finalised_script = self.script_sig.clone();
135
136        // Script Sig Size
137        if let Err(e) = buffer.write_varint(finalised_script.get_script_length() as u64) {
138            return Err(BSVErrors::SerialiseTxIn("script_sig_size".to_string(), e));
139        }
140
141        // Script Sig
142        if let Err(e) = buffer.write(&finalised_script.to_bytes()) {
143            return Err(BSVErrors::SerialiseTxIn("script_sig".to_string(), e));
144        }
145
146        // Sequence
147        if let Err(e) = buffer.write_u32::<LittleEndian>(self.sequence) {
148            return Err(BSVErrors::SerialiseTxIn("sequence".to_string(), e));
149        }
150
151        // Write out bytes
152        Ok(buffer)
153    }
154
155    pub(crate) fn to_hex_impl(&self) -> Result<String, BSVErrors> {
156        Ok(hex::encode(&self.to_bytes_impl()?))
157    }
158
159    pub(crate) fn to_json_string_impl(&self) -> Result<String, BSVErrors> {
160        let json = serde_json::to_string_pretty(self)?;
161        Ok(json)
162    }
163
164    pub(crate) fn from_outpoint_bytes_impl(outpoint: &[u8]) -> Result<TxIn, BSVErrors> {
165        if outpoint.len() != 36 {
166            return Err(BSVErrors::OutOfBounds("An Outpoint must be precisely 36 bytes long".into()));
167        }
168
169        let mut tx_in = TxIn::default();
170
171        let mut outpoint_bytes = outpoint[0..32].to_vec();
172        outpoint_bytes.reverse();
173
174        let vout = u32::from_le_bytes([outpoint[32], outpoint[33], outpoint[34], outpoint[35]]);
175
176        tx_in.set_prev_tx_id(&outpoint_bytes);
177        tx_in.set_vout(vout);
178
179        Ok(tx_in)
180    }
181
182    /**
183     * Deserialises the provided buffer to the TX+ format
184     */
185    pub(crate) fn from_compact_bytes_impl(compact_buffer: &[u8]) -> Result<Self, BSVErrors> {
186        let tx = ciborium::de::from_reader(compact_buffer)?;
187        Ok(tx)
188    }
189
190    /**
191     * Serialises this entire transaction to CBOR, preserving all fields from the standard Transaction format + TX+
192     */
193    pub(crate) fn to_compact_bytes_impl(&self) -> Result<Vec<u8>, BSVErrors> {
194        let mut buffer = vec![];
195        ciborium::ser::into_writer(&self, &mut buffer)?;
196        Ok(buffer)
197    }
198}
199
200/**
201 * Platform Agnostic Functions
202 * ie. Don't need Result<T, E>
203 */
204#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen)]
205impl TxIn {
206    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(constructor))]
207    pub fn new(prev_tx_id: &[u8], vout: u32, script_sig: &Script, sequence: Option<u32>) -> TxIn {
208        TxIn {
209            prev_tx_id: prev_tx_id.to_vec(),
210            vout,
211            script_sig: script_sig.clone(),
212            sequence: match sequence {
213                Some(v) => v,
214                None => u32::MAX,
215            },
216            satoshis: None,
217            unlocking_script: None,
218        }
219    }
220
221    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen)]
222    pub fn default() -> TxIn {
223        TxIn {
224            prev_tx_id: vec![],
225            satoshis: None,
226            script_sig: Script::default(),
227            sequence: u32::MAX,
228            unlocking_script: None,
229            vout: 0,
230        }
231    }
232
233    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getPrevTxId))]
234    pub fn get_prev_tx_id(&self, little_endian: Option<bool>) -> Vec<u8> {
235        match little_endian {
236            Some(true) => {
237                let mut reversed_tx = self.prev_tx_id.clone();
238                reversed_tx.reverse();
239                reversed_tx
240            }
241            _ => self.prev_tx_id.clone(),
242        }
243    }
244
245    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getPrevTxIdHex))]
246    pub fn get_prev_tx_id_hex(&self, little_endian: Option<bool>) -> String {
247        hex::encode(self.get_prev_tx_id(little_endian))
248    }
249
250    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getVOut))]
251    pub fn get_vout(&self) -> u32 {
252        self.vout
253    }
254
255    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getScriptSigSize))]
256    pub fn get_script_sig_size(&self) -> u64 {
257        self.script_sig.get_script_length() as u64
258    }
259
260    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getScriptSig))]
261    pub fn get_script_sig(&self) -> Script {
262        self.script_sig.clone()
263    }
264
265    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getScriptSigHex))]
266    pub fn get_script_sig_hex(&self) -> String {
267        hex::encode(self.script_sig.to_bytes())
268    }
269
270    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getSequence))]
271    pub fn get_sequence(&self) -> u32 {
272        self.sequence
273    }
274
275    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getSequenceAsBytes))]
276    pub fn get_sequence_as_bytes(&self) -> Vec<u8> {
277        self.sequence.to_be_bytes().to_vec()
278    }
279
280    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getOutpointBytes))]
281    pub fn get_outpoint_bytes(&self, little_endian: Option<bool>) -> Vec<u8> {
282        let mut outpoint_bytes = self.get_prev_tx_id(little_endian);
283        outpoint_bytes.extend_from_slice(&self.vout.to_le_bytes());
284        outpoint_bytes
285    }
286
287    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getOutpointHex))]
288    pub fn get_outpoint_hex(&self, little_endian: Option<bool>) -> String {
289        hex::encode(self.get_outpoint_bytes(little_endian))
290    }
291
292    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = setScript))]
293    pub fn set_script(&mut self, script: &Script) {
294        self.script_sig = script.clone();
295    }
296
297    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = setPrevTxId))]
298    pub fn set_prev_tx_id(&mut self, txid: &[u8]) {
299        self.prev_tx_id = txid.to_vec();
300    }
301
302    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = setVOut))]
303    pub fn set_vout(&mut self, vout: u32) {
304        self.vout = vout;
305    }
306
307    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = setSequence))]
308    pub fn set_sequence(&mut self, sequence: u32) {
309        self.sequence = sequence;
310    }
311
312    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = setSatoshis))]
313    pub fn set_satoshis(&mut self, satoshis: u64) {
314        self.satoshis = Some(satoshis);
315    }
316
317    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getSatoshis))]
318    pub fn get_satoshis(&self) -> Option<u64> {
319        self.satoshis
320    }
321
322    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = setUnlockingScript))]
323    pub fn set_unlocking_script(&mut self, unlocking_script: &Script) {
324        self.unlocking_script = Some(unlocking_script.clone());
325    }
326
327    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getUnlockingScript))]
328    pub fn get_unlocking_script(&self) -> Option<Script> {
329        self.unlocking_script.clone()
330    }
331
332    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getUnlockingScriptBytes))]
333    pub fn get_unlocking_script_bytes(&self) -> Option<Vec<u8>> {
334        self.unlocking_script.as_ref().map(|v| v.to_bytes())
335    }
336}
337
338/**
339 * WASM Specific Functions
340 */
341#[cfg(target_arch = "wasm32")]
342#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen)]
343impl TxIn {
344    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = fromHex))]
345    pub fn from_hex(hex_str: &str) -> Result<TxIn, JsValue> {
346        match TxIn::from_hex_impl(hex_str) {
347            Ok(v) => Ok(v),
348            Err(e) => Err(JsValue::from_str(&e.to_string())),
349        }
350    }
351
352    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = toJSON))]
353    pub fn to_json(&self) -> Result<JsValue, JsValue> {
354        match JsValue::from_serde(&self) {
355            Ok(v) => Ok(v),
356            Err(e) => Err(JsValue::from_str(&e.to_string())),
357        }
358    }
359
360    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = toString))]
361    pub fn to_json_string(&self) -> Result<String, JsValue> {
362        match TxIn::to_json_string_impl(&self) {
363            Ok(v) => Ok(v),
364            Err(e) => Err(JsValue::from_str(&e.to_string())),
365        }
366    }
367
368    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = toBytes))]
369    pub fn to_bytes(&self) -> Result<Vec<u8>, JsValue> {
370        match TxIn::to_bytes_impl(&self) {
371            Ok(v) => Ok(v),
372            Err(e) => Err(JsValue::from_str(&e.to_string())),
373        }
374    }
375
376    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = toHex))]
377    pub fn to_hex(&self) -> Result<String, JsValue> {
378        match TxIn::to_hex_impl(&self) {
379            Ok(v) => Ok(v),
380            Err(e) => Err(JsValue::from_str(&e.to_string())),
381        }
382    }
383
384    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = fromOutpointBytes))]
385    pub fn from_outpoint_bytes(outpoint: &[u8]) -> Result<TxIn, JsValue> {
386        match TxIn::from_outpoint_bytes_impl(outpoint) {
387            Ok(v) => Ok(v),
388            Err(e) => Err(JsValue::from_str(&e.to_string())),
389        }
390    }
391
392    /**
393     * Serialises this entire transaction to CBOR, preserving all fields from the standard Transaction format + TX+
394     */
395    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = toCompactBytes))]
396    pub fn to_compact_bytes(&self) -> Result<Vec<u8>, JsValue> {
397        match self.to_compact_bytes_impl() {
398            Ok(v) => Ok(v),
399            Err(e) => Err(JsValue::from_str(&e.to_string())),
400        }
401    }
402
403    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = toCompactHex))]
404    pub fn to_compact_hex(&self) -> Result<String, JsValue> {
405        match self.to_compact_bytes_impl() {
406            Ok(v) => Ok(hex::encode(v)),
407            Err(e) => Err(JsValue::from_str(&e.to_string())),
408        }
409    }
410
411    /**
412     * Deserialises the provided CBOR buffer to the TX+ format
413     */
414    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = fromCompactBytes))]
415    pub fn from_compact_bytes(compact_buffer: &[u8]) -> Result<TxIn, JsValue> {
416        match TxIn::from_compact_bytes_impl(compact_buffer) {
417            Ok(v) => Ok(v),
418            Err(e) => Err(JsValue::from_str(&e.to_string())),
419        }
420    }
421
422    /**
423     * Deserialises the provided CBOR buffer to the TX+ format
424     */
425    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = fromCompactHex))]
426    pub fn from_compact_hex(compact_hex: String) -> Result<TxIn, JsValue> {
427        let compact_buffer = match hex::decode(compact_hex) {
428            Ok(v) => v,
429            Err(e) => return Err(JsValue::from_str(&e.to_string())),
430        };
431
432        match TxIn::from_compact_bytes_impl(&compact_buffer) {
433            Ok(v) => Ok(v),
434            Err(e) => Err(JsValue::from_str(&e.to_string())),
435        }
436    }
437
438    /// Concatenates ScriptSig and UnlockingScript into a single script.
439    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = getFinalisedScript))]
440    pub fn get_finalised_script(&self) -> Result<Script, JsValue> {
441        match self.get_finalised_script_impl() {
442            Ok(v) => Ok(v),
443            Err(e) => Err(JsValue::from_str(&e.to_string())),
444        }
445    }
446
447    // Checks if input is a coinbase
448    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-transaction"), wasm_bindgen(js_name = isCoinbase))]
449    pub fn is_coinbase(&self) -> bool {
450        self.is_coinbase_impl()
451    }
452}
453
454/**
455 * Native Specific Functions
456 */
457#[cfg(not(target_arch = "wasm32"))]
458impl TxIn {
459    pub fn from_hex(hex_str: &str) -> Result<TxIn, BSVErrors> {
460        TxIn::from_hex_impl(hex_str)
461    }
462
463    pub fn to_bytes(&self) -> Result<Vec<u8>, BSVErrors> {
464        TxIn::to_bytes_impl(self)
465    }
466
467    pub fn to_hex(&self) -> Result<String, BSVErrors> {
468        TxIn::to_hex_impl(self)
469    }
470
471    /**
472     * Serialises this entire transaction to CBOR, preserving all fields from the standard Transaction format + XT
473     */
474    pub fn to_compact_bytes(&self) -> Result<Vec<u8>, BSVErrors> {
475        self.to_compact_bytes_impl()
476    }
477
478    /**
479     * Deserialises the provided CBOR buffer to the XT format
480     */
481    pub fn from_compact_bytes(compact_buffer: &[u8]) -> Result<Self, BSVErrors> {
482        TxIn::from_compact_bytes_impl(compact_buffer)
483    }
484
485    /**
486     * Serialises this entire transaction to CBOR Hex, preserving all fields from the standard Transaction format + XT
487     */
488    pub fn to_compact_hex(&self) -> Result<String, BSVErrors> {
489        Ok(hex::encode(self.to_compact_bytes_impl()?))
490    }
491
492    /**
493     * Deserialises the provided CBOR hex to the XT format
494     */
495    pub fn from_compact_hex(compact_hex: &str) -> Result<Self, BSVErrors> {
496        let compact_buffer = hex::decode(compact_hex)?;
497        TxIn::from_compact_bytes_impl(&compact_buffer)
498    }
499
500    #[cfg(not(target_arch = "wasm32"))]
501    pub fn to_json_string(&self) -> Result<String, BSVErrors> {
502        TxIn::to_json_string_impl(self)
503    }
504
505    #[cfg(not(target_arch = "wasm32"))]
506    pub fn to_json(&self) -> Result<serde_json::Value, BSVErrors> {
507        let json = serde_json::to_value(self)?;
508        Ok(json)
509    }
510
511    #[cfg(not(target_arch = "wasm32"))]
512    pub fn from_outpoint_bytes(outpoint: &[u8]) -> Result<TxIn, BSVErrors> {
513        TxIn::from_outpoint_bytes_impl(outpoint)
514    }
515
516    pub fn get_finalised_script(&self) -> Result<Script, BSVErrors> {
517        self.get_finalised_script_impl()
518    }
519
520    // Checks if input is a coinbase
521    #[cfg(not(target_arch = "wasm32"))]
522    pub fn is_coinbase(&self) -> bool {
523        self.is_coinbase_impl()
524    }
525}