Add OrderValidator and WalletOrderValidator signature strategies to Exchange (#1774)

* In `@0x/contracts-exchange`: Add `SignatureType.OrderValidator` support to contracts and refactor signature validation functions.

* In `@0x/types`: Add `SignatureType.OrderValidator` and `RevertReason.InappropriateSignature`.

* In `@0x/contracts-exchange`: Fix contracts and update tests for `SignatureType.OrderValidator`.

* Ran prettier/linter

* Update changelogs

* In `@0x/order-utils`: Add `SignatureOrderValidatorError` to `ExchangeRevertErrors`.

* In `@0x/contracts-exchange`: Add `SignatureOrderValidatorError` rich revert. Fix rebase issues. Rename `IValidator.isValidOrder` to `IValidator.isValidOrderSignature`.

* In `@0x/contracts-exchange`: Add revert test cases for `OrderValidator` signature type.

* In `@0x/order-utils`: Update changelog.

* In `@0x/contracts-exchange`: Split off `SignatureType.OrderValidator` scheme into its own interface and registry.

* In `@0x/types`: Add `SignatureType.WalletOrderValidator`.

* In `@0x/order-utils`: Add `SignatureWalletOrderValidatorError`.

* In `@0x/contracts-exchange`: Add `SignatureType.WalletOrderValidator` support.

* Ran prettier

* In `@0x/types`: Remove `RevertReason.WalletOrderValidator`.

* Update/fix changelogs in `@0x/contracts-exchange`, `@0x/order-utils`, and `@0x/types`.

* In `@0x/contracts-exchange`: Make `isValidOrderSignature` `external` instead of `public`.

* In `@0x/contracts-exchange`: Change `isValidOrderSignature` back to `public` because passing `calldata` to internal functions isn't supported.
This commit is contained in:
Lawrence Forman 2019-04-19 13:07:55 -04:00 committed by Amir Bandeali
parent c24bb139dd
commit 0bcd47b394
26 changed files with 932 additions and 297 deletions

View File

@ -21,6 +21,14 @@
{ {
"note": "Upgrade all string reverts to rich reverts", "note": "Upgrade all string reverts to rich reverts",
"pr": 1761 "pr": 1761
},
{
"note": "Add support for `SignatureType.OrderValidator` for orders",
"pr": 1774
},
{
"note": "Add support for `SignatureType.WalletOrderValidator` for orders",
"pr": 1774
} }
] ]
}, },

View File

@ -33,6 +33,7 @@
"src/interfaces/IExchange.sol", "src/interfaces/IExchange.sol",
"src/interfaces/IExchangeCore.sol", "src/interfaces/IExchangeCore.sol",
"src/interfaces/IMatchOrders.sol", "src/interfaces/IMatchOrders.sol",
"src/interfaces/IOrderValidator.sol",
"src/interfaces/ISignatureValidator.sol", "src/interfaces/ISignatureValidator.sol",
"src/interfaces/ITransactions.sol", "src/interfaces/ITransactions.sol",
"src/interfaces/IValidator.sol", "src/interfaces/IValidator.sol",

View File

@ -17,15 +17,19 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "../src/interfaces/IValidator.sol"; import "../src/interfaces/IValidator.sol";
import "../src/interfaces/IOrderValidator.sol";
contract Validator is contract Validator is
IValidator IValidator,
IOrderValidator
{ {
// The single valid signer for this wallet. // The single valid signer for this validator.
// solhint-disable-next-line var-name-mixedcase // solhint-disable-next-line var-name-mixedcase
address internal VALID_SIGNER; address internal VALID_SIGNER;
@ -35,12 +39,12 @@ contract Validator is
VALID_SIGNER = validSigner; VALID_SIGNER = validSigner;
} }
// solhint-disable no-unused-vars
/// @dev Verifies that a signature is valid. `signer` must match `VALID_SIGNER`. /// @dev Verifies that a signature is valid. `signer` must match `VALID_SIGNER`.
/// @param hash Message hash that is signed. /// @param hash Message hash that is signed.
/// @param signerAddress Address that should have signed the given hash. /// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof of signing. /// @param signature Proof of signing.
/// @return Validity of signature. /// @return Validity of signature.
// solhint-disable no-unused-vars
function isValidSignature( function isValidSignature(
bytes32 hash, bytes32 hash,
address signerAddress, address signerAddress,
@ -53,4 +57,23 @@ contract Validator is
return (signerAddress == VALID_SIGNER); return (signerAddress == VALID_SIGNER);
} }
// solhint-enable no-unused-vars // solhint-enable no-unused-vars
// solhint-disable no-unused-vars
/// @dev Verifies that an order and signature is valid. `signer` must match `VALID_SIGNER`.
/// @param order The order.
/// @param orderHash The order hash.
/// @param signature Proof of signing.
/// @return Validity of signature.
function isValidOrderSignature(
LibOrder.Order calldata order,
bytes32 orderHash,
bytes calldata signature
)
external
view
returns (bool isValid)
{
return (order.makerAddress == VALID_SIGNER);
}
// solhint-enable no-unused-vars
} }

View File

@ -17,9 +17,11 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "../src/interfaces/IWallet.sol"; import "../src/interfaces/IWallet.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract Wallet is contract Wallet is
@ -40,26 +42,59 @@ contract Wallet is
/// @dev Validates an EIP712 signature. /// @dev Validates an EIP712 signature.
/// The signer must match the owner of this wallet. /// The signer must match the owner of this wallet.
/// @param hash Message hash that is signed. /// @param hash Message hash that is signed.
/// @param eip712Signature Proof of signing. /// @param signature Proof of signing.
/// @return Validity of signature. /// @return Validity of signature.
function isValidSignature( function isValidSignature(
bytes32 hash, bytes32 hash,
bytes calldata eip712Signature bytes calldata signature
) )
external external
view view
returns (bool isValid) returns (bool isValid)
{ {
require( require(
eip712Signature.length == 65, signature.length == 65,
"LENGTH_65_REQUIRED" "LENGTH_65_REQUIRED"
); );
uint8 v = uint8(eip712Signature[0]); return validateEIP712Signature(hash, signature);
bytes32 r = eip712Signature.readBytes32(1); }
bytes32 s = eip712Signature.readBytes32(33);
/// @dev Validates an order AND EIP712 signature.
/// The signer must match the owner of this wallet.
/// @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)
{
// Ensure order hash is correct.
require(
order.makerAddress == WALLET_OWNER,
"INVALID_ORDER_MAKER"
);
return validateEIP712Signature(orderHash, signature);
}
function validateEIP712Signature(
bytes32 hash,
bytes memory signature
)
private
view
returns (bool isValid)
{
uint8 v = uint8(signature[0]);
bytes32 r = signature.readBytes32(1);
bytes32 s = signature.readBytes32(33);
address recoveredAddress = ecrecover(hash, v, r, s); address recoveredAddress = ecrecover(hash, v, r, s);
isValid = WALLET_OWNER == recoveredAddress; return WALLET_OWNER == recoveredAddress;
return isValid;
} }
} }

View File

@ -353,7 +353,8 @@ contract MixinExchangeCore is
// Validate Maker signature (check only if first time seen) // Validate Maker signature (check only if first time seen)
if (orderInfo.orderTakerAssetFilledAmount == 0) { if (orderInfo.orderTakerAssetFilledAmount == 0) {
address makerAddress = order.makerAddress; address makerAddress = order.makerAddress;
if (!isValidSignature( if (!isValidOrderWithHashSignature(
order,
orderInfo.orderHash, orderInfo.orderHash,
makerAddress, makerAddress,
signature)) { signature)) {

View File

@ -85,6 +85,44 @@ contract MixinExchangeRichErrors is
); );
} }
function SignatureOrderValidatorError(
bytes32 orderHash,
address signer,
bytes memory signature,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_ORDER_VALIDATOR_ERROR_SELECTOR,
orderHash,
signer,
signature,
errorData
);
}
function SignatureWalletOrderValidatorError(
bytes32 orderHash,
address wallet,
bytes memory signature,
bytes memory errorData
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
SIGNATURE_WALLET_ORDER_VALIDATOR_ERROR_SELECTOR,
orderHash,
wallet,
signature,
errorData
);
}
function OrderStatusError( function OrderStatusError(
LibOrder.OrderStatus orderStatus, LibOrder.OrderStatus orderStatus,
bytes32 orderHash bytes32 orderHash

View File

@ -21,15 +21,18 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol"; import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "./mixins/MSignatureValidator.sol"; import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol"; import "./mixins/MTransactions.sol";
import "./mixins/MExchangeRichErrors.sol"; import "./mixins/MExchangeRichErrors.sol";
import "./interfaces/IWallet.sol"; import "./interfaces/IWallet.sol";
import "./interfaces/IValidator.sol"; import "./interfaces/IValidator.sol";
import "./interfaces/IOrderValidator.sol";
contract MixinSignatureValidator is contract MixinSignatureValidator is
ReentrancyGuard, ReentrancyGuard,
LibOrder,
MSignatureValidator, MSignatureValidator,
MTransactions, MTransactions,
MExchangeRichErrors MExchangeRichErrors
@ -42,6 +45,9 @@ contract MixinSignatureValidator is
// Mapping of signer => validator => approved // Mapping of signer => validator => approved
mapping (address => mapping (address => bool)) public allowedValidators; 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 using any valid signature type. /// @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. /// 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 signerAddress Address that should have signed the given hash.
@ -54,7 +60,7 @@ contract MixinSignatureValidator is
external external
{ {
if (signerAddress != msg.sender) { if (signerAddress != msg.sender) {
if (!isValidSignature( if (!isValidHashSignature(
hash, hash,
signerAddress, signerAddress,
signature)) { signature)) {
@ -69,7 +75,8 @@ contract MixinSignatureValidator is
preSigned[hash][signerAddress] = true; preSigned[hash][signerAddress] = true;
} }
/// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf. /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf
/// using the `Validator` signature type.
/// @param validatorAddress Address of Validator contract. /// @param validatorAddress Address of Validator contract.
/// @param approval Approval or disapproval of Validator contract. /// @param approval Approval or disapproval of Validator contract.
function setSignatureValidatorApproval( function setSignatureValidatorApproval(
@ -88,12 +95,55 @@ 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.
function isValidOrderSignature(
Order memory order,
address signerAddress,
bytes memory signature
)
public
view
returns (bool isValid)
{
bytes32 orderHash = getOrderHash(order);
return isValidOrderWithHashSignature(
order,
orderHash,
signerAddress,
signature
);
}
/// @dev Verifies that a hash has been signed by the given signer. /// @dev Verifies that a hash has been signed by the given signer.
/// @param hash Any 32 byte hash. /// @param hash Any 32-byte hash.
/// @param signerAddress Address that should have signed the given hash. /// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof that the hash has been signed by signer. /// @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. /// @return True if the signature is valid for the given hash and signer.
function isValidSignature( function isValidHashSignature(
bytes32 hash, bytes32 hash,
address signerAddress, address signerAddress,
bytes memory signature bytes memory signature
@ -101,6 +151,90 @@ contract MixinSignatureValidator is
public public
view view
returns (bool isValid) 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.WalletOrderValidator) {
rrevert(SignatureError(
SignatureErrorCodes.INAPPROPRIATE_SIGNATURE_TYPE,
hash,
signerAddress,
signature
));
}
return validateHashSignatureTypes(
signatureType,
hash,
signerAddress,
signature
);
}
/// @dev Verifies that an order, with provided order hash, has been signed
/// by the given signer.
/// @param order The order.
/// @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 True if the signature is valid for the given hash and signer.
function isValidOrderWithHashSignature(
Order memory order,
bytes32 orderHash,
address signerAddress,
bytes memory signature
)
internal
view
returns (bool isValid)
{
SignatureType signatureType = readValidSignatureType(
orderHash,
signerAddress,
signature
);
if (signatureType == SignatureType.OrderValidator) {
// The entire order is verified by validator contract.
isValid = validateOrderWithValidator(
order,
orderHash,
signerAddress,
signature
);
return isValid;
} else if (signatureType == SignatureType.WalletOrderValidator) {
// The entire order is verified by a wallet contract.
isValid = validateOrderWithWallet(
order,
orderHash,
signerAddress,
signature
);
return isValid;
}
// Otherwise, it's one of the hash-compatible signature types.
return validateHashSignatureTypes(
signatureType,
orderHash,
signerAddress,
signature
);
}
/// Reads the `SignatureType` from the end of a signature and validates it.
function readValidSignatureType(
bytes32 hash,
address signerAddress,
bytes memory signature
)
private
pure
returns (SignatureType signatureType)
{ {
if (signature.length == 0) { if (signature.length == 0) {
rrevert(SignatureError( rrevert(SignatureError(
@ -124,126 +258,21 @@ contract MixinSignatureValidator is
)); ));
} }
SignatureType signatureType = SignatureType(signatureTypeRaw);
// Variables are not scoped in Solidity.
uint8 v;
bytes32 r;
bytes32 s;
address recovered;
// Always illegal signature. // Always illegal signature.
// This is always an implicit option since a signer can create a // This is always an implicit option since a signer can create a
// signature array with invalid type or length. We may as well make // signature array with invalid type or length. We may as well make
// it an explicit option. This aids testing and analysis. It is // it an explicit option. This aids testing and analysis. It is
// also the initialization value for the enum type. // also the initialization value for the enum type.
if (signatureType == SignatureType.Illegal) { if (SignatureType(signatureTypeRaw) == SignatureType.Illegal) {
rrevert(SignatureError( rrevert(SignatureError(
SignatureErrorCodes.ILLEGAL, SignatureErrorCodes.ILLEGAL,
hash, hash,
signerAddress, signerAddress,
signature signature
)); ));
// 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) {
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
));
}
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) {
if (signature.length != 66) {
rrevert(SignatureError(
SignatureErrorCodes.INVALID_LENGTH,
hash,
signerAddress,
signature
));
}
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;
// 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 = isValidWalletSignature(
hash,
signerAddress,
signature
);
return isValid;
// Signature verified by validator contract.
} else if (signatureType == SignatureType.Validator) {
isValid = isValidValidatorSignature(
hash,
signerAddress,
signature
);
return isValid;
// Signer signed hash previously using the preSign function.
} else if (signatureType == SignatureType.PreSigned) {
isValid = preSigned[hash][signerAddress];
return isValid;
} }
// Anything else is illegal (We do not return false because return SignatureType(signatureTypeRaw);
// 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.)
rrevert(SignatureError(
SignatureErrorCodes.UNSUPPORTED,
hash,
signerAddress,
signature
));
} }
/// @dev Verifies signature using logic defined by Wallet contract. /// @dev Verifies signature using logic defined by Wallet contract.
@ -251,8 +280,8 @@ contract MixinSignatureValidator is
/// @param walletAddress Address that should have signed the given hash /// @param walletAddress Address that should have signed the given hash
/// and defines its own signature verification method. /// and defines its own signature verification method.
/// @param signature Proof that the hash has been signed by signer. /// @param signature Proof that the hash has been signed by signer.
/// @return True if signature is valid for given wallet.. /// @return True if the signature is validated by the Walidator.
function isValidWalletSignature( function validateHashWithWallet(
bytes32 hash, bytes32 hash,
address walletAddress, address walletAddress,
bytes memory signature bytes memory signature
@ -292,11 +321,12 @@ contract MixinSignatureValidator is
} }
/// @dev Verifies signature using logic defined by Validator contract. /// @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. /// @param hash Any 32 byte hash.
/// @param signerAddress Address that should have signed the given hash. /// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof that the hash has been signed by signer. /// @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. /// @return True if the signature is validated by the Validator.
function isValidValidatorSignature( function validateHashWithValidator(
bytes32 hash, bytes32 hash,
address signerAddress, address signerAddress,
bytes memory signature bytes memory signature
@ -348,4 +378,214 @@ contract MixinSignatureValidator is
returnData returnData
)); ));
} }
/// @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;
// Shave the signature type off the signature.
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(SignatureWalletOrderValidatorError(
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]) {
return false;
}
// Shave the validator address and signature type from the signature.
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,
signature,
returnData
));
}
/// Validates a hash-compatible signature type
/// (anything but `OrderValidator` and `WalletOrderValidator`).
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;
}
// Otherwise, signatureType == SignatureType.PreSigned
assert(signatureType == SignatureType.PreSigned);
// Signer signed hash previously using the preSign function.
return preSigned[hash][signerAddress];
}
} }

View File

@ -70,7 +70,7 @@ contract MixinTransactions is
address signerAddress = transaction.signerAddress; address signerAddress = transaction.signerAddress;
if (signerAddress != msg.sender) { if (signerAddress != msg.sender) {
// Validate signature // Validate signature
if (!isValidSignature( if (!isValidHashSignature(
transactionHash, transactionHash,
signerAddress, signerAddress,
signature)) { signature)) {

View File

@ -0,0 +1,40 @@
/*
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.5;
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

@ -17,6 +17,9 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract ISignatureValidator { contract ISignatureValidator {
@ -41,12 +44,12 @@ contract ISignatureValidator {
) )
external; external;
/// @dev Verifies that a signature is valid. /// @dev Verifies that a signature for a hash is valid.
/// @param hash Message hash that is signed. /// @param hash Message hash that is signed.
/// @param signerAddress Address of signer. /// @param signerAddress Address of signer.
/// @param signature Proof of signing. /// @param signature Proof of signing.
/// @return Validity of order signature. /// @return Validity of order signature.
function isValidSignature( function isValidHashSignature(
bytes32 hash, bytes32 hash,
address signerAddress, address signerAddress,
bytes memory signature bytes memory signature
@ -54,4 +57,18 @@ contract ISignatureValidator {
public public
view view
returns (bool isValid); 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,
address signerAddress,
bytes memory signature
)
public
view
returns (bool isValid);
} }

View File

@ -17,6 +17,9 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract IValidator { contract IValidator {

View File

@ -17,6 +17,9 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract IWallet { contract IWallet {
@ -32,4 +35,18 @@ contract IWallet {
external external
view view
returns (bool isValid); returns (bool isValid);
/// @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

@ -36,7 +36,8 @@ contract MExchangeRichErrors is
BAD_SIGNATURE, BAD_SIGNATURE,
INVALID_LENGTH, INVALID_LENGTH,
UNSUPPORTED, UNSUPPORTED,
ILLEGAL ILLEGAL,
INAPPROPRIATE_SIGNATURE_TYPE
} }
enum AssetProxyDispatchErrorCodes { enum AssetProxyDispatchErrorCodes {
@ -89,6 +90,32 @@ contract MExchangeRichErrors is
pure pure
returns (bytes memory); returns (bytes memory);
bytes4 internal constant SIGNATURE_ORDER_VALIDATOR_ERROR_SELECTOR =
bytes4(keccak256("SignatureOrderValidatorError(bytes32,address,bytes,bytes)"));
function SignatureOrderValidatorError(
bytes32 orderHash,
address signer,
bytes memory signature,
bytes memory errorData
)
internal
pure
returns (bytes memory);
bytes4 internal constant SIGNATURE_WALLET_ORDER_VALIDATOR_ERROR_SELECTOR =
bytes4(keccak256("SignatureWalletOrderValidatorError(bytes32,address,bytes,bytes)"));
function SignatureWalletOrderValidatorError(
bytes32 orderHash,
address wallet,
bytes memory signature,
bytes memory errorData
)
internal
pure
returns (bytes memory);
bytes4 internal constant ORDER_STATUS_ERROR_SELECTOR = bytes4 internal constant ORDER_STATUS_ERROR_SELECTOR =
bytes4(keccak256("OrderStatusError(uint8,bytes32)")); bytes4(keccak256("OrderStatusError(uint8,bytes32)"));

View File

@ -19,6 +19,7 @@
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "../interfaces/ISignatureValidator.sol"; import "../interfaces/ISignatureValidator.sol";
@ -33,13 +34,32 @@ contract MSignatureValidator is
// Allowed signature types. // Allowed signature types.
enum SignatureType { enum SignatureType {
Illegal, // 0x00, default value Illegal, // 0x00, default value
Invalid, // 0x01 Invalid, // 0x01
EIP712, // 0x02 EIP712, // 0x02
EthSign, // 0x03 EthSign, // 0x03
Wallet, // 0x04 Wallet, // 0x04
Validator, // 0x05 Validator, // 0x05
PreSigned, // 0x06 PreSigned, // 0x06
NSignatureTypes // 0x07, number of signature types. Always leave at end. OrderValidator, // 0x07
WalletOrderValidator, // 0x08
NSignatureTypes // 0x09, number of signature types. Always leave at end.
} }
/// @dev Verifies that an order, with provided order hash, has been signed
/// by the given signer.
/// @param order The order.
/// @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 True if the signature is valid for the given hash and signer.
function isValidOrderWithHashSignature(
LibOrder.Order memory order,
bytes32 orderHash,
address signerAddress,
bytes memory signature
)
internal
view
returns (bool isValid);
} }

View File

@ -17,7 +17,9 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
@ -55,4 +57,20 @@ contract TestRevertReceiver {
{ {
revert(REVERT_REASON); revert(REVERT_REASON);
} }
/// @dev Reverts with `REVERT_REASON`. Intended to be used with `OrderValidator` signature type.
/// @param order The order.
/// @param hash Message hash that is signed.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidOrderSignature(
LibOrder.Order calldata order,
bytes32 hash,
bytes calldata signature
)
external
returns (bool isValid)
{
revert(REVERT_REASON);
}
} }

View File

@ -37,21 +37,4 @@ contract TestSignatureValidator is
public public
LibEIP712ExchangeDomain(chainId, address(0)) LibEIP712ExchangeDomain(chainId, address(0))
{} {}
function publicIsValidSignature(
bytes32 hash,
address signer,
bytes memory signature
)
public
view
returns (bool isValid)
{
isValid = isValidSignature(
hash,
signer,
signature
);
return isValid;
}
} }

View File

@ -17,7 +17,9 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
@ -58,6 +60,23 @@ contract TestStaticCallReceiver {
return true; return true;
} }
/// @dev Updates state and returns true. Intended to be used with `OrderValidator` signature type.
/// @param order The order.
/// @param orderHash The order hash.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidOrderSignature(
LibOrder.Order calldata order,
bytes32 orderHash,
bytes calldata signature
)
external
returns (bool isValid)
{
updateState();
return true;
}
/// @dev Approves an ERC20 token to spend tokens from this address. /// @dev Approves an ERC20 token to spend tokens from this address.
/// @param token Address of ERC20 token. /// @param token Address of ERC20 token.
/// @param spender Address that will spend tokens. /// @param spender Address that will spend tokens.

View File

@ -34,7 +34,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
}, },
"config": { "config": {
"abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxyDispatcher|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|IValidator|IWallet|IWrapperFunctions|ReentrantERC20Token|TestAssetProxyDispatcher|TestExchangeInternals|TestRevertReceiver|TestSignatureValidator|TestStaticCallReceiver|Validator|Wallet|Whitelist).json", "abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxyDispatcher|IExchange|IExchangeCore|IMatchOrders|IOrderValidator|ISignatureValidator|ITransactions|IValidator|IWallet|IWrapperFunctions|ReentrantERC20Token|TestAssetProxyDispatcher|TestExchangeInternals|TestRevertReceiver|TestSignatureValidator|TestStaticCallReceiver|Validator|Wallet|Whitelist).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
}, },
"repository": { "repository": {

View File

@ -11,6 +11,7 @@ import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispat
import * as IExchange from '../generated-artifacts/IExchange.json'; import * as IExchange from '../generated-artifacts/IExchange.json';
import * as IExchangeCore from '../generated-artifacts/IExchangeCore.json'; import * as IExchangeCore from '../generated-artifacts/IExchangeCore.json';
import * as IMatchOrders from '../generated-artifacts/IMatchOrders.json'; import * as IMatchOrders from '../generated-artifacts/IMatchOrders.json';
import * as IOrderValidator from '../generated-artifacts/IOrderValidator.json';
import * as ISignatureValidator from '../generated-artifacts/ISignatureValidator.json'; import * as ISignatureValidator from '../generated-artifacts/ISignatureValidator.json';
import * as ITransactions from '../generated-artifacts/ITransactions.json'; import * as ITransactions from '../generated-artifacts/ITransactions.json';
import * as IValidator from '../generated-artifacts/IValidator.json'; import * as IValidator from '../generated-artifacts/IValidator.json';
@ -38,12 +39,13 @@ export const artifacts = {
ISignatureValidator: ISignatureValidator as ContractArtifact, ISignatureValidator: ISignatureValidator as ContractArtifact,
ITransactions: ITransactions as ContractArtifact, ITransactions: ITransactions as ContractArtifact,
IValidator: IValidator as ContractArtifact, IValidator: IValidator as ContractArtifact,
IOrderValidator: IOrderValidator as ContractArtifact,
IWallet: IWallet as ContractArtifact, IWallet: IWallet as ContractArtifact,
IWrapperFunctions: IWrapperFunctions as ContractArtifact, IWrapperFunctions: IWrapperFunctions as ContractArtifact,
ReentrantERC20Token: ReentrantERC20Token as ContractArtifact, ReentrantERC20Token: ReentrantERC20Token as ContractArtifact,
TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact, TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact,
TestExchangeInternals: TestExchangeInternals as ContractArtifact, TestExchangeInternals: TestExchangeInternals as ContractArtifact,
TestRevertReceiver: TestRevertReceiver as ContractArtifact,
TestSignatureValidator: TestSignatureValidator as ContractArtifact, TestSignatureValidator: TestSignatureValidator as ContractArtifact,
TestStaticCallReceiver: TestStaticCallReceiver as ContractArtifact, TestStaticCallReceiver: TestStaticCallReceiver as ContractArtifact,
TestRevertReceiver: TestRevertReceiver as ContractArtifact,
}; };

View File

@ -9,6 +9,7 @@ export * from '../generated-wrappers/i_asset_proxy_dispatcher';
export * from '../generated-wrappers/i_exchange'; export * from '../generated-wrappers/i_exchange';
export * from '../generated-wrappers/i_exchange_core'; export * from '../generated-wrappers/i_exchange_core';
export * from '../generated-wrappers/i_match_orders'; export * from '../generated-wrappers/i_match_orders';
export * from '../generated-wrappers/i_order_validator';
export * from '../generated-wrappers/i_signature_validator'; export * from '../generated-wrappers/i_signature_validator';
export * from '../generated-wrappers/i_transactions'; export * from '../generated-wrappers/i_transactions';
export * from '../generated-wrappers/i_validator'; export * from '../generated-wrappers/i_validator';

View File

@ -42,6 +42,7 @@ describe('MixinSignatureValidator', () => {
let maliciousValidator: TestStaticCallReceiverContract; let maliciousValidator: TestStaticCallReceiverContract;
let revertingWallet: TestRevertReceiverContract; let revertingWallet: TestRevertReceiverContract;
let revertingValidator: TestRevertReceiverContract; let revertingValidator: TestRevertReceiverContract;
let externalRevertReason: string;
let signerAddress: string; let signerAddress: string;
let signerPrivateKey: Buffer; let signerPrivateKey: Buffer;
let notSignerAddress: string; let notSignerAddress: string;
@ -88,33 +89,23 @@ describe('MixinSignatureValidator', () => {
provider, provider,
txDefaults, txDefaults,
); );
externalRevertReason = await revertingWallet.REVERT_REASON.callAsync();
signatureValidatorLogDecoder = new LogDecoder(web3Wrapper, artifacts); signatureValidatorLogDecoder = new LogDecoder(web3Wrapper, artifacts);
await web3Wrapper.awaitTransactionSuccessAsync( const approveValidator = async (validatorAddress: string) => {
await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, { type SendApproveTx = (address: string, approved: boolean, txData: { from: string }) => Promise<string>;
from: signerAddress, const sendTx = async (fn: { sendTransactionAsync: SendApproveTx }) => {
}), return web3Wrapper.awaitTransactionSuccessAsync(
constants.AWAIT_TRANSACTION_MINED_MS, await fn.sendTransactionAsync(validatorAddress, true, { from: signerAddress }),
); constants.AWAIT_TRANSACTION_MINED_MS,
await web3Wrapper.awaitTransactionSuccessAsync( );
await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( };
maliciousValidator.address, await sendTx(signatureValidator.setSignatureValidatorApproval);
true, await sendTx(signatureValidator.setOrderValidatorApproval);
{ };
from: signerAddress, await approveValidator(testValidator.address);
}, await approveValidator(maliciousValidator.address);
), await approveValidator(revertingValidator.address);
constants.AWAIT_TRANSACTION_MINED_MS,
);
await web3Wrapper.awaitTransactionSuccessAsync(
await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(
revertingValidator.address,
true,
{
from: signerAddress,
},
),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const defaultOrderParams = { const defaultOrderParams = {
...constants.STATIC_ORDER_PARAMS, ...constants.STATIC_ORDER_PARAMS,
@ -139,13 +130,9 @@ describe('MixinSignatureValidator', () => {
await blockchainLifecycle.revertAsync(); await blockchainLifecycle.revertAsync();
}); });
describe('isValidSignature', () => { type ValidateCallAsync = (order: SignedOrder, signerAddress: string, ignatureHex: string) => Promise<any>;
const REVERT_REASON = 'you shall not pass';
beforeEach(async () => {
signedOrder = await orderFactory.newSignedOrderAsync();
});
const createHashSignatureTests = (validateCallAsync: ValidateCallAsync) => {
it('should revert when signature is empty', async () => { it('should revert when signature is empty', async () => {
const emptySignature = constants.NULL_BYTES; const emptySignature = constants.NULL_BYTES;
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
@ -155,11 +142,7 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress, signedOrder.makerAddress,
emptySignature, emptySignature,
); );
const tx = signatureValidator.publicIsValidSignature.callAsync( const tx = validateCallAsync(signedOrder, signedOrder.makerAddress, emptySignature);
orderHashHex,
signedOrder.makerAddress,
emptySignature,
);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
@ -173,11 +156,7 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress, signedOrder.makerAddress,
unsupportedSignatureHex, unsupportedSignatureHex,
); );
const tx = signatureValidator.publicIsValidSignature.callAsync( const tx = validateCallAsync(signedOrder, signedOrder.makerAddress, unsupportedSignatureHex);
orderHashHex,
signedOrder.makerAddress,
unsupportedSignatureHex,
);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
@ -190,22 +169,13 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress, signedOrder.makerAddress,
illegalSignatureHex, illegalSignatureHex,
); );
const tx = signatureValidator.publicIsValidSignature.callAsync( const tx = validateCallAsync(signedOrder, signedOrder.makerAddress, illegalSignatureHex);
orderHashHex,
signedOrder.makerAddress,
illegalSignatureHex,
);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
it('should return false when SignatureType=Invalid and signature has a length of zero', async () => { it('should return false when SignatureType=Invalid and signature has a length of zero', async () => {
const signatureHex = `0x${Buffer.from([SignatureType.Invalid]).toString('hex')}`; const signatureHex = `0x${Buffer.from([SignatureType.Invalid]).toString('hex')}`;
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await validateCallAsync(signedOrder, signedOrder.makerAddress, signatureHex);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
signedOrder.makerAddress,
signatureHex,
);
expect(isValidSignature).to.be.false(); expect(isValidSignature).to.be.false();
}); });
@ -221,11 +191,7 @@ describe('MixinSignatureValidator', () => {
signedOrder.makerAddress, signedOrder.makerAddress,
signatureHex, signatureHex,
); );
const tx = signatureValidator.publicIsValidSignature.callAsync( const tx = validateCallAsync(signedOrder, signedOrder.makerAddress, signatureHex);
orderHashHex,
signedOrder.makerAddress,
signatureHex,
);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
@ -243,11 +209,7 @@ describe('MixinSignatureValidator', () => {
]); ]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature // Validate signature
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await validateCallAsync(signedOrder, signerAddress, signatureHex);
orderHashHex,
signerAddress,
signatureHex,
);
expect(isValidSignature).to.be.true(); expect(isValidSignature).to.be.true();
}); });
@ -266,11 +228,7 @@ describe('MixinSignatureValidator', () => {
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature. // Validate signature.
// This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress`
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await validateCallAsync(signedOrder, notSignerAddress, signatureHex);
orderHashHex,
notSignerAddress,
signatureHex,
);
expect(isValidSignature).to.be.false(); expect(isValidSignature).to.be.false();
}); });
@ -289,11 +247,7 @@ describe('MixinSignatureValidator', () => {
]); ]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature // Validate signature
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await validateCallAsync(signedOrder, signerAddress, signatureHex);
orderHashHex,
signerAddress,
signatureHex,
);
expect(isValidSignature).to.be.true(); expect(isValidSignature).to.be.true();
}); });
@ -313,11 +267,7 @@ describe('MixinSignatureValidator', () => {
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature. // Validate signature.
// This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress`
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await validateCallAsync(signedOrder, notSignerAddress, signatureHex);
orderHashHex,
notSignerAddress,
signatureHex,
);
expect(isValidSignature).to.be.false(); expect(isValidSignature).to.be.false();
}); });
@ -335,11 +285,7 @@ describe('MixinSignatureValidator', () => {
]); ]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature // Validate signature
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await validateCallAsync(signedOrder, testWallet.address, signatureHex);
orderHashHex,
testWallet.address,
signatureHex,
);
expect(isValidSignature).to.be.true(); expect(isValidSignature).to.be.true();
}); });
@ -358,11 +304,7 @@ describe('MixinSignatureValidator', () => {
]); ]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature // Validate signature
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await validateCallAsync(signedOrder, testWallet.address, signatureHex);
orderHashHex,
testWallet.address,
signatureHex,
);
expect(isValidSignature).to.be.false(); expect(isValidSignature).to.be.false();
}); });
@ -385,11 +327,7 @@ describe('MixinSignatureValidator', () => {
signatureHex, signatureHex,
constants.NULL_BYTES, constants.NULL_BYTES,
); );
const tx = signatureValidator.publicIsValidSignature.callAsync( const tx = validateCallAsync(signedOrder, maliciousWallet.address, signatureHex);
orderHashHex,
maliciousWallet.address,
signatureHex,
);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
@ -410,13 +348,9 @@ describe('MixinSignatureValidator', () => {
orderHashHex, orderHashHex,
revertingWallet.address, revertingWallet.address,
signatureHex, signatureHex,
new StringRevertError(REVERT_REASON).encode(), new StringRevertError(externalRevertReason).encode(),
);
const tx = signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
revertingWallet.address,
signatureHex,
); );
const tx = validateCallAsync(signedOrder, revertingWallet.address, signatureHex);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
@ -425,12 +359,7 @@ describe('MixinSignatureValidator', () => {
const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`);
const signature = Buffer.concat([validatorAddress, signatureType]); const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await validateCallAsync(signedOrder, signerAddress, signatureHex);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
signerAddress,
signatureHex,
);
expect(isValidSignature).to.be.true(); expect(isValidSignature).to.be.true();
}); });
@ -439,14 +368,9 @@ describe('MixinSignatureValidator', () => {
const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`);
const signature = Buffer.concat([validatorAddress, signatureType]); const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); // This will return false because the validator requires `signerAddress`
// This will return false because we signed the message with `signerAddress`, but // to be the signer.
// are validating against `notSignerAddress` const isValidSignature = await validateCallAsync(signedOrder, notSignerAddress, signatureHex);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
notSignerAddress,
signatureHex,
);
expect(isValidSignature).to.be.false(); expect(isValidSignature).to.be.false();
}); });
@ -462,7 +386,7 @@ describe('MixinSignatureValidator', () => {
signatureHex, signatureHex,
constants.NULL_BYTES, constants.NULL_BYTES,
); );
const tx = signatureValidator.publicIsValidSignature.callAsync(orderHashHex, signerAddress, signatureHex); const tx = validateCallAsync(signedOrder, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
@ -476,9 +400,9 @@ describe('MixinSignatureValidator', () => {
orderHashHex, orderHashHex,
signedOrder.makerAddress, signedOrder.makerAddress,
signatureHex, signatureHex,
new StringRevertError(REVERT_REASON).encode(), new StringRevertError(externalRevertReason).encode(),
); );
const tx = signatureValidator.publicIsValidSignature.callAsync(orderHashHex, signerAddress, signatureHex); const tx = validateCallAsync(signedOrder, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError); return expect(tx).to.revertWith(expectedError);
}); });
@ -497,12 +421,7 @@ describe('MixinSignatureValidator', () => {
const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`);
const signature = Buffer.concat([validatorAddress, signatureType]); const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await validateCallAsync(signedOrder, signerAddress, signatureHex);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
signerAddress,
signatureHex,
);
expect(isValidSignature).to.be.false(); expect(isValidSignature).to.be.false();
}); });
@ -520,24 +439,39 @@ describe('MixinSignatureValidator', () => {
// Validate presigned signature // Validate presigned signature
const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await validateCallAsync(signedOrder, signedOrder.makerAddress, signatureHex);
orderHashHex,
signedOrder.makerAddress,
signatureHex,
);
expect(isValidSignature).to.be.true(); expect(isValidSignature).to.be.true();
}); });
it('should return false when SignatureType=Presigned and signer has not presigned hash', async () => { it('should return false when SignatureType=Presigned and signer has not presigned hash', async () => {
const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await validateCallAsync(signedOrder, signedOrder.makerAddress, signatureHex);
expect(isValidSignature).to.be.false();
});
};
describe('isValidHashSignature', () => {
const validateCallAsync = async (order: SignedOrder, signer: string, signatureHex: string) => {
const orderHashHex = orderHashUtils.getOrderHashHex(order);
return signatureValidator.isValidHashSignature.callAsync(orderHashHex, signer, signatureHex);
};
beforeEach(async () => {
signedOrder = await orderFactory.newSignedOrderAsync();
});
it('should revert when SignatureType=OrderValidator', async () => {
const inappropriateSignatureHex = `0x${Buffer.from([SignatureType.OrderValidator]).toString('hex')}`;
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const expectedError = new ExchangeRevertErrors.SignatureError(
ExchangeRevertErrors.SignatureErrorCode.InappropriateSignatureType,
orderHashHex, orderHashHex,
signedOrder.makerAddress, signedOrder.makerAddress,
signatureHex, inappropriateSignatureHex,
); );
expect(isValidSignature).to.be.false(); const tx = validateCallAsync(signedOrder, signerAddress, inappropriateSignatureHex);
return expect(tx).to.revertWith(expectedError);
}); });
it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => { it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => {
@ -550,7 +484,7 @@ describe('MixinSignatureValidator', () => {
const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`);
const signature = Buffer.concat([v, r, s, trezorSignatureType]); const signature = Buffer.concat([v, r, s, trezorSignatureType]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await signatureValidator.isValidHashSignature.callAsync(
messageHash, messageHash,
signer, signer,
signatureHex, signatureHex,
@ -568,13 +502,179 @@ describe('MixinSignatureValidator', () => {
const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`);
const signature = Buffer.concat([v, r, s, trezorSignatureType]); const signature = Buffer.concat([v, r, s, trezorSignatureType]);
const signatureHex = ethUtil.bufferToHex(signature); const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( const isValidSignature = await signatureValidator.isValidHashSignature.callAsync(
messageHash, messageHash,
signer, signer,
signatureHex, signatureHex,
); );
expect(isValidSignature).to.be.true(); expect(isValidSignature).to.be.true();
}); });
createHashSignatureTests(validateCallAsync);
});
describe('isValidOrderSignature', () => {
const validateCallAsync = async (order: SignedOrder, signer: string, signatureHex: string) => {
return signatureValidator.isValidOrderSignature.callAsync(order, signer, signatureHex);
};
beforeEach(async () => {
signedOrder = await orderFactory.newSignedOrderAsync();
});
it('should return true when SignatureType=OrderValidator, signature is valid and validator is approved', async () => {
const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`);
const signatureType = ethUtil.toBuffer(`0x${SignatureType.OrderValidator}`);
const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await validateCallAsync(signedOrder, signerAddress, signatureHex);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=OrderValidator, signature is invalid and validator is approved', async () => {
const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`);
const signatureType = ethUtil.toBuffer(`0x${SignatureType.OrderValidator}`);
const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature);
// This will return false because the validator requires `signerAddress`
// to be the signer.
const isValidSignature = await validateCallAsync(signedOrder, notSignerAddress, signatureHex);
expect(isValidSignature).to.be.false();
});
it('should revert when `isValidOrderSignature` attempts to update state and SignatureType=OrderValidator', async () => {
const validatorAddress = ethUtil.toBuffer(`${maliciousValidator.address}`);
const signatureType = ethUtil.toBuffer(`0x${SignatureType.OrderValidator}`);
const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const expectedError = new ExchangeRevertErrors.SignatureOrderValidatorError(
orderHashHex,
signedOrder.makerAddress,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateCallAsync(signedOrder, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when `isValidOrderSignature` reverts and SignatureType=OrderValidator', async () => {
const validatorAddress = ethUtil.toBuffer(`${revertingValidator.address}`);
const signatureType = ethUtil.toBuffer(`0x${SignatureType.OrderValidator}`);
const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const expectedError = new ExchangeRevertErrors.SignatureOrderValidatorError(
orderHashHex,
signedOrder.makerAddress,
signatureHex,
new StringRevertError(externalRevertReason).encode(),
);
const tx = validateCallAsync(signedOrder, signerAddress, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should return false when SignatureType=OrderValidator, signature is valid and validator is not approved', async () => {
// Set approval of signature validator to false
await web3Wrapper.awaitTransactionSuccessAsync(
await signatureValidator.setOrderValidatorApproval.sendTransactionAsync(testValidator.address, false, {
from: signerAddress,
}),
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Validate signature
const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`);
const signatureType = ethUtil.toBuffer(`0x${SignatureType.OrderValidator}`);
const signature = Buffer.concat([validatorAddress, signatureType]);
const signatureHex = ethUtil.bufferToHex(signature);
const isValidSignature = await validateCallAsync(signedOrder, signerAddress, signatureHex);
expect(isValidSignature).to.be.false();
});
it('should return true when SignatureType=WalletOrderValidator and signature is valid', async () => {
// Create EIP712 signature
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey);
// Create 0x signature from EIP712 signature
const signature = Buffer.concat([
ethUtil.toBuffer(ecSignature.v),
ecSignature.r,
ecSignature.s,
ethUtil.toBuffer(`0x${SignatureType.WalletOrderValidator}`),
]);
const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature
const isValidSignature = await validateCallAsync(signedOrder, testWallet.address, signatureHex);
expect(isValidSignature).to.be.true();
});
it('should return false when SignatureType=WalletOrderValidator and signature is invalid', async () => {
// Create EIP712 signature using a private key that does not belong to the wallet owner.
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
const notWalletOwnerPrivateKey = notSignerPrivateKey;
const ecSignature = ethUtil.ecsign(orderHashBuffer, notWalletOwnerPrivateKey);
// Create 0x signature from EIP712 signature
const signature = Buffer.concat([
ethUtil.toBuffer(ecSignature.v),
ecSignature.r,
ecSignature.s,
ethUtil.toBuffer(`0x${SignatureType.WalletOrderValidator}`),
]);
const signatureHex = ethUtil.bufferToHex(signature);
// Validate signature
const isValidSignature = await validateCallAsync(signedOrder, testWallet.address, signatureHex);
expect(isValidSignature).to.be.false();
});
it('should revert when `isValidSignature` attempts to update state and SignatureType=WalletOrderValidator', async () => {
// Create EIP712 signature
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey);
// Create 0x signature from EIP712 signature
const signature = Buffer.concat([
ethUtil.toBuffer(ecSignature.v),
ecSignature.r,
ecSignature.s,
ethUtil.toBuffer(`0x${SignatureType.WalletOrderValidator}`),
]);
const signatureHex = ethUtil.bufferToHex(signature);
const expectedError = new ExchangeRevertErrors.SignatureWalletOrderValidatorError(
orderHashHex,
maliciousWallet.address,
signatureHex,
constants.NULL_BYTES,
);
const tx = validateCallAsync(signedOrder, maliciousWallet.address, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
it('should revert when `isValidSignature` reverts and SignatureType=WalletOrderValidator', async () => {
// Create EIP712 signature
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const orderHashBuffer = ethUtil.toBuffer(orderHashHex);
const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey);
// Create 0x signature from EIP712 signature
const signature = Buffer.concat([
ethUtil.toBuffer(ecSignature.v),
ecSignature.r,
ecSignature.s,
ethUtil.toBuffer(`0x${SignatureType.WalletOrderValidator}`),
]);
const signatureHex = ethUtil.bufferToHex(signature);
const expectedError = new ExchangeRevertErrors.SignatureWalletOrderValidatorError(
orderHashHex,
revertingWallet.address,
signatureHex,
new StringRevertError(externalRevertReason).encode(),
);
const tx = validateCallAsync(signedOrder, revertingWallet.address, signatureHex);
return expect(tx).to.revertWith(expectedError);
});
createHashSignatureTests(validateCallAsync);
}); });
describe('setSignatureValidatorApproval', () => { describe('setSignatureValidatorApproval', () => {

View File

@ -9,6 +9,7 @@
"generated-artifacts/IExchange.json", "generated-artifacts/IExchange.json",
"generated-artifacts/IExchangeCore.json", "generated-artifacts/IExchangeCore.json",
"generated-artifacts/IMatchOrders.json", "generated-artifacts/IMatchOrders.json",
"generated-artifacts/IOrderValidator.json",
"generated-artifacts/ISignatureValidator.json", "generated-artifacts/ISignatureValidator.json",
"generated-artifacts/ITransactions.json", "generated-artifacts/ITransactions.json",
"generated-artifacts/IValidator.json", "generated-artifacts/IValidator.json",

View File

@ -17,6 +17,14 @@
{ {
"note": "Add Exchange `RevertError` types to `ExchangeRevertErrors`", "note": "Add Exchange `RevertError` types to `ExchangeRevertErrors`",
"pr": 1761 "pr": 1761
},
{
"note": "Add `SignatureOrderValidatorError` type to `ExchangeRevertErrors`",
"pr": 1774
},
{
"note": "Add `SignatureWalletOrderValidatorError` type to `ExchangeRevertErrors`",
"pr": 1774
} }
] ]
}, },

View File

@ -16,8 +16,7 @@ export enum SignatureErrorCode {
InvalidLength, InvalidLength,
Unsupported, Unsupported,
Illegal, Illegal,
WalletError, InappropriateSignatureType,
ValidatorError,
} }
export enum AssetProxyDispatchErrorCode { export enum AssetProxyDispatchErrorCode {
@ -53,8 +52,19 @@ export class SignatureValidatorError extends RevertError {
} }
export class SignatureWalletError extends RevertError { export class SignatureWalletError extends RevertError {
constructor(hash?: string, wallet?: string, signature?: string, errorData?: string) {
super('SignatureWalletError(bytes32 hash, address wallet, bytes signature, bytes errorData)', {
hash,
wallet,
signature,
errorData,
});
}
}
export class SignatureOrderValidatorError extends RevertError {
constructor(hash?: string, signer?: string, signature?: string, errorData?: string) { constructor(hash?: string, signer?: string, signature?: string, errorData?: string) {
super('SignatureWalletError(bytes32 hash, address signer, bytes signature, bytes errorData)', { super('SignatureOrderValidatorError(bytes32 hash, address signer, bytes signature, bytes errorData)', {
hash, hash,
signer, signer,
signature, signature,
@ -63,6 +73,17 @@ export class SignatureWalletError extends RevertError {
} }
} }
export class SignatureWalletOrderValidatorError extends RevertError {
constructor(hash?: string, wallet?: string, signature?: string, errorData?: string) {
super('SignatureWalletOrderValidatorError(bytes32 hash, address wallet, bytes signature, bytes errorData)', {
hash,
wallet,
signature,
errorData,
});
}
}
export class OrderStatusError extends RevertError { export class OrderStatusError extends RevertError {
constructor(status?: OrderStatus, orderHash?: string) { constructor(status?: OrderStatus, orderHash?: string) {
super('OrderStatusError(uint8 status, bytes32 orderHash)', { status, orderHash }); super('OrderStatusError(uint8 status, bytes32 orderHash)', { status, orderHash });
@ -168,6 +189,8 @@ const types = [
SignatureError, SignatureError,
SignatureWalletError, SignatureWalletError,
SignatureValidatorError, SignatureValidatorError,
SignatureOrderValidatorError,
SignatureWalletOrderValidatorError,
InvalidSenderError, InvalidSenderError,
InvalidTakerError, InvalidTakerError,
InvalidMakerError, InvalidMakerError,

View File

@ -40,6 +40,14 @@
{ {
"note": "Add `chainId` field to `EIP712DomainWithDefaultSchema`", "note": "Add `chainId` field to `EIP712DomainWithDefaultSchema`",
"pr": 1742 "pr": 1742
},
{
"note": "Add `OrderStatus` type",
"pr": 1761
},
{
"note": "Add `SignatureType.OrderValidator` and `SignatureType.WalletOrderValidator`",
"pr": 1774
} }
] ]
}, },

View File

@ -161,6 +161,8 @@ export enum SignatureType {
Wallet, Wallet,
Validator, Validator,
PreSigned, PreSigned,
OrderValidator,
WalletOrderValidator,
NSignatureTypes, NSignatureTypes,
} }