Did you know that you can navigate the posts by swiping left and right?
This part of the tutorial will explore Bitcoin’s public keys. We will not attempt to go into the math behind the cryptography involved but provide the necessary resources for people who want to delve deeper. The tutorial is aimed for people who already have some knowledge of how Bitcoin works at a high-level and want to understand how it works at a low-level.
In ECDSA a public key is generated from the private key. Elliptic curves operate over finite fields and thus all points on the curve are limited to integer coordinates^{1}. The specific curve that Bitcoin uses (secp256k1) is y^{2} = x^{3} + 7 . Then the public key P is generated by multiplying, using elliptic curve multiplication, the private key k with a special constant G called the generator point^{2}: P = k * G
. Elliptic curve multiplication of an integer with a point results in another point in the curve, which is the public key.
The public key is a point P in the elliptic curve. P = (x,y), where both x and y are 32-byte integers. Thus a public key can be expressed with 64 bytes. In Bitcoin, we encode a public key by a prefix that specifies some extra information.
Remember that we can represent a public key in two forms, compressed and uncompressed. This is where we can reduce the size of the blockchain by using the compressed form.
An encoded uncompressed public key is 65 bytes long since it has the two points (32 bytes each) concatenated and a prefix of 0x04 to specify an uncompressed public key.
Since the curve is mirrored in the x axis the y coordinate can only take 2 values (positive/negative) for a specific x. Thus, an encoded compressed public key is only 33 bytes long and has only the x coordinate with a prefix of 0x02 (when y is positive/even) or 0x03 (when y is negative/odd).
Let’s use python-bitcoin-utils library^{3} to construct a private key object from a WIF and use that to create a public key object to show its two forms.
>>> from bitcoinutils.setup import setup
>>> from bitcoinutils.keys import PrivateKey, PublicKey
>>> setup('testnet')
'testnet'
>>> priv = PrivateKey.from_wif('91h2ReUJRwJhTNd828zhc8RRVMU4krX9q3LNi4nVfiVwkMPfA9p')
>>> pub = priv.get_public_key()
>>> pub.to_hex() # default is compressed form
'02c1acdac799fb0308b4b6475ddf7967676759d31484ab55555482472f3bc7c3e7'
>>> pub.to_hex(compressed=False)
'04c1acdac799fb0308b4b6475ddf7967676759d31484ab55555482472f3bc7c3e7addc4cbba6656a4be4bc6933a6af712b897a543a09c4b899e5f7b943d38108a8'
To create the PublicKey from the PrivateKey object we make use^{4} of the python ecdsa library as can be seen in get_public_key() on github. The PublicKey object holds the x and y coordinates and can convert accordingly. It checks if y is even or odd and prefixes it with 0x02 and 0x03 respectively. You can check the code of to_hex() on github.
Another tool that you can use from the command line is BX. It has extensive capabilities including creating WIFs.
$ ./bx wif-to-public 91h2ReUJRwJhTNd828zhc8RRVMU4krX9q3LNi4nVfiVwkMPfA9p
04c1acdac799fb0308b4b6475ddf7967676759d31484ab55555482472f3bc7c3e7addc4cbba6656a4be4bc6933a6af712b897a543a09c4b899e5f7b943d38108a8
$ ./bx wif-to-public cN3fHnPVw4h7ZQSRz2HgE3ko69LTaZa5y3JWpFhoXtAke4MiqVQo
02c1acdac799fb0308b4b6475ddf7967676759d31484ab55555482472f3bc7c3e7
Footnotes:
A finite field is typically accomplished by applying modulo p, where p is a prime number. ↩
This is a special point in the elliptic curve that is pre-defined in secp256k1. ↩
I use this library in several of the University courses and seminars that I teach. It aims to be low-level but with a lot of comments to help students study the more intricate details. In contrast, other low-level libraries don’t elaborate enough in the comments and others are higher-level libraries abstracting away a lot of the details that we are trying to decompose and understand in the lessons. ↩
It is always recommended to reuse well-tested cryptography libraries than implementing your own. ↩