diff --git a/README.md b/README.md index 6ae34cd42..7fe924b1b 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,152 @@ # Ledger Bitcoin Application -## Prerequisite +This is the Bitcoin application for Ledger Nano X/SP, Stax and Flex. -Be sure to have your environment correctly set up (see [Getting Started](https://developers.ledger.com/docs/nano-app/introduction/)) and [ledgerblue](https://pypi.org/project/ledgerblue/) installed. +## Quick start guide -If you want to benefit from [vscode](https://code.visualstudio.com/) integration, it's recommended to move the toolchain in `/opt` and set `BOLOS_ENV` environment variable as follows +### With VSCode +You can quickly setup a convenient environment to build and test your application by using [Ledger's VSCode developer tools extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools) which leverages the [ledger-app-dev-tools](https://github.com/LedgerHQ/ledger-app-builder/pkgs/container/ledger-app-builder%2Fledger-app-dev-tools) docker image. + +It will allow you, whether you are developing on macOS, Windows or Linux to quickly **build** your apps, **test** them on **Speculos** and **load** them on any supported device. + +* Install and run [Docker](https://www.docker.com/products/docker-desktop/). +* Make sure you have an X11 server running : + * On Ubuntu Linux, it should be running by default. + * On macOS, install and launch [XQuartz](https://www.xquartz.org/) (make sure to go to XQuartz > Preferences > Security and check "Allow client connections"). + * On Windows, install and launch [VcXsrv](https://sourceforge.net/projects/vcxsrv/) (make sure to configure it to disable access control). +* Install [VScode](https://code.visualstudio.com/download) and add [Ledger's extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools). +* Open a terminal and clone `app-bitcoin-new` with `git clone git@github.com:LedgerHQ/app-bitcoin-new.git`. +* Open the `app-bitcoin-new` folder with VSCode. +* Use Ledger extension's sidebar menu or open the tasks menu with `ctrl + shift + b` (`command + shift + b` on a Mac) to conveniently execute actions : + * Build the app for the device model of your choice with `Build`. + * Test your binary on [Speculos](https://github.com/LedgerHQ/speculos) with `Run with Speculos`. + * You can also run functional tests, load the app on a physical device, and more. + +:information_source: The terminal tab of VSCode will show you what commands the extension runs behind the scene. + +### With a terminal + +The [ledger-app-dev-tools](https://github.com/LedgerHQ/ledger-app-builder/pkgs/container/ledger-app-builder%2Fledger-app-dev-tools) docker image contains all the required tools and libraries to **build**, **test** and **load** an application. + +You can download it from the ghcr.io docker repository: + +```shell +sudo docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest ``` -BOLOS_ENV=/opt/bolos-devenv -``` -and do the same with `BOLOS_SDK` environment variable +You can then enter this development environment by executing the following command from the directory of the application `git` repository: + +**Linux (Ubuntu)** +```shell +sudo docker run --rm -ti --user "$(id -u):$(id -g)" --privileged -v "/dev/bus/usb:/dev/bus/usb" -v "$(realpath .):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest ``` -BOLOS_SDK=/opt/nanos-secure-sdk + +**macOS** + +```shell +sudo docker run --rm -ti --user "$(id -u):$(id -g)" --privileged -v "$(pwd -P):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest ``` -## Compilation +**Windows (with PowerShell)** +```shell +docker run --rm -ti --privileged -v "$(Get-Location):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest ``` + +The application's code will be available from inside the docker container, you can proceed to the following compilation steps to build your app. + +## Compilation and load + +To easily setup a development environment for compilation and loading on a physical device, you can use the [VSCode integration](#with-vscode) whether you are on Linux, macOS or Windows. + +If you prefer using a terminal to perform the steps manually, you can use the guide below. + +### Compilation + +Setup a compilation environment by following the [shell with docker approach](#with-a-terminal). + +From inside the container, use the following command to build the app : + +```shell make DEBUG=1 # compile optionally with PRINTF -make load # load the app on the Nano using ledgerblue ``` -## Documentation +You can choose which device to compile and load for by setting the `BOLOS_SDK` environment variable to the following values : + +* `BOLOS_SDK=$NANOX_SDK` +* `BOLOS_SDK=$NANOSP_SDK` +* `BOLOS_SDK=$STAX_SDK` + +By default this variable is set to build/load for Nano S+. + +The app is compiled for testnet by default. In order to compile the app for mainnet, add `COIN=bitcoin` when running the `make` command. + +### Loading on a physical device + +This step will vary slightly depending on your platform. + +:information_source: Your physical device must be connected, unlocked and the screen showing the dashboard (not inside an application). + +**Linux (Ubuntu)** -High level documentation on the architecture and interface of the app: -- [bitcoin.md](doc/bitcoin.md): specifications of application commands. -- [wallet.md](doc/wallet.md): supported wallet signing policies. -- [merkle.md](doc/merkle.md): rationale and specifications for the usage of Merkle trees. +First make sure you have the proper udev rules added on your host : -Additional documentation can be generated with [doxygen](https://www.doxygen.nl) +```shell +# Run these commands on your host, from the app's source folder. +sudo cp .vscode/20-ledger.ledgerblue.rules /etc/udev/rules.d/ +sudo udevadm control --reload-rules +sudo udevadm trigger +``` + +Then once you have [opened a terminal](#with-a-terminal) in the `app-builder` image and [built the app](#compilation-and-load) for the device you want, run the following command : +```shell +# Run this command from the app-builder container terminal. +make load # load the app on a Nano S+ by default ``` -doxygen .doxygen/Doxyfile + +[Setting the BOLOS_SDK environment variable](#compilation-and-load) will allow you to load on whichever supported device you want. + +**macOS / Windows (with PowerShell)** + +:information_source: It is assumed you have [Python](https://www.python.org/downloads/) installed on your computer. + +Run these commands on your host from the app's source folder once you have [built the app](#compilation-and-load) for the device you want : + +```shell +# Install Python virtualenv +python3 -m pip install virtualenv +# Create the 'ledger' virtualenv +python3 -m virtualenv ledger ``` -the process outputs HTML and LaTeX documentations in `doc/html` and `doc/latex` folders. +Enter the Python virtual environment -## Client libraries +* macOS : `source ledger/bin/activate` +* Windows : `.\ledger\Scripts\Activate.ps1` -A [Python client library](bitcoin_client), a [TypeScript client library](bitcoin_client_js) and a [Rust client library](bitcoin_client_rs) are available in this repository. +```shell +# Install Ledgerblue (tool to load the app) +python3 -m pip install ledgerblue +# Load the app. +python3 -m ledgerblue.runScript --scp --fileName bin/app.apdu --elfFile bin/app.elf +``` -## Tests & Continuous Integration +## Documentation -The flow processed in [GitHub Actions](https://github.com/features/actions) is the following: +For many use cases, the code examples provided in the following client libraries might be sufficient to get started: +- [Python client library](bitcoin_client) +- [JavaScript client library](bitcoin_client_js) +- [Rust client library](bitcoin_client_rs) -- Code formatting with [clang-format](http://clang.llvm.org/docs/ClangFormat.html) -- Compilation of the application for Ledger Nano S in [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder) -- Unit tests of C functions with [cmocka](https://cmocka.org/) (see [unit-tests/](unit-tests/)) -- End-to-end tests with [Speculos](https://github.com/LedgerHQ/speculos) emulator (see [tests/](tests/)) -- Code coverage with [gcov](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html)/[lcov](http://ltp.sourceforge.net/coverage/lcov.php) and upload to [codecov.io](https://about.codecov.io) -- Documentation generation with [doxygen](https://www.doxygen.nl) +If you need to go deeper into the rabbit hole 🐇🕳️, refer to the following documents: +- [bitcoin.md](doc/bitcoin.md): Low-level documentation of the Bitcoin app's communication protocol and commands. +- [merkle.md](doc/merkle.md): Advanced details on techniques used in the Bitcoin app's secured and scalable communication protocol. +- [wallet.md](doc/wallet.md): Information on the types of scripts supported by the Ledger Bitcoin app and the security requirements for multi-user or multi-key spending policies. +- [debugging.md](doc/debugging.md): Guidance on how to diagnose and resolve issues. -It outputs 4 artifacts: +## Tests -- `bitcoin-app-debug` within output files of the compilation process in debug mode -- `code-coverage` within HTML details of code coverage -- `documentation` within HTML auto-generated documentation +See the [tests](tests) folder for documentation on the functional and end-to-end test suites of the bitcoin app, and the [unit-tests](unit-tests) folder for the C unit tests. diff --git a/doc/bitcoin.md b/doc/bitcoin.md index cd19cb308..dc3dc5d3b 100644 --- a/doc/bitcoin.md +++ b/doc/bitcoin.md @@ -8,7 +8,7 @@ The protocol documentation for version from 2.0.0 and before 2.1.0 is [here](./v ### APDUs -The messaging format of the app is compatible with the [APDU protocol](https://developers.ledger.com/docs/nano-app/application-structure/#apdu-interpretation-loop). The `P1` field is reserved for future use and must be set to `0` in all messages. The `P2` field is used as a protocol version identifier; the current version is `1`, while version `0` is still supported. No other value must be used. +The messaging format of the app is compatible with the [APDU protocol](https://developers.ledger.com/docs/device-app/explanation/io#apdu-interpretation-loop). The `P1` field is reserved for future use and must be set to `0` in all messages. The `P2` field is used as a protocol version identifier; the current version is `1`, while version `0` is still supported. No other value must be used. The main commands use `CLA = 0xE1`, unlike the legacy Bitcoin application that used `CLA = 0xE0`. diff --git a/doc/debugging.md b/doc/debugging.md new file mode 100644 index 000000000..e418c37e9 --- /dev/null +++ b/doc/debugging.md @@ -0,0 +1,23 @@ +This page contains some information for developers on how to debug failures of the commands of the Ledger Bitcoin app. + +# Status Words + +Failures in the app's commands are always reported by a corresponding Status Word; see [bitcoin.md](bitcoin.md#status-words) for the currently defined Status Words. Client libraries generally return structured versions of the same Status Words. + +The Status Word is contained in the last 2 bytes of the APDU Response, interpreted as a big-endian 16-bit constant. + +# Error codes + +In addition to the Status Word, some errors provide further details as an _error code_, contained in the first two bytes of the _data_ portion of the reply. + +Integrations can ignore the error codes and should not rely on them in production (as they are not guaranteed to be consistent across versions of the app); however, they can provide valuable debugging information for developers working on an integration. + +You can see the list of the currently defined error codes in [error_codes.h](../src/error_codes.h). + +# Running on Speculos with semihosting + +When running the bitcoin app on the [speculos](https://github.com/LedgerHQ/speculos) emulator, additional debugging information can be printed on the command line (on the same terminal where speculos is running) by defining the `DEBUG=10` constant when running the `make` command. See the [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder?tab=readme-ov-file#compile-your-app-in-the-container) for instructions on how to build the app for speculos. + +Binaries produced in this way _will_ crash if sideloading on a real device. + +Note: `DEBUG=10` is a feature of the Bitcoin app, and is not used in other Ledger apps. diff --git a/doc/v0/bitcoin.md b/doc/v0/bitcoin.md index 04bc5665d..3ab115280 100644 --- a/doc/v0/bitcoin.md +++ b/doc/v0/bitcoin.md @@ -6,7 +6,7 @@ This page described the _deprecated_ version 0 of the protocol, as implemented i ### APDUs -The messaging format of the app is compatible with the [APDU protocol](https://developers.ledger.com/docs/nano-app/application-structure/#apdu-interpretation-loop). The `P1` and `P2` fields are reserved for future use and must be set to `0` in all messages. +The messaging format of the app is compatible with the [APDU protocol](https://developers.ledger.com/docs/device-app/explanation/io#apdu-interpretation-loop). The `P1` and `P2` fields are reserved for future use and must be set to `0` in all messages. The main commands use `CLA = 0xE1`, unlike the legacy Bitcoin application that used `CLA = 0xE0`. diff --git a/src/boilerplate/dispatcher.h b/src/boilerplate/dispatcher.h index b95450947..8a36dfe5a 100644 --- a/src/boilerplate/dispatcher.h +++ b/src/boilerplate/dispatcher.h @@ -47,6 +47,15 @@ static inline void SEND_RESPONSE(struct dispatcher_context_s *dc, dc->send_response(); } +static inline void SEND_SW_EC(struct dispatcher_context_s *dc, uint16_t sw, uint16_t error_code) { + uint8_t error_code_buf[2]; + error_code_buf[0] = (error_code >> 8) & 0xFF; + error_code_buf[1] = error_code & 0xFF; + dc->add_to_response(&error_code_buf, sizeof(error_code_buf)); + dc->finalize_response(sw); + dc->send_response(); +} + /** * Describes a command that can be processed by the dispatcher. */ diff --git a/src/error_codes.h b/src/error_codes.h new file mode 100644 index 000000000..feb283670 --- /dev/null +++ b/src/error_codes.h @@ -0,0 +1,110 @@ +#pragma once + +/** + * REGISTER_WALLET + */ + +// The name of the policy is not acceptable +#define EC_REGISTER_WALLET_UNACCEPTABLE_POLICY_NAME 0x0000 + +// The wallet policy does not respect the requirement of BIP-388, or the sanity rules of miniscript +#define EC_REGISTER_WALLET_POLICY_NOT_SANE 0x0001 + +// No key in the wallet policy was recognized as internal. +#define EC_REGISTER_WALLET_POLICY_HAS_NO_INTERNAL_KEY 0x0002 + +/** + * SIGN_PSBT + */ + +// The wallet policy is not standard; it must be registered first and the HMAC must be provided. +#define EC_SIGN_PSBT_MISSING_HMAC_FOR_NONDEFAULT_POLICY 0x0000 + +// For standard wallet policies, the name must be zero-length (empty). +#define EC_SIGN_PSBT_NO_NAME_FOR_DEFAULT_POLICY 0x0001 + +// No key in the wallet policy was recognized as internal. +#define EC_SIGN_PSBT_WALLET_POLICY_HAS_NO_INTERNAL_KEY 0x0002 + +// Depending on the transaction type, at least one of the non-witness UTXO or witness UTXO must be +// present in the PSBT. Check in BIP-174 for the specific requirements for the transaction type. +#define EC_SIGN_PSBT_MISSING_NONWITNESSUTXO_AND_WITNESSUTXO 0x0003 + +// Failed to check the txid recomputed from the non-witness-utxo. Make sure that the +// non-witness-utxo and the PSBT_IN_PREVIOUS_TXID fields are filled correctly. +#define EC_SIGN_PSBT_NONWITNESSUTXO_CHECK_FAILED 0x0004 + +// The scriptpubkey or the amount in the non-witness-utxo does not match the one in the +// witness-utxo. +#define EC_SIGN_PSBT_NONWITNESSUTXO_AND_WITNESSUTXO_MISMATCH 0x0005 + +// Per BIP-174, legacy inputs must have the non-witness-utxo, but no witness-utxo. +#define EC_SIGN_PSBT_MISSING_NONWITNESSUTXO_OR_UNEXPECTED_WITNESSUTXO_FOR_LEGACY 0x0006 + +// Per BIP-174, all segwit (or taproot) inputs must have the witness-utxo field. +#define EC_SIGN_PSBT_MISSING_WITNESSUTXO_FOR_SEGWIT 0x0007 + +// If an input has SIGHASH_SINGLE, its index must be less than the number of outputs. +#define EC_SIGN_PSBT_UNALLOWED_SIGHASH_SINGLE 0x0008 + +// The number of change outputs is larger than the maximum that is allowed. +#define EC_SIGN_PSBT_TOO_MANY_CHANGE_OUTPUTS 0x0009 + +// The witness script in the PSBT is incorrect. +#define EC_SIGN_PSBT_MISMATCHING_WITNESS_SCRIPT 0x000a + +// The redeem Script in the PSBT is incorrect. +#define EC_SIGN_PSBT_MISMATCHING_REDEEM_SCRIPT 0x000b + +/** + * Swap + */ + +// For swap error codes, the first byte is standardized across apps. +// Refer to the documentation of app-exchange. + +// Internal application error, forward to the firmware team for analysis. +#define EC_SWAP_ERROR_INTERNAL 0x0000 + +// The amount does not match the one validated in Exchange. +#define EC_SWAP_ERROR_WRONG_AMOUNT 0x0100 + +// The destination address does not match the one validated in Exchange. +#define EC_SWAP_ERROR_WRONG_DESTINATION 0x0200 + +// The fees are different from what was validated in Exchange. +#define EC_SWAP_ERROR_WRONG_FEES 0x0300 + +// The method used is invalid in Exchange context. +#define EC_SWAP_ERROR_WRONG_METHOD 0x0400 +// Only default wallet policies can be used in swaps. +#define EC_SWAP_ERROR_WRONG_METHOD_NONDEFAULT_POLICY 0x0401 +// No external inputs allowed in swap transactions. +#define EC_SWAP_ERROR_WRONG_METHOD_EXTERNAL_INPUTS 0x0402 +// Segwit transaction in swap must have the non-witness UTXO in the PSBT. +#define EC_SWAP_ERROR_WRONG_METHOD_MISSING_NONWITNESSUTXO 0x0403 +// Standard swap transaction must have exactly 1 external output. +#define EC_SWAP_ERROR_WRONG_METHOD_WRONG_N_OF_OUTPUTS 0x0404 +// Invalid or unsupported script for external output. +#define EC_SWAP_ERROR_WRONG_METHOD_WRONG_UNSUPPORTED_OUTPUT 0x0405 + +// The mode used for the cross-chain hash validation is not supported. +#define EC_SWAP_ERROR_CROSSCHAIN_WRONG_MODE 0x0500 + +// The method used is invalid in cross-chain Exchange context. +#define EC_SWAP_ERROR_CROSSCHAIN_WRONG_METHOD 0x0600 +// The first output must be OP_RETURN for a cross-chain swap. +#define EC_SWAP_ERROR_CROSSCHAIN_WRONG_METHOD_INVALID_FIRST_OUTPUT 0x0601 +// OP_RETURN with non-zero value is not supported. +#define EC_SWAP_ERROR_CROSSCHAIN_WRONG_METHOD_NONZERO_AMOUNT 0x0602 + +// The hash for the cross-chain transaction does not match the validated value. +#define EC_SWAP_ERROR_CROSSCHAIN_WRONG_HASH 0x0700 + +// A generic or unspecified error not covered by the specific error codes above. Refer to the +// remaining bytes for further details on the error. +#define EC_SWAP_ERROR_GENERIC 0xFF00 +// Unknown swap mode. +#define EC_SWAP_ERROR_GENERIC_UNKNOWN_MODE 0xFF01 +// handle_swap_sign_transaction.c::copy_transaction_parameters failed. +#define EC_SWAP_ERROR_GENERIC_COPY_TRANSACTION_PARAMETERS_FAILED 0xFF02 diff --git a/src/handler/get_wallet_address.c b/src/handler/get_wallet_address.c index 9a1065148..373cdb146 100644 --- a/src/handler/get_wallet_address.c +++ b/src/handler/get_wallet_address.c @@ -30,6 +30,7 @@ #include "../commands.h" #include "../constants.h" #include "../crypto.h" +#include "../error_codes.h" #include "../ui/display.h" #include "../ui/menu.h" @@ -162,7 +163,7 @@ void handler_get_wallet_address(dispatcher_context_t *dc, uint8_t protocol_versi // Swap feature: check that the wallet policy is a default one if (G_swap_state.called_from_swap && !is_wallet_default) { PRINTF("Must be a default wallet policy for swap feature\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_METHOD_NONDEFAULT_POLICY); finalize_exchange_sign_transaction(false); } diff --git a/src/handler/lib/policy.c b/src/handler/lib/policy.c index 791484b55..63c8e61a7 100644 --- a/src/handler/lib/policy.c +++ b/src/handler/lib/policy.c @@ -889,7 +889,7 @@ __attribute__((warn_unused_result)) static int process_multi_a_sortedmulti_a_nod // bitvector of used keys (only relevant for sorting keys in SORTEDMULTI) uint8_t used[BITVECTOR_REAL_SIZE(MAX_PUBKEYS_PER_MULTISIG)]; - memset(used, 0, sizeof(memset)); + memset(used, 0, sizeof(used)); for (int i = 0; i < policy->n; i++) { uint8_t compressed_pubkey[33]; diff --git a/src/handler/register_wallet.c b/src/handler/register_wallet.c index f54378eee..146bcc67f 100644 --- a/src/handler/register_wallet.c +++ b/src/handler/register_wallet.c @@ -32,6 +32,7 @@ #include "../commands.h" #include "../constants.h" #include "../crypto.h" +#include "../error_codes.h" #include "../ui/display.h" #include "../ui/menu.h" @@ -106,7 +107,7 @@ void handler_register_wallet(dispatcher_context_t *dc, uint8_t protocol_version) // Verify that the name is acceptable if (!is_policy_name_acceptable(wallet_header.name, wallet_header.name_len)) { PRINTF("Policy name is not acceptable\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_REGISTER_WALLET_UNACCEPTABLE_POLICY_NAME); return; } @@ -126,7 +127,7 @@ void handler_register_wallet(dispatcher_context_t *dc, uint8_t protocol_version) wallet_header.n_keys)) { PRINTF("Policy is not sane\n"); - SEND_SW(dc, SW_NOT_SUPPORTED); + SEND_SW_EC(dc, SW_NOT_SUPPORTED, EC_REGISTER_WALLET_POLICY_NOT_SANE); return; } @@ -214,7 +215,7 @@ void handler_register_wallet(dispatcher_context_t *dc, uint8_t protocol_version) // Unclear if there is any use case for registering policies with no internal keys. // We disallow that, might reconsider in future versions if needed. PRINTF("Wallet policy with no internal keys\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_REGISTER_WALLET_POLICY_HAS_NO_INTERNAL_KEY); return; } else if (n_internal_keys != 1 && wallet_header.version == WALLET_POLICY_VERSION_V1) { // for legacy policies, we keep the restriction to exactly 1 internal key diff --git a/src/handler/sign_psbt.c b/src/handler/sign_psbt.c index 1736c912a..419d6dbee 100644 --- a/src/handler/sign_psbt.c +++ b/src/handler/sign_psbt.c @@ -33,6 +33,7 @@ #include "../commands.h" #include "../constants.h" #include "../crypto.h" +#include "../error_codes.h" #include "../ui/display.h" #include "../ui/menu.h" @@ -641,13 +642,13 @@ init_global_state(dispatcher_context_t *dc, sign_psbt_state_t *st) { // No hmac, verify that the policy is indeed a default one if (!is_wallet_policy_standard(dc, &st->wallet_header, st->wallet_policy_map)) { PRINTF("Non-standard policy, and no hmac provided\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_MISSING_HMAC_FOR_NONDEFAULT_POLICY); return false; } if (st->wallet_header.name_len != 0) { PRINTF("Name must be zero-length for a standard wallet policy\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_NO_NAME_FOR_DEFAULT_POLICY); return false; } @@ -753,7 +754,7 @@ static bool find_first_internal_key_placeholder(dispatcher_context_t *dc, } PRINTF("No internal key found in wallet policy"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_WALLET_POLICY_HAS_NO_INTERNAL_KEY); return false; } @@ -847,7 +848,7 @@ preprocess_inputs(dispatcher_context_t *dc, // either witness utxo or non-witness utxo (or both) must be present. if (!input.has_nonWitnessUtxo && !input.has_witnessUtxo) { PRINTF("No witness utxo nor non-witness utxo present in input.\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_MISSING_NONWITNESSUTXO_AND_WITNESSUTXO); return false; } @@ -869,13 +870,14 @@ preprocess_inputs(dispatcher_context_t *dc, } // request non-witness utxo, and get the prevout's value and scriptpubkey + // Also checks that the recomputed transaction hash matches with prevout_hash. if (0 > get_amount_scriptpubkey_from_psbt_nonwitness(dc, &input.in_out.map, &input.prevout_amount, input.in_out.scriptPubKey, &input.in_out.scriptPubKey_len, prevout_hash)) { - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_NONWITNESSUTXO_CHECK_FAILED); return false; } @@ -906,7 +908,9 @@ preprocess_inputs(dispatcher_context_t *dc, PRINTF( "scriptPubKey or amount in non-witness utxo doesn't match with witness " "utxo\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, + SW_INCORRECT_DATA, + EC_SIGN_PSBT_NONWITNESSUTXO_AND_WITNESSUTXO_MISMATCH); return false; } } else { @@ -943,7 +947,10 @@ preprocess_inputs(dispatcher_context_t *dc, if (segwit_version == -1) { if (!input.has_nonWitnessUtxo || input.has_witnessUtxo) { PRINTF("Legacy inputs must have the non-witness utxo, but no witness utxo.\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC( + dc, + SW_INCORRECT_DATA, + EC_SIGN_PSBT_MISSING_NONWITNESSUTXO_OR_UNEXPECTED_WITNESSUTXO_FOR_LEGACY); return false; } } @@ -958,7 +965,7 @@ preprocess_inputs(dispatcher_context_t *dc, // For all segwit transactions, the witness utxo must be present if (segwit_version >= 0 && !input.has_witnessUtxo) { PRINTF("Witness utxo missing for segwit input\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_MISSING_WITNESSUTXO_FOR_SEGWIT); return false; } @@ -1002,7 +1009,7 @@ preprocess_inputs(dispatcher_context_t *dc, if (((input.sighash_type & SIGHASH_SINGLE) == SIGHASH_SINGLE) && (cur_input_index >= st->n_outputs)) { PRINTF("SIGHASH_SINGLE with input idx >= n_output is not allowed \n"); - SEND_SW(dc, SW_NOT_SUPPORTED); + SEND_SW_EC(dc, SW_NOT_SUPPORTED, EC_SIGN_PSBT_UNALLOWED_SIGHASH_SINGLE); return false; } } @@ -1184,7 +1191,7 @@ preprocess_outputs(dispatcher_context_t *dc, // from unknowingly signing a transaction that sends the change to too many outputs // (possibly economically not worth spending). PRINTF("Too many change outputs: %d\n", st->outputs.n_change); - SEND_SW(dc, SW_NOT_SUPPORTED); + SEND_SW_EC(dc, SW_NOT_SUPPORTED, EC_SIGN_PSBT_TOO_MANY_CHANGE_OUTPUTS); return false; } @@ -1198,14 +1205,14 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { // Swap feature: check that wallet policy is a default one if (!st->is_wallet_default) { PRINTF("Must be a default wallet policy for swap feature\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_METHOD_NONDEFAULT_POLICY); finalize_exchange_sign_transaction(false); } // No external inputs allowed if (st->n_external_inputs > 0) { PRINTF("External inputs not allowed in swap transactions\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_METHOD_EXTERNAL_INPUTS); finalize_exchange_sign_transaction(false); } @@ -1213,7 +1220,7 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { // Do not allow transactions with missing non-witness utxos or non-default sighash flags PRINTF( "Missing non-witness utxo or non-default sighash flags are not allowed during swaps\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_METHOD_MISSING_NONWITNESSUTXO); finalize_exchange_sign_transaction(false); } @@ -1229,7 +1236,7 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { // There must be only one external output if (st->n_external_outputs != 1) { PRINTF("Standard swap transaction must have exactly 1 external output\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_METHOD_WRONG_N_OF_OUTPUTS); finalize_exchange_sign_transaction(false); } } else if (G_swap_state.mode == SWAP_MODE_CROSSCHAIN) { @@ -1239,7 +1246,7 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { if (st->n_external_outputs != 2) { PRINTF("Cross-chain swap transaction must have exactly 2 external outputs\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_METHOD_WRONG_N_OF_OUTPUTS); finalize_exchange_sign_transaction(false); } @@ -1248,7 +1255,9 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { size_t opreturn_amount = st->outputs.output_amounts[0]; if (opreturn_script_len < 4 || opreturn_script[0] != OP_RETURN) { PRINTF("The first output must be OP_RETURN for a cross-chain swap\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, + SW_FAIL_SWAP, + EC_SWAP_ERROR_CROSSCHAIN_WRONG_METHOD_INVALID_FIRST_OUTPUT); finalize_exchange_sign_transaction(false); } @@ -1267,21 +1276,21 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { // there are other valid OP_RETURN Scripts that we never expect here, // so we don't bother parsing. PRINTF("Unsupported or invalid OP_RETURN Script in cross-chain swap\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_CROSSCHAIN_WRONG_METHOD); finalize_exchange_sign_transaction(false); } // Make sure there is a singla data push if (opreturn_script_len != 1 + push_opcode_size + data_size) { PRINTF("Invalid OP_RETURN Script length in cross-chain swap\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_CROSSCHAIN_WRONG_METHOD); finalize_exchange_sign_transaction(false); } // Make sure the output's value is 0 if (opreturn_amount != 0) { PRINTF("OP_RETURN with non-zero value during cross-chain swap\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_CROSSCHAIN_WRONG_METHOD_NONZERO_AMOUNT); finalize_exchange_sign_transaction(false); } @@ -1292,18 +1301,18 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { expected_payin_hash, sizeof(expected_payin_hash)) != 0) { PRINTF("Mismatching payin hash in cross-chain swap\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_CROSSCHAIN_WRONG_HASH); finalize_exchange_sign_transaction(false); } } else if (G_swap_state.mode == SWAP_MODE_ERROR) { // an error was detected in handle_swap_sign_transaction.c::copy_transaction_parameters // special case only to improve error reporting in debug mode PRINTF("Invalid parameters for swap feature\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_GENERIC_COPY_TRANSACTION_PARAMETERS_FAILED); finalize_exchange_sign_transaction(false); } else { PRINTF("Unknown swap mode: %d\n", G_swap_state.mode); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_GENERIC_UNKNOWN_MODE); finalize_exchange_sign_transaction(false); } @@ -1313,14 +1322,14 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { // Check that total amount and fees are as expected if (fee != G_swap_state.fees) { PRINTF("Mismatching fee for swap\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_FEES); finalize_exchange_sign_transaction(false); } uint64_t spent_amount = st->outputs.total_amount - st->outputs.change_total_amount; if (spent_amount != G_swap_state.amount) { PRINTF("Mismatching spent amount for swap\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_AMOUNT); finalize_exchange_sign_transaction(false); } @@ -1331,7 +1340,7 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { st->outputs.output_script_lengths[swap_dest_idx], output_description)) { PRINTF("Invalid or unsupported script for external output\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_METHOD_WRONG_UNSUPPORTED_OUTPUT); finalize_exchange_sign_transaction(false); } @@ -1354,7 +1363,7 @@ execute_swap_checks(dispatcher_context_t *dc, sign_psbt_state_t *st) { PRINTF("%c", output_description[i]); } PRINTF("\n"); - SEND_SW(dc, SW_FAIL_SWAP); + SEND_SW_EC(dc, SW_FAIL_SWAP, EC_SWAP_ERROR_WRONG_DESTINATION); finalize_exchange_sign_transaction(false); } @@ -1883,7 +1892,7 @@ static bool __attribute__((noinline)) compute_sighash_segwitv0(dispatcher_contex memcmp(input->script + 2, witnessScript_hash, 32) != 0) { PRINTF("Mismatching witnessScript\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_MISMATCHING_WITNESS_SCRIPT); return false; } } else { @@ -2535,7 +2544,7 @@ static bool __attribute__((noinline)) sign_transaction_input(dispatcher_context_ if (input->in_out.scriptPubKey_len != 23 || memcmp(input->in_out.scriptPubKey, p2sh_redeemscript, 23) != 0) { PRINTF("witnessUtxo's scriptPubKey does not match redeemScript\n"); - SEND_SW(dc, SW_INCORRECT_DATA); + SEND_SW_EC(dc, SW_INCORRECT_DATA, EC_SIGN_PSBT_MISMATCHING_REDEEM_SCRIPT); return false; } diff --git a/tests/test_register_wallet.py b/tests/test_register_wallet.py index 3de33453c..74c5cafcb 100644 --- a/tests/test_register_wallet.py +++ b/tests/test_register_wallet.py @@ -159,11 +159,20 @@ def test_register_wallet_invalid_names(navigator: Navigator, firmware: Firmware, ) with pytest.raises(ExceptionRAPDU) as e: - client.register_wallet(wallet, navigator, + client.register_wallet(wallet, None, testname=test_name) assert DeviceException.exc.get(e.value.status) == IncorrectDataError - assert len(e.value.data) == 0 + # defined in error_codes.h + EC_REGISTER_WALLET_UNACCEPTABLE_POLICY_NAME = 0x0000 + + if invalid_name == too_long_name: + # We don't return an error code for name too long + assert len(e.value.data) == 0 + else: + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_UNACCEPTABLE_POLICY_NAME def test_register_wallet_missing_key(client: RaggerClient): @@ -222,7 +231,7 @@ def test_register_miniscript_long_policy(navigator: Navigator, firmware: Firmwar assert hmac.compare_digest( hmac.new(speculos_globals.wallet_registration_key, - wallet_id, sha256).digest(), + wallet_id, sha256).digest(), wallet_hmac, ) @@ -246,7 +255,14 @@ def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmwar ) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + assert len(e.value.data) == 2 + + # defined in error_codes.h + EC_REGISTER_WALLET_POLICY_NOT_SANE = 0x0001 + + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_POLICY_NOT_SANE # Key placeholders referring to the same key must have distinct derivations with pytest.raises(ExceptionRAPDU) as e: @@ -261,7 +277,9 @@ def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmwar testname=test_name ) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_POLICY_NOT_SANE with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(WalletPolicy( @@ -276,7 +294,9 @@ def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmwar testname=test_name ) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_POLICY_NOT_SANE # Miniscript policy with timelock mixing with pytest.raises(ExceptionRAPDU) as e: @@ -291,7 +311,9 @@ def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmwar testname=test_name ) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_POLICY_NOT_SANE # Miniscript policy that does not always require a signature with pytest.raises(ExceptionRAPDU) as e: @@ -308,7 +330,9 @@ def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmwar testname=test_name ) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_POLICY_NOT_SANE # Malleable policy, even if it requires a signature with pytest.raises(ExceptionRAPDU) as e: @@ -323,7 +347,9 @@ def test_register_wallet_not_sane_policy(navigator: Navigator, firmware: Firmwar testname=test_name ) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_POLICY_NOT_SANE # TODO: we can probably not trigger stack and ops limits with the current limits we have on the # miniscript policy size; otherwise it would be worth to add tests for them, too. diff --git a/tests/test_register_wallet_v1.py b/tests/test_register_wallet_v1.py index fccaa449d..bf651fc92 100644 --- a/tests/test_register_wallet_v1.py +++ b/tests/test_register_wallet_v1.py @@ -32,13 +32,15 @@ def test_register_wallet_accept_legacy_v1(navigator: Navigator, firmware: Firmwa ) wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, - instructions=register_wallet_instruction_approve(firmware), + instructions=register_wallet_instruction_approve( + firmware), testname=test_name) assert wallet_id == wallet.id assert hmac.compare_digest( - hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), + hmac.new(speculos_globals.wallet_registration_key, + wallet_id, sha256).digest(), wallet_hmac, ) @@ -57,13 +59,15 @@ def test_register_wallet_accept_sh_wit_v1(navigator: Navigator, firmware: Firmwa ) wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, - instructions=register_wallet_instruction_approve(firmware), + instructions=register_wallet_instruction_approve( + firmware), testname=test_name) assert wallet_id == wallet.id assert hmac.compare_digest( - hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), + hmac.new(speculos_globals.wallet_registration_key, + wallet_id, sha256).digest(), wallet_hmac, ) @@ -82,13 +86,15 @@ def test_register_wallet_accept_wit_v1(navigator: Navigator, firmware: Firmware, ) wallet_id, wallet_hmac = client.register_wallet(wallet, navigator, - instructions=register_wallet_instruction_approve(firmware), + instructions=register_wallet_instruction_approve( + firmware), testname=test_name) assert wallet_id == wallet.id assert hmac.compare_digest( - hmac.new(speculos_globals.wallet_registration_key, wallet_id, sha256).digest(), + hmac.new(speculos_globals.wallet_registration_key, + wallet_id, sha256).digest(), wallet_hmac, ) @@ -111,7 +117,8 @@ def test_register_wallet_reject_header_v1(navigator: Navigator, firmware: Firmwa with pytest.raises(ExceptionRAPDU) as e: client.register_wallet(wallet, navigator, - instructions=register_wallet_instruction_reject(firmware), + instructions=register_wallet_instruction_reject( + firmware), testname=test_name) assert DeviceException.exc.get(e.value.status) == DenyError @@ -120,12 +127,16 @@ def test_register_wallet_reject_header_v1(navigator: Navigator, firmware: Firmwa def test_register_wallet_invalid_names_v1(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): + too_long_name = "This wallet name is much too long since it requires 65 characters" + assert len(too_long_name) == 65 + for invalid_name in [ "", # empty name not allowed - "Very long walletz", # 17 characters is too long - " Test", "Test ", # can't start with spaces - "Tæst", # characters out of allowed range + too_long_name, + # " Test", "Test ", # can't start or end with spaces + # "Tæst", # characters out of allowed range ]: + print("Testing with:", invalid_name) # TODO: remove wallet = MultisigWallet( name=invalid_name, address_type=AddressType.WIT, @@ -137,11 +148,20 @@ def test_register_wallet_invalid_names_v1(navigator: Navigator, firmware: Firmwa version=WalletType.WALLET_POLICY_V1 ) - with pytest.raises(ExceptionRAPDU) as e: - client.register_wallet(wallet, navigator) + with pytest.raises(ExceptionRAPDU) as e: + client.register_wallet(wallet, None) - assert DeviceException.exc.get(e.value.status) == IncorrectDataError - assert len(e.value.data) == 0 + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + # defined in error_codes.h + EC_REGISTER_WALLET_UNACCEPTABLE_POLICY_NAME = 0x0000 + + if invalid_name == too_long_name: + # We don't return an error code for name too long + assert len(e.value.data) == 0 + else: + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_UNACCEPTABLE_POLICY_NAME def test_register_wallet_unsupported_policy_v1(navigator: Navigator, firmware: Firmware, client: @@ -181,4 +201,9 @@ def test_register_wallet_unsupported_policy_v1(navigator: Navigator, firmware: F # NotSupportedError assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + # defined in error_codes.h + EC_REGISTER_WALLET_POLICY_NOT_SANE = 0x0001 + + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_REGISTER_WALLET_POLICY_NOT_SANE diff --git a/tests/test_sign_psbt.py b/tests/test_sign_psbt.py index 7b31c2f60..1a9296e1a 100644 --- a/tests/test_sign_psbt.py +++ b/tests/test_sign_psbt.py @@ -633,7 +633,13 @@ def test_sign_psbt_fail_11_changes(navigator: Navigator, firmware: Firmware, cli testname=test_name) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + + # defined in error_codes.h + EC_SIGN_PSBT_TOO_MANY_CHANGE_OUTPUTS = 0x0009 + + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_SIGN_PSBT_TOO_MANY_CHANGE_OUTPUTS def test_sign_psbt_fail_wrong_non_witness_utxo(navigator: Navigator, firmware: Firmware, client: @@ -667,10 +673,19 @@ def test_sign_psbt_fail_wrong_non_witness_utxo(navigator: Navigator, firmware: F client.sign_psbt(psbt, wallet, None, navigator, instructions=sign_psbt_instruction_approve(firmware, save_screenshot=False), testname=test_name) - assert DeviceException.exc.get(e.value.status) == IncorrectDataError - assert len(e.value.data) == 0 client._no_clone_psbt = False + assert DeviceException.exc.get(e.value.status) == IncorrectDataError + + # defined in error_codes.h + EC_SIGN_PSBT_NONWITNESSUTXO_CHECK_FAILED = 0x0004 + + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_SIGN_PSBT_NONWITNESSUTXO_CHECK_FAILED + + + def test_sign_psbt_with_opreturn(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str): wallet = WalletPolicy( diff --git a/tests/test_sign_psbt_with_sighash_types.py b/tests/test_sign_psbt_with_sighash_types.py index c00adbaa8..0bf52b027 100644 --- a/tests/test_sign_psbt_with_sighash_types.py +++ b/tests/test_sign_psbt_with_sighash_types.py @@ -282,7 +282,13 @@ def test_sighash_single_3_ins_2_out(navigator: Navigator, firmware: Firmware, cl instructions=sign_psbt_instruction_approve(firmware, has_sighashwarning=True), testname=test_name) assert DeviceException.exc.get(e.value.status) == NotSupportedError - assert len(e.value.data) == 0 + + # defined in error_codes.h + EC_SIGN_PSBT_UNALLOWED_SIGHASH_SINGLE = 0x0008 + + assert len(e.value.data) == 2 + error_code = int.from_bytes(e.value.data, 'big') + assert error_code == EC_SIGN_PSBT_UNALLOWED_SIGHASH_SINGLE def test_sighash_all_anyone_sign(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str):