|
1 | 1 | // SPDX-FileCopyrightText: 2020-2025 IEXEC BLOCKCHAIN TECH <[email protected]> |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
4 | | -pragma solidity ^0.6.0; |
5 | | -pragma experimental ABIEncoderV2; |
| 4 | +pragma solidity ^0.8.0; |
6 | 5 |
|
7 | | -import "@iexec/solidity/contracts/ERC734/IERC734.sol"; |
8 | | -import "@iexec/solidity/contracts/ERC1271/IERC1271.sol"; |
9 | | -import "@iexec/solidity/contracts/ERC1654/IERC1654.sol"; |
10 | | -import {PocoStorageLib} from "../libs/PocoStorageLib.sol"; |
11 | | -import "./FacetBase.sol"; |
| 6 | +import {IERC1271} from "@openzeppelin/contracts-v5/interfaces/IERC1271.sol"; |
| 7 | +import {ECDSA} from "@openzeppelin/contracts-v5/utils/cryptography/ECDSA.sol"; |
| 8 | +import {MessageHashUtils} from "@openzeppelin/contracts-v5/utils/cryptography/MessageHashUtils.sol"; |
| 9 | +import {FacetBase} from "./FacetBase.v8.sol"; |
| 10 | +import {IERC734} from "../external/interfaces/IERC734.sol"; |
| 11 | +import {PocoStorageLib} from "../libs/PocoStorageLib.v8.sol"; |
12 | 12 |
|
13 | 13 | contract SignatureVerifier is FacetBase { |
| 14 | + using ECDSA for bytes32; |
| 15 | + |
14 | 16 | /** |
15 | | - * Prepare message/structure predicat used for signing |
| 17 | + * Hash a Typed Data using the configured domain. |
| 18 | + * @param structHash The original structure hash. |
16 | 19 | */ |
17 | | - function _toEthSignedMessage(bytes32 _msgHash) internal pure returns (bytes memory) { |
18 | | - return abi.encodePacked("\x19Ethereum Signed Message:\n32", _msgHash); |
| 20 | + function _toTypedDataHash(bytes32 structHash) internal view returns (bytes32) { |
| 21 | + PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage(); |
| 22 | + return MessageHashUtils.toTypedDataHash($.m_eip712DomainSeparator, structHash); |
19 | 23 | } |
20 | 24 |
|
21 | | - function _toEthTypedStruct( |
22 | | - bytes32 _structHash, |
23 | | - bytes32 _domainHash |
24 | | - ) internal pure returns (bytes memory) { |
25 | | - return abi.encodePacked("\x19\x01", _domainHash, _structHash); |
| 25 | + /** |
| 26 | + * @notice Verify that an Ethereum Signed Message is signed by a particular account. |
| 27 | + * @param account The expected signer account. |
| 28 | + * @param message The original message that was signed. |
| 29 | + * @param signature The signature to be verified. |
| 30 | + */ |
| 31 | + function _verifySignatureOfEthSignedMessage( |
| 32 | + address account, |
| 33 | + bytes memory message, |
| 34 | + bytes calldata signature |
| 35 | + ) internal view returns (bool) { |
| 36 | + return |
| 37 | + _verifySignature( |
| 38 | + account, |
| 39 | + MessageHashUtils.toEthSignedMessageHash(keccak256(message)), |
| 40 | + signature |
| 41 | + ); |
26 | 42 | } |
27 | 43 |
|
28 | 44 | /** |
29 | | - * recover EOA signature (support both 65 bytes traditional and 64 bytes format EIP2098 format) |
| 45 | + * @notice Verify that a message is signed by an EOA or an ERC1271 smart contract. |
| 46 | + * |
| 47 | + * It supports short signatures. |
| 48 | + * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] |
| 49 | + * & https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4915 |
| 50 | + * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0/contracts/utils/cryptography/ECDSA.sol#L112 |
| 51 | + * |
| 52 | + * @param account The expected signer account. |
| 53 | + * @param messageHash The message hash that was signed. |
| 54 | + * @param signature The signature to be verified. |
30 | 55 | */ |
31 | | - function _recover(bytes32 _hash, bytes memory _sign) internal pure returns (address) { |
32 | | - bytes32 r; |
33 | | - bytes32 s; |
34 | | - uint8 v; |
35 | | - |
36 | | - if (_sign.length == 65) // 65bytes: (r,s,v) form |
37 | | - { |
38 | | - assembly { |
39 | | - r := mload(add(_sign, 0x20)) |
40 | | - s := mload(add(_sign, 0x40)) |
41 | | - v := byte(0, mload(add(_sign, 0x60))) |
42 | | - } |
43 | | - } else if (_sign.length == 64) // 64bytes: (r,vs) form → see EIP2098 |
44 | | - { |
45 | | - assembly { |
46 | | - r := mload(add(_sign, 0x20)) |
47 | | - s := and( |
48 | | - mload(add(_sign, 0x40)), |
49 | | - 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff |
50 | | - ) |
51 | | - v := shr(7, byte(0, mload(add(_sign, 0x40)))) |
52 | | - } |
| 56 | + function _verifySignature( |
| 57 | + address account, |
| 58 | + bytes32 messageHash, |
| 59 | + bytes calldata signature |
| 60 | + ) internal view returns (bool) { |
| 61 | + // When the account is a smart contract, delegate signature verification. |
| 62 | + if (account.code.length > 0) { |
| 63 | + try IERC1271(account).isValidSignature(messageHash, signature) returns (bytes4 result) { |
| 64 | + return result == IERC1271.isValidSignature.selector; |
| 65 | + } catch {} |
| 66 | + return false; |
| 67 | + } |
| 68 | + // When the account is an EoA, check signature validity. |
| 69 | + address recoveredAddress = address(0); // Initialize local variable |
| 70 | + if (signature.length == 65) { |
| 71 | + //slither-disable-next-line unused-return |
| 72 | + (recoveredAddress, , ) = messageHash.tryRecover(signature); |
| 73 | + } else if (signature.length == 64) { |
| 74 | + //slither-disable-next-line unused-return |
| 75 | + (recoveredAddress, , ) = messageHash.tryRecover( // short signature |
| 76 | + bytes32(signature[:32]), |
| 77 | + bytes32(signature[32:]) |
| 78 | + ); |
53 | 79 | } else { |
54 | 80 | revert("invalid-signature-format"); |
55 | 81 | } |
56 | | - |
57 | | - if (v < 27) v += 27; |
58 | | - require(v == 27 || v == 28, "invalid-signature-v"); |
59 | | - return ecrecover(_hash, v, r, s); |
| 82 | + return recoveredAddress == account; |
60 | 83 | } |
61 | 84 |
|
62 | 85 | /** |
63 | | - * Check if contract exist, otherwize assumed to be EOA |
| 86 | + * @notice Verify that a message hash is presigned by a particular account. |
| 87 | + * @param account The expected presigner account. |
| 88 | + * @param messageHash The message hash that was presigned. |
64 | 89 | */ |
65 | | - function _isContract(address account) internal view returns (bool) { |
66 | | - // According to EIP-1052, 0x0 is the value returned for not-yet created accounts |
67 | | - // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned |
68 | | - // for accounts without code, i.e. `keccak256('')` |
69 | | - bytes32 codehash; |
70 | | - bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; |
71 | | - // solhint-disable-next-line no-inline-assembly |
72 | | - assembly { |
73 | | - codehash := extcodehash(account) |
74 | | - } |
75 | | - return (codehash != accountHash && codehash != 0x0); |
| 90 | + function _verifyPresignature( |
| 91 | + address account, |
| 92 | + bytes32 messageHash |
| 93 | + ) internal view returns (bool) { |
| 94 | + PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage(); |
| 95 | + return account != address(0) && account == $.m_presigned[messageHash]; |
76 | 96 | } |
77 | 97 |
|
78 | 98 | /** |
79 | | - * Address to bytes32 casting to ERC734 |
| 99 | + * @notice Verify that a message hash is signed or presigned by a particular account. |
| 100 | + * @param account The expected signer or presigner account. |
| 101 | + * @param messageHash The message hash that was signed or presigned. |
| 102 | + * @param signature The signature to be verified. Not required for a presignature. |
80 | 103 | */ |
81 | | - function _addrToKey(address _addr) internal pure returns (bytes32) { |
82 | | - return bytes32(uint256(_addr)); |
| 104 | + function _verifySignatureOrPresignature( |
| 105 | + address account, |
| 106 | + bytes32 messageHash, |
| 107 | + bytes calldata signature |
| 108 | + ) internal view returns (bool) { |
| 109 | + return |
| 110 | + _verifyPresignature(account, messageHash) || |
| 111 | + _verifySignature(account, messageHash, signature); |
83 | 112 | } |
84 | 113 |
|
85 | 114 | /** |
86 | | - * Identity verification |
| 115 | + * @notice |
| 116 | + * This function makes an external call to an untrusted contract. It has to |
| 117 | + * be carefully called to avoid creating re-entrancy vulnerabilities. Calls to this function |
| 118 | + * has to be done before updating state variables. |
| 119 | + * |
| 120 | + * @notice Verify that an account is authorized based on a given restriction. |
| 121 | + * The given restriction can be: |
| 122 | + * (1) `0x`: No restriction, accept any address; |
| 123 | + * (2) `0x<same-address-than-restriction>`: Only accept the exact same address; |
| 124 | + * (3) `0x<ERC734-contract-address>`: Accept any address in a group (having |
| 125 | + * the given `GROUPMEMBER` purpose) inside an ERC734 Key Manager identity |
| 126 | + * contract. |
| 127 | + * @param restriction A simple address or an ERC734 identity contract |
| 128 | + * that might whitelist a given address in a group. |
| 129 | + * @param account An address to be checked. |
87 | 130 | */ |
88 | | - function _checkIdentity( |
89 | | - address _identity, |
90 | | - address _candidate, |
91 | | - uint256 _purpose |
92 | | - ) internal view returns (bool valid) { |
93 | | - return |
94 | | - _identity == _candidate || |
95 | | - IERC734(_identity).keyHasPurpose(_addrToKey(_candidate), _purpose); // Simple address || ERC 734 identity contract |
96 | | - } |
97 | | - |
98 | | - function _checkPresignature(address _identity, bytes32 _hash) internal view returns (bool) { |
99 | | - PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage(); |
100 | | - return _identity != address(0) && _identity == $.m_presigned[_hash]; |
101 | | - } |
102 | | - |
103 | | - function _checkSignature( |
104 | | - address _identity, |
105 | | - bytes32 _hash, |
106 | | - bytes memory _signature |
| 131 | + function _isAccountAuthorizedByRestriction( |
| 132 | + address restriction, |
| 133 | + address account |
107 | 134 | ) internal view returns (bool) { |
108 | | - if (_isContract(_identity)) { |
109 | | - try IERC1654(_identity).isValidSignature(_hash, _signature) returns (bytes4 value) { |
110 | | - return value == IERC1654(0).isValidSignature.selector; |
111 | | - } catch (bytes memory /*lowLevelData*/) {} |
112 | | - |
113 | | - return false; |
114 | | - } else { |
115 | | - return _recover(_hash, _signature) == _identity; |
| 135 | + if ( |
| 136 | + restriction == address(0) || // No restriction |
| 137 | + restriction == account // Simple address restriction |
| 138 | + ) { |
| 139 | + return true; |
116 | 140 | } |
117 | | - } |
118 | | - |
119 | | - function _checkSignature( |
120 | | - address _identity, |
121 | | - bytes memory _predicat, |
122 | | - bytes memory _signature |
123 | | - ) internal view returns (bool) { |
124 | | - if (_isContract(_identity)) { |
125 | | - try IERC1271(_identity).isValidSignature(_predicat, _signature) returns (bytes4 value) { |
126 | | - return value == IERC1271(0).isValidSignature.selector; |
127 | | - } catch (bytes memory /*lowLevelData*/) {} |
128 | | - |
129 | | - try IERC1654(_identity).isValidSignature(keccak256(_predicat), _signature) returns ( |
130 | | - bytes4 value |
131 | | - ) { |
132 | | - return value == IERC1654(0).isValidSignature.selector; |
133 | | - } catch (bytes memory /*lowLevelData*/) {} |
134 | | - |
135 | | - return false; |
136 | | - } else { |
137 | | - return _recover(keccak256(_predicat), _signature) == _identity; |
| 141 | + if (restriction.code.length > 0) { |
| 142 | + try |
| 143 | + IERC734(restriction).keyHasPurpose( // ERC734 identity contract restriction |
| 144 | + bytes32(uint256(uint160(account))), |
| 145 | + GROUPMEMBER_PURPOSE |
| 146 | + ) |
| 147 | + returns (bool success) { |
| 148 | + return success; |
| 149 | + } catch {} |
138 | 150 | } |
139 | | - } |
140 | | - |
141 | | - function _checkPresignatureOrSignature( |
142 | | - address _identity, |
143 | | - bytes32 _hash, |
144 | | - bytes memory _signature |
145 | | - ) internal view returns (bool) { |
146 | | - return |
147 | | - _checkPresignature(_identity, _hash) || _checkSignature(_identity, _hash, _signature); |
148 | | - } |
149 | | - |
150 | | - function _checkPresignatureOrSignature( |
151 | | - address _identity, |
152 | | - bytes memory _predicat, |
153 | | - bytes memory _signature |
154 | | - ) internal view returns (bool) { |
155 | | - return |
156 | | - _checkPresignature(_identity, keccak256(_predicat)) || |
157 | | - _checkSignature(_identity, _predicat, _signature); |
| 151 | + return false; |
158 | 152 | } |
159 | 153 | } |
0 commit comments