-
-
Notifications
You must be signed in to change notification settings - Fork 56
/
SignatureChecker.vy
183 lines (162 loc) · 7.4 KB
/
SignatureChecker.vy
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
# @version ^0.3.9
"""
@title ECDSA and EIP-1271 Signature Verification Functions
@custom:contract-name SignatureChecker
@license GNU Affero General Public License v3.0
@author pcaversaccio
@notice Signature verification helper functions that can be used
instead of `ECDSA.recover_sig` to seamlessly support both
ECDSA signatures from externally-owned accounts (EOAs) as
well as EIP-1271 (https://eips.ethereum.org/EIPS/eip-1271)
signatures from smart contract wallets like Argent and Gnosis
Safe. For strict EIP-1271 verification, i.e. only valid EIP-1271
signatures are verified, the function `is_valid_ERC1271_signature_now`
can be called. The implementation is inspired by OpenZeppelin's
implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol.
@custom:security Signatures must not be used as unique identifiers since the
`ecrecover` opcode allows for malleable (non-unique) signatures.
"""
IERC1271_ISVALIDSIGNATURE_SELECTOR: public(constant(bytes4)) = 0x1626BA7E
_MALLEABILITY_THRESHOLD: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
_SIGNATURE_INCREMENT: constant(bytes32) = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
@external
@payable
def __init__():
"""
@dev To omit the opcodes for checking the `msg.value`
in the creation-time EVM bytecode, the constructor
is declared as `payable`.
"""
pass
@external
@view
def is_valid_signature_now(signer: address, hash: bytes32, signature: Bytes[65]) -> bool:
"""
@dev Checks if a signature `signature` is valid
for a given `signer` and message digest `hash`.
If the signer is a smart contract, the signature
is validated against that smart contract using
EIP-1271, otherwise it's validated using `ECDSA.recover_sig`.
@notice Unlike ECDSA signatures, contract signatures
are revocable and the result of this function
can therefore change over time. It could return
`True` in block N and `False` in block N+1 (or the opposite).
@param hash The 32-byte message digest that was signed.
@param signature The maximum 65-byte signature of `hash`.
@return bool The verification whether `signature` is valid
for the provided data.
"""
# First check: ECDSA case.
recovered: address = self._recover_sig(hash, signature)
if (recovered == signer):
return True
# Second check: EIP-1271 case.
return self._is_valid_ERC1271_signature_now(signer, hash, signature)
@external
@view
def is_valid_ERC1271_signature_now(signer: address, hash: bytes32, signature: Bytes[65]) -> bool:
"""
@dev Checks if a signature `signature` is valid
for a given `signer` and message digest `hash`.
The signature is validated using EIP-1271.
@notice Unlike ECDSA signatures, contract signatures
are revocable and the result of this function
can therefore change over time. It could return
`True` in block N and `False` in block N+1 (or the opposite).
@param hash The 32-byte message digest that was signed.
@param signature The maximum 65-byte signature of `hash`.
@return bool The verification whether `signature` is valid
for the provided data.
"""
return self._is_valid_ERC1271_signature_now(signer, hash, signature)
@internal
@view
def _is_valid_ERC1271_signature_now(signer: address, hash: bytes32, signature: Bytes[65]) -> bool:
"""
@dev This `internal` function is equivalent to
`is_valid_ERC1271_signature_now`, and can be used
for strict EIP-1271 verification.
@notice Unlike ECDSA signatures, contract signatures
are revocable and the result of this function
can therefore change over time. It could return
`True` in block N and `False` in block N+1 (or the opposite).
@param hash The 32-byte message digest that was signed.
@param signature The maximum 65-byte signature of `hash`.
@return bool The verification whether `signature` is valid
for the provided data.
"""
success: bool = empty(bool)
return_data: Bytes[32] = b""
# The following low-level call does not revert, but instead
# returns `False` if the callable contract does not implement
# the `isValidSignature` function. Since we perform a length
# check of 32 bytes for the return data in the return expression
# at the end, we also return `False` for EOA wallets instead
# of reverting (remember that the EVM always considers a call
# to an EOA as successful with return data `0x`). Furthermore,
# it is important to note that an external call via `raw_call`
# does not perform an external code size check on the target
# address.
success, return_data = \
raw_call(signer, _abi_encode(hash, signature, method_id=IERC1271_ISVALIDSIGNATURE_SELECTOR), max_outsize=32, is_static_call=True, revert_on_failure=False)
return (success and (len(return_data) == 32) and (convert(return_data, bytes32) == convert(IERC1271_ISVALIDSIGNATURE_SELECTOR, bytes32)))
@internal
@pure
def _recover_sig(hash: bytes32, signature: Bytes[65]) -> address:
"""
@dev Sourced from {ECDSA-recover_sig}.
@notice See {ECDSA-recover_sig} for the
function docstring.
"""
sig_length: uint256 = len(signature)
# 65-byte case: r,s,v standard signature.
if (sig_length == 65):
r: uint256 = extract32(signature, 0, output_type=uint256)
s: uint256 = extract32(signature, 32, output_type=uint256)
v: uint256 = convert(slice(signature, 64, 1), uint256)
return self._try_recover_vrs(hash, v, r, s)
# 64-byte case: r,vs signature; see: https://eips.ethereum.org/EIPS/eip-2098.
elif (sig_length == 64):
r: uint256 = extract32(signature, 0, output_type=uint256)
vs: uint256 = extract32(signature, 32, output_type=uint256)
return self._try_recover_r_vs(hash, r, vs)
else:
return empty(address)
@internal
@pure
def _recover_vrs(hash: bytes32, v: uint256, r: uint256, s: uint256) -> address:
"""
@dev Sourced from {ECDSA-_recover_vrs}.
@notice See {ECDSA-_recover_vrs} for the
function docstring.
"""
return self._try_recover_vrs(hash, v, r, s)
@internal
@pure
def _try_recover_r_vs(hash: bytes32, r: uint256, vs: uint256) -> address:
"""
@dev Sourced from {ECDSA-_try_recover_r_vs}.
@notice See {ECDSA-_try_recover_r_vs} for the
function docstring.
"""
s: uint256 = vs & convert(_SIGNATURE_INCREMENT, uint256)
# We do not check for an overflow here since the shift operation
# `vs >> 255` results essentially in a `uint8` type (0 or 1) and
# we use `uint256` as result type.
v: uint256 = unsafe_add(vs >> 255, 27)
return self._try_recover_vrs(hash, v, r, s)
@internal
@pure
def _try_recover_vrs(hash: bytes32, v: uint256, r: uint256, s: uint256) -> address:
"""
@dev Sourced from {ECDSA-_try_recover_vrs}.
@notice See {ECDSA-_try_recover_vrs} for the
function docstring.
"""
if (s > convert(_MALLEABILITY_THRESHOLD, uint256)):
raise "ECDSA: invalid signature 's' value"
signer: address = ecrecover(hash, v, r, s)
if (signer == empty(address)):
raise "ECDSA: invalid signature"
return signer