pybitcash / bitcash

BitCash: Python Bitcoin Cash Library (fork of ofek's Bit)
https://bitcash.dev
MIT License
97 stars 39 forks source link

Unsupported hash type ripemd160 #118

Closed yashasvi-ranawat closed 1 year ago

yashasvi-ranawat commented 1 year ago

Tested on OpenSSL 3.0.2, Python 3.10, Linux Ubuntu 22.04 LTS.

Hashlib raises ValueError: unsupported hash type ripemd160, in ripemd160_sha256 function in crypto.py. Found the reason for the issue here.

Hashlib uses OpenSSL for ripemd160 and apparently OpenSSL disabled some older crypto algos around version 3.0 in November 2021. All the functions are still there but require manual enabling. See issue 16994 of OpenSSL github project for details.

The stackoverflow answer details how to enable legacy crypto algorithms, but I think a default solution is needed to use ripemd160.

merc1er commented 1 year ago

How does this impact BitCash?

yashasvi-ranawat commented 1 year ago

BitCash uses the hash160 (ripemd160_sha256) function to convert public keys to cashaddress and public key hashes for output locking script of P2PKH outputs. This affects the primary functionality of BitCash as a P2PKH wallet.

hash160 uses the hashlib of python, which uses the OpenSSL implementation of ripemd160 which no longer works by default (as tested on OpenSSL 3.0.2, Python 3.10, Linux Ubuntu 22.04 LTS).

merc1er commented 1 year ago

I am unable to reproduce this.

Can you send a minimal reproducible example?

yashasvi-ranawat commented 1 year ago
from hashlib import new, sha256
new("ripemd160", sha256(b"a").digest()).digest()

I tested this on OpenSSL 3.0.2, Python 3.10.6. Apparently OpenSSL had already marked ripemd160 as legacy algorithm. Do check if you have already enabled legacy_sect as noted here, if you are on the same version of OpenSSL.

This is what my default openssl.cnf file looked like. (only showing relevant options)

openssl_conf = openssl_init

[openssl_init] providers = provider_sect

[provider_sect] default = default_sect

[default_sect] # activate = 1

This led the above python code to give "ValueError: unsupported hash type ripemd160"

After when I enabled legacy algorithms, as given here, the ripemd160 code gave no errors.

merc1er commented 1 year ago
from hashlib import new, sha256
new("ripemd160", sha256(b"a").digest()).digest()

This works on my environment (macOS, Python 3.10.6, LibreSSL 3.3.6).

In [1]: from hashlib import new, sha256
   ...: new("ripemd160", sha256(b"a").digest()).digest()
Out[1]: b'\x99CU\x19\x9eQo\xf7lO\xa4\xaa\xb3\x937\xb9\xd8L\xf1+'
merc1er commented 1 year ago

I can try to reproduce in a different environment.

What would be a way to solve this within BitCash? And how much work would that involve?

yashasvi-ranawat commented 1 year ago

It seems its a OpenSSl only problem. Other bitcoin libraries have dealt with the same: https://github.com/bitcoin/bitcoin/issues/23710.

Most straightforward way would be to code python native ripemd160 code, as done here

merc1er commented 1 year ago

Seems pretty straightforward:

def ripemd160(data):
    """Compute the RIPEMD-160 hash of data."""
    # Initialize state.
    state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
    # Process full 64-byte blocks in the input.
    for b in range(len(data) >> 6):
        state = compress(*state, data[64*b:64*(b+1)])
    # Construct final blocks (with padding and size).
    pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
    fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
    # Process final blocks.
    for b in range(len(fin) >> 6):
        state = compress(*state, fin[64*b:64*(b+1)])
    # Produce output.
    return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)

Would you be willing to submit a PR for that?

merc1er commented 1 year ago

I am able to reproduce. Looking forward to merging this change 👍🏻