wagyu_bitcoin/
transaction.rs

1use crate::address::BitcoinAddress;
2use crate::amount::BitcoinAmount;
3use crate::format::BitcoinFormat;
4use crate::network::BitcoinNetwork;
5use crate::private_key::BitcoinPrivateKey;
6use crate::public_key::BitcoinPublicKey;
7use crate::witness_program::WitnessProgram;
8use wagyu_model::{PrivateKey, Transaction, TransactionError, TransactionId};
9
10use base58::FromBase58;
11use bech32::{Bech32, FromBase32};
12use secp256k1;
13use serde::Serialize;
14use sha2::{Digest, Sha256};
15use std::{fmt, io::Read, str::FromStr};
16
17/// Returns the variable length integer of the given value.
18/// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
19pub fn variable_length_integer(value: u64) -> Result<Vec<u8>, TransactionError> {
20    match value {
21        // bounded by u8::max_value()
22        0..=252 => Ok(vec![value as u8]),
23        // bounded by u16::max_value()
24        253..=65535 => Ok([vec![0xfd], (value as u16).to_le_bytes().to_vec()].concat()),
25        // bounded by u32::max_value()
26        65536..=4294967295 => Ok([vec![0xfe], (value as u32).to_le_bytes().to_vec()].concat()),
27        // bounded by u64::max_value()
28        _ => Ok([vec![0xff], value.to_le_bytes().to_vec()].concat()),
29    }
30}
31
32/// Decode the value of a variable length integer.
33/// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
34pub fn read_variable_length_integer<R: Read>(mut reader: R) -> Result<usize, TransactionError> {
35    let mut flag = [0u8; 1];
36    reader.read(&mut flag)?;
37
38    match flag[0] {
39        0..=252 => Ok(flag[0] as usize),
40        0xfd => {
41            let mut size = [0u8; 2];
42            reader.read(&mut size)?;
43            match u16::from_le_bytes(size) {
44                s if s < 253 => return Err(TransactionError::InvalidVariableSizeInteger(s as usize)),
45                s => Ok(s as usize),
46            }
47        }
48        0xfe => {
49            let mut size = [0u8; 4];
50            reader.read(&mut size)?;
51            match u32::from_le_bytes(size) {
52                s if s < 65536 => return Err(TransactionError::InvalidVariableSizeInteger(s as usize)),
53                s => Ok(s as usize),
54            }
55        }
56        _ => {
57            let mut size = [0u8; 8];
58            reader.read(&mut size)?;
59            match u64::from_le_bytes(size) {
60                s if s < 4294967296 => return Err(TransactionError::InvalidVariableSizeInteger(s as usize)),
61                s => Ok(s as usize),
62            }
63        }
64    }
65}
66
67pub struct BitcoinVector;
68
69impl BitcoinVector {
70    /// Read and output a vector with a variable length integer
71    pub fn read<R: Read, E, F>(mut reader: R, func: F) -> Result<Vec<E>, TransactionError>
72    where
73        F: Fn(&mut R) -> Result<E, TransactionError>,
74    {
75        let count = read_variable_length_integer(&mut reader)?;
76        (0..count).map(|_| func(&mut reader)).collect()
77    }
78
79    /// Read and output a vector with a variable length integer and the integer itself
80    pub fn read_witness<R: Read, E, F>(
81        mut reader: R,
82        func: F,
83    ) -> Result<(usize, Result<Vec<E>, TransactionError>), TransactionError>
84    where
85        F: Fn(&mut R) -> Result<E, TransactionError>,
86    {
87        let count = read_variable_length_integer(&mut reader)?;
88        Ok((count, (0..count).map(|_| func(&mut reader)).collect()))
89    }
90}
91
92/// Generate the script_pub_key of a corresponding address
93pub fn create_script_pub_key<N: BitcoinNetwork>(address: &BitcoinAddress<N>) -> Result<Vec<u8>, TransactionError> {
94    match address.format() {
95        BitcoinFormat::P2PKH => {
96            let bytes = &address.to_string().from_base58()?;
97            let pub_key_hash = bytes[1..(bytes.len() - 4)].to_vec();
98
99            let mut script = vec![];
100            script.push(Opcode::OP_DUP as u8);
101            script.push(Opcode::OP_HASH160 as u8);
102            script.extend(variable_length_integer(pub_key_hash.len() as u64)?);
103            script.extend(pub_key_hash);
104            script.push(Opcode::OP_EQUALVERIFY as u8);
105            script.push(Opcode::OP_CHECKSIG as u8);
106            Ok(script)
107        }
108        BitcoinFormat::P2WSH => {
109            let bech32 = Bech32::from_str(&address.to_string())?;
110            let (v, script) = bech32.data().split_at(1);
111            let script = Vec::from_base32(script)?;
112            let mut script_bytes = vec![v[0].to_u8(), script.len() as u8];
113            script_bytes.extend(script);
114            Ok(script_bytes)
115        }
116        BitcoinFormat::P2SH_P2WPKH => {
117            let script_bytes = &address.to_string().from_base58()?;
118            let script_hash = script_bytes[1..(script_bytes.len() - 4)].to_vec();
119
120            let mut script = vec![];
121            script.push(Opcode::OP_HASH160 as u8);
122            script.extend(variable_length_integer(script_hash.len() as u64)?);
123            script.extend(script_hash);
124            script.push(Opcode::OP_EQUAL as u8);
125            Ok(script)
126        }
127        BitcoinFormat::Bech32 => {
128            let bech32 = Bech32::from_str(&address.to_string())?;
129            let (v, program) = bech32.data().split_at(1);
130            let program = Vec::from_base32(program)?;
131            let mut program_bytes = vec![v[0].to_u8(), program.len() as u8];
132            program_bytes.extend(program);
133
134            Ok(WitnessProgram::new(&program_bytes)?.to_scriptpubkey())
135        }
136    }
137}
138
139/// Represents a Bitcoin signature hash
140/// https://en.bitcoin.it/wiki/OP_CHECKSIG
141#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
142#[allow(non_camel_case_types)]
143pub enum SignatureHash {
144    /// Signs all inputs and outputs.
145    SIGHASH_ALL = 0x01,
146    /// Signs all inputs and none of the outputs.
147    /// (e.g. "blank check" transaction, where any address can redeem the output)
148    SIGHASH_NONE = 0x02,
149    /// Signs all inputs and one corresponding output per input.
150    /// (e.g. signing vin 0 will result in signing vout 0)
151    SIGHASH_SINGLE = 0x03,
152    /// Signs only one input and all outputs.
153    /// Allows anyone to add or remove other inputs, forbids changing any outputs.
154    /// (e.g. "crowdfunding" transaction, where the output is the "goal" address)
155    SIGHASH_ALL_SIGHASH_ANYONECANPAY = 0x81,
156    /// Signs only one input and none of the outputs.
157    /// Allows anyone to add or remove other inputs or any outputs.
158    /// (e.g. "dust collector" transaction, where "dust" can be aggregated and spent together)
159    SIGHASH_NONE_SIGHASH_ANYONECANPAY = 0x82,
160    /// Signs only one input and one corresponding output per input.
161    /// Allows anyone to add or remove other inputs.
162    SIGHASH_SINGLE_SIGHASH_ANYONECANPAY = 0x83,
163}
164
165impl fmt::Display for SignatureHash {
166    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167        match self {
168            SignatureHash::SIGHASH_ALL => write!(f, "SIGHASH_ALL"),
169            SignatureHash::SIGHASH_NONE => write!(f, "SIGHASH_NONE"),
170            SignatureHash::SIGHASH_SINGLE => write!(f, "SIGHASH_SINGLE"),
171            SignatureHash::SIGHASH_ALL_SIGHASH_ANYONECANPAY => write!(f, "SIGHASH_ALL | SIGHASH_ANYONECANPAY"),
172            SignatureHash::SIGHASH_NONE_SIGHASH_ANYONECANPAY => write!(f, "SIGHASH_NONE | SIGHASH_ANYONECANPAY"),
173            SignatureHash::SIGHASH_SINGLE_SIGHASH_ANYONECANPAY => write!(f, "SIGHASH_SINGLE | SIGHASH_ANYONECANPAY"),
174        }
175    }
176}
177
178impl SignatureHash {
179    fn from_byte(byte: &u8) -> Self {
180        match byte {
181            0x01 => SignatureHash::SIGHASH_ALL,
182            0x02 => SignatureHash::SIGHASH_NONE,
183            0x03 => SignatureHash::SIGHASH_SINGLE,
184            0x81 => SignatureHash::SIGHASH_ALL_SIGHASH_ANYONECANPAY,
185            0x82 => SignatureHash::SIGHASH_NONE_SIGHASH_ANYONECANPAY,
186            0x83 => SignatureHash::SIGHASH_SINGLE_SIGHASH_ANYONECANPAY,
187            _ => SignatureHash::SIGHASH_ALL,
188        }
189    }
190}
191
192/// Represents the commonly used script opcodes
193#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
194#[allow(non_camel_case_types)]
195pub enum Opcode {
196    OP_DUP = 0x76,
197    OP_HASH160 = 0xa9,
198    OP_CHECKSIG = 0xac,
199    OP_EQUAL = 0x87,
200    OP_EQUALVERIFY = 0x88,
201}
202
203impl fmt::Display for Opcode {
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        match self {
206            Opcode::OP_DUP => write!(f, "OP_DUP"),
207            Opcode::OP_HASH160 => write!(f, "OP_HASH160"),
208            Opcode::OP_CHECKSIG => write!(f, "OP_CHECKSIG"),
209            Opcode::OP_EQUAL => write!(f, "OP_EQUAL"),
210            Opcode::OP_EQUALVERIFY => write!(f, "OP_EQUALVERIFY"),
211        }
212    }
213}
214
215/// Represents a Bitcoin transaction outpoint
216#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
217pub struct Outpoint<N: BitcoinNetwork> {
218    /// The previous transaction hash (32 bytes) (uses reversed hash order from Bitcoin RPC)
219    pub reverse_transaction_id: Vec<u8>,
220    /// The index of the transaction input (4 bytes)
221    pub index: u32,
222    /// The amount associated with this input (used for SegWit transaction signatures)
223    pub amount: Option<BitcoinAmount>,
224    /// The script public key associated with spending this input
225    pub script_pub_key: Option<Vec<u8>>,
226    /// An optional redeem script (for SegWit transactions)
227    pub redeem_script: Option<Vec<u8>>,
228    /// The address of the outpoint
229    pub address: Option<BitcoinAddress<N>>,
230}
231
232impl<N: BitcoinNetwork> Outpoint<N> {
233    /// Returns a new Bitcoin transaction outpoint
234    pub fn new(
235        reverse_transaction_id: Vec<u8>,
236        index: u32,
237        address: Option<BitcoinAddress<N>>,
238        amount: Option<BitcoinAmount>,
239        redeem_script: Option<Vec<u8>>,
240        script_pub_key: Option<Vec<u8>>,
241    ) -> Result<Self, TransactionError> {
242        let (script_pub_key, redeem_script) = match address.clone() {
243            Some(address) => {
244                let script_pub_key = script_pub_key.unwrap_or(create_script_pub_key::<N>(&address)?);
245                let redeem_script = match address.format() {
246                    BitcoinFormat::P2PKH => match redeem_script {
247                        Some(_) => return Err(TransactionError::InvalidInputs("P2PKH".into())),
248                        None => match script_pub_key[0] != Opcode::OP_DUP as u8
249                            && script_pub_key[1] != Opcode::OP_HASH160 as u8
250                            && script_pub_key[script_pub_key.len() - 1] != Opcode::OP_CHECKSIG as u8
251                        {
252                            true => return Err(TransactionError::InvalidScriptPubKey("P2PKH".into())),
253                            false => None,
254                        },
255                    },
256                    BitcoinFormat::P2WSH => match redeem_script {
257                        Some(redeem_script) => match script_pub_key[0] != 0x00 as u8
258                            && script_pub_key[1] != 0x20 as u8 && script_pub_key.len() != 34 // zero [32-byte sha256(witness script)]
259                        {
260                            true => return Err(TransactionError::InvalidScriptPubKey("P2WSH".into())),
261                            false => Some(redeem_script),
262                        },
263                        None => return Err(TransactionError::InvalidInputs("P2WSH".into())),
264                    },
265                    BitcoinFormat::P2SH_P2WPKH => match redeem_script {
266                        Some(redeem_script) => match script_pub_key[0] != Opcode::OP_HASH160 as u8
267                            && script_pub_key[script_pub_key.len() - 1] != Opcode::OP_EQUAL as u8
268                        {
269                            true => return Err(TransactionError::InvalidScriptPubKey("P2SH_P2WPKH".into())),
270                            false => Some(redeem_script),
271                        },
272                        None => return Err(TransactionError::InvalidInputs("P2SH_P2WPKH".into())),
273                    },
274                    BitcoinFormat::Bech32 => match redeem_script.is_some() {
275                        true => return Err(TransactionError::InvalidInputs("Bech32".into())),
276                        false => None,
277                    },
278                };
279
280                (Some(script_pub_key), redeem_script)
281            }
282            None => (None, None),
283        };
284
285        Ok(Self {
286            reverse_transaction_id,
287            index,
288            amount,
289            redeem_script,
290            script_pub_key,
291            address,
292        })
293    }
294}
295
296/// Represents a Bitcoin transaction input
297#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
298pub struct BitcoinTransactionInput<N: BitcoinNetwork> {
299    /// The outpoint (36 bytes)
300    pub outpoint: Outpoint<N>,
301    /// The transaction input script (variable size)
302    pub script_sig: Vec<u8>,
303    /// The sequence number (4 bytes) (0xFFFFFFFF unless lock > 0)
304    /// Also used in replace-by-fee (BIP 125)
305    pub sequence: Vec<u8>,
306    /// The signature hash (4 bytes) (used in signing raw transaction only)
307    pub sighash_code: SignatureHash,
308    /// The witnesses in a SegWit transaction
309    pub witnesses: Vec<Vec<u8>>,
310    /// If true, the input has been signed
311    pub is_signed: bool,
312}
313
314impl<N: BitcoinNetwork> BitcoinTransactionInput<N> {
315    const DEFAULT_SEQUENCE: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
316
317    /// Returns a new Bitcoin transaction input without the script (unlocking).
318    pub fn new(
319        transaction_id: Vec<u8>,
320        index: u32,
321        address: Option<BitcoinAddress<N>>,
322        amount: Option<BitcoinAmount>,
323        redeem_script: Option<Vec<u8>>,
324        script_pub_key: Option<Vec<u8>>,
325        sequence: Option<Vec<u8>>,
326        sighash: SignatureHash,
327    ) -> Result<Self, TransactionError> {
328        if transaction_id.len() != 32 {
329            return Err(TransactionError::InvalidTransactionId(transaction_id.len()));
330        }
331
332        // Byte-wise reverse of computed SHA-256 hash values
333        // https://bitcoin.org/en/developer-reference#hash-byte-order
334        let mut reverse_transaction_id = transaction_id;
335        reverse_transaction_id.reverse();
336
337        let outpoint = Outpoint::<N>::new(
338            reverse_transaction_id,
339            index,
340            address,
341            amount,
342            redeem_script,
343            script_pub_key,
344        )?;
345
346        Ok(Self {
347            outpoint,
348            script_sig: vec![],
349            sequence: sequence.unwrap_or(BitcoinTransactionInput::<N>::DEFAULT_SEQUENCE.to_vec()),
350            sighash_code: sighash,
351            witnesses: vec![],
352            is_signed: false,
353        })
354    }
355
356    /// Read and output a Bitcoin transaction input
357    pub fn read<R: Read>(mut reader: &mut R) -> Result<Self, TransactionError> {
358        let mut transaction_hash = [0u8; 32];
359        let mut vin = [0u8; 4];
360        let mut sequence = [0u8; 4];
361
362        reader.read(&mut transaction_hash)?;
363        reader.read(&mut vin)?;
364
365        let outpoint = Outpoint::<N>::new(
366            transaction_hash.to_vec(),
367            u32::from_le_bytes(vin),
368            None,
369            None,
370            None,
371            None,
372        )?;
373
374        let script_sig: Vec<u8> = BitcoinVector::read(&mut reader, |s| {
375            let mut byte = [0u8; 1];
376            s.read(&mut byte)?;
377            Ok(byte[0])
378        })?;
379
380        reader.read(&mut sequence)?;
381
382        let script_sig_len = read_variable_length_integer(&script_sig[..])?;
383        let sighash_code = SignatureHash::from_byte(&match script_sig_len {
384            0 => 0x01,
385            length => script_sig[length],
386        });
387
388        Ok(Self {
389            outpoint,
390            script_sig: script_sig.to_vec(),
391            sequence: sequence.to_vec(),
392            sighash_code,
393            witnesses: vec![],
394            is_signed: script_sig.len() > 0,
395        })
396    }
397
398    /// Returns the serialized transaction input.
399    pub fn serialize(&self, raw: bool) -> Result<Vec<u8>, TransactionError> {
400        let mut input = vec![];
401        input.extend(&self.outpoint.reverse_transaction_id);
402        input.extend(&self.outpoint.index.to_le_bytes());
403
404        match raw {
405            true => input.extend(vec![0x00]),
406            false => match self.script_sig.len() {
407                0 => match &self.outpoint.address {
408                    Some(address) => match address.format() {
409                        BitcoinFormat::Bech32 => input.extend(vec![0x00]),
410                        BitcoinFormat::P2WSH => input.extend(vec![0x00]),
411                        _ => {
412                            let script_pub_key = match &self.outpoint.script_pub_key {
413                                Some(script) => script,
414                                None => return Err(TransactionError::MissingOutpointScriptPublicKey),
415                            };
416                            input.extend(variable_length_integer(script_pub_key.len() as u64)?);
417                            input.extend(script_pub_key);
418                        }
419                    },
420                    None => input.extend(vec![0x00]),
421                },
422                _ => {
423                    input.extend(variable_length_integer(self.script_sig.len() as u64)?);
424                    input.extend(&self.script_sig);
425                }
426            },
427        };
428
429        input.extend(&self.sequence);
430        Ok(input)
431    }
432}
433
434/// Represents a Bitcoin transaction output
435#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
436pub struct BitcoinTransactionOutput {
437    /// The amount (in Satoshi)
438    pub amount: BitcoinAmount,
439    /// The public key script
440    pub script_pub_key: Vec<u8>,
441}
442
443impl BitcoinTransactionOutput {
444    /// Returns a Bitcoin transaction output.
445    pub fn new<N: BitcoinNetwork>(
446        address: &BitcoinAddress<N>,
447        amount: BitcoinAmount,
448    ) -> Result<Self, TransactionError> {
449        Ok(Self {
450            amount,
451            script_pub_key: create_script_pub_key::<N>(address)?,
452        })
453    }
454
455    /// Read and output a Bitcoin transaction output
456    pub fn read<R: Read>(mut reader: &mut R) -> Result<Self, TransactionError> {
457        let mut amount = [0u8; 8];
458        reader.read(&mut amount)?;
459
460        let script_pub_key: Vec<u8> = BitcoinVector::read(&mut reader, |s| {
461            let mut byte = [0u8; 1];
462            s.read(&mut byte)?;
463            Ok(byte[0])
464        })?;
465
466        Ok(Self {
467            amount: BitcoinAmount::from_satoshi(u64::from_le_bytes(amount) as i64)?,
468            script_pub_key,
469        })
470    }
471
472    /// Returns the serialized transaction output.
473    pub fn serialize(&self) -> Result<Vec<u8>, TransactionError> {
474        let mut output = vec![];
475        output.extend(&self.amount.0.to_le_bytes());
476        output.extend(variable_length_integer(self.script_pub_key.len() as u64)?);
477        output.extend(&self.script_pub_key);
478        Ok(output)
479    }
480}
481
482/// Represents an Bitcoin transaction id and witness transaction id
483/// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-id
484#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
485pub struct BitcoinTransactionId {
486    txid: Vec<u8>,
487    wtxid: Vec<u8>,
488}
489
490impl TransactionId for BitcoinTransactionId {}
491
492impl fmt::Display for BitcoinTransactionId {
493    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
494        write!(f, "{}", &hex::encode(&self.txid))
495    }
496}
497
498/// Represents the Bitcoin transaction parameters
499#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
500pub struct BitcoinTransactionParameters<N: BitcoinNetwork> {
501    /// The version number (4 bytes)
502    pub version: u32,
503    /// The transaction inputs
504    pub inputs: Vec<BitcoinTransactionInput<N>>,
505    /// The transaction outputs
506    pub outputs: Vec<BitcoinTransactionOutput>,
507    /// The lock time (4 bytes)
508    pub lock_time: u32,
509    /// An optional 2 bytes to indicate SegWit transactions
510    pub segwit_flag: bool,
511}
512
513impl<N: BitcoinNetwork> BitcoinTransactionParameters<N> {
514    /// Read and output the Bitcoin transaction parameters
515    pub fn read<R: Read>(mut reader: R) -> Result<Self, TransactionError> {
516        let mut version = [0u8; 4];
517        reader.read(&mut version)?;
518
519        let mut inputs = BitcoinVector::read(&mut reader, BitcoinTransactionInput::<N>::read)?;
520
521        let segwit_flag = match inputs.is_empty() {
522            true => {
523                let mut flag = [0u8; 1];
524                reader.read(&mut flag)?;
525                match flag[0] {
526                    1 => {
527                        inputs = BitcoinVector::read(&mut reader, BitcoinTransactionInput::<N>::read)?;
528                        true
529                    }
530                    _ => return Err(TransactionError::InvalidSegwitFlag(flag[0] as usize)),
531                }
532            }
533            false => false,
534        };
535
536        let outputs = BitcoinVector::read(&mut reader, BitcoinTransactionOutput::read)?;
537
538        if segwit_flag {
539            for input in &mut inputs {
540                let witnesses: Vec<Vec<u8>> = BitcoinVector::read(&mut reader, |s| {
541                    let (size, witness) = BitcoinVector::read_witness(s, |sr| {
542                        let mut byte = [0u8; 1];
543                        sr.read(&mut byte)?;
544                        Ok(byte[0])
545                    })?;
546
547                    Ok([variable_length_integer(size as u64)?, witness?].concat())
548                })?;
549
550                if witnesses.len() > 0 {
551                    input.sighash_code = SignatureHash::from_byte(&witnesses[0][&witnesses[0].len() - 1]);
552                    input.is_signed = true;
553                }
554                input.witnesses = witnesses;
555            }
556        }
557
558        let mut lock_time = [0u8; 4];
559        reader.read(&mut lock_time)?;
560
561        let transaction_parameters = BitcoinTransactionParameters::<N> {
562            version: u32::from_le_bytes(version),
563            inputs,
564            outputs,
565            lock_time: u32::from_le_bytes(lock_time),
566            segwit_flag,
567        };
568
569        Ok(transaction_parameters)
570    }
571}
572
573/// Represents a Bitcoin transaction
574#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
575pub struct BitcoinTransaction<N: BitcoinNetwork> {
576    /// The transaction parameters (version, inputs, outputs, lock_time, segwit_flag)
577    parameters: BitcoinTransactionParameters<N>,
578}
579
580impl<N: BitcoinNetwork> Transaction for BitcoinTransaction<N> {
581    type Address = BitcoinAddress<N>;
582    type Format = BitcoinFormat;
583    type PrivateKey = BitcoinPrivateKey<N>;
584    type PublicKey = BitcoinPublicKey<N>;
585    type TransactionId = BitcoinTransactionId;
586    type TransactionParameters = BitcoinTransactionParameters<N>;
587
588    /// Returns an unsigned transaction given the transaction parameters.
589    fn new(parameters: &Self::TransactionParameters) -> Result<Self, TransactionError> {
590        Ok(Self {
591            parameters: parameters.clone(),
592        })
593    }
594
595    /// Returns a signed transaction given the private key of the sender.
596    fn sign(&self, private_key: &Self::PrivateKey) -> Result<Self, TransactionError> {
597        let mut transaction = self.clone();
598        for (vin, input) in self.parameters.inputs.iter().enumerate() {
599            let address = match &input.outpoint.address {
600                Some(address) => address,
601                None => continue,
602            };
603
604            let address_is_valid = match &address.format() {
605                BitcoinFormat::P2WSH => {
606                    let input_script = match &input.outpoint.redeem_script {
607                        Some(redeem_script) => redeem_script.clone(),
608                        None => return Err(TransactionError::InvalidInputs("P2WSH".into())),
609                    };
610                    let c_address = BitcoinAddress::<N>::p2wsh(&input_script)?;
611                    address == &c_address
612                },
613                _ => address == &private_key.to_address(&address.format())?
614            };
615
616            if address_is_valid && !transaction.parameters.inputs[vin].is_signed {
617                // Transaction hash
618                let preimage = match &address.format() {
619                    BitcoinFormat::P2PKH => transaction.p2pkh_hash_preimage(vin, input.sighash_code)?,
620                    _ => transaction.segwit_hash_preimage(vin, input.sighash_code)?,
621                };
622                let transaction_hash = Sha256::digest(&Sha256::digest(&preimage));
623
624                // Signature
625                let mut signature = secp256k1::Secp256k1::signing_only()
626                    .sign(
627                        &secp256k1::Message::from_slice(&transaction_hash)?,
628                        &private_key.to_secp256k1_secret_key(),
629                    )
630                    .serialize_der()
631                    .to_vec();
632                signature.push((input.sighash_code as u32).to_le_bytes()[0]);
633                let signature = [variable_length_integer(signature.len() as u64)?, signature].concat();
634
635                // Public key
636                let public_key = private_key.to_public_key();
637                let public_key_bytes = match (&address.format(), public_key.is_compressed()) {
638                    (BitcoinFormat::P2PKH, false) => {
639                        public_key.to_secp256k1_public_key().serialize_uncompressed().to_vec()
640                    }
641                    _ => public_key.to_secp256k1_public_key().serialize().to_vec(),
642                };
643                let public_key = [vec![public_key_bytes.len() as u8], public_key_bytes].concat();
644
645                match &address.format() {
646                    BitcoinFormat::P2PKH => {
647                        transaction.parameters.inputs[vin].script_sig = [signature.clone(), public_key].concat();
648                        transaction.parameters.inputs[vin].is_signed = true;
649                    }
650                    BitcoinFormat::P2WSH => {
651                        let input_script = match &input.outpoint.redeem_script {
652                            Some(redeem_script) => redeem_script.clone(),
653                            None => return Err(TransactionError::InvalidInputs("P2WSH".into())),
654                        };
655
656                        let ser_input_script = [variable_length_integer(input_script.len() as u64)?, input_script].concat();
657                        transaction.parameters.segwit_flag = true;
658                        transaction.parameters.inputs[vin].script_sig = vec![]; // length of empty scriptSig
659                        transaction.parameters.inputs[vin]
660                            .witnesses
661                            .append(&mut vec![signature.clone(), ser_input_script.clone()]);
662                        transaction.parameters.inputs[vin].is_signed = true;
663                    }
664                    BitcoinFormat::P2SH_P2WPKH => {
665                        let input_script = match &input.outpoint.redeem_script {
666                            Some(redeem_script) => redeem_script.clone(),
667                            None => return Err(TransactionError::InvalidInputs("P2SH_P2WPKH".into())),
668                        };
669                        transaction.parameters.segwit_flag = true;
670                        transaction.parameters.inputs[vin].script_sig =
671                            [variable_length_integer(input_script.len() as u64)?, input_script].concat();
672                        transaction.parameters.inputs[vin]
673                            .witnesses
674                            .append(&mut vec![signature.clone(), public_key]);
675                        transaction.parameters.inputs[vin].is_signed = true;
676                    }
677                    BitcoinFormat::Bech32 => {
678                        transaction.parameters.segwit_flag = true;
679                        transaction.parameters.inputs[vin]
680                            .witnesses
681                            .append(&mut vec![signature.clone(), public_key]);
682                        transaction.parameters.inputs[vin].is_signed = true;
683                    }
684                };
685            }
686        }
687        // TODO: (raychu86) Raise error if no input was signed
688        Ok(transaction)
689    }
690
691    /// Returns a transaction given the transaction bytes.
692    /// Note:: Raw transaction hex does not include enough
693    fn from_transaction_bytes(transaction: &Vec<u8>) -> Result<Self, TransactionError> {
694        Ok(Self {
695            parameters: Self::TransactionParameters::read(&transaction[..])?,
696        })
697    }
698
699    /// Returns the transaction in bytes.
700    fn to_transaction_bytes(&self) -> Result<Vec<u8>, TransactionError> {
701        let mut transaction = self.parameters.version.to_le_bytes().to_vec();
702
703        if self.parameters.segwit_flag {
704            transaction.extend(vec![0x00, 0x01]);
705        }
706
707        transaction.extend(variable_length_integer(self.parameters.inputs.len() as u64)?);
708        let mut has_witness = false;
709        for input in &self.parameters.inputs {
710            if !has_witness {
711                has_witness = input.witnesses.len() > 0;
712            }
713            transaction.extend(input.serialize(!input.is_signed)?);
714        }
715
716        transaction.extend(variable_length_integer(self.parameters.outputs.len() as u64)?);
717        for output in &self.parameters.outputs {
718            transaction.extend(output.serialize()?);
719        }
720
721        if has_witness {
722            for input in &self.parameters.inputs {
723                match input.witnesses.len() {
724                    0 => transaction.extend(vec![0x00]),
725                    _ => {
726                        transaction.extend(variable_length_integer(input.witnesses.len() as u64)?);
727                        for witness in &input.witnesses {
728                            transaction.extend(witness);
729                        }
730                    }
731                };
732            }
733        }
734
735        transaction.extend(&self.parameters.lock_time.to_le_bytes());
736
737        Ok(transaction)
738    }
739
740    /// Returns the transaction id.
741    fn to_transaction_id(&self) -> Result<Self::TransactionId, TransactionError> {
742        let mut txid = Sha256::digest(&Sha256::digest(&self.to_transaction_bytes_without_witness()?)).to_vec();
743        let mut wtxid = Sha256::digest(&Sha256::digest(&self.to_transaction_bytes()?)).to_vec();
744
745        txid.reverse();
746        wtxid.reverse();
747
748        Ok(Self::TransactionId { txid, wtxid })
749    }
750}
751
752impl<N: BitcoinNetwork> BitcoinTransaction<N> {
753    /// Return the P2PKH hash preimage of the raw transaction.
754    pub fn p2pkh_hash_preimage(&self, vin: usize, sighash: SignatureHash) -> Result<Vec<u8>, TransactionError> {
755        let mut preimage = self.parameters.version.to_le_bytes().to_vec();
756        preimage.extend(variable_length_integer(self.parameters.inputs.len() as u64)?);
757        for (index, input) in self.parameters.inputs.iter().enumerate() {
758            preimage.extend(input.serialize(index != vin)?);
759        }
760        preimage.extend(variable_length_integer(self.parameters.outputs.len() as u64)?);
761        for output in &self.parameters.outputs {
762            preimage.extend(output.serialize()?);
763        }
764        preimage.extend(&self.parameters.lock_time.to_le_bytes());
765        preimage.extend(&(sighash as u32).to_le_bytes());
766        Ok(preimage)
767    }
768
769    /// Return the SegWit hash preimage of the raw transaction
770    /// https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification
771    pub fn segwit_hash_preimage(&self, vin: usize, sighash: SignatureHash) -> Result<Vec<u8>, TransactionError> {
772        let mut prev_outputs = vec![];
773        let mut prev_sequences = vec![];
774        let mut outputs = vec![];
775
776        for input in &self.parameters.inputs {
777            prev_outputs.extend(&input.outpoint.reverse_transaction_id);
778            prev_outputs.extend(&input.outpoint.index.to_le_bytes());
779            prev_sequences.extend(&input.sequence);
780        }
781
782        for output in &self.parameters.outputs {
783            outputs.extend(&output.serialize()?);
784        }
785
786        let input = &self.parameters.inputs[vin];
787        let format = match &input.outpoint.address {
788            Some(address) => address.format(),
789            None => return Err(TransactionError::MissingOutpointAddress),
790        };
791
792        let script = match format {
793            BitcoinFormat::Bech32 => match &input.outpoint.script_pub_key {
794                Some(script) => script[1..].to_vec(),
795                None => return Err(TransactionError::MissingOutpointScriptPublicKey),
796            },
797            BitcoinFormat::P2WSH => match &input.outpoint.redeem_script {
798                Some(redeem_script) => redeem_script.to_vec(),
799                None => return Err(TransactionError::InvalidInputs("P2WSH".into())),
800            },
801            BitcoinFormat::P2SH_P2WPKH => match &input.outpoint.redeem_script {
802                Some(redeem_script) => redeem_script[1..].to_vec(),
803                None => return Err(TransactionError::InvalidInputs("P2SH_P2WPKH".into())),
804            },
805            BitcoinFormat::P2PKH => return Err(TransactionError::UnsupportedPreimage("P2PKH".into())),
806        };
807
808        let mut script_code = vec![];
809        if format == BitcoinFormat::P2WSH {
810            script_code.extend(script);
811        } else {
812            script_code.push(Opcode::OP_DUP as u8);
813            script_code.push(Opcode::OP_HASH160 as u8);
814            script_code.extend(script);
815            script_code.push(Opcode::OP_EQUALVERIFY as u8);
816            script_code.push(Opcode::OP_CHECKSIG as u8);
817        }
818        let script_code = [variable_length_integer(script_code.len() as u64)?, script_code].concat();
819        let hash_prev_outputs = Sha256::digest(&Sha256::digest(&prev_outputs));
820        let hash_sequence = Sha256::digest(&Sha256::digest(&prev_sequences));
821        let hash_outputs = Sha256::digest(&Sha256::digest(&outputs));
822        let outpoint_amount = match &input.outpoint.amount {
823            Some(amount) => amount.0.to_le_bytes(),
824            None => return Err(TransactionError::MissingOutpointAmount),
825        };
826
827        let mut preimage = vec![];
828        preimage.extend(&self.parameters.version.to_le_bytes());
829        preimage.extend(hash_prev_outputs);
830        preimage.extend(hash_sequence);
831        preimage.extend(&input.outpoint.reverse_transaction_id);
832        preimage.extend(&input.outpoint.index.to_le_bytes());
833        preimage.extend(&script_code);
834        preimage.extend(&outpoint_amount);
835        preimage.extend(&input.sequence);
836        preimage.extend(hash_outputs);
837        preimage.extend(&self.parameters.lock_time.to_le_bytes());
838        preimage.extend(&(sighash as u32).to_le_bytes());
839
840        Ok(preimage)
841    }
842
843    /// Returns the transaction with the traditional serialization (no witness).
844    fn to_transaction_bytes_without_witness(&self) -> Result<Vec<u8>, TransactionError> {
845        let mut transaction = self.parameters.version.to_le_bytes().to_vec();
846
847        transaction.extend(variable_length_integer(self.parameters.inputs.len() as u64)?);
848        for input in &self.parameters.inputs {
849            transaction.extend(input.serialize(false)?);
850        }
851
852        transaction.extend(variable_length_integer(self.parameters.outputs.len() as u64)?);
853        for output in &self.parameters.outputs {
854            transaction.extend(output.serialize()?);
855        }
856
857        transaction.extend(&self.parameters.lock_time.to_le_bytes());
858
859        Ok(transaction)
860    }
861
862    /// Update a transaction's input outpoint
863    #[allow(dead_code)]
864    pub fn update_outpoint(&self, outpoint: Outpoint<N>) -> Self {
865        let mut new_transaction = self.clone();
866        for (vin, input) in self.parameters.inputs.iter().enumerate() {
867            if &outpoint.reverse_transaction_id == &input.outpoint.reverse_transaction_id
868                && &outpoint.index == &input.outpoint.index
869            {
870                new_transaction.parameters.inputs[vin].outpoint = outpoint.clone();
871            }
872        }
873        new_transaction
874    }
875}
876
877impl<N: BitcoinNetwork> FromStr for BitcoinTransaction<N> {
878    type Err = TransactionError;
879
880    fn from_str(transaction: &str) -> Result<Self, Self::Err> {
881        Self::from_transaction_bytes(&hex::decode(transaction)?)
882    }
883}
884
885#[cfg(test)]
886mod tests {
887    use super::*;
888    use crate::Mainnet;
889    use wagyu_model::crypto::hash160;
890
891    pub struct TransactionTestCase<'a> {
892        pub version: u32,
893        pub lock_time: u32,
894        pub inputs: &'a [Input],
895        pub outputs: &'a [Output],
896        pub expected_signed_transaction: &'a str,
897        pub expected_transaction_id: &'a str,
898    }
899
900    #[derive(Debug, Clone)]
901    pub struct Input {
902        pub private_key: &'static str,
903        pub address_format: BitcoinFormat,
904        pub transaction_id: &'static str,
905        pub index: u32,
906        pub redeem_script: Option<&'static str>,
907        pub script_pub_key: Option<&'static str>,
908        pub utxo_amount: BitcoinAmount,
909        pub sequence: Option<[u8; 4]>,
910        pub sighash_code: SignatureHash,
911    }
912
913    #[derive(Clone)]
914    pub struct Output {
915        pub address: &'static str,
916        pub amount: BitcoinAmount,
917    }
918
919    fn test_transaction<N: BitcoinNetwork>(
920        version: u32,
921        lock_time: u32,
922        inputs: Vec<Input>,
923        outputs: Vec<Output>,
924        expected_signed_transaction: &str,
925        expected_transaction_id: &str,
926    ) {
927        let mut input_vec = vec![];
928        for input in &inputs {
929            let private_key = BitcoinPrivateKey::from_str(input.private_key).unwrap();
930            let address = private_key.to_address(&input.address_format).unwrap();
931            let transaction_id = hex::decode(input.transaction_id).unwrap();
932            let redeem_script = match (input.redeem_script, input.address_format.clone()) {
933                (Some(script), _) => Some(hex::decode(script).unwrap()),
934                (None, BitcoinFormat::P2SH_P2WPKH) => {
935                    let mut redeem_script = vec![0x00, 0x14];
936                    redeem_script.extend(&hash160(
937                        &private_key.to_public_key().to_secp256k1_public_key().serialize(),
938                    ));
939                    Some(redeem_script)
940                }
941                (None, _) => None,
942            };
943            let script_pub_key = input.script_pub_key.map(|script| hex::decode(script).unwrap());
944            let sequence = input.sequence.map(|seq| seq.to_vec());
945            let transaction_input = BitcoinTransactionInput::<N>::new(
946                transaction_id,
947                input.index,
948                Some(address),
949                Some(input.utxo_amount),
950                redeem_script,
951                script_pub_key,
952                sequence,
953                input.sighash_code,
954            )
955            .unwrap();
956
957            input_vec.push(transaction_input);
958        }
959
960        let mut output_vec = vec![];
961        for output in outputs {
962            let address = BitcoinAddress::<N>::from_str(output.address).unwrap();
963            output_vec.push(BitcoinTransactionOutput::new(&address, output.amount).unwrap());
964        }
965
966        let transaction_parameters = BitcoinTransactionParameters::<N> {
967            version,
968            inputs: input_vec,
969            outputs: output_vec,
970            lock_time,
971            segwit_flag: false,
972        };
973
974        let mut transaction = BitcoinTransaction::<N>::new(&transaction_parameters).unwrap();
975
976        // Sign transaction
977        for input in inputs {
978            transaction = transaction
979                .sign(&BitcoinPrivateKey::from_str(input.private_key).unwrap())
980                .unwrap();
981        }
982
983        let signed_transaction = hex::encode(&transaction.to_transaction_bytes().unwrap());
984        let transaction_id = hex::encode(&transaction.to_transaction_id().unwrap().txid);
985
986        assert_eq!(expected_signed_transaction, &signed_transaction);
987        assert_eq!(expected_transaction_id, &transaction_id);
988    }
989
990    fn test_reconstructed_transaction<N: BitcoinNetwork>(
991        version: u32,
992        lock_time: u32,
993        inputs: Vec<Input>,
994        outputs: Vec<Output>,
995        expected_signed_transaction: &str,
996        expected_transaction_id: &str,
997    ) {
998        let mut input_vec = vec![];
999        for input in &inputs {
1000            let private_key = BitcoinPrivateKey::from_str(input.private_key).unwrap();
1001            let address = private_key.to_address(&input.address_format).unwrap();
1002            let transaction_id = hex::decode(input.transaction_id).unwrap();
1003            let redeem_script = match (input.redeem_script, input.address_format.clone()) {
1004                (Some(script), _) => Some(hex::decode(script).unwrap()),
1005                (None, BitcoinFormat::P2SH_P2WPKH) => {
1006                    let mut redeem_script = vec![0x00, 0x14];
1007                    redeem_script.extend(&hash160(
1008                        &private_key.to_public_key().to_secp256k1_public_key().serialize(),
1009                    ));
1010                    Some(redeem_script)
1011                }
1012                (None, _) => None,
1013            };
1014            let script_pub_key = input.script_pub_key.map(|script| hex::decode(script).unwrap());
1015            let sequence = input.sequence.map(|seq| seq.to_vec());
1016            let transaction_input = BitcoinTransactionInput::<N>::new(
1017                transaction_id,
1018                input.index,
1019                Some(address),
1020                Some(input.utxo_amount),
1021                redeem_script,
1022                script_pub_key,
1023                sequence,
1024                input.sighash_code,
1025            )
1026            .unwrap();
1027
1028            input_vec.push(transaction_input);
1029        }
1030
1031        let mut output_vec = vec![];
1032        for output in outputs {
1033            let address = BitcoinAddress::<N>::from_str(output.address).unwrap();
1034            output_vec.push(BitcoinTransactionOutput::new(&address, output.amount).unwrap());
1035        }
1036
1037        let transaction_parameters = BitcoinTransactionParameters::<N> {
1038            version,
1039            inputs: input_vec.clone(),
1040            outputs: output_vec,
1041            lock_time,
1042            segwit_flag: false,
1043        };
1044
1045        let transaction = BitcoinTransaction::<N>::new(&transaction_parameters).unwrap();
1046        let unsigned_raw_transaction = hex::encode(&transaction.to_transaction_bytes().unwrap());
1047
1048        let mut new_transaction = BitcoinTransaction::<N>::from_str(&unsigned_raw_transaction).unwrap();
1049
1050        // Sign transaction reconstructed from hex
1051        for input in inputs {
1052            let partial_signed_transaction = hex::encode(&new_transaction.to_transaction_bytes().unwrap());
1053            new_transaction = BitcoinTransaction::<N>::from_str(&partial_signed_transaction).unwrap();
1054
1055            let mut reverse_transaction_id = hex::decode(input.transaction_id).unwrap();
1056            reverse_transaction_id.reverse();
1057            let tx_input = input_vec.iter().cloned().find(|tx_input| {
1058                tx_input.outpoint.reverse_transaction_id == reverse_transaction_id
1059                    && tx_input.outpoint.index == input.index
1060            });
1061
1062            if let Some(tx_input) = tx_input {
1063                new_transaction = new_transaction.update_outpoint(tx_input.outpoint);
1064                new_transaction = new_transaction
1065                    .sign(&BitcoinPrivateKey::from_str(input.private_key).unwrap())
1066                    .unwrap();
1067            }
1068        }
1069
1070        let new_signed_transaction = hex::encode(new_transaction.to_transaction_bytes().unwrap());
1071        let new_transaction_id = new_transaction.to_transaction_id().unwrap().to_string();
1072
1073        assert_eq!(expected_signed_transaction, &new_signed_transaction);
1074        assert_eq!(expected_transaction_id, &new_transaction_id);
1075    }
1076
1077    mod test_valid_mainnet_transactions {
1078        use super::*;
1079        type N = Mainnet;
1080
1081        const TRANSACTIONS: [TransactionTestCase; 9] = [
1082            TransactionTestCase { // p2pkh to p2pkh - based on https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js
1083                version: 1,
1084                lock_time: 0,
1085                inputs: &[
1086                    Input {
1087                        private_key: "L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy",
1088                        address_format: BitcoinFormat::P2PKH,
1089                        transaction_id: "61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d",
1090                        index: 0,
1091                        redeem_script: None,
1092                        script_pub_key: None,
1093                        utxo_amount: BitcoinAmount(0),
1094                        sequence: None,
1095                        sighash_code: SignatureHash::SIGHASH_ALL
1096                    },
1097                ],
1098                outputs: &[
1099                    Output {
1100                        address: "1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP",
1101                        amount: BitcoinAmount(12000)
1102                    },
1103                ],
1104                expected_signed_transaction: "01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000",
1105                expected_transaction_id: "7a68099c3f338fa61696a3c54404c88491e3b249e85574d6bbba01ac00ae33ff",
1106            },
1107            TransactionTestCase { // p2sh_p2wpkh to p2pkh - based on https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#p2sh-p2wpkh
1108                version: 1,
1109                lock_time: 1170,
1110                inputs: &[
1111                    Input {
1112                        private_key: "5Kbxro1cmUF9mTJ8fDrTfNB6URTBsFMUG52jzzumP2p9C94uKCh",
1113                        address_format: BitcoinFormat::P2SH_P2WPKH,
1114                        transaction_id: "77541aeb3c4dac9260b68f74f44c973081a9d4cb2ebe8038b2d70faa201b6bdb",
1115                        index: 1,
1116                        redeem_script: None,
1117                        script_pub_key: None,
1118                        utxo_amount: BitcoinAmount(1000000000),
1119                        sequence: Some([0xfe, 0xff, 0xff, 0xff]),
1120                        sighash_code: SignatureHash::SIGHASH_ALL
1121                    },
1122                ],
1123                outputs: &[
1124                    Output {
1125                        address: "1Fyxts6r24DpEieygQiNnWxUdb18ANa5p7",
1126                        amount: BitcoinAmount(199996600)
1127                    },
1128                    Output {
1129                        address: "1Q5YjKVj5yQWHBBsyEBamkfph3cA6G9KK8",
1130                        amount: BitcoinAmount(800000000)
1131                    },
1132                ],
1133                expected_signed_transaction: "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000",
1134                expected_transaction_id: "ef48d9d0f595052e0f8cdcf825f7a5e50b6a388a81f206f3f4846e5ecd7a0c23",
1135            },
1136            TransactionTestCase { //p2sh_p2wpkh to segwit
1137                version: 2,
1138                lock_time: 140,
1139                inputs: &[
1140                    Input {
1141                        private_key: "KwtetKxofS1Lhp7idNJzb5B5WninBRfELdwkjvTMZZGME4G72kMz",
1142                        address_format: BitcoinFormat::P2SH_P2WPKH,
1143                        transaction_id: "375e1622b2690e395df21b33192bad06d2706c139692d43ea84d38df3d183313",
1144                        index: 0,
1145                        redeem_script: Some("0014b93f973eb2bf0b614bddc0f47286788c98c535b4"), // Manually specify redeem_script
1146                        script_pub_key: None,
1147                        utxo_amount: BitcoinAmount(1000000000),
1148                        sequence: Some([0xfe, 0xff, 0xff, 0xff]),
1149                        sighash_code: SignatureHash::SIGHASH_ALL
1150                    },
1151                ],
1152                outputs: &[
1153                    Output {
1154                        address: "3H3Kc7aSPP4THLX68k4mQMyf1gvL6AtmDm",
1155                        amount: BitcoinAmount(100000000)
1156                    },
1157                    Output {
1158                        address: "3MSu6Ak7L6RY5HdghczUcXzGaVVCusAeYj",
1159                        amount: BitcoinAmount(899990000),
1160                    },
1161                ],
1162                expected_signed_transaction: "020000000001011333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e370000000017160014b93f973eb2bf0b614bddc0f47286788c98c535b4feffffff0200e1f5050000000017a914a860f76561c85551594c18eecceffaee8c4822d787f0c1a4350000000017a914d8b6fcc85a383261df05423ddf068a8987bf0287870247304402206214bf6096f0050f8442be6107448f89983a7399974f7160ba02e80f96383a3f02207b2a169fed3f48433850f39599396f8c8237260a57462795a83b85cceff5b1aa012102e1a2ba641bbad8399bf6e16a7824faf9175d246aef205599364cc5b4ad64962f8c000000",
1163                expected_transaction_id: "51f563f37b80c1fab7cc21eea1d6991ab9bd9069ddafb372e1c36e5fc5b56447",
1164            },
1165            TransactionTestCase { // p2pkh and p2sh_p2wpkh to p2pkh - based on https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.js
1166                version: 1,
1167                lock_time: 0,
1168                inputs: &[
1169                    Input {
1170                        private_key: "L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1",
1171                        address_format: BitcoinFormat::P2PKH,
1172                        transaction_id: "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c",
1173                        index: 6,
1174                        redeem_script: None,
1175                        script_pub_key: None,
1176                        utxo_amount: BitcoinAmount(0),
1177                        sequence: Some([0xff, 0xff, 0xff, 0xff]),
1178                        sighash_code: SignatureHash::SIGHASH_ALL
1179                    },
1180                    Input {
1181                        private_key: "KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z",
1182                        address_format: BitcoinFormat::P2PKH,
1183                        transaction_id: "7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730",
1184                        index: 0,
1185                        redeem_script: None,
1186                        script_pub_key: None,
1187                        utxo_amount: BitcoinAmount(0),
1188                        sequence: Some([0xff, 0xff, 0xff, 0xff]),
1189                        sighash_code: SignatureHash::SIGHASH_ALL
1190                    }
1191                ],
1192                outputs: &[
1193                    Output {
1194                        address: "1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb",
1195                        amount: BitcoinAmount(180000)
1196                    },
1197                    Output {
1198                        address: "1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9",
1199                        amount: BitcoinAmount(170000)
1200                    },
1201                ],
1202                expected_signed_transaction: "01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000",
1203                expected_transaction_id: "af0ae7ab766f49c33312d33541868c2185ad559cc0457e7af398311bda4f18f7",
1204            },
1205            TransactionTestCase { // p2sh_p2wsh to multiple address types
1206                version: 2,
1207                lock_time: 0,
1208                inputs: &[
1209                    Input {
1210                        private_key: "Kxxkik2L9KgrGgvdkEvYSkgAxaY4qPGfvxe1M1KBVBB7Ls3xDD8o",
1211                        address_format: BitcoinFormat::P2SH_P2WPKH,
1212                        transaction_id: "7c95424e4c86467eaea85b878985fa77d191bad2b9c5cac5a0cb98f760616afa",
1213                        index: 55,
1214                        redeem_script: None,
1215                        script_pub_key: None,
1216                        utxo_amount: BitcoinAmount(2000000),
1217                        sequence: None,
1218                        sighash_code: SignatureHash::SIGHASH_ALL
1219                    },
1220                ],
1221                outputs: &[
1222                    Output {
1223                        address: "3DTGFEmobt8BaJpfPe62HvCQKp2iGsnYqD",
1224                        amount: BitcoinAmount(30000)
1225                    },
1226                    Output {
1227                        address: "1NxCpkhj6n8orGdhPpxCD3B52WvoR4CS7S",
1228                        amount: BitcoinAmount(2000000)
1229                    },
1230                ],
1231                expected_signed_transaction: "02000000000101fa6a6160f798cba0c5cac5b9d2ba91d177fa8589875ba8ae7e46864c4e42957c37000000171600143d295b6276ff8e4579f3350873db3e839e230f41ffffffff02307500000000000017a9148107a4409368488413295580eb88cbf7609cce658780841e00000000001976a914f0cb63944bcbbeb75c26492196939ae419c515a988ac024730440220243435ca67a713f6715d14d761b5ab073e88b30559a02f8b1add1aee8082f1c902207dfea838a2e815132999035245d9ebf51b4c740cbe4d95c609c7012ba9beb86301210324804353b8e10ce351d073da432fb046a4d13edf22052577a6e09cf9a5090cda00000000",
1232                expected_transaction_id: "ba4dfdff505035b1d70842bbcb3be160528b3b5261292124a9c1b58a0d34f7f8",
1233            },
1234            TransactionTestCase { // p2sh_p2wsh and p2pkh to multiple address types
1235                version: 2,
1236                lock_time: 0,
1237                inputs: &[
1238                    Input {
1239                        private_key: "L3EEWFaodvuDcH7yeTtugQDvNxnBs8Fkzerqf8tgmHYKQ4QkQJDE",
1240                        address_format: BitcoinFormat::P2PKH,
1241                        transaction_id: "6b88c087247aa2f07ee1c5956b8e1a9f4c7f892a70e324f1bb3d161e05ca107b",
1242                        index: 0,
1243                        redeem_script: None,
1244                        script_pub_key: None,
1245                        utxo_amount: BitcoinAmount(0),
1246                        sequence: None,
1247                        sighash_code: SignatureHash::SIGHASH_ALL
1248                    },
1249                    Input {
1250                        private_key: "KzZtscUzkZS38CYqYfRZ8pUKfUr1JnAnwJLK25S8a6Pj6QgPYJkq",
1251                        address_format: BitcoinFormat::P2SH_P2WPKH,
1252                        transaction_id: "93ca92c0653260994680a4caa40cfc7b0aac02a077c4f022b007813d6416c70d",
1253                        index: 1,
1254                        redeem_script: None,
1255                        script_pub_key: None,
1256                        utxo_amount: BitcoinAmount(100000),
1257                        sequence: None,
1258                        sighash_code: SignatureHash::SIGHASH_ALL
1259                    },
1260                ],
1261                outputs: &[
1262                    Output {
1263                        address: "36NCSwdvrL7XpiRSsfYWY99azC4xWgtL3X",
1264                        amount: BitcoinAmount(50000)
1265                    },
1266                    Output {
1267                        address: "17rB37JzbUVmFuKMx8fexrHjdWBtDurpuL",
1268                        amount: BitcoinAmount(123456)
1269                    },
1270                ],
1271                expected_signed_transaction: "020000000001027b10ca051e163dbbf124e3702a897f4c9f1a8e6b95c5e17ef0a27a2487c0886b000000006b483045022100b15c1d8e7de7c1d77612f80ab49c48c3d0c23467a0becaa86fcd98009d2dff6002200f3b2341a591889f38e629dc4b938faf9165aecedc4e3be768b13ef491cbb37001210264174f4ff6006a98be258fe1c371b635b097b000ce714c6a2842d5c269dbf2e9ffffffff0dc716643d8107b022f0c477a002ac0a7bfc0ca4caa4804699603265c092ca9301000000171600142b654d833c287e239f73ba8165bbadf4dee3c00effffffff0250c300000000000017a914334982bfd308f92f8ea5d22e9f7ee52f2265543b8740e20100000000001976a9144b1d83c75928642a41f2945c8a3be48550822a9a88ac0002483045022100a37e7aeb82332d5dc65d1582bb917acbf2d56a90f5a792a932cfec3c09f7a534022033baa11aa0f3ad4ba9723c703e65dce724ccb79c00838e09b96892087e43f1c8012102d9c6aaa344ee7cc41466e4705780020deb70720bef8ddb9b4e83e75df02e1d8600000000",
1272                expected_transaction_id: "0c76c2ad441b7853966ba2dae6fc3f0b890b81761ea3e1421f8df02e80a08050",
1273            },
1274            TransactionTestCase { // p2pkh to bech32
1275                version: 2,
1276                lock_time: 0,
1277                inputs: &[
1278                    Input {
1279                        private_key: "5JsX1A2JNjqVmLFUUhUJuDsVjFH2yfoVdV5qtFQpWhLkYzamKKy",
1280                        address_format: BitcoinFormat::P2PKH,
1281                        transaction_id: "bda2ebcbf0bd6bc4ee1c330a64a9ff95e839cc2d25c593a01e704996bc1e869c",
1282                        index: 0,
1283                        redeem_script: None,
1284                        script_pub_key: None,
1285                        utxo_amount: BitcoinAmount(0),
1286                        sequence: None,
1287                        sighash_code: SignatureHash::SIGHASH_ALL
1288                    },
1289                ],
1290                outputs: &[
1291                    Output {
1292                        address: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
1293                        amount: BitcoinAmount(1234)
1294                    },
1295                    Output {
1296                        address: "bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9",
1297                        amount: BitcoinAmount(111)
1298                    },
1299                ],
1300                expected_signed_transaction: "02000000019c861ebc9649701ea093c5252dcc39e895ffa9640a331ceec46bbdf0cbeba2bd000000008a473044022077be9faa83fc2289bb59eb7538c3801f513d3640466a48cea9845f3b26f14cd802207b80fda8836610c487e8b59105e682d1c438f98f6324d1ea74cdec5d2d74ef04014104cc58298806e4e233ad1acb81feeb90368e05ad79a1d4b3698156dc4766ca077f39fbb47f52cbd5282c15a8a9fb08401e678acb9ef2fd28e043164723e9f29bb2ffffffff02d204000000000000160014e8df018c7e326cc253faac7e46cdc51e68542c426f00000000000000220020c7a1f1a4d6b4c1802a59631966a18359de779e8a6a65973735a3ccdfdabc407d00000000",
1301                expected_transaction_id: "954c460ad8d9ea42e3679a4a70910a3be2e19c2cc8d6018d68038d9416db495e",
1302            },
1303            TransactionTestCase { // p2sh-p2wpkh to bech32
1304                version: 2,
1305                lock_time: 0,
1306                inputs: &[
1307                    Input {
1308                        private_key: "KzZtscUzkZS38CYqYfRZ8pUKfUr1JnAnwJLK25S8a6Pj6QgPYJkq",
1309                        address_format: BitcoinFormat::P2SH_P2WPKH,
1310                        transaction_id: "1a2290470e0aa7549ab1e04b2453274374149ffee517a57715e5206e4142c233",
1311                        index: 1,
1312                        redeem_script: None,
1313                        script_pub_key: None,
1314                        utxo_amount: BitcoinAmount(1500000),
1315                        sequence: None,
1316                        sighash_code: SignatureHash::SIGHASH_ALL
1317                    },
1318                ],
1319                outputs: &[
1320                    Output {
1321                        address: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", // witness version 1
1322                        amount: BitcoinAmount(100000000)
1323                    },
1324                    Output {
1325                        address: "bc1qquxqz4f7wzen367stgwt25tf640gp4vud5vez0",
1326                        amount: BitcoinAmount(42500001)
1327                    },
1328                ],
1329                expected_signed_transaction: "0200000000010133c242416e20e51577a517e5fe9f1474432753244be0b19a54a70a0e4790221a01000000171600142b654d833c287e239f73ba8165bbadf4dee3c00effffffff0200e1f505000000002a5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6a17f880200000000160014070c01553e70b338ebd05a1cb55169d55e80d59c02483045022100b5cf710307329d8634842c1894057ef243e172284e0908b215479e3b1889f62302205dfdd0287899e3034c95526bcfb1f437a0ca66de42a63c3c36aabb5b893459fb012102d9c6aaa344ee7cc41466e4705780020deb70720bef8ddb9b4e83e75df02e1d8600000000",
1330                expected_transaction_id: "7f9c4bc972b1b7749b9883b34bb26be3eb9fd8c4877818ae4c7c27e4cb5eda67",
1331            },
1332            TransactionTestCase { // p2pkh and bech32(p2wpkh) to multiple address types
1333                version: 1,
1334                lock_time: 0,
1335                inputs: &[
1336                    Input {
1337                        private_key: "L1X6apYnZ39CLFJFX6Ny7oriHX3nmeBcjkobeYgmk6arbyZfouJu",
1338                        address_format: BitcoinFormat::P2PKH,
1339                        transaction_id: "9f96ade4b41d5433f4eda31e1738ec2b36f6e7d1420d94a6af99801a88f7f7ff",
1340                        index: 0,
1341                        redeem_script: None,
1342                        script_pub_key: Some("76a9148631bf621f7c6671f8d2d646327b636cbbe79f8c88ac"), // Manually specify script_pub_key
1343                        utxo_amount: BitcoinAmount(0),
1344                        sequence: Some([0xee, 0xff, 0xff, 0xff]),
1345                        sighash_code: SignatureHash::SIGHASH_ALL
1346                    },
1347                    Input {
1348                        private_key: "5JZGuGYM4vfKvpxaJg5g5D3uvVYVQ74UUdueCVvWCNacrAkkvGi",
1349                        address_format: BitcoinFormat::Bech32,
1350                        transaction_id: "8ac60eb9575db5b2d987e29f301b5b819ea83a5c6579d282d189cc04b8e151ef",
1351                        index: 1,
1352                        redeem_script: None,
1353                        script_pub_key: None,
1354                        utxo_amount: BitcoinAmount(600000000),
1355                        sequence: Some([0xff, 0xff, 0xff, 0xff]),
1356                        sighash_code: SignatureHash::SIGHASH_ALL
1357                    },
1358                ],
1359                outputs: &[
1360                    Output {
1361                        address: "bc1qgwu40h9vf3q9ua7llnsr29fws920enj8gflr4d",
1362                        amount: BitcoinAmount(10)
1363                    },
1364                    Output {
1365                        address: "1AD97NRftXXd2rkHDU17x8uq21LWYJFNa1",
1366                        amount: BitcoinAmount(5555555)
1367                    },
1368                    Output {
1369                        address: "3AnU6mchUQHZwgcRUD6JJaHe91fJ7UhajZ",
1370                        amount: BitcoinAmount(9182631)
1371                    },
1372                ],
1373                expected_signed_transaction: "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f000000006b4830450221009eed10e4b7cc9eb23efc36dc9b0907d0b4dd224ae5d0ee9c92d7912c9a9cde7e02203ede96d667901abfb9f3997aba8e08c6b9de218db920916203f2632c713cd99c012103f4edae249cb015280d48cae959d1823440eeab74f9fc9752a8a18cba76c892b6eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff030a0000000000000016001443b957dcac4c405e77dffce035152e8154fcce4763c55400000000001976a9146504e4b146b24898cf7881b0bdcd059dc35dd5a888aca71d8c000000000017a91463c110106d813c69514b3d97e1a1e6c94ad1b56a870002483045022100cfff608b18a97cc46cf8d22e97e78b22343cfcc19028918a5cd06fc9031f532302201b877de8872619a832387d7d0e15482521e449ce0d4daeb2d080995317883cd60121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635700000000",
1374                expected_transaction_id: "62ee2045fa2e3ee0353fed70b39adac13cb4114dbafa3a60a12084104d14f1b0",
1375            },
1376        ];
1377
1378        #[test]
1379        fn test_mainnet_transactions() {
1380            TRANSACTIONS.iter().for_each(|transaction| {
1381                test_transaction::<N>(
1382                    transaction.version,
1383                    transaction.lock_time,
1384                    transaction.inputs.to_vec(),
1385                    transaction.outputs.to_vec(),
1386                    transaction.expected_signed_transaction,
1387                    transaction.expected_transaction_id,
1388                );
1389            });
1390        }
1391
1392        #[test]
1393        fn test_reconstructed_mainnet_transactions() {
1394            TRANSACTIONS.iter().for_each(|transaction| {
1395                test_reconstructed_transaction::<N>(
1396                    transaction.version,
1397                    transaction.lock_time,
1398                    transaction.inputs.to_vec(),
1399                    transaction.outputs.to_vec(),
1400                    transaction.expected_signed_transaction,
1401                    transaction.expected_transaction_id,
1402                );
1403            });
1404        }
1405    }
1406
1407    mod test_real_mainnet_transactions {
1408        use super::*;
1409        type N = Mainnet;
1410
1411        const REAL_TRANSACTIONS: [TransactionTestCase; 5] = [
1412            TransactionTestCase { // Transaction 1 -> Segwit P2SH_P2WPKH to P2PKH and Bech32(P2WPKH)
1413                version: 1,
1414                lock_time: 0,
1415                inputs: &[
1416                    Input {
1417                        private_key: "L1fUQgwdWcqGUAr3kFznuAP36Vw3oFeGHH29XRYMwxN1HpSw5yBm",
1418                        address_format: BitcoinFormat::P2SH_P2WPKH,
1419                        transaction_id: "a5766fafb27aba97e7aeb3e71be79806dd23f03bbd1b61135bf5792159f42ab6",
1420                        index: 0,
1421                        redeem_script: None,
1422                        script_pub_key: None,
1423                        utxo_amount: BitcoinAmount(80000),
1424                        sequence: None,
1425                        sighash_code: SignatureHash::SIGHASH_ALL
1426                    },
1427                ],
1428                outputs: &[
1429                    Output {
1430                        address: "176DPNootfp2bSiE7KQUZp1VZj5EyGQeCt",
1431                        amount: BitcoinAmount(35000)
1432                    },
1433                    Output {
1434                        address: "bc1qcsjz44ce84j3650qfu9k87tyd3z8h4qyxz470n",
1435                        amount: BitcoinAmount(35000)
1436                    },
1437                ],
1438                expected_signed_transaction: "01000000000101b62af4592179f55b13611bbd3bf023dd0698e71be7b3aee797ba7ab2af6f76a50000000017160014b5ccbe3c5a285af4afada113a8619827fb30b2eeffffffff02b8880000000000001976a91442cd2c7460acc561c96b11c4aa96d0346b84db7f88acb888000000000000160014c4242ad7193d651d51e04f0b63f9646c447bd404024730440220449ca32ff3f8da3c17c1813dac91010cb1fea7a77b2f63065184b8318e1b9ed70220315da34cfeae62c26557c40f5ac5cde46b2801349e6677fc96597b4bfee04b0b012102973e9145ca85357b06de3009a12db171d70bae8a648dc8188e49723a2a46459100000000",
1439                expected_transaction_id: "60805eb82c53d9c53900ad6d1c423ffc2235caa0c266625afd9cf03e856bf92c",
1440            },
1441            TransactionTestCase { // Transaction 2 -> P2PKH to P2SH_P2WPKH and P2PKH uncompressed
1442                version: 1,
1443                lock_time: 0,
1444                inputs: &[
1445                    Input {
1446                        private_key: "KzZQ4ZzAecDmeDqxEJqSKpCfpPCa1x74ouyBhXUgMV2UdqNcaJiJ",
1447                        address_format: BitcoinFormat::P2PKH,
1448                        transaction_id: "60805eb82c53d9c53900ad6d1c423ffc2235caa0c266625afd9cf03e856bf92c",
1449                        index: 0,
1450                        redeem_script: None,
1451                        script_pub_key: None,
1452                        utxo_amount: BitcoinAmount(0),
1453                        sequence: None,
1454                        sighash_code: SignatureHash::SIGHASH_ALL
1455                    },
1456                ],
1457                outputs: &[
1458                    Output {
1459                        address: "3QDTHVyuJrHixUhhsdZXQ7M8P9MQngmw1P",
1460                        amount: BitcoinAmount(12000)
1461                    },
1462                    Output {
1463                        address: "1C5RdoaGMVyQy8qjk96NsL4dW79aVPYrCK",
1464                        amount: BitcoinAmount(12000)
1465                    },
1466                ],
1467                expected_signed_transaction: "01000000012cf96b853ef09cfd5a6266c2a0ca3522fc3f421c6dad0039c5d9532cb85e8060000000006a473044022079471aadca4be014260a4788e7dc7d7168712c8f21c536f326caccb843569ab802206c7b464e3fbe0518f147ee7c5fa39c05e04e7ed17fbe464a2773b179fe0ef35401210384faa5d9710f727523906f6d2fe781b40cf58a3139d02eeaad293dd03be7b69cffffffff02e02e00000000000017a914f7146aaa6f24a1012528c1d27cfe49d256d5a70187e02e0000000000001976a914797f9c80ef57ba7f30b31598383683923a5a7a7c88ac00000000",
1468                expected_transaction_id: "76ef90fa70e4c10adc358432a979683a2cf1855ff545f88c5022dea8863ed5ab",
1469            },
1470            TransactionTestCase { // Transaction 3 -> Bech32 (P2WPKH) to P2SH_P2WPKH
1471                version: 1,
1472                lock_time: 0,
1473                inputs: &[
1474                    Input {
1475                        private_key: "L5HiUByNV6D4anzT5aMhheZpG9oKdcvoPXjWJopEPiEzFisNTM7X",
1476                        address_format: BitcoinFormat::Bech32,
1477                        transaction_id: "60805eb82c53d9c53900ad6d1c423ffc2235caa0c266625afd9cf03e856bf92c",
1478                        index: 1,
1479                        redeem_script: None,
1480                        script_pub_key: None,
1481                        utxo_amount: BitcoinAmount(35000),
1482                        sequence: None,
1483                        sighash_code: SignatureHash::SIGHASH_ALL
1484                    },
1485                ],
1486                outputs: &[
1487                    Output {
1488                        address: "3QDTHVyuJrHixUhhsdZXQ7M8P9MQngmw1P",
1489                        amount: BitcoinAmount(25000)
1490                    },
1491                ],
1492                expected_signed_transaction: "010000000001012cf96b853ef09cfd5a6266c2a0ca3522fc3f421c6dad0039c5d9532cb85e80600100000000ffffffff01a86100000000000017a914f7146aaa6f24a1012528c1d27cfe49d256d5a701870247304402206af8b1cad8d8138631f2b2b08535643afb0c9597e1dd9b8daa4a565be274c96902203844c6af50658fb244370afaaffdb6f6e85ca681b80cd094bfd4f3eeae4febf0012103dcf5a50ac66bde7fe9f01c4710fb5d438d51f1da1ce138863d34fee6499f328900000000",
1493                expected_transaction_id: "32464234781c37831398b5d2f1e1766f8dbb55ac3b41ed047e365c07e9b03429",
1494            },
1495            TransactionTestCase { // Transaction 4 -> Segwit P2SH_P2WPKH to Bech32(P2WPKH) and itself
1496                version: 1,
1497                lock_time: 0,
1498                inputs: &[
1499                    Input {
1500                        private_key: "L5TmwLMEyEqMAYj1qd7Fx9YRhNJTCvNn4ofr98ErbgHA99GjLBXC",
1501                        address_format: BitcoinFormat::P2SH_P2WPKH,
1502                        transaction_id: "32464234781c37831398b5d2f1e1766f8dbb55ac3b41ed047e365c07e9b03429",
1503                        index: 0,
1504                        redeem_script: None,
1505                        script_pub_key: None,
1506                        utxo_amount: BitcoinAmount(25000),
1507                        sequence: None,
1508                        sighash_code: SignatureHash::SIGHASH_ALL
1509                    },
1510                    Input {
1511                        private_key: "L5TmwLMEyEqMAYj1qd7Fx9YRhNJTCvNn4ofr98ErbgHA99GjLBXC",
1512                        address_format: BitcoinFormat::P2SH_P2WPKH,
1513                        transaction_id: "76ef90fa70e4c10adc358432a979683a2cf1855ff545f88c5022dea8863ed5ab",
1514                        index: 0,
1515                        redeem_script: None,
1516                        script_pub_key: None,
1517                        utxo_amount: BitcoinAmount(12000),
1518                        sequence: None,
1519                        sighash_code: SignatureHash::SIGHASH_ALL
1520                    },
1521                ],
1522                outputs: &[
1523                    Output {
1524                        address: "bc1qzkuhp5jxuvwx90eg65wkxuw6y2pfe740yw6h5s",
1525                        amount: BitcoinAmount(12000)
1526                    },
1527                    Output {
1528                        address: "3QDTHVyuJrHixUhhsdZXQ7M8P9MQngmw1P",
1529                        amount: BitcoinAmount(15000)
1530                    },
1531                ],
1532                expected_signed_transaction: "010000000001022934b0e9075c367e04ed413bac55bb8d6f76e1f1d2b5981383371c78344246320000000017160014354816a98500d7df9201d46e008c203dd5143b92ffffffffabd53e86a8de22508cf845f55f85f12c3a6879a9328435dc0ac1e470fa90ef760000000017160014354816a98500d7df9201d46e008c203dd5143b92ffffffff02e02e00000000000016001415b970d246e31c62bf28d51d6371da22829cfaaf983a00000000000017a914f7146aaa6f24a1012528c1d27cfe49d256d5a7018702483045022100988bc569371f74d6e49f20ae05ab06abfbe7ba92bbc177b61e38c0c9f430646702207a874da47387b6cfc066c26c24c99ccb75dac6772a0f94b7327703bdb156c4c8012103f850b5fa8fe53be8675dd3045ed89c8a4235155b484d88eb62d0afed7cb9ef050247304402204296465f1f95480f058ccebd70a0f80b9f092021a15793c954f39373e1e6500102206ca2d3f6cb68d1a9fde36ed6ded6509e2284c6afe860abf7f49c3ae18944ffdf012103f850b5fa8fe53be8675dd3045ed89c8a4235155b484d88eb62d0afed7cb9ef0500000000",
1533                expected_transaction_id: "6a06bd83718f24dd1883332939e59fdd26b95d8a328eac37a45b7c489618eac8",
1534            },
1535            TransactionTestCase { // Transaction 5 -> P2SH_P2WPKH, P2PKH uncompressed, and Bech32(P2WPKH) to Bech32(P2WPKH)
1536                version: 1,
1537                lock_time: 0,
1538                inputs: &[
1539                    Input {
1540                        private_key: "L5TmwLMEyEqMAYj1qd7Fx9YRhNJTCvNn4ofr98ErbgHA99GjLBXC",
1541                        address_format: BitcoinFormat::P2SH_P2WPKH,
1542                        transaction_id: "6a06bd83718f24dd1883332939e59fdd26b95d8a328eac37a45b7c489618eac8",
1543                        index: 1,
1544                        redeem_script: None,
1545                        script_pub_key: None,
1546                        utxo_amount: BitcoinAmount(15000),
1547                        sequence: None,
1548                        sighash_code: SignatureHash::SIGHASH_ALL
1549                    },
1550                    Input {
1551                        private_key: "5KRoKpnWWav74XDgz28opnJUsBozUg8STwEQPq354yUa3MiXySn",
1552                        address_format: BitcoinFormat::P2PKH,
1553                        transaction_id: "76ef90fa70e4c10adc358432a979683a2cf1855ff545f88c5022dea8863ed5ab",
1554                        index: 1,
1555                        redeem_script: None,
1556                        script_pub_key: None,
1557                        utxo_amount: BitcoinAmount(0),
1558                        sequence: None,
1559                        sighash_code: SignatureHash::SIGHASH_ALL
1560                    },
1561                    Input {
1562                        private_key: "Kzs2rY8y9brmULJ3VK9gZHiZAhNJ2ttjn7ZuyJbG52pToZfCpQDr",
1563                        address_format: BitcoinFormat::Bech32,
1564                        transaction_id: "6a06bd83718f24dd1883332939e59fdd26b95d8a328eac37a45b7c489618eac8",
1565                        index: 0,
1566                        redeem_script: None,
1567                        script_pub_key: None,
1568                        utxo_amount: BitcoinAmount(12000),
1569                        sequence: None,
1570                        sighash_code: SignatureHash::SIGHASH_ALL
1571                    },
1572                ],
1573                outputs: &[
1574                    Output {
1575                        address: "bc1qmj865gnmg3hv7eh74qmvu5fcde43ecd7haa5hy",
1576                        amount: BitcoinAmount(30000)
1577                    },
1578                ],
1579                expected_signed_transaction: "01000000000103c8ea1896487c5ba437ac8e328a5db926dd9fe53929338318dd248f7183bd066a0100000017160014354816a98500d7df9201d46e008c203dd5143b92ffffffffabd53e86a8de22508cf845f55f85f12c3a6879a9328435dc0ac1e470fa90ef76010000008b4830450221008bf28b9f9e2c6d7d0ef9705b7fd914e7693b2f4f3584deff6dfa9dc83fc9f73402201cdbf5cd78bf04ccedfa11f17cff3728965dd328d30fad4f91ba2be57fb2ccab014104db232c08ac5f0332d317e6cd805f3e29e98b93fc9ca74831a6c5d27a0368cdb0862d536a445250a8de9d92cf1d450c7dc9b8efd6ca2ff0865d553f85f1bd346fffffffffc8ea1896487c5ba437ac8e328a5db926dd9fe53929338318dd248f7183bd066a0000000000ffffffff013075000000000000160014dc8faa227b446ecf66fea836ce51386e6b1ce1be02483045022100c77d6548c8f068d7088d1a5eab91be1f4bd394fdd7e7334699ccb1533af2c6300220621399e24b9f84bb580fab62ced44b979f0b5a06a1c429ffe4f8c2ae27f740fb012103f850b5fa8fe53be8675dd3045ed89c8a4235155b484d88eb62d0afed7cb9ef05000247304402205b3676bb82313d8ed25dec2efc30aa24076b4a5c0dc0e2b2953507a8135a470102207cad2e535a5cac8b947c9d37aeb9162ec745c61b7133eafba790442faa2a19000121030f36fbc8825fcdc2b79e5764b6bb70c2038bf4dba63dbf71483320e4d7f63a0500000000",
1580                expected_transaction_id: "b2eb46fe6f8075caf013cd8e947f3b9dc06a416896d28aef450c9ec8d310361f",
1581            }
1582        ];
1583
1584        #[test]
1585        fn test_real_mainnet_transactions() {
1586            REAL_TRANSACTIONS.iter().for_each(|transaction| {
1587                test_transaction::<N>(
1588                    transaction.version,
1589                    transaction.lock_time,
1590                    transaction.inputs.to_vec(),
1591                    transaction.outputs.to_vec(),
1592                    transaction.expected_signed_transaction,
1593                    transaction.expected_transaction_id,
1594                );
1595            });
1596        }
1597
1598        #[test]
1599        fn test_real_reconstructed_mainnet_transactions() {
1600            REAL_TRANSACTIONS.iter().for_each(|transaction| {
1601                test_reconstructed_transaction::<N>(
1602                    transaction.version,
1603                    transaction.lock_time,
1604                    transaction.inputs.to_vec(),
1605                    transaction.outputs.to_vec(),
1606                    transaction.expected_signed_transaction,
1607                    transaction.expected_transaction_id,
1608                );
1609            });
1610        }
1611    }
1612
1613    mod test_invalid_transactions {
1614        use super::*;
1615        type N = Mainnet;
1616
1617        const INVALID_INPUTS: [Input; 7] = [
1618            Input {
1619                private_key: "L5BsLN6keEWUuF1JxfG6w5U1FDHs29faMpr9QX2MMVuQt7ymTorX",
1620                address_format: BitcoinFormat::P2SH_P2WPKH,
1621                transaction_id: "61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d",
1622                index: 0,
1623                redeem_script: None,
1624                script_pub_key: None,
1625                utxo_amount: BitcoinAmount(0),
1626                sequence: Some([0xff, 0xff, 0xff, 0xff]),
1627                sighash_code: SignatureHash::SIGHASH_ALL,
1628            },
1629            Input {
1630                private_key: "L5BsLN6keEWUuF1JxfG6w5U1FDHs29faMpr9QX2MMVuQt7ymTorX",
1631                address_format: BitcoinFormat::P2PKH,
1632                transaction_id: "7dabce",
1633                index: 0,
1634                redeem_script: None,
1635                script_pub_key: Some("a914e39b100350d6896ad0f572c9fe452fcac549fe7b87"),
1636                utxo_amount: BitcoinAmount(10000),
1637                sequence: Some([0xff, 0xff, 0xff, 0xff]),
1638                sighash_code: SignatureHash::SIGHASH_ALL,
1639            },
1640            Input {
1641                private_key: "L5BsLN6keEWUuF1JxfG6w5U1FDHs29faMpr9QX2MMVuQt7ymTorX",
1642                address_format: BitcoinFormat::P2SH_P2WPKH,
1643                transaction_id: "7dabce",
1644                index: 0,
1645                redeem_script: Some("00142b6e15d83c28acd7e2373ba81bb4adf4dee3c01a"),
1646                script_pub_key: None,
1647                utxo_amount: BitcoinAmount(10000),
1648                sequence: Some([0xff, 0xff, 0xff, 0xff]),
1649                sighash_code: SignatureHash::SIGHASH_ALL,
1650            },
1651            Input {
1652                private_key: "L5BsLN6keEWUuF1JxfG6w5U1FDHs29faMpr9QX2MMVuQt7ymTorX",
1653                address_format: BitcoinFormat::P2SH_P2WPKH,
1654                transaction_id: "7dabce588a8a57786790",
1655                index: 0,
1656                redeem_script: Some("00142b6e15d83c28acd7e2373ba81bb4adf4dee3c01a"),
1657                script_pub_key: None,
1658                utxo_amount: BitcoinAmount(10000),
1659                sequence: Some([0xff, 0xff, 0xff, 0xff]),
1660                sighash_code: SignatureHash::SIGHASH_ALL,
1661            },
1662            Input {
1663                private_key: "L5BsLN6keEWUuF1JxfG6w5U1FDHs29faMpr9QX2MMVuQt7ymTorX",
1664                address_format: BitcoinFormat::P2SH_P2WPKH,
1665                transaction_id: "7dabce588a8a57786790d27810514f5ffccff4148a8105894da57c985d02cdbb7dabce",
1666                index: 0,
1667                redeem_script: Some("00142b6e15d83c28acd7e2373ba81bb4adf4dee3c01a"),
1668                script_pub_key: None,
1669                utxo_amount: BitcoinAmount(10000),
1670                sequence: Some([0xff, 0xff, 0xff, 0xff]),
1671                sighash_code: SignatureHash::SIGHASH_ALL,
1672            },
1673            Input {
1674                private_key: "L5BsLN6keEWUuF1JxfG6w5U1FDHs29faMpr9QX2MMVuQt7ymTorX",
1675                address_format: BitcoinFormat::Bech32,
1676                transaction_id: "61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d",
1677                index: 0,
1678                redeem_script: Some("00142b6e15d83c28acd7e2373ba81bb4adf4dee3c01a"),
1679                script_pub_key: None,
1680                utxo_amount: BitcoinAmount(0),
1681                sequence: Some([0xff, 0xff, 0xff, 0xff]),
1682                sighash_code: SignatureHash::SIGHASH_ALL,
1683            },
1684            Input {
1685                private_key: "",
1686                address_format: BitcoinFormat::P2PKH,
1687                transaction_id: "",
1688                index: 0,
1689                redeem_script: Some(""),
1690                script_pub_key: None,
1691                utxo_amount: BitcoinAmount(0),
1692                sequence: None,
1693                sighash_code: SignatureHash::SIGHASH_ALL,
1694            },
1695        ];
1696
1697        #[test]
1698        fn test_invalid_inputs() {
1699            for input in INVALID_INPUTS.iter() {
1700                let transaction_id = hex::decode(input.transaction_id).unwrap();
1701                let redeem_script = input.redeem_script.map(|script| hex::decode(script).unwrap());
1702                let script_pub_key = input.script_pub_key.map(|script| hex::decode(script).unwrap());
1703                let sequence = input.sequence.map(|seq| seq.to_vec());
1704
1705                let private_key = BitcoinPrivateKey::<N>::from_str(input.private_key);
1706                match private_key {
1707                    Ok(private_key) => {
1708                        let address = private_key.to_address(&input.address_format).unwrap();
1709                        let invalid_input = BitcoinTransactionInput::<N>::new(
1710                            transaction_id,
1711                            input.index,
1712                            Some(address),
1713                            Some(input.utxo_amount),
1714                            redeem_script,
1715                            script_pub_key,
1716                            sequence,
1717                            input.sighash_code,
1718                        );
1719                        assert!(invalid_input.is_err());
1720                    }
1721                    _ => assert!(private_key.is_err()),
1722                }
1723            }
1724        }
1725    }
1726
1727    mod test_helper_functions {
1728        use super::*;
1729
1730        const LENGTH_VALUES: [(u64, [u8; 9]); 14] = [
1731            (20, [0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
1732            (32, [0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
1733            (200, [0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
1734            (252, [0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
1735            (253, [0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
1736            (40000, [0xfd, 0x40, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
1737            (65535, [0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
1738            (65536, [0xfe, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]),
1739            (2000000000, [0xfe, 0x00, 0x94, 0x35, 0x77, 0x00, 0x00, 0x00, 0x00]),
1740            (2000000000, [0xfe, 0x00, 0x94, 0x35, 0x77, 0x00, 0x00, 0x00, 0x00]),
1741            (4294967295, [0xfe, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00]),
1742            (4294967296, [0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]),
1743            (
1744                500000000000000000,
1745                [0xff, 0x00, 0x00, 0xb2, 0xd3, 0x59, 0x5b, 0xf0, 0x06],
1746            ),
1747            (
1748                18446744073709551615,
1749                [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
1750            ),
1751        ];
1752
1753        #[test]
1754        fn test_variable_length_integer() {
1755            LENGTH_VALUES.iter().for_each(|(size, expected_output)| {
1756                let variable_length_int = variable_length_integer(*size).unwrap();
1757                let pruned_expected_output = &expected_output[..variable_length_int.len()];
1758                assert_eq!(hex::encode(pruned_expected_output), hex::encode(&variable_length_int));
1759            });
1760        }
1761    }
1762}