3

tl;dr How should one perform Hash160, using most basic tools?

====================================================

Hi,

I'm trying to figure out, how transactions work in bitcoin.

When I choose inputs for a new tx I want to make sure they belong to a specific address. However, existing txs do not specify previous outputs' addresses, but instead they contain address' hashes.

e.g.:

>> bx fetch-tx 11a1b7ac0a65bd50b7094c720aecd77cfd83d84b1707960fd00dd82a888aab5c --config /home/theo/Desktop/bx-testnet.cfg

{
    hash 11a1b7ac0a65bd50b7094c720aecd77cfd83d84b1707960fd00dd82a888aab5c
    inputs
    {
        input
        {
            address_hash f3b7278583827a049d6be894bf7f516178a0c8e6
            previous_output
            {
                hash 4a3532061d43086299ae9b2409a456bb9638dff32e0858c4ccda27203fb2e4f6
                index 1
            }
            script "[30440220146b8b5b014245a9e27e21122d4dded04c3f39c3a49ac2494743d6f6ae8efff602206d417a4be9c7431ea69699132438510ade1cf8d746607f77d114907762ed1eb301] [023dd2e892290e41bb78efce6ea30a97015ef13eaaa9ebb7b0514485fc365cc391]"
            sequence 4294967295
        }
    }
    lock_time 0
    outputs
    {
        output
        {
            address_hash a73706385fffbf18855f2aee2a6168f29dbb597e
            script "dup hash160 [a73706385fffbf18855f2aee2a6168f29dbb597e] equalverify checksig"
            value 130000000
        }
        output
        {
            address_hash ad6e80394af99ece5d7701bf2f457480b93965b7
            script "dup hash160 [ad6e80394af99ece5d7701bf2f457480b93965b7] equalverify checksig"
            value 49525957813
        }
    }
    version 1
}

Say, I want to check which of the outputs can be sent from address mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa So I take its Hash160 in Python:

>> hashlib.new('ripemd160', hashlib.sha256("mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa".encode('utf-8')).digest()).hexdigest()
'748598cd9b004aecf8a2d97464fb1f2a90562ffe'

That is not the result I expected: a73706385fffbf18855f2aee2a6168f29dbb597e

Meanwhile, this online service calculates hash correctly.

How do I Hash160 a bitcoin address, preferably in Python?

3
  • while I can't help with python, you may want to look at "righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html", it has good explanation. Also I'm not sure if I understand your logic with the addresses to spend from. You would use the UTXOs from previous transactions... So I'd use "listunspent" in the core wallet, to see the prev tx ID and the vout, to use in a new (unsigned) transaction. An address hash cannot be reversed easily, that's why we have hash functions. Easy to go into one direction, impossible to reverse. That gives us the security in the whole bitcoin network. Sep 20, 2017 at 20:48
  • @pebwindkraft But I need the script in previous output to sign, don't I? Also, seems to me I got the encoding idea wrong. Hash160 clearly has bytes bigger than 3a (58 decimal), so my decoding function is nonsense
    – lotrus28
    Sep 20, 2017 at 20:52
  • yes, you give it the scriptPubKey, see the meanwhile famous 19step example, and some python at the end: here: bitcoin.stackexchange.com/questions/3374/… Sep 20, 2017 at 21:05

4 Answers 4

4

The address is already a hash, together with a 4-byte checksum and a version byte. To get from an address to a hash160, you don't have to compute sha256 or ripemd160 of anything. You just have to decode it from base58 back to hex, and discard the unwanted junk.

If you take mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa and base58 decode, you get

 6FA73706385FFFBF18855F2AEE2A6168F29DBB597EF59C240B
 VVxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSSSSSSSS

The first byte (6F) is the version byte; discard it. The last 4 bytes are the checksum; discard it. The remaining 20 bytes are the hash160 of the public key corresponding to the address.

1

I have already answered this question on StackOverflow, but I guess I'll just repost the answer here.

Finally I've made it. Some revelations in my answer may look obvious and basic to you, but I hope they'll be helpful to the people that are completely new to Bitcoin (such as me).


Wiki says that I can get Hash160 by reversing last step of address production

enter image description here (Hash160 is higlighted)

This step is encoding a byte string with base58 alphabet

b58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

This alphabet lacks 0, I, l, O, as these symbols are easy to mix up. And that's the last thing you want to do when one wrong symbol may lead to losing a fat amount of money.

Thus, we need to turn mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa into a byte-string. Bytes go in hexadecimal format and may range from 0x00 (0 decimal) to 0xff (255 decimal). And mind that we have a special b58 alphabet to deal with: decoding the address with utf-8 or other encoding standards will yield nonsense.

At first I thought that I can easily decode the address with this function:

def decode(addr):
    b58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
    decoded = ''
    for i in addr:
        temp = hex(b58.index(i))
        if len(temp) == 3:
            temp = '0' + temp[-1]
        else:
            temp = temp[2:]
        decoded += (temp)
    return (decoded)

decode('mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa')

>> '2c352c06030e090b212127390803312a1d22152c1d010d2c2811242c0d0f23372f21'

But the result is nothing like the hash I looked up in the transaction (a73706385fffbf18855f2aee2a6168f29dbb597e). This way I learned I had no idea how decoding is done. What if Hash160 has 0xff ? There's no such symbol in b58, as 58 in hex is just 0x3a. While decoding b58 we can't treat each symbol independently. The whole address makes up one giant number written in base58 numerical system (its first digit corresponds to 58**34).

To get the byte string I first turned this number into a decimal and only then in byte-string.

If you know how to avoid this detour and get bytes directly -- please comment

def decode(addr):

    b58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

    def base58_to_dec(addr):
        dec = 0
        for i in range(len(addr)):
            dec = int(dec * 58 + b58.index(addr[i]))
        print('Decimal representation')
        print(dec)
        return(dec)

    def dec_to_byte(dec):
        out = ''
        while dec != 0:
            print(dec)
            remn = dec % 256
            dec = int((dec - remn) / 256)
            temp = hex(remn)
            if len(temp) == 3:
                temp = '0' + temp[-1]
            else:
                temp = temp[2:]
            out = temp + out
        return(out)

    dec = base58_to_dec(addr)
    out = dec_to_byte(dec)
    return (out)

decode("mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa")
>> Decimal representation
>> 700858390993795610399098743129153130886272689085970101576715
>> '6fa7370638600000000000000000000000000000000000000b'

That output looks somewhat like what I need (a7370638...) but has way too many zeroes. Don't look that the first byte (6f) doesn't match: it has nothing to do with Hash160 we need, just protocol version.

This is likely a precision error. To deal with it, I used mpmath which lets you operate with integers precisely.

import mpmath as mp
mp.dps = 1000

def decode(addr):

    b58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

    def base58_to_dec(addr):
        dec = 0
        for i in range(len(addr)):
            dec = int(dec * 58 + b58.index(addr[i]))
        return(dec)

    def dec_to_byte(dec):
        out = ''
        while dec != 0:
            remn = mp.mpf(dec % 256)
            dec = mp.mpf((dec - remn) / 256)
            temp = hex(int(remn))
            if len(temp) == 3:
                temp = '0' + temp[-1]
            else:
                temp = temp[2:]
            out = temp + out

        return (out)

    dec = base58_to_dec(addr)
    out = dec_to_byte(dec)
    return (out)

Apply precise modulo operation and we can finally get the Hash160. Just make sure you trip the first and last 4 bytes that carry fat-finger check.

x = decode('mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa')
print(x)
>> 6fa73706385fffbf18855f2aee2a6168f29dbb597ef59c240b

print(x[2:-8])
>> a73706385fffbf18855f2aee2a6168f29dbb597e

Yay! Just like in the transaction!

1
  • I've tested the last code example and it prints the wrong value. print(decode('mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa')) prints 6fa7370638600000000000000000000000000000000000000b instead of 6fa73706385fffbf18855f2aee2a6168f29dbb597ef59c240b (Python 3).
    – kenorb
    Feb 23, 2022 at 23:18
0

Install base58 package (Ubuntu: sudo apt-get install base58) then run the following:

$ printf mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa | base58 -d | xxd -p
6fa73706385fffbf18855f2aee2a6168f29dbb597ef59c240b

To decode into address, use:

$ printf mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa | base58 -d | cut -c2- | xxd -p | head -c40
a73706385fffbf18855f2aee2a6168f29dbb597e
0

Python 3

Install base58 (pip3 install base58) and use the following code:

import base58
print(base58.b58decode_check(b'mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa').hex()[2:])

One-liner:

$ echo 'import base58; print(base58.b58decode_check(b"mvm74FACaagz94rjWbNmW2EmhJdmEGcxpa").hex()[2:])' | python3
a73706385fffbf18855f2aee2a6168f29dbb597e

Script to convert file with list of addresses to the corresponding hashes:

while read addr; do python3 -c "import base58; print(base58.b58decode_check(b'$addr').hex()[2:])"; done < btc.csv > btc.hash

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.