Source code for btclib_ecc.hashes

# Copyright (c) The btclib developers
# Distributed under the MIT software license, see the accompanying
# LICENSE file or https://opensource.org/license/mit for the full text.

"""The hashing btclib_ecc.ecc's schemes do.

BIP340's tagged hash, SEC 1's reduction of a message to one digest, and
the check that a hash function is a constructor. Each is `hashlib` and
nothing else.
"""

from __future__ import annotations

import hashlib

from btclib_ecc._utils import assert_type, bytes_from_octets
from btclib_ecc.alias import HashF, Octets
from btclib_ecc.exceptions import BTClibEccTypeError

__all__ = [
    "reduce_to_hlen",
    "tagged_hash",
]


def _assert_valid_hf(hf: HashF) -> None:
    """Refuse an hf that is not a hash constructor.

    The mistake it catches is `sha256()` written where `sha256` belongs --
    the digest object instead of the class that makes one -- which is a
    caller's own error and not a statement about the message or the
    signature it was passed with.

    `callable` and not a trial call: a digest object is not callable, so
    the check is a slot lookup rather than the hash it would otherwise
    have to build, which matters where it sits in front of a verification
    the bindings answer fast enough for that hash to show.

    So it is not exhaustive, and does not need to be: a callable of the
    wrong shape -- a one-shot digest, which takes the message rather than
    making a digest, the `_one_shot` of `tests/alias_test.py` -- still
    fails at the `hf()` that follows, with the TypeError Python raises for
    the missing argument, which is what that test expects. What matters for
    the verifications is not the class but that neither one is a
    ValueError, so neither is mistaken for a signature that does not
    verify.

    A `BTClibEccTypeError` for the reason the class exists, and with a
    consequence the boolean verifications depend on: it is a `TypeError`,
    so their `except (ValueError, BTClibEccRuntimeError)` does not catch it
    and a caller's mistake reaches the caller instead of being reported as
    a signature that does not verify.
    """
    if not callable(hf):
        raise BTClibEccTypeError(f"not a hash function: {hf!r} is not callable")


[docs] def reduce_to_hlen(msg: Octets, hf: HashF = hashlib.sha256) -> bytes: """Return the message digested by hf, one digest long. Step 4 of SEC 1 v.2 section 4.1.3: what the un-underscored signing and verifying spellings do to a message before handing it to their trailing-underscore twins. """ # here as well as in the verifications this feeds, and not only there: # they take the message already reduced, so this is where an hf of # theirs is first called and where a bad one would otherwise leave as # the bare TypeError of `hf()` _assert_valid_hf(hf) msg = bytes_from_octets(msg) # Step 4 of SEC 1 v.2 section 4.1.3 h = hf() h.update(msg) return h.digest()
[docs] def tagged_hash(tag: bytes, m: bytes, hf: HashF = hashlib.sha256) -> bytes: """Return BIP340's tagged hash: hf(hf(tag) || hf(tag) || m). The doubled tag digest is what makes a hash under one tag invalid under every other. `tag` and `m` are `bytes` and not `Octets`: unlike every octets parameter this package converts through `bytes_from_octets`, neither is ever meant to be read as a hex-string, so `assert_type` is what refuses a caller's mistake here rather than a coercion (issue btclib-org/btclib-ecc#11). """ _assert_valid_hf(hf) assert_type(tag, bytes, "tag") assert_type(m, bytes, "m") # libsecp256k1 computes exactly this in hashes.tagged_sha256, and the # binding is not called because it is slower at every size, and by # more the larger the message: hashlib's SHA256 is OpenSSL's, # hardware-accelerated, where libsecp256k1 compiles its own portable # C. This path also has to stay for hf != sha256, so delegating would # buy neither speed nor one implementation less. h1 = hf() h1.update(tag) tag_hash = h1.digest() h2 = hf() h2.update(tag_hash + tag_hash) # it could be sped up by storing the above midstate h2.update(m) return h2.digest()