aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol')
-rw-r--r--packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol255
1 files changed, 255 insertions, 0 deletions
diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol
new file mode 100644
index 000000000..29172057a
--- /dev/null
+++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol
@@ -0,0 +1,255 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../../utils/LibBytes/LibBytes.sol";
+import "./mixins/MSignatureValidator.sol";
+import "./mixins/MTransactions.sol";
+import "./interfaces/IWallet.sol";
+import "./interfaces/IValidator.sol";
+
+contract MixinSignatureValidator is
+ MSignatureValidator,
+ MTransactions
+{
+ using LibBytes for bytes;
+
+ // Mapping of hash => signer => signed
+ mapping (bytes32 => mapping (address => bool)) public preSigned;
+
+ // Mapping of signer => validator => approved
+ mapping (address => mapping (address => bool)) public allowedValidators;
+
+ /// @dev Approves a hash on-chain using any valid signature type.
+ /// After presigning a hash, the preSign signature type will become valid for that hash and signer.
+ /// @param signerAddress Address that should have signed the given hash.
+ /// @param signature Proof that the hash has been signed by signer.
+ function preSign(
+ bytes32 hash,
+ address signerAddress,
+ bytes signature
+ )
+ external
+ {
+ require(
+ isValidSignature(
+ hash,
+ signerAddress,
+ signature
+ ),
+ "INVALID_SIGNATURE"
+ );
+ preSigned[hash][signerAddress] = true;
+ }
+
+ /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf.
+ /// @param validatorAddress Address of Validator contract.
+ /// @param approval Approval or disapproval of Validator contract.
+ function setSignatureValidatorApproval(
+ address validatorAddress,
+ bool approval
+ )
+ external
+ {
+ address signerAddress = getCurrentContextAddress();
+ allowedValidators[signerAddress][validatorAddress] = approval;
+ emit SignatureValidatorApproval(
+ signerAddress,
+ validatorAddress,
+ approval
+ );
+ }
+
+ /// @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)
+ {
+ // TODO: Domain separation: make hash depend on role. (Taker sig should not be valid as maker sig, etc.)
+ 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");
+ }
+}