-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathModuleAuthDynamic.sol
More file actions
194 lines (168 loc) · 7.41 KB
/
ModuleAuthDynamic.sol
File metadata and controls
194 lines (168 loc) · 7.41 KB
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
// Copyright Immutable Pty Ltd 2018 - 2023
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.17;
import "./ModuleAuthUpgradable.sol";
import "./ImageHashKey.sol";
import "./ModuleStorage.sol";
import "./NonceKey.sol";
import "../../Wallet.sol";
import "../../utils/LibBytes.sol";
abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
using LibBytes for bytes;
bytes32 public immutable INIT_CODE_HASH;
address public immutable FACTORY;
address public immutable IMMUTABLE_SIGNER_CONTRACT;
constructor(address _factory, address _startupWalletImpl, address _immutableSignerContract) {
// Build init code hash of the deployed wallets using that module
bytes32 initCodeHash = keccak256(abi.encodePacked(Wallet.creationCode, uint256(uint160(_startupWalletImpl))));
INIT_CODE_HASH = initCodeHash;
FACTORY = _factory;
IMMUTABLE_SIGNER_CONTRACT = _immutableSignerContract;
}
/**
* @notice Verify if signer is default wallet owner
* @param _hash Hashed signed message
* @param _signature Array of signatures with signers ordered
* like the the keys in the multisig configs
*
* @dev The signature must be solidity packed and contain the total number of owners,
* the threshold, the weight and either the address or a signature for each owner.
*
* Each weight & (address or signature) pair is prefixed by a flag that signals if such pair
* contains an address or a signature. The aggregated weight of the signatures must surpass the threshold.
*
* Flag types:
* 0x00 - Signature
* 0x01 - Address
*
* E.g:
* abi.encodePacked(
* uint16 threshold,
* uint8 01, uint8 weight_1, address signer_1,
* uint8 00, uint8 weight_2, bytes signature_2,
* ...
* uint8 01, uint8 weight_5, address signer_5
* )
*/
function _signatureValidation(
bytes32 _hash,
bytes memory _signature
)
internal virtual override returns (bool)
{
(bool verified, bool needsUpdate, bytes32 imageHash) = _signatureValidationWithUpdateCheck(_hash, _signature);
if (needsUpdate) {
updateImageHashInternal(imageHash);
}
return verified;
}
/**
* @notice Verify signature and determine if image hash needs updating
* @param _hash Hashed signed message
* @param _signature Packed signature data containing threshold, flags, weights, and addresses/signatures
* @return verified True if the signature is valid and weight threshold is met
* @return needsUpdate True if the image hash needs to be stored (first transaction)
* @return imageHash The computed image hash from the signature
*
* @dev This function parses the signature, recovers/reads signer addresses, and validates them.
* For defensive validation, each extracted address is compared against IMMUTABLE_SIGNER_CONTRACT.
* If a match is found, it is recorded.
*
* Special case: If this is the first transaction (nonce was 0, now 1 after increment) and the immutable
* signer contract is one of the signers, the signature is automatically validated and approved without
* checking the stored image hash. This allows the immutable signer to bootstrap the wallet on first use.
*/
function _signatureValidationWithUpdateCheck(
bytes32 _hash,
bytes memory _signature
)
internal view override returns (bool, bool, bytes32)
{
(
uint16 threshold, // required threshold signature
uint256 rindex // read index
) = _signature.readFirstUint16();
// Start image hash generation
bytes32 imageHash = bytes32(uint256(threshold));
// Acumulated weight of signatures
uint256 totalWeight;
// Track if immutable signer contract is one of the signers
bool immutableSignerContractFound = false;
// Iterate until the image is completed
while (rindex < _signature.length) {
// Read next item type and addrWeight
uint256 flag; uint256 addrWeight; address addr;
(flag, addrWeight, rindex) = _signature.readUint8Uint8(rindex);
if (flag == FLAG_ADDRESS) {
// Read plain address
(addr, rindex) = _signature.readAddress(rindex);
} else if (flag == FLAG_SIGNATURE) {
// Read single signature and recover signer
bytes memory signature;
(signature, rindex) = _signature.readBytes66(rindex);
addr = recoverSigner(_hash, signature);
// Acumulate total weight of the signature
totalWeight += addrWeight;
} else if (flag == FLAG_DYNAMIC_SIGNATURE) {
// Read signer
(addr, rindex) = _signature.readAddress(rindex);
// Read signature size
uint256 size;
(size, rindex) = _signature.readUint16(rindex);
// Read dynamic size signature
bytes memory signature;
(signature, rindex) = _signature.readBytes(rindex, size);
require(isValidSignature(_hash, addr, signature), "ModuleAuthDynamic#_signatureValidation: INVALID_SIGNATURE");
// Acumulate total weight of the signature
totalWeight += addrWeight;
} else {
revert("ModuleAuthDynamic#_signatureValidation INVALID_FLAG");
}
// Defensive check: compare extracted address with target address
if (IMMUTABLE_SIGNER_CONTRACT != address(0) && addr == IMMUTABLE_SIGNER_CONTRACT) {
immutableSignerContractFound = true;
}
// Write weight and address to image
imageHash = keccak256(abi.encode(imageHash, addrWeight, addr));
}
// Check if this is the first transaction (nonce was 0 before increment) and immutable signer contract is one of the signers
// Note: _validateNonce increments the nonce before _signatureValidation is called, so we check for 1, not 0
uint256 currentNonce = uint256(ModuleStorage.readBytes32Map(NonceKey.NONCE_KEY, bytes32(uint256(0))));
if (currentNonce == 1 && immutableSignerContractFound) {
return (true, true, imageHash);
}
(bool verified, bool needsUpdate) = _isValidImage(imageHash);
return ((totalWeight >= threshold && verified), needsUpdate, imageHash);
}
/**
* @notice Validates the signature image with the salt used to deploy the contract
* if there is no stored image hash. This will happen prior to the first meta
* transaction. Subsequently, validate the
* signature image with a valid image hash defined in the contract storage
* @param _imageHash Hash image of signature
* @return true if the signature image is valid, and true if the image hash needs to be updated
*/
function _isValidImage(bytes32 _imageHash) internal view override returns (bool, bool) {
bytes32 storedImageHash = ModuleStorage.readBytes32(ImageHashKey.IMAGE_HASH_KEY);
if (storedImageHash == 0) {
// No image hash stored. Check that the image hash was used as the salt when
// deploying the wallet proxy contract.
bool authenticated = address(
uint160(uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
FACTORY,
_imageHash,
INIT_CODE_HASH
)
)
))
) == address(this);
// Indicate need to update = true. This will trigger a call to store the image hash
return (authenticated, true);
}
// Image hash has been stored.
return ((_imageHash != bytes32(0) && _imageHash == storedImageHash), false);
}
}