diff --git a/src/lean_spec/spec/ssz/collections.py b/src/lean_spec/spec/ssz/collections.py index 24bcb545c..f7785545b 100644 --- a/src/lean_spec/spec/ssz/collections.py +++ b/src/lean_spec/spec/ssz/collections.py @@ -599,7 +599,13 @@ def deserialize(cls, stream: IO[bytes], scope: int) -> Self: f"{cls.__name__}: scope {scope} too small for variable-size list" ) first_offset = int(Uint32.deserialize(stream, BYTES_PER_LENGTH_OFFSET)) - if first_offset > scope or first_offset % BYTES_PER_LENGTH_OFFSET != 0: + # A non-empty variable-size list carries at least one offset word before any body. + # A zero first offset is contradictory: it means zero elements yet one full-scope element. + if ( + first_offset < BYTES_PER_LENGTH_OFFSET + or first_offset > scope + or first_offset % BYTES_PER_LENGTH_OFFSET != 0 + ): raise SSZSerializationError(f"{cls.__name__}: invalid offset {first_offset}") num_elements = first_offset // BYTES_PER_LENGTH_OFFSET if num_elements > cls.LIMIT: diff --git a/tests/spec/ssz/test_collections.py b/tests/spec/ssz/test_collections.py index dccbfc3af..9bfd0f195 100644 --- a/tests/spec/ssz/test_collections.py +++ b/tests/spec/ssz/test_collections.py @@ -850,6 +850,12 @@ def test_variable_size_list_rejects_misaligned_first_offset(self) -> None: VariableContainerList2.decode_bytes(b"\x05\x00\x00\x00\x00\x00\x00\x00") assert str(exception_info.value) == "VariableContainerList2: invalid offset 5" + def test_variable_size_list_rejects_zero_first_offset(self) -> None: + """A zero first offset is contradictory and rejected before building the boundary list.""" + with pytest.raises(SSZSerializationError) as exception_info: + VariableContainerList2.decode_bytes(bytes.fromhex("00000000aabbccdd")) + assert str(exception_info.value) == "VariableContainerList2: invalid offset 0" + def test_variable_size_list_rejects_count_beyond_limit(self) -> None: """A first offset that implies more than LIMIT elements is rejected.""" # Layout: