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
17pub fn variable_length_integer(value: u64) -> Result<Vec<u8>, TransactionError> {
20 match value {
21 0..=252 => Ok(vec![value as u8]),
23 253..=65535 => Ok([vec![0xfd], (value as u16).to_le_bytes().to_vec()].concat()),
25 65536..=4294967295 => Ok([vec![0xfe], (value as u32).to_le_bytes().to_vec()].concat()),
27 _ => Ok([vec![0xff], value.to_le_bytes().to_vec()].concat()),
29 }
30}
31
32pub 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 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 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
92pub 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#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
142#[allow(non_camel_case_types)]
143pub enum SignatureHash {
144 SIGHASH_ALL = 0x01,
146 SIGHASH_NONE = 0x02,
149 SIGHASH_SINGLE = 0x03,
152 SIGHASH_ALL_SIGHASH_ANYONECANPAY = 0x81,
156 SIGHASH_NONE_SIGHASH_ANYONECANPAY = 0x82,
160 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#[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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
217pub struct Outpoint<N: BitcoinNetwork> {
218 pub reverse_transaction_id: Vec<u8>,
220 pub index: u32,
222 pub amount: Option<BitcoinAmount>,
224 pub script_pub_key: Option<Vec<u8>>,
226 pub redeem_script: Option<Vec<u8>>,
228 pub address: Option<BitcoinAddress<N>>,
230}
231
232impl<N: BitcoinNetwork> Outpoint<N> {
233 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 {
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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
298pub struct BitcoinTransactionInput<N: BitcoinNetwork> {
299 pub outpoint: Outpoint<N>,
301 pub script_sig: Vec<u8>,
303 pub sequence: Vec<u8>,
306 pub sighash_code: SignatureHash,
308 pub witnesses: Vec<Vec<u8>>,
310 pub is_signed: bool,
312}
313
314impl<N: BitcoinNetwork> BitcoinTransactionInput<N> {
315 const DEFAULT_SEQUENCE: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
316
317 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
436pub struct BitcoinTransactionOutput {
437 pub amount: BitcoinAmount,
439 pub script_pub_key: Vec<u8>,
441}
442
443impl BitcoinTransactionOutput {
444 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 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 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#[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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
500pub struct BitcoinTransactionParameters<N: BitcoinNetwork> {
501 pub version: u32,
503 pub inputs: Vec<BitcoinTransactionInput<N>>,
505 pub outputs: Vec<BitcoinTransactionOutput>,
507 pub lock_time: u32,
509 pub segwit_flag: bool,
511}
512
513impl<N: BitcoinNetwork> BitcoinTransactionParameters<N> {
514 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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
575pub struct BitcoinTransaction<N: BitcoinNetwork> {
576 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 fn new(parameters: &Self::TransactionParameters) -> Result<Self, TransactionError> {
590 Ok(Self {
591 parameters: parameters.clone(),
592 })
593 }
594
595 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 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 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 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![]; 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 Ok(transaction)
689 }
690
691 fn from_transaction_bytes(transaction: &Vec<u8>) -> Result<Self, TransactionError> {
694 Ok(Self {
695 parameters: Self::TransactionParameters::read(&transaction[..])?,
696 })
697 }
698
699 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 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 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 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 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 #[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 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 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 { 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 { 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 { 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"), 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 { 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 { 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 { 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 { 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 { 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", 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 { 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"), 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 { 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 { 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 { 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 { 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 { 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}