Skip to content
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

Add ERC: A Schnorr Signature Scheme for EVM Applications #713

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
206 changes: 206 additions & 0 deletions ERCS/erc-7816.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
---
eip: 7816
title: A Schnorr Signature Scheme for EVM Applications
author: merkleplant (@pmerkleplant)
discussions-to: https://ethereum-magicians.org/t/erc-7816-a-schnorr-signature-scheme-for-evm-applications/21636
status: Draft
type: Standards Track
category: ERC
created: 2024-11-09
---

## Abstract

This document specifies a Schnorr signature scheme over the elliptic curve secp256k1 for EVM applications.

## Motivation

EVM applications have traditionally used ECDSA signatures over secp256k1 in combination with the keccak256 hash function. However, Schnorr signatures have a number of advantages compared to ECDSA signatures:

* **Linearity**: Schnorr signatures are linear which allows them to be easily aggregated, i.e. it enables multiple collaborating parties to produce a signature that is valid over the sum of their public keys. This building block allows for higher level constructions such as multisignatures and threshold signatures.

* **Provable security**: Schnorr signatures are provable secure with weaker assumptions than the best known security proofs for ECDSA. More specifically, Schnorr signatures are strongly unforgeable under chosen message attack (_SUF-CMA_) in the random oracle model (_ROM_) assuming the hardness of the elliptic curve discrete logarithm problem (_ECDLP_)[^1].

* **Non-Malleability**: Schnorr signatures are non-malleable. Note that on the other hand ECDSA signatures are malleable which has lead to numerous security issues.

In contrast to BLS (multi-) signatures, Schnorr signatures are far more efficient to verify inside the EVM and do not require additional precompiles. Note that this is even true for already existing Schnorr multisignature and threshold signature schemes!

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC-2119 and RFC-8174 when, and only when, they appear in all capitals, as shown here.

Let `G` be secp256k1’s generator and `Q` secp256k1’s order. Let `sk` be an secp256k1 secret key and `Pk = [sk]G` be the secret key’s public key in affine coordinates. Let `Pkₑ` be a public key’s Ethereum address, `Pkₓ` a public key’s `x` coordinate, and `Pkₚ` a public key’s `y` coordinate’s parity with `0` if `y` is even and `1` if `y` is odd. Let `‖` be the concatenation operator performing a byte-wise concatenation.

## Hash Functions

Check failure on line 34 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

body has extra section(s)

error[markdown-order-section]: body has extra section(s) --> ERCS/erc-7816.md | 34 | ## Hash Functions | ::: ERCS/erc-7816.md | 48 | ## Signature Creation | ::: ERCS/erc-7816.md | 58 | ## Signature Verification | ::: ERCS/erc-7816.md | 70 | ## Message Hash Construction | ::: ERCS/erc-7816.md | 77 | ## Nonce Generation | ::: ERCS/erc-7816.md | 91 | ## Encoding | ::: ERCS/erc-7816.md | 143 | ## Implementation Notes | = help: see https://ethereum.github.io/eipw/markdown-order-section/

We define the ASCII context string `ctx` containing our scheme and ciphersuite as `ETHEREUM-SCHNORR-SECP256K1-KECCAK256`. This enables domain separating our hash functions and ensures a signed message intended for one context is never deemed valid in a different context. It is RECOMMENDED that future signature schemes define a context string in the same pattern.

With the help of `ctx` we define our context aware, cryptographically secure, hash function `H` as `H(x) = keccak256(ctx ‖ x)`.

Using the hash function `H`, we define the following additional domain separated hash functions:

* `H₁(x) = H("message" ‖ x) (mod Q)`
* `H₂(x) = H("challenge" ‖ x) (mod Q)`
* `H₃(x) = H("nonce" ‖ x) (mod Q)`

Note that all hash functions derived from `H` are defined to return elements in `[1, Q)` via modular reduction with `Q`. While this generally introduces a bias leading to non-uniformly random output, secp256k1's order `Q` is sufficiently close to $2^{256}$ that the modulo bias is acceptable[^2]. Note that the probability of any in this document defined hash function’s output being `0` is deemed negligible.

## Signature Creation

A Schnorr signature is generated over a byte string `message`, under secret key `sk` and public key `Pk = [sk]G` by the following steps:

1. Derive the `message`'s domain separated hash digest `m` as specified in [_Message Hash Construction_](#message-hash-construction)
2. Select a cryptographically secure, uniformly random nonce `k ∊ [1, Q)` as specified in [_Nonce Generation_](#nonce-generation) and compute its public key `R = [k]G`. Let `Rₑ` be the commitment
3. Compute the challenge `e = H₂(Rₑ ‖ Pkₓ ‖ Pkₚ ‖ m) (mod Q)`
4. Using secret key `sk`, compute the Fiat-Shamir response `s = k + (e * sk) (mod Q)`
5. Define the signature over `m` to be `sig = (s, R)`

## Signature Verification

Validating the integrity of `m` using the public key `Pk` and the signature `sig` is performed as:

1. Parse `sig` as `(s, R)` if default encoded and `(s, Rₑ)` if compressed encoded
2. Verify `s ∊ [1, Q)`, otherwise output `0`
3. Compute challenge `e = H₂(Rₑ ‖ Pkₓ ‖ Pkₚ ‖ m) (mod Q)`
4. Compute `R'ₑ = ([s]G - [e]Pk)ₑ`
5. Output `1` if `Rₑ == R'ₑ` to indicate success, otherwise output `0`

Note that the verification is based on `R`'s Ethereum address and not on the public key itself. In order to perform the verification’s `mulmuladd` operation efficiently the `ecrecover` precompile can be abused for secp256k1. For more info, see [_Implementation Notes_](#implementation-notes).

## Message Hash Construction

1. Compute message digest `d = keccak256(message)`
2. Compute the message hash `m = H₁(d)`

In order to guarantee constant calldata size this Schnorr signature scheme does not accept arbitrary length messages. A context aware hash function is used to ensure the message hash cannot be reinterpreted in a different context.

## Nonce Generation

The nonce MUST be computed with a 32-byte randomness value `rand` and the secret key `sk` via `H₃(rand ‖ sk)`.

Note that by combining the randomness value `rand` with the secret key the nonce generation is hedged against a bad RNG possibly used to source `rand`.

Note that domain separating the nonce via the `H₃` function is necessary to prevent secret key leakage due to nonce reuse when the same `rand` value is sourced for different signature schemes. The same `rand` value may be sourced when using deterministic methods such as RFC-6979.

### Sourcing `rand`

The 32 byte `rand` value may be sourced via different methods, e.g. using a CSPRNG or computing it via RFC-6979. However, it MUST be guaranteed that `rand` is not reused for signatures signing different messages.

Furthermore, it is NOT RECOMMENDED to source `rand` deterministically. Note that this Schnorr scheme is compatible with multisignature and threshold signature schemes where a signer’s secret key does not match the public key the signature is created for. This property makes such schemes in general **insecure with deterministic nonce generation**. Additionally, creating nonces independently of signed messages may allow for a preprocessing stage in higher-level protocols where participants generate and publish nonce commitments prior to signing.

## Encoding

A Schnorr signature `sig = (s, R) = (s, (x, y))` is encoded in 96 bytes via:

```
[s 32 bytes][x 32 bytes][y 32 bytes]
```

### Compressed Encoding

A Schnorr signature can be compressed encoded to 52 bytes via compressing the public key `R` to its Ethereum address:

```
[s 32 bytes][Rₑ 20 bytes]
```

## Security Considerations

Check failure on line 107 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

section `Security Considerations` is out of order

error[markdown-order-section]: section `Security Considerations` is out of order --> ERCS/erc-7816.md | 107 | ## Security Considerations | = help: `Security Considerations` should come after `Reference Implementation`

Note that this Schnorr scheme uses `R`'s Ethereum address instead of the public key itself, thereby decreasing the security of brute-forcing the signature from 256 bits (trying random secp256k1 points) to 160 bits (trying random Ethereum addresses). However, the difficulty of cracking a secp256k1 public key using the baby-step giant-step algorithm is `O(√Q)`[^3]. Note that `√Q ~= 3.4e38 < 128 bit`. Therefore, this scheme does not weaken the overall security.

### Signature Malleability

While Schnorr signatures as specified in this document are not malleable due to the definition of `s`, great care must be taken during verification to ensure a Schnorr signatures `s ∊ [1, Q)`. Note that this issue is mentioned explicitly due to Solidity's weak type system and the expectation of most implementations using type `uint256` for the `s` variable.

### Rogue Key Attacks

Rogue key attacks are a serious concern for Schnorr multisignature and threshold signature schemes where a subset of malicious participants use public keys computed as functions of public keys from honest participants allowing them to produce forgeries for the total set of public keys[^4].

Rogue key attacks can be prevented via a knowledge of secret key (KOSK) assumption, i.e. requiring participants to prove knowledge of the secret key during public key registration, or non-trivial public key aggregation schemes[^4][^5].

### Wagner's Birthday Attack

Another serious concern for multisignature and threshold signature schemes is Wagner's birthday attack, where malicious participants can manipulate a final aggregated nonce used for signing by submitting rogue nonces[^6].

Wagner's birthday attack can be prevented via either introducing an additional _nonce commitment_ communication round in the scheme or non-trivial nonce aggregation schemes[^5][^6].

## Rationale

Schnorr signature schemes exist in many different flavors. This Schnorr signature scheme chooses the signature to be `(s, R)` instead of `(e, R)` for closer behavior to Bitcoin’s BIP-340. Note that even though the signature is verified via `Rₑ`, it is still defined via `R` to ensure forward compatibility with Schnorr schemes based on aggregated public keys.

Additionally this Schnorr scheme is _key prefixed_ to protect against "related-key attacks" meaning the public key is prefixed to the message in the challenge hash input[^2]. Note that instead of prefixing the key in affine coordinates, the public key’s `x` coordinate and `y` coordinate’s parity are used to not increase the verifier's resource usage by either computing or storing the full `y` coordinate.

## Test Cases

> [!NOTE]
>
> Test cases still work-in-progress

## Reference Implementation

A reference implementation is provided in [verklegarden/crysol](https://github.com/verklegarden/crysol).

Check failure on line 141 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 141 | A reference implementation is provided in [verklegarden/crysol](https://github.com/verklegarden/crysol). | = help: see https://ethereum.github.io/eipw/markdown-rel-links/

## Implementation Notes

### Elliptic Curve `mulmuladd`

In order to verify Schnorr signatures a `mulmuladd` operation must be performed over secp256k1. As Vitalik notes, the `ecrecover` precompile can be abused to perform such an operation, with the caveat that the result is not returned as elliptic curve point but rather as Ethereum address[^7].

The `ecrecover` precompile can be implemented in pseudocode via:

```python
def ecdsa_raw_recover(msghash, vrs):
v, r, s = vrs
y = # (get y coordinate for EC point with x=r, with same parity as v)
Gz = jacobian_multiply((Gx, Gy, 1), (Q - hash_to_int(msghash)) % Q)
XY = jacobian_multiply((r, y, 1), s)
Qr = jacobian_add(Gz, XY)
N = jacobian_multiply(Qr, inv(r, Q))
return from_jacobian(N)
```

Note that `ecrecover` also uses `s` as variable. From this point forward, let the Schnorr signature's `s` be `sig`.

A single `ecrecover` call can compute `([sig]G - [e]Pk)ₑ = ([k]G)ₑ = Rₑ` via the following inputs:

```
msghash = -sig * Pkₓ
v = Pkₚ + 27
r = Pkₓ
s = Q - (e * Pkₓ)
```

Note that `ecrecover` returns the Ethereum address `Rₑ` and not `R` itself.

The `ecrecover` call then digests to:

```
Gz = [Q - (-sig * Pkₓ)]G | Double negation
= [Q + (sig * Pkₓ)]G | Addition with Q can be removed in (mod Q)
= [sig * Pkₓ]G | sig = k + (e * sk)
= [(k + (e * sk)) * Pkₓ]G

XY = [Q - (e * Pkₓ)]Pk | Pk = [sk]G
= [(Q - (e * Pkₓ)) * sk]G

Qr = Gz + XY | Gz = [(k + (e * sk)) * Pkₓ]G
= [(k + (e * sk)) * Pkₓ]G + XY | XY = [(Q - (e * Pkₓ)) * sk]G
= [(k + (e * sk)) * Pkₓ]G + [(Q - (e * Pkₓ)) * sk]G

N = Qr * Pkₓ⁻¹ | Qr = [(k + (e * sk)) * Pkₓ]G + [(Q - (e * Pkₓ)) * sk]G
= [(k + (e * sk)) * Pkₓ]G + [(Q - (e * Pkₓ)) * sk]G * Pkₓ⁻¹ | Distributive law
= [(k + (e * sk)) * Pkₓ * Pkₓ⁻¹]G + [(Q - (e * Pkₓ)) * sk * Pkₓ⁻¹]G | Pkₓ * Pkₓ⁻¹ = 1
= [(k + (e * sk))]G + [Q - e * sk]G | sig = k + (e * sk)
= [sig]G + [Q - e * x]G | Q - (e * sk) = -(e * sk) in (mod Q)
= [sig]G - [e * sk]G | Pk = [sk]G
= [sig]G - [e]Pk
```

<!-- References -->

Check warning on line 199 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn`

warning[markdown-html-comments]: HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn` --> ERCS/erc-7816.md | 199 | <!-- References --> | = help: see https://ethereum.github.io/eipw/markdown-html-comments/
[^1]:[Security Arguments for Digital Signatures and Blind Signatures](https://www.di.ens.fr/david.pointcheval/Documents/Papers/2000_joc.pdf)

Check failure on line 200 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 200 | [^1]:[Security Arguments for Digital Signatures and Blind Signatures](https://www.di.ens.fr/david.pointcheval/Documents/Papers/2000... |
[^2]:[BIP-0340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#cite_note-13)

Check failure on line 201 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 201 | [^2]:[BIP-0340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#cite_note-13) |
[^3]:[Baby-step giant-step Algorithm](https://en.wikipedia.org/wiki/Baby-step_giant-step)

Check failure on line 202 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 202 | [^3]:[Baby-step giant-step Algorithm](https://en.wikipedia.org/wiki/Baby-step_giant-step) |
[^4]:[Musig](https://eprint.iacr.org/2018/068.pdf)

Check failure on line 203 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 203 | [^4]:[Musig](https://eprint.iacr.org/2018/068.pdf) |
[^5]:[Musig2](https://eprint.iacr.org/2020/1261.pdf)

Check failure on line 204 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 204 | [^5]:[Musig2](https://eprint.iacr.org/2020/1261.pdf) |
[^6]:[On the Security of Two-Round Multi-Signatures](https://eprint.iacr.org/2018/417.pdf)

Check failure on line 205 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 205 | [^6]:[On the Security of Two-Round Multi-Signatures](https://eprint.iacr.org/2018/417.pdf) |
[^7]:[Vitalik "You can *kinda* abuse ECRECOVER to do ECMUL in secp256k1 today"](https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384)

Check failure on line 206 in ERCS/erc-7816.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

non-relative link or image

error[markdown-rel-links]: non-relative link or image --> ERCS/erc-7816.md | 206 | [^7]:[Vitalik "You can *kinda* abuse ECRECOVER to do ECMUL in secp256k1 today"](https://ethresear.ch/t/you-can-kinda-abuse-ecrecove... |
Loading