From 51dd1c5ecaf1c87df699268d2626fd14f02be9e1 Mon Sep 17 00:00:00 2001 From: PiRK Date: Mon, 21 Dec 2020 12:11:53 +0100 Subject: [PATCH 1/2] implement conversion to arbitrary prefixes This commit adds an `prefix` argument to the `Address.cash_address` method. If not specified, it defaults to the current prefix if the object was instantiated from string via a CashAddr, or to the "bitcoincash:" prefix if it was instantiated from string with a legacy address. Test plan: python3 -m tests.test --- cashaddress/convert.py | 7 ++++--- tests/test.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/cashaddress/convert.py b/cashaddress/convert.py index 12bb758..2c14ce3 100644 --- a/cashaddress/convert.py +++ b/cashaddress/convert.py @@ -43,12 +43,13 @@ def legacy_address(self): version_int = Address._address_type('legacy', self.version)[1] return b58encode_check(Address.code_list_to_string([version_int] + self.payload)) - def cash_address(self): + def cash_address(self, prefix=None): + prefix = prefix if prefix is not None else self.prefix version_int = Address._address_type('cash', self.version)[1] payload = [version_int] + self.payload payload = convertbits(payload, 8, 5) - checksum = calculate_checksum(self.prefix, payload) - return self.prefix + ':' + b32encode(payload + checksum) + checksum = calculate_checksum(prefix, payload) + return prefix + ':' + b32encode(payload + checksum) @staticmethod def code_list_to_string(code_list): diff --git a/tests/test.py b/tests/test.py index a95efff..3b504e2 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,6 +1,7 @@ import unittest from cashaddress import convert +from cashaddress.convert import Address class TestConversion(unittest.TestCase): @@ -61,5 +62,26 @@ def test_is_valid(self): self.assertFalse(convert.is_valid(False)) self.assertFalse(convert.is_valid('Hello World!')) + def test_prefixes(self): + """Test a few identical addresses with different CashAddr prefixes""" + legacy_address = "1NLcNpAaBBMekgBZk7NxwdxwtSUTfTV8Aq" + addr = Address.from_string(legacy_address) + default_prefix = addr.prefix + self.assertEqual(default_prefix, Address.MAINNET_PREFIX) + self.assertEqual(addr.cash_address(), + 'bitcoincash:qr4pqy6q4cy2d50zpaek57nnrja7289fkskz6jm7yf') + self.assertEqual(addr.cash_address(prefix='abc'), + 'abc:qr4pqy6q4cy2d50zpaek57nnrja7289fksqt4c50w9') + self.assertEqual(addr.cash_address(prefix='simpleledger'), + 'simpleledger:qr4pqy6q4cy2d50zpaek57nnrja7289fks6e3fw76h') + + regtest_address = 'regtest:qr4pqy6q4cy2d50zpaek57nnrja7289fksjm6es9se' + addr2 = Address.from_string(regtest_address) + self.assertEqual(addr2.legacy_address(), legacy_address) + # The prefix defaults to the one in the input string. + self.assertEqual(addr2.prefix, 'regtest') + self.assertEqual(addr2.cash_address(), regtest_address) + + if __name__ == '__main__': unittest.main() From bc8d6a95dbb6df47371117805dedd54d1d7bdf91 Mon Sep 17 00:00:00 2001 From: PiRK Date: Mon, 21 Dec 2020 12:53:40 +0100 Subject: [PATCH 2/2] support uppercase prefixes If the users specifies an uppercase prefix, the output should be entirely uppercase. Mixed-case prefixes are invalid. Note that the case is not recorded in the instance, when creating an Address from string. If the prefix is not explicitely specified and uppercase, the output from `Address.cash_address()` will always be lowercase. Test plan: python3 -m tests.test --- cashaddress/convert.py | 15 ++++++++++++--- tests/test.py | 18 +++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/cashaddress/convert.py b/cashaddress/convert.py index 2c14ce3..72879a4 100644 --- a/cashaddress/convert.py +++ b/cashaddress/convert.py @@ -45,11 +45,16 @@ def legacy_address(self): def cash_address(self, prefix=None): prefix = prefix if prefix is not None else self.prefix + self._check_case(prefix) + is_uppercase = prefix == prefix.upper() version_int = Address._address_type('cash', self.version)[1] payload = [version_int] + self.payload payload = convertbits(payload, 8, 5) checksum = calculate_checksum(prefix, payload) - return prefix + ':' + b32encode(payload + checksum) + address_string = prefix + ':' + b32encode(payload + checksum) + if is_uppercase: + return address_string.upper() + return address_string @staticmethod def code_list_to_string(code_list): @@ -95,8 +100,7 @@ def _legacy_string(address_string): @staticmethod def _cash_string(address_string): - if address_string.upper() != address_string and address_string.lower() != address_string: - raise InvalidAddress('Cash address contains uppercase and lowercase characters') + Address._check_case(address_string) address_string = address_string.lower() colon_count = address_string.count(':') if colon_count == 0: @@ -114,6 +118,11 @@ def _cash_string(address_string): payload = converted[1:-6] return Address(version, payload, prefix) + @staticmethod + def _check_case(text): + if text.upper() != text and text.lower() != text: + raise InvalidAddress('Cash address contains uppercase and lowercase characters') + def to_cash_address(address): return Address.from_string(address).cash_address() diff --git a/tests/test.py b/tests/test.py index 3b504e2..8dd05e4 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,7 +1,7 @@ import unittest from cashaddress import convert -from cashaddress.convert import Address +from cashaddress.convert import Address, InvalidAddress class TestConversion(unittest.TestCase): @@ -82,6 +82,22 @@ def test_prefixes(self): self.assertEqual(addr2.prefix, 'regtest') self.assertEqual(addr2.cash_address(), regtest_address) + def test_prefix_case(self): + with self.assertRaises(InvalidAddress): + Address.from_string( + 'rEgTeSt:qr4pqy6q4cy2d50zpaek57nnrja7289fksjm6es9se') + with self.assertRaises(InvalidAddress): + Address.from_string( + 'regtest:QR4PQY6Q4CY2D50ZPAEK57NNRJA7289FKSJM6ES9SE') + + addr = Address.from_string('regtest:qr4pqy6q4cy2d50zpaek57nnrja7289fksjm6es9se') + # The address should take the same case as the specified prefix + self.assertEqual(addr.cash_address(prefix="SLP"), + 'SLP:QR4PQY6Q4CY2D50ZPAEK57NNRJA7289FKSWF89PY2G') + # Do not allow mixed-case prefixes + with self.assertRaises(InvalidAddress): + addr.cash_address(prefix="sLp") + if __name__ == '__main__': unittest.main()