3

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

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

0229b3e0919adc41a316aad4f41444d9bf3a9b639550f2aa735676ffff25ba3898
02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27

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

18p4JBHP3EAKCc4jqN8XzuEJtvq8G9NAot
1Dh8oSChJWZQx5sr7ePsBNtw7uKMsNnYNC

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

0

2 Answers 2

11

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('0229b3e0919adc41a316aad4f41444d9bf3a9b639550f2aa735676ffff25ba3898'))).decode())
print(binascii.hexlify(decompress_pubkey(binascii.unhexlify('02f15446771c5c585dd25d8d62df5195b77799aa8eac2f2196c54b73ca05f72f27'))).decode())

The uncompressed public keys are:

0429b3e0919adc41a316aad4f41444d9bf3a9b639550f2aa735676ffff25ba3898d6881e81d2e0163348ff07b3a9a3968401572aa79c79e7edb522f41addc8e6ce
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:

04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28
0229b3e0919adc41a316aad4f41444d9bf3a9b639550f2aa735676ffff25ba3898
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
    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.

18
  • 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
    Commented Apr 23, 2019 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. Commented Apr 23, 2019 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
    Commented Apr 23, 2019 at 18:27
  • 1
    It should be more like: y = y if y%2 == pk[0]%2 else p-y
    – Ugam Kamat
    Commented Apr 23, 2019 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
    Commented Apr 23, 2019 at 18:48
1

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);
console.log(binToHex(uncompressed));
// => 04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28

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

Run in the browser →

Your Answer

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

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