mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-14 03:30:02 +00:00
feat(core): work on pairing, updated protobuf
note: crashes during build
This commit is contained in:
parent
30fd1fe5c3
commit
2637ce299d
@ -12,45 +12,170 @@ option java_outer_classname = "TrezorMessageThp";
|
||||
* @embed
|
||||
*/
|
||||
enum ThpPairingMethod {
|
||||
PairingMethod_NoMethod = 1; // Trust without MITM protection.
|
||||
PairingMethod_CodeEntry = 2; // User types code diplayed on Trezor into the host application.
|
||||
PairingMethod_QrCode = 3; // User scans code displayed on Trezor into host application.
|
||||
PairingMethod_NFC_Unidirectional = 4; // Trezor transmits an authentication key to the host device via NFC.
|
||||
PairingMethod_NoMethod = 1; // Trust without MITM protection.
|
||||
PairingMethod_CodeEntry = 2; // User types code diplayed on Trezor into the host application.
|
||||
PairingMethod_QrCode = 3; // User scans code displayed on Trezor into host application.
|
||||
PairingMethod_NFC_Unidirectional = 4; // Trezor transmits an authentication key to the host device via NFC.
|
||||
}
|
||||
|
||||
/**
|
||||
* @embed
|
||||
*/
|
||||
message ThpDeviceProperties {
|
||||
optional string internal_model = 1; // Internal model name e.g. "T2B1".
|
||||
optional uint32 model_variant = 2; // Encodes the device properties such as color.
|
||||
optional bool bootloader_mode = 3; // Indicates whether the device is in bootloader or firmware mode.
|
||||
optional uint32 protocol_version = 4; // The communication protocol version supported by the firmware.
|
||||
repeated ThpPairingMethod pairing_methods = 5; // The pairing methods supported by the Trezor.
|
||||
optional string internal_model = 1; // Internal model name e.g. "T2B1".
|
||||
optional uint32 model_variant = 2; // Encodes the device properties such as color.
|
||||
optional bool bootloader_mode = 3; // Indicates whether the device is in bootloader or firmware mode.
|
||||
optional uint32 protocol_version = 4; // The communication protocol version supported by the firmware.
|
||||
repeated ThpPairingMethod pairing_methods = 5; // The pairing methods supported by the Trezor.
|
||||
}
|
||||
|
||||
/**
|
||||
* @embed
|
||||
*/
|
||||
message ThpHandshakeCompletionReqNoisePayload {
|
||||
optional bytes host_pairing_credential = 1; // Host's pairing credential
|
||||
repeated ThpPairingMethod pairing_methods = 2; // The pairing methods chosen by the host
|
||||
optional bytes host_pairing_credential = 1; // Host's pairing credential
|
||||
repeated ThpPairingMethod pairing_methods = 2; // The pairing methods chosen by the host
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device for a new session with given passphrase
|
||||
* Request: Ask device for a new session with given passphrase.
|
||||
* @start
|
||||
* @next ThpNewSession
|
||||
*/
|
||||
message ThpCreateNewSession{
|
||||
optional string passphrase = 1;
|
||||
optional bool on_device = 2; // user wants to enter passphrase on the device
|
||||
optional bool on_device = 2; // User wants to enter passphrase on the device
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Contains session_id of the newly created session
|
||||
* Response: Contains session_id of the newly created session.
|
||||
* @end
|
||||
*/
|
||||
message ThpNewSession{
|
||||
optional uint32 new_session_id = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Start pairing process.
|
||||
* @start
|
||||
* @next ThpCodeEntryCommitment
|
||||
* @next ThpQrCodeTag // Sent by the Host
|
||||
* @next ThpNfcUnidirectionalTag // Sent by the Host
|
||||
*/
|
||||
message ThpStartPairingRequest{
|
||||
optional bytes host_name = 1; // Human-readable host name
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: If Code Entry is an allowed pairing option, Trezor responds with a commitment.
|
||||
* @next ThpCodeEntryChallenge
|
||||
*/
|
||||
message ThpCodeEntryCommitment {
|
||||
optional bytes commitment = 1; // SHA-256 of Trezor's random 32-byte secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Host responds to Trezor's Code Entry commitment with a challenge.
|
||||
* @next ThpCodeEntryCpaceHost // Sent by the Host
|
||||
* @next ThpQrCodeTag // Sent by the Host
|
||||
* @next ThpNfcUnidirectionalTag // Sent by the Host
|
||||
*/
|
||||
message ThpCodeEntryChallenge {
|
||||
optional bytes challenge = 1; // host's random 32-byte challenge
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: User selected Code Entry option in Host. Host starts CPACE protocol with Trezor.
|
||||
* @next ThpCodeEntryCpaceTrezor
|
||||
*/
|
||||
message ThpCodeEntryCpaceHost {
|
||||
optional bytes cpace_host_public_key = 1; // Host's ephemeral CPace public key
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Trezor continues with the CPACE protocol.
|
||||
* @next ThpCodeEntryTag
|
||||
*/
|
||||
message ThpCodeEntryCpaceTrezor {
|
||||
optional bytes cpace_trezor_public_key = 1; // Trezor's ephemeral CPace public key
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Host continues with the CPACE protocol.
|
||||
* @next ThpCodeEntrySecret
|
||||
*/
|
||||
message ThpCodeEntryTag {
|
||||
optional bytes tag = 2; // SHA-256 of shared secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Trezor finishes the CPACE protocol.
|
||||
* @end
|
||||
*/
|
||||
message ThpCodeEntrySecret {
|
||||
optional bytes secret = 1; // Trezor's secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: User selected QR Code pairing option. Host sends a QR Tag.
|
||||
* @next ThpQrCodeSecret
|
||||
*/
|
||||
message ThpQrCodeTag {
|
||||
optional bytes tag = 1; // SHA-256 of shared secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Trezor sends the QR secret.
|
||||
* @end
|
||||
*/
|
||||
message ThpQrCodeSecret {
|
||||
optional bytes secret = 1; // Trezor's secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: User selected Unidirectional NFC pairing option. Host sends an Unidirectional NFC Tag.
|
||||
* @next ThpNfcUnideirectionalSecret
|
||||
*/
|
||||
message ThpNfcUnidirectionalTag {
|
||||
optional bytes tag = 1; // SHA-256 of shared secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Trezor sends the Unidirectioal NFC secret.
|
||||
* @end
|
||||
*/
|
||||
message ThpNfcUnideirectionalSecret {
|
||||
optional bytes secret = 1; // Trezor's secret
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Host requests issuance of a new pairing credential.
|
||||
* @start
|
||||
* @next ThpCredentialResponse
|
||||
*/
|
||||
message ThpCredentialRequest {
|
||||
optional bytes host_static_pubkey = 1; // Host's static public key used in the handshake.
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Trezor issues a new pairing credential.
|
||||
* @next ThpCredentialRequest
|
||||
* @next EndRequest
|
||||
*/
|
||||
message ThpCredentialResponse {
|
||||
optional bytes trezor_static_pubkey = 1; // Trezor's static public key used in the handshake.
|
||||
optional bytes credential = 2; // The pairing credential issued by the Trezor to the host.
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Host requests transition to the encrypted traffic phase.
|
||||
* @start
|
||||
* @next EndResponse
|
||||
*/
|
||||
message EndRequest {}
|
||||
|
||||
/**
|
||||
* Response: Trezor approves transition to the encrypted traffic phase
|
||||
* @end
|
||||
*/
|
||||
message EndResponse {}
|
@ -385,5 +385,14 @@ enum MessageType {
|
||||
MessageType_ThpEndResponse = 1005 [(bitcoin_only) = true, (wire_out) = true];
|
||||
MessageType_ThpCreateNewSession = 1006[(bitcoin_only)=true,(wire_in)=true];
|
||||
MessageType_ThpNewSession = 1007[(bitcoin_only)=true,(wire_out)=true];
|
||||
|
||||
MessageType_ThpCodeEntryCommitment = 1016[(bitcoin_only)=true,(wire_out)=true];
|
||||
MessageType_ThpCodeEntryChallenge = 1017[(bitcoin_only)=true,(wire_in)=true];
|
||||
MessageType_ThpCodeEntryCpaceHost = 1018[(bitcoin_only)=true,(wire_in)=true];
|
||||
MessageType_ThpCodeEntryCpaceTrezor = 1019[(bitcoin_only)=true,(wire_out)=true];
|
||||
MessageType_ThpCodeEntryTag = 1020[(bitcoin_only)=true,(wire_in)=true];
|
||||
MessageType_ThpCodeEntrySecret = 1021[(bitcoin_only)=true,(wire_out)=true];
|
||||
MessageType_ThpQrCodeTag = 1024[(bitcoin_only)=true,(wire_in)=true];
|
||||
MessageType_ThpQrCodeSecret = 1025[(bitcoin_only)=true,(wire_out)=true];
|
||||
MessageType_ThpNfcUnidirectionalTag = 1032[(bitcoin_only)=true,(wire_in)=true];
|
||||
MessageType_ThpNfcUnideirectionalSecret = 1033[(bitcoin_only)=true,(wire_in)=true];
|
||||
}
|
||||
|
8
core/src/all_modules.py
generated
8
core/src/all_modules.py
generated
@ -217,6 +217,8 @@ trezor.wire.thp.checksum
|
||||
import trezor.wire.thp.checksum
|
||||
trezor.wire.thp.crypto
|
||||
import trezor.wire.thp.crypto
|
||||
trezor.wire.thp.pairing_context
|
||||
import trezor.wire.thp.pairing_context
|
||||
trezor.wire.thp.session_context
|
||||
import trezor.wire.thp.session_context
|
||||
trezor.wire.thp.thp_messages
|
||||
@ -397,6 +399,12 @@ apps.misc.get_firmware_hash
|
||||
import apps.misc.get_firmware_hash
|
||||
apps.misc.sign_identity
|
||||
import apps.misc.sign_identity
|
||||
apps.thp
|
||||
import apps.thp
|
||||
apps.thp.create_session
|
||||
import apps.thp.create_session
|
||||
apps.thp.pairing
|
||||
import apps.thp.pairing
|
||||
apps.workflow_handlers
|
||||
import apps.workflow_handlers
|
||||
|
||||
|
@ -270,7 +270,7 @@ async def handle_DoPreauthorized(msg: DoPreauthorized) -> protobuf.MessageType:
|
||||
get_context().iface, req.MESSAGE_WIRE_TYPE
|
||||
)
|
||||
if handler is None:
|
||||
return wire.unexpected_message()
|
||||
return wire.message_handler.unexpected_message()
|
||||
|
||||
return await handler(req, authorization.get()) # type: ignore [Expected 1 positional argument]
|
||||
|
||||
|
@ -29,7 +29,10 @@ async def recovery_process() -> Success:
|
||||
import storage
|
||||
from trezor.enums import MessageType
|
||||
|
||||
wire.AVOID_RESTARTING_FOR = (MessageType.Initialize, MessageType.GetFeatures)
|
||||
wire.message_handler.AVOID_RESTARTING_FOR = (
|
||||
MessageType.Initialize,
|
||||
MessageType.GetFeatures,
|
||||
)
|
||||
try:
|
||||
return await _continue_recovery_process()
|
||||
except recover.RecoveryAborted:
|
||||
|
0
core/src/apps/thp/__init__.py
Normal file
0
core/src/apps/thp/__init__.py
Normal file
13
core/src/apps/thp/create_session.py
Normal file
13
core/src/apps/thp/create_session.py
Normal file
@ -0,0 +1,13 @@
|
||||
from typing import TYPE_CHECKING # pyright: ignore[reportShadowedImports]
|
||||
|
||||
from trezor.wire.thp.channel import Channel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.messages import ThpCreateNewSession, ThpNewSession
|
||||
|
||||
|
||||
async def create_new_session(
|
||||
channel: Channel, message: ThpCreateNewSession
|
||||
) -> ThpNewSession:
|
||||
new_session_id: int = channel.create_new_session(message.passphrase)
|
||||
return ThpNewSession(new_session_id=new_session_id)
|
92
core/src/apps/thp/pairing.py
Normal file
92
core/src/apps/thp/pairing.py
Normal file
@ -0,0 +1,92 @@
|
||||
from typing import TYPE_CHECKING # pyright: ignore[reportShadowedImports]
|
||||
|
||||
from trezor.wire.errors import UnexpectedMessage
|
||||
from trezor.wire.thp import ChannelState
|
||||
from trezor.wire.thp.channel import Channel
|
||||
from trezor.wire.thp.thp_session import ThpError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor.enums import ThpPairingMethod
|
||||
from trezor.messages import (
|
||||
ThpCodeEntryChallenge,
|
||||
ThpCodeEntryCommitment,
|
||||
ThpCodeEntryCpaceHost,
|
||||
ThpCodeEntryCpaceTrezor,
|
||||
ThpCodeEntrySecret,
|
||||
ThpCodeEntryTag,
|
||||
ThpNfcUnideirectionalSecret,
|
||||
ThpNfcUnidirectionalTag,
|
||||
ThpQrCodeSecret,
|
||||
ThpQrCodeTag,
|
||||
ThpStartPairingRequest,
|
||||
)
|
||||
|
||||
|
||||
# TODO implement the following handlers
|
||||
|
||||
|
||||
async def handle_pairing_request(
|
||||
channel: Channel, message: ThpStartPairingRequest
|
||||
) -> ThpCodeEntryCommitment | None:
|
||||
_check_state(channel, ChannelState.TP1)
|
||||
if _is_method_included(channel, ThpPairingMethod.PairingMethod_CodeEntry):
|
||||
channel.set_channel_state(ChannelState.TP2)
|
||||
return ThpCodeEntryCommitment()
|
||||
channel.set_channel_state(ChannelState.TP3)
|
||||
return None
|
||||
|
||||
|
||||
async def handle_code_entry_challenge(
|
||||
channel: Channel, message: ThpCodeEntryChallenge
|
||||
) -> None:
|
||||
_check_state(channel, ChannelState.TP2)
|
||||
channel.set_channel_state(ChannelState.TP3)
|
||||
|
||||
|
||||
async def handle_code_entry_cpace(
|
||||
channel: Channel, message: ThpCodeEntryCpaceHost
|
||||
) -> ThpCodeEntryCpaceTrezor:
|
||||
_check_state(channel, ChannelState.TP3)
|
||||
_check_method_is_allowed(channel, ThpPairingMethod.PairingMethod_CodeEntry)
|
||||
channel.set_channel_state(ChannelState.TP4)
|
||||
return ThpCodeEntryCpaceTrezor()
|
||||
|
||||
|
||||
async def handle_code_entry_tag(
|
||||
channel: Channel, message: ThpCodeEntryTag
|
||||
) -> ThpCodeEntrySecret:
|
||||
_check_state(channel, ChannelState.TP4)
|
||||
channel.set_channel_state(ChannelState.TC1)
|
||||
return ThpCodeEntrySecret()
|
||||
|
||||
|
||||
async def handle_qr_code_tag(
|
||||
channel: Channel, message: ThpQrCodeTag
|
||||
) -> ThpQrCodeSecret:
|
||||
_check_state(channel, ChannelState.TP3)
|
||||
_check_method_is_allowed(channel, ThpPairingMethod.PairingMethod_QrCode)
|
||||
channel.set_channel_state(ChannelState.TC1)
|
||||
return ThpQrCodeSecret()
|
||||
|
||||
|
||||
async def handle_nfc_unidirectional_tag(
|
||||
channel: Channel, message: ThpNfcUnidirectionalTag
|
||||
) -> ThpNfcUnideirectionalSecret:
|
||||
_check_state(channel, ChannelState.TP3)
|
||||
_check_method_is_allowed(channel, ThpPairingMethod.PairingMethod_NFC_Unidirectional)
|
||||
channel.set_channel_state(ChannelState.TC1)
|
||||
return ThpNfcUnideirectionalSecret()
|
||||
|
||||
|
||||
def _check_state(channel: Channel, expected_state: ChannelState) -> None:
|
||||
if expected_state is not channel.get_channel_state():
|
||||
raise UnexpectedMessage("Unexpected message")
|
||||
|
||||
|
||||
def _check_method_is_allowed(channel: Channel, method: ThpPairingMethod) -> None:
|
||||
if not _is_method_included(channel, method):
|
||||
raise ThpError("Unexpected pairing method")
|
||||
|
||||
|
||||
def _is_method_included(channel: Channel, method: ThpPairingMethod) -> bool:
|
||||
return method in channel.selected_pairing_methods
|
10
core/src/trezor/enums/MessageType.py
generated
10
core/src/trezor/enums/MessageType.py
generated
@ -105,6 +105,16 @@ ThpEndRequest = 1004
|
||||
ThpEndResponse = 1005
|
||||
ThpCreateNewSession = 1006
|
||||
ThpNewSession = 1007
|
||||
ThpCodeEntryCommitment = 1016
|
||||
ThpCodeEntryChallenge = 1017
|
||||
ThpCodeEntryCpaceHost = 1018
|
||||
ThpCodeEntryCpaceTrezor = 1019
|
||||
ThpCodeEntryTag = 1020
|
||||
ThpCodeEntrySecret = 1021
|
||||
ThpQrCodeTag = 1024
|
||||
ThpQrCodeSecret = 1025
|
||||
ThpNfcUnidirectionalTag = 1032
|
||||
ThpNfcUnideirectionalSecret = 1033
|
||||
if not utils.BITCOIN_ONLY:
|
||||
SetU2FCounter = 63
|
||||
GetNextU2FCounter = 80
|
||||
|
10
core/src/trezor/enums/__init__.py
generated
10
core/src/trezor/enums/__init__.py
generated
@ -272,6 +272,16 @@ if TYPE_CHECKING:
|
||||
ThpEndResponse = 1005
|
||||
ThpCreateNewSession = 1006
|
||||
ThpNewSession = 1007
|
||||
ThpCodeEntryCommitment = 1016
|
||||
ThpCodeEntryChallenge = 1017
|
||||
ThpCodeEntryCpaceHost = 1018
|
||||
ThpCodeEntryCpaceTrezor = 1019
|
||||
ThpCodeEntryTag = 1020
|
||||
ThpCodeEntrySecret = 1021
|
||||
ThpQrCodeTag = 1024
|
||||
ThpQrCodeSecret = 1025
|
||||
ThpNfcUnidirectionalTag = 1032
|
||||
ThpNfcUnideirectionalSecret = 1033
|
||||
|
||||
class FailureType(IntEnum):
|
||||
UnexpectedMessage = 1
|
||||
|
196
core/src/trezor/messages.py
generated
196
core/src/trezor/messages.py
generated
@ -6166,6 +6166,202 @@ if TYPE_CHECKING:
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpNewSession"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpStartPairingRequest(protobuf.MessageType):
|
||||
host_name: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
host_name: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpStartPairingRequest"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpCodeEntryCommitment(protobuf.MessageType):
|
||||
commitment: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
commitment: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpCodeEntryCommitment"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpCodeEntryChallenge(protobuf.MessageType):
|
||||
challenge: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
challenge: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpCodeEntryChallenge"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpCodeEntryCpaceHost(protobuf.MessageType):
|
||||
cpace_host_public_key: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
cpace_host_public_key: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpCodeEntryCpaceHost"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpCodeEntryCpaceTrezor(protobuf.MessageType):
|
||||
cpace_trezor_public_key: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
cpace_trezor_public_key: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpCodeEntryCpaceTrezor"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpCodeEntryTag(protobuf.MessageType):
|
||||
tag: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tag: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpCodeEntryTag"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpCodeEntrySecret(protobuf.MessageType):
|
||||
secret: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
secret: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpCodeEntrySecret"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpQrCodeTag(protobuf.MessageType):
|
||||
tag: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tag: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpQrCodeTag"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpQrCodeSecret(protobuf.MessageType):
|
||||
secret: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
secret: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpQrCodeSecret"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpNfcUnidirectionalTag(protobuf.MessageType):
|
||||
tag: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tag: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpNfcUnidirectionalTag"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class ThpNfcUnideirectionalSecret(protobuf.MessageType):
|
||||
secret: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
secret: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["ThpNfcUnideirectionalSecret"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class CredentialReq(protobuf.MessageType):
|
||||
host_static_pubkey: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
host_static_pubkey: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["CredentialReq"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class CredentialResp(protobuf.MessageType):
|
||||
trezor_static_pubkey: "bytes | None"
|
||||
credential: "bytes | None"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
trezor_static_pubkey: "bytes | None" = None,
|
||||
credential: "bytes | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["CredentialResp"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class EndReq(protobuf.MessageType):
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["EndReq"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class EndResp(protobuf.MessageType):
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: Any) -> TypeGuard["EndResp"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class WebAuthnListResidentCredentials(protobuf.MessageType):
|
||||
|
||||
@classmethod
|
||||
|
@ -23,15 +23,16 @@ reads the message's header. When the message type is known the first handler is
|
||||
|
||||
"""
|
||||
|
||||
from micropython import const
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from storage.cache_common import InvalidSessionError
|
||||
from trezor import log, loop, protobuf, utils
|
||||
from trezor.enums import FailureType
|
||||
from trezor.messages import Failure
|
||||
from trezor.wire import context, message_handler, protocol_common, thp_v1
|
||||
from trezor.wire.errors import DataError, Error
|
||||
from trezor.wire.message_handler import (
|
||||
AVOID_RESTARTING_FOR,
|
||||
WIRE_BUFFER,
|
||||
WIRE_BUFFER_DEBUG,
|
||||
failure,
|
||||
)
|
||||
|
||||
# Import all errors into namespace, so that `wire.Error` is available from
|
||||
# other packages.
|
||||
@ -43,7 +44,6 @@ if TYPE_CHECKING:
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Container,
|
||||
Coroutine,
|
||||
TypeVar,
|
||||
)
|
||||
@ -66,35 +66,6 @@ def setup(iface: WireInterface, is_debug_session: bool = False) -> None:
|
||||
loop.schedule(handle_session(iface, is_debug_session))
|
||||
|
||||
|
||||
def wrap_protobuf_load(
|
||||
buffer: bytes,
|
||||
expected_type: type[LoadedMessageType],
|
||||
) -> LoadedMessageType:
|
||||
try:
|
||||
msg = protobuf.decode(buffer, expected_type, EXPERIMENTAL_ENABLED)
|
||||
if __debug__ and utils.EMULATOR:
|
||||
log.debug(
|
||||
__name__, "received message contents:\n%s", utils.dump_protobuf(msg)
|
||||
)
|
||||
return msg
|
||||
except Exception as e:
|
||||
if __debug__:
|
||||
log.exception(__name__, e)
|
||||
if e.args:
|
||||
raise DataError("Failed to decode message: " + " ".join(e.args))
|
||||
else:
|
||||
raise DataError("Failed to decode message")
|
||||
|
||||
|
||||
_PROTOBUF_BUFFER_SIZE = const(8192)
|
||||
|
||||
WIRE_BUFFER = bytearray(_PROTOBUF_BUFFER_SIZE)
|
||||
|
||||
if __debug__:
|
||||
PROTOBUF_BUFFER_SIZE_DEBUG = 1024
|
||||
WIRE_BUFFER_DEBUG = bytearray(PROTOBUF_BUFFER_SIZE_DEBUG)
|
||||
|
||||
|
||||
async def handle_thp_session(iface: WireInterface, is_debug_session: bool = False):
|
||||
if __debug__ and is_debug_session:
|
||||
ctx_buffer = WIRE_BUFFER_DEBUG
|
||||
@ -195,33 +166,3 @@ async def handle_session(iface: WireInterface, is_debug_session: bool = False) -
|
||||
# loop.clear() above.
|
||||
if __debug__:
|
||||
log.exception(__name__, exc)
|
||||
|
||||
|
||||
def _find_handler_placeholder(iface: WireInterface, msg_type: int) -> Handler | None:
|
||||
"""Placeholder handler lookup before a proper one is registered."""
|
||||
return None
|
||||
|
||||
|
||||
find_handler = _find_handler_placeholder
|
||||
AVOID_RESTARTING_FOR: Container[int] = ()
|
||||
|
||||
|
||||
def failure(exc: BaseException) -> Failure:
|
||||
if isinstance(exc, Error):
|
||||
return Failure(code=exc.code, message=exc.message)
|
||||
elif isinstance(exc, loop.TaskClosed):
|
||||
return Failure(code=FailureType.ActionCancelled, message="Cancelled")
|
||||
elif isinstance(exc, InvalidSessionError):
|
||||
return Failure(code=FailureType.InvalidSession, message="Invalid session")
|
||||
else:
|
||||
# NOTE: when receiving generic `FirmwareError` on non-debug build,
|
||||
# change the `if __debug__` to `if True` to get the full error message.
|
||||
if __debug__:
|
||||
message = str(exc)
|
||||
else:
|
||||
message = "Firmware error"
|
||||
return Failure(code=FailureType.FirmwareError, message=message)
|
||||
|
||||
|
||||
def unexpected_message() -> Failure:
|
||||
return Failure(code=FailureType.UnexpectedMessage, message="Unexpected message")
|
||||
|
@ -125,7 +125,8 @@ class CodecContext(Context):
|
||||
)
|
||||
|
||||
# look up the protobuf class and parse the message
|
||||
from . import wrap_protobuf_load
|
||||
from . import message_handler # noqa: F401
|
||||
from .message_handler import wrap_protobuf_load
|
||||
|
||||
return wrap_protobuf_load(msg.data, expected_type)
|
||||
|
||||
|
@ -86,15 +86,20 @@ async def handle_single_message(
|
||||
msg_type = f"{msg.type} - unknown message type"
|
||||
if ctx.channel_id is not None:
|
||||
sid = int.from_bytes(ctx.channel_id, "big")
|
||||
log.debug(
|
||||
__name__,
|
||||
"%s:%x receive: <%s>",
|
||||
ctx.iface.iface_num(),
|
||||
sid,
|
||||
msg_type,
|
||||
)
|
||||
else:
|
||||
sid = -1
|
||||
log.debug(
|
||||
__name__,
|
||||
"%s:%x receive: <%s>",
|
||||
ctx.iface.iface_num(),
|
||||
sid,
|
||||
msg_type,
|
||||
)
|
||||
log.debug(
|
||||
__name__,
|
||||
"%s:unknown_sid receive: <%s>",
|
||||
ctx.iface.iface_num(),
|
||||
msg_type,
|
||||
)
|
||||
|
||||
res_msg: protobuf.MessageType | None = None
|
||||
|
||||
|
@ -14,7 +14,7 @@ class ChannelState(IntEnum):
|
||||
TP2 = 4
|
||||
TP3 = 5
|
||||
TP4 = 6
|
||||
TP5 = 7
|
||||
TC1 = 7
|
||||
ENCRYPTED_TRANSPORT = 8
|
||||
|
||||
|
||||
|
@ -7,11 +7,7 @@ from storage import cache_thp
|
||||
from storage.cache_thp import KEY_LENGTH, SESSION_ID_LENGTH, TAG_LENGTH, ChannelCache
|
||||
from trezor import log, loop, protobuf, utils
|
||||
from trezor.enums import FailureType, MessageType # , ThpPairingMethod
|
||||
from trezor.messages import (
|
||||
Failure,
|
||||
ThpCreateNewSession,
|
||||
ThpNewSession,
|
||||
)
|
||||
from trezor.messages import Failure, ThpCreateNewSession, ThpNewSession
|
||||
from trezor.wire import message_handler
|
||||
from trezor.wire.thp import ack_handler, thp_messages
|
||||
|
||||
|
199
core/src/trezor/wire/thp/pairing_context.py
Normal file
199
core/src/trezor/wire/thp/pairing_context.py
Normal file
@ -0,0 +1,199 @@
|
||||
from typing import TYPE_CHECKING # pyright: ignore[reportShadowedImports]
|
||||
|
||||
from trezor import log, loop, protobuf, workflow
|
||||
from trezor.messages import ThpStartPairingRequest
|
||||
from trezor.wire import message_handler, protocol_common
|
||||
from trezor.wire.context import UnexpectedMessageWithId
|
||||
from trezor.wire.errors import ActionCancelled
|
||||
from trezor.wire.protocol_common import MessageWithType
|
||||
from trezor.wire.thp.session_context import UnexpectedMessageWithType
|
||||
|
||||
from apps.thp.pairing import handle_pairing_request
|
||||
|
||||
from .channel import Channel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Container # pyright:ignore[reportShadowedImports]
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PairingContext:
|
||||
def __init__(self, channel: Channel) -> None:
|
||||
self.channel = channel
|
||||
self.incoming_message = loop.chan()
|
||||
|
||||
async def handle(self, is_debug_session: bool = False) -> None:
|
||||
if __debug__:
|
||||
log.debug(__name__, "handle - start")
|
||||
if is_debug_session:
|
||||
import apps.debug
|
||||
|
||||
apps.debug.DEBUG_CONTEXT = self
|
||||
|
||||
take = self.incoming_message.take()
|
||||
next_message: MessageWithType | None = None
|
||||
|
||||
# Take a mark of modules that are imported at this point, so we can
|
||||
# roll back and un-import any others.
|
||||
# TODO modules = utils.unimport_begin()
|
||||
while True:
|
||||
try:
|
||||
if next_message is None:
|
||||
# If the previous run did not keep an unprocessed message for us,
|
||||
# wait for a new one.
|
||||
try:
|
||||
message: MessageWithType = await take
|
||||
except protocol_common.WireError as e:
|
||||
if __debug__:
|
||||
log.exception(__name__, e)
|
||||
await self.write(message_handler.failure(e))
|
||||
continue
|
||||
else:
|
||||
# Process the message from previous run.
|
||||
message = next_message
|
||||
next_message = None
|
||||
|
||||
try:
|
||||
next_message = await handle_pairing_message(
|
||||
self, message, use_workflow=not is_debug_session
|
||||
)
|
||||
except Exception as exc:
|
||||
# Log and ignore. The session handler can only exit explicitly in the
|
||||
# following finally block.
|
||||
if __debug__:
|
||||
log.exception(__name__, exc)
|
||||
finally:
|
||||
if not __debug__ or not is_debug_session:
|
||||
# Unload modules imported by the workflow. Should not raise.
|
||||
# This is not done for the debug session because the snapshot taken
|
||||
# in a debug session would clear modules which are in use by the
|
||||
# workflow running on wire.
|
||||
# TODO utils.unimport_end(modules)
|
||||
|
||||
if next_message is None:
|
||||
|
||||
# Shut down the loop if there is no next message waiting.
|
||||
# Let the session be restarted from `main`.
|
||||
loop.clear()
|
||||
return # pylint: disable=lost-exception
|
||||
|
||||
except Exception as exc:
|
||||
# Log and try again. The session handler can only exit explicitly via
|
||||
# loop.clear() above.
|
||||
if __debug__:
|
||||
log.exception(__name__, exc)
|
||||
|
||||
async def read(
|
||||
self,
|
||||
expected_types: Container[int],
|
||||
expected_type: type[protobuf.MessageType] | None = None,
|
||||
) -> protobuf.MessageType:
|
||||
if __debug__:
|
||||
exp_type: str = str(expected_type)
|
||||
if expected_type is not None:
|
||||
exp_type = expected_type.MESSAGE_NAME
|
||||
log.debug(
|
||||
__name__,
|
||||
"Read - with expected types %s and expected type %s",
|
||||
str(expected_types),
|
||||
exp_type,
|
||||
)
|
||||
message: MessageWithType = await self.incoming_message.take()
|
||||
if message.type not in expected_types:
|
||||
raise UnexpectedMessageWithType(message)
|
||||
|
||||
if expected_type is None:
|
||||
expected_type = protobuf.type_for_wire(message.type)
|
||||
|
||||
return message_handler.wrap_protobuf_load(message.data, expected_type)
|
||||
|
||||
async def write(self, msg: protobuf.MessageType) -> None:
|
||||
return await self.channel.write(msg)
|
||||
|
||||
|
||||
async def handle_pairing_message(
|
||||
ctx: PairingContext, msg: protocol_common.MessageWithType, use_workflow: bool
|
||||
) -> protocol_common.MessageWithType | None:
|
||||
|
||||
res_msg: protobuf.MessageType | None = None
|
||||
|
||||
# We need to find a handler for this message type. Should not raise.
|
||||
# TODO register handlers to dict
|
||||
handler = get_handler(msg.type) # pylint: disable=assignment-from-none
|
||||
|
||||
if handler is None:
|
||||
# If no handler is found, we can skip decoding and directly
|
||||
# respond with failure.
|
||||
await ctx.write(message_handler.unexpected_message())
|
||||
return None
|
||||
|
||||
if msg.type in workflow.ALLOW_WHILE_LOCKED:
|
||||
workflow.autolock_interrupts_workflow = False
|
||||
|
||||
# Here we make sure we always respond with a Failure response
|
||||
# in case of any errors.
|
||||
try:
|
||||
# Find a protobuf.MessageType subclass that describes this
|
||||
# message. Raises if the type is not found.
|
||||
req_type = protobuf.type_for_wire(msg.type)
|
||||
|
||||
# Try to decode the message according to schema from
|
||||
# `req_type`. Raises if the message is malformed.
|
||||
req_msg = message_handler.wrap_protobuf_load(msg.data, req_type)
|
||||
|
||||
# Create the handler task.
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(req_msg, ThpStartPairingRequest) # TODO remove
|
||||
task = handler(ctx.channel, req_msg)
|
||||
|
||||
# Run the workflow task. Workflow can do more on-the-wire
|
||||
# communication inside, but it should eventually return a
|
||||
# response message, or raise an exception (a rather common
|
||||
# thing to do). Exceptions are handled in the code below.
|
||||
if use_workflow:
|
||||
# Spawn a workflow around the task. This ensures that concurrent
|
||||
# workflows are shut down.
|
||||
# res_msg = await workflow.spawn(context.with_context(ctx, task))
|
||||
pass # TODO
|
||||
else:
|
||||
# For debug messages, ignore workflow processing and just await
|
||||
# results of the handler.
|
||||
res_msg = await task
|
||||
|
||||
except UnexpectedMessageWithId as exc:
|
||||
# Workflow was trying to read a message from the wire, and
|
||||
# something unexpected came in. See Context.read() for
|
||||
# example, which expects some particular message and raises
|
||||
# UnexpectedMessage if another one comes in.
|
||||
# In order not to lose the message, we return it to the caller.
|
||||
# TODO:
|
||||
# We might handle only the few common cases here, like
|
||||
# Initialize and Cancel.
|
||||
return exc.msg
|
||||
|
||||
except BaseException as exc:
|
||||
# Either:
|
||||
# - the message had a type that has a registered handler, but does not have
|
||||
# a protobuf class
|
||||
# - the message was not valid protobuf
|
||||
# - workflow raised some kind of an exception while running
|
||||
# - something canceled the workflow from the outside
|
||||
if __debug__:
|
||||
if isinstance(exc, ActionCancelled):
|
||||
log.debug(__name__, "cancelled: %s", exc.message)
|
||||
elif isinstance(exc, loop.TaskClosed):
|
||||
log.debug(__name__, "cancelled: loop task was closed")
|
||||
else:
|
||||
log.exception(__name__, exc)
|
||||
res_msg = message_handler.failure(exc)
|
||||
|
||||
if res_msg is not None:
|
||||
# perform the write outside the big try-except block, so that usb write
|
||||
# problem bubbles up
|
||||
await ctx.write(res_msg)
|
||||
return None
|
||||
|
||||
|
||||
def get_handler(messageType: int):
|
||||
return handle_pairing_request
|
@ -3,7 +3,8 @@ from typing import TYPE_CHECKING # pyright: ignore[reportShadowedImports]
|
||||
from storage import cache_thp
|
||||
from storage.cache_thp import SessionThpCache
|
||||
from trezor import log, loop, protobuf
|
||||
from trezor.wire import AVOID_RESTARTING_FOR, failure, message_handler, protocol_common
|
||||
from trezor.wire import message_handler, protocol_common
|
||||
from trezor.wire.message_handler import AVOID_RESTARTING_FOR, failure
|
||||
|
||||
from ..protocol_common import Context, MessageWithType
|
||||
from . import SessionState
|
||||
@ -34,7 +35,7 @@ class SessionContext(Context):
|
||||
"The session has different channel id than the provided channel context!"
|
||||
)
|
||||
super().__init__(channel.iface, channel.channel_id)
|
||||
self.channel_context = channel
|
||||
self.channel = channel
|
||||
self.session_cache = session_cache
|
||||
self.session_id = int.from_bytes(session_cache.session_id, "big")
|
||||
self.incoming_message = loop.chan()
|
||||
@ -132,7 +133,7 @@ class SessionContext(Context):
|
||||
return message_handler.wrap_protobuf_load(message.data, expected_type)
|
||||
|
||||
async def write(self, msg: protobuf.MessageType) -> None:
|
||||
return await self.channel_context.write(msg, self.session_id)
|
||||
return await self.channel.write(msg, self.session_id)
|
||||
|
||||
# ACCESS TO SESSION DATA
|
||||
|
||||
|
@ -22,7 +22,7 @@ _MAX_CID_REQ_PAYLOAD_LENGTH = const(12) # TODO set to reasonable value
|
||||
_BUFFER: bytearray
|
||||
_BUFFER_LOCK = None
|
||||
|
||||
_CHANNEL_CONTEXTS: dict[int, Channel] = {}
|
||||
CHANNELS: dict[int, Channel] = {}
|
||||
|
||||
|
||||
def set_buffer(buffer):
|
||||
@ -31,9 +31,9 @@ def set_buffer(buffer):
|
||||
|
||||
|
||||
async def thp_main_loop(iface: WireInterface, is_debug_session=False):
|
||||
global _CHANNEL_CONTEXTS
|
||||
global CHANNELS
|
||||
global _BUFFER
|
||||
_CHANNEL_CONTEXTS = load_cached_channels(_BUFFER)
|
||||
CHANNELS = load_cached_channels(_BUFFER)
|
||||
|
||||
read = loop.wait(iface.iface_num() | io.POLL_READ)
|
||||
|
||||
@ -55,8 +55,8 @@ async def thp_main_loop(iface: WireInterface, is_debug_session=False):
|
||||
await _handle_broadcast(iface, ctrl_byte, packet)
|
||||
continue
|
||||
|
||||
if cid in _CHANNEL_CONTEXTS:
|
||||
channel = _CHANNEL_CONTEXTS[cid]
|
||||
if cid in CHANNELS:
|
||||
channel = CHANNELS[cid]
|
||||
if channel is None:
|
||||
# TODO send error message to wire
|
||||
raise ThpError("Invalid state of a channel")
|
||||
@ -94,7 +94,7 @@ async def _handle_broadcast(
|
||||
|
||||
new_context: Channel = Channel.create_new_channel(iface, _BUFFER)
|
||||
cid = int.from_bytes(new_context.channel_id, "big")
|
||||
_CHANNEL_CONTEXTS[cid] = new_context
|
||||
CHANNELS[cid] = new_context
|
||||
|
||||
response_data = thp_messages.get_channel_allocation_response(
|
||||
nonce, new_context.channel_id
|
||||
|
203
python/src/trezorlib/messages.py
generated
203
python/src/trezorlib/messages.py
generated
@ -280,6 +280,16 @@ class MessageType(IntEnum):
|
||||
ThpEndResponse = 1005
|
||||
ThpCreateNewSession = 1006
|
||||
ThpNewSession = 1007
|
||||
ThpCodeEntryCommitment = 1016
|
||||
ThpCodeEntryChallenge = 1017
|
||||
ThpCodeEntryCpaceHost = 1018
|
||||
ThpCodeEntryCpaceTrezor = 1019
|
||||
ThpCodeEntryTag = 1020
|
||||
ThpCodeEntrySecret = 1021
|
||||
ThpQrCodeTag = 1024
|
||||
ThpQrCodeSecret = 1025
|
||||
ThpNfcUnidirectionalTag = 1032
|
||||
ThpNfcUnideirectionalSecret = 1033
|
||||
|
||||
|
||||
class FailureType(IntEnum):
|
||||
@ -7792,6 +7802,199 @@ class ThpNewSession(protobuf.MessageType):
|
||||
self.new_session_id = new_session_id
|
||||
|
||||
|
||||
class ThpStartPairingRequest(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1000
|
||||
FIELDS = {
|
||||
1: protobuf.Field("host_name", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
host_name: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.host_name = host_name
|
||||
|
||||
|
||||
class ThpCodeEntryCommitment(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1016
|
||||
FIELDS = {
|
||||
1: protobuf.Field("commitment", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
commitment: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.commitment = commitment
|
||||
|
||||
|
||||
class ThpCodeEntryChallenge(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1017
|
||||
FIELDS = {
|
||||
1: protobuf.Field("challenge", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
challenge: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.challenge = challenge
|
||||
|
||||
|
||||
class ThpCodeEntryCpaceHost(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1018
|
||||
FIELDS = {
|
||||
1: protobuf.Field("cpace_host_public_key", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
cpace_host_public_key: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.cpace_host_public_key = cpace_host_public_key
|
||||
|
||||
|
||||
class ThpCodeEntryCpaceTrezor(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1019
|
||||
FIELDS = {
|
||||
1: protobuf.Field("cpace_trezor_public_key", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
cpace_trezor_public_key: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.cpace_trezor_public_key = cpace_trezor_public_key
|
||||
|
||||
|
||||
class ThpCodeEntryTag(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1020
|
||||
FIELDS = {
|
||||
2: protobuf.Field("tag", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tag: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.tag = tag
|
||||
|
||||
|
||||
class ThpCodeEntrySecret(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1021
|
||||
FIELDS = {
|
||||
1: protobuf.Field("secret", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
secret: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.secret = secret
|
||||
|
||||
|
||||
class ThpQrCodeTag(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1024
|
||||
FIELDS = {
|
||||
1: protobuf.Field("tag", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tag: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.tag = tag
|
||||
|
||||
|
||||
class ThpQrCodeSecret(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1025
|
||||
FIELDS = {
|
||||
1: protobuf.Field("secret", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
secret: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.secret = secret
|
||||
|
||||
|
||||
class ThpNfcUnidirectionalTag(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1032
|
||||
FIELDS = {
|
||||
1: protobuf.Field("tag", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tag: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.tag = tag
|
||||
|
||||
|
||||
class ThpNfcUnideirectionalSecret(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 1033
|
||||
FIELDS = {
|
||||
1: protobuf.Field("secret", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
secret: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.secret = secret
|
||||
|
||||
|
||||
class CredentialReq(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = None
|
||||
FIELDS = {
|
||||
1: protobuf.Field("host_static_pubkey", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
host_static_pubkey: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.host_static_pubkey = host_static_pubkey
|
||||
|
||||
|
||||
class CredentialResp(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = None
|
||||
FIELDS = {
|
||||
1: protobuf.Field("trezor_static_pubkey", "bytes", repeated=False, required=False, default=None),
|
||||
2: protobuf.Field("credential", "bytes", repeated=False, required=False, default=None),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
trezor_static_pubkey: Optional["bytes"] = None,
|
||||
credential: Optional["bytes"] = None,
|
||||
) -> None:
|
||||
self.trezor_static_pubkey = trezor_static_pubkey
|
||||
self.credential = credential
|
||||
|
||||
|
||||
class EndReq(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = None
|
||||
|
||||
|
||||
class EndResp(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = None
|
||||
|
||||
|
||||
class WebAuthnListResidentCredentials(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 800
|
||||
|
||||
|
10
rust/trezor-client/src/messages/generated.rs
generated
10
rust/trezor-client/src/messages/generated.rs
generated
@ -90,6 +90,16 @@ trezor_message_impl! {
|
||||
ThpEndResponse => MessageType_ThpEndResponse,
|
||||
ThpCreateNewSession => MessageType_ThpCreateNewSession,
|
||||
ThpNewSession => MessageType_ThpNewSession,
|
||||
ThpCodeEntryCommitment => MessageType_ThpCodeEntryCommitment,
|
||||
ThpCodeEntryChallenge => MessageType_ThpCodeEntryChallenge,
|
||||
ThpCodeEntryCpaceHost => MessageType_ThpCodeEntryCpaceHost,
|
||||
ThpCodeEntryCpaceTrezor => MessageType_ThpCodeEntryCpaceTrezor,
|
||||
ThpCodeEntryTag => MessageType_ThpCodeEntryTag,
|
||||
ThpCodeEntrySecret => MessageType_ThpCodeEntrySecret,
|
||||
ThpQrCodeTag => MessageType_ThpQrCodeTag,
|
||||
ThpQrCodeSecret => MessageType_ThpQrCodeSecret,
|
||||
ThpNfcUnidirectionalTag => MessageType_ThpNfcUnidirectionalTag,
|
||||
ThpNfcUnideirectionalSecret => MessageType_ThpNfcUnideirectionalSecret,
|
||||
}
|
||||
|
||||
#[cfg(feature = "binance")]
|
||||
|
123
rust/trezor-client/src/protos/generated/messages.rs
generated
123
rust/trezor-client/src/protos/generated/messages.rs
generated
@ -530,6 +530,26 @@ pub enum MessageType {
|
||||
MessageType_ThpCreateNewSession = 1006,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpNewSession)
|
||||
MessageType_ThpNewSession = 1007,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpCodeEntryCommitment)
|
||||
MessageType_ThpCodeEntryCommitment = 1016,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpCodeEntryChallenge)
|
||||
MessageType_ThpCodeEntryChallenge = 1017,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpCodeEntryCpaceHost)
|
||||
MessageType_ThpCodeEntryCpaceHost = 1018,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpCodeEntryCpaceTrezor)
|
||||
MessageType_ThpCodeEntryCpaceTrezor = 1019,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpCodeEntryTag)
|
||||
MessageType_ThpCodeEntryTag = 1020,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpCodeEntrySecret)
|
||||
MessageType_ThpCodeEntrySecret = 1021,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpQrCodeTag)
|
||||
MessageType_ThpQrCodeTag = 1024,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpQrCodeSecret)
|
||||
MessageType_ThpQrCodeSecret = 1025,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpNfcUnidirectionalTag)
|
||||
MessageType_ThpNfcUnidirectionalTag = 1032,
|
||||
// @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_ThpNfcUnideirectionalSecret)
|
||||
MessageType_ThpNfcUnideirectionalSecret = 1033,
|
||||
}
|
||||
|
||||
impl ::protobuf::Enum for MessageType {
|
||||
@ -792,6 +812,16 @@ impl ::protobuf::Enum for MessageType {
|
||||
1005 => ::std::option::Option::Some(MessageType::MessageType_ThpEndResponse),
|
||||
1006 => ::std::option::Option::Some(MessageType::MessageType_ThpCreateNewSession),
|
||||
1007 => ::std::option::Option::Some(MessageType::MessageType_ThpNewSession),
|
||||
1016 => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryCommitment),
|
||||
1017 => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryChallenge),
|
||||
1018 => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryCpaceHost),
|
||||
1019 => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryCpaceTrezor),
|
||||
1020 => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryTag),
|
||||
1021 => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntrySecret),
|
||||
1024 => ::std::option::Option::Some(MessageType::MessageType_ThpQrCodeTag),
|
||||
1025 => ::std::option::Option::Some(MessageType::MessageType_ThpQrCodeSecret),
|
||||
1032 => ::std::option::Option::Some(MessageType::MessageType_ThpNfcUnidirectionalTag),
|
||||
1033 => ::std::option::Option::Some(MessageType::MessageType_ThpNfcUnideirectionalSecret),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
@ -1049,6 +1079,16 @@ impl ::protobuf::Enum for MessageType {
|
||||
"MessageType_ThpEndResponse" => ::std::option::Option::Some(MessageType::MessageType_ThpEndResponse),
|
||||
"MessageType_ThpCreateNewSession" => ::std::option::Option::Some(MessageType::MessageType_ThpCreateNewSession),
|
||||
"MessageType_ThpNewSession" => ::std::option::Option::Some(MessageType::MessageType_ThpNewSession),
|
||||
"MessageType_ThpCodeEntryCommitment" => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryCommitment),
|
||||
"MessageType_ThpCodeEntryChallenge" => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryChallenge),
|
||||
"MessageType_ThpCodeEntryCpaceHost" => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryCpaceHost),
|
||||
"MessageType_ThpCodeEntryCpaceTrezor" => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryCpaceTrezor),
|
||||
"MessageType_ThpCodeEntryTag" => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntryTag),
|
||||
"MessageType_ThpCodeEntrySecret" => ::std::option::Option::Some(MessageType::MessageType_ThpCodeEntrySecret),
|
||||
"MessageType_ThpQrCodeTag" => ::std::option::Option::Some(MessageType::MessageType_ThpQrCodeTag),
|
||||
"MessageType_ThpQrCodeSecret" => ::std::option::Option::Some(MessageType::MessageType_ThpQrCodeSecret),
|
||||
"MessageType_ThpNfcUnidirectionalTag" => ::std::option::Option::Some(MessageType::MessageType_ThpNfcUnidirectionalTag),
|
||||
"MessageType_ThpNfcUnideirectionalSecret" => ::std::option::Option::Some(MessageType::MessageType_ThpNfcUnideirectionalSecret),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
@ -1305,6 +1345,16 @@ impl ::protobuf::Enum for MessageType {
|
||||
MessageType::MessageType_ThpEndResponse,
|
||||
MessageType::MessageType_ThpCreateNewSession,
|
||||
MessageType::MessageType_ThpNewSession,
|
||||
MessageType::MessageType_ThpCodeEntryCommitment,
|
||||
MessageType::MessageType_ThpCodeEntryChallenge,
|
||||
MessageType::MessageType_ThpCodeEntryCpaceHost,
|
||||
MessageType::MessageType_ThpCodeEntryCpaceTrezor,
|
||||
MessageType::MessageType_ThpCodeEntryTag,
|
||||
MessageType::MessageType_ThpCodeEntrySecret,
|
||||
MessageType::MessageType_ThpQrCodeTag,
|
||||
MessageType::MessageType_ThpQrCodeSecret,
|
||||
MessageType::MessageType_ThpNfcUnidirectionalTag,
|
||||
MessageType::MessageType_ThpNfcUnideirectionalSecret,
|
||||
];
|
||||
}
|
||||
|
||||
@ -1567,6 +1617,16 @@ impl ::protobuf::EnumFull for MessageType {
|
||||
MessageType::MessageType_ThpEndResponse => 248,
|
||||
MessageType::MessageType_ThpCreateNewSession => 249,
|
||||
MessageType::MessageType_ThpNewSession => 250,
|
||||
MessageType::MessageType_ThpCodeEntryCommitment => 251,
|
||||
MessageType::MessageType_ThpCodeEntryChallenge => 252,
|
||||
MessageType::MessageType_ThpCodeEntryCpaceHost => 253,
|
||||
MessageType::MessageType_ThpCodeEntryCpaceTrezor => 254,
|
||||
MessageType::MessageType_ThpCodeEntryTag => 255,
|
||||
MessageType::MessageType_ThpCodeEntrySecret => 256,
|
||||
MessageType::MessageType_ThpQrCodeTag => 257,
|
||||
MessageType::MessageType_ThpQrCodeSecret => 258,
|
||||
MessageType::MessageType_ThpNfcUnidirectionalTag => 259,
|
||||
MessageType::MessageType_ThpNfcUnideirectionalSecret => 260,
|
||||
};
|
||||
Self::enum_descriptor().value_by_index(index)
|
||||
}
|
||||
@ -1616,7 +1676,7 @@ pub mod exts {
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x0emessages.proto\x12\x12hw.trezor.messages\x1a\x20google/protobuf/de\
|
||||
scriptor.proto*\x93W\n\x0bMessageType\x12(\n\x16MessageType_Initialize\
|
||||
scriptor.proto*\xfaZ\n\x0bMessageType\x12(\n\x16MessageType_Initialize\
|
||||
\x10\0\x1a\x0c\x80\xa6\x1d\x01\xb0\xb5\x18\x01\x90\xb5\x18\x01\x12\x1e\n\
|
||||
\x10MessageType_Ping\x10\x01\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x12\
|
||||
%\n\x13MessageType_Success\x10\x02\x1a\x0c\x80\xa6\x1d\x01\xa8\xb5\x18\
|
||||
@ -1901,30 +1961,43 @@ static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\x12)\n\x1aMessageType_ThpEndResponse\x10\xed\x07\x1a\x08\x80\xa6\x1d\
|
||||
\x01\x98\xb5\x18\x01\x12.\n\x1fMessageType_ThpCreateNewSession\x10\xee\
|
||||
\x07\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x12(\n\x19MessageType_ThpNe\
|
||||
wSession\x10\xef\x07\x1a\x08\x80\xa6\x1d\x01\x98\xb5\x18\x01\x1a\x04\xc8\
|
||||
\xf3\x18\x01\"\x04\x08Z\x10\\\"\x04\x08r\x10z\"\x06\x08\xdb\x01\x10\xdb\
|
||||
\x01\"\x06\x08\xe0\x01\x10\xe0\x01\"\x06\x08\xac\x02\x10\xb0\x02\"\x06\
|
||||
\x08\xb5\x02\x10\xb8\x02:<\n\x07wire_in\x18\xd2\x86\x03\x20\x01(\x08\x12\
|
||||
!.google.protobuf.EnumValueOptionsR\x06wireIn:>\n\x08wire_out\x18\xd3\
|
||||
\x86\x03\x20\x01(\x08\x12!.google.protobuf.EnumValueOptionsR\x07wireOut:\
|
||||
G\n\rwire_debug_in\x18\xd4\x86\x03\x20\x01(\x08\x12!.google.protobuf.Enu\
|
||||
mValueOptionsR\x0bwireDebugIn:I\n\x0ewire_debug_out\x18\xd5\x86\x03\x20\
|
||||
\x01(\x08\x12!.google.protobuf.EnumValueOptionsR\x0cwireDebugOut:@\n\twi\
|
||||
re_tiny\x18\xd6\x86\x03\x20\x01(\x08\x12!.google.protobuf.EnumValueOptio\
|
||||
nsR\x08wireTiny:L\n\x0fwire_bootloader\x18\xd7\x86\x03\x20\x01(\x08\x12!\
|
||||
.google.protobuf.EnumValueOptionsR\x0ewireBootloader:C\n\x0bwire_no_fsm\
|
||||
\x18\xd8\x86\x03\x20\x01(\x08\x12!.google.protobuf.EnumValueOptionsR\twi\
|
||||
reNoFsm:F\n\x0cbitcoin_only\x18\xe0\xd4\x03\x20\x01(\x08\x12!.google.pro\
|
||||
tobuf.EnumValueOptionsR\x0bbitcoinOnly:U\n\x17has_bitcoin_only_values\
|
||||
\x18\xb9\x8e\x03\x20\x01(\x08\x12\x1c.google.protobuf.EnumOptionsR\x14ha\
|
||||
sBitcoinOnlyValues:T\n\x14experimental_message\x18\xa1\x96\x03\x20\x01(\
|
||||
\x08\x12\x1f.google.protobuf.MessageOptionsR\x13experimentalMessage:>\n\
|
||||
\twire_type\x18\xa2\x96\x03\x20\x01(\r\x12\x1f.google.protobuf.MessageOp\
|
||||
tionsR\x08wireType:N\n\x12experimental_field\x18\x89\x9e\x03\x20\x01(\
|
||||
\x08\x12\x1d.google.protobuf.FieldOptionsR\x11experimentalField:U\n\x17i\
|
||||
nclude_in_bitcoin_only\x18\xe0\xd4\x03\x20\x01(\x08\x12\x1c.google.proto\
|
||||
buf.FileOptionsR\x14includeInBitcoinOnlyB8\n#com.satoshilabs.trezor.lib.\
|
||||
protobufB\rTrezorMessage\x80\xa6\x1d\x01\
|
||||
wSession\x10\xef\x07\x1a\x08\x80\xa6\x1d\x01\x98\xb5\x18\x01\x121\n\"Mes\
|
||||
sageType_ThpCodeEntryCommitment\x10\xf8\x07\x1a\x08\x80\xa6\x1d\x01\x98\
|
||||
\xb5\x18\x01\x120\n!MessageType_ThpCodeEntryChallenge\x10\xf9\x07\x1a\
|
||||
\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x120\n!MessageType_ThpCodeEntryCpac\
|
||||
eHost\x10\xfa\x07\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x122\n#Message\
|
||||
Type_ThpCodeEntryCpaceTrezor\x10\xfb\x07\x1a\x08\x80\xa6\x1d\x01\x98\xb5\
|
||||
\x18\x01\x12*\n\x1bMessageType_ThpCodeEntryTag\x10\xfc\x07\x1a\x08\x80\
|
||||
\xa6\x1d\x01\x90\xb5\x18\x01\x12-\n\x1eMessageType_ThpCodeEntrySecret\
|
||||
\x10\xfd\x07\x1a\x08\x80\xa6\x1d\x01\x98\xb5\x18\x01\x12'\n\x18MessageTy\
|
||||
pe_ThpQrCodeTag\x10\x80\x08\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x12*\
|
||||
\n\x1bMessageType_ThpQrCodeSecret\x10\x81\x08\x1a\x08\x80\xa6\x1d\x01\
|
||||
\x98\xb5\x18\x01\x122\n#MessageType_ThpNfcUnidirectionalTag\x10\x88\x08\
|
||||
\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x126\n'MessageType_ThpNfcUnidei\
|
||||
rectionalSecret\x10\x89\x08\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x1a\
|
||||
\x04\xc8\xf3\x18\x01\"\x04\x08Z\x10\\\"\x04\x08r\x10z\"\x06\x08\xdb\x01\
|
||||
\x10\xdb\x01\"\x06\x08\xe0\x01\x10\xe0\x01\"\x06\x08\xac\x02\x10\xb0\x02\
|
||||
\"\x06\x08\xb5\x02\x10\xb8\x02:<\n\x07wire_in\x18\xd2\x86\x03\x20\x01(\
|
||||
\x08\x12!.google.protobuf.EnumValueOptionsR\x06wireIn:>\n\x08wire_out\
|
||||
\x18\xd3\x86\x03\x20\x01(\x08\x12!.google.protobuf.EnumValueOptionsR\x07\
|
||||
wireOut:G\n\rwire_debug_in\x18\xd4\x86\x03\x20\x01(\x08\x12!.google.prot\
|
||||
obuf.EnumValueOptionsR\x0bwireDebugIn:I\n\x0ewire_debug_out\x18\xd5\x86\
|
||||
\x03\x20\x01(\x08\x12!.google.protobuf.EnumValueOptionsR\x0cwireDebugOut\
|
||||
:@\n\twire_tiny\x18\xd6\x86\x03\x20\x01(\x08\x12!.google.protobuf.EnumVa\
|
||||
lueOptionsR\x08wireTiny:L\n\x0fwire_bootloader\x18\xd7\x86\x03\x20\x01(\
|
||||
\x08\x12!.google.protobuf.EnumValueOptionsR\x0ewireBootloader:C\n\x0bwir\
|
||||
e_no_fsm\x18\xd8\x86\x03\x20\x01(\x08\x12!.google.protobuf.EnumValueOpti\
|
||||
onsR\twireNoFsm:F\n\x0cbitcoin_only\x18\xe0\xd4\x03\x20\x01(\x08\x12!.go\
|
||||
ogle.protobuf.EnumValueOptionsR\x0bbitcoinOnly:U\n\x17has_bitcoin_only_v\
|
||||
alues\x18\xb9\x8e\x03\x20\x01(\x08\x12\x1c.google.protobuf.EnumOptionsR\
|
||||
\x14hasBitcoinOnlyValues:T\n\x14experimental_message\x18\xa1\x96\x03\x20\
|
||||
\x01(\x08\x12\x1f.google.protobuf.MessageOptionsR\x13experimentalMessage\
|
||||
:>\n\twire_type\x18\xa2\x96\x03\x20\x01(\r\x12\x1f.google.protobuf.Messa\
|
||||
geOptionsR\x08wireType:N\n\x12experimental_field\x18\x89\x9e\x03\x20\x01\
|
||||
(\x08\x12\x1d.google.protobuf.FieldOptionsR\x11experimentalField:U\n\x17\
|
||||
include_in_bitcoin_only\x18\xe0\xd4\x03\x20\x01(\x08\x12\x1c.google.prot\
|
||||
obuf.FileOptionsR\x14includeInBitcoinOnlyB8\n#com.satoshilabs.trezor.lib\
|
||||
.protobufB\rTrezorMessage\x80\xa6\x1d\x01\
|
||||
";
|
||||
|
||||
/// `FileDescriptorProto` object which was a source for this generated file
|
||||
|
2354
rust/trezor-client/src/protos/generated/messages_thp.rs
generated
2354
rust/trezor-client/src/protos/generated/messages_thp.rs
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user