fallback to eth_sign if eth_signTypedData fails (#1817)
* fallback to eth_sign if eth_signTypedData fails lots of wallets/web3providers still don't support the new signing methods
This commit is contained in:
parent
c7f474ada1
commit
05d34616b7
@ -5,6 +5,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Fix decoding bug in `DutchAuctionWrapper.decodeDutchAuctionData`",
|
"note": "Fix decoding bug in `DutchAuctionWrapper.decodeDutchAuctionData`",
|
||||||
"pr": 1815
|
"pr": 1815
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Fallback to eth_sign if eth_signedTypedData fails",
|
||||||
|
"pr": 1817
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -740,7 +740,7 @@ export class CoordinatorWrapper extends ContractWrapper {
|
|||||||
data,
|
data,
|
||||||
verifyingContractAddress: this.exchangeAddress,
|
verifyingContractAddress: this.exchangeAddress,
|
||||||
};
|
};
|
||||||
const signedTransaction = await signatureUtils.ecSignTypedDataTransactionAsync(
|
const signedTransaction = await signatureUtils.ecSignTransactionAsync(
|
||||||
this._web3Wrapper.getProvider(),
|
this._web3Wrapper.getProvider(),
|
||||||
transaction,
|
transaction,
|
||||||
transaction.signerAddress,
|
transaction.signerAddress,
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "8.1.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add `ecSignTransactionAsync`",
|
||||||
|
"pr": 1817
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@ -20,6 +20,7 @@ import * as _ from 'lodash';
|
|||||||
import { assert } from './assert';
|
import { assert } from './assert';
|
||||||
import { eip712Utils } from './eip712_utils';
|
import { eip712Utils } from './eip712_utils';
|
||||||
import { orderHashUtils } from './order_hash';
|
import { orderHashUtils } from './order_hash';
|
||||||
|
import { transactionHashUtils } from './transaction_hash';
|
||||||
import { TypedDataError } from './types';
|
import { TypedDataError } from './types';
|
||||||
import { utils } from './utils';
|
import { utils } from './utils';
|
||||||
|
|
||||||
@ -292,6 +293,50 @@ export const signatureUtils = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Signs a transaction and returns a SignedZeroExTransaction. First `eth_signTypedData` is requested
|
||||||
|
* then a fallback to `eth_sign` if not available on the supplied provider.
|
||||||
|
* @param supportedProvider Web3 provider to use for all JSON RPC requests
|
||||||
|
* @param transaction The ZeroExTransaction to sign.
|
||||||
|
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
||||||
|
* must be available via the supplied Provider.
|
||||||
|
* @return A SignedTransaction containing the order and Elliptic curve signature with Signature Type.
|
||||||
|
*/
|
||||||
|
async ecSignTransactionAsync(
|
||||||
|
supportedProvider: SupportedProvider,
|
||||||
|
transaction: ZeroExTransaction,
|
||||||
|
signerAddress: string,
|
||||||
|
): Promise<SignedZeroExTransaction> {
|
||||||
|
assert.doesConformToSchema('transaction', transaction, schemas.zeroExTransactionSchema, [schemas.hexSchema]);
|
||||||
|
try {
|
||||||
|
const signedTransaction = await signatureUtils.ecSignTypedDataTransactionAsync(
|
||||||
|
supportedProvider,
|
||||||
|
transaction,
|
||||||
|
signerAddress,
|
||||||
|
);
|
||||||
|
return signedTransaction;
|
||||||
|
} catch (err) {
|
||||||
|
// HACK: We are unable to handle specific errors thrown since provider is not an object
|
||||||
|
// under our control. It could be Metamask Web3, Ethers, or any general RPC provider.
|
||||||
|
// We check for a user denying the signature request in a way that supports Metamask and
|
||||||
|
// Coinbase Wallet. Unfortunately for signers with a different error message,
|
||||||
|
// they will receive two signature requests.
|
||||||
|
if (err.message.includes('User denied message signature')) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||||
|
const signatureHex = await signatureUtils.ecSignHashAsync(
|
||||||
|
supportedProvider,
|
||||||
|
transactionHash,
|
||||||
|
signerAddress,
|
||||||
|
);
|
||||||
|
const signedTransaction = {
|
||||||
|
...transaction,
|
||||||
|
signature: signatureHex,
|
||||||
|
};
|
||||||
|
return signedTransaction;
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Signs a ZeroExTransaction using `eth_signTypedData` and returns a SignedZeroExTransaction.
|
* Signs a ZeroExTransaction using `eth_signTypedData` and returns a SignedZeroExTransaction.
|
||||||
* @param supportedProvider Web3 provider to use for all JSON RPC requests
|
* @param supportedProvider Web3 provider to use for all JSON RPC requests
|
||||||
@ -495,3 +540,4 @@ function parseSignatureHexAsRSV(signatureHex: string): ECSignature {
|
|||||||
};
|
};
|
||||||
return ecSignature;
|
return ecSignature;
|
||||||
}
|
}
|
||||||
|
// tslint:disable:max-file-line-count
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Order, SignatureType } from '@0x/types';
|
import { assert } from '@0x/assert';
|
||||||
|
import { Order, SignatureType, ZeroExTransaction } from '@0x/types';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
|
import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
|
||||||
@ -6,7 +7,7 @@ import * as ethUtil from 'ethereumjs-util';
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { generatePseudoRandomSalt, orderHashUtils } from '../src';
|
import { generatePseudoRandomSalt, orderHashUtils, transactionHashUtils } from '../src';
|
||||||
import { constants } from '../src/constants';
|
import { constants } from '../src/constants';
|
||||||
import { signatureUtils } from '../src/signature_utils';
|
import { signatureUtils } from '../src/signature_utils';
|
||||||
|
|
||||||
@ -16,10 +17,11 @@ import { provider, web3Wrapper } from './utils/web3_wrapper';
|
|||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe('Signature utils', () => {
|
describe.only('Signature utils', () => {
|
||||||
let makerAddress: string;
|
let makerAddress: string;
|
||||||
const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
|
const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
|
||||||
let order: Order;
|
let order: Order;
|
||||||
|
let transaction: ZeroExTransaction;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
makerAddress = availableAddreses[0];
|
makerAddress = availableAddreses[0];
|
||||||
@ -38,6 +40,12 @@ describe('Signature utils', () => {
|
|||||||
takerAssetAmount: new BigNumber(0),
|
takerAssetAmount: new BigNumber(0),
|
||||||
expirationTimeSeconds: new BigNumber(0),
|
expirationTimeSeconds: new BigNumber(0),
|
||||||
};
|
};
|
||||||
|
transaction = {
|
||||||
|
verifyingContractAddress: fakeExchangeContractAddress,
|
||||||
|
salt: generatePseudoRandomSalt(),
|
||||||
|
signerAddress: makerAddress,
|
||||||
|
data: '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0',
|
||||||
|
};
|
||||||
});
|
});
|
||||||
describe('#isValidSignatureAsync', () => {
|
describe('#isValidSignatureAsync', () => {
|
||||||
let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
|
let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
|
||||||
@ -197,6 +205,55 @@ describe('Signature utils', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#ecSignTransactionAsync', () => {
|
||||||
|
it('should default to eth_sign if eth_signTypedData is unavailable', async () => {
|
||||||
|
const fakeProvider = {
|
||||||
|
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
|
||||||
|
if (payload.method === 'eth_signTypedData') {
|
||||||
|
callback(new Error('Internal RPC Error'));
|
||||||
|
} else if (payload.method === 'eth_sign') {
|
||||||
|
const [address, message] = payload.params;
|
||||||
|
const signature = await web3Wrapper.signMessageAsync(address, message);
|
||||||
|
callback(null, {
|
||||||
|
id: 42,
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
result: signature,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const signedTransaction = await signatureUtils.ecSignTransactionAsync(
|
||||||
|
fakeProvider,
|
||||||
|
transaction,
|
||||||
|
makerAddress,
|
||||||
|
);
|
||||||
|
assert.isHexString('signedTransaction.signature', signedTransaction.signature);
|
||||||
|
});
|
||||||
|
it('should throw if the user denies the signing request', async () => {
|
||||||
|
const fakeProvider = {
|
||||||
|
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
|
||||||
|
if (payload.method === 'eth_signTypedData') {
|
||||||
|
callback(new Error('User denied message signature'));
|
||||||
|
} else if (payload.method === 'eth_sign') {
|
||||||
|
const [address, message] = payload.params;
|
||||||
|
const signature = await web3Wrapper.signMessageAsync(address, message);
|
||||||
|
callback(null, {
|
||||||
|
id: 42,
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
result: signature,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
signatureUtils.ecSignTransactionAsync(fakeProvider, transaction, makerAddress),
|
||||||
|
).to.to.be.rejectedWith('User denied message signature');
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#ecSignHashAsync', () => {
|
describe('#ecSignHashAsync', () => {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
@ -319,6 +376,60 @@ describe('Signature utils', () => {
|
|||||||
expect(signedOrder.signature).to.equal(expectedSignature);
|
expect(signedOrder.signature).to.equal(expectedSignature);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#ecSignTypedDataTransactionAsync', () => {
|
||||||
|
it('should result in the same signature as signing the order hash without an ethereum message prefix', async () => {
|
||||||
|
// Note: Since order hash is an EIP712 hash the result of a valid EIP712 signature
|
||||||
|
// of order hash is the same as signing the order without the Ethereum Message prefix.
|
||||||
|
const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction);
|
||||||
|
const sig = ethUtil.ecsign(
|
||||||
|
ethUtil.toBuffer(transactionHashHex),
|
||||||
|
Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'),
|
||||||
|
);
|
||||||
|
const signatureBuffer = Buffer.concat([
|
||||||
|
ethUtil.toBuffer(sig.v),
|
||||||
|
ethUtil.toBuffer(sig.r),
|
||||||
|
ethUtil.toBuffer(sig.s),
|
||||||
|
ethUtil.toBuffer(SignatureType.EIP712),
|
||||||
|
]);
|
||||||
|
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
|
||||||
|
const signedTransaction = await signatureUtils.ecSignTypedDataTransactionAsync(
|
||||||
|
provider,
|
||||||
|
transaction,
|
||||||
|
makerAddress,
|
||||||
|
);
|
||||||
|
const isValidSignature = await signatureUtils.isValidSignatureAsync(
|
||||||
|
provider,
|
||||||
|
transactionHashHex,
|
||||||
|
signedTransaction.signature,
|
||||||
|
makerAddress,
|
||||||
|
);
|
||||||
|
expect(signatureHex).to.eq(signedTransaction.signature);
|
||||||
|
expect(isValidSignature).to.eq(true);
|
||||||
|
});
|
||||||
|
it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => {
|
||||||
|
const fakeProvider = {
|
||||||
|
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
|
||||||
|
if (payload.method === 'eth_signTypedData') {
|
||||||
|
const [address, typedData] = payload.params;
|
||||||
|
const signature = await web3Wrapper.signTypedDataAsync(address, typedData);
|
||||||
|
callback(null, {
|
||||||
|
id: 42,
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
result: signature,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const signedTransaction = await signatureUtils.ecSignTypedDataTransactionAsync(
|
||||||
|
fakeProvider,
|
||||||
|
transaction,
|
||||||
|
makerAddress,
|
||||||
|
);
|
||||||
|
assert.isHexString('signedTransaction.signature', signedTransaction.signature);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#convertECSignatureToSignatureHex', () => {
|
describe('#convertECSignatureToSignatureHex', () => {
|
||||||
const ecSignature: ECSignature = {
|
const ecSignature: ECSignature = {
|
||||||
v: 27,
|
v: 27,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user