Skip to content

Commit 184a933

Browse files
committed
fix: DER encoding for tag and length in TLV
1 parent 194db7f commit 184a933

2 files changed

Lines changed: 65 additions & 19 deletions

File tree

src/erc7730/common/binary.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,43 +13,52 @@ def from_hex(value: str) -> bytes:
1313
return bytes.fromhex(value.removeprefix("0x"))
1414

1515

16-
def tlv(tag: int | IntEnum, *value: bytes | str | None) -> bytes:
16+
def tlv(tag: int | IntEnum, value: bytes | str | None = None) -> bytes:
1717
"""
1818
Encode a value in TLV format (Tag-Length-Value)
1919
20+
Tag and length are DER encoded. If tag value or length exceed 255 bytes, an OverflowError is raised.
21+
2022
If value is not encoded, it will be encoded as ASCII.
2123
If input string is not ASCII, and UnicodeEncodeError is raised.
2224
23-
If encoded value is longer than 255 bytes, an OverflowError is raised.
24-
2525
@param tag: the tag (can be an enum)
26-
@param value: the value (can be already encoded, or a string)
26+
@param value: the value (can be already encoded, a string or None)
2727
@return: encoded TLV
2828
"""
29-
values_encoded = bytearray()
30-
for v in value:
31-
if v is not None:
32-
values_encoded.extend(v.encode("ascii", errors="strict") if isinstance(v, str) else v)
33-
return (
34-
(tag.value if isinstance(tag, IntEnum) else tag).to_bytes(1, "big")
35-
+ len(values_encoded).to_bytes(1, "big")
36-
+ values_encoded
37-
)
3829

30+
return der_encode_int(tag.value if isinstance(tag, IntEnum) else tag) + length_value(value)
3931

40-
def length_value(value: bytes | str | None) -> bytes:
32+
33+
def length_value(
34+
value: bytes | str | None,
35+
) -> bytes:
4136
"""
42-
Prepend the length of the value encoded on 1 byte to the value itself.
37+
Prepend the length (DER encoded) of the value encoded to the value itself.
38+
If length exceeds 255 bytes, an OverflowError is raised.
4339
4440
If value is not encoded, it will be encoded as ASCII.
4541
If input string is not ASCII, and UnicodeEncodeError is raised.
4642
47-
If encoded value is longer than 255 bytes, an OverflowError is raised.
48-
4943
@param value: the value (can be already encoded, or a string)
5044
@return: encoded TLV
5145
"""
5246
if value is None:
5347
return (0).to_bytes(1, "big")
54-
value_encoded = value.encode("ascii", errors="strict") if isinstance(value, str) else value
55-
return len(value_encoded).to_bytes(1, "big") + value_encoded
48+
match value:
49+
case bytes():
50+
value_encoded = value
51+
case str():
52+
value_encoded = value.encode("ascii", errors="strict")
53+
return der_encode_int(len(value_encoded)) + value_encoded
54+
55+
56+
def der_encode_int(value: int) -> bytes:
57+
"""
58+
Encode an integer in DER format.
59+
60+
@param value: the integer to encode
61+
@return: DER encoded byte array
62+
"""
63+
value_bytes = value.to_bytes(1, "big") # raises OverflowError if value >= 256
64+
return (0x81).to_bytes(1, "big") + value_bytes if value >= 0x80 else value_bytes

tests/common/test_binary.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from enum import IntEnum
2+
3+
import pytest
4+
5+
from erc7730.common.binary import tlv
6+
7+
8+
class _Tag(IntEnum):
9+
FIELD = 5
10+
11+
12+
@pytest.mark.parametrize(
13+
"tag, value, expected",
14+
[
15+
(1, None, b"\x01\x00"),
16+
(1, b"\xab", b"\x01\x01\xab"),
17+
(1, "hi", b"\x01\x02hi"),
18+
(_Tag.FIELD, b"\xff", b"\x05\x01\xff"),
19+
(0x80, None, b"\x81\x80\x00"),
20+
(1, b"\x00" * 128, b"\x01\x81\x80" + b"\x00" * 128),
21+
],
22+
)
23+
def test_tlv(tag: int | IntEnum, value: bytes | str | None, expected: bytes) -> None:
24+
assert tlv(tag, value) == expected
25+
26+
27+
@pytest.mark.parametrize(
28+
"tag, value, exc",
29+
[
30+
(256, None, OverflowError),
31+
(1, b"\x00" * 256, OverflowError),
32+
(1, "là-haut", UnicodeEncodeError),
33+
],
34+
)
35+
def test_tlv_errors(tag: int, value: bytes | str | None, exc: type[Exception]) -> None:
36+
with pytest.raises(exc):
37+
tlv(tag, value)

0 commit comments

Comments
 (0)