From af427675e142f8c8ae15221b1c12c4b93957a4ec Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 10 Feb 2025 14:36:41 -0700 Subject: [PATCH 1/7] passing test --- script/SignatureChecker.s.sol | 2 +- src/SignatureChecker.sol | 48 ++++++++++++++++++++++++++--------- test.json | 9 +++++++ test/SignatureChecker.t.sol | 30 ++++++++++++++-------- 4 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 test.json diff --git a/script/SignatureChecker.s.sol b/script/SignatureChecker.s.sol index bac8bde..c3556ce 100644 --- a/script/SignatureChecker.s.sol +++ b/script/SignatureChecker.s.sol @@ -12,7 +12,7 @@ contract SignatureCheckerScript is Script { function run() public { vm.startBroadcast(); - signatureChecker = new SignatureChecker(); + signatureChecker = new SignatureChecker(0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F); vm.stopBroadcast(); } diff --git a/src/SignatureChecker.sol b/src/SignatureChecker.sol index 05c4ceb..08404fd 100644 --- a/src/SignatureChecker.sol +++ b/src/SignatureChecker.sol @@ -2,23 +2,47 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; -import {ECDSA} from "./ECDSA.sol"; // todo: Do we need to have some stateful capabilities? -contract SignatureChecker { - // todo Replace with our notary address - // 0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F - address public constant NOTARY_ADDRESS = address(0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F); +contract SignatureChecker is Ownable { + // Mapping of notary addresses to their validity + mapping(address => bool) public isNotary; - function isValidSignatureNow(bytes32 hash, bytes memory signature) external pure returns (bool) { - (address recovered, ECDSA.RecoverError err,) = ECDSA.tryRecover(hash, signature); - return err == ECDSA.RecoverError.NoError && recovered == NOTARY_ADDRESS; + // error for invalid signatures + error InvalidSignature(); + error InvalidNotary(); + + // constructor configures the notary address + constructor(address _notaryAddress) Ownable(msg.sender) { + isNotary[_notaryAddress] = true; + } + + function addNotary(address _notaryAddress) external onlyOwner { + isNotary[_notaryAddress] = true; + } + + function removeNotary(address _notaryAddress) external onlyOwner { + isNotary[_notaryAddress] = false; + } + + function verifySignature(bytes32 _hash, uint8 v, bytes32 r, bytes32 s, address signer) + external + view + returns (bool) + { + // check if the signer is a notary + if (!isNotary[signer]) { + revert InvalidNotary(); + } - /// this decomposes the signature into r, s, v // r is the x-coordinate of the curve point // s is the y-coordinate of the curve point - - // v is 0 for an even y-value - // v is 1 for an odd y-value + // v is 27 or 28 based on the y-value being even or odd + // verify the signature + address recoveredSigner = ecrecover(_hash, v, r, s); + if (recoveredSigner != signer) { + revert InvalidSignature(); + } + return true; } } diff --git a/test.json b/test.json new file mode 100644 index 0000000..d2d1894 --- /dev/null +++ b/test.json @@ -0,0 +1,9 @@ +{ + "merkle_root": "0x2943593524b7c19d5448e2489772f3de80d71418c3a62b25ef39559334acbffe", + "leaves": ["request", "response"], + "signature": "0x30450221009039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14022016d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2", + "signature_r": "0x9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14", + "signature_s": "0x16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2", + "signature_v": 28, + "signer": "0xfdf07a5dcfa7b74f4c28dab23ead8b1c43be801f" +} \ No newline at end of file diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index 281494b..7a36b8f 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -8,21 +8,29 @@ contract SignatureCheckerTest is Test { SignatureChecker public signatureChecker; function setUp() public { - signatureChecker = new SignatureChecker(); + signatureChecker = new SignatureChecker(0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F); } function test_isValidSignatureNow() public view { - // Replace with our notary proof hash - bytes32 hash = keccak256("test"); - - // Replace with our notary signature - // 9039BD2DED8FD5E09B62A04A3D25D2D13F0F45F40C1B4CA6C3AECE4A71F92C1416D62561A8939ED49C97D9621C47988CF74B5B5074F89F2B2C6B9B18A6EEB1B2 - // 9039BD2DED8FD5E09B62A04A3D25D2D13F0F45F40C1B4CA6C3AECE4A71F92C1416D62561A8939ED49C97D9621C47988CF74B5B5074F89F2B2C6B9B18A6EEB1B2 + + // TEST vector from NOTARY + // "merkle_root": "0x2943593524b7c19d5448e2489772f3de80d71418c3a62b25ef39559334acbffe", + // "signature" is comprised of 160 characters + // 0x3045022100 : Not sure what the firstones here are + // 9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14 : This is the R value + // 0220 : Not sure what the secondones here are + // 16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2 : This is the S value + // "signature_r": "0x9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14", + // "signature_s": "0x16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2", + // "signature_v": 28, + // "signer": "0xfdf07a5dcfa7b74f4c28dab23ead8b1c43be801f" + address signer = 0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F; // There are interestingly 129 characters in this signature, the ercrecover opcode expects 65 where 32 are R, 32 are S, and 1 is V - bytes memory signature = - abi.encodePacked(bytes32(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef)); - + bytes32 _hash = bytes32(0x2943593524b7c19d5448e2489772f3de80d71418c3a62b25ef39559334acbffe); + uint8 v = 28; + bytes32 r = bytes32(0x9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14); + bytes32 s = bytes32(0x16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2); // verify signature - assertEq(signatureChecker.isValidSignatureNow(hash, signature), true); + assertEq(signatureChecker.verifySignature(_hash, v, r, s, signer), true); } } From 7ef683905d05e25ad26e224539b2bed0971abd17 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 10 Feb 2025 14:38:48 -0700 Subject: [PATCH 2/7] remove ECDSA lib --- src/ECDSA.sol | 180 --------------------------------------- src/SignatureChecker.sol | 21 ++++- 2 files changed, 17 insertions(+), 184 deletions(-) delete mode 100644 src/ECDSA.sol diff --git a/src/ECDSA.sol b/src/ECDSA.sol deleted file mode 100644 index 175db73..0000000 --- a/src/ECDSA.sol +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. - * - * These functions can be used to verify that a message was signed by the holder - * of the private keys of a given address. - */ -library ECDSA { - enum RecoverError { - NoError, - InvalidSignature, - InvalidSignatureLength, - InvalidSignatureS - } - - /** - * @dev The signature derives the `address(0)`. - */ - error ECDSAInvalidSignature(); - - /** - * @dev The signature has an invalid length. - */ - error ECDSAInvalidSignatureLength(uint256 length); - - /** - * @dev The signature has an S value that is in the upper half order. - */ - error ECDSAInvalidSignatureS(bytes32 s); - - /** - * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not - * return address(0) without also returning an error description. Errors are documented using an enum (error type) - * and a bytes32 providing additional information about the error. - * - * If no error is returned, then the address can be used for verification purposes. - * - * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. - * - * Documentation for signature generation: - * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] - * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] - */ - function tryRecover(bytes32 hash, bytes memory signature) - internal - pure - returns (address recovered, RecoverError err, bytes32 errArg) - { - if (signature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - // ecrecover takes the signature parameters, and the only way to get them - // currently is to use assembly. - assembly ("memory-safe") { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - return tryRecover(hash, v, r, s); - } else { - return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature`. This address can then be used for verification purposes. - * - * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. - */ - function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { - (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); - _throwError(error, errorArg); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. - * - * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures] - */ - function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) - internal - pure - returns (address recovered, RecoverError err, bytes32 errArg) - { - unchecked { - bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - // We do not check for an overflow here since the shift operation results in 0 or 1. - uint8 v = uint8((uint256(vs) >> 255) + 27); - return tryRecover(hash, v, r, s); - } - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. - */ - function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { - (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); - _throwError(error, errorArg); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `v`, - * `r` and `s` signature fields separately. - */ - function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) - internal - pure - returns (address recovered, RecoverError err, bytes32 errArg) - { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return (address(0), RecoverError.InvalidSignatureS, s); - } - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(hash, v, r, s); - if (signer == address(0)) { - return (address(0), RecoverError.InvalidSignature, bytes32(0)); - } - - return (signer, RecoverError.NoError, bytes32(0)); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `v`, - * `r` and `s` signature fields separately. - */ - function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { - (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); - _throwError(error, errorArg); - return recovered; - } - - /** - * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. - */ - function _throwError(RecoverError error, bytes32 errorArg) private pure { - if (error == RecoverError.NoError) { - return; // no error: do nothing - } else if (error == RecoverError.InvalidSignature) { - revert ECDSAInvalidSignature(); - } else if (error == RecoverError.InvalidSignatureLength) { - revert ECDSAInvalidSignatureLength(uint256(errorArg)); - } else if (error == RecoverError.InvalidSignatureS) { - revert ECDSAInvalidSignatureS(errorArg); - } - } -} diff --git a/src/SignatureChecker.sol b/src/SignatureChecker.sol index 08404fd..b6d1caf 100644 --- a/src/SignatureChecker.sol +++ b/src/SignatureChecker.sol @@ -3,28 +3,41 @@ pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; -// todo: Do we need to have some stateful capabilities? +/// @title SignatureChecker +/// @notice A contract that verifies signatures contract SignatureChecker is Ownable { - // Mapping of notary addresses to their validity + /// @notice Mapping of notary addresses to their validity mapping(address => bool) public isNotary; - // error for invalid signatures + /// @notice Error for invalid signatures error InvalidSignature(); + /// @notice Error for invalid notary addresses error InvalidNotary(); - // constructor configures the notary address + /// @notice Constructor configures the notary address + /// @param _notaryAddress The address of the notary to add constructor(address _notaryAddress) Ownable(msg.sender) { isNotary[_notaryAddress] = true; } + /// @notice Adds a notary + /// @param _notaryAddress The address of the notary to add function addNotary(address _notaryAddress) external onlyOwner { isNotary[_notaryAddress] = true; } + /// @notice Removes a notary + /// @param _notaryAddress The address of the notary to remove function removeNotary(address _notaryAddress) external onlyOwner { isNotary[_notaryAddress] = false; } + /// @notice Verifies a signature + /// @param _hash The hash of the data that was signed + /// @param v The recovery id + /// @param r The R value of the signature + /// @param s The S value of the signature + /// @param signer The address that signed the data function verifySignature(bytes32 _hash, uint8 v, bytes32 r, bytes32 s, address signer) external view From 3a3169a6e9ef8b5e70709368bdc0a026373954cb Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Thu, 13 Feb 2025 11:23:27 -0700 Subject: [PATCH 3/7] latest test vector digest --- src/SignatureChecker.sol | 7 ++++++- test.json | 9 --------- test/SignatureChecker.t.sol | 26 +++++++------------------- test_vectors.json | 9 +++++++++ 4 files changed, 22 insertions(+), 29 deletions(-) delete mode 100644 test.json create mode 100644 test_vectors.json diff --git a/src/SignatureChecker.sol b/src/SignatureChecker.sol index b6d1caf..82096b5 100644 --- a/src/SignatureChecker.sol +++ b/src/SignatureChecker.sol @@ -9,11 +9,16 @@ contract SignatureChecker is Ownable { /// @notice Mapping of notary addresses to their validity mapping(address => bool) public isNotary; + uint256 public constant BN254_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + /// @notice Error for invalid signatures error InvalidSignature(); /// @notice Error for invalid notary addresses error InvalidNotary(); + /// @notice Error for invalid signature length + error InvalidSignatureLength(); + /// @notice Constructor configures the notary address /// @param _notaryAddress The address of the notary to add constructor(address _notaryAddress) Ownable(msg.sender) { @@ -38,7 +43,7 @@ contract SignatureChecker is Ownable { /// @param r The R value of the signature /// @param s The S value of the signature /// @param signer The address that signed the data - function verifySignature(bytes32 _hash, uint8 v, bytes32 r, bytes32 s, address signer) + function verifyNotarySignature(bytes32 _hash, uint8 v, bytes32 r, bytes32 s, address signer) external view returns (bool) diff --git a/test.json b/test.json deleted file mode 100644 index d2d1894..0000000 --- a/test.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "merkle_root": "0x2943593524b7c19d5448e2489772f3de80d71418c3a62b25ef39559334acbffe", - "leaves": ["request", "response"], - "signature": "0x30450221009039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14022016d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2", - "signature_r": "0x9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14", - "signature_s": "0x16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2", - "signature_v": 28, - "signer": "0xfdf07a5dcfa7b74f4c28dab23ead8b1c43be801f" -} \ No newline at end of file diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index 7a36b8f..96b8041 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -12,25 +12,13 @@ contract SignatureCheckerTest is Test { } function test_isValidSignatureNow() public view { - // TEST vector from NOTARY - // "merkle_root": "0x2943593524b7c19d5448e2489772f3de80d71418c3a62b25ef39559334acbffe", - // "signature" is comprised of 160 characters - // 0x3045022100 : Not sure what the firstones here are - // 9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14 : This is the R value - // 0220 : Not sure what the secondones here are - // 16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2 : This is the S value - // "signature_r": "0x9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14", - // "signature_s": "0x16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2", - // "signature_v": 28, - // "signer": "0xfdf07a5dcfa7b74f4c28dab23ead8b1c43be801f" address signer = 0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F; - // There are interestingly 129 characters in this signature, the ercrecover opcode expects 65 where 32 are R, 32 are S, and 1 is V - bytes32 _hash = bytes32(0x2943593524b7c19d5448e2489772f3de80d71418c3a62b25ef39559334acbffe); - uint8 v = 28; - bytes32 r = bytes32(0x9039bd2ded8fd5e09b62a04a3d25d2d13f0f45f40c1b4ca6c3aece4a71f92c14); - bytes32 s = bytes32(0x16d62561a8939ed49c97d9621c47988cf74b5b5074f89f2b2c6b9b18a6eeb1b2); - // verify signature - assertEq(signatureChecker.verifySignature(_hash, v, r, s, signer), true); + bytes32 digest = bytes32(0x0ad25b24a05589ed9f2332ac85f5690c8400019f32858c2f6bf24877362d41db); + bytes32 r = bytes32(0x86c6ab86ac26bfdfd245ab65a05e90cd18afe9f810acb42532adf7570cd0ed77); + bytes32 s = bytes32(0x17370b1c7a7d7d96155e6144a9bfc9265f81c354b1cb4af7cebe52e601dabfef); + uint8 v = 27; + assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer), true); + } -} +} \ No newline at end of file diff --git a/test_vectors.json b/test_vectors.json new file mode 100644 index 0000000..94a73c8 --- /dev/null +++ b/test_vectors.json @@ -0,0 +1,9 @@ +{ + "digest": "0x0ad25b24a05589ed9f2332ac85f5690c8400019f32858c2f6bf24877362d41db", + "signature": "0x304502210086c6ab86ac26bfdfd245ab65a05e90cd18afe9f810acb42532adf7570cd0ed77022017370b1c7a7d7d96155e6144a9bfc9265f81c354b1cb4af7cebe52e601dabfef", + "signature_r": "0x86c6ab86ac26bfdfd245ab65a05e90cd18afe9f810acb42532adf7570cd0ed77", + "signature_s": "0x17370b1c7a7d7d96155e6144a9bfc9265f81c354b1cb4af7cebe52e601dabfef", + "signature_v": 27, + "signer": "0xfdf07a5dcfa7b74f4c28dab23ead8b1c43be801f" +} + From c8a2c1360de73feed4553763389ecfa52b882dc1 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 17 Feb 2025 16:44:11 -0700 Subject: [PATCH 4/7] feat: stateful --- src/SignatureChecker.sol | 27 +++++++++++++++++++++------ test/SignatureChecker.t.sol | 4 +++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/SignatureChecker.sol b/src/SignatureChecker.sol index 82096b5..757cc3b 100644 --- a/src/SignatureChecker.sol +++ b/src/SignatureChecker.sol @@ -9,13 +9,17 @@ contract SignatureChecker is Ownable { /// @notice Mapping of notary addresses to their validity mapping(address => bool) public isNotary; + /// valid digests for a given address + mapping(address => bytes32) public digests; + uint256 public constant BN254_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; /// @notice Error for invalid signatures error InvalidSignature(); /// @notice Error for invalid notary addresses error InvalidNotary(); - + /// @notice Error for invalid digest + error InvalidDigest(); /// @notice Error for invalid signature length error InvalidSignatureLength(); @@ -37,30 +41,41 @@ contract SignatureChecker is Ownable { isNotary[_notaryAddress] = false; } + // Check to see that the digest is a merkle root of a keccak256 hash of a leafs = (keccak(value), keccak(manifest)) + function verify_digest(bytes32 _digest, bytes32 _manifest, bytes32 _value) internal pure returns (bool) { + bytes32 root = keccak256(abi.encodePacked(_value, _manifest)); + return _digest == root; + } + /// @notice Verifies a signature - /// @param _hash The hash of the data that was signed + /// @param digest The hash of the data that was signed /// @param v The recovery id /// @param r The R value of the signature /// @param s The S value of the signature /// @param signer The address that signed the data - function verifyNotarySignature(bytes32 _hash, uint8 v, bytes32 r, bytes32 s, address signer) + /// @param manifest The manifest of the data + /// @param value The value of the data + function verifyNotarySignature(bytes32 digest, uint8 v, bytes32 r, bytes32 s, address signer, bytes32 manifest, bytes32 value) external - view returns (bool) { // check if the signer is a notary if (!isNotary[signer]) { revert InvalidNotary(); } + if (!verify_digest(digest, manifest, value)) { + revert InvalidDigest(); + } // r is the x-coordinate of the curve point // s is the y-coordinate of the curve point // v is 27 or 28 based on the y-value being even or odd // verify the signature - address recoveredSigner = ecrecover(_hash, v, r, s); + address recoveredSigner = ecrecover(digest, v, r, s); if (recoveredSigner != signer) { revert InvalidSignature(); } + digests[msg.sender] = digest; return true; } -} +} \ No newline at end of file diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index 96b8041..b5efa3e 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -18,7 +18,9 @@ contract SignatureCheckerTest is Test { bytes32 r = bytes32(0x86c6ab86ac26bfdfd245ab65a05e90cd18afe9f810acb42532adf7570cd0ed77); bytes32 s = bytes32(0x17370b1c7a7d7d96155e6144a9bfc9265f81c354b1cb4af7cebe52e601dabfef); uint8 v = 27; - assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer), true); + + + assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true); } } \ No newline at end of file From 27c34c9e058f9aee6e8d97e9260c88d55c3ab61a Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 17 Feb 2025 16:46:35 -0700 Subject: [PATCH 5/7] forge: fmt --- src/SignatureChecker.sol | 18 ++++++++++++------ test/SignatureChecker.t.sol | 4 +--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/SignatureChecker.sol b/src/SignatureChecker.sol index 757cc3b..b0ba0ff 100644 --- a/src/SignatureChecker.sol +++ b/src/SignatureChecker.sol @@ -12,7 +12,8 @@ contract SignatureChecker is Ownable { /// valid digests for a given address mapping(address => bytes32) public digests; - uint256 public constant BN254_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 public constant BN254_MODULUS = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; /// @notice Error for invalid signatures error InvalidSignature(); @@ -55,10 +56,15 @@ contract SignatureChecker is Ownable { /// @param signer The address that signed the data /// @param manifest The manifest of the data /// @param value The value of the data - function verifyNotarySignature(bytes32 digest, uint8 v, bytes32 r, bytes32 s, address signer, bytes32 manifest, bytes32 value) - external - returns (bool) - { + function verifyNotarySignature( + bytes32 digest, + uint8 v, + bytes32 r, + bytes32 s, + address signer, + bytes32 manifest, + bytes32 value + ) external returns (bool) { // check if the signer is a notary if (!isNotary[signer]) { revert InvalidNotary(); @@ -78,4 +84,4 @@ contract SignatureChecker is Ownable { digests[msg.sender] = digest; return true; } -} \ No newline at end of file +} diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index b5efa3e..bd9f9e9 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -19,8 +19,6 @@ contract SignatureCheckerTest is Test { bytes32 s = bytes32(0x17370b1c7a7d7d96155e6144a9bfc9265f81c354b1cb4af7cebe52e601dabfef); uint8 v = 27; - assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true); - } -} \ No newline at end of file +} From e46a167743ea416f9ffe15e3cde2985296935016 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Mon, 17 Feb 2025 16:48:29 -0700 Subject: [PATCH 6/7] forge: build todo --- test/SignatureChecker.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index bd9f9e9..deb8413 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -19,6 +19,8 @@ contract SignatureCheckerTest is Test { bytes32 s = bytes32(0x17370b1c7a7d7d96155e6144a9bfc9265f81c354b1cb4af7cebe52e601dabfef); uint8 v = 27; - assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true); + // TODO(WJ 2025-02-17): get manifest and value from the digest + + // assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true); } } From 276b7381f114ba4400933da09c014b52e02ed1a0 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen Date: Tue, 18 Feb 2025 12:50:09 -0700 Subject: [PATCH 7/7] test: proper test vector --- test/SignatureChecker.t.sol | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/SignatureChecker.t.sol b/test/SignatureChecker.t.sol index deb8413..0c6744e 100644 --- a/test/SignatureChecker.t.sol +++ b/test/SignatureChecker.t.sol @@ -11,16 +11,17 @@ contract SignatureCheckerTest is Test { signatureChecker = new SignatureChecker(0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F); } - function test_isValidSignatureNow() public view { - // TEST vector from NOTARY + function test_isValidSignatureNow() public { + // TEST vector from web-prover @ githash 2dc768e818d6f9fef575a88a2ceb80c0ed11974f address signer = 0xfdf07A5dCfa7b74f4c28DAb23eaD8B1c43Be801F; - bytes32 digest = bytes32(0x0ad25b24a05589ed9f2332ac85f5690c8400019f32858c2f6bf24877362d41db); - bytes32 r = bytes32(0x86c6ab86ac26bfdfd245ab65a05e90cd18afe9f810acb42532adf7570cd0ed77); - bytes32 s = bytes32(0x17370b1c7a7d7d96155e6144a9bfc9265f81c354b1cb4af7cebe52e601dabfef); + bytes32 digest = bytes32(0xe45537be7b5cd288c9c46b7e027b4f5a66202146012f792c1b1cabb65828994b); + bytes32 r = bytes32(0x36e820b3524e9ffffe0b4ee49e4131cc362fd161821c1dfc8757dc6186f31c96); + bytes32 s = bytes32(0x416e537065673e3028eca37cf3cbe805a3d2fafbc47235fee5e89df5f0509a9c); uint8 v = 27; - // TODO(WJ 2025-02-17): get manifest and value from the digest + bytes32 value = 0x8452c9b9140222b08593a26daa782707297be9f7b3e8281d7b4974769f19afd0; + bytes32 manifest = 0x7df909980a1642d0370a4a510422201ce525da6b319a7b9e9656771fa7336d5a; - // assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true); + assertEq(signatureChecker.verifyNotarySignature(digest, v, r, s, signer, manifest, value), true); } }