Skip to main content

hns_transaction/
lib.rs

1#![doc = "Canonical Handshake transaction, witness, address, and coin values."]
2
3mod linkage;
4mod name;
5
6pub use linkage::{CovenantLinkError, CovenantLinkSummary, verify_covenant_links};
7pub use name::{
8    NameTransactionError, build_finalize_output, build_finalize_transaction, build_transfer_output,
9    build_transfer_transaction, verify_finalize_at_index_zero, verify_finalize_output,
10    verify_transfer_at_index_zero, verify_transfer_output,
11};
12
13use blake2::Blake2bVar;
14use blake2::digest::{Update, VariableOutput};
15use hns_covenants::{Covenant, CovenantError};
16use hns_encoding::{Decoder, Encoder};
17pub use hns_primitives::Outpoint;
18use hns_primitives::{Dollarydoos, Height, TransactionHash};
19use thiserror::Error;
20
21pub const MAX_TRANSACTION_SIZE: usize = 1_000_000;
22pub const MAX_TRANSACTION_RAW_SIZE: usize = 4_000_000;
23pub const MAX_TRANSACTION_WEIGHT: usize = 4_000_000;
24pub const MAX_WITNESS_ITEMS: usize = 1000;
25pub const MAX_ADDRESS_HASH_SIZE: usize = 40;
26pub const MIN_ADDRESS_HASH_SIZE: usize = 2;
27
28fn encode_outpoint_to(outpoint: Outpoint, encoder: &mut Encoder) {
29    encoder.put_bytes(&outpoint.encode());
30}
31
32fn decode_outpoint_from(decoder: &mut Decoder<'_>) -> Result<Outpoint, TransactionError> {
33    Ok(Outpoint {
34        transaction_hash: TransactionHash::new(decoder.read_array()?),
35        index: decoder.read_u32_le()?,
36    })
37}
38
39#[derive(Clone, Debug, Default, Eq, PartialEq)]
40pub struct Witness {
41    pub items: Vec<Vec<u8>>,
42}
43
44impl Witness {
45    fn encode_to(&self, encoder: &mut Encoder) -> Result<(), TransactionError> {
46        self.encoded_size()?;
47        encoder.put_compact_size(self.items.len() as u64);
48        for item in &self.items {
49            encoder.put_varbytes(item);
50        }
51        Ok(())
52    }
53
54    fn encoded_size(&self) -> Result<usize, TransactionError> {
55        if self.items.len() > MAX_WITNESS_ITEMS {
56            return Err(TransactionError::TooLarge {
57                actual: self.items.len(),
58                maximum: MAX_WITNESS_ITEMS,
59            });
60        }
61        let mut size = compact_size_len(self.items.len() as u64);
62        for item in &self.items {
63            if item.len() > MAX_TRANSACTION_RAW_SIZE {
64                return Err(TransactionError::TooLarge {
65                    actual: item.len(),
66                    maximum: MAX_TRANSACTION_RAW_SIZE,
67                });
68            }
69            size = size
70                .checked_add(compact_size_len(item.len() as u64))
71                .and_then(|size| size.checked_add(item.len()))
72                .ok_or(TransactionError::ArithmeticOverflow)?;
73            if size > MAX_TRANSACTION_RAW_SIZE {
74                return Err(TransactionError::TooLarge {
75                    actual: size,
76                    maximum: MAX_TRANSACTION_RAW_SIZE,
77                });
78            }
79        }
80        Ok(size)
81    }
82
83    fn decode_from(
84        decoder: &mut Decoder<'_>,
85        transaction_start: usize,
86        maximum_transaction_size: usize,
87    ) -> Result<Self, TransactionError> {
88        let count = decoder.read_compact_usize(MAX_WITNESS_ITEMS, "witness items")?;
89        remaining_decode_budget(decoder, transaction_start, maximum_transaction_size)?;
90        let mut items = Vec::with_capacity(count.min(64));
91        for _ in 0..count {
92            items.push(read_transaction_varbytes(
93                decoder,
94                transaction_start,
95                maximum_transaction_size,
96                "witness item",
97            )?);
98        }
99        Ok(Self { items })
100    }
101}
102
103#[derive(Clone, Debug, Eq, PartialEq)]
104pub struct Input {
105    pub previous_output: Outpoint,
106    pub sequence: u32,
107    pub witness: Witness,
108}
109
110impl Input {
111    fn encode_base_to(&self, encoder: &mut Encoder) {
112        encode_outpoint_to(self.previous_output, encoder);
113        encoder.put_u32_le(self.sequence);
114    }
115
116    fn decode_base_from(decoder: &mut Decoder<'_>) -> Result<Self, TransactionError> {
117        Ok(Self {
118            previous_output: decode_outpoint_from(decoder)?,
119            sequence: decoder.read_u32_le()?,
120            witness: Witness::default(),
121        })
122    }
123}
124
125#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct Address {
127    pub version: u8,
128    pub hash: Vec<u8>,
129}
130
131impl Address {
132    pub fn new(version: u8, hash: Vec<u8>) -> Result<Self, TransactionError> {
133        let address = Self { version, hash };
134        address.validate()?;
135        Ok(address)
136    }
137
138    pub const fn is_null_data(&self) -> bool {
139        self.version == 31
140    }
141
142    /// Construct HSD's version-zero single-key witness program from an
143    /// original compressed secp256k1 public key.
144    ///
145    /// This performs the canonical BLAKE2b-160 address hash. Callers making a
146    /// cryptographic trust decision must separately validate that the SEC1
147    /// bytes encode a curve point.
148    pub fn from_compressed_public_key(public_key: &[u8; 33]) -> Result<Self, TransactionError> {
149        if !matches!(public_key[0], 0x02 | 0x03) {
150            return Err(TransactionError::InvalidAddress(
151                "compressed public key must begin with 02 or 03",
152            ));
153        }
154        Self::new(0, blake2b_160(public_key).to_vec())
155    }
156
157    pub fn validate(&self) -> Result<(), TransactionError> {
158        if self.version > 31 {
159            return Err(TransactionError::InvalidAddress("version exceeds 31"));
160        }
161        if !(MIN_ADDRESS_HASH_SIZE..=MAX_ADDRESS_HASH_SIZE).contains(&self.hash.len()) {
162            return Err(TransactionError::InvalidAddress(
163                "hash length is outside 2..=40",
164            ));
165        }
166        if self.version == 0 && !matches!(self.hash.len(), 20 | 32) {
167            return Err(TransactionError::InvalidAddress(
168                "version 0 program must be 20 or 32 bytes",
169            ));
170        }
171        Ok(())
172    }
173
174    fn encode_to(&self, encoder: &mut Encoder) -> Result<(), TransactionError> {
175        self.validate()?;
176        encoder.put_u8(self.version);
177        encoder.put_u8(self.hash.len() as u8);
178        encoder.put_bytes(&self.hash);
179        Ok(())
180    }
181
182    fn decode_from(decoder: &mut Decoder<'_>) -> Result<Self, TransactionError> {
183        let version = decoder.read_u8()?;
184        let length = decoder.read_u8()? as usize;
185        let hash = decoder.read_bounded_vec(length, MAX_ADDRESS_HASH_SIZE)?;
186        Self::new(version, hash)
187    }
188}
189
190#[derive(Clone, Debug, Eq, PartialEq)]
191pub struct Output {
192    pub value: Dollarydoos,
193    pub address: Address,
194    pub covenant: Covenant,
195}
196
197impl Output {
198    pub fn is_unspendable(&self) -> bool {
199        self.address.is_null_data() || self.covenant.kind.is_unspendable()
200    }
201
202    pub fn encode(&self) -> Result<Vec<u8>, TransactionError> {
203        let mut encoder = Encoder::new();
204        self.encode_to(&mut encoder)?;
205        Ok(encoder.into_bytes())
206    }
207
208    fn encode_to(&self, encoder: &mut Encoder) -> Result<(), TransactionError> {
209        encoder.put_u64_le(self.value.get());
210        self.address.encode_to(encoder)?;
211        self.covenant.encode_to(encoder)?;
212        Ok(())
213    }
214
215    fn decode_from(
216        decoder: &mut Decoder<'_>,
217        transaction_start: usize,
218        maximum_transaction_size: usize,
219    ) -> Result<Self, TransactionError> {
220        let value = Dollarydoos::new(decoder.read_u64_le()?);
221        let address = Address::decode_from(decoder)?;
222        let remaining =
223            remaining_decode_budget(decoder, transaction_start, maximum_transaction_size)?;
224        let covenant = Covenant::decode_from_with_limit(decoder, remaining)?;
225        remaining_decode_budget(decoder, transaction_start, maximum_transaction_size)?;
226        Ok(Self {
227            value,
228            address,
229            covenant,
230        })
231    }
232}
233
234#[derive(Clone, Debug, Eq, PartialEq)]
235pub struct Transaction {
236    pub version: u32,
237    pub inputs: Vec<Input>,
238    pub outputs: Vec<Output>,
239    pub locktime: u32,
240}
241
242impl Transaction {
243    pub fn encode(&self) -> Result<Vec<u8>, TransactionError> {
244        let (base_size, witness_size) = self.encoded_sizes()?;
245        let base = self.base_encode()?;
246        let witness = self.witness_encode()?;
247        debug_assert_eq!(base.len(), base_size);
248        debug_assert_eq!(witness.len(), witness_size);
249        let size = base_size
250            .checked_add(witness_size)
251            .ok_or(TransactionError::ArithmeticOverflow)?;
252        let mut output = Vec::with_capacity(size);
253        output.extend(base);
254        output.extend(witness);
255        Ok(output)
256    }
257
258    pub fn decode(input: &[u8]) -> Result<Self, TransactionError> {
259        if input.len() > MAX_TRANSACTION_RAW_SIZE {
260            return Err(TransactionError::TooLarge {
261                actual: input.len(),
262                maximum: MAX_TRANSACTION_RAW_SIZE,
263            });
264        }
265        let mut decoder = Decoder::new(input);
266        let transaction = Self::decode_from(&mut decoder)?;
267        decoder.finish()?;
268        Ok(transaction)
269    }
270
271    pub fn decode_prefix(input: &[u8]) -> Result<(Self, usize), TransactionError> {
272        let mut decoder = Decoder::new(input);
273        let transaction = Self::decode_from(&mut decoder)?;
274        Ok((transaction, decoder.position()))
275    }
276
277    pub fn decode_from(decoder: &mut Decoder<'_>) -> Result<Self, TransactionError> {
278        let start = decoder.position();
279        let version = decoder.read_u32_le()?;
280        let input_count =
281            decoder.read_compact_usize(MAX_TRANSACTION_SIZE / 40, "transaction inputs")?;
282        remaining_decode_budget(decoder, start, MAX_TRANSACTION_SIZE)?;
283        let mut inputs = Vec::with_capacity(input_count.min(1024));
284        for _ in 0..input_count {
285            inputs.push(Input::decode_base_from(decoder)?);
286            remaining_decode_budget(decoder, start, MAX_TRANSACTION_SIZE)?;
287        }
288        let output_count =
289            decoder.read_compact_usize(MAX_TRANSACTION_SIZE / 12, "transaction outputs")?;
290        remaining_decode_budget(decoder, start, MAX_TRANSACTION_SIZE)?;
291        let mut outputs = Vec::with_capacity(output_count.min(1024));
292        for _ in 0..output_count {
293            outputs.push(Output::decode_from(decoder, start, MAX_TRANSACTION_SIZE)?);
294        }
295        let locktime = decoder.read_u32_le()?;
296        let base_size = decoder.position().saturating_sub(start);
297        remaining_decode_budget(decoder, start, MAX_TRANSACTION_SIZE)?;
298        let base_weight = base_size
299            .checked_mul(4)
300            .ok_or(TransactionError::ArithmeticOverflow)?;
301        let maximum_transaction_size = base_size
302            .checked_add(MAX_TRANSACTION_WEIGHT.checked_sub(base_weight).ok_or(
303                TransactionError::TooLarge {
304                    actual: base_weight,
305                    maximum: MAX_TRANSACTION_WEIGHT,
306                },
307            )?)
308            .ok_or(TransactionError::ArithmeticOverflow)?
309            .min(MAX_TRANSACTION_RAW_SIZE);
310        for input in &mut inputs {
311            input.witness = Witness::decode_from(decoder, start, maximum_transaction_size)?;
312        }
313        let transaction = Self {
314            version,
315            inputs,
316            outputs,
317            locktime,
318        };
319        remaining_decode_budget(decoder, start, maximum_transaction_size)?;
320        Ok(transaction)
321    }
322
323    pub fn base_encode(&self) -> Result<Vec<u8>, TransactionError> {
324        let base_size = self.base_encoded_size()?;
325        let mut encoder = Encoder::with_capacity(base_size);
326        encoder.put_u32_le(self.version);
327        encoder.put_compact_size(self.inputs.len() as u64);
328        for input in &self.inputs {
329            input.encode_base_to(&mut encoder);
330        }
331        encoder.put_compact_size(self.outputs.len() as u64);
332        for output in &self.outputs {
333            output.encode_to(&mut encoder)?;
334        }
335        encoder.put_u32_le(self.locktime);
336        Ok(encoder.into_bytes())
337    }
338
339    pub fn witness_encode(&self) -> Result<Vec<u8>, TransactionError> {
340        let witness_size = self.witness_encoded_size()?;
341        let mut encoder = Encoder::with_capacity(witness_size);
342        for input in &self.inputs {
343            input.witness.encode_to(&mut encoder)?;
344        }
345        Ok(encoder.into_bytes())
346    }
347
348    pub fn transaction_hash(&self) -> Result<TransactionHash, TransactionError> {
349        Ok(TransactionHash::new(blake2b_256(&self.base_encode()?)))
350    }
351
352    pub fn witness_hash(&self) -> Result<[u8; 32], TransactionError> {
353        let transaction_hash = self.transaction_hash()?;
354        let witness_data_hash = blake2b_256(&self.witness_encode()?);
355        Ok(blake2b_256_many(&[
356            transaction_hash.as_bytes(),
357            &witness_data_hash,
358        ]))
359    }
360
361    pub fn base_size(&self) -> Result<usize, TransactionError> {
362        self.base_encoded_size()
363    }
364
365    pub fn size(&self) -> Result<usize, TransactionError> {
366        let (base, witness) = self.encoded_sizes()?;
367        base.checked_add(witness)
368            .ok_or(TransactionError::ArithmeticOverflow)
369    }
370
371    pub fn weight(&self) -> Result<usize, TransactionError> {
372        let (base, witness) = self.encoded_sizes()?;
373        transaction_weight(base, witness)
374    }
375
376    pub fn is_coinbase(&self) -> bool {
377        self.inputs
378            .first()
379            .is_some_and(|input| input.previous_output.is_null())
380    }
381
382    fn base_encoded_size(&self) -> Result<usize, TransactionError> {
383        if self.inputs.len() > MAX_TRANSACTION_SIZE / 40
384            || self.outputs.len() > MAX_TRANSACTION_SIZE / 12
385        {
386            return Err(TransactionError::TooLarge {
387                actual: self.inputs.len().max(self.outputs.len()),
388                maximum: MAX_TRANSACTION_SIZE / 12,
389            });
390        }
391        let input_bytes = self
392            .inputs
393            .len()
394            .checked_mul(40)
395            .ok_or(TransactionError::ArithmeticOverflow)?;
396        let mut size = 4_usize
397            .checked_add(compact_size_len(self.inputs.len() as u64))
398            .and_then(|size| size.checked_add(input_bytes))
399            .and_then(|size| size.checked_add(compact_size_len(self.outputs.len() as u64)))
400            .ok_or(TransactionError::ArithmeticOverflow)?;
401        for output in &self.outputs {
402            output.address.validate()?;
403            let covenant_size = output.covenant.encoded_size()?;
404            size = size
405                .checked_add(8)
406                .and_then(|size| size.checked_add(2))
407                .and_then(|size| size.checked_add(output.address.hash.len()))
408                .and_then(|size| size.checked_add(covenant_size))
409                .ok_or(TransactionError::ArithmeticOverflow)?;
410            if size > MAX_TRANSACTION_SIZE {
411                return Err(TransactionError::TooLarge {
412                    actual: size,
413                    maximum: MAX_TRANSACTION_SIZE,
414                });
415            }
416        }
417        size = size
418            .checked_add(4)
419            .ok_or(TransactionError::ArithmeticOverflow)?;
420        if size > MAX_TRANSACTION_SIZE {
421            return Err(TransactionError::TooLarge {
422                actual: size,
423                maximum: MAX_TRANSACTION_SIZE,
424            });
425        }
426        Ok(size)
427    }
428
429    fn witness_encoded_size(&self) -> Result<usize, TransactionError> {
430        let mut size = 0_usize;
431        for input in &self.inputs {
432            size = size
433                .checked_add(input.witness.encoded_size()?)
434                .ok_or(TransactionError::ArithmeticOverflow)?;
435            if size > MAX_TRANSACTION_RAW_SIZE {
436                return Err(TransactionError::TooLarge {
437                    actual: size,
438                    maximum: MAX_TRANSACTION_RAW_SIZE,
439                });
440            }
441        }
442        Ok(size)
443    }
444
445    fn encoded_sizes(&self) -> Result<(usize, usize), TransactionError> {
446        let base = self.base_encoded_size()?;
447        let witness = self.witness_encoded_size()?;
448        transaction_weight(base, witness)?;
449        Ok((base, witness))
450    }
451}
452
453#[derive(Clone, Debug, Eq, PartialEq)]
454pub struct Coin {
455    pub outpoint: Outpoint,
456    pub value: Dollarydoos,
457    pub height: Height,
458    pub coinbase: bool,
459    pub address: Address,
460    pub covenant: Covenant,
461}
462
463#[derive(Debug, Error)]
464pub enum TransactionError {
465    #[error(transparent)]
466    Decode(#[from] hns_encoding::DecodeError),
467    #[error(transparent)]
468    Covenant(#[from] CovenantError),
469    #[error("transaction field length {actual} exceeds maximum {maximum}")]
470    TooLarge { actual: usize, maximum: usize },
471    #[error("invalid Handshake address: {0}")]
472    InvalidAddress(&'static str),
473    #[error("transaction arithmetic overflow")]
474    ArithmeticOverflow,
475}
476
477fn blake2b_256(input: &[u8]) -> [u8; 32] {
478    blake2b_256_many(&[input])
479}
480
481fn blake2b_160(input: &[u8]) -> [u8; 20] {
482    let mut hasher = Blake2bVar::new(20).expect("valid BLAKE2b output length");
483    hasher.update(input);
484    let mut output = [0_u8; 20];
485    hasher
486        .finalize_variable(&mut output)
487        .expect("valid BLAKE2b output buffer");
488    output
489}
490
491fn remaining_decode_budget(
492    decoder: &Decoder<'_>,
493    start: usize,
494    maximum: usize,
495) -> Result<usize, TransactionError> {
496    let consumed = decoder.position().saturating_sub(start);
497    if consumed > maximum {
498        return Err(TransactionError::TooLarge {
499            actual: consumed,
500            maximum,
501        });
502    }
503    Ok(maximum - consumed)
504}
505
506fn read_transaction_varbytes(
507    decoder: &mut Decoder<'_>,
508    transaction_start: usize,
509    maximum_transaction_size: usize,
510    field: &'static str,
511) -> Result<Vec<u8>, TransactionError> {
512    let length = decoder.read_compact_usize(MAX_TRANSACTION_RAW_SIZE, field)?;
513    let remaining = remaining_decode_budget(decoder, transaction_start, maximum_transaction_size)?;
514    if length > remaining {
515        return Err(TransactionError::TooLarge {
516            actual: decoder
517                .position()
518                .saturating_sub(transaction_start)
519                .saturating_add(length),
520            maximum: maximum_transaction_size,
521        });
522    }
523    Ok(decoder.read_bounded_vec(length, remaining)?)
524}
525
526fn transaction_weight(base: usize, witness: usize) -> Result<usize, TransactionError> {
527    let weight = base
528        .checked_mul(4)
529        .and_then(|weight| weight.checked_add(witness))
530        .ok_or(TransactionError::ArithmeticOverflow)?;
531    if weight > MAX_TRANSACTION_WEIGHT {
532        return Err(TransactionError::TooLarge {
533            actual: weight,
534            maximum: MAX_TRANSACTION_WEIGHT,
535        });
536    }
537    Ok(weight)
538}
539
540fn compact_size_len(value: u64) -> usize {
541    match value {
542        0..=0xfc => 1,
543        0xfd..=0xffff => 3,
544        0x1_0000..=0xffff_ffff => 5,
545        _ => 9,
546    }
547}
548
549fn blake2b_256_many(parts: &[&[u8]]) -> [u8; 32] {
550    let mut hasher = Blake2bVar::new(32).expect("valid BLAKE2b output length");
551    for part in parts {
552        hasher.update(part);
553    }
554    let mut output = [0_u8; 32];
555    hasher
556        .finalize_variable(&mut output)
557        .expect("valid BLAKE2b output buffer");
558    output
559}
560
561#[cfg(test)]
562mod tests {
563    use hns_covenants::CovenantKind;
564
565    use super::*;
566
567    #[test]
568    fn codec_and_hashes_match_pinned_hsd_fixture() {
569        let raw = hex::decode(
570            "0100000001080808080808080808080808080808080808080808080808080808080808080802000000feffffff012a0000000000000000140909090909090909090909090909090909090909020103616263630000000203010203020405",
571        )
572        .expect("hex");
573        let transaction = Transaction::decode(&raw).expect("valid");
574        let mut doubled = raw.clone();
575        doubled.extend_from_slice(&raw);
576        let (prefix, consumed) = Transaction::decode_prefix(&doubled).expect("valid prefix");
577        assert_eq!(prefix, transaction);
578        assert_eq!(consumed, raw.len());
579        assert_eq!(transaction.encode().expect("valid"), raw);
580        assert_eq!(transaction.base_size().expect("valid"), 86);
581        assert_eq!(transaction.size().expect("valid"), 94);
582        assert_eq!(
583            transaction.transaction_hash().expect("valid").to_string(),
584            "420f91c753c7ad480b3359f47ccbcab9e058a59d15fcd5e10bec66e04a55f274"
585        );
586        assert_eq!(
587            hex::encode(transaction.witness_hash().expect("valid")),
588            "fba6fa32ac4b157d754c951d98d1e6e5e13c8d705a72621cd944e835597980a2"
589        );
590    }
591
592    #[test]
593    fn compressed_public_key_address_uses_hsd_blake2b_160() {
594        let public_key: [u8; 33] =
595            hex::decode("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
596                .expect("hex")
597                .try_into()
598                .expect("compressed public key");
599        let address = Address::from_compressed_public_key(&public_key).expect("address");
600        assert_eq!(address.version, 0);
601        assert_eq!(
602            hex::encode(address.hash),
603            "1fa94914d5d30512c4de09199b4018133f485e60"
604        );
605
606        let mut uncompressed_marker = public_key;
607        uncompressed_marker[0] = 0x04;
608        assert!(Address::from_compressed_public_key(&uncompressed_marker).is_err());
609    }
610
611    #[test]
612    fn null_data_and_revoke_outputs_are_unspendable() {
613        let spendable = Output {
614            value: Dollarydoos::new(1),
615            address: Address::new(0, vec![1; 20]).expect("address"),
616            covenant: Covenant::default(),
617        };
618        assert!(!spendable.is_unspendable());
619        let null_data = Output {
620            address: Address::new(31, vec![2; 2]).expect("address"),
621            ..spendable.clone()
622        };
623        assert!(null_data.is_unspendable());
624        let revoke = Output {
625            covenant: Covenant {
626                kind: CovenantKind::Revoke,
627                items: Vec::new(),
628            },
629            ..spendable
630        };
631        assert!(revoke.is_unspendable());
632    }
633
634    #[test]
635    fn noncanonical_counts_and_trailing_bytes_fail_closed() {
636        assert!(Transaction::decode(&[1, 0, 0, 0, 0xfd, 0, 0]).is_err());
637        let transaction = Transaction {
638            version: 1,
639            inputs: Vec::new(),
640            outputs: Vec::new(),
641            locktime: 0,
642        };
643        let mut encoded = transaction.encode().expect("valid");
644        encoded.push(0);
645        assert!(Transaction::decode(&encoded).is_err());
646    }
647
648    #[test]
649    fn prefix_decode_rejects_oversized_witness_before_allocation() {
650        let mut encoder = Encoder::new();
651        encoder.put_u32_le(1);
652        encoder.put_compact_size(1);
653        encoder.put_bytes(&[0; 32]);
654        encoder.put_u32_le(u32::MAX);
655        encoder.put_u32_le(0);
656        encoder.put_compact_size(0);
657        encoder.put_u32_le(0);
658        encoder.put_compact_size(1);
659        encoder.put_compact_size(MAX_TRANSACTION_RAW_SIZE as u64);
660        assert!(matches!(
661            Transaction::decode_prefix(&encoder.into_bytes()),
662            Err(TransactionError::TooLarge {
663                actual: 4_000_056,
664                maximum: 3_999_850
665            })
666        ));
667    }
668
669    #[test]
670    fn witness_serialization_obeys_weight_not_base_size_limit() {
671        let mut transaction = Transaction {
672            version: 1,
673            inputs: vec![Input {
674                previous_output: Outpoint::NULL,
675                sequence: 0,
676                witness: Witness {
677                    items: vec![vec![7; 1_100_000]],
678                },
679            }],
680            outputs: Vec::new(),
681            locktime: 0,
682        };
683        let encoded = transaction.encode().expect("under HSD weight limit");
684        assert!(encoded.len() > MAX_TRANSACTION_SIZE);
685        assert_eq!(Transaction::decode(&encoded).expect("valid"), transaction);
686
687        transaction.inputs[0].witness.items[0] = vec![0; MAX_TRANSACTION_WEIGHT];
688        assert!(matches!(
689            transaction.encode(),
690            Err(TransactionError::TooLarge {
691                maximum: MAX_TRANSACTION_WEIGHT,
692                ..
693            })
694        ));
695    }
696}