Skip to content

Feat: TrezorHostProtocol #13542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .github/workflows/template-connect-test-params.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ jobs:
run: sed -i "/\"node\"/d" package.json
- if: ${{ inputs.testEnv == 'node' }}
run: yarn workspaces focus @trezor/connect
run: yarn install
run: yarn workspace @trezor/transport-bridge build:js

- if: ${{ inputs.disable_cache_tx == 'true' }}
run: echo "ADDITIONAL_ARGS=-c" >> "$GITHUB_ENV"
Expand Down
4 changes: 3 additions & 1 deletion docker/docker-compose.connect-test.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
version: "3.9"
services:
trezor-user-env-unix:
image: ghcr.io/trezor/trezor-user-env:ee7f945dc2cc47e0a5776f3f86dfe5b70455a725
image: ghcr.io/trezor/trezor-user-env:d029907396738bfff46cc58990aa503140f95f9a@sha256:f83c29a9b882aaf530c7b6f07aafabd650299213d438a70e93bcc4f4d634901e
environment:
- SDL_VIDEODRIVER=dummy
- XDG_RUNTIME_DIR=/var/tmp
volumes:
- ../:/trezor-suite
network_mode: host
15 changes: 15 additions & 0 deletions packages/connect-common/files/firmware/t2t1/releases.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
[
{
"required": false,
"version": [2, 8, 99],
"min_firmware_version": [2, 0, 8],
"min_bootloader_version": [2, 0, 0],
"bootloader_version": [2, 1, 8],
"firmware_revision": "d4127aab5d41f6310d5b3f0156e712b23614acfb",
"translations": ["cs-CZ", "de-DE", "es-ES", "fr-FR", "it-IT", "pt-BR"],
"url": "firmware/t2t1/trezor-t2t1-2.8.99-bitcoinonly.bin",
"fingerprint": "f4888bed3e75205464910f3956d1f3ad19bb73e093b31c9141a66226a7081990",
"changelog": "",
"url_bitcoinonly": "firmware/t2t1/trezor-t2t1-2.8.99-bitcoinonly.bin",
"fingerprint_bitcoinonly": "8abd380fa01e1294ccde3e6a5fd25b15f57104859b5d6c4a2534d5feb824993e",
"changelog_bitcoinonly": ""
},
{
"required": false,
"version": [2, 8, 9],
Expand Down
Git LFS file not shown
1 change: 1 addition & 0 deletions packages/connect-iframe/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ const shouldUiEventBeSentToHost = (message: CoreEventMessage) => {
DEVICE.CONNECT,
DEVICE.CONNECT_UNACQUIRED,
DEVICE.CHANGED,
DEVICE.TRANSPORT_STATE_CHANGED,
DEVICE.DISCONNECT,
DEVICE.BUTTON,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export default {
},
{
// https://github.com/trezor/trezor-firmware/pull/2703/
rules: ['<2.8.2'],
rules: ['<2.9.9'],
success: false,
},
],
Expand Down
23 changes: 19 additions & 4 deletions packages/connect/e2e/common.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,11 @@ export const setup = async (
TrezorUserEnvLink.state = options;

// after all is done, start bridge again
await TrezorUserEnvLink.startBridge(
// @ts-expect-error
process.env.TESTS_TRANSPORT,
);
// await TrezorUserEnvLink.startBridge(
// // @ts-expect-error
// process.env.TESTS_TRANSPORT,
// );
await TrezorUserEnvLink.startBridge('node-bridge');
};

type InitParams = Partial<Parameters<typeof TrezorConnect.init>[0]> & { autoConfirm?: boolean };
Expand Down Expand Up @@ -171,17 +172,31 @@ export const initTrezorConnect = async (
});
}

// TrezorConnect.on('ui-request_passphrase', () => {
// TrezorConnect.uiResponse({
// type: 'ui-receive_passphrase',
// payload: { value: '' },
// });
// });

await TrezorConnect.init({
manifest: {
appUrl: 'tests.connect.trezor.io',
email: '[email protected]',
},
transports: ['BridgeTransport'],
// transports: ['UdpTransport'],
debug: false,
popup: false,
pendingTransportEvent: true,
transportReconnect: false,
connectSrc: process.env.TREZOR_CONNECT_SRC, // custom source for karma tests
thp: {
hostName: 'TrezorConnect',
staticKeys: '0007070707070707070707070707070707070707070707070707070707070747',
knownCredentials: [],
pairingMethods: ['NoMethod'] as any,
},
...options,
});
};
Expand Down
151 changes: 151 additions & 0 deletions packages/connect/e2e/tests/device/thpPairing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import TrezorConnect, { ConnectSettings, Device } from '../../../src';
import { getController, initTrezorConnect, setup } from '../../common.setup';

describe('THP pairing', () => {
const controller = getController();

beforeAll(async () => {
await setup(controller, { mnemonic: 'mnemonic_all' });
});

afterEach(() => {
TrezorConnect.dispose();
});

const waitForDevice = async (settings: Partial<ConnectSettings['thp']>) => {
await initTrezorConnect(controller, {
thp: {
hostName: 'TrezorConnect tests:e2e',
staticKeys: '0007070707070707070707070707070707070707070707070707070707070747',
knownCredentials: [],
pairingMethods: [],
...settings,
},
});

return new Promise<Device>((resolve, reject) => {
const onDeviceConnected = (device: Device) => {
TrezorConnect.removeAllListeners('device-connect');
TrezorConnect.removeAllListeners('device-connect_unacquired');
if (device.type === 'unreadable') {
reject(new Error('Device unreadable'));
} else {
resolve(device);
}
};
TrezorConnect.on('device-connect', onDeviceConnected);
TrezorConnect.on('device-connect_unacquired', onDeviceConnected);
});
};

it('ThpPairing SkipPairing', async () => {
const spy = jest.fn();
const device = await waitForDevice({ pairingMethods: ['SkipPairing'] });
TrezorConnect.on('ui-request_thp_pairing', spy);

const address = await TrezorConnect.getAddress({
device,
path: "m/44'/0'/0'/1/1",
showOnTrezor: true,
});
expect(address).toMatchObject({ success: true });
expect(spy).toHaveBeenCalledTimes(0);
});

it('ThpPairing CodeEntry', async () => {
const device = await waitForDevice({ pairingMethods: ['CodeEntry'] });

// eslint-disable-next-line @typescript-eslint/no-shadow
TrezorConnect.on('ui-request_thp_pairing', async ({ device }) => {
const state = await controller.getPairingInfo(device.thp!.state.channel);
TrezorConnect.removeAllListeners('ui-request_thp_pairing');
TrezorConnect.uiResponse({
type: 'ui-receive_thp_pairing_tag',
payload: {
source: 'code-entry',
tag: state.code_entry_code,
},
});
});

const address = await TrezorConnect.getAddress({
device,
path: "m/44'/0'/0'/1/1",
// showOnTrezor: true,
});
expect(address).toMatchObject({ success: true });
});

it('ThpPairing QrCode', async () => {
const device = await waitForDevice({ pairingMethods: ['QrCode'] });

// eslint-disable-next-line @typescript-eslint/no-shadow
TrezorConnect.on('ui-request_thp_pairing', async ({ device }) => {
const state = await controller.getPairingInfo(device.thp!.state.channel);
TrezorConnect.removeAllListeners('ui-request_thp_pairing');
TrezorConnect.uiResponse({
type: 'ui-receive_thp_pairing_tag',
payload: { source: 'qr-code', tag: state.code_qr_code },
});
});

const address = await TrezorConnect.getAddress({
device,
path: "m/44'/0'/0'/1/1",
// showOnTrezor: true,
});
expect(address).toMatchObject({ success: true });
});

it('ThpPairing NFC', async () => {
const device = await waitForDevice({ pairingMethods: ['NFC'] });

// eslint-disable-next-line @typescript-eslint/no-shadow
TrezorConnect.on('ui-request_thp_pairing', async ({ device, nfcData }) => {
// await new Promise(resolve => setTimeout(resolve, 1000));
const state = await controller.getPairingInfo(device.thp!.state.channel, nfcData);
TrezorConnect.removeAllListeners('ui-request_thp_pairing');
TrezorConnect.uiResponse({
type: 'ui-receive_thp_pairing_tag',
payload: { source: 'nfc', tag: state.nfc_secret_trezor },
});
});

const address = await TrezorConnect.getAddress({
device,
path: "m/44'/0'/0'/1/1",
// showOnTrezor: true,
});
expect(address).toMatchObject({ success: true });
});

it('ThpPairing no matching method. device unreadable', async () => {
const device = await waitForDevice({
pairingMethods: ['FooBar', undefined, 1234, null, {}] as any,
});

// TODO: expect(device.type).toEqual('unreadable');
expect(device.type).toEqual('unacquired');
});

it('ThpPairing using known credentials', async () => {
const device = await waitForDevice({
pairingMethods: ['CodeEntry'],
knownCredentials: [
{
trezor_static_pubkey:
'38d6437ef1d67a4742265281de1e9a68df28774636f34b5e3e336d3ab90e671c',
credential:
'0a0f0a0d5472657a6f72436f6e6e6563741220f53793b13dffe2a4f01c2c7272aecc75b8596cf0fce4b09efd4fb353696a179b',
},
],
});

const address = await TrezorConnect.getAddress({
device,
path: "m/44'/0'/0'/1/1",
showOnTrezor: true,
});
expect(address).toMatchObject({ success: true });
});
});
28 changes: 28 additions & 0 deletions packages/connect/src/api/getThpCredentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Assert } from '@trezor/schema-utils';

import { PROTO } from '../constants';
import { AbstractMethod } from '../core/AbstractMethod';
import { UI } from '../events';

export default class GetThpCredentials extends AbstractMethod<'getThpCredentials'> {
init() {
this.allowDeviceMode = [UI.INITIALIZE, UI.SEEDLESS];
this.requiredPermissions = ['management'];
this.useDeviceState = false;
this.skipFinalReload = true;

const { payload } = this;

console.warn(PROTO.Address);

Assert(PROTO.GetFeatures, payload);
}

async run() {
const cmd = this.device.getCommands();
// @ts-expect-error
const response = await cmd.typedCall('BleUnpair', 'Success', this.params);

return response.message as any;
}
}
1 change: 1 addition & 0 deletions packages/connect/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export { default as getOwnershipId } from './getOwnershipId';
export { default as getOwnershipProof } from './getOwnershipProof';
export { default as getPublicKey } from './getPublicKey';
export { default as getSettings } from './getSettings';
export { default as getThpCredentials } from './getThpCredentials';
// export { default as init } from './init';
// export { default as manifest } from './manifest';
// export { default as off } from './off';
Expand Down
3 changes: 3 additions & 0 deletions packages/connect/src/constants/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const ERROR_CODES = {
Device_MultipleNotSupported: 'Multiple devices are not supported', // thrown by methods which require single device
Device_MissingCapability: 'Device is missing capability', // thrown by methods which require specific capability
Device_MissingCapabilityBtcOnly: 'Device is missing capability (BTC only)', // thrown by methods which require specific capability when using BTC only firmware
Device_ThpStateMissing: 'Pairing failed', // thrown by thp related actions
Device_ThpPairingFailed: 'Pairing failed', // thrown by ...
Device_ThpPairingTagInvalid: 'Pairing tag mismatch', // thrown by ...

Failure_ActionCancelled: 'Action cancelled by user',
Failure_FirmwareError: 'Firmware installation failed',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ const setupTest = () => {
registerEvents: () => {},
log: new Log('Test', false),
abortSignal: new AbortController().signal,
uiPromises: { create: jest.fn() },
};

return {
Expand Down
Loading
Loading