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

Serialization & Deserialization Fixes #172

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"@ethereumjs/tx": "^4.0.0",
"@ethereumjs/util": "^8.0.0",
"@metamask/eth-sig-util": "^5.0.2",
"@metamask/utils": "^4.0.0",
"@trezor/connect-plugin-ethereum": "^9.0.1",
"@trezor/connect-web": "^9.0.6",
"hdkey": "0.8.0"
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './trezor-keyring';
export * from './trezor-bridge';
export * from './trezor-connect-bridge';
43 changes: 43 additions & 0 deletions src/trezor-bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type {
ConnectSettings,
EthereumSignedTx,
Manifest,
PROTO,
Response,
Params,
EthereumSignMessage,
EthereumSignTransaction,
EthereumSignTypedDataTypes,
EthereumSignTypedHash,
} from '@trezor/connect-web';

export interface TrezorBridge {
model?: string;

init(
settings: {
manifest: Manifest;
} & Partial<ConnectSettings>,
): Promise<void>;

dispose(): Promise<void>;

// TrezorConnect.getPublicKey has two overloads
// It is not possible to extract them from the library using utility types
getPublicKey(params: {
path: string;
coin: string;
}): Response<{ publicKey: string; chainCode: string }>;

ethereumSignTransaction(
params: Params<EthereumSignTransaction>,
): Response<EthereumSignedTx>;

ethereumSignMessage(
params: Params<EthereumSignMessage>,
): Response<PROTO.MessageSignature>;

ethereumSignTypedData<T extends EthereumSignTypedDataTypes>(
params: Params<EthereumSignTypedHash<T>>,
): Response<PROTO.EthereumTypedDataSignature>;
}
154 changes: 154 additions & 0 deletions src/trezor-connect-bridge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import * as sinon from 'sinon';
import chai from 'chai';

import TrezorConnect, { DEVICE_EVENT } from '@trezor/connect-web';

import { TrezorConnectBridge } from './trezor-connect-bridge';
import { TrezorBridge } from './trezor-bridge';
import { TREZOR_CONNECT_MANIFEST } from './trezor-keyring';

describe('TrezorConnectBridge', function () {
let bridge: TrezorBridge;

beforeEach(function () {
bridge = new TrezorConnectBridge();
});

afterEach(function () {
sinon.restore();
});

describe('init', function () {
it('sets the event listener and calls init', async function () {
const onStub = sinon.stub(TrezorConnect, 'on');
const initStub = sinon.stub(TrezorConnect, 'init');

await bridge.init({
manifest: TREZOR_CONNECT_MANIFEST,
lazyLoad: true,
});

sinon.assert.calledOnce(onStub);
sinon.assert.calledWithExactly(
onStub,
DEVICE_EVENT as any,
sinon.match.func,
);

sinon.assert.calledOnce(initStub);
sinon.assert.calledWithExactly(initStub, {
manifest: TREZOR_CONNECT_MANIFEST,
lazyLoad: true,
});
});

it('is executed once', async function () {
const initStub = sinon.stub(TrezorConnect, 'init');

chai.expect((bridge as any).trezorConnectInitiated).to.equal(false);

await bridge.init({
manifest: TREZOR_CONNECT_MANIFEST,
lazyLoad: true,
});

chai.expect((bridge as any).trezorConnectInitiated).to.equal(true);

// try to re-initialize
await bridge.init({
manifest: TREZOR_CONNECT_MANIFEST,
lazyLoad: true,
});

// underlying init should only be called once
sinon.assert.calledOnce(initStub);
});
});

describe('dispose', function () {
it('calls dispose', async function () {
const disposeStub = sinon.stub(TrezorConnect, 'dispose');

await bridge.dispose();

sinon.assert.calledOnce(disposeStub);
sinon.assert.calledWithExactly(disposeStub);
});
});

describe('getPublicKey', function () {
it('calls getPublicKey', async function () {
const getPublicKeyStub = sinon.stub(TrezorConnect, 'getPublicKey');

const params = {
path: `m/44'/60'/0'/0`,
coin: 'ETH',
} as any;
await bridge.getPublicKey(params);

sinon.assert.calledOnce(getPublicKeyStub);
sinon.assert.calledWithExactly(getPublicKeyStub, params);
});
});

describe('ethereumSignTransaction', function () {
it('calls ethereumSignTransaction', async function () {
const ethereumSignTransactionStub = sinon.stub(
TrezorConnect,
'ethereumSignTransaction',
);

const params: any = {
path: `m/44'/60'/0'/0`,
transaction: {
chainId: 1,
to: '0x0',
},
};
await bridge.ethereumSignTransaction(params);

sinon.assert.calledOnce(ethereumSignTransactionStub);
sinon.assert.calledWithExactly(ethereumSignTransactionStub, params);
});
});

describe('ethereumSignMessage', function () {
it('calls ethereumSignMessage', async function () {
const ethereumSignMessageStub = sinon.stub(
TrezorConnect,
'ethereumSignMessage',
);

const params = {
path: `m/44'/60'/0'/0`,
message: '',
hex: true,
};
await bridge.ethereumSignMessage(params);

sinon.assert.calledOnce(ethereumSignMessageStub);
sinon.assert.calledWithExactly(ethereumSignMessageStub, params);
});
});

describe('ethereumSignTypedData', function () {
it('calls ethereumSignTypedData', async function () {
const ethereumSignTypedDataStub = sinon.stub(
TrezorConnect,
'ethereumSignTypedData',
);

const params = {
path: `m/44'/60'/0'/0`,
data: {},
metamask_v4_compat: true,
domain_separator_hash: '',
message_hash: '',
} as any;
await bridge.ethereumSignTypedData(params);

sinon.assert.calledOnce(ethereumSignTypedDataStub);
sinon.assert.calledWithExactly(ethereumSignTypedDataStub, params);
});
});
});
64 changes: 64 additions & 0 deletions src/trezor-connect-bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import TrezorConnect, { DEVICE_EVENT, DEVICE } from '@trezor/connect-web';
import type {
Manifest,
ConnectSettings,
EthereumSignTransaction,
Params,
EthereumSignMessage,
EthereumSignTypedDataTypes,
EthereumSignTypedHash,
} from '@trezor/connect-web';
import type { TrezorBridge } from './trezor-bridge';

export class TrezorConnectBridge implements TrezorBridge {
model?: string;

trezorConnectInitiated = false;

async init(
settings: {
manifest: Manifest;
} & Partial<ConnectSettings>,
) {
TrezorConnect.on(DEVICE_EVENT, (event) => {
if (event.type !== DEVICE.CONNECT) {
return;
}

this.model = event.payload.features?.model;
});

if (this.trezorConnectInitiated) {
return;
}

await TrezorConnect.init(settings);
this.trezorConnectInitiated = true;
}

dispose() {
// This removes the Trezor Connect iframe from the DOM
// This method is not well documented, but the code it calls can be seen
// here: https://github.com/trezor/connect/blob/dec4a56af8a65a6059fb5f63fa3c6690d2c37e00/src/js/iframe/builder.js#L181
TrezorConnect.dispose();
return Promise.resolve();
}

getPublicKey(params: { path: string; coin: string }) {
return TrezorConnect.getPublicKey(params);
}

ethereumSignTransaction(params: Params<EthereumSignTransaction>) {
return TrezorConnect.ethereumSignTransaction(params);
}

ethereumSignMessage(params: Params<EthereumSignMessage>) {
return TrezorConnect.ethereumSignMessage(params);
}

ethereumSignTypedData<T extends EthereumSignTypedDataTypes>(
params: Params<EthereumSignTypedHash<T>>,
) {
return TrezorConnect.ethereumSignTypedData(params);
}
}
Loading