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 hash(&self) -> Result<[u8; 32], TransactionError> {
132 let bytes = self.to_bytes()?;
133 Ok(hash256(&bytes))
134 }
135
136 pub fn id(&self) -> Result<String, TransactionError> {
140 let mut h = self.hash()?;
141 h.reverse();
142 Ok(bytes_to_hex(&h))
143 }
144
145 pub fn add_input(&mut self, input: TransactionInput) {
147 self.inputs.push(input);
148 }
149
150 pub fn add_output(&mut self, output: TransactionOutput) {
152 self.outputs.push(output);
153 }
154
155 pub fn from_ef(reader: &mut impl Read) -> Result<Self, TransactionError> {
160 let version = read_u32_le(reader)?;
161
162 let mut marker = [0u8; 6];
164 reader.read_exact(&mut marker)?;
165 if marker != EF_MARKER {
166 return Err(TransactionError::InvalidFormat(
167 "invalid EF marker".to_string(),
168 ));
169 }
170
171 let input_count = read_varint(reader)? as usize;
172 let mut inputs = Vec::with_capacity(input_count);
173 for _ in 0..input_count {
174 let mut input = TransactionInput::from_binary(reader)?;
176
177 let source_satoshis = read_u64_le(reader)?;
179
180 let script_len = read_varint(reader)? as usize;
182 let mut script_bytes = vec![0u8; script_len];
183 if script_len > 0 {
184 reader.read_exact(&mut script_bytes)?;
185 }
186 let source_locking_script = LockingScript::from_binary(&script_bytes);
187
188 let mut source_tx = Transaction::new();
190 for _ in 0..input.source_output_index {
192 source_tx.outputs.push(TransactionOutput::default());
193 }
194 source_tx.outputs.push(TransactionOutput {
195 satoshis: Some(source_satoshis),
196 locking_script: source_locking_script,
197 change: false,
198 });
199 input.source_transaction = Some(Box::new(source_tx));
200
201 inputs.push(input);
202 }
203
204 let output_count = read_varint(reader)? as usize;
205 let mut outputs = Vec::with_capacity(output_count);
206 for _ in 0..output_count {
207 outputs.push(TransactionOutput::from_binary(reader)?);
208 }
209
210 let lock_time = read_u32_le(reader)?;
211
212 Ok(Transaction {
213 version,
214 inputs,
215 outputs,
216 lock_time,
217 merkle_path: None,
218 })
219 }
220
221 pub fn from_hex_ef(hex: &str) -> Result<Self, TransactionError> {
223 let bytes = hex_to_bytes(hex)
224 .map_err(|e| TransactionError::InvalidFormat(format!("invalid hex: {}", e)))?;
225 let mut cursor = Cursor::new(bytes);
226 Self::from_ef(&mut cursor)
227 }
228
229 pub fn to_ef(&self, writer: &mut impl Write) -> Result<(), TransactionError> {
231 write_u32_le(writer, self.version)?;
232
233 writer.write_all(&EF_MARKER)?;
235
236 write_varint(writer, self.inputs.len() as u64)?;
237 for input in &self.inputs {
238 input.to_binary(writer)?;
240
241 if let Some(ref source_tx) = input.source_transaction {
243 let idx = input.source_output_index as usize;
244 if idx < source_tx.outputs.len() {
245 let source_output = &source_tx.outputs[idx];
246 write_u64_le(writer, source_output.satoshis.unwrap_or(0))?;
247 let script_bin = source_output.locking_script.to_binary();
248 write_varint(writer, script_bin.len() as u64)?;
249 writer.write_all(&script_bin)?;
250 } else {
251 return Err(TransactionError::MissingSourceTransaction);
252 }
253 } else {
254 return Err(TransactionError::MissingSourceTransaction);
255 }
256 }
257
258 write_varint(writer, self.outputs.len() as u64)?;
259 for output in &self.outputs {
260 output.to_binary(writer)?;
261 }
262
263 write_u32_le(writer, self.lock_time)?;
264 Ok(())
265 }
266
267 pub fn to_hex_ef(&self) -> Result<String, TransactionError> {
269 let mut buf = Vec::new();
270 self.to_ef(&mut buf)?;
271 Ok(bytes_to_hex(&buf))
272 }
273
274 fn resolve_input_txid_bytes(&self, input_index: usize) -> Result<[u8; 32], TransactionError> {
278 let input = &self.inputs[input_index];
279 if let Some(ref txid) = input.source_txid {
280 let mut bytes = hex_to_bytes(txid)
281 .map_err(|e| TransactionError::InvalidFormat(format!("invalid txid hex: {}", e)))?;
282 bytes.reverse(); let mut arr = [0u8; 32];
284 if bytes.len() == 32 {
285 arr.copy_from_slice(&bytes);
286 }
287 Ok(arr)
288 } else if let Some(ref source_tx) = input.source_transaction {
289 source_tx.hash()
290 } else {
291 Err(TransactionError::InvalidFormat(
292 "input has neither source_txid nor source_transaction".to_string(),
293 ))
294 }
295 }
296
297 pub fn sighash_preimage(
308 &self,
309 input_index: usize,
310 scope: u32,
311 source_satoshis: u64,
312 source_locking_script: &LockingScript,
313 ) -> Result<Vec<u8>, TransactionError> {
314 if input_index >= self.inputs.len() {
315 return Err(TransactionError::InvalidSighash(format!(
316 "input_index {} out of range (tx has {} inputs)",
317 input_index,
318 self.inputs.len()
319 )));
320 }
321
322 let base_type = scope & 0x1f;
323 let anyone_can_pay = (scope & SIGHASH_ANYONECANPAY) != 0;
324
325 let mut preimage = Vec::with_capacity(256);
326
327 preimage.extend_from_slice(&self.version.to_le_bytes());
329
330 if !anyone_can_pay {
332 let mut prevouts = Vec::new();
333 for (i, input) in self.inputs.iter().enumerate() {
334 let txid_bytes = self.resolve_input_txid_bytes(i)?;
335 prevouts.extend_from_slice(&txid_bytes);
336 prevouts.extend_from_slice(&input.source_output_index.to_le_bytes());
337 }
338 preimage.extend_from_slice(&hash256(&prevouts));
339 } else {
340 preimage.extend_from_slice(&[0u8; 32]);
341 }
342
343 if !anyone_can_pay && base_type != SIGHASH_NONE && base_type != SIGHASH_SINGLE {
345 let mut sequences = Vec::new();
346 for input in &self.inputs {
347 sequences.extend_from_slice(&input.sequence.to_le_bytes());
348 }
349 preimage.extend_from_slice(&hash256(&sequences));
350 } else {
351 preimage.extend_from_slice(&[0u8; 32]);
352 }
353
354 let this_txid = self.resolve_input_txid_bytes(input_index)?;
356 preimage.extend_from_slice(&this_txid);
357 preimage.extend_from_slice(&self.inputs[input_index].source_output_index.to_le_bytes());
358
359 let script_bytes = source_locking_script.to_binary();
361 write_varint_to_vec(&mut preimage, script_bytes.len() as u64);
362 preimage.extend_from_slice(&script_bytes);
363
364 preimage.extend_from_slice(&source_satoshis.to_le_bytes());
366
367 preimage.extend_from_slice(&self.inputs[input_index].sequence.to_le_bytes());
369
370 if base_type != SIGHASH_NONE && base_type != SIGHASH_SINGLE {
372 let mut outputs_data = Vec::new();
374 for output in &self.outputs {
375 outputs_data.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
376 let script_bytes = output.locking_script.to_binary();
377 write_varint_to_vec(&mut outputs_data, script_bytes.len() as u64);
378 outputs_data.extend_from_slice(&script_bytes);
379 }
380 preimage.extend_from_slice(&hash256(&outputs_data));
381 } else if base_type == SIGHASH_SINGLE && input_index < self.outputs.len() {
382 let output = &self.outputs[input_index];
384 let mut out_data = Vec::new();
385 out_data.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
386 let script_bytes = output.locking_script.to_binary();
387 write_varint_to_vec(&mut out_data, script_bytes.len() as u64);
388 out_data.extend_from_slice(&script_bytes);
389 preimage.extend_from_slice(&hash256(&out_data));
390 } else {
391 preimage.extend_from_slice(&[0u8; 32]);
393 }
394
395 preimage.extend_from_slice(&self.lock_time.to_le_bytes());
397
398 preimage.extend_from_slice(&(scope | SIGHASH_FORKID).to_le_bytes());
400
401 Ok(preimage)
402 }
403
404 pub fn sighash_preimage_legacy(
412 &self,
413 input_index: usize,
414 scope: u32,
415 sub_script: &[u8],
416 ) -> Result<Vec<u8>, TransactionError> {
417 if input_index >= self.inputs.len() {
418 return Err(TransactionError::InvalidSighash(format!(
419 "input_index {} out of range (tx has {} inputs)",
420 input_index,
421 self.inputs.len()
422 )));
423 }
424
425 let sub_script = strip_codeseparator(sub_script);
428
429 let base_type = scope & 0x1f;
430 let anyone_can_pay = (scope & SIGHASH_ANYONECANPAY) != 0;
431 let is_none = base_type == SIGHASH_NONE;
432 let is_single = base_type == SIGHASH_SINGLE;
433
434 if is_single && input_index >= self.outputs.len() {
436 let mut result = vec![0u8; 32];
437 result[0] = 1;
438 return Ok(result);
439 }
440
441 let empty_script: Vec<u8> = Vec::new();
442
443 let mut preimage = Vec::with_capacity(512);
444
445 preimage.extend_from_slice(&self.version.to_le_bytes());
447
448 if anyone_can_pay {
450 write_varint_to_vec(&mut preimage, 1);
452 let txid_bytes = self.resolve_input_txid_bytes(input_index)?;
453 preimage.extend_from_slice(&txid_bytes);
454 preimage.extend_from_slice(&self.inputs[input_index].source_output_index.to_le_bytes());
455 write_varint_to_vec(&mut preimage, sub_script.len() as u64);
456 preimage.extend_from_slice(&sub_script);
457 preimage.extend_from_slice(&self.inputs[input_index].sequence.to_le_bytes());
458 } else {
459 write_varint_to_vec(&mut preimage, self.inputs.len() as u64);
460 for (i, input) in self.inputs.iter().enumerate() {
461 let txid_bytes = self.resolve_input_txid_bytes(i)?;
462 preimage.extend_from_slice(&txid_bytes);
463 preimage.extend_from_slice(&input.source_output_index.to_le_bytes());
464
465 if i == input_index {
467 write_varint_to_vec(&mut preimage, sub_script.len() as u64);
468 preimage.extend_from_slice(&sub_script);
469 } else {
470 write_varint_to_vec(&mut preimage, empty_script.len() as u64);
471 }
472
473 if i == input_index || (!is_single && !is_none) {
475 preimage.extend_from_slice(&input.sequence.to_le_bytes());
476 } else {
477 preimage.extend_from_slice(&0u32.to_le_bytes());
478 }
479 }
480 }
481
482 if is_none {
484 write_varint_to_vec(&mut preimage, 0);
485 } else if is_single {
486 write_varint_to_vec(&mut preimage, (input_index + 1) as u64);
487 for i in 0..input_index {
488 preimage.extend_from_slice(&u64::MAX.to_le_bytes());
490 write_varint_to_vec(&mut preimage, 0);
491 let _ = i;
492 }
493 let output = &self.outputs[input_index];
495 preimage.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
496 let script_bytes = output.locking_script.to_binary();
497 write_varint_to_vec(&mut preimage, script_bytes.len() as u64);
498 preimage.extend_from_slice(&script_bytes);
499 } else {
500 write_varint_to_vec(&mut preimage, self.outputs.len() as u64);
502 for output in &self.outputs {
503 preimage.extend_from_slice(&output.satoshis.unwrap_or(0).to_le_bytes());
504 let script_bytes = output.locking_script.to_binary();
505 write_varint_to_vec(&mut preimage, script_bytes.len() as u64);
506 preimage.extend_from_slice(&script_bytes);
507 }
508 }
509
510 preimage.extend_from_slice(&self.lock_time.to_le_bytes());
512
513 preimage.extend_from_slice(&scope.to_le_bytes());
515
516 Ok(preimage)
517 }
518
519 pub fn sign(
526 &mut self,
527 input_index: usize,
528 template: &dyn ScriptTemplateUnlock,
529 scope: u32,
530 source_satoshis: u64,
531 source_locking_script: &LockingScript,
532 ) -> Result<(), TransactionError> {
533 let preimage =
534 self.sighash_preimage(input_index, scope, source_satoshis, source_locking_script)?;
535 let unlocking_script = template
536 .sign(&preimage)
537 .map_err(|e| TransactionError::SigningFailed(format!("{}", e)))?;
538 self.inputs[input_index].unlocking_script = Some(unlocking_script);
539 Ok(())
540 }
541
542 pub fn sign_all_inputs(
555 &mut self,
556 template: &dyn ScriptTemplateUnlock,
557 scope: u32,
558 ) -> Result<(), TransactionError> {
559 let num_inputs = self.inputs.len();
560
561 for i in 0..num_inputs {
562 if self.inputs[i].unlocking_script.is_some() {
564 continue;
565 }
566
567 let (source_satoshis, source_locking_script) = {
569 let source_tx = self.inputs[i].source_transaction.as_ref().ok_or_else(|| {
570 TransactionError::SigningFailed(format!(
571 "input {}: source_transaction required for sign_all_inputs()",
572 i
573 ))
574 })?;
575 let out_idx = self.inputs[i].source_output_index as usize;
576 let output = source_tx.outputs.get(out_idx).ok_or_else(|| {
577 TransactionError::SigningFailed(format!(
578 "input {}: source transaction has no output at index {}",
579 i, out_idx
580 ))
581 })?;
582 let satoshis = output.satoshis.ok_or_else(|| {
583 TransactionError::SigningFailed(format!(
584 "input {}: source output {} has no satoshis",
585 i, out_idx
586 ))
587 })?;
588 (satoshis, output.locking_script.clone())
589 };
590
591 let preimage =
592 self.sighash_preimage(i, scope, source_satoshis, &source_locking_script)?;
593 let unlocking_script = template
594 .sign(&preimage)
595 .map_err(|e| TransactionError::SigningFailed(format!("input {}: {}", i, e)))?;
596 self.inputs[i].unlocking_script = Some(unlocking_script);
597 }
598
599 Ok(())
600 }
601}
602
603impl Default for Transaction {
604 fn default() -> Self {
605 Self::new()
606 }
607}
608
609fn bytes_to_hex(bytes: &[u8]) -> String {
611 let mut s = String::with_capacity(bytes.len() * 2);
612 for b in bytes {
613 s.push_str(&format!("{:02x}", b));
614 }
615 s
616}
617
618fn strip_codeseparator(script: &[u8]) -> Vec<u8> {
623 const OP_CODESEPARATOR: u8 = 0xab;
624
625 let mut result = Vec::with_capacity(script.len());
626 let mut i = 0;
627 while i < script.len() {
628 let opcode = script[i];
629 if opcode == OP_CODESEPARATOR {
630 i += 1;
632 continue;
633 }
634
635 if opcode > 0 && opcode < 76 {
636 let push_len = opcode as usize;
638 let end = std::cmp::min(i + 1 + push_len, script.len());
639 result.extend_from_slice(&script[i..end]);
640 i = end;
641 } else if opcode == 76 {
642 if i + 1 < script.len() {
644 let push_len = script[i + 1] as usize;
645 let end = std::cmp::min(i + 2 + push_len, script.len());
646 result.extend_from_slice(&script[i..end]);
647 i = end;
648 } else {
649 result.push(opcode);
650 i += 1;
651 }
652 } else if opcode == 77 {
653 if i + 2 < script.len() {
655 let push_len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
656 let end = std::cmp::min(i + 3 + push_len, script.len());
657 result.extend_from_slice(&script[i..end]);
658 i = end;
659 } else {
660 result.extend_from_slice(&script[i..]);
661 break;
662 }
663 } else if opcode == 78 {
664 if i + 4 < script.len() {
666 let push_len = u32::from_le_bytes([
667 script[i + 1],
668 script[i + 2],
669 script[i + 3],
670 script[i + 4],
671 ]) as usize;
672 let end = std::cmp::min(i + 5 + push_len, script.len());
673 result.extend_from_slice(&script[i..end]);
674 i = end;
675 } else {
676 result.extend_from_slice(&script[i..]);
677 break;
678 }
679 } else {
680 result.push(opcode);
682 i += 1;
683 }
684 }
685 result
686}
687
688fn write_varint_to_vec(buf: &mut Vec<u8>, val: u64) {
690 if val < 0xfd {
691 buf.push(val as u8);
692 } else if val <= 0xffff {
693 buf.push(0xfd);
694 buf.extend_from_slice(&(val as u16).to_le_bytes());
695 } else if val <= 0xffff_ffff {
696 buf.push(0xfe);
697 buf.extend_from_slice(&(val as u32).to_le_bytes());
698 } else {
699 buf.push(0xff);
700 buf.extend_from_slice(&val.to_le_bytes());
701 }
702}
703
704fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, String> {
706 if !hex.len().is_multiple_of(2) {
707 return Err("odd length hex string".to_string());
708 }
709 let mut bytes = Vec::with_capacity(hex.len() / 2);
710 for i in (0..hex.len()).step_by(2) {
711 let byte = u8::from_str_radix(&hex[i..i + 2], 16)
712 .map_err(|e| format!("invalid hex at position {}: {}", i, e))?;
713 bytes.push(byte);
714 }
715 Ok(bytes)
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721 use crate::primitives::private_key::PrivateKey;
722 use crate::primitives::transaction_signature::{SIGHASH_ALL, SIGHASH_FORKID};
723 use crate::script::templates::p2pkh::P2PKH;
724 use crate::script::templates::ScriptTemplateLock;
725 use serde::Deserialize;
726
727 #[derive(Deserialize)]
728 struct TestVector {
729 description: String,
730 hex: String,
731 txid: String,
732 version: u32,
733 inputs: usize,
734 outputs: usize,
735 locktime: u32,
736 }
737
738 fn load_test_vectors() -> Vec<TestVector> {
739 let json = include_str!("../../test-vectors/transaction_valid.json");
740 serde_json::from_str(json).expect("failed to parse transaction_valid.json")
741 }
742
743 #[test]
744 fn test_from_binary_round_trip() {
745 let vectors = load_test_vectors();
746 for v in &vectors {
747 let tx = Transaction::from_hex(&v.hex)
748 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.description, e));
749 let result_hex = tx
750 .to_hex()
751 .unwrap_or_else(|e| panic!("failed to serialize '{}': {}", v.description, e));
752 assert_eq!(
753 result_hex, v.hex,
754 "round-trip failed for '{}'",
755 v.description
756 );
757 }
758 }
759
760 #[test]
761 fn test_txid() {
762 let vectors = load_test_vectors();
763 for v in &vectors {
764 let tx = Transaction::from_hex(&v.hex)
765 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.description, e));
766 let txid = tx
767 .id()
768 .unwrap_or_else(|e| panic!("failed to compute id for '{}': {}", v.description, e));
769 assert_eq!(txid, v.txid, "txid mismatch for '{}'", v.description);
770 }
771 }
772
773 #[test]
774 fn test_input_output_counts() {
775 let vectors = load_test_vectors();
776 for v in &vectors {
777 let tx = Transaction::from_hex(&v.hex)
778 .unwrap_or_else(|e| panic!("failed to parse '{}': {}", v.description, e));
779 assert_eq!(
780 tx.inputs.len(),
781 v.inputs,
782 "input count mismatch for '{}'",
783 v.description
784 );
785 assert_eq!(
786 tx.outputs.len(),
787 v.outputs,
788 "output count mismatch for '{}'",
789 v.description
790 );
791 assert_eq!(
792 tx.version, v.version,
793 "version mismatch for '{}'",
794 v.description
795 );
796 assert_eq!(
797 tx.lock_time, v.locktime,
798 "locktime mismatch for '{}'",
799 v.description
800 );
801 }
802 }
803
804 #[test]
805 fn test_empty_transaction() {
806 let tx = Transaction::new();
807 assert_eq!(tx.version, 1);
808 assert!(tx.inputs.is_empty());
809 assert!(tx.outputs.is_empty());
810 assert_eq!(tx.lock_time, 0);
811 assert!(tx.merkle_path.is_none());
812 }
813
814 #[test]
815 fn test_add_input_output() {
816 let mut tx = Transaction::new();
817 assert_eq!(tx.inputs.len(), 0);
818 assert_eq!(tx.outputs.len(), 0);
819
820 tx.add_input(TransactionInput::default());
821 assert_eq!(tx.inputs.len(), 1);
822
823 tx.add_output(TransactionOutput::default());
824 assert_eq!(tx.outputs.len(), 1);
825 }
826
827 #[test]
828 fn test_ef_round_trip() {
829 let ef_hex = "010000000000000000ef01ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff3e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac00000000";
831
832 let tx = Transaction::from_hex_ef(ef_hex).expect("failed to parse EF hex");
833 assert_eq!(tx.inputs.len(), 1);
834 assert_eq!(tx.outputs.len(), 1);
835
836 let input = &tx.inputs[0];
838 assert!(input.source_transaction.is_some());
839 let source_tx = input.source_transaction.as_ref().unwrap();
840 let source_output = &source_tx.outputs[input.source_output_index as usize];
841 assert_eq!(source_output.satoshis, Some(0x663e)); let result_hex = tx.to_hex_ef().expect("failed to serialize to EF");
845 assert_eq!(result_hex, ef_hex);
846 }
847
848 #[test]
849 fn test_hash_and_id_consistency() {
850 let tx2hex = "01000000029e8d016a7b0dc49a325922d05da1f916d1e4d4f0cb840c9727f3d22ce8d1363f000000008c493046022100e9318720bee5425378b4763b0427158b1051eec8b08442ce3fbfbf7b30202a44022100d4172239ebd701dae2fbaaccd9f038e7ca166707333427e3fb2a2865b19a7f27014104510c67f46d2cbb29476d1f0b794be4cb549ea59ab9cc1e731969a7bf5be95f7ad5e7f904e5ccf50a9dc1714df00fbeb794aa27aaff33260c1032d931a75c56f2ffffffffa3195e7a1ab665473ff717814f6881485dc8759bebe97e31c301ffe7933a656f020000008b48304502201c282f35f3e02a1f32d2089265ad4b561f07ea3c288169dedcf2f785e6065efa022100e8db18aadacb382eed13ee04708f00ba0a9c40e3b21cf91da8859d0f7d99e0c50141042b409e1ebbb43875be5edde9c452c82c01e3903d38fa4fd89f3887a52cb8aea9dc8aec7e2c9d5b3609c03eb16259a2537135a1bf0f9c5fbbcbdbaf83ba402442ffffffff02206b1000000000001976a91420bb5c3bfaef0231dc05190e7f1c8e22e098991e88acf0ca0100000000001976a9149e3e2d23973a04ec1b02be97c30ab9f2f27c3b2c88ac00000000";
852 let tx2idhex = "8c9aa966d35bfeaf031409e0001b90ccdafd8d859799eb945a3c515b8260bcf2";
853
854 let tx = Transaction::from_hex(tx2hex).unwrap();
855 let id = tx.id().unwrap();
856 assert_eq!(id, tx2idhex);
857
858 let hash = tx.hash().unwrap();
860 let mut reversed_hash = hash;
861 reversed_hash.reverse();
862 let reversed_hex = bytes_to_hex(&reversed_hash);
863 assert_eq!(reversed_hex, tx2idhex);
864 }
865
866 fn otda_test_vectors() -> Vec<(&'static str, &'static str, usize, u32, &'static str)> {
871 vec![
872 ("0122769903cfc6fedb9c63fe76930fed0c87b44be46f5032a534fa05861548616a5b99034701000000040063656ac864c70228b3f6ebaf97be065b2180ece52fae4f9039c7bf932e567625d7d89582abe1340100000008ac6363650063ab65ffffffff587125b913706705dc799454ab0343ee8aa59a2f2d538f75e12c0deeb40aa741030000000027b3a02003d21f34040000000000fa7f2c0300000000016387213b0300000000056aabacacab00000000", "", 1, 902085315, "49fdc84c5f88a590c5c65e17de58da7d1028133b74ad2d56a8b15ba317ac98f6"),
873 ("7e8c3f7902634018b6e1db2ca591816dff64a6cff74643de7455323ebfc560500aad9eee8c0100000001519c2d146d06fdca2c2e5fa3a2559812df66b6b5e40a3c57b2f7071ae6fe3863c74ab0952d0100000001000bf6638c013c5f6503000000000351ac63cbbf66be", "acab", 0, 2433331782, "f6261bacaed3a70d504cd70d3c0623e3593f6d197cd47316e56cea79ceabe095"),
874 ("459499bb032fdcc39d3c6cf819dcaa0a0165d97578446aa87ab745fb9fdcd3e6177b4cba3d0000000005006a6a5265ffffffff10e5929ebe065273c112cab15f6a1f6d9a8a517c288311b048b16663b3d406dc030000000700535263655151ffffffff981d73a7f3d477ab055398bcf9a7d349db1a8e6362055e20f4207ad1b775bac301000000066a6363ac6552ffffffff0403342603000000000165c4390004000000000965ac52006565006365373ce8010000000005520000516aba5a9404000000000351655300000000", "6a5352", 0, 3544391288, "738b7dcb86260e6fe3fad331ff342429c157730bbcb90c205b9e08568557cd94"),
875 ("cb3b8d30043ccd81c3bda7f594cca60e2eef170c67ffe8a1eb1f1a994dc40a0a5cf89fa9690100000009ab536a52acab5300653bea9324983da711ccb6eaff060930e6f55cf6df75e5abdda91a8d5fc25c3b9b28d0e7370200000003ab51522f86cdbd8aa19b6b8536efb6ca8cc23ebccef585ad00a78b5956d803908482bb44b25c550000000007abab65530051ac8177a2acebc517db1d5b5be14f91ab40e811ec0316cf029ce657a4b06f04f30698f0a0e50000000007516aac636a6351ffffffff02ccbefa02000000000252ab3e297f0100000000060052525163521acc3e2b", "6aac636a63535153", 0, 3406487088, "3568dfad7e968afd3492bd146c8b0e3255f90e5b642a4ec10105693e8b029132"),
876 ]
877 }
878
879 #[test]
880 fn test_sighash_preimage_legacy_vectors() {
881 let vectors = otda_test_vectors();
882 let mut passed = 0;
883
884 for (i, (raw_tx_hex, script_hex, input_index, hash_type, expected_hash)) in
885 vectors.iter().enumerate()
886 {
887 let tx = Transaction::from_hex(raw_tx_hex)
888 .unwrap_or_else(|e| panic!("vector {}: failed to parse tx: {}", i, e));
889
890 let sub_script = if script_hex.is_empty() {
891 vec![]
892 } else {
893 hex_to_bytes(script_hex).unwrap()
894 };
895
896 let preimage = tx
897 .sighash_preimage_legacy(*input_index, *hash_type, &sub_script)
898 .unwrap_or_else(|e| panic!("vector {}: sighash error: {}", i, e));
899
900 let mut hash_bytes = hash256(&preimage);
902 hash_bytes.reverse();
903 let computed_hash = bytes_to_hex(&hash_bytes);
904
905 if computed_hash == *expected_hash {
906 passed += 1;
907 } else {
908 println!(
909 "MISMATCH vector {}: expected={}, got={}",
910 i, expected_hash, computed_hash
911 );
912 }
913 }
914
915 println!(
916 "sighash legacy OTDA vectors: {}/{} passed",
917 passed,
918 vectors.len()
919 );
920 assert_eq!(
921 passed,
922 vectors.len(),
923 "all sighash OTDA vectors should pass"
924 );
925 }
926
927 #[test]
928 fn test_sighash_preimage_bip143() {
929 let unsigned_tx_hex = "010000000193a35408b6068499e0d5abd799d3e827d9bfe70c9b75ebe209c91d25072326510000000000ffffffff02404b4c00000000001976a91404ff367be719efa79d76e4416ffb072cd53b208888acde94a905000000001976a91404d03f746652cfcb6cb55119ab473a045137d26588ac00000000";
931 let source_script_hex = "76a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac";
932 let source_satoshis: u64 = 100_000_000;
933 let expected_preimage_hex = "010000007ced5b2e5cf3ea407b005d8b18c393b6256ea2429b6ff409983e10adc61d0ae83bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e7066504493a35408b6068499e0d5abd799d3e827d9bfe70c9b75ebe209c91d2507232651000000001976a914c0a3c167a28cabb9fbb495affa0761e6e74ac60d88ac00e1f50500000000ffffffff87841ab2b7a4133af2c58256edb7c3c9edca765a852ebe2d0dc962604a30f1030000000041000000";
934
935 let tx = Transaction::from_hex(unsigned_tx_hex).unwrap();
936 let source_script_bytes = hex_to_bytes(source_script_hex).unwrap();
937 let source_locking_script = LockingScript::from_binary(&source_script_bytes);
938
939 let scope = SIGHASH_ALL | SIGHASH_FORKID;
940 let preimage = tx
941 .sighash_preimage(0, scope, source_satoshis, &source_locking_script)
942 .unwrap();
943 let preimage_hex = bytes_to_hex(&preimage);
944
945 assert_eq!(
946 preimage_hex, expected_preimage_hex,
947 "BIP143 preimage should match Go SDK test vector"
948 );
949 }
950
951 #[test]
954 fn test_sign_p2pkh() {
955 let key = PrivateKey::from_hex("1").unwrap();
956 let p2pkh_lock = P2PKH::from_private_key(key.clone());
957 let p2pkh_unlock = P2PKH::from_private_key(key.clone());
958
959 let lock_script = p2pkh_lock.lock().unwrap();
960
961 let mut tx = Transaction::new();
963 tx.add_input(TransactionInput {
964 source_transaction: None,
965 source_txid: Some("00".repeat(32)),
966 source_output_index: 0,
967 unlocking_script: None,
968 sequence: 0xffffffff,
969 });
970 tx.add_output(TransactionOutput {
971 satoshis: Some(50000),
972 locking_script: lock_script.clone(),
973 change: false,
974 });
975
976 let scope = SIGHASH_ALL | SIGHASH_FORKID;
978 tx.sign(0, &p2pkh_unlock, scope, 100000, &lock_script)
979 .expect("signing should succeed");
980
981 let unlock = tx.inputs[0].unlocking_script.as_ref().unwrap();
983 let chunks = unlock.chunks();
984 assert_eq!(
985 chunks.len(),
986 2,
987 "P2PKH unlock should have 2 chunks (sig + pubkey)"
988 );
989
990 let sig_data = chunks[0].data.as_ref().unwrap();
992 assert!(
993 sig_data.len() >= 70 && sig_data.len() <= 74,
994 "signature length {} should be 70-74",
995 sig_data.len()
996 );
997 assert_eq!(
998 *sig_data.last().unwrap(),
999 (SIGHASH_ALL | SIGHASH_FORKID) as u8,
1000 "last byte should be sighash type"
1001 );
1002
1003 let pubkey_data = chunks[1].data.as_ref().unwrap();
1005 assert_eq!(pubkey_data.len(), 33);
1006 }
1007
1008 #[test]
1009 fn test_sign_and_verify_round_trip() {
1010 use crate::script::spend::{Spend, SpendParams};
1011
1012 let key = PrivateKey::from_hex("abcdef01").unwrap();
1013 let p2pkh = P2PKH::from_private_key(key.clone());
1014 let lock_script = p2pkh.lock().unwrap();
1015
1016 let mut tx = Transaction::new();
1018 let source_satoshis = 100_000u64;
1019
1020 tx.add_input(TransactionInput {
1021 source_transaction: None,
1022 source_txid: Some("aa".repeat(32)),
1023 source_output_index: 0,
1024 unlocking_script: None,
1025 sequence: 0xffffffff,
1026 });
1027 tx.add_output(TransactionOutput {
1028 satoshis: Some(90_000),
1029 locking_script: lock_script.clone(),
1030 change: false,
1031 });
1032
1033 let scope = SIGHASH_ALL | SIGHASH_FORKID;
1034 tx.sign(0, &p2pkh, scope, source_satoshis, &lock_script)
1035 .expect("signing should succeed");
1036
1037 let unlock_script = tx.inputs[0].unlocking_script.clone().unwrap();
1039
1040 let mut spend = Spend::new(SpendParams {
1041 locking_script: lock_script.clone(),
1042 unlocking_script: unlock_script,
1043 source_txid: "aa".repeat(32),
1044 source_output_index: 0,
1045 source_satoshis,
1046 transaction_version: tx.version,
1047 transaction_lock_time: tx.lock_time,
1048 transaction_sequence: tx.inputs[0].sequence,
1049 other_inputs: vec![],
1050 other_outputs: tx.outputs.clone(),
1051 input_index: 0,
1052 });
1053
1054 let valid = spend.validate().expect("spend validation should not error");
1055 assert!(valid, "signed transaction should verify successfully");
1056 }
1057}