-
Notifications
You must be signed in to change notification settings - Fork 70
/
MultiOwnedECDSAModule.sol
256 lines (235 loc) · 9.37 KB
/
MultiOwnedECDSAModule.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
/* solhint-disable no-unused-import */
import {BaseAuthorizationModule} from "./BaseAuthorizationModule.sol";
import {EIP1271_MAGIC_VALUE} from "contracts/smart-account/interfaces/ISignatureValidator.sol";
import {UserOperation} from "@account-abstraction/contracts/interfaces/UserOperation.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IMultiOwnedECDSAModule} from "../interfaces/modules/IMultiOwnedECDSAModule.sol";
import {IAuthorizationModule} from "../interfaces/IAuthorizationModule.sol";
import {ISignatureValidator} from "../interfaces/ISignatureValidator.sol";
import {LibAddress} from "../libs/LibAddress.sol";
/**
* @title ECDSA Multi Ownership Authorization Module for Biconomy Smart Accounts.
* @dev Compatible with Biconomy Modular Interface v 0.1
* - It allows to validate user operations signed by EOA private key.
* - EIP-1271 compatible (ensures Smart Account can validate signed messages).
* - Multiple owners per Smart Account.
* - Does not support outdated eth_sign flow for cheaper validations
* (see https://support.metamask.io/hc/en-us/articles/14764161421467-What-is-eth-sign-and-why-is-it-a-risk-)
* !!!!!!! Only EOA owners supported, no Smart Account Owners
* @author Fil Makarov - <filipp.makarov@biconomy.io>
*/
contract MultiOwnedECDSAModule is
BaseAuthorizationModule,
IMultiOwnedECDSAModule
{
using ECDSA for bytes32;
using LibAddress for address;
string public constant NAME = "Multiowned ECDSA Ownership Module";
string public constant VERSION = "0.2.0";
// owner => smartAccount => isOwner
mapping(address => mapping(address => bool)) internal _smartAccountOwners;
mapping(address => uint256) internal numberOfOwners;
/// @inheritdoc IMultiOwnedECDSAModule
function initForSmartAccount(
address[] calldata eoaOwners
) external returns (address) {
if (numberOfOwners[msg.sender] != 0) {
revert AlreadyInitedForSmartAccount(msg.sender);
}
uint256 ownersToAdd = eoaOwners.length;
if (ownersToAdd == 0) revert NoOwnersToAdd();
for (uint256 i; i < ownersToAdd; ++i) {
// if (eoaOwners[i].isContract()) revert NotEOA(eoaOwner);
// This check is not possible because of [OP-041]
// See https://github.com/eth-infinitism/account-abstraction/blob/develop/erc/ERCS/erc-7562.md
// And https://github.com/eth-infinitism/bundler/issues/137 for details
if (eoaOwners[i] == address(0))
revert ZeroAddressNotAllowedAsOwner();
if (_smartAccountOwners[eoaOwners[i]][msg.sender])
revert OwnerAlreadyUsedForSmartAccount(
eoaOwners[i],
msg.sender
);
_smartAccountOwners[eoaOwners[i]][msg.sender] = true;
emit OwnershipTransferred(msg.sender, address(0), eoaOwners[i]);
}
numberOfOwners[msg.sender] = ownersToAdd;
return address(this);
}
/// @inheritdoc IMultiOwnedECDSAModule
function transferOwnership(
address owner,
address newOwner
) external override {
if (newOwner.isContract()) revert NotEOA(newOwner);
if (newOwner == address(0)) revert ZeroAddressNotAllowedAsOwner();
if (owner == newOwner)
revert OwnerAlreadyUsedForSmartAccount(newOwner, msg.sender);
//address(0) is not an owner initially as it points to the address(0) = false
if (!_smartAccountOwners[owner][msg.sender])
revert NotAnOwner(owner, msg.sender);
if (_smartAccountOwners[newOwner][msg.sender])
revert OwnerAlreadyUsedForSmartAccount(newOwner, msg.sender);
_transferOwnership(msg.sender, owner, newOwner);
}
/// @inheritdoc IMultiOwnedECDSAModule
function addOwner(address owner) external override {
if (owner.isContract()) revert NotEOA(owner);
if (owner == address(0)) revert ZeroAddressNotAllowedAsOwner();
if (_smartAccountOwners[owner][msg.sender])
revert OwnerAlreadyUsedForSmartAccount(owner, msg.sender);
_smartAccountOwners[owner][msg.sender] = true;
unchecked {
++numberOfOwners[msg.sender];
}
emit OwnershipTransferred(msg.sender, address(0), owner);
}
/// @inheritdoc IMultiOwnedECDSAModule
function removeOwner(address owner) external override {
if (!_smartAccountOwners[owner][msg.sender])
revert NotAnOwner(owner, msg.sender);
_smartAccountOwners[owner][msg.sender] = false;
unchecked {
--numberOfOwners[msg.sender];
}
emit OwnershipTransferred(msg.sender, owner, address(0));
}
/// @inheritdoc IMultiOwnedECDSAModule
function isOwner(
address smartAccount,
address eoaAddress
) external view override returns (bool) {
return _smartAccountOwners[eoaAddress][smartAccount];
}
/// @inheritdoc IAuthorizationModule
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash
) external view virtual override returns (uint256) {
(bytes memory cleanEcdsaSignature, ) = abi.decode(
userOp.signature,
(bytes, address)
);
if (_verifySignature(userOpHash, cleanEcdsaSignature, userOp.sender)) {
return VALIDATION_SUCCESS;
}
return SIG_VALIDATION_FAILED;
}
/**
* @inheritdoc ISignatureValidator
* @dev Validates a signature for a message.
* To be called from a Smart Account.
* @param dataHash Exact hash of the data that was signed.
* @param moduleSignature Signature to be validated.
* @return EIP1271_MAGIC_VALUE if signature is valid, 0xffffffff otherwise.
*/
function isValidSignature(
bytes32 dataHash,
bytes memory moduleSignature
) public view virtual override returns (bytes4) {
return
isValidSignatureForAddress(dataHash, moduleSignature, msg.sender);
}
/// @inheritdoc IMultiOwnedECDSAModule
function isValidSignatureForAddress(
bytes32 dataHash,
bytes memory moduleSignature,
address smartAccount
) public view virtual override returns (bytes4) {
if (
_verifySignature(
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n52",
dataHash,
smartAccount
)
),
moduleSignature,
smartAccount
)
) {
return EIP1271_MAGIC_VALUE;
}
return bytes4(0xffffffff);
}
/// @inheritdoc ISignatureValidator
function isValidSignatureUnsafe(
bytes32 dataHash,
bytes memory moduleSignature
) public view virtual returns (bytes4) {
return
isValidSignatureForAddressUnsafe(
dataHash,
moduleSignature,
msg.sender
);
}
/// @inheritdoc IMultiOwnedECDSAModule
function isValidSignatureForAddressUnsafe(
bytes32 dataHash,
bytes memory moduleSignature,
address smartAccount
) public view virtual returns (bytes4) {
if (_verifySignature(dataHash, moduleSignature, smartAccount)) {
return EIP1271_MAGIC_VALUE;
}
return bytes4(0xffffffff);
}
/// @inheritdoc IMultiOwnedECDSAModule
function getNumberOfOwners(
address smartAccount
) public view returns (uint256) {
return numberOfOwners[smartAccount];
}
/**
* @dev Transfers ownership for smartAccount and emits an event
* @param newOwner Smart Account address.
*/
function _transferOwnership(
address smartAccount,
address owner,
address newOwner
) internal {
_smartAccountOwners[owner][smartAccount] = false;
_smartAccountOwners[newOwner][smartAccount] = true;
emit OwnershipTransferred(smartAccount, owner, newOwner);
}
/**
* @dev Validates a signature for a message.
* @dev Check if signature was made over dataHash.toEthSignedMessageHash() or just dataHash
* The former is for personal_sign, the latter for the typed_data sign
* Only EOA owners supported, no Smart Account Owners
* For Smart Contract Owners check SmartContractOwnership Module instead
* @param dataHash Hash of the data to be validated.
* @param signature Signature to be validated.
* @param smartAccount expected signer Smart Account address.
* @return true if signature is valid, false otherwise.
*/
function _verifySignature(
bytes32 dataHash,
bytes memory signature,
address smartAccount
) internal view returns (bool) {
if (signature.length < 65) revert WrongSignatureLength();
address recovered = (dataHash.toEthSignedMessageHash()).recover(
signature
);
if (
recovered != address(0) &&
_smartAccountOwners[recovered][smartAccount]
) {
return true;
}
recovered = dataHash.recover(signature);
if (
recovered != address(0) &&
_smartAccountOwners[recovered][smartAccount]
) {
return true;
}
return false;
}
}