Schnorr Signatures
Bitcoin Core Test Framework & Attribution
The code examples in this article are adapted from the Bitcoin Optech Taproot Workshop.
Some code here uses Bitcoin Core's functional test framework.
Working with this will not only help you understand Schnorr signatures but also give you practical experience with tools used in Bitcoin Core development, helping you take your first steps toward contributing to Bitcoin Core.
In the last two lessons we built the pieces: tagged hashes, and x-only public keys.
Now we put them together into a signature. This is the signature Taproot uses, and it's the only thing a key path spend contains.
We have two main characters to help us understand how Schnorr signatures work:
Alice (The signer): wants to sign a message using Schnorr and send it to Bob.
Bob (The verifier): wants to verify that the message is truly signed and comes from Alice.
The interaction between Alice and Bob involves two key steps: Signature Generation and Signature Verification.
Now, let's follow Alice as she creates a Schnorr signature.
Signature Generation (Alice)
Step 1: Generate Key Pair (d, P)
Alice generates a private key d, then calculates her public key P by multiplying her private key with the generator point:
To see this in action, run the following code to generate Alice's key pair and display the private and public keys.
Step 2: Generate Nonce and Nonce Commitment (k, R)
The nonce, referred to as k, is a random value that Alice generates during the signature process.
Its purpose is to introduce randomness into each signature.
Why do we need a nonce?
Answer: The nonce ensures that even if Alice signs the same message multiple times, each signature will be different. Without it, the same key and message would always produce the same s, and the private key would fall straight out of the algebra.
Alice generates the nonce k and computes the Nonce Commitment
k is a scalar, R is a point on the elliptic curve.
Nonce Reuse Attack
Reusing a nonce (k) across two signatures is fatal. It leaks the private key in four lines of algebra.
That is the whole subject of the next lesson.
Note on the y-coordinate of R
R is a point, but only its x-coordinate goes into the signature. So the same rule from the X-only Public Keys lesson applies here:
- R must have an even y coordinate
- if
kgives an odd-y R, negate it and usen − kinstead
Same for the key: P must have an even y, and if it doesn't, Alice uses n − d.
Two negations, one rule. Everything that travels as 32 bytes must be even-y so it can be lifted back to exactly one point.
Let's generate a random nonce k and calculate its associated point R.
This example shows how to check if R's y-coordinate (and its negative, -y) is even or odd.
Step 3: Challenge Computation (h)
Alice computes the challenge hash h:
The challenge hash is created by concatenating R, P, and the message m.
In BIP340 this is a tagged hash, with the tag BIP0340/challenge.
Why is P in there ?
This is called key prefixing, and it matters a lot for Taproot.
Without P in the hash, anyone can take a signature for key P and turn it into a valid signature for the key P + a·G, for any tweak a they choose. That would break every scheme built on adding a tweak to a key, which is exactly what Taproot does.
Putting P inside the hash closes that.
Step 4: Challenge Response (s)
Using the challenge hash, Alice calculates the challenge response s:
Step 5: Create the Signature
The final signature is R.x followed by s. Only the x-coordinate of R is used, which is why R had to have an even y.
sig = R.x || s 32 + 32 = 64 bytesAlice sends the message m and the signature to Bob.
A note on notation
We call the challenge hash h. BIP340 and most implementations call it e. Same value, different letter, don't be surprised when you open the spec.
Hands-On: Complete the Code to Generate a Schnorr Signature
Below is a code snippet that generates a Schnorr signature, but some key parts are missing. Follow the instructions in the code to complete it so that it runs successfully and displays a "Success!" message at the end.
Note: You'll see a method called
tagged_hashin the code. If you need a refresher, see Tagged Hashes.
Solution code
Nonce Generation for Schnorr Signatures
In the previous example, we used a random nonce, which depends on a secure random generator. If that generator is compromised, it can expose the private key.
Why is Nonce Generation Important?
If the randomness in nonce generation is compromised, it can reveal the private key used in signing. To avoid this risk, BIP340 suggests a deterministic method for generating nonces.
Steps to Generate a Nonce
Compute
t: XOR the bytes of the private key (d) with a tagged hash of auxiliary random data (a).Generate
rand: Hash the tagged data witht, the public key (P), and the message (m).Calculate
k(Nonce): Setk = int(rand) mod n, wherenis the order of the curve. This ensures a unique, unpredictable nonce for each signature.
Notice the tags: BIP0340/aux and BIP0340/nonce. Two of the three tags we listed in the Tagged Hashes lesson show up right here.
Coding Exercise: Nonce Generation
To better understand how nonce generation works, try creating a Schnorr signature using BIP340's nonce scheme. Follow the instructions in the code snippet below to implement the nonce generation steps yourself.
Solution code
Verification Process (Bob)
Bob wants to ensure that the message hasn't been compromised during transmission and that it's genuinely signed by Alice.
To verify this, Bob checks if the following verification equation holds:
If the equation is valid, Bob can be confident that the signature was indeed created by Alice.
All the information needed for verification is already known to Bob:
- s: Sent by Alice as part of the signature, so Bob has this value.
- G: A constant that is well-known within the Bitcoin protocol.
- h: Bob computes
h = H(R || P || m). Since he hasR,P, andm, he can calculateh. - P: This is Alice's public key, which Bob knows in advance.
Notice that Bob only receives R.x, not the full point. He recovers R with lift_x, and it works because Alice made sure R had an even y.
Once Bob has confirmed that the equation holds, he can be fully assured that the message is authentic, has not been tampered with, and truly originated from Alice.
Where this shows up in Taproot
That 64-byte signature is the whole unlocking data of a P2TR key path spend. Nothing else.
Pbecomes Q, the tweaked key sitting in the scriptPubKeymbecomes the transaction sighashR.x || sbecomes the single witness item
We'll build all three in the next sections.
Before that, one more thing about k. We said reusing it is fatal. Next lesson shows exactly how fatal.
