import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { addSignedMessagePrefix, assetProxyUtils, MessagePrefixType, orderHashUtils } from '@0xproject/order-utils'; import { RevertReason, SignatureType, SignedOrder } from '@0xproject/types'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); import { TestSignatureValidatorContract, TestSignatureValidatorSignatureValidatorApprovalEventArgs, } from '../../generated_contract_wrappers/test_signature_validator'; import { TestValidatorContract } from '../../generated_contract_wrappers/test_validator'; import { TestWalletContract } from '../../generated_contract_wrappers/test_wallet'; import { addressUtils } from '../utils/address_utils'; import { artifacts } from '../utils/artifacts'; import { expectContractCallFailed } from '../utils/assertions'; import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; import { LogDecoder } from '../utils/log_decoder'; import { OrderFactory } from '../utils/order_factory'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); // tslint:disable:no-unnecessary-type-assertion describe('MixinSignatureValidator', () => { let signedOrder: SignedOrder; let orderFactory: OrderFactory; let signatureValidator: TestSignatureValidatorContract; let testWallet: TestWalletContract; let testValidator: TestValidatorContract; let signerAddress: string; let signerPrivateKey: Buffer; let notSignerAddress: string; let notSignerPrivateKey: Buffer; let signatureValidatorLogDecoder: LogDecoder; before(async () => { await blockchainLifecycle.startAsync(); }); after(async () => { await blockchainLifecycle.revertAsync(); }); before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); const makerAddress = accounts[0]; signerAddress = makerAddress; notSignerAddress = accounts[1]; signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync( artifacts.TestSignatureValidator, provider, txDefaults, ); testWallet = await TestWalletContract.deployFrom0xArtifactAsync( artifacts.TestWallet, provider, txDefaults, signerAddress, ); testValidator = await TestValidatorContract.deployFrom0xArtifactAsync( artifacts.TestValidator, provider, txDefaults, signerAddress, ); signatureValidatorLogDecoder = new LogDecoder(web3Wrapper, signatureValidator.address); await web3Wrapper.awaitTransactionSuccessAsync( await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, { from: signerAddress, }), constants.AWAIT_TRANSACTION_MINED_MS, ); const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, exchangeAddress: signatureValidator.address, makerAddress, feeRecipientAddress: addressUtils.generatePseudoRandomAddress(), makerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), takerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), }; signerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; notSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(notSignerAddress)]; orderFactory = new OrderFactory(signerPrivateKey, defaultOrderParams); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); }); afterEach(async () => { await blockchainLifecycle.revertAsync(); }); describe('isValidSignature', () => { beforeEach(async () => { signedOrder = await orderFactory.newSignedOrderAsync(); }); it('should revert when signature is empty', async () => { const emptySignature = '0x'; const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); return expectContractCallFailed( signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, emptySignature, ), RevertReason.LengthGreaterThan0Required, ); }); it('should revert when signature type is unsupported', async () => { const unsupportedSignatureType = SignatureType.NSignatureTypes; const unsupportedSignatureHex = `0x${unsupportedSignatureType}`; const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); return expectContractCallFailed( signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, unsupportedSignatureHex, ), RevertReason.SignatureUnsupported, ); }); it('should revert when SignatureType=Illegal', async () => { const unsupportedSignatureHex = `0x${SignatureType.Illegal}`; const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); return expectContractCallFailed( signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, unsupportedSignatureHex, ), RevertReason.SignatureIllegal, ); }); it('should return false when SignatureType=Invalid and signature has a length of zero', async () => { const signatureHex = `0x${SignatureType.Invalid}`; const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, signatureHex, ); expect(isValidSignature).to.be.false(); }); it('should revert when SignatureType=Invalid and signature length is non-zero', async () => { const fillerData = ethUtil.toBuffer('0xdeadbeef'); const signatureType = ethUtil.toBuffer(`0x${SignatureType.Invalid}`); const signatureBuffer = Buffer.concat([fillerData, signatureType]); const signatureHex = ethUtil.bufferToHex(signatureBuffer); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); return expectContractCallFailed( signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, signatureHex, ), RevertReason.Length0Required, ); }); it('should return true when SignatureType=EIP712 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.EIP712}`), ]); const signatureHex = ethUtil.bufferToHex(signature); // Validate signature const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signerAddress, signatureHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=EIP712 and signature is invalid', 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.EIP712}`), ]); 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, ); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=EthSign and signature is valid', async () => { // Create EthSign signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const orderHashWithEthSignPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.EthSign); const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); // Create 0x signature from EthSign signature const signature = Buffer.concat([ ethUtil.toBuffer(ecSignature.v), ecSignature.r, ecSignature.s, ethUtil.toBuffer(`0x${SignatureType.EthSign}`), ]); const signatureHex = ethUtil.bufferToHex(signature); // Validate signature const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signerAddress, signatureHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=EthSign and signature is invalid', async () => { // Create EthSign signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const orderHashWithEthSignPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.EthSign); const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); // Create 0x signature from EthSign signature const signature = Buffer.concat([ ethUtil.toBuffer(ecSignature.v), ecSignature.r, ecSignature.s, ethUtil.toBuffer(`0x${SignatureType.EthSign}`), ]); 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, ); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=Caller and signer is caller', async () => { const signature = ethUtil.toBuffer(`0x${SignatureType.Caller}`); const signatureHex = ethUtil.bufferToHex(signature); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signerAddress, signatureHex, { from: signerAddress }, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Caller and signer is not caller', async () => { const signature = ethUtil.toBuffer(`0x${SignatureType.Caller}`); const signatureHex = ethUtil.bufferToHex(signature); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signerAddress, signatureHex, { from: notSignerAddress }, ); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=Wallet 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.Wallet}`), ]); const signatureHex = ethUtil.bufferToHex(signature); // Validate signature const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, testWallet.address, signatureHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Wallet 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.Wallet}`), ]); const signatureHex = ethUtil.bufferToHex(signature); // Validate signature const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, testWallet.address, signatureHex, ); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => { const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); 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, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => { const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); 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, ); expect(isValidSignature).to.be.false(); }); it('should return false when SignatureType=Validator, signature is valid and validator is not approved', async () => { // Set approval of signature validator to false await web3Wrapper.awaitTransactionSuccessAsync( await signatureValidator.setSignatureValidatorApproval.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.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, ); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=Trezor and signature is valid', async () => { // Create Trezor signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const orderHashWithTrezorPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.Trezor); const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey); // Create 0x signature from Trezor signature const signature = Buffer.concat([ ethUtil.toBuffer(ecSignature.v), ecSignature.r, ecSignature.s, ethUtil.toBuffer(`0x${SignatureType.Trezor}`), ]); const signatureHex = ethUtil.bufferToHex(signature); // Validate signature const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signerAddress, signatureHex, ); expect(isValidSignature).to.be.true(); }); it('should return false when SignatureType=Trezor and signature is invalid', async () => { // Create Trezor signature const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const orderHashWithTrezorPrefixHex = addSignedMessagePrefix(orderHashHex, MessagePrefixType.Trezor); const orderHashWithTrezorPrefixBuffer = ethUtil.toBuffer(orderHashWithTrezorPrefixHex); const ecSignature = ethUtil.ecsign(orderHashWithTrezorPrefixBuffer, signerPrivateKey); // Create 0x signature from Trezor signature const signature = Buffer.concat([ ethUtil.toBuffer(ecSignature.v), ecSignature.r, ecSignature.s, ethUtil.toBuffer(`0x${SignatureType.Trezor}`), ]); 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, ); expect(isValidSignature).to.be.false(); }); it('should return true when SignatureType=Presigned and signer has presigned hash', async () => { // Presign hash const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); await web3Wrapper.awaitTransactionSuccessAsync( await signatureValidator.preSign.sendTransactionAsync( orderHashHex, signedOrder.makerAddress, signedOrder.signature, ), constants.AWAIT_TRANSACTION_MINED_MS, ); // 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, ); 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 orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, signedOrder.makerAddress, signatureHex, ); expect(isValidSignature).to.be.false(); }); }); describe('setSignatureValidatorApproval', () => { it('should emit a SignatureValidatorApprovalSet with correct args when a validator is approved', async () => { const approval = true; const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( testValidator.address, approval, { from: signerAddress, }, ), ); expect(res.logs.length).to.equal(1); const log = res.logs[0] as LogWithDecodedArgs; const logArgs = log.args; expect(logArgs.signerAddress).to.equal(signerAddress); expect(logArgs.validatorAddress).to.equal(testValidator.address); expect(logArgs.approved).to.equal(approval); }); it('should emit a SignatureValidatorApprovalSet with correct args when a validator is disapproved', async () => { const approval = false; const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( testValidator.address, approval, { from: signerAddress, }, ), ); expect(res.logs.length).to.equal(1); const log = res.logs[0] as LogWithDecodedArgs; const logArgs = log.args; expect(logArgs.signerAddress).to.equal(signerAddress); expect(logArgs.validatorAddress).to.equal(testValidator.address); expect(logArgs.approved).to.equal(approval); }); }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion