development decred schnorr cryptography Signature Scheme

Schnorr and Adaptor Signatures

10 min read
Schnorr and Adaptor Signatures
⚠️ THIS IS A DRAFT, DO NOT SHARE ⚠️

Introduction

What is Schnorr? In this post, we’ll cover what Schnorr is, its properties, and why it matters. In short, Schnorr signatures are a cryptographic signature scheme.

And what makes them so special? They are provably secure (unlike ECDSA, yeah, you read that right), faster, use less space than other schemes, and they are zero-knowledge (zk)!

They also have useful properties that let us do algebra on signatures while preserving their security guarantees.

So let’s dive in.

Cryptography

To start, let’s first understand cryptography. What is cryptography?

  • Cryptography is the study and practice of secure communication in the presence of adversaries. It provides primitives such as hashing, encryption, and digital signatures. Schnorr is a digital signature scheme that is simple, fast, and enables powerful constructions like multisignatures and adaptor signatures.

Signature Scheme

What are signatures? In real life, signatures have some important properties.

  • They identify the signer.
  • They attest that the signer is responsible for the document:
    • e.g., that a person produced the document or agrees with it.

Digital signatures aim to provide the same properties. They use cryptography to verify the authenticity and integrity of a message using a public/private key pair (one public, one private).

Use Case

So what is this digital signature good for?

Some context:

I made a Decred proposal last year to build a Pong game for Bison Relay, but since then the game still isn’t playable. Why isn’t it playable?

The Pong game as proposed has a fundamental flaw. Players send the bet value to the server, so the server needs to take custody of the bet (which brings many problems that are not in the scope of this text).

How can we solve this? Schnorr!

Let’s analyze this problem.

We have two players who want to play a match and place a bet, and a coordinating server.

We want to lock funds in a way that authorizes spending with our private keys without revealing them.

How can we do that?

Formal Definitions

If you are not a technical person, you can skip this part

0) Setup & Symbols

  • Signature scheme. A signature scheme is a 5-tuple $\Sigma=(\mathcal P,\mathcal A,\mathcal K,\mathcal S,\mathcal V)$ where:

    • $\mathcal P$: finite set of possible messages.
    • $\mathcal A$: finite set of possible signatures.
    • $\mathcal K$: finite keyspace.
    • $\mathcal S={\mathsf{sig}_k \mid k\in\mathcal K}$ with $\mathsf{sig}_k:\mathcal P\to\mathcal A$.
    • $\mathcal V={\mathsf{ver}_k \mid k\in\mathcal K}$ with $\mathsf{ver}_k:\mathcal P\times\mathcal A\to{\mathsf{true},\mathsf{false}}$.
  • Correctness. For all $k\in\mathcal K$ and $x\in\mathcal P$: $\mathsf{ver}_k(x,\mathsf{sig}_k(x))=\mathsf{true}$. (Equivalently, for deterministic $\mathsf{sig}$:
    $\mathsf{ver}_k(x,y)=\mathsf{true}\iff y=\mathsf{sig}_k(x)$.)

  • Signed message. A pair $(x,y)\in\mathcal P\times\mathcal A$ is a signed message (under $k$) iff $\mathsf{ver}_k(x,y)=\mathsf{true}$.

EC‑Schnorr‑DCRv0

  • Curve. secp256k1 with generator $G$, order $n$; all scalar ops are mod $n$.
  • Keys. Private scalar $x\in[1,n-1]$; public key $X = xG$.
  • Message. $m\in{0,1}^{256}$ (exactly 32 bytes). For arbitrary payload $M$, sign $m = \mathrm{BLAKE256}(M)$.
  • Hash. $H = \mathrm{BLAKE256}$. Concatenation by $|$.
  • Nonce. One‑time nonce $k\in[1,n-1]$; nonce point $R = kG$.
  • Adaptor. Secret $\gamma$; adaptor point $T = \gamma G$.
  • Tweaked nonce point. $R’ = R + T = kG + \gamma G$.
  • X‑coordinate. $r’ = (R’)_x$ (32‑byte big‑endian).
  • Challenge. $e = H(r’ | m)$, interpreted as an integer $< n$. If $e \ge n$, resample/iterate the nonce.
  • Pre‑signature scalar. $s’ = k - e,x \pmod n$.
  • Final signature scalar. $s = s’ + \gamma \pmod n$.
  • Signature encoding. $(r’, s)$ as two 32‑byte big‑endian integers (64 bytes total).
  • Parity rule. The published $R’$ must have even $y$. If $R’$ is odd, advance the RFC6979 iteration (i.e., pick a new $k$) until $R’$ is even.

1) Deterministic nonce (RFC6979 + tag)

Derive $k$ deterministically from $(x, m, \text{scheme‑tag}, i)$. This avoids entropy failures and permits the “retry if $e\ge n$” and the even‑$y$ rule by incrementing $i$.

Adaptor Signatures

As we mentioned, Schnorr brings properties that keep signatures provably correct. What does this mean? We can do some algebra and end up with valid signatures without revealing private keys.

One of them is adaptor signatures. They say that if we have a valid point in our Schnorr scheme $R = kG$, we can add an adaptor point $T = \gamma G$ to it.

Then we have an adaptor point $R’ = R + T$ for which signatures verify.

This is useful, for example, to coordinate a bet: reveal $\gamma$ to the winner only after the result so no one can steal funds early; revealing the secret finalizes the correct branch (the winner’s).

Proof

2) Signing (with adaptor)

  1. Sample $k$ via RFC6979; compute $R = kG$.
  2. Form $R’ = R + T$; if $(R’)_y$ is odd, increment the RFC6979 counter and go back to step 1.
  3. Let $r’ = (R’)_x$. Compute $e = H(r’ | m)$; if $e \ge n$, increment RFC6979 and repeat.
  4. Compute the pre‑signature $s’ = k - e,x \pmod n$.
  5. Compute the final signature $s = s’ + \gamma \pmod n$.
  6. Output signature $(r’, s)$.

3) Verification

Given $X$, $m$, and $(r’, s)$:

  1. Compute $e = H(r’ | m)$ and fail if $e \ge n$.
  2. Compute $R_{\text{calc}} = sG + eX$.
  3. Reject if $R_{\text{calc}}$ is the point at infinity or if $\left(R_{\text{calc}}\right)_y$ is odd.
  4. Accept iff $\left(R_{\text{calc}}\right)_x = r’$.

4) Proof

Starting from the definitions $s = s’ + \gamma$ and $s’ = k - e,x$:

\[ \begin{aligned} sG + eX \\ &= (s' + \gamma)G + eX \\ &= s'G + \gamma G + e(xG) \\ &= (k - e\,x)G + \gamma G + e(xG) \\ &= kG - e(xG) + \gamma G + e(xG) \\ &= kG + \gamma G \\ &= R + T \\ &= R' \;. \end{aligned} \]
Thus verification computes $R_{\text{calc}} = sG + eX = R’$ and checks $x$-coordinate and even‑$y$.

5) Notes and gotchas

  • Even‑$y$ on $R’$ (not just $R$). Because $R’ = R+T$, enforcing even parity on $R$ alone doesn’t guarantee even parity on $R’$. Use the RFC6979 counter to pick a new $k$ until $R’$ is even.
  • Message size. The scheme signs 32 bytes. Always hash larger payloads to 32 bytes first.
  • Adaptor secrecy. Anyone who learns both $s$ and $s’$ can recover $\gamma = s - s’ \pmod n$. Keep $s’$ secret until reveal time.
  • Plain (non‑adaptor) signatures. Set $\gamma=0$ so $T=\mathcal O$ and $R’=R$; the equations reduce to standard EC‑Schnorr‑DCRv0 with challenge $e = H(r | m)$, $r=(R)_x$.

End of Technical Part

Adaptor Signatures

As we showed, adaptor signatures let us commit to a secret and reveal it only on specific outcomes.

What if the server vanishes?

If the server never reveals the secret, no player can ever finalize the settlement.

That’s why we add a CSV timelock, so a player can retrieve funds in case of extreme failure.

How it works

Applying this idea in our Pong game: POC Schnorr

End‑to‑end UX (players ↔ server)

The following walks through the flow in plain terms from both players’ and the server’s perspectives.

1) Alice creates a room and funds her deposit

  • Alice creates a waiting room.
  • The app shows a deposit address and its locking code (pkScript).
  • Alice’s wallet should verify the locking code matches exactly before funding.
  • She funds exactly the required amount and waits for confirmations.

2) Bob joins and funds his deposit

  • Bob joins the room.
  • The server tells Bob the required amount, the deposit pkScript, and how many confirmations are needed.
  • Bob’s app reconstructs the locking code locally and only funds if the bytes match exactly.
  • Bob funds the exact amount and waits for confirmations.

3) Server prepares two payout drafts and asks for pre‑approval

  • After both deposits confirm, the server builds two possible payout transactions: “Alice wins” and “Bob wins”.
  • Each draft spends one deposit from Alice and one from Bob, paying everything (minus fee) to the winner’s address that each player provided.
  • The server asks each player to pre‑approve both outcomes for their own deposit.

4) Players pre‑approve on their device

  • Your app re‑checks the drafts (inputs, amounts, and your locking code). If anything differs, it aborts.
  • If all checks pass, your app sends a pre‑approval for each outcome; your private key never leaves your device.
  • These pre‑approvals cannot be repurposed: they only work with the exact drafts that pay the winner.

5) Match result and reveal

  • When the game ends, the server determines the winner and reveals a small secret tied to the winning outcome.
  • The winner’s app combines that secret with the stored pre‑approvals (yours and your opponent’s, provided for the winning side) to finish the payout transaction.
  • Either the winner or the server can broadcast the finished transaction, but it only pays the declared winner’s address.

6) Winner finalizes and broadcasts

  • The app sets an appropriate fee and broadcasts the winning draft.
  • Everyone can verify the result on‑chain.

7) Safety and refunds

  • Deposits are controlled solely by each player’s key. There is no server key in the deposit.
  • If the match never completes or the server withholds the reveal, your funds remain under your control.
  • You can move your own deposit at any time; the timeout is a built‑in fallback path.
  • The server cannot redirect funds to itself.

Formal definitions

can be skipped for non-technical

In our case, there is a server coordinator that chooses a secret scalar $\gamma$ and publishes only its commitment $T=\gamma G$. For each branch (A‑wins, B‑wins), the coordinator also provides the Decred sighash m of the exact draft transaction input.

Coordinator (setup).

  • Choose $\gamma \in \mathbb{Z}_n$ and publish the commitment $T=\gamma G$.
  • For each branch $b\in{\text{A-wins},\text{B-wins}}$, provide the exact Decred sighash $m_b\in{0,1}^{256}$ of the draft input.

Each player, with private key x and public key X = x·G:

Signer (per branch $b$).
Given private key $x\in\mathbb{Z}_n$ and public key $X=xG$ (all scalar ops mod $n$; $H=\mathrm{BLAKE\text{-}256}$):

    1. Pick/derive nonce $k_b\in\mathbb{Z}_n$; set $R_b = k_b G$ and normalize to even-$y$.
    1. Let $r_b = \mathrm{x\text{-}coord}(R_b)$.
    1. Compute challenge $e_b = H(r_b ,|, m_b) \bmod n$.
    1. Compute the DCRv0 pre-signature $s’_b = k_b - e_b,x \pmod n$.
    1. Send $(r_b, s’_b)$ to the coordinator.

With $(r_b, s’_b)$, the server can verify:

Server-side verification of adaptor pre-signatures (DCRv0 Schnorr)

Let $b\in{\text{A-wins},\text{B-wins}}$.
Coordinator publishes $T=\gamma G$. For branch $b$, it supplies the Decred sighash $m_b$.

Signer output (per $b$). With keypair $(x,X=xG)$, the player sends $(r_b,s’_b)$ where
$R_b=k_b G$ is normalized to even-$y$, $r_b=\mathrm{x\text{-}coord}(R_b)$,
$e_b = H(r_b ,|, m_b)\bmod n$ (Decred $H=\mathrm{BLAKE}\text{-}256$), and
$s’_b = k_b - e_b x \pmod n$.

Server verification (given $X,T,m_b,(r_b,s’_b)$).

  1. Lift $R_b \leftarrow \text{lift\_x\_even}(r_b)$, ensure $R_b\neq\mathcal O$.
  2. Range checks: $0<r_b<p$, $0<s’_b<n$; validate $X$ is on curve.
  3. Recompute $e_b \leftarrow H(r_b ,|, m_b)\bmod n$.
  4. Check the adaptor relation:
    \[ s'_b G \stackrel{?}{=} R_b - e_b X - T \quad\text{(equiv. } s'_b G + e_b X + T \stackrel{?}{=} R_b\text{)}. \]
    Accept iff the equality holds.

Finalization (winning branch). When $\gamma$ is revealed, the winner sets

\[ s_b = s'_b + \gamma \pmod n, \]
and verifies the ordinary DCRv0 signature $(r_b,s_b)$ via
\[ s_b G \stackrel{?}{=} R_b - e_b X . \]

Store $(m_b,r_b,s’_b)$ per branch and repeat independently for both branches.

Deposit script (simplified view):

IF            # immediate spend path
  owner_signature
ELSE          # after a delay (CSV)
  csv_delay
  owner_signature
ENDIF

Test

After playing a game between two Pong clients with this POC, we got:

Schnorr signature

The transaction on testnet: https://testnet.dcrdata.org/tx/0b7643a8fcfe8fb7b45468e5917908cbeab581680cc373dd358c7b42f3177b05

Conclusion

Schnorr + adaptors are an incredible tool and bring tons of possibilities. You keep your keys; the server just flips a tiny secret on the winning path. If it ghosts you, CSV is your exit hatch.

Our testnet POC ran end-to-end: self-custody intact, no rug pulls, and finalizing is basically “pre-sig + secret = sig.”

And this is just the start. Next up we’ll explore MuSig2 so $n$ players can co-sign a transaction, still with an adaptor, and settle cleanly to a single winner—perfect for games and tournaments.

Thanks for sticking around! This one ended up running a bit long, but I think the insights were worth it.