1use crate::crypto;
7use crate::encoding;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct OutPoint {
14 pub txid: [u8; 32],
16 pub vout: u32,
18}
19
20#[derive(Clone, Debug)]
22pub struct TxIn {
23 pub previous_output: OutPoint,
25 pub script_sig: Vec<u8>,
27 pub sequence: u32,
29}
30
31#[derive(Clone, Debug)]
33pub struct TxOut {
34 pub value: u64,
36 pub script_pubkey: Vec<u8>,
38}
39
40#[derive(Clone, Debug)]
42pub struct Transaction {
43 pub version: i32,
45 pub inputs: Vec<TxIn>,
47 pub outputs: Vec<TxOut>,
49 pub witnesses: Vec<Vec<Vec<u8>>>,
51 pub locktime: u32,
53}
54
55impl Transaction {
56 #[must_use]
58 pub fn new(version: i32) -> Self {
59 Self {
60 version,
61 inputs: Vec::new(),
62 outputs: Vec::new(),
63 witnesses: Vec::new(),
64 locktime: 0,
65 }
66 }
67
68 #[must_use]
70 pub fn has_witness(&self) -> bool {
71 self.witnesses.iter().any(|w| !w.is_empty())
72 }
73
74 #[must_use]
76 pub fn serialize_legacy(&self) -> Vec<u8> {
77 let mut buf = Vec::with_capacity(256);
78
79 buf.extend_from_slice(&self.version.to_le_bytes());
81
82 encoding::encode_compact_size(&mut buf, self.inputs.len() as u64);
84 for input in &self.inputs {
85 buf.extend_from_slice(&input.previous_output.txid);
86 buf.extend_from_slice(&input.previous_output.vout.to_le_bytes());
87 encoding::encode_compact_size(&mut buf, input.script_sig.len() as u64);
88 buf.extend_from_slice(&input.script_sig);
89 buf.extend_from_slice(&input.sequence.to_le_bytes());
90 }
91
92 encoding::encode_compact_size(&mut buf, self.outputs.len() as u64);
94 for output in &self.outputs {
95 buf.extend_from_slice(&output.value.to_le_bytes());
96 encoding::encode_compact_size(&mut buf, output.script_pubkey.len() as u64);
97 buf.extend_from_slice(&output.script_pubkey);
98 }
99
100 buf.extend_from_slice(&self.locktime.to_le_bytes());
102
103 buf
104 }
105
106 #[must_use]
110 pub fn serialize_witness(&self) -> Vec<u8> {
111 if !self.has_witness() {
112 return self.serialize_legacy();
113 }
114
115 let mut buf = Vec::with_capacity(512);
116
117 buf.extend_from_slice(&self.version.to_le_bytes());
119
120 buf.push(0x00); buf.push(0x01); encoding::encode_compact_size(&mut buf, self.inputs.len() as u64);
126 for input in &self.inputs {
127 buf.extend_from_slice(&input.previous_output.txid);
128 buf.extend_from_slice(&input.previous_output.vout.to_le_bytes());
129 encoding::encode_compact_size(&mut buf, input.script_sig.len() as u64);
130 buf.extend_from_slice(&input.script_sig);
131 buf.extend_from_slice(&input.sequence.to_le_bytes());
132 }
133
134 encoding::encode_compact_size(&mut buf, self.outputs.len() as u64);
136 for output in &self.outputs {
137 buf.extend_from_slice(&output.value.to_le_bytes());
138 encoding::encode_compact_size(&mut buf, output.script_pubkey.len() as u64);
139 buf.extend_from_slice(&output.script_pubkey);
140 }
141
142 for (i, _input) in self.inputs.iter().enumerate() {
144 let witness_stack = self.witnesses.get(i);
145 match witness_stack {
146 Some(stack) if !stack.is_empty() => {
147 encoding::encode_compact_size(&mut buf, stack.len() as u64);
148 for item in stack {
149 encoding::encode_compact_size(&mut buf, item.len() as u64);
150 buf.extend_from_slice(item);
151 }
152 }
153 _ => {
154 buf.push(0x00); }
156 }
157 }
158
159 buf.extend_from_slice(&self.locktime.to_le_bytes());
161
162 buf
163 }
164
165 #[must_use]
169 pub fn txid(&self) -> [u8; 32] {
170 let mut hash = crypto::double_sha256(&self.serialize_legacy());
171 hash.reverse(); hash
173 }
174
175 #[must_use]
179 pub fn wtxid(&self) -> [u8; 32] {
180 let mut hash = crypto::double_sha256(&self.serialize_witness());
181 hash.reverse();
182 hash
183 }
184
185 #[must_use]
190 pub fn vsize(&self) -> usize {
191 let base_size = self.serialize_legacy().len();
192 let total_size = self.serialize_witness().len();
193 let weight = base_size * 3 + total_size;
194 weight.div_ceil(4)
195 }
196}
197
198pub fn parse_unsigned_tx(data: &[u8]) -> Result<Transaction, crate::error::SignerError> {
203 use crate::error::SignerError;
204
205 fn safe_usize(val: u64) -> Result<usize, SignerError> {
207 usize::try_from(val).map_err(|_| {
208 SignerError::ParseError(format!("compact size {val} exceeds platform usize"))
209 })
210 }
211
212 let mut off;
213
214 if data.len() < 4 {
216 return Err(SignerError::ParseError("tx too short for version".into()));
217 }
218 let version = i32::from_le_bytes([data[0], data[1], data[2], data[3]]);
219 off = 4;
220
221 let input_count = safe_usize(encoding::read_compact_size(data, &mut off)?)?;
223
224 let mut inputs = Vec::with_capacity(input_count);
225 for _ in 0..input_count {
226 if off + 36 > data.len() {
227 return Err(SignerError::ParseError(
228 "tx truncated in input outpoint".into(),
229 ));
230 }
231 let mut txid = [0u8; 32];
232 txid.copy_from_slice(&data[off..off + 32]);
233 off += 32;
234 let vout = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
235 off += 4;
236
237 let script_len = safe_usize(encoding::read_compact_size(data, &mut off)?)?;
238 if off + script_len > data.len() {
239 return Err(SignerError::ParseError("tx truncated in scriptSig".into()));
240 }
241 let script_sig = data[off..off + script_len].to_vec();
242 off += script_len;
243
244 if off + 4 > data.len() {
245 return Err(SignerError::ParseError("tx truncated in sequence".into()));
246 }
247 let sequence = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
248 off += 4;
249
250 inputs.push(TxIn {
251 previous_output: OutPoint { txid, vout },
252 script_sig,
253 sequence,
254 });
255 }
256
257 let output_count = safe_usize(encoding::read_compact_size(data, &mut off)?)?;
259
260 let mut outputs = Vec::with_capacity(output_count);
261 for _ in 0..output_count {
262 if off + 8 > data.len() {
263 return Err(SignerError::ParseError(
264 "tx truncated in output value".into(),
265 ));
266 }
267 let mut val_bytes = [0u8; 8];
268 val_bytes.copy_from_slice(&data[off..off + 8]);
269 let value = u64::from_le_bytes(val_bytes);
270 off += 8;
271
272 let spk_len = safe_usize(encoding::read_compact_size(data, &mut off)?)?;
273 if off + spk_len > data.len() {
274 return Err(SignerError::ParseError(
275 "tx truncated in scriptPubKey".into(),
276 ));
277 }
278 let script_pubkey = data[off..off + spk_len].to_vec();
279 off += spk_len;
280
281 outputs.push(TxOut {
282 value,
283 script_pubkey,
284 });
285 }
286
287 if off + 4 > data.len() {
289 return Err(SignerError::ParseError("tx truncated in locktime".into()));
290 }
291 let locktime = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
292 off += 4;
293
294 if off != data.len() {
296 return Err(SignerError::ParseError(format!(
297 "tx has {} trailing bytes after locktime",
298 data.len() - off
299 )));
300 }
301
302 Ok(Transaction {
303 version,
304 inputs,
305 outputs,
306 witnesses: Vec::new(),
307 locktime,
308 })
309}
310
311pub const MIN_RELAY_FEE_SAT_PER_VB: u64 = 1;
317
318pub const DUST_LIMIT_P2WPKH: u64 = 546;
320
321pub const DUST_LIMIT_P2PKH: u64 = 546;
323
324pub const DUST_LIMIT_P2TR: u64 = 330;
326
327pub fn estimate_fee(tx: &Transaction, fee_rate_sat_per_vb: u64) -> u64 {
335 let vsize = tx.vsize() as u64;
336 vsize
337 .saturating_mul(fee_rate_sat_per_vb)
338 .max(MIN_RELAY_FEE_SAT_PER_VB)
339}
340
341pub fn estimate_vsize(
349 num_p2wpkh_inputs: usize,
350 num_p2tr_inputs: usize,
351 num_p2pkh_inputs: usize,
352 num_outputs: usize,
353) -> usize {
354 let overhead = 10 + 2; let p2wpkh_weight = num_p2wpkh_inputs * 271;
360 let p2tr_weight = num_p2tr_inputs * 230;
362 let p2pkh_weight = num_p2pkh_inputs * 592;
364
365 let output_weight = num_outputs * 34 * 4;
367
368 let total_weight = overhead * 4 + p2wpkh_weight + p2tr_weight + p2pkh_weight + output_weight;
369 total_weight.div_ceil(4)
370}
371
372#[derive(Clone, Debug)]
378pub struct Recipient {
379 pub script_pubkey: Vec<u8>,
381 pub amount: u64,
383}
384
385pub fn build_batch_transaction(
399 utxos: &[(OutPoint, u64)],
400 recipients: &[Recipient],
401 change_script_pubkey: &[u8],
402 fee_rate_sat_per_vb: u64,
403) -> Result<Transaction, crate::error::SignerError> {
404 use crate::error::SignerError;
405
406 if utxos.is_empty() {
407 return Err(SignerError::ParseError("no UTXOs provided".into()));
408 }
409 if recipients.is_empty() {
410 return Err(SignerError::ParseError("no recipients provided".into()));
411 }
412
413 let total_input: u64 = utxos.iter().map(|(_, v)| v).sum();
414 let total_output: u64 = recipients.iter().map(|r| r.amount).sum();
415
416 if total_input < total_output {
417 return Err(SignerError::ParseError(format!(
418 "insufficient funds: {} < {}",
419 total_input, total_output
420 )));
421 }
422
423 let num_outputs_with_change = recipients.len() + 1;
425 let estimated_vsize = estimate_vsize(utxos.len(), 0, 0, num_outputs_with_change);
426 let estimated_fee = (estimated_vsize as u64).saturating_mul(fee_rate_sat_per_vb);
427
428 let change_amount = total_input
429 .checked_sub(total_output)
430 .and_then(|r| r.checked_sub(estimated_fee))
431 .unwrap_or(0);
432
433 let mut tx = Transaction::new(2);
434 tx.locktime = 0;
435
436 for (outpoint, _) in utxos {
438 tx.inputs.push(TxIn {
439 previous_output: outpoint.clone(),
440 script_sig: vec![],
441 sequence: 0xFFFFFFFD, });
443 }
444
445 for recipient in recipients {
447 tx.outputs.push(TxOut {
448 value: recipient.amount,
449 script_pubkey: recipient.script_pubkey.clone(),
450 });
451 }
452
453 if change_amount >= DUST_LIMIT_P2WPKH {
455 tx.outputs.push(TxOut {
456 value: change_amount,
457 script_pubkey: change_script_pubkey.to_vec(),
458 });
459 }
460
461 let actual_output_total: u64 = tx.outputs.iter().map(|o| o.value).sum();
463 if total_input < actual_output_total {
464 return Err(SignerError::ParseError(format!(
465 "insufficient after fee: {} < {}",
466 total_input, actual_output_total
467 )));
468 }
469
470 Ok(tx)
471}
472
473#[cfg(test)]
476#[allow(clippy::unwrap_used, clippy::expect_used)]
477mod tests {
478 use super::*;
479
480 fn sample_tx() -> Transaction {
481 let mut tx = Transaction::new(2);
482 tx.inputs.push(TxIn {
483 previous_output: OutPoint {
484 txid: [0xAA; 32],
485 vout: 0,
486 },
487 script_sig: vec![],
488 sequence: 0xFFFFFFFF,
489 });
490 tx.outputs.push(TxOut {
491 value: 50_000,
492 script_pubkey: vec![
493 0x00, 0x14, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
494 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
495 ], });
497 tx
498 }
499
500 #[test]
501 fn test_legacy_serialization_structure() {
502 let tx = sample_tx();
503 let raw = tx.serialize_legacy();
504 assert_eq!(raw.len(), 82);
508 assert_eq!(&raw[..4], &2i32.to_le_bytes());
510 }
511
512 #[test]
513 fn test_witness_serialization_no_witness() {
514 let tx = sample_tx();
515 assert_eq!(tx.serialize_legacy(), tx.serialize_witness());
517 assert!(!tx.has_witness());
518 }
519
520 #[test]
521 fn test_witness_serialization_with_witness() {
522 let mut tx = sample_tx();
523 tx.witnesses.push(vec![
524 vec![0x30; 72], vec![0x02; 33], ]);
527 assert!(tx.has_witness());
528 let witness_raw = tx.serialize_witness();
529 let legacy_raw = tx.serialize_legacy();
530 assert!(witness_raw.len() > legacy_raw.len());
532 assert_eq!(witness_raw[4], 0x00); assert_eq!(witness_raw[5], 0x01); }
536
537 #[test]
538 fn test_txid_is_deterministic() {
539 let tx = sample_tx();
540 assert_eq!(tx.txid(), tx.txid());
541 }
542
543 #[test]
544 fn test_txid_ne_wtxid_with_witness() {
545 let mut tx = sample_tx();
546 tx.witnesses.push(vec![vec![0x01; 64]]);
547 assert_ne!(tx.txid(), tx.wtxid());
549 }
550
551 #[test]
552 fn test_txid_eq_wtxid_without_witness() {
553 let tx = sample_tx();
554 assert_eq!(tx.txid(), tx.wtxid());
555 }
556
557 #[test]
558 fn test_vsize_legacy() {
559 let tx = sample_tx();
560 let base = tx.serialize_legacy().len();
561 assert_eq!(tx.vsize(), base);
563 }
564
565 #[test]
566 fn test_vsize_segwit_is_discounted() {
567 let mut tx = sample_tx();
568 tx.witnesses.push(vec![vec![0x30; 72], vec![0x02; 33]]);
569 let base = tx.serialize_legacy().len();
570 let total = tx.serialize_witness().len();
571 let vsize = tx.vsize();
572 assert!(vsize < total);
574 assert!(vsize >= base);
575 }
576
577 #[test]
578 fn test_outpoint_equality() {
579 let o1 = OutPoint {
580 txid: [0x01; 32],
581 vout: 0,
582 };
583 let o2 = OutPoint {
584 txid: [0x01; 32],
585 vout: 0,
586 };
587 let o3 = OutPoint {
588 txid: [0x02; 32],
589 vout: 0,
590 };
591 assert_eq!(o1, o2);
592 assert_ne!(o1, o3);
593 }
594
595 #[test]
596 fn test_empty_transaction() {
597 let tx = Transaction::new(1);
598 let raw = tx.serialize_legacy();
599 assert_eq!(raw.len(), 10);
601 }
602
603 #[test]
604 fn test_multiple_inputs_outputs() {
605 let mut tx = Transaction::new(2);
606 for i in 0..3 {
607 tx.inputs.push(TxIn {
608 previous_output: OutPoint {
609 txid: [i as u8; 32],
610 vout: 0,
611 },
612 script_sig: vec![],
613 sequence: 0xFFFFFFFF,
614 });
615 }
616 for _ in 0..2 {
617 tx.outputs.push(TxOut {
618 value: 10_000,
619 script_pubkey: vec![0x76, 0xa9, 0x14],
620 });
621 }
622 let raw = tx.serialize_legacy();
623 assert!(raw.len() > 10);
624 assert_eq!(raw[4], 3); }
627
628 #[test]
631 fn test_estimate_fee_basic() {
632 let tx = sample_tx();
633 let fee = estimate_fee(&tx, 10);
634 assert!(fee > 0);
635 assert_eq!(fee, tx.vsize() as u64 * 10);
636 }
637
638 #[test]
639 fn test_estimate_fee_minimum() {
640 let tx = Transaction::new(1);
641 let fee = estimate_fee(&tx, 0);
642 assert!(fee >= MIN_RELAY_FEE_SAT_PER_VB);
643 }
644
645 #[test]
646 fn test_estimate_vsize_basic() {
647 let vsize = estimate_vsize(1, 0, 0, 2);
649 assert!(vsize > 0);
650 assert!(vsize > 100 && vsize < 250);
652 }
653
654 #[test]
655 fn test_estimate_vsize_taproot() {
656 let vsize = estimate_vsize(0, 1, 0, 1);
657 assert!(vsize > 0);
658 assert!(vsize > 50 && vsize < 200);
660 }
661
662 #[test]
663 fn test_dust_limits() {
664 assert_eq!(DUST_LIMIT_P2WPKH, 546);
665 assert_eq!(DUST_LIMIT_P2PKH, 546);
666 assert_eq!(DUST_LIMIT_P2TR, 330);
667 }
668
669 #[test]
672 fn test_batch_build_basic() {
673 let utxos = vec![(
674 OutPoint {
675 txid: [0x01; 32],
676 vout: 0,
677 },
678 100_000,
679 )];
680 let recipients = vec![Recipient {
681 script_pubkey: vec![0x00; 22],
682 amount: 50_000,
683 }];
684 let change_spk = vec![0x00; 22];
685 let tx = build_batch_transaction(&utxos, &recipients, &change_spk, 5).unwrap();
686 assert_eq!(tx.inputs.len(), 1);
687 assert!(tx.outputs.len() >= 1); }
689
690 #[test]
691 fn test_batch_build_with_change() {
692 let utxos = vec![(
693 OutPoint {
694 txid: [0x01; 32],
695 vout: 0,
696 },
697 1_000_000,
698 )];
699 let recipients = vec![Recipient {
700 script_pubkey: vec![0x00; 22],
701 amount: 100_000,
702 }];
703 let change_spk = vec![0x00; 22];
704 let tx = build_batch_transaction(&utxos, &recipients, &change_spk, 5).unwrap();
705 assert_eq!(tx.outputs.len(), 2);
707 let change = &tx.outputs[1];
708 assert!(change.value >= DUST_LIMIT_P2WPKH);
709 }
710
711 #[test]
712 fn test_batch_build_multi_recipient() {
713 let utxos = vec![
714 (
715 OutPoint {
716 txid: [0x01; 32],
717 vout: 0,
718 },
719 500_000,
720 ),
721 (
722 OutPoint {
723 txid: [0x02; 32],
724 vout: 1,
725 },
726 500_000,
727 ),
728 ];
729 let recipients = vec![
730 Recipient {
731 script_pubkey: vec![0x00; 22],
732 amount: 100_000,
733 },
734 Recipient {
735 script_pubkey: vec![0x01; 22],
736 amount: 200_000,
737 },
738 Recipient {
739 script_pubkey: vec![0x02; 22],
740 amount: 150_000,
741 },
742 ];
743 let change_spk = vec![0x00; 22];
744 let tx = build_batch_transaction(&utxos, &recipients, &change_spk, 10).unwrap();
745 assert_eq!(tx.inputs.len(), 2);
746 assert!(tx.outputs.len() >= 3); }
748
749 #[test]
750 fn test_batch_build_insufficient_funds() {
751 let utxos = vec![(
752 OutPoint {
753 txid: [0x01; 32],
754 vout: 0,
755 },
756 1_000,
757 )];
758 let recipients = vec![Recipient {
759 script_pubkey: vec![0x00; 22],
760 amount: 100_000,
761 }];
762 assert!(build_batch_transaction(&utxos, &recipients, &[], 5).is_err());
763 }
764
765 #[test]
766 fn test_batch_build_empty_utxos() {
767 let recipients = vec![Recipient {
768 script_pubkey: vec![],
769 amount: 100,
770 }];
771 assert!(build_batch_transaction(&[], &recipients, &[], 5).is_err());
772 }
773
774 #[test]
775 fn test_batch_build_empty_recipients() {
776 let utxos = vec![(
777 OutPoint {
778 txid: [0x01; 32],
779 vout: 0,
780 },
781 100_000,
782 )];
783 assert!(build_batch_transaction(&utxos, &[], &[], 5).is_err());
784 }
785
786 #[test]
787 fn test_batch_build_rbf_enabled() {
788 let utxos = vec![(
789 OutPoint {
790 txid: [0x01; 32],
791 vout: 0,
792 },
793 100_000,
794 )];
795 let recipients = vec![Recipient {
796 script_pubkey: vec![0x00; 22],
797 amount: 50_000,
798 }];
799 let tx = build_batch_transaction(&utxos, &recipients, &[0x00; 22], 5).unwrap();
800 assert_eq!(tx.inputs[0].sequence, 0xFFFFFFFD);
801 }
802
803 #[test]
816 fn test_btc_deserialize_real_p2pkh_tx() {
817 let raw_hex = "01000000019c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48010000006a47304402203da9d487be5302a6d69e02a861acff1da472885e43d7528ed9b1b537a8e2cac9022002d1bca03a1e9715a99971bafe3b1852b7a4f0168281cbd27a220380a01b3307012102c9950c622494c2e9ff5a003e33b690fe4832477d32c2d256c67eab8bf613b34effffffff02b6f50500000000001976a914bdf63990d6dc33d705b756e13dd135466c06b3b588ac845e0201000000001976a9145fb0e9755a3424efd2ba0587d20b1e98ee29814a88ac00000000";
818 let raw = hex::decode(raw_hex).unwrap();
819 let tx = parse_unsigned_tx(&raw).unwrap();
820
821 assert_eq!(tx.version, 1, "version must be 1");
823
824 assert_eq!(tx.inputs.len(), 1, "must have 1 input");
826 assert_eq!(tx.inputs[0].previous_output.vout, 1, "vout must be 1");
827 assert_eq!(tx.inputs[0].sequence, 0xFFFFFFFF, "sequence must be final");
828 assert_eq!(
830 hex::encode(&tx.inputs[0].previous_output.txid),
831 "9c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48"
832 );
833
834 assert_eq!(tx.inputs[0].script_sig.len(), 106);
836
837 assert_eq!(tx.outputs.len(), 2, "must have 2 outputs");
839 assert_eq!(tx.outputs[0].value, 390_582, "output 0 value: 390582 sats");
840 assert_eq!(
841 tx.outputs[1].value, 16_932_484,
842 "output 1 value: 16932484 sats"
843 );
844
845 assert_eq!(tx.outputs[0].script_pubkey.len(), 25);
847 assert_eq!(tx.outputs[0].script_pubkey[0], 0x76); assert_eq!(tx.outputs[0].script_pubkey[1], 0xa9); assert_eq!(tx.outputs[0].script_pubkey[24], 0xac); assert_eq!(
853 hex::encode(&tx.outputs[0].script_pubkey[3..23]),
854 "bdf63990d6dc33d705b756e13dd135466c06b3b5"
855 );
856
857 assert_eq!(tx.locktime, 0);
859 }
860
861 #[test]
863 fn test_btc_serialize_roundtrip_p2pkh() {
864 let raw_hex = "01000000019c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48010000006a47304402203da9d487be5302a6d69e02a861acff1da472885e43d7528ed9b1b537a8e2cac9022002d1bca03a1e9715a99971bafe3b1852b7a4f0168281cbd27a220380a01b3307012102c9950c622494c2e9ff5a003e33b690fe4832477d32c2d256c67eab8bf613b34effffffff02b6f50500000000001976a914bdf63990d6dc33d705b756e13dd135466c06b3b588ac845e0201000000001976a9145fb0e9755a3424efd2ba0587d20b1e98ee29814a88ac00000000";
865 let raw = hex::decode(raw_hex).unwrap();
866 let tx = parse_unsigned_tx(&raw).unwrap();
867 let re_serialized = tx.serialize_legacy();
868 assert_eq!(
869 hex::encode(&re_serialized),
870 raw_hex,
871 "serialize(deserialize(raw)) must equal raw"
872 );
873 }
874
875 #[test]
877 fn test_btc_txid_from_real_tx() {
878 let raw_hex = "01000000019c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48010000006a47304402203da9d487be5302a6d69e02a861acff1da472885e43d7528ed9b1b537a8e2cac9022002d1bca03a1e9715a99971bafe3b1852b7a4f0168281cbd27a220380a01b3307012102c9950c622494c2e9ff5a003e33b690fe4832477d32c2d256c67eab8bf613b34effffffff02b6f50500000000001976a914bdf63990d6dc33d705b756e13dd135466c06b3b588ac845e0201000000001976a9145fb0e9755a3424efd2ba0587d20b1e98ee29814a88ac00000000";
879 let raw = hex::decode(raw_hex).unwrap();
880 let tx = parse_unsigned_tx(&raw).unwrap();
881 let txid = tx.txid();
882 let txid_hex = hex::encode(txid);
884 assert_eq!(txid_hex.len(), 64);
885 let txid2 = tx.txid();
887 assert_eq!(txid, txid2);
888 }
889
890 #[test]
892 fn test_btc_fee_estimation_known_size() {
893 let raw_hex = "01000000019c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48010000006a47304402203da9d487be5302a6d69e02a861acff1da472885e43d7528ed9b1b537a8e2cac9022002d1bca03a1e9715a99971bafe3b1852b7a4f0168281cbd27a220380a01b3307012102c9950c622494c2e9ff5a003e33b690fe4832477d32c2d256c67eab8bf613b34effffffff02b6f50500000000001976a914bdf63990d6dc33d705b756e13dd135466c06b3b588ac845e0201000000001976a9145fb0e9755a3424efd2ba0587d20b1e98ee29814a88ac00000000";
894 let raw = hex::decode(raw_hex).unwrap();
895 let tx = parse_unsigned_tx(&raw).unwrap();
896
897 let vsize = tx.vsize();
899 assert_eq!(vsize, raw.len(), "legacy tx vsize == serialized length");
900
901 let fee = estimate_fee(&tx, 10);
903 assert_eq!(fee, vsize as u64 * 10);
904
905 let fee_high = estimate_fee(&tx, 50);
907 assert_eq!(fee_high, vsize as u64 * 50);
908 }
909}