diff --git a/CHANGES.md b/CHANGES.md index 75898d1c..e0a3d97a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,24 @@ Note to self: Breaking changes must increment either --> +## 0.30.0 (2024-07-04) + +_**Breaking**_ + +> No breaking changes were introduced in this version. + +_**Features**_ + +- feat: add validator for trx addresses by @msamsami in [#384](https://github.com/python-validators/validators/pull/384) + +_**Maintenance**_ + +- maint: bump version by @msamsami in [#384](https://github.com/python-validators/validators/pull/384) + +**Full Changelog**: [`0.29.0...0.30.0`](https://github.com/python-validators/validators/compare/0.29.0...0.30.0) + +--- + ## 0.29.0 (2024-07-01) _**Breaking**_ ⚠️ @@ -25,6 +43,8 @@ _**Maintenance**_ **Full Changelog**: [`0.28.3...0.29.0`](https://github.com/python-validators/validators/compare/0.28.3...0.29.0) +--- + ## 0.28.3 (2024-05-25) _**Breaking**_ diff --git a/SECURITY.md b/SECURITY.md index 2a65546a..2231e167 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ | Version | Supported | | ---------- | ------------------ | -| `>=0.29.0` | :white_check_mark: | +| `>=0.30.0` | :white_check_mark: | ## Reporting a Vulnerability diff --git a/docs/api/crypto_addresses.md b/docs/api/crypto_addresses.md index 628e061b..226ef0c5 100644 --- a/docs/api/crypto_addresses.md +++ b/docs/api/crypto_addresses.md @@ -2,3 +2,4 @@ ::: validators.crypto_addresses.btc_address ::: validators.crypto_addresses.eth_address +::: validators.crypto_addresses.trx_address diff --git a/docs/api/crypto_addresses.rst b/docs/api/crypto_addresses.rst index 60e733b8..09ebfe41 100644 --- a/docs/api/crypto_addresses.rst +++ b/docs/api/crypto_addresses.rst @@ -4,3 +4,4 @@ crypto_addresses .. module:: validators.crypto_addresses .. autofunction:: btc_address .. autofunction:: eth_address +.. autofunction:: trx_address diff --git a/src/validators/__init__.py b/src/validators/__init__.py index a554051e..a58a574c 100644 --- a/src/validators/__init__.py +++ b/src/validators/__init__.py @@ -5,7 +5,7 @@ from .card import amex, card_number, diners, discover, jcb, mastercard, unionpay, visa from .country import calling_code, country_code, currency from .cron import cron -from .crypto_addresses import btc_address, eth_address +from .crypto_addresses import btc_address, eth_address, trx_address from .domain import domain from .email import email from .encoding import base58, base64 @@ -39,6 +39,7 @@ # crypto_addresses "btc_address", "eth_address", + "trx_address", # cards "amex", "card_number", @@ -104,4 +105,4 @@ "validator", ) -__version__ = "0.29.0" +__version__ = "0.30.0" diff --git a/src/validators/crypto_addresses/__init__.py b/src/validators/crypto_addresses/__init__.py index 87c5e5c8..d6bd2d61 100644 --- a/src/validators/crypto_addresses/__init__.py +++ b/src/validators/crypto_addresses/__init__.py @@ -3,5 +3,6 @@ # local from .btc_address import btc_address from .eth_address import eth_address +from .trx_address import trx_address -__all__ = ("btc_address", "eth_address") +__all__ = ("btc_address", "eth_address", "trx_address") diff --git a/src/validators/crypto_addresses/trx_address.py b/src/validators/crypto_addresses/trx_address.py new file mode 100644 index 00000000..3b021fbc --- /dev/null +++ b/src/validators/crypto_addresses/trx_address.py @@ -0,0 +1,62 @@ +"""TRX Address.""" + +# standard +import hashlib +import re + +# local +from validators.utils import validator + + +def _base58_decode(addr: str) -> bytes: + """Decode a base58 encoded address.""" + alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + num = 0 + for char in addr: + num = num * 58 + alphabet.index(char) + return num.to_bytes(25, byteorder="big") + + +def _validate_trx_checksum_address(addr: str) -> bool: + """Validate TRX type checksum address.""" + if len(addr) != 34: + return False + + try: + address = _base58_decode(addr) + except ValueError: + return False + + if len(address) != 25 or address[0] != 0x41: + return False + + check_sum = hashlib.sha256(hashlib.sha256(address[:-4]).digest()).digest()[:4] + return address[-4:] == check_sum + + +@validator +def trx_address(value: str, /): + """Return whether or not given value is a valid tron address. + + Full validation is implemented for TRC20 tron addresses. + + Examples: + >>> trx_address('TLjfbTbpZYDQ4EoA4N5CLNgGjfbF8ZWz38') + # Output: True + >>> trx_address('TR2G7Rm4vFqF8EpY4U5xdLdQ7XgJ2U8Vd') + # Output: ValidationError(func=trx_address, args=...) + + Args: + value: + Tron address string to validate. + + Returns: + (Literal[True]): If `value` is a valid tron address. + (ValidationError): If `value` is an invalid tron address. + """ + if not value: + return False + + return re.compile(r"^[T][a-km-zA-HJ-NP-Z1-9]{33}$").match( + value + ) and _validate_trx_checksum_address(value) diff --git a/tests/crypto_addresses/test_trx_address.py b/tests/crypto_addresses/test_trx_address.py new file mode 100644 index 00000000..68bb0d94 --- /dev/null +++ b/tests/crypto_addresses/test_trx_address.py @@ -0,0 +1,54 @@ +"""Test TRX address.""" + +# external +import pytest + +# local +from validators import ValidationError, trx_address + + +@pytest.mark.parametrize( + "value", + [ + "TLjfbTbpZYDQ4EoA4N5CLNgGjfbF8ZWz38", + "TDQ6C92wuNqvMWE967sMptCFaXq77uj1PF", + "TFuGbxCQGSL4oLnJzVsen844LDwFbrUY4e", + "TFAPKADDRhkSe3v27CsR8TZSjN8eJ8ycDK", + "TSJHywLNva2MNjCD5iYfn5QAKD9Rk5Ncit", + "TEi1qhi5LuTicg1u9oAstyXCSf5uibSyqo", + "TAGvx5An6VBeHTu91cQwdABNcAYMRPcP4n", + "TXbE5tXTejqT3Q47sYKCDb9NJDm3xrFpab", + "TMTxQWNuWHXvHcYXc5D1wQhFmZFJijAxcG", + "TPHgw9E8QYM3esNWih5KVnUVpUHwLTPfpA", + "TFFLtBTi9jdaGwV3hznjCmPYaJme5AeqwU", + "TC74QG8tbtixG5Raa4fEifywgjrFs45fNz", + ], +) +def test_returns_true_on_valid_trx_address(value: str): + """Test returns true on valid trx address.""" + assert trx_address(value) + + +@pytest.mark.parametrize( + "value", + [ + "T12345678901234567890123456789012345", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678", + "TR2G7Rm4vFqF8EpY4U5xdLdQ7XgJ2U8Vd", + "TP6ah2v5mdsj8Z3hGz1yDMvDq7BzEbK8o", + "TQmmhp6uz2Xre8yL3FsPYZyo4mhtw4vg4XX", + "TQNy2C6VHJPk4P32bsEX3QSGx2Qqm4J2k9", + "TP6ah2v5mdsj8Z3hGz1yDMvDq7BzEbK8oN", + "TSTVdfU1x4L7K3Bc3v5C28Gp2J1rPyeL3f", + "THPByuCzvU5QER9j2NC2mUQ2JPyRCam4e7", + "TW5eZqUZgdW4rxFKAKsc2ryJbfFA94WXvD", + "TR2G7Rm4vFqF8EpY4U5xdLdQ7XgJ2U8Vdd", + "tQmmhp6uz2Xre8yL3FsPYZyo4mhtw4vg4X", + "TR2G7Rm4vFqF8EpY4U5xdLdQ7Xg", + "TQmmhp6uz2Xre8yL3FsPYZyo4mhtw4vg4x", + "my-trox-address.trx", + ], +) +def test_returns_failed_validation_on_invalid_trx_address(value: str): + """Test returns failed validation on invalid trx address.""" + assert isinstance(trx_address(value), ValidationError)