Skip to main content

blvm_primitives/serialization/
transaction.rs

1//! Transaction wire format serialization/deserialization
2//!
3//! Bitcoin transaction wire format specification.
4//! Must match Bitcoin protocol serialization exactly for consensus compatibility.
5
6use super::varint::{decode_varint, encode_varint};
7use crate::error::{ConsensusError, Result};
8use crate::types::*;
9use std::borrow::Cow;
10
11#[cfg(feature = "production")]
12use smallvec::SmallVec;
13
14/// Error type for transaction parsing failures
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum TransactionParseError {
17    InsufficientBytes,
18    InvalidVersion,
19    InvalidInputCount,
20    InvalidOutputCount,
21    InvalidScriptLength,
22    InvalidLockTime,
23    /// Witness flag is set and every input stack is empty.
24    SuperfluousWitness,
25}
26
27impl std::fmt::Display for TransactionParseError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            TransactionParseError::InsufficientBytes => {
31                write!(f, "Insufficient bytes to parse transaction")
32            }
33            TransactionParseError::InvalidVersion => write!(f, "Invalid transaction version"),
34            TransactionParseError::InvalidInputCount => write!(f, "Invalid input count"),
35            TransactionParseError::InvalidOutputCount => write!(f, "Invalid output count"),
36            TransactionParseError::InvalidScriptLength => write!(f, "Invalid script length"),
37            TransactionParseError::InvalidLockTime => write!(f, "Invalid lock time"),
38            TransactionParseError::SuperfluousWitness => {
39                write!(f, "Witness flag set but every witness stack is empty")
40            }
41        }
42    }
43}
44
45impl std::error::Error for TransactionParseError {}
46
47#[inline]
48fn checked_slice_end(offset: usize, len: u64) -> Result<usize> {
49    let len = usize::try_from(len).map_err(|_| {
50        ConsensusError::Serialization(Cow::Owned(
51            TransactionParseError::InvalidScriptLength.to_string(),
52        ))
53    })?;
54    offset.checked_add(len).ok_or_else(|| {
55        ConsensusError::Serialization(Cow::Owned(
56            TransactionParseError::InsufficientBytes.to_string(),
57        ))
58    })
59}
60
61/// After the 4-byte `version`, read the compact input count and optional segwit wrapper.
62///
63/// Matches [`UnserializeTransaction`] in Bitcoin Core: the first varint is always the input count.
64/// If it is zero, the next byte is an optional-features flag. `1` means BIP141 extended encoding
65/// (marker was absorbed into the empty `vin` vector); the real input-count varint follows. Flag `0`
66/// means no extension: `vin` and `vout` both stay empty and **no** output-count varint appears on
67/// the wire before `lock_time`.
68///
69/// **Do not** detect segwit by peeking `0x00 0x01` before decoding that first varint: the compact
70/// encoding of the input count is not always a single `0x00` byte, so peeking mis-aligns the stream.
71fn read_tx_input_count_after_version(
72    data: &[u8],
73    mut offset: usize,
74) -> Result<(bool, u64, usize, bool)> {
75    let (mut input_count, varint_len) = decode_varint(&data[offset..])?;
76    offset += varint_len;
77
78    if input_count > 1_000_000 {
79        return Err(ConsensusError::Serialization(Cow::Owned(
80            TransactionParseError::InvalidInputCount.to_string(),
81        )));
82    }
83
84    let mut is_segwit = false;
85    // When true, Bitcoin Core left vout empty without reading a vector length (flag byte was 0).
86    let mut implicit_empty_outputs = false;
87
88    if input_count == 0 {
89        if offset >= data.len() {
90            return Err(ConsensusError::Serialization(Cow::Owned(
91                TransactionParseError::InsufficientBytes.to_string(),
92            )));
93        }
94        let flag = data[offset];
95        offset += 1;
96
97        if flag == 0 {
98            implicit_empty_outputs = true;
99            return Ok((false, 0, offset, implicit_empty_outputs));
100        }
101
102        if flag != 1 {
103            return Err(ConsensusError::Serialization(Cow::Owned(format!(
104                "Unsupported segwit transaction flag: {flag}"
105            ))));
106        }
107
108        is_segwit = true;
109
110        let (ic2, vl2) = decode_varint(&data[offset..])?;
111        offset += vl2;
112        if ic2 > 1_000_000 {
113            return Err(ConsensusError::Serialization(Cow::Owned(
114                TransactionParseError::InvalidInputCount.to_string(),
115            )));
116        }
117        input_count = ic2;
118    }
119
120    Ok((is_segwit, input_count, offset, implicit_empty_outputs))
121}
122
123/// Serialize a transaction to Bitcoin wire format
124#[inline(always)]
125pub fn serialize_transaction(tx: &Transaction) -> Vec<u8> {
126    let mut result = Vec::new();
127    serialize_transaction_append(&mut result, tx);
128    result
129}
130
131/// Append serialized transaction to buffer (shared logic for into/inner).
132///
133/// When `vin` is empty and `vout` is non-empty, legacy `compact_size(0) || compact_size(n)` would
134/// serialize as `0x00 0x01…`, which our witness-aware deserializer (matching Bitcoin Core with
135/// witnesses allowed) reads as empty `vin` + **flag** `0x01`, not as `vout` count. Emit extended
136/// framing: dummy empty `vin`, flag `0x01`, real input count, then `vout` (see `SerializeTransaction`
137/// in Bitcoin Core).
138#[inline(always)]
139fn serialize_transaction_append(result: &mut Vec<u8>, tx: &Transaction) {
140    result.extend_from_slice(&(tx.version as u32).to_le_bytes());
141
142    if tx.inputs.is_empty() && !tx.outputs.is_empty() {
143        result.extend_from_slice(&encode_varint(0));
144        result.push(0x01);
145        result.extend_from_slice(&encode_varint(tx.inputs.len() as u64));
146    } else {
147        result.extend_from_slice(&encode_varint(tx.inputs.len() as u64));
148    }
149
150    for input in &tx.inputs {
151        result.extend_from_slice(&input.prevout.hash);
152        result.extend_from_slice(&input.prevout.index.to_le_bytes());
153        result.extend_from_slice(&encode_varint(input.script_sig.len() as u64));
154        result.extend_from_slice(&input.script_sig);
155        result.extend_from_slice(&(input.sequence as u32).to_le_bytes());
156    }
157
158    result.extend_from_slice(&encode_varint(tx.outputs.len() as u64));
159
160    for output in &tx.outputs {
161        result.extend_from_slice(&(output.value as u64).to_le_bytes());
162        result.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
163        result.extend_from_slice(&output.script_pubkey);
164    }
165
166    result.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
167}
168
169/// Serialize transaction into an existing buffer
170#[inline(always)]
171pub fn serialize_transaction_into(dst: &mut Vec<u8>, tx: &Transaction) -> usize {
172    dst.clear();
173    serialize_transaction_append(dst, tx);
174    dst.len()
175}
176
177/// Serialize a transaction in SegWit wire format.
178///
179/// A witness slice whose length is not the input count is an error.
180pub fn serialize_transaction_with_witness(
181    tx: &Transaction,
182    witnesses: &[Witness],
183) -> Result<Vec<u8>> {
184    if witnesses.len() != tx.inputs.len() {
185        return Err(ConsensusError::TransactionValidation(
186            format!(
187                "witness count {} must match input count {}",
188                witnesses.len(),
189                tx.inputs.len()
190            )
191            .into(),
192        ));
193    }
194    // A witness flag with only empty stacks is not a legal encoding. Emit the
195    // legacy transaction instead. Zero-input extended framing is unchanged:
196    // that transaction has no stacks to attach.
197    if !tx.inputs.is_empty() && witnesses.iter().all(|stack| stack.is_empty()) {
198        return Ok(serialize_transaction(tx));
199    }
200    let mut result = Vec::new();
201    result.extend_from_slice(&(tx.version as u32).to_le_bytes());
202    result.push(0x00);
203    result.push(0x01);
204    result.extend_from_slice(&encode_varint(tx.inputs.len() as u64));
205    for input in &tx.inputs {
206        result.extend_from_slice(&input.prevout.hash);
207        result.extend_from_slice(&input.prevout.index.to_le_bytes());
208        result.extend_from_slice(&encode_varint(input.script_sig.len() as u64));
209        result.extend_from_slice(&input.script_sig);
210        result.extend_from_slice(&(input.sequence as u32).to_le_bytes());
211    }
212    result.extend_from_slice(&encode_varint(tx.outputs.len() as u64));
213    for output in &tx.outputs {
214        result.extend_from_slice(&(output.value as u64).to_le_bytes());
215        result.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
216        result.extend_from_slice(&output.script_pubkey);
217    }
218    for witness in witnesses {
219        result.extend_from_slice(&encode_varint(witness.len() as u64));
220        for element in witness {
221            result.extend_from_slice(&encode_varint(element.len() as u64));
222            result.extend_from_slice(element);
223        }
224    }
225    result.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
226    Ok(result)
227}
228
229/// Deserialize a transaction from Bitcoin wire format.
230///
231/// Witness data (if present) is validated but not returned; use
232/// [`deserialize_transaction_with_witness`] when witness stacks are needed.
233pub fn deserialize_transaction(data: &[u8]) -> Result<Transaction> {
234    deserialize_transaction_with_witness(data).map(|(tx, _, _)| tx)
235}
236
237// ── Private parsing helpers ───────────────────────────────────────────────────
238
239/// The concrete vector type used for transaction inputs/outputs at runtime.
240/// SmallVec in production builds to avoid heap allocation for ≤2 elements;
241/// plain Vec in non-production builds for simplicity.
242#[cfg(feature = "production")]
243type TxInputVec = SmallVec<[TransactionInput; 2]>;
244#[cfg(not(feature = "production"))]
245type TxInputVec = Vec<TransactionInput>;
246
247#[cfg(feature = "production")]
248type TxOutputVec = SmallVec<[TransactionOutput; 2]>;
249#[cfg(not(feature = "production"))]
250type TxOutputVec = Vec<TransactionOutput>;
251
252/// Parse `count` transaction inputs starting at `*offset`, advancing it in place.
253#[inline]
254fn parse_inputs(data: &[u8], offset: &mut usize, count: u64) -> Result<TxInputVec> {
255    let mut inputs = TxInputVec::new();
256
257    for _ in 0..count {
258        if data.len() < *offset + 36 {
259            return Err(ConsensusError::Serialization(Cow::Owned(
260                TransactionParseError::InsufficientBytes.to_string(),
261            )));
262        }
263        let mut hash = [0u8; 32];
264        hash.copy_from_slice(&data[*offset..*offset + 32]);
265        *offset += 32;
266
267        let index = u32::from_le_bytes([
268            data[*offset],
269            data[*offset + 1],
270            data[*offset + 2],
271            data[*offset + 3],
272        ]);
273        *offset += 4;
274
275        let (script_len, varint_len) = decode_varint(&data[*offset..])?;
276        *offset += varint_len;
277
278        let script_sig_end = checked_slice_end(*offset, script_len)?;
279        if data.len() < script_sig_end {
280            return Err(ConsensusError::Serialization(Cow::Owned(
281                TransactionParseError::InsufficientBytes.to_string(),
282            )));
283        }
284        let script_sig = data[*offset..script_sig_end].to_vec();
285        *offset = script_sig_end;
286
287        if data.len() < *offset + 4 {
288            return Err(ConsensusError::Serialization(Cow::Owned(
289                TransactionParseError::InsufficientBytes.to_string(),
290            )));
291        }
292        let sequence = u32::from_le_bytes([
293            data[*offset],
294            data[*offset + 1],
295            data[*offset + 2],
296            data[*offset + 3],
297        ]) as u64;
298        *offset += 4;
299
300        inputs.push(TransactionInput {
301            prevout: OutPoint { hash, index },
302            script_sig,
303            sequence,
304        });
305    }
306
307    Ok(inputs)
308}
309
310/// Read the output-count varint (or return 0 on implicit-empty-outputs), then parse
311/// all transaction outputs, advancing `*offset` in place.
312#[inline]
313fn parse_outputs(
314    data: &[u8],
315    offset: &mut usize,
316    implicit_empty_outputs: bool,
317) -> Result<TxOutputVec> {
318    let output_count = if implicit_empty_outputs {
319        0
320    } else {
321        let (output_count, varint_len) = decode_varint(&data[*offset..])?;
322        *offset += varint_len;
323
324        if output_count > 1_000_000 {
325            return Err(ConsensusError::Serialization(Cow::Owned(
326                TransactionParseError::InvalidOutputCount.to_string(),
327            )));
328        }
329        output_count
330    };
331
332    let mut outputs = TxOutputVec::new();
333
334    for _ in 0..output_count {
335        if data.len() < *offset + 8 {
336            return Err(ConsensusError::Serialization(Cow::Owned(
337                TransactionParseError::InsufficientBytes.to_string(),
338            )));
339        }
340        let value = i64::from_le_bytes([
341            data[*offset],
342            data[*offset + 1],
343            data[*offset + 2],
344            data[*offset + 3],
345            data[*offset + 4],
346            data[*offset + 5],
347            data[*offset + 6],
348            data[*offset + 7],
349        ]);
350        *offset += 8;
351
352        let (script_len, varint_len) = decode_varint(&data[*offset..])?;
353        *offset += varint_len;
354
355        let script_pubkey_end = checked_slice_end(*offset, script_len)?;
356        if data.len() < script_pubkey_end {
357            return Err(ConsensusError::Serialization(Cow::Owned(
358                TransactionParseError::InsufficientBytes.to_string(),
359            )));
360        }
361        let script_pubkey = data[*offset..script_pubkey_end].to_vec();
362        *offset = script_pubkey_end;
363
364        outputs.push(TransactionOutput {
365            value,
366            script_pubkey,
367        });
368    }
369
370    Ok(outputs)
371}
372
373// ─────────────────────────────────────────────────────────────────────────────
374
375/// Deserialize a transaction, returning (tx, bytes_consumed). Convenience wrapper that discards witness data.
376pub fn deserialize_transaction_with_offset(data: &[u8]) -> Result<(Transaction, usize)> {
377    let (tx, _witnesses, bytes_consumed) = deserialize_transaction_with_witness(data)?;
378    Ok((tx, bytes_consumed))
379}
380
381/// Deserialize a transaction from Bitcoin wire format, returning transaction, witness, and bytes consumed
382pub fn deserialize_transaction_with_witness(
383    data: &[u8],
384) -> Result<(Transaction, Vec<Witness>, usize)> {
385    let mut offset = 0;
386
387    if data.len() < offset + 4 {
388        return Err(ConsensusError::Serialization(Cow::Owned(
389            TransactionParseError::InsufficientBytes.to_string(),
390        )));
391    }
392    // The wire version is four unsigned bytes. Sign-extending into the u64
393    // field turned `0xffffffff` into `u64::MAX`.
394    let version = u32::from_le_bytes([
395        data[offset],
396        data[offset + 1],
397        data[offset + 2],
398        data[offset + 3],
399    ]) as u64;
400    offset += 4;
401
402    let (is_segwit, input_count, mut offset, implicit_empty_outputs) =
403        read_tx_input_count_after_version(data, offset)?;
404
405    let inputs = parse_inputs(data, &mut offset, input_count)?;
406    let outputs = parse_outputs(data, &mut offset, implicit_empty_outputs)?;
407
408    let mut all_witnesses: Vec<Witness> = Vec::new();
409    if is_segwit {
410        for _ in 0..input_count {
411            let (stack_count, varint_len) = decode_varint(&data[offset..])?;
412            offset += varint_len;
413
414            let mut witness_stack: Witness = Vec::new();
415            for _ in 0..stack_count {
416                let (item_len, varint_len) = decode_varint(&data[offset..])?;
417                offset += varint_len;
418
419                let item_end = checked_slice_end(offset, item_len)?;
420                if data.len() < item_end {
421                    return Err(ConsensusError::Serialization(Cow::Owned(
422                        TransactionParseError::InsufficientBytes.to_string(),
423                    )));
424                }
425                witness_stack.push(data[offset..item_end].to_vec());
426                offset = item_end;
427            }
428            all_witnesses.push(witness_stack);
429        }
430        // BIP144: the witness flag requires at least one non-empty stack.
431        // A stack that contains an empty item still counts. This is not
432        // IsWitnessEmpty, which also treats all-empty items as empty.
433        if input_count > 0 && all_witnesses.iter().all(|stack| stack.is_empty()) {
434            return Err(ConsensusError::Serialization(Cow::Owned(
435                TransactionParseError::SuperfluousWitness.to_string(),
436            )));
437        }
438    } else {
439        for _ in 0..input_count {
440            all_witnesses.push(Vec::new());
441        }
442    }
443
444    if data.len() < offset + 4 {
445        return Err(ConsensusError::Serialization(Cow::Owned(
446            TransactionParseError::InsufficientBytes.to_string(),
447        )));
448    }
449    let lock_time = u32::from_le_bytes([
450        data[offset],
451        data[offset + 1],
452        data[offset + 2],
453        data[offset + 3],
454    ]) as u64;
455    offset += 4;
456
457    let tx = Transaction {
458        version,
459        inputs,
460        outputs,
461        lock_time,
462    };
463
464    Ok((tx, all_witnesses, offset))
465}
466
467/// Borrowed output scripts from one wire transaction.
468///
469/// The walk uses the same framing as [`deserialize_transaction_with_witness`].
470pub fn output_script_pubkey_slices_from_wire_tx(data: &[u8]) -> Result<(Vec<&[u8]>, usize)> {
471    if data.len() < 4 {
472        return Err(ConsensusError::Serialization(Cow::Owned(
473            TransactionParseError::InsufficientBytes.to_string(),
474        )));
475    }
476    let (is_segwit, input_count, mut offset, implicit_empty_outputs) =
477        read_tx_input_count_after_version(data, 4)?;
478    skip_input_bodies(data, &mut offset, input_count)?;
479    let scripts = take_output_scripts(data, &mut offset, implicit_empty_outputs)?;
480    if is_segwit {
481        skip_witness_bodies(data, &mut offset, input_count)?;
482    }
483    if data.len() < offset + 4 {
484        return Err(ConsensusError::Serialization(Cow::Owned(
485            TransactionParseError::InsufficientBytes.to_string(),
486        )));
487    }
488    offset += 4;
489    Ok((scripts, offset))
490}
491
492fn skip_input_bodies(data: &[u8], offset: &mut usize, count: u64) -> Result<()> {
493    for _ in 0..count {
494        if data.len() < *offset + 36 {
495            return Err(ConsensusError::Serialization(Cow::Owned(
496                TransactionParseError::InsufficientBytes.to_string(),
497            )));
498        }
499        *offset += 36;
500        let (script_len, varint_len) = decode_varint(&data[*offset..])?;
501        *offset += varint_len;
502        let script_sig_end = checked_slice_end(*offset, script_len)?;
503        if data.len() < script_sig_end {
504            return Err(ConsensusError::Serialization(Cow::Owned(
505                TransactionParseError::InsufficientBytes.to_string(),
506            )));
507        }
508        *offset = script_sig_end;
509        if data.len() < *offset + 4 {
510            return Err(ConsensusError::Serialization(Cow::Owned(
511                TransactionParseError::InsufficientBytes.to_string(),
512            )));
513        }
514        *offset += 4;
515    }
516    Ok(())
517}
518
519fn take_output_scripts<'a>(
520    data: &'a [u8],
521    offset: &mut usize,
522    implicit_empty_outputs: bool,
523) -> Result<Vec<&'a [u8]>> {
524    let output_count = if implicit_empty_outputs {
525        0
526    } else {
527        let (output_count, varint_len) = decode_varint(&data[*offset..])?;
528        *offset += varint_len;
529        if output_count > 1_000_000 {
530            return Err(ConsensusError::Serialization(Cow::Owned(
531                TransactionParseError::InvalidOutputCount.to_string(),
532            )));
533        }
534        output_count
535    };
536    let mut scripts = Vec::with_capacity(usize::try_from(output_count).unwrap_or(0));
537    for _ in 0..output_count {
538        if data.len() < *offset + 8 {
539            return Err(ConsensusError::Serialization(Cow::Owned(
540                TransactionParseError::InsufficientBytes.to_string(),
541            )));
542        }
543        *offset += 8;
544        let (script_len, varint_len) = decode_varint(&data[*offset..])?;
545        *offset += varint_len;
546        let script_end = checked_slice_end(*offset, script_len)?;
547        if data.len() < script_end {
548            return Err(ConsensusError::Serialization(Cow::Owned(
549                TransactionParseError::InsufficientBytes.to_string(),
550            )));
551        }
552        scripts.push(&data[*offset..script_end]);
553        *offset = script_end;
554    }
555    Ok(scripts)
556}
557
558fn skip_witness_bodies(data: &[u8], offset: &mut usize, input_count: u64) -> Result<()> {
559    let mut any_stack = false;
560    for _ in 0..input_count {
561        let (stack_count, varint_len) = decode_varint(&data[*offset..])?;
562        *offset += varint_len;
563        if stack_count > 0 {
564            any_stack = true;
565        }
566        for _ in 0..stack_count {
567            let (item_len, varint_len) = decode_varint(&data[*offset..])?;
568            *offset += varint_len;
569            let item_end = checked_slice_end(*offset, item_len)?;
570            if data.len() < item_end {
571                return Err(ConsensusError::Serialization(Cow::Owned(
572                    TransactionParseError::InsufficientBytes.to_string(),
573                )));
574            }
575            *offset = item_end;
576        }
577    }
578    if input_count > 0 && !any_stack {
579        return Err(ConsensusError::Serialization(Cow::Owned(
580            TransactionParseError::SuperfluousWitness.to_string(),
581        )));
582    }
583    Ok(())
584}
585
586#[cfg(test)]
587mod tests {
588    use super::*;
589
590    #[test]
591    fn test_serialize_deserialize_round_trip() {
592        let tx = Transaction {
593            version: 1,
594            inputs: crate::tx_inputs![TransactionInput {
595                prevout: OutPoint {
596                    hash: [1; 32],
597                    index: 0
598                },
599                script_sig: vec![0x51],
600                sequence: 0xffffffff,
601            }],
602            outputs: crate::tx_outputs![TransactionOutput {
603                value: 5000000000,
604                script_pubkey: vec![0x51],
605            }],
606            lock_time: 0,
607        };
608
609        let serialized = serialize_transaction(&tx);
610        let deserialized = deserialize_transaction(&serialized).unwrap();
611
612        assert_eq!(deserialized.version, tx.version);
613        assert_eq!(deserialized.inputs.len(), tx.inputs.len());
614        assert_eq!(deserialized.outputs.len(), tx.outputs.len());
615        assert_eq!(deserialized.lock_time, tx.lock_time);
616    }
617
618    /// Bitcoin Core: empty `vin` + flag `0` implies empty `vout` without a separate output-count read.
619    #[test]
620    fn empty_tx_round_trip_matches_double_zero_preamble() {
621        let tx = Transaction {
622            version: 1,
623            inputs: crate::tx_inputs![],
624            outputs: crate::tx_outputs![],
625            lock_time: 0,
626        };
627        let bytes = serialize_transaction(&tx);
628        let back = deserialize_transaction(&bytes).unwrap();
629        assert_eq!(back.version, tx.version);
630        assert!(back.inputs.is_empty());
631        assert!(back.outputs.is_empty());
632        assert_eq!(back.lock_time, tx.lock_time);
633        // version(4) + vin=0 + flags=0 + locktime(4) — two 0x00 bytes after version
634        assert_eq!(&bytes[4..6], &[0u8, 0u8]);
635    }
636
637    #[test]
638    fn zero_inputs_one_output_round_trips_extended_framing() {
639        let tx = Transaction {
640            version: 2,
641            inputs: crate::tx_inputs![],
642            outputs: crate::tx_outputs![TransactionOutput {
643                value: 1000,
644                script_pubkey: vec![0x51],
645            }],
646            lock_time: 0x11223344,
647        };
648        let bytes = serialize_transaction(&tx);
649        let back = deserialize_transaction(&bytes).unwrap();
650        assert_eq!(back.version, tx.version);
651        assert!(back.inputs.is_empty());
652        assert_eq!(back.outputs.len(), 1);
653        assert_eq!(back.outputs[0].value, 1000);
654        assert_eq!(back.lock_time, tx.lock_time);
655        // version(4) + 0x00 dummy vin + 0x01 flag + 0x00 real ic + vout count + ...
656        assert_eq!(&bytes[4..8], &[0u8, 1, 0, 1]);
657    }
658
659    #[test]
660    fn high_bit_version_stays_in_the_low_32_bits() {
661        let tx = Transaction {
662            version: 0xffff_ffff,
663            inputs: crate::tx_inputs![],
664            outputs: crate::tx_outputs![],
665            lock_time: 0,
666        };
667        let bytes = serialize_transaction(&tx);
668        assert_eq!(&bytes[0..4], &[0xff, 0xff, 0xff, 0xff]);
669        let back = deserialize_transaction(&bytes).unwrap();
670        assert_eq!(back.version, 0xffff_ffff);
671        assert!(back.version >= 2);
672
673        let witness_bytes = serialize_transaction_with_witness(&tx, &[]).unwrap();
674        assert_eq!(&witness_bytes[0..4], &[0xff, 0xff, 0xff, 0xff]);
675        let (witness_back, stacks, _) =
676            deserialize_transaction_with_witness(&witness_bytes).unwrap();
677        assert_eq!(witness_back.version, 0xffff_ffff);
678        assert!(stacks.is_empty());
679    }
680
681    #[test]
682    fn witness_flag_with_only_empty_stacks_does_not_decode() {
683        // version 1, marker, flag, one input, one empty output, empty witness stack, locktime 0.
684        let bytes = hex_bytes(
685            "0100000000010100000000000000000000000000000000000000000000000000\
686             00000000000000ffffffff00ffffffff010000000000000000000000000000",
687        );
688        let err = deserialize_transaction_with_witness(&bytes).unwrap_err();
689        let msg = err.to_string();
690        assert!(
691            msg.contains("every witness stack is empty"),
692            "unexpected error: {msg}"
693        );
694    }
695
696    #[test]
697    fn witness_stack_of_one_empty_item_still_decodes() {
698        let tx = Transaction {
699            version: 1,
700            inputs: crate::tx_inputs![TransactionInput {
701                prevout: OutPoint {
702                    hash: [0; 32],
703                    index: 0xffffffff,
704                },
705                script_sig: vec![],
706                sequence: 0xffffffff,
707            }],
708            outputs: crate::tx_outputs![TransactionOutput {
709                value: 0,
710                script_pubkey: vec![],
711            }],
712            lock_time: 0,
713        };
714        let bytes = serialize_transaction_with_witness(&tx, &[vec![vec![]]]).unwrap();
715        let (back, stacks, consumed) = deserialize_transaction_with_witness(&bytes).unwrap();
716        assert_eq!(consumed, bytes.len());
717        assert_eq!(back.inputs.len(), 1);
718        assert_eq!(stacks.len(), 1);
719        assert_eq!(stacks[0].len(), 1);
720        assert!(stacks[0][0].is_empty());
721    }
722
723    #[test]
724    fn empty_stacks_serialize_without_the_witness_flag() {
725        let tx = Transaction {
726            version: 1,
727            inputs: crate::tx_inputs![TransactionInput {
728                prevout: OutPoint {
729                    hash: [2; 32],
730                    index: 1,
731                },
732                script_sig: vec![0x51],
733                sequence: 0xfffffffe,
734            }],
735            outputs: crate::tx_outputs![TransactionOutput {
736                value: 50,
737                script_pubkey: vec![0x51],
738            }],
739            lock_time: 0,
740        };
741        let with_empty = serialize_transaction_with_witness(&tx, &[Vec::new()]).unwrap();
742        let legacy = serialize_transaction(&tx);
743        assert_eq!(with_empty, legacy);
744        let (back, stacks, _) = deserialize_transaction_with_witness(&with_empty).unwrap();
745        assert_eq!(back.inputs.len(), 1);
746        assert!(stacks[0].is_empty());
747    }
748
749    #[test]
750    fn witness_count_mismatch_is_an_error() {
751        let tx = Transaction {
752            version: 1,
753            inputs: crate::tx_inputs![TransactionInput {
754                prevout: OutPoint {
755                    hash: [3; 32],
756                    index: 0,
757                },
758                script_sig: vec![],
759                sequence: 0xffffffff,
760            }],
761            outputs: crate::tx_outputs![TransactionOutput {
762                value: 1,
763                script_pubkey: vec![],
764            }],
765            lock_time: 0,
766        };
767        assert!(serialize_transaction_with_witness(&tx, &[]).is_err());
768        assert!(serialize_transaction_with_witness(&tx, &[vec![vec![1]], vec![vec![2]]]).is_err());
769        assert!(serialize_transaction_with_witness(&tx, &[vec![vec![1]]]).is_ok());
770    }
771
772    fn hex_bytes(s: &str) -> Vec<u8> {
773        (0..s.len())
774            .step_by(2)
775            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
776            .collect()
777    }
778}