@0x/contracts-exchange: Consolidate signature types.

`@0x/contracts-exchange`: Fighting with linearization issues.
This commit is contained in:
Lawrence Forman 2019-06-25 18:11:16 -04:00 committed by Amir Bandeali
parent cf6144599d
commit eb9b2f355e
11 changed files with 395 additions and 719 deletions

View File

@ -31,10 +31,8 @@
"src/interfaces/IExchange.sol",
"src/interfaces/IExchangeCore.sol",
"src/interfaces/IMatchOrders.sol",
"src/interfaces/IOrderValidator.sol",
"src/interfaces/ISignatureValidator.sol",
"src/interfaces/ITransactions.sol",
"src/interfaces/IValidator.sol",
"src/interfaces/IWallet.sol",
"src/interfaces/IEIP1271Wallet.sol",
"src/interfaces/IWrapperFunctions.sol",

View File

@ -0,0 +1,64 @@
/*
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.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
contract MixinCommon {
// Defined in MixinSignatureValidator
function _isValidOrderWithHashSignature(
LibOrder.Order memory order,
bytes32 orderHash,
address signerAddress,
bytes memory signature
)
internal
view
returns (bool isValid);
// Defined in MixinSignatureValidator
function _isValidTransactionWithHashSignature(
LibZeroExTransaction.ZeroExTransaction memory transaction,
bytes32 transactionHash,
address signerAddress,
bytes memory signature
)
internal
view
returns (bool isValid);
// Defined in MixinSignatureValidator
function doesSignatureRequireRegularValidation(
bytes32 hash,
address signerAddress,
bytes memory signature
)
public
pure
returns (bool needsRegularValidation);
// Defined in MixinTransactions
function _getCurrentContextAddress()
internal
view
returns (address);
}

View File

@ -24,14 +24,10 @@ import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "./interfaces/IAssetProxyDispatcher.sol";
import "./interfaces/IExchangeCore.sol";
import "./interfaces/ISignatureValidator.sol";
import "./MixinAssetProxyDispatcher.sol";
import "./MixinExchangeRichErrors.sol";
import "./MixinSignatureValidator.sol";
import "./MixinAssetProxyDispatcher.sol";
import "./MixinTransactions.sol";
import "./MixinCommon.sol";
contract MixinExchangeCore is
@ -39,8 +35,10 @@ contract MixinExchangeCore is
LibExchangeSelectors,
LibMath,
LibFillResults,
MixinAssetProxyDispatcher,
MixinSignatureValidator
LibOrder,
ReentrancyGuard,
MixinCommon,
MixinAssetProxyDispatcher
{
using LibBytes for bytes;

View File

@ -62,21 +62,6 @@ contract MixinExchangeRichErrors is
);
}
function SignatureOrderValidatorNotApprovedError(
address signerAddress,
address validatorAddress
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_ORDER_VALIDATOR_NOT_APPROVED_ERROR_SELECTOR,
signerAddress,
validatorAddress
);
}
function SignatureValidatorError(
bytes32 hash,
address signerAddress,
@ -117,46 +102,6 @@ contract MixinExchangeRichErrors is
);
}
function SignatureOrderValidatorError(
bytes32 orderHash,
address signerAddress,
address validatorAddress,
bytes memory signature,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_ORDER_VALIDATOR_ERROR_SELECTOR,
orderHash,
signerAddress,
validatorAddress,
signature,
errorData
);
}
function SignatureOrderWalletError(
bytes32 orderHash,
address wallet,
bytes memory signature,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_ORDER_WALLET_ERROR_SELECTOR,
orderHash,
wallet,
signature,
errorData
);
}
function OrderStatusError(
bytes32 orderHash,
LibOrder.OrderStatus orderStatus

View File

@ -22,24 +22,23 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibEIP1271.sol";
import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol";
import "@0x/contracts-utils/contracts/src/RichErrors.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
import "./interfaces/IWallet.sol";
import "./interfaces/IEIP1271Wallet.sol";
import "./interfaces/IValidator.sol";
import "./interfaces/IOrderValidator.sol";
import "./interfaces/ISignatureValidator.sol";
import "./MixinTransactions.sol";
import "./MixinExchangeRichErrors.sol";
import "./MixinCommon.sol";
contract MixinSignatureValidator is
MixinExchangeRichErrors,
ReentrancyGuard,
LibOrder,
LibEIP1271,
ISignatureValidator,
MixinTransactions
LibOrder,
LibZeroExTransaction,
LibEIP1271,
ReentrancyGuard,
MixinCommon,
MixinExchangeRichErrors
{
using LibBytes for bytes;
@ -49,9 +48,6 @@ contract MixinSignatureValidator is
// Mapping of signer => validator => approved
mapping (address => mapping (address => bool)) public allowedValidators;
// Mapping of signer => order validator => approved
mapping (address => mapping (address => bool)) public allowedOrderValidators;
/// @dev Approves a hash on-chain.
/// After presigning a hash, the preSign signature type will become valid for that hash and signer.
/// @param hash Any 32-byte hash.
@ -83,31 +79,11 @@ contract MixinSignatureValidator is
);
}
/// @dev Approves/unnapproves an OrderValidator contract to verify signatures on signer's behalf
/// using the `OrderValidator` signature type.
/// @param validatorAddress Address of Validator contract.
/// @param approval Approval or disapproval of Validator contract.
function setOrderValidatorApproval(
address validatorAddress,
bool approval
)
external
nonReentrant
{
address signerAddress = _getCurrentContextAddress();
allowedOrderValidators[signerAddress][validatorAddress] = approval;
emit SignatureValidatorApproval(
signerAddress,
validatorAddress,
approval
);
}
/// @dev Verifies that a signature for an order is valid.
/// @param order The order.
/// @param signerAddress Address that should have signed the given order.
/// @param signature Proof that the order has been signed by signer.
/// @return True if the signature is valid for the given order and signer.
/// @return isValid true if the signature is valid for the given order and signer.
function isValidOrderSignature(
Order memory order,
address signerAddress,
@ -126,13 +102,13 @@ contract MixinSignatureValidator is
);
}
/// @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 signature is valid for the given hash and signer.
function isValidHashSignature(
bytes32 hash,
/// @dev Verifies that a signature for a transaction is valid.
/// @param transaction The transaction.
/// @param signerAddress Address that should have signed the given order.
/// @param signature Proof that the order has been signed by signer.
/// @return isValid true if the signature is valid for the given transaction and signer.
function isValidTransactionSignature(
ZeroExTransaction memory transaction,
address signerAddress,
bytes memory signature
)
@ -140,42 +116,24 @@ contract MixinSignatureValidator is
view
returns (bool isValid)
{
SignatureType signatureType = _readValidSignatureType(
hash,
signerAddress,
signature
);
// Only hash-compatible signature types can be handled by this
// function.
if (
signatureType == SignatureType.OrderValidator ||
signatureType == SignatureType.OrderWallet ||
signatureType == SignatureType.EIP1271OrderWallet
) {
_rrevert(SignatureError(
SignatureErrorCodes.INAPPROPRIATE_SIGNATURE_TYPE,
hash,
signerAddress,
signature
));
}
return _validateHashSignatureTypes(
signatureType,
hash,
bytes32 transactionHash = getTransactionHash(transaction);
return _isValidTransactionWithHashSignature(
transaction,
transactionHash,
signerAddress,
signature
);
}
/// @dev Checks if a signature is of a type that should be verified for
/// every subsequent fill.
/// @param orderHash The hash of the order.
/// every action.
/// @param hash The hash of the order/transaction.
/// @param signerAddress The address of the signer.
/// @param signature The signature for `orderHash`.
/// @param signature The signature for `hash`.
/// @return needsRegularValidation True if the signature should be validated
/// for every operation.
/// for every action.
function doesSignatureRequireRegularValidation(
bytes32 orderHash,
bytes32 hash,
address signerAddress,
bytes memory signature
)
@ -184,16 +142,14 @@ contract MixinSignatureValidator is
returns (bool needsRegularValidation)
{
SignatureType signatureType = _readValidSignatureType(
orderHash,
hash,
signerAddress,
signature
);
// Only signature types that take a full order should be validated
// regularly.
return
signatureType == SignatureType.OrderValidator ||
signatureType == SignatureType.OrderWallet ||
signatureType == SignatureType.EIP1271OrderWallet;
signatureType == SignatureType.Wallet ||
signatureType == SignatureType.Validator ||
signatureType == SignatureType.EIP1271Wallet;
}
/// @dev Verifies that an order, with provided order hash, has been signed
@ -202,7 +158,7 @@ contract MixinSignatureValidator is
/// @param orderHash The hash of the order.
/// @param signerAddress Address that should have signed the.Signat given hash.
/// @param signature Proof that the hash has been signed by signer.
/// @return isValid True if the signature is valid for the given hash and signer.
/// @return isValid True if the signature is valid for the given order and signer.
function _isValidOrderWithHashSignature(
Order memory order,
bytes32 orderHash,
@ -218,42 +174,169 @@ contract MixinSignatureValidator is
signerAddress,
signature
);
if (signatureType == SignatureType.OrderValidator) {
// The entire order is verified by validator contract.
isValid = _validateOrderWithValidator(
order,
if (signatureType == SignatureType.Validator) {
// The entire order is verified by a validator contract.
isValid = _validateBytesWithValidator(
abi.encode(order, orderHash),
orderHash,
signerAddress,
signature
);
return isValid;
} else if (signatureType == SignatureType.OrderWallet) {
} else if (signatureType == SignatureType.EIP1271Wallet) {
// The entire order is verified by a wallet contract.
isValid = _validateOrderWithWallet(
order,
isValid = _validateBytesWithWallet(
abi.encode(order, orderHash),
orderHash,
signerAddress,
signature
);
return isValid;
} else if (signatureType == SignatureType.EIP1271OrderWallet) {
// The entire order is verified by a wallet contract.
isValid = _validateOrderWithEIP1271Wallet(
order,
orderHash,
signerAddress,
signature
);
return isValid;
}
// Otherwise, it's one of the hash-compatible signature types.
return _validateHashSignatureTypes(
} else {
// Otherwise, it's one of the hash-only signature types.
isValid = _validateHashSignatureTypes(
signatureType,
orderHash,
signerAddress,
signature
);
}
}
/// @dev Verifies that a transaction, with provided order hash, has been signed
/// by the given signer.
/// @param transaction The transaction.
/// @param transactionHash The hash of the transaction.
/// @param signerAddress Address that should have signed the.Signat given hash.
/// @param signature Proof that the hash has been signed by signer.
/// @return isValid True if the signature is valid for the given transaction and signer.
function _isValidTransactionWithHashSignature(
ZeroExTransaction memory transaction,
bytes32 transactionHash,
address signerAddress,
bytes memory signature
)
internal
view
returns (bool isValid)
{
SignatureType signatureType = _readValidSignatureType(
transactionHash,
signerAddress,
signature
);
if (signatureType == SignatureType.Validator) {
// The entire transaction is verified by a validator contract.
isValid = _validateBytesWithValidator(
abi.encode(transaction, transactionHash),
transactionHash,
signerAddress,
signature
);
} else if (signatureType == SignatureType.EIP1271Wallet) {
// The entire transaction is verified by a wallet contract.
isValid = _validateBytesWithWallet(
abi.encode(transaction, transactionHash),
transactionHash,
signerAddress,
signature
);
} else {
// Otherwise, it's one of the hash-only signature types.
isValid = _validateHashSignatureTypes(
signatureType,
transactionHash,
signerAddress,
signature
);
}
}
/// Validates a hash-only signature type
/// (anything but `Validator` and `EIP1271Wallet`).
function _validateHashSignatureTypes(
SignatureType signatureType,
bytes32 hash,
address signerAddress,
bytes memory signature
)
private
view
returns (bool isValid)
{
// 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.
if (signatureType == SignatureType.Invalid) {
if (signature.length != 1) {
_rrevert(SignatureError(
SignatureErrorCodes.INVALID_LENGTH,
hash,
signerAddress,
signature
));
}
isValid = false;
// Signature using EIP712
} else if (signatureType == SignatureType.EIP712) {
if (signature.length != 66) {
_rrevert(SignatureError(
SignatureErrorCodes.INVALID_LENGTH,
hash,
signerAddress,
signature
));
}
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
address recovered = ecrecover(
hash,
v,
r,
s
);
isValid = signerAddress == recovered;
// Signed using web3.eth_sign
} else if (signatureType == SignatureType.EthSign) {
if (signature.length != 66) {
_rrevert(SignatureError(
SignatureErrorCodes.INVALID_LENGTH,
hash,
signerAddress,
signature
));
}
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
address recovered = ecrecover(
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
hash
)),
v,
r,
s
);
isValid = signerAddress == recovered;
// Signature verified by wallet contract.
} else if (signatureType == SignatureType.Wallet) {
isValid = _validateHashWithWallet(
hash,
signerAddress,
signature
);
// Otherwise, signatureType == SignatureType.PreSigned
} else {
assert(signatureType == SignatureType.PreSigned);
// Signer signed hash previously using the preSign function.
isValid = preSigned[hash][signerAddress];
}
}
/// Reads the `SignatureType` from the end of a signature and validates it.
function _readValidSignatureType(
@ -304,12 +387,12 @@ contract MixinSignatureValidator is
return SignatureType(signatureTypeRaw);
}
/// @dev Verifies signature using logic defined by Wallet contract.
/// @dev Verifies a hash and signature using logic defined by Wallet contract.
/// @param hash Any 32 byte hash.
/// @param walletAddress Address that should have signed the given hash
/// and defines its own signature verification method.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if the signature is validated by the Walidator.
/// @return isValid True if the signature is validated by the Wallet.
function _validateHashWithWallet(
bytes32 hash,
address walletAddress,
@ -319,6 +402,11 @@ contract MixinSignatureValidator is
view
returns (bool isValid)
{
// A signature using this type should be encoded as:
// | Offset | Length | Contents |
// | 0x00 | x | Signature to validate |
// | 0x00 + x | 1 | Signature type is always "\x04" |
uint256 signatureLength = signature.length;
// HACK(dorothy-zbornak): Temporarily shave the signature type
// from the signature for the encode call then restore
@ -351,13 +439,15 @@ contract MixinSignatureValidator is
));
}
/// @dev Verifies signature using logic defined by an EIP1271 Wallet contract.
/// @param hash Any 32 byte hash.
/// @param walletAddress Address that should have signed the given hash
/// and defines its own signature verification method.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if the signature is validated by the Walidator.
function _validateHashWithEIP1271Wallet(
/// @dev Verifies arbitrary data and a signature via an EIP1271 Wallet
/// contract, where the wallet address is also the signer address.
/// @param data Arbitrary signed data.
/// @param hash The hash associated with the data.
/// @param walletAddress Contract that will verify the data and signature.
/// @param signature Proof that the data has been signed by signer.
/// @return isValid True if the signature is validated by the Wallet.
function _validateBytesWithWallet(
bytes memory data,
bytes32 hash,
address walletAddress,
bytes memory signature
@ -366,6 +456,11 @@ contract MixinSignatureValidator is
view
returns (bool isValid)
{
// A signature using this type should be encoded as:
// | Offset | Length | Contents |
// | 0x00 | x | Signature to validate |
// | 0x00 + x | 1 | Signature type is always "\x07" |
uint256 signatureLength = signature.length;
// HACK(dorothy-zbornak): Temporarily shave the signature type
// from the signature for the encode call then restore
@ -374,8 +469,6 @@ contract MixinSignatureValidator is
mstore(signature, sub(signatureLength, 1))
}
// Encode the call data.
bytes memory data = new bytes(32);
data.writeBytes32(0, hash);
bytes memory callData = abi.encodeWithSelector(
IEIP1271Wallet(walletAddress).isValidSignature.selector,
data,
@ -400,13 +493,15 @@ contract MixinSignatureValidator is
));
}
/// @dev Verifies signature using logic defined by Validator contract.
/// If used with an order, the maker of the order can still be an EOA.
/// @param hash Any 32 byte hash.
/// @dev Verifies arbitrary data and a signature via an EIP1271 contract
/// whose address is encoded in the signature.
/// @param data Arbitrary signed data.
/// @param hash The hash associated with the data.
/// @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 signature is validated by the Validator.
function _validateHashWithValidator(
/// @param signature Proof that the data has been signed by signer.
/// @return isValid True if the signature is validated by the validator contract.
function _validateBytesWithValidator(
bytes memory data,
bytes32 hash,
address signerAddress,
bytes memory signature
@ -415,12 +510,11 @@ contract MixinSignatureValidator is
view
returns (bool isValid)
{
// 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" |
// | 0x14 + x | 1 | Signature type is always "\x05" |
uint256 signatureLength = signature.length;
// Read the validator address from the signature.
@ -440,9 +534,8 @@ contract MixinSignatureValidator is
}
// Encode the call data.
bytes memory callData = abi.encodeWithSelector(
IValidator(validatorAddress).isValidSignature.selector,
hash,
signerAddress,
IEIP1271Wallet(validatorAddress).isValidSignature.selector,
data,
signature
);
// Restore the full signature.
@ -451,9 +544,9 @@ contract MixinSignatureValidator is
}
// Static call the verification function.
(bool didSucceed, bytes memory returnData) = validatorAddress.staticcall(callData);
// Return data should be a single bool.
if (didSucceed && returnData.length == 32) {
return returnData.readUint256(0) == 1;
// Return data should be the `EIP1271_MAGIC_VALUE`.
if (didSucceed && returnData.length <= 32) {
return returnData.readBytes4(0) == EIP1271_MAGIC_VALUE;
}
// Static call to verifier failed.
_rrevert(SignatureValidatorError(
@ -465,280 +558,4 @@ contract MixinSignatureValidator is
));
}
/// @dev Verifies order AND signature via a Wallet contract.
/// @param order The order.
/// @param orderHash The order hash.
/// @param walletAddress Address that should have signed the given hash
/// and defines its own order/signature verification method.
/// @param signature Proof that the order has been signed by signer.
/// @return True if order and signature are validated by the Wallet.
function _validateOrderWithWallet(
Order memory order,
bytes32 orderHash,
address walletAddress,
bytes memory signature
)
private
view
returns (bool isValid)
{
uint256 signatureLength = signature.length;
// HACK(dorothy-zbornak): Temporarily shave the signature type
// from the signature for the encode call then restore
// it immediately after because we want to keep signatures intact.
assembly {
mstore(signature, sub(signatureLength, 1))
}
// Encode the call data.
bytes memory callData = abi.encodeWithSelector(
IWallet(walletAddress).isValidOrderSignature.selector,
order,
orderHash,
signature
);
// Restore the full signature.
assembly {
mstore(signature, signatureLength)
}
// Static call the verification function.
(bool didSucceed, bytes memory returnData) = walletAddress.staticcall(callData);
// Return data should be a single bool.
if (didSucceed && returnData.length == 32) {
return returnData.readUint256(0) == 1;
}
// Static call to verifier failed.
_rrevert(SignatureOrderWalletError(
orderHash,
walletAddress,
signature,
returnData
));
}
/// @dev Verifies order AND signature via an EIP1271 Wallet contract.
/// @param order The order.
/// @param orderHash The order hash.
/// @param walletAddress Address that should have signed the given hash
/// and defines its own order/signature verification method.
/// @param signature Proof that the order has been signed by signer.
/// @return True if order and signature are validated by the Wallet.
function _validateOrderWithEIP1271Wallet(
Order memory order,
bytes32 orderHash,
address walletAddress,
bytes memory signature
)
private
view
returns (bool isValid)
{
uint256 signatureLength = signature.length;
// HACK(dorothy-zbornak): Temporarily shave the signature type
// from the signature for the encode call then restore
// it immediately after because we want to keep signatures intact.
assembly {
mstore(signature, sub(signatureLength, 1))
}
// Encode the call data.
bytes memory data = abi.encode(order);
bytes memory callData = abi.encodeWithSelector(
IEIP1271Wallet(walletAddress).isValidSignature.selector,
data,
signature
);
// Restore the full signature.
assembly {
mstore(signature, signatureLength)
}
// Static call the verification function.
(bool didSucceed, bytes memory returnData) = walletAddress.staticcall(callData);
// Return data should be the `EIP1271_MAGIC_VALUE`.
if (didSucceed && returnData.length <= 32) {
return returnData.readBytes4(0) == EIP1271_MAGIC_VALUE;
}
// Static call to verifier failed.
_rrevert(SignatureOrderWalletError(
orderHash,
walletAddress,
signature,
returnData
));
}
/// @dev Verifies order AND signature via Validator contract.
/// If used with an order, the maker of the order can still be an EOA.
/// @param order The order.
/// @param orderHash The order 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 order and signature are validated by the Validator.
function _validateOrderWithValidator(
Order memory order,
bytes32 orderHash,
address signerAddress,
bytes memory signature
)
private
view
returns (bool isValid)
{
// 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 "\x07" |
uint256 signatureLength = signature.length;
// Read the validator address from the signature.
address validatorAddress = signature.readAddress(signatureLength - 21);
// Ensure signer has approved validator.
if (!allowedOrderValidators[signerAddress][validatorAddress]) {
_rrevert(SignatureOrderValidatorNotApprovedError(
signerAddress,
validatorAddress
));
}
// HACK(dorothy-zbornak): Temporarily shave the validator address
// and signature type from the signature for the encode call then restore
// it immediately after because we want to keep signatures intact.
assembly {
mstore(signature, sub(signatureLength, 21))
}
// Encode the call data.
bytes memory callData = abi.encodeWithSelector(
IOrderValidator(validatorAddress).isValidOrderSignature.selector,
order,
orderHash,
signature
);
// Restore the full signature.
assembly {
mstore(signature, signatureLength)
}
// Static call the verification function.
(bool didSucceed, bytes memory returnData) = validatorAddress.staticcall(callData);
// Return data should be a single bool.
if (didSucceed && returnData.length == 32) {
return returnData.readUint256(0) == 1;
}
// Static call to verifier failed.
_rrevert(SignatureOrderValidatorError(
orderHash,
signerAddress,
validatorAddress,
signature,
returnData
));
}
/// Validates a hash-compatible signature type
/// (anything but `OrderValidator` and `OrderWallet`).
function _validateHashSignatureTypes(
SignatureType signatureType,
bytes32 hash,
address signerAddress,
bytes memory signature
)
private
view
returns (bool isValid)
{
// 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.
if (signatureType == SignatureType.Invalid) {
if (signature.length != 1) {
_rrevert(SignatureError(
SignatureErrorCodes.INVALID_LENGTH,
hash,
signerAddress,
signature
));
}
isValid = false;
return isValid;
// Signature using EIP712
} else if (signatureType == SignatureType.EIP712) {
if (signature.length != 66) {
_rrevert(SignatureError(
SignatureErrorCodes.INVALID_LENGTH,
hash,
signerAddress,
signature
));
}
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
address recovered = ecrecover(
hash,
v,
r,
s
);
isValid = signerAddress == recovered;
return isValid;
// Signed using web3.eth_sign
} else if (signatureType == SignatureType.EthSign) {
if (signature.length != 66) {
_rrevert(SignatureError(
SignatureErrorCodes.INVALID_LENGTH,
hash,
signerAddress,
signature
));
}
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
address recovered = ecrecover(
keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
hash
)),
v,
r,
s
);
isValid = signerAddress == recovered;
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 = _validateHashWithWallet(
hash,
signerAddress,
signature
);
return isValid;
// Signature verified by validator contract.
// If used with an order, the maker of the order can still be an EOA.
} else if (signatureType == SignatureType.Validator) {
isValid = _validateHashWithValidator(
hash,
signerAddress,
signature
);
return isValid;
// Signature verified by an EIP1271 wallet contract.
// If used with an order, the maker of the order is the wallet contract.
} else if (signatureType == SignatureType.EIP1271Wallet) {
isValid = _validateHashWithEIP1271Wallet(
hash,
signerAddress,
signature
);
return isValid;
}
// Otherwise, signatureType == SignatureType.PreSigned
assert(signatureType == SignatureType.PreSigned);
// Signer signed hash previously using the preSign function.
return preSigned[hash][signerAddress];
}
}

View File

@ -20,16 +20,16 @@ pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
import "./interfaces/ISignatureValidator.sol";
import "./interfaces/ITransactions.sol";
import "./MixinExchangeRichErrors.sol";
import "./MixinSignatureValidator.sol";
contract MixinTransactions is
MixinExchangeRichErrors,
ITransactions,
LibZeroExTransaction,
ISignatureValidator,
ITransactions
MixinExchangeRichErrors,
MixinSignatureValidator
{
// Mapping of transaction hash => executed
// This prevents transactions from being executed more than once.
@ -113,7 +113,8 @@ contract MixinTransactions is
address signerAddress = transaction.signerAddress;
if (signerAddress != msg.sender) {
// Validate signature
if (!isValidHashSignature(
if (!_isValidTransactionWithHashSignature(
transaction,
transactionHash,
signerAddress,
signature)) {

View File

@ -37,10 +37,6 @@ contract IExchangeRichErrors {
bytes4 internal constant SIGNATURE_VALIDATOR_NOT_APPROVED_ERROR_SELECTOR =
0xa15c0d06;
// bytes4(keccak256("SignatureOrderValidatorNotApprovedError(address,address)"))
bytes4 internal constant SIGNATURE_ORDER_VALIDATOR_NOT_APPROVED_ERROR_SELECTOR =
0x6d273c5a;
// bytes4(keccak256("SignatureValidatorError(bytes32,address,address,bytes,bytes)"))
bytes4 internal constant SIGNATURE_VALIDATOR_ERROR_SELECTOR =
0xa23838b8;
@ -49,14 +45,6 @@ contract IExchangeRichErrors {
bytes4 internal constant SIGNATURE_WALLET_ERROR_SELECTOR =
0x1b8388f7;
// bytes4(keccak256("SignatureOrderValidatorError(bytes32,address,address,bytes,bytes)"))
bytes4 internal constant SIGNATURE_ORDER_VALIDATOR_ERROR_SELECTOR =
0xf45375b7;
// bytes4(keccak256("SignatureOrderWalletError(bytes32,address,bytes,bytes)"))
bytes4 internal constant SIGNATURE_ORDER_WALLET_ERROR_SELECTOR =
0x37fa88c3;
// bytes4(keccak256("OrderStatusError(bytes32,uint8)"))
bytes4 internal constant ORDER_STATUS_ERROR_SELECTOR =
0xfdb6ca8d;

View File

@ -1,40 +0,0 @@
/*
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.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract IOrderValidator {
/// @dev Verifies that an order AND a signature is valid.
/// @param order The order.
/// @param orderHash The order hash.
/// @param signature Proof of signing.
/// @return Validity of order and signature.
function isValidOrderSignature(
LibOrder.Order calldata order,
bytes32 orderHash,
bytes calldata signature
)
external
view
returns (bool isValid);
}

View File

@ -20,6 +20,7 @@ pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
contract ISignatureValidator {
@ -33,11 +34,8 @@ contract ISignatureValidator {
Wallet, // 0x04
Validator, // 0x05
PreSigned, // 0x06
OrderValidator, // 0x07
OrderWallet, // 0x08
EIP1271Wallet, // 0x09
EIP1271OrderWallet, // 0x0A
NSignatureTypes // 0x0B, number of signature types. Always leave at end.
EIP1271Wallet, // 0x07
NSignatureTypes // 0x08, number of signature types. Always leave at end.
}
event SignatureValidatorApproval(
@ -71,13 +69,13 @@ contract ISignatureValidator {
)
external;
/// @dev Verifies that a signature for a hash is valid.
/// @param hash Message hash that is signed.
/// @param signerAddress Address of signer.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidHashSignature(
bytes32 hash,
/// @dev Verifies that a signature for an order is valid.
/// @param order The order.
/// @param signerAddress Address that should have signed the given order.
/// @param signature Proof that the order has been signed by signer.
/// @return isValid true if the signature is valid for the given order and signer.
function isValidOrderSignature(
LibOrder.Order memory order,
address signerAddress,
bytes memory signature
)
@ -85,13 +83,13 @@ contract ISignatureValidator {
view
returns (bool isValid);
/// @dev Verifies that a signature for an order is valid.
/// @param order The order.
/// @param signerAddress Address of signer.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidOrderSignature(
LibOrder.Order memory order,
/// @dev Verifies that a signature for a transaction is valid.
/// @param transaction The transaction.
/// @param signerAddress Address that should have signed the given order.
/// @param signature Proof that the order has been signed by signer.
/// @return isValid true if the signature is valid for the given transaction and signer.
function isValidTransactionSignature(
LibZeroExTransaction.ZeroExTransaction memory transaction,
address signerAddress,
bytes memory signature
)

View File

@ -1,40 +0,0 @@
/*
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.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract IValidator {
/// @dev Verifies that a signature is valid.
/// @param hash Message hash that is signed.
/// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidSignature(
bytes32 hash,
address signerAddress,
bytes calldata signature
)
external
view
returns (bool isValid);
}

View File

@ -1,6 +1,6 @@
/*
Copyright 2018 ZeroEx Intl.
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibEIP1271.sol";
@ -30,6 +31,11 @@ interface ISimplifiedExchange {
external
view
returns (bytes32 orderHash);
function getTransactionHash(LibZeroExTransaction.ZeroExTransaction calldata transaction)
external
view
returns (bytes32 transactionHash);
}
@ -39,20 +45,30 @@ contract TestValidatorWallet is
{
using LibBytes for bytes;
// Revert reason for `Revert` `ValidatorAction`.
/// @dev Revert reason for `Revert` `ValidatorAction`.
string constant public REVERT_REASON = "you shall not pass";
enum DataType {
// No data type; only expecting a hash (default)
None,
// An Order
Order,
// A ZeroExTransaction
ZeroExTransaction,
NTypes
}
enum ValidatorAction {
// Return false (default)
// Return failure (default)
Reject,
// Return true
// Return success
Accept,
// Revert
Revert,
// Update state
UpdateState,
// Validate signature
ValidateSignature,
// Ensure the signature hash matches what was prepared
MatchSignatureHash,
NTypes
}
@ -62,8 +78,10 @@ contract TestValidatorWallet is
uint256 internal _state = 1;
/// @dev What action to execute when a hash is validated .
mapping (bytes32 => ValidatorAction) internal _hashActions;
/// @dev Allowed signers for hash signature types.
mapping (bytes32 => address) internal _validSignerForHash;
/// @dev The data type of a hash.
mapping (bytes32 => DataType) internal _hashDataTypes;
/// @dev keccak256 of the expected signature data for a hash.
mapping (bytes32 => DataType) internal _hashSignatureHashes;
constructor(address exchange) public {
_exchange = ISimplifiedExchange(exchange);
@ -83,32 +101,34 @@ contract TestValidatorWallet is
IERC20Token(token).approve(spender, value);
}
/// @dev Set the action to take when validating a hash.
/// @dev Prepares this contract to validate a signature.
/// @param hash The hash.
/// @param action action to take.
/// @param allowedSigner Signer that must be recovered with
/// `ValidateSignature` action type and `Wallet` or
/// `OrderWallet` signature types.
function setValidateAction(
/// @param dataType The data type associated with the hash.
/// @param action Action to take.
/// @param signatureHash keccak256 of the expected signature data.
function prepare(
bytes32 hash,
DataType dataType,
ValidatorAction action,
address allowedSigner
bytes32 signatureHash
)
external
{
if (uint8(action) >= uint8(ValidatorAction.NTypes)) {
revert("UNSUPPORTED_VALIDATE_ACTION");
if (uint8(dataType) >= uint8(DataType.NTypes)) {
revert("UNSUPPORTED_DATA_TYPE");
}
if (uint8(action) >= uint8(ValidatorAction.NTypes)) {
revert("UNSUPPORTED_VALIDATOR_ACTION");
}
_hashDataTypes[hash] = dataType;
_hashActions[hash] = action;
_validSignerForHash[hash] = allowedSigner;
_hashSignatureHashes[hash] = signatureHash;
}
/// @dev Validates a hash with the following signature types:
/// `EIP1271Wallet` and `EIP1271WalletOrder`signature types.
/// The length of `data` will determine which signature type is in use.
/// @param data Arbitrary data. Either an Order hash or abi-encoded Order.
/// @dev Validates data signed by either `EIP1271Wallet` or `Validator` signature types.
/// @param data Abi-encoded data (Order or ZeroExTransaction) and a hash.
/// @param signature Signature for `data`.
/// @return magicValue Returns `EIP1271_MAGIC_VALUE` if the signature check succeeds.
/// @return magicValue `EIP1271_MAGIC_VALUE` if the signature check succeeds.
function isValidSignature(
bytes memory data,
bytes memory signature
@ -116,35 +136,30 @@ contract TestValidatorWallet is
public
returns (bytes4 magicValue)
{
bytes32 hash = _getOrderHashFromEIP1271Data(data);
bytes32 hash = _decodeAndValidateHashFromEncodedData(data);
ValidatorAction action = _hashActions[hash];
// solhint-disable-next-line no-empty-blocks
if (action == ValidatorAction.Reject) {
// NOOP.
magicValue = 0x0;
} else if (action == ValidatorAction.Accept) {
magicValue = EIP1271_MAGIC_VALUE;
} else if (action == ValidatorAction.Revert) {
revert(REVERT_REASON);
} else if (action == ValidatorAction.UpdateState) {
_updateState();
} else { // action == ValidatorAction.ValidateSignature
if (data.length != 32) {
// `data` is an abi-encoded Order.
LibOrder.Order memory order = _getOrderFromEIP1271Data(data);
if (order.makerAddress == address(this)) {
magicValue = EIP1271_MAGIC_VALUE;
}
} else if (_validSignerForHash[hash] == address(this)) {
} else {
assert(action == ValidatorAction.MatchSignatureHash);
bytes32 expectedSignatureHash = _hashSignatureHashes[hash];
if (keccak256(signature) == expectedSignatureHash) {
magicValue = EIP1271_MAGIC_VALUE;
}
}
}
/// @dev Validates a hash with the `Validator` signature type.
/// @dev Validates a hash with the `Wallet` signature type.
/// @param hash Message hash that is signed.
/// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof of signing.
/// @return Validity of order signature.
/// @return isValid `true` if the signature check succeeds.
function isValidSignature(
bytes32 hash,
address signerAddress,
@ -160,129 +175,61 @@ contract TestValidatorWallet is
isValid = true;
} else if (action == ValidatorAction.Revert) {
revert(REVERT_REASON);
} else if (action == ValidatorAction.ValidateSignature) {
isValid = _isSignedBy(hash, signature, signerAddress);
} else { // action == ValidatorAction.UpdateState
} else if (action == ValidatorAction.UpdateState) {
_updateState();
}
}
/// @dev Validates a hash with the `Wallet` signature type.
/// @param hash Message hash that is signed.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidSignature(
bytes32 hash,
bytes memory signature
)
public
returns (bool isValid)
{
ValidatorAction action = _hashActions[hash];
if (action == ValidatorAction.Reject) {
isValid = false;
} else if (action == ValidatorAction.Accept) {
isValid = true;
} else if (action == ValidatorAction.Revert) {
revert(REVERT_REASON);
} else if (action == ValidatorAction.ValidateSignature) {
isValid = _validSignerForHash[hash] == address(this);
} else { // action == ValidatorAction.UpdateState
_updateState();
}
}
/// @dev Validates a hash with the `OrderValidator` and `OrderWallet`
/// signature types.
/// @param order The order.
/// @param orderHash The order hash.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidOrderSignature(
LibOrder.Order memory order,
bytes32 orderHash,
bytes memory signature
)
public
returns (bool isValid)
{
ValidatorAction action = _hashActions[orderHash];
if (action == ValidatorAction.Reject) {
isValid = false;
} else if (action == ValidatorAction.Accept) {
isValid = true;
} else if (action == ValidatorAction.Revert) {
revert(REVERT_REASON);
} else if (action == ValidatorAction.ValidateSignature) {
if (signature.length == 0) {
// OrderWallet type.
isValid = order.makerAddress == address(this);
} else {
// OrderValidator type.
isValid = _isSignedBy(orderHash, signature, order.makerAddress);
assert(action == ValidatorAction.MatchSignatureHash);
bytes32 expectedSignatureHash = _hashSignatureHashes[hash];
if (keccak256(signature) == expectedSignatureHash) {
isValid = true;
}
} else { // action == ValidatorAction.UpdateState
_updateState();
}
}
/// @dev Increments state variable.
/// @dev Increments state variable to trigger a state change.
function _updateState()
private
{
_state++;
}
/// @dev Verifies the signer of a hash is correct.
function _isSignedBy(
bytes32 hash,
bytes memory signature,
address signerAddress
)
private
pure
returns (bool isSignedBy)
{
require(signature.length == 65, "LENGTH_65_REQUIRED");
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
// Try a naked signature.
address recovered = ecrecover(hash, v, r, s);
if (recovered != signerAddress) {
// Try an eth_sign signature.
bytes32 ethSignHash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
hash
)
);
recovered = ecrecover(ethSignHash, v, r, s);
}
isSignedBy = recovered == signerAddress;
}
function _getOrderHashFromEIP1271Data(bytes memory data)
function _decodeAndValidateHashFromEncodedData(bytes memory data)
private
view
returns (bytes32 hash)
{
if (data.length == 32) {
// `data` is an order hash.
hash = data.readBytes32(0);
// HACK(dorothy-zbornak): First we want the hash, which is the second
// encoded parameter. We will initially treat all fields as inline
// `bytes32`s and ignore the first one to extract it.
(,hash) = abi.decode(data, (bytes32, bytes32));
// Now we can figure out what the data type is from a previous call to
// `prepare()`.
DataType dataType = _hashDataTypes[hash];
require(
dataType != DataType.None,
"EXPECTED_NO_DATA_TYPE"
);
if (dataType == DataType.Order) {
// Decode the first parameter as an `Order` type.
LibOrder.Order memory order = abi.decode(data, (LibOrder.Order));
// Use the Exchange to calculate the hash of the order and assert
// that it matches the one we extracted previously.
require(
_exchange.getOrderHash(order) == hash,
"UNEXPECTED_ORDER_HASH"
);
} else {
// `data` is an abi-encoded Order.
LibOrder.Order memory order = _getOrderFromEIP1271Data(data);
// Use the Exchange contract to convert it into a hash.
hash = _exchange.getOrderHash(order);
assert(dataType == DataType.ZeroExTransaction);
// Decode the first parameter as a `ZeroExTransaction` type.
LibZeroExTransaction.ZeroExTransaction memory transaction =
abi.decode(data, (LibZeroExTransaction.ZeroExTransaction));
// Use the Exchange to calculate the hash of the transaction and assert
// that it matches the one we extracted previously.
require(
_exchange.getTransactionHash(transaction) == hash,
"UNEXPECTED_TRANSACTION_HASH"
);
}
return hash;
}
}
function _getOrderFromEIP1271Data(bytes memory data)
private
returns (LibOrder.Order memory order)
{
require(data.length > 32, "INVALID_EIP1271_ORDER_DATA_LENGTH");
return abi.decode(data, (LibOrder.Order));
}
}