Skip to content

Commit 3d047c0

Browse files
committed
Merge #356: fix: get_padding for larger costs and padding lengths
7871509 fix: get_padding for larger costs and padding lengths (Byron Hambly) Pull request description: `Cost::get_padding` previously always assumed only 1 byte for compactsize encoding when calculating required padding size. For larger differences in cost/budget, this incorrectly resulted in an additional 1 or 2 bytes of padding depending on the difference. I found this when calculating padding for the SimplicityHL hash loop example, where rust-simplicity was calculating a 7426 byte annex padding while libsimplicity required a 7424 byte padding, since the compactsize encoding requires 2 additional bytes. See ElementsProject/elements#1539 ACKs for top commit: ivanlele: ACK. Ran tests at 7871509 apoelstra: ACK 7871509; successfully ran local tests Tree-SHA512: 518d7ba03519c751721f79bae2517e239858c21cac38e6e2b847285653e02bf6d7557dc3e4e68c8b386477314e19c590f7ae50de606c5a8905a630002a00db2e
2 parents ceadecf + 7871509 commit 3d047c0

File tree

1 file changed

+50
-6
lines changed

1 file changed

+50
-6
lines changed

src/analysis.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,33 @@ impl Cost {
152152
return None;
153153
}
154154

155-
// Two bytes are automatically added to the encoded witness stack by adding the annex:
155+
// Adding the annex to the witness stack increases the serialized size by:
156156
//
157-
// 1. The encoded annex starts with the annex byte length
158-
// 2. The first annex byte is always 0x50
157+
// 1. CompactSize(annex_len): the length prefix of the annex item
158+
// 2. annex_len: the annex bytes themselves (0x50 tag + zero padding)
159159
//
160-
// The remaining padding is done by adding (zero) bytes to the annex.
161-
let required_padding = weight - budget - U32Weight(2);
162-
let padding_len = required_padding.0 as usize; // cast safety: 32-bit machine or higher
160+
// CompactSize uses 1 byte for values <= 252, 3 bytes for <= 65535,
161+
// and 5 bytes for larger values. The overhead subtracted must account
162+
// for the actual CompactSize encoding length of the resulting annex.
163+
let deficit = (weight - budget).0 as usize; // cast safety: 32-bit machine or higher
164+
165+
// overhead = compact_size_len + 1 (for 0x50 tag)
166+
let padding_len = match deficit {
167+
// annex_len <= 252, compact_size uses 1 byte, overhead = 2
168+
0..=253 => deficit.saturating_sub(2),
169+
// Boundary region: annex must be >= 253 bytes (3-byte compact_size),
170+
// but deficit - 4 < 252. Use minimum padding for 3-byte encoding.
171+
254..=255 => 252,
172+
// annex_len in 253..=65535, compact_size uses 3 bytes, overhead = 4
173+
256..=65538 => deficit - 4,
174+
// Boundary region for 5-byte compact_size encoding.
175+
65539..=65540 => 65535,
176+
// annex_len >= 65536, compact_size uses 5 bytes, overhead = 6
177+
_ => deficit - 6,
178+
// Note: the 9-byte compact_size boundary (deficit > 4_294_967_300)
179+
// is unreachable because Cost uses u32 milliweight, limiting the
180+
// maximum deficit to ~4_294_968 weight units.
181+
};
163182
let annex_bytes: Vec<u8> = std::iter::once(0x50)
164183
.chain(std::iter::repeat(0x00).take(padding_len))
165184
.collect();
@@ -435,6 +454,31 @@ mod tests {
435454
(Cost::from_milliweight(empty + 4_000), vec![], Some(3)),
436455
(Cost::from_milliweight(empty + 4_001), vec![], Some(4)),
437456
(Cost::from_milliweight(empty + 50_000), vec![], Some(49)),
457+
// Test around CompactSize boundary (annex_len crossing 252 -> 253)
458+
// deficit = 253: annex_len = 252, compact_size = 1 byte, overhead = 2
459+
(Cost::from_milliweight(empty + 253_000), vec![], Some(252)),
460+
// deficit = 254: annex_len must be 253 (3-byte compact_size), overhead = 4
461+
(Cost::from_milliweight(empty + 254_000), vec![], Some(253)),
462+
// deficit = 255: same boundary case
463+
(Cost::from_milliweight(empty + 255_000), vec![], Some(253)),
464+
// deficit = 256: annex_len = 253, compact_size = 3, exact fit
465+
(Cost::from_milliweight(empty + 256_000), vec![], Some(253)),
466+
// deficit = 257: annex_len = 254
467+
(Cost::from_milliweight(empty + 257_000), vec![], Some(254)),
468+
// Large annex (exercises the 3-byte compact_size path)
469+
(
470+
Cost::from_milliweight(empty + 7_424_000),
471+
vec![],
472+
Some(7_421),
473+
),
474+
// Hash loop example
475+
(
476+
Cost::from_milliweight(8_045_103),
477+
vec![vec![], vec![0; 497], vec![0; 32], vec![0; 33]],
478+
Some(7_424),
479+
),
480+
// Max
481+
(Cost::CONSENSUS_MAX, vec![], Some(3_999_994)),
438482
];
439483

440484
for (cost, mut witness, maybe_padding) in test_vectors {

0 commit comments

Comments
 (0)