-
Notifications
You must be signed in to change notification settings - Fork 339
/
ERC1271.sol
316 lines (301 loc) · 16.2 KB
/
ERC1271.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {EIP712} from "../utils/EIP712.sol";
import {SignatureCheckerLib} from "../utils/SignatureCheckerLib.sol";
/// @notice ERC1271 mixin with nested EIP-712 approach.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/ERC1271.sol)
abstract contract ERC1271 is EIP712 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `keccak256("PersonalSign(bytes prefixed)")`.
bytes32 internal constant _PERSONAL_SIGN_TYPEHASH =
0x983e65e5148e570cd828ead231ee759a8d7958721a768f93bc4483ba005c32de;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC1271 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Validates the signature with ERC1271 return,
/// so that this account can also be used as a signer.
function isValidSignature(bytes32 hash, bytes calldata signature)
public
view
virtual
returns (bytes4 result)
{
bool success = _erc1271IsValidSignature(hash, _erc1271UnwrapSignature(signature));
/// @solidity memory-safe-assembly
assembly {
// `success ? bytes4(keccak256("isValidSignature(bytes32,bytes)")) : 0xffffffff`.
// We use `0xffffffff` for invalid, in convention with the reference implementation.
result := shl(224, or(0x1626ba7e, sub(0, iszero(success))))
}
}
/// @dev For automatic detection that the smart account supports the nested EIP-712 workflow.
/// By default, it returns `bytes32(bytes4(keccak256("supportsNestedTypedDataSign()")))`,
/// denoting support for the default behavior, as implemented in
/// `_erc1271IsValidSignatureViaNestedEIP712`, which is called in `isValidSignature`.
/// Future extensions should return a different non-zero `result` to denote different behavior.
/// This method intentionally returns bytes32 to allow freedom for future extensions.
function supportsNestedTypedDataSign() public view virtual returns (bytes32 result) {
result = bytes4(0xd620c85a);
}
/// @dev Returns the ERC1271 signer.
/// Override to return the signer `isValidSignature` checks against.
function _erc1271Signer() internal view virtual returns (address);
/// @dev Returns whether the `msg.sender` is considered safe, such
/// that we don't need to use the nested EIP-712 workflow.
/// Override to return true for more callers.
/// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU
function _erc1271CallerIsSafe() internal view virtual returns (bool) {
// The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c
// is known to include the account in the hash to be signed.
return msg.sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c;
}
/// @dev Returns whether the `hash` and `signature` are valid.
/// Override if you need non-ECDSA logic.
function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature)
internal
view
virtual
returns (bool)
{
return SignatureCheckerLib.isValidSignatureNowCalldata(_erc1271Signer(), hash, signature);
}
/// @dev Unwraps and returns the signature.
function _erc1271UnwrapSignature(bytes calldata signature)
internal
view
virtual
returns (bytes calldata result)
{
result = signature;
/// @solidity memory-safe-assembly
assembly {
// Unwraps the ERC6492 wrapper if it exists.
// See: https://eips.ethereum.org/EIPS/eip-6492
if eq(
calldataload(add(result.offset, sub(result.length, 0x20))),
mul(0x6492, div(not(mload(0x60)), 0xffff)) // `0x6492...6492`.
) {
let o := add(result.offset, calldataload(add(result.offset, 0x40)))
result.length := calldataload(o)
result.offset := add(o, 0x20)
}
}
}
/// @dev Returns whether the `signature` is valid for the `hash.
function _erc1271IsValidSignature(bytes32 hash, bytes calldata signature)
internal
view
virtual
returns (bool)
{
return _erc1271IsValidSignatureViaSafeCaller(hash, signature)
|| _erc1271IsValidSignatureViaNestedEIP712(hash, signature)
|| _erc1271IsValidSignatureViaRPC(hash, signature);
}
/// @dev Performs the signature validation without nested EIP-712 if the caller is
/// a safe caller. A safe caller must include the address of this account in the hash.
function _erc1271IsValidSignatureViaSafeCaller(bytes32 hash, bytes calldata signature)
internal
view
virtual
returns (bool result)
{
if (_erc1271CallerIsSafe()) result = _erc1271IsValidSignatureNowCalldata(hash, signature);
}
/// @dev ERC1271 signature validation (Nested EIP-712 workflow).
///
/// This uses ECDSA recovery by default (see: `_erc1271IsValidSignatureNowCalldata`).
/// It also uses a nested EIP-712 approach to prevent signature replays when a single EOA
/// owns multiple smart contract accounts,
/// while still enabling wallet UIs (e.g. Metamask) to show the EIP-712 values.
///
/// Crafted for phishing resistance, efficiency, flexibility.
/// __________________________________________________________________________________________
///
/// Glossary:
///
/// - `APP_DOMAIN_SEPARATOR`: The domain separator of the `hash` passed in by the application.
/// Provided by the front end. Intended to be the domain separator of the contract
/// that will call `isValidSignature` on this account.
///
/// - `ACCOUNT_DOMAIN_SEPARATOR`: The domain separator of this account.
/// See: `EIP712._domainSeparator()`.
/// __________________________________________________________________________________________
///
/// For the `TypedDataSign` workflow, the final hash will be:
/// ```
/// keccak256(\x19\x01 ‖ APP_DOMAIN_SEPARATOR ‖
/// hashStruct(TypedDataSign({
/// contents: hashStruct(originalStruct),
/// name: keccak256(bytes(eip712Domain().name)),
/// version: keccak256(bytes(eip712Domain().version)),
/// chainId: eip712Domain().chainId,
/// verifyingContract: eip712Domain().verifyingContract,
/// salt: eip712Domain().salt,
/// extensions: keccak256(abi.encodePacked(eip712Domain().extensions))
/// }))
/// )
/// ```
/// where `‖` denotes the concatenation operator for bytes.
/// The order of the fields is important: `contents` comes before `name`.
///
/// The signature will be `r ‖ s ‖ v ‖
/// APP_DOMAIN_SEPARATOR ‖ contents ‖ contentsType ‖ uint16(contentsType.length)`,
/// where `contents` is the bytes32 struct hash of the original struct.
///
/// The `APP_DOMAIN_SEPARATOR` and `contents` will be used to verify if `hash` is indeed correct.
/// __________________________________________________________________________________________
///
/// For the `PersonalSign` workflow, the final hash will be:
/// ```
/// keccak256(\x19\x01 ‖ ACCOUNT_DOMAIN_SEPARATOR ‖
/// hashStruct(PersonalSign({
/// prefixed: keccak256(bytes(\x19Ethereum Signed Message:\n ‖
/// base10(bytes(someString).length) ‖ someString))
/// }))
/// )
/// ```
/// where `‖` denotes the concatenation operator for bytes.
///
/// The `PersonalSign` type hash will be `keccak256("PersonalSign(bytes prefixed)")`.
/// The signature will be `r ‖ s ‖ v`.
/// __________________________________________________________________________________________
///
/// For demo and typescript code, see:
/// - https://github.com/junomonster/nested-eip-712
/// - https://github.com/frangio/eip712-wrapper-for-eip1271
///
/// Their nomenclature may differ from ours, although the high-level idea is similar.
///
/// Of course, if you have control over the codebase of the wallet client(s) too,
/// you can choose a more minimalistic signature scheme like
/// `keccak256(abi.encode(address(this), hash))` instead of all these acrobatics.
/// All these are just for widespread out-of-the-box compatibility with other wallet clients.
/// We want to create bazaars, not walled castles.
/// And we'll use push the Turing Completeness of the EVM to the limits to do so.
function _erc1271IsValidSignatureViaNestedEIP712(bytes32 hash, bytes calldata signature)
internal
view
virtual
returns (bool result)
{
bytes32 t = _typedDataSignFields();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
// `c` is `contentsType.length`, which is stored in the last 2 bytes of the signature.
let c := shr(240, calldataload(add(signature.offset, sub(signature.length, 2))))
for {} 1 {} {
let l := add(0x42, c) // Total length of appended data (32 + 32 + c + 2).
let o := add(signature.offset, sub(signature.length, l)) // Offset of appended data.
mstore(0x00, 0x1901) // Store the "\x19\x01" prefix.
calldatacopy(0x20, o, 0x40) // Copy the `APP_DOMAIN_SEPARATOR` and `contents` struct hash.
// Use the `PersonalSign` workflow if the reconstructed hash doesn't match,
// or if the appended data is invalid, i.e.
// `appendedData.length > signature.length || contentsType.length == 0`.
if or(xor(keccak256(0x1e, 0x42), hash), or(lt(signature.length, l), iszero(c))) {
t := 0 // Set `t` to 0, denoting that we need to `hash = _hashTypedData(hash)`.
mstore(t, _PERSONAL_SIGN_TYPEHASH)
mstore(0x20, hash) // Store the `prefixed`.
hash := keccak256(t, 0x40) // Compute the `PersonalSign` struct hash.
break
}
// Else, use the `TypedDataSign` workflow.
// `TypedDataSign({ContentsName} contents,bytes1 fields,...){ContentsType}`.
mstore(m, "TypedDataSign(") // Store the start of `TypedDataSign`'s type encoding.
let p := add(m, 0x0e) // Advance 14 bytes to skip "TypedDataSign(".
calldatacopy(p, add(o, 0x40), c) // Copy `contentsType` to extract `contentsName`.
// `d & 1 == 1` means that `contentsName` is invalid.
let d := shr(byte(0, mload(p)), 0x7fffffe000000000000010000000000) // Starts with `[a-z(]`.
// Store the end sentinel '(', and advance `p` until we encounter a '(' byte.
for { mstore(add(p, c), 40) } iszero(eq(byte(0, mload(p)), 40)) { p := add(p, 1) } {
d := or(shr(byte(0, mload(p)), 0x120100000001), d) // Has a byte in ", )\x00".
}
mstore(p, " contents,bytes1 fields,string n") // Store the rest of the encoding.
mstore(add(p, 0x20), "ame,string version,uint256 chain")
mstore(add(p, 0x40), "Id,address verifyingContract,byt")
mstore(add(p, 0x60), "es32 salt,uint256[] extensions)")
p := add(p, 0x7f)
calldatacopy(p, add(o, 0x40), c) // Copy `contentsType`.
// Fill in the missing fields of the `TypedDataSign`.
calldatacopy(t, o, 0x40) // Copy the `contents` struct hash to `add(t, 0x20)`.
mstore(t, keccak256(m, sub(add(p, c), m))) // Store `typedDataSignTypehash`.
// The "\x19\x01" prefix is already at 0x00.
// `APP_DOMAIN_SEPARATOR` is already at 0x20.
mstore(0x40, keccak256(t, 0x120)) // `hashStruct(typedDataSign)`.
// Compute the final hash, corrupted if `contentsName` is invalid.
hash := keccak256(0x1e, add(0x42, and(1, d)))
signature.length := sub(signature.length, l) // Truncate the signature.
break
}
mstore(0x40, m) // Restore the free memory pointer.
}
if (t == bytes32(0)) hash = _hashTypedData(hash); // `PersonalSign` workflow.
result = _erc1271IsValidSignatureNowCalldata(hash, signature);
}
/// @dev For use in `_erc1271IsValidSignatureViaNestedEIP712`,
function _typedDataSignFields() private view returns (bytes32 m) {
(
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
) = eip712Domain();
/// @solidity memory-safe-assembly
assembly {
m := mload(0x40) // Grab the free memory pointer.
mstore(0x40, add(m, 0x120)) // Allocate the memory.
// Skip 2 words for the `typedDataSignTypehash` and `contents` struct hash.
mstore(add(m, 0x40), shl(248, byte(0, fields)))
mstore(add(m, 0x60), keccak256(add(name, 0x20), mload(name)))
mstore(add(m, 0x80), keccak256(add(version, 0x20), mload(version)))
mstore(add(m, 0xa0), chainId)
mstore(add(m, 0xc0), shr(96, shl(96, verifyingContract)))
mstore(add(m, 0xe0), salt)
mstore(add(m, 0x100), keccak256(add(extensions, 0x20), shl(5, mload(extensions))))
}
}
/// @dev Performs the signature validation without nested EIP-712 to allow for easy sign ins.
/// This function must always return false or revert if called on-chain.
function _erc1271IsValidSignatureViaRPC(bytes32 hash, bytes calldata signature)
internal
view
virtual
returns (bool result)
{
// Non-zero gasprice is a heuristic to check if a call is on-chain,
// but we can't fully depend on it because it can be manipulated.
// See: https://x.com/NoahCitron/status/1580359718341484544
if (tx.gasprice == uint256(0)) {
/// @solidity memory-safe-assembly
assembly {
mstore(gasprice(), gasprice())
// See: https://gist.github.com/Vectorized/3c9b63524d57492b265454f62d895f71
let b := 0x000000000000378eDCD5B5B0A24f5342d8C10485 // Basefee contract,
pop(staticcall(0xffff, b, codesize(), gasprice(), gasprice(), 0x20))
// If `gasprice < basefee`, the call cannot be on-chain, and we can skip the gas burn.
if iszero(mload(gasprice())) {
let m := mload(0x40) // Cache the free memory pointer.
mstore(gasprice(), 0x1626ba7e) // `isValidSignature(bytes32,bytes)`.
mstore(0x20, b) // Recycle `b` to denote if we need to burn gas.
mstore(0x40, 0x40)
let gasToBurn := or(add(0xffff, gaslimit()), gaslimit())
// Burns gas computationally efficiently. Also, requires that `gas > gasToBurn`.
if or(eq(hash, b), lt(gas(), gasToBurn)) { invalid() }
// Make a call to this with `b`, efficiently burning the gas provided.
// No valid transaction can consume more than the gaslimit.
// See: https://ethereum.github.io/yellowpaper/paper.pdf
// Most RPCs perform calls with a gas budget greater than the gaslimit.
pop(staticcall(gasToBurn, address(), 0x1c, 0x64, gasprice(), gasprice()))
mstore(0x40, m) // Restore the free memory pointer.
}
}
result = _erc1271IsValidSignatureNowCalldata(hash, signature);
}
}
}