1use std::io::{Cursor, Read, Write};
4
5use crate::primitives::hash::hash256;
6use crate::primitives::transaction_signature::{
7 SIGHASH_ANYONECANPAY, SIGHASH_FORKID, SIGHASH_NONE, SIGHASH_SINGLE,
8};
9use crate::script::locking_script::LockingScript;
10use crate::script::templates::ScriptTemplateUnlock;
11use crate::transaction::error::TransactionError;
12use crate::transaction::merkle_path::MerklePath;
13use crate::transaction::transaction_input::TransactionInput;
14use crate::transaction::transaction_output::TransactionOutput;
15use crate::transaction::{
16 read_u32_le, read_u64_le, read_varint, write_u32_le, write_u64_le, write_varint,
17};
18
19const EF_MARKER: [u8; 6] = [0x00, 0x00, 0x00, 0x00, 0x00, 0xEF];
21
22#[derive(Debug, Clone)]
28pub struct Transaction {
29 pub version: u32,
31 pub inputs: Vec<TransactionInput>,
33 pub outputs: Vec<TransactionOutput>,
35 pub lock_time: u32,
37 pub merkle_path: Option<MerklePath>,
39}
40
41impl Transaction {
42 pub fn new() -> Self {
44 Self {
45 version: 1,
46 inputs: Vec::new(),
47 outputs: Vec::new(),
48 lock_time: 0,
49 merkle_path: None,
50 }
51 }
52
53 pub fn from_binary(reader: &mut impl Read) -> Result<Self, TransactionError> {
55 let version = read_u32_le(reader)?;
56
57 let input_count = read_varint(reader)? as usize;
58 let mut inputs = Vec::with_capacity(input_count);
59 for _ in 0..input_count {
60 inputs.push(TransactionInput::from_binary(reader)?);
61 }
62
63 let output_count = read_varint(reader)? as usize;
64 let mut outputs = Vec::with_capacity(output_count);
65 for _ in 0..output_count {
66 outputs.push(TransactionOutput::from_binary(reader)?);
67 }
68
69 let lock_time = read_u32_le(reader)?;
70
71 Ok(Transaction {
72 version,
73 inputs,
74 outputs,
75 lock_time,
76 merkle_path: None,
77 })
78 }
79
80 pub fn from_hex(hex: &str) -> Result<Self, TransactionError> {
82 let bytes = hex_to_bytes(hex)
83 .map_err(|e| TransactionError::InvalidFormat(format!("invalid hex: {}", e)))?;
84 let mut cursor = Cursor::new(bytes);
85 Self::from_binary(&mut cursor)
86 }
87
88 pub fn from_beef(beef_hex: &str) -> Result<Self, TransactionError> {
93 let beef = crate::transaction::beef::Beef::from_hex(beef_hex)?;
94 beef.into_transaction()
95 }
96
97 pub fn to_binary(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
99 write_u32_le(writer, self.version)?;
100
101 write_varint(writer, self.inputs.len() as u64)?;
102 for input in &self.inputs {
103 input.to_binary(writer)?;
104 }
105
106 write_varint(writer, self.outputs.len() as u64)?;
107 for output in &self.outputs {
108 output.to_binary(writer)?;
109 }
110
111 write_u32_le(writer, self.lock_time)?;
112 Ok(())
113 }
114
115 pub fn to_hex(&self) -> Result<String, TransactionError> {
117 let bytes = self.to_bytes()?;
118 Ok(bytes_to_hex(&bytes))
119 }
120
121 pub fn to_bytes(&self) -> Result<Vec<u8>, TransactionError> {
123 let mut buf = Vec::new();
124 self.to_binary(&mut buf)?;
125 Ok(buf)
126 }
127
128 pub fn to_beef(&self) -> Result<Vec<u8>, TransactionError> {
135 use crate::transaction::beef::{Beef, BEEF_V1};
136 use crate::transaction::beef_tx::BeefTx;
137 use std::collections::HashSet;
138
139 let mut beef = Beef::new(BEEF_V1);
140 let mut seen = HashSet::new();
141
142 fn collect(
143 tx: &Transaction,
144 beef: &mut Beef,
145 seen: &mut HashSet<String>,
146 ) -> Result<(), TransactionError> {
147 let txid = tx.id()?;
148 if seen.contains(&txid) {
149 return Ok(());
150 }
151 seen.insert(txid.clone());
152
153 if let Some(ref mp) = tx.merkle_path {
154 let bump_idx = beef.merge_bump(mp)?;
156 let btx = BeefTx::from_tx(tx.clone(), Some(bump_idx))?;
157 beef.remove_existing_txid(&btx.txid);
158 beef.txs.push(btx);
159 } else {
160 for input in &tx.inputs {
162 if let Some(ref source_tx) = input.source_transaction {
163 collect(source_tx, beef, seen)?;
164 } else {
165 return Err(TransactionError::BeefError(format!(
166 "input spending {}:{} has no source transaction and tx {} has no merkle proof — cannot build BEEF",
167 input.source_txid.as_deref().unwrap_or("unknown"),
168 input.source_output_index,
169 txid,
170 )));
171 }
172 }
173 let btx = BeefTx::from_tx(tx.clone(), None)?;
174 beef.remove_existing_txid(&btx.txid);
175 beef.txs.push(btx);
176 }
177
178 Ok(())
179 }
180
181 collect(self, &mut beef, &mut seen)?;
182
183 if beef.bumps.is_empty() {
184 return Err(TransactionError::BeefError(
185 "cannot produce BEEF: no merkle proofs found in the transaction chain".to_string(),
186 ));
187 }
188
189 beef.sort_txs();
190
191 let mut buf = Vec::new();
192 beef.to_binary(&mut buf)?;
193 Ok(buf)
194 }
195
196 pub fn hash(&self) -> Result<[u8; 32], TransactionError> {
200 let bytes = self.to_bytes()?;
201 Ok(hash256(&bytes))
202 }
203
204 pub fn id(&self) -> Result<String, TransactionError> {
208 let mut h = self.hash()?;
209 h.reverse();
210 Ok(bytes_to_hex(&h))
211 }
212
213 pub fn add_input(&mut self, input: TransactionInput) {
215 self.inputs.push(input);
216 }
217
218 pub fn add_output(&mut self, output: TransactionOutput) {
220 self.outputs.push(output);
221 }
222
223 pub fn from_ef(reader: &mut impl Read) -> Result<Self, TransactionError> {
228 let version = read_u32_le(reader)?;
229
230 let mut marker = [0u8; 6];
232 reader.read_exact(&mut marker)?;
233 if marker != EF_MARKER {
234 return Err(TransactionError::InvalidFormat(
235 "invalid EF marker".to_string(),
236 ));
237 }
238
239 let input_count = read_varint(reader)? as usize;
240 let mut inputs = Vec::with_capacity(input_count);
241 for _ in 0..input_count {
242 let mut input = TransactionInput::from_binary(reader)?;
244
245 let source_satoshis = read_u64_le(reader)?;
247
248 let script_len = read_varint(reader)? as usize;
250 let mut script_bytes = vec![0u8; script_len];
251 if script_len > 0 {
252 reader.read_exact(&mut script_bytes)?;
253 }
254 let source_locking_script = LockingScript::from_binary(&script_bytes);
255
256 let mut source_tx = Transaction::new();
258 for _ in 0..input.source_output_index {
260 source_tx.outputs.push(TransactionOutput::default());
261 }
262 source_tx.outputs.push(TransactionOutput {
263 satoshis: Some(source_satoshis),
264 locking_script: source_locking_script,
265 change: false,
266 });
267 input.source_transaction = Some(Box::new(source_tx));
268
269 inputs.push(input);
270 }
271
272 let output_count = read_varint(reader)? as usize;
273 let mut outputs = Vec::with_capacity(output_count);
274 for _ in 0..output_count {
275 outputs.push(TransactionOutput::from_binary(reader)?);
276 }
277
278 let lock_time = read_u32_le(reader)?;
279
280 Ok(Transaction {
281 version,
282 inputs,
283 outputs,
284 lock_time,
285 merkle_path: None,
286 })
287 }
288
289 pub fn from_hex_ef(hex: &str) -> Result<Self, TransactionError> {
291 let bytes = hex_to_bytes(hex)
292 .map_err(|e| TransactionError::InvalidFormat(format!("invalid hex: {}", e)))?;
293 let mut cursor = Cursor::new(bytes);
294 Self::from_ef(&mut cursor)
295 }
296
297 pub fn to_ef(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
299 write_u32_le(writer, self.version)?;
300
301 writer.write_all(&EF_MARKER)?;
303
304 write_varint(writer, self.inputs.len() as u64)?;
305 for input in &self.inputs {
306 input.to_binary(writer)?;
308
309 if let Some(ref source_tx) = input.source_transaction {
311 let idx = input.source_output_index as usize;
312 if idx < source_tx.outputs.len() {
313 let source_output = &source_tx.outputs[idx];
314 write_u64_le(writer, source_output.satoshis.unwrap_or(0))?;
315 let script_bin = source_output.locking_script.to_binary();
316 write_varint(writer, script_bin.len() as u64)?;
317 writer.write_all(&script_bin)?;
318 } else {
319 return Err(TransactionError::MissingSourceTransaction);
320 }
321 } else {
322 return Err(TransactionError::MissingSourceTransaction);
323 }
324 }
325
326 write_varint(writer, self.outputs.len() as u64)?;
327 for output in &self.outputs {
328 output.to_binary(writer)?;
329 }
330
331 write_u32_le(writer, self.lock_time)?;
332 Ok(())
333 }
334
335 pub fn to_hex_ef(&self) -> Result<String, TransactionError> {
337 let mut buf = Vec::new();
338 self.to_ef(&mut buf)?;
339 Ok(bytes_to_hex(&buf))
340 }
341
342 pub fn to_bytes_ef(&self) -> Result<Vec<u8>, TransactionError> {
345 let mut buf = Vec::new();
346 self.to_ef(&mut buf)?;
347 Ok(buf)
348 }
349
350 fn resolve_input_txid_bytes(&self, input_index: usize) -> Result<[u8; 32], TransactionError> {
354 let input = &self.inputs[input_index];
355 if let Some(ref txid) = input.source_txid {
356 let mut bytes = hex_to_bytes(txid)
357 .map_err(|e| TransactionError::InvalidFormat(format!("invalid txid hex: {}", e)))?;
358 bytes.reverse(); let mut arr = [0u8; 32];
360 if bytes.len() == 32 {
361 arr.copy_from_slice(&bytes);
362 }
363 Ok(arr)
364 } else if let Some(ref source_tx) = input.source_transaction {
365 source_tx.hash()
366 } else {
367 Err(TransactionError::InvalidFormat(
368 "input has neither source_txid nor source_transaction".to_string(),
369 ))
370 }
371 }
372
373 pub fn sighash_preimage(
384 &self,
385 input_index: usize,
386 scope: u32,
387 source_satoshis: u64,
388 source_locking_script: &LockingScript,
389 ) -> Result<Vec<u8>, TransactionError> {
390 if input_index >= self.inputs.len() {
391 return Err(TransactionError::InvalidSighash(format!(
392 "input_index {} out of range (tx has {} inputs)",
393 input_index,
394 self.inputs.len()
395 )));
396 }
397
398 let base_type = scope & 0x1f;
399 let anyone_can_pay = (scope & SIGHASH_ANYONECANPAY) != 0;
400
401 let mut preimage = Vec::with_capacity(256);
402
403 preimage.extend_from_slice(&self.version.to_le_bytes());
405
406 if !anyone_can_pay {
408 let mut prevouts = Vec::new();
409 for (i, input) in self.inputs.iter().enumerate() {
410 let txid_bytes = self.resolve_input_txid_bytes(i)?;
411 prevouts.extend_from_slice(&txid_bytes);
412 prevouts.extend_from_slice(&input.source_output_index.to_le_bytes());
413 }
414 preimage.extend_from_slice(&hash256(&prevouts));
415 } else {
416 preimage.extend_from_slice(&[0u8; 32]);
417 }
418
419 if !anyone_can_pay && base_type != SIGHASH_NONE && base_type != SIGHASH_SINGLE {
421 let mut sequences = Vec::new();
422 for input in &self.inputs {
423 sequences.extend_from_slice(&input.sequence.to_le_bytes());
424 }
425 preimage.extend_from_slice(&hash256(&sequences));
426 } else {
427 preimage.extend_from_slice(&[0u8; 32]);
428 }
429
430 let this_txid = self.resolve_input_txid_bytes(input_index)?;
432 preimage.extend_from_slice(&this_txid);
433 preimage.extend_from_slice(&self.inputs[input_index].source_output_index.to_le_bytes());
434
435 let script_bytes = source_locking_script.to_binary();
437 write_varint_to_vec(&mut preimage, script_bytes.len() as u64);
438 preimage.extend_from_slice(&script_bytes);
439
440 preimage.extend_from_slice(&source_satoshis.to_le_bytes());
442
443 preimage.extend_from_slice(&self.inputs[input_index].sequence.to_le_bytes());
445
446 if base_type != SIGHASH_NONE && base_type != SIGHASH_SINGLE {
448 let mut outputs_data = Vec::new();
450 for output in &self.outputs {
451 outputs_data.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
452 let script_bytes = output.locking_script.to_binary();
453 write_varint_to_vec(&mut outputs_data, script_bytes.len() as u64);
454 outputs_data.extend_from_slice(&script_bytes);
455 }
456 preimage.extend_from_slice(&hash256(&outputs_data));
457 } else if base_type == SIGHASH_SINGLE && input_index < self.outputs.len() {
458 let output = &self.outputs[input_index];
460 let mut out_data = Vec::new();
461 out_data.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
462 let script_bytes = output.locking_script.to_binary();
463 write_varint_to_vec(&mut out_data, script_bytes.len() as u64);
464 out_data.extend_from_slice(&script_bytes);
465 preimage.extend_from_slice(&hash256(&out_data));
466 } else {
467 preimage.extend_from_slice(&[0u8; 32]);
469 }
470
471 preimage.extend_from_slice(&self.lock_time.to_le_bytes());
473
474 preimage.extend_from_slice(&(scope | SIGHASH_FORKID).to_le_bytes());
476
477 Ok(preimage)
478 }
479
480 pub fn sighash_preimage_legacy(
488 &self,
489 input_index: usize,
490 scope: u32,
491 sub_script: &[u8],
492 ) -> Result<Vec<u8>, TransactionError> {
493 if input_index >= self.inputs.len() {
494 return Err(TransactionError::InvalidSighash(format!(
495 "input_index {} out of range (tx has {} inputs)",
496 input_index,
497 self.inputs.len()
498 )));
499 }
500
501 let sub_script = strip_codeseparator(sub_script);
504
505 let base_type = scope & 0x1f;
506 let anyone_can_pay = (scope & SIGHASH_ANYONECANPAY) != 0;
507 let is_none = base_type == SIGHASH_NONE;
508 let is_single = base_type == SIGHASH_SINGLE;
509
510 if is_single && input_index >= self.outputs.len() {
512 let mut result = vec![0u8; 32];
513 result[0] = 1;
514 return Ok(result);
515 }
516
517 let empty_script: Vec<u8> = Vec::new();
518
519 let mut preimage = Vec::with_capacity(512);
520
521 preimage.extend_from_slice(&self.version.to_le_bytes());
523
524 if anyone_can_pay {
526 write_varint_to_vec(&mut preimage, 1);
528 let txid_bytes = self.resolve_input_txid_bytes(input_index)?;
529 preimage.extend_from_slice(&txid_bytes);
530 preimage.extend_from_slice(&self.inputs[input_index].source_output_index.to_le_bytes());
531 write_varint_to_vec(&mut preimage, sub_script.len() as u64);
532 preimage.extend_from_slice(&sub_script);
533 preimage.extend_from_slice(&self.inputs[input_index].sequence.to_le_bytes());
534 } else {
535 write_varint_to_vec(&mut preimage, self.inputs.len() as u64);
536 for (i, input) in self.inputs.iter().enumerate() {
537 let txid_bytes = self.resolve_input_txid_bytes(i)?;
538 preimage.extend_from_slice(&txid_bytes);
539 preimage.extend_from_slice(&input.source_output_index.to_le_bytes());
540
541 if i == input_index {
543 write_varint_to_vec(&mut preimage, sub_script.len() as u64);
544 preimage.extend_from_slice(&sub_script);
545 } else {
546 write_varint_to_vec(&mut preimage, empty_script.len() as u64);
547 }
548
549 if i == input_index || (!is_single && !is_none) {
551 preimage.extend_from_slice(&input.sequence.to_le_bytes());
552 } else {
553 preimage.extend_from_slice(&0u32.to_le_bytes());
554 }
555 }
556 }
557
558 if is_none {
560 write_varint_to_vec(&mut preimage, 0);
561 } else if is_single {
562 write_varint_to_vec(&mut preimage, (input_index + 1) as u64);
563 for i in 0..input_index {
564 preimage.extend_from_slice(&u64::MAX.to_le_bytes());
566 write_varint_to_vec(&mut preimage, 0);
567 let _ = i;
568 }
569 let output = &self.outputs[input_index];
571 preimage.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
572 let script_bytes = output.locking_script.to_binary();
573 write_varint_to_vec(&mut preimage, script_bytes.len() as u64);
574 preimage.extend_from_slice(&script_bytes);
575 } else {
576 write_varint_to_vec(&mut preimage, self.outputs.len() as u64);
578 for output in &self.outputs {
579 preimage.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
580 let script_bytes = output.locking_script.to_binary();
581 write_varint_to_vec(&mut preimage, script_bytes.len() as u64);
582 preimage.extend_from_slice(&script_bytes);
583 }
584 }
585
586 preimage.extend_from_slice(&self.lock_time.to_le_bytes());
588
589 preimage.extend_from_slice(&scope.to_le_bytes());
591
592 Ok(preimage)
593 }
594
595 pub fn sign(
602 &mut self,
603 input_index: usize,
604 template: &dyn ScriptTemplateUnlock,
605 scope: u32,
606 source_satoshis: u64,
607 source_locking_script: &LockingScript,
608 ) -> Result<(), TransactionError> {
609 let preimage =
610 self.sighash_preimage(input_index, scope, source_satoshis, source_locking_script)?;
611 let unlocking_script = template
612 .sign(&preimage)
613 .map_err(|e| TransactionError::SigningFailed(format!("{}", e)))?;
614 self.inputs[input_index].unlocking_script = Some(unlocking_script);
615 Ok(())
616 }
617
618 pub fn sign_all_inputs(
631 &mut self,
632 template: &dyn ScriptTemplateUnlock,
633 scope: u32,
634 ) -> Result<(), TransactionError> {
635 let num_inputs = self.inputs.len();
636
637 for i in 0..num_inputs {
638 if self.inputs[i].unlocking_script.is_some() {
640 continue;
641 }
642
643 let (source_satoshis, source_locking_script) = {
645 let source_tx = self.inputs[i].source_transaction.as_ref().ok_or_else(|| {
646 TransactionError::SigningFailed(format!(
647 "input {}: source_transaction required for sign_all_inputs()",
648 i
649 ))
650 })?;
651 let out_idx = self.inputs[i].source_output_index as usize;
652 let output = source_tx.outputs.get(out_idx).ok_or_else(|| {
653 TransactionError::SigningFailed(format!(
654 "input {}: source transaction has no output at index {}",
655 i, out_idx
656 ))
657 })?;
658 let satoshis = output.satoshis.ok_or_else(|| {
659 TransactionError::SigningFailed(format!(
660 "input {}: source output {} has no satoshis",
661 i, out_idx
662 ))
663 })?;
664 (satoshis, output.locking_script.clone())
665 };
666
667 let preimage =
668 self.sighash_preimage(i, scope, source_satoshis, &source_locking_script)?;
669 let unlocking_script = template
670 .sign(&preimage)
671 .map_err(|e| TransactionError::SigningFailed(format!("input {}: {}", i, e)))?;
672 self.inputs[i].unlocking_script = Some(unlocking_script);
673 }
674
675 Ok(())
676 }
677}
678
679impl Default for Transaction {
680 fn default() -> Self {
681 Self::new()
682 }
683}
684
685fn bytes_to_hex(bytes: &[u8]) -> String {
687 let mut s = String::with_capacity(bytes.len() * 2);
688 for b in bytes {
689 s.push_str(&format!("{:02x}", b));
690 }
691 s
692}
693
694fn strip_codeseparator(script: &[u8]) -> Vec<u8> {
699 const OP_CODESEPARATOR: u8 = 0xab;
700
701 let mut result = Vec::with_capacity(script.len());
702 let mut i = 0;
703 while i < script.len() {
704 let opcode = script[i];
705 if opcode == OP_CODESEPARATOR {
706 i += 1;
708 continue;
709 }
710
711 if opcode > 0 && opcode < 76 {
712 let push_len = opcode as usize;
714 let end = std::cmp::min(i + 1 + push_len, script.len());
715 result.extend_from_slice(&script[i..end]);
716 i = end;
717 } else if opcode == 76 {
718 if i + 1 < script.len() {
720 let push_len = script[i + 1] as usize;
721 let end = std::cmp::min(i + 2 + push_len, script.len());
722 result.extend_from_slice(&script[i..end]);
723 i = end;
724 } else {
725 result.push(opcode);
726 i += 1;
727 }
728 } else if opcode == 77 {
729 if i + 2 < script.len() {
731 let push_len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
732 let end = std::cmp::min(i + 3 + push_len, script.len());
733 result.extend_from_slice(&script[i..end]);
734 i = end;
735 } else {
736 result.extend_from_slice(&script[i..]);
737 break;
738 }
739 } else if opcode == 78 {
740 if i + 4 < script.len() {
742 let push_len = u32::from_le_bytes([
743 script[i + 1],
744 script[i + 2],
745 script[i + 3],
746 script[i + 4],
747 ]) as usize;
748 let end = std::cmp::min(i + 5 + push_len, script.len());
749 result.extend_from_slice(&script[i..end]);
750 i = end;
751 } else {
752 result.extend_from_slice(&script[i..]);
753 break;
754 }
755 } else {
756 result.push(opcode);
758 i += 1;
759 }
760 }
761 result
762}
763
764pub(crate) fn write_varint_to_vec(buf: &mut Vec<u8>, val: u64) {
766 if val < 0xfd {
767 buf.push(val as u8);
768 } else if val <= 0xffff {
769 buf.push(0xfd);
770 buf.extend_from_slice(&(val as u16).to_le_bytes());
771 } else if val <= 0xffff_ffff {
772 buf.push(0xfe);
773 buf.extend_from_slice(&(val as u32).to_le_bytes());
774 } else {
775 buf.push(0xff);
776 buf.extend_from_slice(&val.to_le_bytes());
777 }
778}
779
780fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, String> {
782 if !hex.len().is_multiple_of(2) {
783 return Err("odd length hex string".to_string());
784 }
785 let mut bytes = Vec::with_capacity(hex.len() / 2);
786 for i in (0..hex.len()).step_by(2) {
787 let byte = u8::from_str_radix(&hex[i..i + 2], 16)
788 .map_err(|e| format!("invalid hex at position {}: {}", i, e))?;
789 bytes.push(byte);
790 }
791 Ok(bytes)
792}
793
794#[cfg(test)]
795mod tests {
796 use super::*;
797 use crate::primitives::private_key::PrivateKey;
798 use crate::primitives::transaction_signature::{SIGHASH_ALL, SIGHASH_FORKID};
799 use crate::script::templates::p2pkh::P2PKH;
800 use crate::script::templates::ScriptTemplateLock;
801 use serde::Deserialize;
802
803 #[derive(Deserialize)]
804 struct TestVector {
805 description: String,
806 hex: String,
807 txid: String,
808 version: u32,
809 inputs: usize,
810 outputs: usize,
811 locktime: u32,
812 }
813
814 fn load_test_vectors() -> Vec<TestVector> {
815 let json = include_str!("../../test-vectors/transaction_valid.json");
816 serde_json::from_str(json).expect("failed to parse transaction_valid.json")
817 }
818
819 #[test]
820 fn test_from_binary_round_trip() {
821 let vectors = load_test_vectors();
822 for v in &vectors {
823 let tx = Transaction::from_hex(&v.hex)
824 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.description, e));
825 let result_hex = tx
826 .to_hex()
827 .unwrap_or_else(|e| panic!("failed to serialize '{}': {}", v.description, e));
828 assert_eq!(
829 result_hex, v.hex,
830 "round-trip failed for '{}'",
831 v.description
832 );
833 }
834 }
835
836 #[test]
837 fn test_txid() {
838 let vectors = load_test_vectors();
839 for v in &vectors {
840 let tx = Transaction::from_hex(&v.hex)
841 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.description, e));
842 let txid = tx
843 .id()
844 .unwrap_or_else(|e| panic!("failed to compute id for '{}': {}", v.description, e));
845 assert_eq!(txid, v.txid, "txid mismatch for '{}'", v.description);
846 }
847 }
848
849 #[test]
850 fn test_input_output_counts() {
851 let vectors = load_test_vectors();
852 for v in &vectors {
853 let tx = Transaction::from_hex(&v.hex)
854 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.description, e));
855 assert_eq!(
856 tx.inputs.len(),
857 v.inputs,
858 "input count mismatch for '{}'",
859 v.description
860 );
861 assert_eq!(
862 tx.outputs.len(),
863 v.outputs,
864 "output count mismatch for '{}'",
865 v.description
866 );
867 assert_eq!(
868 tx.version, v.version,
869 "version mismatch for '{}'",
870 v.description
871 );
872 assert_eq!(
873 tx.lock_time, v.locktime,
874 "locktime mismatch for '{}'",
875 v.description
876 );
877 }
878 }
879
880 #[test]
881 fn test_empty_transaction() {
882 let tx = Transaction::new();
883 assert_eq!(tx.version, 1);
884 assert!(tx.inputs.is_empty());
885 assert!(tx.outputs.is_empty());
886 assert_eq!(tx.lock_time, 0);
887 assert!(tx.merkle_path.is_none());
888 }
889
890 #[test]
891 fn test_add_input_output() {
892 let mut tx = Transaction::new();
893 assert_eq!(tx.inputs.len(), 0);
894 assert_eq!(tx.outputs.len(), 0);
895
896 tx.add_input(TransactionInput::default());
897 assert_eq!(tx.inputs.len(), 1);
898
899 tx.add_output(TransactionOutput::default());
900 assert_eq!(tx.outputs.len(), 1);
901 }
902
903 fn make_test_tx_with_source_for_ef() -> Transaction {
904 let ef_hex = "010000000000000000ef01ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff3e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac00000000";
907 Transaction::from_hex_ef(ef_hex).expect("make_test_tx_with_source_for_ef: parse failed")
908 }
909
910 #[test]
911 fn test_to_bytes_ef_round_trips_with_to_hex_ef() {
912 let tx = make_test_tx_with_source_for_ef();
913 let bytes = tx.to_bytes_ef().expect("to_bytes_ef");
914 let hex_form = tx.to_hex_ef().expect("to_hex_ef");
915 assert_eq!(hex_form, hex::encode(&bytes));
916 }
917
918 #[test]
919 fn test_ef_round_trip() {
920 let ef_hex = "010000000000000000ef01ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff3e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac00000000";
922
923 let tx = Transaction::from_hex_ef(ef_hex).expect("failed to parse EF hex");
924 assert_eq!(tx.inputs.len(), 1);
925 assert_eq!(tx.outputs.len(), 1);
926
927 let input = &tx.inputs[0];
929 assert!(input.source_transaction.is_some());
930 let source_tx = input.source_transaction.as_ref().unwrap();
931 let source_output = &source_tx.outputs[input.source_output_index as usize];
932 assert_eq!(source_output.satoshis, Some(0x663e)); let result_hex = tx.to_hex_ef().expect("failed to serialize to EF");
936 assert_eq!(result_hex, ef_hex);
937 }
938
939 #[test]
940 fn test_hash_and_id_consistency() {
941 let tx2hex = "01000000029e8d016a7b0dc49a325922d05da1f916d1e4d4f0cb840c9727f3d22ce8d1363f000000008c493046022100e9318720bee5425378b4763b0427158b1051eec8b08442ce3fbfbf7b30202a44022100d4172239ebd701dae2fbaaccd9f038e7ca166707333427e3fb2a2865b19a7f27014104510c67f46d2cbb29476d1f0b794be4cb549ea59ab9cc1e731969a7bf5be95f7ad5e7f904e5ccf50a9dc1714df00fbeb794aa27aaff33260c1032d931a75c56f2ffffffffa3195e7a1ab665473ff717814f6881485dc8759bebe97e31c301ffe7933a656f020000008b48304502201c282f35f3e02a1f32d2089265ad4b561f07ea3c288169dedcf2f785e6065efa022100e8db18aadacb382eed13ee04708f00ba0a9c40e3b21cf91da8859d0f7d99e0c50141042b409e1ebbb43875be5edde9c452c82c01e3903d38fa4fd89f3887a52cb8aea9dc8aec7e2c9d5b3609c03eb16259a2537135a1bf0f9c5fbbcbdbaf83ba402442ffffffff02206b1000000000001976a91420bb5c3bfaef0231dc05190e7f1c8e22e098991e88acf0ca0100000000001976a9149e3e2d23973a04ec1b02be97c30ab9f2f27c3b2c88ac00000000";
943 let tx2idhex = "8c9aa966d35bfeaf031409e0001b90ccdafd8d859799eb945a3c515b8260bcf2";
944
945 let tx = Transaction::from_hex(tx2hex).unwrap();
946 let id = tx.id().unwrap();
947 assert_eq!(id, tx2idhex);
948
949 let hash = tx.hash().unwrap();
951 let mut reversed_hash = hash;
952 reversed_hash.reverse();
953 let reversed_hex = bytes_to_hex(&reversed_hash);
954 assert_eq!(reversed_hex, tx2idhex);
955 }
956
957 fn otda_test_vectors() -> Vec<(&'static str, &'static str, usize, u32, &'static str)> {
962 vec![
963 ("0122769903cfc6fedb9c63fe76930fed0c87b44be46f5032a534fa05861548616a5b99034701000000040063656ac864c70228b3f6ebaf97be065b2180ece52fae4f9039c7bf932e567625d7d89582abe1340100000008ac6363650063ab65ffffffff587125b913706705dc799454ab0343ee8aa59a2f2d538f75e12c0deeb40aa741030000000027b3a02003d21f34040000000000fa7f2c0300000000016387213b0300000000056aabacacab00000000", "", 1, 902085315, "49fdc84c5f88a590c5c65e17de58da7d1028133b74ad2d56a8b15ba317ac98f6"),
964 ("7e8c3f7902634018b6e1db2ca591816dff64a6cff74643de7455323ebfc560500aad9eee8c0100000001519c2d146d06fdca2c2e5fa3a2559812df66b6b5e40a3c57b2f7071ae6fe3863c74ab0952d0100000001000bf6638c013c5f6503000000000351ac63cbbf66be", "acab", 0, 2433331782, "f6261bacaed3a70d504cd70d3c0623e3593f6d197cd47316e56cea79ceabe095"),
965 ("459499bb032fdcc39d3c6cf819dcaa0a0165d97578446aa87ab745fb9fdcd3e6177b4cba3d0000000005006a6a5265ffffffff10e5929ebe065273c112cab15f6a1f6d9a8a517c288311b048b16663b3d406dc030000000700535263655151ffffffff981d73a7f3d477ab055398bcf9a7d349db1a8e6362055e20f4207ad1b775bac301000000066a6363ac6552ffffffff0403342603000000000165c4390004000000000965ac52006565006365373ce8010000000005520000516aba5a9404000000000351655300000000", "6a5352", 0, 3544391288, "738b7dcb86260e6fe3fad331ff342429c157730bbcb90c205b9e08568557cd94"),
966 ("cb3b8d30043ccd81c3bda7f594cca60e2eef170c67ffe8a1eb1f1a994dc40a0a5cf89fa9690100000009ab536a52acab5300653bea9324983da711ccb6eaff060930e6f55cf6df75e5abdda91a8d5fc25c3b9b28d0e7370200000003ab51522f86cdbd8aa19b6b8536efb6ca8cc23ebccef585ad00a78b5956d803908482bb44b25c550000000007abab65530051ac8177a2acebc517db1d5b5be14f91ab40e811ec0316cf029ce657a4b06f04f30698f0a0e50000000007516aac636a6351ffffffff02ccbefa02000000000252ab3e297f0100000000060052525163521acc3e2b", "6aac636a63535153", 0, 3406487088, "3568dfad7e968afd3492bd146c8b0e3255f90e5b642a4ec10105693e8b029132"),
967 ]
968 }
969
970 #[test]
971 fn test_sighash_preimage_legacy_vectors() {
972 let vectors = otda_test_vectors();
973 let mut passed = 0;
974
975 for (i, (raw_tx_hex, script_hex, input_index, hash_type, expected_hash)) in
976 vectors.iter().enumerate()
977 {
978 let tx = Transaction::from_hex(raw_tx_hex)
979 .unwrap_or_else(|e| panic!("vector {}: failed to parse tx: {}", i, e));
980
981 let sub_script = if script_hex.is_empty() {
982 vec![]
983 } else {
984 hex_to_bytes(script_hex).unwrap()
985 };
986
987 let preimage = tx
988 .sighash_preimage_legacy(*input_index, *hash_type, &sub_script)
989 .unwrap_or_else(|e| panic!("vector {}: sighash error: {}", i, e));
990
991 let mut hash_bytes = hash256(&preimage);
993 hash_bytes.reverse();
994 let computed_hash = bytes_to_hex(&hash_bytes);
995
996 if computed_hash == *expected_hash {
997 passed += 1;
998 } else {
999 println!(
1000 "MISMATCH vector {}: expected={}, got={}",
1001 i, expected_hash, computed_hash
1002 );
1003 }
1004 }
1005
1006 println!(
1007 "sighash legacy OTDA vectors: {}/{} passed",
1008 passed,
1009 vectors.len()
1010 );
1011 assert_eq!(
1012 passed,
1013 vectors.len(),
1014 "all sighash OTDA vectors should pass"
1015 );
1016 }
1017
1018 #[test]
1019 fn test_sighash_preimage_bip143() {
1020 let unsigned_tx_hex = "010000000193a35408b6068499e0d5abd799d3e827d9bfe70c9b75ebe209c91d25072326510000000000ffffffff02404b4c00000000001976a91404ff367be719efa79d76e4416ffb072cd53b208888acde94a905000000001976a91404d03f746652cfcb6cb55119ab473a045137d26588ac00000000";
1022 let source_script_hex = "76a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac";
1023 let source_satoshis: u64 = 100_000_000;
1024 let expected_preimage_hex = "010000007ced5b2e5cf3ea407b005d8b18c393b6256ea2429b6ff409983e10adc61d0ae83bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e7066504493a35408b6068499e0d5abd799d3e827d9bfe70c9b75ebe209c91d2507232651000000001976a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac00e1f50500000000ffffffff87841ab2b7a4133af2c58256edb7c3c9edca765a852ebe2d0dc962604a30f1030000000041000000";
1025
1026 let tx = Transaction::from_hex(unsigned_tx_hex).unwrap();
1027 let source_script_bytes = hex_to_bytes(source_script_hex).unwrap();
1028 let source_locking_script = LockingScript::from_binary(&source_script_bytes);
1029
1030 let scope = SIGHASH_ALL | SIGHASH_FORKID;
1031 let preimage = tx
1032 .sighash_preimage(0, scope, source_satoshis, &source_locking_script)
1033 .unwrap();
1034 let preimage_hex = bytes_to_hex(&preimage);
1035
1036 assert_eq!(
1037 preimage_hex, expected_preimage_hex,
1038 "BIP143 preimage should match Go SDK test vector"
1039 );
1040 }
1041
1042 #[test]
1045 fn test_sign_p2pkh() {
1046 let key = PrivateKey::from_hex("1").unwrap();
1047 let p2pkh_lock = P2PKH::from_private_key(key.clone());
1048 let p2pkh_unlock = P2PKH::from_private_key(key.clone());
1049
1050 let lock_script = p2pkh_lock.lock().unwrap();
1051
1052 let mut tx = Transaction::new();
1054 tx.add_input(TransactionInput {
1055 source_transaction: None,
1056 source_txid: Some("00".repeat(32)),
1057 source_output_index: 0,
1058 unlocking_script: None,
1059 sequence: 0xffffffff,
1060 });
1061 tx.add_output(TransactionOutput {
1062 satoshis: Some(50000),
1063 locking_script: lock_script.clone(),
1064 change: false,
1065 });
1066
1067 let scope = SIGHASH_ALL | SIGHASH_FORKID;
1069 tx.sign(0, &p2pkh_unlock, scope, 100000, &lock_script)
1070 .expect("signing should succeed");
1071
1072 let unlock = tx.inputs[0].unlocking_script.as_ref().unwrap();
1074 let chunks = unlock.chunks();
1075 assert_eq!(
1076 chunks.len(),
1077 2,
1078 "P2PKH unlock should have 2 chunks (sig + pubkey)"
1079 );
1080
1081 let sig_data = chunks[0].data.as_ref().unwrap();
1083 assert!(
1084 sig_data.len() >= 70 && sig_data.len() <= 74,
1085 "signature length {} should be 70-74",
1086 sig_data.len()
1087 );
1088 assert_eq!(
1089 *sig_data.last().unwrap(),
1090 (SIGHASH_ALL | SIGHASH_FORKID) as u8,
1091 "last byte should be sighash type"
1092 );
1093
1094 let pubkey_data = chunks[1].data.as_ref().unwrap();
1096 assert_eq!(pubkey_data.len(), 33);
1097 }
1098
1099 #[test]
1100 fn test_sign_and_verify_round_trip() {
1101 use crate::script::spend::{Spend, SpendParams};
1102
1103 let key = PrivateKey::from_hex("abcdef01").unwrap();
1104 let p2pkh = P2PKH::from_private_key(key.clone());
1105 let lock_script = p2pkh.lock().unwrap();
1106
1107 let mut tx = Transaction::new();
1109 let source_satoshis = 100_000u64;
1110
1111 tx.add_input(TransactionInput {
1112 source_transaction: None,
1113 source_txid: Some("aa".repeat(32)),
1114 source_output_index: 0,
1115 unlocking_script: None,
1116 sequence: 0xffffffff,
1117 });
1118 tx.add_output(TransactionOutput {
1119 satoshis: Some(90_000),
1120 locking_script: lock_script.clone(),
1121 change: false,
1122 });
1123
1124 let scope = SIGHASH_ALL | SIGHASH_FORKID;
1125 tx.sign(0, &p2pkh, scope, source_satoshis, &lock_script)
1126 .expect("signing should succeed");
1127
1128 let unlock_script = tx.inputs[0].unlocking_script.clone().unwrap();
1130
1131 let mut spend = Spend::new(SpendParams {
1132 locking_script: lock_script.clone(),
1133 unlocking_script: unlock_script,
1134 source_txid: "aa".repeat(32),
1135 source_output_index: 0,
1136 source_satoshis,
1137 transaction_version: tx.version,
1138 transaction_lock_time: tx.lock_time,
1139 transaction_sequence: tx.inputs[0].sequence,
1140 other_inputs: vec![],
1141 other_outputs: tx.outputs.clone(),
1142 input_index: 0,
1143 });
1144
1145 let valid = spend.validate().expect("spend validation should not error");
1146 assert!(valid, "signed transaction should verify successfully");
1147 }
1148
1149 #[test]
1150 fn test_to_beef_round_trip_all_vectors() {
1151 let vectors_json = std::fs::read_to_string("test-vectors/beef_valid.json").unwrap();
1154 let vectors: Vec<serde_json::Value> = serde_json::from_str(&vectors_json).unwrap();
1155
1156 for (i, v) in vectors.iter().enumerate() {
1157 let hex = v["hex"].as_str().unwrap();
1158 let version = v["version"].as_u64().unwrap();
1159 if version != 1 {
1160 continue; }
1162
1163 let beef = crate::transaction::beef::Beef::from_hex(hex)
1165 .unwrap_or_else(|e| panic!("vector {}: from_hex failed: {}", i, e));
1166 let tx = beef
1167 .into_transaction()
1168 .unwrap_or_else(|e| panic!("vector {}: into_transaction failed: {}", i, e));
1169
1170 let beef_bytes = tx
1172 .to_beef()
1173 .unwrap_or_else(|e| panic!("vector {}: to_beef failed: {}", i, e));
1174
1175 assert_eq!(
1177 &beef_bytes[0..4],
1178 &[0x01, 0x00, 0xBE, 0xEF],
1179 "vector {}: wrong BEEF header",
1180 i
1181 );
1182
1183 let re_hex: String = beef_bytes.iter().map(|b| format!("{:02x}", b)).collect();
1185 let re_parsed = Transaction::from_beef(&re_hex)
1186 .unwrap_or_else(|e| panic!("vector {}: round-trip from_beef failed: {}", i, e));
1187 assert_eq!(
1188 re_parsed.id().unwrap(),
1189 tx.id().unwrap(),
1190 "vector {}: txid mismatch after round-trip",
1191 i
1192 );
1193 assert_eq!(
1194 re_parsed.outputs.len(),
1195 tx.outputs.len(),
1196 "vector {}: output count mismatch",
1197 i
1198 );
1199 }
1200 }
1201
1202 #[test]
1203 fn test_to_beef_byte_equality_simple() {
1204 let vectors_json = std::fs::read_to_string("test-vectors/beef_valid.json").unwrap();
1207 let vectors: Vec<serde_json::Value> = serde_json::from_str(&vectors_json).unwrap();
1208 let original_hex = vectors[0]["hex"].as_str().unwrap();
1209
1210 let tx = Transaction::from_beef(original_hex).expect("from_beef");
1211 let beef_bytes = tx.to_beef().expect("to_beef");
1212 let result_hex: String = beef_bytes.iter().map(|b| format!("{:02x}", b)).collect();
1213
1214 assert_eq!(
1215 result_hex, original_hex,
1216 "round-trip should produce identical bytes"
1217 );
1218 }
1219
1220 #[test]
1221 fn test_to_beef_multi_tx_with_source_chain() {
1222 use crate::script::locking_script::LockingScript;
1225 use crate::transaction::merkle_path::{MerklePath, MerklePathLeaf};
1226 use crate::transaction::transaction_input::TransactionInput;
1227 use crate::transaction::transaction_output::TransactionOutput;
1228
1229 let mut parent = Transaction::new();
1231 parent.add_output(TransactionOutput {
1232 satoshis: Some(50_000),
1233 locking_script: LockingScript::from_binary(&vec![
1234 0x76, 0xa9, 0x14, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
1235 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x88, 0xac,
1236 ]),
1237 change: false,
1238 });
1239 let parent_txid = parent.id().unwrap();
1240
1241 parent.merkle_path = Some(MerklePath {
1243 block_height: 100,
1244 path: vec![vec![
1245 MerklePathLeaf {
1246 offset: 0,
1247 hash: Some(parent_txid.clone()),
1248 txid: true,
1249 duplicate: false,
1250 },
1251 MerklePathLeaf {
1252 offset: 1,
1253 hash: Some("bb".repeat(32)),
1254 txid: false,
1255 duplicate: false,
1256 },
1257 ]],
1258 });
1259
1260 let mut child = Transaction::new();
1262 child.add_input(TransactionInput {
1263 source_transaction: Some(Box::new(parent.clone())),
1264 source_txid: Some(parent_txid.clone()),
1265 source_output_index: 0,
1266 unlocking_script: None,
1267 sequence: 0xffffffff,
1268 });
1269 child.add_output(TransactionOutput {
1270 satoshis: Some(40_000),
1271 locking_script: LockingScript::from_binary(&vec![0x6a, 0x04, 0xde, 0xad]),
1272 change: false,
1273 });
1274
1275 let beef_bytes = child.to_beef().expect("to_beef should succeed");
1277
1278 assert_eq!(&beef_bytes[0..4], &[0x01, 0x00, 0xBE, 0xEF]);
1280
1281 let re_beef = crate::transaction::beef::Beef::from_hex(
1283 &beef_bytes
1284 .iter()
1285 .map(|b| format!("{:02x}", b))
1286 .collect::<String>(),
1287 )
1288 .expect("re-parse BEEF");
1289
1290 assert_eq!(re_beef.bumps.len(), 1, "should have 1 bump");
1291 assert_eq!(re_beef.txs.len(), 2, "should have 2 txs (parent + child)");
1292
1293 assert!(
1295 re_beef.txs[0].bump_index.is_some(),
1296 "first tx should be proven parent"
1297 );
1298 assert!(
1299 re_beef.txs[1].bump_index.is_none(),
1300 "second tx should be unproven child"
1301 );
1302 }
1303
1304 #[test]
1305 fn test_to_beef_errors_no_merkle_proofs() {
1306 let mut tx = Transaction::new();
1308 tx.add_output(TransactionOutput {
1309 satoshis: Some(1000),
1310 locking_script: crate::script::locking_script::LockingScript::from_binary(&vec![
1311 0x6a, 0x02, 0xab, 0xcd,
1312 ]),
1313 change: false,
1314 });
1315
1316 let result = tx.to_beef();
1317 assert!(result.is_err(), "to_beef should fail with no proofs");
1318 let err = result.unwrap_err().to_string();
1319 assert!(
1320 err.contains("merkle proof") || err.contains("source transaction"),
1321 "error should mention missing proofs, got: {}",
1322 err
1323 );
1324 }
1325
1326 #[test]
1327 fn test_to_beef_errors_missing_source_transaction() {
1328 use crate::transaction::transaction_input::TransactionInput;
1331 use crate::transaction::transaction_output::TransactionOutput;
1332
1333 let mut tx = Transaction::new();
1334 tx.add_input(TransactionInput {
1335 source_transaction: None,
1336 source_txid: Some("aa".repeat(32)),
1337 source_output_index: 0,
1338 unlocking_script: None,
1339 sequence: 0xffffffff,
1340 });
1341 tx.add_output(TransactionOutput {
1342 satoshis: Some(1000),
1343 locking_script: crate::script::locking_script::LockingScript::from_binary(&vec![
1344 0x6a, 0x02, 0xab, 0xcd,
1345 ]),
1346 change: false,
1347 });
1348
1349 let result = tx.to_beef();
1350 assert!(
1351 result.is_err(),
1352 "to_beef should fail with missing source tx"
1353 );
1354 let err = result.unwrap_err().to_string();
1355 assert!(
1356 err.contains("no source transaction"),
1357 "error should mention missing source transaction, got: {}",
1358 err
1359 );
1360 }
1361}