X-only Public Keys
Remember from the Pay to Taproot section, the scriptPubKey is:
OP_1 OP_PUSHBYTES_32 <32-byte tweaked public key>Did you notice the scriptPubKey here only has 32 bytes !!
Q, our tweaked public key, is a point on an elliptic curve.
A point on an elliptic curve should have . So normally we would have 64 bytes: 32 for x and 32 for y.
In Schnorr we use only the x coordinate, for compactness. We store those 32 bytes and throw the rest away.
The question you might be asking is: how can we compute the y coordinate?
The curve gives it back to us
Secp256k1 is defined by:
So if we have , we can calculate :
Done.
is a square root, so it has two solutions:
Two solutions?
Same as is both and .
And since our curve is on a finite field over , there are no negative numbers. Every element and is an integer in .
So when we write we have to reduce it into that range:
The field prime p
is the field prime of secp256k1.
So the two solutions are:
Now we need to know which of these two is the correct one, so we can reconstruct the point .
How do we know?
The parity trick
One of those two values is even and the other is odd. They can't be both even or both odd.
Why? Because is an odd number.
- if is odd → is even (odd − odd = even)
- if is even → is odd (odd − even = odd)
So our challenge is just to know whether our is odd or even.
The solution is simple. BIP340 has this rule:
an x-only public key always means the point with the even y coordinate
So whenever we read 32 bytes as a public key, we take the point whose y is even.
lift_x (optional)
Three ways to encode a public key
Bitcoin already had two formats before taproot. Worth seeing all three together.
Uncompressed: 65 bytes
0x04 || x || yStore both coordinates. 32 + 32 = 64 bytes, plus one prefix byte 0x04 meaning "uncompressed". This is the legacy format.
Compressed: 33 bytes
0x02 || x → y is even0x03 || x → y is oddWe know y is only worth one bit, so throw y away and keep the bit. That is the whole job of the prefix byte. This is what you see in P2PKH, P2WPKH, xpubs.
But look at it: a full byte to carry 1 bit. 7 bits wasted on every key.
x-only: 32 bytes
xBIP340 turns that bit into a rule instead of data. Nothing left to store.
| format | size | how the y parity is known |
|---|---|---|
| uncompressed | 65 bytes | y is written out in full |
| compressed | 33 bytes | stored in the prefix byte |
| x-only | 32 bytes | fixed by the rule: always even |
An x-only key is just a compressed key with the prefix removed. That is why BIP340 picked "even" and not some other rule: it keeps taproot compatible with every key generation system that already exists.
Don't we lose half the keys ?
Everyone asks this. The answer is no.
Say your private key produces a point with an even . Then we don't have to do anything, we already have what BIP340 requires.
But if produces a point with an odd , we can't use .
Watch what happens if we use instead:
What is n?
is the order of the group: adding to itself times brings you back to . That is the definition of , and it is why private keys live mod and not mod .
Careful not to mix them: is the modulus for the coordinates, is the modulus for the private keys.
Now:
Reducing into the field:
So the private key gives us an even-y point with the same 32 bytes of .
We call this negating the private key.
Every is still reachable. You never lose a key, you just sometimes flip the private key before signing:
d = d if P has even y n − d if P has odd yWhat it costs
lift_x needs a square root, which is a full exponentiation. That makes verification around 10% slower than if we had stored the full 64-byte point. BIP340 says this openly and picks compactness anyway.
That is the right call for Bitcoin. A node stores the output forever, but verifies it once.
Size is not the only reason though. Having one unambiguous point per 32 bytes is also what makes batch verification possible: checking thousands of signatures at once. We'll come back to that at the end of the module.
Recap
65 bytes 0x04 || x || y uncompressed33 bytes 0x02/0x03 || x compressed (parity as data)32 bytes x x-only (parity as a rule)- A point needs x and y, but y is only worth one bit
- The curve equation recovers y, up to a sign
- BIP340 pins the sign by convention: always even
- Negating the private key gets you there when your key lands on the wrong side
One last thing: it isn't only public keys. A Schnorr signature is R.x || s, 32 + 32 = 64 bytes, and that R is x-only for exactly the same reason.
Which is the next lesson.
