Merge pull request #10911 from SomberNight/202608_transaction_var_int
What changed, and why it matters
This commit is a small code cleanup in Electrum's Bitcoin transaction handling. It merges two duplicate functions for writing variable-length integer sizes and adds explicit error checks for invalid values (negative numbers or numbers too large). The change makes the code more consistent and slightly safer, but it does not appear to fix any active security vulnerability or change behavior in normal use.
No urgent action required. This is a defensive code-quality improvement. Reviewers may want to verify that all callers of `var_int` and `write_compact_size` handle `OverflowError`/`SerializationError` appropriately, and that no other duplicate serialization helpers remain out of sync.
Security signals we found
Replaced assert with explicit exception for negative var_int input
Added explicit upper-bound overflow check for var_int
Deduplicated serialization helper to reduce risk of inconsistent validation
Added unit tests for boundary/overflow cases
Evidence from the diff
The patch deduplicates write_compact_size in electrum/transaction.py by reusing var_int from electrum/bitcoin.py. It also replaces a bare assert in var_int with explicit OverflowError exceptions for negative inputs and adds an explicit upper-bound check (i <= 0xffff_ffff_ffff_ffff) with an OverflowError for inputs exceeding 64-bit unsigned max. Tests are added for both boundary cases. The previous write_compact_size already rejected negative sizes and sizes >= 2**64, so functional behavior is largely unchanged; the main differences are consistent error types and removal of an assert that would be stripped in optimized Python bytecode.
Changed components
electrum/bitcoin.py:var_intelectrum/transaction.py:write_compact_sizetests/test_bitcoin.pytests/test_transaction.pyInspect captured patch +17 / −18
### electrum/bitcoin.py
@@ -226,15 +226,17 @@ def var_int(i: int) -> bytes:
# https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
# https://github.com/bitcoin/bitcoin/blob/efe1ee0d8d7f82150789f1f6840f139289628a2b/src/serialize.h#L247
# "CompactSize"
- assert i >= 0, i
+ if i < 0:
+ raise OverflowError(f"int {i} must be non-negative for var_int")
if i < 0xfd:
return int.to_bytes(i, length=1, byteorder="little", signed=False)
elif i <= 0xffff:
return b"\xfd" + int.to_bytes(i, length=2, byteorder="little", signed=False)
elif i <= 0xffffffff:
return b"\xfe" + int.to_bytes(i, length=4, byteorder="little", signed=False)
- else:
+ elif i <= 0xffff_ffff_ffff_ffff:
return b"\xff" + int.to_bytes(i, length=8, byteorder="little", signed=False)
+ raise OverflowError(f"int {i} too large for var_int")
def witness_push(item: bytes) -> bytes:
### electrum/transaction.py
@@ -648,22 +648,12 @@ def read_compact_size(self):
except IndexError as e:
raise SerializationError("attempt to read past end of buffer") from e
- def write_compact_size(self, size):
- if size < 0:
- raise SerializationError("attempt to write size < 0")
- elif size < 253:
- self.write(bytes([size]))
- elif size < 2**16:
- self.write(b'\xfd')
- self._write_num('<H', size)
- elif size < 2**32:
- self.write(b'\xfe')
- self._write_num('<I', size)
- elif size < 2**64:
- self.write(b'\xff')
- self._write_num('<Q', size)
- else:
- raise Exception(f"size {size} too large for compact_size")
+ def write_compact_size(self, size: int) -> None:
+ try:
+ compact_size = var_int(size)
+ except OverflowError as e:
+ raise SerializationError(f"size {size} outside valid range for compact_size") from e
+ self.write(compact_size)
def _read_num(self, format):
try:
### tests/test_bitcoin.py
@@ -411,6 +411,11 @@ def test_var_int(self):
self.assertEqual(var_int(0x100000000), bfh("ff0000000001000000"))
self.assertEqual(var_int(0x0123456789abcdef), bfh("ffefcdab8967452301"))
+ with self.assertRaises(OverflowError):
+ var_int(-1)
+ with self.assertRaises(OverflowError):
+ var_int(2**64)
+
def test_op_push(self):
self.assertEqual(_op_push(0x00), bfh('00'))
self.assertEqual(_op_push(0x12), bfh('12'))
### tests/test_transaction.py
@@ -34,6 +34,8 @@ def test_compact_size(self):
with self.assertRaises(transaction.SerializationError):
s.write_compact_size(-1)
+ with self.assertRaises(transaction.SerializationError):
+ s.write_compact_size(2**64)
self.assertEqual(s.input.hex(),
'0001fcfdfd00fdfffffe00000100feffffffffff0000000001000000ffffffffffffffffff')Why this scored 22/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.