import { DevUtilsContract } from '@0x/contracts-dev-utils'; import { blockchainTests, constants, expect, hexConcat, hexRandom, LogDecoder, OrderFactory, orderUtils, randomAddress, TransactionFactory, } from '@0x/contracts-test-utils'; import { ExchangeRevertErrors, orderHashUtils, transactionHashUtils } from '@0x/order-utils'; import { SignatureType, SignedOrder, SignedZeroExTransaction } from '@0x/types'; import { BigNumber, StringRevertError } from '@0x/utils'; import { LogWithDecodedArgs } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); import { artifacts } from './artifacts'; import { IEIP1271DataContract, TestSignatureValidatorContract, TestSignatureValidatorSignatureValidatorApprovalEventArgs, TestValidatorWalletContract, } from './wrappers'; import { ValidatorWalletAction } from './utils/constants'; // tslint:disable:no-unnecessary-type-assertion blockchainTests.resets('MixinSignatureValidator', env => { let chainId: number; let signatureValidator: TestSignatureValidatorContract; let validatorWallet: TestValidatorWalletContract; let validatorWalletRevertReason: string; let signerAddress: string; let signerPrivateKey: Buffer; let notSignerAddress: string; const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, env.provider, env.txDefaults); const eip1271Data = new IEIP1271DataContract(constants.NULL_ADDRESS, env.provider, env.txDefaults); before(async () => { chainId = await env.getChainIdAsync(); const accounts = await env.getAccountAddressesAsync(); signerAddress = accounts[0]; notSignerAddress = accounts[1]; signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync( artifacts.TestSignatureValidator, env.provider, env.txDefaults, {}, new BigNumber(chainId), ); validatorWallet = await TestValidatorWalletContract.deployFrom0xArtifactAsync( artifacts.TestValidatorWallet, env.provider, env.txDefaults, {}, signatureValidator.address, ); validatorWalletRevertReason = await validatorWallet.REVERT_REASON().callAsync(); // Approve the validator for both signers. await Promise.all( [signerAddress, notSignerAddress].map(async (addr: string) => { return signatureValidator .setSignatureValidatorApproval(validatorWallet.address, true) .awaitTransactionSuccessAsync({ from: addr }); }), ); signerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(signerAddress)]; }); const SIGNATURE_LENGTH = 65; const generateRandomSignature = (): string => hexRandom(SIGNATURE_LENGTH); const hashBytes = (bytesHex: string): string => ethUtil.bufferToHex(ethUtil.sha3(ethUtil.toBuffer(bytesHex))); const signDataHex = (dataHex: string, privateKey: Buffer): string => { const ecSignature = ethUtil.ecsign(ethUtil.toBuffer(dataHex), privateKey); return hexConcat(ecSignature.v, ecSignature.r, ecSignature.s); }; type ValidateHashSignatureAsync = ( hashHex: string, signerAddress: string, signatureHex: string, validatorAction?: ValidatorWalletAction, validatorExpectedSignatureHex?: string, ) => Promise; const createHashSignatureTests = ( getCurrentHashHex: (signerAddress?: string) => string, validateAsync: ValidateHashSignatureAsync, ) => { it('should revert when signature is empty', async () => { const hashHex = getCurrentHashHex(); const emptySignature = constants.NULL_BYTES; const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidLength, hashHex, signerAddress, emptySignature, ); const tx = validateAsync(hashHex, signerAddress, emptySignature); return expect(tx).to.revertWith(expectedError); }); it('should revert when signature type is unsupported', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(SignatureType.NSignatureTypes); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.Unsupported, hashHex, signerAddress, signatureHex, ); const tx = validateAsync(hashHex, signerAddress, signatureHex); return expect(tx).to.revertWith(expectedError); }); it('should revert when SignatureType=Illegal', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(SignatureType.Illegal); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.Illegal, hashHex, signerAddress, signatureHex, ); const tx = validateAsync(hashHex, signerAddress, signatureHex); return expect(tx).to.revertWith(expectedError); }); it('should return false when SignatureType=Invalid and signature has a length of zero', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(SignatureType.Invalid); const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex); expect(isValidSignature).to.be.false(); }); it('should revert when SignatureType=Invalid and signature length is non-zero', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat('0xdeadbeef', SignatureType.Invalid); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidLength, hashHex, signerAddress, signatureHex, ); const tx = validateAsync(hashHex, signerAddress, signatureHex); return expect(tx).to.revertWith(expectedError); }); it('should return true when SignatureType=EIP712 and signature is valid', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(signDataHex(hashHex, signerPrivateKey), SignatureType.EIP712); const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=EIP712 and signature is invalid', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP712); const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=EthSign and signature is valid', async () => { // Create EthSign signature const hashHex = getCurrentHashHex(); const orderHashWithEthSignPrefixHex = ethUtil.bufferToHex( ethUtil.hashPersonalMessage(ethUtil.toBuffer(hashHex)), ); const signatureHex = hexConcat( signDataHex(orderHashWithEthSignPrefixHex, signerPrivateKey), SignatureType.EthSign, ); const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=EthSign and signature is invalid', async () => { const hashHex = getCurrentHashHex(); // Create EthSign signature const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EthSign); const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=Wallet and signature is valid', async () => { const hashHex = getCurrentHashHex(validatorWallet.address); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, SignatureType.Wallet); const isValidSignature = await validateAsync( hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Wallet and signature is invalid', async () => { const hashHex = getCurrentHashHex(validatorWallet.address); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const notSignatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(notSignatureDataHex, SignatureType.Wallet); // Validate signature const isValidSignature = await validateAsync( hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should revert when validator attempts to update state and SignatureType=Wallet', async () => { const hashHex = getCurrentHashHex(validatorWallet.address); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureHex = hexConcat(generateRandomSignature(), SignatureType.Wallet); const expectedError = new ExchangeRevertErrors.SignatureWalletError( hashHex, validatorWallet.address, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.UpdateState); return expect(tx).to.revertWith(expectedError); }); it('should revert when signer is an EOA and SignatureType=Wallet', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(SignatureType.Wallet); const expectedError = new ExchangeRevertErrors.SignatureWalletError( hashHex, signerAddress, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(hashHex, signerAddress, signatureHex); return expect(tx).to.revertWith(expectedError); }); it('should return false when validator returns `true` and SignatureType=Wallet', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(SignatureType.Wallet); const isValidSignature = await validateAsync( hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.ReturnTrue, ); expect(isValidSignature).to.be.false(); }); it('should revert when validator returns nothing and SignatureType=Wallet', async () => { const hashHex = getCurrentHashHex(validatorWallet.address); const signatureHex = hexConcat(SignatureType.Wallet); const expectedError = new ExchangeRevertErrors.SignatureWalletError( hashHex, validatorWallet.address, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync( hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.ReturnNothing, ); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator reverts and SignatureType=Wallet', async () => { const hashHex = getCurrentHashHex(validatorWallet.address); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureHex = hexConcat(generateRandomSignature(), SignatureType.Wallet); const expectedError = new ExchangeRevertErrors.SignatureWalletError( hashHex, validatorWallet.address, signatureHex, new StringRevertError(validatorWalletRevertReason).encode(), ); const tx = validateAsync(hashHex, validatorWallet.address, signatureHex, ValidatorWalletAction.Revert); return expect(tx).to.revertWith(expectedError); }); it('should return true when SignatureType=Presigned and signer has presigned hash', async () => { const hashHex = getCurrentHashHex(); // Presign the hash await signatureValidator.preSign(hashHex).awaitTransactionSuccessAsync({ from: signerAddress }); // Validate presigned signature const signatureHex = hexConcat(SignatureType.PreSigned); const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Presigned and signer has not presigned hash', async () => { const hashHex = getCurrentHashHex(); const signatureHex = hexConcat(SignatureType.PreSigned); const isValidSignature = await validateAsync(hashHex, signerAddress, signatureHex); expect(isValidSignature).to.be.false(); }); }; describe('isValidHashSignature', () => { let hashHex: string; beforeEach(async () => { hashHex = orderUtils.generatePseudoRandomOrderHash(); }); const validateAsync = async ( _hashHex: string, _signerAddress: string, signatureHex: string, validatorAction?: ValidatorWalletAction, validatorExpectedSignatureHex?: string, ) => { const expectedSignatureHashHex = validatorExpectedSignatureHex === undefined ? constants.NULL_BYTES : hashBytes(validatorExpectedSignatureHex); if (validatorAction !== undefined) { await validatorWallet .prepare(_hashHex, validatorAction, expectedSignatureHashHex) .awaitTransactionSuccessAsync(); } return signatureValidator.isValidHashSignature(_hashHex, _signerAddress, signatureHex).callAsync(); }; it('should revert when signerAddress == 0', async () => { const signatureHex = hexConcat(SignatureType.EIP712); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidSigner, hashHex, constants.NULL_ADDRESS, signatureHex, ); const tx = validateAsync(hashHex, constants.NULL_ADDRESS, signatureHex); return expect(tx).to.revertWith(expectedError); }); it('should revert when SignatureType=Validator', async () => { const signatureHex = hexConcat(SignatureType.Validator); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InappropriateSignatureType, hashHex, signerAddress, signatureHex, ); const tx = validateAsync(hashHex, signerAddress, signatureHex); return expect(tx).to.revertWith(expectedError); }); it('should revert when SignatureType=EIP1271Wallet', async () => { const signatureHex = hexConcat(SignatureType.EIP1271Wallet); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InappropriateSignatureType, hashHex, signerAddress, signatureHex, ); const tx = validateAsync(hashHex, signerAddress, signatureHex); return expect(tx).to.revertWith(expectedError); }); it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => { // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); const signer = '0xc28b145f10f0bcf0fc000e778615f8fd73490bad'; const v = ethUtil.toBuffer('0x1c'); const r = ethUtil.toBuffer('0x7b888b596ccf87f0bacab0dcb483124973f7420f169b4824d7a12534ac1e9832'); const s = ethUtil.toBuffer('0x0c8e14f7edc01459e13965f1da56e0c23ed11e2cca932571eee1292178f90424'); const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); const signature = Buffer.concat([v, r, s, trezorSignatureType]); const signatureHex = ethUtil.bufferToHex(signature); const isValidSignature = await signatureValidator .isValidHashSignature(messageHash, signer, signatureHex) .callAsync(); expect(isValidSignature).to.be.true(); }); it('should return true when message was signed by a Trezor Model T (firmware version 2.0.7)', async () => { // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); const signer = '0x98ce6d9345e8ffa7d99ee0822272fae9d2c0e895'; const v = ethUtil.toBuffer('0x1c'); const r = ethUtil.toBuffer('0x423b71062c327f0ec4fe199b8da0f34185e59b4c1cb4cc23df86cac4a601fb3f'); const s = ethUtil.toBuffer('0x53810d6591b5348b7ee08ee812c874b0fdfb942c9849d59512c90e295221091f'); const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); const signature = Buffer.concat([v, r, s, trezorSignatureType]); const signatureHex = ethUtil.bufferToHex(signature); const isValidSignature = await signatureValidator .isValidHashSignature(messageHash, signer, signatureHex) .callAsync(); expect(isValidSignature).to.be.true(); }); createHashSignatureTests((_signerAddress?: string) => hashHex, validateAsync); }); describe('isValidOrderSignature', () => { let orderFactory: OrderFactory; let signedOrder: SignedOrder; before(async () => { const makerAddress = signerAddress; const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, makerAddress, feeRecipientAddress: randomAddress(), makerAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(), takerAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(), makerFeeAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(), takerFeeAssetData: await devUtils.encodeERC20AssetData(randomAddress()).callAsync(), makerFee: constants.ZERO_AMOUNT, takerFee: constants.ZERO_AMOUNT, exchangeAddress: signatureValidator.address, chainId, }; orderFactory = new OrderFactory(signerPrivateKey, defaultOrderParams); }); beforeEach(async () => { signedOrder = await orderFactory.newSignedOrderAsync(); }); const validateAsync = async ( order: SignedOrder, signatureHex: string, validatorAction?: ValidatorWalletAction, validatorExpectedSignatureHex?: string, ) => { const orderHashHex = orderHashUtils.getOrderHashHex(order); const expectedSignatureHashHex = validatorExpectedSignatureHex === undefined ? constants.NULL_BYTES : hashBytes(validatorExpectedSignatureHex); if (validatorAction !== undefined) { await validatorWallet .prepare(orderHashHex, validatorAction, expectedSignatureHashHex) .awaitTransactionSuccessAsync(); } return signatureValidator.isValidOrderSignature(order, signatureHex).callAsync(); }; it('should revert when signerAddress == 0', async () => { const signatureHex = hexConcat(SignatureType.EIP712); const nullMakerOrder = { ...signedOrder, makerAddress: constants.NULL_ADDRESS, }; const orderHashHex = orderHashUtils.getOrderHashHex(nullMakerOrder); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidSigner, orderHashHex, constants.NULL_ADDRESS, signatureHex, ); const tx = signatureValidator.isValidOrderSignature(nullMakerOrder, signatureHex).callAsync(); return expect(tx).to.revertWith(expectedError); }); it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const isValidSignature = await validateAsync( signedOrder, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const notSignatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(notSignatureDataHex, validatorWallet.address, SignatureType.Validator); const isValidSignature = await validateAsync( signedOrder, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should return false when validator returns `true` and SignatureType=Validator', async () => { const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const isValidSignature = await validateAsync( signedOrder, signatureHex, ValidatorWalletAction.ReturnTrue, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should revert when validator returns nothing and SignatureType=Validator', async () => { const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.ReturnNothing, signatureDataHex); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator attempts to update state and SignatureType=Validator', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.UpdateState); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator reverts and SignatureType=Validator', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, new StringRevertError(validatorWalletRevertReason).encode(), ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert); return expect(tx).to.revertWith(expectedError); }); it('should revert when SignatureType=Validator and signature is shorter than 21 bytes', async () => { // Set approval of signature validator to false await signatureValidator .setSignatureValidatorApproval(validatorWallet.address, false) .awaitTransactionSuccessAsync({ from: signedOrder.makerAddress }); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureHex = hexConcat(SignatureType.Validator); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidLength, orderHashHex, signedOrder.makerAddress, signatureHex, ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.MatchSignatureHash); return expect(tx).to.revertWith(expectedError); }); it('should revert when SignatureType=Validator, signature is valid and validator is not approved', async () => { // Set approval of signature validator to false await signatureValidator .setSignatureValidatorApproval(validatorWallet.address, false) .awaitTransactionSuccessAsync({ from: signedOrder.makerAddress }); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const expectedError = new ExchangeRevertErrors.SignatureValidatorNotApprovedError( signedOrder.makerAddress, validatorWallet.address, ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert); return expect(tx).to.revertWith(expectedError); }); it('should return true when SignatureType=EIP1271Wallet and signature is valid', async () => { signedOrder.makerAddress = validatorWallet.address; // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet); // Validate signature const isValidSignature = await validateAsync( signedOrder, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=EIP1271Wallet and signature is invalid', async () => { signedOrder.makerAddress = validatorWallet.address; // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const notSignatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(notSignatureDataHex, SignatureType.EIP1271Wallet); // Validate signature const isValidSignature = await validateAsync( signedOrder, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should return false when validator returns `true` and SignatureType=EIP1271Wallet', async () => { signedOrder.makerAddress = validatorWallet.address; const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet); // Validate signature const isValidSignature = await validateAsync( signedOrder, signatureHex, ValidatorWalletAction.ReturnTrue, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should revert when validator returns nothing and SignatureType=EIP1271Wallet', async () => { signedOrder.makerAddress = validatorWallet.address; const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.ReturnNothing, signatureDataHex); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator attempts to update state and SignatureType=EIP1271Wallet', async () => { signedOrder.makerAddress = validatorWallet.address; // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP1271Wallet); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.UpdateState); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator reverts and SignatureType=EIP1271Wallet', async () => { signedOrder.makerAddress = validatorWallet.address; const signatureHex = hexConcat(SignatureType.EIP1271Wallet); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, new StringRevertError(validatorWalletRevertReason).encode(), ); const tx = validateAsync(signedOrder, signatureHex, ValidatorWalletAction.Revert); return expect(tx).to.revertWith(expectedError); }); it('should revert when signer is an EOA and SignatureType=EIP1271Wallet', async () => { const signatureHex = hexConcat(SignatureType.EIP1271Wallet); signedOrder.makerAddress = notSignerAddress; const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( notSignerAddress, data, signatureHex, constants.NULL_BYTES, ); const tx = signatureValidator.isValidOrderSignature(signedOrder, signatureHex).callAsync(); return expect(tx).to.revertWith(expectedError); }); it('should revert when signer is an EOA and SignatureType=Validator', async () => { const signatureHex = hexConcat(notSignerAddress, SignatureType.Validator); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const data = eip1271Data.OrderWithHash(signedOrder, orderHashHex).getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( notSignerAddress, data, signatureHex, constants.NULL_BYTES, ); // Register an EOA as a validator. await signatureValidator .setSignatureValidatorApproval(notSignerAddress, true) .awaitTransactionSuccessAsync({ from: signerAddress }); const tx = signatureValidator.isValidOrderSignature(signedOrder, signatureHex).callAsync(); return expect(tx).to.revertWith(expectedError); }); // Run hash-only signature type tests as well. const validateOrderHashAsync = async ( hashHex: string, _signerAddress: string, signatureHex: string, validatorAction?: ValidatorWalletAction, validatorExpectedSignatureHex?: string, ): Promise => { signedOrder.makerAddress = _signerAddress; return validateAsync(signedOrder, signatureHex, validatorAction, validatorExpectedSignatureHex); }; createHashSignatureTests((_signerAddress?: string) => { signedOrder.makerAddress = _signerAddress === undefined ? signerAddress : _signerAddress; return orderHashUtils.getOrderHashHex(signedOrder); }, validateOrderHashAsync); }); describe('isValidTransactionSignature', () => { let transactionFactory: TransactionFactory; let signedTransaction: SignedZeroExTransaction; const TRANSACTION_DATA_LENGTH = 100; before(async () => { transactionFactory = new TransactionFactory(signerPrivateKey, signatureValidator.address, chainId); }); beforeEach(async () => { // We don't actually do anything with the transaction so we can just // fill it with random data. signedTransaction = await transactionFactory.newSignedTransactionAsync({ data: hexRandom(TRANSACTION_DATA_LENGTH), }); }); const validateAsync = async ( transaction: SignedZeroExTransaction, signatureHex: string, validatorAction?: ValidatorWalletAction, validatorExpectedSignatureHex?: string, ) => { const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); const expectedSignatureHashHex = validatorExpectedSignatureHex === undefined ? constants.NULL_BYTES : hashBytes(validatorExpectedSignatureHex); if (validatorAction !== undefined) { await validatorWallet .prepare(transactionHashHex, validatorAction, expectedSignatureHashHex) .awaitTransactionSuccessAsync(); } return signatureValidator.isValidTransactionSignature(transaction, signatureHex).callAsync(); }; it('should revert when signerAddress == 0', async () => { const signatureHex = hexConcat(SignatureType.EIP712); const nullSignerTransaction = { ...signedTransaction, signerAddress: constants.NULL_ADDRESS, }; const transactionHashHex = transactionHashUtils.getTransactionHashHex(nullSignerTransaction); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidSigner, transactionHashHex, constants.NULL_ADDRESS, signatureHex, ); const tx = signatureValidator.isValidTransactionSignature(nullSignerTransaction, signatureHex).callAsync(); return expect(tx).to.revertWith(expectedError); }); it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const isValidSignature = await validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const notSignatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(notSignatureDataHex, validatorWallet.address, SignatureType.Validator); const isValidSignature = await validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should return false when validator returns `true` and SignatureType=Validator', async () => { const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const isValidSignature = await validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.ReturnTrue, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should revert when SignatureType=Validator and signature is shorter than 21 bytes', async () => { // Set approval of signature validator to false await signatureValidator .setSignatureValidatorApproval(validatorWallet.address, false) .awaitTransactionSuccessAsync({ from: signedTransaction.signerAddress }); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureHex = hexConcat(SignatureType.Validator); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const expectedError = new ExchangeRevertErrors.SignatureError( ExchangeRevertErrors.SignatureErrorCode.InvalidLength, transactionHashHex, signedTransaction.signerAddress, signatureHex, ); const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.MatchSignatureHash); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator returns nothing and SignatureType=Validator', async () => { const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.ReturnNothing, signatureDataHex, ); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator attempts to update state and SignatureType=Validator', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.UpdateState); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator reverts and SignatureType=Validator', async () => { // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, new StringRevertError(validatorWalletRevertReason).encode(), ); const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert); return expect(tx).to.revertWith(expectedError); }); it('should revert when SignatureType=Validator, signature is valid and validator is not approved', async () => { // Set approval of signature validator to false await signatureValidator .setSignatureValidatorApproval(validatorWallet.address, false) .awaitTransactionSuccessAsync({ from: signedTransaction.signerAddress }); // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, validatorWallet.address, SignatureType.Validator); const expectedError = new ExchangeRevertErrors.SignatureValidatorNotApprovedError( signedTransaction.signerAddress, validatorWallet.address, ); const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert); return expect(tx).to.revertWith(expectedError); }); it('should return true when SignatureType=EIP1271Wallet and signature is valid', async () => { signedTransaction.signerAddress = validatorWallet.address; // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet); // Validate signature const isValidSignature = await validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=EIP1271Wallet and signature is invalid', async () => { signedTransaction.signerAddress = validatorWallet.address; // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureDataHex = generateRandomSignature(); const notSignatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(notSignatureDataHex, SignatureType.EIP1271Wallet); // Validate signature const isValidSignature = await validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.MatchSignatureHash, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should return false when validator returns `true` and SignatureType=EIP1271Wallet', async () => { signedTransaction.signerAddress = validatorWallet.address; const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet); // Validate signature const isValidSignature = await validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.ReturnTrue, signatureDataHex, ); expect(isValidSignature).to.be.false(); }); it('should revert when validator returns nothing and SignatureType=EIP1271Wallet', async () => { signedTransaction.signerAddress = validatorWallet.address; const signatureDataHex = generateRandomSignature(); const signatureHex = hexConcat(signatureDataHex, SignatureType.EIP1271Wallet); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync( signedTransaction, signatureHex, ValidatorWalletAction.ReturnNothing, signatureDataHex, ); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator attempts to update state and SignatureType=EIP1271Wallet', async () => { signedTransaction.signerAddress = validatorWallet.address; // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP1271Wallet); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, constants.NULL_BYTES, ); const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.UpdateState); return expect(tx).to.revertWith(expectedError); }); it('should revert when validator reverts and SignatureType=EIP1271Wallet', async () => { signedTransaction.signerAddress = validatorWallet.address; // Doesn't have to contain a real signature since our wallet contract // just does a hash comparison. const signatureHex = hexConcat(generateRandomSignature(), SignatureType.EIP1271Wallet); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( validatorWallet.address, data, signatureHex, new StringRevertError(validatorWalletRevertReason).encode(), ); const tx = validateAsync(signedTransaction, signatureHex, ValidatorWalletAction.Revert); return expect(tx).to.revertWith(expectedError); }); it('should revert when signer is an EOA and SignatureType=EIP1271Wallet', async () => { const signatureHex = hexConcat(SignatureType.EIP1271Wallet); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( signedTransaction.signerAddress, data, signatureHex, constants.NULL_BYTES, ); const tx = signatureValidator.isValidTransactionSignature(signedTransaction, signatureHex).callAsync(); return expect(tx).to.revertWith(expectedError); }); it('should revert when signer is an EOA and SignatureType=Validator', async () => { const signatureHex = hexConcat(notSignerAddress, SignatureType.Validator); const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTransaction); const data = eip1271Data .ZeroExTransactionWithHash(signedTransaction, transactionHashHex) .getABIEncodedTransactionData(); const expectedError = new ExchangeRevertErrors.EIP1271SignatureError( notSignerAddress, data, signatureHex, constants.NULL_BYTES, ); // Register an EOA as a validator. await signatureValidator .setSignatureValidatorApproval(notSignerAddress, true) .awaitTransactionSuccessAsync({ from: signerAddress }); const tx = signatureValidator.isValidTransactionSignature(signedTransaction, signatureHex).callAsync(); return expect(tx).to.revertWith(expectedError); }); // Run hash-only signature type tests as well. const validateOrderHashAsync = async ( hashHex: string, _signerAddress: string, signatureHex: string, validatorAction?: ValidatorWalletAction, validatorExpectedSignatureHex?: string, ): Promise => { signedTransaction.signerAddress = _signerAddress; return validateAsync(signedTransaction, signatureHex, validatorAction, validatorExpectedSignatureHex); }; createHashSignatureTests((_signerAddress?: string) => { signedTransaction.signerAddress = _signerAddress === undefined ? signerAddress : _signerAddress; return transactionHashUtils.getTransactionHashHex(signedTransaction); }, validateOrderHashAsync); }); describe('setSignatureValidatorApproval', () => { let signatureValidatorLogDecoder: LogDecoder; before(async () => { signatureValidatorLogDecoder = new LogDecoder(env.web3Wrapper, artifacts); }); it('should emit a SignatureValidatorApprovalSet with correct args when a validator is approved', async () => { const approval = true; const res = await signatureValidator .setSignatureValidatorApproval(validatorWallet.address, approval) .awaitTransactionSuccessAsync({ from: signerAddress, }); expect(res.logs.length).to.equal(1); const log = signatureValidatorLogDecoder.decodeLogOrThrow(res.logs[0]) as LogWithDecodedArgs< TestSignatureValidatorSignatureValidatorApprovalEventArgs >; const logArgs = log.args; expect(logArgs.signerAddress).to.equal(signerAddress); expect(logArgs.validatorAddress).to.equal(validatorWallet.address); expect(logArgs.isApproved).to.equal(approval); }); it('should emit a SignatureValidatorApprovalSet with correct args when a validator is disapproved', async () => { const approval = false; const res = await signatureValidator .setSignatureValidatorApproval(validatorWallet.address, approval) .awaitTransactionSuccessAsync({ from: signerAddress, }); expect(res.logs.length).to.equal(1); const log = signatureValidatorLogDecoder.decodeLogOrThrow(res.logs[0]) as LogWithDecodedArgs< TestSignatureValidatorSignatureValidatorApprovalEventArgs >; const logArgs = log.args; expect(logArgs.signerAddress).to.equal(signerAddress); expect(logArgs.validatorAddress).to.equal(validatorWallet.address); expect(logArgs.isApproved).to.equal(approval); }); }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion