# Schnorr signature verification ecrecover hack
This idea is based off Vitalik's post here: https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384
However, instead of using ecrecover to do ECMUL, it can be hacked to cheaply verify a Schnorr signature.
## Background: Schnorr
### Signing
Given message `m`, private key `x` and hash-to-scalar function `h`, pick random value `k` from field. `G` is the curve generator.
Also define function `address()` which returns the 20-byte Ethereum address given a point. (Note: this is needed only for the hack)
```
R = G*k
e = h(address(R) || m)
s = k + x*e
Signature = (e, s) or (R, s)
```
### Verification
Given signature `(R, s)`, message `m` and public key `P`
```
e = h(address(R) || m)
R' = G*s - P*e
check R == R'
```
or, given signature `(e, s)`
```
R = G*s - P*e
e' = h(address(R) || m)
check e' == e
```
## Background: ecrecover
Ethereum `ecrecover` returns an address (hash of public key) given an ECDSA signature.
Given message `m` and ECDSA signature `(v, r, s)` where `v` denotes the parity of the y-coordinate for the point where x-coordinate `r`
```
ecrecover(m, v, r, s):
R = point derived from r and v
a = -G*m
b = R*s
Qr = a + b
Q = Qr * (1/r)
Q = (1/r) * (R*s - G*m) //recovered pubkey
```
Ethereum's ecrecover returns the last 20 bytes of the keccak256 hash of the 64-byte public key (see https://github.com/ethereum/go-ethereum/blob/eb948962704397bb861fd4c0591b5056456edd4d/crypto/crypto.go#L275)
## The hack
Given signature `(R, s)`, message `m` and public key `P` we can feed values into ecrecover such that the returned address can be used in a comparison to the challenge.
calculate `e = H(address(R) || m)` and `P_x = x-coordinate of P`
pass:
`m = -s*P_x`
`v = parity of P`
`r = x-coordinate of P`
`s = -e*P_x`
then:
```
ecrecover(m=-s*P_x, v=0/1, r=P_x, s=-e*P_x):
P = point derived from r and v (public key)
a = -G*(-s*P_x) = G*s*P_x
b = P*(-m*P_x) = -P*e*P_x
Q = (1/P_x) (a+b)
Q = (1/P_x)(G*s*P_x - P*e*P_x)
Q = G*s - P*e // same as schnorr verify above
```
the returned value is `address(Q)`.
- calculate `e' = h(address(Q) || m)`
- check `e' == e` to verify the signature.
## solidity
```solidity
//SPDX-License-Identifier: LGPLv3
pragma solidity ^0.8.0;
contract Schnorr {
// secp256k1 group order
uint256 constant public Q =
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
// parity := public key y-coord parity (27 or 28)
// px := public key x-coord
// message := 32-byte message
// e := schnorr signature challenge
// s := schnorr signature
function verify(
uint8 parity,
bytes32 px,
bytes32 message,
bytes32 e,
bytes32 s
) public pure returns (bool) {
// ecrecover inputs are (m, v, r, s);
bytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q));
bytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q));
require(sp != 0);
// the ecrecover precompile implementation checks that the `r` and `s`
// inputs are non-zero (in this case, `px` and `ep`), thus we don't need to
// check if they're zero.
address R = ecrecover(sp, parity, px, ep);
require(R != address(0), "ecrecover failed");
return e == keccak256(
abi.encodePacked(R, uint8(parity), px, message)
);
}
}
```
## References/notes
- Chainlink has already implemented a similar idea here: https://github.com/smartcontractkit/chainlink/blob/bb214c5d7ec172de400a72a1d8851ff639c979d2/evm/v0.5/contracts/dev/SchnorrSECP256K1.sol however their implementation uses more than just `ecrecover` and `keccak256`; it also checks that the signing public key's x-coordinate is less than the half-order, amongst other checks. however, I don't think that the `s < Q` check is required; it appears to have been in the yellow paper but not in the actual ecrecover implementation used by Ethereum.
- If you're using this in production, you probably want to add `block.chainId` to the challenge to prevent replay attacks.
- canonical ecrecover implementation:
- https://github.com/ethereum/go-ethereum/blob/8a134014b4b370b4a3632e32a2fc8e84ee2b6947/crypto/secp256k1/secp256.go#L105
- https://github.com/bitcoin-core/secp256k1/blob/aa5d34a8fe99b1f69306be20819f337dbd3283db/src/modules/recovery/main_impl.h#L87