Skip to content

Commit

Permalink
feat: Add eip712 authenticator (#20)
Browse files Browse the repository at this point in the history
* chore: install bytes lib

* forge install: solidity-bytes-utils

v0.8.0

* chore: removed solidity bytes utils

* feat: propose sig verification

* feat: Signature utils contract for tests

* refactor: formatting

* fix: proper eip712 encoding for Propose Sigs

* refactor: moved function signature to abstract base contract

* fix: decoding propose data for sig verification

* refactor: moved typed data hashing functions to SigUtils

* test: updated eth sig auth tests

* fix: cleaned up remappings.txt

* chore: cleared submodule cache

* feat: dedicated type hashing lib

* feat: store salt to prevent replay attacks

* test: propose signature revert cases

* style: formatting

* refactor: moved Signature Verifier to utils

* feat: eip712  authentication for vote

* feat: tests for eip712 vote

* fix: removed redundant 0 check on sig verifier

Co-authored-by: Deep Work Station <[email protected]>
Co-authored-by: Orlando <[email protected]>
  • Loading branch information
3 people authored Jan 25, 2023
1 parent ef22a88 commit 8971527
Show file tree
Hide file tree
Showing 12 changed files with 576 additions and 11 deletions.
4 changes: 0 additions & 4 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,3 @@
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable
branch = v4.8.0
[submodule "lib/solidity-bytes-utils"]
path = lib/solidity-bytes-utils
url = https://github.com/GNSPS/solidity-bytes-utils
branch = v0.8.0
1 change: 0 additions & 1 deletion lib/solidity-bytes-utils
Submodule solidity-bytes-utils deleted from 6458fb
1 change: 0 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ forge-gas-snapshot/=lib/forge-gas-snapshot/src
@prb/test/=lib/prb-test/src/
@zodiac/=lib/zodiac/contracts/
@gnosis.pm/safe-contracts=lib/safe-contracts
@solidity-bytes-utils/contracts/=lib/solidity-bytes-utils/contracts
4 changes: 4 additions & 0 deletions src/authenticators/Authenticator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
pragma solidity ^0.8.15;

abstract contract Authenticator {
bytes4 internal constant PROPOSE_SELECTOR =
bytes4(keccak256("propose(address,string,(address,bytes),(uint8,bytes)[])"));
bytes4 constant VOTE_SELECTOR = bytes4(keccak256("vote(address,uint256,uint8,(uint8,bytes)[])"));

function _call(address target, bytes4 functionSelector, bytes memory data) internal {
(bool success, ) = target.call(abi.encodePacked(functionSelector, data));
if (!success) {
Expand Down
31 changes: 31 additions & 0 deletions src/authenticators/EthSigAuthenticator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

import "./Authenticator.sol";
import "../utils/SignatureVerifier.sol";

contract EthSigAuthenticator is Authenticator, SignatureVerifier {
error InvalidFunctionSelector();

constructor(string memory name, string memory version) SignatureVerifier(name, version) {}

function authenticate(
uint8 v,
bytes32 r,
bytes32 s,
uint256 salt,
address target,
bytes4 functionSelector,
bytes calldata data
) external {
if (functionSelector == PROPOSE_SELECTOR) {
_verifyProposeSig(v, r, s, salt, target, data);
} else if (functionSelector == VOTE_SELECTOR) {
_verifyVoteSig(v, r, s, salt, target, data);
} else {
revert InvalidFunctionSelector();
}
_call(target, functionSelector, data);
}
}
30 changes: 30 additions & 0 deletions src/utils/SOCHash.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

import "src/types.sol";

/// @title SOC Types Hashing Library
/// @notice This library contains functions for hashing SOC types for use in eip712 signatures.
/// TODO: rename once we have a better name for SOC
library SOCHash {
bytes32 private constant STRATEGY_TYPEHASH = keccak256("Strategy(address addy,bytes params)");
bytes32 private constant INDEXED_STRATEGY_TYPEHASH = keccak256("IndexedStrategy(uint8 index,bytes params)");
bytes32 private constant PROPOSE_TYPEHASH =
keccak256(
"Propose(address space,address author,string metadataUri,Strategy executionStrategy,"
"IndexedStrategy[] userVotingStrategies,uint256 salt)"
);

function hash(Strategy memory strategy) internal pure returns (bytes32) {
return keccak256(abi.encode(STRATEGY_TYPEHASH, strategy));
}

function hash(IndexedStrategy[] memory indexedStrategies) internal pure returns (bytes32) {
bytes32[] memory indexedStrategyHashes = new bytes32[](indexedStrategies.length);
for (uint256 i = 0; i < indexedStrategies.length; i++) {
indexedStrategyHashes[i] = keccak256(abi.encode(INDEXED_STRATEGY_TYPEHASH, indexedStrategies[i]));
}
return keccak256(abi.encodePacked(indexedStrategyHashes));
}
}
89 changes: 89 additions & 0 deletions src/utils/SignatureVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "src/types.sol";
import { SOCHash } from "src/utils/SOCHash.sol";

abstract contract SignatureVerifier is EIP712 {
using SOCHash for Strategy;
using SOCHash for IndexedStrategy[];

error InvalidSignature();
error SaltAlreadyUsed();

bytes32 private constant PROPOSE_TYPEHASH =
keccak256(
"Propose(address space,address author,string metadataUri,Strategy executionStrategy,"
"IndexedStrategy[] userVotingStrategies,uint256 salt)"
);
bytes32 private constant VOTE_TYPEHASH =
keccak256(
"Vote(address space,address voter,uint256 proposalId,Choice choice,"
"IndexedStrategy[] userVotingStrategies,uint256 salt)"
);

mapping(address => mapping(uint256 => bool)) private usedSalts;

constructor(string memory name, string memory version) EIP712(name, version) {}

function _verifyProposeSig(uint8 v, bytes32 r, bytes32 s, uint256 salt, address space, bytes memory data) internal {
(
address author,
string memory metadataUri,
Strategy memory executionStrategy,
IndexedStrategy[] memory userVotingStrategies
) = abi.decode(data, (address, string, Strategy, IndexedStrategy[]));

if (usedSalts[author][salt]) revert SaltAlreadyUsed();

address recoveredAddress = ECDSA.recover(
_hashTypedDataV4(
keccak256(
abi.encode(
PROPOSE_TYPEHASH,
space,
author,
keccak256(bytes(metadataUri)),
executionStrategy.hash(),
userVotingStrategies.hash(),
salt
)
)
),
v,
r,
s
);

if (recoveredAddress != author) revert InvalidSignature();

// Mark salt as used to prevent replay attacks
usedSalts[author][salt] = true;
}

function _verifyVoteSig(uint8 v, bytes32 r, bytes32 s, uint256 salt, address space, bytes memory data) internal {
(address voter, uint256 proposeId, Choice choice, IndexedStrategy[] memory userVotingStrategies) = abi.decode(
data,
(address, uint256, Choice, IndexedStrategy[])
);

if (usedSalts[voter][salt]) revert SaltAlreadyUsed();

address recoveredAddress = ECDSA.recover(
_hashTypedDataV4(
keccak256(abi.encode(VOTE_TYPEHASH, space, voter, proposeId, choice, userVotingStrategies.hash(), salt))
),
v,
r,
s
);

if (recoveredAddress != voter) revert InvalidSignature();

// Mark salt as used to prevent replay attacks
usedSalts[voter][salt] = true;
}
}
Loading

0 comments on commit 8971527

Please sign in to comment.