diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 2b9ea58130..f6f530a35d 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -49,7 +49,7 @@ jobs: cat $GITHUB_OUTPUT core_firmware: - name: Build firmware + name: Build firmware (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.type }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest strategy: fail-fast: false @@ -57,10 +57,27 @@ jobs: model: [T2T1, T3B1, T3T1, T3W1] coins: [universal, btconly] type: ${{ fromJSON(github.event_name == 'schedule' && '["normal", "debuglink", "production"]' || '["normal", "debuglink"]') }} + protocol: [v1] include: - model: D001 coins: universal type: normal + - model: T2T1 + coins: universal + type: debuglink + protocol: v2 + - model: T2T1 + coins: btconly + type: debuglink + protocol: v2 + - model: T3T1 + coins: universal + type: debuglink + protocol: v2 + - model: T3T1 + coins: btconly + type: debuglink + protocol: v2 exclude: - model: T3W1 type: production @@ -70,6 +87,7 @@ jobs: PYOPT: ${{ matrix.type == 'debuglink' && '0' || '1' }} PRODUCTION: ${{ matrix.type == 'production' && '1' || '0' }} BOOTLOADER_DEVEL: ${{ matrix.model == 'T3W1' && '1' || '0' }} + THP: ${{ matrix.protocol == 'v2' && '1' || '0'}} steps: - uses: actions/checkout@v4 with: @@ -90,7 +108,7 @@ jobs: if: matrix.coins == 'btconly' && matrix.type != 'debuglink' - uses: actions/upload-artifact@v4 with: - name: core-firmware-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }} + name: core-firmware-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}-protocol_${{ matrix.protocol }} path: | core/build/boardloader/*.bin core/build/bootloader/*.bin @@ -101,7 +119,7 @@ jobs: retention-days: 7 core_emu: - name: Build emu + name: Build emu (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.type }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: param strategy: @@ -112,15 +130,38 @@ jobs: # type: [normal, debuglink] type: [debuglink] asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1] exclude: - type: normal asan: asan + include: + - model: T2T1 + coins: universal + type: debuglink + asan: noasan + protocol: v2 + - model: T2T1 + coins: btconly + type: debuglink + asan: noasan + protocol: v2 + - model: T3T1 + coins: universal + type: debuglink + asan: noasan + protocol: v2 + - model: T3T1 + coins: btconly + type: debuglink + asan: noasan + protocol: v2 env: TREZOR_MODEL: ${{ matrix.model }} BITCOIN_ONLY: ${{ matrix.coins == 'universal' && '0' || '1' }} PYOPT: ${{ matrix.type == 'debuglink' && '0' || '1' }} ADDRESS_SANITIZER: ${{ matrix.asan == 'asan' && '1' || '0' }} LSAN_OPTIONS: "suppressions=../../asan_suppressions.txt" + THP: ${{ matrix.protocol == 'v2' && '1' || '0'}} steps: - uses: actions/checkout@v4 with: @@ -132,7 +173,7 @@ jobs: - run: cp core/build/unix/trezor-emu-core core/build/unix/trezor-emu-core-${{ matrix.model }}-${{ matrix.coins }} - uses: actions/upload-artifact@v4 with: - name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.type }}-${{ matrix.asan }}-protocol_${{ matrix.protocol }} path: | core/build/unix/trezor-emu-core* core/build/bootloader_emu/bootloader.elf @@ -177,7 +218,7 @@ jobs: retention-days: 2 core_unit_python_test: - name: Python unit tests + name: Python unit tests (${{ matrix.model }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: param strategy: @@ -185,10 +226,12 @@ jobs: matrix: model: [T2T1, T3B1, T3T1, T3W1] asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1, v2] env: TREZOR_MODEL: ${{ matrix.model }} ADDRESS_SANITIZER: ${{ matrix.asan == 'asan' && '1' || '0' }} LSAN_OPTIONS: "suppressions=../../asan_suppressions.txt" + THP: ${{ matrix.protocol == 'v2' && '1' || '0'}} steps: - uses: actions/checkout@v4 with: @@ -198,7 +241,7 @@ jobs: - run: nix-shell --run "poetry run make -C core test" core_unit_rust_test: - name: Rust unit tests + name: Rust unit tests (${{ matrix.model }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -208,12 +251,14 @@ jobs: matrix: model: [T2T1, T3B1, T3T1] asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1, v2] env: TREZOR_MODEL: ${{ matrix.model }} ADDRESS_SANITIZER: ${{ matrix.asan == 'asan' && '1' || '0' }} RUSTC_BOOTSTRAP: ${{ matrix.asan == 'asan' && '1' || '0' }} RUSTFLAGS: ${{ matrix.asan == 'asan' && '-Z sanitizer=address' || '' }} LSAN_OPTIONS: "suppressions=../../asan_suppressions.txt" + THP: ${{ matrix.protocol == 'v2' && '1' || '0'}} steps: - uses: actions/checkout@v4 with: @@ -237,7 +282,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-noasan + name: core-emu-${{ matrix.model }}-universal-debuglink-noasan-protocol_v1 path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment @@ -248,7 +293,7 @@ jobs: # See artifacts for a comprehensive report of UI. # See [docs/tests/ui-tests](../tests/ui-tests.md) for more info. core_device_test: - name: Device tests + name: Device tests (${{ matrix.model }}, ${{ matrix.coins }}, ${{ matrix.asan }}, ${{ matrix.lang }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -260,6 +305,13 @@ jobs: coins: [universal, btconly] asan: ${{ fromJSON(needs.param.outputs.asan) }} lang: ${{ fromJSON(needs.param.outputs.test_lang) }} + protocol: [v1] + include: + - model: T2T1 + coins: universal + asan: noasan + lang: en + protocol: v2 env: TREZOR_PROFILING: ${{ matrix.asan == 'noasan' && '1' || '0' }} TREZOR_MODEL: ${{ matrix.model }} @@ -268,13 +320,14 @@ jobs: PYTEST_TIMEOUT: ${{ matrix.asan == 'asan' && 600 || 400 }} ACTIONS_DO_UI_TEST: ${{ matrix.coins == 'universal' && matrix.asan == 'noasan' }} TEST_LANG: ${{ matrix.lang }} + THP: ${{ matrix.protocol == 'v2' && '1' || '0'}} steps: - uses: actions/checkout@v4 with: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-debuglink-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-${{ matrix.coins }}-debuglink-${{ matrix.asan }}-protocol_${{ matrix.protocol }} path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment @@ -283,7 +336,7 @@ jobs: if: failure() - uses: actions/upload-artifact@v4 with: - name: core-test-device-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.lang }}-${{ matrix.asan }} + name: core-test-device-${{ matrix.model }}-${{ matrix.coins }}-${{ matrix.lang }}-${{ matrix.asan }}-protocol_${{ matrix.protocol }} path: tests/trezor.log retention-days: 7 if: always() @@ -299,7 +352,7 @@ jobs: # Click tests - UI. # See [docs/tests/click-tests](../tests/click-tests.md) for more info. core_click_test: - name: Click tests + name: Click tests (${{ matrix.model }}, ${{ matrix.asan }}, ${{ matrix.lang }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -311,6 +364,12 @@ jobs: model: [T2T1, T3B1, T3T1] asan: ${{ fromJSON(needs.param.outputs.asan) }} lang: ${{ fromJSON(needs.param.outputs.test_lang) }} + protocol: [v1] + include: + - model: T2T1 + asan: noasan + lang: en + protocol: v2 env: TREZOR_PROFILING: ${{ matrix.asan == 'noasan' && '1' || '0' }} # MULTICORE: 4 # more could interfere with other jobs @@ -322,7 +381,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment @@ -332,7 +391,7 @@ jobs: if: ${{ matrix.asan == 'asan' }} - uses: actions/upload-artifact@v4 with: - name: core-test-click-${{ matrix.model }}-${{ matrix.lang }}-${{ matrix.asan }} + name: core-test-click-${{ matrix.model }}-${{ matrix.lang }}-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: tests/trezor.log retention-days: 7 if: always() @@ -349,7 +408,7 @@ jobs: # Upgrade tests. # See [docs/tests/upgrade-tests](../tests/upgrade-tests.md) for more info. core_upgrade_test: - name: Upgrade tests + name: Upgrade tests (${{ matrix.model }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -361,6 +420,7 @@ jobs: # FIXME: T3T1 https://github.com/trezor/trezor-firmware/issues/3595 model: [T2T1] asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1, v2] env: TREZOR_UPGRADE_TEST: core PYTEST_TIMEOUT: 400 @@ -370,7 +430,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment @@ -380,7 +440,7 @@ jobs: # Persistence tests - UI. core_persistence_test: - name: Persistence tests + name: Persistence tests (${{ matrix.model }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -391,6 +451,11 @@ jobs: matrix: model: [T2T1, T3B1, T3T1] asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1] + include: + - model: T2T1 + asan: noasan + protocol: v2 env: TREZOR_PROFILING: ${{ matrix.asan == 'noasan' && '1' || '0' }} PYTEST_TIMEOUT: 400 @@ -400,7 +465,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment @@ -433,7 +498,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-noasan + name: core-emu-${{ matrix.model }}-universal-debuglink-noasan-protocol_v1 path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment # XXX poetry maybe not needed @@ -491,7 +556,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-firmware-${{ matrix.model }}-universal-normal # FIXME: s/normal/debuglink/ + name: core-firmware-${{ matrix.model }}-universal-normal-protocol_v1 # FIXME: s/normal/debuglink/ path: core/build - uses: ./.github/actions/environment - run: nix-shell --run "poetry run core/tools/size/checker.py core/build/firmware/firmware.elf" @@ -515,7 +580,7 @@ jobs: fetch-depth: 0 - uses: actions/download-artifact@v4 with: - name: core-firmware-${{ matrix.model }}-universal-normal + name: core-firmware-${{ matrix.model }}-universal-normal-protocol_v1 path: core/build - uses: ./.github/actions/environment - run: nix-shell --run "poetry run core/tools/size/compare_master.py core/build/firmware/firmware.elf -r firmware_elf_size_report.txt" @@ -527,7 +592,7 @@ jobs: # Monero tests. core_monero_test: - name: Monero test + name: Monero test (${{ matrix.model }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -537,6 +602,11 @@ jobs: matrix: model: [T2T1, T3B1, T3T1] asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1] + include: + - model: T2T1 + asan: noasan + protocol: v2 env: TREZOR_PROFILING: ${{ matrix.asan == 'noasan' && '1' || '0' }} PYTEST_TIMEOUT: 400 @@ -546,7 +616,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: cachix/install-nix-action@v23 @@ -557,7 +627,7 @@ jobs: - run: nix-shell --arg fullDeps true --run "unset _PYTHON_SYSCONFIGDATA_NAME && poetry run make -C core test_emu_monero" - uses: actions/upload-artifact@v4 with: - name: core-test-monero-${{ matrix.model }}-${{ matrix.asan }} + name: core-test-monero-${{ matrix.model }}-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: | tests/trezor.log core/tests/trezor_monero_tests.log @@ -568,7 +638,7 @@ jobs: # Tests for U2F and HID. core_u2f_test: - name: U2F test + name: U2F test (${{ matrix.model }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -578,6 +648,11 @@ jobs: matrix: model: [T2T1, T3B1, T3T1] asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1] + include: + - model: T2T1 + asan: noasan + protocol: v2 env: TREZOR_PROFILING: ${{ matrix.asan == 'noasan' && '1' || '0' }} PYTEST_TIMEOUT: 400 @@ -587,7 +662,7 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment @@ -595,7 +670,7 @@ jobs: - run: nix-shell --run "poetry run make -C core test_emu_u2f" - uses: actions/upload-artifact@v4 with: - name: core-test-u2f-${{ matrix.model }}-${{ matrix.asan }} + name: core-test-u2f-${{ matrix.model }}-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: tests/trezor.log retention-days: 7 if: always() @@ -603,7 +678,7 @@ jobs: # FIDO2 device tests. core_fido2_test: - name: FIDO2 test + name: FIDO2 test (${{ matrix.model }}, ${{ matrix.asan }}${{ matrix.protocol=='v2' && ', THP' || ''}}) runs-on: ubuntu-latest needs: - param @@ -613,6 +688,11 @@ jobs: matrix: model: [T2T1, T3T1] # XXX T3B1 https://github.com/trezor/trezor-firmware/issues/2724 asan: ${{ fromJSON(needs.param.outputs.asan) }} + protocol: [v1] + include: + - model: T2T1 + asan: noasan + protocol: v2 env: TREZOR_PROFILING: ${{ matrix.asan == 'noasan' && '1' || '0' }} PYTEST_TIMEOUT: 400 @@ -622,14 +702,14 @@ jobs: submodules: recursive - uses: actions/download-artifact@v4 with: - name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }} + name: core-emu-${{ matrix.model }}-universal-debuglink-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: core/build - run: chmod +x core/build/unix/trezor-emu-core* - uses: ./.github/actions/environment - run: nix-shell --run "poetry run make -C core test_emu_fido2" - uses: actions/upload-artifact@v4 with: - name: core-test-fido2-${{ matrix.model }}-${{ matrix.asan }} + name: core-test-fido2-${{ matrix.model }}-${{ matrix.asan }}-protocol_${{matrix.protocol}} path: | tests/trezor.log retention-days: 7 @@ -728,7 +808,7 @@ jobs: steps: - uses: actions/download-artifact@v4 with: - pattern: core-emu*debuglink-noasan + pattern: core-emu*debuglink-noasan-protocol_v* merge-multiple: true - name: Configure aws credentials uses: aws-actions/configure-aws-credentials@v4 @@ -751,7 +831,7 @@ jobs: steps: - uses: actions/download-artifact@v4 with: - pattern: core-emu*debuglink-noasan + pattern: core-emu*debuglink-noasan-protocol_v* merge-multiple: true - name: Configure aws credentials uses: aws-actions/configure-aws-credentials@v4 diff --git a/common/protob/messages-common.proto b/common/protob/messages-common.proto index 3e8cb9537c..4dd16add5b 100644 --- a/common/protob/messages-common.proto +++ b/common/protob/messages-common.proto @@ -39,6 +39,10 @@ message Failure { Failure_PinMismatch = 12; Failure_WipeCodeMismatch = 13; Failure_InvalidSession = 14; + Failure_ThpUnallocatedSession = 15; + Failure_InvalidProtocol = 16; + Failure_BufferError = 17; + Failure_DeviceIsBusy = 18; Failure_FirmwareError = 99; } } diff --git a/common/protob/messages-debug.proto b/common/protob/messages-debug.proto index 3727b6243f..f91d47b7e4 100644 --- a/common/protob/messages-debug.proto +++ b/common/protob/messages-debug.proto @@ -85,7 +85,7 @@ message DebugLinkRecordScreen { } /** - * Request: Computer asks for device state + * Request: Host asks for device state * @start * @next DebugLinkState */ @@ -134,6 +134,29 @@ message DebugLinkState { repeated string tokens = 13; // current layout represented as a list of string tokens } +/** + * Request: Host asks for device pairing info + * @start + * @next DebugLinkPairingInfo + */ + message DebugLinkGetPairingInfo { + optional bytes channel_id = 1; // ID of the THP channel to get pairing info from + optional bytes handshake_hash = 2; // handshake hash of the THP channel + optional bytes nfc_secret_host = 3; // host's NFC secret (In case of NFC pairing) +} + + /** + * Response: Device pairing info + * @end + */ + message DebugLinkPairingInfo { + optional bytes channel_id = 1; // ID of the THP channel the pairing info is from + optional bytes handshake_hash = 2; // handshake hash of the THP channel + optional uint32 code_entry_code = 3; // CodeEntry pairing code + optional bytes code_qr_code = 4; // QrCode pairing code + optional bytes nfc_secret_trezor = 5; // NFC secret used in NFC pairing +} + /** * Request: Ask device to restart * @start diff --git a/common/protob/messages-thp.proto b/common/protob/messages-thp.proto index c05d9f64d7..d798f882fd 100644 --- a/common/protob/messages-thp.proto +++ b/common/protob/messages-thp.proto @@ -9,6 +9,224 @@ import "options.proto"; option (include_in_bitcoin_only) = true; +/** + * Mapping between Trezor wire identifier (uint) and a Thp protobuf message + */ +enum ThpMessageType { + reserved 0 to 999; // Values reserved by other messages, see messages.proto + + ThpMessageType_ThpCreateNewSession = 1000 [(bitcoin_only)=true]; + ThpMessageType_ThpPairingRequest = 1006 [(bitcoin_only) = true]; + ThpMessageType_ThpPairingRequestApproved = 1007 [(bitcoin_only) = true]; + ThpMessageType_ThpSelectMethod = 1008 [(bitcoin_only) = true]; + ThpMessageType_ThpPairingPreparationsFinished = 1009 [(bitcoin_only) = true]; + ThpMessageType_ThpCredentialRequest = 1010 [(bitcoin_only) = true]; + ThpMessageType_ThpCredentialResponse = 1011 [(bitcoin_only) = true]; + ThpMessageType_ThpEndRequest = 1012 [(bitcoin_only) = true]; + ThpMessageType_ThpEndResponse = 1013 [(bitcoin_only) = true]; + ThpMessageType_ThpCodeEntryCommitment = 1016 [(bitcoin_only)=true]; + ThpMessageType_ThpCodeEntryChallenge = 1017 [(bitcoin_only)=true]; + ThpMessageType_ThpCodeEntryCpaceTrezor = 1018 [(bitcoin_only)=true]; + ThpMessageType_ThpCodeEntryCpaceHostTag = 1019 [(bitcoin_only)=true]; + ThpMessageType_ThpCodeEntrySecret = 1020 [(bitcoin_only)=true]; + ThpMessageType_ThpQrCodeTag = 1024 [(bitcoin_only)=true]; + ThpMessageType_ThpQrCodeSecret = 1025 [(bitcoin_only)=true]; + ThpMessageType_ThpNfcTagHost = 1032 [(bitcoin_only)=true]; + ThpMessageType_ThpNfcTagTrezor = 1033 [(bitcoin_only)=true]; + + reserved 1100 to 2147483647; // Values reserved by other messages, see messages.proto +} + + +/** + * Numeric identifiers of pairing methods. + * @embed + */ +enum ThpPairingMethod { + SkipPairing = 1; // Trust without MITM protection. + CodeEntry = 2; // User types code diplayed on Trezor into the host application. + QrCode = 3; // User scans code displayed on Trezor into host application. + NFC = 4; // Trezor and host application exchange authentication secrets 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 uint32 protocol_version_major = 3; // The major version of the communication protocol used by the firmware. + optional uint32 protocol_version_minor = 4; // The minor version of the communication protocol used 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 +} + +/** + * Request: Ask device for a new session with given passphrase. + * @start + * @next Success + */ +message ThpCreateNewSession{ + optional string passphrase = 1; + optional bool on_device = 2; // User wants to enter passphrase on the device + optional bool derive_cardano = 3; // If True, Cardano keys will be derived. Ignored with BTC-only +} + + +/** + * Request: Start pairing process. + * @start + * @next ThpPairingRequestApproved + */ +message ThpPairingRequest{ + optional string host_name = 1; // Human-readable host name +} + +/** + * Response: Host is allowed to start pairing process. + * @start + * @next ThpSelectMethod + */ +message ThpPairingRequestApproved{ +} + +/** + * Request: Start pairing using the method selected. + * @start + * @next ThpPairingPreparationsFinished + * @next ThpCodeEntryCommitment + */ +message ThpSelectMethod { + optional ThpPairingMethod selected_pairing_method = 1; +} + +/** + * Response: Pairing is ready for user input / OOB communication. + * @next ThpCodeEntryCpace + * @next ThpQrCodeTag + * @next ThpNfcTagHost + */ +message ThpPairingPreparationsFinished{ +} + +/** + * 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 ThpCodeEntryCpaceTrezor + */ +message ThpCodeEntryChallenge { + optional bytes challenge = 1; // Host's random 32-byte challenge +} + + + +/** + * Response: Trezor continues with the CPACE protocol. + * @next ThpCodeEntryCpaceHostTag + */ +message ThpCodeEntryCpaceTrezor { + optional bytes cpace_trezor_public_key = 1; // Trezor's ephemeral CPace public key +} + +/** + * Request: User selected Code Entry option in Host. Host starts CPACE protocol with Trezor. + * @next ThpCodeEntrySecret + */ + message ThpCodeEntryCpaceHostTag { + optional bytes cpace_host_public_key = 1; // Host's ephemeral CPace public key + optional bytes tag = 2; // SHA-256 of shared secret + +} + +/** + * Response: Trezor finishes the CPACE protocol. + * @next ThpCredentialRequest + * @next ThpEndRequest + */ +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. + * @next ThpCredentialRequest + * @next ThpEndRequest + */ +message ThpQrCodeSecret { + optional bytes secret = 1; // Trezor's secret +} + +/** + * Request: User selected Unidirectional NFC pairing option. Host sends an Unidirectional NFC Tag. + * @next ThpNfcTagTrezor + */ +message ThpNfcTagHost { + optional bytes tag = 1; // Host's tag +} + +/** + * Response: Trezor sends the Unidirectioal NFC secret. + * @next ThpCredentialRequest + * @next ThpEndRequest + */ +message ThpNfcTagTrezor { + optional bytes tag = 1; // Trezor's tag +} + +/** + * 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. + optional bool autoconnect = 2; // Whether host wants to autoconnect without user confirmation +} + +/** + * Response: Trezor issues a new pairing credential. + * @next ThpCredentialRequest + * @next ThpEndRequest + */ +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 ThpEndResponse + */ +message ThpEndRequest {} + +/** + * Response: Trezor approves transition to the encrypted traffic phase + * @end + */ +message ThpEndResponse {} + /** * Only for internal use. * @embed @@ -16,6 +234,7 @@ option (include_in_bitcoin_only) = true; message ThpCredentialMetadata { option (internal_only) = true; optional string host_name = 1; // Human-readable host name + optional bool autoconnect = 2; // Whether host is allowed to autoconnect without user confirmation } /** diff --git a/common/protob/messages.proto b/common/protob/messages.proto index ae7fdfda59..f807d8c477 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -134,6 +134,8 @@ enum MessageType { MessageType_DebugLinkWatchLayout = 9006 [(bitcoin_only) = true, (wire_debug_in) = true]; MessageType_DebugLinkResetDebugEvents = 9007 [(bitcoin_only) = true, (wire_debug_in) = true]; MessageType_DebugLinkOptigaSetSecMax = 9008 [(bitcoin_only) = true, (wire_debug_in) = true]; + MessageType_DebugLinkGetPairingInfo = 9009 [(bitcoin_only) = true, (wire_debug_in) = true]; + MessageType_DebugLinkPairingInfo = 9010 [(bitcoin_only) = true, (wire_debug_out) = true]; // Ethereum MessageType_EthereumGetPublicKey = 450 [(wire_in) = true]; diff --git a/common/protob/pb2py b/common/protob/pb2py index d6cbbde171..ea12f8d9c1 100755 --- a/common/protob/pb2py +++ b/common/protob/pb2py @@ -62,6 +62,7 @@ INT_TYPES = ( ) MESSAGE_TYPE_ENUM = "MessageType" +THP_MESSAGE_TYPE_ENUM = "ThpMessageType" LengthDelimited = c.Struct( "len" / c.VarInt, @@ -239,6 +240,9 @@ class ProtoMessage: @classmethod def from_message(cls, descriptor: "Descriptor", message): message_type = find_by_name(descriptor.message_type_enum.value, message.name) + thp_message_type = None + if not isinstance(descriptor.thp_message_type_enum,tuple): + thp_message_type = find_by_name(descriptor.thp_message_type_enum.value, message.name) # use extensions set on the message_type entry (if any) extensions = descriptor.get_extensions(message_type) # override with extensions set on the message itself @@ -248,6 +252,8 @@ class ProtoMessage: wire_type = extensions["wire_type"] elif message_type is not None: wire_type = message_type.number + elif thp_message_type is not None: + wire_type = thp_message_type.number else: wire_type = None @@ -351,10 +357,13 @@ class Descriptor: ] logging.debug(f"found {len(self.files)} bitcoin-only files") - # find message_type enum + # find message_type and thp_message_type enum top_level_enums = itertools.chain.from_iterable(f.enum_type for f in self.files) self.message_type_enum = find_by_name(top_level_enums, MESSAGE_TYPE_ENUM, ()) + top_level_enums = itertools.chain.from_iterable(f.enum_type for f in self.files) + self.thp_message_type_enum = find_by_name(top_level_enums, THP_MESSAGE_TYPE_ENUM, ()) self.convert_enum_value_names(self.message_type_enum) + self.convert_enum_value_names(self.thp_message_type_enum) # find messages and enums self.messages = [] @@ -423,6 +432,8 @@ class Descriptor: self._nested_types_from_message(nested.orig) def convert_enum_value_names(self, enum): + if isinstance(enum,tuple): + return for value in enum.value: value.name = strip_enum_prefix(enum.name, value.name) @@ -558,6 +569,8 @@ class RustBlobRenderer: enums = [] cursor = 0 for enum in sorted(self.descriptor.enums, key=lambda e: e.name): + if enum.name == "MessageType": + continue self.enum_map[enum.name] = cursor enum_blob = ENUM_ENTRY.build(sorted(v.number for v in enum.value)) enums.append(enum_blob) diff --git a/core/Makefile b/core/Makefile index 3bca1bb31d..6f987a48b5 100644 --- a/core/Makefile +++ b/core/Makefile @@ -296,6 +296,10 @@ build_unix: templates ## build unix port build_unix_frozen: templates build_cross ## build unix port with frozen modules $(SCONS) $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) TREZOR_EMULATOR_FROZEN=1 +build_unix_frozen_debug: templates build_cross ## build unix port with frozen modules and DEBUG (PYOPT="0") + $(SCONS) $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) TREZOR_EMULATOR_FROZEN=1 \ + PYOPT=0 + build_unix_debug: templates ## build unix port $(SCONS) --max-drift=1 $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \ TREZOR_EMULATOR_ASAN=1 TREZOR_EMULATOR_DEBUGGABLE=1 diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 57aeb9b677..e524843165 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -578,14 +578,23 @@ if FROZEN: ] if not EVERYTHING else [] )) - SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/*.py')) - SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py')) + if THP: + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/thp/*.py')) + if not THP or PYOPT == '0': + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py')) - SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py', - exclude=[ - SOURCE_PY_DIR + 'storage/sd_salt.py', - ] if not SDCARD else [] - )) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/*.py')) + + + exclude_list = [] + if 'sd_card' not in FEATURES_AVAILABLE: + exclude_list.append(SOURCE_PY_DIR + 'storage/sd_salt.py') + if THP and PYOPT == '1': + exclude_list.append(SOURCE_PY_DIR + 'storage/cache_codec.py') + if not THP: + exclude_list.append(SOURCE_PY_DIR + 'storage/cache_thp.py') + + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py', exclude=exclude_list)) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/*.py', diff --git a/core/SConscript.unix b/core/SConscript.unix index 6b0fd17ff8..13369d2c66 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -637,14 +637,23 @@ if FROZEN: ] if not EVERYTHING else [] )) - SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/*.py')) - SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py')) + if THP: + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/thp/*.py')) + if not THP or PYOPT == '0': + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py')) - SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py', - exclude=[ - SOURCE_PY_DIR + 'storage/sd_salt.py', - ] if 'sd_card' not in FEATURES_AVAILABLE else [] - )) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/*.py')) + + + exclude_list = [] + if 'sd_card' not in FEATURES_AVAILABLE: + exclude_list.append(SOURCE_PY_DIR + 'storage/sd_salt.py') + if THP and PYOPT == '1': + exclude_list.append(SOURCE_PY_DIR + 'storage/cache_codec.py') + if not THP: + exclude_list.append(SOURCE_PY_DIR + 'storage/cache_thp.py') + + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py', exclude=exclude_list)) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/*.py', diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 70651ea3a8..2a23e58aee 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -51,6 +51,8 @@ storage.cache_codec import storage.cache_codec storage.cache_common import storage.cache_common +storage.cache_thp +import storage.cache_thp storage.common import storage.common storage.debug @@ -419,10 +421,50 @@ apps.workflow_handlers import apps.workflow_handlers if utils.USE_THP: + trezor.enums.ThpMessageType + import trezor.enums.ThpMessageType + trezor.enums.ThpPairingMethod + import trezor.enums.ThpPairingMethod + trezor.wire.thp + import trezor.wire.thp + trezor.wire.thp.alternating_bit_protocol + import trezor.wire.thp.alternating_bit_protocol + trezor.wire.thp.channel + import trezor.wire.thp.channel + trezor.wire.thp.channel_manager + import trezor.wire.thp.channel_manager + trezor.wire.thp.checksum + import trezor.wire.thp.checksum + trezor.wire.thp.control_byte + import trezor.wire.thp.control_byte + trezor.wire.thp.cpace + import trezor.wire.thp.cpace + trezor.wire.thp.crypto + import trezor.wire.thp.crypto + trezor.wire.thp.interface_manager + import trezor.wire.thp.interface_manager + trezor.wire.thp.memory_manager + import trezor.wire.thp.memory_manager + trezor.wire.thp.pairing_context + import trezor.wire.thp.pairing_context + trezor.wire.thp.received_message_handler + import trezor.wire.thp.received_message_handler + trezor.wire.thp.session_context + import trezor.wire.thp.session_context + trezor.wire.thp.session_manager + import trezor.wire.thp.session_manager + trezor.wire.thp.thp_main + import trezor.wire.thp.thp_main + trezor.wire.thp.transmission_loop + import trezor.wire.thp.transmission_loop + trezor.wire.thp.writer + import trezor.wire.thp.writer apps.thp import apps.thp apps.thp.credential_manager import apps.thp.credential_manager + apps.thp.pairing + import apps.thp.pairing if not utils.BITCOIN_ONLY: trezor.enums.BinanceOrderSide diff --git a/core/src/apps/base.py b/core/src/apps/base.py index 5552fc86ba..6f388d6e49 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING import storage.device as storage_device from storage.cache_common import APP_COMMON_BUSY_DEADLINE_MS, APP_COMMON_SEED from trezor import TR, config, utils, wire, workflow -from trezor.enums import HomescreenFormat, MessageType +from trezor.enums import HomescreenFormat, MessageType, ThpMessageType from trezor.messages import Success, UnlockPath from trezor.ui.layouts import confirm_action from trezor.wire import context @@ -27,6 +27,9 @@ if TYPE_CHECKING: ) from trezor.wire import Handler, Msg + if utils.USE_THP: + from trezor.messages import Failure, ThpCreateNewSession + _SCREENSAVER_IS_ON = False @@ -204,33 +207,103 @@ def get_features() -> Features: return f -async def handle_Initialize(msg: Initialize) -> Features: - import storage.cache_codec as cache_codec +if utils.USE_THP: - session_id = cache_codec.start_session(msg.session_id) + async def handle_ThpCreateNewSession( + message: ThpCreateNewSession, + ) -> Success | Failure: + """ + Creates a new `ThpSession` based on the provided parameters and returns a + `Success` message on success. - if not utils.BITCOIN_ONLY: - from storage.cache_common import APP_COMMON_DERIVE_CARDANO + Returns an appropriate `Failure` message if session creation fails. + """ + from trezor import log, loop + from trezor.enums import FailureType + from trezor.messages import Failure + from trezor.wire import NotInitialized + from trezor.wire.context import get_context + from trezor.wire.errors import ActionCancelled, DataError + from trezor.wire.thp.session_context import GenericSessionContext + from trezor.wire.thp.session_manager import get_new_session_context - derive_cardano = context.cache_get_bool(APP_COMMON_DERIVE_CARDANO) - have_seed = context.cache_is_set(APP_COMMON_SEED) - if ( - have_seed - and msg.derive_cardano is not None - and msg.derive_cardano != bool(derive_cardano) - ): - # seed is already derived, and host wants to change derive_cardano setting - # => create a new session - cache_codec.end_current_session() - session_id = cache_codec.start_session() - have_seed = False + from apps.common.seed import derive_and_store_roots - if not have_seed: - context.cache_set_bool(APP_COMMON_DERIVE_CARDANO, bool(msg.derive_cardano)) + ctx = get_context() - features = get_features() - features.session_id = session_id - return features + # Assert that context `ctx` is `GenericSessionContext` + assert isinstance(ctx, GenericSessionContext) + + channel = ctx.channel + session_id = ctx.session_id + + # Do not use `ctx` beyond this point, as it is techically + # allowed to change in between await statements + + if not 0 <= session_id <= 255: + return Failure( + code=FailureType.DataError, + message="Invalid session_id for session creation.", + ) + + new_session = get_new_session_context( + channel_ctx=channel, session_id=session_id + ) + try: + await unlock_device() + await derive_and_store_roots(new_session, message) + except DataError as e: + return Failure(code=FailureType.DataError, message=e.message) + except ActionCancelled as e: + return Failure(code=FailureType.ActionCancelled, message=e.message) + except NotInitialized as e: + return Failure(code=FailureType.NotInitialized, message=e.message) + # TODO handle other errors (`Exception` when "Cardano icarus secret is already set!") + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, + "New session with sid %d and passphrase %s created.", + session_id, + message.passphrase if message.passphrase is not None else "", + ) + + channel.sessions[new_session.session_id] = new_session + loop.schedule(new_session.handle()) + + return Success(message="New session created.") + +else: + + async def handle_Initialize(msg: Initialize) -> Features: + import storage.cache_codec as cache_codec + + session_id = cache_codec.start_session(msg.session_id) + + if not utils.BITCOIN_ONLY: + from storage.cache_common import APP_COMMON_DERIVE_CARDANO + + derive_cardano = context.cache_get_bool(APP_COMMON_DERIVE_CARDANO) + have_seed = context.cache_is_set(APP_COMMON_SEED) + if ( + have_seed + and msg.derive_cardano is not None + and msg.derive_cardano != bool(derive_cardano) + ): + # seed is already derived, and host wants to change derive_cardano setting + # => create a new session + cache_codec.end_current_session() + session_id = cache_codec.start_session() + have_seed = False + + if not have_seed: + context.cache_set_bool( + APP_COMMON_DERIVE_CARDANO, bool(msg.derive_cardano) + ) + + features = get_features() + features.session_id = session_id + return features async def handle_GetFeatures(msg: GetFeatures) -> Features: @@ -464,8 +537,12 @@ def boot() -> None: MT = MessageType # local_cache_global # Register workflow handlers + if utils.USE_THP: + TMT = ThpMessageType + workflow_handlers.register(TMT.ThpCreateNewSession, handle_ThpCreateNewSession) + else: + workflow_handlers.register(MT.Initialize, handle_Initialize) for msg_type, handler in [ - (MT.Initialize, handle_Initialize), (MT.GetFeatures, handle_GetFeatures), (MT.Cancel, handle_Cancel), (MT.LockDevice, handle_LockDevice), diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index 35f6b3f60c..781b29b59f 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -6,9 +6,8 @@ from storage.cache_common import ( APP_CARDANO_ICARUS_TREZOR_SECRET, APP_COMMON_DERIVE_CARDANO, ) -from trezor import wire +from trezor import utils, wire from trezor.crypto import cardano -from trezor.wire import context from apps.common import mnemonic from apps.common.seed import get_seed @@ -21,6 +20,7 @@ if TYPE_CHECKING: from trezor import messages from trezor.crypto import bip32 from trezor.enums import CardanoDerivationType + from trezor.wire.protocol_common import Context from apps.common.keychain import Handler, MsgOut from apps.common.paths import Bip32Path @@ -116,9 +116,9 @@ def is_minting_path(path: Bip32Path) -> bool: return path[: len(MINTING_ROOT)] == MINTING_ROOT -def derive_and_store_secrets(passphrase: str) -> None: +def derive_and_store_secrets(ctx: Context, passphrase: str) -> None: assert device.is_initialized() - assert context.cache_get_bool(APP_COMMON_DERIVE_CARDANO) + assert ctx.cache.get_bool(APP_COMMON_DERIVE_CARDANO) if not mnemonic.is_bip39(): # nothing to do for SLIP-39, where we can derive the root from the main seed @@ -138,14 +138,13 @@ def derive_and_store_secrets(passphrase: str) -> None: else: icarus_trezor_secret = icarus_secret - context.cache_set(APP_CARDANO_ICARUS_SECRET, icarus_secret) - context.cache_set(APP_CARDANO_ICARUS_TREZOR_SECRET, icarus_trezor_secret) + ctx.cache.set(APP_CARDANO_ICARUS_SECRET, icarus_secret) + ctx.cache.set(APP_CARDANO_ICARUS_TREZOR_SECRET, icarus_trezor_secret) async def _get_keychain_bip39(derivation_type: CardanoDerivationType) -> Keychain: from trezor.enums import CardanoDerivationType - - from apps.common.seed import derive_and_store_roots + from trezor.wire import context if not device.is_initialized(): raise wire.NotInitialized("Device is not initialized") @@ -164,10 +163,13 @@ async def _get_keychain_bip39(derivation_type: CardanoDerivationType) -> Keychai # _get_secret secret = context.cache_get(cache_entry) - if secret is None: - await derive_and_store_roots() - secret = context.cache_get(cache_entry) - assert secret is not None + if not utils.USE_THP: + if secret is None: + from apps.common.seed import derive_and_store_roots_legacy + + await derive_and_store_roots_legacy() + secret = context.cache_get(cache_entry) + assert secret is not None root = cardano.from_secret(secret) return Keychain(root) diff --git a/core/src/apps/common/backup.py b/core/src/apps/common/backup.py index fc56f42f9b..8037aba698 100644 --- a/core/src/apps/common/backup.py +++ b/core/src/apps/common/backup.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from storage.cache_common import APP_RECOVERY_REPEATED_BACKUP_UNLOCKED -from trezor import wire +from trezor import utils, wire from trezor.enums import MessageType from trezor.wire import context from trezor.wire.message_handler import filters, remove_filter @@ -24,14 +24,23 @@ def deactivate_repeated_backup() -> None: remove_filter(_repeated_backup_filter) -_ALLOW_WHILE_REPEATED_BACKUP_UNLOCKED = ( - MessageType.Initialize, - MessageType.GetFeatures, - MessageType.EndSession, - MessageType.BackupDevice, - MessageType.WipeDevice, - MessageType.Cancel, -) +if utils.USE_THP: + _ALLOW_WHILE_REPEATED_BACKUP_UNLOCKED = ( + MessageType.GetFeatures, + MessageType.EndSession, + MessageType.BackupDevice, + MessageType.WipeDevice, + MessageType.Cancel, + ) +else: + _ALLOW_WHILE_REPEATED_BACKUP_UNLOCKED = ( + MessageType.Initialize, + MessageType.GetFeatures, + MessageType.EndSession, + MessageType.BackupDevice, + MessageType.WipeDevice, + MessageType.Cancel, + ) def _repeated_backup_filter(msg_type: int, prev_handler: Handler[Msg]) -> Handler[Msg]: diff --git a/core/src/apps/common/keychain.py b/core/src/apps/common/keychain.py index 16913d1529..7959789b25 100644 --- a/core/src/apps/common/keychain.py +++ b/core/src/apps/common/keychain.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from trezor import utils from trezor.crypto import bip32 from trezor.wire import DataError @@ -172,6 +173,9 @@ async def get_keychain( ) -> Keychain: from .seed import get_seed + if not utils.USE_THP: + pass + # try to ask for passphrase here seed = await get_seed() keychain = Keychain(seed, curve, schemas, slip21_namespaces) return keychain diff --git a/core/src/apps/common/passphrase.py b/core/src/apps/common/passphrase.py index ef8bb5b185..d150dd4736 100644 --- a/core/src/apps/common/passphrase.py +++ b/core/src/apps/common/passphrase.py @@ -1,84 +1,122 @@ from micropython import const +from typing import TYPE_CHECKING import storage.device as storage_device +from trezor import utils from trezor.wire import DataError _MAX_PASSPHRASE_LEN = const(50) +if TYPE_CHECKING: + from trezor.messages import ThpCreateNewSession + def is_enabled() -> bool: return storage_device.is_passphrase_enabled() -async def get() -> str: - from trezor import workflow - +async def get_passphrase(msg: ThpCreateNewSession) -> str: if not is_enabled(): return "" + + if msg.on_device or storage_device.get_passphrase_always_on_device(): + passphrase = await _get_on_device() else: - workflow.close_others() # request exclusive UI access - if storage_device.get_passphrase_always_on_device(): - from trezor.ui.layouts import request_passphrase_on_device + passphrase = msg.passphrase or "" + if passphrase: + await _handle_displaying_passphrase_from_host(passphrase) - passphrase = await request_passphrase_on_device(_MAX_PASSPHRASE_LEN) - else: - passphrase = await _request_on_host() - if len(passphrase.encode()) > _MAX_PASSPHRASE_LEN: - raise DataError(f"Maximum passphrase length is {_MAX_PASSPHRASE_LEN} bytes") - - return passphrase - - -async def _request_on_host() -> str: - from trezor import TR - from trezor.messages import PassphraseAck, PassphraseRequest - from trezor.ui.layouts import request_passphrase_on_host - from trezor.wire.context import call - - request_passphrase_on_host() - - request = PassphraseRequest() - ack = await call(request, PassphraseAck) - passphrase = ack.passphrase # local_cache_attribute - - if ack.on_device: - from trezor.ui.layouts import request_passphrase_on_device - - if passphrase is not None: - raise DataError("Passphrase provided when it should not be") - return await request_passphrase_on_device(_MAX_PASSPHRASE_LEN) - - if passphrase is None: - raise DataError( - "Passphrase not provided and on_device is False. Use empty string to set an empty passphrase." - ) - - # non-empty passphrase - if passphrase: - from trezor.ui.layouts import confirm_action, confirm_blob - - # We want to hide the passphrase, or show it, according to settings. - if storage_device.get_hide_passphrase_from_host(): - await confirm_action( - "passphrase_host1_hidden", - TR.passphrase__wallet, - description=TR.passphrase__from_host_not_shown, - prompt_screen=True, - prompt_title=TR.passphrase__access_wallet, - ) - else: - await confirm_action( - "passphrase_host1", - TR.passphrase__wallet, - description=TR.passphrase__next_screen_will_show_passphrase, - verb=TR.buttons__continue, - ) - - await confirm_blob( - "passphrase_host2", - TR.passphrase__title_confirm, - passphrase, - info=False, - ) + if len(passphrase.encode()) > _MAX_PASSPHRASE_LEN: + raise DataError(f"Maximum passphrase length is {_MAX_PASSPHRASE_LEN} bytes") return passphrase + + +async def _get_on_device() -> str: + from trezor import workflow + from trezor.ui.layouts import request_passphrase_on_device + + workflow.close_others() # request exclusive UI access + passphrase = await request_passphrase_on_device(_MAX_PASSPHRASE_LEN) + + return passphrase + + +async def _handle_displaying_passphrase_from_host(passphrase: str) -> None: + from trezor import TR + from trezor.ui.layouts import confirm_action, confirm_blob + + # We want to hide the passphrase, or show it, according to settings. + if storage_device.get_hide_passphrase_from_host(): + await confirm_action( + "passphrase_host1_hidden", + TR.passphrase__wallet, + description=TR.passphrase__from_host_not_shown, + prompt_screen=True, + prompt_title=TR.passphrase__access_wallet, + ) + else: + await confirm_action( + "passphrase_host1", + TR.passphrase__wallet, + description=TR.passphrase__next_screen_will_show_passphrase, + verb=TR.buttons__continue, + ) + + await confirm_blob( + "passphrase_host2", + TR.passphrase__title_confirm, + passphrase, + ) + + +if not utils.USE_THP: + + async def get() -> str: + from trezor import workflow + + if not is_enabled(): + return "" + else: + workflow.close_others() # request exclusive UI access + if storage_device.get_passphrase_always_on_device(): + from trezor.ui.layouts import request_passphrase_on_device + + passphrase = await request_passphrase_on_device(_MAX_PASSPHRASE_LEN) + else: + passphrase = await _request_on_host() + if len(passphrase.encode()) > _MAX_PASSPHRASE_LEN: + raise DataError( + f"Maximum passphrase length is {_MAX_PASSPHRASE_LEN} bytes" + ) + + return passphrase + + async def _request_on_host() -> str: + from trezor.messages import PassphraseAck, PassphraseRequest + from trezor.ui.layouts import request_passphrase_on_host + from trezor.wire.context import call + + request_passphrase_on_host() + + request = PassphraseRequest() + ack = await call(request, PassphraseAck) + passphrase = ack.passphrase # local_cache_attribute + + if ack.on_device: + from trezor.ui.layouts import request_passphrase_on_device + + if passphrase is not None: + raise DataError("Passphrase provided when it should not be") + return await request_passphrase_on_device(_MAX_PASSPHRASE_LEN) + + if passphrase is None: + raise DataError( + "Passphrase not provided and on_device is False. Use empty string to set an empty passphrase." + ) + + # non-empty passphrase + if passphrase: + await _handle_displaying_passphrase_from_host(passphrase) + + return passphrase diff --git a/core/src/apps/common/seed.py b/core/src/apps/common/seed.py index b09004ae69..4bb15184f8 100644 --- a/core/src/apps/common/seed.py +++ b/core/src/apps/common/seed.py @@ -5,14 +5,18 @@ from storage.cache_common import APP_COMMON_SEED, APP_COMMON_SEED_WITHOUT_PASSPH from trezor import utils from trezor.crypto import hmac from trezor.wire import context +from trezor.wire.context import get_context +from trezor.wire.errors import DataError from apps.common import cache from . import mnemonic -from .passphrase import get as get_passphrase +from .passphrase import get_passphrase as get_passphrase if TYPE_CHECKING: from trezor.crypto import bip32 + from trezor.messages import ThpCreateNewSession + from trezor.wire.protocol_common import Context from .paths import Bip32Path, Slip21Path @@ -22,6 +26,9 @@ if not utils.BITCOIN_ONLY: APP_COMMON_DERIVE_CARDANO, ) +if not utils.USE_THP: + from .passphrase import get as get_passphrase_legacy + class Slip21Node: """ @@ -54,51 +61,111 @@ class Slip21Node: return Slip21Node(data=self.data) -if not utils.BITCOIN_ONLY: - # === Cardano variant === - # We want to derive both the normal seed and the Cardano seed together, AND - # expose a method for Cardano to do the same +if utils.USE_THP: - async def derive_and_store_roots() -> None: - from trezor import wire - - if not storage_device.is_initialized(): - raise wire.NotInitialized("Device is not initialized") - - need_seed = not context.cache_is_set(APP_COMMON_SEED) - need_cardano_secret = context.cache_get_bool( - APP_COMMON_DERIVE_CARDANO - ) and not context.cache_is_set(APP_CARDANO_ICARUS_SECRET) - - if not need_seed and not need_cardano_secret: - return - - passphrase = await get_passphrase() - - if need_seed: - common_seed = mnemonic.get_seed(passphrase) - context.cache_set(APP_COMMON_SEED, common_seed) - - if need_cardano_secret: - from apps.cardano.seed import derive_and_store_secrets - - derive_and_store_secrets(passphrase) - - @cache.stored_async(APP_COMMON_SEED) - async def get_seed() -> bytes: - await derive_and_store_roots() + async def get_seed() -> bytes: # type: ignore [Function declaration "get_seed" is obscured by a declaration of the same name] common_seed = context.cache_get(APP_COMMON_SEED) assert common_seed is not None return common_seed -else: - # === Bitcoin-only variant === - # We use the simple version of `get_seed` that never needs to derive anything else. + if utils.BITCOIN_ONLY: + # === Bitcoin_only variant === + # We want to derive the normal seed ONLY - @cache.stored_async(APP_COMMON_SEED) - async def get_seed() -> bytes: - passphrase = await get_passphrase() - return mnemonic.get_seed(passphrase) + async def derive_and_store_roots( + ctx: Context, msg: ThpCreateNewSession + ) -> None: + + if msg.passphrase is not None and msg.on_device: + raise DataError("Passphrase provided when it shouldn't be!") + + if ctx.cache.is_set(APP_COMMON_SEED): + raise Exception("Seed is already set!") + + from trezor import wire + + if not storage_device.is_initialized(): + raise wire.NotInitialized("Device is not initialized") + + passphrase = await get_passphrase(msg) + common_seed = mnemonic.get_seed(passphrase) + ctx.cache.set(APP_COMMON_SEED, common_seed) + + else: + # === Cardano variant === + # We want to derive both the normal seed and the Cardano seed together + async def derive_and_store_roots( + ctx: Context, msg: ThpCreateNewSession + ) -> None: + + if msg.passphrase is not None and msg.on_device: + raise DataError("Passphrase provided when it shouldn't be!") + + from trezor import wire + + if not storage_device.is_initialized(): + raise wire.NotInitialized("Device is not initialized") + + if ctx.cache.is_set(APP_CARDANO_ICARUS_SECRET): + raise Exception("Cardano icarus secret is already set!") + + passphrase = await get_passphrase(msg) + common_seed = mnemonic.get_seed(passphrase) + ctx.cache.set(APP_COMMON_SEED, common_seed) + + if msg.derive_cardano: + from apps.cardano.seed import derive_and_store_secrets + + ctx.cache.set_bool(APP_COMMON_DERIVE_CARDANO, True) + derive_and_store_secrets(ctx, passphrase) + +else: + if utils.BITCOIN_ONLY: + # === Bitcoin-only variant === + # We use the simple version of `get_seed` that never needs to derive anything else. + + @cache.stored_async(APP_COMMON_SEED) + async def get_seed() -> bytes: + passphrase = await get_passphrase_legacy() + return mnemonic.get_seed(passphrase=passphrase) + + else: + # === Cardano variant === + # We want to derive both the normal seed and the Cardano seed together, AND + # expose a method for Cardano to do the same + + @cache.stored_async(APP_COMMON_SEED) + async def get_seed() -> bytes: + await derive_and_store_roots_legacy() + common_seed = context.cache_get(APP_COMMON_SEED) + assert common_seed is not None + return common_seed + + async def derive_and_store_roots_legacy() -> None: + from trezor import wire + + if not storage_device.is_initialized(): + raise wire.NotInitialized("Device is not initialized") + + ctx = get_context() + need_seed = not ctx.cache.is_set(APP_COMMON_SEED) + need_cardano_secret = ctx.cache.get_bool( + APP_COMMON_DERIVE_CARDANO + ) and not ctx.cache.is_set(APP_CARDANO_ICARUS_SECRET) + + if not need_seed and not need_cardano_secret: + return + + passphrase = await get_passphrase_legacy() + + if need_seed: + common_seed = mnemonic.get_seed(passphrase) + ctx.cache.set(APP_COMMON_SEED, common_seed) + + if need_cardano_secret: + from apps.cardano.seed import derive_and_store_secrets + + derive_and_store_secrets(ctx, passphrase) @cache.stored(APP_COMMON_SEED_WITHOUT_PASSPHRASE) diff --git a/core/src/apps/debug/__init__.py b/core/src/apps/debug/__init__.py index 21ffc9f1bd..1b5475ffa5 100644 --- a/core/src/apps/debug/__init__.py +++ b/core/src/apps/debug/__init__.py @@ -1,3 +1,5 @@ +from trezor.wire import message_handler + if not __debug__: from trezor.utils import halt @@ -22,20 +24,23 @@ if __debug__: from trezor.messages import ( DebugLinkDecision, DebugLinkEraseSdCard, + DebugLinkGetPairingInfo, DebugLinkGetState, DebugLinkOptigaSetSecMax, + DebugLinkPairingInfo, DebugLinkRecordScreen, DebugLinkReseedRandom, DebugLinkState, ) from trezor.ui import Layout - from trezor.wire import WireInterface, context + from trezor.wire import WireInterface + from trezor.wire.protocol_common import Context Handler = Callable[[Any], Awaitable[Any]] layout_change_box = loop.mailbox() - DEBUG_CONTEXT: context.Context | None = None + DEBUG_CONTEXT: Context | None = None REFRESH_INDEX = 0 @@ -70,9 +75,7 @@ if __debug__: "layout deadlock detected (did you send a ButtonAck?)" ) - async def return_layout_change( - ctx: wire.protocol_common.Context, detect_deadlock: bool = False - ) -> None: + async def return_layout_change(ctx: Context, detect_deadlock: bool = False) -> None: # set up the wait storage.layout_watcher = True @@ -265,6 +268,42 @@ if __debug__: tokens=tokens, ) + async def dispatch_DebugLinkGetPairingInfo( + msg: DebugLinkGetPairingInfo, + ) -> DebugLinkPairingInfo | None: + if not utils.USE_THP: + raise RuntimeError("Trezor does not support THP") + if msg.channel_id is None: + raise RuntimeError("Invalid DebugLinkGetPairingInfo message") + + from trezor.wire.thp.channel import Channel + from trezor.wire.thp.pairing_context import PairingContext + from trezor.wire.thp.thp_main import _CHANNELS + + channel_id = int.from_bytes(msg.channel_id, "big") + channel: Channel | None = None + ctx: PairingContext | None = None + try: + channel = _CHANNELS[channel_id] + ctx = channel.connection_context + except KeyError: + pass + + if ctx is None or not isinstance(ctx, PairingContext): + raise RuntimeError("Trezor is not in pairing mode") + + ctx.nfc_secret_host = msg.nfc_secret_host + ctx.handshake_hash_host = msg.handshake_hash + from trezor.messages import DebugLinkPairingInfo + + return DebugLinkPairingInfo( + channel_id=ctx.channel_id, + handshake_hash=ctx.channel_ctx.get_handshake_hash(), + code_entry_code=ctx.code_code_entry, + code_qr_code=ctx.code_qr_code, + nfc_secret_trezor=ctx.nfc_secret, + ) + async def dispatch_DebugLinkGetState( msg: DebugLinkGetState, ) -> DebugLinkState | None: @@ -395,7 +434,6 @@ if __debug__: ctx.iface.iface_num(), msg_type, ) - if msg.type not in WORKFLOW_HANDLERS: await ctx.write(wire.message_handler.unexpected_message()) continue @@ -408,7 +446,7 @@ if __debug__: await ctx.write(Success()) continue - req_msg = wire.message_handler.wrap_protobuf_load(msg.data, req_type) + req_msg = message_handler.wrap_protobuf_load(msg.data, req_type) try: res_msg = await WORKFLOW_HANDLERS[msg.type](req_msg) except Exception as exc: @@ -427,6 +465,7 @@ if __debug__: WORKFLOW_HANDLERS: dict[int, Handler] = { MessageType.DebugLinkDecision: dispatch_DebugLinkDecision, MessageType.DebugLinkGetState: dispatch_DebugLinkGetState, + MessageType.DebugLinkGetPairingInfo: dispatch_DebugLinkGetPairingInfo, MessageType.DebugLinkReseedRandom: dispatch_DebugLinkReseedRandom, MessageType.DebugLinkRecordScreen: dispatch_DebugLinkRecordScreen, MessageType.DebugLinkEraseSdCard: dispatch_DebugLinkEraseSdCard, diff --git a/core/src/apps/management/reboot_to_bootloader.py b/core/src/apps/management/reboot_to_bootloader.py index 85596c0268..2213d2c17a 100644 --- a/core/src/apps/management/reboot_to_bootloader.py +++ b/core/src/apps/management/reboot_to_bootloader.py @@ -89,7 +89,7 @@ async def reboot_to_bootloader(msg: RebootToBootloader) -> NoReturn: boot_args = None ctx = get_context() - await ctx.write(Success(message="Rebooting")) + await ctx.write_force(Success(message="Rebooting")) # make sure the outgoing USB buffer is flushed await loop.wait(ctx.iface.iface_num() | io.POLL_WRITE) # reboot to the bootloader, pass the firmware header hash if any diff --git a/core/src/apps/management/recovery_device/__init__.py b/core/src/apps/management/recovery_device/__init__.py index 10ca5f6377..f722a63c7f 100644 --- a/core/src/apps/management/recovery_device/__init__.py +++ b/core/src/apps/management/recovery_device/__init__.py @@ -24,6 +24,7 @@ async def recovery_device(msg: RecoveryDevice) -> Success: from trezor import TR, config, wire, workflow from trezor.enums import BackupType, ButtonRequestType from trezor.ui.layouts import confirm_action, confirm_reset_device + from trezor.wire.context import try_get_ctx_ids from apps.common import mnemonic from apps.common.request_pin import ( @@ -69,8 +70,8 @@ async def recovery_device(msg: RecoveryDevice) -> Success: if recovery_type == RecoveryType.NormalRecovery: await confirm_reset_device(recovery=True) - # wipe storage to make sure the device is in a clear state - storage.reset() + # wipe storage to make sure the device is in a clear state (except protocol cache) + storage.reset(excluded=try_get_ctx_ids()) # set up pin if requested if msg.pin_protection: diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index 7ad56a4742..face532e76 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -3,8 +3,9 @@ from typing import TYPE_CHECKING import storage.device as storage_device import storage.recovery as storage_recovery import storage.recovery_shares as storage_recovery_shares -from trezor import TR, wire +from trezor import TR, utils, wire from trezor.messages import Success +from trezor.wire import message_handler from apps.common import backup_types @@ -38,18 +39,27 @@ async def recovery_process() -> Success: recovery_type = storage_recovery.get_type() - wire.message_handler.AVOID_RESTARTING_FOR = ( - MessageType.Initialize, - MessageType.GetFeatures, - MessageType.EndSession, - ) + if utils.USE_THP: + message_handler.AVOID_RESTARTING_FOR = ( + MessageType.GetFeatures, + MessageType.EndSession, + ) + else: + message_handler.AVOID_RESTARTING_FOR = ( + MessageType.Initialize, + MessageType.GetFeatures, + MessageType.EndSession, + ) try: return await _continue_recovery_process() except recover.RecoveryAborted: storage_recovery.end_progress() backup.deactivate_repeated_backup() if recovery_type == RecoveryType.NormalRecovery: - storage.wipe() + from trezor.wire.context import try_get_ctx_ids + + storage.wipe(clear_cache=False) + storage.wipe_cache(excluded=try_get_ctx_ids()) raise wire.ActionCancelled @@ -59,11 +69,17 @@ async def _continue_repeated_backup() -> None: from apps.common import backup from apps.management.backup_device import perform_backup - wire.message_handler.AVOID_RESTARTING_FOR = ( - MessageType.Initialize, - MessageType.GetFeatures, - MessageType.EndSession, - ) + if utils.USE_THP: + message_handler.AVOID_RESTARTING_FOR = ( + MessageType.GetFeatures, + MessageType.EndSession, + ) + else: + message_handler.AVOID_RESTARTING_FOR = ( + MessageType.Initialize, + MessageType.GetFeatures, + MessageType.EndSession, + ) try: await perform_backup(is_repeated_backup=True) diff --git a/core/src/apps/management/reset_device/__init__.py b/core/src/apps/management/reset_device/__init__.py index 4b3d8bf2ef..4840d31a21 100644 --- a/core/src/apps/management/reset_device/__init__.py +++ b/core/src/apps/management/reset_device/__init__.py @@ -38,7 +38,7 @@ async def reset_device(msg: ResetDevice) -> Success: prompt_backup, show_wallet_created_success, ) - from trezor.wire.context import call + from trezor.wire.context import call, try_get_ctx_ids from apps.common.request_pin import request_pin_confirm @@ -60,8 +60,8 @@ async def reset_device(msg: ResetDevice) -> Success: # Rendering empty loader so users do not feel a freezing screen render_empty_loader(config.StorageMessage.PROCESSING_MSG) - # wipe storage to make sure the device is in a clear state - storage.reset() + # wipe storage to make sure the device is in a clear state (except protocol cache) + storage.reset(excluded=try_get_ctx_ids()) # Check backup type, perform type-specific handling if backup_types.is_slip39_backup_type(backup_type): @@ -139,7 +139,7 @@ async def reset_device(msg: ResetDevice) -> Success: if perform_backup: await layout.show_backup_success() - return Success(message="Initialized") + return Success(message="Initialized") # TODO: Why "Initialized?" async def _entropy_check(secret: bytes) -> bool: diff --git a/core/src/apps/management/wipe_device.py b/core/src/apps/management/wipe_device.py index b6e60057a6..e6f787b2c7 100644 --- a/core/src/apps/management/wipe_device.py +++ b/core/src/apps/management/wipe_device.py @@ -1,12 +1,19 @@ from typing import TYPE_CHECKING +from trezor.wire.context import get_context, try_get_ctx_ids + if TYPE_CHECKING: - from trezor.messages import Success, WipeDevice + from typing import NoReturn + + from trezor.messages import WipeDevice + +if __debug__: + from trezor import log -async def wipe_device(msg: WipeDevice) -> Success: +async def wipe_device(msg: WipeDevice) -> NoReturn: import storage - from trezor import TR, config, translations + from trezor import TR, config, loop, translations from trezor.enums import ButtonRequestType from trezor.messages import Success from trezor.pin import render_empty_loader @@ -26,16 +33,31 @@ async def wipe_device(msg: WipeDevice) -> Success: br_code=ButtonRequestType.WipeDevice, ) + if __debug__: + log.debug(__name__, "Device wipe - start") + # start an empty progress screen so that the screen is not blank while waiting render_empty_loader(config.StorageMessage.PROCESSING_MSG) # wipe storage - storage.wipe() + storage.wipe(clear_cache=False) + + # clear cache - exclude current context + storage.wipe_cache(excluded=try_get_ctx_ids()) + # erase translations translations.deinit() translations.erase() + try: + await get_context().write_force(Success(message="Device wiped")) + except Exception: + if __debug__: + log.debug(__name__, "Failed to send Success message after wipe.") + pass + storage.wipe_cache() # reload settings reload_settings_from_storage() - - return Success(message="Device wiped") + loop.clear() + if __debug__: + log.debug(__name__, "Device wipe - finished") diff --git a/core/src/apps/thp/credential_manager.py b/core/src/apps/thp/credential_manager.py index adf2ba6240..9170a06f9e 100644 --- a/core/src/apps/thp/credential_manager.py +++ b/core/src/apps/thp/credential_manager.py @@ -63,17 +63,26 @@ def issue_credential( return credential_raw -def validate_credential( +def decode_credential( encoded_pairing_credential_message: bytes, +) -> ThpPairingCredential: + """ + Decode a protobuf encoded pairing credential. + """ + expected_type = protobuf.type_for_name("ThpPairingCredential") + credential = wrap_protobuf_load(encoded_pairing_credential_message, expected_type) + assert ThpPairingCredential.is_type_of(credential) + return credential + + +def validate_credential( + credential: ThpPairingCredential, host_static_pubkey: bytes, ) -> bool: """ Validate a pairing credential binded to the provided host static public key. """ cred_auth_key = derive_cred_auth_key() - expected_type = protobuf.type_for_name("ThpPairingCredential") - credential = wrap_protobuf_load(encoded_pairing_credential_message, expected_type) - assert ThpPairingCredential.is_type_of(credential) proto_msg = ThpAuthenticatedCredentialData( host_static_pubkey=host_static_pubkey, cred_metadata=credential.cred_metadata, @@ -83,6 +92,27 @@ def validate_credential( return mac == credential.mac +def decode_and_validate_credential( + encoded_pairing_credential_message: bytes, + host_static_pubkey: bytes, +) -> bool: + """ + Decode a protobuf encoded pairing credential and validate it + binded to the provided host static public key. + """ + credential = decode_credential(encoded_pairing_credential_message) + return validate_credential(credential, host_static_pubkey) + + +def is_credential_autoconnect(credential: ThpPairingCredential) -> bool: + assert ThpPairingCredential.is_type_of(credential) + if credential.cred_metadata is None: + return False + if credential.cred_metadata.autoconnect is None: + return False + return credential.cred_metadata.autoconnect + + def _encode_message_into_new_buffer(msg: protobuf.MessageType) -> bytes: msg_len = protobuf.encoded_length(msg) new_buffer = bytearray(msg_len) diff --git a/core/src/apps/thp/pairing.py b/core/src/apps/thp/pairing.py new file mode 100644 index 0000000000..0031c93420 --- /dev/null +++ b/core/src/apps/thp/pairing.py @@ -0,0 +1,469 @@ +from typing import TYPE_CHECKING +from ubinascii import hexlify + +from trezor import protobuf +from trezor.crypto import random +from trezor.crypto.hashlib import sha256 +from trezor.enums import ThpMessageType, ThpPairingMethod +from trezor.messages import ( + Cancel, + ThpCodeEntryChallenge, + ThpCodeEntryCommitment, + ThpCodeEntryCpaceHostTag, + ThpCodeEntryCpaceTrezor, + ThpCodeEntrySecret, + ThpCredentialMetadata, + ThpCredentialRequest, + ThpCredentialResponse, + ThpEndRequest, + ThpEndResponse, + ThpNfcTagHost, + ThpNfcTagTrezor, + ThpPairingPreparationsFinished, + ThpPairingRequest, + ThpQrCodeSecret, + ThpQrCodeTag, + ThpSelectMethod, +) +from trezor.wire import message_handler +from trezor.wire.context import UnexpectedMessageException +from trezor.wire.errors import SilentError, UnexpectedMessage +from trezor.wire.thp import ChannelState, ThpError, crypto, get_enabled_pairing_methods +from trezor.wire.thp.pairing_context import PairingContext + +from .credential_manager import is_credential_autoconnect, issue_credential + +if __debug__: + from trezor import log + +if TYPE_CHECKING: + from typing import Any, Callable, Concatenate, ParamSpec, Tuple + + P = ParamSpec("P") + FuncWithContext = Callable[Concatenate[PairingContext, P], Any] + +# +# Helpers - decorators + + +def check_state_and_log( + *allowed_states: ChannelState, +) -> Callable[[FuncWithContext], FuncWithContext]: + def decorator(f: FuncWithContext) -> FuncWithContext: + def inner(context: PairingContext, *args: P.args, **kwargs: P.kwargs) -> object: + _check_state(context, *allowed_states) + if __debug__: + try: + log.debug(__name__, "started %s", f.__name__) + except AttributeError: + log.debug( + __name__, + "started a function that cannot be named, because it raises AttributeError, eg. closure", + ) + return f(context, *args, **kwargs) + + return inner + + return decorator + + +def check_method_is_allowed_and_selected( + pairing_method: ThpPairingMethod, +) -> Callable[[FuncWithContext], FuncWithContext]: + def decorator(f: FuncWithContext) -> FuncWithContext: + def inner(context: PairingContext, *args: P.args, **kwargs: P.kwargs) -> object: + _check_method_is_allowed(context, pairing_method) + _check_method_is_selected(context, pairing_method) + return f(context, *args, **kwargs) + + return inner + + return decorator + + +# +# Pairing handlers + + +@check_state_and_log(ChannelState.TP0) +async def handle_pairing_request( + ctx: PairingContext, message: protobuf.MessageType +) -> ThpEndResponse: + + if not ThpPairingRequest.is_type_of(message): + raise UnexpectedMessage("Unexpected message") + + ctx.host_name = message.host_name or "" + + await ctx.show_pairing_dialogue() + assert ThpSelectMethod.MESSAGE_WIRE_TYPE is not None + select_method_msg = await ctx.read( + [ + ThpSelectMethod.MESSAGE_WIRE_TYPE, + ] + ) + + assert ThpSelectMethod.is_type_of(select_method_msg) + assert select_method_msg.selected_pairing_method is not None + + ctx.set_selected_method(select_method_msg.selected_pairing_method) + + if ctx.selected_method == ThpPairingMethod.SkipPairing: + return await _end_pairing(ctx) + + while True: + await _prepare_pairing(ctx) + + ctx.channel_ctx.set_channel_state(ChannelState.TP3) + try: + # Should raise UnexpectedMessageException + await ctx.show_pairing_method_screen() + except UnexpectedMessageException as e: + raw_response = e.msg + name = message_handler.get_msg_name(raw_response.type) + if name is None: + req_type = protobuf.type_for_wire(raw_response.type) + else: + req_type = protobuf.type_for_name(name) + response = message_handler.wrap_protobuf_load(raw_response.data, req_type) + + if Cancel.is_type_of(response): + ctx.channel_ctx.clear() + raise SilentError("Action was cancelled by the Host") + + if ThpSelectMethod.is_type_of(response): + assert response.selected_pairing_method is not None + ctx.set_selected_method(response.selected_pairing_method) + ctx.channel_ctx.set_channel_state(ChannelState.TP1) + else: + break + + response: protobuf.MessageType = await _handle_different_pairing_methods( + ctx, response + ) + return await handle_credential_phase( + ctx, + message=response, + show_connection_dialog=False, + ) + + +@check_state_and_log(ChannelState.TC1) +async def handle_credential_phase( + ctx: PairingContext, + message: protobuf.MessageType, + show_connection_dialog: bool = True, +) -> ThpEndResponse: + autoconnect: bool = False + credential = ctx.channel_ctx.credential + + if credential is not None: + autoconnect = is_credential_autoconnect(credential) + if credential.cred_metadata is not None: + ctx.host_name = credential.cred_metadata.host_name + if ctx.host_name is None: + raise Exception("Credential does not have a hostname") + + if show_connection_dialog and not autoconnect: + await ctx.show_connection_dialogue() + + while ThpCredentialRequest.is_type_of(message): + message = await _handle_credential_request(ctx, message) + + return await _handle_end_request(ctx, message) + + +async def _prepare_pairing(ctx: PairingContext) -> None: + ctx.channel_ctx.set_channel_state(ChannelState.TP1) + + if ctx.selected_method == ThpPairingMethod.CodeEntry: + await _handle_code_entry_is_selected(ctx) + elif ctx.selected_method == ThpPairingMethod.NFC: + await _handle_nfc_is_selected(ctx) + elif ctx.selected_method == ThpPairingMethod.QrCode: + await _handle_qr_code_is_selected(ctx) + else: + raise Exception() # TODO unknown pairing method + + +@check_state_and_log(ChannelState.TP1) +async def _handle_code_entry_is_selected(ctx: PairingContext) -> None: + if ctx.code_entry_secret is None: + await _handle_code_entry_is_selected_first_time(ctx) + else: + await ctx.write_force(ThpPairingPreparationsFinished()) + + +async def _handle_code_entry_is_selected_first_time(ctx: PairingContext) -> None: + from trezor.wire.thp.cpace import Cpace + + ctx.code_entry_secret = random.bytes(16) + commitment = sha256(ctx.code_entry_secret).digest() + + challenge_message = await ctx.call( + ThpCodeEntryCommitment(commitment=commitment), ThpCodeEntryChallenge + ) + ctx.channel_ctx.set_channel_state(ChannelState.TP2) + + if not ThpCodeEntryChallenge.is_type_of(challenge_message): + raise UnexpectedMessage("Unexpected message") + + if challenge_message.challenge is None: + raise Exception("Invalid message") + sha_ctx = sha256(ThpPairingMethod.CodeEntry.to_bytes(1, "big")) + sha_ctx.update(ctx.channel_ctx.get_handshake_hash()) + sha_ctx.update(ctx.code_entry_secret) + sha_ctx.update(challenge_message.challenge) + code_code_entry_hash = sha_ctx.digest() + ctx.code_code_entry = int.from_bytes(code_code_entry_hash, "big") % 1000000 + ctx.cpace = Cpace( + ctx.channel_ctx.get_handshake_hash(), + ) + assert ctx.code_code_entry is not None + ctx.cpace.generate_keys_and_secret(ctx.code_code_entry.to_bytes(6, "big")) + await ctx.write_force( + ThpCodeEntryCpaceTrezor(cpace_trezor_public_key=ctx.cpace.trezor_public_key) + ) + + +@check_state_and_log(ChannelState.TP1) +async def _handle_nfc_is_selected(ctx: PairingContext) -> None: + ctx.nfc_secret = random.bytes(16) + await ctx.write_force(ThpPairingPreparationsFinished()) + + +@check_state_and_log(ChannelState.TP1) +async def _handle_qr_code_is_selected(ctx: PairingContext) -> None: + ctx.qr_code_secret = random.bytes(16) + + sha_ctx = sha256(ThpPairingMethod.QrCode.to_bytes(1, "big")) + sha_ctx.update(ctx.channel_ctx.get_handshake_hash()) + sha_ctx.update(ctx.qr_code_secret) + + ctx.code_qr_code = sha_ctx.digest()[:16] + await ctx.write_force(ThpPairingPreparationsFinished()) + + +@check_state_and_log(ChannelState.TP3) +async def _handle_different_pairing_methods( + ctx: PairingContext, response: protobuf.MessageType +) -> protobuf.MessageType: + if ThpCodeEntryCpaceHostTag.is_type_of(response): + return await _handle_code_entry_cpace(ctx, response) + if ThpQrCodeTag.is_type_of(response): + return await _handle_qr_code_tag(ctx, response) + if ThpNfcTagHost.is_type_of(response): + return await _handle_nfc_tag(ctx, response) + raise UnexpectedMessage("Unexpected message" + str(response)) + + +@check_state_and_log(ChannelState.TP3) +@check_method_is_allowed_and_selected(ThpPairingMethod.CodeEntry) +async def _handle_code_entry_cpace( + ctx: PairingContext, message: protobuf.MessageType +) -> protobuf.MessageType: + + if TYPE_CHECKING: + assert ThpCodeEntryCpaceHostTag.is_type_of(message) + if message.cpace_host_public_key is None: + raise ThpError( + "Message ThpCodeEntryCpaceHostTag is missing cpace_host_public_key" + ) + if message.tag is None: + raise ThpError("Message ThpCodeEntryCpaceHostTag is missing tag") + + ctx.cpace.compute_shared_secret(message.cpace_host_public_key) + expected_tag = sha256(ctx.cpace.shared_secret).digest() + if expected_tag != message.tag: + print( + "expected code entry tag:", hexlify(expected_tag).decode() + ) # TODO remove after testing + print( + "expected code entry shared secret:", + hexlify(ctx.cpace.shared_secret).decode(), + ) # TODO remove after testing + raise ThpError("Unexpected Code Entry Tag") + + return await _handle_secret_reveal( + ctx, + msg=ThpCodeEntrySecret(secret=ctx.code_entry_secret), + ) + + +@check_state_and_log(ChannelState.TP3) +@check_method_is_allowed_and_selected(ThpPairingMethod.QrCode) +async def _handle_qr_code_tag( + ctx: PairingContext, message: protobuf.MessageType +) -> protobuf.MessageType: + if TYPE_CHECKING: + assert isinstance(message, ThpQrCodeTag) + assert ctx.code_qr_code is not None + sha_ctx = sha256(ctx.channel_ctx.get_handshake_hash()) + sha_ctx.update(ctx.code_qr_code) + expected_tag = sha_ctx.digest() + if expected_tag != message.tag: + print( + "expected qr code tag:", hexlify(expected_tag).decode() + ) # TODO remove after testing + print( + "expected handshake hash:", + hexlify(ctx.channel_ctx.get_handshake_hash()).decode(), + ) # TODO remove after testing + print( + "expected code qr code:", + hexlify(ctx.code_qr_code).decode(), + ) # TODO remove after testing + print( + "expected secret:", hexlify(ctx.qr_code_secret or b"").decode() + ) # TODO remove after testing + raise ThpError("Unexpected QR Code Tag") + + return await _handle_secret_reveal( + ctx, + msg=ThpQrCodeSecret(secret=ctx.qr_code_secret), + ) + + +@check_state_and_log(ChannelState.TP3) +@check_method_is_allowed_and_selected(ThpPairingMethod.NFC) +async def _handle_nfc_tag( + ctx: PairingContext, message: protobuf.MessageType +) -> protobuf.MessageType: + if TYPE_CHECKING: + assert isinstance(message, ThpNfcTagHost) + + assert ctx.nfc_secret is not None + assert ctx.handshake_hash_host is not None + assert ctx.nfc_secret_host is not None + assert len(ctx.nfc_secret_host) == 16 + + sha_ctx = sha256(ThpPairingMethod.NFC.to_bytes(1, "big")) + sha_ctx.update(ctx.channel_ctx.get_handshake_hash()) + sha_ctx.update(ctx.nfc_secret) + expected_tag = sha_ctx.digest() + if expected_tag != message.tag: + print( + "expected nfc tag:", hexlify(expected_tag).decode() + ) # TODO remove after testing + raise ThpError("Unexpected NFC Unidirectional Tag") + + if ctx.handshake_hash_host[:16] != ctx.channel_ctx.get_handshake_hash()[:16]: + raise ThpError("Handshake hash mismatch") + + sha_ctx = sha256(ThpPairingMethod.NFC.to_bytes(1, "big")) + sha_ctx.update(ctx.channel_ctx.get_handshake_hash()) + sha_ctx.update(ctx.nfc_secret_host) + trezor_tag = sha_ctx.digest() + return await _handle_secret_reveal( + ctx, + msg=ThpNfcTagTrezor(tag=trezor_tag), + ) + + +@check_state_and_log(ChannelState.TP3, ChannelState.TP4) +async def _handle_secret_reveal( + ctx: PairingContext, + msg: protobuf.MessageType, +) -> protobuf.MessageType: + ctx.channel_ctx.set_channel_state(ChannelState.TC1) + return await ctx.call_any( + msg, + ThpMessageType.ThpCredentialRequest, + ThpMessageType.ThpEndRequest, + ) + + +@check_state_and_log(ChannelState.TC1) +async def _handle_credential_request( + ctx: PairingContext, message: protobuf.MessageType +) -> protobuf.MessageType: + + if not ThpCredentialRequest.is_type_of(message): + raise UnexpectedMessage("Unexpected message") + if message.host_static_pubkey is None: + raise Exception("Invalid message") # TODO change failure type + + autoconnect: bool = False + if message.autoconnect is not None: + autoconnect = message.autoconnect + + trezor_static_pubkey = crypto.get_trezor_static_pubkey() + credential_metadata = ThpCredentialMetadata( + host_name=ctx.host_name, + autoconnect=autoconnect, + ) + credential = issue_credential(message.host_static_pubkey, credential_metadata) + + return await ctx.call_any( + ThpCredentialResponse( + trezor_static_pubkey=trezor_static_pubkey, credential=credential + ), + ThpMessageType.ThpCredentialRequest, + ThpMessageType.ThpEndRequest, + ) + + +@check_state_and_log(ChannelState.TC1) +async def _handle_end_request( + ctx: PairingContext, message: protobuf.MessageType +) -> ThpEndResponse: + if not ThpEndRequest.is_type_of(message): + raise UnexpectedMessage("Unexpected message") + return await _end_pairing(ctx) + + +async def _end_pairing(ctx: PairingContext) -> ThpEndResponse: + ctx.channel_ctx.set_channel_state(ChannelState.ENCRYPTED_TRANSPORT) + return ThpEndResponse() + + +# +# Helpers - checkers + + +def _check_state(ctx: PairingContext, *allowed_states: ChannelState) -> None: + if ctx.channel_ctx.get_channel_state() not in allowed_states: + raise UnexpectedMessage("Unexpected message") + + +def _check_method_is_allowed(ctx: PairingContext, method: ThpPairingMethod) -> None: + if method not in get_enabled_pairing_methods(ctx.iface): + raise ThpError("Unexpected pairing method") + + +def _check_method_is_selected(ctx: PairingContext, method: ThpPairingMethod) -> None: + if method is not ctx.selected_method: + raise ThpError("Not selected pairing method") + + +# +# Helpers - getters + + +def _get_accepted_messages(ctx: PairingContext) -> Tuple[int, ...]: + r = _get_possible_pairing_methods(ctx) + mtype = Cancel.MESSAGE_WIRE_TYPE + r += (mtype,) if mtype is not None else () + mtype = ThpSelectMethod.MESSAGE_WIRE_TYPE + r += (mtype,) if mtype is not None else () + + return r + + +def _get_possible_pairing_methods(ctx: PairingContext) -> Tuple[int, ...]: + r = tuple( + [ + _get_message_type_for_method(ctx.selected_method), + ] + ) + return r + + +def _get_message_type_for_method(method: int) -> int: + if method is ThpPairingMethod.CodeEntry: + return ThpMessageType.ThpCodeEntryCpaceHostTag + if method is ThpPairingMethod.NFC: + return ThpMessageType.ThpNfcTagHost + if method is ThpPairingMethod.QrCode: + return ThpMessageType.ThpQrCodeTag + raise ValueError("Unexpected pairing method - no message type available") diff --git a/core/src/storage/__init__.py b/core/src/storage/__init__.py index 3a012874f3..1253249090 100644 --- a/core/src/storage/__init__.py +++ b/core/src/storage/__init__.py @@ -1,12 +1,29 @@ # make sure to import cache unconditionally at top level so that it is imported (and retained) together with the storage module +from typing import TYPE_CHECKING + from storage import cache, common, device +if TYPE_CHECKING: + from typing import Tuple -def wipe() -> None: + pass + + +def wipe(clear_cache: bool = True) -> None: + """ + Wipes the storage. + If the device should communicate after wipe, use `clear_cache=False` and clear cache manually later using + `wipe_cache()`. + """ from trezor import config config.wipe() - cache.clear_all() + if clear_cache: + cache.clear_all() + + +def wipe_cache(excluded: Tuple[bytes, bytes] | None = None) -> None: + cache.clear_all(excluded) def init_unlocked() -> None: @@ -21,12 +38,13 @@ def init_unlocked() -> None: common.set_bool(common.APP_DEVICE, device.INITIALIZED, True, public=True) -def reset() -> None: +def reset(excluded: Tuple[bytes, bytes] | None) -> None: """ Wipes storage but keeps the device id unchanged. """ device_id = device.get_device_id() - wipe() + wipe(clear_cache=False) + wipe_cache(excluded) common.set(common.APP_DEVICE, device.DEVICE_ID, device_id.encode(), public=True) diff --git a/core/src/storage/cache.py b/core/src/storage/cache.py index 72d8a1e418..6db224a782 100644 --- a/core/src/storage/cache.py +++ b/core/src/storage/cache.py @@ -1,26 +1,47 @@ import builtins import gc +from typing import TYPE_CHECKING -from storage import cache_codec from storage.cache_common import SESSIONLESS_FLAG, SessionlessCache +from trezor import utils + +if TYPE_CHECKING: + from typing import Tuple + + pass # Cache initialization _SESSIONLESS_CACHE = SessionlessCache() -_PROTOCOL_CACHE = cache_codec + + +if utils.USE_THP: + from storage import cache_thp + + _PROTOCOL_CACHE = cache_thp +else: + from storage import cache_codec + + _PROTOCOL_CACHE = cache_codec + _PROTOCOL_CACHE.initialize() _SESSIONLESS_CACHE.clear() gc.collect() -def clear_all() -> None: +def clear_all(excluded: Tuple[bytes, bytes] | None = None) -> None: """ Clears all data from both the protocol cache and the sessionless cache. """ global autolock_last_touch autolock_last_touch = None _SESSIONLESS_CACHE.clear() - _PROTOCOL_CACHE.clear_all() + + if utils.USE_THP and excluded is not None: + # If we want to keep THP connection alive, we do not clear communication keys + cache_thp.clear_all_except_one_session_keys(excluded) + else: + _PROTOCOL_CACHE.clear_all() def get_int_all_sessions(key: int) -> builtins.set[int]: diff --git a/core/src/storage/cache_common.py b/core/src/storage/cache_common.py index 90cead81db..40eee905cc 100644 --- a/core/src/storage/cache_common.py +++ b/core/src/storage/cache_common.py @@ -14,6 +14,14 @@ if not utils.BITCOIN_ONLY: APP_CARDANO_ICARUS_TREZOR_SECRET = const(6) APP_MONERO_LIVE_REFRESH = const(7) +# Cache keys for THP channel +if utils.USE_THP: + CHANNEL_HANDSHAKE_HASH = const(0) + CHANNEL_KEY_RECEIVE = const(1) + CHANNEL_KEY_SEND = const(2) + CHANNEL_NONCE_RECEIVE = const(3) + CHANNEL_NONCE_SEND = const(4) + # Keys that are valid across sessions SESSIONLESS_FLAG = const(128) APP_COMMON_SEED_WITHOUT_PASSPHRASE = const(0 | SESSIONLESS_FLAG) diff --git a/core/src/storage/cache_thp.py b/core/src/storage/cache_thp.py new file mode 100644 index 0000000000..53ab110755 --- /dev/null +++ b/core/src/storage/cache_thp.py @@ -0,0 +1,356 @@ +import builtins +from micropython import const +from typing import TYPE_CHECKING + +from storage.cache_common import DataCache + +if TYPE_CHECKING: + from typing import Tuple + + pass + + +# THP specific constants +_MAX_CHANNELS_COUNT = const(10) +_MAX_SESSIONS_COUNT = const(20) + + +_CHANNEL_STATE_LENGTH = const(1) +_WIRE_INTERFACE_LENGTH = const(1) +_SESSION_STATE_LENGTH = const(1) +_CHANNEL_ID_LENGTH = const(2) +SESSION_ID_LENGTH = const(1) +BROADCAST_CHANNEL_ID = const(0xFFFF) +KEY_LENGTH = const(32) +TAG_LENGTH = const(16) +_UNALLOCATED_STATE = const(0) +_ALLOCATED_STATE = const(1) +_SEEDLESS_STATE = const(2) + + +class ThpDataCache(DataCache): + def __init__(self) -> None: + self.channel_id = bytearray(_CHANNEL_ID_LENGTH) + self.last_usage = 0 + super().__init__() + + def clear(self) -> None: + self.channel_id[:] = b"" + self.last_usage = 0 + super().clear() + + +class ChannelCache(ThpDataCache): + def __init__(self) -> None: + self.host_ephemeral_pubkey = bytearray(KEY_LENGTH) + self.state = bytearray(_CHANNEL_STATE_LENGTH) + self.iface = bytearray(1) # TODO add decoding + self.sync = 0x80 # can_send_bit | sync_receive_bit | sync_send_bit | rfu(5) + self.session_id_counter = 0x00 + self.fields = ( + 32, # CHANNEL_HANDSHAKE_HASH + 32, # CHANNEL_KEY_RECEIVE + 32, # CHANNEL_KEY_SEND + 8, # CHANNEL_NONCE_RECEIVE + 8, # CHANNEL_NONCE_SEND + ) + super().__init__() + + def clear(self) -> None: + self.state[:] = bytearray( + int.to_bytes(0, _CHANNEL_STATE_LENGTH, "big") + ) # Set state to UNALLOCATED + self.host_ephemeral_pubkey[:] = bytearray(KEY_LENGTH) + self.state[:] = bytearray(_CHANNEL_STATE_LENGTH) + self.iface[:] = bytearray(1) + super().clear() + + +class SessionThpCache(ThpDataCache): + def __init__(self) -> None: + from trezor import utils + + self.session_id = bytearray(SESSION_ID_LENGTH) + self.state = bytearray(_SESSION_STATE_LENGTH) + if utils.BITCOIN_ONLY: + self.fields = ( + 64, # APP_COMMON_SEED + 2, # APP_COMMON_AUTHORIZATION_TYPE + 128, # APP_COMMON_AUTHORIZATION_DATA + 32, # APP_COMMON_NONCE + ) + else: + self.fields = ( + 64, # APP_COMMON_SEED + 2, # APP_COMMON_AUTHORIZATION_TYPE + 128, # APP_COMMON_AUTHORIZATION_DATA + 32, # APP_COMMON_NONCE + 0, # APP_COMMON_DERIVE_CARDANO + 96, # APP_CARDANO_ICARUS_SECRET + 96, # APP_CARDANO_ICARUS_TREZOR_SECRET + 0, # APP_MONERO_LIVE_REFRESH + ) + super().__init__() + + def clear(self) -> None: + self.state[:] = bytearray( + int.to_bytes(0, _SESSION_STATE_LENGTH, "big") + ) # Set state to UNALLOCATED + self.session_id[:] = b"" + super().clear() + + +_CHANNELS: list[ChannelCache] = [] +_SESSIONS: list[SessionThpCache] = [] +cid_counter: int = 0 + +# Last-used counter +_usage_counter = 0 + + +def initialize() -> None: + global _CHANNELS + global _SESSIONS + global cid_counter + + for _ in range(_MAX_CHANNELS_COUNT): + _CHANNELS.append(ChannelCache()) + for _ in range(_MAX_SESSIONS_COUNT): + _SESSIONS.append(SessionThpCache()) + + for channel in _CHANNELS: + channel.clear() + for session in _SESSIONS: + session.clear() + + from trezorcrypto import random + + cid_counter = random.uniform(0xFFFE) + + +def get_new_channel(iface: bytes) -> ChannelCache: + if len(iface) != _WIRE_INTERFACE_LENGTH: + raise Exception("Invalid WireInterface (encoded) length") + + new_cid = get_next_channel_id() + index = _get_next_channel_index() + + # clear sessions from replaced channel + if _get_channel_state(_CHANNELS[index]) != _UNALLOCATED_STATE: + old_cid = _CHANNELS[index].channel_id + clear_sessions_with_channel_id(old_cid) + + _CHANNELS[index] = ChannelCache() + _CHANNELS[index].channel_id[:] = new_cid + _CHANNELS[index].last_usage = _get_usage_counter_and_increment() + _CHANNELS[index].state[:] = bytearray( + _UNALLOCATED_STATE.to_bytes(_CHANNEL_STATE_LENGTH, "big") + ) + _CHANNELS[index].iface[:] = bytearray(iface) + return _CHANNELS[index] + + +def update_channel_last_used(channel_id: bytes) -> None: + for channel in _CHANNELS: + if channel.channel_id == channel_id: + channel.last_usage = _get_usage_counter_and_increment() + return + + +def update_session_last_used(channel_id: bytes, session_id: bytes) -> None: + for session in _SESSIONS: + if session.channel_id == channel_id and session.session_id == session_id: + session.last_usage = _get_usage_counter_and_increment() + update_channel_last_used(channel_id) + return + + +def get_all_allocated_channels() -> list[ChannelCache]: + _list: list[ChannelCache] = [] + for channel in _CHANNELS: + if _get_channel_state(channel) != _UNALLOCATED_STATE: + _list.append(channel) + return _list + + +def get_allocated_session( + channel_id: bytes, session_id: bytes +) -> SessionThpCache | None: + index = get_allocated_session_index(channel_id, session_id) + if index is None: + return index + return _SESSIONS[index] + + +def get_allocated_session_index(channel_id: bytes, session_id: bytes) -> int | None: + """ + Finds and returns index of the first allocated session matching the given `channel_id` + and `session_id`, or `None` if no match is found. + + Raises `Exception` if either channel_id or session_id has an invalid length. + """ + if len(channel_id) != _CHANNEL_ID_LENGTH or len(session_id) != SESSION_ID_LENGTH: + raise Exception("At least one of arguments has invalid length") + + for i in range(_MAX_SESSIONS_COUNT): + if _get_session_state(_SESSIONS[i]) == _UNALLOCATED_STATE: + continue + if _SESSIONS[i].channel_id != channel_id: + continue + if _SESSIONS[i].session_id != session_id: + continue + return i + return None + + +def is_seedless_session(session_cache: SessionThpCache) -> bool: + return _get_session_state(session_cache) == _SEEDLESS_STATE + + +def set_channel_host_ephemeral_key(channel: ChannelCache, key: bytearray) -> None: + if len(key) != KEY_LENGTH: + raise Exception("Invalid key length") + channel.host_ephemeral_pubkey = key + + +def create_or_replace_session( + channel: ChannelCache, session_id: bytes +) -> SessionThpCache: + index = get_allocated_session_index(channel.channel_id, session_id) + if index is None: + index = _get_next_session_index() + + _SESSIONS[index] = SessionThpCache() + _SESSIONS[index].channel_id[:] = channel.channel_id + _SESSIONS[index].session_id[:] = session_id + _SESSIONS[index].last_usage = _get_usage_counter_and_increment() + channel.last_usage = ( + _get_usage_counter_and_increment() + ) # increment also use of the channel so it does not get replaced + + _SESSIONS[index].state[:] = bytearray( + _ALLOCATED_STATE.to_bytes(_SESSION_STATE_LENGTH, "big") + ) + return _SESSIONS[index] + + +def _get_usage_counter_and_increment() -> int: + global _usage_counter + _usage_counter += 1 + return _usage_counter + + +def _get_next_channel_index() -> int: + idx = _get_unallocated_channel_index() + if idx is not None: + return idx + return _get_least_recently_used_item(_CHANNELS, max_count=_MAX_CHANNELS_COUNT) + + +def _get_next_session_index() -> int: + idx = _get_unallocated_session_index() + if idx is not None: + return idx + return _get_least_recently_used_item(_SESSIONS, max_count=_MAX_SESSIONS_COUNT) + + +def _get_unallocated_channel_index() -> int | None: + for i in range(_MAX_CHANNELS_COUNT): + if _get_channel_state(_CHANNELS[i]) is _UNALLOCATED_STATE: + return i + return None + + +def _get_unallocated_session_index() -> int | None: + for i in range(_MAX_SESSIONS_COUNT): + if (_SESSIONS[i]) is _UNALLOCATED_STATE: + return i + return None + + +def _get_channel_state(channel: ChannelCache) -> int: + return int.from_bytes(channel.state, "big") + + +def _get_session_state(session: SessionThpCache) -> int: + return int.from_bytes(session.state, "big") + + +def get_next_channel_id() -> bytes: + global cid_counter + while True: + cid_counter += 1 + if cid_counter >= BROADCAST_CHANNEL_ID: + cid_counter = 1 + if _is_cid_unique(): + break + return cid_counter.to_bytes(_CHANNEL_ID_LENGTH, "big") + + +def _is_cid_unique() -> bool: + global cid_counter + cid_counter_bytes = cid_counter.to_bytes(_CHANNEL_ID_LENGTH, "big") + for channel in _CHANNELS: + if channel.channel_id == cid_counter_bytes: + return False + return True + + +def _get_least_recently_used_item( + list: list[ChannelCache] | list[SessionThpCache], max_count: int +) -> int: + global _usage_counter + lru_counter = _usage_counter + 1 + lru_item_index = 0 + for i in range(max_count): + if list[i].last_usage < lru_counter: + lru_counter = list[i].last_usage + lru_item_index = i + return lru_item_index + + +def get_int_all_sessions(key: int) -> builtins.set[int]: + values = builtins.set() + for session in _SESSIONS: + encoded = session.get(key) + if encoded is not None: + values.add(int.from_bytes(encoded, "big")) + return values + + +def clear_sessions_with_channel_id(channel_id: bytes) -> None: + for session in _SESSIONS: + if session.channel_id == channel_id: + session.clear() + + +def clear_session(session: SessionThpCache) -> None: + for s in _SESSIONS: + if s.channel_id == session.channel_id and s.session_id == session.session_id: + session.clear() + + +def clear_all() -> None: + for session in _SESSIONS: + session.clear() + for channel in _CHANNELS: + channel.clear() + + +def clear_all_except_one_session_keys(excluded: Tuple[bytes, bytes]) -> None: + cid, sid = excluded + + for channel in _CHANNELS: + if channel.channel_id != cid: + channel.clear() + + for session in _SESSIONS: + if session.channel_id != cid and session.session_id != sid: + session.clear() + else: + s_last_usage = session.last_usage + session.clear() + session.last_usage = s_last_usage + session.state = bytearray(_SEEDLESS_STATE.to_bytes(1, "big")) + session.session_id[:] = bytearray(sid) + session.channel_id[:] = bytearray(cid) diff --git a/core/src/trezor/enums/FailureType.py b/core/src/trezor/enums/FailureType.py index fbb2001e54..e95dcb803f 100644 --- a/core/src/trezor/enums/FailureType.py +++ b/core/src/trezor/enums/FailureType.py @@ -16,4 +16,8 @@ NotInitialized = 11 PinMismatch = 12 WipeCodeMismatch = 13 InvalidSession = 14 +ThpUnallocatedSession = 15 +InvalidProtocol = 16 +BufferError = 17 +DeviceIsBusy = 18 FirmwareError = 99 diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py index ed569795b0..1a955fbcb7 100644 --- a/core/src/trezor/enums/MessageType.py +++ b/core/src/trezor/enums/MessageType.py @@ -97,6 +97,8 @@ DebugLinkEraseSdCard = 9005 DebugLinkWatchLayout = 9006 DebugLinkResetDebugEvents = 9007 DebugLinkOptigaSetSecMax = 9008 +DebugLinkGetPairingInfo = 9009 +DebugLinkPairingInfo = 9010 BenchmarkListNames = 9100 BenchmarkNames = 9101 BenchmarkRun = 9102 diff --git a/core/src/trezor/enums/ThpMessageType.py b/core/src/trezor/enums/ThpMessageType.py new file mode 100644 index 0000000000..3ca4a27c35 --- /dev/null +++ b/core/src/trezor/enums/ThpMessageType.py @@ -0,0 +1,22 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +ThpCreateNewSession = 1000 +ThpPairingRequest = 1006 +ThpPairingRequestApproved = 1007 +ThpSelectMethod = 1008 +ThpPairingPreparationsFinished = 1009 +ThpCredentialRequest = 1010 +ThpCredentialResponse = 1011 +ThpEndRequest = 1012 +ThpEndResponse = 1013 +ThpCodeEntryCommitment = 1016 +ThpCodeEntryChallenge = 1017 +ThpCodeEntryCpaceTrezor = 1018 +ThpCodeEntryCpaceHostTag = 1019 +ThpCodeEntrySecret = 1020 +ThpQrCodeTag = 1024 +ThpQrCodeSecret = 1025 +ThpNfcTagHost = 1032 +ThpNfcTagTrezor = 1033 diff --git a/core/src/trezor/enums/ThpPairingMethod.py b/core/src/trezor/enums/ThpPairingMethod.py new file mode 100644 index 0000000000..0af2487182 --- /dev/null +++ b/core/src/trezor/enums/ThpPairingMethod.py @@ -0,0 +1,8 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +SkipPairing = 1 +CodeEntry = 2 +QrCode = 3 +NFC = 4 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index d16c3c4a66..c39574773f 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -39,6 +39,10 @@ if TYPE_CHECKING: PinMismatch = 12 WipeCodeMismatch = 13 InvalidSession = 14 + ThpUnallocatedSession = 15 + InvalidProtocol = 16 + BufferError = 17 + DeviceIsBusy = 18 FirmwareError = 99 class ButtonRequestType(IntEnum): @@ -347,6 +351,32 @@ if TYPE_CHECKING: Nay = 1 Pass = 2 + class ThpMessageType(IntEnum): + ThpCreateNewSession = 1000 + ThpPairingRequest = 1006 + ThpPairingRequestApproved = 1007 + ThpSelectMethod = 1008 + ThpPairingPreparationsFinished = 1009 + ThpCredentialRequest = 1010 + ThpCredentialResponse = 1011 + ThpEndRequest = 1012 + ThpEndResponse = 1013 + ThpCodeEntryCommitment = 1016 + ThpCodeEntryChallenge = 1017 + ThpCodeEntryCpaceTrezor = 1018 + ThpCodeEntryCpaceHostTag = 1019 + ThpCodeEntrySecret = 1020 + ThpQrCodeTag = 1024 + ThpQrCodeSecret = 1025 + ThpNfcTagHost = 1032 + ThpNfcTagTrezor = 1033 + + class ThpPairingMethod(IntEnum): + SkipPairing = 1 + CodeEntry = 2 + QrCode = 3 + NFC = 4 + class MessageType(IntEnum): Initialize = 0 Ping = 1 @@ -447,6 +477,8 @@ if TYPE_CHECKING: DebugLinkWatchLayout = 9006 DebugLinkResetDebugEvents = 9007 DebugLinkOptigaSetSecMax = 9008 + DebugLinkGetPairingInfo = 9009 + DebugLinkPairingInfo = 9010 EthereumGetPublicKey = 450 EthereumPublicKey = 451 EthereumGetAddress = 56 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 1dbfcbc407..9d0f58203f 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -68,6 +68,8 @@ if TYPE_CHECKING: from trezor.enums import StellarSignerType # noqa: F401 from trezor.enums import TezosBallotType # noqa: F401 from trezor.enums import TezosContractType # noqa: F401 + from trezor.enums import ThpMessageType # noqa: F401 + from trezor.enums import ThpPairingMethod # noqa: F401 from trezor.enums import WordRequestType # noqa: F401 class BenchmarkListNames(protobuf.MessageType): @@ -2950,6 +2952,46 @@ if TYPE_CHECKING: def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkState"]: return isinstance(msg, cls) + class DebugLinkGetPairingInfo(protobuf.MessageType): + channel_id: "bytes | None" + handshake_hash: "bytes | None" + nfc_secret_host: "bytes | None" + + def __init__( + self, + *, + channel_id: "bytes | None" = None, + handshake_hash: "bytes | None" = None, + nfc_secret_host: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkGetPairingInfo"]: + return isinstance(msg, cls) + + class DebugLinkPairingInfo(protobuf.MessageType): + channel_id: "bytes | None" + handshake_hash: "bytes | None" + code_entry_code: "int | None" + code_qr_code: "bytes | None" + nfc_secret_trezor: "bytes | None" + + def __init__( + self, + *, + channel_id: "bytes | None" = None, + handshake_hash: "bytes | None" = None, + code_entry_code: "int | None" = None, + code_qr_code: "bytes | None" = None, + nfc_secret_trezor: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["DebugLinkPairingInfo"]: + return isinstance(msg, cls) + class DebugLinkStop(protobuf.MessageType): @classmethod @@ -6164,7 +6206,61 @@ if TYPE_CHECKING: def is_type_of(cls, msg: Any) -> TypeGuard["TezosManagerTransfer"]: return isinstance(msg, cls) - class ThpCredentialMetadata(protobuf.MessageType): + class ThpDeviceProperties(protobuf.MessageType): + internal_model: "str | None" + model_variant: "int | None" + protocol_version_major: "int | None" + protocol_version_minor: "int | None" + pairing_methods: "list[ThpPairingMethod]" + + def __init__( + self, + *, + pairing_methods: "list[ThpPairingMethod] | None" = None, + internal_model: "str | None" = None, + model_variant: "int | None" = None, + protocol_version_major: "int | None" = None, + protocol_version_minor: "int | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpDeviceProperties"]: + return isinstance(msg, cls) + + class ThpHandshakeCompletionReqNoisePayload(protobuf.MessageType): + host_pairing_credential: "bytes | None" + + def __init__( + self, + *, + host_pairing_credential: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpHandshakeCompletionReqNoisePayload"]: + return isinstance(msg, cls) + + class ThpCreateNewSession(protobuf.MessageType): + passphrase: "str | None" + on_device: "bool | None" + derive_cardano: "bool | None" + + def __init__( + self, + *, + passphrase: "str | None" = None, + on_device: "bool | None" = None, + derive_cardano: "bool | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpCreateNewSession"]: + return isinstance(msg, cls) + + class ThpPairingRequest(protobuf.MessageType): host_name: "str | None" def __init__( @@ -6174,6 +6270,220 @@ if TYPE_CHECKING: ) -> None: pass + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpPairingRequest"]: + return isinstance(msg, cls) + + class ThpPairingRequestApproved(protobuf.MessageType): + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpPairingRequestApproved"]: + return isinstance(msg, cls) + + class ThpSelectMethod(protobuf.MessageType): + selected_pairing_method: "ThpPairingMethod | None" + + def __init__( + self, + *, + selected_pairing_method: "ThpPairingMethod | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpSelectMethod"]: + return isinstance(msg, cls) + + class ThpPairingPreparationsFinished(protobuf.MessageType): + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpPairingPreparationsFinished"]: + 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 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 ThpCodeEntryCpaceHostTag(protobuf.MessageType): + cpace_host_public_key: "bytes | None" + tag: "bytes | None" + + def __init__( + self, + *, + cpace_host_public_key: "bytes | None" = None, + tag: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpCodeEntryCpaceHostTag"]: + 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 ThpNfcTagHost(protobuf.MessageType): + tag: "bytes | None" + + def __init__( + self, + *, + tag: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpNfcTagHost"]: + return isinstance(msg, cls) + + class ThpNfcTagTrezor(protobuf.MessageType): + tag: "bytes | None" + + def __init__( + self, + *, + tag: "bytes | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpNfcTagTrezor"]: + return isinstance(msg, cls) + + class ThpCredentialRequest(protobuf.MessageType): + host_static_pubkey: "bytes | None" + autoconnect: "bool | None" + + def __init__( + self, + *, + host_static_pubkey: "bytes | None" = None, + autoconnect: "bool | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpCredentialRequest"]: + return isinstance(msg, cls) + + class ThpCredentialResponse(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["ThpCredentialResponse"]: + return isinstance(msg, cls) + + class ThpEndRequest(protobuf.MessageType): + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpEndRequest"]: + return isinstance(msg, cls) + + class ThpEndResponse(protobuf.MessageType): + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["ThpEndResponse"]: + return isinstance(msg, cls) + + class ThpCredentialMetadata(protobuf.MessageType): + host_name: "str | None" + autoconnect: "bool | None" + + def __init__( + self, + *, + host_name: "str | None" = None, + autoconnect: "bool | None" = None, + ) -> None: + pass + @classmethod def is_type_of(cls, msg: Any) -> TypeGuard["ThpCredentialMetadata"]: return isinstance(msg, cls) diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py index 11ef808ff9..f3012e8da2 100644 --- a/core/src/trezor/utils.py +++ b/core/src/trezor/utils.py @@ -35,6 +35,10 @@ from trezorutils import ( # noqa: F401 ) from typing import TYPE_CHECKING +DISABLE_ENCRYPTION: bool = False + +ALLOW_DEBUG_MESSAGES: bool = True + if __debug__: if EMULATOR: import uos diff --git a/core/src/trezor/wire/__init__.py b/core/src/trezor/wire/__init__.py index 2662a5610a..287ab3377b 100644 --- a/core/src/trezor/wire/__init__.py +++ b/core/src/trezor/wire/__init__.py @@ -5,7 +5,7 @@ Handles on-the-wire communication with a host computer. The communication is: - Request / response. - Protobuf-encoded, see `protobuf.py`. -- Wrapped in a simple envelope format, see `trezor/wire/codec/codec_v1.py`. +- Wrapped in a simple envelope format, see `trezor/wire/codec/codec_v1.py` or `trezor/wire/thp/thp_main.py`. - Transferred over USB interface, or UDP in case of Unix emulation. This module: @@ -29,7 +29,12 @@ from typing import TYPE_CHECKING from trezor import log, loop, protobuf, utils from . import message_handler, protocol_common -from .codec.codec_context import CodecContext + +if utils.USE_THP: + from .thp import thp_main +else: + from .codec.codec_context import CodecContext + from .context import UnexpectedMessageException from .message_handler import failure @@ -37,10 +42,6 @@ from .message_handler import failure # other packages. from .errors import * # isort:skip # noqa: F401,F403 -_PROTOBUF_BUFFER_SIZE = const(8192) - -WIRE_BUFFER = bytearray(_PROTOBUF_BUFFER_SIZE) - if TYPE_CHECKING: from trezorio import WireInterface from typing import Any, Callable, Coroutine, TypeVar @@ -57,57 +58,91 @@ def setup(iface: WireInterface) -> None: loop.schedule(handle_session(iface)) -async def handle_session(iface: WireInterface) -> None: - ctx = CodecContext(iface, WIRE_BUFFER) - next_msg: protocol_common.Message | None = None +if utils.USE_THP: + # memory_manager is imported to create READ/WRITE buffers + # in more stable area of memory + from .thp import memory_manager # noqa: F401 - # Take a mark of modules that are imported at this point, so we can - # roll back and un-import any others. - modules = utils.unimport_begin() - while True: - try: - if next_msg is None: - # If the previous run did not keep an unprocessed message for us, - # wait for a new one coming from the wire. - try: - msg = await ctx.read_from_wire() - except protocol_common.WireError as exc: - if __debug__: - log.exception(__name__, exc) - await ctx.write(failure(exc)) - continue + async def handle_session(iface: WireInterface) -> None: - else: - # Process the message from previous run. - msg = next_msg - next_msg = None + # Take a mark of modules that are imported at this point, so we can + # roll back and un-import any others. + modules = utils.unimport_begin() - do_not_restart = False + while True: try: - do_not_restart = await message_handler.handle_single_message(ctx, msg) - except UnexpectedMessageException as unexpected: - # The workflow was interrupted by an unexpected message. We need to - # process it as if it was a new message... - next_msg = unexpected.msg - # ...and we must not restart because that would lose the message. - do_not_restart = True - continue + await thp_main.thp_main_loop(iface) except Exception as exc: - # Log and ignore. The session handler can only exit explicitly in the - # following finally block. + # Log and try again. if __debug__: log.exception(__name__, exc) finally: # Unload modules imported by the workflow. Should not raise. + if __debug__: + log.debug(__name__, "utils.unimport_end(modules) and loop.clear()") utils.unimport_end(modules) + loop.clear() + return # pylint: disable=lost-exception - if not do_not_restart: - # Let the session be restarted from `main`. - loop.clear() - return # pylint: disable=lost-exception +else: + _PROTOBUF_BUFFER_SIZE = const(8192) + WIRE_BUFFER = bytearray(_PROTOBUF_BUFFER_SIZE) - 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 handle_session(iface: WireInterface) -> None: + ctx = CodecContext(iface, WIRE_BUFFER) + next_msg: protocol_common.Message | None = None + + # Take a mark of modules that are imported at this point, so we can + # roll back and un-import any others. + modules = utils.unimport_begin() + while True: + try: + if next_msg is None: + # If the previous run did not keep an unprocessed message for us, + # wait for a new one coming from the wire. + try: + msg = await ctx.read_from_wire() + except protocol_common.WireError as exc: + if __debug__: + log.exception(__name__, exc) + await ctx.write(failure(exc)) + continue + + else: + # Process the message from previous run. + msg = next_msg + next_msg = None + + do_not_restart = False + try: + do_not_restart = await message_handler.handle_single_message( + ctx, msg + ) + except UnexpectedMessageException as unexpected: + # The workflow was interrupted by an unexpected message. We need to + # process it as if it was a new message... + next_msg = unexpected.msg + # ...and we must not restart because that would lose the message. + do_not_restart = True + continue + 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: + # Unload modules imported by the workflow. Should not raise. + utils.unimport_end(modules) + + if not do_not_restart: + # Let the session be restarted from `main`. + if __debug__: + log.debug(__name__, "loop.clear()") + 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) diff --git a/core/src/trezor/wire/context.py b/core/src/trezor/wire/context.py index 56df34fbc5..00bfeb77d4 100644 --- a/core/src/trezor/wire/context.py +++ b/core/src/trezor/wire/context.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING from storage import cache from storage.cache_common import SESSIONLESS_FLAG -from trezor import loop, protobuf +from trezor import loop, protobuf, utils from .protocol_common import Context, Message @@ -138,6 +138,17 @@ def with_context(ctx: Context, workflow: loop.Task) -> Generator: send_exc = None +def try_get_ctx_ids() -> tuple[bytes, bytes] | None: + ids = None + if utils.USE_THP: + from trezor.wire.thp.session_context import GenericSessionContext + + ctx = get_context() + if isinstance(ctx, GenericSessionContext): + ids = (ctx.channel_id, ctx.session_id.to_bytes(1, "big")) + return ids + + # ACCESS TO CACHE if TYPE_CHECKING: diff --git a/core/src/trezor/wire/errors.py b/core/src/trezor/wire/errors.py index 376820b583..8f572fcf0c 100644 --- a/core/src/trezor/wire/errors.py +++ b/core/src/trezor/wire/errors.py @@ -8,6 +8,17 @@ class Error(Exception): self.message = message +class SilentError(Exception): + def __init__(self, message: str) -> None: + super().__init__() + self.message = message + + +class WireBufferError(Error): + def __init__(self, message: str = "Buffer error") -> None: + super().__init__(FailureType.BufferError, message) + + class UnexpectedMessage(Error): def __init__(self, message: str) -> None: super().__init__(FailureType.UnexpectedMessage, message) diff --git a/core/src/trezor/wire/message_handler.py b/core/src/trezor/wire/message_handler.py index 21c901dc90..ace23d34ac 100644 --- a/core/src/trezor/wire/message_handler.py +++ b/core/src/trezor/wire/message_handler.py @@ -25,7 +25,12 @@ def wrap_protobuf_load( expected_type: type[LoadedMessageType], ) -> LoadedMessageType: try: - if __debug__ and utils.EMULATOR and utils.USE_THP: + if ( + __debug__ + and utils.EMULATOR + and utils.USE_THP + and utils.ALLOW_DEBUG_MESSAGES + ): log.debug( __name__, "Buffer to be parsed to a LoadedMessage: %s", @@ -38,7 +43,7 @@ def wrap_protobuf_load( ) return msg except Exception as e: - if __debug__: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: log.exception(__name__, e) if e.args: raise DataError("Failed to decode message: " + " ".join(e.args)) @@ -46,6 +51,25 @@ def wrap_protobuf_load( raise DataError("Failed to decode message") +if utils.USE_THP: + from trezor.enums import ThpMessageType + + def get_msg_name(msg_type: int) -> str | None: + for name in dir(ThpMessageType): + if not name.startswith("__"): # Skip built-in attributes + value = getattr(ThpMessageType, name) + if isinstance(value, int): + if value == msg_type: + return name + return None + + def get_msg_type(msg_name: str) -> int | None: + value = getattr(ThpMessageType, msg_name) + if isinstance(value, int): + return value + return None + + async def handle_single_message(ctx: Context, msg: Message) -> bool: """Handle a message that was loaded from a WireInterface by the caller. @@ -60,17 +84,27 @@ async def handle_single_message(ctx: Context, msg: Message) -> bool: the type of message is supposed to be optimized and not disrupt the running state, this function will return `True`. """ - if __debug__: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: try: msg_type = protobuf.type_for_wire(msg.type).MESSAGE_NAME except Exception: msg_type = f"{msg.type} - unknown message type" - log.debug( - __name__, - "%d receive: <%s>", - ctx.iface.iface_num(), - msg_type, - ) + if utils.USE_THP: + cid = int.from_bytes(ctx.channel_id, "big") + log.debug( + __name__, + "%d:%d receive: <%s>", + ctx.iface.iface_num(), + cid, + msg_type, + ) + else: + log.debug( + __name__, + "%d receive: <%s>", + ctx.iface.iface_num(), + msg_type, + ) res_msg: protobuf.MessageType | None = None @@ -132,7 +166,7 @@ async def handle_single_message(ctx: Context, msg: Message) -> bool: # - 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 __debug__ and utils.ALLOW_DEBUG_MESSAGES: if isinstance(exc, ActionCancelled): log.debug(__name__, "cancelled: %s", exc.message) elif isinstance(exc, loop.TaskClosed): diff --git a/core/src/trezor/wire/protocol_common.py b/core/src/trezor/wire/protocol_common.py index ed4105517b..0e54afe8c3 100644 --- a/core/src/trezor/wire/protocol_common.py +++ b/core/src/trezor/wire/protocol_common.py @@ -4,7 +4,7 @@ from trezor import protobuf if TYPE_CHECKING: from trezorio import WireInterface - from typing import Container, TypeVar, overload + from typing import Awaitable, Container, TypeVar, overload from storage.cache_common import DataCache @@ -72,6 +72,9 @@ class Context: """Write a message to the wire.""" ... + def write_force(self, msg: protobuf.MessageType) -> Awaitable[None]: + return self.write(msg) + async def call( self, msg: protobuf.MessageType, diff --git a/core/src/trezor/wire/thp/__init__.py b/core/src/trezor/wire/thp/__init__.py new file mode 100644 index 0000000000..bbc5a62c6c --- /dev/null +++ b/core/src/trezor/wire/thp/__init__.py @@ -0,0 +1,189 @@ +import ustruct +from micropython import const +from typing import TYPE_CHECKING + +from storage.cache_thp import BROADCAST_CHANNEL_ID +from trezor import protobuf, utils +from trezor.enums import ThpPairingMethod +from trezor.messages import ThpDeviceProperties + +from ..protocol_common import WireError + +if TYPE_CHECKING: + from enum import IntEnum + + from trezor.wire import WireInterface + from typing_extensions import Self +else: + IntEnum = object + +CODEC_V1 = const(0x3F) + +HANDSHAKE_INIT_REQ = const(0x00) +HANDSHAKE_INIT_RES = const(0x01) +HANDSHAKE_COMP_REQ = const(0x02) +HANDSHAKE_COMP_RES = const(0x03) +ENCRYPTED = const(0x04) + +ACK_MESSAGE = const(0x20) +CHANNEL_ALLOCATION_REQ = const(0x40) +_CHANNEL_ALLOCATION_RES = const(0x41) +_ERROR = const(0x42) +CONTINUATION_PACKET = const(0x80) + + +class ThpError(WireError): + pass + + +class ThpDecryptionError(ThpError): + pass + + +class ThpInvalidDataError(ThpError): + pass + + +class ThpDeviceLockedError(ThpError): + pass + + +class ThpUnallocatedSessionError(ThpError): + + def __init__(self, session_id: int) -> None: + self.session_id = session_id + + +class ThpErrorType(IntEnum): + TRANSPORT_BUSY = 1 + UNALLOCATED_CHANNEL = 2 + DECRYPTION_FAILED = 3 + INVALID_DATA = 4 + DEVICE_LOCKED = 5 + + +class ChannelState(IntEnum): + UNALLOCATED = 0 + TH1 = 1 + TH2 = 2 + TP0 = 3 + TP1 = 4 + TP2 = 5 + TP3 = 6 + TP4 = 7 + TC1 = 8 + ENCRYPTED_TRANSPORT = 9 + INVALIDATED = 10 + + +class SessionState(IntEnum): + UNALLOCATED = 0 + ALLOCATED = 1 + SEEDLESS = 2 + + +class PacketHeader: + format_str_init = ">BHH" + format_str_cont = ">BH" + + def __init__(self, ctrl_byte: int, cid: int, length: int) -> None: + self.ctrl_byte = ctrl_byte + self.cid = cid + self.length = length + + def to_bytes(self) -> bytes: + return ustruct.pack(self.format_str_init, self.ctrl_byte, self.cid, self.length) + + def pack_to_init_buffer(self, buffer: bytearray, buffer_offset: int = 0) -> None: + """ + Packs header information in the form of **intial** packet + into the provided buffer. + """ + ustruct.pack_into( + self.format_str_init, + buffer, + buffer_offset, + self.ctrl_byte, + self.cid, + self.length, + ) + + def pack_to_cont_buffer(self, buffer: bytearray, buffer_offset: int = 0) -> None: + """ + Packs header information in the form of **continuation** packet header + into the provided buffer. + """ + ustruct.pack_into( + self.format_str_cont, buffer, buffer_offset, CONTINUATION_PACKET, self.cid + ) + + @classmethod + def get_error_header(cls, cid: int, length: int) -> Self: + """ + Returns header for protocol-level error messages. + """ + return cls(_ERROR, cid, length) + + @classmethod + def get_channel_allocation_response_header(cls, length: int) -> Self: + """ + Returns header for allocation response handshake message. + """ + return cls(_CHANNEL_ALLOCATION_RES, BROADCAST_CHANNEL_ID, length) + + +_DEFAULT_ENABLED_PAIRING_METHODS = [ + ThpPairingMethod.CodeEntry, + ThpPairingMethod.QrCode, + ThpPairingMethod.NFC, +] + + +def get_enabled_pairing_methods( + iface: WireInterface | None = None, +) -> list[ThpPairingMethod]: + """ + Returns pairing methods that are currently allowed by the device + with respect to the wire interface the host communicates on. + """ + methods = _DEFAULT_ENABLED_PAIRING_METHODS.copy() + if __debug__: + methods.append(ThpPairingMethod.SkipPairing) + return methods + + +def _get_device_properties(iface: WireInterface) -> ThpDeviceProperties: + # TODO define model variants + return ThpDeviceProperties( + pairing_methods=get_enabled_pairing_methods(iface), + internal_model=utils.INTERNAL_MODEL, + model_variant=0, + protocol_version_major=2, + protocol_version_minor=0, + ) + + +def get_encoded_device_properties(iface: WireInterface) -> bytes: + props = _get_device_properties(iface) + length = protobuf.encoded_length(props) + encoded_properties = bytearray(length) + protobuf.encode(encoded_properties, props) + return encoded_properties + + +def get_channel_allocation_response( + nonce: bytes, new_cid: bytes, iface: WireInterface +) -> bytes: + props_msg = get_encoded_device_properties(iface) + return nonce + new_cid + props_msg + + +if __debug__: + + def state_to_str(state: int) -> str: + name = { + v: k for k, v in ChannelState.__dict__.items() if not k.startswith("__") + }.get(state) + if name is not None: + return name + return "UNKNOWN_STATE" diff --git a/core/src/trezor/wire/thp/alternating_bit_protocol.py b/core/src/trezor/wire/thp/alternating_bit_protocol.py new file mode 100644 index 0000000000..d8ba60c5b2 --- /dev/null +++ b/core/src/trezor/wire/thp/alternating_bit_protocol.py @@ -0,0 +1,102 @@ +from storage.cache_thp import ChannelCache +from trezor import log, utils +from trezor.wire.thp import ThpError + + +def is_ack_valid(cache: ChannelCache, ack_bit: int) -> bool: + """ + Checks if: + - an ACK message is expected + - the received ACK message acknowledges correct sequence number (bit) + """ + if not _is_ack_expected(cache): + return False + + if not _has_ack_correct_sync_bit(cache, ack_bit): + return False + + return True + + +def _is_ack_expected(cache: ChannelCache) -> bool: + is_expected: bool = not is_sending_allowed(cache) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES and not is_expected: + log.debug(__name__, "Received unexpected ACK message") + return is_expected + + +def _has_ack_correct_sync_bit(cache: ChannelCache, sync_bit: int) -> bool: + is_correct: bool = get_send_seq_bit(cache) == sync_bit + if __debug__ and utils.ALLOW_DEBUG_MESSAGES and not is_correct: + log.debug(__name__, "Received ACK message with wrong ack bit") + return is_correct + + +def is_sending_allowed(cache: ChannelCache) -> bool: + """ + Checks whether sending a message in the provided channel is allowed. + + Note: Sending a message in a channel before receipt of ACK message for the previously + sent message (in the channel) is prohibited, as it can lead to desynchronization. + """ + return bool(cache.sync >> 7) + + +def get_send_seq_bit(cache: ChannelCache) -> int: + """ + Returns the sequential number (bit) of the next message to be sent + in the provided channel. + """ + return (cache.sync & 0x20) >> 5 + + +def get_expected_receive_seq_bit(cache: ChannelCache) -> int: + """ + Returns the (expected) sequential number (bit) of the next message + to be received in the provided channel. + """ + return (cache.sync & 0x40) >> 6 + + +def set_sending_allowed(cache: ChannelCache, sending_allowed: bool) -> None: + """ + Set the flag whether sending a message in this channel is allowed or not. + """ + cache.sync &= 0x7F + if sending_allowed: + cache.sync |= 0x80 + + +def set_expected_receive_seq_bit(cache: ChannelCache, seq_bit: int) -> None: + """ + Set the expected sequential number (bit) of the next message to be received + in the provided channel + """ + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "Set sync receive expected seq bit to %d", seq_bit) + if seq_bit not in (0, 1): + raise ThpError("Unexpected receive sync bit") + + # set second bit to "seq_bit" value + cache.sync &= 0xBF + if seq_bit: + cache.sync |= 0x40 + + +def _set_send_seq_bit(cache: ChannelCache, seq_bit: int) -> None: + if seq_bit not in (0, 1): + raise ThpError("Unexpected send seq bit") + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "setting sync send seq bit to %d", seq_bit) + # set third bit to "seq_bit" value + cache.sync &= 0xDF + if seq_bit: + cache.sync |= 0x20 + + +def set_send_seq_bit_to_opposite(cache: ChannelCache) -> None: + """ + Set the sequential bit of the "next message to be send" to the opposite value, + i.e. 1 -> 0 and 0 -> 1 + """ + _set_send_seq_bit(cache=cache, seq_bit=1 - get_send_seq_bit(cache)) diff --git a/core/src/trezor/wire/thp/channel.py b/core/src/trezor/wire/thp/channel.py new file mode 100644 index 0000000000..39baa27e49 --- /dev/null +++ b/core/src/trezor/wire/thp/channel.py @@ -0,0 +1,522 @@ +import ustruct +from typing import TYPE_CHECKING + +from storage.cache_common import ( + CHANNEL_HANDSHAKE_HASH, + CHANNEL_KEY_RECEIVE, + CHANNEL_KEY_SEND, + CHANNEL_NONCE_RECEIVE, + CHANNEL_NONCE_SEND, +) +from storage.cache_thp import ( + SESSION_ID_LENGTH, + TAG_LENGTH, + ChannelCache, + clear_sessions_with_channel_id, +) +from trezor import log, loop, protobuf, utils, workflow +from trezor.wire.errors import WireBufferError + +from . import ENCRYPTED, ChannelState, PacketHeader, ThpDecryptionError, ThpError +from . import alternating_bit_protocol as ABP +from . import ( + checksum, + control_byte, + crypto, + interface_manager, + memory_manager, + received_message_handler, +) +from .checksum import CHECKSUM_LENGTH +from .transmission_loop import TransmissionLoop +from .writer import ( + CONT_HEADER_LENGTH, + INIT_HEADER_LENGTH, + MESSAGE_TYPE_LENGTH, + write_payload_to_wire_and_add_checksum, +) + +if __debug__: + from trezor.utils import get_bytes_as_str + + from . import state_to_str + +if TYPE_CHECKING: + from trezorio import WireInterface + from typing import Awaitable + + from trezor.messages import ThpPairingCredential + + from .pairing_context import PairingContext + from .session_context import GenericSessionContext + + +class Channel: + """ + THP protocol encrypted communication channel. + """ + + def __init__(self, channel_cache: ChannelCache) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "channel initialization") + + # Channel properties + self.iface: WireInterface = interface_manager.decode_iface(channel_cache.iface) + self.channel_cache: ChannelCache = channel_cache + self.channel_id: bytes = channel_cache.channel_id + + # Shared variables + self.buffer: utils.BufferType = bytearray(self.iface.TX_PACKET_LEN) + self.fallback_decrypt: bool = False + self.bytes_read: int = 0 + self.expected_payload_length: int = 0 + self.is_cont_packet_expected: bool = False + self.sessions: dict[int, GenericSessionContext] = {} + + # Objects for writing a message to a wire + self.transmission_loop: TransmissionLoop | None = None + self.write_task_spawn: loop.spawn | None = None + + # Temporary objects + self.handshake: crypto.Handshake | None = None + self.credential: ThpPairingCredential | None = None + self.connection_context: PairingContext | None = None + self.busy_decoder: crypto.BusyDecoder | None = None + self.temp_crc: int | None = None + self.temp_crc_compare: bytearray | None = None + self.temp_tag: bytearray | None = None + + def clear(self) -> None: + clear_sessions_with_channel_id(self.channel_id) + self.channel_cache.clear() + + # ACCESS TO CHANNEL_DATA + + def get_channel_id_int(self) -> int: + return int.from_bytes(self.channel_id, "big") + + def get_channel_state(self) -> int: + state = int.from_bytes(self.channel_cache.state, "big") + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("get_channel_state: ", state_to_str(state)) + return state + + def get_handshake_hash(self) -> bytes: + h = self.channel_cache.get(CHANNEL_HANDSHAKE_HASH) + assert h is not None + return h + + def set_channel_state(self, state: ChannelState) -> None: + self.channel_cache.state = bytearray(state.to_bytes(1, "big")) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("set_channel_state: ", state_to_str(state)) + + # READ and DECRYPT + + def receive_packet(self, packet: utils.BufferType) -> Awaitable[None] | None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("receive packet") + + self._handle_received_packet(packet) + + try: + buffer = memory_manager.get_existing_read_buffer(self.get_channel_id_int()) + except WireBufferError: + pass # TODO ?? + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + try: + self._log("self.buffer: ", get_bytes_as_str(buffer)) + except Exception: + pass # TODO handle nicer - happens in fallback_decrypt + + if self.expected_payload_length + INIT_HEADER_LENGTH == self.bytes_read: + self._finish_message() + if self.fallback_decrypt: + # TODO Check CRC and if valid, check tag, if valid update nonces + self._finish_fallback() + # TODO self.write() failure device is busy - use channel buffer to send this failure message!! + return None + return received_message_handler.handle_received_message(self, buffer) + elif self.expected_payload_length + INIT_HEADER_LENGTH > self.bytes_read: + self.is_cont_packet_expected = True + else: + raise ThpError( + "Read more bytes than is the expected length of the message!" + ) + return None + + def _handle_received_packet(self, packet: utils.BufferType) -> None: + ctrl_byte = packet[0] + if control_byte.is_continuation(ctrl_byte): + self._handle_cont_packet(packet) + return + self._handle_init_packet(packet) + + def _handle_init_packet(self, packet: utils.BufferType) -> None: + self.fallback_decrypt = False + self.bytes_read = 0 + self.expected_payload_length = 0 + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("handle_init_packet") + + _, _, payload_length = ustruct.unpack(PacketHeader.format_str_init, packet) + self.expected_payload_length = payload_length + + # If the channel does not "own" the buffer lock, decrypt first packet + # TODO do it only when needed! + # TODO FIX: If "_decrypt_single_packet_payload" is implemented, it will (possibly) break "decrypt_buffer" and nonces incrementation. + # On the other hand, without the single packet decryption, the "advanced" buffer selection cannot be implemented + # in "memory_manager.select_buffer", because the session id is unknown (encrypted). + + # if control_byte.is_encrypted_transport(ctrl_byte): + # packet_payload = self._decrypt_single_packet_payload(packet_payload) + + cid = self.get_channel_id_int() + length = payload_length + INIT_HEADER_LENGTH + try: + buffer = memory_manager.get_new_read_buffer(cid, length) + except WireBufferError: + # TODO handle not encrypted/(short??), eg. ACK + + self.fallback_decrypt = True + + self._prepare_fallback() + + to_read_len = min(len(packet) - INIT_HEADER_LENGTH, payload_length) + buf = memoryview(self.buffer)[:to_read_len] + utils.memcpy(buf, 0, packet, INIT_HEADER_LENGTH) + + # CRC CHECK + self._handle_fallback_crc(buf) + + # TAG CHECK + self._handle_fallback_decryption(buf) + + self.bytes_read += to_read_len + return + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("handle_init_packet - payload len: ", str(payload_length)) + self._log("handle_init_packet - buffer len: ", str(len(buffer))) + + self._buffer_packet_data(buffer, packet, 0) + + def _handle_fallback_crc(self, buf: memoryview) -> None: + assert self.temp_crc is not None + assert self.temp_crc_compare is not None + if self.expected_payload_length > len(buf) + self.bytes_read + CHECKSUM_LENGTH: + # The CRC checksum is not in this packet, compute crc over whole buffer + self.temp_crc = checksum.compute_int(buf, self.temp_crc) + elif self.expected_payload_length >= len(buf) + self.bytes_read: + # At least a part of the CRC checksum is in this packet, compute CRC over + # the first (max(0, crc_copy_len)) bytes and add the rest of the bytes + # (max 4) as the checksum from message into temp_crc_compare + crc_copy_len = ( + self.expected_payload_length - self.bytes_read - CHECKSUM_LENGTH + ) + self.temp_crc = checksum.compute_int(buf[:crc_copy_len], self.temp_crc) + + crc_checksum = buf[ + self.expected_payload_length + - CHECKSUM_LENGTH + - len(buf) + - self.bytes_read : + ] + offset = CHECKSUM_LENGTH - len(buf[-CHECKSUM_LENGTH:]) + utils.memcpy(self.temp_crc_compare, offset, crc_checksum, 0) + else: + raise Exception( + f"Buffer (+bytes_read) ({len(buf)}+{self.bytes_read})should not be bigger than payload{self.expected_payload_length}" + ) + + def _handle_fallback_decryption(self, buf: memoryview) -> None: + assert self.busy_decoder is not None + assert self.temp_tag is not None + if ( + self.expected_payload_length + > len(buf) + self.bytes_read + CHECKSUM_LENGTH + TAG_LENGTH + ): + # The noise tag is not in this packet, decrypt the whole buffer + self.busy_decoder.decrypt_part(buf) + elif self.expected_payload_length >= len(buf) + self.bytes_read: + # At least a part of the noise tag is in this packet, decrypt + # the first (max(0, dec_len)) bytes and add the rest of the bytes + # as the noise_tag from message into temp_tag + dec_len = ( + self.expected_payload_length + - self.bytes_read + - TAG_LENGTH + - CHECKSUM_LENGTH + ) + self.busy_decoder.decrypt_part(buf[:dec_len]) + + noise_tag = buf[ + self.expected_payload_length + - CHECKSUM_LENGTH + - TAG_LENGTH + - len(buf) + - self.bytes_read : + ] + offset = ( + TAG_LENGTH + CHECKSUM_LENGTH - len(buf[-CHECKSUM_LENGTH - TAG_LENGTH :]) + ) + utils.memcpy(self.temp_tag, offset, noise_tag, 0) + else: + raise Exception("Buffer (+bytes_read) should not be bigger than payload") + + def _handle_cont_packet(self, packet: utils.BufferType) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("handle_cont_packet") + + if not self.is_cont_packet_expected: + raise ThpError("Continuation packet is not expected, ignoring") + + if self.fallback_decrypt: + to_read_len = min( + len(packet) - CONT_HEADER_LENGTH, + self.expected_payload_length - self.bytes_read, + ) + buf = memoryview(self.buffer)[:to_read_len] + utils.memcpy(buf, 0, packet, CONT_HEADER_LENGTH) + + # CRC CHECK + self._handle_fallback_crc(buf) + + # TAG CHECK + self._handle_fallback_decryption(buf) + + self.bytes_read += to_read_len + return + try: + buffer = memory_manager.get_existing_read_buffer(self.get_channel_id_int()) + except WireBufferError: + self.set_channel_state(ChannelState.INVALIDATED) + pass # TODO handle device busy, channel kaput + self._buffer_packet_data(buffer, packet, CONT_HEADER_LENGTH) + + def _buffer_packet_data( + self, payload_buffer: utils.BufferType, packet: utils.BufferType, offset: int + ) -> None: + self.bytes_read += utils.memcpy(payload_buffer, self.bytes_read, packet, offset) + + def _finish_message(self) -> None: + self.bytes_read = 0 + self.expected_payload_length = 0 + self.is_cont_packet_expected = False + + def _finish_fallback(self) -> None: + self.fallback_decrypt = False + self.busy_decoder = None + + def _decrypt_single_packet_payload( + self, payload: utils.BufferType + ) -> utils.BufferType: + # crypto.decrypt(b"\x00", b"\x00", payload_buffer, INIT_DATA_OFFSET, len(payload)) + return payload + + def _prepare_fallback(self) -> None: + # prepare busy decoder + key_receive = self.channel_cache.get(CHANNEL_KEY_RECEIVE) + nonce_receive = self.channel_cache.get_int(CHANNEL_NONCE_RECEIVE) + + assert key_receive is not None + assert nonce_receive is not None + + self.busy_decoder = crypto.BusyDecoder(key_receive, nonce_receive) + + # prepare temp channel values + self.temp_crc = 0 + self.temp_crc_compare = bytearray(4) + self.temp_tag = bytearray(16) + # self.bytes_read = INIT_HEADER_LENGTH + + def decrypt_buffer( + self, message_length: int, offset: int = INIT_HEADER_LENGTH + ) -> None: + buffer = memory_manager.get_existing_read_buffer(self.get_channel_id_int()) + # if buffer is WireBufferError: + # pass # TODO handle deviceBUSY + noise_buffer = memoryview(buffer)[ + offset : message_length - CHECKSUM_LENGTH - TAG_LENGTH + ] + tag = buffer[ + message_length + - CHECKSUM_LENGTH + - TAG_LENGTH : message_length + - CHECKSUM_LENGTH + ] + + if utils.DISABLE_ENCRYPTION: + is_tag_valid = tag == crypto.DUMMY_TAG + else: + key_receive = self.channel_cache.get(CHANNEL_KEY_RECEIVE) + nonce_receive = self.channel_cache.get_int(CHANNEL_NONCE_RECEIVE) + + assert key_receive is not None + assert nonce_receive is not None + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("Buffer before decryption: ", get_bytes_as_str(noise_buffer)) + + is_tag_valid = crypto.dec(noise_buffer, tag, key_receive, nonce_receive) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("Buffer after decryption: ", get_bytes_as_str(noise_buffer)) + + self.channel_cache.set_int(CHANNEL_NONCE_RECEIVE, nonce_receive + 1) + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("Is decrypted tag valid? ", str(is_tag_valid)) + self._log("Received tag: ", get_bytes_as_str(tag)) + self._log("New nonce_receive: ", str((nonce_receive + 1))) + + if not is_tag_valid: + raise ThpDecryptionError() + + # WRITE and ENCRYPT + + async def write( + self, + msg: protobuf.MessageType, + session_id: int = 0, + force: bool = False, + ) -> None: + if __debug__ and utils.EMULATOR: + self._log(f"write message: {msg.MESSAGE_NAME}\n", utils.dump_protobuf(msg)) + + cid = self.get_channel_id_int() + msg_size = protobuf.encoded_length(msg) + payload_size = SESSION_ID_LENGTH + MESSAGE_TYPE_LENGTH + msg_size + length = payload_size + CHECKSUM_LENGTH + TAG_LENGTH + INIT_HEADER_LENGTH + try: + buffer = memory_manager.get_new_write_buffer(cid, length) + noise_payload_len = memory_manager.encode_into_buffer( + buffer, msg, session_id + ) + except WireBufferError: + from trezor.messages import Failure, FailureType + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("Failed to get write buffer, killing channel.") + + noise_payload_len = memory_manager.encode_into_buffer( + self.buffer, + Failure( + code=FailureType.FirmwareError, + message="Failed to obtain write buffer.", + ), + session_id, + ) + self.set_channel_state(ChannelState.INVALIDATED) + task = self._write_and_encrypt(noise_payload_len, force) + if task is not None: + await task + + def write_error(self, err_type: int) -> Awaitable[None]: + msg_data = err_type.to_bytes(1, "big") + length = len(msg_data) + CHECKSUM_LENGTH + header = PacketHeader.get_error_header(self.get_channel_id_int(), length) + return write_payload_to_wire_and_add_checksum(self.iface, header, msg_data) + + def write_handshake_message(self, ctrl_byte: int, payload: bytes) -> None: + self._prepare_write() + self.write_task_spawn = loop.spawn( + self._write_encrypted_payload_loop(ctrl_byte, payload) + ) + + def _write_and_encrypt( + self, noise_payload_len: int, force: bool = False + ) -> Awaitable[None] | None: + buffer = memory_manager.get_existing_write_buffer(self.get_channel_id_int()) + # if buffer is WireBufferError: + # pass # TODO handle deviceBUSY + + self._encrypt(buffer, noise_payload_len) + payload_length = noise_payload_len + TAG_LENGTH + + if self.write_task_spawn is not None: + self.write_task_spawn.close() # UPS TODO might break something + print("\nCLOSED\n") + self._prepare_write() + if force: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("Writing FORCE message (without async or retransmission).") + + return self._write_encrypted_payload_loop( + ENCRYPTED, memoryview(buffer[:payload_length]) + ) + self.write_task_spawn = loop.spawn( + self._write_encrypted_payload_loop( + ENCRYPTED, memoryview(buffer[:payload_length]) + ) + ) + return None + + def _prepare_write(self) -> None: + # TODO add condition that disallows to write when can_send_message is false + ABP.set_sending_allowed(self.channel_cache, False) + + async def _write_encrypted_payload_loop( + self, ctrl_byte: int, payload: bytes + ) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("write_encrypted_payload_loop") + + payload_len = len(payload) + CHECKSUM_LENGTH + sync_bit = ABP.get_send_seq_bit(self.channel_cache) + ctrl_byte = control_byte.add_seq_bit_to_ctrl_byte(ctrl_byte, sync_bit) + header = PacketHeader(ctrl_byte, self.get_channel_id_int(), payload_len) + self.transmission_loop = TransmissionLoop(self, header, payload) + await self.transmission_loop.start() + + ABP.set_send_seq_bit_to_opposite(self.channel_cache) + + # Let the main loop be restarted and clear loop, if there is no other + # workflow and the state is ENCRYPTED_TRANSPORT + if self._can_clear_loop(): + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("clearing loop from channel") + + loop.clear() + + def _encrypt(self, buffer: utils.BufferType, noise_payload_len: int) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("encrypt") + + assert len(buffer) >= noise_payload_len + TAG_LENGTH + CHECKSUM_LENGTH + + noise_buffer = memoryview(buffer)[0:noise_payload_len] + + if utils.DISABLE_ENCRYPTION: + tag = crypto.DUMMY_TAG + else: + key_send = self.channel_cache.get(CHANNEL_KEY_SEND) + nonce_send = self.channel_cache.get_int(CHANNEL_NONCE_SEND) + + assert key_send is not None + assert nonce_send is not None + + tag = crypto.enc(noise_buffer, key_send, nonce_send) + + self.channel_cache.set_int(CHANNEL_NONCE_SEND, nonce_send + 1) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + self._log("New nonce_send: ", str((nonce_send + 1))) + + buffer[noise_payload_len : noise_payload_len + TAG_LENGTH] = tag + + def _can_clear_loop(self) -> bool: + return ( + not workflow.tasks + ) and self.get_channel_state() is ChannelState.ENCRYPTED_TRANSPORT + + if __debug__: + + def _log(self, text_1: str, text_2: str = "") -> None: + log.debug( + __name__, + "(cid: %s) %s%s", + get_bytes_as_str(self.channel_id), + text_1, + text_2, + ) diff --git a/core/src/trezor/wire/thp/channel_manager.py b/core/src/trezor/wire/thp/channel_manager.py new file mode 100644 index 0000000000..75de9485f9 --- /dev/null +++ b/core/src/trezor/wire/thp/channel_manager.py @@ -0,0 +1,30 @@ +from typing import TYPE_CHECKING + +from storage import cache_thp + +from . import ChannelState, interface_manager +from .channel import Channel + +if TYPE_CHECKING: + from trezorio import WireInterface + + +def create_new_channel(iface: WireInterface) -> Channel: + """ + Creates a new channel for the interface `iface`. + """ + channel_cache = cache_thp.get_new_channel(interface_manager.encode_iface(iface)) + channel = Channel(channel_cache) + channel.set_channel_state(ChannelState.TH1) + return channel + + +def load_cached_channels() -> dict[int, Channel]: + """ + Returns all allocated channels from cache. + """ + channels: dict[int, Channel] = {} + cached_channels = cache_thp.get_all_allocated_channels() + for channel in cached_channels: + channels[int.from_bytes(channel.channel_id, "big")] = Channel(channel) + return channels diff --git a/core/src/trezor/wire/thp/checksum.py b/core/src/trezor/wire/thp/checksum.py new file mode 100644 index 0000000000..44aab46630 --- /dev/null +++ b/core/src/trezor/wire/thp/checksum.py @@ -0,0 +1,33 @@ +from micropython import const + +from trezor import utils +from trezor.crypto import crc + +CHECKSUM_LENGTH = const(4) + + +def compute(data: bytes | utils.BufferType, crc_chain: int = 0) -> bytes: + """ + Returns a CRC-32 checksum of the provided `data`. Allows for for chaining + computations over multiple data segments using `crc_chain` (optional). + """ + return crc.crc32(data, crc_chain).to_bytes(CHECKSUM_LENGTH, "big") + + +def compute_int(data: bytes | utils.BufferType, crc_chain: int = 0) -> int: + """ + Returns a CRC-32 checksum of the provided `data`. Allows for for chaining + computations over multiple data segments using `crc_chain` (optional). + + Returns checksum in the form of `int`. + """ + return crc.crc32(data, crc_chain) + + +def is_valid(checksum: bytes | utils.BufferType, data: bytes) -> bool: + """ + Checks whether the CRC-32 checksum of the `data` is the same + as the checksum provided in `checksum`. + """ + data_checksum = compute(data) + return checksum == data_checksum diff --git a/core/src/trezor/wire/thp/control_byte.py b/core/src/trezor/wire/thp/control_byte.py new file mode 100644 index 0000000000..5d4d69b040 --- /dev/null +++ b/core/src/trezor/wire/thp/control_byte.py @@ -0,0 +1,50 @@ +from micropython import const + +from . import ( + ACK_MESSAGE, + CONTINUATION_PACKET, + ENCRYPTED, + HANDSHAKE_COMP_REQ, + HANDSHAKE_INIT_REQ, + ThpError, +) + +_CONTINUATION_PACKET_MASK = const(0x80) +_ACK_MASK = const(0xF7) +_DATA_MASK = const(0xE7) + + +def add_seq_bit_to_ctrl_byte(ctrl_byte: int, seq_bit: int) -> int: + if seq_bit == 0: + return ctrl_byte & 0xEF + if seq_bit == 1: + return ctrl_byte | 0x10 + raise ThpError("Unexpected sequence bit") + + +def add_ack_bit_to_ctrl_byte(ctrl_byte: int, ack_bit: int) -> int: + if ack_bit == 0: + return ctrl_byte & 0xF7 + if ack_bit == 1: + return ctrl_byte | 0x08 + raise ThpError("Unexpected acknowledgement bit") + + +def is_ack(ctrl_byte: int) -> bool: + return ctrl_byte & _ACK_MASK == ACK_MESSAGE + + +def is_continuation(ctrl_byte: int) -> bool: + return ctrl_byte & _CONTINUATION_PACKET_MASK == CONTINUATION_PACKET + + +def is_encrypted_transport(ctrl_byte: int) -> bool: + return ctrl_byte & _DATA_MASK == ENCRYPTED + + +def is_handshake_init_req(ctrl_byte: int) -> bool: + return ctrl_byte & _DATA_MASK == HANDSHAKE_INIT_REQ + + +def is_handshake_comp_req(ctrl_byte: int) -> bool: + return ctrl_byte & _DATA_MASK == HANDSHAKE_COMP_REQ diff --git a/core/src/trezor/wire/thp/cpace.py b/core/src/trezor/wire/thp/cpace.py new file mode 100644 index 0000000000..fad0f705d3 --- /dev/null +++ b/core/src/trezor/wire/thp/cpace.py @@ -0,0 +1,37 @@ +from trezor.crypto import elligator2, random +from trezor.crypto.curve import curve25519 +from trezor.crypto.hashlib import sha512 + +_PREFIX = b"\x08\x43\x50\x61\x63\x65\x32\x35\x35\x06" +_PADDING = b"\x6f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20" + + +class Cpace: + """ + CPace, a balanced composable PAKE: https://datatracker.ietf.org/doc/draft-irtf-cfrg-cpace/ + """ + + def __init__(self, handshake_hash: bytes) -> None: + self.handshake_hash: bytes = handshake_hash + self.shared_secret: bytes + self.trezor_private_key: bytes + self.trezor_public_key: bytes + + def generate_keys_and_secret(self, code_code_entry: bytes) -> None: + """ + Generate ephemeral key pair and a shared secret using Elligator2 with X25519. + """ + sha_ctx = sha512(_PREFIX) + sha_ctx.update(code_code_entry) + sha_ctx.update(_PADDING) + sha_ctx.update(self.handshake_hash) + sha_ctx.update(b"\x00") + pregenerator = sha_ctx.digest()[:32] + generator = elligator2.map_to_curve25519(pregenerator) + self.trezor_private_key = random.bytes(32) + self.trezor_public_key = curve25519.multiply(self.trezor_private_key, generator) + + def compute_shared_secret(self, host_public_key: bytes) -> None: + self.shared_secret = curve25519.multiply( + self.trezor_private_key, host_public_key + ) diff --git a/core/src/trezor/wire/thp/crypto.py b/core/src/trezor/wire/thp/crypto.py new file mode 100644 index 0000000000..4ba7fc71c9 --- /dev/null +++ b/core/src/trezor/wire/thp/crypto.py @@ -0,0 +1,220 @@ +from micropython import const +from trezorcrypto import aesgcm, bip32, curve25519, hmac + +from storage import device +from trezor import log, utils +from trezor.crypto.hashlib import sha256 +from trezor.wire.thp import ThpDecryptionError + +# The HARDENED flag is taken from apps.common.paths +# It is not imported to save on resources +HARDENED = const(0x8000_0000) +PUBKEY_LENGTH = const(32) +if utils.DISABLE_ENCRYPTION: + DUMMY_TAG = b"\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xB0\xB1\xB2\xB3\xB4\xB5" + +if __debug__: + from trezor.utils import get_bytes_as_str + + +def enc( + buffer: utils.BufferType, key: bytes, nonce: int, auth_data: bytes = b"" +) -> bytes: + """ + Encrypts the provided `buffer` with AES-GCM (in place). + Returns a 16-byte long encryption tag. + """ + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "enc (key: %s, nonce: %d)", get_bytes_as_str(key), nonce) + iv = _get_iv_from_nonce(nonce) + aes_ctx = aesgcm(key, iv) + aes_ctx.auth(auth_data) + aes_ctx.encrypt_in_place(buffer) + return aes_ctx.finish() + + +def dec( + buffer: utils.BufferType, tag: bytes, key: bytes, nonce: int, auth_data: bytes = b"" +) -> bool: + """ + Decrypts the provided buffer (in place). Returns `True` if the provided authentication `tag` is the same as + the tag computed in decryption, otherwise it returns `False`. + """ + iv = _get_iv_from_nonce(nonce) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "dec (key: %s, nonce: %d)", get_bytes_as_str(key), nonce) + aes_ctx = aesgcm(key, iv) + aes_ctx.auth(auth_data) + aes_ctx.decrypt_in_place(buffer) + computed_tag = aes_ctx.finish() + return computed_tag == tag + + +class BusyDecoder: + + def __init__(self, key: bytes, nonce: int, auth_data: bytes = b"") -> None: + iv = _get_iv_from_nonce(nonce) + self.aes_ctx = aesgcm(key, iv) + self.aes_ctx.auth(auth_data) + + def decrypt_part(self, part: utils.BufferType) -> None: + self.aes_ctx.decrypt_in_place(part) + + def finish_and_check_tag(self, tag: bytes) -> bool: + computed_tag = self.aes_ctx.finish() + return computed_tag == tag + + +PROTOCOL_NAME = b"Noise_XX_25519_AESGCM_SHA256\x00\x00\x00\x00" +IV_1 = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +IV_2 = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" + + +class Handshake: + """ + `Handshake` holds (temporary) values and keys that are used during the creation of an encrypted channel. + The following values should be saved for future use before disposing of this object: + - `h` - handshake hash, can be used to bind other values to the channel + - `key_receive` - key for decrypting incoming communication + - `key_send` - key for encrypting outgoing communication + """ + + def __init__(self) -> None: + self.trezor_ephemeral_privkey: bytes + self.ck: bytes + self.k: bytes + self.h: bytes + self.key_receive: bytes + self.key_send: bytes + + def handle_th1_crypto( + self, + device_properties: bytes, + host_ephemeral_pubkey: bytes, + ) -> tuple[bytes, bytes, bytes]: + + trezor_static_privkey, trezor_static_pubkey = _derive_static_key_pair() + self.trezor_ephemeral_privkey = curve25519.generate_secret() + trezor_ephemeral_pubkey = curve25519.publickey(self.trezor_ephemeral_privkey) + self.h = _hash_of_two(PROTOCOL_NAME, device_properties) + self.h = _hash_of_two(self.h, host_ephemeral_pubkey) + self.h = _hash_of_two(self.h, trezor_ephemeral_pubkey) + point = curve25519.multiply( + self.trezor_ephemeral_privkey, host_ephemeral_pubkey + ) + self.ck, self.k = _hkdf(PROTOCOL_NAME, point) + mask = _hash_of_two(trezor_static_pubkey, trezor_ephemeral_pubkey) + trezor_masked_static_pubkey = curve25519.multiply(mask, trezor_static_pubkey) + aes_ctx = aesgcm(self.k, IV_1) + encrypted_trezor_static_pubkey = aes_ctx.encrypt(trezor_masked_static_pubkey) + if __debug__: + log.debug( + __name__, "th1 - enc (key: %s, nonce: %d)", get_bytes_as_str(self.k), 0 + ) + aes_ctx.auth(self.h) + tag_to_encrypted_key = aes_ctx.finish() + encrypted_trezor_static_pubkey = ( + encrypted_trezor_static_pubkey + tag_to_encrypted_key + ) + self.h = _hash_of_two(self.h, encrypted_trezor_static_pubkey) + point = curve25519.multiply(trezor_static_privkey, host_ephemeral_pubkey) + self.ck, self.k = _hkdf(self.ck, curve25519.multiply(mask, point)) + aes_ctx = aesgcm(self.k, IV_1) + aes_ctx.auth(self.h) + tag = aes_ctx.finish() + self.h = _hash_of_two(self.h, tag) + return (trezor_ephemeral_pubkey, encrypted_trezor_static_pubkey, tag) + + def handle_th2_crypto( + self, + encrypted_host_static_pubkey: utils.BufferType, + encrypted_payload: utils.BufferType, + ) -> None: + + aes_ctx = aesgcm(self.k, IV_2) + + # The new value of hash `h` MUST be computed before the `encrypted_host_static_pubkey` is decrypted. + # However, decryption of `encrypted_host_static_pubkey` MUST use the previous value of `h` for + # authentication of the gcm tag. + aes_ctx.auth(self.h) # Authenticate with the previous value of `h` + self.h = _hash_of_two(self.h, encrypted_host_static_pubkey) # Compute new value + aes_ctx.decrypt_in_place( + memoryview(encrypted_host_static_pubkey)[:PUBKEY_LENGTH] + ) + if __debug__: + log.debug( + __name__, "th2 - dec (key: %s, nonce: %d)", get_bytes_as_str(self.k), 1 + ) + host_static_pubkey = memoryview(encrypted_host_static_pubkey)[:PUBKEY_LENGTH] + tag = aes_ctx.finish() + if tag != encrypted_host_static_pubkey[-16:]: + raise ThpDecryptionError() + + self.ck, self.k = _hkdf( + self.ck, + curve25519.multiply(self.trezor_ephemeral_privkey, host_static_pubkey), + ) + aes_ctx = aesgcm(self.k, IV_1) + aes_ctx.auth(self.h) + aes_ctx.decrypt_in_place(memoryview(encrypted_payload)[:-16]) + if __debug__: + log.debug( + __name__, "th2 - dec (key: %s, nonce: %d)", get_bytes_as_str(self.k), 0 + ) + tag = aes_ctx.finish() + if tag != encrypted_payload[-16:]: + raise ThpDecryptionError() + + self.h = _hash_of_two(self.h, memoryview(encrypted_payload)[:-16]) + self.key_receive, self.key_send = _hkdf(self.ck, b"") + if __debug__: + log.debug( + __name__, + "(key_receive: %s, key_send: %s)", + get_bytes_as_str(self.key_receive), + get_bytes_as_str(self.key_send), + ) + + def get_handshake_completion_response(self, trezor_state: bytes) -> bytes: + aes_ctx = aesgcm(self.key_send, IV_1) + encrypted_trezor_state = aes_ctx.encrypt(trezor_state) + tag = aes_ctx.finish() + return encrypted_trezor_state + tag + + +def _derive_static_key_pair() -> tuple[bytes, bytes]: + node_int = HARDENED | int.from_bytes(b"\x00THP", "big") + node = bip32.from_seed(device.get_device_secret(), "curve25519") + node.derive(node_int) + + trezor_static_privkey = node.private_key() + trezor_static_pubkey = node.public_key()[1:33] + # Note: the first byte (\x01) of the public key is removed, as it + # only indicates the type of the elliptic curve used + + return trezor_static_privkey, trezor_static_pubkey + + +def get_trezor_static_pubkey() -> bytes: + _, pubkey = _derive_static_key_pair() + return pubkey + + +def _hkdf(chaining_key: bytes, input: bytes) -> tuple[bytes, bytes]: + temp_key = hmac(hmac.SHA256, chaining_key, input).digest() + output_1 = hmac(hmac.SHA256, temp_key, b"\x01").digest() + ctx_output_2 = hmac(hmac.SHA256, temp_key, output_1) + ctx_output_2.update(b"\x02") + output_2 = ctx_output_2.digest() + return (output_1, output_2) + + +def _hash_of_two(part_1: bytes, part_2: bytes) -> bytes: + ctx = sha256(part_1) + ctx.update(part_2) + return ctx.digest() + + +def _get_iv_from_nonce(nonce: int) -> bytes: + utils.ensure(nonce <= 0xFFFFFFFFFFFFFFFF, "Nonce overflow, terminate the channel") + return bytes(4) + nonce.to_bytes(8, "big") diff --git a/core/src/trezor/wire/thp/interface_manager.py b/core/src/trezor/wire/thp/interface_manager.py new file mode 100644 index 0000000000..a1fecfe7d6 --- /dev/null +++ b/core/src/trezor/wire/thp/interface_manager.py @@ -0,0 +1,28 @@ +from typing import TYPE_CHECKING + +import usb + +_WIRE_INTERFACE_USB = b"\x01" +# TODO _WIRE_INTERFACE_BLE = b"\x02" + +if TYPE_CHECKING: + from trezorio import WireInterface + + +def decode_iface(cached_iface: bytes) -> WireInterface: + """Decode the cached wire interface.""" + if cached_iface == _WIRE_INTERFACE_USB: + iface = usb.iface_wire + if iface is None: + raise RuntimeError("There is no valid USB WireInterface") + return iface + # TODO implement bluetooth interface + raise Exception("Unknown WireInterface") + + +def encode_iface(iface: WireInterface) -> bytes: + """Encode wire interface into bytes.""" + if iface is usb.iface_wire: + return _WIRE_INTERFACE_USB + # TODO implement bluetooth interface + raise Exception("Unknown WireInterface") diff --git a/core/src/trezor/wire/thp/memory_manager.py b/core/src/trezor/wire/thp/memory_manager.py new file mode 100644 index 0000000000..681642d8fc --- /dev/null +++ b/core/src/trezor/wire/thp/memory_manager.py @@ -0,0 +1,180 @@ +import utime +from micropython import const + +from storage.cache_thp import SESSION_ID_LENGTH +from trezor import protobuf, utils +from trezor.wire.errors import WireBufferError +from trezor.wire.message_handler import get_msg_type + +from . import ThpError +from .writer import MAX_PAYLOAD_LEN, MESSAGE_TYPE_LENGTH + +_PROTOBUF_BUFFER_SIZE = 8192 +READ_BUFFER = bytearray(_PROTOBUF_BUFFER_SIZE) +WRITE_BUFFER = bytearray(_PROTOBUF_BUFFER_SIZE) +LOCK_TIMEOUT = 200 # miliseconds + + +lock_owner_cid: int | None = None +lock_time: int = 0 + +READ_BUFFER_SLICE: memoryview | None = None +WRITE_BUFFER_SLICE: memoryview | None = None + +# Buffer types +_READ: int = const(0) +_WRITE: int = const(1) + + +# +# Access to buffer slices + + +def get_new_read_buffer(channel_id: int, length: int) -> memoryview: + return _get_new_buffer(_READ, channel_id, length) + + +def get_new_write_buffer(channel_id: int, length: int) -> memoryview: + return _get_new_buffer(_WRITE, channel_id, length) + + +def get_existing_read_buffer(channel_id: int) -> memoryview: + return _get_existing_buffer(_READ, channel_id) + + +def get_existing_write_buffer(channel_id: int) -> memoryview: + return _get_existing_buffer(_WRITE, channel_id) + + +def _get_new_buffer(buffer_type: int, channel_id: int, length: int) -> memoryview: + if is_locked(): + if not is_owner(channel_id): + raise WireBufferError + update_lock_time() + else: + update_lock(channel_id) + + if buffer_type == _READ: + global READ_BUFFER + buffer = READ_BUFFER + elif buffer_type == _WRITE: + global WRITE_BUFFER + buffer = WRITE_BUFFER + else: + raise Exception("Invalid buffer_type") + + if length > MAX_PAYLOAD_LEN or length > len(buffer): + raise ThpError("Message is too large") # TODO reword + + if buffer_type == _READ: + global READ_BUFFER_SLICE + READ_BUFFER_SLICE = memoryview(READ_BUFFER)[:length] + return READ_BUFFER_SLICE + + if buffer_type == _WRITE: + global WRITE_BUFFER_SLICE + WRITE_BUFFER_SLICE = memoryview(WRITE_BUFFER)[:length] + return WRITE_BUFFER_SLICE + + raise Exception("Invalid buffer_type") + + +def _get_existing_buffer(buffer_type: int, channel_id: int) -> memoryview: + if not is_owner(channel_id): + raise WireBufferError + update_lock_time() + + if buffer_type == _READ: + global READ_BUFFER_SLICE + if READ_BUFFER_SLICE is None: + raise WireBufferError + return READ_BUFFER_SLICE + + if buffer_type == _WRITE: + global WRITE_BUFFER_SLICE + if WRITE_BUFFER_SLICE is None: + raise WireBufferError + return WRITE_BUFFER_SLICE + + raise Exception("Invalid buffer_type") + + +# +# Buffer locking + + +def is_locked() -> bool: + global lock_owner_cid + global lock_time + + time_diff = utime.ticks_diff(utime.ticks_ms(), lock_time) + return lock_owner_cid is not None and time_diff < LOCK_TIMEOUT + + +def is_owner(channel_id: int) -> bool: + global lock_owner_cid + return lock_owner_cid is not None and lock_owner_cid == channel_id + + +def update_lock(channel_id: int) -> None: + set_owner(channel_id) + update_lock_time() + + +def set_owner(channel_id: int) -> None: + global lock_owner_cid + lock_owner_cid = channel_id + + +def update_lock_time() -> None: + global lock_time + lock_time = utime.ticks_ms() + + +# +# Helper for encoding messages into buffer + + +def encode_into_buffer( + buffer: utils.BufferType, msg: protobuf.MessageType, session_id: int +) -> int: + """Encode protobuf message `msg` into the `buffer`, including session id + an messages's wire type. Will fail if provided message has no wire type.""" + + # cannot write message without wire type + msg_type = msg.MESSAGE_WIRE_TYPE + if msg_type is None: + msg_type = get_msg_type(msg.MESSAGE_NAME) + if msg_type is None: + raise Exception("Message has no wire type.") + + msg_size = protobuf.encoded_length(msg) + payload_size = SESSION_ID_LENGTH + MESSAGE_TYPE_LENGTH + msg_size + + _encode_session_into_buffer(memoryview(buffer), session_id) + _encode_message_type_into_buffer(memoryview(buffer), msg_type, SESSION_ID_LENGTH) + _encode_message_into_buffer( + memoryview(buffer), msg, SESSION_ID_LENGTH + MESSAGE_TYPE_LENGTH + ) + + return payload_size + + +def _encode_session_into_buffer( + buffer: memoryview, session_id: int, buffer_offset: int = 0 +) -> None: + session_id_bytes = int.to_bytes(session_id, SESSION_ID_LENGTH, "big") + utils.memcpy(buffer, buffer_offset, session_id_bytes, 0) + + +def _encode_message_type_into_buffer( + buffer: memoryview, message_type: int, offset: int = 0 +) -> None: + msg_type_bytes = int.to_bytes(message_type, MESSAGE_TYPE_LENGTH, "big") + utils.memcpy(buffer, offset, msg_type_bytes, 0) + + +def _encode_message_into_buffer( + buffer: memoryview, message: protobuf.MessageType, buffer_offset: int = 0 +) -> None: + protobuf.encode(memoryview(buffer[buffer_offset:]), message) diff --git a/core/src/trezor/wire/thp/pairing_context.py b/core/src/trezor/wire/thp/pairing_context.py new file mode 100644 index 0000000000..4edfea2a90 --- /dev/null +++ b/core/src/trezor/wire/thp/pairing_context.py @@ -0,0 +1,329 @@ +from typing import TYPE_CHECKING +from ubinascii import hexlify + +import trezorui_api +from trezor import loop, protobuf, workflow +from trezor.enums import ButtonRequestType +from trezor.wire import context, message_handler, protocol_common +from trezor.wire.context import UnexpectedMessageException +from trezor.wire.errors import ActionCancelled, SilentError +from trezor.wire.protocol_common import Context, Message +from trezor.wire.thp import ChannelState, get_enabled_pairing_methods + +if TYPE_CHECKING: + from typing import Awaitable, Container + + from trezor.enums import ThpPairingMethod + from trezorui_api import UiResult + + from .channel import Channel + from .cpace import Cpace + + pass + +if __debug__: + from trezor import log + + +class PairingContext(Context): + + def __init__(self, channel_ctx: Channel) -> None: + super().__init__(channel_ctx.iface, channel_ctx.channel_id) + self.channel_ctx: Channel = channel_ctx + self.incoming_message = loop.mailbox() + self.nfc_secret: bytes | None = None + self.qr_code_secret: bytes | None = None + self.code_entry_secret: bytes | None = None + + self.selected_method: ThpPairingMethod + + self.code_code_entry: int | None = None + self.code_qr_code: bytes | None = None + self.code_nfc: bytes | None = None + # The 2 following attributes are important for NFC pairing + self.nfc_secret_host: bytes | None = None + self.handshake_hash_host: bytes | None = None + + self.cpace: Cpace + self.host_name: str | None + + async def handle(self) -> None: + next_message: Message | None = None + + 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: Message = await self.incoming_message + 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_message(self, message) + except Exception as exc: + # Log and ignore. The context handler can only exit explicitly in the + # following finally block. + if __debug__: + log.exception(__name__, exc) + finally: + if next_message is None: + + # Shut down the loop if there is no next message waiting. + return # pylint: disable=lost-exception + + except Exception as exc: + # Log and try again. The context handler can only exit explicitly via + # finally block 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: Message = await self.incoming_message + + if message.type not in expected_types: + raise UnexpectedMessageException(message) + + if expected_type is None: + name = message_handler.get_msg_name(message.type) + if name is None: + expected_type = protobuf.type_for_wire(message.type) + else: + expected_type = protobuf.type_for_name(name) + + return message_handler.wrap_protobuf_load(message.data, expected_type) + + async def write(self, msg: protobuf.MessageType) -> None: + return await self.channel_ctx.write(msg) + + def write_force(self, msg: protobuf.MessageType) -> Awaitable[None]: + return self.channel_ctx.write(msg, force=True) + + async def call( + self, msg: protobuf.MessageType, expected_type: type[protobuf.MessageType] + ) -> protobuf.MessageType: + expected_wire_type = expected_type.MESSAGE_WIRE_TYPE + if expected_wire_type is None: + expected_wire_type = message_handler.get_msg_type( + expected_type.MESSAGE_NAME + ) + + assert expected_wire_type is not None + + await self.write(msg) + del msg + + return await self.read((expected_wire_type,), expected_type) + + async def call_any( + self, msg: protobuf.MessageType, *expected_types: int + ) -> protobuf.MessageType: + await self.write(msg) + del msg + return await self.read(expected_types) + + def set_selected_method(self, selected_method: ThpPairingMethod) -> None: + if selected_method not in get_enabled_pairing_methods(self.iface): + raise Exception("Not allowed to set this method") + self.selected_method = selected_method + + async def show_pairing_dialogue(self) -> None: + from trezor.messages import ThpPairingRequestApproved + from trezor.ui.layouts.common import interact + + result = await interact( + trezorui_api.confirm_action( + title="Pairing dialogue", + action="Do you want to start pairing?", + description="Choose wisely!", + ), + br_name="pairing_request", + br_code=ButtonRequestType.Other, + ) + if result == trezorui_api.CONFIRMED: + await self.write(ThpPairingRequestApproved()) + + async def show_connection_dialogue(self) -> None: + from trezor.ui.layouts.common import interact + + await interact( + trezorui_api.confirm_action( + title="Connection dialogue", + action="Do you want previously connected device to connect?", + description="Choose wisely! (or not)", + ), + br_name="connection_request", + br_code=ButtonRequestType.Other, + ) + + async def show_pairing_method_screen( + self, selected_method: ThpPairingMethod | None = None + ) -> UiResult: + from trezor.enums import ThpPairingMethod + + if selected_method is None: + selected_method = self.selected_method + if selected_method is ThpPairingMethod.CodeEntry: + return await self._show_code_entry_screen() + elif selected_method is ThpPairingMethod.NFC: + return await self._show_nfc_screen() + elif selected_method is ThpPairingMethod.QrCode: + return await self._show_qr_code_screen() + else: + raise Exception("Unknown pairing method") + + async def _show_code_entry_screen(self) -> UiResult: + from trezor.ui.layouts.common import interact + + return await interact( + trezorui_api.show_simple( + title="Copy the following", + text=self._get_code_code_entry_str(), + ), + br_name="pairing_code_entry", + br_code=ButtonRequestType.Other, + ) + + async def _show_nfc_screen(self) -> UiResult: + from trezor.ui.layouts.common import interact + + return await interact( + trezorui_api.show_simple( + title="NFC Pairing", + text="Move your device close to Trezor", + ), + br_name="pairing_nfc", + br_code=ButtonRequestType.Other, + ) + + async def _show_qr_code_screen(self) -> UiResult: + from trezor.ui.layouts.common import interact + + return await interact( + trezorui_api.show_address_details( # noqa + qr_title="Scan QR code to pair", + address=self._get_code_qr_code_str(), + case_sensitive=True, + details_title="", + account="", + path="", + xpubs=[], + ), + br_name="pairing_qr_code", + br_code=ButtonRequestType.Other, + ) + + def _get_code_code_entry_str(self) -> str: + if self.code_code_entry is not None: + code_str = f"{self.code_code_entry:06}" + if __debug__: + log.debug(__name__, "code_code_entry: %s", code_str) + + return code_str[:3] + " " + code_str[3:] + raise Exception("Code entry string is not available") + + def _get_code_qr_code_str(self) -> str: + if self.code_qr_code is not None: + code_str = (hexlify(self.code_qr_code)).decode("utf-8") + if __debug__: + log.debug(__name__, "code_qr_code_hexlified: %s", code_str) + return code_str + raise Exception("QR code string is not available") + + +async def handle_message( + pairing_ctx: PairingContext, + msg: protocol_common.Message, +) -> protocol_common.Message | None: + + res_msg: protobuf.MessageType | None = None + + from apps.thp.pairing import handle_credential_phase, handle_pairing_request + + 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. + name = message_handler.get_msg_name(msg.type) + if name is None: + req_type = protobuf.type_for_wire(msg.type) + else: + req_type = protobuf.type_for_name(name) + + # 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 pairing_ctx.channel_ctx.get_channel_state() == ChannelState.TC1: + task = handle_credential_phase(pairing_ctx, req_msg) + else: + task = handle_pairing_request(pairing_ctx, 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. + res_msg = await workflow.spawn(context.with_context(pairing_ctx, task)) + + except UnexpectedMessageException 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 SilentError as exc: + if __debug__: + log.error(__name__, "SilentError: %s", exc.message) + 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 pairing_ctx.write(res_msg) + return None diff --git a/core/src/trezor/wire/thp/received_message_handler.py b/core/src/trezor/wire/thp/received_message_handler.py new file mode 100644 index 0000000000..f176615387 --- /dev/null +++ b/core/src/trezor/wire/thp/received_message_handler.py @@ -0,0 +1,462 @@ +import ustruct +from typing import TYPE_CHECKING + +from storage.cache_common import ( + CHANNEL_HANDSHAKE_HASH, + CHANNEL_KEY_RECEIVE, + CHANNEL_KEY_SEND, + CHANNEL_NONCE_RECEIVE, + CHANNEL_NONCE_SEND, +) +from storage.cache_thp import ( + KEY_LENGTH, + SESSION_ID_LENGTH, + TAG_LENGTH, + update_channel_last_used, + update_session_last_used, +) +from trezor import config, log, loop, protobuf, utils +from trezor.enums import FailureType +from trezor.messages import Failure +from trezor.wire.thp import memory_manager + +from .. import message_handler +from ..errors import DataError +from ..protocol_common import Message +from . import ( + ACK_MESSAGE, + HANDSHAKE_COMP_RES, + HANDSHAKE_INIT_RES, + ChannelState, + PacketHeader, + SessionState, + ThpDecryptionError, + ThpDeviceLockedError, + ThpError, + ThpErrorType, + ThpInvalidDataError, + ThpUnallocatedSessionError, +) +from . import alternating_bit_protocol as ABP +from . import checksum, control_byte, get_encoded_device_properties, session_manager +from .checksum import CHECKSUM_LENGTH +from .crypto import PUBKEY_LENGTH, Handshake +from .session_context import SeedlessSessionContext +from .writer import ( + INIT_HEADER_LENGTH, + MESSAGE_TYPE_LENGTH, + write_payload_to_wire_and_add_checksum, +) + +if TYPE_CHECKING: + from typing import Awaitable + + from trezor.messages import ThpHandshakeCompletionReqNoisePayload + + from .channel import Channel + +if __debug__: + from trezor.utils import get_bytes_as_str + + +_TREZOR_STATE_UNPAIRED = b"\x00" +_TREZOR_STATE_PAIRED = b"\x01" +_TREZOR_STATE_PAIRED_AUTOCONNECT = b"\x02" + + +async def handle_received_message( + ctx: Channel, message_buffer: utils.BufferType +) -> None: + """Handle a message received from the channel.""" + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "handle_received_message") + if utils.ALLOW_DEBUG_MESSAGES: # TODO remove after performance tests are done + try: + import micropython + + print("micropython.mem_info() from received_message_handler.py") + micropython.mem_info() + print("Allocation count:", micropython.alloc_count()) # type: ignore ["alloc_count" is not a known attribute of module "micropython"] + except AttributeError: + print( + "To show allocation count, create the build with TREZOR_MEMPERF=1" + ) + ctrl_byte, _, payload_length = ustruct.unpack(">BHH", message_buffer) + message_length = payload_length + INIT_HEADER_LENGTH + + _check_checksum(message_length, message_buffer) + + # Synchronization process + seq_bit = (ctrl_byte & 0x10) >> 4 + ack_bit = (ctrl_byte & 0x08) >> 3 + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, + "handle_completed_message - seq bit of message: %d, ack bit of message: %d", + seq_bit, + ack_bit, + ) + # 0: Update "last-time used" + update_channel_last_used(ctx.channel_id) + + # 1: Handle ACKs + if control_byte.is_ack(ctrl_byte): + await _handle_ack(ctx, ack_bit) + return + + if _should_have_ctrl_byte_encrypted_transport( + ctx + ) and not control_byte.is_encrypted_transport(ctrl_byte): + raise ThpError("Message is not encrypted. Ignoring") + + # 2: Handle message with unexpected sequential bit + if seq_bit != ABP.get_expected_receive_seq_bit(ctx.channel_cache): + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "Received message with an unexpected sequential bit") + await _send_ack(ctx, ack_bit=seq_bit) + raise ThpError("Received message with an unexpected sequential bit") + + # 3: Send ACK in response + await _send_ack(ctx, ack_bit=seq_bit) + + ABP.set_expected_receive_seq_bit(ctx.channel_cache, 1 - seq_bit) + + try: + await _handle_message_to_app_or_channel( + ctx, payload_length, message_length, ctrl_byte + ) + except ThpUnallocatedSessionError as e: + error_message = Failure(code=FailureType.ThpUnallocatedSession) + await ctx.write(error_message, e.session_id) + except ThpDecryptionError: + await ctx.write_error(ThpErrorType.DECRYPTION_FAILED) + ctx.clear() + except ThpInvalidDataError: + await ctx.write_error(ThpErrorType.INVALID_DATA) + ctx.clear() + except ThpDeviceLockedError: + await ctx.write_error(ThpErrorType.DEVICE_LOCKED) + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "handle_received_message - end") + + +def _send_ack(ctx: Channel, ack_bit: int) -> Awaitable[None]: + ctrl_byte = control_byte.add_ack_bit_to_ctrl_byte(ACK_MESSAGE, ack_bit) + header = PacketHeader(ctrl_byte, ctx.get_channel_id_int(), CHECKSUM_LENGTH) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, + "Writing ACK message to a channel with id: %d, ack_bit: %d", + ctx.get_channel_id_int(), + ack_bit, + ) + return write_payload_to_wire_and_add_checksum(ctx.iface, header, b"") + + +def _check_checksum(message_length: int, message_buffer: utils.BufferType) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "check_checksum") + if not checksum.is_valid( + checksum=message_buffer[message_length - CHECKSUM_LENGTH : message_length], + data=memoryview(message_buffer)[: message_length - CHECKSUM_LENGTH], + ): + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "Invalid checksum, ignoring message.") + raise ThpError("Invalid checksum, ignoring message.") + + +async def _handle_ack(ctx: Channel, ack_bit: int) -> None: + if not ABP.is_ack_valid(ctx.channel_cache, ack_bit): + return + # ACK is expected and it has correct sync bit + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "Received ACK message with correct ack bit") + if ctx.transmission_loop is not None: + ctx.transmission_loop.stop_immediately() + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "Stopped transmission loop") + + ABP.set_sending_allowed(ctx.channel_cache, True) + + if ctx.write_task_spawn is not None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, 'Control to "write_encrypted_payload_loop" task') + await ctx.write_task_spawn + # Note that no the write_task_spawn could result in loop.clear(), + # which will result in termination of this function - any code after + # this await might not be executed + + +def _handle_message_to_app_or_channel( + ctx: Channel, + payload_length: int, + message_length: int, + ctrl_byte: int, +) -> Awaitable[None]: + state = ctx.get_channel_state() + + if state is ChannelState.ENCRYPTED_TRANSPORT: + return _handle_state_ENCRYPTED_TRANSPORT(ctx, message_length) + + if state is ChannelState.TH1: + return _handle_state_TH1(ctx, payload_length, message_length, ctrl_byte) + + if state is ChannelState.TH2: + return _handle_state_TH2(ctx, message_length, ctrl_byte) + + if _is_channel_state_pairing(state): + return _handle_pairing(ctx, message_length) + + raise ThpError("Unimplemented channel state") + + +async def _handle_state_TH1( + ctx: Channel, + payload_length: int, + message_length: int, + ctrl_byte: int, +) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "handle_state_TH1") + if not control_byte.is_handshake_init_req(ctrl_byte): + raise ThpError("Message received is not a handshake init request!") + if not payload_length == PUBKEY_LENGTH + CHECKSUM_LENGTH: + raise ThpError("Message received is not a valid handshake init request!") + + if not config.is_unlocked(): + raise ThpDeviceLockedError + + ctx.handshake = Handshake() + + buffer = memory_manager.get_existing_read_buffer(ctx.get_channel_id_int()) + # if buffer is BufferError: + # pass # TODO buffer is gone :/ + + host_ephemeral_pubkey = bytearray( + buffer[INIT_HEADER_LENGTH : message_length - CHECKSUM_LENGTH] + ) + trezor_ephemeral_pubkey, encrypted_trezor_static_pubkey, tag = ( + ctx.handshake.handle_th1_crypto( + get_encoded_device_properties(ctx.iface), host_ephemeral_pubkey + ) + ) + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, + "trezor ephemeral pubkey: %s", + get_bytes_as_str(trezor_ephemeral_pubkey), + ) + log.debug( + __name__, + "encrypted trezor masked static pubkey: %s", + get_bytes_as_str(encrypted_trezor_static_pubkey), + ) + log.debug(__name__, "tag: %s", get_bytes_as_str(tag)) + + payload = trezor_ephemeral_pubkey + encrypted_trezor_static_pubkey + tag + + # send handshake init response message + ctx.write_handshake_message(HANDSHAKE_INIT_RES, payload) + ctx.set_channel_state(ChannelState.TH2) + return + + +async def _handle_state_TH2(ctx: Channel, message_length: int, ctrl_byte: int) -> None: + from apps.thp.credential_manager import decode_credential, validate_credential + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "handle_state_TH2") + if not control_byte.is_handshake_comp_req(ctrl_byte): + raise ThpError("Message received is not a handshake completion request!") + if ctx.handshake is None: + raise Exception("Handshake object is not prepared. Retry handshake.") + + if not config.is_unlocked(): + raise ThpDeviceLockedError + + buffer = memory_manager.get_existing_read_buffer(ctx.get_channel_id_int()) + # if buffer is BufferError: + # pass # TODO handle + host_encrypted_static_pubkey = buffer[ + INIT_HEADER_LENGTH : INIT_HEADER_LENGTH + KEY_LENGTH + TAG_LENGTH + ] + handshake_completion_request_noise_payload = buffer[ + INIT_HEADER_LENGTH + KEY_LENGTH + TAG_LENGTH : message_length - CHECKSUM_LENGTH + ] + + ctx.handshake.handle_th2_crypto( + host_encrypted_static_pubkey, handshake_completion_request_noise_payload + ) + + ctx.channel_cache.set(CHANNEL_KEY_RECEIVE, ctx.handshake.key_receive) + ctx.channel_cache.set(CHANNEL_KEY_SEND, ctx.handshake.key_send) + ctx.channel_cache.set(CHANNEL_HANDSHAKE_HASH, ctx.handshake.h) + ctx.channel_cache.set_int(CHANNEL_NONCE_RECEIVE, 0) + ctx.channel_cache.set_int(CHANNEL_NONCE_SEND, 1) + + noise_payload = _decode_message( + buffer[ + INIT_HEADER_LENGTH + + KEY_LENGTH + + TAG_LENGTH : message_length + - CHECKSUM_LENGTH + - TAG_LENGTH + ], + 0, + "ThpHandshakeCompletionReqNoisePayload", + ) + if TYPE_CHECKING: + assert ThpHandshakeCompletionReqNoisePayload.is_type_of(noise_payload) + + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, + "host static pubkey: %s, noise payload: %s", + utils.get_bytes_as_str(host_encrypted_static_pubkey), + utils.get_bytes_as_str(handshake_completion_request_noise_payload), + ) + + # key is decoded in handshake._handle_th2_crypto + host_static_pubkey = host_encrypted_static_pubkey[:PUBKEY_LENGTH] + + paired: bool = False + trezor_state = _TREZOR_STATE_UNPAIRED + + if noise_payload.host_pairing_credential is not None: + try: # TODO change try-except for something better + credential = decode_credential(noise_payload.host_pairing_credential) + paired = validate_credential( + credential, + host_static_pubkey, + ) + if paired: + trezor_state = _TREZOR_STATE_PAIRED + ctx.credential = credential + else: + ctx.credential = None + except DataError as e: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.exception(__name__, e) + pass + + # send hanshake completion response + ctx.write_handshake_message( + HANDSHAKE_COMP_RES, + ctx.handshake.get_handshake_completion_response(trezor_state), + ) + + ctx.handshake = None + + if paired: + ctx.set_channel_state(ChannelState.TC1) + else: + ctx.set_channel_state(ChannelState.TP0) + + +async def _handle_state_ENCRYPTED_TRANSPORT(ctx: Channel, message_length: int) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "handle_state_ENCRYPTED_TRANSPORT") + + ctx.decrypt_buffer(message_length) + + buffer = memory_manager.get_existing_read_buffer(ctx.get_channel_id_int()) + # if buffer is BufferError: + # pass # TODO handle + session_id, message_type = ustruct.unpack( + ">BH", memoryview(buffer)[INIT_HEADER_LENGTH:] + ) + if session_id not in ctx.sessions: + + s = session_manager.get_session_from_cache(ctx, session_id) + + if s is None: + s = SeedlessSessionContext(ctx, session_id) + + ctx.sessions[session_id] = s + loop.schedule(s.handle()) + + elif ctx.sessions[session_id].get_session_state() is SessionState.UNALLOCATED: + raise ThpUnallocatedSessionError(session_id) + + s = ctx.sessions[session_id] + update_session_last_used(s.channel_id, (s.session_id).to_bytes(1, "big")) + + s.incoming_message.put( + Message( + message_type, + buffer[ + INIT_HEADER_LENGTH + + MESSAGE_TYPE_LENGTH + + SESSION_ID_LENGTH : message_length + - CHECKSUM_LENGTH + - TAG_LENGTH + ], + ) + ) + + +async def _handle_pairing(ctx: Channel, message_length: int) -> None: + from .pairing_context import PairingContext + + if ctx.connection_context is None: + ctx.connection_context = PairingContext(ctx) + loop.schedule(ctx.connection_context.handle()) + + ctx.decrypt_buffer(message_length) + buffer = memory_manager.get_existing_read_buffer(ctx.get_channel_id_int()) + # if buffer is BufferError: + # pass # TODO handle + message_type = ustruct.unpack( + ">H", buffer[INIT_HEADER_LENGTH + SESSION_ID_LENGTH :] + )[0] + + ctx.connection_context.incoming_message.put( + Message( + message_type, + buffer[ + INIT_HEADER_LENGTH + + MESSAGE_TYPE_LENGTH + + SESSION_ID_LENGTH : message_length + - CHECKSUM_LENGTH + - TAG_LENGTH + ], + ) + ) + + +def _should_have_ctrl_byte_encrypted_transport(ctx: Channel) -> bool: + if ctx.get_channel_state() in [ + ChannelState.UNALLOCATED, + ChannelState.TH1, + ChannelState.TH2, + ]: + return False + return True + + +def _decode_message( + buffer: bytes, msg_type: int, message_name: str | None = None +) -> protobuf.MessageType: + if __debug__: + log.debug(__name__, "decode message") + if message_name is not None: + expected_type = protobuf.type_for_name(message_name) + else: + expected_type = protobuf.type_for_wire(msg_type) + return message_handler.wrap_protobuf_load(buffer, expected_type) + + +def _is_channel_state_pairing(state: int) -> bool: + if state in ( + ChannelState.TP0, + ChannelState.TP1, + ChannelState.TP2, + ChannelState.TP3, + ChannelState.TP4, + ChannelState.TC1, + ): + return True + return False diff --git a/core/src/trezor/wire/thp/session_context.py b/core/src/trezor/wire/thp/session_context.py new file mode 100644 index 0000000000..0601c1f7b7 --- /dev/null +++ b/core/src/trezor/wire/thp/session_context.py @@ -0,0 +1,169 @@ +from typing import TYPE_CHECKING + +from storage import cache_thp +from storage.cache_common import InvalidSessionError +from storage.cache_thp import SessionThpCache +from trezor import log, loop, protobuf, utils +from trezor.wire import message_handler, protocol_common +from trezor.wire.context import UnexpectedMessageException +from trezor.wire.message_handler import failure + +from ..protocol_common import Context, Message +from . import SessionState + +if TYPE_CHECKING: + from typing import Awaitable, Container + + from storage.cache_common import DataCache + + from .channel import Channel + + pass + +_EXIT_LOOP = True +_REPEAT_LOOP = False + +if __debug__: + from trezor.utils import get_bytes_as_str + + +class GenericSessionContext(Context): + + def __init__(self, channel: Channel, session_id: int) -> None: + super().__init__(channel.iface, channel.channel_id) + self.channel: Channel = channel + self.session_id: int = session_id + self.incoming_message = loop.mailbox() + + async def handle(self) -> None: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, + "handle - start (channel_id (bytes): %s, session_id: %d)", + get_bytes_as_str(self.channel_id), + self.session_id, + ) + + next_message: Message | None = None + + while True: + message = next_message + next_message = None + try: + if await self._handle_message(message): + loop.schedule(self.handle()) + return + except UnexpectedMessageException as unexpected: + # The workflow was interrupted by an unexpected message. We need to + # process it as if it was a new message... + next_message = unexpected.msg + continue + except Exception as exc: + # Log and try again. + if __debug__: + log.exception(__name__, exc) + + async def _handle_message( + self, + next_message: Message | None, + ) -> bool: + + try: + if next_message is not None: + # Process the message from previous run. + message = next_message + next_message = None + else: + # Wait for a new message from wire + message = await self.incoming_message + + except protocol_common.WireError as e: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.exception(__name__, e) + await self.write(failure(e)) + return _REPEAT_LOOP + + await message_handler.handle_single_message(self, message) + return _EXIT_LOOP + + async def read( + self, + expected_types: Container[int], + expected_type: type[protobuf.MessageType] | None = None, + ) -> protobuf.MessageType: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + 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: Message = await self.incoming_message + if message.type not in expected_types: + if __debug__: + log.debug( + __name__, + "EXPECTED TYPES: %s\nRECEIVED TYPE: %s", + str(expected_types), + str(message.type), + ) + raise UnexpectedMessageException(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, self.session_id) + + def write_force(self, msg: protobuf.MessageType) -> Awaitable[None]: + return self.channel.write(msg, self.session_id, force=True) + + def get_session_state(self) -> SessionState: ... + + +class SeedlessSessionContext(GenericSessionContext): + + def __init__(self, channel_ctx: Channel, session_id: int) -> None: + super().__init__(channel_ctx, session_id) + + def get_session_state(self) -> SessionState: + return SessionState.SEEDLESS + + @property + def cache(self) -> DataCache: + raise InvalidSessionError + + +class SessionContext(GenericSessionContext): + + def __init__(self, channel_ctx: Channel, session_cache: SessionThpCache) -> None: + if channel_ctx.channel_id != session_cache.channel_id: + raise Exception( + "The session has different channel id than the provided channel context!" + ) + session_id = int.from_bytes(session_cache.session_id, "big") + super().__init__(channel_ctx, session_id) + self.session_cache = session_cache + + # ACCESS TO SESSION DATA + + def get_session_state(self) -> SessionState: + state = int.from_bytes(self.session_cache.state, "big") + return SessionState(state) + + def set_session_state(self, state: SessionState) -> None: + self.session_cache.state = bytearray(state.to_bytes(1, "big")) + + def release(self) -> None: + if self.session_cache is not None: + cache_thp.clear_session(self.session_cache) + + # ACCESS TO CACHE + @property + def cache(self) -> DataCache: + return self.session_cache diff --git a/core/src/trezor/wire/thp/session_manager.py b/core/src/trezor/wire/thp/session_manager.py new file mode 100644 index 0000000000..a50a1a332a --- /dev/null +++ b/core/src/trezor/wire/thp/session_manager.py @@ -0,0 +1,51 @@ +from typing import TYPE_CHECKING + +from storage import cache_thp + +from .session_context import ( + GenericSessionContext, + SeedlessSessionContext, + SessionContext, +) + +if TYPE_CHECKING: + from .channel import Channel + + +def get_new_session_context( + channel_ctx: Channel, + session_id: int, +) -> SessionContext: + session_cache = cache_thp.create_or_replace_session( + channel=channel_ctx.channel_cache, + session_id=session_id.to_bytes(1, "big"), + ) + return SessionContext(channel_ctx, session_cache) + + +def get_new_seedless_session_ctx( + channel_ctx: Channel, session_id: int +) -> SeedlessSessionContext: + """ + Creates new `SeedlessSessionContext` that is not backed by a cache entry. + + Seed cannot be derived with this type of session. + """ + return SeedlessSessionContext(channel_ctx, session_id) + + +def get_session_from_cache( + channel_ctx: Channel, session_id: int +) -> GenericSessionContext | None: + """ + Returns a `SessionContext` (or `SeedlessSessionContext`) reconstructed from a cache or `None` if backing cache is not found. + """ + session_id_bytes = session_id.to_bytes(1, "big") + session_cache = cache_thp.get_allocated_session( + channel_ctx.channel_id, session_id_bytes + ) + if session_cache is None: + return None + elif cache_thp.is_seedless_session(session_cache): + return SeedlessSessionContext(channel_ctx, session_id) + return SessionContext(channel_ctx, session_cache) diff --git a/core/src/trezor/wire/thp/thp_main.py b/core/src/trezor/wire/thp/thp_main.py new file mode 100644 index 0000000000..5482cf396c --- /dev/null +++ b/core/src/trezor/wire/thp/thp_main.py @@ -0,0 +1,165 @@ +import ustruct +from micropython import const +from typing import TYPE_CHECKING + +from storage.cache_thp import BROADCAST_CHANNEL_ID +from trezor import io, log, loop, utils + +from . import ( + CHANNEL_ALLOCATION_REQ, + CODEC_V1, + ChannelState, + PacketHeader, + ThpError, + ThpErrorType, + channel_manager, + checksum, + control_byte, + get_channel_allocation_response, + writer, +) +from .channel import Channel +from .checksum import CHECKSUM_LENGTH +from .writer import ( + INIT_HEADER_LENGTH, + MAX_PAYLOAD_LEN, + write_payload_to_wire_and_add_checksum, +) + +if TYPE_CHECKING: + from trezorio import WireInterface + +_CID_REQ_PAYLOAD_LENGTH = const(12) +_CHANNELS: dict[int, Channel] = {} + + +async def thp_main_loop(iface: WireInterface) -> None: + global _CHANNELS + _CHANNELS = channel_manager.load_cached_channels() + + read = loop.wait(iface.iface_num() | io.POLL_READ) + packet = bytearray(iface.RX_PACKET_LEN) + while True: + try: + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug(__name__, "thp_main_loop") + packet_len = await read + assert packet_len == len(packet) + iface.read(packet, 0) + + if _get_ctrl_byte(packet) == CODEC_V1: + await _handle_codec_v1(iface, packet) + continue + + cid = ustruct.unpack(">BH", packet)[1] + + if cid == BROADCAST_CHANNEL_ID: + await _handle_broadcast(iface, packet) + continue + + if cid in _CHANNELS: + await _handle_allocated(iface, cid, packet) + else: + await _handle_unallocated(iface, cid, packet) + + except ThpError as e: + if __debug__: + log.exception(__name__, e) + + +async def _handle_codec_v1(iface: WireInterface, packet: bytes) -> None: + # If the received packet is not an initial codec_v1 packet, do not send error message + if not packet[1:3] == b"##": + return + if __debug__: + log.debug(__name__, "Received codec_v1 message, returning error") + error_message = _get_codec_v1_error_message() + await writer.write_packet_to_wire(iface, error_message) + + +async def _handle_broadcast(iface: WireInterface, packet: utils.BufferType) -> None: + if _get_ctrl_byte(packet) != CHANNEL_ALLOCATION_REQ: + raise ThpError("Unexpected ctrl_byte in a broadcast channel packet") + if __debug__: + log.debug(__name__, "Received valid message on the broadcast channel") + + length, nonce = ustruct.unpack(">H8s", packet[3:]) + payload = _get_buffer_for_payload(length, packet[5:], _CID_REQ_PAYLOAD_LENGTH) + if not checksum.is_valid( + payload[-4:], + packet[: _CID_REQ_PAYLOAD_LENGTH + INIT_HEADER_LENGTH - CHECKSUM_LENGTH], + ): + raise ThpError("Checksum is not valid") + + new_channel: Channel = channel_manager.create_new_channel(iface) + cid = int.from_bytes(new_channel.channel_id, "big") + _CHANNELS[cid] = new_channel + + response_data = get_channel_allocation_response( + nonce, new_channel.channel_id, iface + ) + response_header = PacketHeader.get_channel_allocation_response_header( + len(response_data) + CHECKSUM_LENGTH, + ) + if __debug__: + log.debug(__name__, "New channel allocated with id %d", cid) + + await write_payload_to_wire_and_add_checksum(iface, response_header, response_data) + + +async def _handle_allocated( + iface: WireInterface, cid: int, packet: utils.BufferType +) -> None: + channel = _CHANNELS[cid] + if channel is None: + await _handle_unallocated(iface, cid, packet) + raise ThpError("Invalid state of a channel") + if channel.iface is not iface: + # TODO send error message to wire + raise ThpError("Channel has different WireInterface") + + if channel.get_channel_state() != ChannelState.UNALLOCATED: + x = channel.receive_packet(packet) + if x is not None: + await x + + +async def _handle_unallocated(iface: WireInterface, cid: int, packet: bytes) -> None: + if control_byte.is_continuation(_get_ctrl_byte(packet)): + return + data = (ThpErrorType.UNALLOCATED_CHANNEL).to_bytes(1, "big") + header = PacketHeader.get_error_header(cid, len(data) + CHECKSUM_LENGTH) + await write_payload_to_wire_and_add_checksum(iface, header, data) + + +def _get_buffer_for_payload( + payload_length: int, + existing_buffer: utils.BufferType, + max_length: int = MAX_PAYLOAD_LEN, +) -> utils.BufferType: + if payload_length > max_length: + raise ThpError("Message too large") + if payload_length > len(existing_buffer): + try: + new_buffer = bytearray(payload_length) + except MemoryError: + raise ThpError("Message too large") + return new_buffer + return _reuse_existing_buffer(payload_length, existing_buffer) + + +def _reuse_existing_buffer( + payload_length: int, existing_buffer: utils.BufferType +) -> utils.BufferType: + return memoryview(existing_buffer)[:payload_length] + + +def _get_ctrl_byte(packet: bytes) -> int: + return packet[0] + + +def _get_codec_v1_error_message() -> bytes: + # Codec_v1 magic constant "?##" + Failure message type + msg_size + # + msg_data (code = "Failure_InvalidProtocol") + padding to 64 B + ERROR_MSG = b"\x3f\x23\x23\x00\x03\x00\x00\x00\x14\x08\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + return ERROR_MSG diff --git a/core/src/trezor/wire/thp/transmission_loop.py b/core/src/trezor/wire/thp/transmission_loop.py new file mode 100644 index 0000000000..cd3e3ba2f8 --- /dev/null +++ b/core/src/trezor/wire/thp/transmission_loop.py @@ -0,0 +1,54 @@ +from micropython import const +from typing import TYPE_CHECKING + +from trezor import loop + +from .writer import write_payload_to_wire_and_add_checksum + +if TYPE_CHECKING: + from . import PacketHeader + from .channel import Channel + +MAX_RETRANSMISSION_COUNT = const(50) +MIN_RETRANSMISSION_COUNT = const(2) + + +class TransmissionLoop: + + def __init__( + self, channel: Channel, header: PacketHeader, transport_payload: bytes + ) -> None: + self.channel: Channel = channel + self.header: PacketHeader = header + self.transport_payload: bytes = transport_payload + self.wait_task: loop.spawn | None = None + self.min_retransmisson_count_achieved: bool = False + + async def start( + self, max_retransmission_count: int = MAX_RETRANSMISSION_COUNT + ) -> None: + self.min_retransmisson_count_achieved = False + for i in range(max_retransmission_count): + if i >= MIN_RETRANSMISSION_COUNT: + self.min_retransmisson_count_achieved = True + await write_payload_to_wire_and_add_checksum( + self.channel.iface, self.header, self.transport_payload + ) + self.wait_task = loop.spawn(self._wait(i)) + try: + await self.wait_task + except loop.TaskClosed: + self.wait_task = None + break + + def stop_immediately(self) -> None: + if self.wait_task is not None: + self.wait_task.close() + self.wait_task = None + + async def _wait(self, counter: int = 0) -> None: + timeout_ms = round(10200 - 1010000 / (counter + 100)) + await loop.sleep(timeout_ms) + + def __del__(self) -> None: + self.stop_immediately() diff --git a/core/src/trezor/wire/thp/writer.py b/core/src/trezor/wire/thp/writer.py new file mode 100644 index 0000000000..03aedf3690 --- /dev/null +++ b/core/src/trezor/wire/thp/writer.py @@ -0,0 +1,91 @@ +from micropython import const +from trezorcrypto import crc +from typing import TYPE_CHECKING + +from trezor import io, log, loop, utils + +from . import PacketHeader + +INIT_HEADER_LENGTH = const(5) +CONT_HEADER_LENGTH = const(3) +CHECKSUM_LENGTH = const(4) +MAX_PAYLOAD_LEN = const(60000) +MESSAGE_TYPE_LENGTH = const(2) + +if TYPE_CHECKING: + from trezorio import WireInterface + from typing import Awaitable, Sequence + + +def write_payload_to_wire_and_add_checksum( + iface: WireInterface, header: PacketHeader, transport_payload: bytes +) -> Awaitable[None]: + header_checksum: int = crc.crc32(header.to_bytes()) + checksum: bytes = crc.crc32(transport_payload, header_checksum).to_bytes( + CHECKSUM_LENGTH, "big" + ) + data = (transport_payload, checksum) + return write_payloads_to_wire(iface, header, data) + + +async def write_payloads_to_wire( + iface: WireInterface, header: PacketHeader, data: Sequence[bytes] +) -> None: + n_of_data = len(data) + total_length = sum(len(item) for item in data) + + current_data_idx = 0 + current_data_offset = 0 + + packet = bytearray(iface.TX_PACKET_LEN) + header.pack_to_init_buffer(packet) + packet_offset: int = INIT_HEADER_LENGTH + packet_number = 0 + nwritten = 0 + while nwritten < total_length: + if packet_number == 1: + header.pack_to_cont_buffer(packet) + if packet_number >= 1 and nwritten >= total_length - iface.TX_PACKET_LEN: + packet[:] = bytearray(iface.TX_PACKET_LEN) + header.pack_to_cont_buffer(packet) + while True: + n = utils.memcpy( + packet, packet_offset, data[current_data_idx], current_data_offset + ) + packet_offset += n + current_data_offset += n + nwritten += n + + if packet_offset < iface.TX_PACKET_LEN: + current_data_idx += 1 + current_data_offset = 0 + if current_data_idx >= n_of_data: + break + elif packet_offset == iface.TX_PACKET_LEN: + break + else: + raise Exception("Should not happen!!!") + packet_number += 1 + packet_offset = CONT_HEADER_LENGTH + + # write packet to wire (in-lined) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, "write_packet_to_wire: %s", utils.get_bytes_as_str(packet) + ) + written_by_iface: int = 0 + while written_by_iface < len(packet): + await loop.wait(iface.iface_num() | io.POLL_WRITE) + written_by_iface = iface.write(packet) + + +async def write_packet_to_wire(iface: WireInterface, packet: bytes) -> None: + while True: + await loop.wait(iface.iface_num() | io.POLL_WRITE) + if __debug__ and utils.ALLOW_DEBUG_MESSAGES: + log.debug( + __name__, "write_packet_to_wire: %s", utils.get_bytes_as_str(packet) + ) + n_written = iface.write(packet) + if n_written == len(packet): + return diff --git a/core/src/trezor/workflow.py b/core/src/trezor/workflow.py index 67b88f8e68..a7a4791b1d 100644 --- a/core/src/trezor/workflow.py +++ b/core/src/trezor/workflow.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING import storage.cache as storage_cache from trezor import log, loop -from trezor.enums import MessageType +from trezor.enums import MessageType, ThpMessageType if TYPE_CHECKING: from typing import Callable @@ -17,18 +17,30 @@ if __debug__: from trezor import utils - -ALLOW_WHILE_LOCKED = ( - MessageType.Initialize, - MessageType.EndSession, - MessageType.GetFeatures, - MessageType.Cancel, - MessageType.LockDevice, - MessageType.DoPreauthorized, - MessageType.WipeDevice, - MessageType.SetBusy, - MessageType.Ping, -) +if utils.USE_THP: + ALLOW_WHILE_LOCKED = ( + ThpMessageType.ThpCreateNewSession, + MessageType.EndSession, + MessageType.GetFeatures, + MessageType.Cancel, + MessageType.LockDevice, + MessageType.DoPreauthorized, + MessageType.WipeDevice, + MessageType.SetBusy, + MessageType.Ping, + ) +else: + ALLOW_WHILE_LOCKED = ( + MessageType.Initialize, + MessageType.EndSession, + MessageType.GetFeatures, + MessageType.Cancel, + MessageType.LockDevice, + MessageType.DoPreauthorized, + MessageType.WipeDevice, + MessageType.SetBusy, + MessageType.Ping, + ) # Set of workflow tasks. Multiple workflows can be running at the same time. diff --git a/core/tests/mock_wire_interface.py b/core/tests/mock_wire_interface.py new file mode 100644 index 0000000000..13cd033375 --- /dev/null +++ b/core/tests/mock_wire_interface.py @@ -0,0 +1,50 @@ +from trezor.loop import wait + + +class MockHID: + + TX_PACKET_LEN = 64 + RX_PACKET_LEN = 64 + + def __init__(self, num): + self.num = num + self.data = [] + self.packet = None + + def pad_packet(self, data): + if len(data) > self.RX_PACKET_LEN: + raise Exception("Too long packet") + padding_length = self.RX_PACKET_LEN - len(data) + return data + b"\x00" * padding_length + + def iface_num(self): + return self.num + + def write(self, msg): + self.data.append(bytearray(msg)) + return len(msg) + + def mock_read(self, packet, gen): + self.packet = self.pad_packet(packet) + return gen.send(self.RX_PACKET_LEN) + + def read(self, buffer, offset=0): + if self.packet is None: + raise Exception("No packet to read") + + if offset > len(buffer): + raise Exception("Offset out of bounds") + + buffer_space = len(buffer) - offset + + if len(self.packet) > buffer_space: + raise Exception("Buffer too small") + else: + end = offset + len(self.packet) + buffer[offset:end] = self.packet + read = len(self.packet) + self.packet = None + return read + + def wait_object(self, mode): + return wait(mode | self.num) diff --git a/core/tests/myTests.sh b/core/tests/myTests.sh new file mode 100755 index 0000000000..1c29c1fd01 --- /dev/null +++ b/core/tests/myTests.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +declare -a results +declare -i passed=0 failed=0 exit_code=0 +declare COLOR_GREEN='\e[32m' COLOR_RED='\e[91m' COLOR_RESET='\e[39m' +MICROPYTHON="${MICROPYTHON:-../build/unix/trezor-emu-core -X heapsize=2M}" +print_summary() { + echo + echo 'Summary:' + echo '-------------------' + printf '%b\n' "${results[@]}" + if [ $exit_code == 0 ]; then + echo -e "${COLOR_GREEN}PASSED:${COLOR_RESET} $passed/$num_of_tests tests OK!" + else + echo -e "${COLOR_RED}FAILED:${COLOR_RESET} $failed/$num_of_tests tests failed!" + fi +} + +trap 'print_summary; echo -e "${COLOR_RED}Interrupted by user!${COLOR_RESET}"; exit 1' SIGINT + +cd $(dirname $0) + +[ -z "$*" ] && tests=(test_trezor.wire.t*.py ) || tests=($*) + +declare -i num_of_tests=${#tests[@]} + +for test_case in ${tests[@]}; do + echo ${MICROPYTHON} + echo ${test_case} + echo + if $MICROPYTHON $test_case; then + results+=("${COLOR_GREEN}OK:${COLOR_RESET} $test_case") + ((passed++)) + else + results+=("${COLOR_RED}FAIL:${COLOR_RESET} $test_case") + ((failed++)) + exit_code=1 + fi +done + +print_summary +exit $exit_code diff --git a/core/tests/test_apps.bitcoin.approver.py b/core/tests/test_apps.bitcoin.approver.py index 8086fd8e2d..408f96d7ee 100644 --- a/core/tests/test_apps.bitcoin.approver.py +++ b/core/tests/test_apps.bitcoin.approver.py @@ -1,4 +1,5 @@ -from common import H_, await_result, unittest # isort:skip +# flake8: noqa: F403,F405 +from common import * # isort:skip import storage.cache_codec from trezor import wire @@ -20,11 +21,25 @@ from apps.bitcoin.sign_tx.bitcoin import Bitcoin from apps.bitcoin.sign_tx.tx_info import TxInfo from apps.common import coins +if utils.USE_THP: + import thp_common +else: + import storage.cache_codec + from trezor.wire.codec.codec_context import CodecContext + class TestApprover(unittest.TestCase): + if utils.USE_THP: - def setUpClass(self): - context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + def setUpClass(self): + if __debug__: + thp_common.suppres_debug_log() + thp_common.prepare_context() + + else: + + def setUpClass(self): + context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) def tearDownClass(self): context.CURRENT_CONTEXT = None @@ -54,7 +69,8 @@ class TestApprover(unittest.TestCase): coin_name=self.coin.coin_name, script_type=InputScriptType.SPENDTAPROOT, ) - storage.cache_codec.start_session() + if not utils.USE_THP: + storage.cache_codec.start_session() def make_coinjoin_request(self, inputs): return CoinJoinRequest( diff --git a/core/tests/test_apps.bitcoin.authorization.py b/core/tests/test_apps.bitcoin.authorization.py index 03d32651c7..aedcadba92 100644 --- a/core/tests/test_apps.bitcoin.authorization.py +++ b/core/tests/test_apps.bitcoin.authorization.py @@ -1,23 +1,38 @@ -from common import H_, unittest # isort:skip +# flake8: noqa: F403,F405 +from common import * # isort:skip import storage.cache_codec from trezor.enums import InputScriptType from trezor.messages import AuthorizeCoinJoin, GetOwnershipProof, SignTx from trezor.wire import context -from trezor.wire.codec.codec_context import CodecContext from apps.bitcoin.authorization import CoinJoinAuthorization from apps.common import coins _ROUND_ID_LEN = 32 +if utils.USE_THP: + import thp_common +else: + import storage.cache_codec + from trezor.wire.codec.codec_context import CodecContext + class TestAuthorization(unittest.TestCase): coin = coins.by_name("Bitcoin") - def setUpClass(self): - context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + if utils.USE_THP: + + def setUpClass(self): + if __debug__: + thp_common.suppres_debug_log() + thp_common.prepare_context() + + else: + + def setUpClass(self): + context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) def tearDownClass(self): context.CURRENT_CONTEXT = None @@ -34,7 +49,8 @@ class TestAuthorization(unittest.TestCase): ) self.authorization = CoinJoinAuthorization(self.msg_auth) - storage.cache_codec.start_session() + if not utils.USE_THP: + storage.cache_codec.start_session() def test_ownership_proof_account_depth_mismatch(self): # Account depth mismatch. diff --git a/core/tests/test_apps.bitcoin.keychain.py b/core/tests/test_apps.bitcoin.keychain.py index 232d2bf01d..25239dad8c 100644 --- a/core/tests/test_apps.bitcoin.keychain.py +++ b/core/tests/test_apps.bitcoin.keychain.py @@ -1,7 +1,7 @@ # flake8: noqa: F403,F405 from common import * # isort:skip -from storage import cache_codec, cache_common +from storage import cache_common from trezor import wire from trezor.crypto import bip39 from trezor.wire import context @@ -9,20 +9,38 @@ from trezor.wire.codec.codec_context import CodecContext from apps.bitcoin.keychain import _get_coin_by_name, _get_keychain_for_coin +if utils.USE_THP: + import thp_common +else: + from storage import cache_codec + class TestBitcoinKeychain(unittest.TestCase): - def setUpClass(self): - context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + if utils.USE_THP: + + def setUpClass(self): + if __debug__: + thp_common.suppres_debug_log() + thp_common.prepare_context() + + def setUp(self): + seed = bip39.seed(" ".join(["all"] * 12), "") + context.cache_set(cache_common.APP_COMMON_SEED, seed) + + else: + + def setUpClass(self): + context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + + def setUp(self): + cache_codec.start_session() + seed = bip39.seed(" ".join(["all"] * 12), "") + cache_codec.get_active_session().set(cache_common.APP_COMMON_SEED, seed) def tearDownClass(self): context.CURRENT_CONTEXT = None - def setUp(self): - cache_codec.start_session() - seed = bip39.seed(" ".join(["all"] * 12), "") - cache_codec.get_active_session().set(cache_common.APP_COMMON_SEED, seed) - def test_bitcoin(self): coin = _get_coin_by_name("Bitcoin") keychain = await_result(_get_keychain_for_coin(coin)) @@ -98,18 +116,30 @@ class TestBitcoinKeychain(unittest.TestCase): @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") class TestAltcoinKeychains(unittest.TestCase): + if utils.USE_THP: - def setUpClass(self): - context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + def setUpClass(self): + if __debug__: + thp_common.suppres_debug_log() + thp_common.prepare_context() + + def setUp(self): + seed = bip39.seed(" ".join(["all"] * 12), "") + context.cache_set(cache_common.APP_COMMON_SEED, seed) + + else: + + def setUpClass(self): + context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + + def setUp(self): + cache_codec.start_session() + seed = bip39.seed(" ".join(["all"] * 12), "") + cache_codec.get_active_session().set(cache_common.APP_COMMON_SEED, seed) def tearDownClass(self): context.CURRENT_CONTEXT = None - def setUp(self): - cache_codec.start_session() - seed = bip39.seed(" ".join(["all"] * 12), "") - cache_codec.get_active_session().set(cache_common.APP_COMMON_SEED, seed) - def test_bcash(self): coin = _get_coin_by_name("Bcash") keychain = await_result(_get_keychain_for_coin(coin)) diff --git a/core/tests/test_apps.common.keychain.py b/core/tests/test_apps.common.keychain.py index f54f64d74f..8d0839f374 100644 --- a/core/tests/test_apps.common.keychain.py +++ b/core/tests/test_apps.common.keychain.py @@ -2,7 +2,7 @@ from common import * # isort:skip from mock_storage import mock_storage -from storage import cache, cache_codec, cache_common +from storage import cache, cache_common from trezor import wire from trezor.crypto import bip39 from trezor.enums import SafetyCheckLevel @@ -13,18 +13,32 @@ from apps.common import safety_checks from apps.common.keychain import Keychain, LRUCache, get_keychain, with_slip44_keychain from apps.common.paths import PATTERN_SEP5, PathSchema +if utils.USE_THP: + import thp_common +if not utils.USE_THP: + from storage import cache_codec + class TestKeychain(unittest.TestCase): - def setUpClass(self): - context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + if utils.USE_THP: + + def setUpClass(self): + if __debug__: + thp_common.suppres_debug_log() + thp_common.prepare_context() + + else: + + def setUpClass(self): + context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + + def setUp(self): + cache_codec.start_session() def tearDownClass(self): context.CURRENT_CONTEXT = None - def setUp(self): - cache_codec.start_session() - def tearDown(self): cache.clear_all() diff --git a/core/tests/test_apps.ethereum.keychain.py b/core/tests/test_apps.ethereum.keychain.py index 3215aba267..6355da641c 100644 --- a/core/tests/test_apps.ethereum.keychain.py +++ b/core/tests/test_apps.ethereum.keychain.py @@ -3,7 +3,7 @@ from common import * # isort:skip import unittest -from storage import cache_codec, cache_common +from storage import cache_common from trezor import wire from trezor.crypto import bip39 from trezor.wire import context @@ -12,6 +12,12 @@ from trezor.wire.codec.codec_context import CodecContext from apps.common.keychain import get_keychain from apps.common.paths import HARDENED +if utils.USE_THP: + import thp_common +else: + from storage import cache_codec + + if not utils.BITCOIN_ONLY: from ethereum_common import encode_network, make_network from trezor.messages import ( @@ -74,17 +80,30 @@ class TestEthereumKeychain(unittest.TestCase): addr, ) - def setUpClass(self): - context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + if utils.USE_THP: + + def setUpClass(self): + if __debug__: + thp_common.suppres_debug_log() + thp_common.prepare_context() + + def setUp(self): + seed = bip39.seed(" ".join(["all"] * 12), "") + context.cache_set(cache_common.APP_COMMON_SEED, seed) + + else: + + def setUpClass(self): + context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + + def setUp(self): + cache_codec.start_session() + seed = bip39.seed(" ".join(["all"] * 12), "") + cache_codec.get_active_session().set(cache_common.APP_COMMON_SEED, seed) def tearDownClass(self): context.CURRENT_CONTEXT = None - def setUp(self): - cache_codec.start_session() - seed = bip39.seed(" ".join(["all"] * 12), "") - cache_codec.get_active_session().set(cache_common.APP_COMMON_SEED, seed) - def from_address_n(self, address_n): slip44 = _slip44_from_address_n(address_n) network = make_network(slip44=slip44) diff --git a/core/tests/test_apps.thp.credential_manager.py b/core/tests/test_apps.thp.credential_manager.py index 267707d374..e25cc6a002 100644 --- a/core/tests/test_apps.thp.credential_manager.py +++ b/core/tests/test_apps.thp.credential_manager.py @@ -48,16 +48,28 @@ class TestTrezorHostProtocolCredentialManager(unittest.TestCase): cred_3 = _issue_credential(HOST_NAME_2, DUMMY_KEY_1) self.assertNotEqual(cred_1, cred_3) - self.assertTrue(credential_manager.validate_credential(cred_1, DUMMY_KEY_1)) - self.assertTrue(credential_manager.validate_credential(cred_3, DUMMY_KEY_1)) - self.assertFalse(credential_manager.validate_credential(cred_1, DUMMY_KEY_2)) + self.assertTrue( + credential_manager.decode_and_validate_credential(cred_1, DUMMY_KEY_1) + ) + self.assertTrue( + credential_manager.decode_and_validate_credential(cred_3, DUMMY_KEY_1) + ) + self.assertFalse( + credential_manager.decode_and_validate_credential(cred_1, DUMMY_KEY_2) + ) credential_manager.invalidate_cred_auth_key() cred_4 = _issue_credential(HOST_NAME_1, DUMMY_KEY_1) self.assertNotEqual(cred_1, cred_4) - self.assertFalse(credential_manager.validate_credential(cred_1, DUMMY_KEY_1)) - self.assertFalse(credential_manager.validate_credential(cred_3, DUMMY_KEY_1)) - self.assertTrue(credential_manager.validate_credential(cred_4, DUMMY_KEY_1)) + self.assertFalse( + credential_manager.decode_and_validate_credential(cred_1, DUMMY_KEY_1) + ) + self.assertFalse( + credential_manager.decode_and_validate_credential(cred_3, DUMMY_KEY_1) + ) + self.assertTrue( + credential_manager.decode_and_validate_credential(cred_4, DUMMY_KEY_1) + ) def test_protobuf_encoding(self): """ diff --git a/core/tests/test_storage.cache.py b/core/tests/test_storage.cache.py index cc93015e05..07a3904c29 100644 --- a/core/tests/test_storage.cache.py +++ b/core/tests/test_storage.cache.py @@ -1,241 +1,560 @@ # flake8: noqa: F403,F405 from common import * # isort:skip -from mock_storage import mock_storage -from storage import cache, cache_codec, cache_common -from trezor.messages import EndSession, Initialize -from trezor.wire import context -from trezor.wire.codec.codec_context import CodecContext - -from apps.base import handle_EndSession, handle_Initialize -from apps.common.cache import stored, stored_async KEY = 0 +if utils.USE_THP: + import thp_common + from mock_wire_interface import MockHID + from storage import cache, cache_thp + from trezor.wire.thp import ChannelState + from trezor.wire.thp.session_context import SessionContext -# Function moved from cache.py, as it was not used there -def is_session_started() -> bool: - return cache_codec._active_session_idx is not None + _PROTOCOL_CACHE = cache_thp + +else: + from mock_storage import mock_storage + from storage import cache, cache_codec + from trezor.messages import EndSession, Initialize + + from apps.base import handle_EndSession + + _PROTOCOL_CACHE = cache_codec + + def is_session_started() -> bool: + return cache_codec.get_active_session() is not None + + def get_active_session(): + return cache_codec.get_active_session() class TestStorageCache(unittest.TestCase): - def setUpClass(self): - context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + if utils.USE_THP: - def tearDownClass(self): - context.CURRENT_CONTEXT = None + def setUpClass(self): + if __debug__: + thp_common.suppres_debug_log() + super().__init__() - def setUp(self): - cache.clear_all() + def setUp(self): + self.interface = MockHID(0xDEADBEEF) + cache.clear_all() - def test_start_session(self): - session_id_a = cache_codec.start_session() - self.assertIsNotNone(session_id_a) - session_id_b = cache_codec.start_session() - self.assertNotEqual(session_id_a, session_id_b) + def test_new_channel_and_session(self): + channel = thp_common.get_new_channel(self.interface) - cache.clear_all() - with self.assertRaises(cache_common.InvalidSessionError): - context.cache_set(KEY, "something") - with self.assertRaises(cache_common.InvalidSessionError): - context.cache_get(KEY) + # Assert that channel is created without any sessions + self.assertEqual(len(channel.sessions), 0) - def test_end_session(self): - session_id = cache_codec.start_session() - self.assertTrue(is_session_started()) - context.cache_set(KEY, b"A") - cache_codec.end_current_session() - self.assertFalse(is_session_started()) - self.assertRaises(cache_common.InvalidSessionError, context.cache_get, KEY) + cid_1 = channel.channel_id + session_cache_1 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x01" + ) + session_1 = SessionContext(channel, session_cache_1) + self.assertEqual(session_1.channel_id, cid_1) - # ending an ended session should be a no-op - cache_codec.end_current_session() - self.assertFalse(is_session_started()) + session_cache_2 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x02" + ) + session_2 = SessionContext(channel, session_cache_2) + self.assertEqual(session_2.channel_id, cid_1) + self.assertEqual(session_1.channel_id, session_2.channel_id) + self.assertNotEqual(session_1.session_id, session_2.session_id) - session_id_a = cache_codec.start_session(session_id) - # original session no longer exists - self.assertNotEqual(session_id_a, session_id) - # original session data no longer exists - self.assertIsNone(context.cache_get(KEY)) + channel_2 = thp_common.get_new_channel(self.interface) + cid_2 = channel_2.channel_id + self.assertNotEqual(cid_1, cid_2) - # create a new session - session_id_b = cache_codec.start_session() - # switch back to original session - session_id = cache_codec.start_session(session_id_a) - self.assertEqual(session_id, session_id_a) - # end original session - cache_codec.end_current_session() - # switch back to B - session_id = cache_codec.start_session(session_id_b) - self.assertEqual(session_id, session_id_b) + session_cache_3 = cache_thp.create_or_replace_session( + channel_2.channel_cache, b"\x01" + ) + session_3 = SessionContext(channel_2, session_cache_3) + self.assertEqual(session_3.channel_id, cid_2) - def test_session_queue(self): - session_id = cache_codec.start_session() - self.assertEqual(cache_codec.start_session(session_id), session_id) - context.cache_set(KEY, b"A") - for _ in range(cache_codec._MAX_SESSIONS_COUNT): + # Sessions 1 and 3 should have different channel_id, but the same session_id + self.assertNotEqual(session_1.channel_id, session_3.channel_id) + self.assertEqual(session_1.session_id, session_3.session_id) + + self.assertEqual(cache_thp._SESSIONS[0], session_cache_1) + self.assertNotEqual(cache_thp._SESSIONS[0], session_cache_2) + self.assertEqual(cache_thp._SESSIONS[0].channel_id, session_1.channel_id) + + # Check that session data IS in cache for created sessions ONLY + for i in range(3): + self.assertNotEqual(cache_thp._SESSIONS[i].channel_id, b"") + self.assertNotEqual(cache_thp._SESSIONS[i].session_id, b"") + self.assertNotEqual(cache_thp._SESSIONS[i].last_usage, 0) + for i in range(3, cache_thp._MAX_SESSIONS_COUNT): + self.assertEqual(cache_thp._SESSIONS[i].channel_id, b"") + self.assertEqual(cache_thp._SESSIONS[i].session_id, b"") + self.assertEqual(cache_thp._SESSIONS[i].last_usage, 0) + + # Check that session data IS NOT in cache after cache.clear_all() + cache.clear_all() + for session in cache_thp._SESSIONS: + self.assertEqual(session.channel_id, b"") + self.assertEqual(session.session_id, b"") + self.assertEqual(session.last_usage, 0) + self.assertEqual(session.state, b"\x00") + + def test_channel_capacity_in_cache(self): + self.assertTrue(cache_thp._MAX_CHANNELS_COUNT >= 3) + channels = [] + for i in range(cache_thp._MAX_CHANNELS_COUNT): + channels.append(thp_common.get_new_channel(self.interface)) + channel_ids = [channel.channel_cache.channel_id for channel in channels] + + # Assert that each channel_id is unique and that cache and list of channels + # have the same "channels" on the same indexes + for i in range(len(channel_ids)): + self.assertEqual(cache_thp._CHANNELS[i].channel_id, channel_ids[i]) + for j in range(i + 1, len(channel_ids)): + self.assertNotEqual(channel_ids[i], channel_ids[j]) + + # Create a new channel that is over the capacity + new_channel = thp_common.get_new_channel(self.interface) + for c in channels: + self.assertNotEqual(c.channel_id, new_channel.channel_id) + + # Test that the oldest (least used) channel was replaced (_CHANNELS[0]) + self.assertNotEqual(cache_thp._CHANNELS[0].channel_id, channel_ids[0]) + self.assertEqual(cache_thp._CHANNELS[0].channel_id, new_channel.channel_id) + + # Update the "last used" value of the second channel in cache (_CHANNELS[1]) and + # assert that it is not replaced when creating a new channel + cache_thp.update_channel_last_used(channel_ids[1]) + new_new_channel = thp_common.get_new_channel(self.interface) + self.assertEqual(cache_thp._CHANNELS[1].channel_id, channel_ids[1]) + + # Assert that it was in fact the _CHANNEL[2] that was replaced + self.assertNotEqual(cache_thp._CHANNELS[2].channel_id, channel_ids[2]) + self.assertEqual( + cache_thp._CHANNELS[2].channel_id, new_new_channel.channel_id + ) + + def test_session_capacity_in_cache(self): + self.assertTrue(cache_thp._MAX_SESSIONS_COUNT >= 4) + channel_cache_A = thp_common.get_new_channel(self.interface).channel_cache + channel_cache_B = thp_common.get_new_channel(self.interface).channel_cache + + sesions_A = [] + cid = [] + sid = [] + for i in range(3): + sesions_A.append( + cache_thp.create_or_replace_session( + channel_cache_A, (i + 1).to_bytes(1, "big") + ) + ) + cid.append(sesions_A[i].channel_id) + sid.append(sesions_A[i].session_id) + + sessions_B = [] + for i in range(cache_thp._MAX_SESSIONS_COUNT - 3): + sessions_B.append( + cache_thp.create_or_replace_session( + channel_cache_B, (i + 10).to_bytes(1, "big") + ) + ) + + for i in range(3): + self.assertEqual(sesions_A[i], cache_thp._SESSIONS[i]) + self.assertEqual(cid[i], cache_thp._SESSIONS[i].channel_id) + self.assertEqual(sid[i], cache_thp._SESSIONS[i].session_id) + for i in range(3, cache_thp._MAX_SESSIONS_COUNT): + self.assertEqual(sessions_B[i - 3], cache_thp._SESSIONS[i]) + + # Assert that new session replaces the oldest (least used) one (_SESSOIONS[0]) + new_session = cache_thp.create_or_replace_session(channel_cache_B, b"\xab") + self.assertEqual(new_session, cache_thp._SESSIONS[0]) + self.assertNotEqual(new_session.channel_id, cid[0]) + self.assertNotEqual(new_session.session_id, sid[0]) + + # Assert that updating "last used" for session on channel A increases also + # the "last usage" of channel A. + self.assertTrue(channel_cache_A.last_usage < channel_cache_B.last_usage) + cache_thp.update_session_last_used( + channel_cache_A.channel_id, sesions_A[1].session_id + ) + self.assertTrue(channel_cache_A.last_usage > channel_cache_B.last_usage) + + new_new_session = cache_thp.create_or_replace_session( + channel_cache_B, b"\xaa" + ) + + # Assert that creating a new session on channel B shifts the "last usage" again + # and that _SESSIONS[1] was not replaced, but that _SESSIONS[2] was replaced + self.assertTrue(channel_cache_A.last_usage < channel_cache_B.last_usage) + self.assertEqual(sesions_A[1], cache_thp._SESSIONS[1]) + self.assertNotEqual(sesions_A[2], cache_thp._SESSIONS[2]) + self.assertEqual(new_new_session, cache_thp._SESSIONS[2]) + + def test_clear(self): + channel_A = thp_common.get_new_channel(self.interface) + channel_B = thp_common.get_new_channel(self.interface) + cid_A = channel_A.channel_id + cid_B = channel_B.channel_id + sessions = [] + + for i in range(3): + sessions.append( + cache_thp.create_or_replace_session( + channel_A.channel_cache, (i + 1).to_bytes(1, "big") + ) + ) + sessions.append( + cache_thp.create_or_replace_session( + channel_B.channel_cache, (i + 10).to_bytes(1, "big") + ) + ) + + self.assertEqual(cache_thp._SESSIONS[2 * i].channel_id, cid_A) + self.assertNotEqual(cache_thp._SESSIONS[2 * i].last_usage, 0) + + self.assertEqual(cache_thp._SESSIONS[2 * i + 1].channel_id, cid_B) + self.assertNotEqual(cache_thp._SESSIONS[2 * i + 1].last_usage, 0) + + # Assert that clearing of channel A works + self.assertNotEqual(channel_A.channel_cache.channel_id, b"") + self.assertNotEqual(channel_A.channel_cache.last_usage, 0) + self.assertEqual(channel_A.get_channel_state(), ChannelState.TH1) + + channel_A.clear() + + self.assertEqual(channel_A.channel_cache.channel_id, b"") + self.assertEqual(channel_A.channel_cache.last_usage, 0) + self.assertEqual(channel_A.get_channel_state(), ChannelState.UNALLOCATED) + + # Assert that clearing channel A also cleared all its sessions + for i in range(3): + self.assertEqual(cache_thp._SESSIONS[2 * i].last_usage, 0) + self.assertEqual(cache_thp._SESSIONS[2 * i].channel_id, b"") + + self.assertNotEqual(cache_thp._SESSIONS[2 * i + 1].last_usage, 0) + self.assertEqual(cache_thp._SESSIONS[2 * i + 1].channel_id, cid_B) + + cache.clear_all() + for session in cache_thp._SESSIONS: + self.assertEqual(session.last_usage, 0) + self.assertEqual(session.channel_id, b"") + for channel in cache_thp._CHANNELS: + self.assertEqual(channel.channel_id, b"") + self.assertEqual(channel.last_usage, 0) + self.assertEqual( + cache_thp._get_channel_state(channel), ChannelState.UNALLOCATED + ) + + def test_get_set(self): + channel = thp_common.get_new_channel(self.interface) + + session_1 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x01" + ) + session_1.set(KEY, b"hello") + self.assertEqual(session_1.get(KEY), b"hello") + + session_2 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x02" + ) + session_2.set(KEY, b"world") + self.assertEqual(session_2.get(KEY), b"world") + + self.assertEqual(session_1.get(KEY), b"hello") + + cache.clear_all() + self.assertIsNone(session_1.get(KEY)) + self.assertIsNone(session_2.get(KEY)) + + def test_get_set_int(self): + channel = thp_common.get_new_channel(self.interface) + + session_1 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x01" + ) + session_1.set_int(KEY, 1234) + + self.assertEqual(session_1.get_int(KEY), 1234) + + session_2 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x02" + ) + session_2.set_int(KEY, 5678) + self.assertEqual(session_2.get_int(KEY), 5678) + + self.assertEqual(session_1.get_int(KEY), 1234) + + cache.clear_all() + self.assertIsNone(session_1.get_int(KEY)) + self.assertIsNone(session_2.get_int(KEY)) + + def test_get_set_bool(self): + channel = thp_common.get_new_channel(self.interface) + + session_1 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x01" + ) + with self.assertRaises(AssertionError): + session_1.set_bool(KEY, True) + + # Change length of first session field to 0 so that the length check passes + session_1.fields = (0,) + session_1.fields[1:] + + # with self.assertRaises(AssertionError) as e: + session_1.set_bool(KEY, True) + self.assertEqual(session_1.get_bool(KEY), True) + + session_2 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x02" + ) + session_2.fields = session_2.fields = (0,) + session_2.fields[1:] + session_2.set_bool(KEY, False) + self.assertEqual(session_2.get_bool(KEY), False) + + self.assertEqual(session_1.get_bool(KEY), True) + + cache.clear_all() + + # Default value is False + self.assertFalse(session_1.get_bool(KEY)) + self.assertFalse(session_2.get_bool(KEY)) + + def test_delete(self): + channel = thp_common.get_new_channel(self.interface) + session_1 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x01" + ) + + self.assertIsNone(session_1.get(KEY)) + session_1.set(KEY, b"hello") + self.assertEqual(session_1.get(KEY), b"hello") + session_1.delete(KEY) + self.assertIsNone(session_1.get(KEY)) + + session_1.set(KEY, b"hello") + session_2 = cache_thp.create_or_replace_session( + channel.channel_cache, b"\x02" + ) + + self.assertIsNone(session_2.get(KEY)) + session_2.set(KEY, b"hello") + self.assertEqual(session_2.get(KEY), b"hello") + session_2.delete(KEY) + self.assertIsNone(session_2.get(KEY)) + + self.assertEqual(session_1.get(KEY), b"hello") + + else: + + def setUpClass(self): + from trezor.wire import context + from trezor.wire.codec.codec_context import CodecContext + + context.CURRENT_CONTEXT = CodecContext(None, bytearray(64)) + + def tearDownClass(self): + from trezor.wire import context + + context.CURRENT_CONTEXT = None + + def setUp(self): + cache.clear_all() + + def test_start_session(self): + session_id_a = cache_codec.start_session() + self.assertIsNotNone(session_id_a) + session_id_b = cache_codec.start_session() + self.assertNotEqual(session_id_a, session_id_b) + + cache.clear_all() + self.assertIsNone(get_active_session()) + for session in cache_codec._SESSIONS: + self.assertEqual(session.session_id, b"") + self.assertEqual(session.last_usage, 0) + + def test_end_session(self): + session_id = cache_codec.start_session() + self.assertTrue(is_session_started()) + get_active_session().set(KEY, b"A") + cache_codec.end_current_session() + self.assertFalse(is_session_started()) + self.assertIsNone(get_active_session()) + + # ending an ended session should be a no-op + cache_codec.end_current_session() + self.assertFalse(is_session_started()) + + session_id_a = cache_codec.start_session(session_id) + # original session no longer exists + self.assertNotEqual(session_id_a, session_id) + # original session data no longer exists + self.assertIsNone(get_active_session().get(KEY)) + + # create a new session + session_id_b = cache_codec.start_session() + # switch back to original session + session_id = cache_codec.start_session(session_id_a) + self.assertEqual(session_id, session_id_a) + # end original session + cache_codec.end_current_session() + # switch back to B + session_id = cache_codec.start_session(session_id_b) + self.assertEqual(session_id, session_id_b) + + def test_session_queue(self): + session_id = cache_codec.start_session() + self.assertEqual(cache_codec.start_session(session_id), session_id) + get_active_session().set(KEY, b"A") + for _ in range(_PROTOCOL_CACHE._MAX_SESSIONS_COUNT): + cache_codec.start_session() + self.assertNotEqual(cache_codec.start_session(session_id), session_id) + self.assertIsNone(get_active_session().get(KEY)) + + def test_get_set(self): + session_id1 = cache_codec.start_session() + cache_codec.get_active_session().set(KEY, b"hello") + self.assertEqual(cache_codec.get_active_session().get(KEY), b"hello") + + session_id2 = cache_codec.start_session() + cache_codec.get_active_session().set(KEY, b"world") + self.assertEqual(cache_codec.get_active_session().get(KEY), b"world") + + cache_codec.start_session(session_id2) + self.assertEqual(cache_codec.get_active_session().get(KEY), b"world") + cache_codec.start_session(session_id1) + self.assertEqual(cache_codec.get_active_session().get(KEY), b"hello") + + cache.clear_all() + self.assertIsNone(cache_codec.get_active_session()) + + def test_get_set_int(self): + session_id1 = cache_codec.start_session() + get_active_session().set_int(KEY, 1234) + self.assertEqual(get_active_session().get_int(KEY), 1234) + + session_id2 = cache_codec.start_session() + get_active_session().set_int(KEY, 5678) + self.assertEqual(get_active_session().get_int(KEY), 5678) + + cache_codec.start_session(session_id2) + self.assertEqual(get_active_session().get_int(KEY), 5678) + cache_codec.start_session(session_id1) + self.assertEqual(get_active_session().get_int(KEY), 1234) + + cache.clear_all() + self.assertIsNone(get_active_session()) + + def test_delete(self): + session_id1 = cache_codec.start_session() + self.assertIsNone(get_active_session().get(KEY)) + get_active_session().set(KEY, b"hello") + self.assertEqual(get_active_session().get(KEY), b"hello") + get_active_session().delete(KEY) + self.assertIsNone(get_active_session().get(KEY)) + + get_active_session().set(KEY, b"hello") cache_codec.start_session() - self.assertNotEqual(cache_codec.start_session(session_id), session_id) - self.assertIsNone(context.cache_get(KEY)) + self.assertIsNone(get_active_session().get(KEY)) + get_active_session().set(KEY, b"hello") + self.assertEqual(get_active_session().get(KEY), b"hello") + get_active_session().delete(KEY) + self.assertIsNone(get_active_session().get(KEY)) - def test_get_set(self): - session_id1 = cache_codec.start_session() - context.cache_set(KEY, b"hello") - self.assertEqual(context.cache_get(KEY), b"hello") + cache_codec.start_session(session_id1) + self.assertEqual(get_active_session().get(KEY), b"hello") - session_id2 = cache_codec.start_session() - context.cache_set(KEY, b"world") - self.assertEqual(context.cache_get(KEY), b"world") + def test_decorators(self): + run_count = 0 + cache_codec.start_session() + from apps.common.cache import stored - cache_codec.start_session(session_id2) - self.assertEqual(context.cache_get(KEY), b"world") - cache_codec.start_session(session_id1) - self.assertEqual(context.cache_get(KEY), b"hello") + @stored(KEY) + def func(): + nonlocal run_count + run_count += 1 + return b"foo" - cache.clear_all() - with self.assertRaises(cache_common.InvalidSessionError): - context.cache_get(KEY) + # cache is empty + self.assertIsNone(get_active_session().get(KEY)) + self.assertEqual(run_count, 0) + self.assertEqual(func(), b"foo") + # function was run + self.assertEqual(run_count, 1) + self.assertEqual(get_active_session().get(KEY), b"foo") + # function does not run again but returns cached value + self.assertEqual(func(), b"foo") + self.assertEqual(run_count, 1) - def test_get_set_int(self): - session_id1 = cache_codec.start_session() - context.cache_set_int(KEY, 1234) - self.assertEqual(context.cache_get_int(KEY), 1234) + def test_empty_value(self): + cache_codec.start_session() - session_id2 = cache_codec.start_session() - context.cache_set_int(KEY, 5678) - self.assertEqual(context.cache_get_int(KEY), 5678) + self.assertIsNone(get_active_session().get(KEY)) + get_active_session().set(KEY, b"") + self.assertEqual(get_active_session().get(KEY), b"") - cache_codec.start_session(session_id2) - self.assertEqual(context.cache_get_int(KEY), 5678) - cache_codec.start_session(session_id1) - self.assertEqual(context.cache_get_int(KEY), 1234) + get_active_session().delete(KEY) + run_count = 0 - cache.clear_all() - with self.assertRaises(cache_common.InvalidSessionError): - context.cache_get_int(KEY) + from apps.common.cache import stored - def test_delete(self): - session_id1 = cache_codec.start_session() - self.assertIsNone(context.cache_get(KEY)) - context.cache_set(KEY, b"hello") - self.assertEqual(context.cache_get(KEY), b"hello") - context.cache_delete(KEY) - self.assertIsNone(context.cache_get(KEY)) + @stored(KEY) + def func(): + nonlocal run_count + run_count += 1 + return b"" - context.cache_set(KEY, b"hello") - cache_codec.start_session() - self.assertIsNone(context.cache_get(KEY)) - context.cache_set(KEY, b"hello") - self.assertEqual(context.cache_get(KEY), b"hello") - context.cache_delete(KEY) - self.assertIsNone(context.cache_get(KEY)) + self.assertEqual(func(), b"") + # function gets called once + self.assertEqual(run_count, 1) + self.assertEqual(func(), b"") + # function is not called for a second time + self.assertEqual(run_count, 1) - cache_codec.start_session(session_id1) - self.assertEqual(context.cache_get(KEY), b"hello") + @mock_storage + def test_Initialize(self): + from apps.base import handle_Initialize - def test_decorators(self): - run_count = 0 - cache_codec.start_session() + def call_Initialize(**kwargs): + msg = Initialize(**kwargs) + return await_result(handle_Initialize(msg)) - @stored(KEY) - def func(): - nonlocal run_count - run_count += 1 - return b"foo" + # calling Initialize without an ID allocates a new one + session_id = cache_codec.start_session() + features = call_Initialize() + self.assertNotEqual(session_id, features.session_id) - # cache is empty - self.assertIsNone(context.cache_get(KEY)) - self.assertEqual(run_count, 0) - self.assertEqual(func(), b"foo") - # function was run - self.assertEqual(run_count, 1) - self.assertEqual(context.cache_get(KEY), b"foo") - # function does not run again but returns cached value - self.assertEqual(func(), b"foo") - self.assertEqual(run_count, 1) + # calling Initialize with the current ID does not allocate a new one + features = call_Initialize(session_id=session_id) + self.assertEqual(session_id, features.session_id) - @stored_async(KEY) - async def async_func(): - nonlocal run_count - run_count += 1 - return b"bar" + # store "hello" + get_active_session().set(KEY, b"hello") + # check that it is cleared + features = call_Initialize() + session_id = features.session_id + self.assertIsNone(get_active_session().get(KEY)) + # store "hello" again + get_active_session().set(KEY, b"hello") + self.assertEqual(get_active_session().get(KEY), b"hello") - # cache is still full - self.assertEqual(await_result(async_func()), b"foo") - self.assertEqual(run_count, 1) + # supplying a different session ID starts a new session + call_Initialize(session_id=b"A" * _PROTOCOL_CACHE.SESSION_ID_LENGTH) + self.assertIsNone(get_active_session().get(KEY)) - cache_codec.start_session() - self.assertEqual(await_result(async_func()), b"bar") - self.assertEqual(run_count, 2) - # awaitable is also run only once - self.assertEqual(await_result(async_func()), b"bar") - self.assertEqual(run_count, 2) + # but resuming a session loads the previous one + call_Initialize(session_id=session_id) + self.assertEqual(get_active_session().get(KEY), b"hello") - def test_empty_value(self): - cache_codec.start_session() + def test_EndSession(self): - self.assertIsNone(context.cache_get(KEY)) - context.cache_set(KEY, b"") - self.assertEqual(context.cache_get(KEY), b"") - - context.cache_delete(KEY) - run_count = 0 - - @stored(KEY) - def func(): - nonlocal run_count - run_count += 1 - return b"" - - self.assertEqual(func(), b"") - # function gets called once - self.assertEqual(run_count, 1) - self.assertEqual(func(), b"") - # function is not called for a second time - self.assertEqual(run_count, 1) - - @mock_storage - def test_Initialize(self): - def call_Initialize(**kwargs): - msg = Initialize(**kwargs) - return await_result(handle_Initialize(msg)) - - # calling Initialize without an ID allocates a new one - session_id = cache_codec.start_session() - features = call_Initialize() - self.assertNotEqual(session_id, features.session_id) - - # calling Initialize with the current ID does not allocate a new one - features = call_Initialize(session_id=session_id) - self.assertEqual(session_id, features.session_id) - - # store "hello" - context.cache_set(KEY, b"hello") - # check that it is cleared - features = call_Initialize() - session_id = features.session_id - self.assertIsNone(context.cache_get(KEY)) - # store "hello" again - context.cache_set(KEY, b"hello") - self.assertEqual(context.cache_get(KEY), b"hello") - - # supplying a different session ID starts a new cache - call_Initialize(session_id=b"A" * cache_codec.SESSION_ID_LENGTH) - self.assertIsNone(context.cache_get(KEY)) - - # but resuming a session loads the previous one - call_Initialize(session_id=session_id) - self.assertEqual(context.cache_get(KEY), b"hello") - - def test_EndSession(self): - self.assertRaises(cache_common.InvalidSessionError, context.cache_get, KEY) - cache_codec.start_session() - self.assertTrue(is_session_started()) - self.assertIsNone(context.cache_get(KEY)) - await_result(handle_EndSession(EndSession())) - self.assertFalse(is_session_started()) - self.assertRaises(cache_common.InvalidSessionError, context.cache_get, KEY) + self.assertIsNone(get_active_session()) + cache_codec.start_session() + self.assertTrue(is_session_started()) + self.assertIsNone(get_active_session().get(KEY)) + await_result(handle_EndSession(EndSession())) + self.assertFalse(is_session_started()) + self.assertIsNone(cache_codec.get_active_session()) if __name__ == "__main__": diff --git a/core/tests/test_trezor.wire.codec.codec_v1.py b/core/tests/test_trezor.wire.codec.codec_v1.py index 852f5f5b8b..9e6d478590 100644 --- a/core/tests/test_trezor.wire.codec.codec_v1.py +++ b/core/tests/test_trezor.wire.codec.codec_v1.py @@ -3,61 +3,11 @@ from common import * # isort:skip import ustruct +from mock_wire_interface import MockHID from trezor import io -from trezor.loop import wait from trezor.utils import chunks from trezor.wire.codec import codec_v1 - -class MockHID: - - TX_PACKET_LEN = 64 - RX_PACKET_LEN = 64 - - def __init__(self, num): - self.num = num - self.data = [] - self.packet = None - - def pad_packet(self, data): - if len(data) > self.RX_PACKET_LEN: - raise Exception("Too long packet") - padding_length = self.RX_PACKET_LEN - len(data) - return data + b"\x00" * padding_length - - def iface_num(self): - return self.num - - def write(self, msg): - self.data.append(bytearray(msg)) - return len(msg) - - def mock_read(self, packet, gen): - self.packet = self.pad_packet(packet) - return gen.send(self.RX_PACKET_LEN) - - def read(self, buffer, offset=0): - if self.packet is None: - raise Exception("No packet to read") - - if offset > len(buffer): - raise Exception("Offset out of bounds") - - buffer_space = len(buffer) - offset - - if len(self.packet) > buffer_space: - raise Exception("Buffer too small") - else: - end = offset + len(self.packet) - buffer[offset:end] = self.packet - read = len(self.packet) - self.packet = None - return read - - def wait_object(self, mode): - return wait(mode | self.num) - - MESSAGE_TYPE = 0x4242 HEADER_PAYLOAD_LENGTH = MockHID.RX_PACKET_LEN - 3 - ustruct.calcsize(">HL") diff --git a/core/tests/test_trezor.wire.thp.checksum.py b/core/tests/test_trezor.wire.thp.checksum.py new file mode 100644 index 0000000000..f5d59d6805 --- /dev/null +++ b/core/tests/test_trezor.wire.thp.checksum.py @@ -0,0 +1,95 @@ +# flake8: noqa: F403,F405 +from common import * # isort:skip + +if utils.USE_THP: + from trezor.wire.thp import checksum + + +@unittest.skipUnless(utils.USE_THP, "only needed for THP") +class TestTrezorHostProtocolChecksum(unittest.TestCase): + vectors_correct = [ + ( + b"", + b"\x00\x00\x00\x00", + ), + ( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x19\x0A\x55\xAD", + ), + ( + bytes("a", "ascii"), + b"\xE8\xB7\xBE\x43", + ), + ( + bytes("abc", "ascii"), + b"\x35\x24\x41\xC2", + ), + ( + bytes("123456789", "ascii"), + b"\xCB\xF4\x39\x26", + ), + ( + bytes( + "12345678901234567890123456789012345678901234567890123456789012345678901234567890", + "ascii", + ), + b"\x7C\xA9\x4A\x72", + ), + ( + b"\x76\x61\x72\x69\x6F\x75\x73\x20\x43\x52\x43\x20\x61\x6C\x67\x6F\x72\x69\x74\x68\x6D\x73\x20\x69\x6E\x70\x75\x74\x20\x64\x61\x74\x61", + b"\x9B\xD3\x66\xAE", + ), + ( + b"\x67\x3a\x5f\x0e\x39\xc0\x3c\x79\x58\x22\x74\x76\x64\x9e\x36\xe9\x0b\x04\x8c\xd2\xc0\x4d\x76\x63\x1a\xa2\x17\x85\xe8\x50\xa7\x14\x18\xfb\x86\xed\xa3\x59\x2d\x62\x62\x49\x64\x62\x26\x12\xdb\x95\x3d\xd6\xb5\xca\x4b\x22\x0d\xc5\x78\xb2\x12\x97\x8e\x54\x4e\x06\xb7\x9c\x90\xf5\xa0\x21\xa6\xc7\xd8\x39\xfd\xea\x3a\xf1\x7b\xa2\xe8\x71\x41\xd6\xcb\x1e\x5b\x0e\x29\xf7\x0c\xc7\x57\x8b\x53\x20\x1d\x2b\x41\x1c\x25\xf9\x07\xbb\xb4\x37\x79\x6a\x13\x1f\x6c\x43\x71\xc1\x1e\x70\xe6\x74\xd3\x9c\xbf\x32\x15\xee\xf2\xa7\x86\xbe\x59\x99\xc4\x10\x09\x8a\x6a\xaa\xd4\xd1\xd0\x71\xd2\x06\x1a\xdd\x2a\xa0\x08\xeb\x08\x6c\xfb\xd2\x2d\xfb\xaa\x72\x56\xeb\xd1\x92\x92\xe5\x0e\x95\x67\xf8\x38\xc3\xab\x59\x37\xe6\xfd\x42\xb0\xd0\x31\xd0\xcb\x8a\x66\xce\x2d\x53\x72\x1e\x72\xd3\x84\x25\xb0\xb8\x93\xd2\x61\x5b\x32\xd5\xe7\xe4\x0e\x31\x11\xaf\xdc\xb4\xb8\xee\xa4\x55\x16\x5f\x78\x86\x8b\x50\x4d\xc5\x6d\x6e\xfc\xe1\x6b\x06\x5b\x37\x84\x2a\x67\x95\x28\x00\xa4\xd1\x32\x9f\xbf\xe1\x64\xf8\x17\x47\xe1\xad\x8b\x72\xd2\xd9\x45\x5b\x73\x43\x3c\xe6\x21\xf7\x53\xa3\x73\xf9\x2a\xb0\xe9\x75\x5e\xa6\xbe\x9a\xad\xfc\xed\xb5\x46\x5b\x9f\xa9\x5a\x4f\xcb\xb6\x60\x96\x31\x91\x42\xca\xaf\xee\xa5\x0c\xe0\xab\x3e\x83\xb8\xac\x88\x10\x2c\x63\xd3\xc9\xd2\xf2\x44\xef\xea\x3d\x19\x24\x3c\x5b\xe7\x0c\x52\xfd\xfe\x47\x41\x14\xd5\x4c\x67\x8d\xdb\xe5\xd9\xfa\x67\x9c\x06\x31\x01\x92\xba\x96\xc4\x0d\xef\xf7\xc1\xe9\x23\x28\x0f\xae\x27\x9b\xff\x28\x0b\x3e\x85\x0c\xae\x02\xda\x27\xb6\x04\x51\x04\x43\x04\x99\x8c\xa3\x97\x1d\x84\xec\x55\x59\xfb\xf3\x84\xe5\xf8\x40\xf8\x5f\x81\x65\x92\x4c\x92\x7a\x07\x51\x8d\x6f\xff\x8d\x15\x36\x5c\x57\x7a\x5b\x3a\x63\x1c\x87\x65\xee\x54\xd5\x96\x50\x73\x1a\x9c\xff\x59\xe5\xea\x6f\x89\xd2\xbb\xa9\x6a\x12\x21\xf5\x08\x8e\x8a\xc0\xd8\xf5\x14\xe9\x9d\x7e\x99\x13\x88\x29\xa8\xb4\x22\x2a\x41\x7c\xc5\x10\xdf\x11\x5e\xf8\x8d\x0e\xd9\x98\xd5\xaf\xa8\xf9\x55\x1e\xe3\x29\xcd\x2c\x51\x7b\x8a\x8d\x52\xaa\x8b\x87\xae\x8e\xb2\xfa\x31\x27\x60\x90\xcb\x01\x6f\x7a\x79\x38\x04\x05\x7c\x11\x79\x10\x40\x33\x70\x75\xfd\x0b\x88\xa5\xcd\x35\xd8\xa6\x3b\xb0\x45\x82\x64\xd1\xb5\xdc\x06\xc9\x89\xf4\x16\x3e\xc7\xb3\xf1\x9d\xd3\xc5\xe3\xaf\xe8\x25\x86\x7a\x4a\xfd\x10\x5d\x20\xe5\x76\x5a\x22\x5f\x8f\xbc\xaa\x97\xee\xf2\xc2\x4c\x0e\xdc\x7b\xc4\xee\x53\xa3\xe0\xfa\xcd\x1e\x4e\x54\x1d\x5e\xe1\x51\x17\x1f\x1a\x75\x7f\xed\x12\xd7\xf7\xe3\x18\x56\x24\xcf\xc6\x96\x30\x77\x0d\x73\x98\x9c\x09\x69\xa3\xbc\x96\x5e\xaf\xde\x76\xa4\x66\x04\x6b\x36\x2a\xac\x6d\x37\xf8\x1e\xe1\x2a\x3e\x42\x2d\x1d\xe6\x46\xdd\x28\xb9\x08\x44\xa1\x9e\xb2\x22\x7a\x45\x8a\x37\x39\x74\xb4\xae\xc8\x3b\x40\xf7\xec\xbf\xfd\xe5\xde\xb2\x83\x5e\xa4\x46\x19\xa6\x9d\xb0\xe8\x76\x80\xbd\xc1\x80\x7a\xd9\xeb\xe7\x90\x5b\x81\x25\x21\xd9\x5b\x4a\x80\x48\x92\x71\x77\x04\xb2\xac\x05\xc9\xdf\x5e\x44\x5a\xae\x6e\xb3\xd8\x30\x5e\xdc\x77\x2f\x79\xc2\x8e\x8b\x28\x24\x06\x1b\x6f\x8d\x88\x53\x80\x55\x0c\x3a\x7b\x85\xb8\x96\x85\xe9\xf0\x57\x63\xfe\x32\x80\xff\x57\xc9\x3c\xdb\xf6\xcd\x67\x14\x47\x6c\x43\x3d\x6d\x48\x3f\x9c\x00\x60\x0e\xf5\x94\xe4\x52\x97\x86\xcd\xac\xbc\xe4\xe3\xe7\xee\xa2\x91\x6e\x92\xbb\xd1\x55\x0c\x5c\x0d\x63\xdb\x6b\xb8\x6e\x45\x48\x0f\xdf\x44\x48\xd2\xf5\xf7\x4d\x7b\xd4\x4d\xd3\xcd\xcd\x5b\x40\x60\xb1\xb2\x8e\xc9\x9a\x65\xc5\x06\x24\xcf\xe9\xcc\x5e\x2c\x49\x47\x38\x45\x5d\xc5\xc0\x0d\x8a\x07\x1c\xb3\xbb\xb1\x69\xf5\x6d\x0e\x9c\x96\x14\x93\x58\x0c\xc9\x48\x74\xfc\x35\xda\x7d\x4e\x32\x73\xa3\x77\x4a\x9e\xc5\xd1\x08\xfe\xa6\xa0\xf1\x66\x72\xea\xc7\xae\x21\x81\x0e\x8a\xba\x99\x06\x97\xfc\xc6\x2b\x69\x53\xc6\x67\xec\x5d\xa1\xfc\xa1\x3b\xdd\x2a\xd6\x8f\x31\xa7\x8d\xec\xfe\x0a\x3b\x6b\x39\x70\x70\x09\x72\x12\xbc\x84\x67\xca\xd2\x4a\x17\x33\x94\x45\x25\xc7\xfd\x1e\xa2\x4a\x9e\x27\x9d\xfb\x87\xea\xe4\xfd\xb0\x11\x06\x9d\x72\xb9\x1d\xea\x9b\x81\x2e\x6a\x36\x76\x62\xfa\xbe\x96\x67\x7d\x35\xdd\x5e\x5c\x4f\x41\x0d\xce\xdb\x13\xb0\x46\x89\x92\x45\x02\x39\x0f\xe6\xd1\x20\x96\x1c\x34\x00\x8c\xc9\xdf\xe3\xf0\xb6\x92\x3a\xda\x5c\x96\xd9\x0b\x7d\x57\xf5\x78\x11\xc0\xcf\xbf\xb0\x92\x3d\xe5\x6a\x67\x34\xce\xd9\x16\x08\xa0\x09\x42\x0b\x07\x13\x7c\x73\x0c\xc6\x50\x17\x42\xcf\xd9\x85\xd9\x23\x3c\xb1\x40\x40\x0f\x94\x20\xed\x2d\xbf\x10\x44\x6e\x64\x65\xe5\x1d\x5f\xec\x24\xd8\x4b\xe8\xc2\xfb\x06\x11\x24\x3f\xdf\x54\x2d\xe8\x4d\xc2\x1c\x27\x11\xb8\xb3\xd4", + b"\x6B\xA4\xEC\x92", + ), + ] + vectors_incorrect = [ + ( + b"", + b"\x00\x00\x00\x00\x00", + ), + ( + b"", + b"", + ), + ( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b"\x19\x0A\x55\xAE", + ), + ( + bytes("A", "ascii"), + b"\xE8\xB7\xBE\x43", + ), + ( + bytes("abc ", "ascii"), + b"\x35\x24\x41\xC2", + ), + ( + bytes("1234567890", "ascii"), + b"\xCB\xF4\x39\x26", + ), + ( + bytes( + "1234567890123456789012345678901234567890123456789012345678901234567890123456789", + "ascii", + ), + b"\x7C\xA9\x4A\x72", + ), + ] + + def test_computation(self): + for data, chksum in self.vectors_correct: + self.assertEqual(checksum.compute(data), chksum) + + def test_validation_correct(self): + for data, chksum in self.vectors_correct: + self.assertTrue(checksum.is_valid(chksum, data)) + + def test_validation_incorrect(self): + for data, chksum in self.vectors_incorrect: + self.assertFalse(checksum.is_valid(chksum, data)) + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_trezor.wire.thp.crypto.py b/core/tests/test_trezor.wire.thp.crypto.py new file mode 100644 index 0000000000..1eee57f8cb --- /dev/null +++ b/core/tests/test_trezor.wire.thp.crypto.py @@ -0,0 +1,157 @@ +# flake8: noqa: F403,F405 +from common import * # isort:skip +from trezorcrypto import aesgcm, curve25519 + +import storage + +if utils.USE_THP: + import thp_common + from trezor.wire.thp import crypto + from trezor.wire.thp.crypto import IV_1, IV_2, Handshake + + def get_dummy_device_secret(): + return b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08" + + +@unittest.skipUnless(utils.USE_THP, "only needed for THP") +class TestTrezorHostProtocolCrypto(unittest.TestCase): + if utils.USE_THP: + handshake = Handshake() + key_1 = b"\x00\x01\x02\x03\x04\x05\x06\x07\x00\x01\x02\x03\x04\x05\x06\x07\x00\x01\x02\x03\x04\x05\x06\x07\x00\x01\x02\x03\x04\x05\x06\x07" + # 0:key, 1:nonce, 2:auth_data, 3:plaintext, 4:expected_ciphertext, 5:expected_tag + vectors_enc = [ + ( + key_1, + 0, + b"\x55\x64", + b"\x00\x01\x02\x03\x04\05\x06\x07\x08\x09", + b"e2c9dd152fbee5821ea7", + b"10625812de81b14a46b9f1e5100a6d0c", + ), + ( + key_1, + 1, + b"\x55\x64", + b"\x00\x01\x02\x03\x04\05\x06\x07\x08\x09", + b"79811619ddb07c2b99f8", + b"71c6b872cdc499a7e9a3c7441f053214", + ), + ( + key_1, + 369, + b"\x55\x64", + b"\x00\x01\x02\x03\x04\05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + b"03bd030390f2dfe815a61c2b157a064f", + b"c1200f8a7ae9a6d32cef0fff878d55c2", + ), + ( + key_1, + 369, + b"\x55\x64\x73\x82\x91", + b"\x00\x01\x02\x03\x04\05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + b"03bd030390f2dfe815a61c2b157a064f", + b"693ac160cd93a20f7fc255f049d808d0", + ), + ] + # 0:chaining key, 1:input, 2:output_1, 3:output:2 + vectors_hkdf = [ + ( + crypto.PROTOCOL_NAME, + b"\x01\x02", + b"c784373a217d6be057cddc6068e6748f255fc8beb6f99b7b90cbc64aad947514", + b"12695451e29bf08ffe5e4e6ab734b0c3d7cdd99b16cd409f57bd4eaa874944ba", + ), + ( + b"\xc7\x84\x37\x3a\x21\x7d\x6b\xe0\x57\xcd\xdc\x60\x68\xe6\x74\x8f\x25\x5f\xc8\xbe\xb6\xf9\x9b\x7b\x90\xcb\xc6\x4a\xad\x94\x75\x14", + b"\x31\x41\x59\x26\x52\x12\x34\x56\x78\x89\x04\xaa", + b"f88c1e08d5c3bae8f6e4a3d3324c8cbc60a805603e399e69c4bf4eacb27c2f48", + b"5f0216bdb7110ee05372286974da8c9c8b96e2efa15b4af430755f462bd79a76", + ), + ] + vectors_iv = [ + (0, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), + (1, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), + (7, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07"), + (1025, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x01"), + (4294967295, b"\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff"), + (0xFFFFFFFFFFFFFFFF, b"\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff"), + ] + + def __init__(self): + if __debug__: + thp_common.suppres_debug_log() + super().__init__() + + def setUp(self): + utils.DISABLE_ENCRYPTION = False + + def test_encryption(self): + for v in self.vectors_enc: + buffer = bytearray(v[3]) + tag = crypto.enc(buffer, v[0], v[1], v[2]) + self.assertEqual(hexlify(buffer), v[4]) + self.assertEqual(hexlify(tag), v[5]) + self.assertTrue(crypto.dec(buffer, tag, v[0], v[1], v[2])) + self.assertEqual(buffer, v[3]) + + def test_hkdf(self): + for v in self.vectors_hkdf: + ck, k = crypto._hkdf(v[0], v[1]) + self.assertEqual(hexlify(ck), v[2]) + self.assertEqual(hexlify(k), v[3]) + + def test_iv_from_nonce(self): + for v in self.vectors_iv: + # x = v[0] + # y = x.to_bytes(8, "big") + iv = crypto._get_iv_from_nonce(v[0]) + self.assertEqual(iv, v[1]) + with self.assertRaises(AssertionError) as e: + iv = crypto._get_iv_from_nonce(0xFFFFFFFFFFFFFFFF + 1) + self.assertEqual(e.value.value, "Nonce overflow, terminate the channel") + + def test_incorrect_vectors(self): + pass + + def test_th1_crypto(self): + storage.device.get_device_secret = get_dummy_device_secret + handshake = self.handshake + + host_ephemeral_privkey = curve25519.generate_secret() + host_ephemeral_pubkey = curve25519.publickey(host_ephemeral_privkey) + handshake.handle_th1_crypto(b"", host_ephemeral_pubkey) + + def test_th2_crypto(self): + handshake = self.handshake + + host_static_privkey = curve25519.generate_secret() + host_static_pubkey = curve25519.publickey(host_static_privkey) + aes_ctx = aesgcm(handshake.k, IV_2) + aes_ctx.auth(handshake.h) + encrypted_host_static_pubkey = bytearray( + aes_ctx.encrypt(host_static_pubkey) + aes_ctx.finish() + ) + + # Code to encrypt Host's noise encrypted payload correctly: + protomsg = bytearray(b"\x10\x02\x10\x03") + temp_k = handshake.k + temp_h = handshake.h + + temp_h = crypto._hash_of_two(temp_h, encrypted_host_static_pubkey) + _, temp_k = crypto._hkdf( + handshake.ck, + curve25519.multiply(handshake.trezor_ephemeral_privkey, host_static_pubkey), + ) + aes_ctx = aesgcm(temp_k, IV_1) + aes_ctx.encrypt_in_place(protomsg) + aes_ctx.auth(temp_h) + tag = aes_ctx.finish() + encrypted_payload = bytearray(protomsg + tag) + # end of encrypted payload generation + + handshake.handle_th2_crypto(encrypted_host_static_pubkey, encrypted_payload) + self.assertEqual(encrypted_payload[:4], b"\x10\x02\x10\x03") + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_trezor.wire.thp.py b/core/tests/test_trezor.wire.thp.py new file mode 100644 index 0000000000..7ba208a421 --- /dev/null +++ b/core/tests/test_trezor.wire.thp.py @@ -0,0 +1,381 @@ +# flake8: noqa: F403,F405 +from common import * # isort:skip +from mock_wire_interface import MockHID +from trezor import config, io, protobuf +from trezor.crypto.curve import curve25519 +from trezor.enums import ThpMessageType +from trezor.wire.errors import UnexpectedMessage +from trezor.wire.protocol_common import Message + +if utils.USE_THP: + from typing import TYPE_CHECKING + + import thp_common + from storage import cache_thp + from storage.cache_common import ( + CHANNEL_HANDSHAKE_HASH, + CHANNEL_KEY_RECEIVE, + CHANNEL_KEY_SEND, + CHANNEL_NONCE_RECEIVE, + CHANNEL_NONCE_SEND, + ) + from trezor.crypto import elligator2 + from trezor.enums import ThpPairingMethod + from trezor.messages import ( + ThpCodeEntryChallenge, + ThpCodeEntryCpaceHostTag, + ThpCredentialRequest, + ThpEndRequest, + ThpPairingRequest, + ) + from trezor.wire.thp import ( + ChannelState, + checksum, + interface_manager, + memory_manager, + thp_main, + ) + from trezor.wire.thp.crypto import Handshake + from trezor.wire.thp.pairing_context import PairingContext + + from apps.thp import pairing + + if TYPE_CHECKING: + from trezor.wire import WireInterface + + def get_dummy_key() -> bytes: + return b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x01\x02\x03\x04\x05\x06\x07\x08\x09\x20\x01\x02\x03\x04\x05\x06\x07\x08\x09\x30\x31" + + def dummy_encode_iface(iface: WireInterface): + return thp_common._MOCK_INTERFACE_HID + + def send_channel_allocation_request( + interface: MockHID, nonce: bytes | None = None + ) -> bytes: + if nonce is None or len(nonce) != 8: + nonce = b"\x00\x11\x22\x33\x44\x55\x66\x77" + header = b"\x40\xff\xff\x00\x0c" + chksum = checksum.compute(header + nonce) + cid_req = header + nonce + chksum + gen = thp_main.thp_main_loop(interface) + expected_channel_index = cache_thp._get_next_channel_index() + gen.send(None) + interface.mock_read(cid_req, gen) + gen.send(None) + model = bytes(utils.INTERNAL_MODEL, "big") + response_data = ( + b"\x0a\x04" + model + "\x10\x00\x18\x00\x20\x02\x28\x02\x28\x03\x28\x04" + ) + response_without_crc = ( + b"\x41\xff\xff\x00\x20" + + nonce + + cache_thp._CHANNELS[expected_channel_index].channel_id + + response_data + ) + chkcsum = checksum.compute(response_without_crc) + expected_response = response_without_crc + chkcsum + b"\x00" * 27 + return expected_response + + def get_channel_id_from_response(channel_allocation_response: bytes) -> int: + return int.from_bytes(channel_allocation_response[13:15], "big") + + def get_ack(channel_id: bytes) -> bytes: + if len(channel_id) != 2: + raise Exception("Channel id should by two bytes long") + return ( + b"\x20" + + channel_id + + b"\x00\x04" + + checksum.compute(b"\x20" + channel_id + b"\x00\x04") + + b"\x00" * 55 + ) + + +@unittest.skipUnless(utils.USE_THP, "only needed for THP") +class TestTrezorHostProtocol(unittest.TestCase): + + def __init__(self): + if __debug__: + thp_common.suppres_debug_log() + interface_manager.encode_iface = dummy_encode_iface + super().__init__() + + def setUp(self): + self.interface = MockHID(0xDEADBEEF) + memory_manager.READ_BUFFER = bytearray(64) + memory_manager.WRITE_BUFFER = bytearray(256) + interface_manager.decode_iface = thp_common.dummy_decode_iface + + def test_codec_message(self): + self.assertEqual(len(self.interface.data), 0) + gen = thp_main.thp_main_loop(self.interface) + gen.send(None) + + # There should be a failiure response to received init packet (starts with "?##") + test_codec_message = b"?## Some data" + self.interface.mock_read(test_codec_message, gen) + gen.send(None) + self.assertEqual(len(self.interface.data), 1) + + expected_response = b"?##\x00\x03\x00\x00\x00\x14\x08\x10" + self.assertEqual( + self.interface.data[-1][: len(expected_response)], expected_response + ) + + # There should be no response for continuation packet (starts with "?" only) + test_codec_message_2 = b"? Cont packet" + self.interface.mock_read(test_codec_message_2, gen) + + # Check that sending None fails on AssertionError + with self.assertRaises(AssertionError): + gen.send(None) + self.assertEqual(len(self.interface.data), 1) + + def test_message_on_unallocated_channel(self): + gen = thp_main.thp_main_loop(self.interface) + query = gen.send(None) + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ)) + message_to_channel_789a = ( + b"\x04\x78\x9a\x00\x0c\x00\x11\x22\x33\x44\x55\x66\x77\x96\x64\x3c\x6c" + ) + self.interface.mock_read(message_to_channel_789a, gen) + gen.send(None) + unallocated_chanel_error_on_channel_789a = "42789a0005027b743563000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + self.assertEqual( + utils.get_bytes_as_str(self.interface.data[-1]), + unallocated_chanel_error_on_channel_789a, + ) + + def tbd_channel_allocation(self): + self.assertEqual(len(thp_main._CHANNELS), 0) + for c in cache_thp._CHANNELS: + self.assertEqual(int.from_bytes(c.state, "big"), ChannelState.UNALLOCATED) + + expected_channel_index = cache_thp._get_next_channel_index() + expected_response = send_channel_allocation_request(self.interface) + self.assertEqual(self.interface.data[-1], expected_response) + + cid = cache_thp._CHANNELS[expected_channel_index].channel_id + self.assertTrue(int.from_bytes(cid, "big") in thp_main._CHANNELS) + self.assertEqual(len(thp_main._CHANNELS), 1) + # test channel's default state is TH1: + cid = get_channel_id_from_response(self.interface.data[-1]) + self.assertEqual(thp_main._CHANNELS[cid].get_channel_state(), ChannelState.TH1) + + def tbd_invalid_encrypted_tag(self): + gen = thp_main.thp_main_loop(self.interface) + gen.send(None) + # prepare 2 new channels + expected_response_1 = send_channel_allocation_request(self.interface) + expected_response_2 = send_channel_allocation_request(self.interface) + self.assertEqual(self.interface.data[-2], expected_response_1) + self.assertEqual(self.interface.data[-1], expected_response_2) + + # test invalid encryption tag + config.init() + config.wipe() + cid_1 = get_channel_id_from_response(expected_response_1) + channel = thp_main._CHANNELS[cid_1] + channel.iface = self.interface + channel.set_channel_state(ChannelState.ENCRYPTED_TRANSPORT) + header = b"\x04" + channel.channel_id + b"\x00\x14" + + tag = b"\x00" * 16 + chksum = checksum.compute(header + tag) + message_with_invalid_tag = header + tag + chksum + + channel.channel_cache.set(CHANNEL_KEY_RECEIVE, get_dummy_key()) + channel.channel_cache.set_int(CHANNEL_NONCE_RECEIVE, 0) + + cid_1_bytes = int.to_bytes(cid_1, 2, "big") + expected_ack_on_received_message = get_ack(cid_1_bytes) + + self.interface.mock_read(message_with_invalid_tag, gen) + gen.send(None) + + self.assertEqual( + self.interface.data[-1], + expected_ack_on_received_message, + ) + error_without_crc = b"\x42" + cid_1_bytes + b"\x00\x05\x03" + chksum_err = checksum.compute(error_without_crc) + gen.send(None) + + decryption_failed_error = error_without_crc + chksum_err + b"\x00" * 54 + + self.assertEqual( + self.interface.data[-1], + decryption_failed_error, + ) + + def tbd_test_channel_errors(self): + gen = thp_main.thp_main_loop(self.interface) + gen.send(None) + # prepare 2 new channels + expected_response_1 = send_channel_allocation_request(self.interface) + expected_response_2 = send_channel_allocation_request(self.interface) + self.assertEqual(self.interface.data[-2], expected_response_1) + self.assertEqual(self.interface.data[-1], expected_response_2) + + # test invalid encryption tag + config.init() + config.wipe() + cid_1 = get_channel_id_from_response(expected_response_1) + channel = thp_main._CHANNELS[cid_1] + channel.iface = self.interface + channel.set_channel_state(ChannelState.ENCRYPTED_TRANSPORT) + header = b"\x04" + channel.channel_id + b"\x00\x14" + + tag = b"\x00" * 16 + chksum = checksum.compute(header + tag) + message_with_invalid_tag = header + tag + chksum + + channel.channel_cache.set(CHANNEL_KEY_RECEIVE, get_dummy_key()) + channel.channel_cache.set_int(CHANNEL_NONCE_RECEIVE, 0) + + cid_1_bytes = int.to_bytes(cid_1, 2, "big") + # expected_ack_on_received_message = get_ack(cid_1_bytes) + + self.interface.mock_read(message_with_invalid_tag, gen) + # gen.send(None) + + # self.assertEqual( + # self.interface.data[-1], + # expected_ack_on_received_message, + # ) + error_without_crc = b"\x42" + cid_1_bytes + b"\x00\x05\x03" + chksum_err = checksum.compute(error_without_crc) + # gen.send(None) + + decryption_failed_error = error_without_crc + chksum_err + b"\x00" * 54 + + self.assertEqual( + self.interface.data[-1], + decryption_failed_error, + ) + + # test invalid tag in handshake phase + cid_2 = get_channel_id_from_response(expected_response_1) + # cid_2_bytes = cid_2.to_bytes(2, "big") + channel = thp_main._CHANNELS[cid_2] + channel.iface = self.interface + + channel.set_channel_state(ChannelState.TH2) + + message_with_invalid_tag = b"\x0a\x12\x36\x00\x14\x00\x11\x22\x33\x44\x55\x66\x77\x00\x11\x22\x33\x44\x55\x66\x77\x91\x65\x4c\xf9" + + channel.channel_cache.set(CHANNEL_KEY_RECEIVE, get_dummy_key()) + channel.channel_cache.set_int(CHANNEL_NONCE_RECEIVE, 0) + + # gen.send(message_with_invalid_tag) + # gen.send(None) + # gen.send(None) + # for i in self.interface.data: + # print(utils.get_bytes_as_str(i)) + + def tbd_skip_pairing(self): + config.init() + config.wipe() + channel = next(iter(thp_main._CHANNELS.values())) + channel.selected_pairing_methods = [ + ThpPairingMethod.SkipPairing, + ThpPairingMethod.CodeEntry, + ThpPairingMethod.NFC_Unidirectional, + ThpPairingMethod.QrCode, + ] + pairing_ctx = PairingContext(channel) + request_message = ThpPairingRequest() + channel.set_channel_state(ChannelState.TP1) + gen = pairing.handle_pairing_request(pairing_ctx, request_message) + + with self.assertRaises(StopIteration): + gen.send(None) + self.assertEqual(channel.get_channel_state(), ChannelState.ENCRYPTED_TRANSPORT) + + # Teardown: set back initial channel state value + channel.set_channel_state(ChannelState.TH1) + + def TODO_test_pairing(self): + config.init() + config.wipe() + cid = get_channel_id_from_response( + send_channel_allocation_request(self.interface) + ) + channel = thp_main._CHANNELS[cid] + channel.selected_pairing_methods = [ + ThpPairingMethod.CodeEntry, + ThpPairingMethod.NFC, + ThpPairingMethod.QrCode, + ] + pairing_ctx = PairingContext(channel) + request_message = ThpPairingRequest() + with self.assertRaises(UnexpectedMessage) as e: + pairing.handle_pairing_request(pairing_ctx, request_message) + print(e.value.message) + channel.set_channel_state(ChannelState.TP1) + gen = pairing.handle_pairing_request(pairing_ctx, request_message) + + channel.channel_cache.set(CHANNEL_KEY_SEND, get_dummy_key()) + channel.channel_cache.set_int(CHANNEL_NONCE_SEND, 0) + channel.channel_cache.set(CHANNEL_HANDSHAKE_HASH, b"") + + gen.send(None) + + async def _dummy(ctx: PairingContext, expected_types): + return await ctx.read([1018, 1024]) + + # pairing.show_display_data = _dummy + + msg_code_entry = ThpCodeEntryChallenge(challenge=b"\x12\x34") + buffer: bytearray = bytearray(protobuf.encoded_length(msg_code_entry)) + protobuf.encode(buffer, msg_code_entry) + code_entry_challenge = Message(ThpMessageType.ThpCodeEntryChallenge, buffer) + self.interface.mock_read(code_entry_challenge, gen) + + # tag_qrc = b"\x55\xdf\x6c\xba\x0b\xe9\x5e\xd1\x4b\x78\x61\xec\xfa\x07\x9b\x5d\x37\x60\xd8\x79\x9c\xd7\x89\xb4\x22\xc1\x6f\x39\xde\x8f\x3b\xc3" + # tag_nfc = b"\x8f\xf0\xfa\x37\x0a\x5b\xdb\x29\x32\x21\xd8\x2f\x95\xdd\xb6\xb8\xee\xfd\x28\x6f\x56\x9f\xa9\x0b\x64\x8c\xfc\x62\x46\x5a\xdd\xd0" + + pregenerator_host = b"\xf6\x94\xc3\x6f\xb3\xbd\xfb\xba\x2f\xfd\x0c\xd0\x71\xed\x54\x76\x73\x64\x37\xfa\x25\x85\x12\x8d\xcf\xb5\x6c\x02\xaf\x9d\xe8\xbe" + generator_host = elligator2.map_to_curve25519(pregenerator_host) + cpace_host_private_key = b"\x02\x80\x70\x3c\x06\x45\x19\x75\x87\x0c\x82\xe1\x64\x11\xc0\x18\x13\xb2\x29\x04\xb3\xf0\xe4\x1e\x6b\xfd\x77\x63\x11\x73\x07\xa9" + cpace_host_public_key: bytes = curve25519.multiply( + cpace_host_private_key, generator_host + ) + msg = ThpCodeEntryCpaceHost(cpace_host_public_key=cpace_host_public_key) + + # msg = ThpQrCodeTag(tag=tag_qrc) + # msg = ThpNfcTagHost(tag=tag_nfc) + buffer: bytearray = bytearray(protobuf.encoded_length(msg)) + + protobuf.encode(buffer, msg) + user_message = Message(ThpMessageType.ThpCodeEntryCpaceHost, buffer) + self.interface.mock_read(user_message, gen) + + tag_ent = b"\xd0\x15\xd6\x72\x7c\xa6\x9b\x2a\x07\xfa\x30\xee\x03\xf0\x2d\x04\xdc\x96\x06\x77\x0c\xbd\xb4\xaa\x77\xc7\x68\x6f\xae\xa9\xdd\x81" + msg = ThpCodeEntryTag(tag=tag_ent) + + buffer: bytearray = bytearray(protobuf.encoded_length(msg)) + + protobuf.encode(buffer, msg) + user_message = Message(ThpMessageType.ThpCodeEntryTag, buffer) + self.interface.mock_read(user_message, gen) + + host_static_pubkey = b"\x00\x11\x22\x33\x44\x55\x66\x77\x00\x11\x22\x33\x44\x55\x66\x77\x00\x11\x22\x33\x44\x55\x66\x77\x00\x11\x22\x33\x44\x55\x66\x77\x00\x11\x22\x33\x44\x55\x66\x77\x00\x11\x22\x33\x44\x55\x66\x77" + msg = ThpCredentialRequest(host_static_pubkey=host_static_pubkey) + buffer: bytearray = bytearray(protobuf.encoded_length(msg)) + protobuf.encode(buffer, msg) + credential_request = Message(ThpMessageType.ThpCredentialRequest, buffer) + self.interface.mock_read(credential_request, gen) + + msg = ThpEndRequest() + + buffer: bytearray = bytearray(protobuf.encoded_length(msg)) + protobuf.encode(buffer, msg) + end_request = Message(1012, buffer) + with self.assertRaises(StopIteration) as e: + self.interface.mock_read(end_request, gen) + print("response message:", e.value.value.MESSAGE_NAME) + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_trezor.wire.thp.writer.py b/core/tests/test_trezor.wire.thp.writer.py new file mode 100644 index 0000000000..0f6110761a --- /dev/null +++ b/core/tests/test_trezor.wire.thp.writer.py @@ -0,0 +1,150 @@ +# flake8: noqa: F403,F405 +from common import * # isort:skip + +from typing import Any, Awaitable + +if utils.USE_THP: + import thp_common + from mock_wire_interface import MockHID + from trezor.wire.thp import ENCRYPTED, PacketHeader, writer + + +@unittest.skipUnless(utils.USE_THP, "only needed for THP") +class TestTrezorHostProtocolWriter(unittest.TestCase): + short_payload_expected = b"04123400050700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + longer_payload_expected = [ + b"0412340100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a", + b"8012343b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727374757677", + b"80123478797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4", + b"801234b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1", + b"801234f2f3f4f5f6f7f8f9fafbfcfdfeff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ] + eight_longer_payloads_expected = [ + b"0412340800000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a", + b"8012343b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727374757677", + b"80123478797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4", + b"801234b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1", + b"801234f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e", + b"8012342f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b", + b"8012346c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8", + b"801234a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5", + b"801234e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122", + b"801234232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f", + b"801234606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c", + b"8012349d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9", + b"801234dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f10111213141516", + b"8012341718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253", + b"8012345455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f90", + b"8012349192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccd", + b"801234cecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a", + b"8012340b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344454647", + b"80123448494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f8081828384", + b"80123485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1", + b"801234c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe", + b"801234ff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b", + b"8012343c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778", + b"801234797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5", + b"801234b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2", + b"801234f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f", + b"801234303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c", + b"8012346d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9", + b"801234aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6", + b"801234e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223", + b"8012342425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + b"8012346162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d", + b"8012349e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9da", + b"801234dbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000000000000000000000000000000000000000000000000", + ] + empty_payload_with_checksum_expected = b"0412340004edbd479c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + longer_payload_with_checksum_expected = [ + b"0412340100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a", + b"8012343b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727374757677", + b"80123478797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4", + b"801234b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1", + b"801234f2f3f4f5f6f7f8f9fafbfcfdfefff40c65ee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ] + + def await_until_result(self, task: Awaitable) -> Any: + with self.assertRaises(StopIteration): + while True: + task.send(None) + + def __init__(self): + if __debug__: + thp_common.suppres_debug_log() + super().__init__() + + def setUp(self): + self.interface = MockHID(0xDEADBEEF) + + def test_write_empty_packet(self): + self.await_until_result(writer.write_packet_to_wire(self.interface, b"")) + + print(self.interface.data[0]) + self.assertEqual(len(self.interface.data), 1) + self.assertEqual(self.interface.data[0], b"") + + def test_write_empty_payload(self): + header = PacketHeader(ENCRYPTED, 4660, 4) + await_result(writer.write_payloads_to_wire(self.interface, header, (b"",))) + self.assertEqual(len(self.interface.data), 0) + + def test_write_short_payload(self): + header = PacketHeader(ENCRYPTED, 4660, 5) + data = b"\x07" + self.await_until_result( + writer.write_payloads_to_wire(self.interface, header, (data,)) + ) + self.assertEqual(hexlify(self.interface.data[0]), self.short_payload_expected) + + def test_write_longer_payload(self): + data = bytearray(range(256)) + header = PacketHeader(ENCRYPTED, 4660, 256) + self.await_until_result( + writer.write_payloads_to_wire(self.interface, header, (data,)) + ) + + for i in range(len(self.longer_payload_expected)): + self.assertEqual( + hexlify(self.interface.data[i]), self.longer_payload_expected[i] + ) + + def test_write_eight_longer_payloads(self): + data = bytearray(range(256)) + header = PacketHeader(ENCRYPTED, 4660, 2048) + self.await_until_result( + writer.write_payloads_to_wire( + self.interface, header, (data, data, data, data, data, data, data, data) + ) + ) + for i in range(len(self.eight_longer_payloads_expected)): + self.assertEqual( + hexlify(self.interface.data[i]), self.eight_longer_payloads_expected[i] + ) + + def test_write_empty_payload_with_checksum(self): + header = PacketHeader(ENCRYPTED, 4660, 4) + self.await_until_result( + writer.write_payload_to_wire_and_add_checksum(self.interface, header, b"") + ) + + self.assertEqual( + hexlify(self.interface.data[0]), self.empty_payload_with_checksum_expected + ) + + def test_write_longer_payload_with_checksum(self): + data = bytearray(range(256)) + header = PacketHeader(ENCRYPTED, 4660, 256) + self.await_until_result( + writer.write_payload_to_wire_and_add_checksum(self.interface, header, data) + ) + + for i in range(len(self.longer_payload_with_checksum_expected)): + self.assertEqual( + hexlify(self.interface.data[i]), + self.longer_payload_with_checksum_expected[i], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/test_trezor.wire.thp_deprecated.py b/core/tests/test_trezor.wire.thp_deprecated.py new file mode 100644 index 0000000000..fce54f0f09 --- /dev/null +++ b/core/tests/test_trezor.wire.thp_deprecated.py @@ -0,0 +1,337 @@ +# flake8: noqa: F403,F405 +from common import * # isort:skip +import ustruct +from typing import TYPE_CHECKING + +from mock_wire_interface import MockHID +from storage.cache_thp import BROADCAST_CHANNEL_ID +from trezor import io +from trezor.utils import chunks +from trezor.wire.protocol_common import Message + +if utils.USE_THP: + import thp_common + import trezor.wire.thp + from trezor.wire.thp import alternating_bit_protocol as ABP + from trezor.wire.thp import checksum, thp_main + from trezor.wire.thp.checksum import CHECKSUM_LENGTH + +if TYPE_CHECKING: + from trezorio import WireInterface + + +MESSAGE_TYPE = 0x4242 +MESSAGE_TYPE_BYTES = b"\x42\x42" +_MESSAGE_TYPE_LEN = 2 +PLAINTEXT_0 = 0x01 +PLAINTEXT_1 = 0x11 +COMMON_CID = 4660 +CONT = 0x80 + +HEADER_INIT_LENGTH = 5 +HEADER_CONT_LENGTH = 3 +if utils.USE_THP: + INIT_MESSAGE_DATA_LENGTH = HEADER_INIT_LENGTH - _MESSAGE_TYPE_LEN # + PACKET_LENGTH + + +def make_header(ctrl_byte, cid, length): + return ustruct.pack(">BHH", ctrl_byte, cid, length) + + +def make_cont_header(): + return ustruct.pack(">BH", CONT, COMMON_CID) + + +def makeSimpleMessage(header, message_type, message_data): + return header + ustruct.pack(">H", message_type) + message_data + + +def makeCidRequest(header, message_data): + return header + message_data + + +def getPlaintext() -> bytes: + if ABP.get_expected_receive_seq_bit(THP.get_active_session()) == 1: + return PLAINTEXT_1 + return PLAINTEXT_0 + + +async def deprecated_read_message( + iface: WireInterface, buffer: utils.BufferType +) -> Message: + return Message(-1, b"\x00") + + +async def deprecated_write_message( + iface: WireInterface, message: Message, is_retransmission: bool = False +) -> None: + pass + + +# This test suite is an adaptation of test_trezor.wire.codec_v1 +@unittest.skipUnless(utils.USE_THP, "only needed for THP") +class TestWireTrezorHostProtocolV1(unittest.TestCase): + + def __init__(self): + if __debug__: + thp_common.suppres_debug_log() + super().__init__() + + def setUp(self): + self.interface = MockHID(0xDEADBEEF) + + def _simple(self): + cid_req_header = make_header( + ctrl_byte=0x40, cid=BROADCAST_CHANNEL_ID, length=12 + ) + cid_request_dummy_data = b"\x00\x11\x22\x33\x44\x55\x66\x77\x96\x64\x3c\x6c" + cid_req_message = makeCidRequest(cid_req_header, cid_request_dummy_data) + + message_header = make_header(ctrl_byte=0x01, cid=COMMON_CID, length=18) + cid_request_dummy_data_checksum = b"\x67\x8e\xac\xe0" + message = makeSimpleMessage( + message_header, + MESSAGE_TYPE, + cid_request_dummy_data + cid_request_dummy_data_checksum, + ) + + buffer = bytearray(64) + gen = deprecated_read_message(self.interface, buffer) + query = gen.send(None) + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ)) + gen.send(cid_req_message) + gen.send(None) + gen.send(message) + with self.assertRaises(StopIteration) as e: + gen.send(None) + + # e.value is StopIteration. e.value.value is the return value of the call + result = e.value.value + self.assertEqual(result.type, MESSAGE_TYPE) + self.assertEqual(result.data, cid_request_dummy_data) + + buffer_without_zeroes = buffer[: len(message) - 5] + message_without_header = message[5:] + # message should have been read into the buffer + self.assertEqual(buffer_without_zeroes, message_without_header) + + def _read_one_packet(self): + # zero length message - just a header + PLAINTEXT = getPlaintext() + header = make_header( + PLAINTEXT, cid=COMMON_CID, length=_MESSAGE_TYPE_LEN + CHECKSUM_LENGTH + ) + chksum = checksum.compute(header + MESSAGE_TYPE_BYTES) + message = header + MESSAGE_TYPE_BYTES + chksum + + buffer = bytearray(64) + gen = deprecated_read_message(self.interface, buffer) + + query = gen.send(None) + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ)) + gen.send(message) + with self.assertRaises(StopIteration) as e: + gen.send(None) + + # e.value is StopIteration. e.value.value is the return value of the call + result = e.value.value + self.assertEqual(result.type, MESSAGE_TYPE) + self.assertEqual(result.data, b"") + + # message should have been read into the buffer + self.assertEqual(buffer, MESSAGE_TYPE_BYTES + chksum + b"\x00" * 58) + + def _read_many_packets(self): + message = bytes(range(256)) + header = make_header( + getPlaintext(), + COMMON_CID, + len(message) + _MESSAGE_TYPE_LEN + CHECKSUM_LENGTH, + ) + chksum = checksum.compute(header + MESSAGE_TYPE_BYTES + message) + # message = MESSAGE_TYPE_BYTES + message + checksum + + # first packet is init header + 59 bytes of data + # other packets are cont header + 61 bytes of data + cont_header = make_cont_header() + packets = [header + MESSAGE_TYPE_BYTES + message[:INIT_MESSAGE_DATA_LENGTH]] + [ + cont_header + chunk + for chunk in chunks( + message[INIT_MESSAGE_DATA_LENGTH:] + chksum, + 64 - HEADER_CONT_LENGTH, + ) + ] + buffer = bytearray(262) + gen = deprecated_read_message(self.interface, buffer) + query = gen.send(None) + for packet in packets: + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ)) + query = gen.send(packet) + + # last packet will stop + with self.assertRaises(StopIteration) as e: + gen.send(None) + + # e.value is StopIteration. e.value.value is the return value of the call + result = e.value.value + + self.assertEqual(result.type, MESSAGE_TYPE) + self.assertEqual(result.data, message) + + # message should have been read into the buffer ) + self.assertEqual(buffer, MESSAGE_TYPE_BYTES + message + chksum) + + def _read_large_message(self): + message = b"hello world" + header = make_header( + getPlaintext(), + COMMON_CID, + _MESSAGE_TYPE_LEN + len(message) + CHECKSUM_LENGTH, + ) + + packet = ( + header + + MESSAGE_TYPE_BYTES + + message + + checksum.compute(header + MESSAGE_TYPE_BYTES + message) + ) + + # make sure we fit into one packet, to make this easier + # self.assertTrue(len(packet) <= thp_main.PACKET_LENGTH) + + buffer = bytearray(1) + self.assertTrue(len(buffer) <= len(packet)) + + gen = deprecated_read_message(self.interface, buffer) + query = gen.send(None) + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ)) + gen.send(packet) + with self.assertRaises(StopIteration) as e: + gen.send(None) + + # e.value is StopIteration. e.value.value is the return value of the call + result = e.value.value + self.assertEqual(result.type, MESSAGE_TYPE) + self.assertEqual(result.data, message) + + # read should have allocated its own buffer and not touch ours + self.assertEqual(buffer, b"\x00") + + def _roundtrip(self): + message_payload = bytes(range(256)) + message = Message( + MESSAGE_TYPE, message_payload, 1 + ) # TODO use different session id + gen = deprecated_write_message(self.interface, message) + # exhaust the iterator: + # (XXX we can only do this because the iterator is only accepting None and returns None) + for query in gen: + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_WRITE)) + + buffer = bytearray(1024) + gen = deprecated_read_message(self.interface, buffer) + query = gen.send(None) + for packet in self.interface.data: + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ)) + print(utils.get_bytes_as_str(packet)) + query = gen.send(packet) + + with self.assertRaises(StopIteration) as e: + gen.send(None) + + result = e.value.value + self.assertEqual(result.type, MESSAGE_TYPE) + self.assertEqual(result.data, message.data) + + def _write_one_packet(self): + message = Message(MESSAGE_TYPE, b"") + gen = deprecated_write_message(self.interface, message) + + query = gen.send(None) + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_WRITE)) + with self.assertRaises(StopIteration): + gen.send(None) + + header = make_header( + getPlaintext(), COMMON_CID, _MESSAGE_TYPE_LEN + CHECKSUM_LENGTH + ) + expected_message = ( + header + + MESSAGE_TYPE_BYTES + + checksum.compute(header + MESSAGE_TYPE_BYTES) + + b"\x00" * (INIT_MESSAGE_DATA_LENGTH - CHECKSUM_LENGTH) + ) + self.assertTrue(self.interface.data == [expected_message]) + + def _write_multiple_packets(self): + message_payload = bytes(range(256)) + message = Message(MESSAGE_TYPE, message_payload) + gen = deprecated_write_message(self.interface, message) + + header = make_header( + PLAINTEXT_1, + COMMON_CID, + len(message.data) + _MESSAGE_TYPE_LEN + CHECKSUM_LENGTH, + ) + cont_header = make_cont_header() + chksum = checksum.compute( + header + message.type.to_bytes(2, "big") + message.data + ) + packets = [ + header + MESSAGE_TYPE_BYTES + message.data[:INIT_MESSAGE_DATA_LENGTH] + ] + [ + cont_header + chunk + for chunk in chunks( + message.data[INIT_MESSAGE_DATA_LENGTH:] + chksum, + HEADER_CONT_LENGTH, # + PACKET_LENGTH + ) + ] + + for _ in packets: + # we receive as many queries as there are packets + query = gen.send(None) + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_WRITE)) + + # the first sent None only started the generator. the len(packets)-th None + # will finish writing and raise StopIteration + with self.assertRaises(StopIteration): + gen.send(None) + + # packets must be identical up to the last one + self.assertListEqual(packets[:-1], self.interface.data[:-1]) + # last packet must be identical up to message length. remaining bytes in + # the 64-byte packets are garbage -- in particular, it's the bytes of the + # previous packet + last_packet = packets[-1] + packets[-2][len(packets[-1]) :] + self.assertEqual(last_packet, self.interface.data[-1]) + + def _read_huge_packet(self): + PACKET_COUNT = 1180 + # message that takes up 1 180 USB packets + message_size = (PACKET_COUNT - 1) * ( + HEADER_CONT_LENGTH - CHECKSUM_LENGTH - _MESSAGE_TYPE_LEN # + PACKET_LENGTH + ) + INIT_MESSAGE_DATA_LENGTH + + # ensure that a message this big won't fit into memory + # Note: this control is changed, because THP has only 2 byte length field + self.assertTrue(message_size > thp_main.MAX_PAYLOAD_LEN) + # self.assertRaises(MemoryError, bytearray, message_size) + header = make_header(PLAINTEXT_1, COMMON_CID, message_size) + packet = header + MESSAGE_TYPE_BYTES + (b"\x00" * INIT_MESSAGE_DATA_LENGTH) + buffer = bytearray(65536) + gen = deprecated_read_message(self.interface, buffer) + + query = gen.send(None) + + # THP returns "Message too large" error after reading the message size, + # it is different from codec_v1 as it does not allow big enough messages + # to raise MemoryError in this test + self.assertObjectEqual(query, self.interface.wait_object(io.POLL_READ)) + with self.assertRaises(trezor.wire.thp.ThpError) as e: + query = gen.send(packet) + + self.assertEqual(e.value.args[0], "Message too large") + + +if __name__ == "__main__": + unittest.main() diff --git a/core/tests/thp_common.py b/core/tests/thp_common.py new file mode 100644 index 0000000000..ba6f0acdc3 --- /dev/null +++ b/core/tests/thp_common.py @@ -0,0 +1,47 @@ +# flake8: noqa: F403,F405 +from trezor import utils +from trezor.wire.thp import ChannelState + +if utils.USE_THP: + import unittest + from typing import TYPE_CHECKING + + from mock_wire_interface import MockHID + from storage import cache_thp + from trezor.wire import context + from trezor.wire.thp import interface_manager + from trezor.wire.thp.channel import Channel + from trezor.wire.thp.session_context import SessionContext + + _MOCK_INTERFACE_HID = b"\x00" + + if TYPE_CHECKING: + from trezor.wire import WireInterface + + def dummy_decode_iface(cached_iface: bytes): + return MockHID(0xDEADBEEF) + + def get_new_channel(channel_iface: WireInterface | None = None) -> Channel: + interface_manager.decode_iface = dummy_decode_iface + channel_cache = cache_thp.get_new_channel(_MOCK_INTERFACE_HID) + channel = Channel(channel_cache) + channel.set_channel_state(ChannelState.TH1) + if channel_iface is not None: + channel.iface = channel_iface + return channel + + def prepare_context() -> None: + channel = get_new_channel() + session_cache = cache_thp.create_or_replace_session( + channel.channel_cache, session_id=b"\x01" + ) + session_ctx = SessionContext(channel, session_cache) + context.CURRENT_CONTEXT = session_ctx + + +if __debug__: + # Disable log.debug + def suppres_debug_log() -> None: + from trezor import log + + log.debug = lambda name, msg, *args: None diff --git a/legacy/firmware/fsm.c b/legacy/firmware/fsm.c index eefed373db..25a67d1b8b 100644 --- a/legacy/firmware/fsm.c +++ b/legacy/firmware/fsm.c @@ -191,6 +191,18 @@ void fsm_sendFailure(FailureType code, const char *text) case FailureType_Failure_InvalidSession: text = _("Invalid session"); break; + case FailureType_Failure_ThpUnallocatedSession: + text = _("Unallocated session"); + break; + case FailureType_Failure_InvalidProtocol: + text = _("Invalid protocol"); + break; + case FailureType_Failure_BufferError: + text = _("Buffer error"); + break; + case FailureType_Failure_DeviceIsBusy: + text = _("Device is busy"); + break; case FailureType_Failure_FirmwareError: text = _("Firmware error"); break; diff --git a/legacy/firmware/protob/Makefile b/legacy/firmware/protob/Makefile index 48dbd5ddc7..2ea4e10ead 100644 --- a/legacy/firmware/protob/Makefile +++ b/legacy/firmware/protob/Makefile @@ -3,14 +3,14 @@ Q := @ endif SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn \ - DebugLinkRecordScreen DebugLinkEraseSdCard DebugLinkWatchLayout \ - DebugLinkLayout DebugLinkResetDebugEvents GetNonce \ + DebugLinkRecordScreen DebugLinkEraseSdCard DebugLinkWatchLayout DebugLinkLayout \ + DebugLinkResetDebugEvents DebugLinkGetPairingInfo DebugLinkPairingInfo GetNonce \ TxAckInput TxAckOutput TxAckPrev TxAckPaymentRequest \ EthereumSignTypedData EthereumTypedDataStructRequest EthereumTypedDataStructAck \ EthereumTypedDataValueRequest EthereumTypedDataValueAck ShowDeviceTutorial \ UnlockBootloader AuthenticateDevice AuthenticityProof \ Solana StellarClaimClaimableBalanceOp \ - ChangeLanguage TranslationDataRequest TranslationDataAck \ + ChangeLanguage TranslationDataRequest TranslationDataAck Thp\ SetBrightness DebugLinkOptigaSetSecMax EntropyCheckReady EntropyCheckContinue \ BenchmarkListNames BenchmarkRun BenchmarkNames BenchmarkResult diff --git a/legacy/firmware/protob/messages-thp.proto b/legacy/firmware/protob/messages-thp.proto new file mode 120000 index 0000000000..4799efe83a --- /dev/null +++ b/legacy/firmware/protob/messages-thp.proto @@ -0,0 +1 @@ +../../vendor/trezor-common/protob/messages-thp.proto \ No newline at end of file diff --git a/python/requirements.txt b/python/requirements.txt index 161faad77e..f4a121a156 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -8,3 +8,5 @@ libusb1>=1.6.4 construct>=2.9,!=2.10.55 typing_extensions>=4.7.1 construct-classes>=0.1.2 +cryptography >=43.0.3 +platformdirs >=2 diff --git a/rust/trezor-client/src/messages/generated.rs b/rust/trezor-client/src/messages/generated.rs index 551a1e92e2..ab95654936 100644 --- a/rust/trezor-client/src/messages/generated.rs +++ b/rust/trezor-client/src/messages/generated.rs @@ -82,6 +82,8 @@ trezor_message_impl! { DebugLinkWatchLayout => MessageType_DebugLinkWatchLayout, DebugLinkResetDebugEvents => MessageType_DebugLinkResetDebugEvents, DebugLinkOptigaSetSecMax => MessageType_DebugLinkOptigaSetSecMax, + DebugLinkGetPairingInfo => MessageType_DebugLinkGetPairingInfo, + DebugLinkPairingInfo => MessageType_DebugLinkPairingInfo, BenchmarkListNames => MessageType_BenchmarkListNames, BenchmarkNames => MessageType_BenchmarkNames, BenchmarkRun => MessageType_BenchmarkRun, diff --git a/rust/trezor-client/src/protos/generated/messages.rs b/rust/trezor-client/src/protos/generated/messages.rs index 0a265410a6..109f6e5232 100644 --- a/rust/trezor-client/src/protos/generated/messages.rs +++ b/rust/trezor-client/src/protos/generated/messages.rs @@ -226,6 +226,10 @@ pub enum MessageType { MessageType_DebugLinkResetDebugEvents = 9007, // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_DebugLinkOptigaSetSecMax) MessageType_DebugLinkOptigaSetSecMax = 9008, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_DebugLinkGetPairingInfo) + MessageType_DebugLinkGetPairingInfo = 9009, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_DebugLinkPairingInfo) + MessageType_DebugLinkPairingInfo = 9010, // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_EthereumGetPublicKey) MessageType_EthereumGetPublicKey = 450, // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_EthereumPublicKey) @@ -632,6 +636,8 @@ impl ::protobuf::Enum for MessageType { 9006 => ::std::option::Option::Some(MessageType::MessageType_DebugLinkWatchLayout), 9007 => ::std::option::Option::Some(MessageType::MessageType_DebugLinkResetDebugEvents), 9008 => ::std::option::Option::Some(MessageType::MessageType_DebugLinkOptigaSetSecMax), + 9009 => ::std::option::Option::Some(MessageType::MessageType_DebugLinkGetPairingInfo), + 9010 => ::std::option::Option::Some(MessageType::MessageType_DebugLinkPairingInfo), 450 => ::std::option::Option::Some(MessageType::MessageType_EthereumGetPublicKey), 451 => ::std::option::Option::Some(MessageType::MessageType_EthereumPublicKey), 56 => ::std::option::Option::Some(MessageType::MessageType_EthereumGetAddress), @@ -885,6 +891,8 @@ impl ::protobuf::Enum for MessageType { "MessageType_DebugLinkWatchLayout" => ::std::option::Option::Some(MessageType::MessageType_DebugLinkWatchLayout), "MessageType_DebugLinkResetDebugEvents" => ::std::option::Option::Some(MessageType::MessageType_DebugLinkResetDebugEvents), "MessageType_DebugLinkOptigaSetSecMax" => ::std::option::Option::Some(MessageType::MessageType_DebugLinkOptigaSetSecMax), + "MessageType_DebugLinkGetPairingInfo" => ::std::option::Option::Some(MessageType::MessageType_DebugLinkGetPairingInfo), + "MessageType_DebugLinkPairingInfo" => ::std::option::Option::Some(MessageType::MessageType_DebugLinkPairingInfo), "MessageType_EthereumGetPublicKey" => ::std::option::Option::Some(MessageType::MessageType_EthereumGetPublicKey), "MessageType_EthereumPublicKey" => ::std::option::Option::Some(MessageType::MessageType_EthereumPublicKey), "MessageType_EthereumGetAddress" => ::std::option::Option::Some(MessageType::MessageType_EthereumGetAddress), @@ -1137,6 +1145,8 @@ impl ::protobuf::Enum for MessageType { MessageType::MessageType_DebugLinkWatchLayout, MessageType::MessageType_DebugLinkResetDebugEvents, MessageType::MessageType_DebugLinkOptigaSetSecMax, + MessageType::MessageType_DebugLinkGetPairingInfo, + MessageType::MessageType_DebugLinkPairingInfo, MessageType::MessageType_EthereumGetPublicKey, MessageType::MessageType_EthereumPublicKey, MessageType::MessageType_EthereumGetAddress, @@ -1395,154 +1405,156 @@ impl ::protobuf::EnumFull for MessageType { MessageType::MessageType_DebugLinkWatchLayout => 96, MessageType::MessageType_DebugLinkResetDebugEvents => 97, MessageType::MessageType_DebugLinkOptigaSetSecMax => 98, - MessageType::MessageType_EthereumGetPublicKey => 99, - MessageType::MessageType_EthereumPublicKey => 100, - MessageType::MessageType_EthereumGetAddress => 101, - MessageType::MessageType_EthereumAddress => 102, - MessageType::MessageType_EthereumSignTx => 103, - MessageType::MessageType_EthereumSignTxEIP1559 => 104, - MessageType::MessageType_EthereumTxRequest => 105, - MessageType::MessageType_EthereumTxAck => 106, - MessageType::MessageType_EthereumSignMessage => 107, - MessageType::MessageType_EthereumVerifyMessage => 108, - MessageType::MessageType_EthereumMessageSignature => 109, - MessageType::MessageType_EthereumSignTypedData => 110, - MessageType::MessageType_EthereumTypedDataStructRequest => 111, - MessageType::MessageType_EthereumTypedDataStructAck => 112, - MessageType::MessageType_EthereumTypedDataValueRequest => 113, - MessageType::MessageType_EthereumTypedDataValueAck => 114, - MessageType::MessageType_EthereumTypedDataSignature => 115, - MessageType::MessageType_EthereumSignTypedHash => 116, - MessageType::MessageType_NEMGetAddress => 117, - MessageType::MessageType_NEMAddress => 118, - MessageType::MessageType_NEMSignTx => 119, - MessageType::MessageType_NEMSignedTx => 120, - MessageType::MessageType_NEMDecryptMessage => 121, - MessageType::MessageType_NEMDecryptedMessage => 122, - MessageType::MessageType_TezosGetAddress => 123, - MessageType::MessageType_TezosAddress => 124, - MessageType::MessageType_TezosSignTx => 125, - MessageType::MessageType_TezosSignedTx => 126, - MessageType::MessageType_TezosGetPublicKey => 127, - MessageType::MessageType_TezosPublicKey => 128, - MessageType::MessageType_StellarSignTx => 129, - MessageType::MessageType_StellarTxOpRequest => 130, - MessageType::MessageType_StellarGetAddress => 131, - MessageType::MessageType_StellarAddress => 132, - MessageType::MessageType_StellarCreateAccountOp => 133, - MessageType::MessageType_StellarPaymentOp => 134, - MessageType::MessageType_StellarPathPaymentStrictReceiveOp => 135, - MessageType::MessageType_StellarManageSellOfferOp => 136, - MessageType::MessageType_StellarCreatePassiveSellOfferOp => 137, - MessageType::MessageType_StellarSetOptionsOp => 138, - MessageType::MessageType_StellarChangeTrustOp => 139, - MessageType::MessageType_StellarAllowTrustOp => 140, - MessageType::MessageType_StellarAccountMergeOp => 141, - MessageType::MessageType_StellarManageDataOp => 142, - MessageType::MessageType_StellarBumpSequenceOp => 143, - MessageType::MessageType_StellarManageBuyOfferOp => 144, - MessageType::MessageType_StellarPathPaymentStrictSendOp => 145, - MessageType::MessageType_StellarClaimClaimableBalanceOp => 146, - MessageType::MessageType_StellarSignedTx => 147, - MessageType::MessageType_CardanoGetPublicKey => 148, - MessageType::MessageType_CardanoPublicKey => 149, - MessageType::MessageType_CardanoGetAddress => 150, - MessageType::MessageType_CardanoAddress => 151, - MessageType::MessageType_CardanoTxItemAck => 152, - MessageType::MessageType_CardanoTxAuxiliaryDataSupplement => 153, - MessageType::MessageType_CardanoTxWitnessRequest => 154, - MessageType::MessageType_CardanoTxWitnessResponse => 155, - MessageType::MessageType_CardanoTxHostAck => 156, - MessageType::MessageType_CardanoTxBodyHash => 157, - MessageType::MessageType_CardanoSignTxFinished => 158, - MessageType::MessageType_CardanoSignTxInit => 159, - MessageType::MessageType_CardanoTxInput => 160, - MessageType::MessageType_CardanoTxOutput => 161, - MessageType::MessageType_CardanoAssetGroup => 162, - MessageType::MessageType_CardanoToken => 163, - MessageType::MessageType_CardanoTxCertificate => 164, - MessageType::MessageType_CardanoTxWithdrawal => 165, - MessageType::MessageType_CardanoTxAuxiliaryData => 166, - MessageType::MessageType_CardanoPoolOwner => 167, - MessageType::MessageType_CardanoPoolRelayParameters => 168, - MessageType::MessageType_CardanoGetNativeScriptHash => 169, - MessageType::MessageType_CardanoNativeScriptHash => 170, - MessageType::MessageType_CardanoTxMint => 171, - MessageType::MessageType_CardanoTxCollateralInput => 172, - MessageType::MessageType_CardanoTxRequiredSigner => 173, - MessageType::MessageType_CardanoTxInlineDatumChunk => 174, - MessageType::MessageType_CardanoTxReferenceScriptChunk => 175, - MessageType::MessageType_CardanoTxReferenceInput => 176, - MessageType::MessageType_RippleGetAddress => 177, - MessageType::MessageType_RippleAddress => 178, - MessageType::MessageType_RippleSignTx => 179, - MessageType::MessageType_RippleSignedTx => 180, - MessageType::MessageType_MoneroTransactionInitRequest => 181, - MessageType::MessageType_MoneroTransactionInitAck => 182, - MessageType::MessageType_MoneroTransactionSetInputRequest => 183, - MessageType::MessageType_MoneroTransactionSetInputAck => 184, - MessageType::MessageType_MoneroTransactionInputViniRequest => 185, - MessageType::MessageType_MoneroTransactionInputViniAck => 186, - MessageType::MessageType_MoneroTransactionAllInputsSetRequest => 187, - MessageType::MessageType_MoneroTransactionAllInputsSetAck => 188, - MessageType::MessageType_MoneroTransactionSetOutputRequest => 189, - MessageType::MessageType_MoneroTransactionSetOutputAck => 190, - MessageType::MessageType_MoneroTransactionAllOutSetRequest => 191, - MessageType::MessageType_MoneroTransactionAllOutSetAck => 192, - MessageType::MessageType_MoneroTransactionSignInputRequest => 193, - MessageType::MessageType_MoneroTransactionSignInputAck => 194, - MessageType::MessageType_MoneroTransactionFinalRequest => 195, - MessageType::MessageType_MoneroTransactionFinalAck => 196, - MessageType::MessageType_MoneroKeyImageExportInitRequest => 197, - MessageType::MessageType_MoneroKeyImageExportInitAck => 198, - MessageType::MessageType_MoneroKeyImageSyncStepRequest => 199, - MessageType::MessageType_MoneroKeyImageSyncStepAck => 200, - MessageType::MessageType_MoneroKeyImageSyncFinalRequest => 201, - MessageType::MessageType_MoneroKeyImageSyncFinalAck => 202, - MessageType::MessageType_MoneroGetAddress => 203, - MessageType::MessageType_MoneroAddress => 204, - MessageType::MessageType_MoneroGetWatchKey => 205, - MessageType::MessageType_MoneroWatchKey => 206, - MessageType::MessageType_DebugMoneroDiagRequest => 207, - MessageType::MessageType_DebugMoneroDiagAck => 208, - MessageType::MessageType_MoneroGetTxKeyRequest => 209, - MessageType::MessageType_MoneroGetTxKeyAck => 210, - MessageType::MessageType_MoneroLiveRefreshStartRequest => 211, - MessageType::MessageType_MoneroLiveRefreshStartAck => 212, - MessageType::MessageType_MoneroLiveRefreshStepRequest => 213, - MessageType::MessageType_MoneroLiveRefreshStepAck => 214, - MessageType::MessageType_MoneroLiveRefreshFinalRequest => 215, - MessageType::MessageType_MoneroLiveRefreshFinalAck => 216, - MessageType::MessageType_EosGetPublicKey => 217, - MessageType::MessageType_EosPublicKey => 218, - MessageType::MessageType_EosSignTx => 219, - MessageType::MessageType_EosTxActionRequest => 220, - MessageType::MessageType_EosTxActionAck => 221, - MessageType::MessageType_EosSignedTx => 222, - MessageType::MessageType_BinanceGetAddress => 223, - MessageType::MessageType_BinanceAddress => 224, - MessageType::MessageType_BinanceGetPublicKey => 225, - MessageType::MessageType_BinancePublicKey => 226, - MessageType::MessageType_BinanceSignTx => 227, - MessageType::MessageType_BinanceTxRequest => 228, - MessageType::MessageType_BinanceTransferMsg => 229, - MessageType::MessageType_BinanceOrderMsg => 230, - MessageType::MessageType_BinanceCancelMsg => 231, - MessageType::MessageType_BinanceSignedTx => 232, - MessageType::MessageType_WebAuthnListResidentCredentials => 233, - MessageType::MessageType_WebAuthnCredentials => 234, - MessageType::MessageType_WebAuthnAddResidentCredential => 235, - MessageType::MessageType_WebAuthnRemoveResidentCredential => 236, - MessageType::MessageType_SolanaGetPublicKey => 237, - MessageType::MessageType_SolanaPublicKey => 238, - MessageType::MessageType_SolanaGetAddress => 239, - MessageType::MessageType_SolanaAddress => 240, - MessageType::MessageType_SolanaSignTx => 241, - MessageType::MessageType_SolanaTxSignature => 242, - MessageType::MessageType_BenchmarkListNames => 243, - MessageType::MessageType_BenchmarkNames => 244, - MessageType::MessageType_BenchmarkRun => 245, - MessageType::MessageType_BenchmarkResult => 246, + MessageType::MessageType_DebugLinkGetPairingInfo => 99, + MessageType::MessageType_DebugLinkPairingInfo => 100, + MessageType::MessageType_EthereumGetPublicKey => 101, + MessageType::MessageType_EthereumPublicKey => 102, + MessageType::MessageType_EthereumGetAddress => 103, + MessageType::MessageType_EthereumAddress => 104, + MessageType::MessageType_EthereumSignTx => 105, + MessageType::MessageType_EthereumSignTxEIP1559 => 106, + MessageType::MessageType_EthereumTxRequest => 107, + MessageType::MessageType_EthereumTxAck => 108, + MessageType::MessageType_EthereumSignMessage => 109, + MessageType::MessageType_EthereumVerifyMessage => 110, + MessageType::MessageType_EthereumMessageSignature => 111, + MessageType::MessageType_EthereumSignTypedData => 112, + MessageType::MessageType_EthereumTypedDataStructRequest => 113, + MessageType::MessageType_EthereumTypedDataStructAck => 114, + MessageType::MessageType_EthereumTypedDataValueRequest => 115, + MessageType::MessageType_EthereumTypedDataValueAck => 116, + MessageType::MessageType_EthereumTypedDataSignature => 117, + MessageType::MessageType_EthereumSignTypedHash => 118, + MessageType::MessageType_NEMGetAddress => 119, + MessageType::MessageType_NEMAddress => 120, + MessageType::MessageType_NEMSignTx => 121, + MessageType::MessageType_NEMSignedTx => 122, + MessageType::MessageType_NEMDecryptMessage => 123, + MessageType::MessageType_NEMDecryptedMessage => 124, + MessageType::MessageType_TezosGetAddress => 125, + MessageType::MessageType_TezosAddress => 126, + MessageType::MessageType_TezosSignTx => 127, + MessageType::MessageType_TezosSignedTx => 128, + MessageType::MessageType_TezosGetPublicKey => 129, + MessageType::MessageType_TezosPublicKey => 130, + MessageType::MessageType_StellarSignTx => 131, + MessageType::MessageType_StellarTxOpRequest => 132, + MessageType::MessageType_StellarGetAddress => 133, + MessageType::MessageType_StellarAddress => 134, + MessageType::MessageType_StellarCreateAccountOp => 135, + MessageType::MessageType_StellarPaymentOp => 136, + MessageType::MessageType_StellarPathPaymentStrictReceiveOp => 137, + MessageType::MessageType_StellarManageSellOfferOp => 138, + MessageType::MessageType_StellarCreatePassiveSellOfferOp => 139, + MessageType::MessageType_StellarSetOptionsOp => 140, + MessageType::MessageType_StellarChangeTrustOp => 141, + MessageType::MessageType_StellarAllowTrustOp => 142, + MessageType::MessageType_StellarAccountMergeOp => 143, + MessageType::MessageType_StellarManageDataOp => 144, + MessageType::MessageType_StellarBumpSequenceOp => 145, + MessageType::MessageType_StellarManageBuyOfferOp => 146, + MessageType::MessageType_StellarPathPaymentStrictSendOp => 147, + MessageType::MessageType_StellarClaimClaimableBalanceOp => 148, + MessageType::MessageType_StellarSignedTx => 149, + MessageType::MessageType_CardanoGetPublicKey => 150, + MessageType::MessageType_CardanoPublicKey => 151, + MessageType::MessageType_CardanoGetAddress => 152, + MessageType::MessageType_CardanoAddress => 153, + MessageType::MessageType_CardanoTxItemAck => 154, + MessageType::MessageType_CardanoTxAuxiliaryDataSupplement => 155, + MessageType::MessageType_CardanoTxWitnessRequest => 156, + MessageType::MessageType_CardanoTxWitnessResponse => 157, + MessageType::MessageType_CardanoTxHostAck => 158, + MessageType::MessageType_CardanoTxBodyHash => 159, + MessageType::MessageType_CardanoSignTxFinished => 160, + MessageType::MessageType_CardanoSignTxInit => 161, + MessageType::MessageType_CardanoTxInput => 162, + MessageType::MessageType_CardanoTxOutput => 163, + MessageType::MessageType_CardanoAssetGroup => 164, + MessageType::MessageType_CardanoToken => 165, + MessageType::MessageType_CardanoTxCertificate => 166, + MessageType::MessageType_CardanoTxWithdrawal => 167, + MessageType::MessageType_CardanoTxAuxiliaryData => 168, + MessageType::MessageType_CardanoPoolOwner => 169, + MessageType::MessageType_CardanoPoolRelayParameters => 170, + MessageType::MessageType_CardanoGetNativeScriptHash => 171, + MessageType::MessageType_CardanoNativeScriptHash => 172, + MessageType::MessageType_CardanoTxMint => 173, + MessageType::MessageType_CardanoTxCollateralInput => 174, + MessageType::MessageType_CardanoTxRequiredSigner => 175, + MessageType::MessageType_CardanoTxInlineDatumChunk => 176, + MessageType::MessageType_CardanoTxReferenceScriptChunk => 177, + MessageType::MessageType_CardanoTxReferenceInput => 178, + MessageType::MessageType_RippleGetAddress => 179, + MessageType::MessageType_RippleAddress => 180, + MessageType::MessageType_RippleSignTx => 181, + MessageType::MessageType_RippleSignedTx => 182, + MessageType::MessageType_MoneroTransactionInitRequest => 183, + MessageType::MessageType_MoneroTransactionInitAck => 184, + MessageType::MessageType_MoneroTransactionSetInputRequest => 185, + MessageType::MessageType_MoneroTransactionSetInputAck => 186, + MessageType::MessageType_MoneroTransactionInputViniRequest => 187, + MessageType::MessageType_MoneroTransactionInputViniAck => 188, + MessageType::MessageType_MoneroTransactionAllInputsSetRequest => 189, + MessageType::MessageType_MoneroTransactionAllInputsSetAck => 190, + MessageType::MessageType_MoneroTransactionSetOutputRequest => 191, + MessageType::MessageType_MoneroTransactionSetOutputAck => 192, + MessageType::MessageType_MoneroTransactionAllOutSetRequest => 193, + MessageType::MessageType_MoneroTransactionAllOutSetAck => 194, + MessageType::MessageType_MoneroTransactionSignInputRequest => 195, + MessageType::MessageType_MoneroTransactionSignInputAck => 196, + MessageType::MessageType_MoneroTransactionFinalRequest => 197, + MessageType::MessageType_MoneroTransactionFinalAck => 198, + MessageType::MessageType_MoneroKeyImageExportInitRequest => 199, + MessageType::MessageType_MoneroKeyImageExportInitAck => 200, + MessageType::MessageType_MoneroKeyImageSyncStepRequest => 201, + MessageType::MessageType_MoneroKeyImageSyncStepAck => 202, + MessageType::MessageType_MoneroKeyImageSyncFinalRequest => 203, + MessageType::MessageType_MoneroKeyImageSyncFinalAck => 204, + MessageType::MessageType_MoneroGetAddress => 205, + MessageType::MessageType_MoneroAddress => 206, + MessageType::MessageType_MoneroGetWatchKey => 207, + MessageType::MessageType_MoneroWatchKey => 208, + MessageType::MessageType_DebugMoneroDiagRequest => 209, + MessageType::MessageType_DebugMoneroDiagAck => 210, + MessageType::MessageType_MoneroGetTxKeyRequest => 211, + MessageType::MessageType_MoneroGetTxKeyAck => 212, + MessageType::MessageType_MoneroLiveRefreshStartRequest => 213, + MessageType::MessageType_MoneroLiveRefreshStartAck => 214, + MessageType::MessageType_MoneroLiveRefreshStepRequest => 215, + MessageType::MessageType_MoneroLiveRefreshStepAck => 216, + MessageType::MessageType_MoneroLiveRefreshFinalRequest => 217, + MessageType::MessageType_MoneroLiveRefreshFinalAck => 218, + MessageType::MessageType_EosGetPublicKey => 219, + MessageType::MessageType_EosPublicKey => 220, + MessageType::MessageType_EosSignTx => 221, + MessageType::MessageType_EosTxActionRequest => 222, + MessageType::MessageType_EosTxActionAck => 223, + MessageType::MessageType_EosSignedTx => 224, + MessageType::MessageType_BinanceGetAddress => 225, + MessageType::MessageType_BinanceAddress => 226, + MessageType::MessageType_BinanceGetPublicKey => 227, + MessageType::MessageType_BinancePublicKey => 228, + MessageType::MessageType_BinanceSignTx => 229, + MessageType::MessageType_BinanceTxRequest => 230, + MessageType::MessageType_BinanceTransferMsg => 231, + MessageType::MessageType_BinanceOrderMsg => 232, + MessageType::MessageType_BinanceCancelMsg => 233, + MessageType::MessageType_BinanceSignedTx => 234, + MessageType::MessageType_WebAuthnListResidentCredentials => 235, + MessageType::MessageType_WebAuthnCredentials => 236, + MessageType::MessageType_WebAuthnAddResidentCredential => 237, + MessageType::MessageType_WebAuthnRemoveResidentCredential => 238, + MessageType::MessageType_SolanaGetPublicKey => 239, + MessageType::MessageType_SolanaPublicKey => 240, + MessageType::MessageType_SolanaGetAddress => 241, + MessageType::MessageType_SolanaAddress => 242, + MessageType::MessageType_SolanaSignTx => 243, + MessageType::MessageType_SolanaTxSignature => 244, + MessageType::MessageType_BenchmarkListNames => 245, + MessageType::MessageType_BenchmarkNames => 246, + MessageType::MessageType_BenchmarkRun => 247, + MessageType::MessageType_BenchmarkResult => 248, }; Self::enum_descriptor().value_by_index(index) } @@ -1561,7 +1573,7 @@ impl MessageType { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0emessages.proto\x12\x12hw.trezor.messages\x1a\roptions.proto*\xe8U\ + \n\x0emessages.proto\x12\x12hw.trezor.messages\x1a\roptions.proto*\xcdV\ \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_S\ @@ -1681,172 +1693,174 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x1d\x01\xa0\xb5\x18\x01\x124\n%MessageType_DebugLinkResetDebugEvents\ \x10\xafF\x1a\x08\x80\xa6\x1d\x01\xa0\xb5\x18\x01\x123\n$MessageType_Deb\ ugLinkOptigaSetSecMax\x10\xb0F\x1a\x08\x80\xa6\x1d\x01\xa0\xb5\x18\x01\ - \x12+\n\x20MessageType_EthereumGetPublicKey\x10\xc2\x03\x1a\x04\x90\xb5\ - \x18\x01\x12(\n\x1dMessageType_EthereumPublicKey\x10\xc3\x03\x1a\x04\x98\ - \xb5\x18\x01\x12(\n\x1eMessageType_EthereumGetAddress\x108\x1a\x04\x90\ - \xb5\x18\x01\x12%\n\x1bMessageType_EthereumAddress\x109\x1a\x04\x98\xb5\ - \x18\x01\x12$\n\x1aMessageType_EthereumSignTx\x10:\x1a\x04\x90\xb5\x18\ - \x01\x12,\n!MessageType_EthereumSignTxEIP1559\x10\xc4\x03\x1a\x04\x90\ - \xb5\x18\x01\x12'\n\x1dMessageType_EthereumTxRequest\x10;\x1a\x04\x98\ - \xb5\x18\x01\x12#\n\x19MessageType_EthereumTxAck\x10<\x1a\x04\x90\xb5\ - \x18\x01\x12)\n\x1fMessageType_EthereumSignMessage\x10@\x1a\x04\x90\xb5\ - \x18\x01\x12+\n!MessageType_EthereumVerifyMessage\x10A\x1a\x04\x90\xb5\ - \x18\x01\x12.\n$MessageType_EthereumMessageSignature\x10B\x1a\x04\x98\ - \xb5\x18\x01\x12,\n!MessageType_EthereumSignTypedData\x10\xd0\x03\x1a\ - \x04\x90\xb5\x18\x01\x125\n*MessageType_EthereumTypedDataStructRequest\ - \x10\xd1\x03\x1a\x04\x98\xb5\x18\x01\x121\n&MessageType_EthereumTypedDat\ - aStructAck\x10\xd2\x03\x1a\x04\x90\xb5\x18\x01\x124\n)MessageType_Ethere\ - umTypedDataValueRequest\x10\xd3\x03\x1a\x04\x98\xb5\x18\x01\x120\n%Messa\ - geType_EthereumTypedDataValueAck\x10\xd4\x03\x1a\x04\x90\xb5\x18\x01\x12\ - 1\n&MessageType_EthereumTypedDataSignature\x10\xd5\x03\x1a\x04\x98\xb5\ - \x18\x01\x12,\n!MessageType_EthereumSignTypedHash\x10\xd6\x03\x1a\x04\ - \x90\xb5\x18\x01\x12#\n\x19MessageType_NEMGetAddress\x10C\x1a\x04\x90\ - \xb5\x18\x01\x12\x20\n\x16MessageType_NEMAddress\x10D\x1a\x04\x98\xb5\ - \x18\x01\x12\x1f\n\x15MessageType_NEMSignTx\x10E\x1a\x04\x90\xb5\x18\x01\ - \x12!\n\x17MessageType_NEMSignedTx\x10F\x1a\x04\x98\xb5\x18\x01\x12'\n\ - \x1dMessageType_NEMDecryptMessage\x10K\x1a\x04\x90\xb5\x18\x01\x12)\n\ - \x1fMessageType_NEMDecryptedMessage\x10L\x1a\x04\x98\xb5\x18\x01\x12&\n\ - \x1bMessageType_TezosGetAddress\x10\x96\x01\x1a\x04\x90\xb5\x18\x01\x12#\ - \n\x18MessageType_TezosAddress\x10\x97\x01\x1a\x04\x98\xb5\x18\x01\x12\"\ - \n\x17MessageType_TezosSignTx\x10\x98\x01\x1a\x04\x90\xb5\x18\x01\x12$\n\ - \x19MessageType_TezosSignedTx\x10\x99\x01\x1a\x04\x98\xb5\x18\x01\x12(\n\ - \x1dMessageType_TezosGetPublicKey\x10\x9a\x01\x1a\x04\x90\xb5\x18\x01\ - \x12%\n\x1aMessageType_TezosPublicKey\x10\x9b\x01\x1a\x04\x98\xb5\x18\ - \x01\x12$\n\x19MessageType_StellarSignTx\x10\xca\x01\x1a\x04\x90\xb5\x18\ - \x01\x12)\n\x1eMessageType_StellarTxOpRequest\x10\xcb\x01\x1a\x04\x98\ - \xb5\x18\x01\x12(\n\x1dMessageType_StellarGetAddress\x10\xcf\x01\x1a\x04\ - \x90\xb5\x18\x01\x12%\n\x1aMessageType_StellarAddress\x10\xd0\x01\x1a\ - \x04\x98\xb5\x18\x01\x12-\n\"MessageType_StellarCreateAccountOp\x10\xd2\ - \x01\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMessageType_StellarPaymentOp\x10\ - \xd3\x01\x1a\x04\x90\xb5\x18\x01\x128\n-MessageType_StellarPathPaymentSt\ - rictReceiveOp\x10\xd4\x01\x1a\x04\x90\xb5\x18\x01\x12/\n$MessageType_Ste\ - llarManageSellOfferOp\x10\xd5\x01\x1a\x04\x90\xb5\x18\x01\x126\n+Message\ - Type_StellarCreatePassiveSellOfferOp\x10\xd6\x01\x1a\x04\x90\xb5\x18\x01\ - \x12*\n\x1fMessageType_StellarSetOptionsOp\x10\xd7\x01\x1a\x04\x90\xb5\ - \x18\x01\x12+\n\x20MessageType_StellarChangeTrustOp\x10\xd8\x01\x1a\x04\ - \x90\xb5\x18\x01\x12*\n\x1fMessageType_StellarAllowTrustOp\x10\xd9\x01\ - \x1a\x04\x90\xb5\x18\x01\x12,\n!MessageType_StellarAccountMergeOp\x10\ - \xda\x01\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_StellarManageData\ - Op\x10\xdc\x01\x1a\x04\x90\xb5\x18\x01\x12,\n!MessageType_StellarBumpSeq\ - uenceOp\x10\xdd\x01\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageType_StellarMa\ - nageBuyOfferOp\x10\xde\x01\x1a\x04\x90\xb5\x18\x01\x125\n*MessageType_St\ - ellarPathPaymentStrictSendOp\x10\xdf\x01\x1a\x04\x90\xb5\x18\x01\x125\n*\ - MessageType_StellarClaimClaimableBalanceOp\x10\xe1\x01\x1a\x04\x90\xb5\ - \x18\x01\x12&\n\x1bMessageType_StellarSignedTx\x10\xe6\x01\x1a\x04\x98\ - \xb5\x18\x01\x12*\n\x1fMessageType_CardanoGetPublicKey\x10\xb1\x02\x1a\ - \x04\x90\xb5\x18\x01\x12'\n\x1cMessageType_CardanoPublicKey\x10\xb2\x02\ - \x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_CardanoGetAddress\x10\xb3\ - \x02\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_CardanoAddress\x10\ - \xb4\x02\x1a\x04\x98\xb5\x18\x01\x12'\n\x1cMessageType_CardanoTxItemAck\ - \x10\xb9\x02\x1a\x04\x98\xb5\x18\x01\x127\n,MessageType_CardanoTxAuxilia\ - ryDataSupplement\x10\xba\x02\x1a\x04\x98\xb5\x18\x01\x12.\n#MessageType_\ - CardanoTxWitnessRequest\x10\xbb\x02\x1a\x04\x90\xb5\x18\x01\x12/\n$Messa\ - geType_CardanoTxWitnessResponse\x10\xbc\x02\x1a\x04\x98\xb5\x18\x01\x12'\ - \n\x1cMessageType_CardanoTxHostAck\x10\xbd\x02\x1a\x04\x90\xb5\x18\x01\ - \x12(\n\x1dMessageType_CardanoTxBodyHash\x10\xbe\x02\x1a\x04\x98\xb5\x18\ - \x01\x12,\n!MessageType_CardanoSignTxFinished\x10\xbf\x02\x1a\x04\x98\ - \xb5\x18\x01\x12(\n\x1dMessageType_CardanoSignTxInit\x10\xc0\x02\x1a\x04\ - \x90\xb5\x18\x01\x12%\n\x1aMessageType_CardanoTxInput\x10\xc1\x02\x1a\ - \x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_CardanoTxOutput\x10\xc2\x02\ - \x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_CardanoAssetGroup\x10\xc3\ - \x02\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_CardanoToken\x10\xc4\ - \x02\x1a\x04\x90\xb5\x18\x01\x12+\n\x20MessageType_CardanoTxCertificate\ - \x10\xc5\x02\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_CardanoTxWith\ - drawal\x10\xc6\x02\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_CardanoTx\ - AuxiliaryData\x10\xc7\x02\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMessageType_\ - CardanoPoolOwner\x10\xc8\x02\x1a\x04\x90\xb5\x18\x01\x121\n&MessageType_\ - CardanoPoolRelayParameters\x10\xc9\x02\x1a\x04\x90\xb5\x18\x01\x121\n&Me\ - ssageType_CardanoGetNativeScriptHash\x10\xca\x02\x1a\x04\x90\xb5\x18\x01\ - \x12.\n#MessageType_CardanoNativeScriptHash\x10\xcb\x02\x1a\x04\x98\xb5\ - \x18\x01\x12$\n\x19MessageType_CardanoTxMint\x10\xcc\x02\x1a\x04\x90\xb5\ - \x18\x01\x12/\n$MessageType_CardanoTxCollateralInput\x10\xcd\x02\x1a\x04\ - \x90\xb5\x18\x01\x12.\n#MessageType_CardanoTxRequiredSigner\x10\xce\x02\ - \x1a\x04\x90\xb5\x18\x01\x120\n%MessageType_CardanoTxInlineDatumChunk\ - \x10\xcf\x02\x1a\x04\x90\xb5\x18\x01\x124\n)MessageType_CardanoTxReferen\ - ceScriptChunk\x10\xd0\x02\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageType_Car\ - danoTxReferenceInput\x10\xd1\x02\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMessa\ - geType_RippleGetAddress\x10\x90\x03\x1a\x04\x90\xb5\x18\x01\x12$\n\x19Me\ - ssageType_RippleAddress\x10\x91\x03\x1a\x04\x98\xb5\x18\x01\x12#\n\x18Me\ - ssageType_RippleSignTx\x10\x92\x03\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMes\ - sageType_RippleSignedTx\x10\x93\x03\x1a\x04\x90\xb5\x18\x01\x123\n(Messa\ - geType_MoneroTransactionInitRequest\x10\xf5\x03\x1a\x04\x98\xb5\x18\x01\ - \x12/\n$MessageType_MoneroTransactionInitAck\x10\xf6\x03\x1a\x04\x98\xb5\ - \x18\x01\x127\n,MessageType_MoneroTransactionSetInputRequest\x10\xf7\x03\ - \x1a\x04\x98\xb5\x18\x01\x123\n(MessageType_MoneroTransactionSetInputAck\ - \x10\xf8\x03\x1a\x04\x98\xb5\x18\x01\x128\n-MessageType_MoneroTransactio\ - nInputViniRequest\x10\xfb\x03\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType\ - _MoneroTransactionInputViniAck\x10\xfc\x03\x1a\x04\x98\xb5\x18\x01\x12;\ - \n0MessageType_MoneroTransactionAllInputsSetRequest\x10\xfd\x03\x1a\x04\ - \x98\xb5\x18\x01\x127\n,MessageType_MoneroTransactionAllInputsSetAck\x10\ - \xfe\x03\x1a\x04\x98\xb5\x18\x01\x128\n-MessageType_MoneroTransactionSet\ - OutputRequest\x10\xff\x03\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType_Mon\ - eroTransactionSetOutputAck\x10\x80\x04\x1a\x04\x98\xb5\x18\x01\x128\n-Me\ - ssageType_MoneroTransactionAllOutSetRequest\x10\x81\x04\x1a\x04\x98\xb5\ - \x18\x01\x124\n)MessageType_MoneroTransactionAllOutSetAck\x10\x82\x04\ - \x1a\x04\x98\xb5\x18\x01\x128\n-MessageType_MoneroTransactionSignInputRe\ - quest\x10\x83\x04\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType_MoneroTrans\ - actionSignInputAck\x10\x84\x04\x1a\x04\x98\xb5\x18\x01\x124\n)MessageTyp\ - e_MoneroTransactionFinalRequest\x10\x85\x04\x1a\x04\x98\xb5\x18\x01\x120\ - \n%MessageType_MoneroTransactionFinalAck\x10\x86\x04\x1a\x04\x98\xb5\x18\ - \x01\x126\n+MessageType_MoneroKeyImageExportInitRequest\x10\x92\x04\x1a\ - \x04\x98\xb5\x18\x01\x122\n'MessageType_MoneroKeyImageExportInitAck\x10\ - \x93\x04\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType_MoneroKeyImageSyncSt\ - epRequest\x10\x94\x04\x1a\x04\x98\xb5\x18\x01\x120\n%MessageType_MoneroK\ - eyImageSyncStepAck\x10\x95\x04\x1a\x04\x98\xb5\x18\x01\x125\n*MessageTyp\ - e_MoneroKeyImageSyncFinalRequest\x10\x96\x04\x1a\x04\x98\xb5\x18\x01\x12\ - 1\n&MessageType_MoneroKeyImageSyncFinalAck\x10\x97\x04\x1a\x04\x98\xb5\ - \x18\x01\x12'\n\x1cMessageType_MoneroGetAddress\x10\x9c\x04\x1a\x04\x90\ - \xb5\x18\x01\x12$\n\x19MessageType_MoneroAddress\x10\x9d\x04\x1a\x04\x98\ - \xb5\x18\x01\x12(\n\x1dMessageType_MoneroGetWatchKey\x10\x9e\x04\x1a\x04\ - \x90\xb5\x18\x01\x12%\n\x1aMessageType_MoneroWatchKey\x10\x9f\x04\x1a\ - \x04\x98\xb5\x18\x01\x12-\n\"MessageType_DebugMoneroDiagRequest\x10\xa2\ - \x04\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_DebugMoneroDiagAck\ - \x10\xa3\x04\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_MoneroGetTxKeyRe\ - quest\x10\xa6\x04\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_MoneroGe\ - tTxKeyAck\x10\xa7\x04\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType_MoneroL\ - iveRefreshStartRequest\x10\xa8\x04\x1a\x04\x90\xb5\x18\x01\x120\n%Messag\ - eType_MoneroLiveRefreshStartAck\x10\xa9\x04\x1a\x04\x98\xb5\x18\x01\x123\ - \n(MessageType_MoneroLiveRefreshStepRequest\x10\xaa\x04\x1a\x04\x90\xb5\ - \x18\x01\x12/\n$MessageType_MoneroLiveRefreshStepAck\x10\xab\x04\x1a\x04\ - \x98\xb5\x18\x01\x124\n)MessageType_MoneroLiveRefreshFinalRequest\x10\ - \xac\x04\x1a\x04\x90\xb5\x18\x01\x120\n%MessageType_MoneroLiveRefreshFin\ - alAck\x10\xad\x04\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_EosGetPu\ - blicKey\x10\xd8\x04\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_EosPub\ - licKey\x10\xd9\x04\x1a\x04\x98\xb5\x18\x01\x12\x20\n\x15MessageType_EosS\ - ignTx\x10\xda\x04\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_EosTxAct\ - ionRequest\x10\xdb\x04\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_Eos\ - TxActionAck\x10\xdc\x04\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_E\ - osSignedTx\x10\xdd\x04\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_Bin\ - anceGetAddress\x10\xbc\x05\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType\ - _BinanceAddress\x10\xbd\x05\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageTyp\ - e_BinanceGetPublicKey\x10\xbe\x05\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMess\ - ageType_BinancePublicKey\x10\xbf\x05\x1a\x04\x98\xb5\x18\x01\x12$\n\x19M\ - essageType_BinanceSignTx\x10\xc0\x05\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cM\ - essageType_BinanceTxRequest\x10\xc1\x05\x1a\x04\x98\xb5\x18\x01\x12)\n\ - \x1eMessageType_BinanceTransferMsg\x10\xc2\x05\x1a\x04\x90\xb5\x18\x01\ - \x12&\n\x1bMessageType_BinanceOrderMsg\x10\xc3\x05\x1a\x04\x90\xb5\x18\ - \x01\x12'\n\x1cMessageType_BinanceCancelMsg\x10\xc4\x05\x1a\x04\x90\xb5\ - \x18\x01\x12&\n\x1bMessageType_BinanceSignedTx\x10\xc5\x05\x1a\x04\x98\ - \xb5\x18\x01\x126\n+MessageType_WebAuthnListResidentCredentials\x10\xa0\ - \x06\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_WebAuthnCredentials\ - \x10\xa1\x06\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType_WebAuthnAddResid\ - entCredential\x10\xa2\x06\x1a\x04\x90\xb5\x18\x01\x127\n,MessageType_Web\ - AuthnRemoveResidentCredential\x10\xa3\x06\x1a\x04\x90\xb5\x18\x01\x12)\n\ - \x1eMessageType_SolanaGetPublicKey\x10\x84\x07\x1a\x04\x90\xb5\x18\x01\ - \x12&\n\x1bMessageType_SolanaPublicKey\x10\x85\x07\x1a\x04\x98\xb5\x18\ - \x01\x12'\n\x1cMessageType_SolanaGetAddress\x10\x86\x07\x1a\x04\x90\xb5\ - \x18\x01\x12$\n\x19MessageType_SolanaAddress\x10\x87\x07\x1a\x04\x98\xb5\ - \x18\x01\x12#\n\x18MessageType_SolanaSignTx\x10\x88\x07\x1a\x04\x90\xb5\ - \x18\x01\x12(\n\x1dMessageType_SolanaTxSignature\x10\x89\x07\x1a\x04\x98\ - \xb5\x18\x01\x12)\n\x1eMessageType_BenchmarkListNames\x10\x8cG\x1a\x04\ - \x80\xa6\x1d\x01\x12%\n\x1aMessageType_BenchmarkNames\x10\x8dG\x1a\x04\ - \x80\xa6\x1d\x01\x12#\n\x18MessageType_BenchmarkRun\x10\x8eG\x1a\x04\x80\ - \xa6\x1d\x01\x12&\n\x1bMessageType_BenchmarkResult\x10\x8fG\x1a\x04\x80\ - \xa6\x1d\x01\x1a\x04\xc8\xf3\x18\x01\"\x04\x08Z\x10\\\"\x04\x08G\x10J\"\ - \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\"\x06\ - \x08\xe8\x07\x10\xcb\x08B8\n#com.satoshilabs.trezor.lib.protobufB\rTrezo\ - rMessage\x80\xa6\x1d\x01\ + \x122\n#MessageType_DebugLinkGetPairingInfo\x10\xb1F\x1a\x08\x80\xa6\x1d\ + \x01\xa0\xb5\x18\x01\x12/\n\x20MessageType_DebugLinkPairingInfo\x10\xb2F\ + \x1a\x08\x80\xa6\x1d\x01\xa8\xb5\x18\x01\x12+\n\x20MessageType_EthereumG\ + etPublicKey\x10\xc2\x03\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_Et\ + hereumPublicKey\x10\xc3\x03\x1a\x04\x98\xb5\x18\x01\x12(\n\x1eMessageTyp\ + e_EthereumGetAddress\x108\x1a\x04\x90\xb5\x18\x01\x12%\n\x1bMessageType_\ + EthereumAddress\x109\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_Ether\ + eumSignTx\x10:\x1a\x04\x90\xb5\x18\x01\x12,\n!MessageType_EthereumSignTx\ + EIP1559\x10\xc4\x03\x1a\x04\x90\xb5\x18\x01\x12'\n\x1dMessageType_Ethere\ + umTxRequest\x10;\x1a\x04\x98\xb5\x18\x01\x12#\n\x19MessageType_EthereumT\ + xAck\x10<\x1a\x04\x90\xb5\x18\x01\x12)\n\x1fMessageType_EthereumSignMess\ + age\x10@\x1a\x04\x90\xb5\x18\x01\x12+\n!MessageType_EthereumVerifyMessag\ + e\x10A\x1a\x04\x90\xb5\x18\x01\x12.\n$MessageType_EthereumMessageSignatu\ + re\x10B\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_EthereumSignTypedData\ + \x10\xd0\x03\x1a\x04\x90\xb5\x18\x01\x125\n*MessageType_EthereumTypedDat\ + aStructRequest\x10\xd1\x03\x1a\x04\x98\xb5\x18\x01\x121\n&MessageType_Et\ + hereumTypedDataStructAck\x10\xd2\x03\x1a\x04\x90\xb5\x18\x01\x124\n)Mess\ + ageType_EthereumTypedDataValueRequest\x10\xd3\x03\x1a\x04\x98\xb5\x18\ + \x01\x120\n%MessageType_EthereumTypedDataValueAck\x10\xd4\x03\x1a\x04\ + \x90\xb5\x18\x01\x121\n&MessageType_EthereumTypedDataSignature\x10\xd5\ + \x03\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_EthereumSignTypedHash\ + \x10\xd6\x03\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_NEMGetAddress\ + \x10C\x1a\x04\x90\xb5\x18\x01\x12\x20\n\x16MessageType_NEMAddress\x10D\ + \x1a\x04\x98\xb5\x18\x01\x12\x1f\n\x15MessageType_NEMSignTx\x10E\x1a\x04\ + \x90\xb5\x18\x01\x12!\n\x17MessageType_NEMSignedTx\x10F\x1a\x04\x98\xb5\ + \x18\x01\x12'\n\x1dMessageType_NEMDecryptMessage\x10K\x1a\x04\x90\xb5\ + \x18\x01\x12)\n\x1fMessageType_NEMDecryptedMessage\x10L\x1a\x04\x98\xb5\ + \x18\x01\x12&\n\x1bMessageType_TezosGetAddress\x10\x96\x01\x1a\x04\x90\ + \xb5\x18\x01\x12#\n\x18MessageType_TezosAddress\x10\x97\x01\x1a\x04\x98\ + \xb5\x18\x01\x12\"\n\x17MessageType_TezosSignTx\x10\x98\x01\x1a\x04\x90\ + \xb5\x18\x01\x12$\n\x19MessageType_TezosSignedTx\x10\x99\x01\x1a\x04\x98\ + \xb5\x18\x01\x12(\n\x1dMessageType_TezosGetPublicKey\x10\x9a\x01\x1a\x04\ + \x90\xb5\x18\x01\x12%\n\x1aMessageType_TezosPublicKey\x10\x9b\x01\x1a\ + \x04\x98\xb5\x18\x01\x12$\n\x19MessageType_StellarSignTx\x10\xca\x01\x1a\ + \x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_StellarTxOpRequest\x10\xcb\ + \x01\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_StellarGetAddress\x10\ + \xcf\x01\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_StellarAddress\ + \x10\xd0\x01\x1a\x04\x98\xb5\x18\x01\x12-\n\"MessageType_StellarCreateAc\ + countOp\x10\xd2\x01\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMessageType_Stella\ + rPaymentOp\x10\xd3\x01\x1a\x04\x90\xb5\x18\x01\x128\n-MessageType_Stella\ + rPathPaymentStrictReceiveOp\x10\xd4\x01\x1a\x04\x90\xb5\x18\x01\x12/\n$M\ + essageType_StellarManageSellOfferOp\x10\xd5\x01\x1a\x04\x90\xb5\x18\x01\ + \x126\n+MessageType_StellarCreatePassiveSellOfferOp\x10\xd6\x01\x1a\x04\ + \x90\xb5\x18\x01\x12*\n\x1fMessageType_StellarSetOptionsOp\x10\xd7\x01\ + \x1a\x04\x90\xb5\x18\x01\x12+\n\x20MessageType_StellarChangeTrustOp\x10\ + \xd8\x01\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_StellarAllowTrust\ + Op\x10\xd9\x01\x1a\x04\x90\xb5\x18\x01\x12,\n!MessageType_StellarAccount\ + MergeOp\x10\xda\x01\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_Stella\ + rManageDataOp\x10\xdc\x01\x1a\x04\x90\xb5\x18\x01\x12,\n!MessageType_Ste\ + llarBumpSequenceOp\x10\xdd\x01\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageTyp\ + e_StellarManageBuyOfferOp\x10\xde\x01\x1a\x04\x90\xb5\x18\x01\x125\n*Mes\ + sageType_StellarPathPaymentStrictSendOp\x10\xdf\x01\x1a\x04\x90\xb5\x18\ + \x01\x125\n*MessageType_StellarClaimClaimableBalanceOp\x10\xe1\x01\x1a\ + \x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_StellarSignedTx\x10\xe6\x01\ + \x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_CardanoGetPublicKey\x10\ + \xb1\x02\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMessageType_CardanoPublicKey\ + \x10\xb2\x02\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_CardanoGetAdd\ + ress\x10\xb3\x02\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_CardanoAd\ + dress\x10\xb4\x02\x1a\x04\x98\xb5\x18\x01\x12'\n\x1cMessageType_CardanoT\ + xItemAck\x10\xb9\x02\x1a\x04\x98\xb5\x18\x01\x127\n,MessageType_CardanoT\ + xAuxiliaryDataSupplement\x10\xba\x02\x1a\x04\x98\xb5\x18\x01\x12.\n#Mess\ + ageType_CardanoTxWitnessRequest\x10\xbb\x02\x1a\x04\x90\xb5\x18\x01\x12/\ + \n$MessageType_CardanoTxWitnessResponse\x10\xbc\x02\x1a\x04\x98\xb5\x18\ + \x01\x12'\n\x1cMessageType_CardanoTxHostAck\x10\xbd\x02\x1a\x04\x90\xb5\ + \x18\x01\x12(\n\x1dMessageType_CardanoTxBodyHash\x10\xbe\x02\x1a\x04\x98\ + \xb5\x18\x01\x12,\n!MessageType_CardanoSignTxFinished\x10\xbf\x02\x1a\ + \x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_CardanoSignTxInit\x10\xc0\x02\ + \x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_CardanoTxInput\x10\xc1\ + \x02\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_CardanoTxOutput\x10\ + \xc2\x02\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_CardanoAssetGroup\ + \x10\xc3\x02\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_CardanoToken\ + \x10\xc4\x02\x1a\x04\x90\xb5\x18\x01\x12+\n\x20MessageType_CardanoTxCert\ + ificate\x10\xc5\x02\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_Cardan\ + oTxWithdrawal\x10\xc6\x02\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_Ca\ + rdanoTxAuxiliaryData\x10\xc7\x02\x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMessa\ + geType_CardanoPoolOwner\x10\xc8\x02\x1a\x04\x90\xb5\x18\x01\x121\n&Messa\ + geType_CardanoPoolRelayParameters\x10\xc9\x02\x1a\x04\x90\xb5\x18\x01\ + \x121\n&MessageType_CardanoGetNativeScriptHash\x10\xca\x02\x1a\x04\x90\ + \xb5\x18\x01\x12.\n#MessageType_CardanoNativeScriptHash\x10\xcb\x02\x1a\ + \x04\x98\xb5\x18\x01\x12$\n\x19MessageType_CardanoTxMint\x10\xcc\x02\x1a\ + \x04\x90\xb5\x18\x01\x12/\n$MessageType_CardanoTxCollateralInput\x10\xcd\ + \x02\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageType_CardanoTxRequiredSigner\ + \x10\xce\x02\x1a\x04\x90\xb5\x18\x01\x120\n%MessageType_CardanoTxInlineD\ + atumChunk\x10\xcf\x02\x1a\x04\x90\xb5\x18\x01\x124\n)MessageType_Cardano\ + TxReferenceScriptChunk\x10\xd0\x02\x1a\x04\x90\xb5\x18\x01\x12.\n#Messag\ + eType_CardanoTxReferenceInput\x10\xd1\x02\x1a\x04\x90\xb5\x18\x01\x12'\n\ + \x1cMessageType_RippleGetAddress\x10\x90\x03\x1a\x04\x90\xb5\x18\x01\x12\ + $\n\x19MessageType_RippleAddress\x10\x91\x03\x1a\x04\x98\xb5\x18\x01\x12\ + #\n\x18MessageType_RippleSignTx\x10\x92\x03\x1a\x04\x90\xb5\x18\x01\x12%\ + \n\x1aMessageType_RippleSignedTx\x10\x93\x03\x1a\x04\x90\xb5\x18\x01\x12\ + 3\n(MessageType_MoneroTransactionInitRequest\x10\xf5\x03\x1a\x04\x98\xb5\ + \x18\x01\x12/\n$MessageType_MoneroTransactionInitAck\x10\xf6\x03\x1a\x04\ + \x98\xb5\x18\x01\x127\n,MessageType_MoneroTransactionSetInputRequest\x10\ + \xf7\x03\x1a\x04\x98\xb5\x18\x01\x123\n(MessageType_MoneroTransactionSet\ + InputAck\x10\xf8\x03\x1a\x04\x98\xb5\x18\x01\x128\n-MessageType_MoneroTr\ + ansactionInputViniRequest\x10\xfb\x03\x1a\x04\x98\xb5\x18\x01\x124\n)Mes\ + sageType_MoneroTransactionInputViniAck\x10\xfc\x03\x1a\x04\x98\xb5\x18\ + \x01\x12;\n0MessageType_MoneroTransactionAllInputsSetRequest\x10\xfd\x03\ + \x1a\x04\x98\xb5\x18\x01\x127\n,MessageType_MoneroTransactionAllInputsSe\ + tAck\x10\xfe\x03\x1a\x04\x98\xb5\x18\x01\x128\n-MessageType_MoneroTransa\ + ctionSetOutputRequest\x10\xff\x03\x1a\x04\x98\xb5\x18\x01\x124\n)Message\ + Type_MoneroTransactionSetOutputAck\x10\x80\x04\x1a\x04\x98\xb5\x18\x01\ + \x128\n-MessageType_MoneroTransactionAllOutSetRequest\x10\x81\x04\x1a\ + \x04\x98\xb5\x18\x01\x124\n)MessageType_MoneroTransactionAllOutSetAck\ + \x10\x82\x04\x1a\x04\x98\xb5\x18\x01\x128\n-MessageType_MoneroTransactio\ + nSignInputRequest\x10\x83\x04\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType\ + _MoneroTransactionSignInputAck\x10\x84\x04\x1a\x04\x98\xb5\x18\x01\x124\ + \n)MessageType_MoneroTransactionFinalRequest\x10\x85\x04\x1a\x04\x98\xb5\ + \x18\x01\x120\n%MessageType_MoneroTransactionFinalAck\x10\x86\x04\x1a\ + \x04\x98\xb5\x18\x01\x126\n+MessageType_MoneroKeyImageExportInitRequest\ + \x10\x92\x04\x1a\x04\x98\xb5\x18\x01\x122\n'MessageType_MoneroKeyImageEx\ + portInitAck\x10\x93\x04\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType_Moner\ + oKeyImageSyncStepRequest\x10\x94\x04\x1a\x04\x98\xb5\x18\x01\x120\n%Mess\ + ageType_MoneroKeyImageSyncStepAck\x10\x95\x04\x1a\x04\x98\xb5\x18\x01\ + \x125\n*MessageType_MoneroKeyImageSyncFinalRequest\x10\x96\x04\x1a\x04\ + \x98\xb5\x18\x01\x121\n&MessageType_MoneroKeyImageSyncFinalAck\x10\x97\ + \x04\x1a\x04\x98\xb5\x18\x01\x12'\n\x1cMessageType_MoneroGetAddress\x10\ + \x9c\x04\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_MoneroAddress\x10\ + \x9d\x04\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_MoneroGetWatchKey\ + \x10\x9e\x04\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_MoneroWatchKe\ + y\x10\x9f\x04\x1a\x04\x98\xb5\x18\x01\x12-\n\"MessageType_DebugMoneroDia\ + gRequest\x10\xa2\x04\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_Debug\ + MoneroDiagAck\x10\xa3\x04\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_Mon\ + eroGetTxKeyRequest\x10\xa6\x04\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessage\ + Type_MoneroGetTxKeyAck\x10\xa7\x04\x1a\x04\x98\xb5\x18\x01\x124\n)Messag\ + eType_MoneroLiveRefreshStartRequest\x10\xa8\x04\x1a\x04\x90\xb5\x18\x01\ + \x120\n%MessageType_MoneroLiveRefreshStartAck\x10\xa9\x04\x1a\x04\x98\ + \xb5\x18\x01\x123\n(MessageType_MoneroLiveRefreshStepRequest\x10\xaa\x04\ + \x1a\x04\x90\xb5\x18\x01\x12/\n$MessageType_MoneroLiveRefreshStepAck\x10\ + \xab\x04\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType_MoneroLiveRefreshFin\ + alRequest\x10\xac\x04\x1a\x04\x90\xb5\x18\x01\x120\n%MessageType_MoneroL\ + iveRefreshFinalAck\x10\xad\x04\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessage\ + Type_EosGetPublicKey\x10\xd8\x04\x1a\x04\x90\xb5\x18\x01\x12#\n\x18Messa\ + geType_EosPublicKey\x10\xd9\x04\x1a\x04\x98\xb5\x18\x01\x12\x20\n\x15Mes\ + sageType_EosSignTx\x10\xda\x04\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessage\ + Type_EosTxActionRequest\x10\xdb\x04\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMe\ + ssageType_EosTxActionAck\x10\xdc\x04\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17\ + MessageType_EosSignedTx\x10\xdd\x04\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMe\ + ssageType_BinanceGetAddress\x10\xbc\x05\x1a\x04\x90\xb5\x18\x01\x12%\n\ + \x1aMessageType_BinanceAddress\x10\xbd\x05\x1a\x04\x98\xb5\x18\x01\x12*\ + \n\x1fMessageType_BinanceGetPublicKey\x10\xbe\x05\x1a\x04\x90\xb5\x18\ + \x01\x12'\n\x1cMessageType_BinancePublicKey\x10\xbf\x05\x1a\x04\x98\xb5\ + \x18\x01\x12$\n\x19MessageType_BinanceSignTx\x10\xc0\x05\x1a\x04\x90\xb5\ + \x18\x01\x12'\n\x1cMessageType_BinanceTxRequest\x10\xc1\x05\x1a\x04\x98\ + \xb5\x18\x01\x12)\n\x1eMessageType_BinanceTransferMsg\x10\xc2\x05\x1a\ + \x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_BinanceOrderMsg\x10\xc3\x05\ + \x1a\x04\x90\xb5\x18\x01\x12'\n\x1cMessageType_BinanceCancelMsg\x10\xc4\ + \x05\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_BinanceSignedTx\x10\ + \xc5\x05\x1a\x04\x98\xb5\x18\x01\x126\n+MessageType_WebAuthnListResident\ + Credentials\x10\xa0\x06\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_We\ + bAuthnCredentials\x10\xa1\x06\x1a\x04\x98\xb5\x18\x01\x124\n)MessageType\ + _WebAuthnAddResidentCredential\x10\xa2\x06\x1a\x04\x90\xb5\x18\x01\x127\ + \n,MessageType_WebAuthnRemoveResidentCredential\x10\xa3\x06\x1a\x04\x90\ + \xb5\x18\x01\x12)\n\x1eMessageType_SolanaGetPublicKey\x10\x84\x07\x1a\ + \x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_SolanaPublicKey\x10\x85\x07\ + \x1a\x04\x98\xb5\x18\x01\x12'\n\x1cMessageType_SolanaGetAddress\x10\x86\ + \x07\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_SolanaAddress\x10\x87\ + \x07\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_SolanaSignTx\x10\x88\ + \x07\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_SolanaTxSignature\x10\ + \x89\x07\x1a\x04\x98\xb5\x18\x01\x12)\n\x1eMessageType_BenchmarkListName\ + s\x10\x8cG\x1a\x04\x80\xa6\x1d\x01\x12%\n\x1aMessageType_BenchmarkNames\ + \x10\x8dG\x1a\x04\x80\xa6\x1d\x01\x12#\n\x18MessageType_BenchmarkRun\x10\ + \x8eG\x1a\x04\x80\xa6\x1d\x01\x12&\n\x1bMessageType_BenchmarkResult\x10\ + \x8fG\x1a\x04\x80\xa6\x1d\x01\x1a\x04\xc8\xf3\x18\x01\"\x04\x08Z\x10\\\"\ + \x04\x08G\x10J\"\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\"\x06\x08\xe8\x07\x10\xcb\x08B8\n#com.satoshilabs.trezor.lib.pr\ + otobufB\rTrezorMessage\x80\xa6\x1d\x01\ "; /// `FileDescriptorProto` object which was a source for this generated file diff --git a/rust/trezor-client/src/protos/generated/messages_common.rs b/rust/trezor-client/src/protos/generated/messages_common.rs index 4fd72b22f0..55b796f7b4 100644 --- a/rust/trezor-client/src/protos/generated/messages_common.rs +++ b/rust/trezor-client/src/protos/generated/messages_common.rs @@ -414,6 +414,14 @@ pub mod failure { Failure_WipeCodeMismatch = 13, // @@protoc_insertion_point(enum_value:hw.trezor.messages.common.Failure.FailureType.Failure_InvalidSession) Failure_InvalidSession = 14, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.common.Failure.FailureType.Failure_ThpUnallocatedSession) + Failure_ThpUnallocatedSession = 15, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.common.Failure.FailureType.Failure_InvalidProtocol) + Failure_InvalidProtocol = 16, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.common.Failure.FailureType.Failure_BufferError) + Failure_BufferError = 17, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.common.Failure.FailureType.Failure_DeviceIsBusy) + Failure_DeviceIsBusy = 18, // @@protoc_insertion_point(enum_value:hw.trezor.messages.common.Failure.FailureType.Failure_FirmwareError) Failure_FirmwareError = 99, } @@ -441,6 +449,10 @@ pub mod failure { 12 => ::std::option::Option::Some(FailureType::Failure_PinMismatch), 13 => ::std::option::Option::Some(FailureType::Failure_WipeCodeMismatch), 14 => ::std::option::Option::Some(FailureType::Failure_InvalidSession), + 15 => ::std::option::Option::Some(FailureType::Failure_ThpUnallocatedSession), + 16 => ::std::option::Option::Some(FailureType::Failure_InvalidProtocol), + 17 => ::std::option::Option::Some(FailureType::Failure_BufferError), + 18 => ::std::option::Option::Some(FailureType::Failure_DeviceIsBusy), 99 => ::std::option::Option::Some(FailureType::Failure_FirmwareError), _ => ::std::option::Option::None } @@ -462,6 +474,10 @@ pub mod failure { "Failure_PinMismatch" => ::std::option::Option::Some(FailureType::Failure_PinMismatch), "Failure_WipeCodeMismatch" => ::std::option::Option::Some(FailureType::Failure_WipeCodeMismatch), "Failure_InvalidSession" => ::std::option::Option::Some(FailureType::Failure_InvalidSession), + "Failure_ThpUnallocatedSession" => ::std::option::Option::Some(FailureType::Failure_ThpUnallocatedSession), + "Failure_InvalidProtocol" => ::std::option::Option::Some(FailureType::Failure_InvalidProtocol), + "Failure_BufferError" => ::std::option::Option::Some(FailureType::Failure_BufferError), + "Failure_DeviceIsBusy" => ::std::option::Option::Some(FailureType::Failure_DeviceIsBusy), "Failure_FirmwareError" => ::std::option::Option::Some(FailureType::Failure_FirmwareError), _ => ::std::option::Option::None } @@ -482,6 +498,10 @@ pub mod failure { FailureType::Failure_PinMismatch, FailureType::Failure_WipeCodeMismatch, FailureType::Failure_InvalidSession, + FailureType::Failure_ThpUnallocatedSession, + FailureType::Failure_InvalidProtocol, + FailureType::Failure_BufferError, + FailureType::Failure_DeviceIsBusy, FailureType::Failure_FirmwareError, ]; } @@ -508,7 +528,11 @@ pub mod failure { FailureType::Failure_PinMismatch => 11, FailureType::Failure_WipeCodeMismatch => 12, FailureType::Failure_InvalidSession => 13, - FailureType::Failure_FirmwareError => 14, + FailureType::Failure_ThpUnallocatedSession => 14, + FailureType::Failure_InvalidProtocol => 15, + FailureType::Failure_BufferError => 16, + FailureType::Failure_DeviceIsBusy => 17, + FailureType::Failure_FirmwareError => 18, }; Self::enum_descriptor().value_by_index(index) } @@ -2481,9 +2505,9 @@ impl ::protobuf::reflect::ProtobufValue for HDNodeType { static file_descriptor_proto_data: &'static [u8] = b"\ \n\x15messages-common.proto\x12\x19hw.trezor.messages.common\x1a\roption\ s.proto\"%\n\x07Success\x12\x1a\n\x07message\x18\x01\x20\x01(\t:\0R\x07m\ - essage\"\x8f\x04\n\x07Failure\x12B\n\x04code\x18\x01\x20\x01(\x0e2..hw.t\ + essage\"\x82\x05\n\x07Failure\x12B\n\x04code\x18\x01\x20\x01(\x0e2..hw.t\ rezor.messages.common.Failure.FailureTypeR\x04code\x12\x18\n\x07message\ - \x18\x02\x20\x01(\tR\x07message\"\xa5\x03\n\x0bFailureType\x12\x1d\n\x19\ + \x18\x02\x20\x01(\tR\x07message\"\x98\x04\n\x0bFailureType\x12\x1d\n\x19\ Failure_UnexpectedMessage\x10\x01\x12\x1a\n\x16Failure_ButtonExpected\ \x10\x02\x12\x15\n\x11Failure_DataError\x10\x03\x12\x1b\n\x17Failure_Act\ ionCancelled\x10\x04\x12\x17\n\x13Failure_PinExpected\x10\x05\x12\x18\n\ @@ -2492,44 +2516,46 @@ static file_descriptor_proto_data: &'static [u8] = b"\ essError\x10\t\x12\x1a\n\x16Failure_NotEnoughFunds\x10\n\x12\x1a\n\x16Fa\ ilure_NotInitialized\x10\x0b\x12\x17\n\x13Failure_PinMismatch\x10\x0c\ \x12\x1c\n\x18Failure_WipeCodeMismatch\x10\r\x12\x1a\n\x16Failure_Invali\ - dSession\x10\x0e\x12\x19\n\x15Failure_FirmwareError\x10c\"\xab\x06\n\rBu\ - ttonRequest\x12N\n\x04code\x18\x01\x20\x01(\x0e2:.hw.trezor.messages.com\ - mon.ButtonRequest.ButtonRequestTypeR\x04code\x12\x14\n\x05pages\x18\x02\ - \x20\x01(\rR\x05pages\x12\x12\n\x04name\x18\x04\x20\x01(\tR\x04name\"\ - \x99\x05\n\x11ButtonRequestType\x12\x17\n\x13ButtonRequest_Other\x10\x01\ - \x12\"\n\x1eButtonRequest_FeeOverThreshold\x10\x02\x12\x1f\n\x1bButtonRe\ - quest_ConfirmOutput\x10\x03\x12\x1d\n\x19ButtonRequest_ResetDevice\x10\ - \x04\x12\x1d\n\x19ButtonRequest_ConfirmWord\x10\x05\x12\x1c\n\x18ButtonR\ - equest_WipeDevice\x10\x06\x12\x1d\n\x19ButtonRequest_ProtectCall\x10\x07\ - \x12\x18\n\x14ButtonRequest_SignTx\x10\x08\x12\x1f\n\x1bButtonRequest_Fi\ - rmwareCheck\x10\t\x12\x19\n\x15ButtonRequest_Address\x10\n\x12\x1b\n\x17\ - ButtonRequest_PublicKey\x10\x0b\x12#\n\x1fButtonRequest_MnemonicWordCoun\ - t\x10\x0c\x12\x1f\n\x1bButtonRequest_MnemonicInput\x10\r\x120\n(_Depreca\ - ted_ButtonRequest_PassphraseType\x10\x0e\x1a\x02\x08\x01\x12'\n#ButtonRe\ - quest_UnknownDerivationPath\x10\x0f\x12\"\n\x1eButtonRequest_RecoveryHom\ - epage\x10\x10\x12\x19\n\x15ButtonRequest_Success\x10\x11\x12\x19\n\x15Bu\ - ttonRequest_Warning\x10\x12\x12!\n\x1dButtonRequest_PassphraseEntry\x10\ - \x13\x12\x1a\n\x16ButtonRequest_PinEntry\x10\x14J\x04\x08\x03\x10\x04\"\ - \x0b\n\tButtonAck\"\xbb\x02\n\x10PinMatrixRequest\x12T\n\x04type\x18\x01\ - \x20\x01(\x0e2@.hw.trezor.messages.common.PinMatrixRequest.PinMatrixRequ\ - estTypeR\x04type\"\xd0\x01\n\x14PinMatrixRequestType\x12\x20\n\x1cPinMat\ - rixRequestType_Current\x10\x01\x12!\n\x1dPinMatrixRequestType_NewFirst\ - \x10\x02\x12\"\n\x1ePinMatrixRequestType_NewSecond\x10\x03\x12&\n\"PinMa\ - trixRequestType_WipeCodeFirst\x10\x04\x12'\n#PinMatrixRequestType_WipeCo\ - deSecond\x10\x05\"\x20\n\x0cPinMatrixAck\x12\x10\n\x03pin\x18\x01\x20\ - \x02(\tR\x03pin\"5\n\x11PassphraseRequest\x12\x20\n\n_on_device\x18\x01\ - \x20\x01(\x08R\x08OnDeviceB\x02\x18\x01\"g\n\rPassphraseAck\x12\x1e\n\np\ - assphrase\x18\x01\x20\x01(\tR\npassphrase\x12\x19\n\x06_state\x18\x02\ - \x20\x01(\x0cR\x05StateB\x02\x18\x01\x12\x1b\n\ton_device\x18\x03\x20\ - \x01(\x08R\x08onDevice\"=\n!Deprecated_PassphraseStateRequest\x12\x14\n\ - \x05state\x18\x01\x20\x01(\x0cR\x05state:\x02\x18\x01\"#\n\x1dDeprecated\ - _PassphraseStateAck:\x02\x18\x01\"\xc0\x01\n\nHDNodeType\x12\x14\n\x05de\ - pth\x18\x01\x20\x02(\rR\x05depth\x12\x20\n\x0bfingerprint\x18\x02\x20\ - \x02(\rR\x0bfingerprint\x12\x1b\n\tchild_num\x18\x03\x20\x02(\rR\x08chil\ - dNum\x12\x1d\n\nchain_code\x18\x04\x20\x02(\x0cR\tchainCode\x12\x1f\n\ - \x0bprivate_key\x18\x05\x20\x01(\x0cR\nprivateKey\x12\x1d\n\npublic_key\ - \x18\x06\x20\x02(\x0cR\tpublicKeyB>\n#com.satoshilabs.trezor.lib.protobu\ - fB\x13TrezorMessageCommon\x80\xa6\x1d\x01\ + dSession\x10\x0e\x12!\n\x1dFailure_ThpUnallocatedSession\x10\x0f\x12\x1b\ + \n\x17Failure_InvalidProtocol\x10\x10\x12\x17\n\x13Failure_BufferError\ + \x10\x11\x12\x18\n\x14Failure_DeviceIsBusy\x10\x12\x12\x19\n\x15Failure_\ + FirmwareError\x10c\"\xab\x06\n\rButtonRequest\x12N\n\x04code\x18\x01\x20\ + \x01(\x0e2:.hw.trezor.messages.common.ButtonRequest.ButtonRequestTypeR\ + \x04code\x12\x14\n\x05pages\x18\x02\x20\x01(\rR\x05pages\x12\x12\n\x04na\ + me\x18\x04\x20\x01(\tR\x04name\"\x99\x05\n\x11ButtonRequestType\x12\x17\ + \n\x13ButtonRequest_Other\x10\x01\x12\"\n\x1eButtonRequest_FeeOverThresh\ + old\x10\x02\x12\x1f\n\x1bButtonRequest_ConfirmOutput\x10\x03\x12\x1d\n\ + \x19ButtonRequest_ResetDevice\x10\x04\x12\x1d\n\x19ButtonRequest_Confirm\ + Word\x10\x05\x12\x1c\n\x18ButtonRequest_WipeDevice\x10\x06\x12\x1d\n\x19\ + ButtonRequest_ProtectCall\x10\x07\x12\x18\n\x14ButtonRequest_SignTx\x10\ + \x08\x12\x1f\n\x1bButtonRequest_FirmwareCheck\x10\t\x12\x19\n\x15ButtonR\ + equest_Address\x10\n\x12\x1b\n\x17ButtonRequest_PublicKey\x10\x0b\x12#\n\ + \x1fButtonRequest_MnemonicWordCount\x10\x0c\x12\x1f\n\x1bButtonRequest_M\ + nemonicInput\x10\r\x120\n(_Deprecated_ButtonRequest_PassphraseType\x10\ + \x0e\x1a\x02\x08\x01\x12'\n#ButtonRequest_UnknownDerivationPath\x10\x0f\ + \x12\"\n\x1eButtonRequest_RecoveryHomepage\x10\x10\x12\x19\n\x15ButtonRe\ + quest_Success\x10\x11\x12\x19\n\x15ButtonRequest_Warning\x10\x12\x12!\n\ + \x1dButtonRequest_PassphraseEntry\x10\x13\x12\x1a\n\x16ButtonRequest_Pin\ + Entry\x10\x14J\x04\x08\x03\x10\x04\"\x0b\n\tButtonAck\"\xbb\x02\n\x10Pin\ + MatrixRequest\x12T\n\x04type\x18\x01\x20\x01(\x0e2@.hw.trezor.messages.c\ + ommon.PinMatrixRequest.PinMatrixRequestTypeR\x04type\"\xd0\x01\n\x14PinM\ + atrixRequestType\x12\x20\n\x1cPinMatrixRequestType_Current\x10\x01\x12!\ + \n\x1dPinMatrixRequestType_NewFirst\x10\x02\x12\"\n\x1ePinMatrixRequestT\ + ype_NewSecond\x10\x03\x12&\n\"PinMatrixRequestType_WipeCodeFirst\x10\x04\ + \x12'\n#PinMatrixRequestType_WipeCodeSecond\x10\x05\"\x20\n\x0cPinMatrix\ + Ack\x12\x10\n\x03pin\x18\x01\x20\x02(\tR\x03pin\"5\n\x11PassphraseReques\ + t\x12\x20\n\n_on_device\x18\x01\x20\x01(\x08R\x08OnDeviceB\x02\x18\x01\"\ + g\n\rPassphraseAck\x12\x1e\n\npassphrase\x18\x01\x20\x01(\tR\npassphrase\ + \x12\x19\n\x06_state\x18\x02\x20\x01(\x0cR\x05StateB\x02\x18\x01\x12\x1b\ + \n\ton_device\x18\x03\x20\x01(\x08R\x08onDevice\"=\n!Deprecated_Passphra\ + seStateRequest\x12\x14\n\x05state\x18\x01\x20\x01(\x0cR\x05state:\x02\ + \x18\x01\"#\n\x1dDeprecated_PassphraseStateAck:\x02\x18\x01\"\xc0\x01\n\ + \nHDNodeType\x12\x14\n\x05depth\x18\x01\x20\x02(\rR\x05depth\x12\x20\n\ + \x0bfingerprint\x18\x02\x20\x02(\rR\x0bfingerprint\x12\x1b\n\tchild_num\ + \x18\x03\x20\x02(\rR\x08childNum\x12\x1d\n\nchain_code\x18\x04\x20\x02(\ + \x0cR\tchainCode\x12\x1f\n\x0bprivate_key\x18\x05\x20\x01(\x0cR\nprivate\ + Key\x12\x1d\n\npublic_key\x18\x06\x20\x02(\x0cR\tpublicKeyB>\n#com.satos\ + hilabs.trezor.lib.protobufB\x13TrezorMessageCommon\x80\xa6\x1d\x01\ "; /// `FileDescriptorProto` object which was a source for this generated file diff --git a/rust/trezor-client/src/protos/generated/messages_debug.rs b/rust/trezor-client/src/protos/generated/messages_debug.rs index 58328f5250..bf37c7f931 100644 --- a/rust/trezor-client/src/protos/generated/messages_debug.rs +++ b/rust/trezor-client/src/protos/generated/messages_debug.rs @@ -2118,6 +2118,629 @@ impl ::protobuf::reflect::ProtobufValue for DebugLinkState { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:hw.trezor.messages.debug.DebugLinkGetPairingInfo) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct DebugLinkGetPairingInfo { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkGetPairingInfo.channel_id) + pub channel_id: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkGetPairingInfo.handshake_hash) + pub handshake_hash: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkGetPairingInfo.nfc_secret_host) + pub nfc_secret_host: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.debug.DebugLinkGetPairingInfo.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a DebugLinkGetPairingInfo { + fn default() -> &'a DebugLinkGetPairingInfo { + ::default_instance() + } +} + +impl DebugLinkGetPairingInfo { + pub fn new() -> DebugLinkGetPairingInfo { + ::std::default::Default::default() + } + + // optional bytes channel_id = 1; + + pub fn channel_id(&self) -> &[u8] { + match self.channel_id.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_channel_id(&mut self) { + self.channel_id = ::std::option::Option::None; + } + + pub fn has_channel_id(&self) -> bool { + self.channel_id.is_some() + } + + // Param is passed by value, moved + pub fn set_channel_id(&mut self, v: ::std::vec::Vec) { + self.channel_id = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_channel_id(&mut self) -> &mut ::std::vec::Vec { + if self.channel_id.is_none() { + self.channel_id = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.channel_id.as_mut().unwrap() + } + + // Take field + pub fn take_channel_id(&mut self) -> ::std::vec::Vec { + self.channel_id.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional bytes handshake_hash = 2; + + pub fn handshake_hash(&self) -> &[u8] { + match self.handshake_hash.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_handshake_hash(&mut self) { + self.handshake_hash = ::std::option::Option::None; + } + + pub fn has_handshake_hash(&self) -> bool { + self.handshake_hash.is_some() + } + + // Param is passed by value, moved + pub fn set_handshake_hash(&mut self, v: ::std::vec::Vec) { + self.handshake_hash = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_handshake_hash(&mut self) -> &mut ::std::vec::Vec { + if self.handshake_hash.is_none() { + self.handshake_hash = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.handshake_hash.as_mut().unwrap() + } + + // Take field + pub fn take_handshake_hash(&mut self) -> ::std::vec::Vec { + self.handshake_hash.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional bytes nfc_secret_host = 3; + + pub fn nfc_secret_host(&self) -> &[u8] { + match self.nfc_secret_host.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_nfc_secret_host(&mut self) { + self.nfc_secret_host = ::std::option::Option::None; + } + + pub fn has_nfc_secret_host(&self) -> bool { + self.nfc_secret_host.is_some() + } + + // Param is passed by value, moved + pub fn set_nfc_secret_host(&mut self, v: ::std::vec::Vec) { + self.nfc_secret_host = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_nfc_secret_host(&mut self) -> &mut ::std::vec::Vec { + if self.nfc_secret_host.is_none() { + self.nfc_secret_host = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.nfc_secret_host.as_mut().unwrap() + } + + // Take field + pub fn take_nfc_secret_host(&mut self) -> ::std::vec::Vec { + self.nfc_secret_host.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(3); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "channel_id", + |m: &DebugLinkGetPairingInfo| { &m.channel_id }, + |m: &mut DebugLinkGetPairingInfo| { &mut m.channel_id }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "handshake_hash", + |m: &DebugLinkGetPairingInfo| { &m.handshake_hash }, + |m: &mut DebugLinkGetPairingInfo| { &mut m.handshake_hash }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "nfc_secret_host", + |m: &DebugLinkGetPairingInfo| { &m.nfc_secret_host }, + |m: &mut DebugLinkGetPairingInfo| { &mut m.nfc_secret_host }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "DebugLinkGetPairingInfo", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for DebugLinkGetPairingInfo { + const NAME: &'static str = "DebugLinkGetPairingInfo"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.channel_id = ::std::option::Option::Some(is.read_bytes()?); + }, + 18 => { + self.handshake_hash = ::std::option::Option::Some(is.read_bytes()?); + }, + 26 => { + self.nfc_secret_host = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.channel_id.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + if let Some(v) = self.handshake_hash.as_ref() { + my_size += ::protobuf::rt::bytes_size(2, &v); + } + if let Some(v) = self.nfc_secret_host.as_ref() { + my_size += ::protobuf::rt::bytes_size(3, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.channel_id.as_ref() { + os.write_bytes(1, v)?; + } + if let Some(v) = self.handshake_hash.as_ref() { + os.write_bytes(2, v)?; + } + if let Some(v) = self.nfc_secret_host.as_ref() { + os.write_bytes(3, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> DebugLinkGetPairingInfo { + DebugLinkGetPairingInfo::new() + } + + fn clear(&mut self) { + self.channel_id = ::std::option::Option::None; + self.handshake_hash = ::std::option::Option::None; + self.nfc_secret_host = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static DebugLinkGetPairingInfo { + static instance: DebugLinkGetPairingInfo = DebugLinkGetPairingInfo { + channel_id: ::std::option::Option::None, + handshake_hash: ::std::option::Option::None, + nfc_secret_host: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for DebugLinkGetPairingInfo { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("DebugLinkGetPairingInfo").unwrap()).clone() + } +} + +impl ::std::fmt::Display for DebugLinkGetPairingInfo { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for DebugLinkGetPairingInfo { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.debug.DebugLinkPairingInfo) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct DebugLinkPairingInfo { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkPairingInfo.channel_id) + pub channel_id: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkPairingInfo.handshake_hash) + pub handshake_hash: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkPairingInfo.code_entry_code) + pub code_entry_code: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkPairingInfo.code_qr_code) + pub code_qr_code: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkPairingInfo.nfc_secret_trezor) + pub nfc_secret_trezor: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.debug.DebugLinkPairingInfo.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a DebugLinkPairingInfo { + fn default() -> &'a DebugLinkPairingInfo { + ::default_instance() + } +} + +impl DebugLinkPairingInfo { + pub fn new() -> DebugLinkPairingInfo { + ::std::default::Default::default() + } + + // optional bytes channel_id = 1; + + pub fn channel_id(&self) -> &[u8] { + match self.channel_id.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_channel_id(&mut self) { + self.channel_id = ::std::option::Option::None; + } + + pub fn has_channel_id(&self) -> bool { + self.channel_id.is_some() + } + + // Param is passed by value, moved + pub fn set_channel_id(&mut self, v: ::std::vec::Vec) { + self.channel_id = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_channel_id(&mut self) -> &mut ::std::vec::Vec { + if self.channel_id.is_none() { + self.channel_id = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.channel_id.as_mut().unwrap() + } + + // Take field + pub fn take_channel_id(&mut self) -> ::std::vec::Vec { + self.channel_id.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional bytes handshake_hash = 2; + + pub fn handshake_hash(&self) -> &[u8] { + match self.handshake_hash.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_handshake_hash(&mut self) { + self.handshake_hash = ::std::option::Option::None; + } + + pub fn has_handshake_hash(&self) -> bool { + self.handshake_hash.is_some() + } + + // Param is passed by value, moved + pub fn set_handshake_hash(&mut self, v: ::std::vec::Vec) { + self.handshake_hash = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_handshake_hash(&mut self) -> &mut ::std::vec::Vec { + if self.handshake_hash.is_none() { + self.handshake_hash = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.handshake_hash.as_mut().unwrap() + } + + // Take field + pub fn take_handshake_hash(&mut self) -> ::std::vec::Vec { + self.handshake_hash.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional uint32 code_entry_code = 3; + + pub fn code_entry_code(&self) -> u32 { + self.code_entry_code.unwrap_or(0) + } + + pub fn clear_code_entry_code(&mut self) { + self.code_entry_code = ::std::option::Option::None; + } + + pub fn has_code_entry_code(&self) -> bool { + self.code_entry_code.is_some() + } + + // Param is passed by value, moved + pub fn set_code_entry_code(&mut self, v: u32) { + self.code_entry_code = ::std::option::Option::Some(v); + } + + // optional bytes code_qr_code = 4; + + pub fn code_qr_code(&self) -> &[u8] { + match self.code_qr_code.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_code_qr_code(&mut self) { + self.code_qr_code = ::std::option::Option::None; + } + + pub fn has_code_qr_code(&self) -> bool { + self.code_qr_code.is_some() + } + + // Param is passed by value, moved + pub fn set_code_qr_code(&mut self, v: ::std::vec::Vec) { + self.code_qr_code = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_code_qr_code(&mut self) -> &mut ::std::vec::Vec { + if self.code_qr_code.is_none() { + self.code_qr_code = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.code_qr_code.as_mut().unwrap() + } + + // Take field + pub fn take_code_qr_code(&mut self) -> ::std::vec::Vec { + self.code_qr_code.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional bytes nfc_secret_trezor = 5; + + pub fn nfc_secret_trezor(&self) -> &[u8] { + match self.nfc_secret_trezor.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_nfc_secret_trezor(&mut self) { + self.nfc_secret_trezor = ::std::option::Option::None; + } + + pub fn has_nfc_secret_trezor(&self) -> bool { + self.nfc_secret_trezor.is_some() + } + + // Param is passed by value, moved + pub fn set_nfc_secret_trezor(&mut self, v: ::std::vec::Vec) { + self.nfc_secret_trezor = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_nfc_secret_trezor(&mut self) -> &mut ::std::vec::Vec { + if self.nfc_secret_trezor.is_none() { + self.nfc_secret_trezor = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.nfc_secret_trezor.as_mut().unwrap() + } + + // Take field + pub fn take_nfc_secret_trezor(&mut self) -> ::std::vec::Vec { + self.nfc_secret_trezor.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(5); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "channel_id", + |m: &DebugLinkPairingInfo| { &m.channel_id }, + |m: &mut DebugLinkPairingInfo| { &mut m.channel_id }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "handshake_hash", + |m: &DebugLinkPairingInfo| { &m.handshake_hash }, + |m: &mut DebugLinkPairingInfo| { &mut m.handshake_hash }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "code_entry_code", + |m: &DebugLinkPairingInfo| { &m.code_entry_code }, + |m: &mut DebugLinkPairingInfo| { &mut m.code_entry_code }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "code_qr_code", + |m: &DebugLinkPairingInfo| { &m.code_qr_code }, + |m: &mut DebugLinkPairingInfo| { &mut m.code_qr_code }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "nfc_secret_trezor", + |m: &DebugLinkPairingInfo| { &m.nfc_secret_trezor }, + |m: &mut DebugLinkPairingInfo| { &mut m.nfc_secret_trezor }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "DebugLinkPairingInfo", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for DebugLinkPairingInfo { + const NAME: &'static str = "DebugLinkPairingInfo"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.channel_id = ::std::option::Option::Some(is.read_bytes()?); + }, + 18 => { + self.handshake_hash = ::std::option::Option::Some(is.read_bytes()?); + }, + 24 => { + self.code_entry_code = ::std::option::Option::Some(is.read_uint32()?); + }, + 34 => { + self.code_qr_code = ::std::option::Option::Some(is.read_bytes()?); + }, + 42 => { + self.nfc_secret_trezor = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.channel_id.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + if let Some(v) = self.handshake_hash.as_ref() { + my_size += ::protobuf::rt::bytes_size(2, &v); + } + if let Some(v) = self.code_entry_code { + my_size += ::protobuf::rt::uint32_size(3, v); + } + if let Some(v) = self.code_qr_code.as_ref() { + my_size += ::protobuf::rt::bytes_size(4, &v); + } + if let Some(v) = self.nfc_secret_trezor.as_ref() { + my_size += ::protobuf::rt::bytes_size(5, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.channel_id.as_ref() { + os.write_bytes(1, v)?; + } + if let Some(v) = self.handshake_hash.as_ref() { + os.write_bytes(2, v)?; + } + if let Some(v) = self.code_entry_code { + os.write_uint32(3, v)?; + } + if let Some(v) = self.code_qr_code.as_ref() { + os.write_bytes(4, v)?; + } + if let Some(v) = self.nfc_secret_trezor.as_ref() { + os.write_bytes(5, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> DebugLinkPairingInfo { + DebugLinkPairingInfo::new() + } + + fn clear(&mut self) { + self.channel_id = ::std::option::Option::None; + self.handshake_hash = ::std::option::Option::None; + self.code_entry_code = ::std::option::Option::None; + self.code_qr_code = ::std::option::Option::None; + self.nfc_secret_trezor = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static DebugLinkPairingInfo { + static instance: DebugLinkPairingInfo = DebugLinkPairingInfo { + channel_id: ::std::option::Option::None, + handshake_hash: ::std::option::Option::None, + code_entry_code: ::std::option::Option::None, + code_qr_code: ::std::option::Option::None, + nfc_secret_trezor: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for DebugLinkPairingInfo { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("DebugLinkPairingInfo").unwrap()).clone() + } +} + +impl ::std::fmt::Display for DebugLinkPairingInfo { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for DebugLinkPairingInfo { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + // @@protoc_insertion_point(message:hw.trezor.messages.debug.DebugLinkStop) #[derive(PartialEq,Clone,Default,Debug)] pub struct DebugLinkStop { @@ -3707,20 +4330,28 @@ static file_descriptor_proto_data: &'static [u8] = b"\ dPos\x12$\n\x0ereset_word_pos\x18\x0b\x20\x01(\rR\x0cresetWordPos\x12N\n\ \rmnemonic_type\x18\x0c\x20\x01(\x0e2).hw.trezor.messages.management.Bac\ kupTypeR\x0cmnemonicType\x12\x16\n\x06tokens\x18\r\x20\x03(\tR\x06tokens\ - \"\x0f\n\rDebugLinkStop\"P\n\x0cDebugLinkLog\x12\x14\n\x05level\x18\x01\ - \x20\x01(\rR\x05level\x12\x16\n\x06bucket\x18\x02\x20\x01(\tR\x06bucket\ - \x12\x12\n\x04text\x18\x03\x20\x01(\tR\x04text\"G\n\x13DebugLinkMemoryRe\ - ad\x12\x18\n\x07address\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06leng\ - th\x18\x02\x20\x01(\rR\x06length\")\n\x0fDebugLinkMemory\x12\x16\n\x06me\ - mory\x18\x01\x20\x01(\x0cR\x06memory\"^\n\x14DebugLinkMemoryWrite\x12\ - \x18\n\x07address\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06memory\x18\ - \x02\x20\x01(\x0cR\x06memory\x12\x14\n\x05flash\x18\x03\x20\x01(\x08R\ - \x05flash\"-\n\x13DebugLinkFlashErase\x12\x16\n\x06sector\x18\x01\x20\ - \x01(\rR\x06sector\".\n\x14DebugLinkEraseSdCard\x12\x16\n\x06format\x18\ - \x01\x20\x01(\x08R\x06format\"0\n\x14DebugLinkWatchLayout\x12\x14\n\x05w\ - atch\x18\x01\x20\x01(\x08R\x05watch:\x02\x18\x01\"\x1f\n\x19DebugLinkRes\ - etDebugEvents:\x02\x18\x01\"\x1a\n\x18DebugLinkOptigaSetSecMaxB=\n#com.s\ - atoshilabs.trezor.lib.protobufB\x12TrezorMessageDebug\x80\xa6\x1d\x01\ + \"\x87\x01\n\x17DebugLinkGetPairingInfo\x12\x1d\n\nchannel_id\x18\x01\ + \x20\x01(\x0cR\tchannelId\x12%\n\x0ehandshake_hash\x18\x02\x20\x01(\x0cR\ + \rhandshakeHash\x12&\n\x0fnfc_secret_host\x18\x03\x20\x01(\x0cR\rnfcSecr\ + etHost\"\xd2\x01\n\x14DebugLinkPairingInfo\x12\x1d\n\nchannel_id\x18\x01\ + \x20\x01(\x0cR\tchannelId\x12%\n\x0ehandshake_hash\x18\x02\x20\x01(\x0cR\ + \rhandshakeHash\x12&\n\x0fcode_entry_code\x18\x03\x20\x01(\rR\rcodeEntry\ + Code\x12\x20\n\x0ccode_qr_code\x18\x04\x20\x01(\x0cR\ncodeQrCode\x12*\n\ + \x11nfc_secret_trezor\x18\x05\x20\x01(\x0cR\x0fnfcSecretTrezor\"\x0f\n\r\ + DebugLinkStop\"P\n\x0cDebugLinkLog\x12\x14\n\x05level\x18\x01\x20\x01(\r\ + R\x05level\x12\x16\n\x06bucket\x18\x02\x20\x01(\tR\x06bucket\x12\x12\n\ + \x04text\x18\x03\x20\x01(\tR\x04text\"G\n\x13DebugLinkMemoryRead\x12\x18\ + \n\x07address\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06length\x18\x02\ + \x20\x01(\rR\x06length\")\n\x0fDebugLinkMemory\x12\x16\n\x06memory\x18\ + \x01\x20\x01(\x0cR\x06memory\"^\n\x14DebugLinkMemoryWrite\x12\x18\n\x07a\ + ddress\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06memory\x18\x02\x20\ + \x01(\x0cR\x06memory\x12\x14\n\x05flash\x18\x03\x20\x01(\x08R\x05flash\"\ + -\n\x13DebugLinkFlashErase\x12\x16\n\x06sector\x18\x01\x20\x01(\rR\x06se\ + ctor\".\n\x14DebugLinkEraseSdCard\x12\x16\n\x06format\x18\x01\x20\x01(\ + \x08R\x06format\"0\n\x14DebugLinkWatchLayout\x12\x14\n\x05watch\x18\x01\ + \x20\x01(\x08R\x05watch:\x02\x18\x01\"\x1f\n\x19DebugLinkResetDebugEvent\ + s:\x02\x18\x01\"\x1a\n\x18DebugLinkOptigaSetSecMaxB=\n#com.satoshilabs.t\ + rezor.lib.protobufB\x12TrezorMessageDebug\x80\xa6\x1d\x01\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -3741,13 +4372,15 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { deps.push(super::messages_common::file_descriptor().clone()); deps.push(super::messages_management::file_descriptor().clone()); deps.push(super::options::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(16); + let mut messages = ::std::vec::Vec::with_capacity(18); messages.push(DebugLinkDecision::generated_message_descriptor_data()); messages.push(DebugLinkLayout::generated_message_descriptor_data()); messages.push(DebugLinkReseedRandom::generated_message_descriptor_data()); messages.push(DebugLinkRecordScreen::generated_message_descriptor_data()); messages.push(DebugLinkGetState::generated_message_descriptor_data()); messages.push(DebugLinkState::generated_message_descriptor_data()); + messages.push(DebugLinkGetPairingInfo::generated_message_descriptor_data()); + messages.push(DebugLinkPairingInfo::generated_message_descriptor_data()); messages.push(DebugLinkStop::generated_message_descriptor_data()); messages.push(DebugLinkLog::generated_message_descriptor_data()); messages.push(DebugLinkMemoryRead::generated_message_descriptor_data()); diff --git a/rust/trezor-client/src/protos/generated/messages_thp.rs b/rust/trezor-client/src/protos/generated/messages_thp.rs index 9e0d8e8aea..bd04e4d9b0 100644 --- a/rust/trezor-client/src/protos/generated/messages_thp.rs +++ b/rust/trezor-client/src/protos/generated/messages_thp.rs @@ -25,12 +25,3291 @@ /// of protobuf runtime. const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_3_0; +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpDeviceProperties) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpDeviceProperties { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpDeviceProperties.internal_model) + pub internal_model: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpDeviceProperties.model_variant) + pub model_variant: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpDeviceProperties.protocol_version_major) + pub protocol_version_major: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpDeviceProperties.protocol_version_minor) + pub protocol_version_minor: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpDeviceProperties.pairing_methods) + pub pairing_methods: ::std::vec::Vec<::protobuf::EnumOrUnknown>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpDeviceProperties.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpDeviceProperties { + fn default() -> &'a ThpDeviceProperties { + ::default_instance() + } +} + +impl ThpDeviceProperties { + pub fn new() -> ThpDeviceProperties { + ::std::default::Default::default() + } + + // optional string internal_model = 1; + + pub fn internal_model(&self) -> &str { + match self.internal_model.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_internal_model(&mut self) { + self.internal_model = ::std::option::Option::None; + } + + pub fn has_internal_model(&self) -> bool { + self.internal_model.is_some() + } + + // Param is passed by value, moved + pub fn set_internal_model(&mut self, v: ::std::string::String) { + self.internal_model = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_internal_model(&mut self) -> &mut ::std::string::String { + if self.internal_model.is_none() { + self.internal_model = ::std::option::Option::Some(::std::string::String::new()); + } + self.internal_model.as_mut().unwrap() + } + + // Take field + pub fn take_internal_model(&mut self) -> ::std::string::String { + self.internal_model.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional uint32 model_variant = 2; + + pub fn model_variant(&self) -> u32 { + self.model_variant.unwrap_or(0) + } + + pub fn clear_model_variant(&mut self) { + self.model_variant = ::std::option::Option::None; + } + + pub fn has_model_variant(&self) -> bool { + self.model_variant.is_some() + } + + // Param is passed by value, moved + pub fn set_model_variant(&mut self, v: u32) { + self.model_variant = ::std::option::Option::Some(v); + } + + // optional uint32 protocol_version_major = 3; + + pub fn protocol_version_major(&self) -> u32 { + self.protocol_version_major.unwrap_or(0) + } + + pub fn clear_protocol_version_major(&mut self) { + self.protocol_version_major = ::std::option::Option::None; + } + + pub fn has_protocol_version_major(&self) -> bool { + self.protocol_version_major.is_some() + } + + // Param is passed by value, moved + pub fn set_protocol_version_major(&mut self, v: u32) { + self.protocol_version_major = ::std::option::Option::Some(v); + } + + // optional uint32 protocol_version_minor = 4; + + pub fn protocol_version_minor(&self) -> u32 { + self.protocol_version_minor.unwrap_or(0) + } + + pub fn clear_protocol_version_minor(&mut self) { + self.protocol_version_minor = ::std::option::Option::None; + } + + pub fn has_protocol_version_minor(&self) -> bool { + self.protocol_version_minor.is_some() + } + + // Param is passed by value, moved + pub fn set_protocol_version_minor(&mut self, v: u32) { + self.protocol_version_minor = ::std::option::Option::Some(v); + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(5); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "internal_model", + |m: &ThpDeviceProperties| { &m.internal_model }, + |m: &mut ThpDeviceProperties| { &mut m.internal_model }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "model_variant", + |m: &ThpDeviceProperties| { &m.model_variant }, + |m: &mut ThpDeviceProperties| { &mut m.model_variant }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "protocol_version_major", + |m: &ThpDeviceProperties| { &m.protocol_version_major }, + |m: &mut ThpDeviceProperties| { &mut m.protocol_version_major }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "protocol_version_minor", + |m: &ThpDeviceProperties| { &m.protocol_version_minor }, + |m: &mut ThpDeviceProperties| { &mut m.protocol_version_minor }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "pairing_methods", + |m: &ThpDeviceProperties| { &m.pairing_methods }, + |m: &mut ThpDeviceProperties| { &mut m.pairing_methods }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpDeviceProperties", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpDeviceProperties { + const NAME: &'static str = "ThpDeviceProperties"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.internal_model = ::std::option::Option::Some(is.read_string()?); + }, + 16 => { + self.model_variant = ::std::option::Option::Some(is.read_uint32()?); + }, + 24 => { + self.protocol_version_major = ::std::option::Option::Some(is.read_uint32()?); + }, + 32 => { + self.protocol_version_minor = ::std::option::Option::Some(is.read_uint32()?); + }, + 40 => { + self.pairing_methods.push(is.read_enum_or_unknown()?); + }, + 42 => { + ::protobuf::rt::read_repeated_packed_enum_or_unknown_into(is, &mut self.pairing_methods)? + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.internal_model.as_ref() { + my_size += ::protobuf::rt::string_size(1, &v); + } + if let Some(v) = self.model_variant { + my_size += ::protobuf::rt::uint32_size(2, v); + } + if let Some(v) = self.protocol_version_major { + my_size += ::protobuf::rt::uint32_size(3, v); + } + if let Some(v) = self.protocol_version_minor { + my_size += ::protobuf::rt::uint32_size(4, v); + } + for value in &self.pairing_methods { + my_size += ::protobuf::rt::int32_size(5, value.value()); + }; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.internal_model.as_ref() { + os.write_string(1, v)?; + } + if let Some(v) = self.model_variant { + os.write_uint32(2, v)?; + } + if let Some(v) = self.protocol_version_major { + os.write_uint32(3, v)?; + } + if let Some(v) = self.protocol_version_minor { + os.write_uint32(4, v)?; + } + for v in &self.pairing_methods { + os.write_enum(5, ::protobuf::EnumOrUnknown::value(v))?; + }; + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpDeviceProperties { + ThpDeviceProperties::new() + } + + fn clear(&mut self) { + self.internal_model = ::std::option::Option::None; + self.model_variant = ::std::option::Option::None; + self.protocol_version_major = ::std::option::Option::None; + self.protocol_version_minor = ::std::option::Option::None; + self.pairing_methods.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpDeviceProperties { + static instance: ThpDeviceProperties = ThpDeviceProperties { + internal_model: ::std::option::Option::None, + model_variant: ::std::option::Option::None, + protocol_version_major: ::std::option::Option::None, + protocol_version_minor: ::std::option::Option::None, + pairing_methods: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpDeviceProperties { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpDeviceProperties").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpDeviceProperties { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpDeviceProperties { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpHandshakeCompletionReqNoisePayload) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpHandshakeCompletionReqNoisePayload { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpHandshakeCompletionReqNoisePayload.host_pairing_credential) + pub host_pairing_credential: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpHandshakeCompletionReqNoisePayload.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpHandshakeCompletionReqNoisePayload { + fn default() -> &'a ThpHandshakeCompletionReqNoisePayload { + ::default_instance() + } +} + +impl ThpHandshakeCompletionReqNoisePayload { + pub fn new() -> ThpHandshakeCompletionReqNoisePayload { + ::std::default::Default::default() + } + + // optional bytes host_pairing_credential = 1; + + pub fn host_pairing_credential(&self) -> &[u8] { + match self.host_pairing_credential.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_host_pairing_credential(&mut self) { + self.host_pairing_credential = ::std::option::Option::None; + } + + pub fn has_host_pairing_credential(&self) -> bool { + self.host_pairing_credential.is_some() + } + + // Param is passed by value, moved + pub fn set_host_pairing_credential(&mut self, v: ::std::vec::Vec) { + self.host_pairing_credential = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_host_pairing_credential(&mut self) -> &mut ::std::vec::Vec { + if self.host_pairing_credential.is_none() { + self.host_pairing_credential = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.host_pairing_credential.as_mut().unwrap() + } + + // Take field + pub fn take_host_pairing_credential(&mut self) -> ::std::vec::Vec { + self.host_pairing_credential.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "host_pairing_credential", + |m: &ThpHandshakeCompletionReqNoisePayload| { &m.host_pairing_credential }, + |m: &mut ThpHandshakeCompletionReqNoisePayload| { &mut m.host_pairing_credential }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpHandshakeCompletionReqNoisePayload", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpHandshakeCompletionReqNoisePayload { + const NAME: &'static str = "ThpHandshakeCompletionReqNoisePayload"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.host_pairing_credential = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.host_pairing_credential.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.host_pairing_credential.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpHandshakeCompletionReqNoisePayload { + ThpHandshakeCompletionReqNoisePayload::new() + } + + fn clear(&mut self) { + self.host_pairing_credential = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpHandshakeCompletionReqNoisePayload { + static instance: ThpHandshakeCompletionReqNoisePayload = ThpHandshakeCompletionReqNoisePayload { + host_pairing_credential: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpHandshakeCompletionReqNoisePayload { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpHandshakeCompletionReqNoisePayload").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpHandshakeCompletionReqNoisePayload { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpHandshakeCompletionReqNoisePayload { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCreateNewSession) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCreateNewSession { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCreateNewSession.passphrase) + pub passphrase: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCreateNewSession.on_device) + pub on_device: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCreateNewSession.derive_cardano) + pub derive_cardano: ::std::option::Option, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCreateNewSession.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCreateNewSession { + fn default() -> &'a ThpCreateNewSession { + ::default_instance() + } +} + +impl ThpCreateNewSession { + pub fn new() -> ThpCreateNewSession { + ::std::default::Default::default() + } + + // optional string passphrase = 1; + + pub fn passphrase(&self) -> &str { + match self.passphrase.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_passphrase(&mut self) { + self.passphrase = ::std::option::Option::None; + } + + pub fn has_passphrase(&self) -> bool { + self.passphrase.is_some() + } + + // Param is passed by value, moved + pub fn set_passphrase(&mut self, v: ::std::string::String) { + self.passphrase = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_passphrase(&mut self) -> &mut ::std::string::String { + if self.passphrase.is_none() { + self.passphrase = ::std::option::Option::Some(::std::string::String::new()); + } + self.passphrase.as_mut().unwrap() + } + + // Take field + pub fn take_passphrase(&mut self) -> ::std::string::String { + self.passphrase.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional bool on_device = 2; + + pub fn on_device(&self) -> bool { + self.on_device.unwrap_or(false) + } + + pub fn clear_on_device(&mut self) { + self.on_device = ::std::option::Option::None; + } + + pub fn has_on_device(&self) -> bool { + self.on_device.is_some() + } + + // Param is passed by value, moved + pub fn set_on_device(&mut self, v: bool) { + self.on_device = ::std::option::Option::Some(v); + } + + // optional bool derive_cardano = 3; + + pub fn derive_cardano(&self) -> bool { + self.derive_cardano.unwrap_or(false) + } + + pub fn clear_derive_cardano(&mut self) { + self.derive_cardano = ::std::option::Option::None; + } + + pub fn has_derive_cardano(&self) -> bool { + self.derive_cardano.is_some() + } + + // Param is passed by value, moved + pub fn set_derive_cardano(&mut self, v: bool) { + self.derive_cardano = ::std::option::Option::Some(v); + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(3); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "passphrase", + |m: &ThpCreateNewSession| { &m.passphrase }, + |m: &mut ThpCreateNewSession| { &mut m.passphrase }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "on_device", + |m: &ThpCreateNewSession| { &m.on_device }, + |m: &mut ThpCreateNewSession| { &mut m.on_device }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "derive_cardano", + |m: &ThpCreateNewSession| { &m.derive_cardano }, + |m: &mut ThpCreateNewSession| { &mut m.derive_cardano }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCreateNewSession", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCreateNewSession { + const NAME: &'static str = "ThpCreateNewSession"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.passphrase = ::std::option::Option::Some(is.read_string()?); + }, + 16 => { + self.on_device = ::std::option::Option::Some(is.read_bool()?); + }, + 24 => { + self.derive_cardano = ::std::option::Option::Some(is.read_bool()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.passphrase.as_ref() { + my_size += ::protobuf::rt::string_size(1, &v); + } + if let Some(v) = self.on_device { + my_size += 1 + 1; + } + if let Some(v) = self.derive_cardano { + my_size += 1 + 1; + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.passphrase.as_ref() { + os.write_string(1, v)?; + } + if let Some(v) = self.on_device { + os.write_bool(2, v)?; + } + if let Some(v) = self.derive_cardano { + os.write_bool(3, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCreateNewSession { + ThpCreateNewSession::new() + } + + fn clear(&mut self) { + self.passphrase = ::std::option::Option::None; + self.on_device = ::std::option::Option::None; + self.derive_cardano = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCreateNewSession { + static instance: ThpCreateNewSession = ThpCreateNewSession { + passphrase: ::std::option::Option::None, + on_device: ::std::option::Option::None, + derive_cardano: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCreateNewSession { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCreateNewSession").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCreateNewSession { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCreateNewSession { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpPairingRequest) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpPairingRequest { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpPairingRequest.host_name) + pub host_name: ::std::option::Option<::std::string::String>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpPairingRequest.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpPairingRequest { + fn default() -> &'a ThpPairingRequest { + ::default_instance() + } +} + +impl ThpPairingRequest { + pub fn new() -> ThpPairingRequest { + ::std::default::Default::default() + } + + // optional string host_name = 1; + + pub fn host_name(&self) -> &str { + match self.host_name.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_host_name(&mut self) { + self.host_name = ::std::option::Option::None; + } + + pub fn has_host_name(&self) -> bool { + self.host_name.is_some() + } + + // Param is passed by value, moved + pub fn set_host_name(&mut self, v: ::std::string::String) { + self.host_name = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_host_name(&mut self) -> &mut ::std::string::String { + if self.host_name.is_none() { + self.host_name = ::std::option::Option::Some(::std::string::String::new()); + } + self.host_name.as_mut().unwrap() + } + + // Take field + pub fn take_host_name(&mut self) -> ::std::string::String { + self.host_name.take().unwrap_or_else(|| ::std::string::String::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "host_name", + |m: &ThpPairingRequest| { &m.host_name }, + |m: &mut ThpPairingRequest| { &mut m.host_name }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpPairingRequest", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpPairingRequest { + const NAME: &'static str = "ThpPairingRequest"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.host_name = ::std::option::Option::Some(is.read_string()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.host_name.as_ref() { + my_size += ::protobuf::rt::string_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.host_name.as_ref() { + os.write_string(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpPairingRequest { + ThpPairingRequest::new() + } + + fn clear(&mut self) { + self.host_name = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpPairingRequest { + static instance: ThpPairingRequest = ThpPairingRequest { + host_name: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpPairingRequest { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpPairingRequest").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpPairingRequest { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpPairingRequest { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpPairingRequestApproved) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpPairingRequestApproved { + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpPairingRequestApproved.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpPairingRequestApproved { + fn default() -> &'a ThpPairingRequestApproved { + ::default_instance() + } +} + +impl ThpPairingRequestApproved { + pub fn new() -> ThpPairingRequestApproved { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(0); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpPairingRequestApproved", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpPairingRequestApproved { + const NAME: &'static str = "ThpPairingRequestApproved"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpPairingRequestApproved { + ThpPairingRequestApproved::new() + } + + fn clear(&mut self) { + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpPairingRequestApproved { + static instance: ThpPairingRequestApproved = ThpPairingRequestApproved { + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpPairingRequestApproved { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpPairingRequestApproved").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpPairingRequestApproved { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpPairingRequestApproved { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpSelectMethod) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpSelectMethod { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpSelectMethod.selected_pairing_method) + pub selected_pairing_method: ::std::option::Option<::protobuf::EnumOrUnknown>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpSelectMethod.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpSelectMethod { + fn default() -> &'a ThpSelectMethod { + ::default_instance() + } +} + +impl ThpSelectMethod { + pub fn new() -> ThpSelectMethod { + ::std::default::Default::default() + } + + // optional .hw.trezor.messages.thp.ThpPairingMethod selected_pairing_method = 1; + + pub fn selected_pairing_method(&self) -> ThpPairingMethod { + match self.selected_pairing_method { + Some(e) => e.enum_value_or(ThpPairingMethod::SkipPairing), + None => ThpPairingMethod::SkipPairing, + } + } + + pub fn clear_selected_pairing_method(&mut self) { + self.selected_pairing_method = ::std::option::Option::None; + } + + pub fn has_selected_pairing_method(&self) -> bool { + self.selected_pairing_method.is_some() + } + + // Param is passed by value, moved + pub fn set_selected_pairing_method(&mut self, v: ThpPairingMethod) { + self.selected_pairing_method = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "selected_pairing_method", + |m: &ThpSelectMethod| { &m.selected_pairing_method }, + |m: &mut ThpSelectMethod| { &mut m.selected_pairing_method }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpSelectMethod", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpSelectMethod { + const NAME: &'static str = "ThpSelectMethod"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 8 => { + self.selected_pairing_method = ::std::option::Option::Some(is.read_enum_or_unknown()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.selected_pairing_method { + my_size += ::protobuf::rt::int32_size(1, v.value()); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.selected_pairing_method { + os.write_enum(1, ::protobuf::EnumOrUnknown::value(&v))?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpSelectMethod { + ThpSelectMethod::new() + } + + fn clear(&mut self) { + self.selected_pairing_method = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpSelectMethod { + static instance: ThpSelectMethod = ThpSelectMethod { + selected_pairing_method: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpSelectMethod { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpSelectMethod").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpSelectMethod { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpSelectMethod { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpPairingPreparationsFinished) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpPairingPreparationsFinished { + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpPairingPreparationsFinished.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpPairingPreparationsFinished { + fn default() -> &'a ThpPairingPreparationsFinished { + ::default_instance() + } +} + +impl ThpPairingPreparationsFinished { + pub fn new() -> ThpPairingPreparationsFinished { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(0); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpPairingPreparationsFinished", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpPairingPreparationsFinished { + const NAME: &'static str = "ThpPairingPreparationsFinished"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpPairingPreparationsFinished { + ThpPairingPreparationsFinished::new() + } + + fn clear(&mut self) { + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpPairingPreparationsFinished { + static instance: ThpPairingPreparationsFinished = ThpPairingPreparationsFinished { + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpPairingPreparationsFinished { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpPairingPreparationsFinished").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpPairingPreparationsFinished { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpPairingPreparationsFinished { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCodeEntryCommitment) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCodeEntryCommitment { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCodeEntryCommitment.commitment) + pub commitment: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCodeEntryCommitment.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCodeEntryCommitment { + fn default() -> &'a ThpCodeEntryCommitment { + ::default_instance() + } +} + +impl ThpCodeEntryCommitment { + pub fn new() -> ThpCodeEntryCommitment { + ::std::default::Default::default() + } + + // optional bytes commitment = 1; + + pub fn commitment(&self) -> &[u8] { + match self.commitment.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_commitment(&mut self) { + self.commitment = ::std::option::Option::None; + } + + pub fn has_commitment(&self) -> bool { + self.commitment.is_some() + } + + // Param is passed by value, moved + pub fn set_commitment(&mut self, v: ::std::vec::Vec) { + self.commitment = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_commitment(&mut self) -> &mut ::std::vec::Vec { + if self.commitment.is_none() { + self.commitment = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.commitment.as_mut().unwrap() + } + + // Take field + pub fn take_commitment(&mut self) -> ::std::vec::Vec { + self.commitment.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "commitment", + |m: &ThpCodeEntryCommitment| { &m.commitment }, + |m: &mut ThpCodeEntryCommitment| { &mut m.commitment }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCodeEntryCommitment", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCodeEntryCommitment { + const NAME: &'static str = "ThpCodeEntryCommitment"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.commitment = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.commitment.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.commitment.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCodeEntryCommitment { + ThpCodeEntryCommitment::new() + } + + fn clear(&mut self) { + self.commitment = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCodeEntryCommitment { + static instance: ThpCodeEntryCommitment = ThpCodeEntryCommitment { + commitment: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCodeEntryCommitment { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCodeEntryCommitment").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCodeEntryCommitment { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCodeEntryCommitment { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCodeEntryChallenge) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCodeEntryChallenge { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCodeEntryChallenge.challenge) + pub challenge: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCodeEntryChallenge.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCodeEntryChallenge { + fn default() -> &'a ThpCodeEntryChallenge { + ::default_instance() + } +} + +impl ThpCodeEntryChallenge { + pub fn new() -> ThpCodeEntryChallenge { + ::std::default::Default::default() + } + + // optional bytes challenge = 1; + + pub fn challenge(&self) -> &[u8] { + match self.challenge.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_challenge(&mut self) { + self.challenge = ::std::option::Option::None; + } + + pub fn has_challenge(&self) -> bool { + self.challenge.is_some() + } + + // Param is passed by value, moved + pub fn set_challenge(&mut self, v: ::std::vec::Vec) { + self.challenge = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_challenge(&mut self) -> &mut ::std::vec::Vec { + if self.challenge.is_none() { + self.challenge = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.challenge.as_mut().unwrap() + } + + // Take field + pub fn take_challenge(&mut self) -> ::std::vec::Vec { + self.challenge.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "challenge", + |m: &ThpCodeEntryChallenge| { &m.challenge }, + |m: &mut ThpCodeEntryChallenge| { &mut m.challenge }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCodeEntryChallenge", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCodeEntryChallenge { + const NAME: &'static str = "ThpCodeEntryChallenge"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.challenge = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.challenge.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.challenge.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCodeEntryChallenge { + ThpCodeEntryChallenge::new() + } + + fn clear(&mut self) { + self.challenge = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCodeEntryChallenge { + static instance: ThpCodeEntryChallenge = ThpCodeEntryChallenge { + challenge: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCodeEntryChallenge { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCodeEntryChallenge").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCodeEntryChallenge { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCodeEntryChallenge { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCodeEntryCpaceTrezor) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCodeEntryCpaceTrezor { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCodeEntryCpaceTrezor.cpace_trezor_public_key) + pub cpace_trezor_public_key: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCodeEntryCpaceTrezor.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCodeEntryCpaceTrezor { + fn default() -> &'a ThpCodeEntryCpaceTrezor { + ::default_instance() + } +} + +impl ThpCodeEntryCpaceTrezor { + pub fn new() -> ThpCodeEntryCpaceTrezor { + ::std::default::Default::default() + } + + // optional bytes cpace_trezor_public_key = 1; + + pub fn cpace_trezor_public_key(&self) -> &[u8] { + match self.cpace_trezor_public_key.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_cpace_trezor_public_key(&mut self) { + self.cpace_trezor_public_key = ::std::option::Option::None; + } + + pub fn has_cpace_trezor_public_key(&self) -> bool { + self.cpace_trezor_public_key.is_some() + } + + // Param is passed by value, moved + pub fn set_cpace_trezor_public_key(&mut self, v: ::std::vec::Vec) { + self.cpace_trezor_public_key = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_cpace_trezor_public_key(&mut self) -> &mut ::std::vec::Vec { + if self.cpace_trezor_public_key.is_none() { + self.cpace_trezor_public_key = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.cpace_trezor_public_key.as_mut().unwrap() + } + + // Take field + pub fn take_cpace_trezor_public_key(&mut self) -> ::std::vec::Vec { + self.cpace_trezor_public_key.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "cpace_trezor_public_key", + |m: &ThpCodeEntryCpaceTrezor| { &m.cpace_trezor_public_key }, + |m: &mut ThpCodeEntryCpaceTrezor| { &mut m.cpace_trezor_public_key }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCodeEntryCpaceTrezor", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCodeEntryCpaceTrezor { + const NAME: &'static str = "ThpCodeEntryCpaceTrezor"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.cpace_trezor_public_key = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.cpace_trezor_public_key.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.cpace_trezor_public_key.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCodeEntryCpaceTrezor { + ThpCodeEntryCpaceTrezor::new() + } + + fn clear(&mut self) { + self.cpace_trezor_public_key = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCodeEntryCpaceTrezor { + static instance: ThpCodeEntryCpaceTrezor = ThpCodeEntryCpaceTrezor { + cpace_trezor_public_key: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCodeEntryCpaceTrezor { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCodeEntryCpaceTrezor").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCodeEntryCpaceTrezor { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCodeEntryCpaceTrezor { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCodeEntryCpaceHostTag) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCodeEntryCpaceHostTag { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCodeEntryCpaceHostTag.cpace_host_public_key) + pub cpace_host_public_key: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCodeEntryCpaceHostTag.tag) + pub tag: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCodeEntryCpaceHostTag.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCodeEntryCpaceHostTag { + fn default() -> &'a ThpCodeEntryCpaceHostTag { + ::default_instance() + } +} + +impl ThpCodeEntryCpaceHostTag { + pub fn new() -> ThpCodeEntryCpaceHostTag { + ::std::default::Default::default() + } + + // optional bytes cpace_host_public_key = 1; + + pub fn cpace_host_public_key(&self) -> &[u8] { + match self.cpace_host_public_key.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_cpace_host_public_key(&mut self) { + self.cpace_host_public_key = ::std::option::Option::None; + } + + pub fn has_cpace_host_public_key(&self) -> bool { + self.cpace_host_public_key.is_some() + } + + // Param is passed by value, moved + pub fn set_cpace_host_public_key(&mut self, v: ::std::vec::Vec) { + self.cpace_host_public_key = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_cpace_host_public_key(&mut self) -> &mut ::std::vec::Vec { + if self.cpace_host_public_key.is_none() { + self.cpace_host_public_key = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.cpace_host_public_key.as_mut().unwrap() + } + + // Take field + pub fn take_cpace_host_public_key(&mut self) -> ::std::vec::Vec { + self.cpace_host_public_key.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional bytes tag = 2; + + pub fn tag(&self) -> &[u8] { + match self.tag.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_tag(&mut self) { + self.tag = ::std::option::Option::None; + } + + pub fn has_tag(&self) -> bool { + self.tag.is_some() + } + + // Param is passed by value, moved + pub fn set_tag(&mut self, v: ::std::vec::Vec) { + self.tag = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_tag(&mut self) -> &mut ::std::vec::Vec { + if self.tag.is_none() { + self.tag = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.tag.as_mut().unwrap() + } + + // Take field + pub fn take_tag(&mut self) -> ::std::vec::Vec { + self.tag.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "cpace_host_public_key", + |m: &ThpCodeEntryCpaceHostTag| { &m.cpace_host_public_key }, + |m: &mut ThpCodeEntryCpaceHostTag| { &mut m.cpace_host_public_key }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "tag", + |m: &ThpCodeEntryCpaceHostTag| { &m.tag }, + |m: &mut ThpCodeEntryCpaceHostTag| { &mut m.tag }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCodeEntryCpaceHostTag", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCodeEntryCpaceHostTag { + const NAME: &'static str = "ThpCodeEntryCpaceHostTag"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.cpace_host_public_key = ::std::option::Option::Some(is.read_bytes()?); + }, + 18 => { + self.tag = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.cpace_host_public_key.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + if let Some(v) = self.tag.as_ref() { + my_size += ::protobuf::rt::bytes_size(2, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.cpace_host_public_key.as_ref() { + os.write_bytes(1, v)?; + } + if let Some(v) = self.tag.as_ref() { + os.write_bytes(2, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCodeEntryCpaceHostTag { + ThpCodeEntryCpaceHostTag::new() + } + + fn clear(&mut self) { + self.cpace_host_public_key = ::std::option::Option::None; + self.tag = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCodeEntryCpaceHostTag { + static instance: ThpCodeEntryCpaceHostTag = ThpCodeEntryCpaceHostTag { + cpace_host_public_key: ::std::option::Option::None, + tag: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCodeEntryCpaceHostTag { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCodeEntryCpaceHostTag").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCodeEntryCpaceHostTag { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCodeEntryCpaceHostTag { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCodeEntrySecret) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCodeEntrySecret { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCodeEntrySecret.secret) + pub secret: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCodeEntrySecret.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCodeEntrySecret { + fn default() -> &'a ThpCodeEntrySecret { + ::default_instance() + } +} + +impl ThpCodeEntrySecret { + pub fn new() -> ThpCodeEntrySecret { + ::std::default::Default::default() + } + + // optional bytes secret = 1; + + pub fn secret(&self) -> &[u8] { + match self.secret.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_secret(&mut self) { + self.secret = ::std::option::Option::None; + } + + pub fn has_secret(&self) -> bool { + self.secret.is_some() + } + + // Param is passed by value, moved + pub fn set_secret(&mut self, v: ::std::vec::Vec) { + self.secret = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_secret(&mut self) -> &mut ::std::vec::Vec { + if self.secret.is_none() { + self.secret = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.secret.as_mut().unwrap() + } + + // Take field + pub fn take_secret(&mut self) -> ::std::vec::Vec { + self.secret.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "secret", + |m: &ThpCodeEntrySecret| { &m.secret }, + |m: &mut ThpCodeEntrySecret| { &mut m.secret }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCodeEntrySecret", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCodeEntrySecret { + const NAME: &'static str = "ThpCodeEntrySecret"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.secret = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.secret.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.secret.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCodeEntrySecret { + ThpCodeEntrySecret::new() + } + + fn clear(&mut self) { + self.secret = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCodeEntrySecret { + static instance: ThpCodeEntrySecret = ThpCodeEntrySecret { + secret: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCodeEntrySecret { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCodeEntrySecret").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCodeEntrySecret { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCodeEntrySecret { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpQrCodeTag) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpQrCodeTag { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpQrCodeTag.tag) + pub tag: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpQrCodeTag.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpQrCodeTag { + fn default() -> &'a ThpQrCodeTag { + ::default_instance() + } +} + +impl ThpQrCodeTag { + pub fn new() -> ThpQrCodeTag { + ::std::default::Default::default() + } + + // optional bytes tag = 1; + + pub fn tag(&self) -> &[u8] { + match self.tag.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_tag(&mut self) { + self.tag = ::std::option::Option::None; + } + + pub fn has_tag(&self) -> bool { + self.tag.is_some() + } + + // Param is passed by value, moved + pub fn set_tag(&mut self, v: ::std::vec::Vec) { + self.tag = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_tag(&mut self) -> &mut ::std::vec::Vec { + if self.tag.is_none() { + self.tag = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.tag.as_mut().unwrap() + } + + // Take field + pub fn take_tag(&mut self) -> ::std::vec::Vec { + self.tag.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "tag", + |m: &ThpQrCodeTag| { &m.tag }, + |m: &mut ThpQrCodeTag| { &mut m.tag }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpQrCodeTag", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpQrCodeTag { + const NAME: &'static str = "ThpQrCodeTag"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.tag = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.tag.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.tag.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpQrCodeTag { + ThpQrCodeTag::new() + } + + fn clear(&mut self) { + self.tag = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpQrCodeTag { + static instance: ThpQrCodeTag = ThpQrCodeTag { + tag: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpQrCodeTag { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpQrCodeTag").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpQrCodeTag { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpQrCodeTag { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpQrCodeSecret) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpQrCodeSecret { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpQrCodeSecret.secret) + pub secret: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpQrCodeSecret.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpQrCodeSecret { + fn default() -> &'a ThpQrCodeSecret { + ::default_instance() + } +} + +impl ThpQrCodeSecret { + pub fn new() -> ThpQrCodeSecret { + ::std::default::Default::default() + } + + // optional bytes secret = 1; + + pub fn secret(&self) -> &[u8] { + match self.secret.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_secret(&mut self) { + self.secret = ::std::option::Option::None; + } + + pub fn has_secret(&self) -> bool { + self.secret.is_some() + } + + // Param is passed by value, moved + pub fn set_secret(&mut self, v: ::std::vec::Vec) { + self.secret = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_secret(&mut self) -> &mut ::std::vec::Vec { + if self.secret.is_none() { + self.secret = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.secret.as_mut().unwrap() + } + + // Take field + pub fn take_secret(&mut self) -> ::std::vec::Vec { + self.secret.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "secret", + |m: &ThpQrCodeSecret| { &m.secret }, + |m: &mut ThpQrCodeSecret| { &mut m.secret }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpQrCodeSecret", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpQrCodeSecret { + const NAME: &'static str = "ThpQrCodeSecret"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.secret = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.secret.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.secret.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpQrCodeSecret { + ThpQrCodeSecret::new() + } + + fn clear(&mut self) { + self.secret = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpQrCodeSecret { + static instance: ThpQrCodeSecret = ThpQrCodeSecret { + secret: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpQrCodeSecret { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpQrCodeSecret").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpQrCodeSecret { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpQrCodeSecret { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpNfcTagHost) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpNfcTagHost { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpNfcTagHost.tag) + pub tag: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpNfcTagHost.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpNfcTagHost { + fn default() -> &'a ThpNfcTagHost { + ::default_instance() + } +} + +impl ThpNfcTagHost { + pub fn new() -> ThpNfcTagHost { + ::std::default::Default::default() + } + + // optional bytes tag = 1; + + pub fn tag(&self) -> &[u8] { + match self.tag.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_tag(&mut self) { + self.tag = ::std::option::Option::None; + } + + pub fn has_tag(&self) -> bool { + self.tag.is_some() + } + + // Param is passed by value, moved + pub fn set_tag(&mut self, v: ::std::vec::Vec) { + self.tag = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_tag(&mut self) -> &mut ::std::vec::Vec { + if self.tag.is_none() { + self.tag = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.tag.as_mut().unwrap() + } + + // Take field + pub fn take_tag(&mut self) -> ::std::vec::Vec { + self.tag.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "tag", + |m: &ThpNfcTagHost| { &m.tag }, + |m: &mut ThpNfcTagHost| { &mut m.tag }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpNfcTagHost", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpNfcTagHost { + const NAME: &'static str = "ThpNfcTagHost"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.tag = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.tag.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.tag.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpNfcTagHost { + ThpNfcTagHost::new() + } + + fn clear(&mut self) { + self.tag = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpNfcTagHost { + static instance: ThpNfcTagHost = ThpNfcTagHost { + tag: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpNfcTagHost { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpNfcTagHost").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpNfcTagHost { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpNfcTagHost { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpNfcTagTrezor) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpNfcTagTrezor { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpNfcTagTrezor.tag) + pub tag: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpNfcTagTrezor.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpNfcTagTrezor { + fn default() -> &'a ThpNfcTagTrezor { + ::default_instance() + } +} + +impl ThpNfcTagTrezor { + pub fn new() -> ThpNfcTagTrezor { + ::std::default::Default::default() + } + + // optional bytes tag = 1; + + pub fn tag(&self) -> &[u8] { + match self.tag.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_tag(&mut self) { + self.tag = ::std::option::Option::None; + } + + pub fn has_tag(&self) -> bool { + self.tag.is_some() + } + + // Param is passed by value, moved + pub fn set_tag(&mut self, v: ::std::vec::Vec) { + self.tag = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_tag(&mut self) -> &mut ::std::vec::Vec { + if self.tag.is_none() { + self.tag = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.tag.as_mut().unwrap() + } + + // Take field + pub fn take_tag(&mut self) -> ::std::vec::Vec { + self.tag.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "tag", + |m: &ThpNfcTagTrezor| { &m.tag }, + |m: &mut ThpNfcTagTrezor| { &mut m.tag }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpNfcTagTrezor", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpNfcTagTrezor { + const NAME: &'static str = "ThpNfcTagTrezor"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.tag = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.tag.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.tag.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpNfcTagTrezor { + ThpNfcTagTrezor::new() + } + + fn clear(&mut self) { + self.tag = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpNfcTagTrezor { + static instance: ThpNfcTagTrezor = ThpNfcTagTrezor { + tag: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpNfcTagTrezor { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpNfcTagTrezor").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpNfcTagTrezor { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpNfcTagTrezor { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCredentialRequest) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCredentialRequest { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCredentialRequest.host_static_pubkey) + pub host_static_pubkey: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCredentialRequest.autoconnect) + pub autoconnect: ::std::option::Option, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCredentialRequest.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCredentialRequest { + fn default() -> &'a ThpCredentialRequest { + ::default_instance() + } +} + +impl ThpCredentialRequest { + pub fn new() -> ThpCredentialRequest { + ::std::default::Default::default() + } + + // optional bytes host_static_pubkey = 1; + + pub fn host_static_pubkey(&self) -> &[u8] { + match self.host_static_pubkey.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_host_static_pubkey(&mut self) { + self.host_static_pubkey = ::std::option::Option::None; + } + + pub fn has_host_static_pubkey(&self) -> bool { + self.host_static_pubkey.is_some() + } + + // Param is passed by value, moved + pub fn set_host_static_pubkey(&mut self, v: ::std::vec::Vec) { + self.host_static_pubkey = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_host_static_pubkey(&mut self) -> &mut ::std::vec::Vec { + if self.host_static_pubkey.is_none() { + self.host_static_pubkey = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.host_static_pubkey.as_mut().unwrap() + } + + // Take field + pub fn take_host_static_pubkey(&mut self) -> ::std::vec::Vec { + self.host_static_pubkey.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional bool autoconnect = 2; + + pub fn autoconnect(&self) -> bool { + self.autoconnect.unwrap_or(false) + } + + pub fn clear_autoconnect(&mut self) { + self.autoconnect = ::std::option::Option::None; + } + + pub fn has_autoconnect(&self) -> bool { + self.autoconnect.is_some() + } + + // Param is passed by value, moved + pub fn set_autoconnect(&mut self, v: bool) { + self.autoconnect = ::std::option::Option::Some(v); + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "host_static_pubkey", + |m: &ThpCredentialRequest| { &m.host_static_pubkey }, + |m: &mut ThpCredentialRequest| { &mut m.host_static_pubkey }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "autoconnect", + |m: &ThpCredentialRequest| { &m.autoconnect }, + |m: &mut ThpCredentialRequest| { &mut m.autoconnect }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCredentialRequest", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCredentialRequest { + const NAME: &'static str = "ThpCredentialRequest"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.host_static_pubkey = ::std::option::Option::Some(is.read_bytes()?); + }, + 16 => { + self.autoconnect = ::std::option::Option::Some(is.read_bool()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.host_static_pubkey.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + if let Some(v) = self.autoconnect { + my_size += 1 + 1; + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.host_static_pubkey.as_ref() { + os.write_bytes(1, v)?; + } + if let Some(v) = self.autoconnect { + os.write_bool(2, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCredentialRequest { + ThpCredentialRequest::new() + } + + fn clear(&mut self) { + self.host_static_pubkey = ::std::option::Option::None; + self.autoconnect = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCredentialRequest { + static instance: ThpCredentialRequest = ThpCredentialRequest { + host_static_pubkey: ::std::option::Option::None, + autoconnect: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCredentialRequest { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCredentialRequest").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCredentialRequest { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCredentialRequest { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCredentialResponse) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpCredentialResponse { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCredentialResponse.trezor_static_pubkey) + pub trezor_static_pubkey: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCredentialResponse.credential) + pub credential: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCredentialResponse.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpCredentialResponse { + fn default() -> &'a ThpCredentialResponse { + ::default_instance() + } +} + +impl ThpCredentialResponse { + pub fn new() -> ThpCredentialResponse { + ::std::default::Default::default() + } + + // optional bytes trezor_static_pubkey = 1; + + pub fn trezor_static_pubkey(&self) -> &[u8] { + match self.trezor_static_pubkey.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_trezor_static_pubkey(&mut self) { + self.trezor_static_pubkey = ::std::option::Option::None; + } + + pub fn has_trezor_static_pubkey(&self) -> bool { + self.trezor_static_pubkey.is_some() + } + + // Param is passed by value, moved + pub fn set_trezor_static_pubkey(&mut self, v: ::std::vec::Vec) { + self.trezor_static_pubkey = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_trezor_static_pubkey(&mut self) -> &mut ::std::vec::Vec { + if self.trezor_static_pubkey.is_none() { + self.trezor_static_pubkey = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.trezor_static_pubkey.as_mut().unwrap() + } + + // Take field + pub fn take_trezor_static_pubkey(&mut self) -> ::std::vec::Vec { + self.trezor_static_pubkey.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // optional bytes credential = 2; + + pub fn credential(&self) -> &[u8] { + match self.credential.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_credential(&mut self) { + self.credential = ::std::option::Option::None; + } + + pub fn has_credential(&self) -> bool { + self.credential.is_some() + } + + // Param is passed by value, moved + pub fn set_credential(&mut self, v: ::std::vec::Vec) { + self.credential = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_credential(&mut self) -> &mut ::std::vec::Vec { + if self.credential.is_none() { + self.credential = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.credential.as_mut().unwrap() + } + + // Take field + pub fn take_credential(&mut self) -> ::std::vec::Vec { + self.credential.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "trezor_static_pubkey", + |m: &ThpCredentialResponse| { &m.trezor_static_pubkey }, + |m: &mut ThpCredentialResponse| { &mut m.trezor_static_pubkey }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "credential", + |m: &ThpCredentialResponse| { &m.credential }, + |m: &mut ThpCredentialResponse| { &mut m.credential }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpCredentialResponse", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpCredentialResponse { + const NAME: &'static str = "ThpCredentialResponse"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.trezor_static_pubkey = ::std::option::Option::Some(is.read_bytes()?); + }, + 18 => { + self.credential = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.trezor_static_pubkey.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + if let Some(v) = self.credential.as_ref() { + my_size += ::protobuf::rt::bytes_size(2, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.trezor_static_pubkey.as_ref() { + os.write_bytes(1, v)?; + } + if let Some(v) = self.credential.as_ref() { + os.write_bytes(2, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpCredentialResponse { + ThpCredentialResponse::new() + } + + fn clear(&mut self) { + self.trezor_static_pubkey = ::std::option::Option::None; + self.credential = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpCredentialResponse { + static instance: ThpCredentialResponse = ThpCredentialResponse { + trezor_static_pubkey: ::std::option::Option::None, + credential: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpCredentialResponse { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpCredentialResponse").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpCredentialResponse { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpCredentialResponse { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpEndRequest) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpEndRequest { + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpEndRequest.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpEndRequest { + fn default() -> &'a ThpEndRequest { + ::default_instance() + } +} + +impl ThpEndRequest { + pub fn new() -> ThpEndRequest { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(0); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpEndRequest", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpEndRequest { + const NAME: &'static str = "ThpEndRequest"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpEndRequest { + ThpEndRequest::new() + } + + fn clear(&mut self) { + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpEndRequest { + static instance: ThpEndRequest = ThpEndRequest { + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpEndRequest { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpEndRequest").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpEndRequest { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpEndRequest { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpEndResponse) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ThpEndResponse { + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpEndResponse.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ThpEndResponse { + fn default() -> &'a ThpEndResponse { + ::default_instance() + } +} + +impl ThpEndResponse { + pub fn new() -> ThpEndResponse { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(0); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ThpEndResponse", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ThpEndResponse { + const NAME: &'static str = "ThpEndResponse"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ThpEndResponse { + ThpEndResponse::new() + } + + fn clear(&mut self) { + self.special_fields.clear(); + } + + fn default_instance() -> &'static ThpEndResponse { + static instance: ThpEndResponse = ThpEndResponse { + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ThpEndResponse { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ThpEndResponse").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ThpEndResponse { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ThpEndResponse { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + // @@protoc_insertion_point(message:hw.trezor.messages.thp.ThpCredentialMetadata) #[derive(PartialEq,Clone,Default,Debug)] pub struct ThpCredentialMetadata { // message fields // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCredentialMetadata.host_name) pub host_name: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.thp.ThpCredentialMetadata.autoconnect) + pub autoconnect: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:hw.trezor.messages.thp.ThpCredentialMetadata.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -83,14 +3362,38 @@ impl ThpCredentialMetadata { self.host_name.take().unwrap_or_else(|| ::std::string::String::new()) } + // optional bool autoconnect = 2; + + pub fn autoconnect(&self) -> bool { + self.autoconnect.unwrap_or(false) + } + + pub fn clear_autoconnect(&mut self) { + self.autoconnect = ::std::option::Option::None; + } + + pub fn has_autoconnect(&self) -> bool { + self.autoconnect.is_some() + } + + // Param is passed by value, moved + pub fn set_autoconnect(&mut self, v: bool) { + self.autoconnect = ::std::option::Option::Some(v); + } + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(1); + let mut fields = ::std::vec::Vec::with_capacity(2); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( "host_name", |m: &ThpCredentialMetadata| { &m.host_name }, |m: &mut ThpCredentialMetadata| { &mut m.host_name }, )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "autoconnect", + |m: &ThpCredentialMetadata| { &m.autoconnect }, + |m: &mut ThpCredentialMetadata| { &mut m.autoconnect }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "ThpCredentialMetadata", fields, @@ -112,6 +3415,9 @@ impl ::protobuf::Message for ThpCredentialMetadata { 10 => { self.host_name = ::std::option::Option::Some(is.read_string()?); }, + 16 => { + self.autoconnect = ::std::option::Option::Some(is.read_bool()?); + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -127,6 +3433,9 @@ impl ::protobuf::Message for ThpCredentialMetadata { if let Some(v) = self.host_name.as_ref() { my_size += ::protobuf::rt::string_size(1, &v); } + if let Some(v) = self.autoconnect { + my_size += 1 + 1; + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -136,6 +3445,9 @@ impl ::protobuf::Message for ThpCredentialMetadata { if let Some(v) = self.host_name.as_ref() { os.write_string(1, v)?; } + if let Some(v) = self.autoconnect { + os.write_bool(2, v)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -154,12 +3466,14 @@ impl ::protobuf::Message for ThpCredentialMetadata { fn clear(&mut self) { self.host_name = ::std::option::Option::None; + self.autoconnect = ::std::option::Option::None; self.special_fields.clear(); } fn default_instance() -> &'static ThpCredentialMetadata { static instance: ThpCredentialMetadata = ThpCredentialMetadata { host_name: ::std::option::Option::None, + autoconnect: ::std::option::Option::None, special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -537,17 +3851,313 @@ impl ::protobuf::reflect::ProtobufValue for ThpAuthenticatedCredentialData { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] +// @@protoc_insertion_point(enum:hw.trezor.messages.thp.ThpMessageType) +pub enum ThpMessageType { + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCreateNewSession) + ThpMessageType_ThpCreateNewSession = 1000, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpPairingRequest) + ThpMessageType_ThpPairingRequest = 1006, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpPairingRequestApproved) + ThpMessageType_ThpPairingRequestApproved = 1007, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpSelectMethod) + ThpMessageType_ThpSelectMethod = 1008, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpPairingPreparationsFinished) + ThpMessageType_ThpPairingPreparationsFinished = 1009, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCredentialRequest) + ThpMessageType_ThpCredentialRequest = 1010, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCredentialResponse) + ThpMessageType_ThpCredentialResponse = 1011, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpEndRequest) + ThpMessageType_ThpEndRequest = 1012, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpEndResponse) + ThpMessageType_ThpEndResponse = 1013, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCodeEntryCommitment) + ThpMessageType_ThpCodeEntryCommitment = 1016, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCodeEntryChallenge) + ThpMessageType_ThpCodeEntryChallenge = 1017, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCodeEntryCpaceTrezor) + ThpMessageType_ThpCodeEntryCpaceTrezor = 1018, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCodeEntryCpaceHostTag) + ThpMessageType_ThpCodeEntryCpaceHostTag = 1019, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpCodeEntrySecret) + ThpMessageType_ThpCodeEntrySecret = 1020, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpQrCodeTag) + ThpMessageType_ThpQrCodeTag = 1024, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpQrCodeSecret) + ThpMessageType_ThpQrCodeSecret = 1025, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpNfcTagHost) + ThpMessageType_ThpNfcTagHost = 1032, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpMessageType.ThpMessageType_ThpNfcTagTrezor) + ThpMessageType_ThpNfcTagTrezor = 1033, +} + +impl ::protobuf::Enum for ThpMessageType { + const NAME: &'static str = "ThpMessageType"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 1000 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCreateNewSession), + 1006 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpPairingRequest), + 1007 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpPairingRequestApproved), + 1008 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpSelectMethod), + 1009 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpPairingPreparationsFinished), + 1010 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCredentialRequest), + 1011 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCredentialResponse), + 1012 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpEndRequest), + 1013 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpEndResponse), + 1016 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryCommitment), + 1017 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryChallenge), + 1018 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryCpaceTrezor), + 1019 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryCpaceHostTag), + 1020 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntrySecret), + 1024 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpQrCodeTag), + 1025 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpQrCodeSecret), + 1032 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpNfcTagHost), + 1033 => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpNfcTagTrezor), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "ThpMessageType_ThpCreateNewSession" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCreateNewSession), + "ThpMessageType_ThpPairingRequest" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpPairingRequest), + "ThpMessageType_ThpPairingRequestApproved" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpPairingRequestApproved), + "ThpMessageType_ThpSelectMethod" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpSelectMethod), + "ThpMessageType_ThpPairingPreparationsFinished" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpPairingPreparationsFinished), + "ThpMessageType_ThpCredentialRequest" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCredentialRequest), + "ThpMessageType_ThpCredentialResponse" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCredentialResponse), + "ThpMessageType_ThpEndRequest" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpEndRequest), + "ThpMessageType_ThpEndResponse" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpEndResponse), + "ThpMessageType_ThpCodeEntryCommitment" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryCommitment), + "ThpMessageType_ThpCodeEntryChallenge" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryChallenge), + "ThpMessageType_ThpCodeEntryCpaceTrezor" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryCpaceTrezor), + "ThpMessageType_ThpCodeEntryCpaceHostTag" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntryCpaceHostTag), + "ThpMessageType_ThpCodeEntrySecret" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpCodeEntrySecret), + "ThpMessageType_ThpQrCodeTag" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpQrCodeTag), + "ThpMessageType_ThpQrCodeSecret" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpQrCodeSecret), + "ThpMessageType_ThpNfcTagHost" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpNfcTagHost), + "ThpMessageType_ThpNfcTagTrezor" => ::std::option::Option::Some(ThpMessageType::ThpMessageType_ThpNfcTagTrezor), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [ThpMessageType] = &[ + ThpMessageType::ThpMessageType_ThpCreateNewSession, + ThpMessageType::ThpMessageType_ThpPairingRequest, + ThpMessageType::ThpMessageType_ThpPairingRequestApproved, + ThpMessageType::ThpMessageType_ThpSelectMethod, + ThpMessageType::ThpMessageType_ThpPairingPreparationsFinished, + ThpMessageType::ThpMessageType_ThpCredentialRequest, + ThpMessageType::ThpMessageType_ThpCredentialResponse, + ThpMessageType::ThpMessageType_ThpEndRequest, + ThpMessageType::ThpMessageType_ThpEndResponse, + ThpMessageType::ThpMessageType_ThpCodeEntryCommitment, + ThpMessageType::ThpMessageType_ThpCodeEntryChallenge, + ThpMessageType::ThpMessageType_ThpCodeEntryCpaceTrezor, + ThpMessageType::ThpMessageType_ThpCodeEntryCpaceHostTag, + ThpMessageType::ThpMessageType_ThpCodeEntrySecret, + ThpMessageType::ThpMessageType_ThpQrCodeTag, + ThpMessageType::ThpMessageType_ThpQrCodeSecret, + ThpMessageType::ThpMessageType_ThpNfcTagHost, + ThpMessageType::ThpMessageType_ThpNfcTagTrezor, + ]; +} + +impl ::protobuf::EnumFull for ThpMessageType { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().enum_by_package_relative_name("ThpMessageType").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = match self { + ThpMessageType::ThpMessageType_ThpCreateNewSession => 0, + ThpMessageType::ThpMessageType_ThpPairingRequest => 1, + ThpMessageType::ThpMessageType_ThpPairingRequestApproved => 2, + ThpMessageType::ThpMessageType_ThpSelectMethod => 3, + ThpMessageType::ThpMessageType_ThpPairingPreparationsFinished => 4, + ThpMessageType::ThpMessageType_ThpCredentialRequest => 5, + ThpMessageType::ThpMessageType_ThpCredentialResponse => 6, + ThpMessageType::ThpMessageType_ThpEndRequest => 7, + ThpMessageType::ThpMessageType_ThpEndResponse => 8, + ThpMessageType::ThpMessageType_ThpCodeEntryCommitment => 9, + ThpMessageType::ThpMessageType_ThpCodeEntryChallenge => 10, + ThpMessageType::ThpMessageType_ThpCodeEntryCpaceTrezor => 11, + ThpMessageType::ThpMessageType_ThpCodeEntryCpaceHostTag => 12, + ThpMessageType::ThpMessageType_ThpCodeEntrySecret => 13, + ThpMessageType::ThpMessageType_ThpQrCodeTag => 14, + ThpMessageType::ThpMessageType_ThpQrCodeSecret => 15, + ThpMessageType::ThpMessageType_ThpNfcTagHost => 16, + ThpMessageType::ThpMessageType_ThpNfcTagTrezor => 17, + }; + Self::enum_descriptor().value_by_index(index) + } +} + +// Note, `Default` is implemented although default value is not 0 +impl ::std::default::Default for ThpMessageType { + fn default() -> Self { + ThpMessageType::ThpMessageType_ThpCreateNewSession + } +} + +impl ThpMessageType { + fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("ThpMessageType") + } +} + +#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] +// @@protoc_insertion_point(enum:hw.trezor.messages.thp.ThpPairingMethod) +pub enum ThpPairingMethod { + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpPairingMethod.SkipPairing) + SkipPairing = 1, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpPairingMethod.CodeEntry) + CodeEntry = 2, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpPairingMethod.QrCode) + QrCode = 3, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.thp.ThpPairingMethod.NFC) + NFC = 4, +} + +impl ::protobuf::Enum for ThpPairingMethod { + const NAME: &'static str = "ThpPairingMethod"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 1 => ::std::option::Option::Some(ThpPairingMethod::SkipPairing), + 2 => ::std::option::Option::Some(ThpPairingMethod::CodeEntry), + 3 => ::std::option::Option::Some(ThpPairingMethod::QrCode), + 4 => ::std::option::Option::Some(ThpPairingMethod::NFC), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "SkipPairing" => ::std::option::Option::Some(ThpPairingMethod::SkipPairing), + "CodeEntry" => ::std::option::Option::Some(ThpPairingMethod::CodeEntry), + "QrCode" => ::std::option::Option::Some(ThpPairingMethod::QrCode), + "NFC" => ::std::option::Option::Some(ThpPairingMethod::NFC), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [ThpPairingMethod] = &[ + ThpPairingMethod::SkipPairing, + ThpPairingMethod::CodeEntry, + ThpPairingMethod::QrCode, + ThpPairingMethod::NFC, + ]; +} + +impl ::protobuf::EnumFull for ThpPairingMethod { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().enum_by_package_relative_name("ThpPairingMethod").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = match self { + ThpPairingMethod::SkipPairing => 0, + ThpPairingMethod::CodeEntry => 1, + ThpPairingMethod::QrCode => 2, + ThpPairingMethod::NFC => 3, + }; + Self::enum_descriptor().value_by_index(index) + } +} + +// Note, `Default` is implemented although default value is not 0 +impl ::std::default::Default for ThpPairingMethod { + fn default() -> Self { + ThpPairingMethod::SkipPairing + } +} + +impl ThpPairingMethod { + fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("ThpPairingMethod") + } +} + static file_descriptor_proto_data: &'static [u8] = b"\ \n\x12messages-thp.proto\x12\x16hw.trezor.messages.thp\x1a\roptions.prot\ - o\":\n\x15ThpCredentialMetadata\x12\x1b\n\thost_name\x18\x01\x20\x01(\tR\ - \x08hostName:\x04\x98\xb2\x19\x01\"\x82\x01\n\x14ThpPairingCredential\ + o\"\xa0\x02\n\x13ThpDeviceProperties\x12%\n\x0einternal_model\x18\x01\ + \x20\x01(\tR\rinternalModel\x12#\n\rmodel_variant\x18\x02\x20\x01(\rR\ + \x0cmodelVariant\x124\n\x16protocol_version_major\x18\x03\x20\x01(\rR\ + \x14protocolVersionMajor\x124\n\x16protocol_version_minor\x18\x04\x20\ + \x01(\rR\x14protocolVersionMinor\x12Q\n\x0fpairing_methods\x18\x05\x20\ + \x03(\x0e2(.hw.trezor.messages.thp.ThpPairingMethodR\x0epairingMethods\"\ + _\n%ThpHandshakeCompletionReqNoisePayload\x126\n\x17host_pairing_credent\ + ial\x18\x01\x20\x01(\x0cR\x15hostPairingCredential\"y\n\x13ThpCreateNewS\ + ession\x12\x1e\n\npassphrase\x18\x01\x20\x01(\tR\npassphrase\x12\x1b\n\t\ + on_device\x18\x02\x20\x01(\x08R\x08onDevice\x12%\n\x0ederive_cardano\x18\ + \x03\x20\x01(\x08R\rderiveCardano\"0\n\x11ThpPairingRequest\x12\x1b\n\th\ + ost_name\x18\x01\x20\x01(\tR\x08hostName\"\x1b\n\x19ThpPairingRequestApp\ + roved\"s\n\x0fThpSelectMethod\x12`\n\x17selected_pairing_method\x18\x01\ + \x20\x01(\x0e2(.hw.trezor.messages.thp.ThpPairingMethodR\x15selectedPair\ + ingMethod\"\x20\n\x1eThpPairingPreparationsFinished\"8\n\x16ThpCodeEntry\ + Commitment\x12\x1e\n\ncommitment\x18\x01\x20\x01(\x0cR\ncommitment\"5\n\ + \x15ThpCodeEntryChallenge\x12\x1c\n\tchallenge\x18\x01\x20\x01(\x0cR\tch\ + allenge\"P\n\x17ThpCodeEntryCpaceTrezor\x125\n\x17cpace_trezor_public_ke\ + y\x18\x01\x20\x01(\x0cR\x14cpaceTrezorPublicKey\"_\n\x18ThpCodeEntryCpac\ + eHostTag\x121\n\x15cpace_host_public_key\x18\x01\x20\x01(\x0cR\x12cpaceH\ + ostPublicKey\x12\x10\n\x03tag\x18\x02\x20\x01(\x0cR\x03tag\",\n\x12ThpCo\ + deEntrySecret\x12\x16\n\x06secret\x18\x01\x20\x01(\x0cR\x06secret\"\x20\ + \n\x0cThpQrCodeTag\x12\x10\n\x03tag\x18\x01\x20\x01(\x0cR\x03tag\")\n\ + \x0fThpQrCodeSecret\x12\x16\n\x06secret\x18\x01\x20\x01(\x0cR\x06secret\ + \"!\n\rThpNfcTagHost\x12\x10\n\x03tag\x18\x01\x20\x01(\x0cR\x03tag\"#\n\ + \x0fThpNfcTagTrezor\x12\x10\n\x03tag\x18\x01\x20\x01(\x0cR\x03tag\"f\n\ + \x14ThpCredentialRequest\x12,\n\x12host_static_pubkey\x18\x01\x20\x01(\ + \x0cR\x10hostStaticPubkey\x12\x20\n\x0bautoconnect\x18\x02\x20\x01(\x08R\ + \x0bautoconnect\"i\n\x15ThpCredentialResponse\x120\n\x14trezor_static_pu\ + bkey\x18\x01\x20\x01(\x0cR\x12trezorStaticPubkey\x12\x1e\n\ncredential\ + \x18\x02\x20\x01(\x0cR\ncredential\"\x0f\n\rThpEndRequest\"\x10\n\x0eThp\ + EndResponse\"\\\n\x15ThpCredentialMetadata\x12\x1b\n\thost_name\x18\x01\ + \x20\x01(\tR\x08hostName\x12\x20\n\x0bautoconnect\x18\x02\x20\x01(\x08R\ + \x0bautoconnect:\x04\x98\xb2\x19\x01\"\x82\x01\n\x14ThpPairingCredential\ \x12R\n\rcred_metadata\x18\x01\x20\x01(\x0b2-.hw.trezor.messages.thp.Thp\ CredentialMetadataR\x0ccredMetadata\x12\x10\n\x03mac\x18\x02\x20\x01(\ \x0cR\x03mac:\x04\x98\xb2\x19\x01\"\xa8\x01\n\x1eThpAuthenticatedCredent\ ialData\x12,\n\x12host_static_pubkey\x18\x01\x20\x01(\x0cR\x10hostStatic\ Pubkey\x12R\n\rcred_metadata\x18\x02\x20\x01(\x0b2-.hw.trezor.messages.t\ - hp.ThpCredentialMetadataR\x0ccredMetadata:\x04\x98\xb2\x19\x01B;\n#com.s\ - atoshilabs.trezor.lib.protobufB\x10TrezorMessageThp\x80\xa6\x1d\x01\ + hp.ThpCredentialMetadataR\x0ccredMetadata:\x04\x98\xb2\x19\x01*\xeb\x06\ + \n\x0eThpMessageType\x12-\n\"ThpMessageType_ThpCreateNewSession\x10\xe8\ + \x07\x1a\x04\x80\xa6\x1d\x01\x12+\n\x20ThpMessageType_ThpPairingRequest\ + \x10\xee\x07\x1a\x04\x80\xa6\x1d\x01\x123\n(ThpMessageType_ThpPairingReq\ + uestApproved\x10\xef\x07\x1a\x04\x80\xa6\x1d\x01\x12)\n\x1eThpMessageTyp\ + e_ThpSelectMethod\x10\xf0\x07\x1a\x04\x80\xa6\x1d\x01\x128\n-ThpMessageT\ + ype_ThpPairingPreparationsFinished\x10\xf1\x07\x1a\x04\x80\xa6\x1d\x01\ + \x12.\n#ThpMessageType_ThpCredentialRequest\x10\xf2\x07\x1a\x04\x80\xa6\ + \x1d\x01\x12/\n$ThpMessageType_ThpCredentialResponse\x10\xf3\x07\x1a\x04\ + \x80\xa6\x1d\x01\x12'\n\x1cThpMessageType_ThpEndRequest\x10\xf4\x07\x1a\ + \x04\x80\xa6\x1d\x01\x12(\n\x1dThpMessageType_ThpEndResponse\x10\xf5\x07\ + \x1a\x04\x80\xa6\x1d\x01\x120\n%ThpMessageType_ThpCodeEntryCommitment\ + \x10\xf8\x07\x1a\x04\x80\xa6\x1d\x01\x12/\n$ThpMessageType_ThpCodeEntryC\ + hallenge\x10\xf9\x07\x1a\x04\x80\xa6\x1d\x01\x121\n&ThpMessageType_ThpCo\ + deEntryCpaceTrezor\x10\xfa\x07\x1a\x04\x80\xa6\x1d\x01\x122\n'ThpMessage\ + Type_ThpCodeEntryCpaceHostTag\x10\xfb\x07\x1a\x04\x80\xa6\x1d\x01\x12,\n\ + !ThpMessageType_ThpCodeEntrySecret\x10\xfc\x07\x1a\x04\x80\xa6\x1d\x01\ + \x12&\n\x1bThpMessageType_ThpQrCodeTag\x10\x80\x08\x1a\x04\x80\xa6\x1d\ + \x01\x12)\n\x1eThpMessageType_ThpQrCodeSecret\x10\x81\x08\x1a\x04\x80\ + \xa6\x1d\x01\x12'\n\x1cThpMessageType_ThpNfcTagHost\x10\x88\x08\x1a\x04\ + \x80\xa6\x1d\x01\x12)\n\x1eThpMessageType_ThpNfcTagTrezor\x10\x89\x08\ + \x1a\x04\x80\xa6\x1d\x01\"\x05\x08\0\x10\xe7\x07\"\t\x08\xcc\x08\x10\xff\ + \xff\xff\xff\x07*G\n\x10ThpPairingMethod\x12\x0f\n\x0bSkipPairing\x10\ + \x01\x12\r\n\tCodeEntry\x10\x02\x12\n\n\x06QrCode\x10\x03\x12\x07\n\x03N\ + FC\x10\x04B;\n#com.satoshilabs.trezor.lib.protobufB\x10TrezorMessageThp\ + \x80\xa6\x1d\x01\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -566,11 +4176,33 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { let mut deps = ::std::vec::Vec::with_capacity(1); deps.push(super::options::file_descriptor().clone()); - let mut messages = ::std::vec::Vec::with_capacity(3); + let mut messages = ::std::vec::Vec::with_capacity(23); + messages.push(ThpDeviceProperties::generated_message_descriptor_data()); + messages.push(ThpHandshakeCompletionReqNoisePayload::generated_message_descriptor_data()); + messages.push(ThpCreateNewSession::generated_message_descriptor_data()); + messages.push(ThpPairingRequest::generated_message_descriptor_data()); + messages.push(ThpPairingRequestApproved::generated_message_descriptor_data()); + messages.push(ThpSelectMethod::generated_message_descriptor_data()); + messages.push(ThpPairingPreparationsFinished::generated_message_descriptor_data()); + messages.push(ThpCodeEntryCommitment::generated_message_descriptor_data()); + messages.push(ThpCodeEntryChallenge::generated_message_descriptor_data()); + messages.push(ThpCodeEntryCpaceTrezor::generated_message_descriptor_data()); + messages.push(ThpCodeEntryCpaceHostTag::generated_message_descriptor_data()); + messages.push(ThpCodeEntrySecret::generated_message_descriptor_data()); + messages.push(ThpQrCodeTag::generated_message_descriptor_data()); + messages.push(ThpQrCodeSecret::generated_message_descriptor_data()); + messages.push(ThpNfcTagHost::generated_message_descriptor_data()); + messages.push(ThpNfcTagTrezor::generated_message_descriptor_data()); + messages.push(ThpCredentialRequest::generated_message_descriptor_data()); + messages.push(ThpCredentialResponse::generated_message_descriptor_data()); + messages.push(ThpEndRequest::generated_message_descriptor_data()); + messages.push(ThpEndResponse::generated_message_descriptor_data()); messages.push(ThpCredentialMetadata::generated_message_descriptor_data()); messages.push(ThpPairingCredential::generated_message_descriptor_data()); messages.push(ThpAuthenticatedCredentialData::generated_message_descriptor_data()); - let mut enums = ::std::vec::Vec::with_capacity(0); + let mut enums = ::std::vec::Vec::with_capacity(2); + enums.push(ThpMessageType::generated_enum_descriptor_data()); + enums.push(ThpPairingMethod::generated_enum_descriptor_data()); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( file_descriptor_proto(), deps, diff --git a/tests/device_tests/thp/__init__.py b/tests/device_tests/thp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/device_tests/thp/test_thp.py b/tests/device_tests/thp/test_thp.py new file mode 100644 index 0000000000..2b7c7fe1f9 --- /dev/null +++ b/tests/device_tests/thp/test_thp.py @@ -0,0 +1,332 @@ +import os +import random +import typing as t +from hashlib import sha256 + +import pytest +import typing_extensions as tx + +from trezorlib import protobuf +from trezorlib.client import ProtocolV2 +from trezorlib.debuglink import TrezorClientDebugLink as Client +from trezorlib.messages import ( + ButtonAck, + ButtonRequest, + ThpCodeEntryChallenge, + ThpCodeEntryCommitment, + ThpCodeEntryCpaceHostTag, + ThpCodeEntryCpaceTrezor, + ThpCodeEntrySecret, + ThpCredentialRequest, + ThpCredentialResponse, + ThpEndRequest, + ThpEndResponse, + ThpNfcTagHost, + ThpNfcTagTrezor, + ThpPairingMethod, + ThpPairingPreparationsFinished, + ThpPairingRequest, + ThpPairingRequestApproved, + ThpQrCodeSecret, + ThpQrCodeTag, + ThpSelectMethod, +) +from trezorlib.transport.thp import curve25519 +from trezorlib.transport.thp.cpace import Cpace +from trezorlib.transport.thp.protocol_v2 import _hkdf + +if t.TYPE_CHECKING: + P = tx.ParamSpec("P") + +MT = t.TypeVar("MT", bound=protobuf.MessageType) + +pytestmark = [pytest.mark.protocol("protocol_v2")] + + +def _prepare_protocol(client: Client) -> ProtocolV2: + protocol = client.protocol + assert isinstance(protocol, ProtocolV2) + protocol._reset_sync_bits() + return protocol + + +def _prepare_protocol_for_pairing(client: Client) -> ProtocolV2: + protocol = _prepare_protocol(client) + protocol._do_channel_allocation() + protocol._do_handshake() + return protocol + + +def _handle_pairing_request(client: Client, protocol: ProtocolV2) -> None: + protocol._send_message(ThpPairingRequest()) + button_req = protocol._read_message(ButtonRequest) + assert button_req.name == "pairing_request" + + protocol._send_message(ButtonAck()) + + client.debug.press_yes() + + protocol._read_message(ThpPairingRequestApproved) + + +def test_allocate_channel(client: Client) -> None: + protocol = _prepare_protocol(client) + + nonce = random.randbytes(8) + + # Use valid nonce + protocol._send_channel_allocation_request(nonce) + protocol._read_channel_allocation_response(nonce) + + # Expect different nonce + protocol._send_channel_allocation_request(nonce) + with pytest.raises(Exception, match="Invalid channel allocation response."): + protocol._read_channel_allocation_response( + expected_nonce=b"\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF" + ) + client.invalidate() + + +def test_handshake(client: Client) -> None: + protocol = _prepare_protocol(client) + + host_ephemeral_privkey = curve25519.get_private_key(os.urandom(32)) + host_ephemeral_pubkey = curve25519.get_public_key(host_ephemeral_privkey) + + protocol._do_channel_allocation() + protocol._send_handshake_init_request(host_ephemeral_pubkey) + protocol._read_ack() + init_response = protocol._read_handshake_init_response() + + trezor_ephemeral_pubkey = init_response[:32] + encrypted_trezor_static_pubkey = init_response[32:80] + noise_tag = init_response[80:96] + + # TODO check noise_tag is valid + + ck = protocol._send_handshake_completion_request( + host_ephemeral_pubkey, + host_ephemeral_privkey, + trezor_ephemeral_pubkey, + encrypted_trezor_static_pubkey, + ) + protocol._read_ack() + protocol._read_handshake_completion_response() + protocol.key_request, protocol.key_response = _hkdf(ck, b"") + protocol.nonce_request = 0 + protocol.nonce_response = 1 + + # TODO - without pairing, the client is damaged and results in fail of the following test + # so far no luck in solving it - it should be also tackled in FW, as it causes unexpected FW error + protocol._do_pairing(client.debug) + + # TODO the following is just to make style checker happy + assert noise_tag is not None + + +def test_pairing_qr_code(client: Client) -> None: + protocol = _prepare_protocol_for_pairing(client) + _handle_pairing_request(client, protocol) + protocol._send_message( + ThpSelectMethod(selected_pairing_method=ThpPairingMethod.QrCode) + ) + protocol._read_message(ThpPairingPreparationsFinished) + + # QR Code shown + protocol._read_message(ButtonRequest) + protocol._send_message(ButtonAck()) + + # Read code from "Trezor's display" using debuglink + + pairing_info = client.debug.pairing_info( + thp_channel_id=protocol.channel_id.to_bytes(2, "big") + ) + code = pairing_info.code_qr_code + + # Compute tag for response + sha_ctx = sha256(protocol.handshake_hash) + sha_ctx.update(code) + tag = sha_ctx.digest() + + protocol._send_message(ThpQrCodeTag(tag=tag)) + + secret_msg = protocol._read_message(ThpQrCodeSecret) + + # Check that the `code` was derived from the revealed secret + sha_ctx = sha256(ThpPairingMethod.QrCode.to_bytes(1, "big")) + sha_ctx.update(protocol.handshake_hash) + sha_ctx.update(secret_msg.secret) + computed_code = sha_ctx.digest()[:16] + assert code == computed_code + + protocol._send_message(ThpEndRequest()) + protocol._read_message(ThpEndResponse) + + protocol._has_valid_channel = True + + +def test_pairing_code_entry(client: Client) -> None: + protocol = _prepare_protocol_for_pairing(client) + + _handle_pairing_request(client, protocol) + + protocol._send_message( + ThpSelectMethod(selected_pairing_method=ThpPairingMethod.CodeEntry) + ) + + commitment_msg = protocol._read_message(ThpCodeEntryCommitment) + commitment = commitment_msg.commitment + + challenge = random.randbytes(16) + protocol._send_message(ThpCodeEntryChallenge(challenge=challenge)) + + cpace_trezor = protocol._read_message(ThpCodeEntryCpaceTrezor) + cpace_trezor_public_key = cpace_trezor.cpace_trezor_public_key + + # Code Entry code shown + protocol._read_message(ButtonRequest) + protocol._send_message(ButtonAck()) + + pairing_info = client.debug.pairing_info( + thp_channel_id=protocol.channel_id.to_bytes(2, "big") + ) + code = pairing_info.code_entry_code + + cpace = Cpace(handshake_hash=protocol.handshake_hash) + cpace.random_bytes = random.randbytes + cpace.generate_keys_and_secret(code.to_bytes(6, "big"), cpace_trezor_public_key) + sha_ctx = sha256(cpace.shared_secret) + tag = sha_ctx.digest() + + protocol._send_message( + ThpCodeEntryCpaceHostTag( + cpace_host_public_key=cpace.host_public_key, + tag=tag, + ) + ) + + secret_msg = protocol._read_message(ThpCodeEntrySecret) + + # Check `commitment` and `code` + sha_ctx = sha256(secret_msg.secret) + computed_commitment = sha_ctx.digest() + assert commitment == computed_commitment + + sha_ctx = sha256(ThpPairingMethod.CodeEntry.to_bytes(1, "big")) + sha_ctx.update(protocol.handshake_hash) + sha_ctx.update(secret_msg.secret) + sha_ctx.update(challenge) + code_hash = sha_ctx.digest() + computed_code = int.from_bytes(code_hash, "big") % 1000000 + assert code == computed_code + + protocol._send_message(ThpEndRequest()) + protocol._read_message(ThpEndResponse) + + protocol._has_valid_channel = True + + +def test_pairing_nfc(client: Client) -> None: + protocol = _prepare_protocol_for_pairing(client) + + _nfc_pairing(client, protocol) + + protocol._send_message(ThpEndRequest()) + protocol._read_message(ThpEndResponse) + protocol._has_valid_channel = True + + +def _nfc_pairing(client: Client, protocol: ProtocolV2): + + _handle_pairing_request(client, protocol) + + protocol._send_message( + ThpSelectMethod(selected_pairing_method=ThpPairingMethod.NFC) + ) + protocol._read_message(ThpPairingPreparationsFinished) + + # NFC screen shown + protocol._read_message(ButtonRequest) + protocol._send_message(ButtonAck()) + + nfc_secret_host = random.randbytes(16) + # Read `nfc_secret` and `handshake_hash` from Trezor using debuglink + pairing_info = client.debug.pairing_info( + thp_channel_id=protocol.channel_id.to_bytes(2, "big"), + handshake_hash=protocol.handshake_hash, + nfc_secret_host=nfc_secret_host, + ) + handshake_hash_trezor = pairing_info.handshake_hash + nfc_secret_trezor = pairing_info.nfc_secret_trezor + + assert handshake_hash_trezor[:16] == protocol.handshake_hash[:16] + + # Compute tag for response + sha_ctx = sha256(ThpPairingMethod.NFC.to_bytes(1, "big")) + sha_ctx.update(protocol.handshake_hash) + sha_ctx.update(nfc_secret_trezor) + tag_host = sha_ctx.digest() + + protocol._send_message(ThpNfcTagHost(tag=tag_host)) + + tag_trezor_msg = protocol._read_message(ThpNfcTagTrezor) + + # Check that the `code` was derived from the revealed secret + sha_ctx = sha256(ThpPairingMethod.NFC.to_bytes(1, "big")) + sha_ctx.update(protocol.handshake_hash) + sha_ctx.update(nfc_secret_host) + computed_tag = sha_ctx.digest() + assert tag_trezor_msg.tag == computed_tag + + +def test_credential_phase(client: Client): + protocol = _prepare_protocol_for_pairing(client) + _nfc_pairing(client, protocol) + + # Request credential with confirmation after pairing + host_static_privkey = curve25519.get_private_key(os.urandom(32)) + host_static_pubkey = curve25519.get_public_key(host_static_privkey) + protocol._send_message( + ThpCredentialRequest(host_static_pubkey=host_static_pubkey, autoconnect=False) + ) + credential_response = protocol._read_message(ThpCredentialResponse) + + assert credential_response.credential is not None + credential = credential_response.credential + protocol._send_message(ThpEndRequest()) + protocol._read_message(ThpEndResponse) + + # Connect using credential with confirmation + protocol = _prepare_protocol(client) + protocol._do_channel_allocation() + protocol._do_handshake(credential, host_static_privkey) + protocol._send_message(ThpEndRequest()) + button_req = protocol._read_message(ButtonRequest) + assert button_req.name == "connection_request" + protocol._send_message(ButtonAck()) + client.debug.press_yes() + protocol._read_message(ThpEndResponse) + + # Connect using credential with confirmation and ask for autoconnect credential + protocol = _prepare_protocol(client) + protocol._do_channel_allocation() + protocol._do_handshake(credential, host_static_privkey) + protocol._send_message( + ThpCredentialRequest(host_static_pubkey=host_static_pubkey, autoconnect=True) + ) + button_req = protocol._read_message(ButtonRequest) + assert button_req.name == "connection_request" + protocol._send_message(ButtonAck()) + client.debug.press_yes() + credential_response_2 = protocol._read_message(ThpCredentialResponse) + assert credential_response_2.credential is not None + credential_auto = credential_response_2.credential + protocol._send_message(ThpEndRequest()) + protocol._read_message(ThpEndResponse) + + # Connect using autoconnect credential + protocol = _prepare_protocol(client) + protocol._do_channel_allocation() + protocol._do_handshake(credential_auto, host_static_privkey) + protocol._send_message(ThpEndRequest()) + protocol._read_message(ThpEndResponse)