2024-03-24 13:00:54 +00:00
|
|
|
// SPDX-License-Identifier: AGPLv3
|
2022-07-16 21:45:41 +00:00
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
|
|
|
|
// see https://github.com/noot/schnorr-verify for implementation details
|
|
|
|
contract Schnorr {
|
|
|
|
// secp256k1 group order
|
|
|
|
uint256 constant public Q =
|
|
|
|
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
|
|
|
|
|
2024-03-24 13:00:54 +00:00
|
|
|
error InvalidSOrA();
|
|
|
|
error InvalidSignature();
|
|
|
|
|
2022-07-16 21:45:41 +00:00
|
|
|
// parity := public key y-coord parity (27 or 28)
|
|
|
|
// px := public key x-coord
|
2024-03-24 13:00:54 +00:00
|
|
|
// message := 32-byte hash of the message
|
|
|
|
// c := schnorr signature challenge
|
2022-07-16 21:45:41 +00:00
|
|
|
// s := schnorr signature
|
|
|
|
function verify(
|
|
|
|
uint8 parity,
|
|
|
|
bytes32 px,
|
|
|
|
bytes32 message,
|
2024-03-24 13:00:54 +00:00
|
|
|
bytes32 c,
|
|
|
|
bytes32 s
|
2022-07-16 21:45:41 +00:00
|
|
|
) public view returns (bool) {
|
|
|
|
// ecrecover = (m, v, r, s);
|
2024-03-24 13:00:54 +00:00
|
|
|
bytes32 sa = bytes32(Q - mulmod(uint256(s), uint256(px), Q));
|
|
|
|
bytes32 ca = bytes32(Q - mulmod(uint256(c), uint256(px), Q));
|
2022-07-16 21:45:41 +00:00
|
|
|
|
2024-03-24 13:00:54 +00:00
|
|
|
if (sa == 0) revert InvalidSOrA();
|
2022-07-16 21:45:41 +00:00
|
|
|
// the ecrecover precompile implementation checks that the `r` and `s`
|
2024-03-24 13:00:54 +00:00
|
|
|
// inputs are non-zero (in this case, `px` and `ca`), thus we don't need to
|
|
|
|
// check if they're zero.
|
|
|
|
address R = ecrecover(sa, parity, px, ca);
|
|
|
|
if (R == address(0)) revert InvalidSignature();
|
|
|
|
return c == keccak256(
|
2022-07-16 21:45:41 +00:00
|
|
|
abi.encodePacked(R, uint8(parity), px, block.chainid, message)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|