Created
June 25, 2020 11:39
-
-
Save frozeman/350410d7ff56bd36622af8a57060a1dc to your computer and use it in GitHub Desktop.
Signature verification in solidity, taken from 0x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @dev Verifies that a hash has been signed by the given signer. | |
/// @param hash Any 32 byte hash. | |
/// @param signerAddress Address that should have signed the given hash. | |
/// @param signature Proof that the hash has been signed by signer. | |
/// @return True if the address recovered from the provided signature matches the input signer address. | |
function isValidSignature( | |
bytes32 hash, | |
address signerAddress, | |
bytes memory signature | |
) | |
public | |
view | |
returns (bool isValid) | |
{ | |
require( | |
signature.length > 0, | |
"LENGTH_GREATER_THAN_0_REQUIRED" | |
); | |
// Ensure signature is supported | |
uint8 signatureTypeRaw = uint8(signature.popLastByte()); | |
require( | |
signatureTypeRaw < uint8(SignatureType.NSignatureTypes), | |
"SIGNATURE_UNSUPPORTED" | |
); | |
// Pop last byte off of signature byte array. | |
SignatureType signatureType = SignatureType(signatureTypeRaw); | |
// Variables are not scoped in Solidity. | |
uint8 v; | |
bytes32 r; | |
bytes32 s; | |
address recovered; | |
// Always illegal signature. | |
// This is always an implicit option since a signer can create a | |
// signature array with invalid type or length. We may as well make | |
// it an explicit option. This aids testing and analysis. It is | |
// also the initialization value for the enum type. | |
if (signatureType == SignatureType.Illegal) { | |
revert("SIGNATURE_ILLEGAL"); | |
// Always invalid signature. | |
// Like Illegal, this is always implicitly available and therefore | |
// offered explicitly. It can be implicitly created by providing | |
// a correctly formatted but incorrect signature. | |
} else if (signatureType == SignatureType.Invalid) { | |
require( | |
signature.length == 0, | |
"LENGTH_0_REQUIRED" | |
); | |
isValid = false; | |
return isValid; | |
// Signature using EIP712 | |
} else if (signatureType == SignatureType.EIP712) { | |
require( | |
signature.length == 65, | |
"LENGTH_65_REQUIRED" | |
); | |
v = uint8(signature[0]); | |
r = signature.readBytes32(1); | |
s = signature.readBytes32(33); | |
recovered = ecrecover(hash, v, r, s); | |
isValid = signerAddress == recovered; | |
return isValid; | |
// Signed using web3.eth_sign | |
} else if (signatureType == SignatureType.EthSign) { | |
require( | |
signature.length == 65, | |
"LENGTH_65_REQUIRED" | |
); | |
v = uint8(signature[0]); | |
r = signature.readBytes32(1); | |
s = signature.readBytes32(33); | |
recovered = ecrecover( | |
keccak256(abi.encodePacked( | |
"\x19Ethereum Signed Message:\n32", | |
hash | |
)), | |
v, | |
r, | |
s | |
); | |
isValid = signerAddress == recovered; | |
return isValid; | |
// Implicitly signed by caller. | |
// The signer has initiated the call. In the case of non-contract | |
// accounts it means the transaction itself was signed. | |
// Example: let's say for a particular operation three signatures | |
// A, B and C are required. To submit the transaction, A and B can | |
// give a signature to C, who can then submit the transaction using | |
// `Caller` for his own signature. Or A and C can sign and B can | |
// submit using `Caller`. Having `Caller` allows this flexibility. | |
} else if (signatureType == SignatureType.Caller) { | |
require( | |
signature.length == 0, | |
"LENGTH_0_REQUIRED" | |
); | |
isValid = signerAddress == msg.sender; | |
return isValid; | |
// Signature verified by wallet contract. | |
// If used with an order, the maker of the order is the wallet contract. | |
} else if (signatureType == SignatureType.Wallet) { | |
isValid = IWallet(signerAddress).isValidSignature(hash, signature); | |
return isValid; | |
// Signature verified by validator contract. | |
// If used with an order, the maker of the order can still be an EOA. | |
// A signature using this type should be encoded as: | |
// | Offset | Length | Contents | | |
// | 0x00 | x | Signature to validate | | |
// | 0x00 + x | 20 | Address of validator contract | | |
// | 0x14 + x | 1 | Signature type is always "\x06" | | |
} else if (signatureType == SignatureType.Validator) { | |
// Pop last 20 bytes off of signature byte array. | |
address validatorAddress = signature.popLast20Bytes(); | |
// Ensure signer has approved validator. | |
if (!allowedValidators[signerAddress][validatorAddress]) { | |
return false; | |
} | |
isValid = IValidator(validatorAddress).isValidSignature( | |
hash, | |
signerAddress, | |
signature | |
); | |
return isValid; | |
// Signer signed hash previously using the preSign function. | |
} else if (signatureType == SignatureType.PreSigned) { | |
isValid = preSigned[hash][signerAddress]; | |
return isValid; | |
// Signature from Trezor hardware wallet. | |
// It differs from web3.eth_sign in the encoding of message length | |
// (Bitcoin varint encoding vs ascii-decimal, the latter is not | |
// self-terminating which leads to ambiguities). | |
// See also: | |
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer | |
// https://github.com/trezor/trezor-mcu/blob/master/firmware/ethereum.c#L602 | |
// https://github.com/trezor/trezor-mcu/blob/master/firmware/crypto.c#L36 | |
} else if (signatureType == SignatureType.Trezor) { | |
require( | |
signature.length == 65, | |
"LENGTH_65_REQUIRED" | |
); | |
v = uint8(signature[0]); | |
r = signature.readBytes32(1); | |
s = signature.readBytes32(33); | |
recovered = ecrecover( | |
keccak256(abi.encodePacked( | |
"\x19Ethereum Signed Message:\n\x20", | |
hash | |
)), | |
v, | |
r, | |
s | |
); | |
isValid = signerAddress == recovered; | |
return isValid; | |
} | |
// Anything else is illegal (We do not return false because | |
// the signature may actually be valid, just not in a format | |
// that we currently support. In this case returning false | |
// may lead the caller to incorrectly believe that the | |
// signature was invalid.) | |
revert("SIGNATURE_UNSUPPORTED"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment