Simple question but I couldn’t find the answer anywhere.

What’s the 65 bytes version of those public keys ?


which for reference yields according to Bitcoin core getrawtransaction 5ed3694e8a4fa8d3ec5c75eb6789492c69e65511522b220e94ab51da2b6dd53f 1 "00000000000000001e76250b3725547b5887329cfe3a8bb930a70e66747384d3" :


with transaction output being :

1 04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28 0229b3e0919adc41a316aad4f41444d9bf3a9b639550f2aa735676ffff25ba3898 02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27 3 OP_CHECKMULTISIG

So that while the first Public key is uncompressed, they are no version uncompressed version of the 2 others keys on the Blockchain which means Bitcoin core is recovering the 2 addresses above from the compressed public keys (the first one being not included because the Public key is uncompressed).


Points are decompressed by solving for y in the equation used for secp256k1's elliptic curve where x is the last 32 bytes of your public key. The equation is y^2 = x^3 + 7. You will get 2 possible y values, one even and one odd. The correct one is indicated by the prefix byte of your public key which indicates whether y is even or odd.

Note that all operations must be modulo p which is defined by secp256k1's spec as being 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F.

This python script will decompress the two public keys that you have posted:

#! /usr/bin/env python3

import binascii


def decompress_pubkey(pk):
    x = int.from_bytes(pk[1:33], byteorder='big')
    y_sq = (pow(x, 3, p) + 7) % p
    y = pow(y_sq, (p + 1) // 4, p)
    if y % 2 != pk[0] % 2:
        y = p - y
    y = y.to_bytes(32, byteorder='big')
    return b'\x04' + pk[1:33] + y


The uncompressed public keys are:


Pubkey decompression is completely different and unrelated to address generation. Addresses are generated by taking a public key as is (either compressed or uncompressed) hashing it, and then encoding it. The compression matters because the resulting serialized public will produce one hash (and thus one address) when it is compressed, and a different hash (and thus different address) when uncompressed.

Addresses are encoded by hashing the serialized public key with SHA256 and then with RIPEMD160. The resulting hash is encoded using Base58 Check encoding.

In the transaction you provided, the three public keys:


have the following hashes respectively:


Encoding those as version 0 addresses with Base 58 Check encoding results in the addresses you are expecting:


The code I used for this is:

#! /usr/bin/env python3

import binascii
import hashlib

b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

def sha256(s):
    return hashlib.new('sha256', s).digest()

def ripemd160(s):
    return hashlib.new('ripemd160', s).digest()

def hash256(s):
    return sha256(sha256(s))

def hash160(s):
    return ripemd160(sha256(s))

def encode(b):
    # Convert big-endian bytes to integer
    n = int('0x0' + binascii.hexlify(b).decode('utf8'), 16)

    # Divide that integer into bas58
    res = []
    while n > 0:
        n, r = divmod (n, 58)
    res = ''.join(res[::-1])

    # Encode leading zeros as base58 zeros
    import sys
    czero = b'\x00'
    if sys.version > '3':
        # In Python3 indexing a bytes returns numbers, not characters.
        czero = 0
    pad = 0
    for c in b:
        if c == czero: pad += 1
        else: break
    return b58_digits[0] * pad + res

def to_address(b, version):
    data = version + b
    checksum = hash256(data)[0:4]
    data += checksum
    return encode(data)

pk1 = binascii.unhexlify("0229b3e0919adc41a316aad4f41444d9bf3a9b639550f2aa735676ffff25ba3898")
pk2 = binascii.unhexlify("02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27")
pk3 = binascii.unhexlify("04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28")

h1 = hash160(pk1)
h2 = hash160(pk2)
h3 = hash160(pk3)

print(to_address(h3, b'\x00'))
print(to_address(h1, b'\x00'))
print(to_address(h2, b'\x00'))

(Base58 encoding function from https://github.com/bitcoin-core/HWI/blob/master/hwilib/base58.py)

As you can see, this does not do any point compression or decompression. Rather it takes the public key as it is and encodes it.

  • 2
    @user2284570 however care must be taken not to interchangeably use the compressed and uncompressed public keys. Addresses generated from uncompressed and compressed public keys are different. – Ugam Kamat Apr 23 '19 at 18:04
  • 2
    @Andrew Chow This is not exactly right, you're implementing "03 means nonsquare Y, 02 means square Y". It should odd/even. – Pieter Wuille Apr 23 '19 at 18:24
  • 1
    @AndrewChow There is a slight error in the code. You need to check the sign of the y-coordinate generated from the modular sqrt and then check if its sign is equal to the one attached to the compressed public key. That is because you do not know whether the y you have generated is odd or even. You are doing it the other way around. – Ugam Kamat Apr 23 '19 at 18:27
  • 1
    It should be more like: y = y if y%2 == pk[0]%2 else p-y – Ugam Kamat Apr 23 '19 at 18:37
  • 1
    @user2284570 Bitcoin Core by default uses compressed public keys. So the bare multi-sig addresses would be generated by base58encode of hash160(M<publickey1>...<PubkeyN>N). Note that if the public keys are uncompressed, the multi-sig address generated will be completely different than what is generated with compressed public keys. – Ugam Kamat Apr 23 '19 at 18:48

If you're looking for a quick utility to compress/uncompress Secp256k1 public keys, try bitcoin-ts:

const {instantiateSecp256k1, hexToBin, binToHex} = require("bitcoin-ts")
const secp256k1 = await instantiateSecp256k1();

const compressed = hexToBin('02ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe');

const uncompressed = secp256k1.uncompressPublicKey(compressed);
// => 04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28

const recompressed = secp256k1.compressPublicKey(uncompressed);
// => 02ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe

Run in the browser →

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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