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`",
|
||||
"pr": 1815
|
||||
},
|
||||
{
|
||||
"note": "Fallback to eth_sign if eth_signedTypedData fails",
|
||||
"pr": 1817
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -740,7 +740,7 @@ export class CoordinatorWrapper extends ContractWrapper {
|
||||
data,
|
||||
verifyingContractAddress: this.exchangeAddress,
|
||||
};
|
||||
const signedTransaction = await signatureUtils.ecSignTypedDataTransactionAsync(
|
||||
const signedTransaction = await signatureUtils.ecSignTransactionAsync(
|
||||
this._web3Wrapper.getProvider(),
|
||||
transaction,
|
||||
transaction.signerAddress,
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "8.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `ecSignTransactionAsync`",
|
||||
"pr": 1817
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "8.0.2",
|
||||
"changes": [
|
||||
|
@ -20,6 +20,7 @@ import * as _ from 'lodash';
|
||||
import { assert } from './assert';
|
||||
import { eip712Utils } from './eip712_utils';
|
||||
import { orderHashUtils } from './order_hash';
|
||||
import { transactionHashUtils } from './transaction_hash';
|
||||
import { TypedDataError } from './types';
|
||||
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.
|
||||
* @param supportedProvider Web3 provider to use for all JSON RPC requests
|
||||
@ -495,3 +540,4 @@ function parseSignatureHexAsRSV(signatureHex: string): 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 * as chai from 'chai';
|
||||
import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
|
||||
@ -6,7 +7,7 @@ import * as ethUtil from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
|
||||
import { generatePseudoRandomSalt, orderHashUtils } from '../src';
|
||||
import { generatePseudoRandomSalt, orderHashUtils, transactionHashUtils } from '../src';
|
||||
import { constants } from '../src/constants';
|
||||
import { signatureUtils } from '../src/signature_utils';
|
||||
|
||||
@ -16,10 +17,11 @@ import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Signature utils', () => {
|
||||
describe.only('Signature utils', () => {
|
||||
let makerAddress: string;
|
||||
const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
|
||||
let order: Order;
|
||||
let transaction: ZeroExTransaction;
|
||||
before(async () => {
|
||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
makerAddress = availableAddreses[0];
|
||||
@ -38,6 +40,12 @@ describe('Signature utils', () => {
|
||||
takerAssetAmount: new BigNumber(0),
|
||||
expirationTimeSeconds: new BigNumber(0),
|
||||
};
|
||||
transaction = {
|
||||
verifyingContractAddress: fakeExchangeContractAddress,
|
||||
salt: generatePseudoRandomSalt(),
|
||||
signerAddress: makerAddress,
|
||||
data: '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0',
|
||||
};
|
||||
});
|
||||
describe('#isValidSignatureAsync', () => {
|
||||
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', () => {
|
||||
before(async () => {
|
||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
@ -319,6 +376,60 @@ describe('Signature utils', () => {
|
||||
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', () => {
|
||||
const ecSignature: ECSignature = {
|
||||
v: 27,
|
||||
|
Loading…
x
Reference in New Issue
Block a user