Skip to content

Add Schnorr wizardry cheat sheet #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ A collection of some in-depth articles about the Lightning Network:
* [Lightning transactions: from Zero to Hero](./lightning-txs.md)
* [Pinning attacks](./pinning-attacks.md)
* [Sphinx onion encryption: from Zero to Hero](./sphinx.md)
* [Schnorr Wizardry](./schnorr.md)
* [Spamming the Lightning Network](./spam-prevention.md)
* [Feature flags: from Zero to Hero](./feature-flags.md)
233 changes: 233 additions & 0 deletions schnorr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# Schnorr Wizardry

Bitcoin added support for schnorr signatures with the Taproot update, which activated at block 709 632.
In this article, we dive into how it works and some of the advanced schemes it unlocks.

## Table of Contents

* [Schnorr signatures](#schnorr-signatures)
* [Linearity of Schnorr signatures](#linearity-of-schnorr-signatures)
* [Adaptor signatures](#adaptor-signatures)
* [Musig2](#musig2)
* [Musig2 adaptor signatures](#musig2-adaptor-signatures)

## Schnorr signatures

Schnorr signatures are specified in [BIP 340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
Ignoring many details described in the BIP, at a high level the signing algorithm works as follows:

```text
P = k*G -> signer's public key
m -> message to sign

Sign:
r = {0;1}^256 -> random nonce
R = r*G
e = H(R || P || m)
s = r + e*k
(s, R) -> message signature

Verify:
e = H(R || P || m)
R' = s*G - e*P
If R = R', the signature is valid
```

## Linearity of Schnorr signatures

A very interesting property of schnorr signatures compared to ECDSA signatures is that they make it easy to combine signatures for a given message if we slightly change the signing algorithm.

```text
A = a*G -> Alice's public key
B = b*G -> Bob's public key
P = A + B -> combined public key
m -> message to sign

Sign:
ra = {0;1}^256 -> random nonce generated by Alice
RA = ra*G -> public partial nonce sent by Alice to Bob
rb = {0;1}^256 -> random nonce generated by Bob
RB = rb*G -> public partial nonce sent by Bob to Alice
R = RA + RB -> public nonce
e = H(R || P || m)
sA = ra + e*a -> partial signature produced by Alice
sB = rb + e*b -> partial signature produced by Bob
s = sA + sB
(s, R) -> combined message signature

Verify:
e = H(R || P || m)
R' = s*G - e*P
If R = R', the signature is valid
```

You should notice that the verification algorithm hasn't changed.
This means that the verifier doesn't even know that multiple signers were involved: the signature looks like it comes from a standard single signer.

:warning: This signing scheme is not secure when Alice or Bob is malicious. We will describe a secure signing scheme in the Musig2 section below.

## Adaptor signatures

The linearity of schnorr signatures also allows revealing a secret through a signature.

```text
P = k*G -> signer's public key
T = t*G -> tweak (t is the secret that will be revealed)
m -> message to sign

Sign:
r = {0;1}^256
R = r*G
e = H(R + T || P || m) -> notice that we tweak the nonce here with T
s = r + e*k -> but we don't tweak it here with t
(s, R, T) -> adaptor signature: will automatically reveal t when a valid signature for nonce R + T is produced

Verify:
e = H(R + T || P || m)
R' = s*G - e*P
If R = R', the adaptor signature is valid
Note that (s, R) or (s, R + T) are not valid schnorr signatures

Complete:
s' = s + t
R' = R + T
(s', R') -> valid schnorr signature

Extract:
e' = H(R' || P || m)
R'' = s'*G - e'*P
If R'' = R', the signature is valid
t = s' - s
The verifier has learnt t through the schnorr signature
```

NB: for this to work, the verifier must ensure that the nonce `R + T` is fixed beforehand.
Otherwise the signer may create a valid signature for an unrelated nonce, which would not reveal the secret `t`.
This is most useful when combined with Musig2, where participants commit to the nonce before signing.

Adaptor signatures can also be used in the opposite way.
If the signer doesn't know the secret `t`, it can produce an adaptor signature.
Then another participant that knows `t` can convert that adaptor signature to a valid signature.

## Musig2

[Musig2](https://eprint.iacr.org/2020/1261.pdf) is a secure scheme for combining multiple signatures into a single schnorr signature.
It only needs two rounds of communications between participants, and the first round can be done ahead of time (independently of the message to sign).
The novel idea in that scheme is to use multiple nonces for each participant and combine them in a smart way to produce the final nonce.
This results in an elegant scheme that looks very similar to standard schnorr signing.

```text
PA = pa*G -> public key of participant A
PB = pb*G -> public key of participant B
L = sorted(PA, PB) -> sorted list of participants public keys
P = H(H(L) || PA)*PA + PB -> combined public key

NonceGenA (run by participant A):
ra1 = {0;1}^256
ra2 = {0;1}^256
RA1 = ra1*G
RA2 = ra2*G

NonceGenB (run by participant B):
rb1 = {0;1}^256
rb2 = {0;1}^256
RB1 = rb1*G
RB2 = rb2*G

NonceExchange (communication round 1):
Participant A sends RA1, RA2 to participant B
Participant B sends RB1, RB2 to participant A

SignA (run by participant A):
x = H(P || RA1 + RB1 || RA2 + RB2 || m)
R = RA1 + RB1 + x*(RA2 + RB2)
ra = ra1 + x*ra2
e = H(R || P || m)
sa = ra + e*H(L || PA)*pa
(sa, R) -> participant A's partial signature

SignB (run by participant B):
x = H(P || RA1 + RB1 || RA2 + RB2 || m)
R = RA1 + RB1 + x*(RA2 + RB2)
rb = rb1 + x*rb2
e = H(R || P || m)
sb = rb + e*H(L || PB)*pb
(sb, R) -> participant B's partial signature

Combine (communication round 2):
s = sa + sb
(s, R) -> valid schnorr signature for public key P

Verify:
e = H(R || P || m)
R' = s*G - e*P
= (sa + sb)*G - e*P
= (ra + e*H(L || PA)*pa)*G + (rb + e*H(L || PB)*pb)*G - e*P
= (ra + rb)*G + e*(H(L || PA)*pa + H(L || PB)*pb)*G - e*P
= R + e*P - e*P
= R
-> this is a valid schnorr signature
```

NB: we used only two participants to simplify the example, but Musig2 works with any number of participants.

## Musig2 adaptor signatures

Musig2 can be combined with adaptor signatures:

```text
# The first round (pre-computing nonces) is vanilla Musig2

PA = pa*G -> public key of participant A
PB = pb*G -> public key of participant B
L = sorted(PA, PB) -> sorted list of participants public keys
P = H(H(L) || PA)*PA + PB -> combined public key

NonceGenA (run by participant A):
ra1 = {0;1}^256
ra2 = {0;1}^256
RA1 = ra1*G
RA2 = ra2*G

NonceGenB (run by participant B):
rb1 = {0;1}^256
rb2 = {0;1}^256
RB1 = rb1*G
RB2 = rb2*G

NonceExchange (communication round 1):
Participant A sends RA1, RA2 to participant B
Participant B sends RB1, RB2 to participant A

# The second round simply tweaks the combined nonce with the secret

T = t*G -> secret t known only by B

SignA (run by participant A):
x = H(P || RA1 + RB1 + T || RA2 + RB2 || m)
R = RA1 + RB1 + x*(RA2 + RB2)
ra = ra1 + x*ra2
e = H(R + T || P || m)
sa = ra + e*H(L || PA)*pa
(sa, R + T) -> participant A's partial signature

SignB (run by participant B):
x = H(P || RA1 + RB1 + T || RA2 + RB2 || m)
R = RA1 + RB1 + x*(RA2 + RB2)
rb = rb1 + x*rb2
e = H(R + T || P || m)
sb = rb + e*H(L || PB)*pb
(sb, R, T) -> participant B's adaptor signature

Participant A can verify participant B's adaptor signature before sending its partial signature:
(sa + sb)*G must be equal to R + H(R + T || P || m)*P
-> NB: it's not a valid schnorr signature, notice the mismatch between R (outside of the hash) and R + T (inside the hash)

Complete (run by participant B):
s = sa + sb + t
R' = R + T
(s, R') -> valid schnorr signature for public key P and nonce R + T

Extract (run by participant A):
t = s - sa - sb
```