diff --git a/silkaj/crypto_tools.py b/silkaj/crypto_tools.py index 4ebd8bd1969a979e46c679f85e93ea45172ce90c..0ec8126a8a3472def76de0e6f6b0545edca6ec7c 100644 --- a/silkaj/crypto_tools.py +++ b/silkaj/crypto_tools.py @@ -19,32 +19,57 @@ import re from nacl import encoding, hash from silkaj.constants import PUBKEY_PATTERN +from silkaj.tools import message_exit PUBKEY_DELIMITED_PATTERN = "^{0}$".format(PUBKEY_PATTERN) CHECKSUM_PATTERN = "[1-9A-HJ-NP-Za-km-z]{3}" PUBKEY_CHECKSUM_PATTERN = "^{0}:{1}$".format(PUBKEY_PATTERN, CHECKSUM_PATTERN) -def check_public_key(pubkey, display_error): +def is_pubkey_and_check(pubkey): """ - Check public key format - Check pubkey checksum which could be append after the pubkey - If check pass: return pubkey + Checks if the given argument contains a pubkey. + If so, verifies the checksum if needed and returns the pubkey. + Exits if the checksum is wrong. + Else, return False """ - if re.search(re.compile(PUBKEY_DELIMITED_PATTERN), pubkey): + if re.search(re.compile(PUBKEY_PATTERN), pubkey): + if check_pubkey_format(pubkey, True): + return validate_checksum(pubkey) return pubkey - elif re.search(re.compile(PUBKEY_CHECKSUM_PATTERN), pubkey): - pubkey, checksum = pubkey.split(":") - checksum_calculed = gen_checksum(pubkey) - if checksum_calculed == checksum: - return pubkey - else: - print("Error: Wrong checksum for following public key:") - return False + return False + +def check_pubkey_format(pubkey, display_error=True): + """ + Checks if a pubkey has a checksum. + Exits if the pubkey is invalid. + """ + if re.search(re.compile(PUBKEY_DELIMITED_PATTERN), pubkey): + return False + elif re.search(re.compile(PUBKEY_CHECKSUM_PATTERN), pubkey): + return True elif display_error: - print("Error: bad format for following public key:", pubkey) - return False + message_exit("Error: bad format for following public key: " + pubkey) + return + + +def validate_checksum(pubkey_checksum): + """ + Check pubkey checksum after the pubkey, delimited by ":". + If check pass: return pubkey + Else: exit. + """ + pubkey, checksum = pubkey_checksum.split(":") + if checksum == gen_checksum(pubkey): + return pubkey + message_exit( + "Error: public key '" + + pubkey + + "' does not match checksum '" + + checksum + + "'.\nPlease verify the public key." + ) def gen_checksum(pubkey): diff --git a/silkaj/money.py b/silkaj/money.py index 945d6c29f3c99d9bd7c181c735fc52b14a9c25b5..61bab2be5502013a5816da3af065b87194f1b8fd 100644 --- a/silkaj/money.py +++ b/silkaj/money.py @@ -22,7 +22,10 @@ from silkaj.network_tools import ClientInstance from silkaj.blockchain_tools import HeadBlock from silkaj.tools import CurrencySymbol, message_exit, coroutine from silkaj.auth import auth_method -from silkaj.wot import check_public_key + +# had to import wot to prevent loop dependency. No use here. +from silkaj import wot +from silkaj.crypto_tools import check_pubkey_format, validate_checksum from silkaj.tui import display_amount from duniterpy.api.bma import tx, blockchain @@ -43,17 +46,19 @@ async def cmd_amount(ctx, pubkeys): ): if not pubkeys: message_exit("You should specify one or many pubkeys") + pubkey_list = list() for pubkey in pubkeys: - pubkey = check_public_key(pubkey, True) - if not pubkey: - return + if check_pubkey_format(pubkey): + pubkey_list.append(validate_checksum(pubkey)) + else: + pubkey_list.append(pubkey) total = [0, 0] - for pubkey in pubkeys: + for pubkey in pubkey_list: inputs_balance = await get_amount_from_pubkey(pubkey) await show_amount_from_pubkey(pubkey, inputs_balance) total[0] += inputs_balance[0] total[1] += inputs_balance[1] - if len(pubkeys) > 1: + if len(pubkey_list) > 1: await show_amount_from_pubkey("Total", total) else: key = auth_method() diff --git a/silkaj/tx.py b/silkaj/tx.py index 080245f7e02e325d3fcfc73525cdcc7f6a19181c..b482cd278c09a2d6ab8c50eae6e0f34057a0eafa 100644 --- a/silkaj/tx.py +++ b/silkaj/tx.py @@ -24,7 +24,7 @@ from click import command, option, FloatRange from silkaj.cli_tools import MutuallyExclusiveOption from silkaj.network_tools import ClientInstance from silkaj.blockchain_tools import HeadBlock -from silkaj.crypto_tools import check_public_key +from silkaj.crypto_tools import validate_checksum, check_pubkey_format from silkaj.tools import message_exit, CurrencySymbol, coroutine from silkaj.auth import auth_method from silkaj import money @@ -204,14 +204,11 @@ def check_transaction_values( """ checkComment(comment) for i, outputAddress in enumerate(outputAddresses): - outputAddresses[i] = check_public_key(outputAddress, True) - if not outputAddresses[i]: - message_exit(outputAddress) + if check_pubkey_format(outputAddress): + outputAddresses[i] = validate_checksum(outputAddress) if outputBackChange: - pubkey = outputBackChange - outputBackChange = check_public_key(outputBackChange, True) - if not outputBackChange: - message_exit(pubkey) + if check_pubkey_format(outputBackChange): + outputBackChange = validate_checksum(outputBackChange) if enough_source: message_exit( issuer_pubkey + " pubkey doesn’t have enough money for this transaction." diff --git a/silkaj/tx_history.py b/silkaj/tx_history.py index c265e950b12dcc17ed8aa1deba6df3c0428d2cfa..1d7849508cd2dd50d90f22f3a379ea6efb71b74e 100644 --- a/silkaj/tx_history.py +++ b/silkaj/tx_history.py @@ -24,7 +24,7 @@ from duniterpy.documents.transaction import Transaction from silkaj.network_tools import ClientInstance from silkaj.tools import coroutine from silkaj.tui import convert_time -from silkaj.crypto_tools import check_public_key +from silkaj.crypto_tools import validate_checksum, check_pubkey_format from silkaj.wot import identity_of, identities_from_pubkeys from silkaj.money import get_amount_from_pubkey, amount_in_current_base, UDValue from silkaj.tools import CurrencySymbol @@ -35,8 +35,8 @@ from silkaj.tools import CurrencySymbol @option("--uids", "-u", is_flag=True, help="Display uids") @coroutine async def transaction_history(pubkey, uids): - if not check_public_key(pubkey, True): - return + if check_pubkey_format(pubkey): + pubkey = validate_checksum(pubkey) client = ClientInstance().client ud_value = await UDValue().ud_value diff --git a/silkaj/wot.py b/silkaj/wot.py index efe87ddd7c80d3d7588f76beb19d15c1e906502d..25a029d67b82a4f78fac73fe904d5f248f201162 100644 --- a/silkaj/wot.py +++ b/silkaj/wot.py @@ -24,7 +24,7 @@ from duniterpy.api.bma import wot, blockchain from duniterpy.api.errors import DuniterError from silkaj.network_tools import ClientInstance -from silkaj.crypto_tools import check_public_key +from silkaj.crypto_tools import is_pubkey_and_check from silkaj.tools import message_exit, coroutine from silkaj.tui import convert_time from silkaj.blockchain_tools import BlockchainParams @@ -159,7 +159,10 @@ def date_approximation(block_id, time_first_block, avgentime): @coroutine async def id_pubkey_correspondence(id_pubkey): client = ClientInstance().client - if check_public_key(id_pubkey, False): + # determine if id_pubkey is a pubkey + checked_pubkey = is_pubkey_and_check(id_pubkey) + if checked_pubkey: + id_pubkey = checked_pubkey try: idty = await identity_of(id_pubkey) print( @@ -169,6 +172,7 @@ async def id_pubkey_correspondence(id_pubkey): ) except: message_exit("No matching identity") + # if not ; then it is a uid else: pubkeys = await wot_lookup(id_pubkey) print("Public keys found matching '{}':\n".format(id_pubkey)) diff --git a/tests/test_crypto_tools.py b/tests/test_crypto_tools.py index 3634e04fdf8e05cb882f6a38c0598447f19fd5c8..b157167736398ac50f83e570c1e448aebd459247 100644 --- a/tests/test_crypto_tools.py +++ b/tests/test_crypto_tools.py @@ -19,7 +19,7 @@ import pytest from silkaj import crypto_tools - +# test gen_checksum @pytest.mark.parametrize( "pubkey, checksum", [ @@ -28,3 +28,86 @@ from silkaj import crypto_tools ) def test_gen_checksum(pubkey, checksum): assert checksum == crypto_tools.gen_checksum(pubkey) + + +# test validate_checksum +@pytest.mark.parametrize( + "pubkey, checksum, expected", + [ + ("J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", "KAv", None), + ( + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + "KA", + "Error: Wrong checksum for following public key: J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + ), + ], +) +def test_validate_checksum(pubkey, checksum, expected, capsys): + pubkey_with_ck = str(pubkey + ":" + checksum) + if expected == None: + assert pubkey == crypto_tools.validate_checksum(pubkey_with_ck) + else: + with pytest.raises(SystemExit) as pytest_exit: + test = crypto_tools.validate_checksum(pubkey_with_ck) + assert capsys.readouterr() == expected + assert pytest_exit.type == SystemExit + + +# test check_pubkey_format +@pytest.mark.parametrize( + "pubkey, display_error, expected", + [ + ("J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX:KAv", True, True), + ("J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", True, False), + ("Youpi", False, None), + ("Youpi", True, "Error: bad format for following public key: Youpi"), + ], +) +def test_check_pubkey_format(pubkey, display_error, expected, capsys): + if isinstance(expected, str): + with pytest.raises(SystemExit) as pytest_exit: + test = crypto_tools.check_pubkey_format(pubkey, display_error) + assert capsys.readouterr() == expected + assert pytest_exit.type == SystemExit + else: + assert expected == crypto_tools.check_pubkey_format(pubkey, display_error) + + +# test is_pubkey_and_check +@pytest.mark.parametrize( + "uid_pubkey, expected", + [ + ( + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX:KAv", + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + ), + ( + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + ), + ("Youpi", False), + ], +) +def test_is_pubkey_and_check(uid_pubkey, expected): + assert expected == crypto_tools.is_pubkey_and_check(uid_pubkey) + + +# test is_pubkey_and_check errors +@pytest.mark.parametrize( + "uid_pubkey, expected", + [ + ( + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX:KA", + "Error: bad format for following public key: J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX:KA", + ), + ( + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX:KAt", + "Error: Wrong checksum for following public key: J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + ), + ], +) +def test_is_pubkey_and_check_errors(uid_pubkey, expected, capsys): + with pytest.raises(SystemExit) as pytest_exit: + test = crypto_tools.is_pubkey_and_check(uid_pubkey) + assert capsys.readouterr() == expected + assert pytest_exit.type == SystemExit