Compressed public key → point, hash160, addresses
Decompress a 33-byte public key back into a full curve point: recover Y from X, verify it really lies on secp256k1, then hash and encode it into P2PKH, P2SH-P2WPKH, P2WPKH and P2TR addresses. The uncompressed form of the same point is shown alongside, because it produces a different address — and no private key is used anywhere.
Enter a value above and press Convert. Not sure what to type? Press “Use example” to load the sample from the placeholder.
What this page computes
You give it a compressed public key — 66 hex characters, starting with
02 or 03 — and it does two things a wallet does every time it loads an
address: it decompresses the key back into a full curve point, verifies that the point really
lies on secp256k1, and then hashes and encodes it into every address type it can produce. No private
key is involved anywhere in this page, and none is needed.
It also shows the uncompressed serialization of the very same point and the different address that comes out of it, because the compressed/uncompressed distinction is the most common source of "my wallet shows the wrong balance" confusion in Bitcoin.
1. The 02/03 prefix is a parity bit
A public key is a point (X, Y) on the curve y² = x³ + 7 over the prime field
p. Storing it naively costs 64 bytes. In 2012 the ecosystem switched to storing only
X plus one bit, since Y can be recomputed exactly. That bit rides in the first
byte:
| Prefix byte | Total length | Meaning |
|---|---|---|
0x02 | 33 bytes (66 hex) | compressed encoding; the true Y is even |
0x03 | 33 bytes (66 hex) | compressed encoding; the true Y is odd |
0x04 | 65 bytes (130 hex) | uncompressed: 04 ‖ X ‖ Y, both coordinates stored explicitly |
0x06/0x07 | 65 bytes | hybrid, legacy: X ‖ Y plus a redundant parity bit that must agree with Y |
The bit is not a checksum and not a version number. It is the answer to exactly one question: "which of the two possible points with this X do I mean?"
2. How decompression actually works
For a given X, the curve equation becomes an equation in one unknown:
A quadratic over a field has at most two solutions, and if one is Y the other is always
p − Y, since (p − Y)² = Y² mod p. The two roots always have
opposite parity: p is odd, so p − Y ≡ −Y (mod 2) flips the lowest bit.
One even root, one odd root — precisely why a single bit suffices.
Note that a random X has no square root about half the time: only about half of all field
elements are quadratic residues, so roughly half of all 33-byte strings starting with 02 or
03 are not valid public keys at all.
Finding the root is usually done by trial (Tonelli–Shanks), but secp256k1's prime has a special shape that gives a closed form. Because
for any quadratic residue a. The reason is short: if y = a(p+1)/4
then y² = a(p+1)/2 = a · a(p−1)/2, and Euler's criterion says
a(p−1)/2 ≡ 1 whenever a is a residue. So we can go straight to a
modular exponentiation with the fixed exponent
and then replace it with p − Y if the parity is wrong. On secp256k1 p mod 4 = 3
exactly, so the shortcut is always available; curves with p ≡ 1 (mod 4) need the slower general
algorithm.
It is tempting to think the 33-byte form "loses" something. It does not: the encoding maps bijectively
onto the valid keys with that X. Nothing is recovered from the private key — the information was never
removed, only recomputed from the curve equation. Both 02 ‖ X and 04 ‖ X ‖ Y
describe the same point.
3. Why an off-curve point must be rejected
A curve equation is a test, not a hint: a pair (X, Y) either satisfies
Y² = X³ + 7 (mod p) or it does not, and nothing is "almost" on the curve. If the check fails,
the number you hold is not a public key — it is not an element of the group, so scalar
multiplication with it is undefined and every protocol above it is fed garbage.
That matters because of a family of attacks known collectively as invalid curve attacks. Suppose a protocol takes a point supplied by the other party and multiplies it by a secret scalar (this is what ECDH does). If the point is not validated, an attacker can send points that lie on a different curve, or in a subgroup of small order. The result of the multiplication then depends on only a few bits of the secret. By trying many such points and combining the partial answers with the Chinese Remainder Theorem, the attacker reconstructs the whole private key. Several TLS and smart-card implementations have been broken this way; the defence is one line of code — validate every externally supplied point before use.
The honest caveat: secp256k1 has cofactor 1 and prime order n. There are
no small subgroups, and no weak twist of smooth order, so the textbook small-subgroup version of the
attack has nothing to grab hold of on this curve. Bitcoins are not lost simply because someone
once parsed a bad point. But the discipline is not optional, for three practical reasons:
- The check is trivial — one cubing, one addition, one squaring. There is no performance argument for skipping it.
- Security proofs assume it. Schemes are proven over valid group elements; accepting invalid ones voids the proof, and the concrete break tends to be found by someone else later.
- Real software is not a single curve. A library that accepts off-curve points is one refactor away from a curve where it matters, and the bug will not announce itself.
This page parses the point, checks the equation, and refuses to continue if it fails. An input of
02 followed by 64 zeroes is a good test: it is rejected with a readable message rather than
silently deriving addresses from a meaningless number.
The canonical SEC1 encoding requires 0 < X < p. Strict decoders — including
libsecp256k1, which Bitcoin consensus relies on — reject an X that is greater than or equal
to p as non-canonical. This teaching implementation instead reduces X modulo
p and continues, so a handful of numerically odd inputs are accepted here that a consensus
parser would reject. That is a deliberate simplification, and it is the kind of difference that matters
when you compare two implementations byte for byte.
4. Addresses need no private key — and that has consequences
Everything below this point is public arithmetic: hash the serialized public key, encode the hash.
Nothing in that pipeline needs the scalar k, and nothing in it can be inverted to find
k.
- Watch-only wallets. Give a wallet a public key or an extended public key (xpub) and it can generate and monitor addresses forever without holding a spending key; the seed stays on a hardware device.
- Anyone who learns your public key learns all your addresses. A 33-byte public key
sits in the
scriptSigof every P2PKH input you spend, so from one transaction an observer can derive all four address types here and watch them forever. Publishing a key is safe for the funds — the discrete logarithm protects them — but fatal to privacy. - This is how blockchain analysis works. Address clustering — "these two addresses belong to one entity" — is built on exactly this cheap, public derivation. It is why HD wallets use one key per address (BIP32), and why Taproot hides the key behind a tweak.
- It is why quantum risk is asymmetric. A public key already on the chain is exposed to Shor's algorithm; an unspent hash-based address that never revealed its key is not — until it spends.
5. Compressed versus uncompressed
Both forms are valid encodings of the same point and both are accepted by consensus, yet they produce different addresses — the address is a hash of the serialized bytes, and those differ.
| Compressed | Uncompressed | |
|---|---|---|
| Serialization | 02/03 ‖ X | 04 ‖ X ‖ Y |
| Length | 33 bytes | 65 bytes |
| hash160 of the demo key | 21f57b6debcfc5b67182dfb4af1641f0a8189012 | 3f0e966dc089c611a9c1d03bd4f69ea53b0223f9 |
| P2PKH address of the demo key | 146ZNjH7XTXnMd1ofe3z7CL2i8WM7N2UqT | 16kR3eiswUY6tmGnxZhwQmW6LAvTLGWG2G |
| scriptSig when spending | ≈107 bytes (push + ~72-byte signature + 33-byte key) | ≈139 bytes (same, with a 65-byte key) |
| Usable in P2WPKH / P2SH-P2WPKH / P2TR | Yes — the only form allowed | No; witness programs are defined over compressed keys |
| Status today | The default everywhere (since 2012) | Legacy, but still valid and spendable |
Because the uncompressed key is 32 bytes longer and embeds directly in the input script, spending from such an address costs more in fees — the whole reason the compressed form won.
6. Worked example — the real demo key
Every value below is what this page computes for the sample in the input box
(02ff812e…67ee80):
| Step | Value |
|---|---|
| Input, 66 hex characters | 02ff812e26116a9aa140abe629e7f8a38401653caf925119def59ed3758c67ee80 |
| Prefix byte | 02 → Y is even |
| X (the 64 characters after the prefix) | ff812e26116a9aa140abe629e7f8a38401653caf925119def59ed3758c67ee80 |
| X³ + 7 mod p (the value to take a square root of) | 174ca53008928cd3c9c380e12c4b1dee0c5a0b6d851b1c472a58d744ca28c6dc |
| Recovered Y — the even root | 8f49cde13c7ffd64f1d730bf87a743a578eda4971ddac4a08118160732b424ae |
| Last hex digit of Y | e → even, which matches the 02 prefix |
| The other root, p − Y | 70b6321ec380029b0e28cf407858bc5a87125b68e2253b5f7ee7e9f7cd4bd781 |
| Last hex digit of p − Y | 1 → odd, as expected for the twin root |
| On-curve check | PASS — Y² equals X³ + 7 mod p |
| hash160 (compressed) | 21f57b6debcfc5b67182dfb4af1641f0a8189012 |
| P2PKH (compressed) | 146ZNjH7XTXnMd1ofe3z7CL2i8WM7N2UqT |
| P2SH-P2WPKH | 3DZT76KyKyc5zkzvLugP8umgt4RPY4XPQw |
| P2WPKH | bc1qy86hkm0telzmvuvzm76279jp7z5p3yqjnaerk6 |
| P2TR | bc1pmsrhxnvmj8tjxxu9wj69ntvwvhvr28qufyc26tshz3rjj777tx3sh54u63 |
The uncompressed counterpart of the same point is the 130-character string
04ff812e…b424ae, which produces a completely different address:
16kR3eiswUY6tmGnxZhwQmW6LAvTLGWG2G.
Finally, flip the prefix bit: same X, declared 03. The page recovers the
odd root 70b6321e…cd4bd781, the negation of the original point. Its hash160 is
bf2aa927b06822b645d48e54ac82676c2e586675 and its P2PKH address is
1JRoAmA3S9nPbucndLyARsmY2Awuzgdazb. If P = k·G, this twin is
(n − k)·G — one bit in the encoding, a different key entirely.
7. What happens next in the chain
- P2PKH (
1…) —Base58Check(0x00 ‖ hash160). See hash160 → addresses. - P2SH-P2WPKH (
3…) —Base58Check(0x05 ‖ hash160(0x0014 ‖ hash160)). For the demo key the inner script hash is823333d5fd23a83661e8e47629552c4a5bfc8b6e. - P2WPKH (
bc1q…) —bech32("bc", 0, hash160), and the witness program is the hash160 itself, unchanged. - P2TR (
bc1p…) — a different construction: the x coordinate is tweaked withTapTweak, giving an output keyQ = P + t·G. For the demo key the tweak isbae9168523281e7f9e91d897a8d23f7ae6fe66d61854019dcb8abb5185e8fa4d. See bech32 / bech32m decoder.
8. Security notes
- A public key is meant to be public — it reveals nothing usable about the private key while the discrete logarithm problem stays hard. But it is not private: every address type here is derivable from it, forever, by anyone.
- Always validate received points. Off-curve points are not keys. Reject them at the boundary, as this page does, before any arithmetic touches them.
- Quantum computing is the long-term exception. Shor's algorithm solves the discrete logarithm directly, so a key already revealed on-chain is the exposed case — not an unspent hash-based address.
9. Common mistakes
- Treating the compressed key as a different key from the uncompressed one. They are two serializations of one point and one private key. Only the address differs.
- Mixing up the two hash160 values.
21f57b6d…and3f0e966d…are both "the hash160 of the demo key" — of its compressed and uncompressed forms respectively. Always say which. - Assuming
02says something about the private key. It is the parity ofY— a property of the point, not ofk. - Assuming any 33-byte
02/03string is a valid key. Only about half have a square root forX³ + 7; the rest are rejected. - Confusing the public key with the address. The 66-character key is never an address; the address is the 20-byte hash of the key, encoded.
- Forgetting that the parity bit changes the key.
02 ‖ Xand03 ‖ Xare different points (negatives of each other) with different private keys (kandn − k) and different addresses.
10. Quick reference
| Item | Value |
|---|---|
| Input | 66 hex characters: 02 or 03 followed by a 32-byte X |
| Prefix meaning | 02 = even Y, 03 = odd Y |
| Field prime p | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F |
| p mod 4 | 3 — so the closed-form square root applies |
| Root exponent (p+1)/4 | 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFFF0C |
| Two roots | Y and p − Y, always of opposite parity |
| On-curve test | Y² ≡ X³ + 7 (mod p) |
| Outputs | X, Y, parity, hash160, P2PKH (compressed and uncompressed), P2SH-P2WPKH, P2WPKH, P2TR |
| Private key needed? | No — none of this is reversible to k |