diff --git a/silkaj/crypto_tools.py b/silkaj/crypto_tools.py index 4ebd8bd1969a979e46c679f85e93ea45172ce90c..3fcb1d87e93f83c7863aed9ee425f4cd1480f8b0 100644 --- a/silkaj/crypto_tools.py +++ b/silkaj/crypto_tools.py @@ -19,32 +19,50 @@ 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(uid_pubkey): """ - Check public key format - Check pubkey checksum which could be append after the pubkey - If check pass: return pubkey + checks if the given arguments 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_PATTERN), uid_pubkey): + if has_pubkey_checksum(uid_pubkey, True): + return check_public_key(uid_pubkey) + return uid_pubkey + return False + + +def has_pubkey_checksum(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 pubkey + return False 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 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 check_public_key(pubkey_checksum): + """ + Check pubkey checksum which could be append after the pubkey + If check pass: return pubkey + """ + pubkey, checksum = pubkey_checksum.split(":") + if checksum == gen_checksum(pubkey): + return pubkey + message_exit("Error: Wrong checksum for following public key: " + pubkey) def gen_checksum(pubkey): diff --git a/silkaj/money.py b/silkaj/money.py index 945d6c29f3c99d9bd7c181c735fc52b14a9c25b5..4b240fa6ada37755f1015d685ad1bebe3ff9f073 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 has_pubkey_checksum, check_public_key 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 has_pubkey_checksum(pubkey): + pubkey_list.append(check_public_key(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..ec771483ce8ee1a135051ac79fcc20a4d83e9250 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 check_public_key, has_pubkey_checksum 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 has_pubkey_checksum(outputAddress): + outputAddresses[i] = check_public_key(outputAddress) if outputBackChange: - pubkey = outputBackChange - outputBackChange = check_public_key(outputBackChange, True) - if not outputBackChange: - message_exit(pubkey) + if has_pubkey_checksum(outputBackChange): + outputBackChange = check_public_key(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..fbbc2387ac43d24e16b09a278690517dade321d1 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 check_public_key, has_pubkey_checksum 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 has_pubkey_checksum(pubkey): + pubkey = check_public_key(pubkey) client = ClientInstance().client ud_value = await UDValue().ud_value diff --git a/silkaj/wot.py b/silkaj/wot.py index efe87ddd7c80d3d7588f76beb19d15c1e906502d..9cc9424cee40fd1781739cdc72b3f52a224d7121 100644 --- a/silkaj/wot.py +++ b/silkaj/wot.py @@ -15,6 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with Silkaj. If not, see <https://www.gnu.org/licenses/>. """ +import re import click from time import time from tabulate import tabulate @@ -24,7 +25,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 +160,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 +173,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..ca49918dccd311c938c6f086dd547c88893d8974 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 check_pubkey +@pytest.mark.parametrize( + "pubkey, checksum, expected", + [ + ("J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", "KAv", None), + ( + "J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + "KA", + "Error: Wrong checksum for following public key: J4c8CARmP9vAFNGtHRuzx14zvxojyRWHW2darguVqjtX", + ), + ], +) +def test_check_pubkey(pubkey, checksum, expected, capsys): + pubkey_with_ck = str(pubkey + ":" + checksum) + if expected == None: + assert pubkey == crypto_tools.check_public_key(pubkey_with_ck) + else: + with pytest.raises(SystemExit) as pytest_exit: + test = crypto_tools.check_public_key(pubkey_with_ck) + assert capsys.readouterr() == expected + assert pytest_exit.type == SystemExit + + +# test has_pubkey_checksum +@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_has_pubkey_checksum(pubkey, display_error, expected, capsys): + if isinstance(expected, str): + with pytest.raises(SystemExit) as pytest_exit: + test = crypto_tools.has_pubkey_checksum(pubkey, display_error) + assert capsys.readouterr() == expected + assert pytest_exit.type == SystemExit + else: + assert expected == crypto_tools.has_pubkey_checksum(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(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