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",
"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/IExchangeCore.sol",
"src/interfaces/IMatchOrders.sol",
"src/interfaces/IOrderValidator.sol",
"src/interfaces/ISignatureValidator.sol",
"src/interfaces/ITransactions.sol",
"src/interfaces/IValidator.sol",

View File

@ -17,15 +17,19 @@
*/
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/IOrderValidator.sol";
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
address internal VALID_SIGNER;
@ -35,12 +39,12 @@ contract Validator is
VALID_SIGNER = validSigner;
}
// solhint-disable no-unused-vars
/// @dev Verifies that a signature is valid. `signer` must match `VALID_SIGNER`.
/// @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 signature.
// solhint-disable no-unused-vars
function isValidSignature(
bytes32 hash,
address signerAddress,
@ -53,4 +57,23 @@ contract Validator is
return (signerAddress == VALID_SIGNER);
}
// 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 experimental ABIEncoderV2;
import "../src/interfaces/IWallet.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract Wallet is
@ -40,26 +42,59 @@ contract Wallet is
/// @dev Validates an EIP712 signature.
/// The signer must match the owner of this wallet.
/// @param hash Message hash that is signed.
/// @param eip712Signature Proof of signing.
/// @param signature Proof of signing.
/// @return Validity of signature.
function isValidSignature(
bytes32 hash,
bytes calldata eip712Signature
bytes calldata signature
)
external
view
returns (bool isValid)
{
require(
eip712Signature.length == 65,
signature.length == 65,
"LENGTH_65_REQUIRED"
);
uint8 v = uint8(eip712Signature[0]);
bytes32 r = eip712Signature.readBytes32(1);
bytes32 s = eip712Signature.readBytes32(33);
return validateEIP712Signature(hash, signature);
}
/// @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);
isValid = WALLET_OWNER == recoveredAddress;
return isValid;
return WALLET_OWNER == recoveredAddress;
}
}

View File

@ -353,7 +353,8 @@ contract MixinExchangeCore is
// Validate Maker signature (check only if first time seen)
if (orderInfo.orderTakerAssetFilledAmount == 0) {
address makerAddress = order.makerAddress;
if (!isValidSignature(
if (!isValidOrderWithHashSignature(
order,
orderInfo.orderHash,
makerAddress,
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(
LibOrder.OrderStatus orderStatus,
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/ReentrancyGuard.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol";
import "./mixins/MExchangeRichErrors.sol";
import "./interfaces/IWallet.sol";
import "./interfaces/IValidator.sol";
import "./interfaces/IOrderValidator.sol";
contract MixinSignatureValidator is
ReentrancyGuard,
LibOrder,
MSignatureValidator,
MTransactions,
MExchangeRichErrors
@ -42,6 +45,9 @@ 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 using any valid signature type.
/// After presigning a hash, the preSign signature type will become valid for that hash and signer.
/// @param signerAddress Address that should have signed the given hash.
@ -54,7 +60,7 @@ contract MixinSignatureValidator is
external
{
if (signerAddress != msg.sender) {
if (!isValidSignature(
if (!isValidHashSignature(
hash,
signerAddress,
signature)) {
@ -69,7 +75,8 @@ contract MixinSignatureValidator is
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 approval Approval or disapproval of Validator contract.
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.
/// @param hash Any 32 byte hash.
/// @param hash Any 32-byte hash.
/// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if the address recovered from the provided signature matches the input signer address.
function isValidSignature(
/// @return True if the signature is valid for the given hash and signer.
function isValidHashSignature(
bytes32 hash,
address signerAddress,
bytes memory signature
@ -101,6 +151,90 @@ contract MixinSignatureValidator is
public
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.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) {
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.
// This is always an implicit option since a signer can create a
// signature array with invalid type or length. We may as well make
// it an explicit option. This aids testing and analysis. It is
// also the initialization value for the enum type.
if (signatureType == SignatureType.Illegal) {
if (SignatureType(signatureTypeRaw) == SignatureType.Illegal) {
rrevert(SignatureError(
SignatureErrorCodes.ILLEGAL,
hash,
signerAddress,
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
// 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
));
return SignatureType(signatureTypeRaw);
}
/// @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
/// and defines its own signature verification method.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if signature is valid for given wallet..
function isValidWalletSignature(
/// @return True if the signature is validated by the Walidator.
function validateHashWithWallet(
bytes32 hash,
address walletAddress,
bytes memory signature
@ -292,11 +321,12 @@ 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.
/// @param signerAddress Address that should have signed the given hash.
/// @param signature Proof that the hash has been signed by signer.
/// @return True if the address recovered from the provided signature matches the input signer address.
function isValidValidatorSignature(
/// @return True if the signature is validated by the Validator.
function validateHashWithValidator(
bytes32 hash,
address signerAddress,
bytes memory signature
@ -348,4 +378,214 @@ contract MixinSignatureValidator is
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;
if (signerAddress != msg.sender) {
// Validate signature
if (!isValidSignature(
if (!isValidHashSignature(
transactionHash,
signerAddress,
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 experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract ISignatureValidator {
@ -41,12 +44,12 @@ contract ISignatureValidator {
)
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 signerAddress Address of signer.
/// @param signature Proof of signing.
/// @return Validity of order signature.
function isValidSignature(
function isValidHashSignature(
bytes32 hash,
address signerAddress,
bytes memory signature
@ -54,4 +57,18 @@ contract ISignatureValidator {
public
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,
address signerAddress,
bytes memory signature
)
public
view
returns (bool isValid);
}

View File

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

View File

@ -17,6 +17,9 @@
*/
pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
contract IWallet {
@ -32,4 +35,18 @@ contract IWallet {
external
view
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,
INVALID_LENGTH,
UNSUPPORTED,
ILLEGAL
ILLEGAL,
INAPPROPRIATE_SIGNATURE_TYPE
}
enum AssetProxyDispatchErrorCodes {
@ -89,6 +90,32 @@ contract MExchangeRichErrors is
pure
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(keccak256("OrderStatusError(uint8,bytes32)"));

View File

@ -19,6 +19,7 @@
pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "../interfaces/ISignatureValidator.sol";
@ -40,6 +41,25 @@ contract MSignatureValidator is
Wallet, // 0x04
Validator, // 0x05
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 experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
@ -55,4 +57,20 @@ contract TestRevertReceiver {
{
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
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 experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
@ -58,6 +60,23 @@ contract TestStaticCallReceiver {
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.
/// @param token Address of ERC20 token.
/// @param spender Address that will spend tokens.

View File

@ -34,7 +34,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"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."
},
"repository": {

View File

@ -11,6 +11,7 @@ import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispat
import * as IExchange from '../generated-artifacts/IExchange.json';
import * as IExchangeCore from '../generated-artifacts/IExchangeCore.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 ITransactions from '../generated-artifacts/ITransactions.json';
import * as IValidator from '../generated-artifacts/IValidator.json';
@ -38,12 +39,13 @@ export const artifacts = {
ISignatureValidator: ISignatureValidator as ContractArtifact,
ITransactions: ITransactions as ContractArtifact,
IValidator: IValidator as ContractArtifact,
IOrderValidator: IOrderValidator as ContractArtifact,
IWallet: IWallet as ContractArtifact,
IWrapperFunctions: IWrapperFunctions as ContractArtifact,
ReentrantERC20Token: ReentrantERC20Token as ContractArtifact,
TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact,
TestExchangeInternals: TestExchangeInternals as ContractArtifact,
TestRevertReceiver: TestRevertReceiver as ContractArtifact,
TestSignatureValidator: TestSignatureValidator 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_core';
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_transactions';
export * from '../generated-wrappers/i_validator';

View File

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

View File

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

View File

@ -17,6 +17,14 @@
{
"note": "Add Exchange `RevertError` types to `ExchangeRevertErrors`",
"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,
Unsupported,
Illegal,
WalletError,
ValidatorError,
InappropriateSignatureType,
}
export enum AssetProxyDispatchErrorCode {
@ -53,8 +52,19 @@ export class SignatureValidatorError 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) {
super('SignatureWalletError(bytes32 hash, address signer, bytes signature, bytes errorData)', {
super('SignatureOrderValidatorError(bytes32 hash, address signer, bytes signature, bytes errorData)', {
hash,
signer,
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 {
constructor(status?: OrderStatus, orderHash?: string) {
super('OrderStatusError(uint8 status, bytes32 orderHash)', { status, orderHash });
@ -168,6 +189,8 @@ const types = [
SignatureError,
SignatureWalletError,
SignatureValidatorError,
SignatureOrderValidatorError,
SignatureWalletOrderValidatorError,
InvalidSenderError,
InvalidTakerError,
InvalidMakerError,

View File

@ -40,6 +40,14 @@
{
"note": "Add `chainId` field to `EIP712DomainWithDefaultSchema`",
"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,
Validator,
PreSigned,
OrderValidator,
WalletOrderValidator,
NSignatureTypes,
}