diff --git a/README.md b/README.md index 08db0f84..ec379c4d 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,19 @@ # A Python library for 'bitcoin cryptography' -[![python](https://img.shields.io/pypi/pyversions/btclib.svg?logo=python)](https://pypi.python.org/pypi/btclib/) -[![calver: yyy.m.d](https://img.shields.io/badge/cal%20ver-yyyy.m.d-1674b1.svg?logo=calver)](https://calver.org/) -[![pypi](https://img.shields.io/pypi/v/btclib.svg?logo=pypi)](https://pypi.python.org/pypi/btclib/) -[![downloads](https://static.pepy.tech/badge/btclib)](https://pepy.tech/project/btclib) -[![status](https://img.shields.io/pypi/status/btclib.svg)](https://pypi.python.org/pypi/btclib/) -[![license](https://img.shields.io/github/license/btclib-org/btclib.svg)](https://github.com/btclib-org/btclib/blob/master/LICENSE) -[![imports: isort](https://img.shields.io/badge/imports-isort-yellowgreen.svg?logo=isort)](https://pycqa.github.io/isort/) -[![code style: black](https://img.shields.io/badge/code%20style-black-yellowgreen.svg?logo=black)](https://github.com/psf/black) -[![lint: flake8](https://img.shields.io/badge/lint-flake8-yellowgreen.svg?logo=flake8)](https://flake8.pycqa.org) -[![lint: pylint](https://img.shields.io/badge/lint-pylint-yellowgreen.svg?logo=pylint)](https://github.com/PyCQA/pylint) -[![type-check: mypy](https://img.shields.io/badge/type--check-mypy-yellowgreen.svg?logo=mypy)](http://mypy-lang.org/) -[![type-check: pyright](https://img.shields.io/badge/type--check-pyright-yellowgreen.svg)](https://github.com/microsoft/pyright) -[![security: bandit](https://img.shields.io/badge/security-bandit-yellowgreen.svg?logo=bandit)](https://github.com/PyCQA/bandit) -[![refactor: sourcery](https://img.shields.io/badge/refactor-sourcery-yellowgreen.svg?logo=sourcery)](https://sourcery.ai) -[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/btclib-org/btclib/master.svg)](https://results.pre-commit.ci/latest/github/btclib-org/btclib/master) -[![lint](https://github.com/btclib-org/btclib/actions/workflows/lint.yml/badge.svg)](https://github.com/btclib-org/btclib/actions/workflows/lint.yml) -[![test](https://github.com/btclib-org/btclib/actions/workflows/test.yml/badge.svg)](https://github.com/btclib-org/btclib/actions/workflows/test.yml) -[![test-py312](https://github.com/btclib-org/btclib/actions/workflows/test-py312.yml/badge.svg)](https://github.com/btclib-org/btclib/actions/workflows/test-py312.yml) -[![docs](https://img.shields.io/readthedocs/btclib.svg?logo=readthedocs)](https://btclib.readthedocs.io) -[![coverage](https://coveralls.io/repos/github/btclib-org/btclib/badge.svg?logo=coveralls)](https://coveralls.io/github/btclib-org/btclib) - -[![Follow on Twitter](https://img.shields.io/twitter/follow/btclib?style=social&logo=twitter)](https://twitter.com/intent/follow?screen_name=btclib) - ---- + +| | | +| --- | --- | +| Package | [![python](https://img.shields.io/pypi/pyversions/btclib.svg?logo=python)](https://pypi.python.org/pypi/btclib/) [![calver: yyy.m.d](https://img.shields.io/badge/cal_ver-yyyy.m.d-1674b1.svg?logo=calver)](https://calver.org/) [![pypi](https://img.shields.io/pypi/v/btclib.svg?logo=pypi)](https://pypi.python.org/pypi/btclib/) [![downloads](https://static.pepy.tech/badge/btclib)](https://pepy.tech/project/btclib) [![status](https://img.shields.io/pypi/status/btclib.svg)](https://pypi.python.org/pypi/btclib/) [![license](https://img.shields.io/github/license/btclib-org/btclib.svg)](https://github.com/btclib-org/btclib/blob/master/LICENSE) | +| Coding standards | [![lint: pydocstringformatter](https://img.shields.io/badge/lint-pydocstringformatter-yellowgreen.svg?logo=pydocstringformatter)](https://github.com/DanielNoord/pydocstringformatter) [![lint: pyupgrade](https://img.shields.io/badge/lint-pyupgrade-yellowgreen.svg?logo=pyupgrade)](https://github.com/asottile/pyupgrade) [![lint: isort](https://img.shields.io/badge/lint-isort-yellowgreen.svg?logo=isort)](https://github.com/PyCQA/isort) [![lint: black](https://img.shields.io/badge/lint-black-yellowgreen.svg?logo=black)](https://github.com/psf/black) [![lint: autoflake](https://img.shields.io/badge/lint-autoflake-yellowgreen.svg?logo=autoflake)](https://github.com/PyCQA/autoflake) [![lint: flake8](https://img.shields.io/badge/lint-flake8-yellowgreen.svg?logo=flake8)]() [![lint: yesqa](https://img.shields.io/badge/lint-yesqa-yellowgreen.svg?logo=yesqa)]() [![lint: pylint](https://img.shields.io/badge/lint-pylint-yellowgreen.svg?logo=pylint)](https://github.com/PyCQA/pylint) [![type check: mypy](https://img.shields.io/badge/type_check-mypy-yellowgreen.svg?logo=mypy)](http://mypy-lang.org/) [![type check: pyright](https://img.shields.io/badge/type_check-pyright-yellowgreen.svg)](https://github.com/microsoft/pyright) [![lint: sourcery](https://img.shields.io/badge/lint-sourcery-yellowgreen.svg?logo=sourcery)](https://sourcery.ai) [![security: bandit](https://img.shields.io/badge/security-bandit-yellowgreen.svg?logo=bandit)](https://github.com/PyCQA/bandit) [![lint: markdownlint-cli2](https://img.shields.io/badge/lint-markdownlint--cli2-yellowgreen.svg?logo=sourcery)](https://github.com/DavidAnson/markdownlint-cli2) | +| CI/CD | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/btclib-org/btclib/master.svg)](https://results.pre-commit.ci/latest/github/btclib-org/btclib/master) [![lint](https://github.com/btclib-org/btclib/actions/workflows/lint.yml/badge.svg)](https://github.com/btclib-org/btclib/actions/workflows/lint.yml) [![test](https://github.com/btclib-org/btclib/actions/workflows/test.yml/badge.svg)](https://github.com/btclib-org/btclib/actions/workflows/test.yml) [![test-py312](https://github.com/btclib-org/btclib/actions/workflows/test-py312.yml/badge.svg)](https://github.com/btclib-org/btclib/actions/workflows/test-py312.yml) [![coverage](https://coveralls.io/repos/github/btclib-org/btclib/badge.svg?logo=coveralls)](https://coveralls.io/github/btclib-org/btclib) | [![docs](https://img.shields.io/readthedocs/btclib.svg?logo=readthedocs)](https://btclib.readthedocs.io) | +| Conversations | [![slack](https://img.shields.io/badge/slack-btclib_dev-white.svg?logo=slack)](https://bbt-training.slack.com/messages/C01CCJ85AES) [![Follow on Twitter](https://img.shields.io/twitter/follow/btclib?style=social&logo=twitter)](https://twitter.com/intent/follow?screen_name=btclib)| +| [Browse GitHub Code Repository](https://github.com/btclib-org/btclib/) --- + [btclib](https://btclib.org) is a Python3 [type annotated](https://docs.python.org/3/library/typing.html) diff --git a/btclib/ec/curve.py b/btclib/ec/curve.py index 5ef6816e..ecce5f3e 100644 --- a/btclib/ec/curve.py +++ b/btclib/ec/curve.py @@ -40,7 +40,7 @@ def __init__(self, p: Integer, a: Integer, b: Integer, G: Point) -> None: # 2. check that xG and yG are integers in the interval [0, p−1] # 4. Check that yG^2 = xG^3 + a*xG + b (mod p) if len(G) != 2: - raise BTClibValueError("Generator must a be a sequence[int, int]") + raise BTClibValueError("generator must a be a sequence[int, int]") self.G = (int_from_integer(G[0]), int_from_integer(G[1])) if not self.is_on_curve(self.G): raise BTClibValueError("Generator is not on the curve") diff --git a/btclib/mnemonic/entropy.py b/btclib/mnemonic/entropy.py index b8f509aa..93eaf2e8 100644 --- a/btclib/mnemonic/entropy.py +++ b/btclib/mnemonic/entropy.py @@ -186,7 +186,7 @@ def bin_str_entropy_from_int( int_entropy = int(int_entropy) if int_entropy < 0: - raise BTClibValueError(f"Negative entropy: {int_entropy}") + raise BTClibValueError(f"negative entropy: {int_entropy}") # if a single int, make it a tuple if isinstance(bits, int): @@ -325,7 +325,7 @@ def bin_str_entropy_from_rolls( i += roll - 1 min_roll_number -= 1 if min_roll_number > 0: - msg = f"Too few rolls in the usable [1-{base}] range" + msg = f"too few rolls in the usable [1-{base}] range" msg += f", missing {min_roll_number} rolls" raise BTClibValueError(msg) @@ -363,7 +363,7 @@ def bin_str_entropy_from_random( hf = sha512() max_bits = hf.digest_size * 8 if bits > max_bits: - err_msg = f"Too many bits required: {bits}, max is {max_bits}" + err_msg = f"too many bits required: {bits}, max is {max_bits}" raise BTClibValueError(err_msg) n_bytes = math.ceil(i.bit_length() / 8) h512 = sha512(i.to_bytes(n_bytes, byteorder="big", signed=False)).digest() diff --git a/btclib/number_theory.py b/btclib/number_theory.py index 93ff31cf..1a8b9943 100644 --- a/btclib/number_theory.py +++ b/btclib/number_theory.py @@ -54,7 +54,7 @@ def mod_inv(a: int, m: int) -> int: g, x, _ = xgcd(a, m) if g == 1: return x % m - err_msg = "No inverse for " + err_msg = "no inverse for " err_msg += f"{hex_string(a)}" if a > 0xFFFFFFFF else f"{a}" err_msg += " mod " err_msg += f"{hex_string(m)}" if m > 0xFFFFFFFF else f"{m}" diff --git a/btclib/psbt/psbt_in.py b/btclib/psbt/psbt_in.py index c2f08e57..a51837e5 100644 --- a/btclib/psbt/psbt_in.py +++ b/btclib/psbt/psbt_in.py @@ -115,28 +115,28 @@ def _assert_valid_ripemd160_preimages( for h, preimage in ripemd160_preimages.items(): if ripemd160(preimage) != h: - raise BTClibValueError("Invalid RIPEMD160 preimage") + raise BTClibValueError("invalid RIPEMD160 preimage") def _assert_valid_sha256_preimages(sha256_preimages: Mapping[bytes, bytes]) -> None: for h, preimage in sha256_preimages.items(): if sha256(preimage) != h: - raise BTClibValueError("Invalid SHA256 preimage") + raise BTClibValueError("invalid SHA256 preimage") def _assert_valid_hash160_preimages(hash160_preimages: Mapping[bytes, bytes]) -> None: for h, preimage in hash160_preimages.items(): if hash160(preimage) != h: - raise BTClibValueError("Invalid HASH160 preimage") + raise BTClibValueError("invalid HASH160 preimage") def _assert_valid_hash256_preimages(hash256_preimages: Mapping[bytes, bytes]) -> None: for h, preimage in hash256_preimages.items(): if hash256(preimage) != h: - raise BTClibValueError("Invalid HASH256 preimage") + raise BTClibValueError("invalid HASH256 preimage") @dataclass diff --git a/btclib/script/script.py b/btclib/script/script.py index 4312b969..e353a24f 100644 --- a/btclib/script/script.py +++ b/btclib/script/script.py @@ -339,13 +339,13 @@ def parse(stream: BinaryData, exit_on_op_success: bool = False) -> list[Command] x = 4 y = s.read(x) if len(y) != x: - raise BTClibValueError("Not enough data for pushdata length") + raise BTClibValueError("not enough data for pushdata length") data_length = int.from_bytes(y, byteorder="little") if data_length > 520: - raise BTClibValueError(f"Invalid pushdata length: {data_length}") + raise BTClibValueError(f"invalid pushdata length: {data_length}") data = s.read(data_length) if len(data) != data_length: - raise BTClibValueError("Not enough data for pushdata") + raise BTClibValueError("not enough data for pushdata") command = data.hex().upper() elif i in OP_CODE_NAME_FROM_INT: # OP_CODE command = OP_CODE_NAME_FROM_INT[i] diff --git a/btclib/script/sig_hash.py b/btclib/script/sig_hash.py index 22b405d4..2dc09cdd 100644 --- a/btclib/script/sig_hash.py +++ b/btclib/script/sig_hash.py @@ -248,7 +248,7 @@ def from_tx(prevouts: list[TxOut], tx: Tx, vin_i: int, hash_type: int) -> bytes: if is_p2sh(script): script = tx.vin[vin_i].script_sig if is_p2tr(script): - raise BTClibValueError("Taproot scripts cannot be wrapped in p2sh") + raise BTClibValueError("taproot scripts cannot be wrapped in p2sh") if is_p2wpkh(script): script_ = witness_v0_script(script)[0] @@ -269,7 +269,7 @@ def _script_from_p2tr( witness = tx.vin[vin_i].script_witness if len(witness.stack) == 0: - raise BTClibValueError("Empty stack") + raise BTClibValueError("empty stack") annex = b"" if len(witness.stack) >= 2 and witness.stack[-1][0] == 0x50: diff --git a/btclib/script/taproot.py b/btclib/script/taproot.py index eae21525..871b3de9 100644 --- a/btclib/script/taproot.py +++ b/btclib/script/taproot.py @@ -60,7 +60,7 @@ def output_pubkey( ec: Curve = secp256k1, ) -> tuple[bytes, int]: if not internal_pubkey and not script_tree: - raise BTClibValueError("Missing data") + raise BTClibValueError("missing data") if internal_pubkey: pubkey = pub_keyinfo_from_key(internal_pubkey, compressed=True)[0][1:] else: @@ -124,10 +124,10 @@ def check_output_pubkey( script = bytes_from_octets(script) control = bytes_from_octets(control) if len(control) > 4129: # 33 + 32 * 128 - raise BTClibValueError("Control block too long") + raise BTClibValueError("control block too long") m = (len(control) - 33) // 32 if len(control) != 33 + 32 * m: - raise BTClibValueError("Invalid control block length") + raise BTClibValueError("invalid control block length") leaf_version = control[0] & 0xFE preimage = leaf_version.to_bytes(1, "big") + var_bytes.serialize(script) k = tagged_hash(b"TapLeaf", preimage) diff --git a/tests/bip32/test_der_path.py b/tests/bip32/test_der_path.py index f2e06f05..1120ca85 100644 --- a/tests/bip32/test_der_path.py +++ b/tests/bip32/test_der_path.py @@ -101,12 +101,12 @@ def test_index_int_to_from_str() -> None: assert i == int_from_index_str(str_from_index_int(i)) for i in (-1, 0xFFFFFFFF + 1): - with pytest.raises(BTClibValueError): + with pytest.raises(BTClibValueError, match="invalid index: "): str_from_index_int(i) # sourcery skip: simplify-fstring-formatting for s in ("-1", "-1h", f"{0x80000000}h", f"{0xFFFFFFFF + 1}"): - with pytest.raises(BTClibValueError): + with pytest.raises(BTClibValueError, match="invalid index: "): int_from_index_str(s) with pytest.raises(BTClibValueError, match="invalid hardening symbol: "): diff --git a/tests/ec/test_curve.py b/tests/ec/test_curve.py index e5d477b3..071d2918 100644 --- a/tests/ec/test_curve.py +++ b/tests/ec/test_curve.py @@ -69,7 +69,7 @@ def test_exceptions() -> None: with pytest.raises(BTClibValueError, match="zero discriminant"): Curve(11, 7, 7, (1, 9), 19, 1, False) - err_msg = "Generator must a be a sequence\\[int, int\\]" + err_msg = "generator must a be a sequence\\[int, int\\]" with pytest.raises(BTClibValueError, match=err_msg): Curve(13, 0, 2, (1, 9, 1), 19, 1, False) # type: ignore[arg-type] @@ -301,11 +301,11 @@ def test_symmetry() -> None: with pytest.raises(BTClibValueError, match=err_msg): mod_sqrt(y_even, ec.p) - with pytest.raises(BTClibValueError): + with pytest.raises(BTClibValueError, match="invalid x-coordinate: "): secp256k1.y_even(INF[0]) - with pytest.raises(BTClibValueError): + with pytest.raises(BTClibValueError, match="invalid x-coordinate: "): secp256k1.y_low(INF[0]) - with pytest.raises(BTClibValueError): + with pytest.raises(BTClibValueError, match="invalid x-coordinate: "): secp256k1.y_quadratic_residue(INF[0]) diff --git a/tests/mnemonic/test_entropy.py b/tests/mnemonic/test_entropy.py index 445e1f69..4921fbdc 100644 --- a/tests/mnemonic/test_entropy.py +++ b/tests/mnemonic/test_entropy.py @@ -143,7 +143,7 @@ def test_exceptions() -> None: assert len(entropy) == 224 assert int(entropy, 2) == int_entropy211 - err_msg = "Negative entropy: " + err_msg = "negative entropy: " with pytest.raises(BTClibValueError, match=err_msg): bin_str_entropy_from_entropy(-1 * int_entropy211) @@ -240,13 +240,13 @@ def test_bin_str_entropy_from_rolls() -> None: bin_str = bin_str_entropy_from_random(bits, bin_str_rolls) rolls = [secrets.randbelow(base) + 1 for _ in range(roll_number - 2)] - err_msg = "Too few rolls in the usable " # [1-16] range, missing 2 rolls + err_msg = "too few rolls in the usable " # [1-16] range, missing 2 rolls with pytest.raises(BTClibValueError, match=err_msg): bin_str_entropy_from_rolls(bits, dice_base, rolls) rolls = [secrets.randbelow(base) + 1 for _ in range(roll_number)] rolls[1] = base + 1 - err_msg = "Too few rolls in the usable " # [1-16] range, missing 1 rolls + err_msg = "too few rolls in the usable " # [1-16] range, missing 1 rolls with pytest.raises(BTClibValueError, match=err_msg): bin_str_entropy_from_rolls(bits, dice_base, rolls) @@ -289,6 +289,6 @@ def test_bin_str_entropy_from_random() -> None: bin_str = bin_str_entropy_from_random(1024, to_be_hashed=False) assert len(bin_str) == 1024 - err_msg = "Too many bits required: " + err_msg = "too many bits required: " with pytest.raises(BTClibValueError, match=err_msg): bin_str_entropy_from_random(1024) diff --git a/tests/psbt/test_psbt_utils.py b/tests/psbt/test_psbt_utils.py index 09736571..998faca7 100644 --- a/tests/psbt/test_psbt_utils.py +++ b/tests/psbt/test_psbt_utils.py @@ -17,5 +17,5 @@ def test_invalid_serialize_hd_key_paths() -> None: - with pytest.raises(BTClibValueError): + with pytest.raises(BTClibValueError, match="invalid type marker lenght: "): serialize_hd_key_paths(b"\x01\x01", []) # type: ignore[arg-type] diff --git a/tests/script/test_script.py b/tests/script/test_script.py index e02c49d4..432ed9eb 100644 --- a/tests/script/test_script.py +++ b/tests/script/test_script.py @@ -77,10 +77,10 @@ def test_serialize_bytes_command() -> None: def test_invalid_op_success() -> None: - err_msg = "invalid OP_SUCCESS number:" + err_msg = "invalid OP_SUCCESS number: " with pytest.raises(BTClibValueError, match=err_msg): _serialize_str_command("OP_SUCCESS1") - err_msg = "invalid OP_SUCCESS number:" + err_msg = "invalid OP_SUCCESS number: " with pytest.raises(BTClibValueError, match=err_msg): _serialize_str_command("OP_SUCCESS173") @@ -131,7 +131,7 @@ def test_exceptions() -> None: # A script_pub_key with OP_PUSHDATA4 can't be decoded script_bytes = "4e09020000" + "0A" * 521 + "75" # ['0A'*521, 'OP_DROP'] - err_msg = "Invalid pushdata length: " + err_msg = "invalid pushdata length: " with pytest.raises(BTClibValueError, match=err_msg): parse(script_bytes) @@ -156,10 +156,10 @@ def test_encoding() -> None: def test_opcode_length() -> None: - err_msg = "Not enough data for pushdata length" + err_msg = "not enough data for pushdata length" with pytest.raises(BTClibValueError, match=err_msg): parse(b"\x4e\x00") - err_msg = "Not enough data for pushdata" + err_msg = "not enough data for pushdata" with pytest.raises(BTClibValueError, match=err_msg): parse(b"\x40\x00") diff --git a/tests/script/test_sig_hash_taproot.py b/tests/script/test_sig_hash_taproot.py index 44afa5da..95169c86 100644 --- a/tests/script/test_sig_hash_taproot.py +++ b/tests/script/test_sig_hash_taproot.py @@ -149,7 +149,7 @@ def test_valid_sighash_type() -> None: if hash_type in sig_hash.SIG_HASH_TYPES: sig_hash.assert_valid_hash_type(hash_type) else: - err_msg = "invalid sig_hash type:" + err_msg = "invalid sig_hash type: " with pytest.raises(BTClibValueError, match=err_msg): sig_hash.assert_valid_hash_type(hash_type) @@ -165,7 +165,7 @@ def test_empty_stack() -> None: tx_in = TxIn(OutPoint(), "", 1, Witness([])) tx = Tx(vin=[tx_in], vout=[TxOut(100000000, "")]) - err_msg = "Empty stack" + err_msg = "empty stack" with pytest.raises(BTClibValueError, match=err_msg): sig_hash.from_tx([utxo], tx, 0, 0) @@ -182,7 +182,7 @@ def test_wrapped_p2tr() -> None: tx_in = TxIn(OutPoint(), serialize(script), 1, Witness(["0A" * 32])) tx = Tx(vin=[tx_in], vout=[TxOut(100000000, "")]) - err_msg = "Taproot scripts cannot be wrapped in p2sh" + err_msg = "taproot scripts cannot be wrapped in p2sh" with pytest.raises(BTClibValueError, match=err_msg): sig_hash.from_tx([utxo], tx, 0, 0) diff --git a/tests/script/test_taproot.py b/tests/script/test_taproot.py index 0442817d..c8c1ff6a 100644 --- a/tests/script/test_taproot.py +++ b/tests/script/test_taproot.py @@ -85,17 +85,17 @@ def test_taproot_key_tweaking() -> None: def test_invalid_control_block() -> None: - err_msg = "Control block too long" + err_msg = "control block too long" with pytest.raises(BTClibValueError, match=err_msg): check_output_pubkey(b"\x00" * 32, b"\x00", b"\x00" * 4130) - err_msg = "Invalid control block length" + err_msg = "invalid control block length" with pytest.raises(BTClibValueError, match=err_msg): check_output_pubkey(b"\x00" * 32, b"\x00", b"\x00" * 100) def test_unspendable_script() -> None: - err_msg = "Missing data" + err_msg = "missing data" with pytest.raises(BTClibValueError, match=err_msg): output_pubkey() diff --git a/tests/test_bech32.py b/tests/test_bech32.py index 05d782df..01584d3d 100644 --- a/tests/test_bech32.py +++ b/tests/test_bech32.py @@ -69,7 +69,7 @@ def test_bech32() -> None: assert encode(*decoded, _BECH32_1_CONST).decode() == test.lower() pos = test.rfind("1") test = test[: pos + 1] + chr(ord(test[pos + 1]) ^ 1) + test[pos + 2 :] - with pytest.raises(BTClibValueError): + with pytest.raises(BTClibValueError): # assorted error messages decode(test, _BECH32_1_CONST) invalid_checksum = [ diff --git a/tests/test_number_theory.py b/tests/test_number_theory.py index e57fd5a1..78f22d02 100644 --- a/tests/test_number_theory.py +++ b/tests/test_number_theory.py @@ -60,7 +60,7 @@ def test_mod_inv_prime() -> None: for p in primes: - with pytest.raises(BTClibValueError, match="No inverse for 0 mod"): + with pytest.raises(BTClibValueError, match="no inverse for 0 mod"): mod_inv(0, p) for a in range(1, min(p, 500)): # exhausted only for small p inv = mod_inv(a, p) @@ -81,7 +81,7 @@ def test_mod_inv() -> None: inv = mod_inv(a + m, m) assert a * inv % m == 1 else: - err_msg = "No inverse for " + err_msg = "no inverse for " with pytest.raises(BTClibValueError, match=err_msg): mod_inv(a, m)