Skip to content
This repository was archived by the owner on Nov 23, 2023. It is now read-only.

Commit be4e076

Browse files
committed
fixup! feat: Support Ethereum EIP-712 Sign Typed Data
Add unit tests. Renames helpers/ethereumTypeDataConversions.js to helpers/ethereumTypeData.js
1 parent c2e9887 commit be4e076

File tree

4 files changed

+320
-6
lines changed

4 files changed

+320
-6
lines changed

src/js/core/methods/EthereumSignTypedData.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
} from '../../types/trezor/protobuf';
1515
import { ERRORS } from '../../constants';
1616
import type { EthereumSignTypedData as EthereumSignTypedDataParams } from '../../types/networks/ethereum';
17-
import { getFieldType, parseArrayType, encodeData } from './helpers/ethereumTypeDataConversions';
17+
import { getFieldType, parseArrayType, encodeData } from './helpers/ethereumSignTypedData';
1818

1919
type Params = {
2020
...EthereumSignTypedDataParams,
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import BigNumber from 'bignumber.js';
2+
import { TrezorError } from '../../../../constants/errors';
3+
import { Enum_EthereumDataType } from '../../../../types/trezor/protobuf';
4+
5+
export const parseArrayType = [
6+
{
7+
description: 'should parse sized arrays',
8+
input: 'uint8[26]',
9+
output: {
10+
entryTypeName: 'uint8',
11+
arraySize: 26,
12+
},
13+
},
14+
{
15+
description: 'should parse dynamic arrays',
16+
input: 'int32[]',
17+
output: {
18+
entryTypeName: 'int32',
19+
arraySize: null,
20+
},
21+
},
22+
{
23+
description: 'should throw error for non-array type',
24+
input: 'bytes',
25+
error: [TrezorError, 'could not be parsed'],
26+
},
27+
];
28+
29+
export const getFieldType = [
30+
{
31+
description: `should parse uints`,
32+
input: {
33+
typeName: 'uint256',
34+
types: {},
35+
},
36+
output: {
37+
data_type: Enum_EthereumDataType.UINT,
38+
size: 32,
39+
},
40+
},
41+
{
42+
description: `should parse ints`,
43+
input: {
44+
typeName: 'int8',
45+
types: {},
46+
},
47+
output: {
48+
data_type: Enum_EthereumDataType.INT,
49+
size: 1,
50+
},
51+
},
52+
{
53+
description: `should parse booleans`,
54+
input: {
55+
typeName: 'bool',
56+
types: {},
57+
},
58+
output: {
59+
data_type: Enum_EthereumDataType.BOOL,
60+
},
61+
},
62+
{
63+
description: `should parse address`,
64+
input: {
65+
typeName: 'address',
66+
types: {},
67+
},
68+
output: {
69+
data_type: Enum_EthereumDataType.ADDRESS,
70+
},
71+
},
72+
{
73+
description: `should parse dynamic bytes`,
74+
input: {
75+
typeName: 'bytes',
76+
types: {},
77+
},
78+
output: {
79+
data_type: Enum_EthereumDataType.BYTES,
80+
size: undefined,
81+
},
82+
},
83+
{
84+
description: `should parse fixed-size bytes`,
85+
input: {
86+
typeName: 'bytes18',
87+
types: {},
88+
},
89+
output: {
90+
data_type: Enum_EthereumDataType.BYTES,
91+
size: 18,
92+
},
93+
},
94+
{
95+
description: `should parse array types`,
96+
input: {
97+
typeName: 'uint256[57]',
98+
types: {},
99+
},
100+
output: {
101+
data_type: Enum_EthereumDataType.ARRAY,
102+
size: 57,
103+
entry_type: {
104+
data_type: Enum_EthereumDataType.UINT,
105+
size: 32,
106+
},
107+
},
108+
},
109+
{
110+
description: `should parse dynamic array types`,
111+
input: {
112+
typeName: 'uint256[]',
113+
types: {},
114+
},
115+
output: {
116+
data_type: Enum_EthereumDataType.ARRAY,
117+
size: undefined,
118+
entry_type: {
119+
data_type: Enum_EthereumDataType.UINT,
120+
size: 32,
121+
},
122+
},
123+
},
124+
{
125+
description: `should parse Struct types`,
126+
input: {
127+
typeName: 'ExampleStruct',
128+
types: {
129+
ExampleStruct: [
130+
{ name: 'message', type: 'string' },
131+
{ name: 'cost', type: 'int8' },
132+
],
133+
},
134+
},
135+
output: {
136+
data_type: Enum_EthereumDataType.STRUCT,
137+
size: 2,
138+
struct_name: 'ExampleStruct',
139+
},
140+
},
141+
{
142+
description: `should throw if Struct type not defined`,
143+
input: {
144+
typeName: 'MissingType',
145+
types: {
146+
// empty
147+
},
148+
},
149+
error: [TrezorError, 'No type definition specified: MissingType'],
150+
},
151+
];
152+
153+
export const encodeData = [
154+
{
155+
description: 'should remove leading `0x` from byte hex-string',
156+
input: {
157+
typeName: 'bytes',
158+
data: '0x123456789abcdef',
159+
},
160+
output: '123456789abcdef',
161+
},
162+
{
163+
description: 'should remove leading `0x` from Ethereum address',
164+
input: {
165+
typeName: 'address',
166+
data: '0x000000000000000000000000000000000000dead',
167+
},
168+
output: '000000000000000000000000000000000000dead',
169+
},
170+
// INTEGER CONVERSIONS
171+
{
172+
description: `should encode unsigned integers`,
173+
input: {
174+
typeName: 'uint8',
175+
data: 255,
176+
},
177+
output: 'ff',
178+
},
179+
{
180+
description: `should encode positive signed integers`,
181+
input: {
182+
typeName: 'int8',
183+
data: 127,
184+
},
185+
output: '7f',
186+
},
187+
{
188+
description: `should encode negative signed integers`,
189+
input: {
190+
typeName: 'int8',
191+
data: -1,
192+
},
193+
output: 'ff',
194+
},
195+
{
196+
description: `should encode int from bigint`,
197+
input: {
198+
typeName: 'int256',
199+
// `1n << 254n` instead of `2n ** 254n` since Babel replaces ** with Math.pow()
200+
data: -(1n << 254n) + 1n,
201+
},
202+
// Python (-(2 ** 254) + 1).to_bytes(32, "big", signed=True).hex()
203+
output: 'c000000000000000000000000000000000000000000000000000000000000001',
204+
},
205+
{
206+
description: `should encode int from string`,
207+
input: {
208+
typeName: 'int256',
209+
data: `${-(1n << 254n) + 1n}`,
210+
},
211+
// Python (-(2 ** 254) + 1).to_bytes(32, "big", signed=True).hex()
212+
output: 'c000000000000000000000000000000000000000000000000000000000000001',
213+
},
214+
{
215+
description: `should encode int from BigNumber`,
216+
input: {
217+
typeName: 'int256',
218+
data: new BigNumber(2).pow(254).negated().plus(1),
219+
},
220+
// Python (-(2 ** 254) + 1).to_bytes(32, "big", signed=True).hex()
221+
output: 'c000000000000000000000000000000000000000000000000000000000000001',
222+
},
223+
{
224+
description: `should throw overflow error when int too large`,
225+
input: {
226+
typeName: 'int8',
227+
data: 128,
228+
},
229+
error: [TrezorError, 'overflow'],
230+
},
231+
{
232+
description: 'should encode string as utf-8',
233+
input: {
234+
typeName: 'string',
235+
data: 'Trezor Model T is an amazing piece of hardware. 😋😋',
236+
},
237+
output: '5472657a6f72204d6f64656c205420697320616e20616d617a696e67207069656365206f662068617264776172652e20f09f988bf09f988b',
238+
},
239+
{
240+
description: 'should encode bool as a single byte',
241+
input: {
242+
typeName: 'bool',
243+
data: true,
244+
},
245+
output: '01',
246+
},
247+
{
248+
description: 'should encode bool as a single byte',
249+
input: {
250+
typeName: 'bool',
251+
data: false,
252+
},
253+
output: '00',
254+
},
255+
{
256+
description: 'should throw for array types',
257+
input: {
258+
typeName: 'bool[1]',
259+
data: [true],
260+
},
261+
error: [TrezorError, 'Unsupported'],
262+
},
263+
];
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { parseArrayType, encodeData, getFieldType } from '../ethereumSignTypedData';
2+
import * as fixtures from '../__fixtures__/ethereumSignTypedData';
3+
4+
describe('helpers/ethereumSignTypeData', () => {
5+
describe('parseArrayType', () => {
6+
fixtures.parseArrayType.forEach(f => {
7+
it(`${f.description} - ${f.input}`, () => {
8+
if (f.error) {
9+
expect(() => parseArrayType(f.input)).toThrowError(...f.error);
10+
} else {
11+
expect(parseArrayType(f.input)).toEqual(f.output);
12+
}
13+
});
14+
});
15+
});
16+
17+
describe('getFieldType', () => {
18+
fixtures.getFieldType.forEach(f => {
19+
const { typeName, types } = f.input;
20+
it(`${f.description} - ${typeName}`, () => {
21+
if (f.error) {
22+
expect(() => getFieldType(typeName, types)).toThrowError(...f.error);
23+
} else {
24+
expect(getFieldType(typeName, types)).toEqual(f.output);
25+
}
26+
});
27+
});
28+
});
29+
30+
describe('encodeData', () => {
31+
fixtures.encodeData.forEach(f => {
32+
const { typeName, data } = f.input;
33+
it(`${f.description}`, () => {
34+
if (f.error) {
35+
expect(() => encodeData(typeName, data)).toThrowError(...f.error);
36+
} else {
37+
expect(encodeData(typeName, data)).toEqual(f.output);
38+
}
39+
});
40+
});
41+
});
42+
});

src/js/core/methods/helpers/ethereumTypeDataConversions.js renamed to src/js/core/methods/helpers/ethereumSignTypedData.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,22 @@ export function parseArrayType(arrayTypeName: string): {
4343
*/
4444
function twosComplement(number: BigNumber, bytes: number): BigNumber {
4545
if (bytes < 1 || bytes > 32) {
46-
throw new Error('Int byte size must be between is 1 and 32');
46+
throw ERRORS.TypedError(
47+
'Method_InvalidParameter',
48+
'Int byte size must be between 1 and 32 (8 and 256 bits)',
49+
);
4750
}
4851
// Determine value range
4952
const minValue = new BigNumber(2).exponentiatedBy(bytes * 8 - 1).negated();
50-
const maxValue = minValue.negated().plus(1);
53+
const maxValue = minValue.negated().minus(1);
5154

5255
const bigNumber = new BigNumber(number);
5356

5457
if (bigNumber.isGreaterThan(maxValue) || bigNumber.isLessThan(minValue)) {
55-
throw new Error(`Overflow when trying to convert number ${number} into ${bytes} bytes`);
58+
throw ERRORS.TypedError(
59+
'Method_InvalidParameter',
60+
`Overflow when trying to convert number ${number} into ${bytes} bytes`,
61+
);
5662
}
5763

5864
if (bigNumber.isPositive()) {
@@ -74,7 +80,10 @@ function intToHex(
7480
const hex = bigNumber.toString(16);
7581
const hexChars = bytes * 2;
7682
if (hex.length > hexChars) {
77-
throw new Error(`Overflow when trying to convert number ${number} into ${bytes} bytes`);
83+
throw ERRORS.TypedError(
84+
'Method_InvalidParameter',
85+
`Overflow when trying to convert number ${number} into ${bytes} bytes`,
86+
);
7887
}
7988
return hex.padStart(bytes * 2, '0');
8089
}
@@ -174,5 +183,5 @@ export function getFieldType(
174183
};
175184
}
176185

177-
throw ERRORS.TypedError('Method_InvalidParameter', `Unsupported type name: ${typeName}`);
186+
throw ERRORS.TypedError('Method_InvalidParameter', `No type definition specified: ${typeName}`);
178187
}

0 commit comments

Comments
 (0)