Skip to content
This repository was archived by the owner on Oct 7, 2024. It is now read-only.

Commit a99ce3f

Browse files
aloisklinkalisinabhBrandonNoad
authored
Add EIP-712 signTypedData_v4 support (#117)
* Add EIP-712 signTypedData_v4 support for Model T Required updating trezor-connect to v8.2.5-extended * Call this.getModel() after this.unlock() There is potentially an issue the this.getModel() branch, see #117 (comment) I think this might be because the model information has not yet been loaded, so I've moved it to after the unlock, and added the current model in the Error message for debugging. * Fix failing test after EIP-712 model check moved * Load model if this.getModel() returns undefined Apparently this.getModel() sometimes returns `undefined`. If this happens, since we're in an `async` function, we can load the model using the official Trezor API. Co-authored-by: alisinabh <[email protected]> * Cleanup keyring while running tests Prevents NodeJS from printing a memory leak warning. * Adapt signTypedData to support Trezor one * Remove primaryType: EIP712Domain tests There is currently a mismatch between Metamask and Trezor connect in this case, so future versions of Trezor connect will most likely change this behaviour. See trezor/trezor-firmware#2036 * Fix linting issues in @alisinabh commit Fixes lint issues in f368848 * Update trezor-connect to 8.2.6-extended This should add EIP-712 support for Model 1 too. * Fix incorrect EIP-712 hash passing for Model 1 The domain and message hash do not go into the data object, see #117 (comment) Co-authored-by: Brandon Noad <[email protected]> Co-authored-by: alisinabh <[email protected]> Co-authored-by: Brandon Noad <[email protected]>
1 parent c86116c commit a99ce3f

File tree

5 files changed

+332
-145
lines changed

5 files changed

+332
-145
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- Support for EIP-721 signTypedData_v4 ([#117](https://github.com/MetaMask/eth-trezor-keyring/pull/117))
810

911
## [0.9.1]
1012
### Changed

index.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const ethUtil = require('ethereumjs-util');
33
const HDKey = require('hdkey');
44
const TrezorConnect = require('trezor-connect').default;
55
const { TransactionFactory } = require('@ethereumjs/tx');
6+
const transformTypedData = require('trezor-connect/lib/plugins/ethereum/typedData');
67

78
const hdPathString = `m/44'/60'/0'/0`;
89
const SLIP0044TestnetPath = `m/44'/1'/0'/0`;
@@ -67,6 +68,12 @@ class TrezorKeyring extends EventEmitter {
6768
TrezorConnect.init({ manifest: TREZOR_CONNECT_MANIFEST });
6869
}
6970

71+
/**
72+
* Gets the model, if known.
73+
* This may be `undefined` if the model hasn't been loaded yet.
74+
*
75+
* @returns {"T" | "1" | undefined}
76+
*/
7077
getModel() {
7178
return this.model;
7279
}
@@ -384,9 +391,53 @@ class TrezorKeyring extends EventEmitter {
384391
});
385392
}
386393

387-
signTypedData() {
388-
// Waiting on trezor to enable this
389-
return Promise.reject(new Error('Not supported on this device'));
394+
/**
395+
* EIP-712 Sign Typed Data
396+
*/
397+
async signTypedData(address, data, { version }) {
398+
const dataWithHashes = transformTypedData(data, version === 'V4');
399+
400+
// set default values for signTypedData
401+
// Trezor is stricter than @metamask/eth-sig-util in what it accepts
402+
const {
403+
types: { EIP712Domain = [], ...otherTypes } = {},
404+
message = {},
405+
domain = {},
406+
primaryType,
407+
// snake_case since Trezor uses Protobuf naming conventions here
408+
domain_separator_hash, // eslint-disable-line camelcase
409+
message_hash, // eslint-disable-line camelcase
410+
} = dataWithHashes;
411+
412+
// This is necessary to avoid popup collision
413+
// between the unlock & sign trezor popups
414+
const status = await this.unlock();
415+
await wait(status === 'just unlocked' ? DELAY_BETWEEN_POPUPS : 0);
416+
417+
const response = await TrezorConnect.ethereumSignTypedData({
418+
path: this._pathFromAddress(address),
419+
data: {
420+
types: { EIP712Domain, ...otherTypes },
421+
message,
422+
domain,
423+
primaryType,
424+
},
425+
metamask_v4_compat: true,
426+
// Trezor 1 only supports blindly signing hashes
427+
domain_separator_hash,
428+
message_hash,
429+
});
430+
431+
if (response.success) {
432+
if (ethUtil.toChecksumAddress(address) !== response.payload.address) {
433+
throw new Error('signature doesnt match the right address');
434+
}
435+
return response.payload.signature;
436+
}
437+
438+
throw new Error(
439+
(response.payload && response.payload.error) || 'Unknown error',
440+
);
390441
}
391442

392443
exportAccount() {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@
3535
},
3636
"dependencies": {
3737
"@ethereumjs/tx": "^3.2.1",
38+
"@metamask/eth-sig-util": "^4.0.0",
3839
"ethereumjs-util": "^7.0.9",
3940
"hdkey": "0.8.0",
40-
"trezor-connect": "8.2.3-extended"
41+
"trezor-connect": "8.2.6-extended"
4142
},
4243
"devDependencies": {
4344
"@ethereumjs/common": "^2.4.0",

test/test-eth-trezor-keyring.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ describe('TrezorKeyring', function () {
108108
});
109109

110110
afterEach(function () {
111+
if (keyring) {
112+
keyring.dispose();
113+
}
111114
sinon.restore();
112115
});
113116

@@ -556,16 +559,52 @@ describe('TrezorKeyring', function () {
556559
});
557560

558561
describe('signTypedData', function () {
559-
it('should throw an error because it is not supported', async function () {
562+
it('should throw an error on signTypedData_v3 because it is not supported', async function () {
560563
let error = null;
561564
try {
562-
await keyring.signTypedData();
565+
await keyring.signTypedData(null, null, { version: 'V3' });
563566
} catch (e) {
564567
error = e;
565568
}
566569

567-
expect(error instanceof Error, true);
568-
expect(error.toString(), 'Not supported on this device');
570+
expect(error).to.be.an.instanceof(Error);
571+
expect(error.toString()).to.contain(
572+
'Only version 4 of typed data signing is supported',
573+
);
574+
});
575+
576+
it('should call TrezorConnect.ethereumSignTypedData', async function () {
577+
sinon
578+
.stub(TrezorConnect, 'ethereumSignTypedData')
579+
.callsFake(async () => ({
580+
success: true,
581+
payload: { signature: '0x00', address: fakeAccounts[0] },
582+
}));
583+
584+
this.timeout = 60000;
585+
await keyring.signTypedData(
586+
fakeAccounts[0],
587+
// Message with missing data that @metamask/eth-sig-util accepts
588+
{ types: { EmptyMessage: [] }, primaryType: 'EmptyMessage' },
589+
{ version: 'V4' },
590+
);
591+
592+
assert(TrezorConnect.ethereumSignTypedData.calledOnce);
593+
sinon.assert.calledWithExactly(TrezorConnect.ethereumSignTypedData, {
594+
path: "m/44'/60'/0'/0/0",
595+
data: {
596+
// Empty message that trezor-connect/EIP-712 spec accepts
597+
types: { EIP712Domain: [], EmptyMessage: [] },
598+
primaryType: 'EmptyMessage',
599+
domain: {},
600+
message: {},
601+
},
602+
metamask_v4_compat: true,
603+
domain_separator_hash:
604+
'6192106f129ce05c9075d319c1fa6ea9b3ae37cbd0c1ef92e2be7137bb07baa1',
605+
message_hash:
606+
'c9e71eb57cf9fa86ec670283b58cb15326bb6933c8d8e2ecb2c0849021b3ef42',
607+
});
569608
});
570609
});
571610

0 commit comments

Comments
 (0)