Skip to content

Commit ee5c30c

Browse files
Support EIP1271 signatures (#4193)
* Support EIP1271 signatures * Add test * Add spinner when processing signature
1 parent e05fede commit ee5c30c

File tree

5 files changed

+103
-13
lines changed

5 files changed

+103
-13
lines changed

src/features/SignAndVerifyMessage/VerifyMessage.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { FunctionComponent, useEffect, useState } from 'react';
22

3-
import { Button } from '@mycrypto/ui';
43
import { parse } from 'query-string';
54
import { RouteComponentProps, withRouter } from 'react-router-dom';
65
import styled from 'styled-components';
76

8-
import { InputField } from '@components';
7+
import { Button, InputField } from '@components';
8+
import { ProviderHandler } from '@services/EthService';
9+
import { selectDefaultNetwork, useSelector } from '@store';
910
import { BREAK_POINTS, COLORS } from '@theme';
1011
import translate, { translateRaw } from '@translations';
1112
import { ISignedMessage } from '@types';
@@ -23,13 +24,7 @@ const Content = styled.div`
2324
align-items: center;
2425
`;
2526

26-
interface VerifyButtonProps {
27-
disabled?: boolean;
28-
}
29-
30-
const VerifyButton = styled(Button)<VerifyButtonProps>`
31-
${(props) => props.disabled && 'opacity: 0.4;'}
32-
27+
const VerifyButton = styled(Button)`
3328
@media (max-width: ${SCREEN_XS}) {
3429
width: 100%;
3530
}
@@ -61,18 +56,26 @@ const VerifyMessage: FunctionComponent<RouteComponentProps & Props> = ({ locatio
6156
const [message, setMessage] = useState('');
6257
const [error, setError] = useState<string | undefined>(undefined);
6358
const [signedMessage, setSignedMessage] = useState<ISignedMessage | null>(null);
59+
const [loading, setLoading] = useState<boolean>(false);
60+
const network = useSelector(selectDefaultNetwork);
61+
const provider = new ProviderHandler(network);
6462

6563
const handleClick = () => handleVerifySignedMessage();
6664

67-
const handleVerifySignedMessage = (json?: string, trySingleQuotes?: boolean): void => {
65+
const handleVerifySignedMessage = async (
66+
json?: string,
67+
trySingleQuotes?: boolean
68+
): Promise<void> => {
6869
const rawMessage = json ?? message;
70+
setLoading(true);
6971

7072
try {
7173
const normalizedMessage = trySingleQuotes ? normalizeSingleQuotes(rawMessage) : rawMessage;
7274
const parsedSignature: ISignedMessage = normalizeJson(normalizedMessage);
7375

7476
const isValid = verifySignedMessage(parsedSignature);
75-
if (!isValid) {
77+
const isValidEIP1271 = !isValid && (await provider.isValidEIP1271Signature(parsedSignature));
78+
if (!isValid && !isValidEIP1271) {
7679
throw Error();
7780
}
7881

@@ -85,6 +88,8 @@ const VerifyMessage: FunctionComponent<RouteComponentProps & Props> = ({ locatio
8588

8689
setError(translateRaw('ERROR_38'));
8790
setSignedMessage(null);
91+
} finally {
92+
setLoading(false);
8893
}
8994
};
9095

@@ -127,7 +132,7 @@ const VerifyMessage: FunctionComponent<RouteComponentProps & Props> = ({ locatio
127132
height="150px"
128133
inputError={error}
129134
/>
130-
<VerifyButton disabled={!message} onClick={handleClick}>
135+
<VerifyButton disabled={!message} loading={loading} onClick={handleClick}>
131136
{translate('MSG_VERIFY')}
132137
</VerifyButton>
133138
{signedMessage && (
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const EIP1271_ABI = [
2+
'function isValidSignature(bytes32 _message, bytes _signature) public view returns (bool)'
3+
];

src/services/EthService/contracts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export { UniDistributor } from './uniDistributor';
88
export { AaveMigrator } from './aaveMigrator';
99
export { AntMigrator } from './antMigrator';
1010
export { GolemV2Migration } from './golemV2Migration';
11+
export * from './eip1271';

src/services/EthService/network/providerHandler.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,61 @@ describe('ProviderHandler', () => {
4848
).resolves.toBeUndefined();
4949
});
5050
});
51+
52+
describe('isValidEIP1271Signature', () => {
53+
nock.disableNetConnect();
54+
55+
afterEach(() => {
56+
nock.cleanAll();
57+
});
58+
59+
it('checks whether a signature is valid using EIP1271', async () => {
60+
const provider = new ProviderHandler(fNetworks[0]);
61+
62+
nock(/.*/)
63+
.post(/.*/)
64+
.reply(200, () => ({
65+
id: 1,
66+
jsonrpc: '2.0',
67+
result: '0x1626ba7e00000000000000000000000000000000000000000000000000000000'
68+
}));
69+
70+
await expect(
71+
provider.isValidEIP1271Signature({
72+
address: '0x14987eb8d2553B3dfBe7B72AC5e062392E044644',
73+
msg: 'testing with argent',
74+
sig:
75+
'0x0fe68c13c9f7cf89c565bce2f4930bd5aa0adf406c4edf0205a794e10c085ee36293abb9469f243d6438cada470f843c3f5bb1d9778af8465bbb7221da8e28de1b',
76+
version: '2'
77+
})
78+
).resolves.toBe(true);
79+
});
80+
81+
it('returns false in case of errors', async () => {
82+
const provider = new ProviderHandler(fNetworks[0]);
83+
84+
nock(/.*/)
85+
.post(/.*/)
86+
.reply(200, () => ({
87+
id: 1,
88+
jsonrpc: '2.0',
89+
error: {
90+
code: 3,
91+
message: 'execution reverted: TM: Invalid signer',
92+
data:
93+
'0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012544d3a20496e76616c6964207369676e65720000000000000000000000000000'
94+
}
95+
}));
96+
97+
await expect(
98+
provider.isValidEIP1271Signature({
99+
address: '0x14987eb8d2553B3dfBe7B72AC5e062392E044644',
100+
msg: 'bla',
101+
sig:
102+
'0x0fe68c13c9f7cf89c565bce2f4930bd5aa0adf406c4edf0205a794e10c085ee36293abb9469f243d6438cada470f843c3f5bb1d9778af8465bbb7221da8e28de1b',
103+
version: '2'
104+
})
105+
).resolves.toBe(false);
106+
});
107+
});
51108
});

src/services/EthService/network/providerHandler.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FeeData } from '@ethersproject/abstract-provider';
22
import { BigNumber } from '@ethersproject/bignumber';
33
import { Contract } from '@ethersproject/contracts';
4+
import { hashMessage } from '@ethersproject/hash';
45
import {
56
BaseProvider,
67
Block,
@@ -15,10 +16,19 @@ import Resolution from '@unstoppabledomains/resolution';
1516
import { DEFAULT_ASSET_DECIMAL } from '@config';
1617
import { ERC20 } from '@services/EthService';
1718
import { erc20Abi } from '@services/EthService/contracts/erc20';
18-
import { Asset, ITxObject, ITxSigned, Network, TAddress, TokenInformation } from '@types';
19+
import {
20+
Asset,
21+
ISignedMessage,
22+
ITxObject,
23+
ITxSigned,
24+
Network,
25+
TAddress,
26+
TokenInformation
27+
} from '@types';
1928
import { baseToConvertedUnit } from '@utils';
2029
import { FallbackProvider } from '@vendor';
2130

31+
import { EIP1271_ABI } from '../contracts';
2232
import { EthersJS } from './ethersJsProvider';
2333
import { createCustomNodeProvider } from './helpers';
2434

@@ -174,6 +184,20 @@ export class ProviderHandler {
174184
});
175185
}
176186

187+
public async isValidEIP1271Signature({ address, msg, sig }: ISignedMessage): Promise<boolean> {
188+
return this.injectClient(async (client) => {
189+
try {
190+
const hash = hashMessage(msg);
191+
const contract = new Contract(address, EIP1271_ABI, client);
192+
const result = await contract.isValidSignature(hash, sig);
193+
194+
return result;
195+
} catch (e) {
196+
return false;
197+
}
198+
});
199+
}
200+
177201
public resolveENSName(name: string): Promise<string | null> {
178202
return this.injectClient((client) => {
179203
// Use Unstoppable if supported, otherwise is probably an ENS name

0 commit comments

Comments
 (0)