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 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}