Skip to content
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

Feat: TrezorHostProtocol #13542

Draft
wants to merge 15 commits into
base: develop
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
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 @@ -87,6 +87,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.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:36dfd1174f56dde0b0b85b3acd927bfda4a63043
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
1 change: 1 addition & 0 deletions packages/connect-iframe/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,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 @@ -119,10 +119,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');
};

export const initTrezorConnect = async (
Expand Down Expand Up @@ -164,17 +165,31 @@ export const initTrezorConnect = async (
setTimeout(() => TrezorUserEnvLink.send({ type: 'emulator-press-yes' }), 1);
});

// 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
142 changes: 142 additions & 0 deletions packages/connect/e2e/tests/device/thpPairing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { getController, initTrezorConnect, setup } from '../../common.setup';
import TrezorConnect, { ConnectSettings, Device } from '../../../src';

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',
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'));
}
resolve(device);
};
TrezorConnect.on('device-connect', onDeviceConnected);
TrezorConnect.on('device-connect_unacquired', onDeviceConnected);
});
};

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

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

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

TrezorConnect.on('ui-request_thp_pairing', async ({ device }) => {

Check failure on line 54 in packages/connect/e2e/tests/device/thpPairing.test.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

'device' is already declared in the upper scope on line 52 column 15
const state = await controller.getDebugState(device.protocolState.channel);
TrezorConnect.removeAllListeners('ui-request_thp_pairing');
TrezorConnect.uiResponse({
type: 'ui-receive_thp_pairing_tag',
payload: {
source: 'code-entry',
value: state.thp_pairing_code_entry_code.toString(),
},
});
});

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'] });

TrezorConnect.on('ui-request_thp_pairing', async ({ device }) => {

Check failure on line 77 in packages/connect/e2e/tests/device/thpPairing.test.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

'device' is already declared in the upper scope on line 75 column 15
const state = await controller.getDebugState(device.protocolState.channel);
TrezorConnect.removeAllListeners('ui-request_thp_pairing');
TrezorConnect.uiResponse({
type: 'ui-receive_thp_pairing_tag',
payload: { source: 'qr-code', value: state.thp_pairing_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_Unidirectional', async () => {
const device = await waitForDevice({ pairingMethods: ['NFC_Unidirectional'] });

TrezorConnect.on('ui-request_thp_pairing', async ({ device }) => {

Check failure on line 97 in packages/connect/e2e/tests/device/thpPairing.test.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

'device' is already declared in the upper scope on line 95 column 15
// await new Promise(resolve => setTimeout(resolve, 1000));
const state = await controller.getDebugState(device.protocolState.channel);
TrezorConnect.removeAllListeners('ui-request_thp_pairing');
TrezorConnect.uiResponse({
type: 'ui-receive_thp_pairing_tag',
payload: { source: 'nfc', value: state.thp_pairing_code_nfc_unidirectional },
});
});

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,
});
expect(device.type).toEqual('unreadable');
});

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 });
});
});
36 changes: 31 additions & 5 deletions packages/connect/src/api/resetDevice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// origin: https://github.com/trezor/connect/blob/develop/src/js/core/methods/ResetDevice.js

import { Assert } from '@trezor/schema-utils';
// import { Assert } from '@trezor/schema-utils';

import { AbstractMethod } from '../core/AbstractMethod';
import { UI } from '../events';
Expand All @@ -16,7 +16,7 @@

const { payload } = this;
// validate bundle type
Assert(PROTO.ResetDevice, payload);
// Assert(PROTO.ResetDevice, payload);

this.params = {
strength: payload.strength || 256,
Expand All @@ -43,9 +43,35 @@
}

async run() {
const cmd = this.device.getCommands();
const response = await cmd.typedCall('ResetDevice', 'Success', this.params);
// const cmd = this.device.getCommands();
// const response = await cmd.typedCall('ResetDevice', 'Success', this.params);
console.log('----> running reset function!');

Check warning on line 48 in packages/connect/src/api/resetDevice.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Unexpected console statement
const response = await this.device.transport.call({
session: this.device.getLocalSession()!,
name: 'LoadDevice',
data: {
pin: '',
label: 'THP device',
passphrase_protection: true,
mnemonics: ['all all all all all all all all all all all all'],
skip_checksum: true,
},
protocol: this.device.protocol,
protocolState: this.device.protocolState,
});

return response.message;
if (response.success && response.payload.type === 'ButtonRequest') {
return this.device.transport.call({
session: this.device.getLocalSession()!,
name: 'ButtonAck',
data: {},
protocol: this.device.protocol,
protocolState: this.device.protocolState,
});
}

console.log('----> end reset function!', response);

Check warning on line 73 in packages/connect/src/api/resetDevice.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Unexpected console statement

return response as any;
}
}
53 changes: 40 additions & 13 deletions packages/connect/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,11 @@
const getInvalidDeviceState = async (
{ sendCoreMessage }: CoreContext,
device: Device,
preauthorized?: boolean,
method: AbstractMethod<any>,
): Promise<StaticSessionId | undefined> => {
for (let i = 0; i < MAX_PIN_TRIES - 1; ++i) {
try {
return await device.validateState(preauthorized);
return await device.validateState(method.preauthorized, method.useCardanoDerivation);
} catch (error) {
if (error.message.includes('PIN invalid')) {
sendCoreMessage(
Expand All @@ -207,7 +207,7 @@
}
}

return device.validateState(preauthorized);
return device.validateState(method.preauthorized, method.useCardanoDerivation);
};

/**
Expand Down Expand Up @@ -376,11 +376,7 @@
const isDeviceUnlocked = device.features.unlocked;
if (method.useDeviceState) {
try {
let invalidDeviceState = await getInvalidDeviceState(
context,
device,
method.preauthorized,
);
let invalidDeviceState = await getInvalidDeviceState(context, device, method);
if (isUsingPopup) {
while (invalidDeviceState) {
const uiPromise = uiPromises.create(UI.INVALID_PASSPHRASE_ACTION, device);
Expand All @@ -397,11 +393,7 @@
device.setState({ sessionId: undefined });
await device.initialize(method.useCardanoDerivation);

invalidDeviceState = await getInvalidDeviceState(
context,
device,
method.preauthorized,
);
invalidDeviceState = await getInvalidDeviceState(context, device, method);
} else {
// set new state as requested
device.setState({ staticSessionId: invalidDeviceState });
Expand Down Expand Up @@ -651,6 +643,11 @@
createUiMessage(UI.REQUEST_PASSPHRASE_ON_DEVICE, { device: device.toMessageObject() }),
);
});
device.on(DEVICE.THP_PAIRING, onThpPairingHandler(context));
device.on(DEVICE.TRANSPORT_STATE_CHANGED, () => {
postMessage(createDeviceMessage(DEVICE.TRANSPORT_STATE_CHANGED, device.toMessageObject()));
});

if (useCoreInPopup && env === 'webextension' && origin) {
device.initStorage(new WebextensionStateStorage(origin));
}
Expand Down Expand Up @@ -896,6 +893,31 @@
callback({ value: '' });
};

const onThpPairingHandler =
(context: CoreContext): DeviceEvents['thp_pairing'] =>
async (...[device, callback]) => {
const { uiPromises, sendCoreMessage } = context;
// wait for popup handshake
await waitForPopup(context);
// create ui promise
const uiPromise = uiPromises.create(UI.RECEIVE_THP_PAIRING_TAG, device);
sendCoreMessage(
createUiMessage(UI.REQUEST_THP_PAIRING, {
device: device.toMessageObject(),
type: device.protocolState.handshakeCredentials?.pairingMethods || [],
}),
);
// wait for response
try {
const uiResp = await uiPromise.promise;
console.log('RECEIVED THP TAG', uiResp);

Check warning on line 913 in packages/connect/src/core/index.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Unexpected console statement
callback(uiResp.payload);
} catch (error) {
console.log('RECEIVED THP TAG error', error);

Check warning on line 916 in packages/connect/src/core/index.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Unexpected console statement
callback(null, error);
}
};

/**
* Handle popup closed by user.
* @returns {void}
Expand Down Expand Up @@ -1011,6 +1033,10 @@
sendCoreMessage(createTransportMessage(TRANSPORT.START, transportType)),
);

deviceList.on(DEVICE.TRANSPORT_STATE_CHANGED, device => {
postMessage(createDeviceMessage(DEVICE.TRANSPORT_STATE_CHANGED, device));
});

deviceList.on(TRANSPORT.ERROR, error => {
_log.warn('TRANSPORT.ERROR', error);
sendCoreMessage(createTransportMessage(TRANSPORT.ERROR, { error }));
Expand Down Expand Up @@ -1126,6 +1152,7 @@
case UI.RECEIVE_PIN:
case UI.RECEIVE_PASSPHRASE:
case UI.INVALID_PASSPHRASE_ACTION:
case UI.RECEIVE_THP_PAIRING_TAG:
case UI.RECEIVE_ACCOUNT:
case UI.RECEIVE_FEE:
case UI.RECEIVE_WORD:
Expand Down
Loading
Loading