# How to uncompress a public key?

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

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

02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27

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

18p4JBHP3EAKCc4jqN8XzuEJtvq8G9NAot
1Dh8oSChJWZQx5sr7ePsBNtw7uKMsNnYNC

with transaction output being :

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

p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

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

print(binascii.hexlify(decompress_pubkey(binascii.unhexlify('02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27'))).decode())

The uncompressed public keys are:

04f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f274d335b71c85e064f80191e1f7e2437afa676a3e2a5a5fafcf0d27940cd33e4b4

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:

02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27

have the following hashes respectively:

946cb2e08075bcbaf157e47bcb67eb2b2339d242
55af2ea3c45819c6c5ae710d29fcaaced5b00cc7
8b38a8d40e08362046dee55c1c94e7991d7dec75

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

1EXoDusjGwvnjZUyKkxZ4UHEf77z6A5S4P
18p4JBHP3EAKCc4jqN8XzuEJtvq8G9NAot
1Dh8oSChJWZQx5sr7ePsBNtw7uKMsNnYNC

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.append(b58_digits[r])
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
for c in b:
if c == czero: pad += 1
else: break
return b58_digits[0] * pad + res

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

pk2 = binascii.unhexlify("02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27")

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

(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.

• @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. Commented Apr 23, 2019 at 18:04
• @Andrew Chow This is not exactly right, you're implementing "03 means nonsquare Y, 02 means square Y". It should odd/even. Commented Apr 23, 2019 at 18:24
• @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. Commented Apr 23, 2019 at 18:27
• It should be more like: y = y if y%2 == pk[0]%2 else p-y Commented Apr 23, 2019 at 18:37
• @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. Commented Apr 23, 2019 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();