Skip to content

Commit 0b359e6

Browse files
committed
add eip 191 exceptions
1 parent ede8ce5 commit 0b359e6

8 files changed

Lines changed: 221 additions & 8 deletions

File tree

packages/provider/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"author": "Horizon Blockchain Games",
1010
"license": "Apache-2.0",
1111
"scripts": {
12-
"test": "echo",
12+
"test": "yarn test:file tests/**/*.spec.ts",
13+
"test:file": "TS_NODE_PROJECT=../../tsconfig.test.json mocha -r ts-node/register --timeout 30000",
1314
"typecheck": "tsc --noEmit"
1415
},
1516
"dependencies": {
@@ -34,4 +35,4 @@
3435
"src",
3536
"dist"
3637
]
37-
}
38+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { ethers } from 'ethers'
2+
3+
export function messageIsExemptFromEIP191Prefix(message: Uint8Array, origin: string | undefined): boolean {
4+
return EIP_191_PREFIX_EXCEPTIONS.some(e => e.predicate(message, origin))
5+
}
6+
7+
const EIP_191_PREFIX_EXCEPTIONS: Array<{
8+
name: string
9+
predicate: (message: Uint8Array, origin?: string) => boolean
10+
}> = [
11+
// NOTE: Decentraland does not support 191 correctly.
12+
{ name: 'Decentraland Exception', predicate: (_message, origin) => origin === 'https://play.decentraland.org' },
13+
14+
// NOTE: 0x v3 does not support 191 correctly.
15+
// See https://gov.0x.org/t/zeip-proposal-fix-v3-eip-191-non-compliance-when-validating-eip-1271-signatures/3396 for more info.
16+
{ name: '0x v3 Exception', predicate: isZeroExV3Order }
17+
]
18+
19+
// try to interpret bytes as abi-encoded 0x v3 OrderWithHash -
20+
// see https://github.com/0xProject/0x-protocol-specification/blob/master/v3/v3-specification.md
21+
export function isZeroExV3Order(bytes: Uint8Array): boolean {
22+
const abi = new ethers.utils.Interface(ZeroXV3EIP1271OrderWithHashAbi)
23+
try {
24+
abi.decodeFunctionData('OrderWithHash', bytes)
25+
return true
26+
} catch (err) {
27+
// failed to decode ABI, so it's not a v3 order.
28+
return false
29+
}
30+
}
31+
32+
const ZeroXV3EIP1271OrderWithHashAbi = [
33+
{
34+
inputs: [
35+
{
36+
components: [
37+
{
38+
internalType: 'address',
39+
name: 'makerAddress',
40+
type: 'address'
41+
},
42+
{
43+
internalType: 'address',
44+
name: 'takerAddress',
45+
type: 'address'
46+
},
47+
{
48+
internalType: 'address',
49+
name: 'feeRecipientAddress',
50+
type: 'address'
51+
},
52+
{
53+
internalType: 'address',
54+
name: 'senderAddress',
55+
type: 'address'
56+
},
57+
{
58+
internalType: 'uint256',
59+
name: 'makerAssetAmount',
60+
type: 'uint256'
61+
},
62+
{
63+
internalType: 'uint256',
64+
name: 'takerAssetAmount',
65+
type: 'uint256'
66+
},
67+
{
68+
internalType: 'uint256',
69+
name: 'makerFee',
70+
type: 'uint256'
71+
},
72+
{
73+
internalType: 'uint256',
74+
name: 'takerFee',
75+
type: 'uint256'
76+
},
77+
{
78+
internalType: 'uint256',
79+
name: 'expirationTimeSeconds',
80+
type: 'uint256'
81+
},
82+
{
83+
internalType: 'uint256',
84+
name: 'salt',
85+
type: 'uint256'
86+
},
87+
{
88+
internalType: 'bytes',
89+
name: 'makerAssetData',
90+
type: 'bytes'
91+
},
92+
{
93+
internalType: 'bytes',
94+
name: 'takerAssetData',
95+
type: 'bytes'
96+
},
97+
{
98+
internalType: 'bytes',
99+
name: 'makerFeeAssetData',
100+
type: 'bytes'
101+
},
102+
{
103+
internalType: 'bytes',
104+
name: 'takerFeeAssetData',
105+
type: 'bytes'
106+
}
107+
],
108+
internalType: 'struct IEIP1271Data.Order',
109+
name: 'order',
110+
type: 'tuple'
111+
},
112+
{
113+
internalType: 'bytes32',
114+
name: 'orderHash',
115+
type: 'bytes32'
116+
}
117+
],
118+
name: 'OrderWithHash',
119+
outputs: [],
120+
stateMutability: 'pure',
121+
type: 'function'
122+
}
123+
]

packages/provider/src/transports/wallet-request-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ export class WalletRequestHandler implements ExternalProvider, JsonRpcHandler, P
300300

301301
// Message must be prefixed with "\x19Ethereum Signed Message:\n"
302302
// as defined by EIP-191
303-
const prefixedMessage = prefixEIP191Message(message)
303+
const prefixedMessage = prefixEIP191Message(message, this.connectOptions?.origin)
304304

305305
// TODO:
306306
// if (process.env.TEST_MODE === 'true' && this.prompter === null) {

packages/provider/src/utils.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { WalletConfig, addressOf, DecodedSignature, isConfigEqual } from '@0xseq
44
import { packMessageData, encodeMessageDigest, TypedData, encodeTypedDataDigest } from '@0xsequence/utils'
55
import { Web3Provider } from './provider'
66
import { isValidSignature as _isValidSignature, recoverConfig, Signer } from '@0xsequence/wallet'
7+
import { messageIsExemptFromEIP191Prefix } from './eip191exceptions'
78

89
const eip191prefix = ethers.utils.toUtf8Bytes('\x19Ethereum Signed Message:\n')
910

@@ -15,9 +16,13 @@ export const messageToBytes = (message: BytesLike): Uint8Array => {
1516
return ethers.utils.toUtf8Bytes(message)
1617
}
1718

18-
export const prefixEIP191Message = (message: BytesLike): Uint8Array => {
19+
export const prefixEIP191Message = (message: BytesLike, origin: string | undefined): Uint8Array => {
1920
const messageBytes = messageToBytes(message)
20-
return ethers.utils.concat([eip191prefix, ethers.utils.toUtf8Bytes(String(messageBytes.length)), messageBytes])
21+
if (messageIsExemptFromEIP191Prefix(messageBytes, origin)) {
22+
return messageBytes
23+
} else {
24+
return ethers.utils.concat([eip191prefix, ethers.utils.toUtf8Bytes(String(messageBytes.length)), messageBytes])
25+
}
2126
}
2227

2328
export const isValidSignature = async (
@@ -45,7 +50,7 @@ export const isValidMessageSignature = async (
4550
chainId?: number,
4651
walletContext?: WalletContext
4752
): Promise<boolean | undefined> => {
48-
const prefixed = prefixEIP191Message(message)
53+
const prefixed = prefixEIP191Message(message, undefined)
4954
const digest = encodeMessageDigest(prefixed)
5055
return isValidSignature(address, digest, signature, provider, chainId, walletContext)
5156
}

packages/provider/src/utils/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class WalletUtils {
7676
): Promise<boolean | undefined> {
7777
const provider = this.wallet.getProvider(chainId)
7878
if (!provider) throw new Error(`unable to get provider for chainId ${chainId}`)
79-
const prefixed = prefixEIP191Message(message)
79+
const prefixed = prefixEIP191Message(message, undefined)
8080
const digest = encodeMessageDigest(prefixed)
8181
return isValidSignature(address, digest, signature, provider, chainId, walletContext)
8282
}
@@ -113,7 +113,13 @@ export class WalletUtils {
113113
walletContext?: WalletContext
114114
): Promise<WalletConfig> => {
115115
walletContext = walletContext || (await this.wallet.getWalletContext())
116-
return recoverWalletConfig(address, encodeMessageDigest(prefixEIP191Message(message)), signature, chainId, walletContext)
116+
return recoverWalletConfig(
117+
address,
118+
encodeMessageDigest(prefixEIP191Message(message, undefined)),
119+
signature,
120+
chainId,
121+
walletContext
122+
)
117123
}
118124

119125
// Recover the WalletConfig from a signature of a typedData object
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import chaiAsPromised from 'chai-as-promised'
2+
import * as chai from 'chai'
3+
4+
import { isZeroExV3Order, messageIsExemptFromEIP191Prefix } from '../src/eip191exceptions'
5+
import { message1, zeroExV3Order } from './messages'
6+
import { ethers } from 'ethers'
7+
const { expect } = chai.use(chaiAsPromised)
8+
9+
describe('191 prefix exceptions', () => {
10+
it('decentraland is exempt', () => {
11+
expect(messageIsExemptFromEIP191Prefix(message1, 'https://play.decentraland.org')).equal(true)
12+
})
13+
14+
it('should strip 191 prefix from 0x v3 orders', () => {
15+
expect(messageIsExemptFromEIP191Prefix(zeroExV3Order, undefined)).equal(true)
16+
})
17+
18+
it('should not strip 191 prefix from other messages', () => {
19+
expect(messageIsExemptFromEIP191Prefix(message1, undefined)).equal(false)
20+
expect(messageIsExemptFromEIP191Prefix(message1, 'https://play.example.com')).equal(false)
21+
})
22+
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Ethereum personal sign: Hello, World!
2+
export const message1 = new Uint8Array([
3+
25, 69, 116, 104, 101, 114, 101, 117, 109, 32, 83, 105, 103, 110, 101, 100, 32, 77, 101, 115, 115, 97, 103, 101, 58, 10, 49, 51,
4+
72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33
5+
])
6+
7+
// Ethereum personal sign 0x v3 order
8+
export const zeroExV3Order = new Uint8Array([
9+
62, 254, 80, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 3, 153, 144,
10+
71, 27, 241, 205, 119, 186, 5, 60, 99, 148, 99, 19, 201, 174, 101, 93, 86, 211, 104, 110, 31, 232, 176, 9, 52, 53, 122, 24, 41,
11+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 3, 19, 123, 56, 230, 5, 28, 73, 127, 92, 7, 29, 45, 29, 189, 8, 190, 24, 26, 0, 0, 0,
12+
0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 55, 18, 71, 48, 244, 210, 213, 18, 72, 210, 192, 93, 42, 229, 203, 210, 136, 237, 103, 0, 0, 0,
13+
0, 0, 0, 0, 0, 0, 0, 0, 0, 188, 192, 42, 21, 92, 55, 66, 99, 50, 17, 85, 85, 92, 207, 65, 7, 0, 23, 100, 158, 0, 0, 0, 0, 0, 0,
14+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
15+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
16+
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
18+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150, 122, 103, 240, 255, 23, 36, 169, 85, 88, 7, 31, 44, 217, 97, 21, 252, 202, 109, 69, 32, 114,
19+
145, 27, 10, 160, 236, 62, 181, 81, 143, 220, 202, 36, 172, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
20+
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
21+
32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0,
22+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
23+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 36, 148, 207, 205, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24+
0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0,
25+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
26+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, 179, 167, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
27+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
28+
0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 244, 114, 97, 176, 0, 0,
29+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 190, 65, 193, 52, 252, 53, 23, 203, 14, 201, 75, 110, 234, 251, 102, 207, 153, 152, 120, 47, 0, 0,
30+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
31+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
32+
36, 148, 207, 205, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0,
33+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
34+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
35+
0, 13, 224, 182, 179, 167, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
36+
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0,
37+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 244, 114, 97, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
38+
170, 165, 185, 230, 197, 137, 100, 47, 152, 161, 205, 169, 155, 157, 2, 75, 132, 7, 40, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
39+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
40+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
42+
])
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import chaiAsPromised from 'chai-as-promised'
2+
import * as chai from 'chai'
3+
4+
import { isZeroExV3Order } from '../src/eip191exceptions'
5+
import { message1, zeroExV3Order } from './messages'
6+
const { expect } = chai.use(chaiAsPromised)
7+
8+
describe('Utils / 0xv3', () => {
9+
it('should detect 0x v3 order', () => {
10+
expect(isZeroExV3Order(message1)).equals(false)
11+
expect(isZeroExV3Order(zeroExV3Order.slice(0, -1))).equals(false)
12+
expect(isZeroExV3Order(zeroExV3Order)).equals(true)
13+
})
14+
})

0 commit comments

Comments
 (0)