From 2876ed73f35be3aca4cebad0d97841a1e236dead Mon Sep 17 00:00:00 2001 From: Yerassyl Zhanymkanov Date: Sat, 21 Oct 2023 21:39:26 +0600 Subject: [PATCH 1/2] feat: As a user, I want to have an example `ecrecover` module --- .../src/examples/modules/ECDSAModule.sol | 52 +++++++++++++ contracts/test/example/ECDSAModule.t.sol | 76 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 contracts/src/examples/modules/ECDSAModule.sol create mode 100644 contracts/test/example/ECDSAModule.t.sol diff --git a/contracts/src/examples/modules/ECDSAModule.sol b/contracts/src/examples/modules/ECDSAModule.sol new file mode 100644 index 00000000..87baa186 --- /dev/null +++ b/contracts/src/examples/modules/ECDSAModule.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { ECDSA } from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; +import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol"; + +import { AbstractModule } from "../../interface/AbstractModule.sol"; +import { AttestationPayload } from "../../types/Structs.sol"; + +/** + * @title ECDSA Module + * @author Consensys x DappSheriff + * @notice This contract illustrates a valid Module that is used to verify ECDSA signatures of payload + */ +contract ECDSAModule is AbstractModule, Ownable { + address public signer; + mapping(uint256 => bool) public usedNonces; // nonce => used + + constructor(address initialOwner, address _signer) { + signer = _signer; + } + + /// @dev This empty method prevents Foundry from counting this contract in code coverage + function test() public {} + + /** + * @notice This method is used to run the module's validation logic + * @param attestationPayload - AttestationPayload containing the user address as `subject` and nonce as `attestationData` + * @param validationPayload - Payload encoded with abi.encode(uint256).toEthSignedMessageHash().sign(signer) + */ + function run( + AttestationPayload memory attestationPayload, + bytes memory validationPayload, + address /*txSender*/, + uint256 /*value*/ + ) public override { + address signee = abi.decode(attestationPayload.subject, (address)); + uint256 nonce = abi.decode(attestationPayload.attestationData, (uint256)); + if (usedNonces[nonce]) { + revert("Nonce already used"); + } + + bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(signee, nonce)); + require(ECDSA.recover(hash, validationPayload) == signer, "Wrong signature"); + + usedNonces[nonce] = true; + } + + function setSigner(address _signer) external onlyOwner { + signer = _signer; + } +} diff --git a/contracts/test/example/ECDSAModule.t.sol b/contracts/test/example/ECDSAModule.t.sol new file mode 100644 index 00000000..1f3bea1f --- /dev/null +++ b/contracts/test/example/ECDSAModule.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { ECDSA } from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; + +import { ECDSAModule } from "../../src/examples/modules/ECDSAModule.sol"; +import { AttestationPayload } from "../../src/types/Structs.sol"; + +contract ECDSAModuleTest is Test { + ECDSAModule private ecdsaModule; + address private signer; + uint256 private signerPk; + + function setUp() public { + (signer, signerPk) = makeAddrAndKey("veraxUser"); + ecdsaModule = new ECDSAModule(signer, signer); + + vm.deal(signer, 1 ether); + } + + function test_ECDSAModule_validSignature() public { + address user = makeAddr("user"); + uint256 nonce = 1234567; + AttestationPayload memory attestationPayload = AttestationPayload( + bytes32(uint256(1234)), + 0, + abi.encode(user), + abi.encode(nonce) + ); + + bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(user, nonce)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, hash); + bytes memory signature = abi.encodePacked(r, s, v); + + ecdsaModule.run(attestationPayload, signature, signer, 0); + } + + function test_ECDSAModule_revertInvalidSignature() public { + (address user, uint256 fakeKey) = makeAddrAndKey("user"); + uint256 nonce = 1234567; + AttestationPayload memory attestationPayload = AttestationPayload( + bytes32(uint256(1234)), + 0, + abi.encode(user), + abi.encode(nonce) + ); + + bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(user, nonce)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(fakeKey, hash); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.expectRevert("Wrong signature"); + ecdsaModule.run(attestationPayload, signature, user, 0); + } + + function test_ECDSAModule_revertNonceUsed() public { + address user = makeAddr("user"); + uint256 nonce = 1234567; + AttestationPayload memory attestationPayload = AttestationPayload( + bytes32(uint256(1234)), + 0, + abi.encode(user), + abi.encode(nonce) + ); + + bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(user, nonce)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, hash); + bytes memory signature = abi.encodePacked(r, s, v); + + ecdsaModule.run(attestationPayload, signature, user, 0); + vm.expectRevert("Nonce already used"); + ecdsaModule.run(attestationPayload, signature, user, 0); + } +} From 0c1a41e13b11f0253c73c196193a0509f82b1d77 Mon Sep 17 00:00:00 2001 From: Yerassyl Zhanymkanov Date: Sat, 21 Oct 2023 22:08:25 +0600 Subject: [PATCH 2/2] chore: Refactor ECDSA modules --- .../src/examples/modules/ECDSAModule.sol | 2 +- contracts/test/example/ECDSAModule.t.sol | 57 ++++++------------- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/contracts/src/examples/modules/ECDSAModule.sol b/contracts/src/examples/modules/ECDSAModule.sol index 87baa186..06847709 100644 --- a/contracts/src/examples/modules/ECDSAModule.sol +++ b/contracts/src/examples/modules/ECDSAModule.sol @@ -16,7 +16,7 @@ contract ECDSAModule is AbstractModule, Ownable { address public signer; mapping(uint256 => bool) public usedNonces; // nonce => used - constructor(address initialOwner, address _signer) { + constructor(address _signer) { signer = _signer; } diff --git a/contracts/test/example/ECDSAModule.t.sol b/contracts/test/example/ECDSAModule.t.sol index 1f3bea1f..2bc3f736 100644 --- a/contracts/test/example/ECDSAModule.t.sol +++ b/contracts/test/example/ECDSAModule.t.sol @@ -11,66 +11,45 @@ contract ECDSAModuleTest is Test { ECDSAModule private ecdsaModule; address private signer; uint256 private signerPk; + AttestationPayload private attestationPayload; + + address private user = makeAddr("veraxUser"); + uint256 private nonce = 1234567; function setUp() public { - (signer, signerPk) = makeAddrAndKey("veraxUser"); - ecdsaModule = new ECDSAModule(signer, signer); + (signer, signerPk) = makeAddrAndKey("veraxSigner"); + ecdsaModule = new ECDSAModule(signer); - vm.deal(signer, 1 ether); + attestationPayload = AttestationPayload(bytes32(uint256(1234)), 0, abi.encode(user), abi.encode(nonce)); } function test_ECDSAModule_validSignature() public { - address user = makeAddr("user"); - uint256 nonce = 1234567; - AttestationPayload memory attestationPayload = AttestationPayload( - bytes32(uint256(1234)), - 0, - abi.encode(user), - abi.encode(nonce) - ); - - bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(user, nonce)); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, hash); - bytes memory signature = abi.encodePacked(r, s, v); + bytes memory signature = makeSignature(signerPk); ecdsaModule.run(attestationPayload, signature, signer, 0); } function test_ECDSAModule_revertInvalidSignature() public { - (address user, uint256 fakeKey) = makeAddrAndKey("user"); - uint256 nonce = 1234567; - AttestationPayload memory attestationPayload = AttestationPayload( - bytes32(uint256(1234)), - 0, - abi.encode(user), - abi.encode(nonce) - ); + Account memory fakeSigner = makeAccount("user"); - bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(user, nonce)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(fakeKey, hash); - bytes memory signature = abi.encodePacked(r, s, v); + bytes memory signature = makeSignature(fakeSigner.key); vm.expectRevert("Wrong signature"); ecdsaModule.run(attestationPayload, signature, user, 0); } function test_ECDSAModule_revertNonceUsed() public { - address user = makeAddr("user"); - uint256 nonce = 1234567; - AttestationPayload memory attestationPayload = AttestationPayload( - bytes32(uint256(1234)), - 0, - abi.encode(user), - abi.encode(nonce) - ); - - bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(user, nonce)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, hash); - bytes memory signature = abi.encodePacked(r, s, v); + bytes memory signature = makeSignature(signerPk); ecdsaModule.run(attestationPayload, signature, user, 0); vm.expectRevert("Nonce already used"); ecdsaModule.run(attestationPayload, signature, user, 0); } + + function makeSignature(uint256 _signer) private view returns (bytes memory) { + bytes32 hash = ECDSA.toEthSignedMessageHash(abi.encodePacked(user, nonce)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signer, hash); + return abi.encodePacked(r, s, v); + } }