refactor(protocol): use protocol-v2 constants in protocol-thp

This commit is contained in:
Szymon Lesisz
2025-12-08 12:19:55 +01:00
committed by Szymon Lesisz
parent c149719480
commit 27668ed095
4 changed files with 35 additions and 82 deletions

View File

@@ -1,15 +1,3 @@
export const THP_CREATE_CHANNEL_REQUEST = 0x40;
export const THP_CREATE_CHANNEL_RESPONSE = 0x41;
export const THP_HANDSHAKE_INIT_REQUEST = 0x00;
export const THP_HANDSHAKE_INIT_RESPONSE = 0x01;
export const THP_HANDSHAKE_COMPLETION_REQUEST = 0x02;
export const THP_HANDSHAKE_COMPLETION_RESPONSE = 0x03;
export const THP_ERROR_HEADER_BYTE = 0x42;
export const THP_READ_ACK_HEADER_BYTE = 0x20; // [0x20, 0x30];
export const THP_CONTROL_BYTE_ENCRYPTED = 0x04; // [0x04, 0x14];
export const THP_CONTROL_BYTE_DECRYPTED = 0x05; // [0x05, 0x15];
export const THP_CONTINUATION_PACKET = 0x80;
export const THP_DEFAULT_CHANNEL = Buffer.from([0xff, 0xff]);
export const CRC_LENGTH = 4;

View File

@@ -1,16 +1,7 @@
import { ThpState } from './ThpState';
import {
CRC_LENGTH,
TAG_LENGTH,
THP_CONTROL_BYTE_DECRYPTED,
THP_CONTROL_BYTE_ENCRYPTED,
THP_CREATE_CHANNEL_RESPONSE,
THP_ERROR_HEADER_BYTE,
THP_HANDSHAKE_COMPLETION_RESPONSE,
THP_HANDSHAKE_INIT_RESPONSE,
THP_READ_ACK_HEADER_BYTE,
} from './constants';
import { CRC_LENGTH, TAG_LENGTH } from './constants';
import { aesgcm } from './crypto';
import { THP_CONTROL_BYTE } from '../protocol-v2/constants';
import { TransportProtocolDecode } from '../types';
import { crc32 } from './crypto/crc32';
import { getHandshakeHash, getTrezorState } from './crypto/pairing';
@@ -189,10 +180,10 @@ export const decodeSendAck = (decodedMessage: MessageV2) => {
validateCrc(decodedMessage);
const { magic } = readThpHeader(decodedMessage.header);
if (magic === THP_ERROR_HEADER_BYTE) {
if (magic === THP_CONTROL_BYTE.ERROR) {
return decodeThpError(decodedMessage.payload);
}
if (magic === THP_READ_ACK_HEADER_BYTE) {
if (magic === THP_CONTROL_BYTE.ACK_MESSAGE) {
return decodeReadAck();
}
};
@@ -217,32 +208,27 @@ export const decode = (
};
const { magic } = header;
if (magic === THP_ERROR_HEADER_BYTE) {
if (magic === THP_CONTROL_BYTE.ERROR) {
return decodeThpError(message.payload);
}
if (magic === THP_READ_ACK_HEADER_BYTE) {
if (magic === THP_CONTROL_BYTE.ACK_MESSAGE) {
return decodeReadAck();
}
if (magic === THP_CREATE_CHANNEL_RESPONSE) {
if (magic === THP_CONTROL_BYTE.CHANNEL_ALLOCATION_RES) {
return createChannelResponse(message, protobufDecoder);
}
if (magic === THP_HANDSHAKE_INIT_RESPONSE) {
if (magic === THP_CONTROL_BYTE.HANDSHAKE_INIT_RES) {
return readHandshakeInitResponse(message);
}
if (magic === THP_HANDSHAKE_COMPLETION_RESPONSE) {
if (magic === THP_CONTROL_BYTE.HANDSHAKE_COMP_RES) {
return readHandshakeCompletionResponse(message);
}
if (magic === THP_CONTROL_BYTE_ENCRYPTED) {
return readProtobufMessage(message, protobufDecoder);
}
// TODO: decrypted message decoding (not implemented in FW)
if (magic === THP_CONTROL_BYTE_DECRYPTED) {
if (magic === THP_CONTROL_BYTE.ENCRYPTED) {
return readProtobufMessage(message, protobufDecoder);
}

View File

@@ -1,15 +1,7 @@
import { ThpState } from './ThpState';
import {
CRC_LENGTH,
TAG_LENGTH,
THP_CONTROL_BYTE_ENCRYPTED,
THP_CREATE_CHANNEL_REQUEST,
THP_DEFAULT_CHANNEL,
THP_HANDSHAKE_COMPLETION_REQUEST,
THP_HANDSHAKE_INIT_REQUEST,
THP_READ_ACK_HEADER_BYTE,
} from './constants';
import { CRC_LENGTH, TAG_LENGTH, THP_DEFAULT_CHANNEL } from './constants';
import { aesgcm, crc32 } from './crypto';
import { THP_CONTROL_BYTE } from '../protocol-v2/constants';
import { getIvFromNonce } from './crypto/tools';
import { addAckBit, addSequenceBit, isThpMessageName } from './utils';
@@ -103,7 +95,7 @@ const createChannelRequest = (data: Buffer, channel: Buffer) => {
const length = Buffer.alloc(2);
length.writeUInt16BE(data.length + CRC_LENGTH); // 8 nonce + 4 crc
const magic = Buffer.from([THP_CREATE_CHANNEL_REQUEST]);
const magic = Buffer.from([THP_CONTROL_BYTE.CHANNEL_ALLOCATION_REQ]);
const message = Buffer.concat([magic, channel, length, data]);
const crc = crc32(message);
@@ -114,7 +106,7 @@ const handshakeInitRequest = (data: Buffer, channel: Buffer) => {
const length = Buffer.alloc(2);
length.writeUInt16BE(data.length + CRC_LENGTH);
const magic = Buffer.from([THP_HANDSHAKE_INIT_REQUEST]);
const magic = Buffer.from([THP_CONTROL_BYTE.HANDSHAKE_INIT_REQ]);
const message = Buffer.concat([magic, channel, length, data]);
const crc = crc32(message);
@@ -125,7 +117,7 @@ const handshakeCompletionRequest = (data: Buffer, channel: Buffer, sendBit: numb
const length = Buffer.alloc(2);
length.writeUInt16BE(data.length + CRC_LENGTH);
const magic = addSequenceBit(THP_HANDSHAKE_COMPLETION_REQUEST, sendBit);
const magic = addSequenceBit(THP_CONTROL_BYTE.HANDSHAKE_COMP_REQ, sendBit);
const message = Buffer.concat([magic, channel, length, data]);
const crc = crc32(message);
@@ -169,7 +161,7 @@ export const encodeProtobufMessage = (
length.writeUInt16BE(1 + 2 + data.length + TAG_LENGTH + CRC_LENGTH); // 1 session_id + 2 messageType + protobuf len + 16 tag + 4 crc
// TODO: distinguish encrypted and decrypted messages (not implemented in FW)
const magic = addSequenceBit(THP_CONTROL_BYTE_ENCRYPTED, thpState.sendBit);
const magic = addSequenceBit(THP_CONTROL_BYTE.ENCRYPTED, thpState.sendBit);
const header = Buffer.concat([magic, channel]);
const messageTypeBytes = Buffer.alloc(2);
@@ -195,7 +187,7 @@ export const encodeAck = (state: ThpState) => {
const length = Buffer.alloc(2);
length.writeUInt16BE(CRC_LENGTH);
const magic = addAckBit(THP_READ_ACK_HEADER_BYTE, state.recvAckBit);
const magic = addAckBit(THP_CONTROL_BYTE.ACK_MESSAGE, state.recvAckBit);
const message = Buffer.concat([magic, state.channel, length]);
const crc = crc32(message);

View File

@@ -1,18 +1,6 @@
import type { ThpState } from './ThpState';
import {
THP_CONTINUATION_PACKET,
THP_CONTROL_BYTE_DECRYPTED,
THP_CONTROL_BYTE_ENCRYPTED,
THP_CREATE_CHANNEL_REQUEST,
THP_CREATE_CHANNEL_RESPONSE,
THP_ERROR_HEADER_BYTE,
THP_HANDSHAKE_COMPLETION_REQUEST,
THP_HANDSHAKE_COMPLETION_RESPONSE,
THP_HANDSHAKE_INIT_REQUEST,
THP_HANDSHAKE_INIT_RESPONSE,
THP_READ_ACK_HEADER_BYTE,
} from './constants';
import { ThpMessageSyncBit, ThpPairingMethod } from './messages';
import { THP_CONTROL_BYTE } from '../protocol-v2/constants';
export const addAckBit = (magic: number, ackBit: number) => {
const result = Buffer.alloc(1);
@@ -60,7 +48,9 @@ export const readThpHeader = (bytes: Buffer) => {
// Trezor doesn't expect ThpAck ThpCreateChannelResponse
export const isAckExpected = (bytesOrMagic: Buffer | number[]) => {
const isCreateChannelMessage = (magic: number) =>
[THP_CREATE_CHANNEL_REQUEST, THP_CREATE_CHANNEL_RESPONSE].includes(magic);
[THP_CONTROL_BYTE.CHANNEL_ALLOCATION_REQ, THP_CONTROL_BYTE.CHANNEL_ALLOCATION_RES].includes(
magic,
);
if (Array.isArray(bytesOrMagic)) {
return !bytesOrMagic.find(n => isCreateChannelMessage(n));
@@ -73,20 +63,17 @@ export const isAckExpected = (bytesOrMagic: Buffer | number[]) => {
export const getExpectedResponses = (bytes: Buffer) => {
const { magic } = readThpHeader(bytes);
if (magic === THP_CREATE_CHANNEL_REQUEST) {
return [THP_CREATE_CHANNEL_RESPONSE];
if (magic === THP_CONTROL_BYTE.CHANNEL_ALLOCATION_REQ) {
return [THP_CONTROL_BYTE.CHANNEL_ALLOCATION_RES];
}
if (magic === THP_HANDSHAKE_INIT_REQUEST) {
return [THP_HANDSHAKE_INIT_RESPONSE, THP_CONTINUATION_PACKET];
if (magic === THP_CONTROL_BYTE.HANDSHAKE_INIT_REQ) {
return [THP_CONTROL_BYTE.HANDSHAKE_INIT_RES, THP_CONTROL_BYTE.CONTINUATION_PACKET];
}
if (magic === THP_HANDSHAKE_COMPLETION_REQUEST) {
return [THP_HANDSHAKE_COMPLETION_RESPONSE, THP_CONTINUATION_PACKET];
if (magic === THP_CONTROL_BYTE.HANDSHAKE_COMP_REQ) {
return [THP_CONTROL_BYTE.HANDSHAKE_COMP_RES, THP_CONTROL_BYTE.CONTINUATION_PACKET];
}
if (magic === THP_CONTROL_BYTE_ENCRYPTED) {
return [THP_CONTROL_BYTE_ENCRYPTED, THP_CONTINUATION_PACKET];
}
if (magic === THP_CONTROL_BYTE_DECRYPTED) {
return [THP_CONTROL_BYTE_DECRYPTED, THP_CONTINUATION_PACKET];
if (magic === THP_CONTROL_BYTE.ENCRYPTED) {
return [THP_CONTROL_BYTE.ENCRYPTED, THP_CONTROL_BYTE.CONTINUATION_PACKET];
}
return [];
@@ -95,12 +82,12 @@ export const getExpectedResponses = (bytes: Buffer) => {
// get expected responses from ThpState (stored as numbers)
// and join them with the channel to receive 3 bytes header
export const getExpectedHeaders = (state: ThpState): Buffer[] =>
[...state.expectedResponses, THP_ERROR_HEADER_BYTE] // error could be sent any time
[...state.expectedResponses, THP_CONTROL_BYTE.ERROR] // error could be sent any time
.map(resp => {
switch (resp) {
case THP_CONTINUATION_PACKET:
return Buffer.from([resp]); // THP_CONTINUATION_PACKET is not masked with sequence bit
case THP_READ_ACK_HEADER_BYTE:
case THP_CONTROL_BYTE.CONTINUATION_PACKET:
return Buffer.from([resp]); // THP_CONTROL_BYTE.CONTINUATION_PACKET is not masked with sequence bit
case THP_CONTROL_BYTE.ACK_MESSAGE:
return addAckBit(resp, state.sendAckBit);
default:
return addSequenceBit(resp, state.recvBit);
@@ -121,7 +108,7 @@ export const isExpectedResponse = (bytes: Buffer, state: ThpState) => {
}
const { magic } = header;
if (magic === THP_ERROR_HEADER_BYTE) {
if (magic === THP_CONTROL_BYTE.ERROR) {
// ThpError is always expected
return true;
}
@@ -131,7 +118,7 @@ export const isExpectedResponse = (bytes: Buffer, state: ThpState) => {
if (magic === expectedResponses[i]) {
// continuation packet is not masked by controlBit
if (
magic !== THP_CONTINUATION_PACKET &&
magic !== THP_CONTROL_BYTE.CONTINUATION_PACKET &&
(header.sequenceBit !== state?.recvBit || header.ackBit !== state?.recvAckBit)
) {
console.warn('Unexpected control bit');