Skip to content
DonateБГ

Keys and addresses

Public key

How a Bitcoin public key is made from a private key on the secp256k1 curve, and its compressed, uncompressed and x-only forms.

By Dimitar Zurnov · Updated 4 Nov 2026
Adapted from Learn Me a Bitcoin by Greg Walker.

You make a Bitcoin public key by multiplying a fixed point on the secp256k1 elliptic curve by your private key. The result is a point on that curve. It is the public half of the pair, so you can share it safely. Coins can be locked to a public key, and only the matching private key can unlock them. Bitcoin writes a public key as hex in one of three forms: compressed, uncompressed or x-only.

How is a public key made?

A public key comes from elliptic curve multiplication. The curve has a fixed starting point called the generator point, G. You multiply G by your private key, a number from 1 to n-1, and the result is a new point on the curve. That point is the public key. Multiplying is quick. Working backwards from the point to the private key is not practical with any known method. The elliptic curve page covers the maths.

The parameters of secp256k1 are published in SEC 2. This short script multiplies G by a private key using no library. It is slow, so it is for learning only. Real code should use a tested library.

# secp256k1 field prime and generator point
P = 2**256 - 2**32 - 977
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
     0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)

def add(a, b):
    if a is None: return b
    if b is None: return a
    if a[0] == b[0] and (a[1] + b[1]) % P == 0: return None
    if a == b: s = 3 * a[0] * a[0] * pow(2 * a[1], -1, P) % P
    else: s = (b[1] - a[1]) * pow(b[0] - a[0], -1, P) % P
    x = (s * s - a[0] - b[0]) % P
    return (x, (s * (a[0] - x) - a[1]) % P)

def public_key(k):
    """Multiply the generator point G by the private key k."""
    point, result = G, None
    while k:
        if k & 1: result = add(result, point)
        point = add(point, point)
        k >>= 1
    return result

k = int("dbf34ebb797c7b62960c0c7d1784c3209d5de7abf307df4a07dac9c76f22ee72", 16)
x, y = public_key(k)
prefix = "02" if y % 2 == 0 else "03"
print(prefix + f"{x:064x}")  # 02cd33111c7fc75b33bc30ad959dd27433d631dee5bf072733334163dedc403070

The example key is the one from the private key page. Its compressed public key is 02cd33111c7fc75b33bc30ad959dd27433d631dee5bf072733334163dedc403070. Do not use this key with real coins, because anyone can see it.

What are the three forms of a public key?

One public key in three forms: a compressed key of 33 bytes, an uncompressed key of 65 bytes and an x-only key of 32 bytes. Compressed: 33 bytes The default. A prefix of 02 or 03, then x. 02 or 03 x: 32 bytes 02cd33111c…dedc403070 Uncompressed: 65 bytes Legacy. Still valid in old scripts. 04 x: 32 bytes y: 32 bytes 04cd33111c…ea9c198538 x-only: 32 bytes Taproot, BIP 340. No prefix, so y is even. x: 32 bytes cd33111c7f…dedc403070
The same point written three ways. Compressed is the default today.

Every form starts with the same x coordinate, a 32-byte number. The forms differ in what they add to it. A compressed key adds a one-byte prefix that records the parity of y. An uncompressed key adds the full y coordinate. An x-only key drops the prefix entirely.

What is a compressed public key?

A compressed public key is 33 bytes. It holds the full x coordinate, plus a prefix that says whether y is even or odd. The prefix is 02 when y is even and 03 when y is odd.

The curve is symmetric, so for any x that lies on the curve there are only two possible y values. They have opposite parity, one even and one odd. The prefix is enough to pick the right one, so the key stores the prefix and not the whole y. Our example key has an even y, so its prefix is 02.

You should use compressed keys by default. They are shorter, and Bitcoin Core's default policy rejects uncompressed keys in SegWit v0 scripts such as P2WPKH and P2WSH. Such transactions are not relayed or mined by default. The check is the SCRIPT_VERIFY_WITNESS_PUBKEYTYPE flag in Bitcoin Core's script interpreter. BIP 143 sets the same rule.

What is an uncompressed public key?

An uncompressed public key is 65 bytes. It starts with 04, followed by the full 32-byte x coordinate and the full 32-byte y coordinate. Early software used this form, before compressed keys were in common use. Our example key in uncompressed form is:

04cd33111c7fc75b33bc30ad959dd27433d631dee5bf072733334163dedc403070002efe73430d2af79531c7d8f64500b5c392db9af29ccfd60e2ca7ea9c198538

Uncompressed keys still work in legacy locking scripts, such as P2PK, P2PKH, P2SH and bare multisig. Default policy rejects them in P2WPKH and P2WSH outputs. So there is little reason to choose them today.

What is an x-only public key?

An x-only public key is the 32-byte x coordinate with no prefix. Taproot uses this form, as defined in BIP 340. Because there is no prefix, the key is read with an even y. Any point with an odd y can be replaced by its even partner, so only x needs to be stored.

Our example key has an even y, so its x-only form is the compressed key without its 02 prefix: cd33111c7fc75b33bc30ad959dd27433d631dee5bf072733334163dedc403070. A Taproot output locks to a tweaked version of this key. The Taproot word page explains the upgrade, which has been live since block 709,632.

How do you decompress a public key?

You can rebuild the full uncompressed key from a compressed one. The secp256k1 curve follows the equation y² = x³ + 7, taken modulo the field prime p. You know x, so you solve for y. The answer is a square root, which has two values, one even and one odd. The prefix tells you which to keep.

Because p is 3 modulo 4, the square root is one exponentiation. The short script below rebuilds the uncompressed key from the compressed one.

P = 2**256 - 2**32 - 977

compressed = "02cd33111c7fc75b33bc30ad959dd27433d631dee5bf072733334163dedc403070"
x = int(compressed[2:], 16)

# y^2 = x^3 + 7 (mod P); P is 3 mod 4, so a square root is one power away
y = pow((x**3 + 7) % P, (P + 1) // 4, P)

# the prefix says whether y must be even (02) or odd (03)
if (y % 2 == 0) != (compressed[:2] == "02"):
    y = P - y

print("04" + f"{x:064x}" + f"{y:064x}")
Decompressing: the prefix picks the even or odd root of the curve equation, giving the uncompressed key. Start: the compressed key 02cd33111c…dedc403070 y² = x³ + 7 (mod p) Solve for y two roots: one even, one odd Even y: keep 002efe73…9c198538 picked by prefix 02 Odd y: drop ffd1018c…63e676f7 not used with 02 Uncompressed key: 65 bytes 04cd33111c…ea9c198538 04, then x, then the even y
The even root is the y of our example key. The odd root is the other point with the same x.

Where do public keys appear on the blockchain?

A public key usually appears on the blockchain only when its coins are spent. The exception is a lock that holds the full key, such as P2PK or bare multisig. The table shows where it sits for three common types of output.

Locking script Where the public key sits Real example
P2PK Written in full in the scriptPubKey, so it is public as soon as the coins arrive 18a16d32…ee444, a coinbase output in block 150,007
P2PKH Only a hash is in the lock. The full key is revealed in the scriptSig when spent faf2987f…0808c, which spends a P2PKH output in block 770,002
P2WPKH Only a hash is in the lock. The key is in the witness when spent 1674761a…4a6, which spends a P2WPKH output in block 682,085

P2PK is the simplest lock, but it exposes the key from the start. P2PKH and P2WPKH hide the key behind a hash until you spend. A Taproot output is different again: it shows a tweaked version of the key from the start, not the key you hold.

How does a public key receive bitcoins?

When someone pays you, the output is locked to your public key or to its hash. Only a signature made with the matching private key can unlock it. The network checks that signature against the key in the lock, and it never needs your private key.

Most wallets show you an address rather than the raw key, because an address is shorter and easier to copy. The address is built from the public key hash, which is the next page in this section.

Which key should you use?

Keep the private key secret. The public key is the part you can publish. For legacy and SegWit v0 outputs, use compressed keys, which are 33 bytes. Taproot uses the x-only form of the same point, which is the compressed key without its prefix.

The next page, public key hash, shows how this key is hashed into the value that most addresses carry.

In this section: Keys and addresses

All of Bitcoin from zero

Something look wrong? Tell us